/Users/lyon/j4p/src/classUtils/pack/util/SymbolTable.java
|
1 package classUtils.pack.util;
2
3 import java.util.ArrayList;
4 import java.util.BitSet;
5 import java.util.HashMap;
6 import java.util.HashSet;
7 import java.util.Iterator;
8 import java.util.List;
9 import java.util.Map;
10 import java.util.Set;
11
12 /**
13 * A symbol table.
14 * <p>
15 * The {@link #defineSymbol(java.lang.String, java.lang.Object) defineSymbol()}
16 * method can be invoked to define symbols.
17 * <p>
18 * The {@link #evaluate(java.lang.String) evaluate()} method can be used to
19 * evaluate a string containing references to the symbol. In this case,
20 * the "string value" obtained by invoking the {@link #getStringValue(java.lang.String)
21 * getStringValue()} method is used.
22 * <p>
23 * Such references have the format <tt><marker><open bracket><i>name</i><close bracket></tt>.
24 * The default marker is '$', and the default brackets are <tt>()</tt>, so a default reference
25 * looks like <tt>$(<i>name</i>)</tt>.
26 * <p>
27 * By default, if the values contains other symbols, the translation occurs recursively.
28 * <p>
29 * If a symbol is not defined, the table may either fail, substitute a blank
30 * or leave the symbol itself.
31 *
32 * @version 1.1
33 * @author Cristiano Sadun
34 */
35 public class SymbolTable {
36
37 public class UndefinedSymbolException extends IllegalArgumentException {
38
39 private String symbolName;
40
41 UndefinedSymbolException(String symbolName, String msg) {
42 super(msg);
43 this.symbolName=symbolName;
44 }
45
46 /**
47 * Returns the symbolName.
48 * @return String
49 */
50 public String getSymbolName() {
51 return symbolName;
52 }
53
54 }
55
56 /**
57 * A <tt>String</tt> to <tt>String</tt> map containing associations between symbol names
58 * (without markers or brackets) and values.
59 */
60 protected Map symbolTable;
61
62 /**
63 * A BitSet for the options.
64 */
65 protected BitSet options = new BitSet();
66
67 /**
68 * The current symbol marker character. Defaults to <tt>$</tt> (dollar character).
69 */
70 protected char symbolMarker = '$';
71
72
73 /**
74 * A char[2] array containing the current bracket characters. Defaults to <tt>(</tt>, <tt>)</tt> (round braces).
75 */
76 protected char[] bracketPair = new char[] { '(', ')' };
77
78 private String symbolDefSequence =
79 symbolMarker + String.valueOf(bracketPair[0]);
80 private String symbolEndSequence = String.valueOf(bracketPair[1]);
81
82 private static final int NORMAL = 0;
83 private static final int MARKERFOUND = 1;
84 private static final int SEARCHMATCHINGBRACE = 2;
85 private Set undefinedSymbols = new HashSet();
86
87 /**
88 * Options mask (0x00) for the "fail on undefined symbol" option.
89 */
90 public static final int FAIL_ON_UNDEFINED_SYMBOL = 0;
91
92 /**
93 * Options mask (0x01) for the "return blank on undefined symbol" option.
94 */
95 public static final int RETURN_BLANK_ON_UNDEFINED_SYMBOL = 1;
96
97 /**
98 * Options mask (0x02) for the "return symbol on undefined symbol" option.
99 */
100 public static final int RETURN_SYMBOL_ON_UNDEFINED_SYMBOL = 2;
101
102 /**
103 * Options mask (0x04) for the "evaluate symbol values " option.
104 */
105 public static final int EVALUATE_SYMBOL_VALUES = 4;
106
107 /**
108 * Create a symbol table with the given symbol map and the given
109 * failure policy.
110 *
111 * @param symbolTable the symbol table to use, mapping <tt>String</tt> objects
112 * to <tt>String</tt> objects
113 * @param failOnUndefinedSymbol if <b>true</b>, an <tt>IllegalArgumentException</tt>
114 * will be raised if an undefined is found when {@link #evaluate(java.lang.String) resolving}
115 * a String. If <b>false</b> the symbol table will ignore the symbol.
116 */
117 public SymbolTable(Map symbolTable, int failureBehaviour) {
118 this.symbolTable = symbolTable;
119 if (failureBehaviour < FAIL_ON_UNDEFINED_SYMBOL
120 || failureBehaviour > RETURN_SYMBOL_ON_UNDEFINED_SYMBOL)
121 throw new IllegalArgumentException(
122 "Internal error: failureBehaviour must be an integer comprised between "
123 + FAIL_ON_UNDEFINED_SYMBOL
124 + " and "
125 + RETURN_SYMBOL_ON_UNDEFINED_SYMBOL);
126 options.clear(FAIL_ON_UNDEFINED_SYMBOL);
127 options.clear(RETURN_BLANK_ON_UNDEFINED_SYMBOL);
128 options.clear(RETURN_SYMBOL_ON_UNDEFINED_SYMBOL);
129 options.set(EVALUATE_SYMBOL_VALUES);
130 options.set(failureBehaviour);
131 }
132
133 /**
134 * Create a symbol table with the given symbol map, which fails on undefined symbols.
135 *
136 * @see #SymbolTable(java.util.Map, int)
137 *
138 * @param symbolTable the symbol table to use, mapping <tt>String</tt> objects
139 * to <tt>String</tt> objects
140 */
141 public SymbolTable(Map symbolTable) {
142 this(symbolTable, FAIL_ON_UNDEFINED_SYMBOL);
143 }
144
145 /**
146 * Create a symbol table with an empty symbol map, which fails on undefined symbols.
147 */
148 public SymbolTable() {
149 this(new HashMap());
150 }
151
152 /**
153 * Defines a symbol providing symbol name and value.
154 *
155 * @param name the name of the symbol, in the form <tt>$(<i>name</i>)</tt> or
156 * <i>name</i>
157 * @param value the value of the symbol
158 */
159 public synchronized void defineSymbol(String name, Object value) {
160 if (name.startsWith(symbolDefSequence)
161 && name.endsWith(symbolEndSequence))
162 name = name.substring(2, name.length() - 1);
163 symbolTable.put(name, value);
164 }
165
166 /**
167 * Return the value of the symbol with the given name, or <b>null</b> if the
168 * symbol is undefined.
169 * @param name the name of the symbol, in the form <tt>$(<i>name</i>)</tt> or
170 * <i>name</i>
171 * @return the value of the symbol with the given name, or <b>null</b> if the
172 * symbol is undefined.
173 */
174 public synchronized Object getValue(String name) {
175 return symbolTable.get(name);
176 }
177
178 /**
179 * Return the value of the symbol with the given name, translated to String
180 * by its <tt>toString</tt> method, or <b>null</b> if the symbol is undefined.
181 * @param name the name of the symbol, in the form <tt>$(<i>name</i>)</tt> or
182 * <i>name</i>
183 * @return the value of the symbol with the given name, or <b>null</b> if the
184 * symbol is undefined.
185 */
186 public synchronized String getStringValue(String name) {
187 Object obj = symbolTable.get(name);
188 if (obj==null) return null;
189 return obj.toString();
190 }
191
192 /**
193 * Return <b>true</b> if a symbol is defined.
194 *
195 * @param name the name of the symbol, in the form <tt>$(<i>name</i>)</tt> or
196 * <i>name</i>
197 * @return <b>true</b> if a symbol is defined
198 */
199 public boolean isDefined(String name) {
200 if (name.startsWith(symbolDefSequence)
201 && name.endsWith(symbolEndSequence))
202 name = name.substring(2, name.length() - 1);
203 return symbolTable.containsKey(name);
204 }
205
206 /**
207 * Evaluates a String, replacing occurences of each symbol (see the class comment for
208 * the format) with the corresponding value.
209 * <p>
210 * Depending on the current failure policy, will either ignore undefined
211 * symbols, or fail with an <tt>IllegalArgumentException</tt>, and the evaluation
212 * will or will not recursively apply to the symbol values.
213 *
214 * @param s the string to translate
215 * @return the evaluated string
216 */
217 public synchronized String evaluate(String s) {
218 undefinedSymbols.clear();
219 return evaluate0(s, new ArrayList());
220 }
221
222 private String evaluate0(String s, List symbolChain) {
223
224 StringBuffer sb = new StringBuffer();
225 int state = NORMAL;
226 int brace_1 = -1, brace_2 = -1;
227 int brace_count = -1;
228 for (int i = 0; i < s.length(); i++) {
229 char c = s.charAt(i);
230 switch (state) {
231 case NORMAL :
232 if (c == symbolMarker)
233 state = MARKERFOUND;
234 else
235 sb.append(c);
236 break;
237 case MARKERFOUND :
238 if (c == symbolMarker) {
239 // Double dollar found, append '$'
240 sb.append(symbolMarker);
241 state = NORMAL;
242 } else if (c == bracketPair[0]) {
243 brace_1 = i;
244 brace_count = 1;
245 state = SEARCHMATCHINGBRACE;
246 } else
247 throw new IllegalArgumentException(
248 "'"
249 + symbolMarker
250 + "' can be followed only by '"
251 + symbolMarker
252 + "' or '"
253 + bracketPair[0]
254 + "' : "
255 + s.substring(i - 1));
256 break;
257 case SEARCHMATCHINGBRACE :
258 if (s.charAt(i) == bracketPair[0])
259 brace_count++;
260 else if (s.charAt(i) == bracketPair[1]) {
261 brace_count--;
262 if (brace_count == 0) {
263 brace_2 = i;
264 String symbolName =
265 s.substring(brace_1 + 1, brace_2);
266
267 if (symbolChain.contains(symbolName))
268 throw new RuntimeException(
269 "Recursive definition: symbol "
270 + symbolName
271 + " is defined in terms of itself");
272 else
273 symbolChain.add(symbolName);
274
275 String symbolValue = getStringValue(symbolName);
276 if (symbolValue == null) {
277 if (options.get(FAIL_ON_UNDEFINED_SYMBOL)) {
278 throw new UndefinedSymbolException(
279 symbolName,
280 "Symbol "
281 + symbolDefSequence
282 + symbolName
283 + symbolEndSequence
284 + " is not defined");
285 } else if (
286 options.get(
287 RETURN_SYMBOL_ON_UNDEFINED_SYMBOL)) {
288 symbolValue =
289 symbolDefSequence
290 + symbolName
291 + symbolEndSequence;
292 undefinedSymbols.add(symbolName);
293 } else if (
294 options.get(
295 RETURN_BLANK_ON_UNDEFINED_SYMBOL)) {
296 symbolValue = "";
297 }
298 sb.append(symbolValue);
299 } else {
300 if (options.get(EVALUATE_SYMBOL_VALUES))
301 sb.append(evaluate0(symbolValue, symbolChain));
302 else
303 sb.append(symbolValue);
304 }
305
306 if (symbolChain.size() > 0)
307 symbolChain.remove(symbolChain.size() - 1);
308
309 i = brace_2;
310 state = NORMAL;
311 }
312 }
313 break;
314 }
315 }
316
317 return sb.toString();
318 }
319
320 /**
321 * Sets the behaviour in case of symbol not found.
322 *
323 * @param failureBehaviour one of the failure behaviour constants
324 */
325 public synchronized void setBehaviourOnUndefinedSymbol(int failureBehaviour) {
326 if (failureBehaviour < FAIL_ON_UNDEFINED_SYMBOL
327 || failureBehaviour > RETURN_SYMBOL_ON_UNDEFINED_SYMBOL)
328 throw new IllegalArgumentException(
329 "Internal error: failureBehaviour must be an integer comprised between "
330 + FAIL_ON_UNDEFINED_SYMBOL
331 + " and "
332 + RETURN_SYMBOL_ON_UNDEFINED_SYMBOL);
333 options.clear(FAIL_ON_UNDEFINED_SYMBOL);
334 options.clear(RETURN_BLANK_ON_UNDEFINED_SYMBOL);
335 options.clear(RETURN_SYMBOL_ON_UNDEFINED_SYMBOL);
336 options.set(failureBehaviour);
337 }
338
339 /**
340 * Returns the failure behaviour constant denoting the current failure policy.
341 * @param failureBehaviour one of the failure behaviour constants
342 * @return the failure behaviour constant denoting the current failure policy
343 * @param failureBehaviour one of the failure behaviour constants
344 */
345 public int setBehaviourOnUndefinedSymbol() {
346 int behaviour = -1;
347 if (options.get(FAIL_ON_UNDEFINED_SYMBOL))
348 behaviour = FAIL_ON_UNDEFINED_SYMBOL;
349 else if (options.get(RETURN_BLANK_ON_UNDEFINED_SYMBOL))
350 behaviour = RETURN_BLANK_ON_UNDEFINED_SYMBOL;
351 else if (options.get(RETURN_SYMBOL_ON_UNDEFINED_SYMBOL))
352 behaviour = RETURN_SYMBOL_ON_UNDEFINED_SYMBOL;
353 return behaviour;
354 }
355
356 /**
357 * Returns the symbolMarker.
358 * @return char
359 */
360 public char getSymbolMarker() {
361 return symbolMarker;
362 }
363
364 /**
365 * Sets the symbolMarker.
366 * @param symbolMarker The symbolMarker to set
367 */
368 public void setSymbolMarker(char symbolMarker) {
369 this.symbolMarker = symbolMarker;
370 }
371
372 /**
373 * Returns the bracketPair.
374 * @return char[]
375 */
376 public synchronized char[] getBracketPair() {
377 return new char[] { bracketPair[0], bracketPair[1] };
378 }
379
380 /**
381 * Sets the bracketPair. Only char[2] arrays are allowed.
382 * @param bracketPair The bracketPair to set
383 */
384 public synchronized void setBracketPair(char[] bracketPair) {
385 if (bracketPair.length != 2) throw new IllegalArgumentException("bracket pair must be a char[2] array");
386 if (bracketPair[0] == bracketPair[1]) throw new IllegalArgumentException("bracket pair array must contain 2 different characters");
387 this.bracketPair = bracketPair;
388 this.symbolDefSequence = symbolMarker + String.valueOf(bracketPair[0]);
389 this.symbolEndSequence = String.valueOf(bracketPair[1]);
390 }
391
392 /**
393 * Return <b>true</b> if the map recursively evaluates symbol values.
394 * @return <b>true</b> if the map recursively evaluates symbol values.
395 */
396 public boolean isEvaluateSymbolValues() {
397 return options.get(EVALUATE_SYMBOL_VALUES);
398 }
399
400 /**
401 * Set whether or not the map recursively evaluates symbol values.
402 * @param v if <b>true</b>, {@link #evaluate(java.lang.String) evaluate()} will
403 * recursively evaluate symbol values.
404 */
405 public void setEvaluateSymbolValues(boolean v) {
406 if (v)
407 options.set(EVALUATE_SYMBOL_VALUES);
408 else
409 options.clear(EVALUATE_SYMBOL_VALUES);
410 }
411
412 /**
413 * If {@link #RETURN_SYMBOL_ON_UNDEFINED_SYMBOL RETURN_SYMBOL_ON_UNDEFINED_SYMBOL} is
414 * the failure behaviour, return the undefined symbols found during the last evaluation; else
415 * return <n>null</b>.
416 * <p>
417 *
418 * @return Set
419 */
420 public Set getUndefinedSymbolsForLastEvaluation() {
421 if (! options.get(RETURN_SYMBOL_ON_UNDEFINED_SYMBOL)) return null;
422 return undefinedSymbols;
423 }
424
425 /**
426 * Return an iterator over the defined symbol names.
427 * @return an iterator over the defined symbol names.
428 */
429 public Iterator definedSymbols() {
430 return symbolTable.keySet().iterator();
431 }
432
433 /*
434 * A test method
435 *
436 public static void main(String args[]) throws Exception {
437 SymbolTable st = new SymbolTable();
438 st.setSymbolMarker('#');
439 st.defineSymbol("s1", "life");
440 st.defineSymbol("s2", "is");
441 st.defineSymbol("s3", "#(s1)");
442 System.out.println(st.evaluate("#(s1) #(s2) #(s3)"));
443 }*/
444
445
446
447 }
448