com.yahoo.yqlplus.engine.internal.bytecode.UnitGenerator.java Source code

Java tutorial

Introduction

Here is the source code for com.yahoo.yqlplus.engine.internal.bytecode.UnitGenerator.java

Source

/*
 * Copyright (c) 2016 Yahoo Inc.
 * Licensed under the terms of the Apache version 2.0 license.
 * See LICENSE file for terms.
 */

package com.yahoo.yqlplus.engine.internal.bytecode;

import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.yahoo.yqlplus.api.types.YQLCoreType;
import com.yahoo.yqlplus.api.types.YQLTypeException;
import com.yahoo.yqlplus.engine.api.NativeEncoding;
import com.yahoo.yqlplus.engine.api.PropertyNotFoundException;
import com.yahoo.yqlplus.engine.internal.compiler.CodeEmitter;
import com.yahoo.yqlplus.engine.internal.compiler.ConstructorGenerator;
import com.yahoo.yqlplus.engine.internal.compiler.MethodGenerator;
import com.yahoo.yqlplus.engine.internal.plan.types.*;
import com.yahoo.yqlplus.engine.internal.plan.types.base.BaseTypeWidget;
import com.yahoo.yqlplus.engine.internal.plan.types.base.ClosedPropertyAdapter;
import com.yahoo.yqlplus.engine.internal.plan.types.base.Dynamic;
import com.yahoo.yqlplus.engine.internal.plan.types.base.InvokeDynamicExpression;
import com.yahoo.yqlplus.engine.internal.plan.types.base.NativeObjectSerializer;
import com.yahoo.yqlplus.engine.internal.plan.types.base.PropertyAdapter;

import org.objectweb.asm.*;

import java.io.OutputStream;
import java.io.PrintStream;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.objectweb.asm.Opcodes.*;

public class UnitGenerator {

    protected final String name;
    protected final String className;
    protected final String internalName;
    protected final String superInternalName;
    protected final String descriptor;
    protected final List<Type> interfaces;
    protected final Type jvmType;
    protected final ASMClassSource environment;
    protected final Annotatable annotations;
    protected TypeWidget typeWidget;

    public ASMClassSource getEnvironment() {
        return environment;
    }

    public final BytecodeSequence BASE_SUPER = new BytecodeSequence() {
        @Override
        public void generate(CodeEmitter mvE) {
            if (superInternalName != null) {
                mvE.getLocal("this").read().generate(mvE);
                MethodVisitor mv = mvE.getMethodVisitor();
                mv.visitMethodInsn(INVOKESPECIAL, superInternalName, "<init>", "()V");
            }
        }
    };

    private BytecodeSequence superInit = BASE_SUPER;

    protected final List<ConstructorGenerator> constructors = Lists.newArrayList();
    protected final Map<String, FieldDefinition> fields = Maps.newLinkedHashMap();
    protected final Map<String, FieldDefinition> superFields = Maps.newLinkedHashMap();
    protected final LinkedHashMultimap<String, MethodGenerator> methods = LinkedHashMultimap.create();

    protected UnitGenerator(String name, ASMClassSource environment) {
        this(name, environment.getType(Object.class).getInternalName(), environment);
    }

    protected UnitGenerator(String name, Class<?> superClazz, ASMClassSource environment) {
        this(name, environment.getType(superClazz).getInternalName(), environment);
        while (superClazz != Object.class) {
            readSuperFields(superClazz);
            superClazz = superClazz.getSuperclass();
        }
    }

