001    /**
002     *      jline - Java console input library
003     *      Copyright (c) 2002-2006, Marc Prud'hommeaux <mwp1@cornell.edu>
004     *      All rights reserved.
005     *
006     *      Redistribution and use in source and binary forms, with or
007     *      without modification, are permitted provided that the following
008     *      conditions are met:
009     *
010     *      Redistributions of source code must retain the above copyright
011     *      notice, this list of conditions and the following disclaimer.
012     *
013     *      Redistributions in binary form must reproduce the above copyright
014     *      notice, this list of conditions and the following disclaimer
015     *      in the documentation and/or other materials provided with
016     *      the distribution.
017     *
018     *      Neither the name of JLine nor the names of its contributors
019     *      may be used to endorse or promote products derived from this
020     *      software without specific prior written permission.
021     *
022     *      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
023     *      "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
024     *      BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
025     *      AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
026     *      EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
027     *      FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
028     *      OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
029     *      PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
030     *      DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
031     *      AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
032     *      LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
033     *      IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
034     *      OF THE POSSIBILITY OF SUCH DAMAGE.
035     */
036    package jline;
037    
038    import java.io.*;
039    import java.net.*;
040    import java.util.*;
041    import java.util.jar.JarFile;
042    import java.util.jar.JarEntry;
043    
044    
045    /**
046     *  A Completor implementation that completes java class names. By default,
047     *  it scans the java class path to locate all the classes.
048     *
049     *  @author  <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
050     */
051    public class ClassNameCompletor
052            extends SimpleCompletor
053    {
054            /**
055             *  Complete candidates using all the classes available in the
056             *  java <em>CLASSPATH</em>.
057             */
058            public ClassNameCompletor ()
059                    throws IOException
060            {
061                    this (null);
062            }
063    
064    
065            public ClassNameCompletor (final SimpleCompletorFilter filter)
066                    throws IOException
067            {
068                    super (getClassNames (), filter);
069                    setDelimiter (".");
070            }
071    
072    
073            public static String[] getClassNames ()
074                    throws IOException
075            {
076                    Set urls = new HashSet ();
077                    for (ClassLoader loader = ClassNameCompletor.class.getClassLoader ();
078                            loader != null; loader = loader.getParent ())
079                    {
080                            if (!(loader instanceof URLClassLoader))
081                                    continue;
082    
083                            urls.addAll (Arrays.asList (((URLClassLoader)loader).getURLs ()));
084                    }
085    
086                    // Now add the URL that holds java.lang.String. This is because
087                    // some JVMs do not report the core classes jar in the list of
088                    // class loaders.
089                    Class[] systemClasses = new Class[] {
090                            String.class,
091                            javax.swing.JFrame.class
092                            };
093                    for (int i = 0; i < systemClasses.length; i++)
094                    {
095                            URL classURL = systemClasses[i].getResource ("/"
096                                    + systemClasses[i].getName ().replace ('.', '/') + ".class");
097                            if (classURL != null)
098                            {
099                                    URLConnection uc = (URLConnection)classURL.openConnection ();
100                                    if (uc instanceof JarURLConnection)
101                                            urls.add (((JarURLConnection)uc).getJarFileURL ());
102                            }
103                    }
104    
105    
106                    Set classes = new HashSet ();
107                    for (Iterator i = urls.iterator (); i.hasNext (); )
108                    {
109                            URL url = (URL)i.next ();
110                            File file = new File (url.getFile ());
111                            if (file.isDirectory ())
112                            {
113                                    Set files = getClassFiles (file.getAbsolutePath (),
114                                            new HashSet (), file, new int[] { 200 });
115                                    classes.addAll (files);
116                                    continue;
117                            }
118    
119                            if (file == null || !file.isFile ()) // TODO: handle directories
120                                    continue;
121    
122                            JarFile jf = new JarFile (file);
123                            for (Enumeration entries = jf.entries ();
124                                    entries.hasMoreElements () ;)
125                            {
126                                    JarEntry entry = (JarEntry)entries.nextElement ();
127                                    if (entry == null)
128                                            continue;
129    
130                                    String name = entry.getName ();
131                                    if (!name.endsWith (".class")) // only use class files
132                                            continue;
133    
134                                    classes.add (name);
135                            }
136                    }
137    
138                    // now filter classes by changing "/" to "." and trimming the
139                    // trailing ".class"
140                    Set classNames = new TreeSet ();
141                    for (Iterator i = classes.iterator (); i.hasNext (); )
142                    {
143                            String name = (String)i.next ();
144                            classNames.add (name.replace ('/', '.').substring (0,
145                                    name.length () - 6));
146                    }
147    
148                    return (String[])classNames.toArray (new String[classNames.size ()]);
149            }
150    
151    
152            private static Set getClassFiles (String root, Set holder, File directory,
153                    int[] maxDirectories)
154            {
155                    // we have passed the maximum number of directories to scan
156                    if (maxDirectories[0]-- < 0)
157                            return holder;
158    
159                    File[] files = directory.listFiles ();
160                    for (int i = 0; files != null && i < files.length; i++)
161                    {
162                            String name = files[i].getAbsolutePath ();
163                            if (!(name.startsWith (root)))
164                                    continue;
165                            else if (files[i].isDirectory ())
166                                    getClassFiles (root, holder, files[i], maxDirectories);
167                            else if (files[i].getName ().endsWith (".class"))
168                                    holder.add (files[i].getAbsolutePath ().substring (
169                                            root.length () + 1));
170                    }
171    
172                    return holder;
173            }
174    }
175