View Javadoc
1 package attrib4j; 2 3 import java.io.File; 4 import java.io.InputStream; 5 import java.net.MalformedURLException; 6 import java.net.URL; 7 import java.net.URLClassLoader; 8 import java.util.ArrayList; 9 import java.util.Set; 10 import java.util.StringTokenizer; 11 12 import attrib4j.attributes.JavadocAttribute; 13 import attrib4j.utensil.*; 14 15 import com.sun.javadoc.ClassDoc; 16 import com.sun.javadoc.DocErrorReporter; 17 import com.sun.javadoc.FieldDoc; 18 import com.sun.javadoc.MethodDoc; 19 import com.sun.javadoc.RootDoc; 20 import com.sun.javadoc.Tag; 21 22 /*** 23 * A Doclet that emulates (in a post-processing step) the attribute 24 * declarations supported by C# and other .NET compilers. It is run over 25 * an already-compiled class, using the -doclet option of the javadoc tool; 26 * it parses the source, extracts javadoc-style comments of the tag 27 * "@attribute", and uses IBM CFParse or Jakarta's BCEL byte code library 28 * to insert custom attributes into the compiled .class file metadata. These 29 * attributes can later be extracted at runtime. 30 * 31 * @author <a href="mailto:mpollack@speakeasy.net">Mark Pollack</a> 32 * @author Ted Neward 33 * @version $Revision: 1.18 $ $Date: 2003/09/13 06:45:58 $ 34 */ 35 public class AttributeDoclet { 36 37 private static URLClassLoader _attributeClassLoader; 38 private static String _userClasspath; 39 private static boolean _useJavadocAttribute = false; 40 private static URLClassLoader _userClassLoader; 41 private static String _destDir = "."; 42 private static String _logLevel = "INFO"; 43 44 /*** 45 * The name of the property used to identify the AttributeFactory 46 * implementation class name. 47 */ 48 private static final String SYNTAX_PROPERTY = "attrib4j.Syntax"; 49 50 /*** 51 * To support the old syntax. 52 */ 53 private static final String SYNTAX_JAVADOC = "javadoc"; 54 55 /*** 56 * The sytax to use if no system property can be found. 57 */ 58 private static final String SYNTAX_JSR = "jsr"; 59 60 /*** 61 * The command line passed list of packages that contain attributes. 62 * This string will be parsed to prepend the package name to the 63 * classname listed in the javadoc @attribute. 64 */ 65 private static String[] _attributePackages; 66 67 private static String _packageString; 68 69 /*** 70 * A hack boolean to switch the how the sytnax of attributes are defined 71 * old syntax is '@attribute Debug(true)' new syntax is @Debug(true); 72 * _jsrSyntax = true specifies the 'new' shorter syntax. 73 */ 74 private static boolean _jsrSyntax = true; 75 76 /*** 77 * Entry point for the Doclet when invoked from javadoc. 78 * 79 * @param root a value of type 'RootDoc' 80 * @return a value of type 'boolean' 81 */ 82 public static boolean start(RootDoc root) { 83 readOptions(root.options()); 84 85 setLogLevel(); 86 87 //Keep using two classloaders till I investigate more into why 88 //this choice was made... 89 90 _attributeClassLoader = 91 new URLClassLoader( 92 pathArrayToURLArray(_userClasspath), 93 AttributeDoclet.class.getClassLoader()); 94 95 _userClassLoader = 96 new URLClassLoader( 97 pathArrayToURLArray(_userClasspath), 98 AttributeDoclet.class.getClassLoader()); 99 /* 100 URL[] urls = _userClassLoader.getURLs(); 101 for (int i = 0; i<urls.length; i++) { 102 System.out.println("***** USERCLASSLOADER URLS = " + urls[i]); 103 } 104 */ 105 106 String syntaxType = System.getProperty(SYNTAX_PROPERTY); 107 if (syntaxType == null) { 108 //use the jsr syntax 109 _jsrSyntax = true; 110 } else { 111 if (syntaxType.trim().compareTo(SYNTAX_JSR) == 0) { 112 //use the jsr syntax 113 _jsrSyntax = true; 114 115 } else if (syntaxType.trim().equals(SYNTAX_JAVADOC)) { 116 //use the old syntax. 117 _jsrSyntax = false; 118 } 119 } 120 121 try { 122 ClassDoc[] classes = root.classes(); 123 for (int i = 0; i < classes.length; ++i) { 124 String fqn = classes[i].qualifiedName(); 125 126 String classFileName = fqn.replace('.', '/') + ".class"; 127 128 InputStream clsInput = 129 _userClassLoader.getResourceAsStream(classFileName); 130 131 if (clsInput == null) { 132 throw new AttributeException( 133 "could not create input stream from classfileName = " 134 + classFileName); 135 } 136 137 ClassAnnotator annotator = 138 AttributeFactory.getFactory().getClassAnnotator( 139 clsInput, 140 classFileName); 141 Log.debug( 142 "AttributeDoclet", 143 "Searching for attributes on class = " 144 + classes[i].qualifiedName()); 145 annotateClass(classes[i], annotator); 146 //TODO: End up writing to 'generated' also the classes that do not contain 147 //any @attribute tags, which is a bit wasteful.... 148 annotator.write(_destDir); 149 } 150 return true; 151 } catch (AttributeException t) { 152 t.printStackTrace(); 153 return false; 154 } 155 } 156 157 /*** 158 * API called by javadoc to determine argument counts on javadoc 159 * command-line. 160 * 161 * 162 * @param option a value of type 'String' 163 * @return a value of type 'int' 164 */ 165 public static int optionLength(String option) { 166 option = option.toLowerCase(); 167 if (option.equals("-attributepath")) { 168 return 2; 169 } 170 if (option.equals("-userclasspath")) { 171 return 2; 172 } 173 if (option.equals("-d") 174 || option.equals("-dest") 175 || option.equals("-destination")) { 176 return 2; 177 } 178 if (option.equals("-usejavadocattribute")) { 179 return 2; 180 } 181 if (option.equals("-loglevel")) { 182 return 2; 183 } 184 if (option.equals("-attributepackages")) { 185 return 2; 186 } 187 188 return 0; 189 } 190 191 /*** 192 * API called by javadoc to validate arguments on javadoc command-line. 193 * 194 * @param options the incoming command line args from javadoc command line 195 * @param reporter a value of type 'DocErrorReporter' 196 * @return a value of type 'boolean' 197 */ 198 public static boolean validOptions( 199 String options[][], 200 DocErrorReporter reporter) { 201 202 boolean foundAttributePathOption = false; 203 boolean foundUserPathOption = false; 204 boolean foundUseJavadocOption = false; 205 boolean foundPackagesOption = false; 206 207 boolean ok = true; 208 209 //loop over all options. 210 for (int i = 0; i < options.length; i++) { 211 String[] opt = options[i]; 212 213 /* 214 if (opt[0].equals("-attributepath")) { 215 if (foundAttributePathOption) { 216 reporter.printError( 217 "Only one -attributepath option allowed."); 218 ok = false; 219 } else { 220 foundAttributePathOption = true; 221 } 222 } 223 */ 224 225 if (opt[0].equals("-userclasspath")) { 226 if (foundUserPathOption) { 227 reporter.printError( 228 "Only one -userclasspath option allowed."); 229 ok = false; 230 } else { 231 foundUserPathOption = true; 232 } 233 } 234 235 if (opt[0].equals("-usejavadocattribute")) { 236 if (foundUseJavadocOption) { 237 reporter.printError( 238 "Only one -usejavadocattribute option allowed."); 239 ok = false; 240 } else { 241 String optionValue = opt[1].trim().toLowerCase(); 242 if (optionValue.equals("true") 243 || optionValue.equals("false")) { 244 foundUseJavadocOption = true; 245 } else { 246 reporter.printError( 247 "The usejavaattribute option takes only true or " 248 + "false values. value = " 249 + optionValue); 250 ok = false; 251 } 252 } 253 } 254 //TODO: check for loglevel option 255 256 if (opt[0].equals("-attributepackages")) { 257 if (foundPackagesOption) { 258 reporter.printError( 259 "Only one -attributepackages option allowed."); 260 ok = false; 261 } else { 262 foundPackagesOption = true; 263 } 264 } 265 } 266 return ok; 267 } 268 269 /*** 270 * Internal routine to pick apart the options passed on the 271 * javadoc command-line. 272 * 273 * @param options a value of type 'String[][]' 274 */ 275 private static void readOptions(String[][] options) { 276 for (int i = 0; i < options.length; i++) { 277 String opt = options[i][0].toLowerCase(); 278 279 if (opt.equals("-userclasspath")) { 280 _userClasspath = options[i][1]; 281 } 282 if (opt.equals("-d") 283 || opt.equals("-dest") 284 || opt.equals("-destination")) { 285 _destDir = options[i][1]; 286 } 287 if (opt.equals("-usejavadocattribute")) { 288 _useJavadocAttribute = 289 Boolean.valueOf(options[i][1]).booleanValue(); 290 } 291 if (opt.equals("-loglevel")) { 292 _logLevel = options[i][1]; 293 } 294 if (opt.equals("-attributepackages")) { 295 _packageString = options[i][1]; 296 StringTokenizer st = new StringTokenizer(_packageString, ";"); 297 if (st.countTokens() != 0) { 298 _attributePackages = new String[st.countTokens()]; 299 int count = 0; 300 while (st.hasMoreTokens()) { 301 _attributePackages[count++] = st.nextToken().trim(); 302 } 303 } 304 } 305 306 } 307 } 308 309 /*** 310 * Internal routine to pick apart class declaration and annotate .class 311 * file with attribute instances specified there. Recurses in turn 312 * over fields and methods for attribute annotation there, too. 313 * 314 * @param classdoc The Javadoc metadata description of a class. 315 * @param annotator a value of type 'ClassAnnotator' 316 */ 317 private static void annotateClass( 318 ClassDoc classdoc, 319 ClassAnnotator annotator) { 320 String[] tags = findCandidateClassTags(classdoc); 321 for (int t = 0; t < tags.length; t++) { 322 if (Log.getLevel() <= Log.INFO) { 323 Log.info( 324 "AttributeDoclet", 325 "Found class level attribute " 326 + classdoc.qualifiedName() 327 + " = " 328 + tags[t].toString()); 329 } 330 Object ca = 331 annotator.createAttributeInstance( 332 tags[t], 333 _attributeClassLoader, 334 _attributePackages); 335 annotator.insertClassAttribute(ca); 336 } 337 338 //TODO will have to come back and fix this later for use with new 339 //'direct' attribute sytnax. 340 341 //Store normal javadoc tags as metadata if option is selected. 342 if (_useJavadocAttribute) { 343 Tag[] allTags = classdoc.tags(); 344 for (int t = 0; t < allTags.length; t++) { 345 //name() returns with the @ symbol. 346 if (!allTags[t].name().equals("@attribute")) { 347 String name = allTags[t].name(); 348 Log.debug( 349 "AttributeDoclet", 350 "Creating JavadocAttribute for " 351 + name 352 + " " 353 + allTags[t].text()); 354 //strip the '@' symbol off. 355 JavadocAttribute attrib = 356 new JavadocAttribute( 357 name.substring(1, name.length()), 358 allTags[t].text()); 359 annotator.insertClassAttribute(attrib); 360 } 361 } 362 } 363 364 FieldDoc[] fields = classdoc.fields(); 365 for (int i = 0; i < fields.length; i++) { 366 annotateField(fields[i], annotator); 367 } 368 369 MethodDoc[] methods = classdoc.methods(); 370 for (int i = 0; i < methods.length; i++) { 371 annotateMethod(methods[i], annotator); 372 } 373 } 374 375 /*** 376 * @param classdoc The incoming ClassDoc description. 377 * @return String[] The extracted Strings that should be considered for 378 * attribute creation. 379 */ 380 private static String[] findCandidateClassTags(ClassDoc classdoc) { 381 382 ArrayList tagList = new ArrayList(); 383 if (_jsrSyntax) { 384 Tag[] tags = classdoc.tags(); 385 386 Set jdocTags = Utensil.getJavadocClassTags(); 387 for (int i = 0; i < tags.length; i++) { 388 if (!jdocTags.contains(tags[i].name())) { 389 390 //TODO The Javadoc engine will strip out trailing and leading 391 //spaces between the "name" and "text". This algorithm seems to be 392 //based on finding the first occurance of a space. This is a problem if 393 //the attribute signature has spaces in it.. 394 //@PointCut("hello") will be ok, but 395 //@PointCut("* *hello*"), will not, it will turn into @PointCut("**hello*") 396 397 //A simple workaround is to do @PointCut ("* *hello*"); 398 399 //Log.debug("AttributeDoclet","findCandClassTags ****** <<<<RawClassTag>>>>> ***** = " + tags[i].toString()); 400 //Log.debug("AttributeDoclet","findCandClassTags ****** <<<<NameTag>>>>> ***** = " + tags[i].name()); 401 //Log.debug("AttributeDoclet","findCandClassTags ****** <<<<TextTag>>>>> ***** = " + tags[i].text()); 402 403 //The substring is to remove the "@" symbol attached to the name, so we strip it out. 404 405 tagList.add(tags[i].name().substring(1) + tags[i].text()); 406 407 } 408 } 409 410 } else { 411 Tag[] tags = classdoc.tags("attribute"); 412 for (int i = 0; i < tags.length; i++) { 413 tagList.add(tags[i].text()); 414 //tagList.add(new JavadocTag(tags[i].name(), tags[i].text())); 415 } 416 } 417 return (String[]) tagList.toArray(new String[tagList.size()]); 418 } 419 420 /*** 421 * Internal routine to pick apart method declarations and annotate 422 * the .class file with attribute instances. 423 * 424 * @param methoddoc The Javadoc metadata description of a method. 425 * @param annotator a value of type 'ClassAnnotator' 426 */ 427 private static void annotateMethod( 428 MethodDoc methoddoc, 429 ClassAnnotator annotator) { 430 String[] tags = findCandidateMethodTags(methoddoc); 431 //Tag[] tags = methoddoc.tags("attribute"); 432 for (int t = 0; t < tags.length; t++) { 433 if (Log.getLevel() <= Log.INFO) { 434 Log.info( 435 "AttributeDoclet", 436 "Found method level attribute: " 437 + methoddoc.qualifiedName() 438 + methoddoc.signature() 439 + " = " 440 + tags[t]); 441 } 442 Object attr = 443 annotator.createAttributeInstance( 444 tags[t], 445 _attributeClassLoader, 446 _attributePackages); 447 //TODO think about altering method signature to not depend on javadoc 448 /* 449 String[] params = new String[methoddoc.parameters().length]; 450 for (int i=0; i<params.length; i++) 451 //MLP should be qualifiedTypeName in order to accept 452 //params other than from the java.lang package? 453 //what about dimension info? 454 params[i] = methoddoc.parameters()[i].typeName(); 455 456 annotator.insertMethodAttribute(methoddoc.name(), params, attr); 457 */ 458 annotator.insertMethodAttribute(methoddoc, attr); 459 } 460 } 461 462 /*** 463 * @param classdoc The incoming ClassDoc description. 464 * @return String[] The extracted strings that should be considered for 465 * attribute creation. 466 */ 467 private static String[] findCandidateMethodTags(MethodDoc methodDoc) { 468 469 ArrayList tagList = new ArrayList(); 470 if (_jsrSyntax) { 471 Tag[] tags = methodDoc.tags(); 472 Set jdocTags = Utensil.getJavadocMethodTags(); 473 for (int i = 0; i < tags.length; i++) { 474 if (!jdocTags.contains(tags[i].name())) { 475 //The substring is to remove the "@" symbol attached to the name, so we strip it out. 476 tagList.add(tags[i].name().substring(1) + tags[i].text()); 477 } 478 } 479 480 } else { 481 Tag[] tags = methodDoc.tags("attribute"); 482 for (int i = 0; i < tags.length; i++) { 483 tagList.add(tags[i].text()); 484 } 485 } 486 return (String[]) tagList.toArray(new String[tagList.size()]); 487 } 488 489 /*** 490 * Internal routine to pick apart field declarations and annotate 491 * the .class file with attribute instances. 492 * 493 * @param fielddoc The Javadoc metadata description of a field. 494 * @param annotator a value of type 'ClassAnnotator' 495 */ 496 private static void annotateField( 497 FieldDoc fielddoc, 498 ClassAnnotator annotator) { 499 String[] tags = findCandidateFieldTags(fielddoc); 500 for (int t = 0; t < tags.length; t++) { 501 if (Log.getLevel() <= Log.INFO) { 502 Log.info( 503 "AttributeDoclet", 504 "Found field attribute " 505 + fielddoc.qualifiedName() 506 + " = " 507 + tags[t]); 508 } 509 Object attr = 510 annotator.createAttributeInstance( 511 tags[t], 512 _attributeClassLoader, 513 _attributePackages); 514 annotator.insertFieldAttribute(fielddoc.name(), attr); 515 } 516 } 517 518 /*** 519 * @param classdoc The incoming ClassDoc description. 520 * @return String[] The extracted Strings that should be considered for 521 * attribute creation. 522 */ 523 private static String[] findCandidateFieldTags(FieldDoc fieldDoc) { 524 ArrayList tagList = new ArrayList(); 525 if (_jsrSyntax) { 526 Tag[] tags = fieldDoc.tags(); 527 Set jdocTags = Utensil.getJavadocFieldTags(); 528 for (int i = 0; i < tags.length; i++) { 529 if (!jdocTags.contains(tags[i].name())) { 530 //The substring is to remove the "@" symbol attached to the name, so we strip it out. 531 tagList.add(tags[i].name().substring(1) + tags[i].text()); 532 } 533 } 534 } else { 535 Tag[] tags = fieldDoc.tags("attribute"); 536 for (int i = 0; i < tags.length; i++) { 537 tagList.add(tags[i].text()); 538 } 539 } 540 return (String[]) tagList.toArray(new String[tagList.size()]); 541 } 542 543 /*** 544 * Returns an array of URL objects that represent the 545 * pathSeparator-separated list of directories passed in. 546 * 547 * @param path a value of type 'String' 548 * @return a value of type 'URL[]' 549 */ 550 private static URL[] pathArrayToURLArray(String path) { 551 URL[] urls = null; 552 try { 553 String[] paths = PathUtil.translatePath(path); 554 if (paths != null) { 555 urls = new URL[paths.length]; 556 for (int i = 0; i < paths.length; i++) { 557 urls[i] = new File(paths[i]).toURL(); 558 } 559 } 560 } catch (MalformedURLException malURLEx) { 561 //TODO clean up exception handling. 562 malURLEx.printStackTrace(); 563 } 564 return urls; 565 } 566 /*** 567 * Poor mans way of setting the logging level. 568 */ 569 private static void setLogLevel() { 570 String sLevel = _logLevel.toLowerCase().trim(); 571 if (sLevel.equals("info")) { 572 Log.setLevel(Log.INFO); 573 } else if (sLevel.equals("debug")) { 574 Log.setLevel(Log.DEBUG); 575 } else if (sLevel.equals("notice")) { 576 Log.setLevel(Log.NOTICE); 577 } else if (sLevel.equals("warning")) { 578 Log.setLevel(Log.WARNING); 579 } else if (sLevel.equals("error")) { 580 Log.setLevel(Log.ERROR); 581 } else if (sLevel.equals("critical")) { 582 Log.setLevel(Log.CRITICAL); 583 } else if (sLevel.equals("alert")) { 584 Log.setLevel(Log.ALERT); 585 } else if (sLevel.equals("emergency")) { 586 Log.setLevel(Log.EMERGENCY); 587 } else { 588 Log.setLevel(Log.INFO); 589 } 590 591 } 592 593 }

This page was automatically generated by Maven