KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > izforge > izpack > util > VariableSubstitutor


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

21
22 package com.izforge.izpack.util;
23
24 import java.io.IOException JavaDoc;
25 import java.io.InputStream JavaDoc;
26 import java.io.InputStreamReader JavaDoc;
27 import java.io.OutputStream JavaDoc;
28 import java.io.OutputStreamWriter JavaDoc;
29 import java.io.Reader JavaDoc;
30 import java.io.Serializable JavaDoc;
31 import java.io.StringReader JavaDoc;
32 import java.io.StringWriter JavaDoc;
33 import java.io.UnsupportedEncodingException JavaDoc;
34 import java.io.Writer JavaDoc;
35 import java.util.HashMap JavaDoc;
36 import java.util.Map JavaDoc;
37 import java.util.Properties JavaDoc;
38
39 /**
40  * Substitutes variables occurring in an input stream or a string. This implementation supports a
41  * generic variable value mapping and escapes the possible special characters occurring in the
42  * substituted values. The file types specifically supported are plain text files (no escaping),
43  * Java properties files, and XML files. A valid variable name matches the regular expression
44  * [a-zA-Z][a-zA-Z0-9_]* and names are case sensitive. Variables are referenced either by $NAME or
45  * ${NAME} (the latter syntax being useful in situations like ${NAME}NOTPARTOFNAME). If a referenced
46  * variable is undefined then it is not substituted but the corresponding part of the stream is
47  * copied as is.
48  *
49  * @author Johannes Lehtinen <johannes.lehtinen@iki.fi>
50  */

