001 package com.softnetConsult.utils.reflect;
002
003 import java.io.File;
004 import java.io.IOException;
005 import java.io.InputStream;
006 import java.util.ArrayList;
007 import java.util.Collections;
008 import java.util.Comparator;
009 import java.util.Enumeration;
010 import java.util.List;
011 import java.util.zip.ZipEntry;
012 import java.util.zip.ZipFile;
013
014 import com.softnetConsult.utils.log.ScreenFileLogger;
015 import com.softnetConsult.utils.log.ScreenFileLoggerFactory;
016 import com.softnetConsult.utils.sys.SystemTools;
017
018 /**
019 * This class implements a small executable tool that can analyse a Java class file,
020 * a directory, or a Java archive (JAR or ZIP) and print version information for Java
021 * class binaries found. This tool uses the library methods
022 * {@link com.softnetConsult.utils.sys.SystemTools#getClassVersionInfo(File)} and
023 * {@link com.softnetConsult.utils.reflect.ClassTools#getClassVersionInfo(InputStream)}
024 * for the actual parsing of Java class binaries.<br />
025 * For usage and command line arguments, see {@link #printUsage()} and {@link #main(String[])}.
026 *
027 * <p style="font-size:smaller;">This product includes software developed by the
028 * <strong>SoftNet-Consult Java Utility Library</strong> project and its contributors.<br />
029 * (<a href="http://java-tools.sourceforge.net" target="_blank">http://java-tools.sourceforge.net</a>)<br />
030 * Copyright (c) 2007-2008 SoftNet-Consult.<br />
031 * Copyright (c) 2007-2008 G. Paperin.<br />
032 * All rights reserved.
033 * </p>
034 * <p style="font-size:smaller;">File: ClassFileVersionTool.java<br />
035 * Library API version: {@value com.softnetConsult.utils.APIProperties#apiVersion}<br />
036 * Java compliance version: {@value com.softnetConsult.utils.APIProperties#javaComplianceVersion}
037 * </p>
038 * <p style="font-size:smaller;">Redistribution and use in source and binary forms, with or
039 * without modification, are permitted provided that the following terms and conditions are met:
040 * </p>
041 * <p style="font-size:smaller;">1. Redistributions of source code must retain the above
042 * acknowledgement of the SoftNet-Consult Java Utility Library project, the above copyright
043 * notice, this list of conditions and the following disclaimer.<br />
044 * 2. Redistributions in binary form must reproduce the above acknowledgement of the
045 * SoftNet-Consult Java Utility Library project, the above copyright notice, this list of
046 * conditions and the following disclaimer in the documentation and/or other materials
047 * provided with the distribution.<br />
048 * 3. All advertising materials mentioning features or use of this software or any derived
049 * software must display the following acknowledgement:<br />
050 * <em>This product includes software developed by the SoftNet-Consult Java Utility Library
051 * project and its contributors.</em>
052 * </p>
053 * <p style="font-size:smaller;">THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY
054 * OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
055 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
056 * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
057 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
058 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
059 * </p>
060 * @author Greg Paperin (<a href="http://www.paperin.org" target="_blank">http://www.paperin.org</a>)
061 * @version {@value com.softnetConsult.utils.APIProperties#apiVersion}
062 *
063 */
064 public class ClassFileVersionTool {
065
066
067 /**
068 * Prints version info for a class binary in a nice looing way.
069 *
070 * @param name File name of class file.
071 * @param info Version info.
072 * @param out The indenting screen logger to use for all output.
073 */
074 private static void printInfo(String name, ClassVersionInfo info, ScreenFileLogger out) {
075 if (null == info) {
076 out.println(name + ": Invalid magic number. Not a class file?");
077 } else {
078 out.println(name + ": " + info.majorClassVersion + "." + info.minorClassVersion
079 + " (Java " + info.reqJavaVersion + ")");
080 }
081 }
082
083
084 /**
085 * Analyses the specified Java binary and prints its version info.
086 *
087 * @param classfile The class binary to analyse.
088 * @param out The indenting screen logger to use for all output.
089 */
090 private static void procClass(File classfile, ScreenFileLogger out) {
091 try {
092 ClassVersionInfo verInfo = SystemTools.getClassVersionInfo(classfile);
093 printInfo(classfile.getName(), verInfo, out);
094 } catch (IOException e) {
095 out.println(classfile.getName() + ": Error during processing (" + e.getMessage() + ").");
096 }
097 }
098
099
100 /**
101 * Analyses the specified archive and prints the version info for all Java class
102 * files contained therein.
103 *
104 * @param archive The archive to analyse.
105 * @param out The indenting screen logger to use for all output.
106 */
107 private static void procZip(File archive, ScreenFileLogger out) {
108
109 out.println();
110 out.println("Archive \"" + archive.getAbsolutePath() + "\":");
111 out.incIndentLevel();
112
113 try {
114 ZipFile zip = new ZipFile(archive, ZipFile.OPEN_READ);
115 try {
116 int skippedSubArchives = 0;
117 List<ZipEntry> classes = new ArrayList<ZipEntry>();
118
119 Enumeration<? extends ZipEntry> entries = zip.entries();
120 while (entries.hasMoreElements()) {
121 ZipEntry entry = entries.nextElement();
122 if (entry.isDirectory())
123 continue;
124 String name = entry.getName().toLowerCase();
125 if (name.endsWith(".jar") || name.endsWith(".zip"))
126 skippedSubArchives++;
127 else if (name.endsWith(".class"))
128 classes.add(entry);
129 }
130
131 Collections.sort(classes, new Comparator<ZipEntry>() {
132 public int compare(ZipEntry ze1, ZipEntry ze2) {
133 return ze1.getName().compareTo(ze2.getName());
134 }
135 });
136
137 if (skippedSubArchives > 0)
138 out.println("Skipped archives within archive: " + skippedSubArchives + ".");
139
140 for (ZipEntry entry : classes) {
141 InputStream in = zip.getInputStream(entry);
142 ClassVersionInfo version = ClassTools.getClassVersionInfo(in);
143 printInfo(entry.getName(), version, out);
144 }
145
146 } finally {
147 zip.close();
148 }
149 } catch (IOException e) {
150 out.println("Error processing archive: " + e.getMessage());
151 }
152
153 out.decIndentLevel();
154 }
155
156
157 /**
158 * Analyses the specified directory and prints the version info for all Java class
159 * files contained therein.
160 *
161 * @param dir The directory to analyse.
162 * @param recursive Whther to recursively analyse subdirectories and archived contained
163 * therein. Anchives within archives are not analysed.
164 * @param out The indenting screen logger to use for all output.
165 */
166 private static void procDir(File dir, boolean recursive, ScreenFileLogger out) {
167
168 out.println();
169 out.println("Directory \"" + dir.getAbsolutePath() + "\":");
170 out.incIndentLevel();
171
172 List<File> subDirs = new ArrayList<File>();
173 List<File> archives = new ArrayList<File>();
174 List<File> classes = new ArrayList<File>();
175 int skippedSubDirs = 0;
176
177 File[] files = dir.listFiles();
178 for (File file : files) {
179 String name = file.getName().toLowerCase();
180 if (file.isDirectory())
181 if (recursive)
182 subDirs.add(file);
183 else
184 skippedSubDirs++;
185 else if (name.endsWith(".jar") || name.endsWith(".zip"))
186 archives.add(file);
187 else if (name.endsWith(".class"))
188 classes.add(file);
189 }
190
191 Collections.sort(subDirs);
192 Collections.sort(archives);
193 Collections.sort(classes);
194
195 if (skippedSubDirs > 0)
196 out.println("Recursive processing not enabled. Skipped subdirectories: " + skippedSubDirs + ".");
197
198 for (File classfile : classes) {
199 procClass(classfile, out);
200 }
201
202 for (File archive : archives) {
203 procZip(archive, out);
204 }
205
206 for (File subDir : subDirs) {
207 procDir(subDir, recursive, out);
208 }
209
210 out.decIndentLevel();
211 }
212
213
214 /**
215 * The actual main program. Analyses the specified path and prints the version info for all
216 * Java class files contained therein.
217 *
218 * @param path A path of a class binary, a directory or a Java archive (ZIP or JAR).
219 * @param recursive Whther to recursively analyse subdirectories and archived contained
220 * therein. Anchives within archives are not analysed.
221 */
222 public static void exec(String path, boolean recursive) {
223
224 if (null == path) {
225 System.out.println("No path specified (null). Cannot continue.");
226 return;
227 }
228
229 File file = new File(path);
230 if (!file.exists()) {
231 System.out.println(file.getAbsolutePath() + " does not exist. Cannot continue.");
232 return;
233 }
234
235 ScreenFileLogger out = ScreenFileLoggerFactory.createLogger();
236 out.println("Processing " + path + ".");
237 out.incIndentLevel();
238
239 String name = file.getName().toLowerCase();
240
241 if (file.isDirectory())
242 procDir(file, recursive, out);
243 else if (name.endsWith(".jar") || name.endsWith(".zip"))
244 procZip(file, out);
245 else if (name.endsWith(".class"))
246 procClass(file, out);
247 else
248 out.println("This file is not a dir, zip, jar or class file.");
249
250 out.decIndentLevel();
251 out.print("Done.");
252 }
253
254
255 /**
256 * Used internally to print detailed information about the command line parameters.
257 */
258 private static void printUsage() {
259 System.out.println();
260 System.out.println("Usage: java ClassFileVersionPrinter [-R] PATH");
261 System.out.println(" * PATH must be an existing class file, a directory, a ZIP-file or a JAR-file.");
262 System.out.println(" * If PATH is a directory and -R is specified, all subdirectories will be");
263 System.out.println(" processed recursively. Otherwise only class files contained directly in the");
264 System.out.println(" specified directory will be processed.");
265 }
266
267
268 /**
269 * This executable program prints version information about all Java class files
270 * within a specified subdirectory.
271 *
272 * @param args Command line parameters:<br />
273 * 1. (optional) {@code -R} to request recursive analysis of all subdirectories.<br />
274 * 2. Path of a Java class binary, directory or archive to analyse.<br/>
275 * For futher info on the command line parameters, see {@link #printUsage()}.
276 */
277 public static void main(String[] args) {
278
279 if (null == args || args.length < 1 || 2 < args.length) {
280 printUsage();
281 return;
282 }
283
284 String path;
285 boolean recursive;
286
287 if (2 == args.length) {
288 if (!args[0].equalsIgnoreCase("-R")) {
289 printUsage();
290 return;
291 }
292 path = args[1];
293 recursive = true;
294 } else { // 1 == args.length
295 path = args[0];
296 recursive = false;
297 }
298
299 exec(path, recursive);
300 }
301
302 } // ClassFileVersionTool