/* * [JarCheck.java] * * Summary: Ensures javac -target versions of the class files in a jar are as expected. * * Copyright: (c) 2006-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 1.0 2006-01-16 initial version * 1.1 2006-01-16 * 1.2 2006-03-05 reformat with IntelliJ, add Javadoc * 1.3 2008-04-21 display version number of each class file checked. * 1.4 2011-07-30 add support for Java 1.7 * 1.5 2014-03-23 add support for Java 11.8 */ package com.mindprod.jarcheck; import java.io.EOFException; import java.io.FileInputStream; import java.io.IOException; import java.util.HashMap; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import static java.lang.System.*; /* TODO: check class minor version as well. */ /** * Ensures javac -target versions of the class files in a jar are as expected. * * @author Roedy Green, Canadian Mind Products * @version 1.5 2014-03-23 add support for Java 1.8 * @since 2006-01-16 */ public final class JarCheck { /** * how many bytes at beginning of class file we read
4=ca-fe-ba-be + 2=minor + 2=major */ private static final int chunkLength = 8; private static final int FIRST_COPYRIGHT_YEAR = 2006; /** * undisplayed copyright notice */ private static final String EMBEDDED_COPYRIGHT = "Copyright: (c) 2006-2017 Roedy Green, Canadian Mind Products, http://mindprod.com"; private static final String RELEASE_DATE = "2014-03-23"; /** * embedded version string. */ private static final String VERSION_STRING = "1.5"; /** * translate class file major version number to human JVM version */ private static final HashMap convertMachineToHuman = new HashMap<>( 23 ); /** * translate from human JDK version to class file major version number */ private static final HashMap convertHumanToMachine = new HashMap<>( 23 ); /** * expected first 4 bytes of a class file */ private static final byte[] expectedMagicNumber = { ( byte ) 0xca, ( byte ) 0xfe, ( byte ) 0xba, ( byte ) 0xbe }; static { convertHumanToMachine.put( "1.0", 44 ); convertHumanToMachine.put( "1.1", 45 ); convertHumanToMachine.put( "1.2", 46 ); convertHumanToMachine.put( "1.3", 47 ); convertHumanToMachine.put( "1.4", 48 ); convertHumanToMachine.put( "1.5", 49 ); convertHumanToMachine.put( "1.6", 50 ); convertHumanToMachine.put( "1.7", 51 ); convertHumanToMachine.put( "1.8", 52 ); } static { convertMachineToHuman.put( 44, "1.0" ); convertMachineToHuman.put( 45, "1.1" ); convertMachineToHuman.put( 46, "1.2" ); convertMachineToHuman.put( 47, "1.3" ); convertMachineToHuman.put( 48, "1.4" ); convertMachineToHuman.put( 49, "1.5" ); convertMachineToHuman.put( 50, "1.6" ); convertMachineToHuman.put( 51, "1.7" ); convertMachineToHuman.put( 52, "1.8" ); } /** * check one jar to make sure all class files have compatible versions. * * @param jarFilename name of jar file whose classes are to be tested. * @param low low bound for major version e.g. 44 * @param high high bound for major version. e.g. 50 * * @return true if all is ok. False if not, with long on System.err of problems. */ private static boolean checkJar( String jarFilename, int low, int high ) { out.println( "\nChecking jar " + jarFilename ); boolean success = true; FileInputStream fis; ZipInputStream zip = null; try { try { fis = new FileInputStream( jarFilename ); zip = new ZipInputStream( fis ); // loop for each jar entry entryLoop: while ( true ) { ZipEntry entry = zip.getNextEntry(); if ( entry == null ) { break; } // relative name with slashes to separate dirnames. String elementName = entry.getName(); if ( !elementName.endsWith( ".class" ) ) { // ignore anything but a .final class file continue; } byte[] chunk = new byte[ chunkLength ]; int bytesRead = zip.read( chunk, 0, chunkLength ); zip.closeEntry(); if ( bytesRead != chunkLength ) { err.println( ">> Corrupt class file: " + elementName ); success = false; continue; } // make sure magic number signature is as expected. for ( int i = 0; i < expectedMagicNumber.length; i++ ) { if ( chunk[ i ] != expectedMagicNumber[ i ] ) { err.println( ">> Bad magic number in " + elementName ); success = false; continue entryLoop; } } /* * pick out big-endian ushort major version in last two * bytes of chunk */ int major = ( ( chunk[ chunkLength - 2 ] & 0xff ) << 8 ) + ( chunk[ chunkLength - 1 ] & 0xff ); /* F I N A L L Y. All this has been leading up to this TEST */ if ( low <= major && major <= high ) { out.print( " OK " ); out.println( convertMachineToHuman.get( major ) + " (" + major + ") " + elementName ); // leave success set as previously } else { err.println( ">> Wrong Version " ); err.println( convertMachineToHuman.get( major ) + " (" + major + ") " + elementName ); success = false; } } // end while } catch ( EOFException e ) { // normal exit } zip.close(); return success; } catch ( IOException e ) { err.println( ">> Problem reading jar file." ); return false; } } /** * Main command line jarfileName lowVersion highVersion e.g. myjar.jar 1.0 1.8 * * @param args rot used */ public static void main( String[] args ) { try { if ( args.length != 3 ) { err.println( "usage: java -ea -jar jarcheck.jar jarFileName.jar 1.1 1.7" ); System.exit( 2 ); } String jarFilename = args[ 0 ]; int low = convertHumanToMachine.get( args[ 1 ] ); int high = convertHumanToMachine.get( args[ 2 ] ); boolean success = checkJar( jarFilename, low, high ); if ( !success ) { System.exit( 1 ); } } catch ( NullPointerException e ) { err.println( "usage: java -ea -jar jarcheck.jar jarFileName.jar 1.1 1.8" ); System.exit( 2 ); } } }