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 java.io.*;
013    import java.util.*;
014    import java.util.zip.*;
015    import EVolve.Scene;
016    import org.apache.bcel.classfile.*;
017    import EVolve.util.settings.SceneSetting;
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            SceneSetting.v().readDataFromFile();
075            ArrayList additional = SceneSetting.v().getAdditionalClassPath();
076            for (int i=0; i<additional.size(); i++) {
077                classPath.add(additional.get(i));
078            }
079        }
080    
081        private void prepareSourcePath() {
082            String path = System.getProperty("java.library.path");
083            StringTokenizer token = new StringTokenizer(path,System.getProperty("path.separator"));
084    
085            sourcePath.clear();
086            while (token.hasMoreTokens()) {
087                sourcePath.add(token.nextToken());
088            }
089            ArrayList additional = SceneSetting.v().getSourcePath();
090            for (int i=0; i<additional.size(); i++) {
091                sourcePath.add(additional.get(i));
092            }
093        }
094    
095        private JavaClass loadClass(String className) {
096            JavaClass returnVal =  null;
097    
098            for (int i=0; i<classPath.size(); i++) {
099                String path = (String)classPath.get(i);
100                File f = new File(path);
101    
102                if (!f.exists()) continue;
103    
104                if (isArchive(f)) {
105                    returnVal = processArchive(className,f);
106                } else if (isClassFile(f)) {
107                    returnVal = processClassFile(className, f);
108                } else if (f.isDirectory()) {
109                    returnVal = processDirectory(className, f);
110                }
111    
112                if (returnVal != null) break;
113            }
114    
115            return returnVal;
116        }
117    
118        private boolean isArchive(File f) {
119            if (f.isFile() && f.canRead()) {
120                String path;
121                try {
122                    path = f.getCanonicalPath();
123                } catch(IOException e) {
124                    return false;
125                }
126    
127                if(path.endsWith("zip") || path.endsWith("jar")) {
128                    return true;
129                }
130            }
131            return false;
132        }
133    
134        private boolean isClassFile(File file) {
135            try {
136                if (!file.isFile()) {
137                    return false;
138                }
139                DataInputStream stream = new DataInputStream(new FileInputStream(file));
140    
141                String path = file.getCanonicalPath();
142    
143                if (path.endsWith(".class") && checkMagic(stream)) {
144                    return true;
145                }
146    
147                return false;
148            } catch (IOException e) {
149                return false;
150            }
151        }
152    
153        private boolean checkMagic(DataInputStream classFileStream) {
154            try {
155                return ((classFileStream.readInt() & 0xFFFFFFFFL) == 0xCAFEBABEL);
156            } catch (IOException e) {
157                return false;
158            }
159        }
160    
161        private JavaClass processArchive(String className, File archive) {
162            ZipFile zip;
163    
164            try {
165                zip = new ZipFile(archive);
166            } catch (ZipException e) {
167                return null;
168            } catch (IOException e) {
169                return null;
170            }
171    
172            Enumeration enum = zip.entries();
173            while (enum.hasMoreElements()) {
174                ZipEntry entry = (ZipEntry) enum.nextElement();
175                String entryName = entry.getName(), path;
176    
177                /* make sure that the class name is valid */
178                if (entryName != null && entryName.endsWith(".class")) {
179                    String tempClassName = entryName.substring(0, entryName.length() - 6).replace('/', '.');
180                    try {
181                        path = archive.getCanonicalPath();
182                        if (tempClassName.equals(className)) {
183                            return new ClassParser(path, entryName).parse();
184                        }
185                    } catch (IOException e) {
186                        return null;
187                    }
188                }
189            }
190    
191            return null;
192        }
193    
194        private JavaClass processClassFile(String className, File classFile) {
195            JavaClass javaClass = getClass(classFile);
196    
197            if (javaClass == null) return null;
198            /* This method is only invoked when the file exists and is a regular file,
199               so these conditions are not checked again */
200            String tempClassName = javaClass.getClassName();
201            if (tempClassName.indexOf('.') >= 0) {
202                /* This class file is part of a package, which does not match
203                   the location of the candidate file */
204                return null;
205            }
206            if (tempClassName.equals(className)) {
207                return javaClass;
208            }
209            return null;
210        }
211    
212        private JavaClass processDirectory(String className, File dir) {
213            JavaClass javaClass;
214    
215            /* If the class we are looking for is located in this directory,
216               then theoreticalPath must represent its location */
217            String theoreticalPath = dir.getPath() + File.separator + className.replace('.', File.separatorChar) + ".class";
218            File theoreticalFile = new File(theoreticalPath);
219    
220    
221            if (theoreticalFile.exists() && theoreticalFile.isFile()) {
222                javaClass = getClass(theoreticalFile);
223    
224                if (javaClass == null) return null;
225                /* This could be a match */
226                String tempClassName = javaClass.getClassName();
227                if (tempClassName != null && tempClassName.equals(className)) {
228                    /* This is a match */
229                    return javaClass;
230                }
231            }
232    
233            return null;
234        }
235    
236        private JavaClass getClass(File classFile) {
237            try {
238                DataInputStream stream = new DataInputStream(new FileInputStream(classFile));
239    
240                ClassParser classParser = new ClassParser(stream, classFile.getName());
241                return classParser.parse();
242            } catch (FileNotFoundException e) {
243                return null;
244            } catch (IOException e) {
245                return null;
246            }
247        }
248    }