View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.jxr;
20  
21  import java.io.File;
22  import java.io.FileOutputStream;
23  import java.io.OutputStreamWriter;
24  import java.io.Writer;
25  import java.nio.file.Path;
26  import java.nio.file.Paths;
27  import java.util.Map;
28  import java.util.TreeMap;
29  
30  import org.apache.maven.jxr.pacman.ClassType;
31  import org.apache.maven.jxr.pacman.PackageManager;
32  import org.apache.maven.jxr.pacman.PackageType;
33  import org.apache.velocity.Template;
34  import org.apache.velocity.VelocityContext;
35  import org.apache.velocity.app.VelocityEngine;
36  
37  /**
38   * This class creates the navigational pages for jxr's cross-referenced source
39   * files. The navigation is inspired by javadoc, so it should have a familiar feel.
40   *
41   * Creates the following files:
42   * <ul>
43   * <li>{@code index.html}            main index containing the frameset</li>
44   * <li>{@code overview-frame.html}   list of the project's packages              (top left)</li>
45   * <li>{@code allclasses-frame.html} list of all classes in the project          (bottom left)</li>
46   * <li>{@code overview-summary.html} top-level listing of the project's packages (main frame)</li>
47   *
48   * <li>
49   * Package specific:
50   * <ul>
51   * <li>{@code package-summary.html} listing of all classes in this package    (main frame)</li>
52   * <li>{@code package-frame.html}   listing of all classes in this package    (bottom left)</li>
53   * </ul>
54   * </li>
55   * </ul>
56   *
57   * @author <a href="mailto:bellingard@gmail.com">Fabrice Bellingard </a>
58   * @author <a href="mailto:brian@brainslug.org">Brian Leonard</a>
59   */
60  public class DirectoryIndexer {
61      /*
62       * JavaCodeTransform uses this to cross-reference package references
63       * with that package's main summary page.
64       */
65      static final String INDEX = "package-summary.html";
66  
67      /*
68       * Path to the root output directory.
69       */
70      private String root;
71  
72      /*
73       * Package Manager for this project.
74       */
75      private PackageManager packageManager;
76  
77      /*
78       * see the getter/setter docs for these properties
79       */
80      private String outputEncoding;
81  
82      private String templateDir;
83  
84      private String windowTitle;
85  
86      private String docTitle;
87  
88      private String bottom;
89  
90      /**
91       * Constructor for the DirectoryIndexer object
92       *
93       * @param packageManager PackageManager for this project
94       * @param root Path of the root output directory
95       */
96      public DirectoryIndexer(PackageManager packageManager, String root) {
97          this.packageManager = packageManager;
98          this.root = root;
99      }
100 
101     /**
102      * OutputEncoding is the encoding of output files.
103      *
104      * @param outputEncoding output encoding
105      */
106     public void setOutputEncoding(String outputEncoding) {
107         this.outputEncoding = outputEncoding;
108     }
109 
110     /**
111      * Gets the output encoding.
112      * @return output encoding
113      */
114     public String getOutputEncoding() {
115         return outputEncoding;
116     }
117 
118     /**
119      * {@code templateDir} is the location of the jelly template files used
120      * to generate the navigation pages.
121      *
122      * @param templateDir location of the template directory
123      */
124     public void setTemplateDir(String templateDir) {
125         this.templateDir = templateDir;
126     }
127 
128     /**
129      * Gets the template directory.
130      *
131      * @return location of the template directory
132      */
133     public String getTemplateDir() {
134         return templateDir;
135     }
136 
137     /**
138      * {@code windowTitle} is used in the output's &lt;title&gt; tags
139      * see the javadoc documentation for the property of the same name
140      *
141      * @param windowTitle the &lt;title&gt; attribute
142      */
143     public void setWindowTitle(String windowTitle) {
144         this.windowTitle = windowTitle;
145     }
146 
147     /**
148      * Gets the window title.
149      *
150      * @return window title
151      */
152     public String getWindowTitle() {
153         return windowTitle;
154     }
155 
156     /**
157      * {@code docTitle} is used as a page heading for the summary files.<br>
158      * See the javadoc documentation for the property of the same name
159      *
160      * @param docTitle major page heading
161      */
162     public void setDocTitle(String docTitle) {
163         this.docTitle = docTitle;
164     }
165 
166     /**
167      * Gets the major page heading.
168      *
169      * @return major page heading
170      */
171     public String getDocTitle() {
172         return docTitle;
173     }
174 
175     /**
176      * Bottom is a footer for the navigation pages, usually a copyright.<br>
177      * See the javadoc documentation for the property of the same name.
178      *
179      * @param bottom page footer
180      */
181     public void setBottom(String bottom) {
182         this.bottom = bottom;
183     }
184 
185     /**
186      * Gets the footer for the navigation pages.
187      *
188      * @return bottom page footer
189      */
190     public String getBottom() {
191         return bottom;
192     }
193 
194     /**
195      * Does the actual indexing.
196      *
197      * @throws JxrException If something went wrong
198      */
199     public void process() throws JxrException {
200         ProjectInfo info = getProjectInfo();
201 
202         VelocityEngine engine = new VelocityEngine();
203         setProperties(engine);
204         try {
205             engine.init();
206         } catch (Exception e) {
207             throw new JxrException("Error initializing Velocity", e);
208         }
209 
210         VelocityContext context = new VelocityContext();
211         context.put("outputEncoding", getOutputEncoding());
212         context.put("windowTitle", getWindowTitle());
213         context.put("docTitle", getDocTitle());
214         context.put("bottom", getBottom());
215         context.put("info", info);
216 
217         doVelocity("index", root, context, engine);
218         doVelocity("overview-frame", root, context, engine);
219         doVelocity("allclasses-frame", root, context, engine);
220         doVelocity("overview-summary", root, context, engine);
221 
222         for (PackageInfo pkgInfo : info.getAllPackages().values()) {
223             VelocityContext subContext = new VelocityContext(context);
224             subContext.put("pkgInfo", pkgInfo);
225 
226             String outDir = root + '/' + pkgInfo.getDir();
227             doVelocity("package-summary", outDir, subContext, engine);
228             doVelocity("package-frame", outDir, subContext, engine);
229         }
230     }
231 
232     /*
233      * Sets Velocity properties to find templates.
234      */
235     private void setProperties(VelocityEngine engine) {
236         Path templateDirFile = Paths.get(getTemplateDir());
237         if (templateDirFile.isAbsolute()) {
238             // the property has been overridden: need to use a FileResourceLoader
239             engine.setProperty("resource.loader", "file");
240             engine.setProperty(
241                     "file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.FileResourceLoader");
242             engine.setProperty("file.resource.loader.path", templateDirFile.toString());
243         } else {
244             // use of the default templates
245             engine.setProperty("resource.loader", "classpath");
246             engine.setProperty(
247                     "classpath.resource.loader.class",
248                     "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
249         }
250         // avoid "unable to find resource 'VM_global_library.vm' in any resource loader."
251         engine.setProperty("velocimacro.library", "");
252         //        engine.setProperty( Log.class.getName(), log );
253         //        engine.setProperty( "runtime.log.logsystem.class", VelocityLogger.class.getName() );
254     }
255 
256     /*
257      * Generate the HTML file according to the Velocity template
258      */
259     private void doVelocity(String templateName, String outDir, VelocityContext context, VelocityEngine engine)
260             throws JxrException {
261         // output file
262         File file = new File(outDir, templateName + ".html");
263         file.getParentFile().mkdirs();
264 
265         try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), getOutputEncoding())) {
266             // template file
267             StringBuilder templateFile = new StringBuilder();
268             File templateDirFile = new File(getTemplateDir());
269             if (!templateDirFile.isAbsolute()) {
270                 // default templates
271                 templateFile.append(getTemplateDir());
272                 templateFile.append('/');
273             }
274             templateFile.append(templateName);
275             templateFile.append(".vm");
276             Template template = engine.getTemplate(templateFile.toString());
277 
278             // do the merge
279             template.merge(context, writer);
280             writer.flush();
281         } catch (Exception e) {
282             throw new JxrException("Error merging velocity template", e);
283         }
284     }
285 
286     /*
287      * Creates a Map of other Maps containing information about
288      * this project's packages and classes, obtained from the PackageManager.
289      *
290      * allPackages collection of Maps with package info, with the following format
291      *   {name}    package name (e.g., "org.apache.maven.jxr")
292      *   {dir}     package dir relative to the root output dir (e.g., "org/apache/maven/jxr")
293      *   {rootRef} relative link to root output dir (e.g., "../../../../") note trailing slash
294      *   {classes} collection of Maps with class info
295      *      {name}  class name (e.g., "DirectoryIndexer")
296      *      {dir}   duplicate of package {dir}
297      *
298      * allClasses collection of Maps with class info, format as above
299      *
300      */
301     ProjectInfo getProjectInfo() {
302         Map<String, PackageInfo> allPackages = new TreeMap<>();
303         Map<String, ClassInfo> allClasses = new TreeMap<>();
304 
305         for (PackageType pkg : packageManager.getPackageTypes()) {
306             String pkgName = pkg.getName();
307             String pkgDir = pkgName.replace('.', '/');
308             String rootRef = pkgName.replaceAll("[^\\.]+(\\.|$)", "../");
309 
310             // special case for the default package
311             // javadoc doesn't deal with it, but it's easy for us
312             if (pkgName.length() == 0) {
313                 pkgName = "(default package)";
314                 pkgDir = ".";
315                 rootRef = "./";
316             }
317 
318             Map<String, ClassInfo> pkgClasses = new TreeMap<>();
319             for (ClassType clazz : pkg.getClassTypes()) {
320                 String className = clazz.getName();
321 
322                 ClassInfo classInfo = new ClassInfo(className, pkgDir);
323 
324                 classInfo.setFilename(clazz.getFilename());
325 
326                 pkgClasses.put(className, classInfo);
327 
328                 // Adding package name to key in order to ensure classes with identical names in different packages are
329                 // all included.
330                 allClasses.put(className + "#" + pkgName, classInfo);
331             }
332 
333             PackageInfo pkgInfo = new PackageInfo(pkgName, pkgDir);
334             pkgInfo.setClasses(pkgClasses);
335             pkgInfo.setRootRef(rootRef);
336 
337             allPackages.put(pkgName, pkgInfo);
338         }
339 
340         return new ProjectInfo(allPackages, allClasses);
341     }
342 
343     /**
344      *
345      * @author Robert Scholte
346      * @since 3.2.0
347      */
348     public static class ProjectInfo {
349         private final Map<String, PackageInfo> allPackages;
350 
351         private final Map<String, ClassInfo> allClasses;
352 
353         public ProjectInfo(Map<String, PackageInfo> allPackages, Map<String, ClassInfo> allClasses) {
354             this.allPackages = allPackages;
355             this.allClasses = allClasses;
356         }
357 
358         public Map<String, PackageInfo> getAllPackages() {
359             return allPackages;
360         }
361 
362         public Map<String, ClassInfo> getAllClasses() {
363             return allClasses;
364         }
365     }
366 
367     /**
368      *
369      * @author Robert Scholte
370      * @since 3.2.0
371      */
372     public static class PackageInfo {
373         private final String name;
374 
375         private final String dir;
376 
377         Map<String, ClassInfo> classes;
378 
379         private String rootRef;
380 
381         public PackageInfo(String name, String dir) {
382             this.name = name;
383             this.dir = dir;
384         }
385 
386         public String getName() {
387             return name;
388         }
389 
390         public String getDir() {
391             return dir;
392         }
393 
394         public void setClasses(Map<String, ClassInfo> classes) {
395             this.classes = classes;
396         }
397 
398         public Map<String, ClassInfo> getClasses() {
399             return classes;
400         }
401 
402         public void setRootRef(String rootRef) {
403             this.rootRef = rootRef;
404         }
405 
406         public String getRootRef() {
407             return rootRef;
408         }
409     }
410 
411     /**
412      * Holds class information
413      *
414      * @author Robert Scholte
415      * @since 3.2.0
416      */
417     public static class ClassInfo {
418         private final String name;
419 
420         private final String dir;
421 
422         private String filename;
423 
424         public ClassInfo(String name, String dir) {
425             super();
426             this.name = name;
427             this.dir = dir;
428         }
429 
430         public String getName() {
431             return name;
432         }
433 
434         public String getDir() {
435             return dir;
436         }
437 
438         public void setFilename(String filename) {
439             this.filename = filename;
440         }
441 
442         public String getFilename() {
443             return filename;
444         }
445     }
446 }