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