All Downloads are FREE. Search and download functionalities are using the official Maven repository.

webit.script.asm.AsmResolverManager Maven / Gradle / Ivy

// Copyright (c) 2013-2014, Webit Team. All Rights Reserved.
package webit.script.asm;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import webit.script.asm.lib.ClassWriter;
import webit.script.asm.lib.Constants;
import webit.script.asm.lib.Label;
import webit.script.asm.lib.MethodWriter;
import webit.script.resolvers.GetResolver;
import webit.script.resolvers.ResolverManager;
import webit.script.resolvers.SetResolver;
import webit.script.util.ClassMap;
import webit.script.util.ClassUtil;
import webit.script.util.StringUtil;
import webit.script.util.bean.FieldInfo;
import webit.script.util.bean.FieldInfoResolver;

/**
 *
 * @author Zqq
 */
public class AsmResolverManager extends ResolverManager {

    private static final String[] ASM_RESOLVER = new String[]{"webit/script/asm/AsmResolver"};
    private static final ClassMap CACHE = new ClassMap();

    @Override
    protected SetResolver resolveSetResolver(Class type) {
        SetResolver resolver = getAsmResolver(type);
        if (resolver != null) {
            return resolver;
        }
        return super.resolveSetResolver(type);
    }

    @Override
    protected GetResolver resolveGetResolver(Class type) {
        GetResolver resolver = getAsmResolver(type);
        if (resolver != null) {
            return resolver;
        }
        return super.resolveGetResolver(type);
    }

    private AsmResolver getAsmResolver(Class type) {
        AsmResolver resolver = CACHE.get(type);
        if (resolver == null) {
            synchronized (CACHE) {
                resolver = CACHE.get(type);
                if (resolver == null) {
                    try {
                        resolver = (AsmResolver) createResolverClass(type).newInstance();
                        resolver = CACHE.putIfAbsent(type, resolver);
                    } catch (Exception e) {
                        logger.error("Failed to create resolver for:".concat(type.getName()), e);
                    } catch (Error e) {
                        logger.error("Failed to create resolver for:".concat(type.getName()), e);
                    }
                }
            }
        }
        return resolver;
    }

    static Class createResolverClass(Class beanClass) throws Exception {
        //XXX: rewrite
        if (ClassUtil.isPublic(beanClass)) {
            final String className = "webit.script.asm.Resolver".concat(ASMUtil.getSn());

            final ClassWriter classWriter = new ClassWriter(Constants.V1_5, Constants.ACC_PUBLIC + Constants.ACC_FINAL, ASMUtil.getInternalName(className), "java/lang/Object", ASM_RESOLVER);
            ASMUtil.visitConstructor(classWriter);

            final FieldInfo[] all = FieldInfoResolver.resolve(beanClass);
            Arrays.sort(all);
            final int size = all.length;
            int[] hashs;
            int[] indexer;
            if (size > 0) {
                hashs = new int[size];
                indexer = new int[size];
                int hashsCount = 0;
                int hash;
                hashs[hashsCount++] = hash = all[0].hashCode;
                int i = 1;
                while (i < size) {
                    FieldInfo fieldInfo = all[i];
                    if (hash != fieldInfo.hashCode) {
                        indexer[hashsCount - 1] = i;
                        hashs[hashsCount++] = hash = fieldInfo.hashCode;
                    }
                    i++;
                }
                indexer[hashsCount - 1] = size;
                hashs = Arrays.copyOf(hashs, hashsCount);
                indexer = Arrays.copyOf(indexer, hashsCount);
            } else {
                hashs = null;
                indexer = null;
            }

            visitXetMethod(true, classWriter, beanClass, all, hashs, indexer);
            visitXetMethod(false, classWriter, beanClass, all, hashs, indexer);

            //getMatchClass
            final MethodWriter m = classWriter.visitMethod(Constants.ACC_PUBLIC, "getMatchClass", "()Ljava/lang/Class;", null);
            m.visitInsn(Constants.ACONST_NULL);
            m.visitInsn(Constants.ARETURN);
            m.visitMaxs();

            return ASMUtil.loadClass(className, classWriter);
        } else {
            throw new Exception(StringUtil.format("Class [{}] is not public", beanClass));
        }

    }

