Java - Updating / Changing serialized classes - java

I am creating a program for a small business. This program is meant to have smaller modules that, when developed, will be attatched to the rest of the program. It contains an "Article" and a "Category" class, which is contained in lists in a "ArticleDatabase" class.
This class is serialized and saved to a file to the harddrive.
The Register module is complete, and the "Receipt" class, is likewise contained within lists in a "RegisterDatabase" class, which is serialized and saved to a separate file.
System settings, are saved in the same manner.
However, now i am designing a Invoice module, and found out that i need to add a field to the "Article" class, and to the System data.
The register is now being used, and contains actual data that needs to be saved, and therefore i can't just change the class, since this gives an InvalidClassException when i load.
Since i know that this will be a common problem in the future too, i need some advice on how to tackle this problem.
How can i setup a system i which i can save a file from a class, and load the data into an updated or new version of this class, or should i approach this in an entirely new way?
I have tried loading the data form the old file in to a duplicate class with the needed fields addded, but reconfiguring the program to use the new files instead is a very cumbersome task, and if i have to do this every now and again, a lot of time will be wasted doing this.
The methods used for saving loading are as follows:
public void saveArticleDB() throws IOException {
// Write to disk with FileOutputStream
FileOutputStream f_out = new FileOutputStream("articles.data");
// Write object with ObjectOutputStream
ObjectOutputStream obj_out = new ObjectOutputStream(f_out);
obj_out.writeObject(MyMain.articleDB);
}
public ArticleDB loadArticleDB() throws IOException {
try {
FileInputStream f_in = new FileInputStream("articles.data");
ObjectInputStream obj_in = new ObjectInputStream(f_in);
Object obj = obj_in.readObject();
if (obj instanceof ArticleDB) {
return (ArticleDB) obj;
} else return null;
} catch (FileNotFoundException e) {
new MessageDialog("Article DB - File not found");
return null;
} catch (InvalidClassException e) {
new MessageDialog("Article DB - Class didnt match");
return null;
} catch (ClassNotFoundException e) {
new MessageDialog("Article DB - Class not found");
return null;
}
}
The classes that delivers data to the save file, implements Serializable, and thats the only code used regarding the saving and loading of the class.
This is my first attempt with serializing, saving and loading, which means i am quite new to this, and therefore know/understand very few of the concepts regarding these subjects.
Advice is much appreciated :-)

Related

Gson RuntimeTypeAdapter - Cannot serialize class because it already defines a field name

I ran into an issue with serializing polymorphic objects so I'm trying to use RuntimeTypeAdapter to fix that.
Here's how my Gson instance is defined and how I'm using it:
RuntimeTypeAdapterFactory<Enemy> adapter = RuntimeTypeAdapterFactory.of(Enemy.class,"enemyType")
.registerSubtype(Ranged.class, "Ranged")
.registerSubtype(Melee.class, "Melee")
.registerSubtype(Sniper.class, "Sniper")
;
gson = new GsonBuilder().registerTypeAdapterFactory(adapter).create();
...
public GameStateData loadGameState(String path) throws FileNotFoundException {
FileReader fileReader = new FileReader(path);
gameStateData = gson.fromJson(fileReader, GameStateData.class);
try {
fileReader.close();
} catch (IOException e) {
System.err.println("Error closing save file");
}
return gameStateData;
}
public void writeGameState(String path) throws IOException {
FileWriter fileWriter = new FileWriter(path);
gson.toJson(gameStateData, fileWriter);
fileWriter.close();
}
Both type and enemyType are already existing fields within Enemy, when trying to use either or without the adapter's typeFieldName specified leads to the following error when trying to serialize:
com.google.gson.JsonParseException: cannot serialize enemies.Ranged because it already defines a field named enemyType
I also tried setting an unused value for typeFieldName but that lead to the issue that my Ranged type entity was saved as an Enemy. Both type and enemyType are enums, although different ones. What type should typeFieldName be?
Edit: Ended up solving this issue by re-filling the ArrayList with new Objects with their specific constructors based on enemyType. Looking at my code my thoroughly my error seemed to have been using a different (and differently configured) Gson instance for deserializing.

