View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.river.tool.classdepend;
19  
20  import java.io.File;
21  import java.io.FileFilter;
22  import java.io.FileNotFoundException;
23  import java.io.IOException;
24  import java.util.Enumeration;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.Set;
28  import java.util.SortedSet;
29  import java.util.StringTokenizer;
30  import java.util.TreeSet;
31  import java.util.jar.JarEntry;
32  import java.util.jar.JarFile;
33  
34  /** Utility class for finding the names of the classes in a set of packages. */
35  public class PackageClasses {
36  
37      /** The names of directories in the classpath. */
38      private final Set directories = new HashSet();
39  
40      /** The names of files in JAR files in the classpath. */
41      private final Set jarContents = new HashSet();
42  
43      /**
44       * Prints the classes in a package in the class path to standard output
45       * using the default character encoding.  If second argument is specified,
46       * it is used as the class path, otherwise the system class path is used.
47       *
48       * @param	args the arguments
49       * @throws	IllegalArgumentException if less than one or more than two
50       *		arguments are provided
51       * @throws	IOException if an I/O error occurs
52       */
53      public static void main(String[] args) throws IOException {
54  	String classpath;
55  	if (args.length == 1) {
56  	    classpath = System.getProperty("java.class.path");
57  	} else if (args.length == 2) {
58  	    classpath = args[1];
59  	} else {
60  	    throw new IllegalArgumentException(
61  		"Usage: java " + PackageClasses.class.getName() +
62  		" package [classpath]");
63  	}
64  	PackageClasses pc = new PackageClasses(classpath);
65  	SortedSet classes = new TreeSet(pc.compute(args[0]));
66          Iterator classesIter = classes.iterator();
67  	while (classesIter.hasNext()) {
68  	    System.out.println(classesIter.next());
69  	}
70      }
71  
72      /**
73       * Creates an instance with the specified class path.  The class path is
74       * interpreted as a list of file names, separated by the {@link
75       * File#pathSeparator File.pathSeparator} character.  Empty names are
76       * treated as the current directory, names ending in the {@link
77       * File#separator File.separator} character are treated as directories, and
78       * other names are treated as JAR files.
79       *
80       * @param	classpath the class path
81       * @throws	IOException if a problem occurs accessing files in the class
82       *		path
83       */
84      public PackageClasses(String classpath) throws IOException {
85  	if (classpath == null) {
86  	    throw new NullPointerException("The classpath cannot be null");
87  	}
88  	StringTokenizer tokens =
89  	    new StringTokenizer(classpath, File.pathSeparator);
90  	while (tokens.hasMoreTokens()) {
91  	    String token = tokens.nextToken();
92  	    if (token.equals("")) {
93  		token = ".";
94  	    }
95  	    File file = new File(token);
96  	    if (!file.exists()) {
97  		throw new FileNotFoundException(
98  		    "File or directory not found: " + file);
99  	    } else if (file.isDirectory()) {
100 		String path = file.getPath();
101 		if (!path.endsWith(File.separator)) {
102 		    path += File.separator;
103 		}
104 		directories.add(path);
105 	    } else {
106 		JarFile jarFile;
107 		try {
108 		    jarFile = new JarFile(file);
109 		} catch (IOException e) {
110 		    throw new IOException(
111 			"Problem accessing file or directory: " + file, e);
112 		}
113 		try {
114 		    for (Enumeration entries = jarFile.entries();
115 			 entries.hasMoreElements(); )
116 		    {
117 			JarEntry entry = (JarEntry) entries.nextElement();
118 			jarContents.add(entry.getName());
119 		    }
120 		} finally {
121 		    try {
122 			jarFile.close();
123 		    } catch (IOException e) {
124 		    }
125 		}
126 	    }
127 	}
128     }
129 
130     /**
131      * Returns a set of the fully qualified names of classes in the specified
132      * packages, not including classes in subpackages of those packages.
133      *
134      * @param	packages the packages
135      * @return	the class names
136      * @throws	IOException if a problem occurs accessing files in the class
137      *		path
138      */
139     public Set compute(String[] packages)
140 	throws IOException
141     {
142 	return compute(false, packages);
143     }
144     
145     public Set compute(String packAge)
146             throws IOException
147     {
148         String [] packages = {packAge};
149         return compute(false, packages);
150     }
151 
152     /**
153      * Returns a set of the fully qualified names of classes in the specified
154      * packages, optionally including classes in subpackages of those packages.
155      *
156      * @param	recurse if <code>true</code>, find classes in subpackages of
157      *		the specified packages
158      * @param	packages the packages
159      * @return	the class names
160      * @throws	IOException if a problem occurs accessing files in the class
161      *		path
162      */
163     public Set compute(boolean recurse, String[] packages)
164 	throws IOException
165     {
166 	if (packages == null) {
167 	    throw new NullPointerException(
168 		"The packages argument cannot be null");
169 	}
170 	Set classes = new HashSet();
171         int l = packages.length;
172 	for (int i = 0 ; i < l ; i++) {
173             String pkg = packages[i];
174 	    if (pkg == null) {
175 		throw new NullPointerException(
176 		    "Elements of the packages argument cannot be null");
177 	    }
178 	    String pkgFileName = pkg.replace('.', File.separatorChar);
179 	    if (!"".equals(pkgFileName)) {
180 		pkgFileName += File.separatorChar;
181 	    }
182             Iterator dirIter = directories.iterator();
183 	    while (dirIter.hasNext()) {
184                 String dir = (String) dirIter.next();
185 		File file = new File(dir, pkgFileName);
186 		if (file.exists()) {
187 		    collectClasses(file, recurse, pkg, classes);
188 		}
189 	    }
190 	    /* JAR files always use forward slashes */
191 	    if (File.separatorChar != '/') {
192 		pkgFileName = pkgFileName.replace(File.separatorChar, '/');
193 	    }
194             Iterator jarContentIter = jarContents.iterator();
195 	    while (jarContentIter.hasNext()) {
196                 String file = (String) jarContentIter.next();
197 		if (file.startsWith(pkgFileName) && file.endsWith(".class")) {
198 		    file = removeDotClass(file);
199 		    if (recurse ||
200 			file.indexOf('/', pkgFileName.length() + 1) < 0)
201 		    {
202 			/*
203 			 * Include the file if it is in a subdirectory only if
204 			 * recurse is true.  Otherwise, check that the class
205 			 * file matches the package name exactly.
206 			 * -tjb@sun.com (06/07/2006)
207 			 */
208 			classes.add(file.replace('/', '.'));
209 		    }
210 		}
211 	    }
212 	}
213 	return classes;
214     }
215     
216     /**
217      * Returns a set of the fully qualified names of classes in the specified
218      * packages, optionally including classes in subpackages of those packages.
219      *
220      * @param	recurse if <code>true</code>, find classes in subpackages of
221      *		the specified package
222      * @param	packAge the package
223      * @return	the class names
224      * @throws	IOException if a problem occurs accessing files in the class
225      *		path
226      */
227     public Set compute(boolean recurse, String packAge)
228 	throws IOException
229     {
230         String [] packages = {packAge};
231         return compute(recurse, packages);
232     }
233     
234     /**
235      * Adds the names of classes in the directory to the set of classes,
236      * recursiving into subdirectories if requested.
237      */
238     private static void collectClasses(File directory,
239 				       final boolean recurse,
240 				       final String pkg,
241 				       final Set classes)
242 	throws IOException
243     {
244 	final IOException[] failed = { null };
245 	File[] result = directory.listFiles(new FileFilter() {
246 	    public boolean accept(File child) {
247 		String name = child.getName();
248 		if (name != null) {
249 		    if (name.endsWith(".class") && child.isFile()) {
250 			String classname = removeDotClass(name);
251 			if (!"".equals(pkg)) {
252 			    classname = pkg + '.' + classname;
253 			}
254 			classes.add(classname);
255 		    } else if (recurse && child.isDirectory()) {
256 			String subpackage =
257 			    "".equals(pkg) ? name : pkg + '.' + name;
258 			try {
259 			    collectClasses(
260 				child, recurse, subpackage, classes);
261 			} catch (IOException e) {
262 			    failed[0] = e;
263 			}
264 		    }
265 		}
266 		return false;
267 	    }
268 	});
269 	if (failed[0] != null) {
270 	    throw failed[0];
271 	} else if (result == null) {
272 	    throw new IOException(
273 		"A problem occurred accessing directory: " + directory);
274 	}
275     }
276 
277     /** Strips the .class suffix from a string. */
278     private static String removeDotClass(String s) {
279 	return s.substring(0, s.length() - 6);
280     }
281 }