001: /*
002: * PropertiesFile.java
003: * Copyright (c) 2000,2001 Dirk Moebius (dmoebius@gmx.net)
004: *
005: * jEdit buffer options:
006: * :tabSize=4:indentSize=4:noTabs=false:maxLineLen=0:
007: *
008: * This program is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU General Public License
010: * as published by the Free Software Foundation; either version 2
011: * of the License, or any later version.
012: *
013: * This program is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: * GNU General Public License for more details.
017: *
018: * You should have received a copy of the GNU General Public License
019: * along with this program; if not, write to the Free Software
020: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
021: */
022: package org.acm.seguin.ide.common.options;
023:
024: import java.io.BufferedWriter;
025: import java.io.File;
026: import java.io.FileOutputStream;
027: import java.io.IOException;
028: import java.io.OutputStreamWriter;
029: import java.util.Enumeration;
030: import java.util.Hashtable;
031: import org.acm.seguin.util.FileSettings;
032: import org.acm.seguin.util.SettingNotFoundException;
033: import org.acm.seguin.ide.common.IDEPlugin;
034: import org.acm.seguin.ide.common.IDEInterface;
035:
036: /**
037: * An extension to <code>org.acm.seguin.util.FileSettings</code> with methods
038: * for storing and saving new properties.
039: *
040: *@author Mike Atkinson (<a href="mailto:javastyle@ladyshot.demon.co.uk">
041: * Mike@ladyshot.demon.co.uk</a> )
042: *@author Dirk Moebius (<a href="mailto:dmoebius@gmx.net">dmoebius@gmx.net
043: * </a>)
044: *@created 03 September 2003
045: *@version $Version: $
046: *@since 1.0
047: */
048: public class PropertiesFile {
049:
050: /**
051: * sentinel for deleted keys
052: */
053: private final static Object DELETED = new Object();
054:
055: private FileSettings props = null;
056: private Hashtable newValues = new Hashtable();
057:
058: /**
059: * initializes the properties with the settings of a <code>FileSettings</code>
060: * instance.
061: *
062: *@param props Description of the Parameter
063: */
064: public PropertiesFile(FileSettings props) {
065: this .props = props;
066: }
067:
068: /**
069: * Gets the localProperty attribute of the PropertiesFile object
070: *
071: *@param key Description of the Parameter
072: *@return The localProperty value
073: */
074: public boolean isLocalProperty(String key) {
075: boolean lp = props.isLocalProperty(key);
076: return lp;
077: }
078:
079: /**
080: *@param key the property key
081: *@return the value of the property as String, or null if the property
082: * cannot be found.
083: */
084: public String getString(String key) {
085: Object obj = newValues.get(key);
086: if (obj == DELETED) {
087: return null;
088: } else if (obj != null) {
089: return obj.toString();
090: } else {
091: try {
092: return props.getString(key);
093: } catch (SettingNotFoundException snfex) {
094: return null;
095: }
096: }
097: }
098:
099: /**
100: *@param key the property key
101: *@param defaultValue a default value
102: *@return the value of the property as String, or the default
103: * value if the property cannot be found.
104: */
105: public String getString(String key, String defaultValue) {
106: String val = getString(key);
107: return val != null ? val : defaultValue;
108: }
109:
110: /**
111: *@param key the property key
112: *@param defaultValue a default value
113: *@return the value of the property as int, or the
114: * defaultValue if either the property cannot be found, or the stored
115: * property is not a valid integer.
116: */
117: public int getInteger(String key, int defaultValue) {
118: String value = getString(key);
119: if (value == null) {
120: return defaultValue;
121: }
122:
123: try {
124: return Integer.parseInt(value);
125: } catch (NumberFormatException e) {
126: IDEPlugin.log(IDEInterface.ERROR, this ,
127: "invalid number for " + key + ": " + value);
128: return defaultValue;
129: }
130: }
131:
132: /**
133: *@param key the property key
134: *@return true, if the value of the property equals "true", false
135: * otherwise
136: */
137: public boolean getBoolean(String key) {
138: String value = getString(key);
139: if (value == null) {
140: return true;
141: }
142: return new Boolean(value).booleanValue();
143: }
144:
145: /**
146: *@param key the property key
147: *@param defaultValue Description of the Parameter
148: *@return true, if the value of the property equals "true",
149: * false otherwise
150: */
151: public boolean getBoolean(String key, boolean defaultValue) {
152: String value = getString(key);
153: if (value == null) {
154: //throw new SettingNotFoundException("Refactory", "pretty", key);
155: return defaultValue;
156: }
157: return new Boolean(value).booleanValue();
158: }
159:
160: /**
161: * Sets the string attribute of the PropertiesFile object
162: *
163: *@param key The new string value
164: *@param value The new string value
165: */
166: public void setString(String key, String value) {
167: newValues.put(key, value);
168: }
169:
170: /**
171: * Description of the Method
172: *
173: *@param key Description of the Parameter
174: */
175: public void deleteKey(String key) {
176: // Mark the key as deleted in the newValues table. The key is not
177: // simply removed from newValues, because it could still be in
178: // props (and we have no means to remove from props).
179: newValues.put(key, DELETED);
180: }
181:
182: /**
183: * Reloads the original property file, overrides the old values with the
184: * stored new ones, and saves the property file, if there are any changes.
185: */
186: public synchronized void save() {
187: if (newValues.isEmpty()) {
188: // no new values: nothing to do!
189: return;
190: }
191:
192: // determine if any property was changed
193: boolean changed = false;
194: // insert new values into writeValues, or delete the keys that are
195: // marked as DELETED in newValues
196: for (Enumeration e = newValues.keys(); e.hasMoreElements();) {
197: String key = (String) e.nextElement();
198: Object newval = newValues.get(key);
199: if (newval == null || newval == DELETED) {
200: props.removeKey(key);
201: changed = true;
202: } else {
203: // compare new value with old value from pretty.settings:
204: String oldval = null;
205: try {
206: oldval = props.getString(key);
207: } catch (SettingNotFoundException snfex) {
208: }
209: if (oldval == null || !newval.toString().equals(oldval)) {
210: props.setString(key, newval.toString());
211: changed = true;
212: }
213: }
214: }
215:
216: if (changed) {
217: // only bother to save if propery changed or deleted.
218: props.save();
219: }
220: }
221:
222: /**
223: * Description of the Method
224: *
225: *@return Description of the Return Value
226: */
227: public String toString() {
228: return "PropertiesFile[" + props + "]";
229: }
230:
231: /**
232: * Reloads the original property file, overrides the old values with the
233: * stored new ones, and saves the property file, if there are any changes.
234: *
235: *@param file Description of the Parameter
236: *@exception IOException Description of the Exception
237: */
238: public synchronized void save(File file) throws IOException {
239: if (newValues.isEmpty()) {
240: return;
241: }
242: // no new values: nothing to do!
243:
244: // create a hashtable for the values to be written
245: Hashtable writeValues = new Hashtable();
246: // reload property file
247: props.setReloadNow(true);
248: // copy values of original property file into writeValues
249: for (Enumeration e = props.getKeys(); e.hasMoreElements();) {
250: String key = (String) e.nextElement();
251: String val = props.getString(key);
252: writeValues.put(key, val);
253: }
254:
255: // determine if any property was changed
256: boolean changed = false;
257: // insert new values into writeValues, or delete the keys that are
258: // marked as DELETED in newValues
259: for (Enumeration e = newValues.keys(); e.hasMoreElements();) {
260: String key = (String) e.nextElement();
261: Object newval = newValues.get(key);
262: if (newval == null || newval == DELETED) {
263: writeValues.remove(key);
264: changed = true;
265: } else {
266: // compare new value with old value from pretty.settings:
267: String oldval = null;
268: try {
269: oldval = props.getString(key);
270: } catch (SettingNotFoundException snfex) {
271: }
272: if (oldval == null || !newval.toString().equals(oldval)) {
273: writeValues.put(key, newval.toString());
274: changed = true;
275: }
276: }
277: }
278:
279: // if no property was changed or deleted, we can stop here
280: if (!changed) {
281: return;
282: }
283:
284: // open file for write:
285: BufferedWriter awriter = new BufferedWriter(
286: new OutputStreamWriter(new FileOutputStream(file),
287: "8859_1"));
288:
289: // save property file with all new and old entries from writeValues
290: for (Enumeration e = writeValues.keys(); e.hasMoreElements();) {
291: String key = (String) e.nextElement();
292: String val = writeValues.get(key).toString();
293: key = saveConvert(key, true);
294: val = saveConvert(val, false);
295: awriter.write(key + "=" + val);
296: awriter.newLine();
297: }
298:
299: // close file:
300: awriter.flush();
301: awriter.close();
302: }
303:
304: /*
305: * Converts unicodes to encoded \uxxxx
306: * and writes out any of the characters in specialSaveChars
307: * with a preceding slash
308: */
309: /**
310: * Description of the Method
311: *
312: *@param theString Description of the Parameter
313: *@param escapeSpace Description of the Parameter
314: *@return Description of the Return Value
315: */
316: private static String saveConvert(String theString,
317: boolean escapeSpace) {
318: int len = theString.length();
319: StringBuffer outBuffer = new StringBuffer(len * 2);
320:
321: for (int x = 0; x < len; x++) {
322: char aChar = theString.charAt(x);
323: switch (aChar) {
324: case ' ':
325: if (x == 0 || escapeSpace) {
326: outBuffer.append('\\');
327: }
328: outBuffer.append(' ');
329: break;
330: case '\\':
331: outBuffer.append('\\');
332: outBuffer.append('\\');
333: break;
334: case '\t':
335: outBuffer.append('\\');
336: outBuffer.append('t');
337: break;
338: case '\n':
339: outBuffer.append('\\');
340: outBuffer.append('n');
341: break;
342: case '\r':
343: outBuffer.append('\\');
344: outBuffer.append('r');
345: break;
346: case '\f':
347: outBuffer.append('\\');
348: outBuffer.append('f');
349: break;
350: default:
351: if ((aChar < 0x0020) || (aChar > 0x007e)) {
352: outBuffer.append('\\');
353: outBuffer.append('u');
354: outBuffer.append(toHex((aChar >> 12) & 0xF));
355: outBuffer.append(toHex((aChar >> 8) & 0xF));
356: outBuffer.append(toHex((aChar >> 4) & 0xF));
357: outBuffer.append(toHex(aChar & 0xF));
358: } else {
359: if (specialSaveChars.indexOf(aChar) != -1) {
360: outBuffer.append('\\');
361: }
362: outBuffer.append(aChar);
363: }
364: }
365: }
366:
367: return outBuffer.toString();
368: }
369:
370: /**
371: * Convert a nibble to a hex character
372: *
373: *@param nibble the nibble to convert.
374: *@return Description of the Return Value
375: */
376: private static char toHex(int nibble) {
377: return hexDigit[(nibble & 0xF)];
378: }
379:
380: /**
381: * A table of hex digits
382: */
383: private final static char[] hexDigit = { '0', '1', '2', '3', '4',
384: '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
385:
386: private final static String specialSaveChars = "=: \t\r\n\f#!";
387:
388: }
|