package com.google.code.joto.util.io;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.Externalizable;
import java.io.IOException;
import java.io.InvalidClassException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* helper class for encoding/decoding ObjectStreamClass in a contextual compressed way
*/
public class IdToObjectStreamClassCompressionContext implements Externalizable {
private int idGenerator = 1;
private ArrayList<ObjectStreamClass> idToValue = new ArrayList<ObjectStreamClass>();
private Map<Class<?>,Integer> valueToId = new HashMap<Class<?>,Integer>();
//-------------------------------------------------------------------------
public IdToObjectStreamClassCompressionContext() {
clear();
}
//-------------------------------------------------------------------------
public void clear() {
idToValue.clear();
valueToId.clear();
idGenerator = 1;
idToValue.add(null); // add NULL value
// TOADD register default classes ..
// for String,List,Integer,Boolean,...
}
public void encodeContextualValue(ObjectStreamClass valueClassData, ObjectOutputStream out) throws IOException {
if (valueClassData == null) {
out.writeInt(0);
} else {
Class<?> value = valueClassData.forClass();
Integer id = valueToId.get(value);
if (id != null) {
out.writeInt(id.intValue());
} else {
// generate a new unique id... encode as "-id,value"
// even if the decoder knows the eventIdGenerator value,
// it is "better" to use a marker for newly generated value
// benefit: the context can re-read encoded values, by itself or others
// even it would be no more a "newly" generated id/value
int newid = addNewValue(valueClassData);
out.writeInt(-newid); // with negative sign!
doWriteValue(valueClassData, out);
}
}
}
public ObjectStreamClass decodeContextualValue(ObjectInputStream in) throws IOException {
ObjectStreamClass res;
int id = in.readInt();
if (id == 0) {
res = null;
} else if (id > 0) {
// "(+)id" : existing value
res = idToValue.get(id);
} else { // id < 0
// "-id,value"
id = -id;
res = doReadValue(in);
Class<?> value = res.forClass();
// register newly id (not needed when re-reading from context)
if (id > idGenerator) {
idGenerator = id + 1; //??
if (idToValue.size() < id) {
idToValue.ensureCapacity(id);
for (int i = idToValue.size(); i < id; i++) {
idToValue.add(null);
}
}
idToValue.set(id, res);
valueToId.put(value, Integer.valueOf(id));
} // else re-read value..
}
return res;
}
private ObjectStreamClass doReadValue(DataInput in) throws IOException {
ObjectStreamClass res;
String className = in.readUTF(); // TODO decode read real ObjectStreamClass instead of className?... cf corresponding doWriteValue()
Class<?> clss;
try {
clss = Class.forName(className);
} catch(Exception ex) {
throw new InvalidClassException(className);
}
res = ObjectStreamClass.lookup(clss);
return res;
}
private void doWriteValue(ObjectStreamClass classData, DataOutput out) throws IOException {
Class<?> value = classData.forClass();
out.writeUTF(value.getName()); // TODO encode real valueClassData instead of name??? .. cf corresponding doReadValue()
}
// ObjectStreamClass classData = ObjectStreamClass.lookup(value);
protected int addNewValue(ObjectStreamClass classData) {
int newid = idGenerator++;
idToValue.add(classData);
Class<?> value = classData.forClass();
valueToId.put(value, Integer.valueOf(newid));
return newid;
}
// -------------------------------------------------------------------------
/**
* implements java.io.Externalizable (optim for Serializable)
* => used to encode self ... do not mismatch with encodeContextualValue!
*/
@Override
public void writeExternal(ObjectOutput out) throws IOException {
writeExternal2(out);
}
/**
* implements java.io.Externalizable (optim for Serializable)
* => used to decode self ... do not mismatch with decodeContextualValue!
*/
@Override
public void readExternal(ObjectInput in) throws IOException {
readExternal2(in);
}
/**
* simpler than java.io.Externalizable: used DataOutput instead of ObjectOutput..
*/
public void writeExternal2(DataOutput out) throws IOException {
out.writeInt(idGenerator);
int size = idToValue.size();
out.writeInt(size);
for (int i = 1; i < size; i++) {
ObjectStreamClass valueClassData = idToValue.get(i);
doWriteValue(valueClassData, out);
}
}
/**
* simpler than java.io.Externalizable: used DataInput instead of ObjectInput..
*/
public void readExternal2(DataInput in) throws IOException {
clear();
idGenerator = in.readInt();
int size = in.readInt();
for (int i = 1; i < size; i++) {
ObjectStreamClass valueClassData = doReadValue(in);
Class<?> value = valueClassData.forClass();
idToValue.add(valueClassData);
valueToId.put(value, Integer.valueOf(i));
}
}
// override java.lang.Object / debugging helper
// -------------------------------------------------------------------------
@Override
public String toString() {
return "IdToObjectStreamClassCompressionContext[size:" + toStringSize() + "]";
}
public String toStringSize() {
return String.valueOf(idGenerator);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
IdToObjectStreamClassCompressionContext other = (IdToObjectStreamClassCompressionContext) obj;
if (idGenerator != other.idGenerator)
return false;
if (idToValue == null) {
if (other.idToValue != null)
return false;
} else if (!idToValue.equals(other.idToValue))
return false;
return true;
}
}