/******************************************************************************* * Copyright (c) 2013 CEA LIST and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * E.D.Willink(CEA LIST) - Initial API and implementation *******************************************************************************/ package org.eclipse.ocl.examples.codegen.asm5; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Attribute; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Handle; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.TypePath; import org.objectweb.asm.TypeReference; /** * JavaAnnotationReader supports determination of the declared @NonNull, @Nullable return annotation of a method. * * (The internal processing also determines the parameter type annotations, but as yet there is no API to exploit this.) */ public class ASM5JavaAnnotationReader { private static final Logger logger = Logger.getLogger(ASM5JavaAnnotationReader.class); private final @NonNull Map<@NonNull String, Map<@NonNull Integer, @Nullable Boolean>> desc2typerefValue2state = new HashMap<@NonNull String, Map<@NonNull Integer, @Nullable Boolean>>(); private final @NonNull Set<String> readClasses = new HashSet<String>(); @SuppressWarnings("null") private final @NonNull String nonNullDesc = Type.getDescriptor(NonNull.class); @SuppressWarnings("null") private final @NonNull String nullableDesc = Type.getDescriptor(Nullable.class); /** * Return true for an @NonNull annotation, false for an @Nullable annotation, null otherwise. */ public ASM5JavaAnnotationReader() {} public @Nullable Boolean getIsNonNull(@NonNull Method method) { final String className = method.getDeclaringClass().getName(); final String requiredDesc = getMethodKey(className, method.getName(), Type.getMethodDescriptor(method)); Map<@NonNull Integer, @Nullable Boolean> typeref2state = desc2typerefValue2state.get(requiredDesc); Integer returnTypeReference = TypeReference.newTypeReference(TypeReference.METHOD_RETURN).getValue(); if (typeref2state != null) { return typeref2state.get(returnTypeReference); } if (!readClasses.add(className)) { return null; } // System.out.println("getIsNonNull: " + requiredDesc + " " + Integer.toHexString(returnTypeReference)); InputStream classStream = null; try { final int flags = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE; ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); String classFileName = className.replace('.', '/') + ".class"; classStream = contextClassLoader.getResourceAsStream(classFileName); final ClassReader cr = new ClassReader(classStream) { @Override public void accept(ClassVisitor classVisitor, int flags) { super.accept(classVisitor, flags); } @Override public void accept(ClassVisitor classVisitor, Attribute[] attrs, int flags) { super.accept(classVisitor, attrs, flags); } }; ClassVisitor cv = new ClassVisitor(Opcodes.ASM5) { @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {} @Override public AnnotationVisitor visitAnnotation(String desc,boolean visible) { return null; } @Override public void visitAttribute(Attribute attr) {} @Override public void visitEnd() {} @Override public FieldVisitor visitField(int access, String name,String desc, String signature, Object value) { return null; } @Override public void visitInnerClass(String name, String outerName,String innerName, int access) {} @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { final String methodDesc = getMethodKey(className, name, desc);// + " " + signature; // System.out.println(" ClassVisitor.visitMethod: " + methodDesc); final HashMap<@NonNull Integer, @Nullable Boolean> typerefValue2state = new HashMap<@NonNull Integer, @Nullable Boolean>(); desc2typerefValue2state.put(methodDesc, typerefValue2state); return new MethodVisitor(Opcodes.ASM5) { @Override public AnnotationVisitor visitAnnotation(String annotationDesc, boolean visible) { return null; } @Override public AnnotationVisitor visitAnnotationDefault() { return null; } @Override public void visitAttribute(Attribute attr) {} @Override public void visitCode() {} @Override public void visitEnd() {} @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) {} @Override public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {} @Override public void visitIincInsn(int var, int increment) {} @Override public void visitInsn(int opcode) {} @Override public AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { return null; } @Override public void visitIntInsn(int opcode, int operand) {} @Override public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { } @Override public void visitJumpInsn(int opcode, Label label) {} @Override public void visitLabel(Label label) {} @Override public void visitLdcInsn(Object cst) {} @Override public void visitLineNumber(int line, Label start) {} @Override public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {} @Override public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String desc, boolean visible) { return null; } @Override public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {} @Override public void visitMaxs(int maxStack, int maxLocals) {} @Override public void visitMethodInsn(int opcode, String owner, String name, String desc) {} @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { } @Override public void visitMultiANewArrayInsn(String desc, int dims) {} @Override public void visitParameter(String name, int access) { } @Override public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { return null; } @Override public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {} @Override public AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { return null; } @Override public void visitTryCatchBlock(Label start, Label end,Label handler, String type) {} @Override public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { // System.out.println(" MethodVisitor-TypeAnnotation:" + Integer.toHexString(typeRef) + " " + typePath + " " + desc + " " + visible); // TypeReference typeReference = new TypeReference(typeRef); // System.out.println(" : " + Integer.toHexString(typeReference.getValue()) + ":" + typeReference.getSort() + ":" + typeReference.getTypeParameterIndex()); if (desc.equals(nonNullDesc)) { // System.out.println(" MethodVisitor-TypeAnnotation:" + Integer.toHexString(typeRef) + " " + typePath + " " + desc); typerefValue2state.put(typeRef, true); } else if (desc.equals(nullableDesc)) { // System.out.println(" MethodVisitor-TypeAnnotation:" + Integer.toHexString(typeRef) + " " + typePath + " " + desc); typerefValue2state.put(typeRef, false); } return null; } @Override public void visitTypeInsn(int opcode, String type) {} @Override public void visitVarInsn(int opcode, int var) {} }; } @Override public void visitOuterClass(String owner, String name, String desc) {} @Override public void visitSource(String source, String debug) {} @Override public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { // System.out.println(" ClassVisitor-TypeAnnotation:" + typeRef + " " + typePath + " " + desc + " " + visible); return null; } }; cr.accept(cv, flags); } catch (IOException e) { logger.error("Failed to read '" + className + "'", e); } finally { if (classStream != null) { try { classStream.close(); } catch (IOException e) {} } } typeref2state = desc2typerefValue2state.get(requiredDesc); if (typeref2state == null) { return null; } Boolean state = typeref2state.get(returnTypeReference); // System.out.println(" => " + state); return state; } protected @NonNull String getMethodKey(/*@NonNull*/ String className, /*@NonNull*/ String name, /*@NonNull*/ String desc) { return className + " " + name + " " + desc; } }