/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package testprogress2;
import com.sun.javadoc.AnnotationDesc;
import com.sun.javadoc.AnnotationValue;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.ExecutableMemberDoc;
import com.sun.javadoc.FieldDoc;
import com.sun.javadoc.Parameter;
import com.sun.javadoc.ParameterizedType;
import com.sun.javadoc.Type;
import com.sun.javadoc.TypeVariable;
import com.sun.javadoc.AnnotationDesc.ElementValuePair;
import testprogress2.TestMethodInformation.Level;
/**
* holder for a TestTargetNew annotation
*/
public class TestTargetNew {
private final Originator originator;
private Level level = null;
private String notes = null;
/*
* method or constructor of target class
*/
private ExecutableMemberDoc targetMethod = null;
/*
* only set if the target points -only- to a class, not to a method. e.g for
* special "!..." targets
*/
private ClassDoc targetClass = null;
/*
* read from annotation, e.g. foobar(java.lang.String)
*/
private String readMethodSignature = null;
/*
* e.g. foobar
*/
private String readMethodName = null;
/*
* read from annotation
*/
private ClassDoc readTargetClass = null;
private boolean havingProblems = false;
private TestTargetNew(Originator originator) {
this.originator = originator;
}
/**
* @param originator the origin (class or method)
* @param ttn the annotation (testtargetnew)
* @param classLevelTargetClass the default target class as given in the
* testtargetclass annotation
*/
public TestTargetNew(Originator originator, AnnotationDesc ttn,
ClassDoc classLevelTargetClass) {
this.originator = originator;
parseTargetClassAndMethodSignature(ttn, classLevelTargetClass);
// post: readMethod, readMethodSignature and readTargetClass are now set
// test for artificial method targets
if (readMethodName.startsWith("!")) {
targetMethod = null;
targetClass = readTargetClass;
// level = Level.ADDITIONAL;
// notes already set
notes = "target: " + readMethodName
+ (notes != null ? ", " + "notes: " + notes : "");
} else if (level == Level.TODO) {
notes = "TODO :" + notes;
havingProblems = true;
} else {
// prepare method target:
// if the signature contains a "." then the prefix is used as a
// reference
// to an inner class. This is an alternative to using the clazz
// attribute in cases where the class is an inner protected class,
// because then the inner class is not visible for the compiler at
// the
// place of the annotation.
// e.g. clazz = Certificate.CertificateRep.class does not work,
// so we use clazz = Certificate.class (enclosing class), and method
// "Certificate.CertificateRep.<methodHere>", e.g.
// "CertificateRep.CertificateRep"
// to denote the constructor of the inner protected class
// CertificateRep
// within Certificate
int dotPos = readMethodName.lastIndexOf('.');
if (dotPos != -1) {
String prefixClassName = readMethodName.substring(0, dotPos);
readMethodName = readMethodName.substring(dotPos + 1);
ClassDoc[] iCs = readTargetClass.innerClasses();
for (ClassDoc iC : iCs) {
if (iC.name().equals(prefixClassName)) {
readTargetClass = iC;
break;
}
}
}
String methodAndSig = readMethodName + readMethodSignature;
ExecutableMemberDoc tmeth = findMethodSignatureIn(methodAndSig,
readTargetClass);
// we need this double test for the note below
if (tmeth == null) {
// a) wrong signature or
// b) a testMethod in a superclass or superinterface, ok also
tmeth = findTargetMethodInSelfAndSupers(methodAndSig,
readTargetClass);
if (tmeth != null) {
if (notes == null)
notes = "";
notes += "- targetmethod (" + tmeth + ") was found in a "
+ "superclass/superinterface of the target<br>";
}
}
if (tmeth != null) {
// found
targetMethod = tmeth;
} else {
havingProblems = true;
notes = "From " + originator.asString()
+ " -> could not resolve " + "targetMethod for class "
+ readTargetClass + ", " + "annotation was:" + ttn
+ ", testMethodSig " + "= " + methodAndSig + "<br>";
System.err.println(">>> warning: " + notes);
}
}
}
private ExecutableMemberDoc findMethodSignatureIn(String sig,
ClassDoc targetClass) {
ExecutableMemberDoc targetMethod = null;
// find the matching method in the target class, check all methods
for (ExecutableMemberDoc mdoc : targetClass.methods()) {
if (equalsSignature(mdoc, sig)) {
return mdoc;
}
}
// check constructors, too
for (ExecutableMemberDoc mdoc : targetClass.constructors()) {
if (equalsSignature(mdoc, sig)) {
return mdoc;
}
}
return null;
}
private ExecutableMemberDoc findTargetMethodInSelfAndSupers(String sig,
ClassDoc targetClass) {
ExecutableMemberDoc mem = findMethodSignatureIn(sig, targetClass);
if (mem != null) {
return mem;
}
// else visit parent class or parent interface(s)
ClassDoc[] ifs = targetClass.interfaces();
for (int i = 0; i < ifs.length; i++) {
ClassDoc iface = ifs[i];
mem = findTargetMethodInSelfAndSupers(sig, iface);
if (mem != null) {
return mem;
}
}
ClassDoc superclass = targetClass.superclass();
if (superclass != null) {
mem = findTargetMethodInSelfAndSupers(sig, superclass);
if (mem != null) {
return mem;
}
}
return null;
}
private void parseTargetClassAndMethodSignature(AnnotationDesc targetAnnot,
ClassDoc targetClass) {
ElementValuePair[] pairs = targetAnnot.elementValues();
String methodName = null;
String args = "";
for (ElementValuePair kval : pairs) {
if (kval.element().name().equals("method")) {
methodName = (String)kval.value().value();
} else if (kval.element().name().equals("clazz")) {
// optional: a different target class than the test-class-level
// default.
Object obj = kval.value().value();
if (obj instanceof ClassDoc) {
targetClass = (ClassDoc)obj;
} else if (obj instanceof ParameterizedType) {
targetClass = ((ParameterizedType)obj).asClassDoc();
} else {
throw new RuntimeException("annotation elem value is of "
+ "type " + obj.getClass().getName() + " target "
+ "annotation = " + targetAnnot);
}
} else if (kval.element().name().equals("args")) {
AnnotationValue[] vals = (AnnotationValue[])kval.value()
.value();
for (int i = 0; i < vals.length; i++) {
AnnotationValue arg = vals[i];
String argV;
// TODO: we should be able to use Type.asClassDoc() here
if (arg.value() instanceof ClassDoc) {
ClassDoc cd = (ClassDoc)arg.value();
argV = cd.qualifiedName();
} else { // primitive type or array type
// is there a nicer way to do this?
argV = arg.toString();
}
// strip .class out of args since signature does not contain
// those
if (argV.endsWith(".class")) {
argV = argV.substring(0, argV.length() - 6);
}
args += (i > 0 ? "," : "") + argV;
}
} else if (kval.element().name().equals("level")) {
AnnotationValue lev = kval.value();
FieldDoc fd = (FieldDoc)lev.value();
String slevel = fd.name();
try {
level = Enum.valueOf(Level.class, slevel);
} catch (IllegalArgumentException iae) {
throw new RuntimeException("COMPILE ERROR!!! enum "
+ slevel + " used in targetMethod for class "
+ "\"+targetClass+\", "
+ "annotation was:\"+targetAnnot+\", "
+ "testMethod = \"+methodDoc.toString()");
}
} else if (kval.element().name().equals("notes")) {
notes = (String)kval.value().value();
if (notes.equals("")) {
notes = null;
}
}
}
// String refSig = methodName + "(" + args + ")";
// both methodName and methodArgs != null because of Annotation
// definition
this.readTargetClass = targetClass;
this.readMethodSignature = "(" + args + ")";
this.readMethodName = methodName;
}
private boolean equalsSignature(ExecutableMemberDoc mdoc,
String refSignature) {
Parameter[] params = mdoc.parameters();
String targs = "";
for (int i = 0; i < params.length; i++) {
Parameter parameter = params[i];
// check for generic type types
Type ptype = parameter.type();
TypeVariable typeVar = ptype.asTypeVariable();
String ptname;
if (typeVar != null) {
ptname = "java.lang.Object"; // the default fallback
Type[] bounds = typeVar.bounds();
if (bounds.length > 0) {
ClassDoc typeClass = bounds[0].asClassDoc();
ptname = typeClass.qualifiedName();
}
String dim = ptype.dimension();
if (dim != null && dim.length() > 0) {
ptname += dim;
}
} else {
// regular var
// ptname = parameter.type().qualifiedTypeName();
ptname = parameter.type().toString();
// System.out.println("quali:"+ptname);
// ptname = parameter.typeName();
// omit type signature
ptname = ptname.replaceAll("<.*>", "");
}
targs += (i > 0 ? "," : "") + ptname;
}
String methodName = mdoc.name();
int lastDot = methodName.lastIndexOf('.');
if (lastDot != -1) {
// we have a inner class constructor
// shrink the name to just name the constructor
methodName = methodName.substring(lastDot + 1);
}
String testSig = methodName + "(" + targs + ")";
// return testSig.equals(refSignature);
if (testSig.equals(refSignature)) {
// System.out.println("match!!!: ref = "+refSignature+",
// test = "+testSig);
return true;
} else {
// System.out.println("no match: ref = "+refSignature+",
// test = "+testSig);
return false;
}
}
public Level getLevel() {
return level;
}
public boolean isHavingProblems() {
return havingProblems;
}
public Originator getOriginator() {
return originator;
}
TestTargetNew cloneMe(String extraNote) {
TestTargetNew anew = new TestTargetNew(this.originator);
anew.level = this.level;
anew.notes = this.notes;
anew.targetMethod = this.targetMethod;
anew.readMethodSignature = this.readMethodSignature;
anew.readTargetClass = this.readTargetClass;
// mark indirectly tested method always as green, independent
// of the original status (to better estimate workload)
// anew.level = Level.COMPLETE;
anew.notes = extraNote + (notes != null ? ", " + notes : "");
return anew;
}
public ExecutableMemberDoc getTargetMethod() {
return targetMethod;
}
/**
* @return the class of the testtargetnew which method starts with "!", null
* otherwise
*/
public ClassDoc getTargetClass() {
return targetClass;
}
public String getNotes() {
return notes;
}
}