org.nustaq.serialization.FSTObjectOutput Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fst Show documentation
Show all versions of fst Show documentation
A fast java serialization drop in-replacement and some serialization based utils such as Structs and OffHeap Memory.
The newest version!
/*
* Copyright 2014 Ruediger Moeller.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.nustaq.serialization;
import org.nustaq.logging.FSTLogger;
import org.nustaq.serialization.util.FSTUtil;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
/**
* Created with IntelliJ IDEA.
* User: Möller
* Date: 03.11.12
* Time: 12:26
* To change this template use File | Settings | File Templates.
*/
/**
* replacement of ObjectOutputStream
*/
public class FSTObjectOutput implements ObjectOutput {
private static final FSTLogger LOGGER = FSTLogger.getLogger(FSTObjectOutput.class);
public static Object NULL_PLACEHOLDER = new Object() { public String toString() { return "NULL_PLACEHOLDER"; }};
public static final byte SPECIAL_COMPATIBILITY_OBJECT_TAG = -19; // see issue 52
public static final byte ONE_OF = -18;
public static final byte BIG_BOOLEAN_FALSE = -17;
public static final byte BIG_BOOLEAN_TRUE = -16;
public static final byte BIG_LONG = -10;
public static final byte BIG_INT = -9;
public static final byte DIRECT_ARRAY_OBJECT = -8;
public static final byte HANDLE = -7;
public static final byte ENUM = -6;
public static final byte ARRAY = -5;
public static final byte STRING = -4;
public static final byte TYPED = -3; // var class == object written class
public static final byte DIRECT_OBJECT = -2;
public static final byte NULL = -1;
public static final byte OBJECT = 0;
protected FSTEncoder codec;
protected FSTConfiguration conf; // immutable, should only be set by FSTConf mechanics
protected FSTObjectRegistry objects;
protected int curDepth = 0;
protected int writeExternalWriteAhead = 8000; // max size an external may occupy FIXME: document this, create annotation to configure this
protected FSTSerialisationListener listener;
// double state to reduce pointer chasing
protected boolean dontShare;
protected final FSTClazzInfo stringInfo;
protected boolean isCrossPlatform;
protected ThreadLocal refsLocal = new ThreadLocal() {
@Override
protected Object initialValue() {
return new FSTClazzInfo.FSTFieldInfo[20];
}
};
FSTClazzInfo.FSTFieldInfo[] refs;
/**
* Creates a new FSTObjectOutput stream to write data to the specified
* underlying output stream.
* uses Default Configuration singleton
*/
public FSTObjectOutput(OutputStream out) {
this(out, FSTConfiguration.getDefaultConfiguration());
}
/**
* Creates a new FSTObjectOutput stream to write data to the specified
* underlying output stream. The counter written
is
* set to zero.
* Don't create a FSTConfiguration with each stream, just create one global static configuration and reuse it.
* FSTConfiguration is threadsafe.
*
* @param out the underlying output stream, to be saved for later
* use.
*/
public FSTObjectOutput(OutputStream out, FSTConfiguration conf) {
this.conf = conf;
setCodec(conf.createStreamEncoder());
getCodec().setOutstream(out);
isCrossPlatform = conf.isCrossPlatform();
objects = (FSTObjectRegistry) conf.getCachedObject(FSTObjectRegistry.class);
if ( objects == null ) {
objects = new FSTObjectRegistry(conf);
objects.disabled = !conf.isShareReferences();
} else {
objects.clearForWrite(conf);
}
dontShare = objects.disabled;
stringInfo = getClassInfoRegistry().getCLInfo(String.class, conf);
}
/**
* serialize without an underlying stream, the resulting byte array of writing to
* this FSTObjectOutput can be accessed using getBuffer(), the size using getWritten().
*
* Don't create a FSTConfiguration with each stream, just create one global static configuration and reuse it.
* FSTConfiguration is threadsafe.
* @param conf
* @throws IOException
*/
public FSTObjectOutput(FSTConfiguration conf) {
this(null,conf);
getCodec().setOutstream(null);
}
/**
* serialize without an underlying stream, the resulting byte array of writing to
* this FSTObjectOutput can be accessed using getBuffer(), the size using getWritten().
* Note once you call close or flush, the tmp byte array is lost. (grab array before flushing/closing)
*
* uses default configuration singleton
*
* @throws IOException
*/
public FSTObjectOutput() {
this(null, FSTConfiguration.getDefaultConfiguration());
getCodec().setOutstream(null);
}
/**
* Flushes this data output stream. This forces any buffered output
* bytes to be written out to the stream.
*
* The flush
method of DataOutputStream
* calls the flush
method of its underlying output stream.
*
* @throws java.io.IOException if an I/O error occurs.
* @see java.io.FilterOutputStream#out
* @see java.io.OutputStream#flush()
*/
@Override
public void flush() throws IOException {
getCodec().flush();
resetAndClearRefs();
}
protected static ByteArrayOutputStream empty = new ByteArrayOutputStream(0);
protected boolean closed = false;
@Override
public void close() throws IOException {
flush();
closed = true;
getCodec().close();
resetAndClearRefs();
conf.returnObject(objects);
}
/**
* since the stock writeXX methods on InputStream are final, i can't ensure sufficient bufferSize on the output buffer
* before calling writeExternal. Default value is 5000 bytes. If you make use of the externalizable interface
* and write larger Objects a) cast the ObjectOutput in readExternal to FSTObjectOutput and call ensureFree on this
* in your writeExternal method or b) statically set a sufficient maximum using this method.
*/
public int getWriteExternalWriteAhead() {
return writeExternalWriteAhead;
}
/**
* since the stock writeXX methods on InputStream are final, i can't ensure sufficient bufferSize on the output buffer
* before calling writeExternal. Default value is 5000 bytes. If you make use of the externalizable interface
* and write larger Objects a) cast the ObjectOutput in readExternal to FSTObjectOutput and call ensureFree on this
* in your writeExternal method or b) statically set a sufficient maximum using this method.
* @param writeExternalWriteAhead
*/
public void setWriteExternalWriteAhead(int writeExternalWriteAhead) {
this.writeExternalWriteAhead = writeExternalWriteAhead;
}
public void ensureFree(int bytes) throws IOException {
getCodec().ensureFree(bytes);
}
//////////////////////////////////////////////////////////////////////////
//
// ObjectOutput interface impl
//
@Override
public void writeObject(Object obj) throws IOException {
writeObject(obj,(Class[])null);
}
@Override
public void write(int b) throws IOException {
getCodec().writeFByte(b);
}
@Override
public void write(byte[] b) throws IOException {
getCodec().writePrimitiveArray(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
getCodec().writePrimitiveArray(b, off, len);
}
@Override
public void writeBoolean(boolean v) throws IOException {
getCodec().writeFByte(v ? 1 : 0);
}
@Override
public void writeByte(int v) throws IOException {
getCodec().writeFByte(v);
}
@Override
public void writeShort(int v) throws IOException {
getCodec().writeFShort((short) v);
}
@Override
public void writeChar(int v) throws IOException {
getCodec().writeFChar((char) v);
}
@Override
public void writeInt(int v) throws IOException {
getCodec().writeFInt(v);
}
@Override
public void writeLong(long v) throws IOException {
getCodec().writeFLong(v);
}
@Override
public void writeFloat(float v) throws IOException {
getCodec().writeFFloat(v);
}
@Override
public void writeDouble(double v) throws IOException {
getCodec().writeFDouble(v);
}
@Override
public void writeBytes(String s) throws IOException {
byte[] bytes = s.getBytes();
getCodec().writePrimitiveArray(bytes, 0, bytes.length);
}
@Override
public void writeChars(String s) throws IOException {
char[] chars = s.toCharArray();
getCodec().writePrimitiveArray(chars, 0, chars.length);
}
@Override
public void writeUTF(String s) throws IOException {
getCodec().writeStringUTF(s);
}
//
// .. end interface impl
/////////////////////////////////////////////////////
public void writeObject(Object obj, Class... possibles) throws IOException {
if ( isCrossPlatform ) {
writeObjectInternal(obj, null); // not supported cross platform
return;
}
if ( possibles != null && possibles.length > 1 ) {
for (int i = 0; i < possibles.length; i++) {
Class possible = possibles[i];
getCodec().registerClass(possible);
}
}
writeObjectInternal(obj, null, possibles);
}
//avoid creation of dummy ref
protected FSTClazzInfo.FSTFieldInfo getCachedFI( Class... possibles ) {
if ( refs == null ) {
refs = refsLocal.get();
}
if ( curDepth >= refs.length ) {
return new FSTClazzInfo.FSTFieldInfo(possibles, null, true);
} else {
FSTClazzInfo.FSTFieldInfo inf = refs[curDepth];
if ( inf == null ) {
inf = new FSTClazzInfo.FSTFieldInfo(possibles, null, true);
refs[curDepth] = inf;
return inf;
}
inf.setPossibleClasses(possibles);
return inf;
}
}
/**
*
* @param obj
* @param ci
* @param possibles
* @return last FSTClazzInfo if class is plain reusable (not replaceable, needs compatible mode)
* @throws IOException
*/
public FSTClazzInfo writeObjectInternal(Object obj, FSTClazzInfo ci, Class... possibles) throws IOException {
FSTClazzInfo.FSTFieldInfo info = getCachedFI(possibles);
curDepth++;
FSTClazzInfo fstClazzInfo = writeObjectWithContext(info, obj, ci);
curDepth--;
if ( fstClazzInfo == null )
return null;
return fstClazzInfo.useCompatibleMode() ? null:fstClazzInfo;
}
public FSTSerialisationListener getListener() {
return listener;
}
/**
* note this might slow down serialization significantly
* * @param listener
*/
public void setListener(FSTSerialisationListener listener) {
this.listener = listener;
}
/**
* hook for debugging profiling. register a FSTSerialisationListener to use
* @param obj
* @param streamPosition
*/
protected void objectWillBeWritten( Object obj, int streamPosition ) {
if (listener != null) {
listener.objectWillBeWritten(obj, streamPosition);
}
}
/**
* hook for debugging profiling. empty impl, you need to subclass to make use of this hook
* @param obj
* @param oldStreamPosition
* @param streamPosition
*/
protected void objectHasBeenWritten( Object obj, int oldStreamPosition, int streamPosition ) {
if (listener != null) {
listener.objectHasBeenWritten(obj, oldStreamPosition, streamPosition);
}
}
protected FSTClazzInfo writeObjectWithContext(FSTClazzInfo.FSTFieldInfo referencee, Object toWrite) throws IOException {
return writeObjectWithContext(referencee,toWrite,null);
}
protected int tmp[] = {0};
// splitting this slows down ...
protected FSTClazzInfo writeObjectWithContext(FSTClazzInfo.FSTFieldInfo referencee, Object toWrite, FSTClazzInfo ci) throws IOException {
int startPosition = 0;
try {
if ( toWrite == null ) {
getCodec().writeTag(NULL, null, 0, toWrite, this);
return null;
}
startPosition = getCodec().getWritten();
objectWillBeWritten(toWrite, startPosition);
final Class clazz = toWrite.getClass();
if ( clazz == String.class ) {
String[] oneOf = referencee.getOneOf();
if ( oneOf != null ) {
for (int i = 0; i < oneOf.length; i++) {
String s = oneOf[i];
if ( s.equals(toWrite) ) {
getCodec().writeTag(ONE_OF, oneOf, i, toWrite, this);
getCodec().writeFByte(i);
return null;
}
}
}
// shortpath
if (! dontShare && writeHandleIfApplicable(toWrite, stringInfo))
return stringInfo;
getCodec().writeTag(STRING, toWrite, 0, toWrite, this);
getCodec().writeStringUTF((String) toWrite);
return null;
} else if ( clazz == Integer.class ) {
getCodec().writeTag(BIG_INT, null, 0, toWrite, this);
getCodec().writeFInt(((Integer) toWrite).intValue());
return null;
} else if ( clazz == Long.class ) {
getCodec().writeTag(BIG_LONG, null, 0, toWrite, this);
getCodec().writeFLong(((Long) toWrite).longValue());
return null;
} else if ( clazz == Boolean.class ) {
getCodec().writeTag(((Boolean) toWrite).booleanValue() ? BIG_BOOLEAN_TRUE : BIG_BOOLEAN_FALSE, null, 0, toWrite, this); return null;
} else if ( (referencee.getType() != null && referencee.getType().isEnum()) || toWrite instanceof Enum ) {
return writeEnum(referencee, toWrite);
}
FSTClazzInfo serializationInfo = ci == null ? getFstClazzInfo(referencee, clazz) : ci;
// check for identical / equal objects
FSTObjectSerializer ser = serializationInfo.getSer();
if ( ! dontShare && ! referencee.isFlat() && ! serializationInfo.isFlat() && ( ser == null || !ser.alwaysCopy() ) ) {
if (writeHandleIfApplicable(toWrite, serializationInfo))
return serializationInfo;
}
if (clazz.isArray()) {
if (getCodec().writeTag(ARRAY, toWrite, 0, toWrite, this))
return serializationInfo; // some codecs handle primitive arrays like an primitive type
writeArray(referencee, toWrite);
getCodec().writeArrayEnd();
} else if ( ser == null ) {
// default write object wihtout custom serializer
// handle write replace
//if ( ! dontShare ) GIT ISSUE 80
FSTClazzInfo originalInfo = serializationInfo;
{
if ( serializationInfo.getWriteReplaceMethod() != null ) {
Object replaced = null;
try {
replaced = serializationInfo.getWriteReplaceMethod().invoke(toWrite);
} catch (Exception e) {
FSTUtil.rethrow(e);
}
if ( replaced != toWrite ) {
toWrite = replaced;
serializationInfo = getClassInfoRegistry().getCLInfo(toWrite.getClass(), conf);
// fixme: update object map ?
}
}
// clazz uses some JDK special stuff (frequently slow)
if ( serializationInfo.useCompatibleMode() && ! serializationInfo.isExternalizable() ) {
writeObjectCompatible(referencee, toWrite, serializationInfo);
return originalInfo;
}
}
if (! writeObjectHeader(serializationInfo, referencee, toWrite) ) { // skip in case codec can write object as primitive
ser = serializationInfo.getSer();
if ( ser == null ) {
defaultWriteObject(toWrite, serializationInfo);
if ( serializationInfo.isExternalizable() )
getCodec().externalEnd(serializationInfo);
} else {
// handle edge case: there is a serializer registered for replaced class
// copied from below :(
int pos = getCodec().getWritten();
// write object depending on type (custom, externalizable, serializable/java, default)
ser.writeObject(this, toWrite, serializationInfo, referencee, pos);
getCodec().externalEnd(serializationInfo);
}
}
return originalInfo;
} else { // object has custom serializer
// Object header (nothing written till here)
if (! writeObjectHeader(serializationInfo, referencee, toWrite) ) { // skip in case code can write object as primitive
int pos = getCodec().getWritten();
// write object depending on type (custom, externalizable, serializable/java, default)
ser.writeObject(this, toWrite, serializationInfo, referencee, pos);
getCodec().externalEnd(serializationInfo);
}
}
return serializationInfo;
} finally {
objectHasBeenWritten(toWrite, startPosition, getCodec().getWritten());
}
}
protected FSTClazzInfo writeEnum(FSTClazzInfo.FSTFieldInfo referencee, Object toWrite) throws IOException {
if ( ! getCodec().writeTag(ENUM, toWrite, 0, toWrite, this) ) {
boolean isEnumClass = toWrite.getClass().isEnum();
if (!isEnumClass) {
// anonymous enum subclass
Class c = toWrite.getClass();
c = toWrite.getClass().getSuperclass();
if (c == null) {
throw new RuntimeException("Can't handle this enum: " + toWrite.getClass());
}
getCodec().writeClass(c);
} else {
FSTClazzInfo fstClazzInfo = getFstClazzInfo(referencee, toWrite.getClass());
getCodec().writeClass(fstClazzInfo);
getCodec().writeFInt(((Enum) toWrite).ordinal());
return fstClazzInfo;
}
getCodec().writeFInt(((Enum) toWrite).ordinal());
}
return null;
}
protected boolean writeHandleIfApplicable(Object toWrite, FSTClazzInfo serializationInfo) throws IOException {
int writePos = getCodec().getWritten();
int handle = objects.registerObjectForWrite(toWrite, writePos, serializationInfo, tmp);
// determine class header
if ( handle >= 0 ) {
final boolean isIdentical = tmp[0] == 0; //objects.getReadRegisteredObject(handle) == toWrite;
if ( isIdentical ) {
// System.out.println("POK writeHandle"+handle+" "+toWrite.getClass().getName());
if ( ! getCodec().writeTag(HANDLE, null, handle, toWrite, this) )
getCodec().writeFInt(handle);
return true;
}
}
return false;
}
/**
* if class is same as last referenced, returned cached clzinfo, else do a lookup
*/
protected FSTClazzInfo getFstClazzInfo(FSTClazzInfo.FSTFieldInfo referencee, Class clazz) {
FSTClazzInfo serializationInfo = null;
FSTClazzInfo lastInfo = referencee.lastInfo;
if ( lastInfo != null && lastInfo.getClazz() == clazz && lastInfo.conf == conf ) {
serializationInfo = lastInfo;
} else {
serializationInfo = getClassInfoRegistry().getCLInfo(clazz, conf);
referencee.lastInfo = serializationInfo;
}
return serializationInfo;
}
public void defaultWriteObject(Object toWrite, FSTClazzInfo serializationInfo) throws IOException {
if ( serializationInfo.isExternalizable() ) {
getCodec().ensureFree(writeExternalWriteAhead);
((Externalizable) toWrite).writeExternal(this);
} else {
FSTClazzInfo.FSTFieldInfo[] fieldInfo = serializationInfo.getFieldInfo();
writeObjectFields(toWrite, serializationInfo, fieldInfo, 0, 0);
}
}
protected void writeObjectCompatible(FSTClazzInfo.FSTFieldInfo referencee, Object toWrite, FSTClazzInfo serializationInfo) throws IOException {
// Object header (nothing written till here)
writeObjectHeader(serializationInfo, referencee, toWrite);
Class cl = serializationInfo.getClazz();
writeObjectCompatibleRecursive(referencee,toWrite,serializationInfo,cl);
}
protected void writeObjectCompatibleRecursive(FSTClazzInfo.FSTFieldInfo referencee, Object toWrite, FSTClazzInfo serializationInfo, Class cl) throws IOException {
FSTClazzInfo.FSTCompatibilityInfo fstCompatibilityInfo = serializationInfo.getCompInfo().get(cl);
if ( ! Serializable.class.isAssignableFrom(cl) ) {
return; // ok here, as compatible mode will never be triggered for "forceSerializable"
}
writeObjectCompatibleRecursive(referencee,toWrite,serializationInfo,cl.getSuperclass());
if ( fstCompatibilityInfo != null && fstCompatibilityInfo.getWriteMethod() != null ) {
try {
writeByte(55); // tag this is written with writeMethod
fstCompatibilityInfo.getWriteMethod().invoke(toWrite,getObjectOutputStream(cl, serializationInfo,referencee,toWrite));
} catch (Exception e) {
if ( e instanceof InvocationTargetException == true && ((InvocationTargetException) e).getTargetException() != null ) {
FSTUtil.rethrow(((InvocationTargetException) e).getTargetException());
}
FSTUtil.rethrow(e);
}
} else {
if ( fstCompatibilityInfo != null ) {
writeByte(66); // tag this is written from here no writeMethod
writeObjectFields(toWrite, serializationInfo, fstCompatibilityInfo.getFieldArray(), 0, 0 );
}
}
}
protected void writeObjectFields(Object toWrite, FSTClazzInfo serializationInfo, FSTClazzInfo.FSTFieldInfo[] fieldInfo, int startIndex, int version) throws IOException {
try {
int booleanMask = 0;
int boolcount = 0;
final int length = fieldInfo.length;
int j = startIndex;
if ( ! getCodec().isWritingAttributes() ) { // pack bools into bits in case it's not a chatty codec
for (;; j++) {
if ( j == length || fieldInfo[j].getVersion() != version ) {
if ( boolcount > 0 ) {
getCodec().writeFByte(booleanMask << (8 - boolcount));
}
break;
}
final FSTClazzInfo.FSTFieldInfo subInfo = fieldInfo[j];
if ( subInfo.getIntegralType() != subInfo.BOOL ) {
if ( boolcount > 0 ) {
getCodec().writeFByte(booleanMask << (8 - boolcount));
}
break;
} else {
if ( boolcount == 8 ) {
getCodec().writeFByte(booleanMask << (8 - boolcount));
boolcount = 0; booleanMask = 0;
}
boolean booleanValue = subInfo.getBooleanValue( toWrite);
booleanMask = booleanMask<<1;
booleanMask = (booleanMask|(booleanValue?1:0));
boolcount++;
}
}
}
for (int i = j; i < length; i++)
{
final FSTClazzInfo.FSTFieldInfo subInfo = fieldInfo[i];
if (subInfo.getVersion() != version ) {
getCodec().writeVersionTag(subInfo.getVersion());
writeObjectFields(toWrite, serializationInfo, fieldInfo, i, subInfo.getVersion());
return;
}
if ( getCodec().writeAttributeName(subInfo, toWrite) ) {
continue;
}
if ( subInfo.isPrimitive() ) {
// speed safe
int integralType = subInfo.getIntegralType();
switch (integralType) {
case FSTClazzInfo.FSTFieldInfo.BOOL:
getCodec().writeFByte(subInfo.getBooleanValue(toWrite) ? 1 : 0); break;
case FSTClazzInfo.FSTFieldInfo.BYTE:
getCodec().writeFByte(subInfo.getByteValue(toWrite)); break;
case FSTClazzInfo.FSTFieldInfo.CHAR:
getCodec().writeFChar((char) subInfo.getCharValue(toWrite)); break;
case FSTClazzInfo.FSTFieldInfo.SHORT:
getCodec().writeFShort((short) subInfo.getShortValue(toWrite)); break;
case FSTClazzInfo.FSTFieldInfo.INT:
getCodec().writeFInt(subInfo.getIntValue(toWrite)); break;
case FSTClazzInfo.FSTFieldInfo.LONG:
getCodec().writeFLong(subInfo.getLongValue(toWrite)); break;
case FSTClazzInfo.FSTFieldInfo.FLOAT:
getCodec().writeFFloat(subInfo.getFloatValue(toWrite)); break;
case FSTClazzInfo.FSTFieldInfo.DOUBLE:
getCodec().writeFDouble(subInfo.getDoubleValue(toWrite)); break;
}
} else if (subInfo.isConditional())
{
final int conditional = getCodec().getWritten();
getCodec().skip(4);
// object
Object subObject = subInfo.getObjectValue(toWrite);
if ( subObject == null ) {
getCodec().writeTag(NULL, null, 0, toWrite, this);
} else {
writeObjectWithContext(subInfo, subObject);
}
int v = getCodec().getWritten();
getCodec().writeInt32At(conditional, v);
} else {
// object
Object subObject = subInfo.getObjectValue(toWrite);
if ( subObject == null ) {
getCodec().writeTag(NULL, null, 0, toWrite, this);
} else {
writeObjectWithContext(subInfo, subObject);
}
}
}
getCodec().writeVersionTag((byte) 0);
getCodec().writeFieldsEnd(serializationInfo);
} catch (IllegalAccessException ex) {
FSTUtil.rethrow(ex);
}
}
// write identical to other version, but take field values from hashmap (because of annoying putField/getField feature)
protected void writeCompatibleObjectFields(Object toWrite, Map fields, FSTClazzInfo.FSTFieldInfo[] fieldInfo) throws IOException {
int booleanMask = 0;
int boolcount = 0;
for (int i = 0; i < fieldInfo.length; i++) {
try {
FSTClazzInfo.FSTFieldInfo subInfo = fieldInfo[i];
boolean isarr = subInfo.isArray();
Class subInfType = subInfo.getType();
if ( subInfType != boolean.class || isarr) {
if ( boolcount > 0 ) {
getCodec().writeFByte(booleanMask << (8 - boolcount));
boolcount = 0; booleanMask = 0;
}
}
if ( subInfo.isIntegral() && !isarr) {
if ( subInfType == boolean.class ) {
if ( boolcount == 8 ) {
getCodec().writeFByte(booleanMask << (8 - boolcount));
boolcount = 0; booleanMask = 0;
}
boolean booleanValue = ((Boolean)fields.get(subInfo.getName())).booleanValue();
booleanMask = booleanMask<<1;
booleanMask = (booleanMask|(booleanValue?1:0));
boolcount++;
} else
if ( subInfType == int.class ) {
getCodec().writeFInt(((Number) fields.get(subInfo.getName())).intValue());
} else
if ( subInfType == long.class ) {
getCodec().writeFLong(((Number) fields.get(subInfo.getName())).longValue());
} else
if ( subInfType == byte.class ) {
getCodec().writeFByte(((Number) fields.get(subInfo.getName())).byteValue());
} else
if ( subInfType == char.class ) {
getCodec().writeFChar((char) ((Number) fields.get(subInfo.getName())).intValue());
} else
if ( subInfType == short.class ) {
getCodec().writeFShort(((Number) fields.get(subInfo.getName())).shortValue());
} else
if ( subInfType == float.class ) {
getCodec().writeFFloat(((Number) fields.get(subInfo.getName())).floatValue());
} else
if ( subInfType == double.class ) {
getCodec().writeFDouble(((Number) fields.get(subInfo.getName())).doubleValue());
}
} else {
// object
Object subObject = fields.get(subInfo.getName());
writeObjectWithContext(subInfo, subObject);
}
} catch (Exception ex) {
FSTUtil.rethrow(ex);
}
}
if ( boolcount > 0 ) {
getCodec().writeFByte(booleanMask << (8 - boolcount));
}
}
/**
*
* @param clsInfo
* @param referencee
* @param toWrite
* @return true if header already wrote object
* @throws IOException
*/
protected boolean writeObjectHeader(final FSTClazzInfo clsInfo, final FSTClazzInfo.FSTFieldInfo referencee, final Object toWrite) throws IOException {
if ( toWrite.getClass() == referencee.getType()
&& ! clsInfo.useCompatibleMode() )
{
return getCodec().writeTag(TYPED, clsInfo, 0, toWrite, this);
} else {
final Class[] possibleClasses = referencee.getPossibleClasses();
if ( possibleClasses == null ) {
if ( !getCodec().writeTag(OBJECT, clsInfo, 0, toWrite, this) ) {
getCodec().writeClass(clsInfo);
return false;
} else {
return true;
}
} else {
final int length = possibleClasses.length;
for (int j = 0; j < length; j++) {
final Class possibleClass = possibleClasses[j];
if ( possibleClass == toWrite.getClass() ) {
getCodec().writeFByte(j + 1);
return false;
}
}
if (!getCodec().writeTag(OBJECT, clsInfo, 0, toWrite, this) ) {
getCodec().writeClass(clsInfo);
return false;
} else {
return true;
}
}
}
}
// incoming array is already registered
protected void writeArray(FSTClazzInfo.FSTFieldInfo referencee, Object array) throws IOException {
if ( array == null ) {
getCodec().writeClass(Object.class);
getCodec().writeFInt(-1);
return;
}
final int len = Array.getLength(array);
Class> componentType = array.getClass().getComponentType();
getCodec().writeClass(array.getClass());
getCodec().writeFInt(len);
if ( ! componentType.isArray() ) {
if (getCodec().isPrimitiveArray(array, componentType)) {
getCodec().writePrimitiveArray(array, 0, len);
} else { // objects
Object arr[] = (Object[])array;
Class lastClz = null;
FSTClazzInfo lastInfo = null;
for ( int i = 0; i < len; i++ )
{
Object toWrite = arr[i];
if ( toWrite != null ) {
lastInfo = writeObjectWithContext(referencee, toWrite, lastClz == toWrite.getClass() ? lastInfo : null);
lastClz = toWrite.getClass();
} else
writeObjectWithContext(referencee, toWrite, null);
}
}
} else { // multidim array. FIXME shared refs to subarrays are not tested !!!
Object[] arr = (Object[])array;
FSTClazzInfo.FSTFieldInfo ref1 = new FSTClazzInfo.FSTFieldInfo(referencee.getPossibleClasses(), null, conf.getCLInfoRegistry().isIgnoreAnnotations());
for ( int i = 0; i < len; i++ ) {
Object subArr = arr[i];
boolean needsWrite = true;
if ( getCodec().isTagMultiDimSubArrays() ) {
if ( subArr == null ) {
needsWrite = !getCodec().writeTag(NULL, null, 0, null, this);
} else {
needsWrite = !getCodec().writeTag(ARRAY, subArr, 0, subArr, this);
}
}
if ( needsWrite ) {
writeArray(ref1, subArr);
getCodec().writeArrayEnd();
}
}
}
}
public void writeStringUTF(String str) throws IOException {
getCodec().writeStringUTF(str);
}
protected void resetAndClearRefs() {
getCodec().reset(null);
objects.clearForWrite(conf);
}
/**
* if out == null => automatically create/reuse a bytebuffer
*
* @param out
*/
public void resetForReUse( OutputStream out ) {
if ( closed )
throw new RuntimeException("Can't reuse closed stream");
getCodec().reset(null);
if ( out != null ) {
getCodec().setOutstream(out);
}
objects.clearForWrite(conf);
}
/**
* reset keeping the last used byte[] buffer
*/
public void resetForReUse() {
resetForReUse((byte[])null);
}
public void resetForReUse( byte[] out ) {
if ( closed )
throw new RuntimeException("Can't reuse closed stream");
getCodec().reset(out);
objects.clearForWrite(conf);
}
public FSTClazzInfoRegistry getClassInfoRegistry() {
return conf.getCLInfoRegistry();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////// java serialization compatibility ////////////////////////////////////////////
/**
*
* @param cl - class or superclass of currently serialized obj, write declared fields of this class only
* @param clinfo
* @param referencee
* @param toWrite
* @return
* @throws IOException
*/
public ObjectOutputStream getObjectOutputStream(final Class cl, final FSTClazzInfo clinfo, final FSTClazzInfo.FSTFieldInfo referencee, final Object toWrite) throws IOException {
ObjectOutputStream out = new ObjectOutputStream() {
@Override
public void useProtocolVersion(int version) throws IOException {
}
@Override
protected void writeObjectOverride(Object obj) throws IOException {
getCodec().writeFByte( SPECIAL_COMPATIBILITY_OBJECT_TAG );
FSTObjectOutput.this.writeObjectInternal(obj, null, referencee.getPossibleClasses());
}
@Override
public void writeUnshared(Object obj) throws IOException {
writeObjectOverride(obj); // fixme
}
@Override
public void defaultWriteObject() throws IOException {
writeByte(99); // tag defaultwriteObject
FSTClazzInfo newInfo = clinfo;
Object replObj = toWrite;
if ( newInfo.getWriteReplaceMethod() != null ) {
LOGGER.log(FSTLogger.Level.WARN, "WRITE REPLACE NOT FULLY SUPPORTED", null);
try {
Object replaced = newInfo.getWriteReplaceMethod().invoke(replObj);
if ( replaced != null && replaced != toWrite ) {
replObj = replaced;
newInfo = getClassInfoRegistry().getCLInfo(replObj.getClass(), conf);
}
} catch (Exception e) {
FSTUtil.rethrow(e);
}
}
FSTObjectOutput.this.writeObjectFields(replObj, newInfo, newInfo.getCompInfo().get(cl).getFieldArray(),0,0);
}
PutField pf;
HashMap fields = new HashMap(); // fixme: init lazy
@Override
public PutField putFields() throws IOException {
if ( pf == null ) {
pf = new PutField() {
@Override
public void put(String name, boolean val) {
fields.put(name,val);
}
@Override
public void put(String name, byte val) {
fields.put(name,val);
}
@Override
public void put(String name, char val) {
fields.put(name,val);
}
@Override
public void put(String name, short val) {
fields.put(name,val);
}
@Override
public void put(String name, int val) {
fields.put(name,val);
}
@Override
public void put(String name, long val) {
fields.put(name,val);
}
@Override
public void put(String name, float val) {
fields.put(name,val);
}
@Override
public void put(String name, double val) {
fields.put(name,val);
}
@Override
public void put(String name, Object val) {
fields.put(name,val);
}
@Override
public void write(ObjectOutput out) throws IOException {
throw new IOException("cannot act compatible, use a custom serializer for this class");
}
};
}
return pf;
}
@Override
public void writeFields() throws IOException {
writeByte(77); // tag writeFields
// FSTClazzInfo.FSTCompatibilityInfo fstCompatibilityInfo = clinfo.compInfo.get(cl);
// if ( fstCompatibilityInfo.isAsymmetric() ) {
// FSTObjectOutput.this.writeCompatibleObjectFields(toWrite, fields, fstCompatibilityInfo.getFieldArray());
// } else {
FSTObjectOutput.this.writeObjectInternal(fields, null, HashMap.class);
// }
}
@Override
public void reset() throws IOException {
throw new IOException("cannot act compatible, use a custom serializer for this class");
}
@Override
public void write(int val) throws IOException {
getCodec().writeFByte(val);
}
@Override
public void write(byte[] buf) throws IOException {
FSTObjectOutput.this.write(buf);
}
@Override
public void write(byte[] buf, int off, int len) throws IOException {
FSTObjectOutput.this.write(buf, off, len);
}
@Override
public void flush() throws IOException {
FSTObjectOutput.this.flush();
}
@Override
public void close() throws IOException {
}
@Override
public void writeBoolean(boolean val) throws IOException {
FSTObjectOutput.this.writeBoolean(val);
}
@Override
public void writeByte(int val) throws IOException {
getCodec().writeFByte(val);
}
@Override
public void writeShort(int val) throws IOException {
getCodec().writeFShort((short) val);
}
@Override
public void writeChar(int val) throws IOException {
getCodec().writeFChar((char) val);
}
@Override
public void writeInt(int val) throws IOException {
getCodec().writeFInt(val);
}
@Override
public void writeLong(long val) throws IOException {
getCodec().writeFLong(val);
}
@Override
public void writeFloat(float val) throws IOException {
getCodec().writeFFloat(val);
}
@Override
public void writeDouble(double val) throws IOException {
getCodec().writeFDouble(val);
}
@Override
public void writeBytes(String str) throws IOException {
FSTObjectOutput.this.writeBytes(str);
}
@Override
public void writeChars(String str) throws IOException {
FSTObjectOutput.this.writeChars(str);
}
@Override
public void writeUTF(String str) throws IOException {
getCodec().writeStringUTF(str);
}
};
return out;
}
public FSTObjectRegistry getObjectMap() {
return objects;
}
/**
* @return the written buffer reference. use getWritten() to obtain the length of written bytes. WARNING:
* if more than one objects have been written, an implicit flush is triggered, so the buffer only contains
* the last written object. getWritten() then has a larger size than the buffer length.
* only usable if one single object is written to the stream (e.g. messaging)
*
* note: in case of non-standard underlyings (e.g. serializing to direct offheap or DirectBuffer, this method
* might cause creation of a byte array and a copy.
*/
public byte[] getBuffer() {
return getCodec().getBuffer();
}
/**
* @return a copy of written bytes.
* Warning: if the stream has been flushed, this will fail with an exception.
* a flush is triggered after each 1st level writeObject.
*
* note: in case of non-stream based serialization (directbuffer, offheap mem) getBuffer will return a copy anyways.
*/
public byte[] getCopyOfWrittenBuffer() {
if ( ! getCodec().isByteArrayBased() ) {
return getBuffer();
}
byte res [] = new byte[getCodec().getWritten()];
byte[] buffer = getBuffer();
System.arraycopy(buffer,0,res,0, getCodec().getWritten());
return res;
}
public FSTConfiguration getConf() {
return conf;
}
/**
* @return the number of bytes written to this stream. This also is the number of
* valid bytes in the buffer one obtains from the various getBuffer, getCopyOfBuffer methods.
* Warning: if the stream has been flushed (done after each 1st level object write),
* the buffer will be smaller than the value given here or contain invalid bytes.
*/
public int getWritten() {
return getCodec().getWritten();
}
public void writeClassTag(Class aClass) {
getCodec().writeClass(aClass);
}
public FSTEncoder getCodec() {
return codec;
}
protected void setCodec(FSTEncoder codec) {
this.codec = codec;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy