/* * 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.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.TreeSet; 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.LoggingUtil; import de.lmu.ifi.dbs.elki.utilities.ELKIServiceRegistry; import de.lmu.ifi.dbs.elki.utilities.documentation.Reference; import de.lmu.ifi.dbs.elki.utilities.pairs.Pair; import de.lmu.ifi.dbs.elki.utilities.xml.HTMLUtil; /** * Build a reference documentation for all available parameters. * * @author Erich Schubert * @since 0.3 * * @apiviz.uses Reference */ public class DocumentReferences { private static final String CSSFILE = "stylesheet.css"; private static final String MODIFICATION_WARNING = "WARNING: THIS DOCUMENT IS AUTOMATICALLY GENERATED. MODIFICATIONS MAY GET LOST."; /** * Logger */ private static final Logging LOG = Logging.getLogger(DocumentReferences.class); /** * @param args Command line arguments */ public static void main(String[] args) { if(args.length < 1 || args.length > 2) { LoggingUtil.warning("I need exactly one or two file names to operate!"); System.exit(1); } if(!args[0].endsWith(".html") || (args.length > 1 && !args[1].endsWith(".trac"))) { LoggingUtil.warning("File name doesn't end in expected extension!"); System.exit(1); } List<Pair<Reference, TreeSet<Object>>> refs = sortedReferences(); File references = new File(args[0]); try (FileOutputStream reffo = new FileOutputStream(references); // OutputStream refstream = new BufferedOutputStream(reffo)) { Document refdoc = documentReferences(refs); HTMLUtil.writeXHTML(refdoc, refstream); } catch(IOException e) { LoggingUtil.exception("IO Exception writing HTML output.", e); System.exit(1); } if(args.length > 1) { File refwiki = new File(args[1]); try (FileOutputStream reffow = new FileOutputStream(refwiki); // PrintStream refstreamW = new PrintStream(reffow, false, "UTF-8")) { documentReferencesWiki(refs, refstreamW); } catch(IOException e) { LoggingUtil.exception("IO Exception writing Wiki output.", e); System.exit(1); } } } private static Document documentReferences(List<Pair<Reference, TreeSet<Object>>> refs) 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("ELKI references overview."); head.appendChild(title); } // Heading { Element h1 = htmldoc.createElement(HTMLUtil.HTML_H1_TAG); h1.setTextContent("ELKI references overview:"); body.appendChild(h1); } // Main definition list Element maindl = htmldoc.createElement(HTMLUtil.HTML_DL_TAG); body.appendChild(maindl); for(Pair<Reference, TreeSet<Object>> pair : refs) { // DT = definition term Element classdt = htmldoc.createElement(HTMLUtil.HTML_DT_TAG); // Anchor for references { boolean first = true; for(Object o : pair.second) { if(!first) { classdt.appendChild(htmldoc.createTextNode(", ")); } if(o instanceof Class<?>) { Class<?> cls = (Class<?>) o; 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); } else if(o instanceof Package) { Package pkg = (Package) o; Element classan = htmldoc.createElement(HTMLUtil.HTML_A_TAG); classan.setAttribute(HTMLUtil.HTML_NAME_ATTRIBUTE, pkg.getName()); classdt.appendChild(classan); // Link back to original class Element classa = htmldoc.createElement(HTMLUtil.HTML_A_TAG); classa.setAttribute(HTMLUtil.HTML_HREF_ATTRIBUTE, linkForPackageName(pkg.getName())); classa.setTextContent(pkg.getName()); classdt.appendChild(classa); } first = false; } } maindl.appendChild(classdt); // DD = definition description Element classdd = htmldoc.createElement(HTMLUtil.HTML_DD_TAG); maindl.appendChild(classdd); { Reference ref = pair.first; // Prefix if(ref.prefix().length() > 0) { Element prediv = htmldoc.createElement(HTMLUtil.HTML_DIV_TAG); prediv.setTextContent(ref.prefix()); classdd.appendChild(prediv); } // Authors Element authorsdiv = htmldoc.createElement(HTMLUtil.HTML_DIV_TAG); authorsdiv.setTextContent(ref.authors()); classdd.appendChild(authorsdiv); // Title Element titlediv = htmldoc.createElement(HTMLUtil.HTML_DIV_TAG); Element titleb = htmldoc.createElement(HTMLUtil.HTML_B_TAG); titleb.setTextContent(ref.title()); titlediv.appendChild(titleb); classdd.appendChild(titlediv); // Booktitle if(ref.booktitle().length() > 0) { Element booktitlediv = htmldoc.createElement(HTMLUtil.HTML_DIV_TAG); booktitlediv.setTextContent("In: " + ref.booktitle()); if(ref.booktitle().startsWith("Online:")) { booktitlediv.setTextContent(ref.booktitle()); } classdd.appendChild(booktitlediv); } // URL if(ref.url().length() > 0) { Element urldiv = htmldoc.createElement(HTMLUtil.HTML_DIV_TAG); Element urla = htmldoc.createElement(HTMLUtil.HTML_A_TAG); urla.setAttribute(HTMLUtil.HTML_HREF_ATTRIBUTE, ref.url()); urla.setTextContent(ref.url()); urldiv.appendChild(urla); classdd.appendChild(urldiv); } } } return htmldoc; } private static void documentReferencesWiki(List<Pair<Reference, TreeSet<Object>>> refs, PrintStream refstreamW) { for(Pair<Reference, TreeSet<Object>> pair : refs) { // JavaDoc links for relevant classes and packages. boolean first = true; for(Object o : pair.second) { if(!first) { refstreamW.println(",[[br]]"); } if(o instanceof Class<?>) { Class<?> cls = (Class<?>) o; refstreamW.print("[[javadoc("); refstreamW.print(cls.getName()); refstreamW.print(','); refstreamW.print(cls.getName()); refstreamW.print(")]]"); } else if(o instanceof Package) { Package pkg = (Package) o; refstreamW.print("[[javadoc("); refstreamW.print(pkg.getName()); refstreamW.print(','); refstreamW.print(pkg.getName()); refstreamW.print(")]]"); } first = false; } refstreamW.println(); String indent = " "; { Reference ref = pair.first; // Prefix if(ref.prefix().length() > 0) { refstreamW.println(indent + ref.prefix() + " [[br]]"); } // Authors refstreamW.println(indent + "By: " + ref.authors() + " [[br]]"); // Title refstreamW.println(indent + "'''" + ref.title() + "'''" + " [[br]]"); // Booktitle if(ref.booktitle().length() > 0) { String prefix = ref.booktitle().startsWith("Online:") ? "" : "In: "; refstreamW.println(indent + prefix + ref.booktitle() + " [[br]]"); } // URL if(ref.url().length() > 0) { refstreamW.println(indent + "Online: [" + ref.url() + "][[br]]"); } } refstreamW.println(); refstreamW.println(); } } private static List<Pair<Reference, TreeSet<Object>>> sortedReferences() { List<Pair<Reference, TreeSet<Object>>> refs = new ArrayList<>(); Map<Reference, TreeSet<Object>> map = new HashMap<>(); HashSet<Package> packages = new HashSet<>(); for(Class<?> cls : ELKIServiceRegistry.findAllImplementations(Object.class, true, false)) { inspectClass(cls, refs, map); if(packages.add(cls.getPackage())) { inspectPackage(cls.getPackage(), refs, map); } } // Sort references by first class. Collections.sort(refs, new Comparator<Pair<Reference, TreeSet<Object>>>() { @Override public int compare(Pair<Reference, TreeSet<Object>> p1, Pair<Reference, TreeSet<Object>> p2) { final Object o1 = p1.second.first(), o2 = p2.second.first(); int c = COMPARATOR.compare(o1, o2); if(c == 0) { Reference r1 = p1.first, r2 = p2.first; c = compareNull(r1.title(), r2.title()); c = (c != 0) ? c : compareNull(r1.authors(), r2.authors()); c = (c != 0) ? c : compareNull(r1.booktitle(), r2.booktitle()); } return c; } /** * Null-tolerant String comparison. * * @param s1 First string * @param s2 Second string * @return Order */ private int compareNull(String s1, String s2) { return (s1 == s2) ? 0 // : (s1 == null) ? -1 // : (s2 == null) ? +1 // : s1.compareTo(s2); } }); return refs; } /** * Comparator for sorting the list of classes for each reference. */ private static final Comparator<Object> COMPARATOR = new Comparator<Object>() { @Override public int compare(Object o1, Object o2) { String n1 = (o1 instanceof Class) ? ((Class<?>) o1).getName() : ((Package) o1).getName(); String n2 = (o2 instanceof Class) ? ((Class<?>) o2).getName() : ((Package) o2).getName(); return n1.compareTo(n2); } }; private static void inspectClass(final Class<?> cls, List<Pair<Reference, TreeSet<Object>>> refs, Map<Reference, TreeSet<Object>> map) { if(cls.getSimpleName().equals("package-info")) { return; } try { if(cls.isAnnotationPresent(Reference.class)) { Reference ref = cls.getAnnotation(Reference.class); addReference(cls, ref, refs, map); } // Inner classes for(Class<?> c2 : cls.getDeclaredClasses()) { inspectClass(c2, refs, map); } for(Method m : cls.getDeclaredMethods()) { if(m.isAnnotationPresent(Reference.class)) { addReference(cls, m.getAnnotation(Reference.class), refs, map); } } for(Field f : cls.getDeclaredFields()) { if(f.isAnnotationPresent(Reference.class)) { addReference(cls, f.getAnnotation(Reference.class), refs, map); } } } catch(Error e) { LOG.warning("Exception in finding references for class " + cls.getCanonicalName() + ": " + e, e); } } private static void addReference(Object cls, Reference ref, List<Pair<Reference, TreeSet<Object>>> refs, Map<Reference, TreeSet<Object>> map) { TreeSet<Object> list = map.get(ref); if(list == null) { map.put(ref, list = new TreeSet<>(COMPARATOR)); refs.add(new Pair<>(ref, list)); } list.add(cls); } private static void inspectPackage(Package p, List<Pair<Reference, TreeSet<Object>>> refs, Map<Reference, TreeSet<Object>> map) { if(p.isAnnotationPresent(Reference.class)) { addReference(p, p.getAnnotation(Reference.class), refs, map); } } private static String linkForClassName(String name) { return name.replace('.', '/') + ".html"; } private static String linkForPackageName(String name) { return name.replace('.', '/') + "/package-summary.html"; } /** * Find all classes that have the reference annotation * * @return All classes with the reference annotation. */ public static ArrayList<Class<?>> findAllClassesWithReferences() { ArrayList<Class<?>> references = new ArrayList<>(); for(final Class<?> cls : ELKIServiceRegistry.findAllImplementations(Object.class, true, false)) { if(cls.isAnnotationPresent(Reference.class)) { references.add(cls); } else { for(Method m : cls.getDeclaredMethods()) { if(m.isAnnotationPresent(Reference.class)) { references.add(cls); } } } } return references; } }