package org.objectweb.asm;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.TypeAnnotationVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.EmptyVisitor;
/**
* An AnnotationVerifier provides a way to check to see if two
* versions of the same class (from two different .class files),
* have the same annotations on the same elements.
*/
public class AnnotationVerifier {
/**
* The "correct" version of the class to verify against.
*/
private ClassRecorder originalVisitor;
/**
* The uncertain version of the class to verify.
*/
private ClassRecorder newVisitor;
/**
* Constructs a new AnnotationVerifier that does not yet have
* any information about the class.
*/
public AnnotationVerifier() {
originalVisitor = new ClassRecorder();
newVisitor = new ClassRecorder();
}
/**
* Returns the ClassVisitor which should be made to visit the
* version of the class known to be correct.
*
* @return a visitor for the good version of the class
*/
public ClassVisitor originalVisitor() {
return originalVisitor;
}
/**
* Returns the ClassVisitor which should be made to visit the
* version of the class being tested.
*
* @return a visitor the the experimental version of the class
*/
public ClassVisitor newVisitor() {
return newVisitor;
}
/**
* Verifies that the visitors returned by {@link #originalVisitor()} and
* {@link #newVisitor()} have visited the same class. This method can only
* be called if both visitors have already visited a class.
*
* @throws AnnotationMismatchException if the two visitors have not visited
* two versions of the same class that contain idential annotations.
*/
public void verify() {
if(!newVisitor.name.equals(originalVisitor.name)) {
throw new AnnotationMismatchException(
"Cannot verify two different classes " +
newVisitor.name + " cannot be verified against " +
originalVisitor.name);
}
newVisitor.verifyAgainst(originalVisitor);
}
/**
* A ClassRecorder records all the annotations that it visits, and serves
* as a ClassVisitor, FieldVisitor and MethodVisitor.
*/
private class ClassRecorder extends EmptyVisitor {
private String description;
public String name;
private String signature;
private Map fieldRecorders;
// key is unparameterized name
private Map methodRecorders;
// key is complete method signature
// general annotations
private Map anns;
private Map xanns;
//method specific annotations
private Set danns;
private Map panns;
public ClassRecorder() {
this("[class: ","","");
}
public ClassRecorder(String internalDescription, String name, String signature) {
this.description = internalDescription;
this.name = name;
this.signature = signature;
fieldRecorders = new HashMap();
methodRecorders = new HashMap();
anns = new HashMap();
xanns = new HashMap();
danns = new HashSet();
panns = new HashMap();
}
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.name = name;
this.signature = signature;
description = description + name;
}
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
AnnotationRecorder av = new AnnotationRecorder(
description + " annotation: " + desc);
anns.put(desc,av);
return av;
}
public TypeAnnotationVisitor visitTypeAnnotation(String desc, boolean visible) {
AnnotationRecorder av = new AnnotationRecorder(
description + " annotation: " + desc);
xanns.put(desc,av);
return av;
}
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
ClassRecorder fr =
new ClassRecorder(
description + " field: " + name,
name, signature);
fieldRecorders.put(name, fr);
return fr;
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
ClassRecorder mr =
new ClassRecorder(
description + " method: " + name + desc, name+desc, signature);
methodRecorders.put(name+desc, mr);
return mr;
}
// MethodVisitor methods:
public AnnotationVisitor visitAnnotationDefault() {
AnnotationRecorder dr = new AnnotationRecorder(
description + " default annotation");
danns.add(dr);
return dr;
}
public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
ParameterDescription pd =
new ParameterDescription(parameter, desc, visible);
AnnotationRecorder pr =
new AnnotationRecorder(description +
" parameter annotation: " + pd);
panns.put(pd, pr);
return pr;
}
public void verifyAgainst(ClassRecorder correct) {
// first, ensure all annotations are correct
verifyAnns(this.anns, correct.anns);
verifyAnns(this.xanns, correct.xanns);
// then recurse into any annotations on fields/methods
verifyMemberAnns(this.fieldRecorders, correct.fieldRecorders);
verifyMemberAnns(this.methodRecorders, correct.methodRecorders);
}
private void verifyAnns(
Map questionableAnns,
Map correctAnns) {
Set unresolvedQuestionableAnns =
new HashSet(questionableAnns.values());
for(Map.Entry entry :
correctAnns.entrySet()) {
String name = entry.getKey();
AnnotationRecorder correctAnn = entry.getValue();
AnnotationRecorder questionableAnn = questionableAnns.get(name);
if(questionableAnn == null) {
throw new AnnotationMismatchException(description +
" does not contain expected annotation: " + correctAnn);
}
questionableAnn.verifyAgainst(correctAnn);
unresolvedQuestionableAnns.remove(questionableAnn);
}
for(AnnotationRecorder unexpectedAnnOnThis : unresolvedQuestionableAnns) {
throw new AnnotationMismatchException(description +
" contains unexpected annotation : " + unexpectedAnnOnThis);
}
}
private void verifyMemberAnns(
Map questionableMembers,
Map correctMembers) {
Set unresolvedQuestionableMembers =
new HashSet(questionableMembers.values());
for(Map.Entry entry :
correctMembers.entrySet()) {
String name = entry.getKey();
ClassRecorder correctMember = entry.getValue();
ClassRecorder questionableMember = questionableMembers.get(name);
if(questionableMember == null) {
throw new AnnotationMismatchException(description +
" does not contain expected member: " + correctMember);
}
questionableMember.verifyAgainst(correctMember);
unresolvedQuestionableMembers.remove(questionableMember);
}
for(ClassRecorder unexpectedMemberOnThis : unresolvedQuestionableMembers) {
System.out.println("Going to throw exception: ");
System.out.println("questionable: " + questionableMembers);
System.out.println("correct: " + correctMembers);
throw new AnnotationMismatchException(description +
" contains unexpected member: " + unexpectedMemberOnThis);
}
}
public String toString() {
return description;
}
}
/**
* An AnnotationRecorder is an TypeAnnotationVisitor that records all the
* information it visits.
*/
private class AnnotationRecorder implements TypeAnnotationVisitor {
private String description;
private List fieldArgs1;
private List