major cleanup:
[mir.git] / source / mir / util / PropertiesManipulator.java
1 /*
2  * Copyright (C) 2001, 2002 The Mir-coders group
3  *
4  * This file is part of Mir.
5  *
6  * Mir is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Mir is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Mir; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * In addition, as a special exception, The Mir-coders gives permission to link
21  * the code of this program with  any library licensed under the Apache Software License,
22  * The Sun (tm) Java Advanced Imaging library (JAI), The Sun JIMI library
23  * (or with modified versions of the above that use the same license as the above),
24  * and distribute linked combinations including the two.  You must obey the
25  * GNU General Public License in all respects for all of the code used other than
26  * the above mentioned libraries.  If you modify this file, you may extend this
27  * exception to your version of the file, but you are not obligated to do so.
28  * If you do not wish to do so, delete this exception statement from your version.
29  */
30 package mir.util;
31
32 import java.io.InputStream;
33 import java.io.InputStreamReader;
34 import java.io.LineNumberReader;
35 import java.io.OutputStream;
36 import java.io.OutputStreamWriter;
37 import java.io.PrintWriter;
38 import java.util.HashMap;
39 import java.util.Iterator;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Vector;
43
44 import multex.Exc;
45 import multex.Failure;
46
47 public class PropertiesManipulator {
48   private List entries;
49   private Map values;
50
51   public PropertiesManipulator() {
52     entries = new Vector();
53     values = new HashMap();
54   }
55
56   public void addEmptyLine() {
57     entries.add(new EmptyLine());
58   }
59
60   public void addComment(String aComment) {
61     entries.add(new Comment(aComment));
62   }
63
64   public void addEntry(String aKey, String aValue) {
65     entries.add(new Entry(aKey, aValue));
66     values.put(aKey, aValue);
67   }
68
69   public Iterator getEntries() {
70     return entries.iterator();
71   }
72
73   public String get(String aKey) {
74     return (String) values.get(aKey);
75   }
76
77   public boolean containsKey(String aKey) {
78     return values.containsKey(aKey);
79   }
80
81   public static class Comment {
82     private String comment;
83
84     public Comment(String aComment) {
85       comment = aComment;
86     }
87
88     public String getComment() {
89       return comment;
90     }
91   }
92
93   public static class EmptyLine {
94     public EmptyLine() {
95     }
96   }
97
98   public static class Entry {
99     private String key;
100     private String value;
101
102     public Entry(String aKey, String aValue) {
103       key = aKey;
104       value = aValue;
105     }
106
107     public String getKey() {
108       return key;
109     }
110
111     public String getValue() {
112       return value;
113     }
114   }
115
116   private final static String PLAIN= "[^\\\\]*";
117   private final static String ESCAPE= "\\\\[ tn]";
118   private final static String UNICODE= "\\\\u[a-fA-F0-9][a-fA-F0-9][a-fA-F0-9][a-fA-F0-9]";
119
120
121   private static String decode(String aValue) {
122     try {
123       SimpleParser parser = new SimpleParser(aValue);
124       StringBuffer result = new StringBuffer();
125
126       while (!parser.isAtEnd()) {
127         result.append(parser.parse(PLAIN));
128
129         if (!parser.isAtEnd()) {
130           if (parser.parses(UNICODE)) {
131             String unicode = parser.parse(UNICODE);
132
133             result.append((char) Integer.parseInt(unicode.substring(2,6), 16));
134           }
135           else if (parser.parses(ESCAPE)) {
136             String escape = parser.parse(ESCAPE);
137             result.append(escape.substring(1));
138           }
139           else
140             throw new PropertiesManipulatorExc("Invalid escape code: " + parser.remainingData());
141         }
142       }
143
144       return result.toString();
145     }
146     catch (Throwable t) {
147       throw new PropertiesManipulatorFailure(t);
148     }
149   }
150
151   private static String encode(String aValue, boolean aUseUnicodeEscapes) {
152     try {
153       StringBuffer result = new StringBuffer();
154       boolean leadingspace=true;
155
156       for (int i = 0; i<aValue.length(); i++) {
157         char c = aValue.charAt(i);
158
159         if (aUseUnicodeEscapes && (c<0x20 || c>0x7e)) {
160           String code=Integer.toHexString(c);
161           result.append("\\u");
162           for (int j=0; j<4-code.length(); j++)
163             result.append("0");
164           result.append(code);
165         }
166         else if (c=='\\')
167         {
168           result.append("\\\\");
169         }
170         else if (c=='\n')
171         {
172           result.append("\\n");
173         }
174         else if (c=='\r')
175         {
176           result.append("\\r");
177         }
178         else if (c=='\t')
179         {
180           result.append("\\t");
181         }
182         else if (c==' ' && leadingspace) {
183           result.append("\\ ");
184         }
185         else {
186           result.append(c);
187         }
188
189         leadingspace = leadingspace && c ==' ';
190       }
191
192       return result.toString();
193     }
194     catch (Throwable t) {
195       throw new PropertiesManipulatorFailure(t);
196     }
197   }
198
199   // ML: to be fixed
200   private final static String SPACE = "[\t\n\r ]*";
201   private final static String KEY = "(([\\\\].)|([^\\\\=: \t\n\r]))*";
202   private final static String SEPARATOR = "[\t\n\r ]*[:=]?[\t\n\r ]*";
203   private final static String VALUE = "(([\\\\].)|([^\\\\]))*";
204
205   public static PropertiesManipulator readProperties(InputStream anInputStream) throws PropertiesManipulatorExc, PropertiesManipulatorFailure {
206     return readProperties(anInputStream, "ISO-8859-1");
207   }
208
209   public static PropertiesManipulator readProperties(InputStream anInputStream, String anEncoding) throws PropertiesManipulatorExc, PropertiesManipulatorFailure {
210     try {
211       PropertiesManipulator result = new PropertiesManipulator();
212       LineNumberReader reader = new LineNumberReader(new InputStreamReader(anInputStream, anEncoding));
213
214       String line = reader.readLine();
215
216       while (line != null) {
217         String trimmedLine = line.trim();
218
219         if (trimmedLine.length() == 0) {
220           result.addEmptyLine();
221         }
222         else if (trimmedLine.startsWith("!") || trimmedLine.startsWith("#")) {
223           result.addComment(line);
224         }
225         else {
226           SimpleParser parser = new SimpleParser(line);
227           parser.skip(SPACE);
228           String key = parser.parse(KEY);
229           parser.skip(SEPARATOR);
230           String value = parser.parse(VALUE);
231           while (parser.remainingData().length()>0) {
232             if (!parser.remainingData().equals("\\"))
233               throw new PropertiesManipulatorExc("internal error: remainingData = " + parser.remainingData());
234
235             line = reader.readLine();
236             if (line==null) {
237               throw new PropertiesManipulatorExc("Unexpected end of file");
238             }
239             parser = new SimpleParser(line);
240             parser.skip(SPACE);
241             value = value + parser.parse(VALUE);
242           }
243
244           result.addEntry(decode(key), decode(value));
245         }
246         line = reader.readLine();
247       }
248
249       reader.close();
250
251       return result;
252     }
253     catch (PropertiesManipulatorExc t) {
254       throw t;
255     }
256     catch (Throwable t) {
257       throw new PropertiesManipulatorFailure(t);
258     }
259   }
260
261   public static void writeProperties(PropertiesManipulator aProperties, OutputStream anOutputStream) throws PropertiesManipulatorExc, PropertiesManipulatorFailure {
262     writeProperties(aProperties, anOutputStream, "ISO-8859-1", true);
263   }
264
265   public static void writeProperties(PropertiesManipulator aProperties, OutputStream anOutputStream, String anEncoding, boolean aUseUnicodeEscapes) throws PropertiesManipulatorExc, PropertiesManipulatorFailure {
266     try {
267       PrintWriter p = new PrintWriter(new OutputStreamWriter(anOutputStream, anEncoding));
268
269       try {
270         Iterator i = aProperties.getEntries();
271
272         while (i.hasNext()) {
273           Object entry = i.next();
274
275           if (entry instanceof EmptyLine) {
276             p.println();
277           }
278           else if (entry instanceof Comment) {
279             p.println(((Comment) entry).getComment());
280           }
281           else if (entry instanceof Entry) {
282             String key = encode( ( (Entry) entry).getKey(), aUseUnicodeEscapes);
283             String value = "";
284             if ( ( (Entry) entry).getValue() != null)
285               value = encode( ( (Entry) entry).getValue(), aUseUnicodeEscapes);
286
287             String line = key + " = " + value;
288
289             p.println(line);
290
291           }
292           else throw new PropertiesManipulatorExc("Unknown entry class: " +entry.getClass().getName());
293         }
294       }
295       finally {
296         p.close();
297       }
298     }
299     catch (Throwable t) {
300       throw new PropertiesManipulatorFailure(t);
301     }
302   }
303
304   public static class PropertiesManipulatorFailure extends Failure {
305     public PropertiesManipulatorFailure(Throwable aThrowable) {
306       super(aThrowable.getMessage(), aThrowable);
307     }
308
309     public PropertiesManipulatorFailure(String aMessage, Throwable aThrowable) {
310       super(aMessage, aThrowable);
311     }
312   }
313
314   public static class PropertiesManipulatorExc extends Exc {
315     public PropertiesManipulatorExc(String aMessage) {
316       super(aMessage);
317     }
318   }
319 }