/* * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package annotations.classfile; import java.io.*; import java.net.URL; import com.sun.tools.classfile.*; import com.sun.tools.classfile.ConstantPool.InvalidIndex; import com.sun.tools.classfile.ConstantPool.UnexpectedEntry; /** * A class providing utilities for writing tests that inspect class * files directly, looking for specific type annotations. * * Note: this framework does not currently handle repeating * annotations. */ public class ClassfileInspector { /** * A group of expected annotations to be found in a given class. * If the class name is null, then the template will be applied to * every class. */ public static class Expected { /** * The name of the class. If {@code null} this template will * apply to every class; otherwise, it will only be applied to * the named class. */ public final String classname; /** * The expected class annotations. These will be checked * against the class' attributes. */ public final ExpectedAnnotation[] classAnnos; /** * The expected method annotations. These will be checked * against all methods in the class. */ public final ExpectedMethodAnnotation[] methodAnnos; /** * The expected method parameter annotations. These will be checked * against all methods in the class. */ public final ExpectedParameterAnnotation[] methodParamAnnos; /** * The expected field type annotations. These will be checked * against all fields in the class. */ public final ExpectedFieldAnnotation[] fieldAnnos; /** * The expected class type annotations. These will be checked * against the class' attributes. */ public final ExpectedTypeAnnotation[] classTypeAnnos; /** * The expected method type annotations. These will be checked * against all methods in the class. */ public final ExpectedMethodTypeAnnotation[] methodTypeAnnos; /** * The expected field type annotations. These will be checked * against all fields in the class. */ public final ExpectedFieldTypeAnnotation[] fieldTypeAnnos; /** * Create an {@code Expected} from all components. * * @param classname The name of the class to match, or {@code * null} for all classes. * @param classAnnos The expected class annotations. * @param methodAnnos The expected method annotations. * @param methodParamAnnos The expected method parameter annotations. * @param fieldAnnos The expected field annotations. * @param classTypeAnnos The expected class type annotations. * @param methodTypeAnnos The expected method type annotations. * @param fieldTypeAnnos The expected field type annotations. */ public Expected(String classname, ExpectedAnnotation[] classAnnos, ExpectedMethodAnnotation[] methodAnnos, ExpectedParameterAnnotation[] methodParamAnnos, ExpectedFieldAnnotation[] fieldAnnos, ExpectedTypeAnnotation[] classTypeAnnos, ExpectedMethodTypeAnnotation[] methodTypeAnnos, ExpectedFieldTypeAnnotation[] fieldTypeAnnos) { this.classname = classname; this.classAnnos = classAnnos; this.methodAnnos = methodAnnos; this.methodParamAnnos = methodParamAnnos; this.fieldAnnos = fieldAnnos; this.classTypeAnnos = classTypeAnnos; this.methodTypeAnnos = methodTypeAnnos; this.fieldTypeAnnos = fieldTypeAnnos; } /** * Create an {@code Expected} from regular annotation components. * * @param classname The name of the class to match, or {@code * null} for all classes. * @param classAnnos The expected class annotations. * @param methodAnnos The expected method annotations. * @param methodParamAnnos The expected method parameter annotations. * @param fieldAnnos The expected field annotations. */ public Expected(String classname, ExpectedAnnotation[] classAnnos, ExpectedMethodAnnotation[] methodAnnos, ExpectedParameterAnnotation[] methodParamAnnos, ExpectedFieldAnnotation[] fieldAnnos) { this(classname, classAnnos, methodAnnos, methodParamAnnos, fieldAnnos, null, null, null); } /** * Create an {@code Expected} from type annotation components. * * @param classname The name of the class to match, or {@code * null} for all classes. * @param classTypeAnnos The expected class type annotations. * @param methodTypeAnnos The expected method type annotations. * @param fieldTypeAnnos The expected field type annotations. */ public Expected(String classname, ExpectedTypeAnnotation[] classTypeAnnos, ExpectedMethodTypeAnnotation[] methodTypeAnnos, ExpectedFieldTypeAnnotation[] fieldTypeAnnos) { this(classname, null, null, null, null, classTypeAnnos, methodTypeAnnos, fieldTypeAnnos); } @Override public String toString() { final StringBuilder sb = new StringBuilder(); final String newline = System.lineSeparator(); sb.append("Expected on class ").append(classname); if (null != classAnnos) { sb.append(newline).append("Class annotations:").append(newline); for(ExpectedAnnotation anno : classAnnos) { sb.append(anno).append(newline); } } if (null != methodAnnos) { sb.append(newline).append("Method annotations:").append(newline); for(ExpectedAnnotation anno : methodAnnos) { sb.append(anno).append(newline); } } if (null != methodParamAnnos) { sb.append(newline).append("Method param annotations:").append(newline); for(ExpectedAnnotation anno : methodParamAnnos) { sb.append(anno).append(newline); } } if (null != fieldAnnos) { sb.append(newline).append("Field annotations:").append(newline); for(ExpectedAnnotation anno : fieldAnnos) { sb.append(anno).append(newline); } } if (null != classTypeAnnos) { sb.append(newline).append("Class type annotations:").append(newline); for(ExpectedAnnotation anno : classTypeAnnos) { sb.append(anno).append(newline); } } if (null != methodTypeAnnos) { sb.append(newline).append("Method type annotations:").append(newline); for(ExpectedAnnotation anno : methodTypeAnnos) { sb.append(anno).append(newline); } } if (null != fieldTypeAnnos) { sb.append(newline).append("Field type annotations:").append(newline); for(ExpectedAnnotation anno : fieldTypeAnnos) { sb.append(anno).append(newline); } } return sb.toString(); } /** * See if this template applies to a class. * * @param classname The classname to check. * @return Whether or not this template should apply. */ public boolean matchClassName(String classname) { return this.classname == null || this.classname.equals(classname); } /** * After applying the template to all classes, check to see if * any of the expected annotations weren't matched. * * @return The number of missed matches. */ public int check() { int count = 0; if (classAnnos != null) { for(ExpectedAnnotation expected : classAnnos) { if (!expected.check()) { count++; } } } if (methodAnnos != null) { for(ExpectedAnnotation expected : methodAnnos) { if (!expected.check()) { count++; } } } if (methodParamAnnos != null) { for(ExpectedAnnotation expected : methodParamAnnos) { if (!expected.check()) { count++; } } } if (fieldAnnos != null) { for(ExpectedAnnotation expected : fieldAnnos) { if (!expected.check()) { count++; } } } if (classTypeAnnos != null) { for(ExpectedAnnotation expected : classTypeAnnos) { if (!expected.check()) { count++; } } } if (methodTypeAnnos != null) { for(ExpectedAnnotation expected : methodTypeAnnos) { if (!expected.check()) { count++; } } } if (fieldTypeAnnos != null) { for(ExpectedAnnotation expected : fieldTypeAnnos) { if (!expected.check()) { count++; } } } return count; } } /** * An expected annotation. This is both a superclass for * method, field, and type annotations, as well as a class for * annotations on a class. */ public static class ExpectedAnnotation { protected int count = 0; protected final String expectedName; protected final int expectedCount; protected final boolean visibility; /** * Create an {@code ExpectedAnnotation} from its * components. It is usually a better idea to use a {@code * Builder} to do this. * * @param expectedName The expected annotation name. * @param visibility Whether this annotation should be runtime-visible. * @param expectedCount The number of annotations that should * be seen. If 0, this asserts that the * described annotation is not present. */ public ExpectedAnnotation(String expectedName, boolean visibility, int expectedCount) { this.expectedName = expectedName; this.visibility = visibility; this.expectedCount = expectedCount; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("Expected "); sb.append(expectedCount); sb.append(" annotation "); sb.append(expectedName); sb.append(visibility ? ", runtime visibile " : ", runtime invisibile "); return sb.toString(); } /** * See if this template matches the given visibility. * * @param Whether or not the annotation is visible at runtime. * @return Whether or not this template matches the visibility. */ public boolean matchVisibility(boolean visibility) { return this.visibility == visibility; } /** * Attempty to match this template against an annotation. If * it does match, then the match count for the template will * be incremented. Otherwise, nothing will be done. * * @param anno The annotation to attempt to match. */ public void matchAnnotation(ConstantPool cpool, Annotation anno) { if (checkMatch(cpool, anno)) { count++; } } /** * Indicate whether an annotation matches this expected * annotation. * * @param ConstantPool The constant pool to use. * @param anno The annotation to check. * @return Whether the annotation matches. */ protected boolean checkMatch(ConstantPool cpool, Annotation anno) { try { return cpool.getUTF8Info(anno.type_index).value.equals("L" + expectedName + ";"); } catch (InvalidIndex | UnexpectedEntry e) { return false; } } /** * After all matching, check to see if the expected number of * matches equals the actual number. If not, then print a * failure message and return {@code false}. * * @return Whether or not the expected number of matched * equals the actual number. */ public boolean check() { if (count != expectedCount) { System.err.println(this + ", but saw " + count); return false; } else { return true; } } } /** * An annotation found on a method. */ public static class ExpectedMethodAnnotation extends ExpectedAnnotation { protected final String methodname; /** * Create an {@code ExpectedMethodAnnotation} from its * components. It is usually a better idea to use a {@code * Builder} to do this. * * @param methodname The expected method name. * @param expectedName The expected annotation name. * @param visibility Whether this annotation should be runtime-visible. * @param expectedCount The number of annotations that should be seen. */ public ExpectedMethodAnnotation(String methodname, String expectedName, boolean visibility, int expectedCount) { super(expectedName, visibility, expectedCount); this.methodname = methodname; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("Expected "); sb.append(expectedCount); sb.append(" annotation "); sb.append(expectedName); sb.append(visibility ? ", runtime visibile " : ", runtime invisibile "); sb.append(" on method "); sb.append(methodname); return sb.toString(); } /** * See if this template applies to a method. * * @param methodname The method name to check. * @return Whether or not this template should apply. */ public boolean matchMethodName(String methodname) { return this.methodname.equals(methodname); } } /** * An annotation found on a method parameter. */ public static class ExpectedParameterAnnotation extends ExpectedMethodAnnotation { protected final int index; /** * Create an {@code ExpectedParameterAnnotation} from its * components. It is usually a better idea to use a {@code * Builder} to do this. * * @param methodname The expected method name. * @param index The parameter index. * @param expectedName The expected annotation name. * @param visibility Whether this annotation should be runtime-visible. * @param expectedCount The number of annotations that should be seen. */ public ExpectedParameterAnnotation(String methodname, int index, String expectedName, boolean visibility, int expectedCount) { super(methodname, expectedName, visibility, expectedCount); this.index = index; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("Expected "); sb.append(expectedCount); sb.append(" annotation "); sb.append(expectedName); sb.append(visibility ? ", runtime visibile " : ", runtime invisibile "); sb.append(" on method "); sb.append(methodname); sb.append(" parameter "); sb.append(index); return sb.toString(); } } /** * An annotation found on a field. */ public static class ExpectedFieldAnnotation extends ExpectedAnnotation { private final String fieldname; /** * Create an {@code ExpectedFieldAnnotation} from its * components. It is usually a better idea to use a {@code * Builder} to do this. * * @param fieldname The expected field name. * @param expectedName The expected annotation name. * @param visibility Whether this annotation should be runtime-visible. * @param expectedCount The number of annotations that should be seen. */ public ExpectedFieldAnnotation(String fieldname, String expectedName, boolean visibility, int expectedCount) { super(expectedName, visibility, expectedCount); this.fieldname = fieldname; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("Expected ").append(expectedCount) .append(" annotation ").append(expectedName) .append(visibility ? ", runtime visibile " : ", runtime invisibile ") .append(" on field ").append(fieldname); return sb.toString(); } /** * See if this template applies to a field. * * @param fieldname The field name to check. * @return Whether or not this template should apply. */ public boolean matchFieldName(String fieldname) { return this.fieldname.equals(fieldname); } } /** * An expected type annotation. This is both a superclass for * method and field type annotations, as well as a class for type * annotations on a class. */ public static class ExpectedTypeAnnotation extends ExpectedAnnotation { protected final TypeAnnotation.TargetType targetType; protected final int bound_index; protected final int parameter_index; protected final int type_index; protected final int exception_index; protected final TypeAnnotation.Position.TypePathEntry[] typePath; /** * Create an {@code ExpectedTypeAnnotation} from its * components. It is usually a better idea to use a {@code * Builder} to do this. * * @param expectedName The expected annotation name. * @param visibility Whether this annotation should be runtime-visible. * @param expectedCount The number of annotations that should * be seen. If 0, this asserts that the * described annotation is not present. * @param targetType The expected target type. * @param bound_index The expected bound index, or {@code Integer.MIN_VALUE}. * @param parameter_index The expected parameter index, or * {@code Integer.MIN_VALUE}. * @param type_index The expected type index, or {@code Integer.MIN_VALUE}. * @param exception_index The expected exception index, or * {@code Integer.MIN_VALUE}. * @param typePath The expected type path. */ public ExpectedTypeAnnotation(String expectedName, boolean visibility, int expectedCount, TypeAnnotation.TargetType targetType, int bound_index, int parameter_index, int type_index, int exception_index, TypeAnnotation.Position.TypePathEntry... typePath) { super(expectedName, visibility, expectedCount); this.targetType = targetType; this.bound_index = bound_index; this.parameter_index = parameter_index; this.type_index = type_index; this.exception_index = exception_index; this.typePath = typePath; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("Expected "); sb.append(expectedCount); sb.append(" annotation "); sb.append(expectedName); sb.append(visibility ? ", runtime visibile " : ", runtime invisibile "); sb.append(targetType); sb.append(", bound_index = "); sb.append(bound_index); sb.append(", parameter_index = "); sb.append(parameter_index); sb.append(", type_index = "); sb.append(type_index); sb.append(", exception_index = "); sb.append(exception_index); sb.append(", type_path = ["); for(int i = 0; i < typePath.length; i++) { if (i != 0) { sb.append(", "); } sb.append(typePath[i]); } sb.append("]"); return sb.toString(); } @Override public void matchAnnotation(ConstantPool cpool, Annotation anno) {} public void matchAnnotation(TypeAnnotation anno) { if (checkMatch(anno)) { count++; } } public boolean checkMatch(TypeAnnotation anno) { boolean matches = checkMatch(anno.constant_pool, anno.annotation); matches = matches && anno.position.type == targetType; matches = matches && anno.position.bound_index == bound_index; matches = matches && anno.position.parameter_index == parameter_index; matches = matches && anno.position.type_index == type_index; matches = matches && anno.position.exception_index == exception_index; matches = matches && anno.position.location.size() == typePath.length; if (matches) { int i = 0; for(TypeAnnotation.Position.TypePathEntry entry : anno.position.location) { matches = matches && typePath[i++].equals(entry); } } return matches; } /** * A builder class for creating {@code * ExpectedTypeAnnotation}s in a more convenient fashion. The * constructor for {@code ExpectedTypeAnnotation} takes a * large number of parameters (by necessity). This class * allows users to construct a {@code ExpectedTypeAnnotation}s * using only the ones they need. */ public static class Builder { protected final String expectedName; protected final boolean visibility; protected final int expectedCount; protected final TypeAnnotation.TargetType targetType; protected int bound_index = Integer.MIN_VALUE; protected int parameter_index = Integer.MIN_VALUE; protected int type_index = Integer.MIN_VALUE; protected int exception_index = Integer.MIN_VALUE; protected TypeAnnotation.Position.TypePathEntry[] typePath = new TypeAnnotation.Position.TypePathEntry[0]; /** * Create a {@code Builder} from the mandatory parameters. * * @param expectedName The expected annotation name. * @param targetType The expected target type. * @param visibility Whether this annotation should be runtime-visible. * @param expectedCount The number of annotations that should be seen. */ public Builder(String expectedName, TypeAnnotation.TargetType targetType, boolean visibility, int expectedCount) { this.expectedName = expectedName; this.visibility = visibility; this.expectedCount = expectedCount; this.targetType = targetType; } /** * Create an {@code ExpectedTypeAnnotation} from all * parameters that have been provided. The default values * will be used for those that have not. * * @return The cretaed {@code ExpectedTypeAnnotation}. */ public ExpectedTypeAnnotation build() { return new ExpectedTypeAnnotation(expectedName, visibility, expectedCount, targetType, bound_index, parameter_index, type_index, exception_index, typePath); } /** * Provide a bound index parameter. * * @param bound_index The bound_index value. */ public Builder setBoundIndex(int bound_index) { this.bound_index = bound_index; return this; } /** * Provide a parameter index parameter. * * @param bound_index The parameter_index value. */ public Builder setParameterIndex(int parameter_index) { this.parameter_index = parameter_index; return this; } /** * Provide a type index parameter. * * @param type_index The type_index value. */ public Builder setTypeIndex(int type_index) { this.type_index = type_index; return this; } /** * Provide an exception index parameter. * * @param exception_index The exception_index value. */ public Builder setExceptionIndex(int exception_index) { this.exception_index = exception_index; return this; } /** * Provide a type path parameter. * * @param typePath The type path value. */ public Builder setTypePath(TypeAnnotation.Position.TypePathEntry[] typePath) { this.typePath = typePath; return this; } } } /** * A type annotation found on a method. */ public static class ExpectedMethodTypeAnnotation extends ExpectedTypeAnnotation { private final String methodname; /** * Create an {@code ExpectedMethodTypeAnnotation} from its * components. It is usually a better idea to use a {@code * Builder} to do this. * * @param methodname The expected method name. * @param expectedName The expected annotation name. * @param visibility Whether this annotation should be runtime-visible. * @param expectedCount The number of annotations that should be seen. * @param targetType The expected target type. * @param bound_index The expected bound index, or {@code Integer.MIN_VALUE}. * @param parameter_index The expected parameter index, or * {@code Integer.MIN_VALUE}. * @param type_index The expected type index, or {@code Integer.MIN_VALUE}. * @param exception_index The expected exception index, or * {@code Integer.MIN_VALUE}. * @param typePath The expected type path. */ public ExpectedMethodTypeAnnotation(String methodname, String expectedName, boolean visibility, int expectedCount, TypeAnnotation.TargetType targetType, int bound_index, int parameter_index, int type_index, int exception_index, TypeAnnotation.Position.TypePathEntry... typePath) { super(expectedName, visibility, expectedCount, targetType, bound_index, parameter_index, type_index, exception_index, typePath); this.methodname = methodname; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("Expected "); sb.append(expectedCount); sb.append(" annotation "); sb.append(expectedName); sb.append(visibility ? ", runtime visibile " : ", runtime invisibile "); sb.append(targetType); sb.append(", bound_index = "); sb.append(bound_index); sb.append(", parameter_index = "); sb.append(parameter_index); sb.append(", type_index = "); sb.append(type_index); sb.append(", exception_index = "); sb.append(exception_index); sb.append(", type_path = ["); for(int i = 0; i < typePath.length; i++) { if (i != 0) { sb.append(", "); } sb.append(typePath[i]); } sb.append("]"); sb.append(" on method "); sb.append(methodname); return sb.toString(); } /** * See if this template applies to a method. * * @param methodname The method name to check. * @return Whether or not this template should apply. */ public boolean matchMethodName(String methodname) { return this.methodname.equals(methodname); } /** * A builder class for creating {@code * ExpectedMethodTypeAnnotation}s in a more convenient fashion. The * constructor for {@code ExpectedMethodTypeAnnotation} takes a * large number of parameters (by necessity). This class * allows users to construct a {@code ExpectedMethodTypeAnnotation}s * using only the ones they need. */ public static class Builder extends ExpectedTypeAnnotation.Builder { protected final String methodname; /** * Create a {@code Builder} from the mandatory parameters. * * @param methodname The expected method name. * @param expectedName The expected annotation name. * @param targetType The expected target type. * @param visibility Whether this annotation should be runtime-visible. * @param expectedCount The number of annotations that should be seen. */ public Builder(String methodname, String expectedName, TypeAnnotation.TargetType targetType, boolean visibility, int expectedCount) { super(expectedName, targetType, visibility, expectedCount); this.methodname = methodname; } /** * Create an {@code ExpectedMethodTypeAnnotation} from all * parameters that have been provided. The default values * will be used for those that have not. * * @return The created {@code ExpectedMethodTypeAnnotation}. */ @Override public ExpectedMethodTypeAnnotation build() { return new ExpectedMethodTypeAnnotation(methodname, expectedName, visibility, expectedCount, targetType, bound_index, parameter_index, type_index, exception_index, typePath); } } } /** * A type annotation found on a field. */ public static class ExpectedFieldTypeAnnotation extends ExpectedTypeAnnotation { private final String fieldname; /** * Create an {@code ExpectedFieldTypeAnnotation} from its * components. It is usually a better idea to use a {@code * Builder} to do this. * * @param fieldname The expected field name. * @param expectedName The expected annotation name. * @param visibility Whether this annotation should be runtime-visible. * @param expectedCount The number of annotations that should be seen. * @param targetType The expected target type. * @param bound_index The expected bound index, or {@code Integer.MIN_VALUE}. * @param parameter_index The expected parameter index, or * {@code Integer.MIN_VALUE}. * @param type_index The expected type index, or {@code Integer.MIN_VALUE}. * @param exception_index The expected exception index, or * {@code Integer.MIN_VALUE}. * @param typePath The expected type path. */ public ExpectedFieldTypeAnnotation(String fieldname, String expectedName, boolean visibility, int expectedCount, TypeAnnotation.TargetType targetType, int bound_index, int parameter_index, int type_index, int exception_index, TypeAnnotation.Position.TypePathEntry... typePath) { super(expectedName, visibility, expectedCount, targetType, bound_index, parameter_index, type_index, exception_index, typePath); this.fieldname = fieldname; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("Expected ").append(expectedCount) .append(" annotation ").append(expectedName) .append(visibility ? ", runtime visibile " : ", runtime invisibile ") .append(targetType) .append(", bound_index = ").append(bound_index) .append(", parameter_index = ").append(parameter_index) .append(", type_index = ").append(type_index) .append(", exception_index = ").append(exception_index) .append(", type_path = ["); for(int i = 0; i < typePath.length; i++) { if (i != 0) { sb.append(", "); } sb.append(typePath[i]); } sb.append("]") .append(" on field ").append(fieldname); return sb.toString(); } /** * See if this template applies to a field. * * @param fieldname The field name to check. * @return Whether or not this template should apply. */ public boolean matchFieldName(String fieldname) { return this.fieldname.equals(fieldname); } /** * A builder class for creating {@code * ExpectedFieldTypeAnnotation}s in a more convenient fashion. The * constructor for {@code ExpectedFieldTypeAnnotation} takes a * large number of parameters (by necessity). This class * allows users to construct a {@code ExpectedFieldTypeAnnotation}s * using only the ones they need. */ public static class Builder extends ExpectedTypeAnnotation.Builder { protected final String fieldname; /** * Create a {@code Builder} from the mandatory parameters. * * @param fieldname The expected field name. * @param expectedName The expected annotation name. * @param targetType The expected target type. * @param visibility Whether this annotation should be runtime-visible. * @param expectedCount The number of annotations that should be seen. */ public Builder(String fieldname, String expectedName, TypeAnnotation.TargetType targetType, boolean visibility, int expectedCount) { super(expectedName, targetType, visibility, expectedCount); this.fieldname = fieldname; } /** * Create an {@code ExpectedFieldTypeAnnotation} from all * parameters that have been provided. The default values * will be used for those that have not. * * @return The created {@code ExpectedFieldTypeAnnotation}. */ @Override public ExpectedFieldTypeAnnotation build() { return new ExpectedFieldTypeAnnotation(fieldname, expectedName, visibility, expectedCount, targetType, bound_index, parameter_index, type_index, exception_index, typePath); } } } private void matchClassAnnotation(ClassFile classfile, ExpectedAnnotation expected) throws ConstantPoolException { for(Attribute attr : classfile.attributes) { attr.accept(annoMatcher(classfile.constant_pool), expected); } } private void matchMethodAnnotation(ClassFile classfile, ExpectedMethodAnnotation expected) throws ConstantPoolException { for(Method meth : classfile.methods) { if (expected.matchMethodName(meth.getName(classfile.constant_pool))) { for(Attribute attr : meth.attributes) { attr.accept(annoMatcher(classfile.constant_pool), expected); } } } } private void matchParameterAnnotation(ClassFile classfile, ExpectedParameterAnnotation expected) throws ConstantPoolException { for(Method meth : classfile.methods) { if (expected.matchMethodName(meth.getName(classfile.constant_pool))) { for(Attribute attr : meth.attributes) { attr.accept(paramMatcher(classfile.constant_pool), expected); } } } } private void matchFieldAnnotation(ClassFile classfile, ExpectedFieldAnnotation expected) throws ConstantPoolException { for(Field field : classfile.fields) { if (expected.matchFieldName(field.getName(classfile.constant_pool))) { for(Attribute attr : field.attributes) { attr.accept(annoMatcher(classfile.constant_pool), expected); } } } } private void matchClassTypeAnnotation(ClassFile classfile, ExpectedTypeAnnotation expected) throws ConstantPoolException { for(Attribute attr : classfile.attributes) { attr.accept(typeAnnoMatcher, expected); } } private void matchMethodTypeAnnotation(ClassFile classfile, ExpectedMethodTypeAnnotation expected) throws ConstantPoolException { for(Method meth : classfile.methods) { if (expected.matchMethodName(meth.getName(classfile.constant_pool))) { for(Attribute attr : meth.attributes) { attr.accept(typeAnnoMatcher, expected); } } } } private void matchFieldTypeAnnotation(ClassFile classfile, ExpectedFieldTypeAnnotation expected) throws ConstantPoolException { for(Field field : classfile.fields) { if (expected.matchFieldName(field.getName(classfile.constant_pool))) { for(Attribute attr : field.attributes) { attr.accept(typeAnnoMatcher, expected); } } } } private void matchClassAnnotations(ClassFile classfile, ExpectedAnnotation[] expected) throws ConstantPoolException { for(ExpectedAnnotation one : expected) { matchClassAnnotation(classfile, one); } } private void matchMethodAnnotations(ClassFile classfile, ExpectedMethodAnnotation[] expected) throws ConstantPoolException { for(ExpectedMethodAnnotation one : expected) { matchMethodAnnotation(classfile, one); } } private void matchParameterAnnotations(ClassFile classfile, ExpectedParameterAnnotation[] expected) throws ConstantPoolException { for(ExpectedParameterAnnotation one : expected) { matchParameterAnnotation(classfile, one); } } private void matchFieldAnnotations(ClassFile classfile, ExpectedFieldAnnotation[] expected) throws ConstantPoolException { for(ExpectedFieldAnnotation one : expected) { matchFieldAnnotation(classfile, one); } } private void matchClassTypeAnnotations(ClassFile classfile, ExpectedTypeAnnotation[] expected) throws ConstantPoolException { for(ExpectedTypeAnnotation one : expected) { matchClassTypeAnnotation(classfile, one); } } private void matchMethodTypeAnnotations(ClassFile classfile, ExpectedMethodTypeAnnotation[] expected) throws ConstantPoolException { for(ExpectedMethodTypeAnnotation one : expected) { matchMethodTypeAnnotation(classfile, one); } } private void matchFieldTypeAnnotations(ClassFile classfile, ExpectedFieldTypeAnnotation[] expected) throws ConstantPoolException { for(ExpectedFieldTypeAnnotation one : expected) { matchFieldTypeAnnotation(classfile, one); } } /** * Run a template on a single {@code ClassFile}. * * @param classfile The {@code ClassFile} on which to run tests. * @param expected The expected annotation template. */ public void run(ClassFile classfile, Expected... expected) throws ConstantPoolException { run(new ClassFile[] { classfile }, expected); } /** * Run a template on multiple {@code ClassFile}s. * * @param classfile The {@code ClassFile}s on which to run tests. * @param expected The expected annotation template. */ public void run(ClassFile[] classfiles, Expected... expected) throws ConstantPoolException { for(ClassFile classfile : classfiles) { for(Expected one : expected) { if (one.matchClassName(classfile.getName())) { if (one.classAnnos != null) matchClassAnnotations(classfile, one.classAnnos); if (one.methodAnnos != null) matchMethodAnnotations(classfile, one.methodAnnos); if (one.methodParamAnnos != null) matchParameterAnnotations(classfile, one.methodParamAnnos); if (one.fieldAnnos != null) matchFieldAnnotations(classfile, one.fieldAnnos); if (one.classTypeAnnos != null) matchClassTypeAnnotations(classfile, one.classTypeAnnos); if (one.methodTypeAnnos != null) matchMethodTypeAnnotations(classfile, one.methodTypeAnnos); if (one.fieldTypeAnnos != null) matchFieldTypeAnnotations(classfile, one.fieldTypeAnnos); } } } int count = 0; for (Expected one : expected) { count += one.check(); } if (count != 0) { throw new RuntimeException(count + " errors occurred in test"); } } /** * Get a {@code ClassFile} from its file name. * * @param name The class' file name. * @param host A class in the same package. * @return The {@code ClassFile} */ public static ClassFile getClassFile(String name, Class host) throws IOException, ConstantPoolException { final URL url = host.getResource(name); try (InputStream in = url.openStream()) { return ClassFile.read(in); } } private static class AbstractAttributeVisitor implements Attribute.Visitor { @Override public Void visitDefault(DefaultAttribute attr, T p) { return null; } @Override public Void visitAnnotationDefault(AnnotationDefault_attribute attr, T p) { return null; } @Override public Void visitBootstrapMethods(BootstrapMethods_attribute attr, T p) { return null; } @Override public Void visitCharacterRangeTable(CharacterRangeTable_attribute attr, T p) { return null; } @Override public Void visitCode(Code_attribute attr, T p) { return null; } @Override public Void visitCompilationID(CompilationID_attribute attr, T p) { return null; } @Override public Void visitConstantValue(ConstantValue_attribute attr, T p) { return null; } @Override public Void visitDeprecated(Deprecated_attribute attr, T p) { return null; } @Override public Void visitEnclosingMethod(EnclosingMethod_attribute attr, T p) { return null; } @Override public Void visitExceptions(Exceptions_attribute attr, T p) { return null; } @Override public Void visitInnerClasses(InnerClasses_attribute attr, T p) { return null; } @Override public Void visitLineNumberTable(LineNumberTable_attribute attr, T p) { return null; } @Override public Void visitLocalVariableTable(LocalVariableTable_attribute attr, T p) { return null; } @Override public Void visitLocalVariableTypeTable(LocalVariableTypeTable_attribute attr, T p) { return null; } @Override public Void visitNestHost(NestHost_attribute attr, T p) { return null; } @Override public Void visitMethodParameters(MethodParameters_attribute attr, T p) { return null; } @Override public Void visitModule(Module_attribute attr, T p) { return null; } @Override public Void visitModuleHashes(ModuleHashes_attribute attr, T p) { return null; } @Override public Void visitModuleMainClass(ModuleMainClass_attribute attr, T p) { return null; } @Override public Void visitModulePackages(ModulePackages_attribute attr, T p) { return null; } @Override public Void visitModuleResolution(ModuleResolution_attribute attr, T p) { return null; } @Override public Void visitModuleTarget(ModuleTarget_attribute attr, T p) { return null; } @Override public Void visitNestMembers(NestMembers_attribute attr, T p) { return null; } @Override public Void visitRuntimeInvisibleAnnotations(RuntimeInvisibleAnnotations_attribute attr, T p) { return null; } @Override public Void visitRuntimeInvisibleParameterAnnotations(RuntimeInvisibleParameterAnnotations_attribute attr, T p) { return null; } @Override public Void visitRuntimeInvisibleTypeAnnotations(RuntimeInvisibleTypeAnnotations_attribute attr, T p) { return null; } @Override public Void visitRuntimeVisibleAnnotations(RuntimeVisibleAnnotations_attribute attr, T p) { return null; } @Override public Void visitRuntimeVisibleParameterAnnotations(RuntimeVisibleParameterAnnotations_attribute attr, T p) { return null; } @Override public Void visitRuntimeVisibleTypeAnnotations(RuntimeVisibleTypeAnnotations_attribute attr, T p) { return null; } @Override public Void visitSignature(Signature_attribute attr, T p) { return null; } @Override public Void visitSourceDebugExtension(SourceDebugExtension_attribute attr, T p) { return null; } @Override public Void visitSourceFile(SourceFile_attribute attr, T p) { return null; } @Override public Void visitSourceID(SourceID_attribute attr, T p) { return null; } @Override public Void visitStackMap(StackMap_attribute attr, T p) { return null; } @Override public Void visitStackMapTable(StackMapTable_attribute attr, T p) { return null; } @Override public Void visitSynthetic(Synthetic_attribute attr, T p) { return null; } } private static final Attribute.Visitor typeAnnoMatcher = new AbstractAttributeVisitor() { @Override public Void visitRuntimeVisibleTypeAnnotations(RuntimeVisibleTypeAnnotations_attribute attr, ExpectedTypeAnnotation expected) { if (expected.matchVisibility(true)) { for (TypeAnnotation anno : attr.annotations) { expected.matchAnnotation(anno); } } return null; } @Override public Void visitRuntimeInvisibleTypeAnnotations(RuntimeInvisibleTypeAnnotations_attribute attr, ExpectedTypeAnnotation expected) { if (expected.matchVisibility(false)) { for (TypeAnnotation anno : attr.annotations) { expected.matchAnnotation(anno); } } return null; } }; private static Attribute.Visitor annoMatcher(ConstantPool cpool) { return new AbstractAttributeVisitor() { @Override public Void visitRuntimeVisibleAnnotations(RuntimeVisibleAnnotations_attribute attr, ExpectedAnnotation expected) { if (expected.matchVisibility(true)) { for(Annotation anno : attr.annotations) { expected.matchAnnotation(cpool, anno); } } return null; } @Override public Void visitRuntimeInvisibleAnnotations(RuntimeInvisibleAnnotations_attribute attr, ExpectedAnnotation expected) { if (expected.matchVisibility(false)) { for(Annotation anno : attr.annotations) { expected.matchAnnotation(cpool, anno); } } return null; } }; } private static Attribute.Visitor paramMatcher(ConstantPool cpool) { return new AbstractAttributeVisitor() { @Override public Void visitRuntimeVisibleParameterAnnotations(RuntimeVisibleParameterAnnotations_attribute attr, ExpectedParameterAnnotation expected) { if (expected.matchVisibility(true)) { if (expected.index < attr.parameter_annotations.length) { for(Annotation anno : attr.parameter_annotations[expected.index]) { expected.matchAnnotation(cpool, anno); } } } return null; } @Override public Void visitRuntimeInvisibleParameterAnnotations(RuntimeInvisibleParameterAnnotations_attribute attr, ExpectedParameterAnnotation expected) { if (expected.matchVisibility(false)) { if (expected.index < attr.parameter_annotations.length) { for(Annotation anno : attr.parameter_annotations[expected.index]) { expected.matchAnnotation(cpool, anno); } } } return null; } }; } }