001: /*****************************************************************************
002: * Java Plug-in Framework (JPF)
003: * Copyright (C) 2004-2007 Dmitry Olshansky
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: *****************************************************************************/package org.java.plugin.tools.docgen;
019:
020: import java.io.BufferedOutputStream;
021: import java.io.File;
022: import java.io.FileOutputStream;
023: import java.io.OutputStreamWriter;
024: import java.io.Writer;
025: import java.net.MalformedURLException;
026: import java.net.URL;
027: import java.util.Collection;
028: import java.util.Collections;
029: import java.util.Comparator;
030: import java.util.HashMap;
031: import java.util.LinkedList;
032: import java.util.List;
033: import java.util.Map;
034:
035: import org.java.plugin.PathResolver;
036: import org.java.plugin.registry.Documentation;
037: import org.java.plugin.registry.Extension;
038: import org.java.plugin.registry.ExtensionPoint;
039: import org.java.plugin.registry.Identity;
040: import org.java.plugin.registry.PluginDescriptor;
041: import org.java.plugin.registry.PluginElement;
042: import org.java.plugin.registry.PluginFragment;
043: import org.java.plugin.registry.PluginPrerequisite;
044: import org.java.plugin.registry.PluginRegistry;
045: import org.java.plugin.util.IoUtil;
046: import org.onemind.jxp.FilePageSource;
047: import org.onemind.jxp.JxpProcessingContext;
048: import org.onemind.jxp.JxpProcessor;
049:
050: /**
051: * Tool class to generate documentation for plug-ins using <a
052: * href="http://jxp.sourceforge.net" target="_new">JXP</a> templates.
053: *
054: * @version $Id$
055: */
056: public final class DocGenerator {
057: private static String getRelativePath(final int level) {
058: StringBuilder result = new StringBuilder();
059: if (level > 0) {
060: for (int i = 0; i < level; i++) {
061: if (i > 0) {
062: result.append("/"); //$NON-NLS-1$
063: }
064: result.append(".."); //$NON-NLS-1$
065: }
066: } else {
067: result.append("."); //$NON-NLS-1$
068: }
069: return result.toString();
070: }
071:
072: private final PluginRegistry registry;
073:
074: private final PathResolver pathResolver;
075:
076: private JxpProcessor processor;
077:
078: private Collection<PluginDescriptor> allPluginDescriptors;
079:
080: private Collection<PluginFragment> allPluginFragments;
081:
082: private Collection<ExtensionPoint> allExtensionPoints;
083:
084: private Collection<Extension> allExtensions;
085:
086: private String documentationOverview;
087:
088: private String stylesheet;
089:
090: private String outputEncoding = "UTF-8"; //$NON-NLS-1$
091:
092: /**
093: * Constructs generator configured to use pre-defined set of templates.
094: *
095: * @param aRegistry
096: * plug-ins registry
097: * @param aPathResolver
098: * path resolver
099: * @throws Exception
100: * if an error has occurred
101: */
102: public DocGenerator(final PluginRegistry aRegistry,
103: final PathResolver aPathResolver) throws Exception {
104: this (aRegistry, aPathResolver, DocGenerator.class.getName()
105: .substring(0,
106: DocGenerator.class.getName().lastIndexOf('.'))
107: .replace('.', '/')
108: + "/templates/", null); //$NON-NLS-1$
109: }
110:
111: /**
112: * Constructs generator configured to use custom templates available in the
113: * classpath.
114: *
115: * @param aRegistry
116: * plug-ins registry
117: * @param aPathResolver
118: * path resolver
119: * @param templatesPath
120: * path to templates (should be available in classpath)
121: * @param templatesEncoding
122: * templates characters encoding, if <code>null</code>, system
123: * default will be used
124: * @throws Exception
125: * if an error has occurred
126: */
127: public DocGenerator(final PluginRegistry aRegistry,
128: final PathResolver aPathResolver,
129: final String templatesPath, final String templatesEncoding)
130: throws Exception {
131: this (aRegistry, aPathResolver, new JxpProcessor(
132: new ClassPathPageSource(templatesPath,
133: templatesEncoding)));
134: }
135:
136: /**
137: * Constructs generator configured to use custom templates located somewhere
138: * in the local file system.
139: *
140: * @param aRegistry
141: * plug-ins registry
142: * @param aPathResolver
143: * path resolver
144: * @param templatesFolder
145: * folder with templates
146: * @param templatesEncoding
147: * templates characters encoding, if <code>null</code>, system
148: * default will be used
149: * @throws Exception
150: * if an error has occurred
151: */
152: public DocGenerator(final PluginRegistry aRegistry,
153: final PathResolver aPathResolver,
154: final File templatesFolder, final String templatesEncoding)
155: throws Exception {
156: // TODO: use character encoding when that will be possible in JXP
157: // library
158: this (aRegistry, aPathResolver, new JxpProcessor(
159: new FilePageSource(templatesFolder.getCanonicalPath())));
160: }
161:
162: private DocGenerator(final PluginRegistry aRegistry,
163: final PathResolver aPathResolver, final JxpProcessor proc) {
164: registry = aRegistry;
165: pathResolver = aPathResolver;
166: processor = proc;
167: allPluginDescriptors = getAllPluginDescriptors();
168: allPluginFragments = getAllPluginFragments();
169: allExtensionPoints = getAllExtensionPoints();
170: allExtensions = getAllExtensions();
171: }
172:
173: /**
174: * @return documentation overview HTML content
175: */
176: public String getDocumentationOverview() {
177: return documentationOverview;
178: }
179:
180: /**
181: * @param aDocumentationOverview
182: * documentation overview HTML content
183: */
184: public void setDocumentationOverview(
185: final String aDocumentationOverview) {
186: this .documentationOverview = aDocumentationOverview;
187: }
188:
189: /**
190: * @return CSS style sheet content
191: */
192: public String getStylesheet() {
193: return stylesheet;
194: }
195:
196: /**
197: * @param aStylesheet
198: * CSS style sheet content
199: */
200: public void setStylesheet(final String aStylesheet) {
201: this .stylesheet = aStylesheet;
202: }
203:
204: /**
205: * @return output files encoding name
206: */
207: public String getOutputEncoding() {
208: return outputEncoding;
209: }
210:
211: /**
212: * @param encoding
213: * output files encoding name (default is UTF-8)
214: */
215: public void setOutputEncoding(final String encoding) {
216: this .outputEncoding = encoding;
217: }
218:
219: private void processTemplateFile(final Map<String, Object> ctx,
220: final String template, final File outFile) throws Exception {
221: Writer out = new OutputStreamWriter(new BufferedOutputStream(
222: new FileOutputStream(outFile, false)), outputEncoding);
223: try {
224: processor.process(template, new JxpProcessingContext(out,
225: ctx));
226: } finally {
227: out.close();
228: }
229: }
230:
231: private void processTemplateContent(final Map<String, Object> ctx,
232: final String template, final File outFile) throws Exception {
233: File tmpFile = File.createTempFile("~jpf-jxp", null); //$NON-NLS-1$
234: tmpFile.deleteOnExit();
235: Writer tmpOut = new OutputStreamWriter(
236: new BufferedOutputStream(new FileOutputStream(tmpFile,
237: false)), "UTF-8"); //$NON-NLS-1$
238: try {
239: tmpOut.write(template);
240: } finally {
241: tmpOut.close();
242: }
243: Writer out = new OutputStreamWriter(new BufferedOutputStream(
244: new FileOutputStream(outFile, false)), outputEncoding);
245: try {
246: JxpProcessor proc = new JxpProcessor(new FilePageSource(
247: tmpFile.getParentFile().getCanonicalPath()));
248: // TODO: use character encoding when that will be possible in JXP
249: // library (UTF-8 in this case)
250: proc.process(tmpFile.getName(), new JxpProcessingContext(
251: out, ctx));
252: } finally {
253: tmpFile.delete();
254: out.close();
255: }
256: }
257:
258: /**
259: * Generates documentation for all registered plug-ins.
260: *
261: * @param destDir
262: * target folder
263: * @throws Exception
264: * if an error has occurred
265: */
266: public void generate(final File destDir) throws Exception {
267: // generating index page
268: Map<String, Object> ctx = createConext(0);
269: processTemplateFile(ctx, "index.jxp", //$NON-NLS-1$
270: new File(destDir, "index.html")); //$NON-NLS-1$
271: // generating style sheet file
272: generateCss(destDir);
273: // generating menu page
274: ctx = createConext(0);
275: processTemplateFile(ctx, "menu.jxp", //$NON-NLS-1$
276: new File(destDir, "menu.html")); //$NON-NLS-1$
277: // generating overview page
278: ctx = createConext(0);
279: if (documentationOverview != null) {
280: ctx.put("overview", documentationOverview.replaceAll( //$NON-NLS-1$
281: "(?i)(?d)(?m).*<body>(.*)</body>.*", "$1")); //$NON-NLS-1$ //$NON-NLS-2$
282: } else {
283: ctx.put("overview", ""); //$NON-NLS-1$ //$NON-NLS-2$
284: }
285: processTemplateFile(ctx, "overview.jxp", //$NON-NLS-1$
286: new File(destDir, "overview.html")); //$NON-NLS-1$
287: // generating "all plug-ins" page
288: ctx = createConext(0);
289: processTemplateFile(ctx, "allplugins.jxp", //$NON-NLS-1$
290: new File(destDir, "allplugins.html")); //$NON-NLS-1$
291: // generating "all plug-in fragments" page
292: ctx = createConext(0);
293: processTemplateFile(ctx, "allfragments.jxp", //$NON-NLS-1$
294: new File(destDir, "allfragments.html")); //$NON-NLS-1$
295: // generating "all extension points" page
296: ctx = createConext(0);
297: processTemplateFile(ctx, "allextpoints.jxp", //$NON-NLS-1$
298: new File(destDir, "allextpoints.html")); //$NON-NLS-1$
299: // generating "all extensions" page
300: ctx = createConext(0);
301: processTemplateFile(ctx, "allexts.jxp", //$NON-NLS-1$
302: new File(destDir, "allexts.html")); //$NON-NLS-1$
303: // generating tree page
304: ctx = createConext(0);
305: processTemplateFile(ctx, "tree.jxp", //$NON-NLS-1$
306: new File(destDir, "tree.html")); //$NON-NLS-1$
307: // per plug-in generation
308: for (PluginDescriptor descriptor : registry
309: .getPluginDescriptors()) {
310: generateForPluginDescriptor(destDir, descriptor);
311: }
312: }
313:
314: private void generateCss(final File destDir) throws Exception {
315: final Map<String, Object> ctx = createConext(0);
316: if (stylesheet == null) {
317: processTemplateFile(ctx, "stylesheet.jxp", //$NON-NLS-1$
318: new File(destDir, "stylesheet.css")); //$NON-NLS-1$
319: } else {
320: processTemplateContent(ctx, stylesheet, new File(destDir,
321: "stylesheet.css")); //$NON-NLS-1$
322: }
323: }
324:
325: private void generateForPluginDescriptor(final File baseDir,
326: final PluginDescriptor descr) throws Exception {
327: File destDir = new File(baseDir, descr.getId());
328: destDir.mkdirs();
329: File srcDocsFolder = IoUtil.url2file(pathResolver.resolvePath(
330: descr, descr.getDocsPath()));
331: if ((srcDocsFolder != null) && srcDocsFolder.isDirectory()) {
332: File destDocsFolder = new File(destDir, "extra"); //$NON-NLS-1$
333: destDocsFolder.mkdir();
334: IoUtil.copyFolder(srcDocsFolder, destDocsFolder, true);
335: }
336: List<PluginDescriptor> dependedPlugins = new LinkedList<PluginDescriptor>();
337: for (PluginDescriptor dependedDescr : registry
338: .getPluginDescriptors()) {
339: if (dependedDescr.getId().equals(descr.getId())) {
340: continue;
341: }
342: for (PluginPrerequisite pre : dependedDescr
343: .getPrerequisites()) {
344: if (pre.getPluginId().equals(descr.getId())
345: && pre.matches()) {
346: dependedPlugins.add(dependedDescr);
347: break;
348: }
349: }
350: }
351: Map<String, Object> ctx = createConext(1);
352: ctx.put("descriptor", descr); //$NON-NLS-1$
353: ctx.put("dependedPlugins", dependedPlugins); //$NON-NLS-1$
354: processTemplateFile(ctx, "plugin.jxp", //$NON-NLS-1$
355: new File(destDir, "index.html")); //$NON-NLS-1$
356: // per plug-in fragment generation
357: for (PluginFragment fragment : descr.getFragments()) {
358: generateForPluginFragment(baseDir, fragment);
359: }
360: // generating extension points
361: if (!descr.getExtensionPoints().isEmpty()) {
362: File extPointsDir = new File(destDir, "extp"); //$NON-NLS-1$
363: extPointsDir.mkdir();
364: for (ExtensionPoint extPoint : descr.getExtensionPoints()) {
365: ctx = createConext(3);
366: ctx.put("extPoint", extPoint); //$NON-NLS-1$
367: File dir = new File(extPointsDir, extPoint.getId());
368: dir.mkdir();
369: processTemplateFile(ctx, "extpoint.jxp", //$NON-NLS-1$
370: new File(dir, "index.html")); //$NON-NLS-1$
371: }
372: }
373: // generating extensions
374: if (!descr.getExtensions().isEmpty()) {
375: File extsDir = new File(destDir, "ext"); //$NON-NLS-1$
376: extsDir.mkdir();
377: for (Extension ext : descr.getExtensions()) {
378: ctx = createConext(3);
379: ctx.put("ext", ext); //$NON-NLS-1$
380: File dir = new File(extsDir, ext.getId());
381: dir.mkdir();
382: processTemplateFile(ctx, "ext.jxp", //$NON-NLS-1$
383: new File(dir, "index.html")); //$NON-NLS-1$
384: }
385: }
386: }
387:
388: private void generateForPluginFragment(final File baseDir,
389: final PluginFragment fragment) throws Exception {
390: final File destDir = new File(baseDir, fragment.getId());
391: destDir.mkdirs();
392: Map<String, Object> ctx = createConext(1);
393: ctx.put("fragment", fragment); //$NON-NLS-1$
394: processTemplateFile(ctx, "fragment.jxp", //$NON-NLS-1$
395: new File(destDir, "index.html")); //$NON-NLS-1$
396: }
397:
398: private Map<String, Object> createConext(final int level) {
399: final Map<String, Object> result = new HashMap<String, Object>();
400: String relativePath = getRelativePath(level);
401: result.put("tool", new Tool(relativePath)); //$NON-NLS-1$
402: result.put("relativePath", relativePath); //$NON-NLS-1$
403: result.put("registry", registry); //$NON-NLS-1$
404: result.put("allPluginDescriptors", allPluginDescriptors); //$NON-NLS-1$
405: result.put("allPluginFragments", allPluginFragments); //$NON-NLS-1$
406: result.put("allExtensionPoints", allExtensionPoints); //$NON-NLS-1$
407: result.put("allExtensions", allExtensions); //$NON-NLS-1$
408: return result;
409: }
410:
411: private Collection<PluginDescriptor> getAllPluginDescriptors() {
412: final List<PluginDescriptor> result = new LinkedList<PluginDescriptor>();
413: result.addAll(registry.getPluginDescriptors());
414: Collections.sort(result, new IdentityComparator());
415: return Collections.unmodifiableCollection(result);
416: }
417:
418: private Collection<PluginFragment> getAllPluginFragments() {
419: final List<PluginFragment> result = new LinkedList<PluginFragment>();
420: result.addAll(registry.getPluginFragments());
421: Collections.sort(result, new IdentityComparator());
422: return Collections.unmodifiableCollection(result);
423: }
424:
425: private Collection<ExtensionPoint> getAllExtensionPoints() {
426: final List<ExtensionPoint> result = new LinkedList<ExtensionPoint>();
427: for (PluginDescriptor descriptor : registry
428: .getPluginDescriptors()) {
429: result.addAll(descriptor.getExtensionPoints());
430: }
431: Collections.sort(result, new IdentityComparator());
432: return Collections.unmodifiableCollection(result);
433: }
434:
435: private Collection<Extension> getAllExtensions() {
436: final List<Extension> result = new LinkedList<Extension>();
437: for (PluginDescriptor descriptor : registry
438: .getPluginDescriptors()) {
439: result.addAll(descriptor.getExtensions());
440: }
441: Collections.sort(result, new IdentityComparator());
442: return Collections.unmodifiableCollection(result);
443: }
444:
445: /**
446: * Utility class to be used from JXP templates.
447: *
448: * @version $Id$
449: */
450: public static final class Tool {
451: private String relativePath;
452:
453: protected Tool(final String aRelativePath) {
454: this .relativePath = aRelativePath;
455: }
456:
457: /**
458: * @param ref
459: * documentation reference element
460: * @return link to be used in "href" attribute
461: */
462: public String getLink(final Documentation.Reference<?> ref) {
463: if (isAbsoluteUrl(ref.getRef())) {
464: return ref.getRef();
465: }
466: String id;
467: Identity idt = ref.getDeclaringIdentity();
468: if (idt instanceof PluginElement) {
469: PluginElement<?> element = (PluginElement) idt;
470: PluginFragment fragment = element
471: .getDeclaringPluginFragment();
472: if (fragment != null) {
473: id = fragment.getId();
474: } else {
475: id = element.getDeclaringPluginDescriptor().getId();
476: }
477: } else {
478: id = idt.getId();
479: }
480: return relativePath + "/" + id + "/extra/" + ref.getRef(); //$NON-NLS-1$ //$NON-NLS-2$
481: }
482:
483: /**
484: * @param url
485: * an URL to check
486: * @return <code>true</code> if given link is an absolute URL
487: */
488: public boolean isAbsoluteUrl(final String url) {
489: try {
490: String protocol = new URL(url).getProtocol();
491: return (protocol != null) && (protocol.length() > 0);
492: } catch (MalformedURLException e) {
493: return false;
494: }
495: }
496:
497: /**
498: * Substitutes all ${relativePath} variables with their values.
499: *
500: * @param text
501: * text to be processed
502: * @return processed documentation text
503: */
504: public String processDocText(final String text) {
505: if ((text == null) || (text.length() == 0)) {
506: return ""; //$NON-NLS-1$
507: }
508: return text.replaceAll("(?d)(?m)\\$\\{relativePath\\}", //$NON-NLS-1$
509: relativePath);
510: }
511: }
512:
513: static final class IdentityComparator implements
514: Comparator<Identity> {
515: /**
516: * @param o1 first object to compare
517: * @param o2 second object to compare
518: * @return comparison result
519: * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
520: */
521: public int compare(final Identity o1, final Identity o2) {
522: return o1.getId().compareTo(o2.getId());
523: }
524: }
525: }
|