blob: 1aec2330a2d7affc5158bf52f5b0d7f01619207c [file] [log] [blame]
/***
* ASM: a very small and fast Java bytecode manipulation framework
* Copyright (c) 2000-2011 INRIA, France Telecom
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.objectweb.asm.tools;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.zip.GZIPInputStream;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Handle;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
/**
* A command line tool to transform classes in order to make them compatible
* with Java 1.5, and to check that they use only the JDK 1.5 API and JDK 1.5
* classfile features.
*
* @author Eric Bruneton
* @author Eugene Kuleshov
*/
public class Retrofitter {
/**
* The fields and methods of the JDK 1.5 API. Each string has the form
* "<owner> <name><descriptor>".
*/
static final HashSet<String> API = new HashSet<String>();
/**
* The class hierarchy of the JDK 1.5 API. Maps each class name to the name
* of its super class.
*/
static final HashMap<String, String> HIERARCHY = new HashMap<String, String>();
/**
* Transforms the classes from a source directory into a destination
* directory to make them compatible with the JDK 1.5, and checks that they
* only use the JDK 1.5 API.
*
* @param args
* First argument: name of the .txt.gz file specifying the JDK
* 1.5 API. Second argument: source directory. Third argument:
* destination directory.
* @throws IOException
* if a file can't be read or written.
*/
public static void main(final String[] args) throws IOException {
File api = new File(args[0]);
InputStream inputStream = new GZIPInputStream(new FileInputStream(api));
BufferedReader reader = new LineNumberReader(
new InputStreamReader(inputStream));
while (true) {
String line = reader.readLine();
if (line != null) {
if (line.startsWith("class")) {
String className = line.substring(6, line.lastIndexOf(' '));
String superClassName = line
.substring(line.lastIndexOf(' ') + 1);
HIERARCHY.put(className, superClassName);
} else {
API.add(line);
}
} else {
break;
}
}
File src = new File(args[1]);
File dst = new File(args[2]);
if (!retrofit(src, dst)) {
System.exit(1);
}
}
/**
* Transforms the source class file, or if it is a directory, its files
* (recursively), into the destination file or directory, in order to make
* them compatible with the JDK 1.5.
*
* @param src
* source file or directory
* @param dst
* destination file or directory
* @return true if all the source classes use only the JDK 1.5 API.
* @throws IOException
*/
static boolean retrofit(final File src, final File dst) throws IOException {
if (src.isDirectory()) {
boolean result = true;
File[] files = src.listFiles();
if (files == null) {
throw new IOException("unable to read files of " + src);
}
for (int i = 0; i < files.length; ++i) {
result &= retrofit(files[i], new File(dst, files[i].getName()));
}
return result;
} else if (src.getName().endsWith(".class")) {
if (!dst.exists() || dst.lastModified() < src.lastModified()) {
ClassReader classReader = new ClassReader(new FileInputStream(src));
ClassWriter classWriter = new ClassWriter(0);
// No actual retrofit to do since we compile with target=1.5.
ClassVerifier classVerifier = new ClassVerifier(classWriter);
classReader.accept(classVerifier, 0);
if (!dst.getParentFile().exists()
&& !dst.getParentFile().mkdirs()) {
throw new IOException(
"Cannot create directory " + dst.getParentFile());
}
OutputStream os = new FileOutputStream(dst);
try {
os.write(classWriter.toByteArray());
} finally {
os.close();
}
return classVerifier.ok;
} else {
return true;
}
} else {
return true;
}
}
/**
* A ClassVisitor checking that a class uses only JDK 1.5 class file
* features and the JDK 1.5 API.
*/
static class ClassVerifier extends ClassVisitor {
/** The name of the visited class/ */
String className;
/** The name of the currently visited method. */
String currentMethodName;
/** Whether the class uses only JDK 1.5 class file features and APIs. */
boolean ok;
public ClassVerifier(ClassVisitor cv) {
// Make sure use we don't use Java 9 or higher classfile features.
// We also want to make sure we don't use Java 6, 7 or 8 classfile
// features (invokedynamic), but this can't be done in the same way.
// Instead, we use manual checks below.
super(Opcodes.ASM4, cv);
ok = true;
}
@Override
public void visit(final int version, final int access,
final String name, final String signature,
final String superName, final String[] interfaces) {
className = name;
super.visit(version, access, name, signature, superName,
interfaces);
}
@Override
public MethodVisitor visitMethod(final int access, final String name,
final String desc, final String signature,
final String[] exceptions) {
currentMethodName = name + desc;
MethodVisitor mv = super.visitMethod(access, name, desc, signature,
exceptions);
return new MethodVisitor(Opcodes.ASM4, mv) {
@Override
public void visitFieldInsn(final int opcode, final String owner,
final String name, final String desc) {
check(owner, name);
super.visitFieldInsn(opcode, owner, name, desc);
}
@Override
public void visitMethodInsn(final int opcode,
final String owner, final String name,
final String desc, final boolean itf) {
check(owner, name + desc);
super.visitMethodInsn(opcode, owner, name, desc, itf);
}
@Override
public void visitLdcInsn(Object cst) {
if (cst instanceof Type) {
int sort = ((Type) cst).getSort();
if (sort == Type.METHOD) {
System.err.println("ERROR: ldc with a MethodType called in "
+ className + ' ' + currentMethodName
+ " is not available in JDK 1.5");
ok = false;
}
} else if (cst instanceof Handle) {
System.err.println("ERROR: ldc with a MethodHandle called in "
+ className + ' ' + currentMethodName
+ " is not available in JDK 1.5");
ok = false;
}
super.visitLdcInsn(cst);
}
@Override
public void visitInvokeDynamicInsn(String name, String desc,
Handle bsm, Object... bsmArgs) {
System.err.println("ERROR: invokedynamic called in "
+ className + ' ' + currentMethodName
+ " is not available in JDK 1.5");
ok = false;
super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
}
};
}
/**
* Checks whether or not a field or method is defined in the JDK 1.5
* API.
*
* @param owner
* A class name.
* @param member
* A field name or a method name and descriptor.
*/
void check(String owner, String member) {
if (owner.startsWith("java/")) {
String o = owner;
while (o != null) {
if (API.contains(o + ' ' + member)) {
return;
}
o = HIERARCHY.get(o);
}
System.err.println("ERROR: " + owner + ' ' + member
+ " called in " + className + ' ' + currentMethodName
+ " is not defined in the JDK 1.5 API");
ok = false;
}
}
}
}