    private static void visitXetMethod(final boolean isGetter, final ClassWriter classWriter, final Class beanClass, final FieldInfo[] all, final int[] hashs, final int[] indexer) {
        final String beanName = ASMUtil.getBoxedInternalName(beanClass);
        final MethodWriter m;
        if (isGetter) {
            m = classWriter.visitMethod(Constants.ACC_PUBLIC, "get", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", null);
        } else {
            m = classWriter.visitMethod(Constants.ACC_PUBLIC, "set", "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V", null);
        }
        final int fieldInfosLength = all.length;
        if (fieldInfosLength != 0) {
            final Label finalEndLabel = new Label();
            if (fieldInfosLength < 4) {
                visitXetFields(isGetter, m, all, 0, fieldInfosLength, beanName, finalEndLabel);
            } else {
                m.visitVarInsn(Constants.ALOAD, 2);
                m.invokeVirtual("java/lang/Object", "hashCode", "()I");

                final int size = hashs.length;
                Label[] labels = new Label[size];
                for (int i = 0; i < size; i++) {
                    labels[i] = new Label();
                }

                m.visitLookupSwitchInsn(finalEndLabel, hashs, labels);
                int start = 0;
                for (int i = 0; i < size; i++) {
                    int end = indexer[i];
                    m.visitLabel(labels[i]);
                    visitXetFields(isGetter, m, all, start, end, beanName, finalEndLabel);
                    start = end;
                }
            }
            m.visitLabel(finalEndLabel);
        }
        //Exception
        m.visitTypeInsn(Constants.NEW, "webit/script/exceptions/ScriptRuntimeException");
        m.visitInsn(Constants.DUP);
        m.visitLdcInsn(StringUtil.concat("Invalid property ", beanClass.getName(), "#"));
        m.visitVarInsn(Constants.ALOAD, 2);
        m.invokeStatic("java/lang/String", "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;");
        m.invokeVirtual("java/lang/String", "concat", "(Ljava/lang/String;)Ljava/lang/String;");
        m.visitMethodInsn(Constants.INVOKESPECIAL, "webit/script/exceptions/ScriptRuntimeException", "", "(Ljava/lang/String;)V");
        m.visitInsn(Constants.ATHROW);
        m.visitMaxs();
    }

    private static void visitXetFields(final boolean isGetter, final MethodWriter m, final FieldInfo[] fieldInfos, final int start, final int end, final String beanName, final Label failedMatchLabel) {
        final Label[] gotoTable = new Label[end - start];
        //if ==
        for (int i = start; i < end; i++) {
            Label label = new Label();
            gotoTable[i - start] = label;
            m.visitLdcInsn(fieldInfos[i].name);
            m.visitVarInsn(Constants.ALOAD, 2);
            // if == goto
            m.visitJumpInsn(Constants.IF_ACMPEQ, label);
        }
        //if equals
        for (int i = start; i < end; i++) {
            m.visitLdcInsn(fieldInfos[i].name);
            m.visitVarInsn(Constants.ALOAD, 2);
            m.invokeVirtual("java/lang/String", "equals", "(Ljava/lang/Object;)Z");
            // if true goto
            m.visitJumpInsn(Constants.IFNE, gotoTable[i - start]);
        }
        //failed, to end
        m.visitJumpInsn(Constants.GOTO, failedMatchLabel);
        //actions
        for (int i = start; i < end; i++) {
            m.visitLabel(gotoTable[i - start]);
            FieldInfo info = fieldInfos[i];
            if (isGetter) {
                appendGetFieldCode(m, info, beanName);
            } else {
                appendSetFieldCode(m, info, beanName);
            }
        }
    }

    private static void appendGetFieldCode(final MethodWriter m, final FieldInfo fieldInfo, final String beanName) {
        final Method getter = fieldInfo.getGetter();
        final Field field = fieldInfo.getField();
        if (getter != null || field != null) {
            Class resultType = getter != null ? getter.getReturnType() : field.getType();
            m.visitVarInsn(Constants.ALOAD, 1);
            m.checkCast(beanName);
            if (getter != null) {
                //return book.getName()
                m.invokeVirtual(beanName, getter.getName(), ASMUtil.getDescriptor(getter));
            } else {
                //return book.name
                m.visitFieldInsn(Constants.GETFIELD, beanName, fieldInfo.name, ASMUtil.getDescriptor(resultType));
            }
            ASMUtil.visitBoxIfNeed(m, resultType);
            m.visitInsn(Constants.ARETURN);
        } else {
            //Unreadable Exception
            ASMUtil.visitScriptRuntimeException(m, StringUtil.concat("Unreadable property ", fieldInfo.owner.getName(), "#", fieldInfo.name));
        }
    }

    private static void appendSetFieldCode(final MethodWriter m, final FieldInfo fieldInfo, final String beanName) {
        final Method setter = fieldInfo.getSetter();
        if (setter != null || fieldInfo.isFieldSettable()) {
            Class fieldClass = setter != null ? setter.getParameterTypes()[0] : fieldInfo.getField().getType();
            m.visitVarInsn(Constants.ALOAD, 1);
            m.checkCast(beanName);
            m.visitVarInsn(Constants.ALOAD, 3);
            m.checkCast(ASMUtil.getBoxedInternalName(fieldClass));
            ASMUtil.visitUnboxIfNeed(m, fieldClass);
            if (setter != null) {
                //book.setName((String)name)
                m.invokeVirtual(beanName, setter.getName(), ASMUtil.getDescriptor(setter));
            } else {
                //book.name = (String) name
                m.visitFieldInsn(Constants.PUTFIELD, beanName, fieldInfo.name, ASMUtil.getDescriptor(fieldClass));
            }

            m.visitInsn(Constants.RETURN);
        } else {
            //UnwriteableException
            ASMUtil.visitScriptRuntimeException(m, StringUtil.concat("Unwriteable property ", fieldInfo.owner.getName(), "#", fieldInfo.name));
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy