package org.infinispan.commons.marshall;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.List;
import org.jboss.marshalling.util.IdentityIntMap;
/**
* An advanced externalizer that when implemented will allow for child instances that also extend this class to use object
* instances instead of serializing a brand new object.
* @author wburns
* @since 7.1
*/
public abstract class InstanceReusingAdvancedExternalizer<T> extends AbstractExternalizer<T> {
static class ReusableData {
IdentityIntMap<Object> map = new IdentityIntMap<>();
int offset;
}
private static ThreadLocal<ReusableData> cachedWriteObjects = new ThreadLocal<ReusableData>();
private static ThreadLocal<List<Object>> cachedReadObjects = new ThreadLocal<List<Object>>();
private static final int ID_NO_REPEAT = 0x01;
private static final int ID_REPEAT_OBJECT_NEAR = 0x02;
private static final int ID_REPEAT_OBJECT_NEARISH = 0x03;
private static final int ID_REPEAT_OBJECT_FAR = 0x04;
public InstanceReusingAdvancedExternalizer() {
this(true);
}
public InstanceReusingAdvancedExternalizer(boolean hasChildren) {
this.hasChildren = hasChildren;
}
/**
* This boolean controls whether or not it makes sense to actually create the context or not. In the case of
* classes that don't expect to have children that support this they shouldn't do the creation
*/
private final boolean hasChildren;
@Override
public final void writeObject(ObjectOutput output, T object) throws IOException {
ReusableData data = cachedWriteObjects.get();
boolean shouldRemove;
if (hasChildren && data == null) {
data = new ReusableData();
cachedWriteObjects.set(data);
shouldRemove = true;
} else {
shouldRemove = false;
}
try {
int id;
if (data != null && (id = data.map.get(object, -1)) != -1) {
final int diff = id - data.offset;
if (diff >= -256) {
output.write(ID_REPEAT_OBJECT_NEAR);
output.write(diff);
} else if (diff >= -65536) {
output.write(ID_REPEAT_OBJECT_NEARISH);
output.writeShort(diff);
} else {
output.write(ID_REPEAT_OBJECT_FAR);
output.writeInt(id);
}
} else {
output.write(ID_NO_REPEAT);
doWriteObject(output, object);
// Set this before writing object in case of circular dependencies
if (data != null) {
data.map.put(object, data.offset++);
}
}
} finally {
if (shouldRemove) {
cachedWriteObjects.remove();
}
}
}
public abstract void doWriteObject(ObjectOutput output, T object) throws IOException;
@Override
public final T readObject(ObjectInput input) throws IOException, ClassNotFoundException {
List<Object> data = cachedReadObjects.get();
boolean shouldRemove;
if (hasChildren && data == null) {
data = new ArrayList<>();
cachedReadObjects.set(data);
shouldRemove = true;
} else {
shouldRemove = false;
}
try {
int type = input.read();
switch (type) {
case ID_NO_REPEAT:
T object = doReadObject(input);
if (data != null) {
data.add(object);
}
return object;
case ID_REPEAT_OBJECT_NEAR:
int offset = input.read() | 0xffffff00;
return getFromCache(data, offset + data.size());
case ID_REPEAT_OBJECT_NEARISH:
offset = input.readShort() | 0xffff0000;
return getFromCache(data, offset + data.size());
case ID_REPEAT_OBJECT_FAR:
return getFromCache(data, input.readInt());
default:
throw new IllegalStateException("Unexpected byte encountered: " + type);
}
} finally {
if (shouldRemove) {
cachedReadObjects.remove();
}
}
}
private T getFromCache(List<Object> data, int index) throws InvalidObjectException {
try {
Object obj = data.get(index);
if (obj != null) {
return (T) obj;
}
} catch (IndexOutOfBoundsException e) {
}
throw new InvalidObjectException("Attempt to read a backreference for " + getClass() + " with an invalid ID (absolute "
+ index + ")");
}
public abstract T doReadObject(ObjectInput input) throws IOException, ClassNotFoundException;
}