View Javadoc
1   package org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import com.thoughtworks.qdox.JavaProjectBuilder;
23  import com.thoughtworks.qdox.model.JavaAnnotatedElement;
24  import com.thoughtworks.qdox.model.JavaClass;
25  import com.thoughtworks.qdox.model.JavaField;
26  import com.thoughtworks.qdox.model.JavaMethod;
27  import com.thoughtworks.qdox.model.JavaPackage;
28  import com.thoughtworks.qdox.model.JavaSource;
29  import org.apache.maven.plugin.logging.Log;
30  import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.ClassLocation;
31  import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.FieldLocation;
32  import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.MethodLocation;
33  import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.location.PackageLocation;
34  import org.codehaus.mojo.jaxb2.shared.FileSystemUtilities;
35  import org.codehaus.mojo.jaxb2.shared.Validate;
36  
37  import java.io.File;
38  import java.io.IOException;
39  import java.net.URL;
40  import java.util.Collection;
41  import java.util.Collections;
42  import java.util.List;
43  import java.util.Map;
44  import java.util.SortedMap;
45  import java.util.SortedSet;
46  import java.util.TreeMap;
47  
48  /**
49   * <p>The schemagen tool operates on compiled bytecode, where JavaDoc comments are not present.
50   * However, the javadoc documentation present in java source files is required within the generated
51   * XSD to increase usability and produce an XSD which does not loose out on important usage information.</p>
52   * <p>The JavaDocExtractor is used as a post processor after creating the XSDs within the compilation
53   * unit, and injects XSD annotations into the appropriate XSD elements or types.</p>
54   *
55   * @author <a href="mailto:lj@jguru.se">Lennart J&ouml;relid</a>, jGuru Europe AB
56   * @since 2.0
57   */
58  public class JavaDocExtractor {
59  
60      // Internal state
61      private JavaProjectBuilder builder;
62      private Log log;
63  
64      /**
65       * Creates a JavaDocExtractor wrapping the supplied Maven Log.
66       *
67       * @param log A non-null Log.
68       */
69      public JavaDocExtractor(final Log log) {
70  
71          // Check sanity
72          Validate.notNull(log, "log");
73  
74          // Create internal state
75          this.log = log;
76          this.builder = new JavaProjectBuilder();
77      }
78  
79      /**
80       * Adds the supplied sourceCodeFiles for processing by this JavaDocExtractor.
81       *
82       * @param sourceCodeFiles The non-null List of source code files to add.
83       * @return This JavaDocExtractor, for call chaining.
84       * @throws IllegalArgumentException If any of the given sourceCodeFiles could not be read properly.
85       */
86      public JavaDocExtractor addSourceFiles(final List<File> sourceCodeFiles) throws IllegalArgumentException {
87  
88          // Check sanity
89          Validate.notNull(sourceCodeFiles, "addSourceFiles");
90  
91          // Add the files.
92          for (File current : sourceCodeFiles) {
93              try {
94                  builder.addSource(current);
95              } catch (IOException e) {
96                  throw new IllegalArgumentException("Could not add file ["
97                          + FileSystemUtilities.getCanonicalPath(current) + "]", e);
98              }
99          }
100 
101         // All done.
102         return this;
103     }
104 
105     /**
106      * Adds the supplied sourceCodeFiles for processing by this JavaDocExtractor.
107      *
108      * @param sourceCodeURLs The non-null List of source code URLs to add.
109      * @return This JavaDocExtractor, for call chaining.
110      * @throws IllegalArgumentException If any of the given sourceCodeURLs could not be read properly.
111      */
112     public JavaDocExtractor addSourceURLs(final List<URL> sourceCodeURLs) throws IllegalArgumentException {
113 
114         // Check sanity
115         Validate.notNull(sourceCodeURLs, "sourceCodeURLs");
116 
117         // Add the URLs
118         for (URL current : sourceCodeURLs) {
119             try {
120                 builder.addSource(current);
121             } catch (IOException e) {
122                 throw new IllegalArgumentException("Could not add URL [" + current.toString() + "]", e);
123             }
124         }
125 
126         // All done
127         return this;
128     }
129 
130     /**
131      * Processes all supplied Java source Files and URLs to extract JavaDocData for all ClassLocations from which
132      * JavaDoc has been collected.
133      *
134      * @return A SearchableDocumentation relating SortableLocations and their paths to harvested JavaDocData.
135      */
136     public SearchableDocumentation process() {
137 
138         // Start processing.
139         final SortedMap<SortableLocation, JavaDocData> dataHolder = new TreeMap<SortableLocation, JavaDocData>();
140         final Collection<JavaSource> sources = builder.getSources();
141 
142         if (log.isInfoEnabled()) {
143             log.info("Processing [" + sources.size() + "] java sources.");
144         }
145 
146         for (JavaSource current : sources) {
147 
148             // Add the package-level JavaDoc
149             final JavaPackage currentPackage = current.getPackage();
150             final String packageName = currentPackage.getName();
151             addEntry(dataHolder, new PackageLocation(packageName), currentPackage);
152 
153             if (log.isDebugEnabled()) {
154                 log.debug("Added package-level JavaDoc for [" + packageName + "]");
155             }
156 
157             for (JavaClass currentClass : current.getClasses()) {
158 
159                 // Add the class-level JavaDoc
160                 final String simpleClassName = currentClass.getName();
161                 final ClassLocation classLocation = new ClassLocation(packageName, simpleClassName);
162                 addEntry(dataHolder, classLocation, currentClass);
163 
164                 if (log.isDebugEnabled()) {
165                     log.debug("Added class-level JavaDoc for [" + classLocation + "]");
166                 }
167 
168                 for (JavaField currentField : currentClass.getFields()) {
169 
170                     // Add the field-level JavaDoc
171                     final FieldLocation fieldLocation = new FieldLocation(
172                             packageName,
173                             simpleClassName,
174                             currentField.getName());
175 
176                     addEntry(dataHolder, fieldLocation, currentField);
177 
178                     if (log.isDebugEnabled()) {
179                         log.debug("Added field-level JavaDoc for [" + fieldLocation + "]");
180                     }
181                 }
182 
183                 for (JavaMethod currentMethod : currentClass.getMethods()) {
184 
185                     // Add the method-level JavaDoc
186                     final MethodLocation location = new MethodLocation(packageName,
187                             simpleClassName,
188                             currentMethod.getName(),
189                             currentMethod.getParameters());
190                     addEntry(dataHolder, location, currentMethod);
191 
192                     if (log.isDebugEnabled()) {
193                         log.debug("Added method-level JavaDoc for [" + location + "]");
194                     }
195                 }
196             }
197         }
198 
199         // All done.
200         return new ReadOnlySearchableDocumentation(dataHolder);
201     }
202 
203     //
204     // Private helpers
205     //
206 
207     private void addEntry(final SortedMap<SortableLocation, JavaDocData> map,
208                           final SortableLocation key,
209                           final JavaAnnotatedElement value) {
210 
211         // Check sanity
212         if (map.containsKey(key)) {
213 
214             // Get something to compare with
215             final JavaDocData existing = map.get(key);
216 
217             // Is this an empty package-level documentation?
218             if (key instanceof PackageLocation) {
219 
220                 final boolean emptyExisting = existing.getComment() == null || existing.getComment().isEmpty();
221                 final boolean emptyGiven = value.getComment() == null || value.getComment().isEmpty();
222 
223                 if (emptyGiven) {
224                     if (log.isDebugEnabled()) {
225                         log.debug("Skipping processing empty Package javadoc from [" + key + "]");
226                     }
227                     return;
228                 } else if (emptyExisting && log.isWarnEnabled()) {
229                     log.warn("Overwriting empty Package javadoc from [" + key + "]");
230                 }
231             } else {
232                 final String given = "[" + value.getClass().getName() + "]: " + value.getComment();
233                 throw new IllegalArgumentException("Not processing duplicate SortableLocation [" + key + "]. "
234                         + "\n Existing: " + existing
235                         + ".\n Given: [" + given + "]");
236             }
237         }
238 
239         // Validate.isTrue(!map.containsKey(key), "Found duplicate SortableLocation [" + key + "] in map. "
240         //         + "Current map keySet: " + map.keySet() + ". Got comment: [" + value.getComment() + "]");
241 
242         map.put(key, new JavaDocData(value.getComment(), value.getTags()));
243     }
244 
245     /**
246      * Standard read-only SearchableDocumentation implementation.
247      */
248     class ReadOnlySearchableDocumentation implements SearchableDocumentation {
249 
250         // Internal state
251         private TreeMap<String, SortableLocation> keyMap;
252         private SortedMap<? extends SortableLocation, JavaDocData> valueMap;
253 
254         ReadOnlySearchableDocumentation(final SortedMap<SortableLocation, JavaDocData> valueMap) {
255 
256             // Create internal state
257             this.valueMap = valueMap;
258 
259             keyMap = new TreeMap<String, SortableLocation>();
260             for (Map.Entry<SortableLocation, JavaDocData> current : valueMap.entrySet()) {
261 
262                 final SortableLocation key = current.getKey();
263                 keyMap.put(key.getPath(), key);
264             }
265         }
266 
267         /**
268          * {@inheritDoc}
269          */
270         @Override
271         public SortedSet<String> getPaths() {
272             return Collections.unmodifiableSortedSet(keyMap.navigableKeySet());
273         }
274 
275         /**
276          * {@inheritDoc}
277          */
278         @Override
279         public JavaDocData getJavaDoc(final String path) {
280 
281             // Check sanity
282             Validate.notNull(path, "path");
283 
284             // All done.
285             final SortableLocation location = getLocation(path);
286             return (location == null) ? null : valueMap.get(location);
287         }
288 
289         /**
290          * {@inheritDoc}
291          */
292         @Override
293         @SuppressWarnings("unchecked")
294         public <T extends SortableLocation> T getLocation(final String path) {
295 
296             // Check sanity
297             Validate.notNull(path, "path");
298 
299             // All done
300             return (T) keyMap.get(path);
301         }
302 
303         /**
304          * {@inheritDoc}
305          */
306         @Override
307         @SuppressWarnings("unchecked")
308         public SortedMap<SortableLocation, JavaDocData> getAll() {
309             return (SortedMap<SortableLocation, JavaDocData>) Collections.unmodifiableSortedMap(valueMap);
310         }
311 
312         /**
313          * {@inheritDoc}
314          */
315         @Override
316         @SuppressWarnings("unchecked")
317         public <T extends SortableLocation> SortedMap<T, JavaDocData> getAll(final Class<T> type) {
318 
319             // Check sanity
320             Validate.notNull(type, "type");
321 
322             // Filter the valueMap.
323             final SortedMap<T, JavaDocData> toReturn = new TreeMap<T, JavaDocData>();
324             for (Map.Entry<? extends SortableLocation, JavaDocData> current : valueMap.entrySet()) {
325                 if (type == current.getKey().getClass()) {
326                     toReturn.put((T) current.getKey(), current.getValue());
327                 }
328             }
329 
330             // All done.
331             return toReturn;
332         }
333     }
334 }