// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2009-2011 Google, All Rights reserved
// Copyright 2011-2012 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0
package com.google.appinventor.components.scripts;
import com.google.appinventor.components.common.ComponentCategory;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import java.io.IOException;
import java.io.Writer;
import java.util.Map;
/**
* <p>Generates user-level HTML documentation for Young Android components into a
* single html file.</p>
*
* <p>Historical note: This was once an abstract superclass of multiple documentation
* generators (one single-page, one multi-page). If there is a need to make it so
* again, change the private output methods back to protected, and override them as
* desired in concrete subclasses.</p>
*
* @author spertus@google.com (Ellen Spertus)
*/
public class DocumentationGenerator extends ComponentProcessor {
private static final String OUTPUT_FILE_NAME = "component-doc.html";
/**
* Returns string introducing a component.
*/
private String getComponentOutputString(String componentName, String componentDescription) {
return String.format("\n<h2 id=\"%1$s\">%1$s</h2>\n\n" +
"<p>%2$s</p>\n\n",
componentName, componentDescription);
}
/**
* Returns string describing a given property.
*/
private String getPropertyDefinition(String name, String description,
boolean isUserVisible, boolean isReadable, boolean isWritable) {
if (!isUserVisible) {
return String.format(" <dt><code>%s</code> (designer only)</dt>\n <dd>%s</dd>\n",
name, description);
}
else if (isReadable && !isWritable) {
return String.format(" <dt><code><em>%s</em></code></dt>\n <dd>%s</dd>\n",
name, description);
}
return String.format(" <dt><code>%s</code></dt>\n <dd>%s</dd>\n",
name, description);
}
/**
* Returns string to summarize a specific event for a component.
*/
private String getEventDefinition(String name, String parameters, String description) {
return String.format(" <dt><code>%s(%s)</code></dt>\n <dd>%s</dd>\n",
name, parameters, description);
}
/**
* Returns string to summarize a specific method for a component.
* If the method is void, <code>returnType</code> will be the empty string.
*/
private String getMethodDefinition(String name, String parameters, String returnType,
String description) {
return String.format(" <dt><code>%s%s%s(%s)</code></dt>\n <dd>%s</dd>\n",
returnType, returnType.isEmpty() ? "" : " ",
name, parameters, description);
}
protected final void outputResults() throws IOException {
// Begin writing output file.
FileObject src = createOutputFileObject(OUTPUT_FILE_NAME);
Writer writer = src.openWriter();
// Output table at top showing components by category.
outputCategories(writer);
// Components are already sorted.
for (Map.Entry<String, ComponentInfo> entry : components.entrySet()) {
ComponentInfo component = entry.getValue();
outputComponent(writer, component);
}
// Close output file
writer.flush();
writer.close();
messager.printMessage(Diagnostic.Kind.NOTE, "Wrote file " + src.toUri());
}
/**
* Outputs all information for the given component, including its
* name, description, properties, events, and methods.
*
* @param writer the destination for the page
* @param component the component to document
*/
private void outputComponent(Writer writer, ComponentInfo component) throws IOException {
// Output component name and description.
writer.write(getComponentOutputString(component.name,
component.description));
// Output properties, events, and methods.
outputProperties(writer, component);
outputEvents(writer, component);
outputMethods(writer, component);
}
// Output table showing categories (e.g., "Sensors") and components in them.
// This hardcodes a two-column table as output with certain categories in the
// left column and certain other categories in the right column.
private void outputCategories(Writer writer)
throws java.io.IOException {
// Output table header
writer.write("<table style=\"border-color: rgb(136, 136, 136); border-width: 0px; " +
"border-collapse: collapse;\" border=\"0\" bordercolor=\"#888888\" " +
"cellpadding=\"5\" cellspacing=\"5\">\n");
writer.write("<tbody valign=\"top\">\n");
writer.write("<tr>\n");
// Specify which categories are in which output column.
final ComponentCategory[][] categories = {
// Column one categories
{
ComponentCategory.USERINTERFACE,
ComponentCategory.LAYOUT,
ComponentCategory.MEDIA,
ComponentCategory.ANIMATION,
ComponentCategory.SOCIAL
},
// Column two categories
{
ComponentCategory.STORAGE,
ComponentCategory.CONNECTIVITY,
ComponentCategory.SENSORS,
ComponentCategory.LEGOMINDSTORMS,
//ComponentCategory.EXPERIMENTAL
}
};
// Output the body of the table.
for (int column = 0; column < java.lang.reflect.Array.getLength(categories); column++) {
writer.write("<td>");
for (int row = 0; row < java.lang.reflect.Array.getLength(categories[column]); row++) {
// Output the category header.
String categoryName = categories[column][row].getName();
writer.write(String.format("<b><font size=\"5\">%s</font></b>\n<ul>\n",
categoryName.replace("\u00AE", "©")));
// Output the components with this category. This algorithm for getting
// components by category has poor complexity performance but is probably
// more efficient in practice than maintaining a hash table mapping
// categories to components.
for (Map.Entry<String, ComponentInfo> entry : components.entrySet()) {
ComponentInfo component = entry.getValue();
if (categoryName.equals(component.getCategory())) {
writer.write(String.format(" <li><a href=\"#%1$s\">%1$s</a></li>\n",
component.name));
}
}
writer.write("</ul>\n");
}
writer.write("</td>\n");
// For aesthetic purposes, output empty column between category columns.
writer.write("<td style=\"width: 60px;\"><b><font size=\"5\"><br/></font></b></td>");
}
// Write table footer
writer.write("</tr>\n</tbody>\n</table>\n");
}
private void outputProperties(Writer writer, ComponentInfo component)
throws java.io.IOException {
writer.write("<h3>Properties</h3>\n");
// Only list properties that are user-visible or designer properties.
boolean definitionWritten = false;
for (Map.Entry<String, Property> entry : component.properties.entrySet()) {
Property property = entry.getValue();
if (property.isDeprecated()) continue;
if (property.isUserVisible() || component.designerProperties.containsKey(property.name)) {
if (!definitionWritten) {
writer.write("<dl>\n");
}
writer.write(getPropertyDefinition(property.name,
property.getDescription(),
property.isUserVisible(),
property.isReadable(),
property.isWritable()));
definitionWritten = true;
}
}
if (definitionWritten) {
writer.write("</dl>\n\n");
} else {
writer.write("none\n\n");
}
}
private void outputEvents(Writer writer, ComponentInfo component) throws java.io.IOException {
writer.write("<h3>Events</h3>\n");
// Only list events that are user-visible.
boolean definitionWritten = false;
for (Map.Entry<String, Event> entry : component.events.entrySet()) {
Event event = entry.getValue();
if (event.deprecated) continue;
if (event.userVisible) {
if (!definitionWritten) {
writer.write("<dl>\n");
}
writer.write(getEventDefinition(event.name, event.toParameterString(), event.description));
definitionWritten = true;
}
}
if (definitionWritten) {
writer.write("</dl>\n\n");
} else {
writer.write("none\n\n");
}
}
private void outputMethods(Writer writer, ComponentInfo component) throws java.io.IOException {
writer.write("<h3>Methods</h3>\n");
// Only list methods that are user-visible.
boolean definitionWritten = false;
for (Map.Entry<String, Method> entry : component.methods.entrySet()) {
Method method = entry.getValue();
if (method.deprecated) continue;
if (method.userVisible) {
if (!definitionWritten) {
writer.write("<dl>\n");
}
String returnType = method.getReturnType();
writer.write(getMethodDefinition(
method.name, method.toParameterString(),
returnType == null ? "" : javaTypeToYailType(returnType),
method.description));
definitionWritten = true;
}
}
if (definitionWritten) {
writer.write("</dl>\n\n");
} else {
writer.write("none\n\n");
}
}
}