/*
* This file is part of ELKI:
* Environment for Developing KDD-Applications Supported by Index-Structures
*
* Copyright (C) 2017
* ELKI Development Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.lmu.ifi.dbs.elki.application.internal;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import de.lmu.ifi.dbs.elki.logging.Logging;
import de.lmu.ifi.dbs.elki.logging.Logging.Level;
import de.lmu.ifi.dbs.elki.logging.LoggingConfiguration;
import de.lmu.ifi.dbs.elki.utilities.ClassGenericsUtil;
import de.lmu.ifi.dbs.elki.utilities.ELKIServiceRegistry;
import de.lmu.ifi.dbs.elki.utilities.ELKIServiceScanner;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.OptionID;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.Parameterizer;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.Parameterization;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.SerializedParameterization;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.TrackParameters;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.TrackedParameter;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameterization.UnParameterization;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.ClassListParameter;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.ClassParameter;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.Parameter;
import de.lmu.ifi.dbs.elki.utilities.optionhandling.parameters.RandomParameter;
import de.lmu.ifi.dbs.elki.utilities.pairs.Pair;
import de.lmu.ifi.dbs.elki.utilities.random.RandomFactory;
import de.lmu.ifi.dbs.elki.utilities.xml.HTMLUtil;
/**
* Class to generate HTML parameter descriptions for all classes that have ELKI
* {@link Parameter}s. Used in documentation generation only.
*
* @author Erich Schubert
* @since 0.2
*
* @apiviz.uses Parameter
*/
public class DocumentParameters {
private static final Logging LOG = Logging.getLogger(DocumentParameters.class);
private static final String HEADER_PARAMETER_FOR = "Parameter for: ";
private static final String HEADER_DEFAULT_VALUE = "Default: ";
private static final String NO_DEFAULT_VALUE = "No default value.";
private static final String HEADER_CLASS_RESTRICTION = "Class Restriction: ";
private static final String HEADER_CLASS_RESTRICTION_IMPLEMENTING = "implements ";
private static final String HEADER_CLASS_RESTRICTION_EXTENDING = "extends ";
private static final String NO_CLASS_RESTRICTION = "No class restriction.";
private static final String CSSFILE = "stylesheet.css";
private static final String MODIFICATION_WARNING = "WARNING: THIS DOCUMENT IS AUTOMATICALLY GENERATED. MODIFICATIONS MAY GET LOST.";
private static final String HEADER_KNOWN_IMPLEMENTATIONS = "Known implementations: ";
/**
* Enable the full wiki output. Currently not sensible, as there is a size
* restriction on wiki pages, so we would need to split this somehow!
*/
private static final boolean FULL_WIKI_OUTPUT = false;
/**
* @param args Command line arguments
*/
public static void main(String[] args) {
LoggingConfiguration.setVerbose(Level.VERBOSE);
if(args.length != 2 && args.length != 4) {
LOG.warning("I need exactly two or four file names to operate!");
System.exit(1);
}
if(!args[0].endsWith(".html")) {
LOG.warning("First file name doesn't end with .html!");
System.exit(1);
}
if(!args[1].endsWith(".html")) {
LOG.warning("Second file name doesn't end with .html!");
System.exit(1);
}
if(args.length > 2 && !args[2].endsWith(".trac")) {
LOG.warning("Third file name doesn't end with .trac!");
System.exit(1);
}
if(args.length > 3 && !args[3].endsWith(".trac")) {
LOG.warning("Fourth file name doesn't end with .trac!");
System.exit(1);
}
File byclsname = new File(args[0]);
File byoptname = new File(args[1]);
File byclsnamew = args.length >= 3 ? new File(args[2]) : null;
File byoptnamew = args.length >= 4 ? new File(args[3]) : null;
try {
Files.createDirectories(byclsname.toPath().getParent());
Files.createDirectories(byoptname.toPath().getParent());
if(byclsnamew != null) {
Files.createDirectories(byclsnamew.toPath().getParent());
}
if(byoptnamew != null) {
Files.createDirectories(byoptnamew.toPath().getParent());
}
}
catch(IOException e) {
LOG.exception(e);
System.exit(1);
}
Map<Class<?>, List<Parameter<?>>> byclass = new HashMap<>();
Map<OptionID, List<Pair<Parameter<?>, Class<?>>>> byopt = new HashMap<>();
try {
buildParameterIndex(byclass, byopt);
}
catch(Exception e) {
LOG.exception(e);
System.exit(1);
}
try (FileOutputStream byclassfo = new FileOutputStream(byclsname); //
OutputStream byclassstream = new BufferedOutputStream(byclassfo)) {
Document byclassdoc = makeByClassOverviewHTML(byclass);
HTMLUtil.writeXHTML(byclassdoc, byclassstream);
}
catch(IOException e) {
LOG.exception("IO Exception writing output.", e);
System.exit(1);
}
if(byclsnamew != null) {
try (FileOutputStream byclassfo = new FileOutputStream(byclsnamew); //
PrintStream byclassstream = new PrintStream(new BufferedOutputStream(byclassfo), false, "UTF-8")) {
makeByClassOverviewWiki(byclass, new WikiStream(byclassstream));
}
catch(IOException e) {
LOG.exception("IO Exception writing output.", e);
System.exit(1);
}
}
try (FileOutputStream byoptfo = new FileOutputStream(byoptname); //
OutputStream byoptstream = new BufferedOutputStream(byoptfo)) {
Document byoptdoc = makeByOptOverviewHTML(byopt);
HTMLUtil.writeXHTML(byoptdoc, byoptfo);
}
catch(IOException e) {
LOG.exception("IO Exception writing output.", e);
System.exit(1);
}
if(byoptnamew != null) {
try (FileOutputStream byoptfo = new FileOutputStream(byoptnamew); //
PrintStream byoptstream = new PrintStream(new BufferedOutputStream(byoptfo))) {
makeByOptOverviewWiki(byopt, new WikiStream(byoptstream));
}
catch(IOException e) {
LOG.exception("IO Exception writing output.", e);
throw new RuntimeException(e);
}
}
// Forcibly terminate, as some class may have spawned a thread.
System.exit(0);
}
private static void buildParameterIndex(Map<Class<?>, List<Parameter<?>>> byclass, Map<OptionID, List<Pair<Parameter<?>, Class<?>>>> byopt) {
final ArrayList<TrackedParameter> options = new ArrayList<>();
ExecutorService es = Executors.newSingleThreadExecutor();
List<Class<?>> objs = ELKIServiceRegistry.findAllImplementations(Object.class, false, true);
Collections.sort(objs, new ELKIServiceScanner.ClassSorter());
Class<?> appc = appBaseClass();
for(final Class<?> cls : objs) {
// Doesn't have a proper name?
if(cls.getCanonicalName() == null) {
continue;
}
// Some of the "applications" do currently not have appropriate
// constructors / parameterizers and may start AWT threads - skip them.
if(appc != null && appc.isAssignableFrom(cls)) {
continue;
}
UnParameterization config = new UnParameterization();
TrackParameters track = new TrackParameters(config, cls);
try {
// Wait up to one second.
es.submit(new FutureTask<Object>(//
new Instancer(cls, track, options), null))//
.get(1L, TimeUnit.SECONDS);
}
catch(TimeoutException e) {
LOG.warning("Timeout on instantiating " + cls.getName());
es.shutdownNow();
throw new RuntimeException(e);
}
catch(Exception e) {
LOG.warning("Error instantiating " + cls.getName(), e.getCause());
continue;
}
}
LOG.debug("Documenting " + options.size() + " parameter instances.");
for(TrackedParameter pp : options) {
if(pp.getOwner() == null || pp.getParameter() == null) {
LOG.debugFiner("Null: " + pp.getOwner() + " " + pp.getParameter());
continue;
}
Class<?> c = (Class<?>) ((pp.getOwner() instanceof Class) ? pp.getOwner() : pp.getOwner().getClass());
Parameter<?> o = pp.getParameter();
// just collect unique occurrences
{
List<Parameter<?>> byc = byclass.get(c);
boolean inlist = false;
if(byc != null) {
for(Parameter<?> par : byc) {
if(par.getOptionID() == o.getOptionID()) {
inlist = true;
break;
}
}
}
if(!inlist) {
List<Parameter<?>> ex = byclass.get(c);
if(ex == null) {
byclass.put(c, ex = new ArrayList<>());
}
ex.add(o);
}
}
{
List<Pair<Parameter<?>, Class<?>>> byo = byopt.get(o.getOptionID());
boolean inlist = false;
if(byo != null) {
for(Pair<Parameter<?>, Class<?>> pair : byo) {
if(pair.second.equals(c)) {
inlist = true;
break;
}
}
}
if(!inlist) {
List<Pair<Parameter<?>, Class<?>>> ex = byopt.get(o.getOptionID());
if(ex == null) {
byopt.put(o.getOptionID(), ex = new ArrayList<>());
}
ex.add(new Pair<Parameter<?>, Class<?>>(o, c));
}
}
}
}
/**
* Get the base application class (to be ignored).
*
* @return Application class.
*/
private static Class<?> appBaseClass() {
try {
return Class.forName("de.lmu.ifi.dbs.elki.application.AbstractApplication");
}
catch(ClassNotFoundException e) {
return null;
}
}
protected static Constructor<?> getConstructor(final Class<?> cls) {
try {
return cls.getConstructor(Parameterization.class);
}
catch(java.lang.NoClassDefFoundError e) {
return null;
}
catch(RuntimeException e) {
// Not parameterizable, usually not even found ...
LOG.warning("RuntimeException: ", e);
return null;
}
catch(Exception e) {
// Not parameterizable.
return null;
}
catch(java.lang.Error e) {
// Not parameterizable.
LOG.warning("Error: ", e);
return null;
}
}
private static class Instancer implements Runnable {
/**
* Class to instantiate.
*/
private Class<?> cls;
/**
* Parameter tracking helper.
*/
private TrackParameters track;
/**
* Options list.
*/
private ArrayList<TrackedParameter> options;
/**
* Constructor.
*
* @param cls Class to instantiate
* @param track Parameter tracking helper
* @param options Options list.
*/
public Instancer(Class<?> cls, TrackParameters track, ArrayList<TrackedParameter> options) {
this.cls = cls;
this.track = track;
this.options = options;
}
@Override
public void run() {
// Only support V3 style parameterizers now:
Parameterizer par = ClassGenericsUtil.getParameterizer(cls);
if(par != null) {
par.configure(track);
for(TrackedParameter pair : track.getAllParameters()) {
if(pair.getOwner() == null) {
LOG.warning("No owner for parameter " + pair.getParameter() + " expected a " + cls.getName());
continue;
}
options.add(pair);
}
}
// Not parameterizable.
}
}
private static Document makeByClassOverviewHTML(Map<Class<?>, List<Parameter<?>>> byclass) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder;
try {
builder = factory.newDocumentBuilder();
}
catch(ParserConfigurationException e1) {
throw new RuntimeException(e1);
}
DOMImplementation impl = builder.getDOMImplementation();
Document htmldoc = impl.createDocument(HTMLUtil.HTML_NAMESPACE, HTMLUtil.HTML_HTML_TAG, null);
// head
Element head = htmldoc.createElement(HTMLUtil.HTML_HEAD_TAG);
htmldoc.getDocumentElement().appendChild(head);
// body
Element body = htmldoc.createElement(HTMLUtil.HTML_BODY_TAG);
htmldoc.getDocumentElement().appendChild(body);
// modification warnings
head.appendChild(htmldoc.createComment(MODIFICATION_WARNING));
body.appendChild(htmldoc.createComment(MODIFICATION_WARNING));
// meta with charset information
{
Element meta = htmldoc.createElement(HTMLUtil.HTML_META_TAG);
meta.setAttribute(HTMLUtil.HTML_HTTP_EQUIV_ATTRIBUTE, HTMLUtil.HTML_HTTP_EQUIV_CONTENT_TYPE);
meta.setAttribute(HTMLUtil.HTML_CONTENT_ATTRIBUTE, HTMLUtil.CONTENT_TYPE_HTML_UTF8);
head.appendChild(meta);
}
// stylesheet
{
Element css = htmldoc.createElement(HTMLUtil.HTML_LINK_TAG);
css.setAttribute(HTMLUtil.HTML_REL_ATTRIBUTE, HTMLUtil.HTML_REL_STYLESHEET);
css.setAttribute(HTMLUtil.HTML_TYPE_ATTRIBUTE, HTMLUtil.CONTENT_TYPE_CSS);
css.setAttribute(HTMLUtil.HTML_HREF_ATTRIBUTE, CSSFILE);
head.appendChild(css);
}
// title
{
Element title = htmldoc.createElement(HTMLUtil.HTML_TITLE_TAG);
title.setTextContent("Command line parameter overview.");
head.appendChild(title);
}
// Heading
{
Element h1 = htmldoc.createElement(HTMLUtil.HTML_H1_TAG);
h1.setTextContent("ELKI command line parameter overview:");
body.appendChild(h1);
}
// Main definition list
Element maindl = htmldoc.createElement(HTMLUtil.HTML_DL_TAG);
body.appendChild(maindl);
List<Class<?>> classes = new ArrayList<>(byclass.keySet());
Collections.sort(classes, new ELKIServiceScanner.ClassSorter());
for(Class<?> cls : classes) {
// DT = definition term
Element classdt = htmldoc.createElement(HTMLUtil.HTML_DT_TAG);
// Anchor for references
{
Element classan = htmldoc.createElement(HTMLUtil.HTML_A_TAG);
classan.setAttribute(HTMLUtil.HTML_NAME_ATTRIBUTE, cls.getName());
classdt.appendChild(classan);
}
// Link back to original class
{
Element classa = htmldoc.createElement(HTMLUtil.HTML_A_TAG);
classa.setAttribute(HTMLUtil.HTML_HREF_ATTRIBUTE, linkForClassName(cls.getName()));
classa.setTextContent(cls.getName());
classdt.appendChild(classa);
}
maindl.appendChild(classdt);
// DD = definition description
Element classdd = htmldoc.createElement(HTMLUtil.HTML_DD_TAG);
maindl.appendChild(classdd);
// nested definition list for options
Element classdl = htmldoc.createElement(HTMLUtil.HTML_DL_TAG);
classdd.appendChild(classdl);
for(Parameter<?> opt : byclass.get(cls)) {
// DT definition term: option name, in TT for typewriter optics
Element elemdt = htmldoc.createElement(HTMLUtil.HTML_DT_TAG);
{
Element elemtt = htmldoc.createElement(HTMLUtil.HTML_TT_TAG);
elemtt.setTextContent(SerializedParameterization.OPTION_PREFIX + opt.getName() + " " + opt.getSyntax());
elemdt.appendChild(elemtt);
}
classdl.appendChild(elemdt);
// DD definition description - put the option description here.
Element elemdd = htmldoc.createElement(HTMLUtil.HTML_DD_TAG);
Element elemp = htmldoc.createElement(HTMLUtil.HTML_P_TAG);
if(opt.getShortDescription() != null) {
HTMLUtil.appendMultilineText(htmldoc, elemp, opt.getShortDescription());
}
elemdd.appendChild(elemp);
// class restriction?
if(opt instanceof ClassParameter<?>) {
appendClassRestriction(htmldoc, ((ClassParameter<?>) opt).getRestrictionClass(), elemdd);
}
// default value? completions?
appendDefaultValueIfSet(htmldoc, opt, elemdd);
// known values?
if(opt instanceof ClassParameter<?>) {
Class<?> restriction = ((ClassParameter<?>) opt).getRestrictionClass();
appendKnownImplementationsIfNonempty(htmldoc, restriction, elemdd);
}
else if(opt instanceof ClassListParameter<?>) {
Class<?> restriction = ((ClassListParameter<?>) opt).getRestrictionClass();
appendKnownImplementationsIfNonempty(htmldoc, restriction, elemdd);
}
classdl.appendChild(elemdd);
}
}
return htmldoc;
}
/**
* Write to a Wiki format.
*
* @author Erich Schubert
*
* @apiviz.exclude
*/
private static class WikiStream {
PrintStream out;
int indent = 0;
// Newline mode.
int newline = 0;
WikiStream(PrintStream out) {
this.out = out;
this.newline = -1;
}
WikiStream print(String p) {
insertNewline();
out.print(p);
return this;
}
WikiStream print(char c) {
insertNewline();
out.print(c);
return this;
}
WikiStream insertNewline() {
if(newline == 2) {
out.print("[[br]]");
}
if(newline != 0) {
printIndent();
newline = 0;
}
return this;
}
WikiStream printIndent() {
if(newline > 0) {
out.println();
}
for(int i = indent; i > 0; i--) {
out.print(' ');
}
return this;
}
WikiStream println(String p) {
insertNewline();
out.print(p);
newline = 2;
return this;
}
WikiStream println() {
newline = 2;
return this;
}
WikiStream printitem(String item) {
printIndent();
newline = 0;
print(item);
return this;
}
WikiStream javadocLink(Class<?> cls, Class<?> base) {
insertNewline();
out.print("[[javadoc(");
out.print(cls.getCanonicalName());
if(base != null) {
out.print(',');
out.print(ClassParameter.canonicalClassName(cls, base));
}
out.print(")]]");
return this;
}
}
private static void makeByClassOverviewWiki(Map<Class<?>, List<Parameter<?>>> byclass, WikiStream out) {
List<Class<?>> classes = new ArrayList<>(byclass.keySet());
Collections.sort(classes, new ELKIServiceScanner.ClassSorter());
Class<?> base = getBaseClass();
for(Class<?> cls : classes) {
out.indent = 0;
out.printitem("'''").javadocLink(cls, base).println("''':");
out.indent = 1;
out.newline = 1; // No BR needed, we increase the indent.
for(Parameter<?> opt : byclass.get(cls)) {
out.printitem("* ");
out.print("{{{").print(SerializedParameterization.OPTION_PREFIX) //
.print(opt.getName()).print(' ').print(opt.getSyntax()) //
.println("}}}");
if(opt.getShortDescription() != null) {
appendMultilineTextWiki(out, opt.getShortDescription());
}
// class restriction?
if(opt instanceof ClassParameter<?>) {
appendClassRestrictionWiki(out, ((ClassParameter<?>) opt).getRestrictionClass());
}
// default value?
if(opt.hasDefaultValue()) {
appendDefaultValueWiki(out, opt);
}
// known values?
if(FULL_WIKI_OUTPUT) {
if(opt instanceof ClassParameter<?>) {
Class<?> restriction = ((ClassParameter<?>) opt).getRestrictionClass();
appendKnownImplementationsWiki(out, restriction);
}
else if(opt instanceof ClassListParameter<?>) {
Class<?> restriction = ((ClassListParameter<?>) opt).getRestrictionClass();
appendKnownImplementationsWiki(out, restriction);
}
}
}
}
}
/**
* Get the base class, for naming.
*
* @return Base class.
*/
private static Class<?> getBaseClass() {
try {
return Class.forName("de.lmu.ifi.dbs.elki.KDDTask");
}
catch(ClassNotFoundException e) {
return null; // Just worse links, not serious.
}
}
private static void appendMultilineTextWiki(WikiStream out, String text) {
for(String line : text.split("\n")) {
out.println(line);
}
}
private static Document makeByOptOverviewHTML(Map<OptionID, List<Pair<Parameter<?>, Class<?>>>> byopt) throws IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder;
try {
builder = factory.newDocumentBuilder();
}
catch(ParserConfigurationException e1) {
throw new IOException(e1);
}
DOMImplementation impl = builder.getDOMImplementation();
Document htmldoc = impl.createDocument(HTMLUtil.HTML_NAMESPACE, HTMLUtil.HTML_HTML_TAG, null);
// head
Element head = htmldoc.createElement(HTMLUtil.HTML_HEAD_TAG);
htmldoc.getDocumentElement().appendChild(head);
// body
Element body = htmldoc.createElement(HTMLUtil.HTML_BODY_TAG);
htmldoc.getDocumentElement().appendChild(body);
// modification warnings
head.appendChild(htmldoc.createComment(MODIFICATION_WARNING));
body.appendChild(htmldoc.createComment(MODIFICATION_WARNING));
// meta with charset information
{
Element meta = htmldoc.createElement(HTMLUtil.HTML_META_TAG);
meta.setAttribute(HTMLUtil.HTML_HTTP_EQUIV_ATTRIBUTE, HTMLUtil.HTML_HTTP_EQUIV_CONTENT_TYPE);
meta.setAttribute(HTMLUtil.HTML_CONTENT_ATTRIBUTE, HTMLUtil.CONTENT_TYPE_HTML_UTF8);
head.appendChild(meta);
}
// stylesheet
{
Element css = htmldoc.createElement(HTMLUtil.HTML_LINK_TAG);
css.setAttribute(HTMLUtil.HTML_REL_ATTRIBUTE, HTMLUtil.HTML_REL_STYLESHEET);
css.setAttribute(HTMLUtil.HTML_TYPE_ATTRIBUTE, HTMLUtil.CONTENT_TYPE_CSS);
css.setAttribute(HTMLUtil.HTML_HREF_ATTRIBUTE, CSSFILE);
head.appendChild(css);
}
// title
{
Element title = htmldoc.createElement(HTMLUtil.HTML_TITLE_TAG);
title.setTextContent("Command line parameter overview - by option");
head.appendChild(title);
}
// Heading
{
Element h1 = htmldoc.createElement(HTMLUtil.HTML_H1_TAG);
h1.setTextContent("ELKI command line parameter overview:");
body.appendChild(h1);
}
// Main definition list
Element maindl = htmldoc.createElement(HTMLUtil.HTML_DL_TAG);
body.appendChild(maindl);
final Comparator<OptionID> osort = new SortByOption();
final Comparator<Class<?>> csort = new ELKIServiceScanner.ClassSorter();
Comparator<Pair<Parameter<?>, Class<?>>> psort = new Comparator<Pair<Parameter<?>, Class<?>>>() {
@Override
public int compare(Pair<Parameter<?>, Class<?>> o1, Pair<Parameter<?>, Class<?>> o2) {
int c = osort.compare(o1.first.getOptionID(), o2.first.getOptionID());
return (c != 0) ? c : csort.compare(o1.second, o2.second);
}
};
List<OptionID> opts = new ArrayList<>(byopt.keySet());
Collections.sort(opts, osort);
for(OptionID oid : opts) {
final Parameter<?> firstopt = byopt.get(oid).get(0).getFirst();
// DT = definition term
Element optdt = htmldoc.createElement(HTMLUtil.HTML_DT_TAG);
// Anchor for references
{
Element optan = htmldoc.createElement(HTMLUtil.HTML_A_TAG);
optan.setAttribute(HTMLUtil.HTML_NAME_ATTRIBUTE, firstopt.getName());
optdt.appendChild(optan);
}
// option name
{
Element elemtt = htmldoc.createElement(HTMLUtil.HTML_TT_TAG);
elemtt.setTextContent(SerializedParameterization.OPTION_PREFIX + firstopt.getName() + " " + firstopt.getSyntax());
optdt.appendChild(elemtt);
}
maindl.appendChild(optdt);
// DD = definition description
Element optdd = htmldoc.createElement(HTMLUtil.HTML_DD_TAG);
{
Element elemp = htmldoc.createElement(HTMLUtil.HTML_P_TAG);
HTMLUtil.appendMultilineText(htmldoc, elemp, firstopt.getShortDescription());
optdd.appendChild(elemp);
}
// class restriction?
Class<?> superclass = getRestrictionClass(oid, firstopt, byopt);
if(superclass != null) {
appendClassRestriction(htmldoc, superclass, optdd);
}
// default value?
appendDefaultValueIfSet(htmldoc, firstopt, optdd);
// known values?
if(superclass != null) {
appendKnownImplementationsIfNonempty(htmldoc, superclass, optdd);
}
maindl.appendChild(optdd);
// nested definition list for options
Element classesul = htmldoc.createElement(HTMLUtil.HTML_UL_TAG);
{
Element p = htmldoc.createElement(HTMLUtil.HTML_P_TAG);
p.appendChild(htmldoc.createTextNode(HEADER_PARAMETER_FOR));
optdd.appendChild(p);
}
optdd.appendChild(classesul);
List<Pair<Parameter<?>, Class<?>>> plist = byopt.get(oid);
Collections.sort(plist, psort);
for(Pair<Parameter<?>, Class<?>> clinst : plist) {
// DT definition term: option name, in TT for typewriter optics
Element classli = htmldoc.createElement(HTMLUtil.HTML_LI_TAG);
// Link back to original class
{
Element classa = htmldoc.createElement(HTMLUtil.HTML_A_TAG);
classa.setAttribute(HTMLUtil.HTML_HREF_ATTRIBUTE, linkForClassName(clinst.getSecond().getName()));
classa.setTextContent(clinst.getSecond().getName());
classli.appendChild(classa);
}
if(clinst.getFirst() instanceof ClassParameter<?> && firstopt instanceof ClassParameter<?>) {
ClassParameter<?> cls = (ClassParameter<?>) clinst.getFirst();
if(cls.getRestrictionClass() != null) {
// TODO: if it is null, it could still be different!
if(!cls.getRestrictionClass().equals(superclass)) {
appendClassRestriction(htmldoc, cls.getRestrictionClass(), classli);
}
}
else {
appendNoClassRestriction(htmldoc, classli);
}
}
Parameter<?> param = clinst.getFirst();
if(param.getDefaultValue() != null) {
if(!param.getDefaultValue().equals(firstopt.getDefaultValue())) {
appendDefaultValueIfSet(htmldoc, param, classli);
}
}
else if(firstopt.getDefaultValue() != null) {
appendNoDefaultValue(htmldoc, classli);
}
classesul.appendChild(classli);
}
}
return htmldoc;
}
/**
* Get the restriction class of an option.
*
* @param oid
* @param firstopt
* @param byopt
* @return
*/
private static Class<?> getRestrictionClass(OptionID oid, final Parameter<?> firstopt, Map<OptionID, List<Pair<Parameter<?>, Class<?>>>> byopt) {
Class<?> superclass;
if(firstopt instanceof ClassParameter<?>) {
superclass = ((ClassParameter<?>) firstopt).getRestrictionClass();
}
else if(firstopt instanceof ClassListParameter<?>) {
superclass = ((ClassListParameter<?>) firstopt).getRestrictionClass();
}
else {
return null;
}
// Also look for more general restrictions:
for(Pair<Parameter<?>, Class<?>> clinst : byopt.get(oid)) {
if(clinst.getFirst() instanceof ClassParameter) {
ClassParameter<?> cls = (ClassParameter<?>) clinst.getFirst();
if(!cls.getRestrictionClass().equals(superclass) && cls.getRestrictionClass().isAssignableFrom(superclass)) {
superclass = cls.getRestrictionClass();
}
}
if(clinst.getFirst() instanceof ClassListParameter) {
ClassListParameter<?> cls = (ClassListParameter<?>) clinst.getFirst();
if(!cls.getRestrictionClass().equals(superclass) && cls.getRestrictionClass().isAssignableFrom(superclass)) {
superclass = cls.getRestrictionClass();
}
}
}
return superclass;
}
private static void makeByOptOverviewWiki(Map<OptionID, List<Pair<Parameter<?>, Class<?>>>> byopt, WikiStream out) {
List<OptionID> opts = new ArrayList<>(byopt.keySet());
Collections.sort(opts, new SortByOption());
for(OptionID oid : opts) {
final Parameter<?> firstopt = byopt.get(oid).get(0).getFirst();
out.indent = 1;
out.printitem("").print("{{{").print(SerializedParameterization.OPTION_PREFIX) //
.print(firstopt.getName()).print(' ').print(firstopt.getSyntax()).println("}}}:: ");
out.newline = 1; // No BR needed, we increase the indent.
out.indent = 2;
appendMultilineTextWiki(out, firstopt.getShortDescription());
// class restriction?
Class<?> superclass = getRestrictionClass(oid, firstopt, byopt);
if(superclass != null) {
appendClassRestrictionWiki(out, superclass);
}
// default value?
if(firstopt.hasDefaultValue()) {
appendDefaultValueWiki(out, firstopt);
}
if(FULL_WIKI_OUTPUT) {
// known values?
if(superclass != null) {
appendKnownImplementationsWiki(out, superclass);
}
// List of classes that use this parameter
out.println("Used by:");
for(Pair<Parameter<?>, Class<?>> clinst : byopt.get(oid)) {
out.indent = 3;
out.printitem("* ").javadocLink(clinst.getSecond(), null).println();
if(clinst.getFirst() instanceof ClassParameter<?> && firstopt instanceof ClassParameter<?>) {
ClassParameter<?> cls = (ClassParameter<?>) clinst.getFirst();
if(cls.getRestrictionClass() != null) {
// TODO: if it is null, it could still be different!
if(!cls.getRestrictionClass().equals(superclass)) {
appendClassRestrictionWiki(out, cls.getRestrictionClass());
}
}
else {
appendNoClassRestrictionWiki(out);
}
}
Parameter<?> param = clinst.getFirst();
if(param.getDefaultValue() != null) {
if(!param.getDefaultValue().equals(firstopt.getDefaultValue())) {
appendDefaultValueWiki(out, param);
}
}
else if(firstopt.getDefaultValue() != null) {
appendNoDefaultValueWiki(out);
}
out.println();
}
}
}
}
private static void appendDefaultClassLink(Document htmldoc, Parameter<?> opt, Element p) {
Element defa = htmldoc.createElement(HTMLUtil.HTML_A_TAG);
defa.setAttribute(HTMLUtil.HTML_HREF_ATTRIBUTE, linkForClassName(((ClassParameter<?>) opt).getDefaultValue().getCanonicalName()));
defa.setTextContent(((ClassParameter<?>) opt).getDefaultValue().getCanonicalName());
p.appendChild(defa);
}
private static void appendClassRestriction(Document htmldoc, Class<?> restriction, Element elemdd) {
assert (restriction != null);
Element p = htmldoc.createElement(HTMLUtil.HTML_P_TAG);
p.appendChild(htmldoc.createTextNode(HEADER_CLASS_RESTRICTION));
if(restriction.isInterface()) {
p.appendChild(htmldoc.createTextNode(HEADER_CLASS_RESTRICTION_IMPLEMENTING));
}
else {
p.appendChild(htmldoc.createTextNode(HEADER_CLASS_RESTRICTION_EXTENDING));
}
Element defa = htmldoc.createElement(HTMLUtil.HTML_A_TAG);
defa.setAttribute(HTMLUtil.HTML_HREF_ATTRIBUTE, linkForClassName(restriction.getName()));
defa.setTextContent(restriction.getName());
p.appendChild(defa);
elemdd.appendChild(p);
}
private static void appendNoClassRestriction(Document htmldoc, Element elemdd) {
Element p = htmldoc.createElement(HTMLUtil.HTML_P_TAG);
p.appendChild(htmldoc.createTextNode(HEADER_CLASS_RESTRICTION));
p.appendChild(htmldoc.createTextNode(NO_CLASS_RESTRICTION));
elemdd.appendChild(p);
}
private static void appendClassRestrictionWiki(WikiStream out, Class<?> restriction) {
assert (restriction != null);
out.print(HEADER_CLASS_RESTRICTION) //
.print(restriction.isInterface() ? HEADER_CLASS_RESTRICTION_IMPLEMENTING : HEADER_CLASS_RESTRICTION_EXTENDING) //
.javadocLink(restriction, null).println();
}
private static void appendNoClassRestrictionWiki(WikiStream out) {
out.print(HEADER_CLASS_RESTRICTION).print(NO_CLASS_RESTRICTION).println();
}
private static void appendKnownImplementationsIfNonempty(Document htmldoc, Class<?> restriction, Element elemdd) {
if(restriction != Object.class) {
List<Class<?>> iter = ELKIServiceRegistry.findAllImplementations(restriction);
if(!iter.isEmpty()) {
Element p = htmldoc.createElement(HTMLUtil.HTML_P_TAG);
p.appendChild(htmldoc.createTextNode(HEADER_KNOWN_IMPLEMENTATIONS));
elemdd.appendChild(p);
Element ul = htmldoc.createElement(HTMLUtil.HTML_UL_TAG);
for(Class<?> c : iter) {
Element li = htmldoc.createElement(HTMLUtil.HTML_LI_TAG);
Element defa = htmldoc.createElement(HTMLUtil.HTML_A_TAG);
defa.setAttribute(HTMLUtil.HTML_HREF_ATTRIBUTE, linkForClassName(c.getName()));
defa.setTextContent(ClassParameter.canonicalClassName(c, restriction));
li.appendChild(defa);
ul.appendChild(li);
}
elemdd.appendChild(ul);
}
// FIXME: The following currently cannot be used:
// Report when not in properties file.
// Iterator<Class<?>> clss = new
// ELKIServiceLoader(opt.getRestrictionClass()).load();
// if(!clss.hasNext() &&
// !opt.getRestrictionClass().getName().startsWith("experimentalcode.")) {
// LOG.warning(opt.getRestrictionClass().getName() + " not in properties.
// No autocompletion available in release GUI.");
// }
}
}
private static void appendKnownImplementationsWiki(WikiStream out, Class<?> restriction) {
List<Class<?>> implementations = ELKIServiceRegistry.findAllImplementations(restriction);
if(implementations.isEmpty()) {
return;
}
out.println(HEADER_KNOWN_IMPLEMENTATIONS);
out.indent++;
for(Class<?> c : implementations) {
out.printitem("* ").javadocLink(c, restriction).println();
}
out.indent--;
}
/**
* Append string containing the default value.
*
* @param htmldoc Document
* @param par Parameter
* @param optdd HTML Element
*/
private static void appendDefaultValueIfSet(Document htmldoc, Parameter<?> par, Element optdd) {
if(par.hasDefaultValue()) {
Element p = htmldoc.createElement(HTMLUtil.HTML_P_TAG);
p.appendChild(htmldoc.createTextNode(HEADER_DEFAULT_VALUE));
if(par instanceof ClassParameter<?>) {
appendDefaultClassLink(htmldoc, par, p);
}
else if(par instanceof RandomParameter && par.getDefaultValue() == RandomFactory.DEFAULT) {
p.appendChild(htmldoc.createTextNode("use global random seed"));
}
else {
p.appendChild(htmldoc.createTextNode(par.getDefaultValueAsString()));
}
optdd.appendChild(p);
}
}
/**
* Append string that there is not default value.
*
* @param htmldoc Document
* @param optdd HTML Element
*/
private static void appendNoDefaultValue(Document htmldoc, Element optdd) {
Element p = htmldoc.createElement(HTMLUtil.HTML_P_TAG);
p.appendChild(htmldoc.createTextNode(HEADER_DEFAULT_VALUE));
p.appendChild(htmldoc.createTextNode(NO_DEFAULT_VALUE));
optdd.appendChild(p);
}
private static void appendDefaultValueWiki(WikiStream out, Parameter<?> par) {
out.print(HEADER_DEFAULT_VALUE);
if(par instanceof ClassParameter<?>) {
final Class<?> name = ((ClassParameter<?>) par).getDefaultValue();
out.javadocLink(name, null).println();
}
else if(par instanceof RandomParameter && par.getDefaultValue() == RandomFactory.DEFAULT) {
out.println("use global random seed");
}
else {
out.println(par.getDefaultValueAsString());
}
}
private static void appendNoDefaultValueWiki(WikiStream out) {
out.print(HEADER_DEFAULT_VALUE).println(NO_DEFAULT_VALUE);
}
/**
* Return a link for the class name
*
* @param name Class name
* @return (relative) link destination
*/
private static String linkForClassName(String name) {
return name.replace('.', '/') + ".html";
}
/**
* Sort parameters by their option
*
* @author Erich Schubert
*
* @apiviz.exclude
*/
protected static class SortByOption implements Comparator<OptionID> {
@Override
public int compare(OptionID o1, OptionID o2) {
return o1.getName().compareToIgnoreCase(o2.getName());
}
}
}