1 package org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import com.thoughtworks.qdox.JavaProjectBuilder;
23 import com.thoughtworks.qdox.model.JavaAnnotatedElement;
24 import com.thoughtworks.qdox.model.JavaAnnotation;
25 import com.thoughtworks.qdox.model.JavaClass;
26 import com.thoughtworks.qdox.model.JavaField;
27 import com.thoughtworks.qdox.model.JavaMethod;
28 import com.thoughtworks.qdox.model.JavaPackage;
29 import com.thoughtworks.qdox.model.JavaSource;
30 import org.apache.maven.plugin.logging.Log;
31 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.ClassLocation;
32 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.FieldLocation;
33 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.MethodLocation;
34 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.PackageLocation;
35 import org.codehaus.mojo.jaxb2.shared.FileSystemUtilities;
36 import org.codehaus.mojo.jaxb2.shared.Validate;
37
38 import javax.xml.bind.annotation.XmlAttribute;
39 import javax.xml.bind.annotation.XmlElement;
40 import javax.xml.bind.annotation.XmlElementWrapper;
41 import javax.xml.bind.annotation.XmlEnumValue;
42 import javax.xml.bind.annotation.XmlType;
43 import java.io.File;
44 import java.io.IOException;
45 import java.net.URL;
46 import java.util.Collection;
47 import java.util.Collections;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.SortedMap;
51 import java.util.SortedSet;
52 import java.util.TreeMap;
53
54
55
56
57
58
59
60
61
62
63
64 public class JavaDocExtractor {
65
66
67
68
69
70 private static final String DEFAULT_VALUE = "##default";
71
72
73 private JavaProjectBuilder builder;
74 private Log log;
75
76
77
78
79
80
81 public JavaDocExtractor(final Log log) {
82
83
84 Validate.notNull(log, "log");
85
86
87 this.log = log;
88 this.builder = new JavaProjectBuilder();
89 }
90
91
92
93
94
95
96 public void setEncoding(final String encoding) {
97 this.builder.setEncoding(encoding);
98 }
99
100
101
102
103
104
105
106
107 public JavaDocExtractor addSourceFiles(final List<File> sourceCodeFiles) throws IllegalArgumentException {
108
109
110 Validate.notNull(sourceCodeFiles, "addSourceFiles");
111
112
113 for (File current : sourceCodeFiles) {
114 try {
115 builder.addSource(current);
116 } catch (IOException e) {
117 throw new IllegalArgumentException("Could not add file ["
118 + FileSystemUtilities.getCanonicalPath(current) + "]", e);
119 }
120 }
121
122
123 return this;
124 }
125
126
127
128
129
130
131
132
133 public JavaDocExtractor addSourceURLs(final List<URL> sourceCodeURLs) throws IllegalArgumentException {
134
135
136 Validate.notNull(sourceCodeURLs, "sourceCodeURLs");
137
138
139 for (URL current : sourceCodeURLs) {
140 try {
141 builder.addSource(current);
142 } catch (IOException e) {
143 throw new IllegalArgumentException("Could not add URL [" + current.toString() + "]", e);
144 }
145 }
146
147
148 return this;
149 }
150
151
152
153
154
155
156
157 public SearchableDocumentation process() {
158
159
160 final SortedMap<SortableLocation, JavaDocData> dataHolder = new TreeMap<SortableLocation, JavaDocData>();
161 final Collection<JavaSource> sources = builder.getSources();
162
163 if (log.isInfoEnabled()) {
164 log.info("Processing [" + sources.size() + "] java sources.");
165 }
166
167 for (JavaSource current : sources) {
168
169
170 final JavaPackage currentPackage = current.getPackage();
171 final String packageName = currentPackage.getName();
172 addEntry(dataHolder, new PackageLocation(packageName), currentPackage);
173
174 if (log.isDebugEnabled()) {
175 log.debug("Added package-level JavaDoc for [" + packageName + "]");
176 }
177
178 for (JavaClass currentClass : current.getClasses()) {
179
180
181 final String simpleClassName = currentClass.getName();
182 final String classXmlName = getAnnotationAttributeValueFrom(XmlType.class,
183 "name",
184 currentClass.getAnnotations());
185
186 final ClassLocation classLocation = new ClassLocation(packageName, simpleClassName, classXmlName);
187 addEntry(dataHolder, classLocation, currentClass);
188
189 if (log.isDebugEnabled()) {
190 log.debug("Added class-level JavaDoc for [" + classLocation + "]");
191 }
192
193 for (JavaField currentField : currentClass.getFields()) {
194
195 final List<JavaAnnotation> currentFieldAnnotations = currentField.getAnnotations();
196 String annotatedXmlName = null;
197
198
199
200
201
202
203 if (hasAnnotation(XmlElementWrapper.class, currentFieldAnnotations)) {
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223 annotatedXmlName = getAnnotationAttributeValueFrom(
224 XmlElementWrapper.class,
225 "name",
226 currentFieldAnnotations);
227
228 if (annotatedXmlName == null || annotatedXmlName.equals(DEFAULT_VALUE)) {
229 annotatedXmlName = currentField.getName();
230 }
231 }
232
233
234 if (annotatedXmlName == null) {
235 annotatedXmlName = getAnnotationAttributeValueFrom(
236 XmlElement.class,
237 "name",
238 currentFieldAnnotations);
239 }
240
241 if (annotatedXmlName == null) {
242 annotatedXmlName = getAnnotationAttributeValueFrom(
243 XmlAttribute.class,
244 "name",
245 currentFieldAnnotations);
246 }
247 if (annotatedXmlName == null) {
248 annotatedXmlName = getAnnotationAttributeValueFrom(
249 XmlEnumValue.class,
250 "value",
251 currentFieldAnnotations);
252 }
253
254
255 final FieldLocation fieldLocation = new FieldLocation(
256 packageName,
257 simpleClassName,
258 classXmlName,
259 currentField.getName(),
260 annotatedXmlName);
261
262 addEntry(dataHolder, fieldLocation, currentField);
263
264 if (log.isDebugEnabled()) {
265 log.debug("Added field-level JavaDoc for [" + fieldLocation + "]");
266 }
267 }
268
269 for (JavaMethod currentMethod : currentClass.getMethods()) {
270
271 final List<JavaAnnotation> currentMethodAnnotations = currentMethod.getAnnotations();
272 String annotatedXmlName = null;
273
274
275
276
277
278
279 if (hasAnnotation(XmlElementWrapper.class, currentMethodAnnotations)) {
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299 annotatedXmlName = getAnnotationAttributeValueFrom(
300 XmlElementWrapper.class,
301 "name",
302 currentMethodAnnotations);
303
304 if (annotatedXmlName == null || annotatedXmlName.equals(DEFAULT_VALUE)) {
305 annotatedXmlName = currentMethod.getName();
306 }
307 }
308
309
310
311 if (annotatedXmlName == null) {
312 annotatedXmlName = getAnnotationAttributeValueFrom(
313 XmlElement.class,
314 "name",
315 currentMethod.getAnnotations());
316 }
317
318 if (annotatedXmlName == null) {
319 annotatedXmlName = getAnnotationAttributeValueFrom(
320 XmlAttribute.class,
321 "name",
322 currentMethod.getAnnotations());
323 }
324
325
326 final MethodLocation location = new MethodLocation(packageName,
327 simpleClassName,
328 classXmlName,
329 currentMethod.getName(),
330 annotatedXmlName,
331 currentMethod.getParameters());
332 addEntry(dataHolder, location, currentMethod);
333
334 if (log.isDebugEnabled()) {
335 log.debug("Added method-level JavaDoc for [" + location + "]");
336 }
337 }
338 }
339 }
340
341
342 return new ReadOnlySearchableDocumentation(dataHolder);
343 }
344
345
346
347
348
349
350
351
352
353
354
355
356
357 private static String getAnnotationAttributeValueFrom(
358 final Class<?> annotationType,
359 final String attributeName,
360 final List<JavaAnnotation> annotations) {
361
362
363
364 final String fullyQualifiedClassName = annotationType.getName();
365
366 JavaAnnotation annotation = null;
367 String toReturn = null;
368
369 if (annotations != null) {
370
371 for (JavaAnnotation current : annotations) {
372 if (current.getType().isA(fullyQualifiedClassName)) {
373 annotation = current;
374 break;
375 }
376 }
377
378 if (annotation != null) {
379
380 final Object nameValue = annotation.getNamedParameter(attributeName);
381
382 if (nameValue != null && nameValue instanceof String) {
383
384 toReturn = ((String) nameValue).trim();
385
386
387 if (toReturn.startsWith("\"") && toReturn.endsWith("\"")) {
388 toReturn = (((String) nameValue).trim()).substring(1, toReturn.length() - 1);
389 }
390 }
391 }
392 }
393
394
395 return toReturn;
396 }
397
398 private static boolean hasAnnotation(final Class<?> annotationType,
399 final List<JavaAnnotation> annotations) {
400
401 if (annotations != null && !annotations.isEmpty() && annotationType != null) {
402
403 final String fullAnnotationClassName = annotationType.getName();
404
405 for (JavaAnnotation current : annotations) {
406 if (current.getType().isA(fullAnnotationClassName)) {
407 return true;
408 }
409 }
410 }
411
412 return false;
413 }
414
415
416
417
418
419 private void addEntry(final SortedMap<SortableLocation, JavaDocData> map,
420 final SortableLocation key,
421 final JavaAnnotatedElement value) {
422
423
424 if (map.containsKey(key)) {
425
426
427 final JavaDocData existing = map.get(key);
428
429
430 if (key instanceof PackageLocation) {
431
432 final boolean emptyExisting = existing.getComment() == null || existing.getComment().isEmpty();
433 final boolean emptyGiven = value.getComment() == null || value.getComment().isEmpty();
434
435 if (emptyGiven) {
436 if (log.isDebugEnabled()) {
437 log.debug("Skipping processing empty Package javadoc from [" + key + "]");
438 }
439 return;
440 } else if (emptyExisting && log.isWarnEnabled()) {
441 log.warn("Overwriting empty Package javadoc from [" + key + "]");
442 }
443 } else {
444 final String given = "[" + value.getClass().getName() + "]: " + value.getComment();
445 throw new IllegalArgumentException("Not processing duplicate SortableLocation [" + key + "]. "
446 + "\n Existing: " + existing
447 + ".\n Given: [" + given + "]");
448 }
449 }
450
451
452
453
454 map.put(key, new JavaDocData(value.getComment(), value.getTags()));
455 }
456
457
458
459
460 private class ReadOnlySearchableDocumentation implements SearchableDocumentation {
461
462
463 private TreeMap<String, SortableLocation> keyMap;
464 private SortedMap<? extends SortableLocation, JavaDocData> valueMap;
465
466 ReadOnlySearchableDocumentation(final SortedMap<SortableLocation, JavaDocData> valueMap) {
467
468
469 this.valueMap = valueMap;
470
471 keyMap = new TreeMap<String, SortableLocation>();
472 for (Map.Entry<SortableLocation, JavaDocData> current : valueMap.entrySet()) {
473
474 final SortableLocation key = current.getKey();
475 keyMap.put(key.getPath(), key);
476 }
477 }
478
479
480
481
482 @Override
483 public SortedSet<String> getPaths() {
484 return Collections.unmodifiableSortedSet(keyMap.navigableKeySet());
485 }
486
487
488
489
490 @Override
491 public JavaDocData getJavaDoc(final String path) {
492
493
494 Validate.notNull(path, "path");
495
496
497 final SortableLocation location = getLocation(path);
498 return (location == null) ? null : valueMap.get(location);
499 }
500
501
502
503
504 @Override
505 @SuppressWarnings("unchecked")
506 public <T extends SortableLocation> T getLocation(final String path) {
507
508
509 Validate.notNull(path, "path");
510
511
512 return (T) keyMap.get(path);
513 }
514
515
516
517
518 @Override
519 @SuppressWarnings("unchecked")
520 public SortedMap<SortableLocation, JavaDocData> getAll() {
521 return (SortedMap<SortableLocation, JavaDocData>) Collections.unmodifiableSortedMap(valueMap);
522 }
523
524
525
526
527 @Override
528 @SuppressWarnings("unchecked")
529 public <T extends SortableLocation> SortedMap<T, JavaDocData> getAll(final Class<T> type) {
530
531
532 Validate.notNull(type, "type");
533
534
535 final SortedMap<T, JavaDocData> toReturn = new TreeMap<T, JavaDocData>();
536 for (Map.Entry<? extends SortableLocation, JavaDocData> current : valueMap.entrySet()) {
537 if (type == current.getKey().getClass()) {
538 toReturn.put((T) current.getKey(), current.getValue());
539 }
540 }
541
542
543 return toReturn;
544 }
545 }
546 }