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 &quot;{@link com.github.aliteralmind.codelet.CodeletType#SOURCE_CODE SOURCE_CODE}&quot;, &quot;{@link com.github.aliteralmind.codelet.CodeletType#CONSOLE_OUT CONSOLE_OUT}&quot;, &quot;{@link com.github.aliteralmind.codelet.CodeletType#SOURCE_AND_OUT SOURCE_AND_OUT}&quot;, or &quot;{@link com.github.aliteralmind.codelet.CodeletType#FILE_TEXT FILE_TEXT}&quot;.</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}(&quot;(?:\\t|[ \\t]{2,})&quot;)</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/> &nbsp; &nbsp; <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}