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/apijava/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}