Source code: com/eireneh/util/PropertiesUtil.java
1
2 package com.eireneh.util;
3
4 import java.util.*;
5 import java.io.*;
6
7 import com.eireneh.util.*;
8
9 /**
10 * A generic class of Properties utils.
11 * It would be good if we could put this stuff in java.lang ...
12 *
13 * <table border='1' cellPadding='3' cellSpacing='0' width="100%">
14 * <tr><td bgColor='white'class='TableRowColor'><font size='-7'>
15 * Distribution Licence:<br />
16 * Project B is free software; you can redistribute it
17 * and/or modify it under the terms of the GNU General Public License,
18 * version 2 as published by the Free Software Foundation.<br />
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 * General Public License for more details.<br />
23 * The License is available on the internet
24 * <a href='http://www.gnu.org/copyleft/gpl.html'>here</a>, by writing to
25 * <i>Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
26 * MA 02111-1307, USA</i>, Or locally at the Licence link below.<br />
27 * The copyright to this program is held by it's authors.
28 * </font></td></tr></table>
29 * @see <a href='http://www.eireneh.com/servlets/Web'>Project B Home</a>
30 * @see docs.Licence
31 * @author Joe Walker
32 */
33 public class PropertiesUtil
34 {
35 /**
36 * Write a Properties to a Stream an leave the stream in tact
37 */
38 public static void save(Properties prop, OutputStream out, String title) throws IOException
39 {
40 // In JDK 1.2 this should change to store. The quashed exception
41 // problem is probably not an issue here, since it would get
42 // re-raised by the following line.
43 saveInternal(prop, out, title);
44 out.write((DONE+SEPARATOR).getBytes());
45 }
46
47 /**
48 * Writes this property list (key and element pairs) in this
49 * <code>Properties</code> table to the output stream in a format suitable
50 * for loading into a <code>Properties</code> table using the
51 * <code>load</code> method.
52 * @param out an output stream.
53 * @param header a description of the property list.
54 */
55 private static synchronized void saveInternal(Properties prop, OutputStream out, String header) throws IOException
56 {
57 BufferedWriter awriter = new BufferedWriter(new OutputStreamWriter(out, "8859_1"));
58 if (header != null)
59 writeln(awriter, "#" + header);
60
61 writeln(awriter, "#" + new Date().toString());
62 for (Enumeration e = prop.keys(); e.hasMoreElements();)
63 {
64 String key = (String) e.nextElement();
65 String val = (String) prop.get(key);
66 key = saveConvert(key);
67 val = saveConvert(val);
68 writeln(awriter, key + "=" + val);
69 }
70 awriter.flush();
71 }
72
73 /**
74 * Write a line to a file
75 */
76 private static void writeln(BufferedWriter bw, String s) throws IOException
77 {
78 bw.write(s);
79 bw.newLine();
80 }
81
82 /**
83 * Converts unicodes to encoded \\uxxxx
84 * and writes out any of the characters in specialSaveChars
85 * with a preceding slash
86 */
87 private static String saveConvert(String theString)
88 {
89 char aChar;
90 int len = theString.length();
91 StringBuffer outBuffer = new StringBuffer(len*2);
92
93 for (int x=0; x<len; )
94 {
95 aChar = theString.charAt(x++);
96 switch (aChar)
97 {
98 case '\\':
99 outBuffer.append('\\');
100 outBuffer.append('\\');
101 continue;
102
103 case '\t':
104 outBuffer.append('\\');
105 outBuffer.append('t');
106 continue;
107
108 case '\n':
109 outBuffer.append('\\');
110 outBuffer.append('n');
111 continue;
112
113 case '\r':
114 outBuffer.append('\\');
115 outBuffer.append('r');
116 continue;
117
118 case '\f':
119 outBuffer.append('\\');
120 outBuffer.append('f');
121 continue;
122
123 default:
124 if ((aChar < 20) || (aChar > 127))
125 {
126 outBuffer.append('\\');
127 outBuffer.append('u');
128 outBuffer.append(toHex((aChar >> 12) & 0xF));
129 outBuffer.append(toHex((aChar >> 8) & 0xF));
130 outBuffer.append(toHex((aChar >> 4) & 0xF));
131 outBuffer.append(toHex((aChar >> 0) & 0xF));
132 }
133 else
134 {
135 if (specialSaveChars.indexOf(aChar) != -1)
136 outBuffer.append('\\');
137
138 outBuffer.append(aChar);
139 }
140 }
141 }
142
143 return outBuffer.toString();
144 }
145
146 /**
147 * Write a Properties to a Stream an leave the stream in tact
148 */
149 public static void load(Properties prop, InputStream in) throws IOException
150 {
151 BufferedReader bin = new BufferedReader(new InputStreamReader(in));
152 StringBuffer file = new StringBuffer();
153
154 boolean alive = true;
155
156 try
157 {
158 while (alive)
159 {
160 String line = bin.readLine();
161
162 if (line == null || line.equals(DONE))
163 {
164 alive = false;
165 }
166 else
167 {
168 file.append(line);
169 file.append(SEPARATOR);
170 }
171 }
172 }
173 catch (Exception ex)
174 {
175 Reporter.informUser(PropertiesUtil.class, ex);
176 }
177
178 InputStream ain = new ByteArrayInputStream(file.toString().getBytes());
179 loadInternal(ain, prop);
180 }
181
182 /**
183 * Reads a property list (key and element pairs) from the input stream.
184 * @param in the input stream
185 * @exception IOException if an error occurred when reading from the input stream.
186 */
187 private static void loadInternal(InputStream inStream, Properties prop) throws IOException
188 {
189 BufferedReader in = new BufferedReader(new InputStreamReader(inStream, "8859_1"));
190 while (true)
191 {
192 // Get next line
193 String line = in.readLine();
194 if (line == null)
195 return;
196
197 if (line.length() > 0)
198 {
199 // Continue lines that end in slashes if they are not comments
200 char firstChar = line.charAt(0);
201 if ((firstChar != '#') && (firstChar != '!'))
202 {
203 while (continueLine(line))
204 {
205 String nextLine = in.readLine();
206 if(nextLine == null)
207 nextLine = new String("");
208
209 String loppedLine = line.substring(0, line.length()-1);
210
211 // Advance beyond whitespace on new line
212 int startIndex=0;
213 for(startIndex=0; startIndex<nextLine.length(); startIndex++)
214 if (whiteSpaceChars.indexOf(nextLine.charAt(startIndex)) == -1)
215 break;
216 nextLine = nextLine.substring(startIndex,nextLine.length());
217 line = new String(loppedLine+nextLine);
218 }
219
220 // Find start of key
221 int len = line.length();
222 int keyStart;
223 for(keyStart=0; keyStart<len; keyStart++)
224 {
225 if (whiteSpaceChars.indexOf(line.charAt(keyStart)) == -1)
226 break;
227 }
228
229 // Find separation between key and value
230 int separatorIndex;
231 for(separatorIndex=keyStart; separatorIndex<len; separatorIndex++)
232 {
233 char currentChar = line.charAt(separatorIndex);
234 if (currentChar == '\\')
235 separatorIndex++;
236 else if(keyValueSeparators.indexOf(currentChar) != -1)
237 break;
238 }
239
240 // Skip over whitespace after key if any
241 int valueIndex;
242 for (valueIndex=separatorIndex; valueIndex<len; valueIndex++)
243 if (whiteSpaceChars.indexOf(line.charAt(valueIndex)) == -1)
244 break;
245
246 // Skip over one non whitespace key value separators if any
247 if (valueIndex < len)
248 if (strictKeyValueSeparators.indexOf(line.charAt(valueIndex)) != -1)
249 valueIndex++;
250
251 // Skip over white space after other separators if any
252 while (valueIndex < len)
253 {
254 if (whiteSpaceChars.indexOf(line.charAt(valueIndex)) == -1)
255 break;
256 valueIndex++;
257 }
258
259 String key = line.substring(keyStart, separatorIndex);
260 String value = (separatorIndex < len) ? line.substring(valueIndex, len) : "";
261
262 // Convert then store key and value
263 key = loadConvert(key);
264 value = loadConvert(value);
265
266 prop.put(key, value);
267 }
268 }
269 }
270 }
271
272 /**
273 * Returns true if the given line is a line that must
274 * be appended to the next line
275 */
276 private static boolean continueLine(String line)
277 {
278 int slashCount = 0;
279 int index = line.length() - 1;
280 while((index >= 0) && (line.charAt(index--) == '\\'))
281 slashCount++;
282
283 return (slashCount % 2 == 1);
284 }
285
286 /**
287 * Converts encoded \\uxxxx to unicode chars
288 * and changes special saved chars to their original forms
289 */
290 private static String loadConvert(String theString)
291 {
292 char aChar;
293 int len = theString.length();
294 StringBuffer outBuffer = new StringBuffer(len);
295
296 for (int x=0; x<len; )
297 {
298 aChar = theString.charAt(x++);
299 if (aChar == '\\')
300 {
301 aChar = theString.charAt(x++);
302 if (aChar == 'u')
303 {
304 // Read the xxxx
305 int value=0;
306 for (int i=0; i<4; i++)
307 {
308 aChar = theString.charAt(x++);
309 switch (aChar)
310 {
311 case '0': case '1': case '2': case '3': case '4':
312 case '5': case '6': case '7': case '8': case '9':
313 value = (value << 4) + aChar - '0';
314 break;
315
316 case 'a': case 'b': case 'c':
317 case 'd': case 'e': case 'f':
318 value = (value << 4) + 10 + aChar - 'a';
319 break;
320
321 case 'A': case 'B': case 'C':
322 case 'D': case 'E': case 'F':
323 value = (value << 4) + 10 + aChar - 'A';
324 break;
325
326 default:
327 throw new IllegalArgumentException("Malformed \\uxxxx encoding.");
328 }
329 }
330
331 outBuffer.append((char)value);
332 }
333 else
334 {
335 if (aChar == 't') aChar = '\t';
336 else if (aChar == 'r') aChar = '\r';
337 else if (aChar == 'n') aChar = '\n';
338 else if (aChar == 'f') aChar = '\f';
339
340 outBuffer.append(aChar);
341 }
342 }
343 else
344 {
345 outBuffer.append(aChar);
346 }
347 }
348 return outBuffer.toString();
349 }
350
351 /**
352 * Convert a nibble to a hex character
353 * @param nibble the nibble to convert.
354 */
355 private static char toHex(int nibble)
356 {
357 return hexDigit[(nibble & 0xF)];
358 }
359
360 /** A table of hex digits */
361 private static final char[] hexDigit =
362 {
363 '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
364 };
365
366 /** Cache of line separator */
367 private static final String SEPARATOR = System.getProperty("line.separator");
368
369 /** The end marker text */
370 private static final String DONE = "DONE";
371
372 /* From Properties */
373 private static final String keyValueSeparators = "=: \t\r\n\f";
374 private static final String strictKeyValueSeparators = "=:";
375 private static final String specialSaveChars = "=: \t\r\n\f#!";
376 private static final String whiteSpaceChars = " \t\r\n\f";
377 }