/*
* File DocMaker.java
*
* Copyright (C) 2010 Remco Bouckaert remco@cs.auckland.ac.nz
*
* This file is part of BEAST2.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership and licensing.
*
* BEAST 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
* of the License, or (at your option) any later version.
*
* BEAST 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 BEAST; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
package beast.app;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import beast.core.BEASTObject;
import beast.core.Citation;
import beast.core.Description;
import beast.core.Input;
import beast.core.Loggable;
import beast.core.util.Log;
import beast.util.AddOnManager;
/**
* Plug in documentation generator.
* Goes through all plug-ins and generate one page per plug-in.
* <p/>
* Usage: DocMaker <target directory>
* where <target directory> is the place where the HTML files
* should go. Default directory is /tmp.
* *
*/
public class DocMaker {
/**
* output directory *
*/
String m_sDir = "/tmp";
/**
* names of the plug-ins to document *
*/
List<String> m_beastObjectNames;
/**
* map of plug-in name to its derived plug-ins *
*/
HashMap<String, String[]> m_isa;
/**
* map of plug-in name to its ancestor plug-ins *
*/
HashMap<String, List<String>> m_ancestors;
/**
* map of plug-in name to its description (from @Description annotations) *
*/
HashMap<String, String> m_descriptions;
Set<String> m_sLoggables;
BEASTVersion2 version = new BEASTVersion2();
public DocMaker(String[] args) {
this();
if (args.length > 0) {
if (args[0].equals("-javadoc")) {
makeJavaDoc();
System.exit(0);
}
m_sDir = args[0];
}
} // c'tor
public DocMaker() {
// find plug ins to document
m_beastObjectNames = AddOnManager.find(beast.core.BEASTObject.class, AddOnManager.IMPLEMENTATION_DIR);
/** determine hierarchical relation between plug-ins **/
m_isa = new HashMap<>();
m_ancestors = new HashMap<>();
m_descriptions = new HashMap<>();
m_sLoggables = new HashSet<>();
for (String beastObjectName : m_beastObjectNames) {
m_ancestors.put(beastObjectName, new ArrayList<>());
}
for (String beastObjectName : m_beastObjectNames) {
try {
Class<?> _class = Class.forName(beastObjectName);
BEASTObject beastObject = (BEASTObject) _class.newInstance();
String description = getInheritableDescription(beastObject.getClass());
Log.warning.println(beastObjectName + " => " + description);
m_descriptions.put(beastObjectName, description);
String[] implementations = getImplementations(beastObject);
m_isa.put(beastObjectName, implementations);
for (String imp : implementations) {
m_ancestors.get(imp).add(beastObjectName);
}
if (beastObject instanceof Loggable) {
m_sLoggables.add(beastObjectName);
}
} catch (Exception e) {
Log.err.println(beastObjectName + " not documented :" + e.getMessage());
}
}
} // c'tor
/**
* print @Description and Input.description info so that it can
* be inserted in the code before creating Javadoc documentation
* for the Beast II SDK.
*/
void makeJavaDoc() {
for (String beastObjectName : m_beastObjectNames) {
try {
BEASTObject beastObject = (BEASTObject) Class.forName(beastObjectName).newInstance();
Log.info.println(beastObjectName + ":@description:" + beastObject.getDescription());
for (Input<?> input : beastObject.listInputs()) {
Log.info.println(beastObjectName + ":" + input.getName() + ":" + input.getTipText());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* create CSS style sheet for all pages
* @throws FileNotFoundException *
*/
void createCSS() throws FileNotFoundException {
PrintStream out = new PrintStream(m_sDir + "/doc.css");
out.println(getCSS());
out.close();
}
String getCSS() {
return "<!--\n" +
"a.summary-letter {text-decoration: none}\n" +
"blockquote.smallquotation {font-size: smaller}\n" +
"div.display {margin-left: 3.2em}\n" +
"div.example {margin-left: 3.2em}\n" +
"div.indentedblock {margin-left: 3.2em}\n" +
"div.lisp {margin-left: 3.2em}\n" +
"div.smalldisplay {margin-left: 3.2em}\n" +
"div.smallexample {margin-left: 3.2em}\n" +
"div.smallindentedblock {margin-left: 3.2em; font-size: smaller}\n" +
"div.smalllisp {margin-left: 3.2em}\n" +
"kbd {font-style:oblique}\n" +
"pre.display {font-family: inherit}\n" +
"pre.format {font-family: inherit}\n" +
"pre.menu-comment {font-family: serif}\n" +
"pre.menu-preformatted {font-family: serif}\n" +
"pre.smalldisplay {font-family: inherit; font-size: smaller}\n" +
"pre.smallexample {font-size: smaller}\n" +
"pre.smallformat {font-family: inherit; font-size: smaller}\n" +
"pre.smalllisp {font-size: smaller}\n" +
"span.nocodebreak {white-space:nowrap}\n" +
"span.nolinebreak {white-space:nowrap}\n" +
"span.roman {font-family:serif; font-weight:normal}\n" +
"span.sansserif {font-family:sans-serif; font-weight:normal}\n" +
"ul.no-bullet {list-style: none}\n" +
"body {margin-left: 5%; margin-right: 5%;}\n" +
"\n" +
"H1 { \n" +
" background: white;\n" +
" color: rgb(25%, 25%, 25%);\n" +
" font-family: monospace;\n" +
" font-size: xx-large;\n" +
" text-align: center\n" +
"}\n" +
"\n" +
"H2 {\n" +
" background: white;\n" +
" color: rgb(40%, 40%, 40%);\n" +
" font-family: monospace;\n" +
" font-size: x-large;\n" +
" text-align: center\n" +
"}\n" +
"\n" +
"H3 {\n" +
" background: white;\n" +
" color: rgb(40%, 40%, 40%);\n" +
" font-family: monospace;\n" +
" font-size: large\n" +
"}\n" +
"\n" +
"H4 {\n" +
" background: white;\n" +
" color: rgb(40%, 40%, 40%);\n" +
" font-family: monospace\n" +
"}\n" +
"\n" +
"span.samp{font-family: monospace}\n" +
"span.command{font-family: monospace}\n" +
"span.option{font-family: monospace}\n" +
"span.file{font-family: monospace}\n" +
"span.env{font-family: monospace}\n" +
"\n" +
"ul {\n" +
" margin-top: 0.25ex;\n" +
" margin-bottom: 0.25ex;\n" +
"}\n" +
"li {\n" +
" margin-top: 0.25ex;\n" +
" margin-bottom: 0.25ex;\n" +
"}\n" +
"p {\n" +
" margin-top: 0.6ex;\n" +
" margin-bottom: 1.2ex;\n" +
"}\n" +
"caption {\n" +
" font: 20pt Arial, Helvetica, sans-serif;\n" +
" text-align: left;\n" +
" height: 45px;\n" +
" color: #243D02;\n" +
" border-top: 1px solid #243D02;\n" +
"}\n" +
"table, th, td {border: 0px;)\n"
;
}
/**
* create plug in index pages, shown in left frame
* @throws FileNotFoundException *
*/
void createIndex() throws FileNotFoundException {
{
PrintStream out = new PrintStream(m_sDir + "/index.html");
out.println("<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Frameset//EN'\n" +
" 'http://www.w3.org/TR/html4/frameset.dtd'>\n" +
" <HTML>\n" +
" <HEAD>\n" +
" <TITLE>BEAST " + version.getVersionString() + " Documentation</TITLE>\n" +
" </HEAD>\n" +
" <FRAMESET cols='20%, 80%'>\n" +
" <FRAMESET rows='50, 200'>\n" +
// " <FRAME src='http://www.omnomnomnom.com/random/rotate.php' align='center'>\n" +
" <FRAME src='beast.png' align='center'>\n" +
" <FRAME src='contents.html'>\n" +
" </FRAMESET>\n" +
" <FRAME name='display' src='contents.html'>\n" +
" </FRAMESET>\n" +
" </HTML>");
out.close();
}
{
}
{
try {
InputStream in = new FileInputStream(new File("src/beast/app/draw/icons/beast.png"));
OutputStream out = new FileOutputStream(new File(m_sDir + "/beast.png"));
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
} catch (Exception e) {
// ignore exception -- too bad we could not
Log.warning.println("WARNING: something went wrong copying beast.png image:" + e.getMessage());
}
}
PrintStream out = new PrintStream(m_sDir + "/contents.html");
out.println("<html>\n<head><title>BEAST " + version.getVersionString() + " Documentation index</title>\n" +
"<link rel='StyleSheet' href='doc.css' type='text/css'>\n" +
"</head>\n");
out.println("<body>\n");
out.println("<h1>BEAST " + version.getVersionString() + " Documentation index</h1>\n");
String prev = null;
String prevPackage = null;
for (String beastObjectName : m_beastObjectNames) {
String next = beastObjectName.substring(0, beastObjectName.indexOf('.'));
if (prev != null && !next.equals(prev)) {
out.println("<hr/>");
}
prev = next;
String name = beastObjectName.substring(beastObjectName.lastIndexOf('.') + 1);
String packageName = beastObjectName.substring(0, beastObjectName.lastIndexOf('.'));
// count nr of packages
int i = 0;
while (beastObjectName.indexOf('.', i) > 0) {
name = "." + name;
i = beastObjectName.indexOf('.', i) + 1;
}
Log.warning.println(name + " <= " + beastObjectName);
if (prevPackage == null || !packageName.equals(prevPackage)) {
out.println("<span style='color:grey'>" + packageName + "</span><br/>");
}
out.println("<a href='" + beastObjectName + ".html' target='display'>" + name + "</a><br/>");
prevPackage = packageName;
}
out.println("</body>\n");
out.println("</html>\n");
out.close();
} // createIndex
/**
* Find all beastObjects that are derived from given beastObject *
*/
String[] getImplementations(BEASTObject beastObject) {
String name = beastObject.getClass().getName();
List<String> implementations = new ArrayList<>();
for (String beastObjectName : m_beastObjectNames) {
try {
if (!beastObjectName.equals(name) && beastObject.getClass().isAssignableFrom(Class.forName(beastObjectName))) {
implementations.add(beastObjectName);
}
} catch (ClassNotFoundException e) {
}
}
return implementations.toArray(new String[0]);
}
/**
* Extract description from @Description annotation
* but only if the description is inheritable *
*/
String getInheritableDescription(Class<?> beastObjectClass) {
String str = "";
Class<?> superClass = beastObjectClass.getSuperclass();
if (superClass != null) {
String superName = getInheritableDescription(superClass);
if (superName != null) {
str += superName + "<br/>";
}
}
Annotation[] classAnnotations = beastObjectClass.getAnnotations();
for (Annotation annotation : classAnnotations) {
if (annotation instanceof Description) {
Description description = (Description) annotation;
if (description.isInheritable()) {
str += description.value();
} else {
return null;
}
}
}
return str;
}
/**
* Create page for individual plug-in
* @throws FileNotFoundException *
*/
void createBEASTObjectPage(String beastObjectName) throws FileNotFoundException {
PrintStream out = new PrintStream(m_sDir + "/" + beastObjectName + ".html");
try {
out.print(getHTML(beastObjectName, true));
} catch (Exception e) {
Log.warning.println("Page creation failed for " + beastObjectName + ": " + e.getMessage());
}
out.close();
} // createBEASTObjectPage
public String getHTML(String beastObjectName, boolean useExternalStyleSheet) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
StringBuffer buf = new StringBuffer();
buf.append("<html>\n<head>\n<title>BEAST " + version.getVersionString() + " Documentation: " + beastObjectName + "</title>\n");
if (useExternalStyleSheet) {
buf.append("<link rel='StyleSheet' href='/tmp/styles.css' type='text/css'>\n");
} else {
buf.append("<style type='text/css'>\n");
buf.append(getCSS());
buf.append("</style>\n");
}
buf.append("</head>\n");
buf.append("<body>\n");
buf.append("<h1>BEAST " + version.getVersionString() + " Documentation: " + beastObjectName + "</h1>\n");
BEASTObject beastObject = (BEASTObject) Class.forName(beastObjectName).newInstance();
// show all implementation of this plug-in
String[] implementations = m_isa.get(beastObjectName);
if (implementations == null) {
// this class is not documented, perhaps outside ClassDiscover path?
buf.append("No documentation available for " + beastObjectName + ". Perhaps it is not in the ClassDiscovery path\n");
buf.append("</body>\n");
buf.append("</html>\n");
return buf.toString();
}
if (implementations.length > 0) {
buf.append("<table border='1px'>\n");
buf.append("<thead><tr><td>implemented by the following</td></tr></thead>\n");
for (String imp : implementations) {
buf.append("<tr><td><a href='" + imp + ".html'>" + imp + "</a></td></tr>\n");
}
buf.append("</table>\n");
}
// show descriptions of all plug-ins implemented by this plug in...
buf.append("<p>" + m_descriptions.get(beastObjectName) + "</p>\n");
// show citation (if any)
for (Citation citation : beastObject.getCitationList()) {
buf.append("<h2>Reference:</h2><p>" + citation.value() + "</p>\n");
if (citation.DOI().length() > 0) {
buf.append("<p><a href=\"http://dx.doi.org/" + citation.DOI() + "\">doi:" + citation.DOI() + "</a></p>\n");
}
}
// show if this is Loggable
if (m_sLoggables.contains(beastObjectName)) {
buf.append("<p>Logable:");
buf.append(" yes, this can be used in a log.");
buf.append("</p>\n");
// } else {
// buf.append(" no, this cannot be used in a log.");
}
// show short list its inputs
buf.append("<h2>Inputs:</h2>\n");
buf.append("<p>");
List<Input<?>> inputs = beastObject.listInputs();
for (Input<?> input : inputs) {
buf.append("<a href='#" + input.getName()+"'>" + input.getName() + "</a>, ");
}
buf.delete(buf.length() - 3, buf.length()-1);
buf.append("</p>\n");
// list its inputs
if (inputs.size() == 0) {
buf.append("<none>");
}
for (Input<?> input : inputs) {
buf.append("<p> </p>");
buf.append("<table id='" + input.getName() + "' border='1px' width='90%'>\n");
buf.append("<caption>" + input.getName() + "</caption>\n");
buf.append("<thead><tr bgcolor='#AAAAAA'><td>type: " + getType(beastObject, input.getName()) + "</td></tr></thead>\n");
buf.append("<tr><td>" + input.getTipText() + "</td></tr>\n");
buf.append("<tr><td>\n");
switch (input.getRule()) {
case OPTIONAL:
buf.append("Optional input");
if (input.defaultValue != null) {
if (input.defaultValue instanceof Integer ||
input.defaultValue instanceof Double ||
input.defaultValue instanceof Boolean ||
input.defaultValue instanceof String) {
buf.append(". Default: " + input.defaultValue.toString());
}
}
break;
case REQUIRED:
buf.append("Required input");
break;
case XOR:
buf.append("Either this, or " + input.getOther().getName() + " needs to be specified");
break;
case FORBIDDEN:
buf.append("Forbidden: must not be specified");
break;
}
buf.append("</td></tr>\n");
buf.append("</table>\n");
}
buf.append("</body>\n");
buf.append("</html>\n");
return buf.toString();
} // getHTML
/**
* determine type of input of a plug in with name name
*/
String getType(BEASTObject beastObject, String name) {
try {
Field[] fields = beastObject.getClass().getFields();
for (int i = 0; i < fields.length; i++) {
if (fields[i].getType().isAssignableFrom(Input.class)) {
final Input<?> input = (Input<?>) fields[i].get(beastObject);
if (input.getName().equals(name)) {
Type t = fields[i].getGenericType();
Type[] genericTypes = ((ParameterizedType) t).getActualTypeArguments();
if (input.getType() != null) {
return (input.getType().isAssignableFrom(BEASTObject.class) ? "<a href='" + input.getType().getName() + ".html'>" : "") +
input.getType().getName() +
(input.get() != null && input.get() instanceof List<?> ? "***" : "") +
(input.getType().isAssignableFrom(BEASTObject.class) ? "</a>" : "");
}
if (input.get() != null && input.get() instanceof List<?>) {
Type[] genericTypes2 = ((ParameterizedType) genericTypes[0]).getActualTypeArguments();
Class<?> _class = (Class<?>) genericTypes2[0];
Object o = null;
try {
o = Class.forName(_class.getName()).newInstance();
} catch (Exception e) {
}
if (o != null && o instanceof BEASTObject) {
return "<a href='" + _class.getName() + ".html'>" + _class.getName() + "***</a>";
} else {
return _class.getName() + "***";
}
} else {
Class<?> genericType = (Class<?>) genericTypes[0];
Class<?> _class = genericType;
Object o = null;
try {
o = Class.forName(_class.getName()).newInstance();
} catch (Exception e) {
}
if (o != null && o instanceof BEASTObject) {
return "<a href='" + _class.getName() + ".html'>" + _class.getName() + "</a>";
} else {
return _class.getName();
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return "?";
} // getType
/**
* generate set of documents for plug ins
* including index page + frame
* individual pages for each plug in
* *
* @throws FileNotFoundException
*/
public void generateDocs() throws FileNotFoundException {
// first, produce CSS & index page
createCSS();
createIndex();
// next, produce pages for individual plug-ins
for (String beastObjectName : m_beastObjectNames) {
createBEASTObjectPage(beastObjectName);
}
} // generateDocs
/**
* Usage: DocMaker <target directory>
* where <target directory> is the place where the HTML files
* should go. Default directory is /tmp
*/
public static void main(String[] args) {
try {
System.err.println("Producing documentation...");
AddOnManager.loadExternalJars();
DocMaker b = new DocMaker(args);
b.generateDocs();
System.err.println("Done!!!");
} catch (Exception e) {
e.printStackTrace();
}
} // main
} // BeastDocMaker