fr.insalyon.citi.golo.compiler.JavaBytecodeStructGenerator.java Source code

Java tutorial

Introduction

Here is the source code for fr.insalyon.citi.golo.compiler.JavaBytecodeStructGenerator.java

Source

/*
 * Copyright 2012-2014 Institut National des Sciences Appliques de Lyon (INSA-Lyon)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package fr.insalyon.citi.golo.compiler;

import fr.insalyon.citi.golo.compiler.ir.Struct;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;

import static fr.insalyon.citi.golo.compiler.JavaBytecodeUtils.loadInteger;
import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
import static org.objectweb.asm.Opcodes.*;

class JavaBytecodeStructGenerator {

    private static final String $_frozen = "$_frozen";
    public static final String IMMUTABLE_FACTORY_METHOD = "$_immutable";

    public CodeGenerationResult compile(Struct struct, String sourceFilename) {
        ClassWriter classWriter = new ClassWriter(COMPUTE_FRAMES | COMPUTE_MAXS);
        classWriter.visitSource(sourceFilename, null);
        classWriter.visit(V1_7, ACC_PUBLIC | ACC_SUPER | ACC_FINAL, struct.getPackageAndClass().toJVMType(), null,
                "gololang/GoloStruct", null);
        makeFields(classWriter, struct);
        makeAccessors(classWriter, struct);
        makeConstructors(classWriter, struct);
        makeImmutableFactory(classWriter, struct);
        makeToString(classWriter, struct);
        makeCopy(classWriter, struct, false);
        makeCopy(classWriter, struct, true);
        makeHashCode(classWriter, struct);
        makeEquals(classWriter, struct);
        makeValuesMethod(classWriter, struct);
        makeGetMethod(classWriter, struct);
        makeSetMethod(classWriter, struct);
        classWriter.visitEnd();
        return new CodeGenerationResult(classWriter.toByteArray(), struct.getPackageAndClass());
    }

    private void makeSetMethod(ClassWriter classWriter, Struct struct) {
        String owner = struct.getPackageAndClass().toJVMType();
        MethodVisitor visitor = classWriter.visitMethod(ACC_PUBLIC, "set",
                "(Ljava/lang/String;Ljava/lang/Object;)Lgololang/GoloStruct;", null, null);
        visitor.visitCode();
        insertPrivateElementCheck(struct, visitor);
        Label nextCase = new Label();
        for (String member : struct.getMembers()) {
            visitor.visitLdcInsn(member);
            visitor.visitVarInsn(ALOAD, 1);
            visitor.visitJumpInsn(IF_ACMPNE, nextCase);
            visitor.visitVarInsn(ALOAD, 0);
            visitor.visitVarInsn(ALOAD, 2);
            visitor.visitMethodInsn(INVOKEVIRTUAL, owner, member, "(Ljava/lang/Object;)Lgololang/GoloStruct;");
            visitor.visitInsn(ARETURN);
            visitor.visitLabel(nextCase);
            nextCase = new Label();
        }
        insertUnknowElementCode(struct, visitor);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    private void makeGetMethod(ClassWriter classWriter, Struct struct) {
        String owner = struct.getPackageAndClass().toJVMType();
        MethodVisitor visitor = classWriter.visitMethod(ACC_PUBLIC, "get", "(Ljava/lang/String;)Ljava/lang/Object;",
                null, null);
        visitor.visitCode();
        insertPrivateElementCheck(struct, visitor);
        Label nextCase = new Label();
        for (String member : struct.getMembers()) {
            visitor.visitLdcInsn(member);
            visitor.visitVarInsn(ALOAD, 1);
            visitor.visitJumpInsn(IF_ACMPNE, nextCase);
            visitor.visitVarInsn(ALOAD, 0);
            visitor.visitMethodInsn(INVOKEVIRTUAL, owner, member, "()Ljava/lang/Object;");
            visitor.visitInsn(ARETURN);
            visitor.visitLabel(nextCase);
            nextCase = new Label();
        }
        insertUnknowElementCode(struct, visitor);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    private void insertPrivateElementCheck(Struct struct, MethodVisitor visitor) {
        Label afterPrivateCheck = new Label();
        visitor.visitVarInsn(ALOAD, 1);
        visitor.visitLdcInsn("_");
        visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "startsWith", "(Ljava/lang/String;)Z");
        visitor.visitJumpInsn(IFEQ, afterPrivateCheck);
        visitor.visitTypeInsn(NEW, "java/lang/IllegalArgumentException");
        visitor.visitInsn(DUP);
        visitor.visitLdcInsn("Private member of " + struct.getPackageAndClass().toString());
        visitor.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>",
                "(Ljava/lang/String;)V");
        visitor.visitInsn(ATHROW);
        visitor.visitLabel(afterPrivateCheck);
    }

    private void insertUnknowElementCode(Struct struct, MethodVisitor visitor) {
        visitor.visitTypeInsn(NEW, "java/lang/IllegalArgumentException");
        visitor.visitInsn(DUP);
        visitor.visitLdcInsn("Unknown member in " + struct.getPackageAndClass().toString());
        visitor.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>",
                "(Ljava/lang/String;)V");
        visitor.visitInsn(ATHROW);
    }

    private void makeValuesMethod(ClassWriter classWriter, Struct struct) {
        String owner = struct.getPackageAndClass().toJVMType();
        MethodVisitor visitor = classWriter.visitMethod(ACC_PUBLIC, "values", "()Lgololang/Tuple;", null, null);
        visitor.visitCode();
        loadInteger(visitor, struct.getPublicMembers().size());
        visitor.visitTypeInsn(ANEWARRAY, "java/lang/Object");
        int index = 0;
        for (String member : struct.getPublicMembers()) {
            visitor.visitInsn(DUP);
            loadInteger(visitor, index);
            visitor.visitVarInsn(ALOAD, 0);
            visitor.visitFieldInsn(GETFIELD, owner, member, "Ljava/lang/Object;");
            visitor.visitInsn(AASTORE);
            index = index + 1;
        }
        visitor.visitMethodInsn(INVOKESTATIC, "gololang/Tuple", "fromArray",
                "([Ljava/lang/Object;)Lgololang/Tuple;");
        visitor.visitInsn(ARETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    private void makeEquals(ClassWriter classWriter, Struct struct) {
        String owner = struct.getPackageAndClass().toJVMType();
        MethodVisitor visitor = classWriter.visitMethod(ACC_PUBLIC, "equals", "(Ljava/lang/Object;)Z", null, null);
        Label notFrozenLabel = new Label();
        Label falseLabel = new Label();
        Label sameTypeLabel = new Label();
        visitor.visitCode();
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitFieldInsn(GETFIELD, owner, $_frozen, "Z");
        visitor.visitJumpInsn(IFNE, notFrozenLabel);
        // super.equals()
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitVarInsn(ALOAD, 1);
        visitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z");
        visitor.visitInsn(IRETURN);
        // The receiver is frozen
        visitor.visitLabel(notFrozenLabel);
        visitor.visitVarInsn(ALOAD, 1);
        visitor.visitTypeInsn(INSTANCEOF, owner);
        visitor.visitJumpInsn(IFNE, sameTypeLabel);
        visitor.visitJumpInsn(GOTO, falseLabel);
        // The argument is of the same type, too
        visitor.visitLabel(sameTypeLabel);
        visitor.visitVarInsn(ALOAD, 1);
        visitor.visitTypeInsn(CHECKCAST, owner);
        visitor.visitFieldInsn(GETFIELD, owner, $_frozen, "Z");
        visitor.visitJumpInsn(IFEQ, falseLabel);
        // The argument is not frozen
        for (String member : struct.getMembers()) {
            visitor.visitVarInsn(ALOAD, 0);
            visitor.visitFieldInsn(GETFIELD, owner, member, "Ljava/lang/Object;");
            visitor.visitVarInsn(ALOAD, 1);
            visitor.visitTypeInsn(CHECKCAST, owner);
            visitor.visitFieldInsn(GETFIELD, owner, member, "Ljava/lang/Object;");
            visitor.visitMethodInsn(INVOKESTATIC, "java/util/Objects", "equals",
                    "(Ljava/lang/Object;Ljava/lang/Object;)Z");
            visitor.visitJumpInsn(IFEQ, falseLabel);
        }
        visitor.visitInsn(ICONST_1);
        visitor.visitInsn(IRETURN);
        // False
        visitor.visitLabel(falseLabel);
        visitor.visitInsn(ICONST_0);
        visitor.visitInsn(IRETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    private void makeHashCode(ClassWriter classWriter, Struct struct) {
        String owner = struct.getPackageAndClass().toJVMType();
        MethodVisitor visitor = classWriter.visitMethod(ACC_PUBLIC, "hashCode", "()I", null, null);
        Label notFrozenLabel = new Label();
        visitor.visitCode();
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitFieldInsn(GETFIELD, owner, $_frozen, "Z");
        visitor.visitJumpInsn(IFNE, notFrozenLabel);
        // super.hashCode()
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "hashCode", "()I");
        visitor.visitInsn(IRETURN);
        // The receiver is frozen
        visitor.visitLabel(notFrozenLabel);
        loadInteger(visitor, struct.getMembers().size());
        visitor.visitTypeInsn(ANEWARRAY, "java/lang/Object");
        int i = 0;
        for (String member : struct.getMembers()) {
            visitor.visitInsn(DUP);
            loadInteger(visitor, i);
            visitor.visitVarInsn(ALOAD, 0);
            visitor.visitFieldInsn(GETFIELD, owner, member, "Ljava/lang/Object;");
            visitor.visitInsn(AASTORE);
            i = i + 1;
        }
        visitor.visitMethodInsn(INVOKESTATIC, "java/util/Objects", "hash", "([Ljava/lang/Object;)I");
        visitor.visitInsn(IRETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    private void makeCopy(ClassWriter classWriter, Struct struct, boolean frozen) {
        String owner = struct.getPackageAndClass().toJVMType();
        String methodName = frozen ? "frozenCopy" : "copy";
        MethodVisitor visitor = classWriter.visitMethod(ACC_PUBLIC, methodName, "()Lgololang/GoloStruct;", null,
                null);
        visitor.visitCode();
        visitor.visitTypeInsn(NEW, owner);
        visitor.visitInsn(DUP);
        for (String member : struct.getMembers()) {
            visitor.visitVarInsn(ALOAD, 0);
            visitor.visitFieldInsn(GETFIELD, owner, member, "Ljava/lang/Object;");
        }
        visitor.visitMethodInsn(INVOKESPECIAL, owner, "<init>", allArgsConstructorSignature(struct));
        visitor.visitInsn(DUP);
        visitor.visitInsn(frozen ? ICONST_1 : ICONST_0);
        visitor.visitFieldInsn(PUTFIELD, owner, $_frozen, "Z");
        visitor.visitInsn(ARETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    private void makeToString(ClassWriter classWriter, Struct struct) {
        String owner = struct.getPackageAndClass().toJVMType();
        MethodVisitor visitor = classWriter.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
        visitor.visitCode();
        visitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
        visitor.visitInsn(DUP);
        visitor.visitLdcInsn("struct " + struct.getPackageAndClass().className() + "{");
        visitor.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
        boolean first = true;
        for (String member : struct.getPublicMembers()) {
            visitor.visitInsn(DUP);
            visitor.visitLdcInsn((!first ? ", " : "") + member + "=");
            first = false;
            visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
                    "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
            visitor.visitInsn(DUP);
            visitor.visitVarInsn(ALOAD, 0);
            visitor.visitFieldInsn(GETFIELD, owner, member, "Ljava/lang/Object;");
            visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
                    "(Ljava/lang/Object;)Ljava/lang/StringBuilder;");
        }
        visitor.visitLdcInsn("}");
        visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
                "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
        visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
        visitor.visitInsn(ARETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    private void makeConstructors(ClassWriter classWriter, Struct struct) {
        String owner = struct.getPackageAndClass().toJVMType();
        makeNoArgsConstructor(classWriter, struct);
        makeAllArgsConstructor(classWriter, struct, owner);
    }

    private void makeAllArgsConstructor(ClassWriter classWriter, Struct struct, String owner) {
        MethodVisitor visitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", allArgsConstructorSignature(struct),
                null, null);
        visitor.visitCode();
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitMethodInsn(INVOKESPECIAL, "gololang/GoloStruct", "<init>", "()V");
        int arg = 1;
        for (String name : struct.getMembers()) {
            visitor.visitVarInsn(ALOAD, 0);
            visitor.visitVarInsn(ALOAD, arg);
            visitor.visitFieldInsn(PUTFIELD, owner, name, "Ljava/lang/Object;");
            arg = arg + 1;
        }
        initMembersField(struct, owner, visitor);
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitInsn(ICONST_0);
        visitor.visitFieldInsn(PUTFIELD, owner, $_frozen, "Z");
        visitor.visitInsn(RETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    private void makeImmutableFactory(ClassWriter classWriter, Struct struct) {
        String constructorDesc = allArgsConstructorSignature(struct);
        String desc = constructorDesc.substring(0, constructorDesc.length() - 1);
        String classType = struct.getPackageAndClass().toJVMType();
        desc = desc + "L" + classType + ";";
        MethodVisitor visitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, IMMUTABLE_FACTORY_METHOD, desc,
                null, null);
        visitor.visitCode();
        visitor.visitTypeInsn(NEW, classType);
        visitor.visitInsn(DUP);
        int arg = 0;
        for (String name : struct.getMembers()) {
            visitor.visitVarInsn(ALOAD, arg);
            arg = arg + 1;
        }
        visitor.visitMethodInsn(INVOKESPECIAL, classType, "<init>", constructorDesc);
        visitor.visitInsn(DUP);
        visitor.visitInsn(ICONST_1);
        visitor.visitFieldInsn(PUTFIELD, classType, $_frozen, "Z");
        visitor.visitInsn(ARETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    private void initMembersField(Struct struct, String owner, MethodVisitor visitor) {
        int arg;
        visitor.visitVarInsn(ALOAD, 0);
        loadInteger(visitor, struct.getPublicMembers().size());
        visitor.visitTypeInsn(ANEWARRAY, "java/lang/String");
        arg = 0;
        for (String name : struct.getPublicMembers()) {
            visitor.visitInsn(DUP);
            loadInteger(visitor, arg);
            visitor.visitLdcInsn(name);
            visitor.visitInsn(AASTORE);
            arg = arg + 1;
        }
        visitor.visitFieldInsn(PUTFIELD, owner, "members", "[Ljava/lang/String;");
    }

    private String allArgsConstructorSignature(Struct struct) {
        StringBuilder signatureBuilder = new StringBuilder("(");
        for (int i = 0; i < struct.getMembers().size(); i++) {
            signatureBuilder.append("Ljava/lang/Object;");
        }
        signatureBuilder.append(")V");
        return signatureBuilder.toString();
    }

    private void makeNoArgsConstructor(ClassWriter classWriter, Struct struct) {
        String owner = struct.getPackageAndClass().toJVMType();
        MethodVisitor visitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
        visitor.visitCode();
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitMethodInsn(INVOKESPECIAL, "gololang/GoloStruct", "<init>", "()V");
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitInsn(ICONST_0);
        visitor.visitFieldInsn(PUTFIELD, struct.getPackageAndClass().toJVMType(), $_frozen, "Z");
        initMembersField(struct, owner, visitor);
        visitor.visitInsn(RETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    private void makeFields(ClassWriter classWriter, Struct struct) {
        classWriter.visitField(ACC_PRIVATE | ACC_FINAL, $_frozen, "Z", null, null).visitEnd();
        for (String name : struct.getMembers()) {
            FieldVisitor fieldVisitor = classWriter.visitField(ACC_PRIVATE, name, "Ljava/lang/Object;", null, null);
            fieldVisitor.visitEnd();
        }
    }

    private void makeAccessors(ClassWriter classWriter, Struct struct) {
        String owner = struct.getPackageAndClass().toJVMType();
        for (String name : struct.getMembers()) {
            makeGetter(classWriter, owner, name);
            makeSetter(classWriter, owner, name);
        }
        makeFrozenGetter(classWriter, owner);
    }

    private void makeFrozenGetter(ClassWriter classWriter, String owner) {
        MethodVisitor visitor = classWriter.visitMethod(ACC_PUBLIC, "isFrozen", "()Z", null, null);
        visitor.visitCode();
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitFieldInsn(GETFIELD, owner, $_frozen, "Z");
        visitor.visitInsn(IRETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    private void makeSetter(ClassWriter classWriter, String owner, String name) {
        int accessFlag = name.startsWith("_") ? ACC_PRIVATE : ACC_PUBLIC;
        MethodVisitor visitor = classWriter.visitMethod(accessFlag, name,
                "(Ljava/lang/Object;)Lgololang/GoloStruct;", null, null);
        visitor.visitCode();
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitFieldInsn(GETFIELD, owner, $_frozen, "Z");
        Label setLabel = new Label();
        visitor.visitJumpInsn(IFEQ, setLabel);
        visitor.visitTypeInsn(NEW, "java/lang/IllegalStateException");
        visitor.visitInsn(DUP);
        visitor.visitLdcInsn("The struct instance is frozen");
        visitor.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalStateException", "<init>",
                "(Ljava/lang/String;)V");
        visitor.visitInsn(ATHROW);
        visitor.visitLabel(setLabel);
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitVarInsn(ALOAD, 1);
        visitor.visitFieldInsn(PUTFIELD, owner, name, "Ljava/lang/Object;");
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitInsn(ARETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }

    private void makeGetter(ClassWriter classWriter, String owner, String name) {
        int accessFlag = name.startsWith("_") ? ACC_PRIVATE : ACC_PUBLIC;
        MethodVisitor visitor = classWriter.visitMethod(accessFlag, name, "()Ljava/lang/Object;", null, null);
        visitor.visitCode();
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitFieldInsn(GETFIELD, owner, name, "Ljava/lang/Object;");
        visitor.visitInsn(ARETURN);
        visitor.visitMaxs(0, 0);
        visitor.visitEnd();
    }
}