In this chapter, we are going to store object properties using the Java library Properties class. This stores properties on disk as human-readable key-value pairs, sometimes known as property stores.
In the last chapter, we persisted objects by writing out their properties one after the other. It's fragile because you must make sure your code matches the persisted format for both reading and writing. If you change the object class definition in the code - for example, adding a mobile phone number - then you may no longer be able to read from old files because some lines could be read into the wrong properties. 😢
What can we do about this?
One solution, the one we'll look at in this chapter, is to store the property name along with the property value, like this:
telephone = +441632960248
This takes up more space on the disk, but it helps both humans and computers associate some meaning with the data. Let's see how this works for our robot part shop application.
Persisting a Person With Properties
Java comes with a host of libraries that provide ready-made solutions for a range of tasks. One of them is the Properties
class, which you can use to store value strings with associated key strings. We will use the key to store the name of the field (e.g. telephone number), and the value to store the content of that field (e.g. 07700 900081).
Here's how to store customer details using key-value pairs:
Let’s look at this in more detail starting with the example from the previous chapter:
public class Person {
public String name;
public String address;
public String email;
public String telephone;
}
To use the Property library, create an instance of the Properties class, copy the Person fields into it by setting named properties with the Person values, and then store it, as follows:
public class PropertyPersonPersister {
public void serialisePerson(Person person2store) throws IOException {
Properties p = new Properties();
p.setProperty("name", person2store.name);
p.setProperty("address", person2store.address);
p.setProperty("telephone", person2store.telephone);
p.setProperty("email", person2store.email);
FileWriter fw = new FileWriter("Store.txt");
p.store(fw, "Stored person");
fw.close();
}
}
To store it, a writer was created and opened to a Store.txt
file in the local directory. The Properties class writes out the key-value pairs to it.
Here is a sample main method that you can run to test it:
public static void main(String[] args) throws IOException {
Person samjones = new Person();
samjones.name = "Sam Jones";
samjones.address = "12 Letsbe Avenue, Royston Vasey, MK2 3AU";
samjones.email = "sam.jones@openclassrooms.co.uk";
samjones.telephone = "+44 7700 900081";
PropertyPersonPersister persister = new PropertyPersonPersister();
persister.serialisePerson(samjones);
}
It creates an instance of a person, populates it with Sam Jones's details, creates an instance of the persister, and passes the person instance to it to store.
If you run it, and look at the contents of Store.txt, it will be like this:
#Stored person
#Mon May 27 14:26:19 BST 2019
address=12 Letsbe Avenue, Royston Vasey, MK2 3AU
telephone=+44 7700 900081
email=sam.jones@openclassrooms.co.uk
name=Sam Jones
And there you have a persisted Person, using key-value pairs!
You can see this tells us - both humans and computers - more. It includes some basic semantics - that is, some information about what the value means. You could specify whether an address is a work address or a home one, or whether a telephone is work, mobile, home land, personal, etc.
Try It Out for Yourself: Persist a Robot Part With Properties
Now it’s your turn to write the property persister for the robot part.
Using your RobotPart class from the previous chapter, write a Persister that uses the Property library to write out the robot part properties to a file. Take a look at the Property library documentation and see if you can write the deserializer method.
An example solution follows the image below.
Here is one way of storing a robot part using the Properties library:
Let's look at this in more detail. Here is a property key-value persister for a robot part - hopefully, you have been more ambitious in describing your RobotPart, and so yours is more varied!
public class RobotPartPersister {
private String filestore = "Store.txt";
public RobotPartPersister(String aFilename) {
this.filestore = aFilename;
}
public void serialisePart(RobotPart part2store) throws IOException {
Properties p = new Properties();
p.setProperty("name", part2store.name);
p.setProperty("description", part2store.description);
p.setProperty("supplier", part2store.supplier);
p.setProperty("weight", ""+part2store.weight);
FileWriter fw = new FileWriter(filestore);
p.store(fw, "Robot Part");
fw.close();
}
public RobotPart deserialisePart() throws IOException {
Properties p = new Properties();
FileReader fr = new FileReader(filestore);
p.load(fr);
fr.close();
RobotPart part = new RobotPart();
part.name = p.getProperty("name");
part.description =p.getProperty("description");
part.supplier = p.getProperty("supplier");
part.weight = Integer.parseInt(p.getProperty("weight"));
return part;
}
public static void main(String[] args) throws IOException {
RobotPart sharklaseradapter = new RobotPart();
sharklaseradapter.name = "Shark Laser Adapter";
sharklaseradapter.description = "Collar and fittings for shark-mounted laser";
sharklaseradapter.supplier = "Dr NoNo";
sharklaseradapter.weight = 5000;
RobotPartPersister persister = new RobotPartPersister("Store.txt");
persister.serialisePart(sharklaseradapter);
}
}
Let's break this code down:
The persister takes a filename as a constructor argument, that it will then use to read and write the properties to. This means you can create different persister instances to store data in different files.
The
serialisePart
method takes a RobotPart, copies over the details to a properties instance, and then calls the store method on the Properties to write them out.The
deserialisePart
does the reverse: it creates a properties instance, loads it from the file, copies the contents into a new RobotPart, and returns that.The main method at the end can be used to test the persister; it creates a RobotPart and serializes it, then calls the deserializer and checks that the deserialized one matches the serialized one. This is useful for checking that you haven't made spelling mistakes or missed fields.
Can I use this for more than one object?
As with the last chapter, this approach only really works when storing one object rather than a list. You could, for example, have one file per object (perhaps a different file for each customer), but this becomes unwieldy for many objects.
Typically, the Properties library is used to store objects that you have one of - for example, configuration. There are other ways of storing configuration, but this is a simple way to store the setup of your application: where to output log files, what color scheme the user interface should use, which servers to use, etc.
Using Key-Value Pairs: Advantages and Disadvantages
The file is human-readable and can be edited in a text editor - which has some pros and cons. For example, you can easily change the contents without having to write a special user interface. Just start a text editor and edit away.
However, you can easily make mistakes like misspelling a key, or putting a letter in the telephone number. You can see that if you type a letter in the weight property value, then your software can fail with an error when it tries to parse the string into a Java integer object. You can solve this by adding code to the deserializer method to report such errors, for example:
try {
Integer weight = Integer.valueOf(p.getProperty("Weight"));
}
catch (NumberFormatException nfe) {
//report a problem with the configuration. Report all relevant items
//to help fix the problem: the error, the field it had a problem with,
//and the file it was reading from.
System.out.println("ERROR: "+nfe
+" reading weight "+p.getProperty("Weight")
+" from file "+filestore);
}
Also, the data structure here is flat; there is no built-in way of grouping batches of information together. However, some common practices can give you something like this. One way is to group key-value pairs, if you are manually editing configuration settings, for example, you might arrange them like this:
# Theme
Background=BLUE
Foreground=YELLOW
Music=SynthiGaragePop
# Server Details
URL=http://my.hovercraft.com/is/full
timeout=40
Another way to group information is to add prefixes to the keys, usually separated by a dot. For example, if you want to store contact details (address, telephone number, email) for work and again for home, you could use keys like this:
home.address=12 Letsbe Avenue
home.telephone=07700 900494
home.email=silliussoddus@montypython.org.uk
work.address=24 Open Close
work.telephone=07655 529752
work.email=sam@sensibleplace.com.eu
And now it's ready to go!
Let's Recap!
You now know how to use the Properties library class to write out a single object to a disk file that can be viewed and edited by humans, and which handles the serializing and deserializing for you. This is particularly useful for those cases where you want to persist a few values over time, for example, the settings of an application.
There are still some outstanding issues for using it to store a collection of objects:
It is only really useful for one object, rather than collections.
The data structure is flat, which makes storing complex structures awkward.
The units of values are not required, so distances, weights, speeds, and others can be ambiguous.
Data types are not specified, so it can be possible to edit the files and insert text where numbers should be.
In the next chapters, we will look at other ways of persisting objects that solve some of these.