    protected UnitGenerator(String name, String superClassName, ASMClassSource environment) {
        this.name = name;
        this.environment = environment;
        if (name.contains(".")) {
            className = name;
        } else {
            className = "com.yahoo.yqlplus.generated." + environment.getUniqueElement() + "." + name;
        }
        this.internalName = className.replace('.', '/');
        this.superInternalName = superClassName.replace(".", "/");
        this.descriptor = "L" + internalName + ";";
        this.jvmType = Type.getType(descriptor);
        this.interfaces = Lists.newArrayList();
        this.annotations = new Annotatable(environment);
        this.typeWidget = new BaseTypeWidget(Type.getType("L" + internalName + ";")) {
            @Override
            public YQLCoreType getValueCoreType() {
                return YQLCoreType.OBJECT;
            }

            @Override
            public BytecodeExpression invoke(BytecodeExpression target, TypeWidget outputType, String methodName,
                    List<BytecodeExpression> arguments) {
                final Type[] argTypes = new Type[arguments.size()];
                for (int i = 0; i < arguments.size(); ++i) {
                    argTypes[i] = arguments.get(i).getType().getJVMType();
                }
                // semi-gross that we always use invokedynamic here, fix later
                List<BytecodeExpression> fullArgs = Lists.newArrayListWithCapacity(arguments.size() + 1);
                fullArgs.add(target);
                fullArgs.addAll(arguments);
                return new InvokeDynamicExpression(Dynamic.H_BOOTSTRAP, "dyn:callMethod:" + methodName, outputType,
                        fullArgs);
            }

            @Override
            public PropertyAdapter getPropertyAdapter() {
                List<PropertyAdapter.Property> props = Lists.newArrayList();
                for (FieldDefinition field : Iterables.concat(superFields.values(), fields.values())) {
                    props.add(new PropertyAdapter.Property(field.getName(), field.getType()));
                }
                return new ClosedPropertyAdapter(this, props) {
                    private FieldDefinition getField(String propertyName) {
                        if (fields.containsKey(propertyName)) {
                            return fields.get(propertyName);
                        } else if (superFields.containsKey(propertyName)) {
                            return superFields.get(propertyName);
                        }
                        throw new PropertyNotFoundException("Property '%s' not found", propertyName);
                    }

                    @Override
                    protected AssignableValue getPropertyValue(BytecodeExpression target, String propertyName) {
                        return getField(propertyName).get(target);
                    }
                };
            }

            @Override
            public boolean hasProperties() {
                return !fields.isEmpty() || !superFields.isEmpty();
            }

            @Override
            protected SerializationAdapter getJsonSerializationAdapter() {
                return new NativeObjectSerializer(getPropertyAdapter(), NativeEncoding.JSON);
            }

            @Override
            public BytecodeExpression construct(BytecodeExpression... arguments) {
                if (arguments.length > 0) {
                    return invokeNew(getJVMType(), constructors, arguments);
                } else {
                    return super.construct(arguments);
                }
            }
        };
        environment.addUnit(this);
    }

    protected void readSuperFields(Class<?> superClazz) {
        String owner = Type.getInternalName(superClazz);
        for (Field field : superClazz.getFields()) {
            if (Modifier.isPublic(field.getModifiers())) {
                defineField(owner, environment.adaptInternal(field.getType()), field.getName());
            }
        }
    }

    protected TypeWidget getTypeWidget() {
        return typeWidget;
    }

    public ProgramValueTypeAdapter getValueTypeAdapter() {
        return environment.getValueTypeAdapter();
    }

    public void setTypeWidget(TypeWidget typeWidget) {
        this.typeWidget = typeWidget;
    }

    public Annotatable.AnnotationDefinition annotate(Type type) {
        return annotations.annotate(type);
    }

    public Annotatable.AnnotationDefinition annotate(Class<?> clazz) {
        return annotations.annotate(clazz);
    }

    public void addInterface(Class<?> clazz) {
        interfaces.add(environment.getType(clazz));
    }

    public FieldDefinition defineField(String owner, TypeWidget type, String name) {
        FieldDefinition superField = new FieldDefinition(environment, owner, type, name);
        superFields.put(name, superField);
        return superField;
    }

    public FieldDefinition createField(TypeWidget type, String name) {
        FieldDefinition field = new FieldDefinition(this, type, name);
        fields.put(name, field);
        return field;
    }

    public FieldDefinition getField(String name) {
        Preconditions.checkArgument(superFields.containsKey(name) || fields.containsKey(name),
                "Requested field '%s' does not exist", name);
        if (fields.containsKey(name)) {
            return fields.get(name);
        } else {
            return superFields.get(name);
        }
    }