51 public class VariableSubstitutor implements Serializable JavaDoc
52 {
53
54     /**
55      *
56      */

57     private static final long serialVersionUID = 3907213762447685687L;
58
59     /** The variable value mappings */
60     protected transient Properties JavaDoc variables;
61
62     /** Whether braces are required for substitution. */
63     protected boolean bracesRequired = false;
64
65     /** A constant for file type. Plain file. */
66     protected final static int TYPE_PLAIN = 0;
67
68     /** A constant for file type. Java properties file. */
69     protected final static int TYPE_JAVA_PROPERTIES = 1;
70
71     /** A constant for file type. XML file. */
72     protected final static int TYPE_XML = 2;
73
74     /** A constant for file type. Shell file. */
75     protected final static int TYPE_SHELL = 3;
76
77     /** A constant for file type. Plain file with '@' start char. */
78     protected final static int TYPE_AT = 4;
79     
80     /** PLAIN = "plain" */
81     public final static String JavaDoc PLAIN = "plain";
82
83     /** A mapping of file type names to corresponding integer constants. */
84     protected final static Map JavaDoc typeNameToConstantMap;
85
86     // Initialize the file type map
87
static
88     {
89         typeNameToConstantMap = new HashMap JavaDoc();
90         typeNameToConstantMap.put("plain", new Integer JavaDoc(TYPE_PLAIN));
91         typeNameToConstantMap.put("javaprop", new Integer JavaDoc(TYPE_JAVA_PROPERTIES));
92         typeNameToConstantMap.put("xml", new Integer JavaDoc(TYPE_XML));
93         typeNameToConstantMap.put("shell", new Integer JavaDoc(TYPE_SHELL));
94         typeNameToConstantMap.put("at", new Integer JavaDoc(TYPE_AT));
95     }
96
97     /**
98      * Constructs a new substitutor using the specified variable value mappings. The environment
99      * hashtable is copied by reference. Braces are not required by default
100      *
101      * @param variables the map with variable value mappings
102      */

103     public VariableSubstitutor(Properties JavaDoc variables)
104     {
105         this.variables = variables;
106     }
107
108     /**
109      * Get whether this substitutor requires braces.
110      */

111     public boolean areBracesRequired()
112     {
113         return bracesRequired;
114     }
115
116     /**
117      * Specify whether this substitutor requires braces.
118      */

119     public void setBracesRequired(boolean braces)
120     {
121         bracesRequired = braces;
122     }
123
124     /**
125      * Substitutes the variables found in the specified string. Escapes special characters using
126      * file type specific escaping if necessary.
127      *
128      * @param str the string to check for variables
129      * @param type the escaping type or null for plain
130      * @return the string with substituted variables
131      * @exception IllegalArgumentException if unknown escaping type specified
132      */

133     public String JavaDoc substitute(String JavaDoc str, String JavaDoc type) throws IllegalArgumentException JavaDoc
134     {
135         if (str == null) return null;
136
137         // Create reader and writer for the strings
138
StringReader JavaDoc reader = new StringReader JavaDoc(str);
139         StringWriter JavaDoc writer = new StringWriter JavaDoc();
140
141         // Substitute any variables
142
try
143         {
144             substitute(reader, writer, type);
145         }
146         catch (IOException JavaDoc e)
147         {
148             throw new Error JavaDoc("Unexpected I/O exception when reading/writing memory "
149                     + "buffer; nested exception is: " + e);
150         }
151
152         // Return the resulting string
153
return writer.getBuffer().toString();
154     }
155
156     /**
157      * Substitutes the variables found in the specified input stream. Escapes special characters
158      * using file type specific escaping if necessary.
159      *
160      * @param in the input stream to read
161      * @param out the output stream to write
162      * @param type the file type or null for plain
163      * @param encoding the character encoding or null for default
164      * @exception IllegalArgumentException if unknown file type specified
165      * @exception UnsupportedEncodingException if encoding not supported
166      * @exception IOException if an I/O error occurs
167      *
168      * @return the number of substitutions made
169      */

170     public int substitute(InputStream JavaDoc in, OutputStream JavaDoc out, String JavaDoc type, String JavaDoc encoding)
171             throws IllegalArgumentException JavaDoc, UnsupportedEncodingException JavaDoc, IOException JavaDoc
172     {
173         // Check if file type specific default encoding known
174
if (encoding == null)
175         {
176             int t = getTypeConstant(type);
177             switch (t)
178             {
179             case TYPE_JAVA_PROPERTIES:
180                 encoding = "ISO-8859-1";
181                 break;
182             case TYPE_XML:
183                 encoding = "UTF-8";
184                 break;
185             }
186         }
187
188         // Create the reader and writer
189
InputStreamReader JavaDoc reader = (encoding != null ? new InputStreamReader JavaDoc(in, encoding)
190                 : new InputStreamReader JavaDoc(in));
191         OutputStreamWriter JavaDoc writer = (encoding != null ? new OutputStreamWriter JavaDoc(out, encoding)
192                 : new OutputStreamWriter JavaDoc(out));
193
194         // Copy the data and substitute variables
195
int subs = substitute(reader, writer, type);
196
197         // Flush the writer so that everything gets written out
198
writer.flush();
199
200         return subs;
201     }
202     
203     /**
204      * Substitute method Variant that gets An Input Stream and returns A String
205      *
206      * @param in The Input Stream, with Placeholders
207      * @param type The used FormatType
208      *
209      * @throws IllegalArgumentException If a wrong input was given.
210      * @throws UnsupportedEncodingException If the file comes with a wrong Encoding
211      * @throws IOException If an I/O Error occurs.
212      *
213      * @return the substituted result as string
214      */

215     public String JavaDoc substitute( InputStream JavaDoc in, String JavaDoc type
216                              )
217                     throws IllegalArgumentException JavaDoc, UnsupportedEncodingException JavaDoc,
218                            IOException JavaDoc
219     {
220       // Check if file type specific default encoding known
221
String JavaDoc encoding = PLAIN;
222       {
223         int t = getTypeConstant( type );
224
225         switch( t )
226         {
227           case TYPE_JAVA_PROPERTIES:
228             encoding = "ISO-8859-1";
229
230             break;
231
232           case TYPE_XML:
233             encoding = "UTF-8";
234
235             break;
236         }
237       }
238
239       // Create the reader and writer
240
InputStreamReader JavaDoc reader = ( ( encoding != null )
241                                     ? new InputStreamReader JavaDoc( in, encoding )
242                                     : new InputStreamReader JavaDoc( in ) );
243       StringWriter JavaDoc writer = new StringWriter JavaDoc( ) ;
244
245       // Copy the data and substitute variables
246
substitute( reader, writer, type );
247
248       // Flush the writer so that everything gets written out
249
writer.flush( );
250       
251       return writer.getBuffer().toString();
252     }
253
254
255     /**
256      * Substitutes the variables found in the data read from the specified reader. Escapes special
257      * characters using file type specific escaping if necessary.
258      *
259      * @param reader the reader to read
260      * @param writer the writer used to write data out
261      * @param type the file type or null for plain
262      * @exception IllegalArgumentException if unknown file type specified
263      * @exception IOException if an I/O error occurs
264      *
265      * @return the number of substitutions made
266      */

267     public int substitute(Reader JavaDoc reader, Writer JavaDoc writer, String JavaDoc type)
268             throws IllegalArgumentException JavaDoc, IOException JavaDoc
269     {
270         // Check the file type
271
int t = getTypeConstant(type);
272
273         // determine character which starts a variable
274
char variable_start = '$';
275         if (t == TYPE_SHELL)
276             variable_start = '%';
277         else if (t == TYPE_AT) variable_start = '@';
278
279         int subs = 0;
280
281         // Copy data and substitute variables
282
int c = reader.read();
283         // Ignore BOM of UTF-8
284
if( c == 0xEF )
285         {
286             for (int i = 0; i < 2; i++)
287             {
288                 c = reader.read();
289             }
290         }
291         // Ignore quaint return values at UTF-8 BOMs.
292
if( c > 0xFF )
293             c = reader.read();
294         while (true)
295         {
296             // Find the next potential variable reference or EOF
297
while (c != -1 && c != variable_start)
298             {
299                 writer.write(c);
300                 c = reader.read();
301             }
302             if (c == -1) return subs;
303
304             // Check if braces used or start char escaped
305
boolean braces = false;
306             c = reader.read();
307             if (c == '{')
308             {
309                 braces = true;
310                 c = reader.read();
311             }
312             else if (bracesRequired)
313             {
314                 writer.write(variable_start);
315                 continue;
316             }
317             else if (c == -1)
318             {
319                 writer.write(variable_start);
320                 return subs;
321             }
322
323             // Read the variable name
324
StringBuffer JavaDoc nameBuffer = new StringBuffer JavaDoc();
325             while (c != -1 && (braces && c != '}') || (c >= 'a' && c <= 'z')
326                     || (c >= 'A' && c <= 'Z') || (braces && (c == '[') || (c == ']'))
327                     || (((c >= '0' && c <= '9') || c == '_') && nameBuffer.length() > 0))
328             {
329                 nameBuffer.append((char) c);
330                 c = reader.read();
331             }
332             String JavaDoc name = nameBuffer.toString();
333
334             // Check if a legal and defined variable found
335
String JavaDoc varvalue = null;
336
337             if ((!braces || c == '}') && name.length() > 0)
338             {
339                 // check for environment variables
340
if (braces && name.startsWith("ENV[")
341                         && (name.lastIndexOf(']') == name.length() - 1))
342                 {
343                     varvalue = IoHelper.getenv(name.substring(4, name.length() - 1));
344                 }
345                 else
346                     varvalue = variables.getProperty(name);
347
348                 subs++;
349             }
350
351             // Substitute the variable...
352
if (varvalue != null)
353             {
354                 writer.write(escapeSpecialChars(varvalue, t));
355                 if (braces) c = reader.read();
356             }
357             // ...or ignore it
358
else
359             {
360                 writer.write(variable_start);
361                 if (braces) writer.write('{');
362                 writer.write(name);
363             }
364         }
365     }
366
367     /**
368      * Returns the internal constant for the specified file type.
369      *
370      * @param type the type name or null for plain
371      * @return the file type constant
372      */

373     protected int getTypeConstant(String JavaDoc type)
374     {
375         if (type == null) return TYPE_PLAIN;
376         Integer JavaDoc integer = (Integer JavaDoc) typeNameToConstantMap.get(type);
377         if (integer == null)
378             throw new IllegalArgumentException JavaDoc("Unknown file type " + type);
379         else
380             return integer.intValue();
381     }
382
383     /**
384      * Escapes the special characters in the specified string using file type specific rules.
385      *
386      * @param str the string to check for special characters
387      * @param type the target file type (one of TYPE_xxx)
388      * @return the string with the special characters properly escaped
389      */

390     protected String JavaDoc escapeSpecialChars(String JavaDoc str, int type)
391     {
392         StringBuffer JavaDoc buffer;
393         int len;
394         int i;
395         switch (type)
396         {
397         case TYPE_PLAIN:
398         case TYPE_SHELL:
399         case TYPE_AT:
400             return str;
401         case TYPE_JAVA_PROPERTIES:
402             buffer = new StringBuffer JavaDoc(str);
403             len = str.length();
404             for (i = 0; i < len; i++)
405             {
406                 // Check for control characters
407
char c = buffer.charAt(i);
408                 if (c == '\t' || c == '\n' || c == '\r')
409                 {
410                     char tag;
411                     if (c == '\t')
412                         tag = 't';
413                     else if (c == '\n')
414                         tag = 'n';
415                     else
416                         tag = 'r';
417                     buffer.replace(i, i + 1, "\\" + tag);
418                     len++;
419                     i++;
420                 }
421
422                 // Check for special characters
423
if (c == '\\' || c == '"' || c == '\'' || c == ' ')
424                 {
425                     buffer.insert(i, '\\');
426                     len++;
427                     i++;
428                 }
429             }
430             return buffer.toString();
431         case TYPE_XML:
432             buffer = new StringBuffer JavaDoc(str);
433             len = str.length();
434             for (i = 0; i < len; i++)
435             {
436                 String JavaDoc r = null;
437                 char c = buffer.charAt(i);
438                 switch (c)
439                 {
440                 case '<':
441                     r = "&lt;";
442                     break;
443                 case '>':
444                     r = "&gt;";
445                     break;
446                 case '&':
447                     r = "&amp;";
448                     break;
449                 case '\'':
450                     r = "&apos;";
451                     break;
452                 case '"':
453                     r = "&quot;";
454                     break;
455                 }
456                 if (r != null)
457                 {
458                     buffer.replace(i, i + 1, r);
459                     len = buffer.length();
460                     i += r.length() - 1;
461                 }
462             }
463             return buffer.toString();
464         default:
465             throw new Error JavaDoc("Unknown file type constant " + type);
466         }
467     }
468 }
469
Popular Tags