Save a variable when the server is off

In fact I am making a Minecraft plugin and I was wondering how some plugins (without using DB) manage to keep information even when the server is off.
For example if we make a grade plugin and we create a different list or we stack the players who constitute each. When the server will shut down and restart afterwards, the lists will become empty again (as I initialized them).
So I wanted to know if anyone had any idea how to keep this information.
If a plugin want to save informations only for itself, and it don't need to make it accessible from another way (a PHP website for example), you can use YAML format.
Create the config file :
File usersFile = new File(plugin.getDataFolder(), "user-data.yml");
if(!usersFile.exists()) { // don't exist
usersFile.createNewFile();
// OR you can copy file, but the plugin should contains a default file
/*try (InputStream in = plugin.getResource("user-data.yml");
OutputStream out = new FileOutputStream(usersFile)) {
ByteStreams.copy(in, out);
} catch (Exception e) {
e.printStackTrace();
}*/
}
Load the file as Yaml content :
YamlConfiguration config = YamlConfiguration.loadConfiguration(usersFile);
Edit content :
config.set(playerUUID, myVar);
Save content :
config.save(usersFile);
Also, I suggest you to make I/O async (read & write) with scheduler.
Bonus:
If you want to make ONE config file per user, and with default config, do like that :
File oneUsersFile = new File(plugin.getDataFolder(), playerUUID + ".yml");
if(!oneUsersFile.exists()) { // don't exist
try (InputStream in = plugin.getResource("my-def-file.yml");
OutputStream out = new FileOutputStream(oneUsersFile)) {
ByteStreams.copy(in, out); // copy default to current
} catch (Exception e) {
e.printStackTrace();
}
}
YamlConfiguration userConfig = YamlConfiguration.loadConfiguration(oneUsersFile);
PS: the variable plugin is the instance of your plugin, i.e. the class which extends "JavaPlugin".
You can use PersistentDataContainers:
To read data from a player, use
PersistentDataContainer p = player.getPersistentDataContainer();
int blocksBroken = p.get(new NamespacedKey(plugin, "blocks_broken"), PersistentDataType.INTEGER); // You can also use DOUBLE, STRING, etc.
The Namespaced key refers to the name or pointer to the data being stored. The PersistentDataType refers to the type of data that is being stored, which can be any Java primitive type or String. To write data to a player, use
p.set(new NamespacedKey(plugin, "blocks_broken"), PersistentDataType.INTEGER, blocksBroken + 1);

Save JList into Txt File

