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 &quot;AS IS&quot;, 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