/* Copyright (C) 2007 Christian Schneider
*
* This file is part of Nomad.
*
* Nomad is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Nomad is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Nomad; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.sf.nmedit.projectutils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* Validates the java .class files major version number.
*/
public class ClassFileVersionValidator
{
public static final int MAJ_J2SE6 = 50;
public static final int MAJ_J2SE5 = 49;
public static final int MAJ_JDK14 = 48;
public static final int MAJ_JDK13 = 47;
public static final int MAJ_JDK12 = 46;
public static final String MAJ_J2SE6_NAME = "J2SE_6.0";
public static final String MAJ_J2SE5_NAME = "J2SE_5.0";
public static final String MAJ_JDK14_NAME = "JDK_1.4";
public static final String MAJ_JDK13_NAME = "JDK_1.3";
public static final String MAJ_JDK12_NAME = "JDK_1.2";
public static final String majorVersionToString(int majorVersion)
{
switch (majorVersion)
{
case MAJ_J2SE6: return "J2SE 6.0";
case MAJ_J2SE5: return "J2SE 5.0";
case MAJ_JDK14: return "JDK 1.4";
case MAJ_JDK13: return "JDK 1.3";
case MAJ_JDK12: return "JDK 1.2";
default: return null;
}
}
public static final int stringToMajorVersion(String version)
{
if (version.equals(MAJ_J2SE6_NAME))
return MAJ_J2SE6;
if (version.equals(MAJ_J2SE5_NAME))
return MAJ_J2SE5;
if (version.equals(MAJ_JDK14_NAME))
return MAJ_JDK14;
if (version.equals(MAJ_JDK13_NAME))
return MAJ_JDK13;
if (version.equals(MAJ_JDK12_NAME))
return MAJ_JDK12;
return -1;
}
public static class ClassFileInfo
{
String name;
int minorVersion;
int majorVersion;
public ClassFileInfo(String name, int minorVersion, int majorVersion)
{
this.name = name;
this.minorVersion = minorVersion;
this.majorVersion = majorVersion;
}
public String toString()
{
return name + " major:" + majorVersion +" ("+ majorName() + ") minor:"+minorVersion;
}
private String majorName()
{
String name = majorVersionToString(majorVersion);
if (name == null)
{
name = "undefined";
}
return name;
}
}
public static ClassFileInfo getInfo(String name, InputStream in) throws IOException
{
byte[] b = new byte[8];
byte[] magick = new byte[] {(byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE};
in.read(b, 0, magick.length);
for (int i=0;i<magick.length;i++)
if (magick[i]!=b[i])
return null; // magick number wrong => not a class file
// byte 4-5: minor version number of the class file format being used
// byte 6-7: major version number of the class file format being used. J2SE 6.0=50, J2SE 5.0=49, JDK 1.4=48, JDK 1.3=47, JDK 1.2=46. For details of earlier version numbers see footnote 1 at The JavaTM Virtual Machine Specification 2nd edition
in.read(b, magick.length, 4);
int minor = (b(b[4])<<8)|b(b[5]);
int major = (b(b[6])<<8)|b(b[7]);
return new ClassFileInfo(name, minor, major);
}
private static int b(byte b)
{
return b&0xFF;
}
private static int maxVersionId;
private static int classesCount = 0;
private static int jarCount = 0;
public static void main(String[] args) throws IOException
{
if (args.length == 1 && args[0].equals("-help"))
{
System.out.println("usage: -version <version> [<file>|<directory>]+");
System.out.println("<version>-strings: "
+MAJ_J2SE6_NAME+", "
+MAJ_J2SE5_NAME+", "
+MAJ_JDK14_NAME+", "
+MAJ_JDK13_NAME+", "
+MAJ_JDK12_NAME);
System.exit(0);
}
if (args.length<3)
{
System.err.println("missing arguments");
System.exit(1);
}
if (!args[0].equals("-version"))
{
System.err.println("'-version' not specified");
System.exit(1);
}
String version = args[1].toUpperCase();
maxVersionId = stringToMajorVersion(version);
if (maxVersionId<0)
{
System.err.println("unknown version string: "+args[1]);
System.exit(1);
}
System.out.println("Checking java .class files major version: <="+majorVersionToString(maxVersionId));
try
{
for (int i=2;i<args.length;i++)
validate(args[i]);
}
catch (FileNotFoundException e)
{
System.err.println(e.getMessage());
System.exit(1);
}
catch (VersionError e)
{
System.err.println(e.getMessage());
System.exit(1);
}
System.out.println("Done.");
System.out.println(".classes-files: "+classesCount);
System.out.println(".jar-files: "+jarCount);
System.out.println("All class files are "+majorVersionToString(maxVersionId)+" compliant.");
System.out.println("No nested jar files found.");
}
private static void validate(String fileName) throws IOException, VersionError
{
File file = new File(fileName);
if (!file.exists())
throw new FileNotFoundException("file not found: "+fileName);
if (file.isDirectory())
validateDir(file);
else if (file.isFile())
validateFile(file);
}
private static void validateDir(File file) throws IOException, VersionError
{
boolean first = false;
for (File f: file.listFiles())
{
if (f.isFile())
{
String n = file.getName();
if (first && (n.endsWith(".class")||n.endsWith(".jar")))
{
System.out.println(file.getAbsolutePath());
first = false;
}
validateFile(f);
}
else
validateDir(f);
}
}
private static void validateJar(File f) throws IOException, VersionError
{
System.out.println("jar: "+f.getAbsolutePath());
JarFile jar = new JarFile(f);
validateJar(jar);
}
// TODO check jar files inside jar files
private static void validateJar(JarFile jar) throws IOException, VersionError
{
jarCount++;
for (Enumeration<JarEntry> en=jar.entries();en.hasMoreElements();)
{
JarEntry e = en.nextElement();
if (e.getName().endsWith(".class"))
{
InputStream in = jar.getInputStream(e);
try
{
validateFile(e.getName(), in);
}
finally
{
in.close();
}
}
else if (e.getName().endsWith(".jar"))
{
throw new RuntimeException("not implemented: checking nested jar files");
}
}
}
private static void validateFile(String name, InputStream in) throws IOException, VersionError
{
ClassFileInfo info = getInfo(name, in);
if (info == null)
{
System.out.println("not a class file: "+name+" (magick number wrong or missing)");
}
else
{
classesCount ++;
// System.out.println(info);
if (info.majorVersion>maxVersionId)
throw new VersionError("invalid major version: "+info);
}
}
private static boolean validateFile(File file) throws IOException, VersionError
{
String n = file.getName();
if (n.toLowerCase().endsWith(".jar"))
{
validateJar(file);
return false;
}
if (!n.endsWith(".class"))
return false;
InputStream in = new FileInputStream(file);
try
{
validateFile(file.getAbsolutePath(), in);
return true;
}
finally
{
in.close();
}
}
private static class VersionError extends Exception
{
/**
*
*/
private static final long serialVersionUID = -8031013124643544471L;
public VersionError(String message)
{
super(message);
}
}
}