org.glowroot.agent.weaving.PluginDetailBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.glowroot.agent.weaving.PluginDetailBuilder.java

Source

/*
 * Copyright 2018-2019 the original author or authors.
 *
 * 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 org.glowroot.agent.weaving;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.List;
import java.util.Map;

import javax.annotation.Nullable;

import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.io.Resources;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.glowroot.agent.config.PluginDescriptor;
import org.glowroot.agent.plugin.api.weaving.MethodModifier;
import org.glowroot.agent.plugin.api.weaving.Mixin;
import org.glowroot.agent.plugin.api.weaving.Pointcut;
import org.glowroot.agent.weaving.PluginDetail.MixinClass;
import org.glowroot.agent.weaving.PluginDetail.PointcutClass;
import org.glowroot.agent.weaving.PluginDetail.PointcutMethod;
import org.glowroot.common.util.OnlyUsedByTests;

import static com.google.common.base.Preconditions.checkNotNull;
import static org.objectweb.asm.Opcodes.ASM7;

class PluginDetailBuilder {

    private static final Logger logger = LoggerFactory.getLogger(PluginDetailBuilder.class);

    private PluginDescriptor pluginDescriptor;

    PluginDetailBuilder(PluginDescriptor pluginDescriptor) {
        this.pluginDescriptor = pluginDescriptor;
    }

    PluginDetail build() throws IOException {
        ImmutablePluginDetail.Builder builder = ImmutablePluginDetail.builder();
        for (String aspect : pluginDescriptor.aspects()) {
            byte[] bytes = getBytes(ClassNames.toInternalName(aspect), pluginDescriptor.pluginJar());
            AspectClassVisitor cv = new AspectClassVisitor();
            new ClassReader(bytes).accept(cv, ClassReader.SKIP_CODE);
            for (String innerClassName : cv.innerClassNames) {
                bytes = getBytes(innerClassName, pluginDescriptor.pluginJar());
                MemberClassVisitor mcv = new MemberClassVisitor();
                new ClassReader(bytes).accept(mcv, ClassReader.SKIP_CODE);
                if (mcv.pointcutAnnotationVisitor != null) {
                    builder.addPointcutClasses(mcv.buildPointcutClass(bytes, pluginDescriptor.collocate(),
                            pluginDescriptor.pluginJar()));
                } else if (mcv.mixinAnnotationVisitor != null) {
                    builder.addMixinClasses(mcv.buildMixinClass(pluginDescriptor.collocate(), bytes));
                } else if (mcv.shim) {
                    builder.addShimClasses(mcv.buildShimClass(pluginDescriptor.collocate()));
                }
            }
        }
        return builder.build();
    }

    static PointcutClass buildAdviceClass(byte[] bytes) {
        MemberClassVisitor acv = new MemberClassVisitor();
        new ClassReader(bytes).accept(acv, ClassReader.SKIP_CODE);
        return acv.buildPointcutClass(bytes, false, null);
    }

    static byte[] getBytes(String className, @Nullable File pluginJar) throws IOException {
        String resourceName = "/" + className + ".class";
        URL url = PluginDetailBuilder.class.getResource(resourceName);
        if (url != null) {
            return Resources.toByteArray(url);
        }
        if (pluginJar != null) {
            url = new URL("jar:" + pluginJar.toURI() + "!" + resourceName);
            return Resources.toByteArray(url);
        }
        throw new IOException("Class not found: " + className);
    }

    @OnlyUsedByTests
    static PointcutClass buildAdviceClass(Class<?> clazz) throws IOException {
        return buildAdviceClassLookAtSuperClass(ClassNames.toInternalName(clazz.getName()));
    }

    @OnlyUsedByTests
    static MixinClass buildMixinClass(Class<?> clazz) throws IOException {
        URL url = checkNotNull(
                PluginDetailBuilder.class.getResource("/" + ClassNames.toInternalName(clazz.getName()) + ".class"));
        byte[] bytes = Resources.asByteSource(url).read();
        MemberClassVisitor mcv = new MemberClassVisitor();
        new ClassReader(bytes).accept(mcv, ClassReader.SKIP_CODE);
        return mcv.buildMixinClass(false, bytes);
    }

    @OnlyUsedByTests
    private static PointcutClass buildAdviceClassLookAtSuperClass(String internalName) throws IOException {
        URL url = checkNotNull(PluginDetailBuilder.class.getResource("/" + internalName + ".class"));
        byte[] bytes = Resources.asByteSource(url).read();
        MemberClassVisitor mcv = new MemberClassVisitor();
        new ClassReader(bytes).accept(mcv, ClassReader.SKIP_CODE);
        ImmutablePointcutClass pointcutClass = mcv.buildPointcutClass(bytes, false, null);
        String superName = checkNotNull(mcv.superName);
        if (!"java/lang/Object".equals(superName)) {
            pointcutClass = ImmutablePointcutClass.builder().copyFrom(pointcutClass)
                    .addAllMethods(buildAdviceClassLookAtSuperClass(superName).methods()).build();
        }
        return pointcutClass;
    }

    private static class AspectClassVisitor extends ClassVisitor {

        private List<String> innerClassNames = Lists.newArrayList();

        private AspectClassVisitor() {
            super(ASM7);
        }

        @Override
        public void visitInnerClass(String name, String outerName, String innerName, int access) {
            innerClassNames.add(name);
        }
    }

    private static class MemberClassVisitor extends ClassVisitor {

        private @Nullable String name;
        private @Nullable String superName;
        private String /*@Nullable*/ [] interfaces;
        private @Nullable PointcutAnnotationVisitor pointcutAnnotationVisitor;
        private @Nullable MixinAnnotationVisitor mixinAnnotationVisitor;
        private List<PointcutMethodVisitor> pointcutMethodVisitors = Lists.newArrayList();
        private List<MixinMethodVisitor> mixinMethodVisitors = Lists.newArrayList();
        private boolean shim;

        private MemberClassVisitor() {
            super(ASM7);
        }

        @Override
        public void visit(int version, int access, String name, @Nullable String signature,
                @Nullable String superName, String /*@Nullable*/ [] interfaces) {
            this.name = name;
            this.superName = superName;
            this.interfaces = interfaces;
        }

        @Override
        public @Nullable AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
            if (descriptor.equals("Lorg/glowroot/agent/plugin/api/weaving/Pointcut;")) {
                pointcutAnnotationVisitor = new PointcutAnnotationVisitor();
                return pointcutAnnotationVisitor;
            }
            if (descriptor.equals("Lorg/glowroot/agent/plugin/api/weaving/Mixin;")) {
                mixinAnnotationVisitor = new MixinAnnotationVisitor();
                return mixinAnnotationVisitor;
            }
            if (descriptor.equals("Lorg/glowroot/agent/plugin/api/weaving/Shim;")) {
                shim = true;
            }
            return null;
        }

        @Override
        public @Nullable MethodVisitor visitMethod(int access, String name, String descriptor,
                @Nullable String signature, String /*@Nullable*/ [] exceptions) {
            if (pointcutAnnotationVisitor != null) {
                PointcutMethodVisitor methodVisitor = new PointcutMethodVisitor(name, descriptor);
                pointcutMethodVisitors.add(methodVisitor);
                return methodVisitor;
            }
            if (mixinAnnotationVisitor != null) {
                MixinMethodVisitor methodVisitor = new MixinMethodVisitor(name, descriptor);
                mixinMethodVisitors.add(methodVisitor);
                return methodVisitor;
            }
            return null;
        }

        private ImmutablePointcutClass buildPointcutClass(byte[] bytes, boolean collocateInClassLoader,
                @Nullable File pluginJar) {
            ImmutablePointcutClass.Builder builder = ImmutablePointcutClass.builder()
                    .type(Type.getObjectType(checkNotNull(name)));
            for (PointcutMethodVisitor methodVisitor : pointcutMethodVisitors) {
                builder.addMethods(methodVisitor.build());
            }
            return builder.pointcut(checkNotNull(pointcutAnnotationVisitor).build()).bytes(bytes)
                    .collocateInClassLoader(collocateInClassLoader).pluginJar(pluginJar).build();
        }

        private ImmutableMixinClass buildMixinClass(boolean collocateInClassLoader, byte[] bytes) {
            String initMethodName = null;
            for (MixinMethodVisitor methodVisitor : mixinMethodVisitors) {
                if (methodVisitor.init) {
                    if (initMethodName != null) {
                        throw new IllegalStateException("@Mixin has more than one @MixinInit");
                    }
                    initMethodName = methodVisitor.name;
                }
            }
            ImmutableMixinClass.Builder builder = ImmutableMixinClass.builder()
                    .type(Type.getObjectType(checkNotNull(name)));
            if (interfaces != null) {
                for (String iface : interfaces) {
                    if (collocateInClassLoader && !iface.endsWith(PluginClassRenamer.MIXIN_SUFFIX)) {
                        // see PluginClassRenamer.hack() for reason why consistent Mixin suffix is
                        // important
                        logger.warn("mixin interface name should end with \"Mixin\": {}", iface);
                    }
                    builder.addInterfaces(Type.getObjectType(iface));
                }
            }
            return builder.mixin(checkNotNull(mixinAnnotationVisitor).build()).initMethodName(initMethodName)
                    .bytes(bytes).build();
        }

        private ImmutableShimClass buildShimClass(boolean collocateInClassLoader) {
            if (collocateInClassLoader && !checkNotNull(name).endsWith(PluginClassRenamer.SHIM_SUFFIX)) {
                // see PluginClassRenamer.hack() for reason why consistent Shim suffix is important
                logger.warn("shim interface name should end with \"Shim\": {}", name);
            }
            return ImmutableShimClass.builder().type(Type.getObjectType(checkNotNull(name))).build();
        }
    }

    private static class PointcutAnnotationVisitor extends AnnotationVisitor {

        private String className = "";
        private String classAnnotation = "";
        private String subTypeRestriction = "";
        private String superTypeRestriction = "";
        private String methodName = "";
        private String methodAnnotation = "";
        private List<String> methodParameterTypes = Lists.newArrayList();
        private String methodReturnType = "";
        private List<MethodModifier> methodModifiers = Lists.newArrayList();
        private String nestingGroup = "";
        private String timerName = "";
        private int order;
        private String suppressibleUsingKey = "";
        private String suppressionKey = "";

        private PointcutAnnotationVisitor() {
            super(ASM7);
        }

        @Override
        public void visit(@Nullable String name, Object value) {
            if ("className".equals(name)) {
                className = (String) value;
            } else if ("classAnnotation".equals(name)) {
                classAnnotation = (String) value;
            } else if ("subTypeRestriction".equals(name)) {
                subTypeRestriction = (String) value;
            } else if ("superTypeRestriction".equals(name)) {
                superTypeRestriction = (String) value;
            } else if ("methodName".equals(name)) {
                methodName = (String) value;
            } else if ("methodAnnotation".equals(name)) {
                methodAnnotation = (String) value;
            } else if ("methodReturnType".equals(name)) {
                methodReturnType = (String) value;
            } else if ("nestingGroup".equals(name)) {
                nestingGroup = (String) value;
            } else if ("timerName".equals(name)) {
                timerName = (String) value;
            } else if ("order".equals(name)) {
                order = (Integer) value;
            } else if ("suppressibleUsingKey".equals(name)) {
                suppressibleUsingKey = (String) value;
            } else if ("suppressionKey".equals(name)) {
                suppressionKey = (String) value;
            } else {
                throw new IllegalStateException("Unexpected @Pointcut attribute name: " + name);
            }
        }

        @Override
        public AnnotationVisitor visitArray(String name) {
            if ("methodParameterTypes".equals(name)) {
                return new StringArrayAnnotationVisitor(methodParameterTypes);
            } else if ("methodModifiers".equals(name)) {
                return new MethodModifierArrayAnnotationVisitor(methodModifiers);
            } else {
                throw new IllegalStateException("Unexpected @Pointcut attribute name: " + name);
            }
        }

        private Pointcut build() {
            return new Pointcut() {
                @Override
                public Class<? extends Annotation> annotationType() {
                    return Pointcut.class;
                }

                @Override
                public String className() {
                    return className;
                }

                @Override
                public String classAnnotation() {
                    return classAnnotation;
                }

                @Override
                public String subTypeRestriction() {
                    return subTypeRestriction;
                }

                @Override
                public String superTypeRestriction() {
                    return superTypeRestriction;
                }

                @Override
                public String methodName() {
                    return methodName;
                }

                @Override
                public String methodAnnotation() {
                    return methodAnnotation;
                }

                @Override
                public String[] methodParameterTypes() {
                    return Iterables.toArray(methodParameterTypes, String.class);
                }

                @Override
                public String methodReturnType() {
                    return methodReturnType;
                }

                @Override
                public MethodModifier[] methodModifiers() {
                    return Iterables.toArray(methodModifiers, MethodModifier.class);
                }

                @Override
                public String nestingGroup() {
                    return nestingGroup;
                }

                @Override
                public String timerName() {
                    return timerName;
                }

                @Override
                public int order() {
                    return order;
                }

                @Override
                public String suppressibleUsingKey() {
                    return suppressibleUsingKey;
                }

                @Override
                public String suppressionKey() {
                    return suppressionKey;
                }
            };
        }
    }

    private static class PointcutMethodVisitor extends MethodVisitor {

        private final String name;
        private final String descriptor;
        private final List<Type> annotationTypes = Lists.newArrayList();
        private final Map<Integer, List<Type>> parameterAnnotationTypes = Maps.newHashMap();

        private PointcutMethodVisitor(String name, String descriptor) {
            super(ASM7);
            this.name = name;
            this.descriptor = descriptor;
        }

        @Override
        public @Nullable AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
            annotationTypes.add(Type.getType(descriptor));
            return null;
        }

        @Override
        public @Nullable AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor,
                boolean visible) {
            List<Type> list = parameterAnnotationTypes.get(parameter);
            if (list == null) {
                list = Lists.newArrayList();
                parameterAnnotationTypes.put(parameter, list);
            }
            list.add(Type.getType(descriptor));
            return null;
        }

        private PointcutMethod build() {
            return ImmutablePointcutMethod.builder().name(name).descriptor(descriptor)
                    .addAllAnnotationTypes(annotationTypes).putAllParameterAnnotationTypes(parameterAnnotationTypes)
                    .build();
        }
    }

    private static class MixinAnnotationVisitor extends AnnotationVisitor {

        private MixinAnnotationVisitor() {
            super(ASM7);
        }

        private List<String> values = Lists.newArrayList();

        @Override
        public void visit(@Nullable String name, Object value) {
            throw new IllegalStateException("Unexpected @Mixin attribute name: " + name);
        }

        @Override
        public AnnotationVisitor visitArray(String name) {
            if ("value".equals(name)) {
                return new StringArrayAnnotationVisitor(values);
            } else {
                throw new IllegalStateException("Unexpected @Mixin attribute name: " + name);
            }
        }

        private Mixin build() {
            return new Mixin() {
                @Override
                public Class<? extends Annotation> annotationType() {
                    return Mixin.class;
                }

                @Override
                public String[] value() {
                    return Iterables.toArray(values, String.class);
                }
            };
        }
    }

    private static class MixinMethodVisitor extends MethodVisitor {

        private final String name;
        private final String descriptor;

        private boolean init;

        private MixinMethodVisitor(String name, String descriptor) {
            super(ASM7);
            this.name = name;
            this.descriptor = descriptor;
        }

        @Override
        public @Nullable AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
            if (descriptor.equals("Lorg/glowroot/agent/plugin/api/weaving/MixinInit;")) {
                if (Type.getArgumentTypes(this.descriptor).length > 0) {
                    throw new IllegalStateException("@MixinInit method cannot have any parameters");
                }
                if (!Type.getReturnType(this.descriptor).equals(Type.VOID_TYPE)) {
                    throw new IllegalStateException("@MixinInit method must return void");
                }
                init = true;
            }
            return null;
        }
    }

    private static class StringArrayAnnotationVisitor extends AnnotationVisitor {

        private final List<String> list;

        private StringArrayAnnotationVisitor(List<String> list) {
            super(ASM7);
            this.list = list;
        }

        @Override
        public void visit(@Nullable String name, Object value) {
            list.add((String) value);
        }
    }

    private static class MethodModifierArrayAnnotationVisitor extends AnnotationVisitor {

        private final List<MethodModifier> list;

        private MethodModifierArrayAnnotationVisitor(List<MethodModifier> list) {
            super(ASM7);
            this.list = list;
        }

        @Override
        public void visitEnum(String name, String descriptor, String value) {
            list.add(MethodModifier.valueOf(value));
        }
    }
}