blob: 4a8ccea3a96bd7509b9343016c0c1813009dd835 [file] [log] [blame]
/*
* Copyright (c) 2012, 2015, 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<T> implements Attribute.Visitor<Void, T> {
@Override
public Void visitBootstrapMethods(BootstrapMethods_attribute attr, T p) {
return null;
}
@Override
public Void visitConcealedPackages(ConcealedPackages_attribute attr, T p) {
return null;
}
@Override
public Void visitDefault(DefaultAttribute attr, T p) {
return null;
}
@Override
public Void visitAnnotationDefault(AnnotationDefault_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 visitHashes(Hashes_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 visitMainClass(MainClass_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 visitRuntimeVisibleAnnotations(RuntimeVisibleAnnotations_attribute attr, T p) {
return null;
}
@Override
public Void visitRuntimeInvisibleAnnotations(RuntimeInvisibleAnnotations_attribute attr, T p) {
return null;
}
@Override
public Void visitRuntimeVisibleParameterAnnotations(RuntimeVisibleParameterAnnotations_attribute attr, T p) {
return null;
}
@Override
public Void visitRuntimeInvisibleParameterAnnotations(RuntimeInvisibleParameterAnnotations_attribute attr, T p) {
return null;
}
@Override
public Void visitRuntimeVisibleTypeAnnotations(RuntimeVisibleTypeAnnotations_attribute attr, T p) {
return null;
}
@Override
public Void visitRuntimeInvisibleTypeAnnotations(RuntimeInvisibleTypeAnnotations_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;
}
@Override
public Void visitTargetPlatform(TargetPlatform_attribute attr, T p) {
return null;
}
@Override
public Void visitVersion(Version_attribute attr, T p) {
return null;
}
}
private static final Attribute.Visitor<Void, ExpectedTypeAnnotation> typeAnnoMatcher
= new AbstractAttributeVisitor<ExpectedTypeAnnotation>() {
@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<Void, ExpectedAnnotation> annoMatcher(ConstantPool cpool) {
return new AbstractAttributeVisitor<ExpectedAnnotation>() {
@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<Void, ExpectedParameterAnnotation> paramMatcher(ConstantPool cpool) {
return new AbstractAttributeVisitor<ExpectedParameterAnnotation>() {
@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;
}
};
}
}