View Javadoc

1   /*
2    *  XNap Commons
3    *
4    *  Copyright (C) 2005  Steffen Pingel
5    *
6    *  This library is free software; you can redistribute it and/or
7    *  modify it under the terms of the GNU Lesser General Public
8    *  License as published by the Free Software Foundation; either
9    *  version 2.1 of the License, or (at your option) any later version.
10   *
11   *  This library 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 GNU
14   *  Lesser General Public License for more details.
15   *
16   *  You should have received a copy of the GNU Lesser General Public
17   *  License along with this library; if not, write to the Free Software
18   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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 		// FIXME need to notify all properties that their value may have
127 		// changed!
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 }