001: /*
002: * $Id: VariableSubstitutor.java 1005 2004-12-16 17:18:02Z tschwarze $
003: * IzPack
004: * Copyright (C) 2001 Johannes Lehtinen
005: *
006: * File : VariableSubstitutor.java
007: * Description : Variable substitutor backend.
008: * Author's email : johannes.lehtinen@iki.fi
009: * Author's Website : http://www.iki.fi/jle/
010: *
011: * This program is free software; you can redistribute it and/or
012: * modify it under the terms of the GNU General Public License
013: * as published by the Free Software Foundation; either version 2
014: * of the License, or any later version.
015: *
016: * This program is distributed in the hope that it will be useful,
017: * but WITHOUT ANY WARRANTY; without even the implied warranty of
018: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
019: * GNU General Public License for more details.
020: *
021: * You should have received a copy of the GNU General Public License
022: * along with this program; if not, write to the Free Software
023: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
024: */
025: package com.izforge.izpack.installer;
026:
027: import java.io.IOException;
028: import java.io.InputStream;
029: import java.io.InputStreamReader;
030: import java.io.OutputStream;
031: import java.io.OutputStreamWriter;
032: import java.io.Reader;
033: import java.io.StringReader;
034: import java.io.StringWriter;
035: import java.io.UnsupportedEncodingException;
036: import java.io.Writer;
037: import java.util.HashMap;
038: import java.util.Map;
039:
040: import com.izforge.izpack.util.IoHelper;
041:
042: /**
043: * Substitutes variables occurring in an input stream or a string. This
044: * implementation supports a generic variable value mapping and escapes the
045: * possible special characters occurring in the substituted values. The file
046: * types specifically supported are plain text files (no escaping), Java
047: * properties files, and XML files. A valid variable name matches the regular
048: * expression [a-zA-Z][a-zA-Z0-9_]* and names are case sensitive. Variables are
049: * referenced either by $NAME or ${NAME} (the latter syntax being useful in
050: * situations like ${NAME}NOTPARTOFNAME). If a referenced variable is undefined
051: * then it is not substituted but the corresponding part of the stream is
052: * copied as is.
053: *
054: * @author Johannes Lehtinen <johannes.lehtinen@iki.fi>
055: */
056: public class VariableSubstitutor {
057:
058: /** The variable value mappings */
059: protected Map variables;
060:
061: /** A constant for file type. Plain file. */
062: protected final static int TYPE_PLAIN = 0;
063:
064: /** A constant for file type. Java properties file. */
065: protected final static int TYPE_JAVA_PROPERTIES = 1;
066:
067: /** A constant for file type. XML file. */
068: protected final static int TYPE_XML = 2;
069:
070: /** A constant for file type. Shell file. */
071: protected final static int TYPE_SHELL = 3;
072:
073: /** A mapping of file type names to corresponding integer constants. */
074: protected static Map typeNameToConstantMap;
075:
076: // Initialize the file type map
077: static {
078: typeNameToConstantMap = new HashMap();
079: typeNameToConstantMap.put("plain", new Integer(TYPE_PLAIN));
080: typeNameToConstantMap.put("javaprop", new Integer(
081: TYPE_JAVA_PROPERTIES));
082: typeNameToConstantMap.put("xml", new Integer(TYPE_XML));
083: typeNameToConstantMap.put("shell", new Integer(TYPE_SHELL));
084: }
085:
086: /**
087: * Constructs a new substitutor using the specified variable value mappings.
088: * The given map is copied by reference.
089: *
090: * @param variables the map with variable value mappings
091: */
092: public VariableSubstitutor(Map variables) {
093: this .variables = variables;
094: }
095:
096: /**
097: * Substitutes the variables found in the specified string. Escapes special
098: * characters using file type specific escaping if necessary.
099: *
100: * @param str the string to check for variables
101: * @param type the escaping type or null for plain
102: * @return the string with substituted variables
103: * @exception IllegalArgumentException if unknown escaping type specified
104: */
105: public String substitute(String str, String type)
106: throws IllegalArgumentException {
107: if (str == null)
108: return null;
109:
110: // Create reader and writer for the strings
111: StringReader reader = new StringReader(str);
112: StringWriter writer = new StringWriter();
113:
114: // Substitute any variables
115: try {
116: substitute(reader, writer, type);
117: } catch (IOException e) {
118: throw new Error(
119: "Unexpected I/O exception when reading/writing memory "
120: + "buffer; nested exception is: " + e);
121: }
122:
123: // Return the resulting string
124: return writer.getBuffer().toString();
125: }
126:
127: /**
128: * Substitutes the variables found in the specified input stream. Escapes
129: * special characters using file type specific escaping if necessary.
130: *
131: * @param in the input stream to read
132: * @param out the output stream to write
133: * @param type the file type or null for plain
134: * @param encoding the character encoding or null
135: * for default
136: * @exception IllegalArgumentException if unknown file type specified
137: * @exception UnsupportedEncodingException if encoding not supported
138: * @exception IOException if an I/O error occurs
139: */
140: public void substitute(InputStream in, OutputStream out,
141: String type, String encoding)
142: throws IllegalArgumentException,
143: UnsupportedEncodingException, IOException {
144: // Check if file type specific default encoding known
145: if (encoding == null) {
146: int t = getTypeConstant(type);
147: switch (t) {
148: case TYPE_JAVA_PROPERTIES:
149: encoding = "ISO-8859-1";
150: break;
151: case TYPE_XML:
152: encoding = "UTF-8";
153: break;
154: }
155: }
156:
157: // Create the reader and writer
158: InputStreamReader reader = (encoding != null ? new InputStreamReader(
159: in, encoding)
160: : new InputStreamReader(in));
161: OutputStreamWriter writer = (encoding != null ? new OutputStreamWriter(
162: out, encoding)
163: : new OutputStreamWriter(out));
164:
165: // Copy the data and substitute variables
166: substitute(reader, writer, type);
167:
168: // Flush the writer so that everything gets written out
169: writer.flush();
170: }
171:
172: /**
173: * Substitutes the variables found in the data read from the specified
174: * reader. Escapes special characters using file type specific escaping if
175: * necessary.
176: *
177: * @param reader the reader to read
178: * @param writer the writer used to write data out
179: * @param type the file type or null for plain
180: * @exception IllegalArgumentException if unknown file type specified
181: * @exception IOException if an I/O error occurs
182: */
183: public void substitute(Reader reader, Writer writer, String type)
184: throws IllegalArgumentException, IOException {
185: // Check the file type
186: int t = getTypeConstant(type);
187:
188: // determine character which starts a variable
189: char variable_start = (t == TYPE_SHELL ? '%' : '$');
190:
191: // Copy data and substitute variables
192: int c = reader.read();
193: while (true) {
194: // Find the next potential variable reference or EOF
195: while (c != -1 && c != variable_start) {
196: writer.write(c);
197: c = reader.read();
198: }
199: if (c == -1)
200: return;
201:
202: // Check if braces used
203: boolean braces = false;
204: c = reader.read();
205: if (c == '{') {
206: braces = true;
207: c = reader.read();
208: } else if (c == -1) {
209: writer.write(variable_start);
210: return;
211: }
212:
213: // Read the variable name
214: StringBuffer nameBuffer = new StringBuffer();
215: while ((c >= 'a' && c <= 'z')
216: || (c >= 'A' && c <= 'Z')
217: || (c == '[')
218: || (c == ']')
219: || (((c >= '0' && c <= '9') || c == '_') && nameBuffer
220: .length() > 0)) {
221: nameBuffer.append((char) c);
222: c = reader.read();
223: }
224: String name = nameBuffer.toString();
225:
226: // Check if a legal and defined variable found
227: String varvalue = null;
228:
229: if ((!braces || c == '}') && name.length() > 0) {
230: // check for environment variables
231: if (braces && name.startsWith("ENV[")
232: && (name.lastIndexOf(']') == name.length() - 1)) {
233: varvalue = IoHelper.getenv(name.substring(4, name
234: .length() - 1));
235: } else
236: varvalue = (String) variables.get(name);
237: }
238:
239: // Substitute the variable...
240: if (varvalue != null) {
241: writer.write(escapeSpecialChars(varvalue, t));
242: if (braces)
243: c = reader.read();
244: }
245: // ...or ignore it
246: else {
247: writer.write(variable_start);
248: if (braces)
249: writer.write('{');
250: writer.write(name);
251: }
252: }
253: }
254:
255: /**
256: * Returns the internal constant for the specified file type.
257: *
258: * @param type the type name or null for plain
259: * @return the file type constant
260: */
261: protected int getTypeConstant(String type) {
262: if (type == null)
263: return TYPE_PLAIN;
264: Integer integer = (Integer) typeNameToConstantMap.get(type);
265: if (integer == null)
266: throw new IllegalArgumentException("Unknown file type "
267: + type);
268: else
269: return integer.intValue();
270: }
271:
272: /**
273: * Escapes the special characters in the specified string using file type
274: * specific rules.
275: *
276: * @param str the string to check for special characters
277: * @param type the target file type (one of TYPE_xxx)
278: * @return the string with the special characters properly escaped
279: */
280: protected String escapeSpecialChars(String str, int type) {
281: StringBuffer buffer;
282: int len;
283: int i;
284: switch (type) {
285: case TYPE_PLAIN:
286: case TYPE_SHELL:
287: return str;
288: case TYPE_JAVA_PROPERTIES:
289: buffer = new StringBuffer(str);
290: len = str.length();
291: for (i = 0; i < len; i++) {
292: // Check for control characters
293: char c = buffer.charAt(i);
294: if (c == '\t' || c == '\n' || c == '\r') {
295: char tag;
296: if (c == '\t')
297: tag = 't';
298: else if (c == '\n')
299: tag = 'n';
300: else
301: tag = 'r';
302: buffer.replace(i, i + 1, "\\" + tag);
303: len++;
304: i++;
305: }
306:
307: // Check for special characters
308: if (c == '\\' || c == '"' || c == '\'' || c == ' ') {
309: buffer.insert(i, '\\');
310: len++;
311: i++;
312: }
313: }
314: return buffer.toString();
315: case TYPE_XML:
316: buffer = new StringBuffer(str);
317: len = str.length();
318: for (i = 0; i < len; i++) {
319: String r = null;
320: char c = buffer.charAt(i);
321: switch (c) {
322: case '<':
323: r = "<";
324: break;
325: case '>':
326: r = ">";
327: break;
328: case '&':
329: r = "&";
330: break;
331: case '\'':
332: r = "'";
333: break;
334: case '"':
335: r = """;
336: break;
337: }
338: if (r != null) {
339: buffer.replace(i, i + 1, r);
340: len = buffer.length();
341: i += r.length() - 1;
342: }
343: }
344: return buffer.toString();
345: default:
346: throw new Error("Unknown file type constant " + type);
347: }
348: }
349: }
|