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 */
007package jline;
008
009import java.io.*;
010import java.net.*;
011import java.util.*;
012import java.util.jar.JarEntry;
013import 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 */
021public 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            if (!file.toString().endsWith (".jar"))
089                continue;
090
091            JarFile jf = new JarFile(file);
092
093            for (Enumeration e = jf.entries(); e.hasMoreElements();) {
094                JarEntry entry = (JarEntry) e.nextElement();
095
096                if (entry == null) {
097                    continue;
098                }
099
100                String name = entry.getName();
101
102                if (!name.endsWith(".class")) // only use class files
103                 {
104                    continue;
105                }
106
107                classes.add(name);
108            }
109        }
110
111        // now filter classes by changing "/" to "." and trimming the
112        // trailing ".class"
113        Set classNames = new TreeSet();
114
115        for (Iterator i = classes.iterator(); i.hasNext();) {
116            String name = (String) i.next();
117            classNames.add(name.replace('/', '.').
118                substring(0, name.length() - 6));
119        }
120
121        return (String[]) classNames.toArray(new String[classNames.size()]);
122    }
123
124    private static Set getClassFiles(String root, Set holder, File directory,
125        int[] maxDirectories) {
126        // we have passed the maximum number of directories to scan
127        if (maxDirectories[0]-- < 0) {
128            return holder;
129        }
130
131        File[] files = directory.listFiles();
132
133        for (int i = 0; (files != null) && (i < files.length); i++) {
134            String name = files[i].getAbsolutePath();
135
136            if (!(name.startsWith(root))) {
137                continue;
138            } else if (files[i].isDirectory()) {
139                getClassFiles(root, holder, files[i], maxDirectories);
140            } else if (files[i].getName().endsWith(".class")) {
141                holder.add(files[i].getAbsolutePath().
142                    substring(root.length() + 1));
143            }
144        }
145
146        return holder;
147    }
148}