rubah.tools.UpdateClassGenerator.java Source code

Java tutorial

Introduction

Here is the source code for rubah.tools.UpdateClassGenerator.java

Source

/*******************************************************************************
 *     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.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;

import org.apache.commons.io.IOUtils;
import org.javatuples.Pair;
import org.objectweb.asm.Opcodes;

import rubah.bytecode.transformers.AddHashCodeField;
import rubah.framework.Clazz;
import rubah.framework.Field;
import rubah.framework.Namespace;
import rubah.framework.Type;
import rubah.runtime.Version;
import rubah.update.change.Change;
import rubah.update.change.ChangeSet;
import rubah.update.change.ChangeType;
import rubah.update.change.ClassChange;

import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.converters.BooleanConverter;
import com.beust.jcommander.converters.FileConverter;

import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;

public class UpdateClassGenerator implements Opcodes {

    public static final String V0_PREFFIX = "v0";
    public static final String V1_PREFFIX = "v1";
    public static final String METHOD_NAME = "convert";
    public static final String METHOD_NAME_STATIC = "convertStatic";
    public static final String COPY_METHOD_NAME_STATIC = "copyFields";

    private static class ArgParser {
        @Parameter(converter = FileConverter.class, description = "Previous version descriptor", names = {
                "-v0" }, required = true)
        private File v0Descriptor;

        @Parameter(converter = FileConverter.class, description = "New version descriptor", names = {
                "-v1" }, required = true)
        private File v1Descriptor;

        @Parameter(converter = FileConverter.class, description = "File where to write the update class Java source", names = {
                "-u", "--update-class" }, required = true)
        private File outJavaFile;

        @Parameter(description = "Package of the generated Java source file", names = { "-p", "--package" })
        private String outJavaPackage;

        @Parameter(converter = FileConverter.class, description = "Jar file where to writhe the conversion classes", names = {
                "-o", "--out" }, required = true)
        private File outJar;

        @Parameter(converter = BooleanConverter.class, description = "Flag that makes this tool generate conversion code for unchanged classes", names = {
                "-a", "--all" })
        private boolean allClasses = false;
    }

    private final static Map<Type, String> primitiveNullValues;

    static {
        primitiveNullValues = new HashMap<Type, String>();
        primitiveNullValues.put(Type.BOOLEAN_TYPE, "false");
        primitiveNullValues.put(Type.BYTE_TYPE, "(byte) 0");
        primitiveNullValues.put(Type.CHAR_TYPE, "\0");
        primitiveNullValues.put(Type.DOUBLE_TYPE, "0.0");
        primitiveNullValues.put(Type.FLOAT_TYPE, "0");
        primitiveNullValues.put(Type.INT_TYPE, "0");
        primitiveNullValues.put(Type.LONG_TYPE, "0L");
        primitiveNullValues.put(Type.SHORT_TYPE, "0");
    }

    private boolean allClasses;
    private Namespace defaultNamespace = new Namespace();
    private Version v0, v1;
    private Map<Clazz, ClassChange> changes;
    private Map<Namespace, String> preffixes = new HashMap<Namespace, String>();

    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);
        }

        UpdateClassGenerator ucg = new UpdateClassGenerator(parser.v0Descriptor, parser.v1Descriptor,
                parser.allClasses);

        ucg.generateConversionJar(parser.outJar);

        ucg.generateUpdateClass(parser.outJavaFile, parser.outJavaPackage);

    }

    public UpdateClassGenerator(File v0Descriptor, File v1Descriptor, boolean allClasses) throws IOException {
        this.allClasses = allClasses;

        this.v0 = new Version(0, UpdatableJarAnalyzer.readFile(v0Descriptor, this.defaultNamespace), null);
        this.v1 = new Version(1, UpdatableJarAnalyzer.readFile(v1Descriptor, this.defaultNamespace), this.v0);

        this.preffixes.put(this.v0.getNamespace(), V0_PREFFIX);
        this.preffixes.put(this.v1.getNamespace(), V1_PREFFIX);

        this.changes = new Comparator(this.v0, this.v1).computeChanges(false);
    }

    public void generateUpdateClass(File outJavaFile, String outJavaPackage) throws IOException {

        String template = new String(
                IOUtils.toCharArray(UpdateClassGenerator.class.getResourceAsStream("/UpdateClass.template.java")));

        Configuration cfg = new Configuration();
        StringTemplateLoader loader = new StringTemplateLoader();
        loader.putTemplate("template", template);
        cfg.setTemplateLoader(loader);
        cfg.setObjectWrapper(new DefaultObjectWrapper());

        Template temp = cfg.getTemplate("template");

        FileWriter fw = new FileWriter(outJavaFile);
        BufferedWriter bw = new BufferedWriter(fw);

        try {
            temp.process(this.buildTemplateModel(outJavaPackage), bw);
        } catch (TemplateException e) {
            throw new Error(e);
        }

        bw.close();
        fw.close();
    }

    private Map<String, Object> buildTemplateModel(String outJavaPackage) {
        Map<String, Object> templateModel = new HashMap<String, Object>();

        templateModel.put("package", outJavaPackage);
        templateModel.put("namespace", this.defaultNamespace);
        templateModel.put("modifier", new Modifier());
        templateModel.put("helper", this);
        templateModel.put("convertName", METHOD_NAME);
        templateModel.put("convertStaticName", METHOD_NAME_STATIC);
        templateModel.put("copyStaticFieldsMethodName", COPY_METHOD_NAME_STATIC);

        HashSet<ChangesTemplateModel> detectedChanges = new HashSet<ChangesTemplateModel>();
        for (Entry<Clazz, ClassChange> change : this.changes.entrySet()) {

            if (change.getValue().getChangeSet().hasChange(ChangeType.NEW_CLASS)) {
                continue;
            }

            ChangesTemplateModel ctm = new ChangesTemplateModel(
                    this.addPreffixToFQN(change.getValue().getOriginal()), this.addPreffixToFQN(change.getKey()));

            if (this.allClasses) {
                ctm.hasStaticChanges = true;
                ctm.hasNonStaticChanges = true;
            }

            for (Entry<Field, Change<Field>> fieldChange : change.getValue().getFieldChanges().entrySet()) {

                if (this.isHashCode(fieldChange)) {
                    continue;
                }

                ChangeSet changeSet = fieldChange.getValue().getChangeSet();

                if (changeSet.hasChange(ChangeType.NEW_FIELD)) {
                    if (Modifier.isStatic(fieldChange.getKey().getAccess())) {
                        ctm.hasStaticChanges = true;
                    } else {
                        ctm.hasNonStaticChanges = true;
                    }
                    ctm.newFields.add(new Pair<Field, Change<Field>>(fieldChange.getKey(), fieldChange.getValue()));
                } else if (changeSet.hasChange(ChangeType.NEW_CONSTANT)) {
                    ctm.hasStaticChanges = true;
                    ctm.newConstants
                            .add(new Pair<Field, Change<Field>>(fieldChange.getKey(), fieldChange.getValue()));
                } else if (changeSet.hasChange(ChangeType.FIELD_TYPE_CHANGE)) {
                    if (Modifier.isStatic(fieldChange.getKey().getAccess())) {
                        ctm.hasStaticChanges = true;
                    } else {
                        ctm.hasNonStaticChanges = true;
                    }
                    ctm.typeChangedFields
                            .add(new Pair<Field, Change<Field>>(fieldChange.getKey(), fieldChange.getValue()));
                } else {
                    ctm.unmodifiedFields
                            .add(new Pair<Field, Change<Field>>(fieldChange.getKey(), fieldChange.getValue()));
                }
            }

            if (ctm.hasNonStaticChanges || ctm.hasStaticChanges) {
                detectedChanges.add(ctm);
            }
        }

        templateModel.put("classes", detectedChanges);

        return templateModel;
    }

    private boolean isHashCode(Entry<Field, Change<Field>> fieldChange) {
        return fieldChange.getKey().getName().equals(AddHashCodeField.FIELD_NAME)
                || (fieldChange.getValue().getOriginal() != null
                        && fieldChange.getValue().getOriginal().getName().equals(AddHashCodeField.FIELD_NAME));
    }

    public String addPreffixToFQNIgnoreArrays(Clazz c) {

        if (c.isArray()) {
            c = c.getNamespace().getClass(c.getASMType().getElementType());
        }

        String preffix = this.preffixes.get(c.getNamespace());

        if (preffix != null) {
            return preffix + "." + c.getFqn();
        }

        return c.getFqn();
    }

    public String getNullValue(Clazz c) {
        if (c.getASMType().isPrimitive()) {
            return primitiveNullValues.get(c.getASMType());
        } else {
            return "null";
        }
    }

    private String addPreffixToFQN(Clazz c) {
        String preffix = this.preffixes.get(c.getNamespace());

        if (preffix != null) {
            return preffix + "." + c.getFqn();
        }

        return c.getFqn();
    }

    public void generateConversionJar(File outJar) throws IOException {

        FileOutputStream fos = new FileOutputStream(outJar);
        JarOutputStream jos = new JarOutputStream(fos, new Manifest());

        this.generateConversionClasses(jos, this.v0, this.preffixes.get(this.v1.getNamespace()));
        this.generateConversionClasses(jos, this.v1, "");

        jos.close();
        fos.close();
    }

    private void generateConversionClasses(JarOutputStream jos, Version v, String newPreffix) throws IOException {

        ConversionClassGenerator ccg = new ConversionClassGenerator(v.getNamespace(),
                this.preffixes.get(v.getNamespace()), newPreffix);

        for (Clazz cl : v.getNamespace().getDefinedClasses()) {
            ccg.addConversionClassesToJar(jos, cl);
        }
    }

    public static class SortedChanges {
    }

    public static class ChangesTemplateModel {
        private final String v0, v1;
        private boolean hasStaticChanges = false;
        private boolean hasNonStaticChanges = false;
        public final Set<Pair<Field, Change<Field>>> unmodifiedFields = new HashSet<Pair<Field, Change<Field>>>();
        public final Set<Pair<Field, Change<Field>>> newFields = new HashSet<Pair<Field, Change<Field>>>();
        public final Set<Pair<Field, Change<Field>>> newConstants = new HashSet<Pair<Field, Change<Field>>>();
        public final Set<Pair<Field, Change<Field>>> typeChangedFields = new HashSet<Pair<Field, Change<Field>>>();

        public ChangesTemplateModel(String v0, String v1) {
            this.v0 = v0;
            this.v1 = v1;
        }

        public String getV0() {
            return this.v0;
        }

        public String getV1() {
            return this.v1;
        }

        public boolean isHasStaticChanges() {
            return this.hasStaticChanges;
        }

        public void setHasStaticChanges(boolean hasStaticChanges) {
            this.hasStaticChanges = hasStaticChanges;
        }

        public boolean isHasNonStaticChanges() {
            return this.hasNonStaticChanges;
        }

        public void setHasNonStaticChanges(boolean hasNonStaticChanges) {
            this.hasNonStaticChanges = hasNonStaticChanges;
        }

        public Set<Pair<Field, Change<Field>>> getUnmodifiedFields() {
            return this.unmodifiedFields;
        }

        public Set<Pair<Field, Change<Field>>> getNewFields() {
            return this.newFields;
        }

        public Set<Pair<Field, Change<Field>>> getNewConstants() {
            return this.newConstants;
        }

        public Set<Pair<Field, Change<Field>>> getTypeChangedFields() {
            return this.typeChangedFields;
        }
    }
}