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/api/java/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}