    public FieldDefinition createField(String name, BytecodeExpression defaultValue) {
        FieldDefinition field = new FieldDefinition(this, name, defaultValue);
        fields.put(name, field);
        return field;
    }

    public MethodGenerator createMethod(String name) {
        MethodGenerator method = new MethodGenerator(this, name, false);
        this.methods.put(name, method);
        return method;
    }

    public MethodGenerator createStaticMethod(String name) {
        MethodGenerator method = new MethodGenerator(this, name, true);
        this.methods.put(name, method);
        return method;
    }

    public boolean hasMethod(String name) {
        return methods.containsKey(name);
    }

    //TODO: Currently this method is only called in the case only one MethodGenerator for the specific method name
    // Will add method to return whole set of MethodGenerators if necessary

    public MethodGenerator getMethod(String name) {
        Preconditions.checkArgument(methods.containsKey(name), "Requested method '%s' does not exist", name);
        return methods.get(name).iterator().next();
    }

    protected ConstructorGenerator createConstructor() {
        ConstructorGenerator construct = new ConstructorGenerator(this);
        constructors.add(construct);
        return construct;
    }

    public void setSuperInit(BytecodeSequence superInit) {
        this.superInit = superInit;
    }

    public BytecodeSequence getSuperInit() {
        return new BytecodeSequence() {
            @Override
            public void generate(CodeEmitter code) {
                superInit.generate(code);

                for (Map.Entry<String, FieldDefinition> e : fields.entrySet()) {
                    e.getValue().generateInit(code);
                }
            }
        };
    }

    public String getSuperInternalName() {
        return superInternalName;
    }

    public BytecodeExpression constant(Class<?> declaredType, Object value) {
        Preconditions.checkNotNull(value);
        Preconditions.checkArgument(declaredType.isAssignableFrom(value.getClass()));
        return constant(environment.adaptInternal(declaredType), value);
    }

    public BytecodeExpression constant(Object value) {
        return constant(value.getClass(), value);
    }

    public BytecodeExpression constant(TypeWidget type, Object value) {
        return environment.constant(type, value);

    }

    public String getInternalName() {
        return internalName;
    }

    public void trace(OutputStream out) {
        TraceClassGenerator gen = new TraceClassGenerator();
        generate(gen);
        PrintStream pout = new PrintStream(out);
        for (StringWriter writer : gen.getWriters()) {
            pout.print(writer.toString());
        }
    }

    public void prepare(Class<?> clazz) {
        try {
            clazz.getField("$TYPE$").set(null, getType());
        } catch (IllegalAccessException | NoSuchFieldException e) {
            throw new YQLTypeException("Unable to init $TYPE$ field", e);
        }
    }

    public void generate(ClassSink cws) {
        if (constructors.size() == 0) {
            createConstructor();
        }

        ClassVisitor cw = cws.create(internalName);
        String[] interfaceList = null;
        if (interfaces.size() > 0) {
            interfaceList = new String[interfaces.size()];
            for (int i = 0; i < interfaces.size(); ++i) {
                interfaceList[i] = interfaces.get(i).getInternalName();
            }
        }
        cw.visit(V1_7, ACC_PUBLIC | ACC_FINAL | ACC_SUPER, internalName, null, superInternalName, interfaceList);

        annotations.generateAnnotations(cw);
        {
            FieldVisitor fv = cw.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "$TYPE$",
                    Type.getDescriptor(TypeWidget.class), null, null);
            fv.visitEnd();
        }

        for (Map.Entry<String, FieldDefinition> e : fields.entrySet()) {
            e.getValue().generate(cw);
        }

        for (ConstructorGenerator constructor : constructors) {
            constructor.generate(cw);
        }

        for (Map.Entry<String, MethodGenerator> e : methods.entries()) {
            e.getValue().generate(cw);
        }

        cw.visitEnd();
    }

    public Class<?> getGeneratedClass() throws ClassNotFoundException {
        return environment.getGeneratedClass(this);
    }

    public final TypeWidget getType() {
        return typeWidget;
    }

}