1 /* 2 * This file is part of ELKI: 3 * Environment for Developing KDD-Applications Supported by Index-Structures 4 * 5 * Copyright (C) 2018 6 * ELKI Development Team 7 * 8 * This program is free software: you can redistribute it and/or modify 9 * it under the terms of the GNU Affero General Public License as published by 10 * the Free Software Foundation, either version 3 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU Affero General Public License for more details. 17 * 18 * You should have received a copy of the GNU Affero General Public License 19 * along with this program. If not, see <http://www.gnu.org/licenses/>. 20 */ 21 package de.lmu.ifi.dbs.elki.application.internal; 22 23 import java.io.BufferedOutputStream; 24 import java.io.File; 25 import java.io.FileOutputStream; 26 import java.io.IOException; 27 import java.io.OutputStream; 28 import java.lang.reflect.Field; 29 import java.lang.reflect.Method; 30 import java.util.ArrayList; 31 import java.util.Collections; 32 import java.util.Comparator; 33 import java.util.HashMap; 34 import java.util.HashSet; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.TreeSet; 38 39 import javax.xml.parsers.DocumentBuilder; 40 import javax.xml.parsers.DocumentBuilderFactory; 41 import javax.xml.parsers.ParserConfigurationException; 42 43 import org.w3c.dom.DOMImplementation; 44 import org.w3c.dom.Document; 45 import org.w3c.dom.Element; 46 47 import de.lmu.ifi.dbs.elki.logging.Logging; 48 import de.lmu.ifi.dbs.elki.utilities.ELKIServiceRegistry; 49 import de.lmu.ifi.dbs.elki.utilities.documentation.Reference; 50 import de.lmu.ifi.dbs.elki.utilities.xml.HTMLUtil; 51 52 /** 53 * Build a reference documentation for all available parameters. 54 * 55 * @author Erich Schubert 56 * @since 0.3 57 * 58 * @assoc - - - Reference 59 */ 60 public class DocumentReferences { 61 private static final String DOIPREFIX = "https://doi.org/"; 62 63 private static final String DBLPPREFIX = "DBLP:"; 64 65 private static final String DBLPURL = "https://dblp.uni-trier.de/rec/bibtex/"; 66 67 /** 68 * Logger 69 */ 70 private static final Logging LOG = Logging.getLogger(DocumentReferences.class); 71 72 /** 73 * @param args Command line arguments 74 */ main(String[] args)75 public static void main(String[] args) { 76 if(args.length < 1 || args.length > 2) { 77 LOG.warning("I need exactly one or two file names to operate!"); 78 System.exit(1); 79 } 80 if(!args[0].endsWith(".html") || (args.length > 1 && !args[1].endsWith(".md"))) { 81 LOG.warning("File name doesn't end in expected extension!"); 82 System.exit(1); 83 } 84 85 List<Map.Entry<Reference, TreeSet<Object>>> refs = sortedReferences(); 86 File references = new File(args[0]); 87 try (FileOutputStream reffo = new FileOutputStream(references); // 88 OutputStream refstream = new BufferedOutputStream(reffo)) { 89 documentReferences(refs, new HTMLFormat()).writeTo(refstream); 90 } 91 catch(IOException e) { 92 LOG.exception("IO Exception writing HTML output.", e); 93 System.exit(1); 94 } 95 if(args.length > 1) { 96 File refwiki = new File(args[1]); 97 try (FileOutputStream reffow = new FileOutputStream(refwiki); // 98 MarkdownDocStream refstreamW = new MarkdownDocStream(reffow)) { 99 documentReferences(refs, new MarkdownFormat(refstreamW)); 100 } 101 catch(IOException e) { 102 LOG.exception("IO Exception writing Wiki output.", e); 103 System.exit(1); 104 } 105 } 106 } 107 108 /** 109 * Abstract interface for output formats. 110 * 111 * @author Erich Schubert 112 * 113 * @param <T> Entry type 114 */ 115 private interface Format<T> { newEntry()116 T newEntry(); 117 init(String title)118 void init(String title); 119 writeClass(T classdt, Class<?> cls)120 void writeClass(T classdt, Class<?> cls); 121 writePackage(T classdt, Package pkg)122 void writePackage(T classdt, Package pkg); 123 writeReference(Reference ref)124 void writeReference(Reference ref); 125 linkFor(Class<?> cls)126 default String linkFor(Class<?> cls) { 127 return cls.getName().replace('.', '/') + ".html"; 128 } 129 linkFor(Package name)130 default String linkFor(Package name) { 131 return name.getName().replace('.', '/') + "/package-summary.html"; 132 } 133 } 134 135 /** 136 * HTML output format. 137 * 138 * @author Erich Schubert 139 */ 140 private static class HTMLFormat implements Format<Element> { 141 private static final String CSSFILE = "stylesheet.css"; 142 143 private static final String MODIFICATION_WARNING = "WARNING: THIS DOCUMENT IS AUTOMATICALLY GENERATED. MODIFICATIONS MAY GET LOST."; 144 145 Document htmldoc; 146 147 Element maindl; 148 HTMLFormat()149 HTMLFormat() throws IOException { 150 DocumentBuilder builder; 151 try { 152 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 153 builder = factory.newDocumentBuilder(); 154 } 155 catch(ParserConfigurationException e) { 156 throw new IOException(e); 157 } 158 DOMImplementation impl = builder.getDOMImplementation(); 159 htmldoc = impl.createDocument(HTMLUtil.HTML_NAMESPACE, HTMLUtil.HTML_HTML_TAG, null); 160 } 161 162 @Override init(String title)163 public void init(String title) { 164 assert !htmldoc.getDocumentElement().hasChildNodes(); 165 // head 166 Element head = htmldoc.createElement(HTMLUtil.HTML_HEAD_TAG); 167 head.appendChild(htmldoc.createComment(MODIFICATION_WARNING)); 168 htmldoc.getDocumentElement().appendChild(head); 169 // meta with charset information 170 Element meta = htmldoc.createElement(HTMLUtil.HTML_META_TAG); 171 meta.setAttribute(HTMLUtil.HTML_HTTP_EQUIV_ATTRIBUTE, HTMLUtil.HTML_HTTP_EQUIV_CONTENT_TYPE); 172 meta.setAttribute(HTMLUtil.HTML_CONTENT_ATTRIBUTE, HTMLUtil.CONTENT_TYPE_HTML_UTF8); 173 head.appendChild(meta); 174 // stylesheet 175 Element css = htmldoc.createElement(HTMLUtil.HTML_LINK_TAG); 176 css.setAttribute(HTMLUtil.HTML_REL_ATTRIBUTE, HTMLUtil.HTML_REL_STYLESHEET); 177 css.setAttribute(HTMLUtil.HTML_TYPE_ATTRIBUTE, HTMLUtil.CONTENT_TYPE_CSS); 178 css.setAttribute(HTMLUtil.HTML_HREF_ATTRIBUTE, CSSFILE); 179 head.appendChild(css); 180 // title 181 head.appendChild(htmldoc.createElement(HTMLUtil.HTML_TITLE_TAG)).setTextContent(title); 182 // body 183 Element body = htmldoc.createElement(HTMLUtil.HTML_BODY_TAG); 184 htmldoc.getDocumentElement().appendChild(body)// 185 .appendChild(htmldoc.createComment(MODIFICATION_WARNING)); 186 body.appendChild(htmldoc.createElement(HTMLUtil.HTML_H1_TAG)).setTextContent(title + ":"); 187 // Main definition list 188 maindl = htmldoc.createElement(HTMLUtil.HTML_DL_TAG); 189 body.appendChild(maindl); 190 } 191 192 @Override newEntry()193 public Element newEntry() { 194 // DT = definition term 195 Element classdt = htmldoc.createElement(HTMLUtil.HTML_DT_TAG); 196 maindl.appendChild(classdt); 197 return classdt; 198 } 199 200 @Override writeClass(Element classdt, Class<?> cls)201 public void writeClass(Element classdt, Class<?> cls) { 202 if(classdt.hasChildNodes()) { 203 classdt.appendChild(htmldoc.createTextNode(", ")); 204 } 205 Element classa = htmldoc.createElement(HTMLUtil.HTML_A_TAG); 206 classa.setAttribute(HTMLUtil.HTML_ID_ATTRIBUTE, cls.getName()); 207 classa.setAttribute(HTMLUtil.HTML_HREF_ATTRIBUTE, linkFor(cls)); 208 classa.setTextContent(cls.getName()); 209 classdt.appendChild(classa); 210 } 211 212 @Override writePackage(Element classdt, Package pkg)213 public void writePackage(Element classdt, Package pkg) { 214 if(classdt.hasChildNodes()) { 215 classdt.appendChild(htmldoc.createTextNode(", ")); 216 } 217 Element classa = htmldoc.createElement(HTMLUtil.HTML_A_TAG); 218 classa.setAttribute(HTMLUtil.HTML_ID_ATTRIBUTE, pkg.getName()); 219 classa.setAttribute(HTMLUtil.HTML_HREF_ATTRIBUTE, linkFor(pkg)); 220 classa.setTextContent(pkg.getName()); 221 classdt.appendChild(classa); 222 } 223 224 @Override writeReference(Reference ref)225 public void writeReference(Reference ref) { 226 // DD = definition description 227 Element classdd = htmldoc.createElement(HTMLUtil.HTML_DD_TAG); 228 maindl.appendChild(classdd); 229 230 // Prefix 231 if(!ref.prefix().isEmpty()) { 232 Element prediv = htmldoc.createElement(HTMLUtil.HTML_DIV_TAG); 233 prediv.setTextContent(ref.prefix()); 234 classdd.appendChild(prediv); 235 } 236 // Authors 237 Element authorsdiv = htmldoc.createElement(HTMLUtil.HTML_DIV_TAG); 238 authorsdiv.setTextContent(ref.authors()); 239 classdd.appendChild(authorsdiv); 240 // Title 241 Element titlediv = htmldoc.createElement(HTMLUtil.HTML_DIV_TAG); 242 Element titleb = htmldoc.createElement(HTMLUtil.HTML_B_TAG); 243 titleb.setTextContent(ref.title()); 244 titlediv.appendChild(titleb); 245 classdd.appendChild(titlediv); 246 // Booktitle 247 if(!ref.booktitle().isEmpty()) { 248 Element booktitlediv = htmldoc.createElement(HTMLUtil.HTML_DIV_TAG); 249 booktitlediv.setTextContent("In: " + ref.booktitle()); 250 if(ref.booktitle().startsWith("Online:")) { 251 booktitlediv.setTextContent(ref.booktitle()); 252 } 253 classdd.appendChild(booktitlediv); 254 } 255 // URL 256 if(!ref.url().isEmpty()) { 257 Element urldiv = htmldoc.createElement(HTMLUtil.HTML_DIV_TAG); 258 Element urla = htmldoc.createElement(HTMLUtil.HTML_A_TAG); 259 urla.setAttribute(HTMLUtil.HTML_HREF_ATTRIBUTE, ref.url()); 260 urla.setTextContent(ref.url()); 261 urldiv.appendChild(urla); 262 classdd.appendChild(urldiv); 263 } 264 // Bibkey 265 if(!ref.bibkey().isEmpty()) { 266 if(ref.bibkey().startsWith(DBLPPREFIX)) { 267 Element urldiv = htmldoc.createElement(HTMLUtil.HTML_DIV_TAG); 268 Element urla = htmldoc.createElement(HTMLUtil.HTML_A_TAG); 269 urla.setAttribute(HTMLUtil.HTML_HREF_ATTRIBUTE, DBLPURL + ref.bibkey()); 270 urla.setTextContent(ref.url()); 271 urldiv.appendChild(urla); 272 classdd.appendChild(urldiv); 273 } 274 else { 275 classdd.appendChild(htmldoc.createComment(ref.bibkey())); 276 } 277 } 278 } 279 writeTo(OutputStream refstream)280 public void writeTo(OutputStream refstream) throws IOException { 281 HTMLUtil.writeXHTML(htmldoc, refstream); 282 } 283 } 284 285 /** 286 * Markdown output format. 287 * 288 * @author Erich Schubert 289 */ 290 private static class MarkdownFormat implements Format<Void> { 291 MarkdownDocStream out; 292 293 boolean firstInEntry = false; 294 MarkdownFormat(MarkdownDocStream out)295 public MarkdownFormat(MarkdownDocStream out) { 296 this.out = out; 297 } 298 299 @Override init(String title)300 public void init(String title) { 301 out.append("# ").append(title).par(); 302 } 303 304 @Override newEntry()305 public Void newEntry() { 306 firstInEntry = true; 307 return null; 308 } 309 310 @Override writeClass(Void classdt, Class<?> cls)311 public void writeClass(Void classdt, Class<?> cls) { 312 if(!firstInEntry) { 313 out.append(',').lf(); 314 } 315 out.append('[').append(cls.getName()).append("](./releases/current/doc/") // 316 .append(linkFor(cls)).append(')'); 317 firstInEntry = false; 318 } 319 320 @Override writePackage(Void classdt, Package pkg)321 public void writePackage(Void classdt, Package pkg) { 322 if(!firstInEntry) { 323 out.append(',').lf(); 324 } 325 out.append('[').append(pkg.getName()).append("](./releases/current/doc/") // 326 .append(linkFor(pkg)).append(')'); 327 firstInEntry = false; 328 } 329 330 @Override writeReference(Reference ref)331 public void writeReference(Reference ref) { 332 out.lf(); 333 // Prefix 334 if(!ref.prefix().isEmpty()) { 335 out.escaped(ref.prefix()).lf(); 336 } 337 // Authors 338 out // 339 // authors 340 .append(ref.authors()).lf() // 341 // Title 342 .append("**").escaped(ref.title()).append("**").lf(); 343 // Booktitle 344 if(!ref.booktitle().isEmpty() && !ref.booktitle().equals(ref.url()) && !ref.booktitle().equals("Online")) { 345 out.append(ref.booktitle().startsWith("Online:") ? "" : "In: ").escaped(ref.booktitle()).lf(); 346 } 347 // URL 348 if(!ref.url().isEmpty()) { 349 if(ref.url().startsWith(DOIPREFIX)) { 350 out.append("[DOI:").append(ref.url(), DOIPREFIX.length(), ref.url().length())// 351 .append("](").append(ref.url()).append(')').lf(); 352 } 353 else { 354 out.append("Online: <").append(ref.url()).append('>').lf(); 355 } 356 } 357 // Bibkey, if we can link to DBLP: 358 if(!ref.bibkey().isEmpty()) { 359 if(ref.bibkey().startsWith(DBLPPREFIX)) { 360 out.append("[DBLP:").append(ref.bibkey(), DBLPPREFIX.length(), ref.bibkey().length())// 361 .append("](").append(DBLPURL)// 362 .append(ref.bibkey(), DBLPPREFIX.length(), ref.bibkey().length()).append(')').lf(); 363 } 364 else { 365 out.nl().append("<!-- ").append(ref.bibkey()).append(" -->").lf(); 366 } 367 } 368 out.par(); 369 } 370 } 371 documentReferences(List<Map.Entry<Reference, TreeSet<Object>>> refs, F format)372 private static <T, F extends Format<T>> F documentReferences(List<Map.Entry<Reference, TreeSet<Object>>> refs, F format) throws IOException { 373 format.init("ELKI references overview"); 374 for(Map.Entry<Reference, TreeSet<Object>> pair : refs) { 375 T classdt = format.newEntry(); 376 for(Object o : pair.getValue()) { 377 if(o instanceof Class<?>) { 378 format.writeClass(classdt, (Class<?>) o); 379 } 380 else if(o instanceof Package) { 381 format.writePackage(classdt, (Package) o); 382 } 383 } 384 format.writeReference(pair.getKey()); 385 } 386 return format; 387 } 388 sortedReferences()389 private static List<Map.Entry<Reference, TreeSet<Object>>> sortedReferences() { 390 Map<Reference, TreeSet<Object>> map = new HashMap<>(); 391 392 HashSet<Package> packages = new HashSet<>(); 393 for(Class<?> cls : ELKIServiceRegistry.findAllImplementations(Object.class, true, false)) { 394 inspectClass(cls, map); 395 if(packages.add(cls.getPackage())) { 396 Package p = cls.getPackage(); 397 addReference(p, p.getAnnotationsByType(Reference.class), map); 398 } 399 } 400 // Sort references by first class. 401 List<Map.Entry<Reference, TreeSet<Object>>> refs = new ArrayList<>(map.entrySet()); 402 Collections.sort(refs, SORT_BY_FIRST_CLASS); 403 return refs; 404 } 405 inspectClass(final Class<?> cls, Map<Reference, TreeSet<Object>> map)406 private static void inspectClass(final Class<?> cls, Map<Reference, TreeSet<Object>> map) { 407 if(cls.getSimpleName().equals("package-info")) { 408 return; 409 } 410 try { 411 addReference(cls, cls.getAnnotationsByType(Reference.class), map); 412 for(Method m : cls.getDeclaredMethods()) { 413 addReference(cls, m.getAnnotationsByType(Reference.class), map); 414 } 415 for(Field f : cls.getDeclaredFields()) { 416 addReference(cls, f.getAnnotationsByType(Reference.class), map); 417 } 418 // Inner classes 419 for(Class<?> c2 : cls.getDeclaredClasses()) { 420 inspectClass(c2, map); 421 } 422 } 423 catch(Error e) { 424 LOG.warning("Exception in finding references for class " + cls.getCanonicalName() + ": " + e, e); 425 } 426 } 427 addReference(Object cls, Reference[] r, Map<Reference, TreeSet<Object>> map)428 private static void addReference(Object cls, Reference[] r, Map<Reference, TreeSet<Object>> map) { 429 for(Reference ref : r) { 430 TreeSet<Object> list = map.get(ref); 431 if(list == null) { 432 map.put(ref, list = new TreeSet<>(SORT_PKGS_AND_CLASSES)); 433 } 434 list.add(cls); 435 } 436 } 437 438 /** 439 * Comparator for sorting the list of classes for each reference. 440 */ 441 private static final Comparator<Object> SORT_PKGS_AND_CLASSES = new Comparator<Object>() { 442 @Override 443 public int compare(Object o1, Object o2) { 444 String n1 = (o1 instanceof Class) ? ((Class<?>) o1).getName() : ((Package) o1).getName(); 445 String n2 = (o2 instanceof Class) ? ((Class<?>) o2).getName() : ((Package) o2).getName(); 446 return n1.compareTo(n2); 447 } 448 }; 449 450 private static Comparator<Map.Entry<Reference, TreeSet<Object>>> SORT_BY_FIRST_CLASS = new Comparator<Map.Entry<Reference, TreeSet<Object>>>() { 451 @Override 452 public int compare(Map.Entry<Reference, TreeSet<Object>> p1, Map.Entry<Reference, TreeSet<Object>> p2) { 453 final Object o1 = p1.getValue().first(), o2 = p2.getValue().first(); 454 int c = SORT_PKGS_AND_CLASSES.compare(o1, o2); 455 if(c == 0) { 456 Reference r1 = p1.getKey(), r2 = p2.getKey(); 457 c = compareNull(r1.title(), r2.title()); 458 c = (c != 0) ? c : compareNull(r1.authors(), r2.authors()); 459 c = (c != 0) ? c : compareNull(r1.booktitle(), r2.booktitle()); 460 } 461 return c; 462 } 463 464 /** 465 * Null-tolerant String comparison. 466 * 467 * @param s1 First string 468 * @param s2 Second string 469 * @return Order 470 */ 471 private int compareNull(String s1, String s2) { 472 return (s1 == s2) ? 0 // 473 : (s1 == null) ? -1 // 474 : (s2 == null) ? +1 // 475 : s1.compareTo(s2); 476 } 477 }; 478 479 } 480