Abstract: The java.util.Properties class can load and store the properties in an XML file. We furthermore periodically check whether the file has changed and if so, reload the properties.
Welcome to the 95th edition of The Java(tm) Specialists' Newsletter. A special thank you goes to Ng Keng Yap from Malaysia for motivating me to publish this newsletter.
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
I was chatting to a friend of mine tonight, who mentioned that after installing automatic Windows updates, the glorious operating system was now prompting him every five minutes whether he wanted to restart his computer. Irritating, is it not?
Whenever possible, I try to write software that you can configure "on the fly" without requiring a restart. This can get difficult when the only way of configuration is a bunch of XML files. Usually, the only way for these changes to apply to our system is to restart the program once the XML files have been edited.
To start, I use an interface that I call Configuration. It allows me to get one or all properties, and allows me to add listeners for property changes. I try to use generics whenever I can, since they make the code clearer, in my opinion (not shared by many ;-) If you look at getAllProperties(), we can clearly see what it returns - a set of Map.Entry implementations, each of which consists of a String/Object pair.
import java.beans.PropertyChangeListener; import java.util.*; public interface Configuration { Object getProperty(String propertyName); Set<Map.Entry<String,Object>> getAllProperties(); void addPropertyChangeListener(PropertyChangeListener listener); boolean removePropertyChangeListener(PropertyChangeListener listener); }
I have implemented a basic AbstractConfiguration that could be extended with many different types of config loaders. Whenever a property is set, it checks whether it is the same value as before. If it is not, it sends a PropertyChangeEvent to all the listeners.
There are two types of lists that I use: ArrayList and my CircularArrayList. I have not found a single use for LinkedList yet, have you? A new list that I will use here is the CopyOnWriteArrayList from the new java.util.concurrent package. This list is handy when the contents of your list change seldomly. Whenever the list is changed, it makes a copy of the old list and changes the copy. You can therefore have active Iterators on the old list and you will not get a ConcurrentModificationException anymore.
import java.beans.*; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; /** * @author Heinz Kabutz */ public abstract class AbstractConfiguration implements Configuration { /** * A map of all the properties for the configuration */ private final Map<String, Object> properties = new HashMap<String, Object>(); private final Collection<PropertyChangeListener> listeners = new CopyOnWriteArrayList<PropertyChangeListener>(); /** Make a daemon timer to check for changes. */ private final Timer timer = new Timer(true); /** * This class has a timer to check periodically if the * configuration has changed. If it has, it reloads the * properties. This may cause the property change events to * fire. * * @param period number of milliseconds between checking for * property changes. */ protected AbstractConfiguration(int period) { timer.schedule(new TimerTask() { public void run() { checkForPropertyChanges(); } }, period, period); } /** * This method should be overridden to check whether the * properties could maybe have changed, and if yes, to reload * them. */ protected abstract void checkForPropertyChanges(); public final Object getProperty(String propertyName) { synchronized (properties) { return properties.get(propertyName); } } public Set<Map.Entry<String, Object>> getAllProperties() { synchronized (properties) { return properties.entrySet(); } } /** * Each time we set a property, we check whether it has changed * and if it has, we let the listeners know. */ protected final void setProperty(String propertyName, Object value) { synchronized (properties) { Object old = properties.get(propertyName); if ((value != null && !value.equals(old)) || value == null && old != null) { properties.put(propertyName, value); PropertyChangeEvent event = new PropertyChangeEvent(this, propertyName, old, value); for (PropertyChangeListener listener : listeners) { listener.propertyChange(event); } } } } public void addPropertyChangeListener(PropertyChangeListener l) { listeners.add(l); } public boolean removePropertyChangeListener(PropertyChangeListener l) { return listeners.remove(l); } }
In my code, I usually have a central place to send Exceptions so that they can be logged, or the user alerted. I usually call this class Exceptions:
/** * In your code, you can make Exceptions more robust. For * example, show a dialog to the user, send an email to the QA * team, etc. * * @author Heinz Kabutz */ public class Exceptions { public static void throwException(Throwable e) { e.printStackTrace(); } }
JDK 5 java.util.Properties has the ability to load and store property files in XML format directly, thanks to Ng Keng Yap for pointing this out to me. Our XMLFileConfiguration class reads the file date time and if it has changed, reads the values and sets them. Those properties that have changed will now cause events.
import java.io.*; import java.util.*; public class XMLFileConfiguration extends AbstractConfiguration { private final File file; private long lastModified = 0; public XMLFileConfiguration(Properties defaults, String filename, int period) throws IOException { super(period); setAllProperties(defaults); file = new File(filename); if (!file.exists()) { storeProperties(); } loadProperties(); } private void loadProperties() throws IOException { Properties properties = new Properties(); properties.loadFromXML(new FileInputStream(file)); setAllProperties(properties); } private void setAllProperties(Properties properties) { for (Map.Entry<Object, Object> entry : properties.entrySet()) { setProperty((String) entry.getKey(), entry.getValue()); } } public void storeProperties() { Properties properties = new Properties(); for (Map.Entry<String, Object> entry : getAllProperties()) { properties.put(entry.getKey(), entry.getValue()); } try { properties.storeToXML(new FileOutputStream(file), "Generated by XMLFileConfiguration"); } catch (IOException e) { Exceptions.throwException(e); } } protected void checkForPropertyChanges() { if (lastModified != file.lastModified()) { try { lastModified = file.lastModified(); loadProperties(); } catch (IOException e) { Exceptions.throwException(e); } } } }
This code is by no means robust. I wrote it very quickly, and there are many caveats that are not dealt with. For example, what if we need several properties in order to effectively change our system, such as a user name and password.
To show how it works, here is a short test class:
import java.beans.*; import java.util.Properties; import java.io.FileOutputStream; public class Test { public static void main(String[] args) throws Exception { String filename = "test.xml"; Properties defaults = new Properties(); defaults.setProperty("name", "Heinz"); defaults.setProperty("yahooid", "heinzkabutz"); defaults.setProperty("age", "32"); Configuration cfg = new XMLFileConfiguration(defaults, filename, 300); cfg.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { System.out.println("Property " + evt.getPropertyName() + " has now changed from <" + evt.getOldValue() + "> to <" + evt.getNewValue() + ">"); } }); Thread.sleep(1000); defaults.setProperty("age", "33"); FileOutputStream fos = new FileOutputStream(filename); defaults.storeToXML(fos, ""); fos.close(); Thread.sleep(1000); defaults.setProperty("age", "32"); fos = new FileOutputStream(filename); defaults.storeToXML(fos, ""); fos.close(); Thread.sleep(1000); defaults.setProperty("age", "2 ^ 4"); fos = new FileOutputStream(filename); defaults.storeToXML(fos, ""); fos.close(); Thread.sleep(1000); } }
Tomorrow (oh tomorrow is already today ;-) I am driving with a good friend all over the countryside upgrading a system that we built. We leave at 06:00, so now (00:35) I need to stop writing. Please let me know if you find obvious errors in the code :-)
Kind regards
Heinz
We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)
We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.