I'm kind of stuck with a problem. I do understand the concept of serialization. Nevertheless I'm getting errors when I try to serialize/deserialize (deepCopy) an object:
I have a basic domain objects that hold information (two files):
public class DomainObject implements java.io.Serializable {
private String defaultDescription = "";
private List<Translation> translations;
public DomainObject() {
;
}
public void setTranslations(final List<Translation> newTranslations) {
this.translations = newTranslations;
}
public List<Translation> getTranslations() {
return this.translations;
}
public void setDefaultDescription(final String newDescription) {
this.defaultDescription = newDescription;
}
public String getDefaultDescription() {
return this.defaultDescription;
}
}
public class Translations implements java.io.Serializable {
private String description = "";
public Translation() {
;
}
public void setDescription(final String newDescription) {
this.description = newDescription;
}
public String getDescription() {
return this.description;
}
}
I also have a frame so the user can fill in all the necessary information for this domain object. Since I have multiple domain objects (this example only shows one) with different fields I have different frames for each domain object. Each of these frames includes a "MultiLanguageFrame" which gives the user the ability to add optional translations for this domain object's description.
public class MultiLanguageFrame extends org.eclipse.swt.widgets.Composite {
private List<Translation> translations = new ArrayList<Translation>();
public MultiLanguageFrame(final Composite parent, final int style) {
super(parent, style);
...
}
public List<Translation> getTranslations() {
return translations;
}
}
I deepCopy objects via this method:
...
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(object);
oos.flush();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (Exception t) {
logger.error(deepCopy() error: " + t.getMessage()); //$NON-NLS-1$
throw new RuntimeException("deepCopy() error", t); //$NON-NLS-1$
}
So now to the error:
When i try to do something like this:
MultiLanguageFrame frame = new MultiLanguageFrame(parent, SWT.NONE);
DomainObject dom = new DomainObject();
dom.setDefaultDescription("Testobject");
dom.setTranslations(frame.getTranslations())
deepCopy(dom);
I receive an error telling me that MultiLanguageFrame is not Serializable. Why would Java try to serialize the frame when I only want that DomainObject?
I thought maybe it is because of the reference in frame. So when I add the Serializable-Interface to MultiLanguageFrame and markt the SWT-Components as transient it tells me that no valid constructor was found. I can't add a parameterless constructor because it would logically make no sense and also SWT-Components need a parent to exist.
I'm really stuck with this problem because I do not know how to work around this. Thanks for answers in advance!
I found the solution myself. I'll just post this so others can see it, it might help.
Thanks to #greg-449 who lead the way. I do have an inner class TranslationHelper which extends Translation in MultiLanguageFrame. The purpose of this is so I can save some flags (deleted, changed, new) for Translations without changing Translation itself. When I call frame.getTranslations() I cast the elements from TranslationsHelper to Translation. The instance of the object remains a TranslationHelper though.
Now it all makes sense that MultiLanguageFrame was involved in all of this.
Related
Presently, I am writing a Java application that uses the Singleton pattern and serialisation. I have a dedicated serialiser class that serialises and deserialises an object to and from a given file path. One of my objects is serialised and deserialised without issue: I make some changes to my application's state while it's open, then close the application, and those changes are still there when I reopen it. However, this does not work with another one of my objects, even though as far as I can tell there is no major difference between them that should cause this to be the case.
Here is the code for my Serialiser class:
public static Boolean serialise(Object target, String filePath){
try (FileOutputStream fileOut = new FileOutputStream(filePath);
ObjectOutputStream objectOut = new ObjectOutputStream(fileOut);){
objectOut.writeObject(target);
return true;
} catch (FileNotFoundException ex) {
Logger.getLogger(Serialiser.class.getName()).log(Level.SEVERE, null, ex);
return false;
} catch (IOException ex) {
Logger.getLogger(Serialiser.class.getName()).log(Level.SEVERE, null, ex);
return false;
}
}
public static Object deserialise(String filePath){
try (FileInputStream fileIn = new FileInputStream(filePath);
ObjectInputStream objectIn = new ObjectInputStream(fileIn);){
Object readObject = objectIn.readObject();
return readObject;
} catch (IOException | ClassNotFoundException ex) {
Logger.getLogger(Serialiser.class.getName()).log(Level.SEVERE, null, ex);
return null;
}
}
And here is the relevant code for my objects. First, the one that works:
public class AccountManager implements Serializable {
private static final long serialVersionUID = 1L;
private static final String FILE_PATH = "data//account_manager.ser";
private static AccountManager instance;
private ArrayList<Account> accounts = null;
private AccountManager(){
accounts = new ArrayList<>();
}
public static AccountManager getInstance(){
if (instance == null){
instance = (AccountManager)Serialiser.deserialise(FILE_PATH);
if (instance == null){
instance = new AccountManager();
Serialiser.serialise(instance, FILE_PATH);
}
}
return instance;
}
And now the one that does not work:
public class MessageManager implements Serializable {
private static final long serialVersionUID = 1L;
private static final String FILE_PATH = "data//message_manager.ser";
private static MessageManager instance = null;
private ArrayList<Message> messages = null;
private MessageManager(){
messages = new ArrayList<>();
}
public static MessageManager getInstance(){
if (instance == null){
instance = (MessageManager)Serialiser.deserialise(FILE_PATH);
if (instance == null){
instance = new MessageManager();
Serialiser.serialise(instance, FILE_PATH);
}
}
return instance;
}
Essentially, both classes work the same way: they store an list of objects of a certain type and provide access to and perform operations on the contents of their respective lists. When the Singleton instance of the classes are accessed, it checks if it could deserialise an instance from a file. If it can't it instantiates a new one and serialises it. Again, this works for one, AccountManager, but not for the other, MessageManager. That is, if create new Account object and store it using the AccountManager while the application is running, it will still be there if I restart the application. The same is not true for MessageManager and Message objects.
When a new Account is created, the instance, and presumably its associated fields, i.e. the accounts list are serialised.
public Account createAccount(String password, String givenName, String surname, String address, Gender gender, LocalDate dateOfBirth){
String id = IDGenerator.getInstance().generateID(AccountType.PATIENT, accounts);
Account createdAccount = null;
if (id != null){
createdAccount = new PatientAccount(id, password, givenName, surname, address, gender, dateOfBirth);
if (createdAccount != null){
accounts.add(createdAccount);
Serialiser.serialise(instance, FILE_PATH);
}
return createdAccount;
}
return null;
}
In MessageManager, when a new Message instance is added its list, and the instance, and again, presumably its list, are serialised:
public Boolean sendMessage(Message message){
if (messages.add(message)){
Serialiser.serialise(instance, FILE_PATH);
return true;
}
return false;
}
Both Account and Message implement Serializable, and both have serialVersionUIDs. I do not get any NotSerializable exceptions.
Any help would in fixing this problem would be greatly appreciated. If necessary, I can provide more of my application's code.
I managed to solve my problem. My issue was in a different part of my application. Essentially, I had believed that MessageManager was not be serialised because I couldn't get the data I was expecting back from it. It turns out the issue was in the method that returned the data: I was using an object reference to identify what data to return. Of course, deserialisation creates new instances, so, after recreating my objects, I could not longer access data identified by an old instance.
To fix my issue, I used a different means of identifying data: a string property whose value would be the same across instances, before and after deserialisation.
Thank you to everyone who looked at my question and/or attempted to answer it.
I have a class that overrides ArrayList like:
public class SkmeList extends ArrayList<SkmeStatement> {
private static final long serialVersionUID = 1L;
private int skmeMajor = 0;
private int skmeMinor = 0;
private String skmeTable = null;
public void setTable(String table) {
System.out.println("Set Table: " + table);
skmeTable = table;
}
public String getTable() {
return skmeTable;
}
public void setMajor(int major) {
System.out.println("SetMajor: " + major);
skmeMajor = major;
}
public int getMajor() {
return skmeMajor;
}
public void setMinor(int minor) {
System.out.println("SetMinor: " + minor);
skmeMinor = minor;
}
public int getMinor() {
return skmeMinor;
}
}
when I attempt to write this class to a file or even a string using jackson I can only see the list contents, I do not see any of class specific attributes like Major or minor in the string/file? I treat this class just like any other java class. Is there something that is different with lists in jackson object mapper?
public void WriteJson(SkmeList statements) {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final ObjectMapper mapper = new ObjectMapper();
try {
mapper.writeValue(out, statements);
final byte[] data = out.toByteArray();
System.out.println(new String(data));
}
catch (IOException ioe) {
System.out.println("Foo");
}
}
A List has elements and no further non-element data. If you need more data, you need something that's more than a List.
The user of your class already has to treat it specially if they care about any of the extra fields you've added.
In favoring composition over inheritance, here's how I'd suggest this class could look like.
public class SkmeList {
private final int major;
private final int minor;
private final String table;
private final List<SkmeStatement> statements;
// ctor, getters, hashCode, equals and toString omitted
}
With more context on what Skme means, we could make the naming even clearer.
To make it easier to reason about, the class should be immutable, to make it safe for use in a Collection it should have hashCode() and equals(), and a toString() in case it ever gets printed/logged/debugged around.
If you don't feel like implementing all the omitted methods, consider AutoValue: you specify the getters and a factory method, the rest is generated for you.
For the user of your class, it's almost the same:
SkmeList list = ...
for (SkmeStatement stmt : list) {
...
now becomes
SkmeList list = ...
for (SkmeStatement stmt : list.getStatements()) {
...
What happens during serialization in java, if two object refrences are pointing to the same serializable Object? Does the Serializable Objects get saved twice ?
for example :
class King implements java.io.Serializable {
private String name="Akbar";
}
class Kingdom implements java.io.Serializable {
King goodKing=new King();
King badKing=goodKing;
}
public class TestSerialization {
public static void serializeObject(String outputFileName,
Object serializableObject) throws IOException {
FileOutputStream fileStream=new FileOutputStream(outputFileName);
ObjectOutputStream outStream=new ObjectOutputStream(fileStream);
outStream.writeObject(serializableObject);
outStream.close();
}
public static void main(String[] args) {
Kingdom kingdom=new Kingdom();
try {
TestSerialization.serializeObject("Kingdom1.out", kingdom);
}catch(IOException ex) {
ex.getMessage();
}
}
}
Now, whether only one object state is saved for both goodKing and badKing refrences or the King object get saved twice ?
The documentation for ObjectOutputStream says what happens:
The default serialization mechanism for an object writes the class of the object, the class signature, and the values of all non-transient and non-static fields. References to other objects (except in transient or static fields) cause those objects to be written also. Multiple references to a single object are encoded using a reference sharing mechanism so that graphs of objects can be restored to the same shape as when the original was written.
(My emphasis)
E.g., if you have multiple references to a single object, when the graph is reconstituted, you end up with multiple references to a single reconstituted version of that object, not references to multiple equivalent instances of it.
Of course, if the container being serialized implements a different mechanism, the behavior is dictated by that mechanism, not the default one.
So for instance, if we have Thing and Test:
Thing.java:
import java.io.*;
import java.util.*;
public class Thing implements Serializable {
private Map<String,String> map1;
private Map<String,String> map2;
public Thing() {
this.map1 = new HashMap();
this.map2 = this.map1; // Referring to same object
}
public void put(String key, String value) {
this.map1.put(key, value);
}
public boolean mapsAreSameObject() {
return this.map1 == this.map2;
}
}
Test.java:
import java.io.*;
public class Test implements Serializable {
public static final void main(String[] args) {
try
{
// Create a Thing
Thing t = new Thing();
t.put("foo", "bar");
// Write it out
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("foo"));
os.writeObject(t);
os.close();
os = null;
// Read it in
Thing t2;
ObjectInputStream is = new ObjectInputStream(new FileInputStream("foo"));
t2 = (Thing)is.readObject();
is.close();
is = null;
// Same underlying map for both properties?
System.out.println("t2.mapsAreSameObject? " + t2.mapsAreSameObject());
}
catch (Exception e)
{
System.out.println("Exception: " + e.getMessage());
}
}
}
And run java Test, we get:
t2.mapsAreSameObject? true
...because both of Thing's members, map1 and map2 end up pointing to a single HashMap instance.
I'm trying to serialize my model inside my program. The model is named "ImageModel" and it implements Serializable. This model also contains another custom object named "Perspective" which is also implementing Serializable. I have a static utility class that serializes my model and reads it. This code has been tested in the main method with an "ImageModel" and everything is working perfectly.
But when I try to use it in the actual program I'm running into an issue. The "ImageModel" class is declared in my system class, named "MainWindow" which extends JFrame and is the link between most of the different classes. For some reason, I can't serialize the model doing something like MainWindow.getModel(). The compiler argues that my "EventFactory" is not serializable. This class is also declared in "MainWindow", but I'm not even understanding why Java wants to serialize it, I'm under the impression that java is not just trying to serialize the model, but also the GUI.
Here are segments of code :
My model:
public class ImageModel extends Observable implements Cloneable, Serializable {
private String path;
private ArrayList<Observer> observers;
private ArrayList<Perspective> perspectives;
private int numPerspectives;
private Perspective selectedPerspective;
...
}
The perspective class:
public class Perspective implements Serializable {
private ImageModel image;
private int degreeOfRotation;
private Point topLeftPoint;
private int zoomPercentage;
private int height;
private int width;
...
}
The actual GUI that declares the model and other elements:
public class MainWindow extends JFrame {
private final int GRID_ROWS = 0;
private final int GRID_COLUMNS = 2;
private final int NUM_PERSPECTIVE = 3;
private JPanel mainPane;
private ArrayList<View> perspectiveList;
private ImageModel imageModel;
private EventFactory eventFactory;
private JMenu menu;
private JToolBar toolBar;
...
}
The main method:
MainWindow mw = new MainWindow();
/*
* Does NOT work:
* ImageModel imageModel= mw.getImageModel();
* Utility.serializeModel(imageModel); //Crashes
*
* Works:
*
* ImageModel imageModel= new ImageModel();
* Utility.serializeModel(imageModel);
*
*/
Here are my two utility functions in case you need them :
public static void serializeModel(ImageModel imageModel)
{
String filename = "TEST.ser";
FileOutputStream fos = null;
ObjectOutputStream out = null;
try
{
fos = new FileOutputStream(filename);
out = new ObjectOutputStream(fos);
out.writeObject(imageModel);
out.close();
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
public static ImageModel restoreModel(String filename)
{
ImageModel imageModel = null;
FileInputStream fis = null;
ObjectInputStream in = null;
try
{
fis = new FileInputStream(filename);
in = new ObjectInputStream(fis);
imageModel = (ImageModel)in.readObject();
in.close();
}
catch(IOException ex)
{
ex.printStackTrace();
}
catch(ClassNotFoundException ex)
{
ex.printStackTrace();
}
return imageModel;
}
Here's the STACK_TRACE of the error I'm receiving when working on the actual use case:
http://pastie.org/3008549
So yeah, like I'm saying, it's like if Java was trying to serialize other stuff around the model.
I'm guessing EventFactory is somehow making it's way into ImageModel's fields. Maybe indirectly linked from an Observer. Perhaps you should clear that list before attempting to serialise or set that field as transient.
I have a class to be saved into appengine datastore, which among others contains, a Text field (String-like appengine datatype, but not limited to 500 chars). Also a twin class which is basically the same, but is used on the client side (ie without any com.google.appengine.api.datastore.* import).
Is there any datatype, which would let me save the Text server-side field into client-Side?
A possible option would be split the Text into some Strings, but that sounds pretty ugly...
Any suggestions?
You can call getValue() to make it a String.
You can use Text for your persistable field. You just need to have a RPC serializer to be able to use it on the client (in GWT).
Take a look at http://blog.js-development.com/2010/02/gwt-app-engine-and-app-engine-data.html, it explains how to do it.
some additions to custom serializable libraries posted before
( http://juristr.com/blog/2010/02/gwt-app-engine-and-app-engine-data/
http://www.resmarksystems.com/code/
- get com.google.appengine.api.datastore.Text and other datastore types transferred to client)
need also to update com.google.appengine.eclipse.core.prefs to include library:
filesCopiedToWebInfLib=...|appengine-utils-client-1.1.jar
another workaround is making string serializable blob to overcome 1500 bytes limit (it will lost sort and filter ability fir this field):
#Persistent(serialized = "true")
public String content;
it is possible to have less overhead on client with converting from com.google.appengine.api.datastore.Text to String with lifecycle listeners (not instance listeners, they will got send to client and make it fail). use it together with custom serialization which allows client support for com.google.appengine.api.datastore.Text with no additional transport class is required.
com.google.appengine.api.datastore.Text may be cleared before sending to client to avoid sending overhead (simplest way is to mark it transient).
on server side we have to avoid setting String property directly, because jdo will not catch it change (will catch only for new records or when some persistent field is modified after). this is very little overhead.
detaching of records should be performed via pm.makeTransient. when using pm.detachCopy it is required to mark entity as detachable = "true" (DetachLifecycleListener to be called) and implement DetachLifecycleListener.postDetach similar way as StoreLifecycleListener.preStore. othwerwise non-persistent fields will not be copied (by pm.detachCopy) and will be empty on client.
it is possible to handle several classes similar way
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.listener.DetachLifecycleListener;
import javax.jdo.listener.InstanceLifecycleEvent;
import javax.jdo.listener.LoadLifecycleListener;
import javax.jdo.listener.StoreLifecycleListener;
import com.google.appengine.api.datastore.Text;
import com.mycompany.mywebapp.shared.Entity;
import com.mycompany.mywebapp.shared.Message;
#SuppressWarnings("rawtypes")
public class PersistenceManagerStuff
{
public static final PersistenceManagerFactory PMF = JDOHelper.getPersistenceManagerFactory("transactions-optional");
public static EntityLifecycleListener entityLifecycleListener = new EntityLifecycleListener();
public static Class[] entityClassList = new Class[] { Entity.class };
public static MessageLifecycleListener messageLifecycleListener = new MessageLifecycleListener();
public static Class[] messageClassList = new Class[] { Message.class };
public static PersistenceManager getPersistenceManager()
{
PersistenceManager pm = PMF.getPersistenceManager();
pm.addInstanceLifecycleListener(entityLifecycleListener, entityClassList);
pm.addInstanceLifecycleListener(messageLifecycleListener, messageClassList);
return pm;
}
// [start] lifecycle listeners
public static class EntityLifecycleListener implements LoadLifecycleListener, StoreLifecycleListener//, DetachLifecycleListener
{
public void postLoad(InstanceLifecycleEvent event)
{
Entity entity = ((Entity) event.getSource());
if (entity.content_long != null)
entity.content = entity.content_long.getValue();
else
entity.content = null;
}
public void preStore(InstanceLifecycleEvent event)
{
Entity entity = ((Entity) event.getSource());
entity.setContent(entity.content);
/*
need mark class #PersistenceAware to use code below, otherwise use setter
if (entity.content != null)
entity.content_long = new Text(entity.content);
else
entity.content_long = null;
*/
}
public void postStore(InstanceLifecycleEvent event)
{
}
/*public void postDetach(InstanceLifecycleEvent event)
{
}
public void preDetach(InstanceLifecycleEvent event)
{
}*/
}
public static class MessageLifecycleListener implements LoadLifecycleListener, StoreLifecycleListener
{
public void postLoad(InstanceLifecycleEvent event)
{
Message message = ((Message) event.getSource());
if (message.content_long != null)
message.content = message.content_long.getValue();
else
message.content = null;
}
public void preStore(InstanceLifecycleEvent event)
{
Message message = ((Message) event.getSource());
message.setContent(message.content);
}
public void postStore(InstanceLifecycleEvent event)
{
}
}
// [end] lifecycle listeners
}
#SuppressWarnings("serial")
#PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "false")
public class Entity implements Serializable
{
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
public Long id;
#NotPersistent
public String content;
#Persistent(column = "content")
public transient com.google.appengine.api.datastore.Text content_long;
public void setContent(String content)
{
this.content = content;
if (content != null)
content_long = new Text(content);
else
content_long = null;
}
public Entity() {}
}
#PersistenceAware
public class DataServiceImpl extends RemoteServiceServlet implements DataService
{
public Entity renameEntity(long id, String newContent) throws NotLoggedInException
{
PersistenceManager pm = PersistenceManagerStuff.getPersistenceManager();
Entity result = null;
try
{
Entity entity = (Entity) pm.getObjectById(Entity.class, id);
if (entity.longUserId != getLongUserId(pm))
throw new NotLoggedInException(String.format("wrong entity %d ownership", entity.id));
entity.modificationDate = java.lang.System.currentTimeMillis(); // will call lifecycle handlers for entity.content, but is still old value
//entity.content = newContent; // will not work, even owner class is #PersistenceAware
entity.setContent(newContent); // correct way to set long value
pm.makeTransient(result = entity);
}
catch (Exception e)
{
LOG.log(Level.WARNING, e.getMessage());
throw e;
}
finally
{
pm.close();
}
return result;
}
}
also in lifecycle handlers it is possible to mix old (short) and new (long) values into single entity if you have both (with different field names) and do not want to convert old to new. but is seems com.google.appengine.api.datastore.Text supports loading from old String values.
some low-level code to batch convert old values into new (using low level com.google.appengine.api.datastore api):
DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
Query q = new Query("Entity");
PreparedQuery pq = datastore.prepare(q);
for (com.google.appengine.api.datastore.Entity result : pq.asIterable())
{
String content = (String) result.getProperty("content");
if (content != null)
{
result.setProperty("content", new com.google.appengine.api.datastore.Text(content));
datastore.put(result);
}
}