/*******************************************************************************
* Copyright 2014,
* Luis Pina <luis@luispina.me>,
* Michael Hicks <mwh@cs.umd.edu>
*
* This file is part of Rubah.
*
* Rubah 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 3 of the License, or
* (at your option) any later version.
*
* Rubah 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 Rubah. If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************/
package rubah.tools;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import org.apache.commons.io.IOUtils;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import rubah.framework.Clazz;
import rubah.framework.Field;
import rubah.framework.Method;
import rubah.framework.Namespace;
import rubah.framework.Type;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.converters.FileConverter;
public class ConversionClassGenerator implements Opcodes {
private static final int CLASS_VERSION = Opcodes.V1_5;
private static final int CLASS_ACCESS = ACC_PUBLIC;
private static final int FIELD_ACCESS = ACC_PUBLIC;
private static final int FIELD_STATIC_ACCESS = ACC_STATIC | ACC_PUBLIC;
private static final int CONVERT_METHOD_ACCESS = ACC_PUBLIC | ACC_ABSTRACT;
private static final int CONVERT_ARRAY_METHOD_ACCESS = ACC_PUBLIC | ACC_STATIC | ACC_NATIVE;
private static final int COPY_METHOD_ACCESS_STATIC = ACC_PUBLIC | ACC_STATIC | ACC_NATIVE;
private static class ArgParser {
@Parameter(
converter=FileConverter.class,
description="Previous version descriptor",
names={"-d","--descriptor"},
required=true)
private File versionDescriptor;
@Parameter(
description="Package preffix",
names={"-p","--preffix"},
required=true)
private String preffix;
@Parameter(
converter=FileConverter.class,
description="Out jar file",
names={"-o","--out"},
required=true)
private File outJar;
}
private Namespace namespace;
private String preffix;
private String newPreffix = "";
public static void main(String[] args) throws IOException {
ArgParser parser = new ArgParser();
JCommander argParser = new JCommander(parser);
try {
argParser.parse(args);
} catch (ParameterException e) {
System.out.println(e.getMessage());
argParser.usage();
System.exit(1);
}
ConversionClassGenerator c =
new ConversionClassGenerator(
UpdatableJarAnalyzer.readFile(parser.versionDescriptor, new Namespace()).namespace,
parser.preffix
);
c.generateConversionClasses(parser.outJar);
}
public ConversionClassGenerator(Namespace namespace, String preffix) {
this.namespace = namespace;
this.preffix = preffix;
}
public ConversionClassGenerator(Namespace namespace, String preffix, String newPreffix) {
this.namespace = namespace;
this.preffix = preffix;
this.newPreffix = newPreffix;
}
public void generateConversionClasses(File outJar) throws IOException {
FileOutputStream fos = new FileOutputStream(outJar);
JarOutputStream jos = new JarOutputStream(fos, new Manifest());
for (Clazz cl : this.namespace.getDefinedClasses()) {
this.addConversionClassesToJar(jos, cl);
}
jos.close();
fos.close();
}
public void addConversionClassesToJar(JarOutputStream jos, Clazz cl) throws IOException {
jos.putNextEntry(new JarEntry(this.getType(cl).getInternalName() + ".class"));
IOUtils.write(this.getClassBytes(cl), jos);
}
private byte[] getClassBytes(Clazz cl) {
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
String[] interfaces = new String[cl.getInterfaces().size()];
{
int i = 0;
for (Clazz iface : cl.getInterfaces()) {
interfaces[i++] = this.getType(iface).getInternalName();
}
}
writer.visit(
CLASS_VERSION,
CLASS_ACCESS | (cl.isInterface() ? Modifier.INTERFACE : 0),
this.getType(cl).getInternalName(),
null,
this.getType(cl.getParent()).getInternalName(),
interfaces);
for (Field f : cl.getFields()) {
writer.visitField(
Modifier.isStatic(f.getAccess()) ? FIELD_STATIC_ACCESS : FIELD_ACCESS,
f.getName(),
this.getType(f.getType()).getDescriptor(),
null,
null);
}
for (Method m : cl.getMethods()) {
Type ret = this.getType(m.getRetType());
Type[] args = new Type[m.getArgTypes().size()];
{
int i = 0;
for (Clazz arg : m.getArgTypes()) {
args[i++] = this.getType(arg);
}
}
writer.visitMethod(
(Modifier.isStatic(m.getAccess()) ? FIELD_STATIC_ACCESS : FIELD_ACCESS) | ACC_ABSTRACT,
m.getName(),
Type.getMethodDescriptor(ret, args),
null,
null);
}
writer.visitMethod(
CONVERT_METHOD_ACCESS,
UpdateClassGenerator.METHOD_NAME,
Type.getMethodDescriptor(this.getType(cl, this.newPreffix)),
null,
null);
writer.visitMethod(
CONVERT_METHOD_ACCESS,
UpdateClassGenerator.METHOD_NAME,
Type.getMethodDescriptor(
this.getType(cl, this.newPreffix),
this.getType(cl, this.newPreffix)),
null,
null);
if (this.newPreffix.equals("")) {
writer.visitMethod(
COPY_METHOD_ACCESS_STATIC,
UpdateClassGenerator.COPY_METHOD_NAME_STATIC,
Type.getMethodDescriptor(Type.VOID_TYPE),
null,
null);
}
Clazz arrayCl = this.namespace.getClass(cl.getASMType(), 1);
writer.visitMethod(
CONVERT_ARRAY_METHOD_ACCESS,
UpdateClassGenerator.METHOD_NAME,
Type.getMethodDescriptor(
this.getType(arrayCl, this.newPreffix),
this.getType(arrayCl)),
null,
null);
writer.visitEnd();
return writer.toByteArray();
}
private Type getType(Clazz cl) {
return this.getType(cl, this.preffix);
}
private Type getType(Clazz cl, String preffix) {
Type ret = cl.getASMType();
if (cl.getNamespace().equals(this.namespace)) {
if (cl.isArray()) {
StringBuffer typeDesc = new StringBuffer();
for (int i = 0 ; i < ret.getDimensions() ; i++) {
typeDesc.append('[');
}
typeDesc.append("L");
typeDesc.append(preffix.equals("") ? "" : preffix + "/");
typeDesc.append(ret.getElementType().getInternalName());
typeDesc.append(";");
return Type.getType(typeDesc.toString());
} else {
return Type.getObjectType((preffix.equals("") ? "" : preffix + "/") + ret.getInternalName());
}
} else {
return ret;
}
}
}