001/*license*\ 002 Codelet: Copyright (C) 2014, Jeff Epstein (aliteralmind __DASH__ github __AT__ yahoo __DOT__ com) 003 004 This software is dual-licensed under the: 005 - Lesser General Public License (LGPL) version 3.0 or, at your option, any later version; 006 - Apache Software License (ASL) version 2.0. 007 008 Either license may be applied at your discretion. More information may be found at 009 - http://en.wikipedia.org/wiki/Multi-licensing. 010 011 The text of both licenses is available in the root directory of this project, under the names "LICENSE_lgpl-3.0.txt" and "LICENSE_asl-2.0.txt". The latest copies may be downloaded at: 012 - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt 013 - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt 014\*license*/ 015package com.github.aliteralmind.codelet; 016 import com.github.aliteralmind.codelet.type.ConsoleOutTemplate; 017 import com.github.aliteralmind.codelet.type.FileTextTemplate; 018 import com.github.aliteralmind.codelet.type.SourceAndOutTemplate; 019 import com.github.aliteralmind.codelet.type.SourceCodeTemplate; 020 import com.github.xbn.io.IOUtil; 021 import com.github.xbn.io.TextAppenter; 022 import com.github.xbn.lang.CrashIfObject; 023 import com.github.aliteralmind.templatefeather.FeatherTemplate; 024 import com.github.xbn.text.CrashIfString; 025 import java.util.HashMap; 026 import java.util.Iterator; 027 import java.util.Map; 028 import java.util.regex.Pattern; 029 import static com.github.aliteralmind.codelet.CodeletBaseConfig.*; 030 import static com.github.xbn.lang.XbnConstants.*; 031/** 032 <p>For optional overriding of default templates, for a single JavaDoc page or an entire package. For overriding a template in a single taglet, a customizer must be used.</p> 033 034 <p>Configuration is in a text file named {@linkplain CodeletBootstrap#TMPL_OVERRIDES_CONFIG_FILE_NAME template_overrides_config.txt}, which is located in the same directory as {@link com.github.aliteralmind.codelet.CodeletBaseConfig codelet.properties} (view <a href="{@docRoot}/../codelet_config/template_overrides_config.txt">{@code {@docRoot}/../codelet_config/template_overrides_config.txt}</a>). Loading is executed by {@link com.github.aliteralmind.codelet.CodeletBootstrap}.</p> 035 036 <p>If {@code template_overrides_config.txt} is empty (or contains only comments), then default templates are always used.</p> 037 038 <p>Each line is an override for either a specific JavaDoc file, or an entire package. It's format:</p> 039 040<blockquote><pre><i>[fully-qualified-JavaDoc-file-name] [codelet-type] [relative-path-of-template-file]</i></pre></blockquote> 041 042 <p><b>Examples:</b></p> 043 044<blockquote><pre>com.github.smith.overview-summary.html SOURCE_CODE overview_codelet_tmpl.txt 045com.github.smith.overview-summary.html SOURCE_AND_OUT overview_codelet_and_out.txt 046com.github.smith.sub.package SOURCE_CODE sub_packages\smith_pkg_codelet_tmpl.txt 047com.github.smith.sub.package.AClass.java CONSOLE_OUT sub_packages\com_github_smith_overview_codelet_dot_out_tmpl.txt 048com.github.smith.sub.package.AClass.java FILE_TEXT sub_packages\com_github_smith_overview_file_textlet_tmpl.txt</pre></blockquote> 049 050 <p>There are three columns, {@linkplain #SPLIT_PATTERN separated by} one-or-more tabs, or two-or-more spaces:<ol> 051 <li><b>{@code [fully-qualified-JavaDoc-file-name]}:</b> The file or package to override.</li> 052 <li><b>{@code [codelet-type]}:</b> The {@linkplain CodeletType type} of codelet to override. Must equal "{@link com.github.aliteralmind.codelet.CodeletType#SOURCE_CODE SOURCE_CODE}", "{@link com.github.aliteralmind.codelet.CodeletType#CONSOLE_OUT CONSOLE_OUT}", "{@link com.github.aliteralmind.codelet.CodeletType#SOURCE_AND_OUT SOURCE_AND_OUT}", or "{@link com.github.aliteralmind.codelet.CodeletType#FILE_TEXT FILE_TEXT}".</li> 053 <li><b>{@code [relative-path-of-template-file]}:</b> The relative directory to the template file, as it exists in the user-template {@linkplain CodeletBaseConfig#USER_TEMPLATE_BASE_DIR base directory}.</li> 054 </ol></p> 055 056 <p>Lines may be indented, and any lines starting with a hash ({@code '#'}) are ignored. Empty lines are also ignored.</p> 057 058 <p><b>Column one:</b> <u><i>{@code [fully-qualified-JavaDoc-file-name]}</i></u></p> 059 060 <p><TABLE ALIGN="center" WIDTH="100%" BORDER="1" CELLSPACING="0" CELLPADDING="4" BGCOLOR="#EEEEEE"><TR ALIGN="center" VALIGN="middle"> 061 <TD><u><b>Item type</b></u></TD> 062 <TD><b><u>Example</u></b></TD> 063 <TD><b><u>Description</u></b></TD> 064 </TR><TR> 065 <TD>An individual class</TD> 066 <TD>{@code fully.qualified.AClass.java}</TD> 067 <TD>Its fully-qualified class name, plus {@code ".java"}</TD> 068 </TR><TR> 069 <TD>The package summary page</TD> 070 <TD>{@code fully.qualified.package-info.java} 071 <br/>{@code fully.qualified.package-summary.html}</TD> 072 <TD>It's fully-qualified name, including postfix, as it is <i>read by</i> the {@code javadoc} application (use the name of its <i>source</i>-file).</TD> 073 </TR><TR> 074 <TD>The overview summary page</TD> 075 <TD>{@code overview-summary.html}</TD> 076 <TD>The relative path of overview file, as it is configured into the <a href="http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#overview">{@code -overview} option</a>.</TD> 077 </TR><TR> 078 <TD>An entire package</TD> 079 <TD>{@code fully.qualified}</TD> 080 <TD>The fully qualified package name. This only overrides classes directly in this package. No sub-packages are affected.</TD> 081 </TR></TABLE></p> 082 083 <p>If both a file and its package are overridden, the individual file's override always takes precedence.</p> 084 085 * @since 0.1.0 086 * @author Copyright (C) 2014, Jeff Epstein ({@code aliteralmind __DASH__ github __AT__ yahoo __DOT__ com}), dual-licensed under the LGPL (version 3.0 or later) or the ASL (version 2.0). See source code for details. <a href="http://codelet.aliteralmind.com">{@code http://codelet.aliteralmind.com}</a>, <a href="https://github.com/aliteralmind/codelet">{@code https://github.com/aliteralmind/codelet}</a> 087 **/ 088public enum TemplateOverrides { 089 INSTANCE; 090 private static boolean wasLoaded ; 091 private static Map<String,TemplateMapForItem> pkgMap ; 092 private static Map<String,TemplateMapForItem> fileMap ; 093 /** 094 <p>The pattern to split each line on (after it's trimmed)--Equal to <code>{@link java.util.regex.Pattern Pattern}.{@link java.util.regex.Pattern#compile(String) compile}("(?:\\t|[ \\t]{2,})")</code></p> 095 */ 096 public static final Pattern SPLIT_PATTERN = Pattern.compile("(?:\\t|[ \\t]{2,})"); 097 /** 098 <p>YYY</p> 099 100 * @exception IllegalStateException If 101 <br/> <code>{@link com.github.aliteralmind.codelet.CodeletTemplateConfig CodeletTemplateConfig}.{@link com.github.aliteralmind.codelet.CodeletTemplateConfig#wasLoaded() wasLoaded}</code> 102 <br/>is {@code false}, or {@link #wasLoaded() wasLoaded}{@code ()} is {@code true}. 103 **/ 104 static final TemplateOverrides loadConfigGetInstance(Iterator<String> configFile_lineItr) { 105 if(!CodeletTemplateConfig.wasLoaded()) { 106 throw new IllegalStateException("CodeletTemplateConfig.wasLoaded() is false."); 107 } 108 if(wasLoaded()) { 109 throw new IllegalStateException("wasLoaded() is true."); 110 } 111 112 if(isDebugOn(null, "zzconfiguration.progress")) { 113 debugln(" Loading template overrides config"); 114 } 115 116 pkgMap = new HashMap<String,TemplateMapForItem>(500); 117 fileMap = new HashMap<String,TemplateMapForItem>(500); 118 119 int lineNum = 1; 120 try { 121 while(configFile_lineItr.hasNext()) { 122 processLine(lineNum++, configFile_lineItr.next()); 123 } 124 } catch(RuntimeException rx) { 125 throw CrashIfObject.nullOrReturnCause(configFile_lineItr, "configFile_lineItr", null, rx); 126 } 127 128 if(isDebugOn(null, "zzconfiguration.progress")) { 129 debugln(" - Template overrides:"); 130 debugln(staticToString()); 131 } 132 133 wasLoaded = true; 134 135 return INSTANCE; 136 } 137 /** 138 <p>Was configuration loaded?.</p> 139 140 * @return {@code true} If all values loaded successfully. 141 */ 142 public static final boolean wasLoaded() { 143 return wasLoaded; 144 } 145 private static final void processLine(int line_num, String line) { 146 line = line.trim(); 147 boolean doDebug = isDebugOn(null, 148 "zzconfiguration.templateoverrides.eachentryasloaded"); 149 150 if(doDebug) { 151 debugln(" [" + line_num + "] " + line); 152 } 153 154 if(line.startsWith("#") || line.length() == 0) { 155 return; 156 } 157 String[] split = SPLIT_PATTERN.split(line.trim()); 158 if(split.length != 3) { 159 throw new TemplateOverridesConfigLineException(line_num, line, "Line contains " + split.length + " elements after splitting on \"" + SPLIT_PATTERN + "\"."); 160 } 161 162 String itemName = split[0]; 163 CodeletType type = null; 164 try { 165 type = CodeletType.newTypeForTagletName(split[1], "[split line element index 1 (second element)]"); 166 } catch(IllegalArgumentException iax) { 167 throw new TemplateOverridesConfigLineException(line_num, line, "Element two is not a valid Codelet type", iax); 168 } 169 String path = getCustomTemplateDir() + split[2]; 170 CodeletTemplateBase tmpl = newTemplateForTypeFromPath(type, path, line_num); 171 if(itemName.endsWith(".html") || itemName.endsWith(".java")) { 172 String nameNoDotExt = itemName.substring(0, itemName.length() - 5); 173 174 if(doDebug) { 175 debugln(" File exception"); 176 } 177 178 addLineItem(fileMap, nameNoDotExt, tmpl, line_num, line); 179 } else { 180 181 if(doDebug) { 182 debugln(" Package exception"); 183 } 184 185 addLineItem(pkgMap, itemName, tmpl, line_num, line); 186 } 187 } 188 private static final void addLineItem(Map<String,TemplateMapForItem> item_map, String item_name, CodeletTemplateBase template, int line_num, String line) { 189 if(item_map.containsKey(item_name)) { 190 item_map.get(item_name).addTemplate(template, line_num, line); 191 } else { 192 item_map.put(item_name, new TemplateMapForFile(item_name, template, line_num, line)); 193 } 194 } 195 /** 196 <p>Get the template.</p> 197 198 * @return If this taglet's<ol> 199 <li>{@linkplain CodeletInstance#getEnclosingFile() enclosing file} has a template-override: The template as {@linkplain TemplateOverrides configured}</li> 200 <li>{@linkplain CodeletInstance#getEnclosingPackage() enclosing package} has a template-override: The template as configured.</li> 201 </ol>Otherwise, the default template for the taglet's {@linkplain CodeletType type}. <i>In all cases, because JavaDoc is multi-threaded, the returned template object is duplicated.</i> 202 * @exception IllegalArgumentStateException If the enclosing file's path does not start with the enclosing class {@linkplain CodeletBaseConfig#ENCLOSING_CLASS_SRC_BASE_DIRS base directory}. 203 */ 204 public static final <T extends CodeletTemplateBase> T get(CodeletInstance instance, Appendable debugDest_ifNonNull) { 205 String itemName = null; 206 207 try { 208 if(!instance.isOverviewSummary()) { 209 //According to the user, this uses getEnclosingBaseDirList(). 210 //We're actually using getEnclosingBaseDirs(), which is to 211 //avoid translating the list to an array each time. 212 itemName = IOUtil.getRelativePathCrashIfBadBaseDir(instance.getEnclosingFile().getPath(), 213 "instance.getEnclosingFile().getPath()", "getEnclosingBaseDirList()", getEnclosingBaseDirs()). 214 replace(FILE_SEP, "."); 215 } 216 } catch(RuntimeException rx) { 217 throw CrashIfObject.nullOrReturnCause(instance, "instance", null, rx); 218 } 219 220 boolean doDebug = isDebugOn(null, "zzTemplateOverrides.getresult"); 221 if(doDebug) { 222 debugln(" TemplateOverrides.get: " + itemName + ", type: " + instance.getType()); 223 } 224 225 CodeletTemplateBase tmpl = null; 226 try { 227 if(fileMap.containsKey(itemName)) { 228 TemplateMapForItem tpmlForItem = fileMap.get(itemName); 229 if(doDebug) { 230 debugln(" File override " + tpmlForItem); 231 } 232 tmpl = tpmlForItem.getDuplicatedTemplateOfType(instance.getType(), debugDest_ifNonNull); 233 } else if(pkgMap.containsKey(itemName)) { 234// tmpl = pkgMap.get(itemName).getDuplicatedTemplateOfType(instance.getType(), debugDest_ifNonNull); 235 TemplateMapForItem tpmlForItem = pkgMap.get(itemName); 236 if(doDebug) { 237 debugln(" Package override " + tpmlForItem); 238 } 239 tmpl = tpmlForItem.getDuplicatedTemplateOfType(instance.getType(), debugDest_ifNonNull); 240 } 241 } catch(RuntimeException rx) { 242 throw CrashIfObject.nullOrReturnCause(instance, "instance", null, rx); 243 } 244 if(tmpl == null) { 245 if(doDebug) { 246 debugln(" No override. Returning default template."); 247 } 248 if(instance.getType().isSourceCode()) { 249 tmpl = CodeletTemplateConfig.getDefaultSourceCodeTemplate(). 250 getObjectCopy(debugDest_ifNonNull); 251 } 252 if(instance.getType().isConsoleOut()) { 253 tmpl = CodeletTemplateConfig.getDefaultConsoleOutTemplate(). 254 getObjectCopy(debugDest_ifNonNull); 255 } 256 if(instance.getType().isSourceAndOut()) { 257 tmpl = CodeletTemplateConfig.getDefaultSourceAndOutTemplate(). 258 getObjectCopy(debugDest_ifNonNull); 259 } 260 if(instance.getType().isFileText()) { 261 tmpl = CodeletTemplateConfig.getDefaultFileTextTemplate(). 262 getObjectCopy(debugDest_ifNonNull); 263 } 264 265 } 266 @SuppressWarnings("unchecked") 267 T t = (T)tmpl; 268 if(doDebug) { 269 debugln(" Returning template: " + t.getPath()); 270 } 271 return t; 272 } 273 @SuppressWarnings("unchecked") 274 private static final <T extends CodeletTemplateBase> T newTemplateForTypeFromPath(CodeletType type, String path, int line_num) { 275 CodeletTemplateBase tmplBase = null; 276 String lineNumDesc = "TemplateOverrides configuration file, line " + line_num; 277 try { 278 switch(type) { 279 case SOURCE_CODE: 280 tmplBase = SourceCodeTemplate.newFromPathAndUserExtraGaps(path, lineNumDesc, 281 CodeletTemplateConfig.getUserExtraGapsClass()); break; 282 case CONSOLE_OUT: 283 tmplBase = ConsoleOutTemplate.newFromPathAndUserExtraGaps(path, lineNumDesc, 284 CodeletTemplateConfig.getUserExtraGapsClass()); break; 285 case SOURCE_AND_OUT: 286 tmplBase = SourceAndOutTemplate.newFromPathAndUserExtraGaps(path, lineNumDesc, 287 CodeletTemplateConfig.getUserExtraGapsClass()); break; 288 case FILE_TEXT: 289 tmplBase = FileTextTemplate.newFromPathAndUserExtraGaps(path, lineNumDesc, 290 CodeletTemplateConfig.getUserExtraGapsClass()); break; 291 default: throw new IllegalStateException("Unknown type: template.getType()=" + type); 292 } 293 } catch(RuntimeException rx) { 294 throw CrashIfObject.nullOrReturnCause(type, "type", null, rx); 295 } 296 @SuppressWarnings("unchecked") 297 T t = (T)tmplBase; 298 return t; 299 } 300 public static final String staticToString() { 301 return appendStaticToString(new StringBuilder()).toString(); 302 } 303 public static final StringBuilder appendStaticToString(StringBuilder to_appendTo) { 304 to_appendTo.append("File overrides (" + fileMap.size() + " total)"); 305 306 boolean doDebug = isDebugOn(null, 307 "zzconfiguration.templateoverrides.allentriespostloaded"); 308 309 if(doDebug) { 310 to_appendTo.append(":").append(LINE_SEP); 311 for (Map.Entry<String,TemplateMapForItem> entry : fileMap.entrySet()) { 312 entry.getValue().appendToString(to_appendTo); 313 } 314 } else { 315 to_appendTo.append(LINE_SEP); 316 } 317 318 to_appendTo.append("Package overrides (" + pkgMap.size() + " total)"); 319 320 if(doDebug) { 321 to_appendTo.append(":").append(LINE_SEP); 322 for (Map.Entry<String,TemplateMapForItem> entry : pkgMap.entrySet()) { 323 entry.getValue().appendToString(to_appendTo); 324 } 325 } 326 return to_appendTo; 327 } 328} 329class TemplateMapForItem { 330 private String itemName; 331 public SourceCodeTemplate scTmpl ; 332 public ConsoleOutTemplate coTmpl; 333 public SourceAndOutTemplate saoTmpl; 334 public FileTextTemplate ftTmpl ; 335 public TemplateMapForItem(String item_name, CodeletTemplateBase first_tmpl, int line_num, String line) { 336 CrashIfString.nullEmpty(item_name, "item_name", null); 337 itemName = item_name; 338 addTemplate(first_tmpl, line_num, line); 339 } 340 public String getItemName() { 341 return itemName; 342 } 343 public CodeletTemplateBase getDuplicatedTemplateOfType(CodeletType type, Appendable debugDest_ifNonNull) { 344 switch(type) { 345 case SOURCE_CODE: return (new SourceCodeTemplate(scTmpl, debugDest_ifNonNull)); 346 case CONSOLE_OUT: return (new ConsoleOutTemplate(coTmpl, debugDest_ifNonNull)); 347 case SOURCE_AND_OUT: return (new SourceAndOutTemplate(saoTmpl, debugDest_ifNonNull)); 348 case FILE_TEXT: return (new FileTextTemplate(ftTmpl, debugDest_ifNonNull)); 349 default: throw new IllegalStateException("Unknown type: " + type); 350 } 351 } 352 @SuppressWarnings("unchecked") 353 public void addTemplate(CodeletTemplateBase template, int line_num, String line) { 354 CodeletType type = template.getType(); 355 switch(type) { 356 case SOURCE_CODE: 357 scTmpl = TemplateMapForItem.<SourceCodeTemplate>getParamTmplOrCrashIfAlreadySet 358 (this, scTmpl, (SourceCodeTemplate)template, line_num, line); 359 break; 360 case CONSOLE_OUT: 361 coTmpl = TemplateMapForItem.<ConsoleOutTemplate>getParamTmplOrCrashIfAlreadySet 362 (this, coTmpl, (ConsoleOutTemplate)template, line_num, line); 363 break; 364 case SOURCE_AND_OUT: 365 saoTmpl = TemplateMapForItem.<SourceAndOutTemplate>getParamTmplOrCrashIfAlreadySet 366 (this, saoTmpl, (SourceAndOutTemplate)template, line_num, line); 367 break; 368 case FILE_TEXT: 369 ftTmpl = TemplateMapForItem.<FileTextTemplate>getParamTmplOrCrashIfAlreadySet 370 (this, ftTmpl, (FileTextTemplate)template, line_num, line); 371 break; 372 default: throw new IllegalStateException("Unknown type: template.getType()=" + type); 373 } 374 } 375 private static final <T extends CodeletTemplateBase> T getParamTmplOrCrashIfAlreadySet(TemplateMapForItem map_forItem, T class_tmpl, T param_tmpl, int line_num, String line) { 376 if(class_tmpl != null) { 377 throw new TemplateOverridesConfigLineException(line_num, line, "Template of type " + param_tmpl.getType() + " type already set for " + map_forItem.getItemName()); 378 } 379 return param_tmpl; 380 } 381 public String toString() { 382 return appendToString(new StringBuilder()).toString(); 383 } 384 public StringBuilder appendToString(StringBuilder to_appendTo) { 385 to_appendTo.append(" ").append(getItemName()).append(LINE_SEP); 386 appendTypeToString(to_appendTo, scTmpl); 387 appendTypeToString(to_appendTo, coTmpl); 388 appendTypeToString(to_appendTo, saoTmpl); 389 appendTypeToString(to_appendTo, ftTmpl); 390 return to_appendTo; 391 } 392 public StringBuilder appendTypeToString(StringBuilder to_appendTo, CodeletTemplateBase tmpl) { 393 return ((scTmpl == null) ? to_appendTo 394 : to_appendTo.append(" - ").append(scTmpl.getType()).append(": ").append(scTmpl.getPath())).append(LINE_SEP); 395 } 396} 397class TemplateMapForPackage extends TemplateMapForItem { 398 public TemplateMapForPackage(String item_name, CodeletTemplateBase first_tmpl, int line_num, String line) { 399 super(item_name, first_tmpl, line_num, line); 400 if(item_name.endsWith(".html")) { 401 throw new TemplateOverridesConfigLineException(line_num, line, "Not a package (ends with \".html\")"); 402 } 403 } 404} 405class TemplateMapForFile extends TemplateMapForItem { 406 public TemplateMapForFile(String item_name, CodeletTemplateBase first_tmpl, int line_num, String line) { 407 super(item_name, first_tmpl, line_num, line); 408 if(item_name.endsWith(".html")) { 409 throw new TemplateOverridesConfigLineException(line_num, line, "Not a file (does not end with \".html\")"); 410 } 411 } 412}