1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.xnap.commons.settings;
21
22 import java.beans.PropertyChangeListener;
23 import java.beans.PropertyChangeSupport;
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.util.Properties;
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31
32 /***
33 * This class provides a default implementation for a preferences framework.
34 * Methods are provided that can read and write native types, arrays and a few
35 * custom types.
36 */
37 public class PropertyResource implements SettingResource {
38
39 protected static Log logger = LogFactory.getLog(PropertyResource.class);
40
41 protected transient PropertyChangeSupport propertyChange
42 = new PropertyChangeSupport(this);
43
44 /***
45 * Determines if preferences need to be saved.
46 */
47 protected boolean changedFlag = false;
48
49 /***
50 * Version of database format.
51 */
52 protected int version;
53
54 /***
55 * Version of database when read.
56 */
57 protected int oldVersion = -1;
58
59 /***
60 * The namespace is prefixed to each key.
61 */
62 protected String namespace;
63
64 /***
65 * Preferences.
66 */
67 protected Properties properties = new Properties();
68
69 /***
70 * Constructs a <code>PreferencesSupport</code> object.
71 * @param version the current version of the preferences, this version
72 * can be more recent than the version of the file
73 * @param namespace the namespace, used as a prefix for all keys
74 */
75 public PropertyResource(int version, String namespace)
76 {
77 this.version = version;
78
79 if (namespace == null || namespace.length() == 0) {
80 this.namespace = "";
81 }
82 else if (!namespace.endsWith(".")) {
83 this.namespace = namespace + ".";
84 }
85 else {
86 this.namespace = namespace;
87 }
88 }
89
90 public PropertyResource()
91 {
92 this(0, "");
93 }
94
95 /***
96 * Reads preferences from <code>prefsFile</code>.
97 */
98 public void load(File prefsFile) throws IOException
99 {
100 logger.info("Reading settings from " + prefsFile);
101
102 FileInputStream in = new FileInputStream(prefsFile);
103 try {
104 properties.load(in);
105
106 try {
107 oldVersion = Integer.parseInt
108 (properties.getProperty("props.ver", "-1"));
109 }
110 catch (NumberFormatException e) {
111 oldVersion = -1;
112 }
113
114 if (oldVersion != -1 && oldVersion < getVersion()) {
115 logger.debug("converting from version " + oldVersion + " to "
116 + getVersion());
117 convert(oldVersion);
118 }
119 }
120 finally {
121 try {
122 in.close();
123 } catch (Exception e) {}
124 }
125
126
127
128 }
129
130 /***
131 * Writes preferences to default preferences file. If preferences have not
132 * changed since the last read or write operation, no action is taken.
133 *
134 * @return true, if file is written successfully or preferences were not
135 * changed; false, if an <code>IOException</code> has occured
136 * @throws IOException
137 */
138 public boolean store(File prefsFile) throws IOException
139 {
140 if (!changedFlag) {
141 logger.info("nothing changed, not writing " + prefsFile);
142 return true;
143 }
144
145 logger.info("writing " + prefsFile);
146
147 FileOutputStream out = new FileOutputStream(prefsFile);
148
149 try {
150 properties.put("props.ver", version + "");
151 properties.store(out, "This file was automatically generated.");
152 }
153 finally {
154 try {
155 out.close();
156 } catch (IOException e) {}
157 }
158
159 changedFlag = false;
160 return true;
161 }
162
163 /***
164 * Returns true if <code>o</code> is the same object or if its
165 * version number, namespace and properties map are equal to
166 * this' fields.
167 */
168 @Override
169 public boolean equals(Object o) {
170 if (o == this) {
171 return true;
172 }
173 if (!(o instanceof PropertyResource)) {
174 return false;
175 }
176 PropertyResource r = (PropertyResource)o;
177 return version == r.version && namespace.equals(r.namespace)
178 && properties.equals(r.properties);
179 }
180
181 /***
182 * Returns the version of the preferences in the file at the point
183 * of the last read operation.
184 */
185 public int getOldVersion()
186 {
187 return oldVersion;
188 }
189
190 /***
191 * Returns the current version of the preferences.
192 */
193 public int getVersion()
194 {
195 return version;
196 }
197
198 /***
199 * Invoked by {@link #load(File)} to converts preferences from
200 * <code>oldVersion</code> to current version.
201 */
202 protected void convert(int oldVersion)
203 {
204 }
205
206 /***
207 * Adds a preferences listener.
208 */
209 public synchronized void addPropertyChangeListener(PropertyChangeListener l)
210 {
211 propertyChange.addPropertyChangeListener(l);
212 }
213
214 /***
215 * Adds a preferences listener for a specific key.
216 */
217 public synchronized void addPropertyChangeListener(String key, PropertyChangeListener l)
218 {
219 propertyChange.addPropertyChangeListener(key, l);
220 }
221
222 /***
223 * Fires PropertyChangeEvent without namespace.
224 */
225 public void firePropertyChange(String key, Object oldValue, Object newValue)
226 {
227 int i = key.lastIndexOf(".");
228 if (key.length() > i + 1) {
229 key = key.substring(i + 1);
230 }
231
232 propertyChange.firePropertyChange(key, oldValue, newValue);
233 }
234
235 /***
236 * Removes a preferences listener.
237 */
238 public synchronized void removePropertyChangeListener(PropertyChangeListener l)
239 {
240 propertyChange.removePropertyChangeListener(l);
241 }
242
243 public synchronized void removePropertyChangeListener(String key, PropertyChangeListener l)
244 {
245 propertyChange.removePropertyChangeListener(key, l);
246 }
247
248 public String get(String key, String defaultValue)
249 {
250 return properties.getProperty(namespace + key, defaultValue);
251 }
252
253 public void put(String key, String newValue)
254 {
255 properties.setProperty(namespace + key, newValue);
256 changedFlag = true;
257 }
258
259 /***
260 * Ignores namespace.
261 */
262 public synchronized void remove(String key)
263 {
264 properties.remove(namespace + key);
265 changedFlag = true;
266 }
267
268 /***
269 * Renames a property, used for conversion of property file formats.
270 * Ignores namespace. Does not fire change event.
271 */
272 public synchronized void renameProperty(String oldKey, String newKey)
273 {
274 String value = properties.getProperty(oldKey, null);
275 if (value != null) {
276 Object oldValue = properties.remove(oldKey);
277 if (oldValue != null) {
278 properties.setProperty(newKey, value);
279 changedFlag = true;
280 }
281 }
282 }
283
284 }