Java Object Serialization nested objects - java

I was studying Serialization in Java when I came across saving the state of objects which are not serializable and are referenced in Class(instance variables) to be serialized. In the following code, I am having class Dog (Serializable) which has reference to class Collar(not serializable); which in turn has reference to class Color (not serializable). I am getting error despite trying all the possibilities. This is the latest code I came up with:
class Color {
private String colorName;
public String getColorName() {
return colorName;
}
public void setColorName(String colorName) {
this.colorName = colorName;
}
Color(String color) {
this.colorName = color;
}
}
class Collar {
private Color color;
private int size;
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
Collar(int size, Color color) {
this.size = size;
this.color = color;
}
}
class Dog implements Serializable {
Dog(String breed, Collar collar) {
this.breed = breed;
this.collar = collar;
}
private String breed;
public String getBreed() {
return breed;
}
public void setBreed(String breed) {
this.breed = breed;
}
public Collar getCollar() {
return collar;
}
public void setCollar(Collar collar) {
this.collar = collar;
}
transient private Collar collar;
private void writeObject(ObjectOutputStream os) {
try {
os.defaultWriteObject();
os.writeInt(this.getCollar().getSize());
os.writeUTF(this.getCollar().getColor().getColorName());
os.close();
} catch (IOException ex) {
Logger.getLogger(Dog.class.getName()).log(Level.SEVERE, null, ex);
}
}
private void readObject(ObjectInputStream is) {
try {
is.defaultReadObject();
int size = is.readInt();
String colorName = is.readUTF();
this.setCollar(new Collar(size, new Color(colorName)));
is.close();
} catch (Exception ex) {
Logger.getLogger(Dog.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
public class App0001 {
public static void main(String[] args) {
try {
Dog d = new Dog("Great Dane", new Collar(3, new Color("RED")));
//System.out.println(d.getCollar().getColor().getColorName());
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("obj.ser"));
os.writeObject(d);
os.close();
ObjectInputStream is = new ObjectInputStream(new FileInputStream("obj.ser"));
d = (Dog) is.readObject();
System.out.println(d.getCollar().getColor().getColorName());
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
And I am getting following error:
java.io.IOException: Write error
at java.io.FileOutputStream.writeBytes(Native Method)
at java.io.FileOutputStream.write(FileOutputStream.java:260)
at java.io.ObjectOutputStream$BlockDataOutputStream.drain(ObjectOutputStream.java:1847)
at java.io.ObjectOutputStream$BlockDataOutputStream.setBlockDataMode(ObjectOutputStream.java:1756)
at java.io.ObjectOutputStream.writeNonProxyDesc(ObjectOutputStream.java:1257)
at java.io.ObjectOutputStream.writeClassDesc(ObjectOutputStream.java:1211)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1395)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1158)
at java.io.ObjectOutputStream.writeFatalException(ObjectOutputStream.java:1547)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:333)
at Serialization.App0001.main(App0001.java:121)
This is not a production code. This is just for practice and understanding.

You must not close the streams in readObject and writeObject! If you do so, the next write/read attempt fails.
Usually, streams (as other resources) should be treated as follows:
If your method owns the stream, i.e. your method opened it - close it
in the same method (usually this is done in a try-with-resource
statement).
If your method does NOT own the stream, i.e. it got the stream passed from somewhere else (usually passed via method parameter), don't close it as you don't know what the owner of the stream wants to do with it after your method returns.

When writing to a stream, the "Write error" in an IOException occurs, if the stream is closed.
Analyzing your code, I see that you have a custom writeObject method in your class Dog. In that you must not close the stream, as it is needed for continued writing. So just remove the line
os.close();
in your writeObject method. Oh, and also remove the line
is.close();
in the readObject method.
Ok, I will explain it a bit more. You have the following code in your main method:
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("obj.ser"));
os.writeObject(d);
os.close();
Here you are creating the stream, using it, and afterwards you close it. This is the correct place to close it, as this is the responsible place for the stream.
Imagine, you have a nested structure of serializable objects, whose class definitions all contain a custom writeObject method. When calling the writeObject method of an ObjectOutputStream, it walks through the object graph with calling the writeObject method of each object. The ObjectOutputStream is controlling the write order, and it also write control bytes itself. Creating and closing must be done outside (as you already did).

Related

How to write data in file in specific format with FileOutputStream and PrintWriter

I have class Artical:
first variable is code of artical, second variable is name of article and third is price of article.
public class Artical {
private final String codeOfArtical;
private final String nameOfArtical;
private double priceOfArtical;
public Artical(String codeOfArtical, String nameOfArtical, double priceOfArtical) {
this.codeOfArtical= codeOfArtical;
this.nameOfArtical= nameOfArtical;
this.priceOfArtical= priceOfArtical;
}
public void setPriceOfArtical(double priceOfArtical) {
this.priceOfArtical= priceOfArtical;
}
public String getCodeOfArtical() {
return codeOfArtical;
}
public String getNameOfArtical() {
return nameOfArtical;
}
public double getPriceOfArtical() {
return priceOfArtical;
}
}
I want in main class to write something like:
Artical a1 = new Artical("841740102156", "LG Monitor", 600.00);
new ShowArticalClass(a1).do();
new WriteArticalInFileClass(new File("baza.csv"), a1).do();
so that data in file will be written in format like this:
841740102156; Monitor LG; 600.00;
914918414989; Intel CPU; 250.00;
Those 2 classes ShowArticalClass and WriteArticalInFileClass arent important, those are abstract classes.*
So my question is: How do I set format to look like this, where every line is new Artical.
A very naive implementation can be the following:
Create a class that in turn creates a CSVWriter (assuming you want to write to a CSV). That class will expose a public method allowing you to pass in a path where the desired csv file lives as well as the Artical object you want to write to this file. Using that class you will format your data and write them to the file. An example of this could be:
public class CsvWriter {
private static final Object LOCK = new Object();
private static CsvWriter writer;
private CsvWriter() {}
public static CsvWriter getInstance() {
synchronized (LOCK) {
if (null == writer) {
writer = new CsvWriter();
}
return writer;
}
}
public void writeCsv(String filePath, Artical content) throws IOException {
try (var writer = createWriter(filePath)) {
writer.append(getDataline(content)).append("\n");
}
}
private String getDataline(Artical content) {
return String.join(",", content.getCode(), content.getName(), Double.toString(content.getPrice()));
}
private PrintWriter createWriter(String stringPath) throws IOException {
var path = Paths.get(stringPath);
try {
if (Files.exists(path)) {
System.out.printf("File under path %s exists. Will append to it%n", stringPath);
return new PrintWriter(new FileWriter(path.toFile(), true));
}
return new PrintWriter(path.toFile());
} catch (Exception e) {
System.out.println("An error has occurred while writing to a file");
throw e;
}
}
}
Note that this will take into account where the file provided is already in place (thus appending to it). In any other case the file will be created and written to directly.
Call this write method in a fashion similar to this:
public static void main(String... args) throws IOException {
var artical = new Artical("1", "Test", 10.10);
CsvWriter.getInstance().writeCsv("/tmp/test1.csv", artical);
var artical2 = new Artical("2", "Test", 11.14);
CsvWriter.getInstance().writeCsv("/tmp/test1.csv", artical2);
}
With that as a starting point you can go ahead and modify the code to be able to handle list of Artical objects.
If you really need to support CSV files though I would strongly recommend into looking at the various CSV related libraries that are out there instead of implementing your own code.

Abstract Generic Object class

Hi
In the game Minecraft you have to send data between the client and the server in order to sync stuff, one of the things that need to be synced in particular circumstances is TileEntities with a render. The data is stored on the server and sent to the client which is thereafter used for render.
You create a class that contains the data that needs to be synced and attach it to a channel creation API called "SimpleImpl". My Network object and a Message Object:
public class IntercraftPacketHandler
{
private static int index = 1;
private static final ResourceLocation CHANNEL_NAME = new ResourceLocation(Reference.MODID,"network");
private static final String PROTOCOL_VERSION = new ResourceLocation(Reference.MODID,"1").toString();
public static SimpleChannel getNetworkChannel()
{
final SimpleChannel channel = NetworkRegistry.ChannelBuilder.named(CHANNEL_NAME)
.clientAcceptedVersions(version -> true)
.serverAcceptedVersions(version -> true)
.networkProtocolVersion(() -> PROTOCOL_VERSION)
.simpleChannel();
// Sync Capability Identity Hidden data message.
channel.messageBuilder(MessageIdentityHidden.class,index)
.encoder(MessageIdentityHidden::encode)
.decoder(MessageIdentityHidden::decode)
.consumer(MessageIdentityHidden::handle)
.add(); index++;
// Send TreeTapTileEntity data to client.
channel.messageBuilder(MessageTreeTap.class,index)
.encoder(MessageTreeTap::encode)
.decoder(MessageTreeTap::decode)
.consumer(MessageTreeTap::handle)
.add(); index++;
// Send ChunkLoaderTileEntity data to client.
channel.messageBuilder(MessageChunkLoader.class,index)
.encoder(MessageChunkLoader::encode)
.decoder(MessageChunkLoader::decode)
.consumer(MessageChunkLoader::handle)
.add(); index++;
return channel;
}
}
public class MessageChunkLoader
{
private BlockPos pos;
private boolean canLoad;
public MessageChunkLoader(BlockPos pos,boolean canLoad)
{
this.pos = pos;
this.canLoad = canLoad;
}
public void handle(Supplier<NetworkEvent.Context> ctx)
{
ctx.get().enqueueWork(() -> {
try {
ChunkLoaderBaseTileEntity tile = (ChunkLoaderBaseTileEntity) Minecraft.getInstance().world.getTileEntity(pos);
tile.canLoad = canLoad;
} catch (NullPointerException err) {
System.out.println(String.format("Could not find ChunkLoaderTileEntity at %s %s %s!",pos.getX(),pos.getY(),pos.getZ()));
}
});
}
public static void encode(MessageChunkLoader message, PacketBuffer buffer)
{
buffer.writeBlockPos(message.pos);
buffer.writeBoolean(message.canLoad);
}
public static MessageChunkLoader decode(final PacketBuffer buffer)
{
return new MessageChunkLoader(buffer.readBlockPos(),buffer.readBoolean());
}
}
I then initialize it in my main mod class used by objects in my mod project.
#Mod(Reference.MODID)
public class IntercraftCore
{
public static final SimpleChannel NETWORK = IntercraftPacketHandler.getNetworkChannel();
...
The problem and this post's question; right now I create a new message class from the formula I follow in MessageChunkLoader (public static encode & decode method and a handle method). I would like to create a more generic class for creating message classes for TileEntities, but I'm having problems with that. Here's the current class:
public abstract class MessageTileEntity<T extends TileEntity>
{
protected final BlockPos pos;
protected final Class<T> clazz;
public MessageTileEntity(BlockPos pos, Class<T> clazz)
{
this.pos = pos;
this.clazz = clazz;
}
public abstract void handle(Supplier<NetworkEvent.Context> ctx);
protected T getTileEntity()
{
try {
return clazz.cast(Minecraft.getInstance().world.getTileEntity(pos));
} catch (NullPointerException e) {
System.out.println(String.format("Could not find %s at [%d %d %d]!",clazz.getSimpleName(),pos.getX(),pos.getY(),pos.getZ()));
throw e;
} catch (ClassCastException e) {
System.out.println(String.format("TileEntity at [%d %d %d] is not %s!",pos.getX(),pos.getY(),pos.getZ(),clazz.getSimpleName()));
throw e;
}
}
public static void encode(MessageTileEntity message, PacketBuffer buffer)
{
}
public static MessageTileEntity decode(final PacketBuffer buffer)
{
return null;
}
}
The main problem is I lack the proper Java skills to make it like I want it to function. The method handle is easy as it's non-static and needs to be custom to every TileEntity message, but the methods encode and decode which needs to be static gives me problems. I have no idea what I'm trying to achieve is possible, asking won't hurt. Maybe the solution is easier than I think.

Serialization of an object "that has fields reverenced by a SWT class"

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.

java.io.InvalidClassException in serialization

after reading for serialization, i tried to perform an experiment on the example provided in the book. Following code has some variation and this is basically picked from SCJP book.
import java.io.FileInputStream;
public class SerializationTest {
public static void main(String[] args) {
Collar c = new Collar(4);
Dog d = new Dog(c, "Sheru", 32);
FileOutputStream fos = null;
ObjectOutputStream oos = null;
try {
fos = new FileOutputStream(
"C:\\Users\\dell\\Desktop\\NewDir\\DogState.txt");
oos = new ObjectOutputStream(fos);
oos.writeObject(d);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
oos.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// ***************************************************************************************************
// //
Dog restore = null;
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
fis = new FileInputStream(
"C:\\Users\\dell\\Desktop\\NewDir\\DogState.txt");
ois = new ObjectInputStream(fis);
restore = (Dog) ois.readObject();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
fis.close();
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("after: dog name: "+ restore.name +" , collar=" + restore.getCollar());
System.out.println("Animal material is:" + restore.getWeight());
}
}
// Intentionally added parameterized constructor so that default constructor is not called.
class Animal{
int weight = 42;
public Animal(int weight) {
this.weight = weight;
System.out.println("animal constructor");
}
}
class Dog extends Animal implements Serializable {
String name;
transient Collar collar;
public Collar getCollar() {
return collar;
}
public void setCollar(Collar collar) {
this.collar = collar;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public Dog(Collar collar, String name, int weight) {
super(weight);
System.out.println("Dog constructor");
this.collar = collar;
this.name = name;
}
}
class Collar {
int size;
public Collar(int size) {
System.out.println("Collar constructor");
this.size = size;
}
}
Here my question is why InvalidClassException is occuring, Please explain what is the root cause of exception.
Current output is
Collar constructor
animal constructor
Dog constructor
java.io.InvalidClassException: Dog; Dog; no valid constructor
at java.io.ObjectStreamClass.checkDeserialize(Unknown Source)
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)
at SerializationTest.main(SerializationTest.java:39)
Caused by: java.io.InvalidClassException: Dog; no valid constructor
at java.io.ObjectStreamClass.<init>(Unknown Source)
at java.io.ObjectStreamClass.lookup(Unknown Source)
at java.io.ObjectOutputStream.writeObject0(Unknown Source)
at java.io.ObjectOutputStream.writeObject(Unknown Source)
at SerializationTest.main(SerializationTest.java:18)
Exception in thread "main" java.lang.NullPointerException
at SerializationTest.main(SerializationTest.java:54)
If i remove Animal constructor and comment out the super(weight) in Dog constructor, then output is
Collar constructor
Dog constructor
after: dog name: Sheru , collar=null
Animal material is:42
I understand this output, and i also get the fact that during de-serialization serialzable class's superclass constructor is called but here no default constructor is present, so exception occured. But why this exception occur i want to know.
The exception is thrown by the time you try to read the from the file:
at java.io.ObjectInputStream.readObject(Unknown Source)
at SerializationTest.main(SerializationTest.java:39)
The stack trace clearly indicates that your program aborts when it attempts to read an object. What may get you confused is the second stack trace referring to the write:
at java.io.ObjectOutputStream.writeObject(Unknown Source)
at SerializationTest.main(SerializationTest.java:18)
But you seem to have skipped this very important line:
Caused by: java.io.InvalidClassException: Dog; no valid constructor
Java stacktraces can be nested, one exception can lead to another; and this here is a litte ackward. As a matter of fact, during the serialization of an object it is already computed that there is no default constructor. Here's an excerpt of the involved source code:
...
cons = getSerializableConstructor(cl);
...
} else if (cons == null) {
deserializeEx = new InvalidClassException(name, "no valid constructor");
}
This means that during the write, it is already clear that there is no valid constructor. However, the exception is not thrown but serialized along with the object. Later, when deserializing, this code is called:
void checkDeserialize() throws InvalidClassException {
if (deserializeEx != null) {
InvalidClassException ice =
new InvalidClassException(deserializeEx.classname,
deserializeEx.getMessage());
ice.initCause(deserializeEx);
throw ice;
}
}
Here, a "real" exception is thrown, but the cause of it is set to be the one stored during serialization of the object.
This mechanism is only found in SUN/Oracle's Java implementation; OpenJDK clearly throws an exception by the time a read is attempted, and does not keep around a stack trace from writing.
The non-Serializable base class Dog must have an accessible default constructor. When you comment out the Dog(weight) constructor, you force the compiler to provide one, and when you leave it in the compiler doesn't provide one.
There is a rule that the parent class of the serializing class or any associated class to that must be implementing Serializable.In your case, when you remove thesuper(weight); then it checks for default constructor and runs properlyBut also if you put class Animal implements Serializable then also code runs properly.

How to assign new object to existing one

I've got a problem with reading an object from file..
in main, I made eshop end call a method loadshop.
MyShop eshop = new MyShop();
eshop.loadShop("eshop.txt");
and also I'm getting the shop object from file.
#Override
public void loadShop(String filename) {
try{
ObjectInput out = new ObjectInputStream(new FileInputStream(filename));
MyShop shop = (MyShop)out.readObject();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
But I just can't figure out how to assign "shop" to "eshop"
Thank you for any answer..:-) (Sorry for my English)
Just redesign, you don't need to write
MyShop eshop = new MyShop();
Instead, write
MyShop eshop = MyShop.loadShop("eshop.txt");
and make the loadShop method static. And make it return the MyShop instance, if it isn't already doing that.
If this is not an option, it is still better to get that instance through a hack than to write manual copying code:
public class MyShop {
private MyShop readShop;
public MyShop getReadShop() { return readShop; }
public void loadShop(String fName) {
try {
ObjectInput out = new ObjectInputStream(new FileInputStream(filename));
this.readShop = (MyShop)out.readObject();
out.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
You'd use this like
MyShop tempShop = new MyShop();
tempShop.loadShop();
MyShop eshop = tempShop.getReadShop();
Make your loadShop method a static method. Make the method return the MyShop object. Call it on the MyShop class. Assign the return value to eshop. Example:
public class MyShop {
public static MyShop loadShop(String filename) {
// ...
MyShop shop = (MyShop)out.readObject();
// ...
return shop;
}
}
Where you call the method:
MyShop eshop = MyShop.loadShop("eshop.txt");
Simply edit all eshop's fields, to match shop.

Categories