package act.job.bytecode; /*- * #%L * ACT Framework * %% * Copyright (C) 2014 - 2017 ActFramework * %% * 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. * #L% */ import act.app.AppByteCodeScannerBase; import act.app.event.AppEventId; import act.asm.AnnotationVisitor; import act.asm.MethodVisitor; import act.asm.Opcodes; import act.asm.Type; import act.job.JobAnnotationProcessor; import act.job.meta.JobClassMetaInfo; import act.job.meta.JobClassMetaInfoManager; import act.job.meta.JobMethodMetaInfo; import act.sys.Env; import act.sys.meta.EnvAnnotationVisitor; import act.util.AsmTypes; import act.util.ByteCodeVisitor; import org.osgl.$; import org.osgl.util.C; import org.osgl.util.E; import org.osgl.util.S; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.List; /** * Scan class to collect Job class meta info */ public class JobByteCodeScanner extends AppByteCodeScannerBase { private JobAnnotationProcessor annotationProcessor; private JobClassMetaInfo classInfo; private volatile JobClassMetaInfoManager classInfoBase; @Override protected boolean shouldScan(String className) { classInfo = new JobClassMetaInfo(); return true; } @Override protected void onAppSet() { annotationProcessor = new JobAnnotationProcessor(app()); } @Override public ByteCodeVisitor byteCodeVisitor() { return new _ByteCodeVisitor(); } @Override public void scanFinished(String className) { classInfoBase().registerJobMetaInfo(classInfo); } private JobClassMetaInfoManager classInfoBase() { if (null == classInfoBase) { synchronized (this) { if (null == classInfoBase) { classInfoBase = app().classLoader().jobClassMetaInfoManager(); } } } return classInfoBase; } private class _ByteCodeVisitor extends ByteCodeVisitor { @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { classInfo.className(name); Type superType = Type.getObjectType(superName); classInfo.superType(superType); if (isAbstract(access)) { classInfo.setAbstract(); } super.visit(version, access, name, signature, superName, interfaces); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); if (!isEligibleMethod(access, name, desc)) { return mv; } return new JobMethodVisitor(mv, access, name, desc, signature, exceptions); } private boolean isEligibleMethod(int access, String name, String desc) { // TODO: analyze parameters return isPublic(access); } private class JobMethodVisitor extends MethodVisitor implements Opcodes { private String methodName; private int access; private boolean requireScan; private JobMethodMetaInfo methodInfo; private ActionAnnotationVisitor aav; private EnvAnnotationVisitor eav; private List<String> paramTypes; JobMethodVisitor(MethodVisitor mv, int access, String methodName, String desc, String signature, String[] exceptions) { super(ASM5, mv); this.access = access; this.methodName = methodName; Type[] arguments = Type.getArgumentTypes(desc); paramTypes = C.newList(); if (null != arguments) { for (Type type : arguments) { paramTypes.add(type.getClassName()); } } } @SuppressWarnings("unchecked") @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { AnnotationVisitor av = super.visitAnnotation(desc, visible); Type type = Type.getType(desc); String className = type.getClassName(); try { Class<? extends Annotation> c = (Class<? extends Annotation>)Class.forName(className); if (JobClassMetaInfo.isActionAnnotation(c)) { markRequireScan(); if (null == methodInfo) { JobMethodMetaInfo tmp = new JobMethodMetaInfo(classInfo, paramTypes); methodInfo = tmp; classInfo.addAction(tmp); this.aav = new ActionAnnotationVisitor(av, c, methodInfo); } else { this.aav.add(c); } return this.aav; } else if (Env.isEnvAnnotation(c)) { this.eav = new EnvAnnotationVisitor(av, c); return this.eav; } } catch (Exception e) { throw E.unexpected(e); } //markNotTargetClass(); return av; } @Override public void visitEnd() { if (!requireScan()) { super.visitEnd(); return; } JobMethodMetaInfo info = methodInfo; info.name(methodName); boolean isStatic = AsmTypes.isStatic(access); if (isStatic) { info.invokeStaticMethod(); } else { info.invokeInstanceMethod(); } if (null != aav) { if (null == eav || eav.matched()) { aav.doRegistration(); } } super.visitEnd(); } private void markRequireScan() { this.requireScan = true; } private boolean requireScan() { return requireScan; } private class ActionAnnotationVisitor extends AnnotationVisitor implements Opcodes { List<AnnoInfo> annoInfos = new ArrayList<>(); AnnoInfo currentInfo; JobMethodMetaInfo method; public ActionAnnotationVisitor(AnnotationVisitor av, Class<? extends Annotation> c, JobMethodMetaInfo methodMetaInfo) { super(ASM5, av); this.method = methodMetaInfo; this.add(c); } void add(Class<? extends Annotation> annotationClass) { currentInfo = new AnnoInfo(annotationClass); annoInfos.add(currentInfo); } @Override public void visitEnum(String name, String desc, String value) { if (desc.contains("AppEventId")) { this.currentInfo.value = AppEventId.valueOf(value); } super.visitEnum(name, desc, value); } @Override public void visit(String name, Object value) { if ("value".equals(name)) { this.currentInfo.value = value; } else if ("async".equals(name)) { this.currentInfo.async = value; } else if ("id".equals(name)) { this.method.id(S.string(value)); } super.visit(name, value); } public void doRegistration() { for (AnnoInfo info : annoInfos) { Object value = info.value; Object async = info.async; if (value != null && async != null) { value = $.T2(value, async); } else if (value == null) { value = async; } annotationProcessor.register(method, info.annotationType, value); } } } } } private static class AnnoInfo { Object value; Object async; Class<? extends Annotation> annotationType; AnnoInfo(Class <? extends Annotation> annoType) { this.annotationType = annoType; } } }