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