001    /*
002     * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved.
003     *
004     * This software is distributable under the BSD license. See the terms of the
005     * BSD license in the documentation provided with this software.
006     */
007    package jline;
008    
009    import java.io.*;
010    import java.net.*;
011    import java.util.*;
012    import java.util.jar.JarEntry;
013    import java.util.jar.JarFile;
014    
015    /**
016     *  A Completor implementation that completes java class names. By default,
017     *  it scans the java class path to locate all the classes.
018     *
019     *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
020     */
021    public class ClassNameCompletor extends SimpleCompletor {
022    
023        /**
024         *  Complete candidates using all the classes available in the
025         *  java <em>CLASSPATH</em>.
026         */
027        public ClassNameCompletor() throws IOException {
028            this(null);
029        }
030    
031        public ClassNameCompletor(final SimpleCompletorFilter filter)
032            throws IOException {
033            super(getClassNames(), filter);
034            setDelimiter(".");
035        }
036    
037        public static String[] getClassNames() throws IOException {
038            Set urls = new HashSet();
039    
040            for (ClassLoader loader = ClassNameCompletor.class
041                .getClassLoader(); loader != null;
042                     loader = loader.getParent()) {
043                if (!(loader instanceof URLClassLoader)) {
044                    continue;
045                }
046    
047                urls.addAll(Arrays.asList(((URLClassLoader) loader).getURLs()));
048            }
049    
050            // Now add the URL that holds java.lang.String. This is because
051            // some JVMs do not report the core classes jar in the list of
052            // class loaders.
053            Class[] systemClasses = new Class[] {
054                String.class, javax.swing.JFrame.class
055                };
056    
057            for (int i = 0; i < systemClasses.length; i++) {
058                URL classURL = systemClasses[i].getResource("/"
059                    + systemClasses[i].getName() .replace('.', '/') + ".class");
060    
061                if (classURL != null) {
062                    URLConnection uc = (URLConnection) classURL.openConnection();
063    
064                    if (uc instanceof JarURLConnection) {
065                        urls.add(((JarURLConnection) uc).getJarFileURL());
066                    }
067                }
068            }
069    
070            Set classes = new HashSet();
071    
072            for (Iterator i = urls.iterator(); i.hasNext();) {
073                URL url = (URL) i.next();
074                File file = new File(url.getFile());
075    
076                if (file.isDirectory()) {
077                    Set files = getClassFiles(file.getAbsolutePath(),
078                        new HashSet(), file, new int[] { 200 });
079                    classes.addAll(files);
080    
081                    continue;
082                }
083    
084                if ((file == null) || !file.isFile()) // TODO: handle directories
085                 {
086                    continue;
087                }
088    
089                JarFile jf = new JarFile(file);
090    
091                for (Enumeration e = jf.entries(); e.hasMoreElements();) {
092                    JarEntry entry = (JarEntry) e.nextElement();
093    
094                    if (entry == null) {
095                        continue;
096                    }
097    
098                    String name = entry.getName();
099    
100                    if (!name.endsWith(".class")) // only use class files
101                     {
102                        continue;
103                    }
104    
105                    classes.add(name);
106                }
107            }
108    
109            // now filter classes by changing "/" to "." and trimming the
110            // trailing ".class"
111            Set classNames = new TreeSet();
112    
113            for (Iterator i = classes.iterator(); i.hasNext();) {
114                String name = (String) i.next();
115                classNames.add(name.replace('/', '.').
116                    substring(0, name.length() - 6));
117            }
118    
119            return (String[]) classNames.toArray(new String[classNames.size()]);
120        }
121    
122        private static Set getClassFiles(String root, Set holder, File directory,
123            int[] maxDirectories) {
124            // we have passed the maximum number of directories to scan
125            if (maxDirectories[0]-- < 0) {
126                return holder;
127            }
128    
129            File[] files = directory.listFiles();
130    
131            for (int i = 0; (files != null) && (i < files.length); i++) {
132                String name = files[i].getAbsolutePath();
133    
134                if (!(name.startsWith(root))) {
135                    continue;
136                } else if (files[i].isDirectory()) {
137                    getClassFiles(root, holder, files[i], maxDirectories);
138                } else if (files[i].getName().endsWith(".class")) {
139                    holder.add(files[i].getAbsolutePath().
140                        substring(root.length() + 1));
141                }
142            }
143    
144            return holder;
145        }
146    }