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> 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> 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 "the level" (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> <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) <= 0)</CODE> 108 <BR>Where {@code actualLevel} is equal to 109 <BR> <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> ? 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> ? 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> {@link PlainTextFileUtil}.{@link PlainTextFileUtil#getLineIterator(String, String) getLineIterator}(configFile_path, path_varName), 159 <BR> 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> <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 "{@linkplain #isActive(String, DebugLevel, DebugLevel...) active}".</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}