/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>&lt;marker&gt;&lt;open bracket&gt;<i>name</i>&lt;close bracket&gt;</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