After searching for an answer for hours I decided to ask it here, since the solutions I found didn't work.
I have a simple GUI to register a persons first/last name and date of birth. After entering the values, the data is listed in a JList. Now I want to save the data from the JList into a Txt file. But I can't find a way to get the data from the JList.
public void save(){
try(BufferedWriter bw = new BufferedWriter(new FileWriter("jlist.txt")))
{
/* Here should be the part, where I get the data from the JList */
bw.write(person.getNachname() + " ; " + person.getVorname() + " ; " + person.getDate() + "\n");
} catch (Exception speichern) {
speichern.printStackTrace();
}
}
Later I want to take the created Txt file and load it back into the same JList.
Maybe there is even a better way to do this but I haven't found something.
Some tips would be helpful :)
There is no JList method that does this for you.
You need to get the data from the ListModel.
You get the ListModel from the JList using the getModel() method.
You need to write a loop to:
get each element from the ListModel using the getElementAt(...) method.
convert the element to a String and write the data to your file.
Some tips would be helpful
Not related to your question, but typically data like this would be displayed in a JTable. Then you have a separate column for each of the first name, last name and date. Read the section from the Swing tutorial on How to Use Tables for more information.
As camickr point out there is no method implemented for what you a trying to achieve, instead there is a combination of things that you could do for archiving your goal.
You are facing the problem of data persistence. In now-a-days for small|medium|big size industrial applications the recommended approach is to relay on databases. I guess that is out the scope for one person that is starting to code, so using files for storing info is OK but is not straightforward.
In your case, if your application is for non-commercial purposes I would suggest to use the default mechanism for serializing and deserializing objects that comes bundled with the platform. With this you could write an entire object (including its data, a.k.a. its state) to a file on a disk, and later retrieve it with few lines codes. There are details about how the object gets serialize ("translate object to bits") and deserialized ("translate bits to object") that doesn't comes into place right now, but is well to advice to study them in the future if you planning to use this method in a commercial application.
So I suggest that you load and store the information of your application on start-up and shutdown respectively, thus only one load and store per application instance, while the application is active work with the data on memory. THIS is the simplest approach you could have in any application, and for that reason I suggest to start with this ideal scenario.
So, I say a lot of things but let's goes to the code that shows an example of storing (serialize) and loading (deserialize)
import java.io.*;
import java.util.*;
class Person implements Serializable {
String name;
int birthDate;
public Person(String name, int birthDate) {
this.name = name;
this.birthDate = birthDate;
}
}
class Main {
public static void main(String[] args) {
Collection<Person> collection = createExampleCollection();
System.out.println(collection);
storeCollection(collection, "persons.data");
Collection<Person> otherCollection = loadCollection("persons.data");
System.out.println(otherCollection);
}
private static Collection<Person> createExampleCollection() {
Collection<Person> collection = new ArrayList<Person>();
collection.add(new Person("p1",0));
collection.add(new Person("p2",10));
collection.add(new Person("p2",20));
return collection;
}
// here I'm doing two separated things that could gone in separate functions, 1) I'm converting into bytes and object of an specific class, 2) saving those bytes into a file on the disk. The thing is that the platform offers us convenient objects to do this work easily
private static void storeCollection(Collection<Person> collection, String filename) {
try {
FileOutputStream fos = new FileOutputStream(filename);
ObjectOutputStream out = new ObjectOutputStream(fos);
out.writeObject(collection);
out.close();
fos.close();
} catch (IOException i) {
i.printStackTrace();
}
}
// again there two things going on inside, 1) loading bytes from disk 2) converting those bits into a object of a specific class.
private static Collection<Person> loadCollection(String filename) {
try {
FileInputStream fis = new FileInputStream(filename);
ObjectInputStream in = new ObjectInputStream(fis);
Collection<Person> persons = (Collection<Person>) in.readObject();
in.close();
fis.close();
return persons;
} catch (Exception i) {
i.printStackTrace();
return null;
}
}
}
You should try to use the functions of loadCollection and storeCollection on start-up and shutdown respectively.
I made this code with comments for jButton and jList in jFrame, Button saves text Items to File from jList.
private void btnSaveActionPerformed(java.awt.event.ActionEvent evt) { //jButton name: "btnSave"
try { //trying to save file
BufferedWriter bw = new BufferedWriter(new FileWriter("data.txt")); //file where I store the data of jList1 (file will be stored at: C:\Users\%username%\Documents\NetBeansProjects\<ThisProjectName>\data.txt) (if You use NetBeans)
for (int i=0; i<jList1.getModel().getSize(); i++){ //opens a cycle to automatically store data of all items
bw.write(jList1.getModel().getElementAt(i)); //writing a line from jList1
bw.newLine(); //making a new line for the next item (by removing this line, You will write only one line of all items in file)
} //cycle closes
bw.close(); //file writing closes
} catch (IOException ex) { //catching the error when file is not saved
Logger.getLogger(NewJFrame.class.getName()).log(Level.SEVERE, null, ex); //showing the error
} //Exception closes
} //Action closes

How to Serialise objects from multiple classes

