KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sf > uitags > util > UiString


1 /**
2  * Nov 14, 2004
3  *
4  * Copyright 2004 uitags
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */

18 package net.sf.uitags.util;
19
20 import java.util.ArrayList JavaDoc;
21 import java.util.Iterator JavaDoc;
22
23 import org.apache.commons.collections.OrderedMap;
24 import org.apache.commons.collections.map.ListOrderedMap;
25
26 /**
27  * Generates <code>String</code> by substituting placeholders within a
28  * <i>template</i> string with named/positional parameters.
29  *
30  * @author hgani
31  * @version $Id: UiString.java,v 1.2 2006/07/02 03:02:51 hgani Exp $
32  */

33 public final class UiString {
34   /**
35    * Generates normal string.
36    */

37   public static final int OPTION_NONE = 0;
38   /**
39    * Surround parameters with double quotes.
40    */

41   public static final int OPTION_AUTO_SURROUND = 1;
42   /**
43    * Encode parameters for use within <i>HTML</i> code.
44    */

45   public static final int OPTION_HTML_ENCODING = 2;
46   /**
47    * Escape parameters' <i>Javascript</i> special characters.
48    */

49   public static final int OPTION_JS_ESCAPE = 4;
50   /**
51    * Process parameters with all options turned on.
52    */

53   public static final int OPTION_ALL =
54       OPTION_AUTO_SURROUND | OPTION_HTML_ENCODING | OPTION_JS_ESCAPE;
55
56   /**
57    * Named parameters for the string generation.
58    */

59   private OrderedMap pairs;
60   /**
61    * Positional parameters for the string generation.
62    */

63   private ArrayList JavaDoc params;
64   /**
65    * The formatting template.
66    */

67   private String JavaDoc template;
68   /**
69    * String generation options.
70    */

71   private int options;
72   /**
73    * String that surrounds each parameter, when OPTION_AUTO_SURROUND is set.
74    */

75   private String JavaDoc surroundString = "\"";
76
77   /**
78    * Creates a "string constructor" object with default options.
79    *
80    * @param template the template
81    */

82   public UiString(String JavaDoc template) {
83     this(template, OPTION_NONE);
84   }
85
86   /**
87    * Constructs a "string constructor" object with options that will be
88    * applied to every paremeter it processes.
89    *
90    * @param template the template
91    * @param options the default options, can be combined using bitwise OR.
92    */

93   public UiString(String JavaDoc template, int options) {
94     this.template = template;
95     this.params = new ArrayList JavaDoc();
96     this.pairs = new ListOrderedMap();
97     setDefaultOptions(options);
98   }
99
100   /**
101    * Sets the default options for the <code>String</code> construction.
102    *
103    * @param opts the default options, can be combined using bitwise OR.
104    */

105   public void setDefaultOptions(int opts) {
106     if (opts < OPTION_NONE) {
107       throw new IllegalArgumentException JavaDoc("Invalid options value.");
108     }
109     if (opts > OPTION_ALL) {
110       throw new IllegalArgumentException JavaDoc("Invalid options value.");
111     }
112     this.options = opts;
113   }
114
115   /**
116    * See {@link #OPTION_NONE}.
117    */

118   public void clearOptions() {
119     setDefaultOptions(OPTION_NONE);
120   }
121
122   /**
123    * See {@link #OPTION_AUTO_SURROUND}.
124    */

125   public void autoSurround() {
126     setDefaultOptions(this.options | OPTION_AUTO_SURROUND);
127   }
128
129   /**
130    * See {@link #OPTION_HTML_ENCODING}.
131    */

132   public void htmlEncode() {
133     setDefaultOptions(this.options | OPTION_HTML_ENCODING);
134   }
135
136   /**
137    * See {@link #OPTION_JS_ESCAPE}.
138    */

139   public void jsEscape() {
140     setDefaultOptions(this.options | OPTION_JS_ESCAPE);
141   }
142
143   /**
144    * Sets the <code>String</code> to surround each parameter. This is
145    * useful only when OPTION_AUTO_SURROUND is set.
146    *
147    * @param str the <code>String</code>
148    */

149   public void setSurroundString(String JavaDoc str) {
150     this.surroundString = str;
151   }
152
153   /**
154    * Binds a positional parameter. Its position depends on the order of
155    * the bindings. It is relative to the number of parameters that have
156    * been bound before the current binding.
157    *
158    * @param param the parameter
159    */

160   public void bind(Object JavaDoc param) {
161     bind(param, this.options);
162   }
163
164   /**
165    * Binds a positional parameter with options.
166    *
167    * @see #bind(Object)
168    * @param param the parameter
169    * @param opts the options
170    */

171   public void bind(Object JavaDoc param, int opts) {
172     if (isMapped()) {
173       throw new IllegalStateException JavaDoc("Bind cannot be used along with map.");
174     }
175     this.params.add(process(param, opts));
176   }
177
178   /**
179    * Maps a parameter to a key or name.
180    *
181    * @param key the key or name
182    * @param value the variable or object
183    */

184   public void map(String JavaDoc key, Object JavaDoc value) {
185     map(key, value, this.options);
186   }
187
188   /**
189    * Maps a parameter to a key or name, specifying the string generation
190    * options.
191    *
192    * @param key the key or name
193    * @param value the variable or object
194    * @param opts the options
195    */

196   public void map(String JavaDoc key, Object JavaDoc value, int opts) {
197     if (isBound()) {
198       throw new IllegalStateException JavaDoc("Map cannot be used along with bind.");
199     }
200     this.pairs.put(key, process(value, opts));
201   }
202
203   /**
204    * Checks whether the parameters are named (previously set using
205    * <code>map</code> methods).
206    *
207    * @return true if it is, false otherwise
208    */

209   private boolean isMapped() {
210     return this.pairs.size() != 0;
211   }
212
213   /**
214    * Checks whether the parameters are positional (previously set using
215    * <code>bind</code> methods).
216    *
217    * @return true if it is, false otherwise
218    */

219   private boolean isBound() {
220     return this.params.size() != 0;
221   }
222
223   /**
224    * Processes parameter according to the specified options.
225    *
226    * @param obj the parameter
227    * @param opts the options
228    * @return the processed string
229    */

230   private String JavaDoc process(Object JavaDoc obj, int opts) {
231     String JavaDoc temp = String.valueOf(obj);
232     if (isSet(opts, OPTION_HTML_ENCODING)) {
233       temp = encodeHtml(temp);
234     }
235
236     if (isSet(opts, OPTION_JS_ESCAPE)) {
237       temp = escapeJs(temp);
238     }
239
240     if (isSet(opts, OPTION_AUTO_SURROUND)) {
241       temp = this.surroundString + temp + this.surroundString;
242     }
243
244     return temp;
245   }
246
247   /**
248    * Clears all parameters.
249    */

250   public void clearBindParameters() {
251     this.params.clear();
252   }
253
254   /**
255    * Checks whether a specific option is set.
256    *
257    * @param opts the specified options combination
258    * @param specific a certain option to test with
259    * @return true if the <code>specific</code> option is in the
260    * <code>options</code> combination
261    */

262   private boolean isSet(int opts, int specific) {
263     return (opts & specific) != 0;
264   }
265
266   /**
267    * Encodes string for safe use within <i>HTML</i> code.
268    *
269    * @param str the string to encode
270    * @return the encoded string
271    */

272   private String JavaDoc encodeHtml(String JavaDoc str) {
273     StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
274     char curr;
275     for (int i = 0; i < str.length(); i++) {
276       curr = str.charAt(i);
277       switch (curr) {
278         case '<':
279           buffer.append("&lt;");
280           break;
281         case '>':
282           buffer.append("&gt;");
283           break;
284         case '&':
285           buffer.append("&amp;");
286           break;
287         case ' ':
288           buffer.append("&nbsp;");
289           break;
290         case '"':
291           buffer.append("&quot;");
292           break;
293         case '\'':
294           buffer.append("&#039;");
295           break;
296         default:
297           buffer.append(curr);
298       }
299     }
300     return String.valueOf(buffer);
301   }
302
303   /**
304    * Escape <i>Javascript</i> special characters. Currently this does the
305    * exactly the same thing as {@link #escapeJava(String)}.
306    *
307    * @param str the string to encode
308    * @return the encoded string
309    */

310   private String JavaDoc escapeJs(String JavaDoc str) {
311     return escapeJava(str);
312   }
313
314   /**
315    * Escape <i>Java</i> special characters.
316    *
317    * @param str the string to encode
318    * @return the encoded string
319    */

320   private String JavaDoc escapeJava(String JavaDoc str) {
321     StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
322     char curr;
323     for (int i = 0; i < str.length(); i++) {
324       curr = str.charAt(i);
325       switch (curr) {
326         case '\'':
327           buffer.append("\\\'");
328           break;
329         case '"':
330           buffer.append("\\\"");
331           break;
332         case '\\':
333           buffer.append("\\\\");
334           break;
335         default:
336           buffer.append(curr);
337       }
338     }
339     return String.valueOf(buffer);
340   }
341
342   /**
343    * Constructs a formatted string from template and specified parameters.
344    * Because we cannot always determine that a parameter has at least
345    * matching holder, for consistency reason, we do not perform validation
346    * for non-matching parameters/holders (no matter whether <i>mapping</i>
347    * technique or <i>binding</i> technique is used).
348    * <p>
349    * When the mapped String itself is of form <code>{key}</code>, where
350    * <code>key</code> is the name of the subsequent holder, the expected
351    * behaviour, is that when the subsequent holder is parsed, the holder
352    * will be replaced with its mapped String.
353    * For example:
354    * Template: "{a} and {b}"
355    * If, {a} map to {b}
356    * If, {b} map to bValue
357    * Constructed String: "bValue and bValue"
358    * <p>
359    * So, if the intended mapped String is "{b}" instead of referring to
360    * another holder, the braces should be escaped with <code>\</code>.
361    * For example:
362    * Template: "{a} and {b}"
363    * If, {a} map to \{b\}
364    * If, {b} map to bValue
365    * Constructed String: "{b} and bValue"
366    * <p>
367    * It is strongly recommended to always avoid having a holder-like mapped
368    * String, because the constructed String really depends on the order
369    * of the mapping specified by the programmer.
370    * For example:
371    * Template: "{a} and {b} and {c}"
372    * Assumming that the mapping was done in the following order:
373    * If, {c} map to {b}
374    * If, {b} map to bValue
375    * If, {a} map to {b}
376    * Constructed String: "{b} and bValue and bValue"
377    *
378    * @return the formatted string
379    */

380   public String JavaDoc construct() {
381     if (isBound()) {
382       try {
383         return simpleConstruct(this.template,
384             (String JavaDoc[]) this.params.toArray(new String JavaDoc[this.params.size()]));
385       }
386       catch (IllegalArgumentException JavaDoc e) {
387         throw new IllegalStateException JavaDoc(e.getMessage());
388       }
389     }
390
391     String JavaDoc result = this.template;
392     //Map.Entry entry;
393
//Iterator iter = this.pairs.entrySet().iterator();
394
Iterator JavaDoc iter = this.pairs.orderedMapIterator();
395     while (iter.hasNext()) {
396       //entry = (Map.Entry) iter.next();
397
String JavaDoc key = (String JavaDoc) iter.next();
398
399       // we need to escape the slashes twice (for both the pattern
400
// and the replacement), because they are part of the
401
// regular expression
402
result = result.replaceAll("\\{" + key + "\\}",
403           escapeJava(String.valueOf(this.pairs.get(key))));
404     }
405     result = result.replaceAll("\\\\\\{", "\\{");
406     result = result.replaceAll("\\\\\\}", "\\}");
407
408     return result;
409   }
410
411   /**
412    * Constructs a formatted string without having to instantiate this class.
413    * If the number bound parameters and the number of place holders do not
414    * matched, any parameters/holders that do not have corresponding matchings
415    * will be ignored.
416    *
417    * @see #construct()
418    * @param template the format template
419    * @param params the positional parameters array
420    * @return the formatted string
421    * @throws NumberFormatException if invalid parameter position number
422    * encountered
423    * @throws IllegalArgumentException if there is no corresponding
424    * positional parameter
425    */

426   public static String JavaDoc simpleConstruct(String JavaDoc template, String JavaDoc[] params) {
427     int currIndex = 0;
428     int openIndex = 0;
429     int closeIndex = 0;
430     int listIndex = 0;
431     StringBuffer JavaDoc result = new StringBuffer JavaDoc();
432     boolean loop = true;
433
434     // any "{num}" pattern will be referred as node
435
while (true) {
436       openIndex = template.indexOf("{", currIndex);
437       if (openIndex < 0) {
438         loop = false;
439       }
440       else {
441         closeIndex = template.indexOf("}", openIndex);
442         if (closeIndex < 0) {
443           loop = false;
444         }
445       }
446       if (!loop) {
447         result.append(template.substring(currIndex, template.length()));
448         break;
449       }
450       result.append(template.substring(currIndex, openIndex));
451       try {
452         listIndex = Integer.parseInt(
453             template.substring(openIndex + 1, closeIndex));
454         if (listIndex < 0) { // not a valid node, ignore the right brace
455
throw new NumberFormatException JavaDoc(
456               "Invalid parameter position index '" + listIndex + "'.");
457         }
458         if (listIndex >= params.length) { // just ignore
459
// put back the holder
460
String JavaDoc holder = "{" + listIndex + "}";
461           result.append(holder);
462           currIndex = openIndex + holder.length();
463         }
464         else {
465           result.append(String.valueOf(params[listIndex]));
466           // having processed the current node, we can be certain that
467
// the next node is after the current right brace
468
currIndex = closeIndex + 1;
469         }
470       }
471       catch (NumberFormatException JavaDoc x) { // not a valid node
472
result.append("{");
473         // nothing else to do, find the next node
474
// start next search from current open index, because there
475
// may be another left brace between the current left brace
476
// and the current right brace
477
currIndex = openIndex + 1;
478       }
479     }
480     return result.toString();
481   }
482 }
Popular Tags