/*
* $Id$
*
* Copyright (C) 2003-2014 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.build.documentation;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.types.FileSet;
import org.jnode.build.AbstractPluginTask;
import org.jnode.plugin.ConfigurationElement;
import org.jnode.plugin.Extension;
import org.jnode.plugin.ExtensionPoint;
import org.jnode.plugin.Library;
import org.jnode.plugin.PluginDescriptor;
import org.jnode.plugin.PluginPrerequisite;
/**
* @author Martin Husted Hartvig (hagar@jnode.org)
*/
public class PluginDocumentationTask extends AbstractPluginTask {
private final LinkedList<FileSet> descriptorSets = new LinkedList<FileSet>();
private File destdir;
private boolean tree = false;
private final SortedMap<String, PluginData> descriptors = new TreeMap<String, PluginData>();
private static final String DOCHEADER = "JNode plugin documentation";
private static final String PLUGINS_SUBDIR = "plugins";
private static final String EXT = ".html";
private static final String ALL_FRAME = "allFrame";
private static final String ALL_FILE = "all-frame" + EXT;
private static final String OVERVIEW_SUMMARY_FILE = "overview-summary" + EXT;
private static final String OVERVIEW_SUMMARY_FRAME = "overviewSummary";
private static final String OVERVIEW_PACKAGE_FILE = "overview-package" + EXT;
private static final String OVERVIEW_LICENSE_FILE = "overview-license" + EXT;
private static final String OVERVIEW_TREE_FILE = "overview-tree" + EXT;
private static final String OVERVIEW_TREE_DOTFILE = "overview-tree.dot";
private static final String OVERVIEW_TREE_PNGFILE = "overview-tree.png";
private static final String CSS_FILE = "index.css";
private static final String INDEX = "index" + EXT;
private static final ToolbarEntry[] TOOLBAR_ENTRIES = {
new ToolbarEntry("Overview", OVERVIEW_SUMMARY_FILE),
new ToolbarEntry("Java packages", OVERVIEW_PACKAGE_FILE),
new ToolbarEntry("Licenses", OVERVIEW_LICENSE_FILE),
new ToolbarEntry("Tree", OVERVIEW_TREE_FILE)};
private static final LicenseEntry[] KNOWN_LICENSES = {
new LicenseEntry("bsd",
"http://opensource.org/licenses/bsd-license.php"),
new LicenseEntry("gpl",
"http://opensource.org/licenses/gpl-license.php"),
new LicenseEntry("lgpl",
"http://opensource.org/licenses/lgpl-license.php"),
new LicenseEntry("cpl", "http://opensource.org/licenses/cpl1.0.php"),
new LicenseEntry("classpath",
"http://www.gnu.org/software/classpath/license.html"),
new LicenseEntry("apache",
"http://opensource.org/licenses/apachepl.php"),
new LicenseEntry("apache2.0",
"http://www.apache.org/licenses/LICENSE-2.0"),
new LicenseEntry("zlib",
"http://www.opensource.org/licenses/zlib-license.html")};
public FileSet createDescriptors() {
final FileSet fs = new FileSet();
descriptorSets.add(fs);
return fs;
}
/**
* Get a list of all included descriptor files.
*
* @return the descriptor files.
*/
protected File[] getDescriptorFiles() {
final List<File> files = new ArrayList<File>();
for (FileSet fs : descriptorSets) {
final DirectoryScanner ds = fs.getDirectoryScanner(getProject());
final String[] filesNames = ds.getIncludedFiles();
for (String filename : filesNames) {
files.add(new File(ds.getBasedir(), filename));
}
}
return files.toArray(new File[files.size()]);
}
/**
* @throws org.apache.tools.ant.BuildException
*
* @see org.apache.tools.ant.Task#execute()
*/
public void execute() throws BuildException {
descriptors.clear();
if (descriptorSets.isEmpty()) {
throw new BuildException("At at least 1 descriptorset element");
}
if (destdir == null) {
throw new BuildException("The todir attribute must be set");
}
if (getPluginDir() == null) {
throw new BuildException("The pluginDir attribute must be set");
}
if (!destdir.exists()) {
destdir.mkdirs();
} else if (!destdir.isDirectory()) {
throw new BuildException("destdir must be a directory");
}
try {
// Load the plugin data
for (File descrFile : getDescriptorFiles()) {
loadPluginData(descriptors, descrFile);
}
// Write the plugin documentation
for (PluginData data : descriptors.values()) {
writePluginDocumentation(data);
}
// Write the index files
writeCSS();
writeAllFrame(descriptors, DOCHEADER);
writeOverviewSummary(descriptors, DOCHEADER);
writeOverviewPackage(descriptors, DOCHEADER);
writeOverviewLicense(descriptors, DOCHEADER);
if (isTree()) {
writeOverviewTree(descriptors, DOCHEADER);
}
writeIndex(descriptors, DOCHEADER);
} catch (IOException ex) {
throw new BuildException(ex);
}
}
private void writeCSS() throws IOException {
final File file = new File(getDestdir(), CSS_FILE);
final PrintWriter out = new PrintWriter(new FileWriter(file));
try {
out.println("body { background-color: #FFFFFF }");
out
.println(".frameItem { font-size: 80%; font-family: Verdana, Arial, sans-serif }");
out.println(".summaryTable { border: 1px solid; }");
out
.println(".summaryTableHdr { background: #CCCCFF; font-size: 120%; font-weight: bold; }");
out
.println(".toolbar { background: #EEEEFF; font-size: 110%; font-weight: bold; }");
out.println(".toolbarItem:link { text-decoration: none; }");
out.println(".toolbarItem:visited { text-decoration: none; }");
} finally {
out.close();
}
}
private void writeIndex(Map<String, PluginData> descriptors, String title)
throws IOException {
final File file = new File(getDestdir(), INDEX);
final PrintWriter out = new PrintWriter(new FileWriter(file));
try {
out.println("<html><head>");
out.println("<title>" + title + "</title>");
out.println("</head>");
out.println("<frameset cols='20%,80%' title=''>");
out.println("<frame src='" + ALL_FILE + "' name='" + ALL_FRAME
+ "' scrolling='yes'>");
out.println("<frame src='" + OVERVIEW_SUMMARY_FILE + "' name='"
+ OVERVIEW_SUMMARY_FRAME + "' scrolling='yes'>");
out.println("</frameset>");
out.println("</body></html>");
} finally {
out.close();
}
}
private void writeOverviewSummary(Map<String, PluginData> descriptors,
String title) throws IOException {
final File file = new File(getDestdir(), OVERVIEW_SUMMARY_FILE);
final PrintWriter out = new PrintWriter(new FileWriter(file));
try {
out.println("<html><head>");
out.println("<title>" + title + "</title>");
out.println("<link rel='stylesheet' TYPE='text/css' href='"
+ CSS_FILE + "'>");
out.println("</head><body>");
addToolbar(out, "");
addSummaryTableHdr(out, "Plugins");
for (PluginData data : descriptors.values()) {
final String link = "<a href='" + data.getHtmlFile() + "'>"
+ data.getDescriptor().getId() + "</a>";
addTableRow(out, link, data.getDescriptor().getName());
}
endSummaryTableHdr(out);
addFooter(out);
out.println("</body></html>");
} finally {
out.close();
}
}
private void writeOverviewPackage(Map<String, PluginData> descriptors,
String title) throws IOException {
final File file = new File(getDestdir(), OVERVIEW_PACKAGE_FILE);
final PrintWriter out = new PrintWriter(new FileWriter(file));
try {
out.println("<html><head>");
out.println("<title>" + title + "</title>");
out.println("<link rel='stylesheet' TYPE='text/css' href='"
+ CSS_FILE + "'>");
out.println("</head><body>");
addToolbar(out, "");
// Build the packages list'
final List<PackageData> pkgs = new ArrayList<PackageData>();
for (PluginData data : descriptors.values()) {
if (data.getDescriptor().getRuntime() != null) {
for (Library lib : data.getDescriptor().getRuntime()
.getLibraries()) {
for (String exp : lib.getExports()) {
if (!exp.equals("*")) {
if (exp.endsWith(".*")) {
exp = exp.substring(0, exp.length() - 2);
}
pkgs.add(new PackageData(exp, data));
}
}
}
}
}
Collections.sort(pkgs);
addSummaryTableHdr(out, "Java packages");
for (PackageData pkg : pkgs) {
final PluginData data = pkg.getPlugin();
final String link = "<a href='" + data.getHtmlFile() + "'>"
+ data.getDescriptor().getId() + "</a>";
addTableRow(out, pkg.getPackageName(), link);
}
endSummaryTableHdr(out);
addFooter(out);
out.println("</body></html>");
} finally {
out.close();
}
}
/**
* Generate an overview package that list all license and the plugins that
* use that.
*
* @param descriptors
* @param title
* @throws IOException
*/
private void writeOverviewLicense(Map<String, PluginData> descriptors,
String title) throws IOException {
final File file = new File(getDestdir(), OVERVIEW_LICENSE_FILE);
final PrintWriter out = new PrintWriter(new FileWriter(file));
try {
out.println("<html><head>");
out.println("<title>" + title + "</title>");
out.println("<link rel='stylesheet' TYPE='text/css' href='"
+ CSS_FILE + "'>");
out.println("</head><body>");
addToolbar(out, "");
// Build the packages list'
final SortedMap<LicenseEntry, List<PluginData>> lics = new TreeMap<LicenseEntry, List<PluginData>>();
for (PluginData data : descriptors.values()) {
final LicenseEntry lic = findLicense(data.getDescriptor());
List<PluginData> list = lics.get(lic);
if (list == null) {
list = new ArrayList<PluginData>();
lics.put(lic, list);
}
list.add(data);
}
addSummaryTableHdr(out, "Licenses");
for (Map.Entry<LicenseEntry, List<PluginData>> entry : lics.entrySet()) {
final StringBuilder sb = new StringBuilder();
for (PluginData data : entry.getValue()) {
final String link = "<a href='" + data.getHtmlFile() + "'>"
+ data.getDescriptor().getId() + "</a><br/>";
sb.append(link);
}
addTableRow(out, entry.getKey().toHtmlString(), sb.toString());
}
endSummaryTableHdr(out);
addFooter(out);
out.println("</body></html>");
} finally {
out.close();
}
}
/**
* Generate an overview package that list all license and the plugins that
* use that.
*
* @param descriptors
* @param title
* @throws IOException
*/
private void writeOverviewTree(Map<String, PluginData> descriptors,
String title) throws IOException {
final File file = new File(getDestdir(), OVERVIEW_TREE_FILE);
final File dotFile = new File(getDestdir(), OVERVIEW_TREE_DOTFILE);
final File pngFile = new File(getDestdir(), OVERVIEW_TREE_PNGFILE);
final PrintWriter out = new PrintWriter(new FileWriter(file));
try {
out.println("<html><head>");
out.println("<title>" + title + "</title>");
out.println("<link rel='stylesheet' TYPE='text/css' href='"
+ CSS_FILE + "'>");
out.println("</head><body>");
addToolbar(out, "");
// Build dot file
final DotBuilder dot = new DotBuilder(dotFile, pngFile);
for (PluginData data : descriptors.values()) {
dot.add(data);
}
dot.close();
addSummaryTableHdr(out, "Tree");
addTableRow(out, "<img src='" + OVERVIEW_TREE_PNGFILE + "'>");
endSummaryTableHdr(out);
addFooter(out);
out.println("</body></html>");
} finally {
out.close();
}
}
private void writeAllFrame(Map<String, PluginData> descriptors, String title)
throws IOException {
final File file = new File(getDestdir(), ALL_FILE);
final PrintWriter out = new PrintWriter(new FileWriter(file));
try {
out.println("<html><head>");
out.println("<title>" + title + "</title>");
out.println("<link rel='stylesheet' TYPE='text/css' href='"
+ CSS_FILE + "'>");
out.println("</head><body>");
out.println("<table border='0' width='100%'><tr><td nowrap>");
out.println("<b>All plugins</a>");
out.println("</td></tr></table><p/>");
out.println("<table border='0' width='100%'><tr><td nowrap>");
for (PluginData data : descriptors.values()) {
out.println("<a class='frameItem' href='" + data.getHtmlFile()
+ "' target='" + OVERVIEW_SUMMARY_FRAME + "'>");
out.println(data.getDescriptor().getId());
out.println("</a><br/>");
}
out.println("</td></tr></table>");
out.println("</body></html>");
} finally {
out.close();
}
}
private void loadPluginData(Map<String, PluginData> descriptors,
File descrFile) {
final PluginDescriptor descr = readDescriptor(descrFile);
final String fullId = descr.getId() + "_" + descr.getVersion();
if (descriptors.containsKey(fullId)) {
final PluginData otherData = descriptors.get(fullId);
throw new BuildException("Same id(" + fullId + ") for 2 plugins: "
+ otherData.getDescriptorFile() + ", " + descrFile);
}
// Create & store plugin data
final PluginData data = new PluginData(descrFile, fullId);
data.setDescriptor(descr);
final String fname = PLUGINS_SUBDIR + "/" + descr.getId() + EXT;
data.setHtmlFile(fname);
descriptors.put(fullId, data);
}
/**
* Build the documentation for the plugin.
*
* @param data the plugin's data
* @throws BuildException
* @throws IOException
* @throws SecurityException
*/
protected void writePluginDocumentation(PluginData data)
throws BuildException, IOException {
final File file = new File(getDestdir(), data.getHtmlFile());
file.getParentFile().mkdirs();
final PrintWriter out = new PrintWriter(new FileWriter(file));
try {
final PluginDescriptor descr = data.getDescriptor();
out.println("<html><head>");
out.println("<title>" + descr.getId() + "</title>");
out.println("<link rel='stylesheet' TYPE='text/css' href='../"
+ CSS_FILE + "'>");
out.println("</head><body>");
addToolbar(out, "../");
final String title;
if (descr.isFragment()) {
title = "Fragment summary";
} else {
title = "Plugin summary";
}
addSummaryTableHdr(out, title);
addTableRow(out, "Id", descr.getId());
addTableRow(out, "Name", descr.getName());
addTableRow(out, "Version", descr.getVersion().toString());
addTableRow(out, "Provider", formatProvider(descr));
addTableRow(out, "License", formatLicense(descr));
addTableRow(out, "Plugin class",
descr.hasCustomPluginClass() ? descr
.getCustomPluginClassName() : "-");
addTableRow(out, "Flags", formatFlags(descr));
endSummaryTableHdr(out);
if (descr.getPrerequisites().length > 0) {
addSummaryTableHdr(out, "Requires");
for (PluginPrerequisite prereq : descr.getPrerequisites()) {
final String href = prereq.getPluginReference().getId() + EXT;
final PluginData prereqData = getPluginData(prereq
.getPluginReference().getId());
final String name = (prereqData != null) ? prereqData
.getDescriptor().getName() : "?";
addTableRow(out, "<a href='" + href + "'>"
+ prereq.getPluginReference().getId() + "</a>", name);
}
endSummaryTableHdr(out);
}
final List<PluginData> requiredBy = getRequiredBy(descr.getId());
if (!requiredBy.isEmpty()) {
addSummaryTableHdr(out, "Required by");
for (PluginData reqBy : requiredBy) {
final String id = reqBy.getDescriptor().getId();
final String href = id + EXT;
final String name = reqBy.getDescriptor().getName();
addTableRow(out, "<a href='" + href + "'>" + id + "</a>",
name);
}
endSummaryTableHdr(out);
}
if (descr.getRuntime() != null) {
addSummaryTableHdr(out, "Libraries");
for (Library library : descr.getRuntime().getLibraries()) {
final String libName = library.getName();
final StringBuilder sb = new StringBuilder();
for (String export : library.getExports()) {
sb.append(export);
sb.append("<br/>");
}
for (String exclude : library.getExcludes()) {
sb.append("exclude: ");
sb.append(exclude);
sb.append("<br/>");
}
addTableRow(out, libName, sb.toString());
}
endSummaryTableHdr(out);
}
final ExtensionPoint[] epList = descr.getExtensionPoints();
if ((epList != null) && (epList.length > 0)) {
addSummaryTableHdr(out, "Extension points");
for (ExtensionPoint ep : epList) {
addTableRow(out, ep.getSimpleIdentifier(), ep.getName());
}
endSummaryTableHdr(out);
}
final Extension[] extList = descr.getExtensions();
if ((extList != null) && (extList.length > 0)) {
addSummaryTableHdr(out, "Extensions");
for (Extension ext : extList) {
final StringBuilder sb = new StringBuilder();
for (ConfigurationElement cfg : ext
.getConfigurationElements()) {
format(cfg, sb, "");
sb.append("<br/>");
}
addTableRow(out, format(ext), sb.toString());
}
endSummaryTableHdr(out);
}
addFooter(out);
out.println("</body></html>");
} finally {
out.close();
}
}
private void format(ConfigurationElement cfg, StringBuilder out,
String indent) {
final ConfigurationElement[] children = cfg.getElements();
final boolean hasChildren = ((children != null) && (children.length > 0));
out.append(indent);
out.append("<");
out.append(cfg.getName());
for (String key : cfg.attributeNames()) {
out.append(' ');
out.append(key);
out.append("='<i>");
out.append(cfg.getAttribute(key));
out.append("</i>\'");
}
if (hasChildren) {
out.append(">");
for (ConfigurationElement child : children) {
format(child, out, indent + "&nbs ");
out.append("<br/>");
}
} else {
out.append("/>");
}
}
private String format(Extension ext) {
final PluginData epPlugin = getPluginData(ext
.getExtensionPointPluginId());
if (epPlugin != null) {
final String href = ext.getExtensionPointPluginId() + EXT;
return "<a href='" + href + "'>"
+ ext.getExtensionPointUniqueIdentifier() + "</a>";
} else {
return ext.getExtensionPointUniqueIdentifier();
}
}
private String formatFlags(PluginDescriptor descr) {
final StringBuilder sb = new StringBuilder();
if (descr.isSystemPlugin()) {
sb.append("system");
}
if (descr.isAutoStart()) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append("auto-start");
}
if (sb.length() > 0) {
return sb.toString();
} else {
return "-";
}
}
private LicenseEntry findLicense(PluginDescriptor descr) {
final String name = descr.getLicenseName();
final String url = descr.getLicenseUrl();
if ((name != null) && (url != null)) {
return new LicenseEntry(name, url);
} else if (name != null) {
for (LicenseEntry lic : KNOWN_LICENSES) {
if (lic.getName().equalsIgnoreCase(name)) {
return lic;
}
}
return new LicenseEntry(name, null);
} else {
return LicenseEntry.UNKNOWN;
}
}
private String formatLicense(PluginDescriptor descr) {
return findLicense(descr).toHtmlString();
}
private String formatProvider(PluginDescriptor descr) {
final String name = descr.getProviderName();
final String url = descr.getProviderUrl();
if ((name != null) && (url != null)) {
return "<a href='" + url + "'>" + name + "</a>";
} else if (name != null) {
return name;
} else {
return "-";
}
}
private void addSummaryTableHdr(PrintWriter out, String title) {
out
.println("<table class='summaryTable' border='1' cellpadding='3' cellspacing='0' width='100%'>");
out.println("<tr><td class='summaryTableHdr' colspan='2'>");
out.println(title);
out.println("</td></tr>");
}
private void addToolbar(PrintWriter out, String base) {
out
.println("<table class='toolbar' border='0' cellpadding='3' cellspacing='0' width='100%'>");
out.println("<tr>");
for (ToolbarEntry tbe : TOOLBAR_ENTRIES) {
if (!tbe.getHref().equals(OVERVIEW_TREE_FILE) || isTree()) {
out.print("<td nowrap>");
out.print("<a href='" + base + tbe.getHref()
+ "' class='toolbarItem'>");
out.print(tbe.getTitle());
out.println("</a></td>");
}
}
out.println("</td></tr></table>");
out.println("<p/>");
}
private void addFooter(PrintWriter out) {
out.println("<hr>");
out.println("<font size='-1'>");
out.println("This file is generated on " + new Date());
out.println("<p/>");
out
.println("For more info visit <a href='http://jnode.org' target='_top'>http://jnode.org</a>");
out.println("</font>");
}
private void endSummaryTableHdr(PrintWriter out) {
out.println("</table>");
out.println("<p/>");
}
private void addTableRow(PrintWriter out, String... cols) {
out.println("<tr>");
for (String col : cols) {
out.println("<td valign='top'>");
out.println(col);
out.println("</td>");
}
out.println("</tr>");
}
/**
* @return Returns the destdir.
*/
public final File getDestdir() {
return this.destdir;
}
/**
* @param destdir The destdir to set.
*/
public final void setDestdir(File destdir) {
this.destdir = destdir;
}
private PluginData getPluginData(String id) {
for (PluginData data : descriptors.values()) {
if (data.getDescriptor().getId().equals(id)) {
return data;
}
}
return null;
}
/**
* Gets a list of all plugins that are require the given plugin id.
*
* @param id
* @return
*/
private List<PluginData> getRequiredBy(String id) {
final ArrayList<PluginData> list = new ArrayList<PluginData>();
for (PluginData data : descriptors.values()) {
final PluginPrerequisite[] reqs = data.getDescriptor()
.getPrerequisites();
if ((reqs != null) && (reqs.length > 0)) {
for (PluginPrerequisite req : reqs) {
if (req.getPluginReference().getId().equals(id)) {
list.add(data);
break;
}
}
}
}
return list;
}
/**
* @return Returns the tree.
*/
public final boolean isTree() {
return tree;
}
/**
* @param tree The tree to set.
*/
public final void setTree(boolean tree) {
this.tree = tree;
}
}