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}