001    /**
002     * Created by IntelliJ IDEA.
003     * User: Wei Wang
004     * Date: Jan 15, 2003
005     * Time: 1:19:37 PM
006     * Some codes are attributed to ClassPathExplorer.java in Bruno Dufour's AdaptC toolkit.
007     * please refer to http://www.sable.mcgill.ca/~bdufou1/ for more details
008     */
009    
010    package EVolve.util.SourceBrowser;
011    
012    import org.apache.bcel.classfile.*;
013    import java.util.*;
014    import java.util.zip.*;
015    import java.io.*;
016    import EVolve.util.SceneSetting;
017    import EVolve.Scene;
018    
019    public class ClassExplorer {
020        private HashMap mapClassToSourcename;
021        private ArrayList classPath,sourcePath;
022        private static ClassExplorer instance = null;
023    
024        public ClassExplorer() {
025            mapClassToSourcename = new HashMap();
026            classPath = new ArrayList();
027            sourcePath = new ArrayList();
028        }
029    
030        public static ClassExplorer v() {
031            if (instance == null) instance = new ClassExplorer();
032            return instance;
033        }
034    
035        public String getSourceFileFromClass(String className) {
036            prepareClassPath();
037            prepareSourcePath();
038    
039            if (!mapClassToSourcename.containsKey(className)) {
040    
041                JavaClass javaClass = loadClass(className);
042    
043                if (javaClass != null) {
044                    String sourceName = javaClass.getSourceFileName();
045                    boolean fileFound = false;
046    
047                    for (int i=0; i<sourcePath.size(); i++) {
048                        String path = (String)sourcePath.get(i)+File.separatorChar+sourceName;
049                        File f = new File(path);
050                        if (f.exists()) {
051                            mapClassToSourcename.put(className, path);
052                            fileFound = true;
053                            break;
054                        }
055                    }
056    
057                    if (!fileFound) {
058                        Scene.showErrorMessage("Can not find source file \""+sourceName+"\". \n");
059                    }
060                }
061            }
062    
063            return (String)mapClassToSourcename.get(className);
064        }
065    
066        private void prepareClassPath() {
067            String path = System.getProperty("java.class.path");
068            StringTokenizer token = new StringTokenizer(path,System.getProperty("path.separator"));
069    
070            classPath.clear();
071            while (token.hasMoreTokens()) {
072                classPath.add(token.nextToken());
073            }
074            ArrayList additional = SceneSetting.v().getAdditionalClassPath();
075            for (int i=0; i<additional.size(); i++) {
076                classPath.add(additional.get(i));
077            }
078        }
079    
080        private void prepareSourcePath() {
081            String path = System.getProperty("java.library.path");
082            StringTokenizer token = new StringTokenizer(path,System.getProperty("path.separator"));
083    
084            sourcePath.clear();
085            while (token.hasMoreTokens()) {
086                sourcePath.add(token.nextToken());
087            }
088            ArrayList additional = SceneSetting.v().getSourcePath();
089            for (int i=0; i<additional.size(); i++) {
090                sourcePath.add(additional.get(i));
091            }
092        }
093    
094        private JavaClass loadClass(String className) {
095            JavaClass returnVal =  null;
096    
097            for (int i=0; i<classPath.size(); i++) {
098                String path = (String)classPath.get(i);
099                File f = new File(path);
100    
101                if (!f.exists()) continue;
102    
103                if (isArchive(f)) {
104                    returnVal = processArchive(className,f);
105                } else if (isClassFile(f)) {
106                    returnVal = processClassFile(className, f);
107                } else if (f.isDirectory()) {
108                    returnVal = processDirectory(className, f);
109                }
110    
111                if (returnVal != null) break;
112            }
113    
114            return returnVal;
115        }
116    
117        private boolean isArchive(File f) {
118            if (f.isFile() && f.canRead()) {
119                String path;
120                try {
121                    path = f.getCanonicalPath();
122                } catch(IOException e) {
123                    return false;
124                }
125    
126                if(path.endsWith("zip") || path.endsWith("jar")) {
127                    return true;
128                }
129            }
130            return false;
131        }
132    
133        private boolean isClassFile(File file) {
134            try {
135                if (!file.isFile()) {
136                    return false;
137                }
138                DataInputStream stream = new DataInputStream(new FileInputStream(file));
139    
140                String path = file.getCanonicalPath();
141    
142                if (path.endsWith(".class") && checkMagic(stream)) {
143                    return true;
144                }
145    
146                return false;
147            } catch (IOException e) {
148                return false;
149            }
150        }
151    
152        private boolean checkMagic(DataInputStream classFileStream) {
153            try {
154                return ((classFileStream.readInt() & 0xFFFFFFFFL) == 0xCAFEBABEL);
155            } catch (IOException e) {
156                return false;
157            }
158        }
159    
160        private JavaClass processArchive(String className, File archive) {
161            ZipFile zip;
162    
163            try {
164                zip = new ZipFile(archive);
165            } catch (ZipException e) {
166                return null;
167            } catch (IOException e) {
168                return null;
169            }
170    
171            Enumeration enum = zip.entries();
172            while (enum.hasMoreElements()) {
173                ZipEntry entry = (ZipEntry) enum.nextElement();
174                String entryName = entry.getName(), path;
175    
176                /* make sure that the class name is valid */
177                if (entryName != null && entryName.endsWith(".class")) {
178                    String tempClassName = entryName.substring(0, entryName.length() - 6).replace('/', '.');
179                    try {
180                        path = archive.getCanonicalPath();
181                        if (tempClassName.equals(className)) {
182                            return new ClassParser(path, entryName).parse();
183                        }
184                    } catch (IOException e) {
185                        return null;
186                    }
187                }
188            }
189    
190            return null;
191        }
192    
193        private JavaClass processClassFile(String className, File classFile) {
194            JavaClass javaClass = getClass(classFile);
195    
196            if (javaClass == null) return null;
197            /* This method is only invoked when the file exists and is a regular file,
198               so these conditions are not checked again */
199            String tempClassName = javaClass.getClassName();
200            if (tempClassName.indexOf('.') >= 0) {
201                /* This class file is part of a package, which does not match
202                   the location of the candidate file */
203                return null;
204            }
205            if (tempClassName.equals(className)) {
206                return javaClass;
207            }
208            return null;
209        }
210    
211        private JavaClass processDirectory(String className, File dir) {
212            JavaClass javaClass;
213    
214            /* If the class we are looking for is located in this directory,
215               then theoreticalPath must represent its location */
216            String theoreticalPath = dir.getPath() + File.separator + className.replace('.', File.separatorChar) + ".class";
217            File theoreticalFile = new File(theoreticalPath);
218    
219    
220            if (theoreticalFile.exists() && theoreticalFile.isFile()) {
221                javaClass = getClass(theoreticalFile);
222    
223                if (javaClass == null) return null;
224                /* This could be a match */
225                String tempClassName = javaClass.getClassName();
226                if (tempClassName != null && tempClassName.equals(className)) {
227                    /* This is a match */
228                    return javaClass;
229                }
230            }
231    
232            return null;
233        }
234    
235        private JavaClass getClass(File classFile) {
236            try {
237                DataInputStream stream = new DataInputStream(new FileInputStream(classFile));
238    
239                ClassParser classParser = new ClassParser(stream, classFile.getName());
240                return classParser.parse();
241            } catch (FileNotFoundException e) {
242                return null;
243            } catch (IOException e) {
244                return null;
245            }
246        }
247    }