clientapi.load.transform.impl.ValueAccessorTransformer.java Source code

Java tutorial

Introduction

Here is the source code for clientapi.load.transform.impl.ValueAccessorTransformer.java

Source

/*
 * Copyright 2018 ImpactDevelopment
 *
 * 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 clientapi.load.transform.impl;

import clientapi.load.transform.ITransformer;
import clientapi.value.holder.ValueAccessor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;

import java.util.LinkedHashMap;
import java.util.Map;

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

/**
 * Transforms classes containing Labeled fields to create
 * Consumers and Suppliers to directly access the fields
 * without the need for reflections.
 *
 * @see ValueAccessor
 *
 * @author Brady
 * @since 9/13/2017 10:24 PM
 */
public final class ValueAccessorTransformer implements ITransformer {

    /**
     * Instance of the handle for {@code LambdaMetaFactory#metafactory(MethodHandles.Lookup, String, MethodType, MethodType, MethodHandle, MethodType)}
     */
    private static final Handle METAFACTORY = new Handle(H_INVOKESTATIC, "java/lang/invoke/LambdaMetafactory",
            "metafactory",
            "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;");

    /**
     * Map of value ids and their respective field nodes for the current class
     * being "operated" on. Reset after each class is processed.
     */
    private final Map<String, FieldNode> fieldCache = new LinkedHashMap<>();

    /**
     * Current Lambda bootstrap method index. Reset after each class is processed.
     */
    private int current;

    @Override
    public final void transform(ClassNode cn) {
        // Don't process the class if it already implements ValueAccessor
        if (cn.interfaces.contains("clientapi/value/holder/ValueAccessor"))
            return;

        // Clear the cache for the new class
        fieldCache.clear();
        current = 0;

        // Assume that any values annotated with @Label should be given accessors
        for (FieldNode fn : cn.fields) {
            if (fn.visibleAnnotations == null)
                continue;

            for (AnnotationNode an : fn.visibleAnnotations) {
                if (!an.desc.equals("Lclientapi/util/annotation/Label;"))
                    continue;

                fieldCache.put(fn.name, fn);
            }
        }

        // Don't process the class if there weren't any labeled fields
        if (fieldCache.isEmpty())
            return;

        // Add the ValueAccessor interface and implement the required methods
        cn.interfaces.add("clientapi/value/holder/ValueAccessor");
        createFieldGetter(cn);
        createFieldSetter(cn);
    }

    /**
     * Creates the field-getter getter method in the specified {@code ClassNode}
     *
     * @param cn The ClassNode
     */
    private void createFieldGetter(ClassNode cn) {
        MethodNode mn = new MethodNode(ACC_PUBLIC | ACC_FINAL, "getFieldGetter",
                "(Ljava/lang/String;)Ljava/util/function/Supplier;", null, null);

        // Create a check for all labeled fields in the cache
        fieldCache.forEach((id, fn) -> {
            MethodNode handle;
            {
                // Create lambda handle method
                handle = new MethodNode(ACC_PRIVATE | ACC_SYNTHETIC, "lambda$getFieldGetter$" + current++,
                        "()Ljava/lang/Object;", null, null);

                // Get the field value
                handle.visitVarInsn(ALOAD, 0);
                handle.visitFieldInsn(GETFIELD, cn.name, fn.name, fn.desc);

                // If the field is a primitive type, get the object representation
                String object = getObject(fn.desc);
                if (!object.equals(fn.desc))
                    handle.visitMethodInsn(INVOKESTATIC, object, "valueOf", "(" + fn.desc + ")L" + object + ";",
                            false);

                // Return the value
                handle.visitInsn(ARETURN);

                // Add the handle method to the class
                cn.methods.add(handle);
            }

            // Create label for IF statement jump
            Label skip = new Label();

            // Compare the target value with the expected value
            mn.visitVarInsn(ALOAD, 1);
            mn.visitLdcInsn(id);
            mn.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false);

            // Jump if the input doesn't match the expected value
            mn.visitJumpInsn(IFEQ, skip);

            // Return the getter
            mn.visitVarInsn(ALOAD, 0);
            mn.visitInvokeDynamicInsn("get", "(L" + cn.name + ";)Ljava/util/function/Supplier;",
                    // Define the bootstrap method
                    METAFACTORY,
                    // Fill the remaining 3 args for the method
                    Type.getMethodType("()Ljava/lang/Object;"), createMethodHandle(H_INVOKESPECIAL, cn, handle),
                    Type.getMethodType("()Ljava/lang/Object;"));
            mn.visitInsn(ARETURN);

            // Indicate where the IF statement should jump to if it fails
            mn.visitLabel(skip);
        });
        mn.visitInsn(ACONST_NULL);
        mn.visitInsn(ARETURN);

        cn.methods.add(mn);
    }

    /**
     * Creates the field-setter getter method in the specified {@code ClassNode}
     *
     * @see ValueAccessor
     *
     * @param cn The ClassNode
     */
    private void createFieldSetter(ClassNode cn) {
        MethodNode mn = new MethodNode(ACC_PUBLIC | ACC_FINAL, "getFieldSetter",
                "(Ljava/lang/String;)Ljava/util/function/Consumer;", null, null);

        // Create a check for all labeled fields in the cache
        fieldCache.forEach((id, fn) -> {
            MethodNode handle;
            {
                // Create lambda handle method
                handle = new MethodNode(ACC_PRIVATE | ACC_SYNTHETIC, "lambda$getFieldSetter$" + current++,
                        "(Ljava/lang/Object;)V", null, null);

                handle.visitVarInsn(ALOAD, 0);
                handle.visitVarInsn(ALOAD, 1);
                handle.visitTypeInsn(CHECKCAST, getStrippedDesc(getObject(fn.desc)));

                // If the field is a primitive type, get the primitive value
                String object = getObject(fn.desc);
                if (!object.equals(fn.desc))
                    handle.visitMethodInsn(INVOKEVIRTUAL, object, getClassName(fn.desc) + "Value", "()" + fn.desc,
                            false);

                // Set the field value
                handle.visitFieldInsn(PUTFIELD, cn.name, fn.name, fn.desc);
                handle.visitInsn(RETURN);

                // Add the handle method to the class
                cn.methods.add(handle);
            }

            // Create label for IF statement jump
            Label skip = new Label();

            // Compare the target value with the expected value
            mn.visitVarInsn(ALOAD, 1);
            mn.visitLdcInsn(id);
            mn.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false);

            // Jump if the input doesn't match the expected value
            mn.visitJumpInsn(IFEQ, skip);

            // Return the setter
            mn.visitVarInsn(ALOAD, 0);
            mn.visitInvokeDynamicInsn("accept", "(L" + cn.name + ";)Ljava/util/function/Consumer;",
                    // Define the bootstrap method
                    METAFACTORY,
                    // Fill the remaining 3 args for the method
                    Type.getMethodType("(Ljava/lang/Object;)V"), createMethodHandle(H_INVOKESPECIAL, cn, handle),
                    Type.getMethodType("(Ljava/lang/Object;)V"));
            mn.visitInsn(ARETURN);

            // Indicate where the IF statement should jump to if it fails
            mn.visitLabel(skip);
        });
        mn.visitInsn(ACONST_NULL);
        mn.visitInsn(ARETURN);

        cn.methods.add(mn);
    }
}