001package org.dllearner.cli; 002 003import org.apache.commons.collections.bidimap.DualHashBidiMap; 004import org.apache.commons.lang3.mutable.MutableInt; 005import org.dllearner.cli.DocumentationGeneratorMeta.GlobalDoc; 006import org.dllearner.configuration.spring.editors.ConfigHelper; 007import org.dllearner.core.AnnComponentManager; 008import org.dllearner.core.Component; 009import org.dllearner.core.config.ConfigOption; 010import org.dllearner.utilities.Files; 011import org.semanticweb.owlapi.model.OWLClass; 012 013import java.io.File; 014import java.lang.reflect.Field; 015import java.util.*; 016import java.util.Map.Entry; 017 018/** 019 * Script for generating documentation for all components, in particular 020 * their configuration options, in HTML format. The script is based on 021 * the new (as of 2011) annotation based component design. 022 * 023 * @author Jens Lehmann 024 * 025 */ 026@SuppressWarnings("StringConcatenationInsideStringBufferAppend") 027public class DocumentationHTMLGenerator { 028 static { 029 if (System.getProperty("log4j.configuration") == null) 030 System.setProperty("log4j.configuration", "log4j.properties"); 031 } 032 033 private AnnComponentManager cm; 034 035 public DocumentationHTMLGenerator() { 036 cm = AnnComponentManager.getInstance(); 037 } 038 039 public void writeConfigDocumentation(File file) { 040 041 Map<Class<?>, String> componentNames = new DualHashBidiMap(); 042 componentNames.putAll(cm.getComponentsNamed()); 043 componentNames.put(CLI.class, "Command Line Interface"); 044 componentNames.put(GlobalDoc.class, "GLOBAL OPTIONS"); 045 TreeMap<String, Class<?>> componentNamesInv = new TreeMap<>(); 046 047 // create inverse, ordered map for displaying labels 048 for(Entry<Class<?>, String> entry : componentNames.entrySet()) { 049 componentNamesInv.put(entry.getValue(), entry.getKey()); 050 } 051 052 StringBuffer sb = new StringBuffer(); 053 sb.append(getHeader()); 054 055 // heading 056 sb.append("<h1>DL-Learner Components</h1>\n"); 057 058 // filter interface 059 Map<Class, List<Class>> compTree = new TreeMap<>(new Comparator<Class>() { 060 @Override 061 public int compare(Class o1, Class o2) { 062 return o1.getName().compareTo(o2.getName()); 063 } 064 }); 065 Map<Class, MutableInt> compSublevel = new HashMap<>(); 066 for (Class coreClazz : AnnComponentManager.coreComponentClasses) { 067 compSublevel.put(coreClazz, new MutableInt(0)); 068 } 069 for (Class coreClazz : AnnComponentManager.coreComponentClasses) { 070 compTree.put(coreClazz, new ArrayList<>()); 071 for (Class subClazz : AnnComponentManager.coreComponentClasses) { 072 if (subClazz.equals(coreClazz)) { 073 continue; 074 } 075 try { 076 subClazz.asSubclass(coreClazz); 077 compTree.get(coreClazz).add(subClazz); 078 compSublevel.get(subClazz).increment(); 079 } catch (ClassCastException e) { 080 // no subclass 081 } 082 } 083 } 084 085 sb.append("<p>Click on the following items to filter the listing below by implemented interfaces (requires Javascript):</p>\n"); 086 sb.append("<a href=\"#\" onClick=\"showAllCat()\">show all</a><ul class=\"list-unstyled\">"); 087 sb.append("<li><a href=\"#\" onClick=\"showOnlyCat('Class')\">Non-component Class</a></li>"); 088 printFilter(sb, compTree, compSublevel, Arrays.asList(AnnComponentManager.coreComponentClasses), 0); 089 sb.append("<li><a href=\"#\" onClick=\"showOnlyCat('OtherComponent')\">other</a></li>"); 090 sb.append("</ul>"); 091 092 // general explanations 093 sb.append("<p>Click on a component to get an overview on its configuration options.</p>"); 094 095 // generate component overview 096 sb.append("<ul>\n"); 097 for(Entry<String, Class<?>> compEntry : componentNamesInv.entrySet()) { 098 sb.append("<div class=\"type menu " + getCoreTypes(compEntry.getValue()) + "\"><li><a href=\"#" + compEntry.getValue().getName() + "\">"+compEntry.getKey()+"</a></li></div>\n"); 099 } 100 sb.append("</ul>\n"); 101 102 // generate actual documentation per component 103 for(Entry<String, Class<?>> compEntry : componentNamesInv.entrySet()) { 104 Class<?> comp = compEntry.getValue(); 105 sb.append("<div class=\"type " + getCoreTypes(comp) + "\">"); 106 // heading + anchor 107 sb.append("<a name=\"" + comp.getName() + "\"><h2>"+compEntry.getKey()+" <small class=\"default-hidden\">" + comp.getName() + "</small></h2></a>\n"); 108 // some information about the component 109 if (Component.class.isAssignableFrom(comp)) { 110 Class<? extends Component> ccomp = (Class<? extends Component>) comp; 111 112 sb.append("<dl class=\"dl-horizontal\"><dt>short name</dt><dd>" + AnnComponentManager.getShortName(ccomp) + "</dd>"); 113 sb.append("<dt>version</dt><dd>" + AnnComponentManager.getVersion(ccomp) + "</dd>"); 114 sb.append("<dt>implements</dt><dd><ul class=\"list-inline\"><li>" + getCoreTypes(comp).replace(" ", "</li><li>") + "</li></ul></dd>"); 115 String description = AnnComponentManager.getDescription(ccomp); 116 if(description.length() > 0) { 117 sb.append("<dt>description</dt><dd>" + AnnComponentManager.getDescription(ccomp) + "</dd>"); 118 } 119 sb.append("</dl>"); 120 } 121 optionsTable(sb, comp); 122 sb.append("</div>\n"); 123 } 124 125 sb.append(getFooter()); 126 127 Files.createFile(file, sb.toString()); 128 } 129 130 private void printFilter(StringBuffer sb, Map<Class, List<Class>> compTree, Map<Class, MutableInt> compSublevel, List<Class> filter, int level) { 131 for (Class e : filter) { 132 if (level != compSublevel.get(e).intValue()) { 133 continue; 134 } 135 sb.append("<li><a href=\"#\" onClick=\"showOnlyCat('" + e.getSimpleName() + "')\">" + e.getSimpleName() + "</a>"); 136 List<Class> subClazzes = compTree.get(e); 137 if (!subClazzes.isEmpty()) { 138 sb.append("<ul>"); 139 printFilter(sb, compTree, compSublevel, compTree.get(e), level + 1); 140 sb.append("</ul>"); 141 } 142 sb.append("</li>"); 143 } 144 } 145 146 private void optionsTable(StringBuffer sb, Class<?> comp) { 147 // generate table for configuration options 148 Map<Field,Class<?>> options = ConfigHelper.getConfigOptionTypes(comp); 149 if(options.isEmpty()) { 150 sb.append("This component does not have configuration options."); 151 } else { 152 sb.append("<div class=\"table-responsive\"><table class=\"hor-minimalist-a table table-hover\"><thead><tr><th>option name</th><th>description</th><th>type</th><th>default value</th><th>required?</th></tr></thead><tbody>\n"); 153 for(Entry<Field,Class<?>> entry : options.entrySet()) { 154 String optionName = AnnComponentManager.getName(entry.getKey()); 155 ConfigOption option = entry.getKey().getAnnotation(ConfigOption.class); 156 String type = entry.getValue().getSimpleName(); 157 if(entry.getValue().equals(OWLClass.class)) { 158 type = "IRI"; 159 } 160 sb.append("<tr><td>" + optionName + "</td><td>" + option.description() 161 + (option.exampleValue().length() > 0 ? (" <strong>Example:</strong> " + option.exampleValue()) : "") 162 + "</td><td> " + type + "</td><td>" 163 + option.defaultValue() + "</td><td> " 164 + option.required() + "</td></tr>\n"); 165 } 166 sb.append("</tbody></table></div>\n"); 167 } 168 } 169 170 private String getHeader() { 171 StringBuffer sb = new StringBuffer(); 172 //sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); 173 sb.append("<html><head><meta charset=\"UTF-8\"><title>DL-Learner components and configuration options</title>\n"); 174 sb.append("<style type=\"text/css\">\n"); 175 sb.append("@import url(\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.css\");\n"); 176 sb.append("body { line-height: 1.6em; font-size: 15px; font-family: \"Lucida Sans Unicode\", \"Lucida Grande\", Sans-Serif; }\n"); 177 sb.append("h1, h2 { font-family: \"Droid Serif\", Serif; font-weight: 800; color: #c33; }\n"); 178 sb.append(".hor-minimalist-a { font-size: 13px; background: #fff; margin: 30px; width: 90%;border-collapse: collapse; text-align: left; } \n"); 179 sb.append(".hor-minimalist-a th { font-size: 15px; font-weight: normal; color: #039; padding: 10px 8px; border-bottom: 2px solid #6678b1; white-space: nowrap; }\n"); 180 sb.append(".hor-minimalist-a td { color: #669;padding: 9px 8px 0px 8px; }\n"); 181 sb.append(".hor-minimalist-a tbody tr:hover td { color: #009; }\n"); 182 sb.append("@media screen and (max-width: 767px) {\n" 183 + ".table-responsive > .table > thead > tr > th, .table-responsive > .table > tbody > tr > th, .table-responsive > .table > tfoot > tr > th, " 184 + ".table-responsive > .table > thead > tr > td, .table-responsive > .table > tbody > tr > td, .table-responsive > .table > tfoot > tr > td { white-space: inherit; } }\n"); 185 sb.append("h2 small.default-hidden { visibility: hidden; }\n"); 186 sb.append("a:hover h2 small.default-hidden, a:active h2 small.default-hidden { visibility: visible; }\n"); 187 sb.append("</style>\n"); 188 sb.append("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js\"></script>"); 189 sb.append("<script type=\"text/javascript\" language=\"javascript\">\n"); 190 sb.append("//<![CDATA[\n"); 191 sb.append("function showOnlyCat(className){\n"); 192 sb.append(" $('div.type').show(); $('div.type').not('.'+className).hide(); }\n"); 193 sb.append("function showAllCat(){\n"); 194 sb.append(" $('div.type').show() };\n"); 195 sb.append("//]]>\n"); 196 sb.append("</script>\n"); 197 sb.append("</head><body><div class=\"container-fluid\">\n"); 198 return sb.toString(); 199 } 200 201 private String getFooter() { 202 return "</div></body></html>"; 203 } 204 205 // this is a hack, because we just assume that every PropertyEditor is named 206 // as TypeEditor (e.g. OWLObjectPropertyEditor); however that hack does not too much harm here 207// private static String getOptionType(ConfigOption option) { 208// String name = option.propertyEditorClass().getSimpleName(); 209// return name.substring(0, name.length()-6); 210// } 211 212 private static String getCoreTypes(Class<?> comp) { 213 if (Component.class.isAssignableFrom(comp)) { 214 List<Class<? extends Component>> types = AnnComponentManager.getCoreComponentTypes((Class<? extends Component>) comp); 215 String str = ""; 216 for(Class<?extends Component> type : types) { 217 str += " " + type.getSimpleName(); 218 } 219 // not every component belongs to one of the core types 220 if(str.length()==0) { 221 return "OtherComponent"; 222 } else { 223 return str.substring(1); 224 } 225 } else { 226 return "Class"; 227 } 228 } 229 230 public static void main(String[] args) { 231 File file = new File("doc/configOptions.html"); 232 DocumentationHTMLGenerator dg = new DocumentationHTMLGenerator(); 233 dg.writeConfigDocumentation(file); 234 System.out.println("Done"); 235 } 236 237}