1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package testprogress2; 18 19 import com.sun.javadoc.AnnotationDesc; 20 import com.sun.javadoc.AnnotationValue; 21 import com.sun.javadoc.ClassDoc; 22 import com.sun.javadoc.ExecutableMemberDoc; 23 import com.sun.javadoc.FieldDoc; 24 import com.sun.javadoc.Parameter; 25 import com.sun.javadoc.ParameterizedType; 26 import com.sun.javadoc.Type; 27 import com.sun.javadoc.TypeVariable; 28 import com.sun.javadoc.AnnotationDesc.ElementValuePair; 29 30 import testprogress2.TestMethodInformation.Level; 31 32 /** 33 * holder for a TestTargetNew annotation 34 */ 35 public class TestTargetNew { 36 private final Originator originator; 37 38 private Level level = null; 39 40 private String notes = null; 41 42 /* 43 * method or constructor of target class 44 */ 45 private ExecutableMemberDoc targetMethod = null; 46 47 /* 48 * only set if the target points -only- to a class, not to a method. e.g for 49 * special "!..." targets 50 */ 51 private ClassDoc targetClass = null; 52 53 /* 54 * read from annotation, e.g. foobar(java.lang.String) 55 */ 56 private String readMethodSignature = null; 57 58 /* 59 * e.g. foobar 60 */ 61 private String readMethodName = null; 62 63 /* 64 * read from annotation 65 */ 66 private ClassDoc readTargetClass = null; 67 68 private boolean havingProblems = false; 69 70 private TestTargetNew(Originator originator) { 71 this.originator = originator; 72 } 73 74 /** 75 * @param originator the origin (class or method) 76 * @param ttn the annotation (testtargetnew) 77 * @param classLevelTargetClass the default target class as given in the 78 * testtargetclass annotation 79 */ 80 public TestTargetNew(Originator originator, AnnotationDesc ttn, 81 ClassDoc classLevelTargetClass) { 82 this.originator = originator; 83 parseTargetClassAndMethodSignature(ttn, classLevelTargetClass); 84 // post: readMethod, readMethodSignature and readTargetClass are now set 85 86 // test for artificial method targets 87 if (readMethodName.startsWith("!")) { 88 targetMethod = null; 89 targetClass = readTargetClass; 90 // level = Level.ADDITIONAL; 91 // notes already set 92 notes = "target: " + readMethodName 93 + (notes != null ? ", " + "notes: " + notes : ""); 94 95 } else if (level == Level.TODO) { 96 notes = "TODO :" + notes; 97 havingProblems = true; 98 } else { 99 // prepare method target: 100 // if the signature contains a "." then the prefix is used as a 101 // reference 102 // to an inner class. This is an alternative to using the clazz 103 // attribute in cases where the class is an inner protected class, 104 // because then the inner class is not visible for the compiler at 105 // the 106 // place of the annotation. 107 // e.g. clazz = Certificate.CertificateRep.class does not work, 108 // so we use clazz = Certificate.class (enclosing class), and method 109 // "Certificate.CertificateRep.<methodHere>", e.g. 110 // "CertificateRep.CertificateRep" 111 // to denote the constructor of the inner protected class 112 // CertificateRep 113 // within Certificate 114 int dotPos = readMethodName.lastIndexOf('.'); 115 if (dotPos != -1) { 116 String prefixClassName = readMethodName.substring(0, dotPos); 117 readMethodName = readMethodName.substring(dotPos + 1); 118 ClassDoc[] iCs = readTargetClass.innerClasses(); 119 for (ClassDoc iC : iCs) { 120 if (iC.name().equals(prefixClassName)) { 121 readTargetClass = iC; 122 break; 123 } 124 } 125 } 126 127 String methodAndSig = readMethodName + readMethodSignature; 128 ExecutableMemberDoc tmeth = findMethodSignatureIn(methodAndSig, 129 readTargetClass); 130 // we need this double test for the note below 131 if (tmeth == null) { 132 // a) wrong signature or 133 // b) a testMethod in a superclass or superinterface, ok also 134 tmeth = findTargetMethodInSelfAndSupers(methodAndSig, 135 readTargetClass); 136 if (tmeth != null) { 137 if (notes == null) 138 notes = ""; 139 notes += "- targetmethod (" + tmeth + ") was found in a " 140 + "superclass/superinterface of the target<br>"; 141 } 142 } 143 if (tmeth != null) { 144 // found 145 targetMethod = tmeth; 146 } else { 147 havingProblems = true; 148 notes = "From " + originator.asString() 149 + " -> could not resolve " + "targetMethod for class " 150 + readTargetClass + ", " + "annotation was:" + ttn 151 + ", testMethodSig " + "= " + methodAndSig + "<br>"; 152 System.err.println(">>> warning: " + notes); 153 } 154 } 155 } 156 157 private ExecutableMemberDoc findMethodSignatureIn(String sig, 158 ClassDoc targetClass) { 159 ExecutableMemberDoc targetMethod = null; 160 // find the matching method in the target class, check all methods 161 for (ExecutableMemberDoc mdoc : targetClass.methods()) { 162 if (equalsSignature(mdoc, sig)) { 163 return mdoc; 164 } 165 } 166 // check constructors, too 167 for (ExecutableMemberDoc mdoc : targetClass.constructors()) { 168 if (equalsSignature(mdoc, sig)) { 169 return mdoc; 170 } 171 } 172 return null; 173 } 174 175 private ExecutableMemberDoc findTargetMethodInSelfAndSupers(String sig, 176 ClassDoc targetClass) { 177 ExecutableMemberDoc mem = findMethodSignatureIn(sig, targetClass); 178 if (mem != null) { 179 return mem; 180 } 181 182 // else visit parent class or parent interface(s) 183 ClassDoc[] ifs = targetClass.interfaces(); 184 for (int i = 0; i < ifs.length; i++) { 185 ClassDoc iface = ifs[i]; 186 mem = findTargetMethodInSelfAndSupers(sig, iface); 187 if (mem != null) { 188 return mem; 189 } 190 } 191 192 ClassDoc superclass = targetClass.superclass(); 193 if (superclass != null) { 194 mem = findTargetMethodInSelfAndSupers(sig, superclass); 195 if (mem != null) { 196 return mem; 197 } 198 } 199 return null; 200 } 201 202 private void parseTargetClassAndMethodSignature(AnnotationDesc targetAnnot, 203 ClassDoc targetClass) { 204 ElementValuePair[] pairs = targetAnnot.elementValues(); 205 String methodName = null; 206 String args = ""; 207 for (ElementValuePair kval : pairs) { 208 if (kval.element().name().equals("method")) { 209 methodName = (String)kval.value().value(); 210 } else if (kval.element().name().equals("clazz")) { 211 // optional: a different target class than the test-class-level 212 // default. 213 Object obj = kval.value().value(); 214 if (obj instanceof ClassDoc) { 215 targetClass = (ClassDoc)obj; 216 } else if (obj instanceof ParameterizedType) { 217 targetClass = ((ParameterizedType)obj).asClassDoc(); 218 } else { 219 throw new RuntimeException("annotation elem value is of " 220 + "type " + obj.getClass().getName() + " target " 221 + "annotation = " + targetAnnot); 222 } 223 } else if (kval.element().name().equals("args")) { 224 AnnotationValue[] vals = (AnnotationValue[])kval.value() 225 .value(); 226 for (int i = 0; i < vals.length; i++) { 227 AnnotationValue arg = vals[i]; 228 String argV; 229 // TODO: we should be able to use Type.asClassDoc() here 230 if (arg.value() instanceof ClassDoc) { 231 ClassDoc cd = (ClassDoc)arg.value(); 232 argV = cd.qualifiedName(); 233 } else { // primitive type or array type 234 // is there a nicer way to do this? 235 argV = arg.toString(); 236 } 237 // strip .class out of args since signature does not contain 238 // those 239 if (argV.endsWith(".class")) { 240 argV = argV.substring(0, argV.length() - 6); 241 } 242 args += (i > 0 ? "," : "") + argV; 243 } 244 } else if (kval.element().name().equals("level")) { 245 AnnotationValue lev = kval.value(); 246 FieldDoc fd = (FieldDoc)lev.value(); 247 String slevel = fd.name(); 248 249 try { 250 level = Enum.valueOf(Level.class, slevel); 251 } catch (IllegalArgumentException iae) { 252 throw new RuntimeException("COMPILE ERROR!!! enum " 253 + slevel + " used in targetMethod for class " 254 + "\"+targetClass+\", " 255 + "annotation was:\"+targetAnnot+\", " 256 + "testMethod = \"+methodDoc.toString()"); 257 } 258 } else if (kval.element().name().equals("notes")) { 259 notes = (String)kval.value().value(); 260 if (notes.equals("")) { 261 notes = null; 262 } 263 } 264 } 265 266 // String refSig = methodName + "(" + args + ")"; 267 // both methodName and methodArgs != null because of Annotation 268 // definition 269 this.readTargetClass = targetClass; 270 this.readMethodSignature = "(" + args + ")"; 271 this.readMethodName = methodName; 272 } 273 274 private boolean equalsSignature(ExecutableMemberDoc mdoc, 275 String refSignature) { 276 Parameter[] params = mdoc.parameters(); 277 String targs = ""; 278 for (int i = 0; i < params.length; i++) { 279 Parameter parameter = params[i]; 280 // check for generic type types 281 Type ptype = parameter.type(); 282 283 TypeVariable typeVar = ptype.asTypeVariable(); 284 String ptname; 285 if (typeVar != null) { 286 ptname = "java.lang.Object"; // the default fallback 287 Type[] bounds = typeVar.bounds(); 288 if (bounds.length > 0) { 289 ClassDoc typeClass = bounds[0].asClassDoc(); 290 ptname = typeClass.qualifiedName(); 291 } 292 String dim = ptype.dimension(); 293 if (dim != null && dim.length() > 0) { 294 ptname += dim; 295 } 296 } else { 297 // regular var 298 // ptname = parameter.type().qualifiedTypeName(); 299 ptname = parameter.type().toString(); 300 301 // System.out.println("quali:"+ptname); 302 // ptname = parameter.typeName(); 303 // omit type signature 304 ptname = ptname.replaceAll("<.*>", ""); 305 } 306 targs += (i > 0 ? "," : "") + ptname; 307 } 308 309 String methodName = mdoc.name(); 310 int lastDot = methodName.lastIndexOf('.'); 311 if (lastDot != -1) { 312 // we have a inner class constructor 313 // shrink the name to just name the constructor 314 methodName = methodName.substring(lastDot + 1); 315 } 316 317 String testSig = methodName + "(" + targs + ")"; 318 319 // return testSig.equals(refSignature); 320 if (testSig.equals(refSignature)) { 321 // System.out.println("match!!!: ref = "+refSignature+", 322 // test = "+testSig); 323 return true; 324 } else { 325 // System.out.println("no match: ref = "+refSignature+", 326 // test = "+testSig); 327 return false; 328 } 329 } 330 331 public Level getLevel() { 332 return level; 333 } 334 335 public boolean isHavingProblems() { 336 return havingProblems; 337 } 338 339 public Originator getOriginator() { 340 return originator; 341 } 342 343 TestTargetNew cloneMe(String extraNote) { 344 TestTargetNew anew = new TestTargetNew(this.originator); 345 anew.level = this.level; 346 anew.notes = this.notes; 347 anew.targetMethod = this.targetMethod; 348 anew.readMethodSignature = this.readMethodSignature; 349 anew.readTargetClass = this.readTargetClass; 350 351 // mark indirectly tested method always as green, independent 352 // of the original status (to better estimate workload) 353 // anew.level = Level.COMPLETE; 354 anew.notes = extraNote + (notes != null ? ", " + notes : ""); 355 return anew; 356 } 357 358 public ExecutableMemberDoc getTargetMethod() { 359 return targetMethod; 360 } 361 362 /** 363 * @return the class of the testtargetnew which method starts with "!", null 364 * otherwise 365 */ 366 public ClassDoc getTargetClass() { 367 return targetClass; 368 } 369 370 public String getNotes() { 371 return notes; 372 } 373 } 374