I am writing a program that uses Serialization to store the program's objects (users, admins, books, etc). I have multiple controllers that control the adding of different objects to different array lists.
Example:
Login controller controls the adding and removing of users to the system
Example:
Book controller which controls adding and removing of books to the system
I would like to know the best way of saving all these different objects from different controllers into my serialized file.
Currently, I have been reading the serialized file in each controller to populate the array lists. this is my reading method in the "Book" controller.
And I have a save to file method as well however I'm not sure how to implement the ArrayList from different controllers.
private void populateArrayLists() {
System.out.print("Im here in ArrayList");
ArrayList<Object> deserialised = new ArrayList<Object>();
try {
FileInputStream file = new FileInputStream("info.ser");
ObjectInputStream inputFile = new ObjectInputStream(file);
deserialised = (ArrayList<Object>) inputFile.readObject();
inputFile.close();
file.close();
} catch (IOException | ClassNotFoundException e) {
JOptionPane.showMessageDialog(null, e.getMessage());
}
books = (ArrayList<Book>) deserialised.get(2);
}
private void saveData() {
ArrayList<Object> allData = new ArrayList<Object>();
books.add(book1);
admins.add(admin1);
users.add(user1);
allData.add(users);
allData.add(admins);
allData.add(books);
try {
FileOutputStream file;
file = new FileOutputStream("info.ser");
ObjectOutputStream outputFile = new ObjectOutputStream(file);
outputFile.writeObject(allData);
outputFile.close();
file.close();
JOptionPane.showMessageDialog(null, "Saved");
} catch (IOException e) {
JOptionPane.showMessageDialog(null, e.getMessage());
}
}
I would like a method of saving all the different objects in different controllers in one place.
Given your question description, I suggest you implement one solution where you make use of the Repository concept in the way described by Domain Driven Design. It allows you to centralize the logic for handling the collection of objects you have so far. Since you decide to use files, I recommend you to have one file per class of objects instead of one file for all your objects collections. Here you can find a basic approach, it can be improved further with generics.

How to write test for serialization and deserialization which involves javafx file chooser?

I need to write unit tests for serialization() and deserialization() functions which I've no idea how to cover FileChooser and FileInputStream.
And also, if there are two functions in this file, I must write two and only two corresponding test functions?
The two functions are as followings:
/**
* serialize and save all the datasets and charts
* #param myDataset
* pass all the existing datasets into this function for serialization purpose
* #param myChart
* pass all the existing charts into this function for serialization purpose
*/
public static void serialize (HashMap<String, DataTable> myDataset, HashMap<String, Chart> myChart) {
// first, let the user select a directory to save
FileChooser chooser = new FileChooser();
chooser.setTitle("Save");
FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("comp3111 type", "*.comp3111");
chooser.getExtensionFilters().add(extFilter);
File file = chooser.showSaveDialog(new Stage());
try {
FileOutputStream fOutput = new FileOutputStream(file);
ObjectOutputStream objOutput = new ObjectOutputStream(fOutput);
// put all the DataTable type objects into an array
objOutput.writeObject(myDataset);
objOutput.writeObject(myChart);
objOutput.close();
fOutput.close();
} catch (FileNotFoundException e) {
System.out.println(e.getMessage());
e.printStackTrace();
} catch (IOException e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
/**
* choose a file with *.comp3111 extension for deserialization purpose
* #return
* the chosen *.comp3111 file
*/
public static File deserialize () {
FileChooser chooser = new FileChooser();
FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("comp3111 type", "*.comp3111");
chooser.getExtensionFilters().add(extFilter);
File mfile = chooser.showOpenDialog(new Stage());
return mfile;
}
The issue is that you have code in your serialize and deserialize methods that doesn't belong there. They are too broad: not only do they serialise and deserialise objects (although the code for deserialisation appears to be missing), they also open file choosers.
While this may make sense from a program flow point of view, it also breaks the single responsibility principle (note: I'm aware that SRP is usually applied to a class and not to a method). Your methods do too many things.
So you should write your methods in such a way that they accept a File or InputStream as an input parameter, and return a collection of objects as a result (for deserialisation), or take a collection of objects and return an OutputStream as a result (for serialisation). That way, you can test exactly that behaviour.
Even better would be to first write the test, where you could for example use a test file, both to read from to check if it produces the expected objects, or you test that a ByteOutputStream returned by the serialiser matches that same file.
It's called test-driven development, and it's a good way to write testable and stable code.

Categories

Resources