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.util;
016   import  com.github.xbn.io.DebugLevel;
017   import  com.github.xbn.io.NewTextAppenterFor;
018   import  com.github.xbn.io.PlainTextFileUtil;
019   import  com.github.xbn.io.TextAppenter;
020   import  com.github.xbn.lang.CrashIfObject;
021   import  com.github.xbn.lang.Null;
022   import  com.github.xbn.list.MapUtil;
023   import  com.github.xbn.util.JavaRegexes;
024   import  java.util.Collections;
025   import  java.util.Iterator;
026   import  java.util.Map;
027   import  java.util.NoSuchElementException;
028   import  java.util.TreeMap;
029   import  java.util.regex.Matcher;
030   import  java.util.regex.Pattern;
031/**
032   <p>Collection of names referring to specific debugging tasks, each associated to an arbitrary numeric level.</p>
033
034 * @author  Copyright (C) 2014, Jeff Epstein, dual-licensed under the LGPL (version 3.0 or later) or the ASL (version 2.0). See source code for details. <code><a href="http://codelet.aliteralmind.com">http://codelet.aliteralmind.com</a></code>, <code><a href="https://github.com/aliteralmind/codelet">https://github.com/aliteralmind/codelet</a></code>
035 **/
036public class NamedDebuggers  {
037   private final Map<String,DebugLevel> nameLvlMap;
038   private TextAppenter dbgAptrAllQueries;
039   /**
040      <p>Create a new instance from the path of a configuration file.</p>
041
042      <p>This sets {@link #getMap() getMap}{@code ()} to an {@linkplain java.util.Collections#unmodifiableMap(Map) immutable version} of</p>
043
044<blockquote>NamedDebuggers.{@link #newMapFromConfigFile(Map, String, String, Appendable) newMapFromConfigFile}(map_toAddToIfNonNull, configFile_path, path_varName,
045<br/> &nbsp; &nbsp; debugAll_ifNonNull)</blockquote>
046
047        @see  #NamedDebuggers(Map, Iterator, String, Appendable)
048    */
049   public NamedDebuggers(Map<String,DebugLevel> map_toAddToIfNonNull, String configFile_path, String path_varName, Appendable debugAll_ifNonNull)  {
050      Map<String,DebugLevel> nameLvlMap2 = NamedDebuggers.
051         newMapFromConfigFile(map_toAddToIfNonNull, configFile_path, path_varName,
052            debugAll_ifNonNull);
053      nameLvlMap = Collections.unmodifiableMap(nameLvlMap2);
054      setAllQueriesDebug(null);
055   }
056   /**
057      <p>Create a new instance from a configuration file's line iterator.</p>
058
059      <p>This sets {@link #getMap() getMap}{@code ()} to an {@linkplain java.util.Collections#unmodifiableMap(Map) immutable version} of</p>
060
061<blockquote>NamedDebuggers.{@link #newMapFromConfigFile(Map, Iterator, String, Appendable) newMapFromConfigFile}(map_toAddToIfNonNull, configFile_path, path_varName,
062<br/> &nbsp; &nbsp; debugAll_ifNonNull)</blockquote>
063
064        @see  #NamedDebuggers(Map, String, String, Appendable)
065    */
066   public NamedDebuggers(Map<String,DebugLevel> map_toAddToIfNonNull, Iterator<String> configFile_lineItr, String itr_VarName, Appendable debugAll_ifNonNull)  {
067      Map<String,DebugLevel> nameLvlMap2 = NamedDebuggers.newMapFromConfigFile(map_toAddToIfNonNull, configFile_lineItr, itr_VarName, debugAll_ifNonNull);
068      nameLvlMap = Collections.unmodifiableMap(nameLvlMap2);
069      setAllQueriesDebug(null);
070   }
071   /**
072      <p>Unmodifiable name-to-level map.</p>
073    */
074   public Map<String,DebugLevel> getMap()  {
075      return  nameLvlMap;
076   }
077   /**
078      <p>Set debugging for outputting all {@code isActive} queries. This is useful for determining levels that may be unused.</p>
079
080    * @param  debugEachQuery_ifNonNull  Get with {@link #getAllQueriesDebugAptr() getAllQueriesDebugAptr}{@code ()}.
081    * @see  #isActive(String, DebugLevel, DebugLevel...) isActive
082    */
083   public void setAllQueriesDebug(Appendable debugEachQuery_ifNonNull)  {
084      dbgAptrAllQueries = NewTextAppenterFor.appendableUnusableIfNull(debugEachQuery_ifNonNull);
085   }
086   /**
087      <p>Get the text appenter for debugging each query.</p>
088
089    * @return  A non-{@code null} appenter.
090    * @see  #setAllQueriesDebug(Appendable)
091    */
092   public TextAppenter getAllQueriesDebugAptr()  {
093      return  dbgAptrAllQueries;
094   }
095   /**
096      <p>Is a named level active?.</p>
097
098      <p>If {@linkplain #getAllQueriesDebugAptr() each-query debugging} is on, this outputs {@code name}, regardless the value returned by this function.</p>
099
100    * @param  name  Must be an existing name.
101    * @param  actual_level1  The actual debugging level is the highest level in this or any of the elements in {@code actualLevels_2AndUp}. May not be {@code null}.
102    * @param  actualLevels_2AndUp  If multiple levels make up &quot;the level&quot; (the {@linkplain com.github.xbn.io.DebugLevel#getHighestLevel(DebugLevel...) highest one} between all of them), these are those additional level elements.
103    * @return  If <code>{@link #getMap() getMap}().{@link java.util.Map#get(Object) get}(name)</code> is<ul>
104         <li>{@code null}: {@code false}</li>
105         <li><code>{@link com.github.xbn.io.DebugLevel#OFF OFF}</code>: {@code true}</li>
106      </ul>Otherwise:
107      <br/> &nbsp; &nbsp; <code>(getMap}().get(name).<!-- GENERIC PARAMETERS FAIL IN @link --><a href="http://docs.oracle.com/javase/7/docs/api/java/lang/Comparable.html#compareTo(T)">compareTo</a>(actualLevel) &lt;= 0)</code>
108      <br/>Where {@code actualLevel} is equal to
109      <br/> &nbsp; &nbsp; <code>actual_level1.{@link com.github.xbn.io.DebugLevel#getHighestLevel(DebugLevel...) getHighestLevel}(actualLevels_2AndUp)</code>
110    * @exception  NoSuchElementException  If <code>{@link #getMap() getMap}().{@link java.util.Map#containsKey(Object)}(name)</code> is {@code false}.
111    */
112   public boolean isActive(String name, DebugLevel actual_level1, DebugLevel... actualLevels_2AndUp)  {
113      if(!getMap().containsKey(name))  {
114         throw  new NoSuchElementException("name=\"" + name + "\"");
115      }
116
117      if(dbgAptrAllQueries.isUseable())  {
118         dbgAptrAllQueries.appentln("NamedDebuggers.isActive query for \"" + name + "\".");
119      }
120
121      DebugLevel nameLevel = getMap().get(name);
122      if(nameLevel == null)  {
123         return  false;
124      }
125      if(nameLevel.isOff())  {
126         return  true;
127      }
128      DebugLevel actualLevel = actual_level1.getHighestLevel(actualLevels_2AndUp);
129
130      return  (nameLevel.compareTo(actualLevel) <= 0);  //nameLevel <= actualLevel
131   }
132   /**
133      <p>If a named-level is active, get an appenter for it.</p>
134
135    * @return  <code>({@link #isActive(String, DebugLevel, DebugLevel...) isActive}(name, actual_level1, actualLevels_2AndUp)
136      <br/> &nbsp; &nbsp; ? &nbsp;debug_aptr : null)</code>
137    * @see  #getAppendableIfActive(String, TextAppenter, DebugLevel, DebugLevel...)
138    */
139   public TextAppenter getAppenterIfActive(String name, TextAppenter debug_aptr, DebugLevel actual_level1, DebugLevel... actualLevels_2AndUp)  {
140      return  (isActive(name, actual_level1, actualLevels_2AndUp)
141         ?  debug_aptr : null);
142   }
143   /**
144      <p>If a named-level is active, get an appendable for it.</p>
145
146    * @return  <code>({@link #isActive(String, DebugLevel, DebugLevel...) isActive}(name, actual_level1, actualLevels_2AndUp)
147      <br/> &nbsp; &nbsp; ? &nbsp;debug_aptr.{@link TextAppenter#getAppendable() getAppendable}() : null)</code>
148    * @see  #getAppendableIfActive(String, TextAppenter, DebugLevel, DebugLevel...)
149    */
150   public Appendable getAppendableIfActive(String name, TextAppenter debug_aptr, DebugLevel actual_level1, DebugLevel... actualLevels_2AndUp)  {
151      return  (isActive(name, actual_level1, actualLevels_2AndUp)
152         ?  debug_aptr.getAppendable() : null);
153   }
154   /**
155      <p>Creates a new map associated level names to level numbers, as read in from a configuration text file.</p>
156
157    * @return  <code>{@link #newMapFromConfigFile(Map, Iterator, String, Appendable) newMapFromConfigFile}(map_toAddToIfNonNull,
158         <br/> &nbsp; &nbsp; {@link PlainTextFileUtil}.{@link PlainTextFileUtil#getLineIterator(String, String) getLineIterator}(configFile_path, path_varName),
159         <br/> &nbsp; &nbsp; path_varName, debugAll_ifNonNull)</code>
160    */
161   public static final Map<String,DebugLevel> newMapFromConfigFile(Map<String,DebugLevel> map_toAddToIfNonNull, String configFile_path, String path_varName, Appendable debugAll_ifNonNull)  {
162      return  newMapFromConfigFile(map_toAddToIfNonNull,
163         PlainTextFileUtil.getLineIterator(configFile_path, path_varName),
164         path_varName, debugAll_ifNonNull);
165   }
166   /**
167      <p>Creates a new map associated level names to level numbers, as read in from a configuration text file.</p>
168
169      <h4>Configuration file format</h4>
170
171      <p>Overall:<ul>
172         <li>Whitespace at the ends of lines is permissible.</li>
173         <li>Lines (after trimming) that start with a hash ({@code '#'}) are ignored.</li>
174         <li>Empty lines are permissible.</li>
175      </ul></p>
176
177      <p>Each line is in the format</p>
178
179<blockquote><pre><i>[level-name]</i>=<i>[level-number]</i></pre></blockquote>
180
181      <p><ul>
182         <li><i>[level-name]</i> is the descriptive name of the level. Must be non-{@code null}, and must be in the same format as a valid Java package name--that is, it must match
183         <br/> &nbsp; &nbsp; <code>{@link com.github.xbn.util.JavaRegexes}.{@link com.github.xbn.util.JavaRegexes#PACKAGE_NAME PACKAGE_NAME}</code>
184         <br/>Names must be unique.</li>
185         <li><i>[level-number]</i> is a number between {@code -1} and {@code 5}, inclusive.<ul>
186<!--
187   Originates in
188      com.github.aliteralmind.codelet.util.NamedDebuggers
189   needed by
190      com.github.aliteralmind.codelet.CodeletBootstrap
191   ...START
192   -->
193            <li><b>{@code -1}</b>: Never output.</li>
194            <li><b>{@code 0}</b>: Always output (even if {@link com.github.xbn.io.DebugLevel#OFF OFF}).</li>
195            <li><b>{@code 1}</b>: Output only when the actual level {@linkplain com.github.xbn.io.DebugLevel#isOn() on}--equal to or greater than {@link com.github.xbn.io.DebugLevel#ONE ONE}.</li>
196            <li><b>{@code 2}</b>: Output when the actual level is {@link com.github.xbn.io.DebugLevel#TWO TWO} or greater.</li>
197            <li><b>{@code 3}</b>: Output when the actual level is {@link com.github.xbn.io.DebugLevel#THREE THREE} or greater.</li>
198            <li><b>{@code 4}</b>: Output when the actual level is {@link com.github.xbn.io.DebugLevel#FOUR FOUR} or {@link com.github.xbn.io.DebugLevel#FIVE FIVE}.</li>
199            <li><b>{@code 5}</b>: Output only when the actual level is {@code FIVE}.</li>
200<!--
201   Originates in
202      com.github.aliteralmind.codelet.util.NamedDebuggers
203   needed by
204      com.github.aliteralmind.codelet.CodeletBootstrap
205   ...END
206   -->
207         </ul>When a named level is output, it is &quot;{@linkplain #isActive(String, DebugLevel, DebugLevel...) active}&quot;.</li>
208      </ul></p>
209
210      <h4>Example config file</h4>
211
212<blockquote><pre>### Possible values are -1, 0, 1, 2, 3, 4, 5
213BasicCustomizers.eliminateCmtBlocksPkgLineAndPkgReferences.alterers=3
214BasicCustomizers.lineRange.alterers.eliminateIndentationOrNull=5
215BasicCustomizers.lineRange.alterers=3
216BasicCustomizers.lineRange.filter=1
217BasicCustomizers.lineRange.template=2
218CodeletTemplateBase.newTemplateFromPath.templateparseandfill=3
219CodeletTemplateBase.templateparseandfill=3
220ConsoleOutProcessor.consoleoutputfromexamplecodestringsig=1
221SourceAndOutProcessor.template=2
222SourceCodeProcessor.progress=1
223TagletOfTypeProcessor.classcustomizersplit=2
224TagletOfTypeProcessor.getCustomizerSigFromString=1
225TagletOfTypeProcessor.newInstructionsForDefaults.tabtospacealterer=3
226TagletOfTypeProcessor.newInstructionsForDefaults.templateparseandfill=3
227TagletProcessor.codeletfound=0
228configuration.allvaluessummary=1
229configuration.progress=0
230configuration.templateoverrides.allentriespostloaded=3</pre></blockquote>
231
232    * @param  map_toAddToIfNonNull  If non-{@code null}, the lines read in from the configuration file are added to this map. Must allow <!-- GENERIC PARAMETERS FAIL IN @link --><a href="http://docs.oracle.com/javase/7/docs/api/java/util/Map.html#put(K, V)">{@code put}</a>, and may not contain any names in the configuration file.
233    * @param  configFile_lineItr  May not be {@code null}, and must refer to a validly-formatted configuration file.
234    * @param  itr_VarName  Descriptive name of {@code configFile_lineItr}. <i>Should</i> not be {@code null} or empty.
235    * @param  debugAll_ifNonNull  If non-{@code null}, each line is output via this, before being added to the map. The current size of the map is also output.
236    * @exception  NamedDebuggerFormatException  If a line is badly formatted or contains illegal values.
237    * @exception  BadDuplicateException  If two lines contain the same name.
238        @exception  UnsupportedOperationException  If {@code put} is unsupported by {@code map_toAddToIfNonNull}.
239    * @see  #newMapFromConfigFile(Map, String, String, Appendable)
240        @see  #NamedDebuggers(Map, String, String, Appendable)
241        @see  #NamedDebuggers(Map, Iterator, String, Appendable)
242    */
243   public static final Map<String,DebugLevel> newMapFromConfigFile(Map<String,DebugLevel> map_toAddToIfNonNull, Iterator<String> configFile_lineItr, String itr_VarName, Appendable debugAll_ifNonNull)  {
244      TextAppenter dbgAll = NewTextAppenterFor.appendableUnusableIfNull(debugAll_ifNonNull);
245      Map<String,DebugLevel> map = ((map_toAddToIfNonNull == null)
246         ?  new TreeMap<String,DebugLevel>()
247         :  map_toAddToIfNonNull);
248
249      try  {
250         int lineNum = 0;
251         while(configFile_lineItr.hasNext())  {
252            lineNum++;
253            String line = configFile_lineItr.next().trim();
254
255            if(line.length() == 0  ||  line.charAt(0) == '#')  {
256               continue;
257            }
258
259            if(dbgAll.isUseable())  {
260               dbgAll.appentln("[" + itr_VarName + ":" + lineNum + ", mapsize=" + map.size() + "] About to add \"" + line + "\".");
261            }
262
263            String[] nameLevel = line.split("=");
264            if(nameLevel.length != 2)  {
265               throw  new NamedDebuggerFormatException("[" + itr_VarName + ":" + lineNum + ", mapsize=" + map.size() + "] Does not contain exactly one equals sign ('='). line=\"" + line + "\"");
266            }
267            String name = nameLevel[0];
268            if(!pkgMtchr.reset(name).matches())  {
269               throw  new NamedDebuggerFormatException("[" + itr_VarName + ":" + lineNum + ", mapsize=" + map.size() + "] Name (\"" + name + "\") does not match JavaRegexes.PACKAGE_NAME. line=\"" + line + "\"");
270            }
271            DebugLevel level = null;
272            try  {
273               level = DebugLevel.getFromString012345OrNeg1ForNull(nameLevel[1], null);
274            }  catch(NamedDebuggerFormatException iax)  {
275               throw  new NamedDebuggerFormatException("[" + itr_VarName + ":" + lineNum + ", mapsize=" + map.size() + "] Level number (\"" + nameLevel[1] + "\") is not valid: \"" + line + "\"", iax);
276            }
277
278            try  {
279               MapUtil.putOrCrashIfContainsKey(map, itr_VarName, name, Null.BAD,
280                  "[debug-item-name]", level, Null.OK, "[debug-item-level]");
281            }  catch(RuntimeException rx)  {
282               throw  new NamedDebuggerFormatException("itr_VarName=" + itr_VarName + ", line=\"" + line + "\", name=" + name + ", level=" + level, rx);
283            }
284         }
285      }  catch(RuntimeException rx)  {
286         throw  CrashIfObject.nullOrReturnCause(configFile_lineItr, itr_VarName, null, rx);
287      }
288      return  map;
289   }
290      private static final Matcher pkgMtchr = Pattern.compile(JavaRegexes.PACKAGE_NAME).matcher("");
291}