org.nustaq.serialization.FSTConfiguration 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 com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.io.IOContext;
import com.fasterxml.jackson.core.json.UTF8JsonGenerator;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import org.nustaq.offheap.bytez.onheap.HeapBytez;
import org.nustaq.serialization.coders.*;
import org.nustaq.serialization.util.DefaultFSTInt2ObjectMapFactory;
import org.nustaq.serialization.util.FSTInputStream;
import org.nustaq.serialization.util.FSTInt2ObjectMapFactory;
import org.nustaq.serialization.util.FSTUtil;
import org.nustaq.serialization.serializers.*;
import org.objenesis.Objenesis;
import org.objenesis.ObjenesisStd;
import java.io.*;
import java.lang.ref.SoftReference;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Created with IntelliJ IDEA.
* User: ruedi
* Date: 18.11.12
* Time: 20:41
*
* Holds a serialization configuration/metadata.
* Reuse this class !!! construction is very expensive. (just keep static instances around or use thread locals)
*
*/
public class FSTConfiguration {
static enum ConfType {
DEFAULT, UNSAFE, MINBIN, JSON, JSONPRETTY
}
/**
* if all attempts fail to find a class this guy is asked.
* Can be used in case e.g. dynamic classes need get generated.
*/
public interface LastResortClassResolver {
public Class getClass( String clName );
}
/**
* Security: disallow packages/classes upon deserialization
*/
public interface ClassSecurityVerifier {
/**
* return false if your application does not allow to deserialize objects of type
* cl. This can be implemented using whitelisting/blacklisting whole packages, subpackages, single classes
*
* Note: this also disallows serialization of forbidden classes. For assymetric use cases register a custom
* serializer in order to prevent reading/writing of certain classes.
*
* @param cl - the class being serialized/deserialized
* @return
*/
boolean allowClassDeserialization( Class cl );
}
StreamCoderFactory streamCoderFactory = new FSTDefaultStreamCoderFactory(this);
String name;
ClassSecurityVerifier verifier;
ConfType type = ConfType.DEFAULT;
FSTClazzInfoRegistry serializationInfoRegistry = new FSTClazzInfoRegistry();
HashMap> cachedObjects = new HashMap>(97);
FSTInt2ObjectMapFactory intToObjectMapFactory = new DefaultFSTInt2ObjectMapFactory();
FSTClazzNameRegistry classRegistry = new FSTClazzNameRegistry(null);
boolean preferSpeed = false; // hint to prefer speed over size in case, currently ignored.
boolean shareReferences = true;
volatile ClassLoader classLoader = getClass().getClassLoader();
boolean forceSerializable = false; // serialize objects which are not instanceof serializable using default serialization scheme.
FSTClassInstantiator instantiator = new FSTDefaultClassInstantiator();
Object coderSpecific;
LastResortClassResolver lastResortResolver;
boolean forceClzInit = false; // always execute default fields init, even if no transients
FSTJsonFieldNames jsonFieldNames = new FSTJsonFieldNames("typ", "obj", "styp", "seq", "enum", "val", "ref");
public FSTJsonFieldNames getJsonFieldNames() {
return jsonFieldNames;
}
public void setJsonFieldNames(final FSTJsonFieldNames fieldNames) {
this.jsonFieldNames = fieldNames;
}
public ClassSecurityVerifier getVerifier() {
return verifier;
}
public FSTConfiguration setVerifier(ClassSecurityVerifier verifier) {
this.verifier = verifier;
return this;
}
public FSTInt2ObjectMapFactory getIntToObjectMapFactory() {
return intToObjectMapFactory;
}
// cache fieldinfo. This can be shared with derived FSTConfigurations in order to reduce footprint
static class FieldKey {
Class clazz;
String fieldName;
public FieldKey(Class clazz, String fieldName) {
this.clazz = clazz;
this.fieldName = fieldName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FieldKey fieldKey = (FieldKey) o;
if (!clazz.equals(fieldKey.clazz)) return false;
return fieldName.equals(fieldKey.fieldName);
}
@Override
public int hashCode() {
int result = clazz.hashCode();
result = 31 * result + fieldName.hashCode();
return result;
}
}
ConcurrentHashMap fieldInfoCache;
/**
* debug helper
* @return
*/
public String getName() {
return name;
}
/**
* debug helper
* @param name
*/
public void setName(String name) {
this.name = name;
}
/////////////////////////////////////
// cross platform stuff only
int cpAttrIdCount = 0;
// contains symbol => full qualified name
private HashMap minbinNames = new HashMap<>();
// may contain symbol => cached binary output
private HashMap minBinNamesBytez = new HashMap<>();
// contains full qualified name => symbol
private HashMap minbinNamesReverse = new HashMap<>();
private boolean crossPlatform = false; // if true do not support writeObject/readObject etc.
// non-final for testing
public static boolean isAndroid = System.getProperty("java.runtime.name", "no").toLowerCase().contains("android");
// end cross platform stuff only
/////////////////////////////////////
static AtomicBoolean conflock = new AtomicBoolean(false);
static FSTConfiguration singleton;
public static FSTConfiguration getDefaultConfiguration() {
do { } while ( !conflock.compareAndSet(false, true) );
try {
if (singleton == null)
singleton = createDefaultConfiguration();
return singleton;
} finally {
conflock.set(false);
}
}
/**
* Warning: MinBin contains full metainformation (fieldnames,..), so its way slower than the other configs.
* It should be used in case of cross language (e.g. java - javascript) serialization only.
* Additionally you can read MinBin serialized streams without access to original classes.
*
* See MBPrinter on an example on how to read a MinBin stream without having access to
* original classes. Useful for cross language serialization/long term archiving.
*
* Warning: MinBin serialization ('binary JSon') is much slower than the other
* serialization configurations.
*
* @return a configuration to encode MinBin format.
*/
public static FSTConfiguration createMinBinConfiguration() {
return createMinBinConfiguration(null);
}
protected static FSTConfiguration createMinBinConfiguration(ConcurrentHashMap shared) {
final FSTConfiguration res = createDefaultConfiguration(shared);
res.setCrossPlatform(true);
res.type = ConfType.MINBIN;
res.setStreamCoderFactory(new MinBinStreamCoderFactory(res));
// override some serializers
FSTSerializerRegistry reg = res.serializationInfoRegistry.getSerializerRegistry();
reg.putSerializer(EnumSet.class, new FSTCPEnumSetSerializer(), true);
reg.putSerializer(Throwable.class, new FSTCPThrowableSerializer(), true);
// for crossplatform fallback does not work => register default serializers for collections and subclasses
reg.putSerializer(AbstractCollection.class, new FSTCollectionSerializer(), true); // subclass should register manually
reg.putSerializer(AbstractMap.class, new FSTMapSerializer(), true); // subclass should register manually
res.registerCrossPlatformClassMapping(new String[][]{
{"map", HashMap.class.getName()},
{"list", ArrayList.class.getName()},
{"set", HashSet.class.getName()},
{"long", Long.class.getName()},
{"integer", Integer.class.getName()},
{"short", Short.class.getName()},
{"byte", Byte.class.getName()},
{"char", Character.class.getName()},
{"float", Float.class.getName()},
{"double", Double.class.getName()},
{"date", Date.class.getName()},
{"enumSet", "java.util.RegularEnumSet"},
{"array", "[Ljava.lang.Object;"},
{"String[]", "[Ljava.lang.String;"},
{"Double[]", "[Ljava.lang.Double;"},
{"Float[]", "[Ljava.lang.Float;"},
{"double[]", "[D"},
{"float[]", "[F"}
});
res.registerSerializer( BigDecimal.class, new FSTJSonSerializers.BigDecSerializer(), true );
return res;
}
/**
* @return a configuration encoding to JSon without support for reference sharing (=> NO cyclic object graphs)
*/
public static FSTConfiguration createJsonNoRefConfiguration() {
return createJsonConfiguration(false, false);
}
/**
* create a json conf with given attributes. Note that shared refs = true for jason might be not as stable as for binary encodings
* as fst relies on stream positions to identify objects within a given input, so any inbetween formatting will break proper reference
* resolution.
*
* WARNING: use of sharedrefs = true is Deprecated as its flakey
*
* @param prettyPrint
* @param shareReferences
* @return
*/
public static FSTConfiguration createJsonConfiguration(boolean prettyPrint, boolean shareReferences ) {
if ( shareReferences && prettyPrint ) {
throw new RuntimeException("unsupported flag combination");
}
return createJsonConfiguration(prettyPrint,shareReferences,null);
}
public static FSTConfiguration createJsonConfiguration() {
return createJsonConfiguration(false,false);
}
protected static FSTConfiguration createJsonConfiguration(boolean prettyPrint, boolean shareReferences, ConcurrentHashMap shared ) {
if ( prettyPrint && shareReferences ) {
throw new RuntimeException("cannot use prettyPrint with shared refs to 'true'. Set shareRefs to false.");
}
return constructJsonConf(prettyPrint, shareReferences, shared);
}
public static class JacksonAccessWorkaround extends UTF8JsonGenerator {
public JacksonAccessWorkaround(IOContext ctxt, int features, ObjectCodec codec, OutputStream out) {
super(ctxt, features, codec, out);
}
public JacksonAccessWorkaround(IOContext ctxt, int features, ObjectCodec codec, OutputStream out, byte[] outputBuffer, int outputOffset, boolean bufferRecyclable) {
super(ctxt, features, codec, out, outputBuffer, outputOffset, bufferRecyclable);
}
public int getOutputTail() {
return _outputTail;
}
}
private static FSTConfiguration constructJsonConf(boolean prettyPrint, boolean shareReferences, ConcurrentHashMap shared) {
final FSTConfiguration conf = createMinBinConfiguration(shared);
FSTSerializerRegistry reg = conf.serializationInfoRegistry.getSerializerRegistry();
reg.putSerializer(FSTJSonUnmodifiableCollectionSerializer.UNMODIFIABLE_COLLECTION_CLASS, new FSTJSonUnmodifiableCollectionSerializer(), true);
reg.putSerializer(FSTJSonUnmodifiableMapSerializer.UNMODIFIABLE_MAP_CLASS, new FSTJSonUnmodifiableMapSerializer(), false);
conf.type = prettyPrint ? ConfType.JSONPRETTY : ConfType.JSON;
JsonFactory fac;
if ( prettyPrint ) {
fac = new JsonFactory() {
protected JsonGenerator _createUTF8Generator(OutputStream out, IOContext ctxt) throws IOException {
UTF8JsonGenerator gen = new JacksonAccessWorkaround(ctxt,
_generatorFeatures, _objectCodec, out);
if (_characterEscapes != null) {
gen.setCharacterEscapes(_characterEscapes);
}
SerializableString rootSep = _rootValueSeparator;
if (rootSep != DefaultPrettyPrinter.DEFAULT_ROOT_VALUE_SEPARATOR) {
gen.setRootValueSeparator(rootSep);
}
return gen;
}
@Override
public JsonGenerator createGenerator(OutputStream out) throws IOException {
return super.createGenerator(out).setPrettyPrinter(new DefaultPrettyPrinter());
}
}
.disable(JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM)
.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
} else {
fac = new JsonFactory() {
protected JsonGenerator _createUTF8Generator(OutputStream out, IOContext ctxt) throws IOException {
UTF8JsonGenerator gen = new JacksonAccessWorkaround(ctxt,
_generatorFeatures, _objectCodec, out);
if (_characterEscapes != null) {
gen.setCharacterEscapes(_characterEscapes);
}
SerializableString rootSep = _rootValueSeparator;
if (rootSep != DefaultPrettyPrinter.DEFAULT_ROOT_VALUE_SEPARATOR) {
gen.setRootValueSeparator(rootSep);
}
return gen;
}
};
fac.disable(JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM)
.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
}
conf.setCoderSpecific(fac);
conf.setStreamCoderFactory(new JSonStreamCoderFactory(conf));
conf.setShareReferences(shareReferences);
conf.setLastResortResolver(new LastResortClassResolver() {
@Override
public Class getClass(String clName) {
return Unknown.class;
}
});
return conf;
}
/**
* debug only, very slow (creates config with each call). Creates new conf so custom serializers are ignored.
*
* @param o
*/
public static void prettyPrintJson(Object o) {
FSTConfiguration conf = constructJsonConf(true, true, null);
System.out.println(conf.asJsonString(o));
}
/**
*
* Configuration for use on Android. Its binary compatible with getDefaultConfiguration().
* So one can write on server with getDefaultConf and read on mobile client with getAndroidConf().
*
* @return
*/
public static FSTConfiguration createAndroidDefaultConfiguration() {
return createAndroidDefaultConfiguration(null);
}
protected static FSTConfiguration createAndroidDefaultConfiguration(ConcurrentHashMap shared) {
final Objenesis genesis = new ObjenesisStd();
FSTConfiguration conf = new FSTConfiguration(shared) {
@Override
public FSTClassInstantiator getInstantiator(Class clazz) {
return new FSTObjenesisInstantiator(genesis,clazz);
}
};
initDefaultFstConfigurationInternal(conf);
if ( isAndroid ) {
try {
conf.registerSerializer(Class.forName("com.google.gson.internal.LinkedTreeMap"), new FSTMapSerializer(), true);
} catch (ClassNotFoundException e) {
//silent
}
try {
conf.registerSerializer(Class.forName("com.google.gson.internal.LinkedHashTreeMap"), new FSTMapSerializer(), true);
} catch (ClassNotFoundException e) {
//silent
}
}
return conf;
}
public static FSTConfiguration createConfiguration(ConfType ct, boolean shareRefs) {
return createConfiguration(ct,shareRefs);
}
protected static FSTConfiguration createConfiguration(ConfType ct, boolean shareRefs,ConcurrentHashMap shared ) {
FSTConfiguration res;
switch (ct) {
case DEFAULT:
res = createDefaultConfiguration(shared);
break;
case MINBIN:
res = createMinBinConfiguration(shared);
break;
case UNSAFE:
res = createUnsafeBinaryConfiguration(shared);
break;
case JSON:
res = createJsonConfiguration( false, shareRefs, shared);
break;
case JSONPRETTY:
res = createJsonConfiguration( true, shareRefs, shared);
break;
default:
throw new RuntimeException("unsupported conftype for factory method");
}
res.setShareReferences(shareRefs);
return res;
}
/**
* the standard FSTConfiguration.
* - safe (no unsafe r/w)
* - platform independent byte order
* - moderate compression
*
* note that if you are just read/write from/to byte arrays, its faster
* to use DefaultCoder.
*
* This should be used most of the time.
*
* @return
*/
public static FSTConfiguration createDefaultConfiguration() {
return createDefaultConfiguration(null);
}
protected static FSTConfiguration createDefaultConfiguration(ConcurrentHashMap shared) {
if (isAndroid) {
return createAndroidDefaultConfiguration(shared);
}
FSTConfiguration conf = new FSTConfiguration(shared);
return initDefaultFstConfigurationInternal(conf);
}
protected static FSTConfiguration initDefaultFstConfigurationInternal(FSTConfiguration conf) {
conf.registerIntToObjectMapFactory(new DefaultFSTInt2ObjectMapFactory());
conf.addDefaultClazzes();
// serializers
FSTSerializerRegistry reg = conf.getCLInfoRegistry().getSerializerRegistry();
reg.putSerializer(Class.class, new FSTClassSerializer(), false);
reg.putSerializer(String.class, new FSTStringSerializer(), false);
reg.putSerializer(Byte.class, new FSTBigNumberSerializers.FSTByteSerializer(), false);
reg.putSerializer(Character.class, new FSTBigNumberSerializers.FSTCharSerializer(), false);
reg.putSerializer(Short.class, new FSTBigNumberSerializers.FSTShortSerializer(), false);
reg.putSerializer(Float.class, new FSTBigNumberSerializers.FSTFloatSerializer(), false);
reg.putSerializer(Double.class, new FSTBigNumberSerializers.FSTDoubleSerializer(), false);
reg.putSerializer(Date.class, new FSTDateSerializer(), false);
reg.putSerializer(StringBuffer.class, new FSTStringBufferSerializer(), true);
reg.putSerializer(StringBuilder.class, new FSTStringBuilderSerializer(), true);
reg.putSerializer(EnumSet.class, new FSTEnumSetSerializer(), true);
// for most cases don't register for subclasses as in many cases we'd like to fallback to JDK implementation
// (e.g. TreeMap) in order to guarantee complete serialization
reg.putSerializer(ArrayList.class, new FSTArrayListSerializer(), false);
reg.putSerializer(Vector.class, new FSTCollectionSerializer(), true);
reg.putSerializer(LinkedList.class, new FSTCollectionSerializer(), false); // subclass should register manually
reg.putSerializer(HashSet.class, new FSTCollectionSerializer(), false); // subclass should register manually
reg.putSerializer(HashMap.class, new FSTMapSerializer(), false); // subclass should register manually
reg.putSerializer(LinkedHashMap.class, new FSTMapSerializer(), false); // subclass should register manually
reg.putSerializer(Hashtable.class, new FSTMapSerializer(), true);
reg.putSerializer(ConcurrentHashMap.class, new FSTMapSerializer(), true);
reg.putSerializer(Throwable.class, new FSTThrowableSerializer(),true);
reg.putSerializer(BitSet.class, new FSTBitSetSerializer(),true);
reg.putSerializer(Timestamp.class, new FSTTimestampSerializer(),true);
// serializers for classes failing in fst JDK emulation (e.g. Android<=>JDK)
reg.putSerializer(BigInteger.class, new FSTBigIntegerSerializer(), true);
return conf;
}
public void registerIntToObjectMapFactory(FSTInt2ObjectMapFactory intToObjectMapFactory){
if (intToObjectMapFactory != null) {
this.intToObjectMapFactory = intToObjectMapFactory;
}
}
/**
* Returns a configuration using Unsafe to read write data.
* - platform dependent byte order
* - no value compression attempts
* - makes heavy use of Unsafe, which can be dangerous in case
* of version conflicts
*
* Use only in case it makes a significant difference and you absolutely need the performance gain.
* Performance gains depend on data. There are cases where this is even slower,
* in some scenarios (many native arrays) it can be several times faster.
* see also OffHeapCoder, OnHeapCoder.
*
*/
public static FSTConfiguration createUnsafeBinaryConfiguration() {
return createUnsafeBinaryConfiguration(null);
}
protected static FSTConfiguration createUnsafeBinaryConfiguration(ConcurrentHashMap shared) {
if ( isAndroid )
throw new RuntimeException("not supported under android platform, use default configuration");
final FSTConfiguration conf = FSTConfiguration.createDefaultConfiguration(shared);
conf.type = ConfType.UNSAFE;
conf.setStreamCoderFactory(new FBinaryStreamCoderFactory(conf));
return conf;
}
/**
* register a custom serializer for a given class or the class and all of its subclasses.
* Serializers must be configured identical on read/write side and should be set before
* actually making use of the Configuration.
*
* @param clazz
* @param ser
* @param alsoForAllSubclasses
*/
public void registerSerializer(Class clazz, FSTObjectSerializer ser, boolean alsoForAllSubclasses ) {
serializationInfoRegistry.getSerializerRegistry().putSerializer(clazz, ser, alsoForAllSubclasses);
}
public boolean isForceClzInit() {
return forceClzInit;
}
public LastResortClassResolver getLastResortResolver() {
return lastResortResolver;
}
public void setLastResortResolver(LastResortClassResolver lastResortResolver) {
this.lastResortResolver = lastResortResolver;
}
/**
* always execute default fields init, even if no transients (so would get overwritten anyway)
* required for lossy codecs (kson)
*
* @param forceClzInit
* @return
*/
public FSTConfiguration setForceClzInit(boolean forceClzInit) {
this.forceClzInit = forceClzInit;
return this;
}
public FSTClassInstantiator getInstantiator(Class clazz) {
return instantiator;
}
public void setInstantiator(FSTClassInstantiator instantiator) {
this.instantiator = instantiator;
}
public T getCoderSpecific() {
return (T) coderSpecific;
}
public void setCoderSpecific(Object coderSpecific) {
this.coderSpecific = coderSpecific;
}
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* special configuration used internally for struct emulation
* @return
*/
public static FSTConfiguration createStructConfiguration() {
FSTConfiguration conf = new FSTConfiguration(null);
conf.setStructMode(true);
return conf;
}
protected FSTConfiguration(ConcurrentHashMap sharedFieldInfos) {
this.fieldInfoCache = sharedFieldInfos;
}
public StreamCoderFactory getStreamCoderFactory() {
return streamCoderFactory;
}
/**
* allows to use subclassed stream codecs. Can also be used to change class loading behaviour, as
* clasForName is part of a codec's interface.
*
* e.g. new StreamCoderFactory() {
* @Override
* public FSTEncoder createStreamEncoder() {
* return new FSTStreamEncoder(FSTConfiguration.this);
* }
*
* @Override
* public FSTDecoder createStreamDecoder() {
* return new FSTStreamDecoder(FSTConfiguration.this) { public Class classForName(String name) { ... } } ;
* }
* };
*
* You need to work with thread locals most probably as the factory is ~global (assigned to fstconfiguration shared amongst
* streams)
*
* @param streamCoderFactory
*/
public void setStreamCoderFactory(StreamCoderFactory streamCoderFactory) {
this.streamCoderFactory = streamCoderFactory;
}
/**
* reuse heavy weight objects. If a FSTStream is closed, objects are returned and can be reused by new stream instances.
* the objects are held in soft references, so there should be no memory issues. FIXME: point of contention !
* @param cached
*/
public void returnObject( Object cached ) {
try {
while (!cacheLock.compareAndSet(false, true)) {
// empty
}
List li = cachedObjects.get(cached.getClass());
if ( li == null ) {
li = new ArrayList();
cachedObjects.put(cached.getClass(),li);
}
if ( li.size() < 5 )
li.add(new SoftReference(cached));
} finally {
cacheLock.set(false);
}
}
/**
* ignored currently
* @return
*/
public boolean isPreferSpeed() {
return preferSpeed;
}
/**
* this options lets FST favour speed of encoding over size of the encoded object.
* Warning: this option alters the format of the written stream, so both reader and writer should have
* the same setting, else exceptions will occur
* @param preferSpeed
*/
public void setPreferSpeed(boolean preferSpeed) {
this.preferSpeed = preferSpeed;
}
/**
* for optimization purposes, do not use to benchmark processing time or in a regular program as
* this methods creates a temporary binaryoutputstream and serializes the object in order to measure the
* size.
*/
public int calcObjectSizeBytesNotAUtility( Object obj ) throws IOException {
ByteArrayOutputStream bout = new ByteArrayOutputStream(10000);
FSTObjectOutput ou = new FSTObjectOutput(bout,this);
ou.writeObject(obj, obj.getClass());
ou.close();
return bout.toByteArray().length;
}
/**
* patch default serializer lookup. set to null to delete.
* Should be set prior to any serialization going on (serializer lookup is cached).
* @param del
*/
public void setSerializerRegistryDelegate( FSTSerializerRegistryDelegate del ) {
serializationInfoRegistry.setSerializerRegistryDelegate(del);
}
AtomicBoolean cacheLock = new AtomicBoolean(false);
public Object getCachedObject( Class cl ) {
try {
while (!cacheLock.compareAndSet(false, true)) {
// empty
}
List li = cachedObjects.get(cl);
if ( li == null ) {
return null;
}
for (int i = li.size()-1; i >= 0; i--) {
SoftReference softReference = li.get(i);
Object res = softReference.get();
li.remove(i);
if ( res != null ) {
return res;
}
}
} finally {
cacheLock.set(false);
}
return null;
}
public boolean isForceSerializable() {
return forceSerializable;
}
/**
* treat unserializable classes same as if they would be serializable.
* @param forceSerializable
// */
public FSTConfiguration setForceSerializable(boolean forceSerializable) {
this.forceSerializable = forceSerializable;
return this;
}
/**
* clear global deduplication caches. Useful for class reloading scenarios, else counter productive as
* j.reflect.Fiwld + Construtors will be instantiated more than once per class.
*/
public static void clearGlobalCaches() {
FSTClazzInfo.sharedFieldSets.clear();
FSTDefaultClassInstantiator.constructorMap.clear();
}
/**
* clear cached softref's and ThreadLocal.
*/
public void clearCaches() {
try {
FSTInputStream.cachedBuffer.set(null);
while (!cacheLock.compareAndSet(false, true)) {
// empty
}
cachedObjects.clear();
} finally {
cacheLock.set( false );
}
}
public boolean isShareReferences() {
return shareReferences;
}
/**
* if false, identical objects will get serialized twice. Gains speed as long there are no double objects/cyclic references (typical for small snippets as used in e.g. RPC)
*
* Cycles and Objects referenced more than once will not be detected (if set to false).
* Additionally JDK compatibility is not supported (read/writeObject and stuff). Use case is highperformance
* serialization of plain cycle free data (e.g. messaging). Can perform significantly faster (20-40%).
*
* @param shareReferences
*
*/
public void setShareReferences(boolean shareReferences) {
this.shareReferences = shareReferences;
}
/**
*
* Preregister a class (use at init time). This avoids having to write class names.
* Its a very simple and effective optimization (frequently > 2 times faster for small objects).
*
* Read and write side need to have classes preregistered in the exact same order.
*
* The list does not have to be complete. Just add your most frequently serialized classes here
* to get significant gains in speed and smaller serialized representation size.
*
*/
public void registerClass( Class ... c) {
for (int i = 0; i < c.length; i++) {
classRegistry.registerClass(c[i],this);
try {
Class ac = Class.forName("[L"+c[i].getName()+";");
classRegistry.registerClass(ac,this);
} catch (ClassNotFoundException e) {
// silent
}
}
}
void addDefaultClazzes() {
classRegistry.registerClass(String.class,this);
classRegistry.registerClass(Byte.class,this);
classRegistry.registerClass(Short.class,this);
classRegistry.registerClass(Integer.class,this);
classRegistry.registerClass(Long.class,this);
classRegistry.registerClass(Float.class,this);
classRegistry.registerClass(Double.class,this);
classRegistry.registerClass(BigDecimal.class,this);
classRegistry.registerClass(BigInteger.class,this);
classRegistry.registerClass(Character.class,this);
classRegistry.registerClass(Boolean.class,this);
classRegistry.registerClass(TreeMap.class,this);
classRegistry.registerClass(HashMap.class,this);
classRegistry.registerClass(ArrayList.class,this);
classRegistry.registerClass(ConcurrentHashMap.class,this);
classRegistry.registerClass(URL.class,this);
classRegistry.registerClass(Date.class,this);
classRegistry.registerClass(java.sql.Date.class,this);
classRegistry.registerClass(SimpleDateFormat.class,this);
classRegistry.registerClass(TreeSet.class,this);
classRegistry.registerClass(LinkedList.class,this);
classRegistry.registerClass(SimpleTimeZone.class,this);
classRegistry.registerClass(GregorianCalendar.class,this);
classRegistry.registerClass(Vector.class,this);
classRegistry.registerClass(Hashtable.class,this);
classRegistry.registerClass(BitSet.class,this);
classRegistry.registerClass(Timestamp.class, this);
classRegistry.registerClass(Locale.class,this);
classRegistry.registerClass(StringBuffer.class,this);
classRegistry.registerClass(StringBuilder.class,this);
classRegistry.registerClass(Object.class,this);
classRegistry.registerClass(Object[].class,this);
classRegistry.registerClass(Object[][].class,this);
classRegistry.registerClass(Object[][][].class,this);
classRegistry.registerClass(byte[].class,this);
classRegistry.registerClass(byte[][].class,this);
classRegistry.registerClass(char[].class,this);
classRegistry.registerClass(char[][].class,this);
classRegistry.registerClass(short[].class,this);
classRegistry.registerClass(short[][].class,this);
classRegistry.registerClass(int[].class,this);
classRegistry.registerClass(int[][].class,this);
classRegistry.registerClass(float[].class,this);
classRegistry.registerClass(float[][].class,this);
classRegistry.registerClass(double[].class,this);
classRegistry.registerClass(double[][].class,this);
classRegistry.registerClass(long[].class,this);
classRegistry.registerClass(long[][].class,this);
}
public FSTClazzNameRegistry getClassRegistry() {
return classRegistry;
}
public FSTClazzInfoRegistry getCLInfoRegistry() {
return serializationInfoRegistry;
}
public ClassLoader getClassLoader() {
return classLoader;
}
public FSTClazzInfo getClassInfo(Class type) {
return serializationInfoRegistry.getCLInfo(type, this);
}
/**
* utility for thread safety and reuse. Do not close the resulting stream. However you should close
* the given InputStream 'in'
* @param in
* @return
*/
public FSTObjectInput getObjectInput( InputStream in ) {
FSTObjectInput fstObjectInput = getIn();
try {
fstObjectInput.resetForReuse(in);
return fstObjectInput;
} catch (IOException e) {
FSTUtil.rethrow(e);
}
return null;
}
public FSTObjectInput getObjectInput() {
return getObjectInput((InputStream)null);
}
/**
* take the given array as input. the array is NOT copied.
*
* WARNING: the input streams takes over ownership and might overwrite content
* of this array in subsequent IO operations.
*
* @param arr
* @return
*/
public FSTObjectInput getObjectInput( byte arr[]) {
return getObjectInput(arr, arr.length);
}
/**
* take the given array as input. the array is NOT copied.
*
* WARNING: the input streams takes over ownership and might overwrite content
* of this array in subsequent IO operations.
*
* @param arr
* @param len
* @return
*/
public FSTObjectInput getObjectInput( byte arr[], int len ) {
FSTObjectInput fstObjectInput = getIn();
try {
fstObjectInput.resetForReuseUseArray(arr,len);
return fstObjectInput;
} catch (IOException e) {
FSTUtil.rethrow(e);
}
return null;
}
/**
* take the given array and copy it to input. the array IS copied
* @param arr
* @param len
* @return
*/
public FSTObjectInput getObjectInputCopyFrom( byte arr[],int off, int len ) {
FSTObjectInput fstObjectInput = getIn();
try {
fstObjectInput.resetForReuseCopyArray(arr, off, len);
return fstObjectInput;
} catch (IOException e) {
FSTUtil.rethrow(e);
}
return null;
}
protected FSTObjectInput getIn() {
FSTObjectInput fstObjectInput = (FSTObjectInput) streamCoderFactory.getInput().get();
if ( fstObjectInput != null && fstObjectInput.isClosed() )
fstObjectInput = null;
if ( fstObjectInput == null ) {
streamCoderFactory.getInput().set(new FSTObjectInput(this));
return getIn();
}
fstObjectInput.conf = this;
fstObjectInput.getCodec().setConf(this);
return fstObjectInput;
}
protected FSTObjectOutput getOut() {
FSTObjectOutput fstOut = (FSTObjectOutput) streamCoderFactory.getOutput().get();
if ( fstOut == null || fstOut.closed ) {
streamCoderFactory.getOutput().set(new FSTObjectOutput(this));
return getOut();
}
fstOut.conf = this;
fstOut.getCodec().setConf(this);
return fstOut;
}
/**
* utility for thread safety and reuse. Do not close the resulting stream. However you should close
* the given OutputStream 'out'
* @param out - can be null (temp bytearrays stream is created then)
* @return
*/
public FSTObjectOutput getObjectOutput(OutputStream out) {
FSTObjectOutput fstObjectOutput = getOut();
fstObjectOutput.resetForReUse(out);
return fstObjectOutput;
}
/**
* @return a recycled outputstream reusing its last recently used byte[] buffer
*/
public FSTObjectOutput getObjectOutput() {
return getObjectOutput((OutputStream)null);
}
public FSTObjectOutput getObjectOutput(byte[] outByte) {
FSTObjectOutput fstObjectOutput = getOut();
fstObjectOutput.resetForReUse(outByte);
return fstObjectOutput;
}
/**
* ignores all serialization related interfaces (Serializable, Externalizable) and serializes all classes using the
* default scheme. Warning: this is a special mode of operation which fail serializing/deserializing many standard
* JDK classes.
*
* @param ignoreSerialInterfaces
*/
public void setStructMode(boolean ignoreSerialInterfaces) {
serializationInfoRegistry.setStructMode(ignoreSerialInterfaces);
}
/**
* special for structs
* @return
*/
public boolean isStructMode() {
return serializationInfoRegistry.isStructMode();
}
public FSTClazzInfo getClazzInfo(Class rowClass) {
return getCLInfoRegistry().getCLInfo(rowClass, this);
}
public void setCrossPlatform(boolean crossPlatform) {
this.crossPlatform = crossPlatform;
}
public boolean isCrossPlatform() {
return crossPlatform;
}
public T deepCopy(T metadata) {
return (T) asObject(asByteArray(metadata));
}
public static interface StreamCoderFactory {
FSTEncoder createStreamEncoder();
FSTDecoder createStreamDecoder();
ThreadLocal getInput();
ThreadLocal getOutput();
}
public FSTEncoder createStreamEncoder() {
return streamCoderFactory.createStreamEncoder();
}
public FSTDecoder createStreamDecoder() {
return streamCoderFactory.createStreamDecoder();
}
AtomicBoolean cplock = new AtomicBoolean(false);
public void registerCrossPlatformClassBinaryCache( String fulLQName, byte[] binary ) {
try {
while (cplock.compareAndSet(false, true)) { } // spin
minBinNamesBytez.put(fulLQName, binary);
} finally {
cplock.set(false);
}
}
public byte[] getCrossPlatformBinaryCache(String symbolicName) {
try {
while ( cplock.compareAndSet(false, true)) { } // spin
return minBinNamesBytez.get(symbolicName);
} finally {
cplock.set(false);
}
}
/**
* init right after creation of configuration, not during operation as it is not threadsafe regarding mutation
* currently only for minbin serialization
*
* @param keysAndVals { { "symbolicName", "fullQualifiedClazzName" }, .. }
*/
public FSTConfiguration registerCrossPlatformClassMapping( String[][] keysAndVals ) {
for (int i = 0; i < keysAndVals.length; i++) {
String[] keysAndVal = keysAndVals[i];
registerCrossPlatformClassMapping(keysAndVal[0], keysAndVal[1]);
}
return this;
}
public FSTConfiguration registerCrossPlatformClassMapping( String shortName, String fqName ) {
minbinNames.put(shortName, fqName);
minbinNamesReverse.put(fqName, shortName);
return this;
}
/**
* shorthand for registerCrossPlatformClassMapping(_,_)
* @param shortName - class name in json type field
* @param clz - class
* @return
*/
public FSTConfiguration cpMap(String shortName, Class clz) {
return registerCrossPlatformClassMapping(shortName,clz.getName());
}
/**
* init right after creation of configuration, not during operation as it is not threadsafe regarding mutation
*/
public FSTConfiguration registerCrossPlatformClassMappingUseSimpleName( Class ... classes ) {
registerCrossPlatformClassMappingUseSimpleName(Arrays.asList(classes));
return this;
}
public FSTConfiguration registerCrossPlatformClassMappingUseSimpleName( List classes ) {
for (int i = 0; i < classes.size(); i++) {
Class clz = classes.get(i);
minbinNames.put(clz.getSimpleName(), clz.getName());
minbinNamesReverse.put(clz.getName(), clz.getSimpleName());
try {
if (!clz.isArray() ) {
Class ac = Class.forName("[L"+clz.getName()+";");
minbinNames.put(clz.getSimpleName()+"[]", ac.getName());
minbinNamesReverse.put(ac.getName(), clz.getSimpleName()+"[]");
}
} catch (ClassNotFoundException e) {
FSTUtil.rethrow(e);
}
}
return this;
}
/**
* get cross platform symbolic class identifier
* @param cl
* @return
*/
public String getCPNameForClass( Class cl ) {
String res = minbinNamesReverse.get(cl.getName());
if (res == null) {
if (cl.isAnonymousClass()) {
return getCPNameForClass(cl.getSuperclass());
}
return cl.getName();
}
return res;
}
public String getClassForCPName( String name ) {
String res = minbinNames.get(name);
if (res == null) {
return name;
}
return res;
}
/**
* convenience
*/
public Object asObject( byte b[] ) {
try {
return getObjectInput(b).readObject();
} catch (Exception e) {
System.out.println("unable to decode:" +new String(b,0,0,Math.min(b.length,100)) );
try {
String debug = new String(b, "UTF-8");
} catch (UnsupportedEncodingException e1) {
//
}
try {
getObjectInput(b).readObject();
} catch (Exception e1) {
// debug hook
}
FSTUtil.rethrow(e);
}
return null;
}
/**
* convenience. (object must be serializable)
*/
public byte[] asByteArray( Object object ) {
FSTObjectOutput objectOutput = getObjectOutput();
try {
objectOutput.writeObject(object);
return objectOutput.getCopyOfWrittenBuffer();
} catch (IOException e) {
try {
// FSTConfiguration.prettyPrintJson(object); endless cycle !
} catch (Exception ee) {
//
}
FSTUtil.rethrow(e);
}
return null;
}
/**
* Warning: avoids allocation + copying.
* The returned byteArray is a direct pointer to underlying buffer.
* the int length[] is expected to have at least on element.
* The buffer can be larger than written data, therefore length[0] will contain written length.
*
* The buffer content must be used (e.g. sent to network, copied to offheap) before doing another
* asByteArray on the current Thread.
*/
public byte[] asSharedByteArray( Object object, int length[] ) {
FSTObjectOutput objectOutput = getObjectOutput();
try {
objectOutput.writeObject(object);
length[0] = objectOutput.getWritten();
return objectOutput.getBuffer();
} catch (IOException e) {
FSTUtil.rethrow(e);
}
return null;
}
/**
* utility/debug method. Use "asByteArray" for programmatic use as the
* byte array will already by UTF-8 and ready to be sent on network.
*
* @param o
* @return
*/
public String asJsonString(Object o) {
if ( getCoderSpecific() instanceof JsonFactory == false ) {
return "can be called on JsonConfiguration only";
} else {
return new String(asByteArray(o), StandardCharsets.UTF_8);
}
}
/**
* helper to write series of objects to streams/files > Integer.MAX_VALUE.
* it
* - serializes the object
* - writes the length of the serialized object to the stream
* - the writes the serialized object data
*
* on reader side (e.g. from a blocking socketstream, the reader then
* - reads the length
* - reads [length] bytes from the stream
* - deserializes
*
* @see decodeFromStream
*
* @param out
* @param toSerialize
* @throws IOException
*/
public void encodeToStream( OutputStream out, Object toSerialize ) throws IOException {
FSTObjectOutput objectOutput = getObjectOutput(); // could also do new with minor perf impact
objectOutput.writeObject(toSerialize);
int written = objectOutput.getWritten();
out.write((written >>> 0) & 0xFF);
out.write((written >>> 8) & 0xFF);
out.write((written >>> 16) & 0xFF);
out.write((written >>> 24) & 0xFF);
// copy internal buffer to bufferedoutput
out.write(objectOutput.getBuffer(), 0, written);
objectOutput.flush();
}
/**
* @see encodeToStream
*
* @param in
* @return
* @throws Exception
*/
public Object decodeFromStream( InputStream in ) throws Exception {
int read = in.read();
if ( read < 0 )
throw new EOFException("stream is closed");
int ch1 = (read + 256) & 0xff;
int ch2 = (in.read()+ 256) & 0xff;
int ch3 = (in.read() + 256) & 0xff;
int ch4 = (in.read() + 256) & 0xff;
int len = (ch4 << 24) + (ch3 << 16) + (ch2 << 8) + (ch1 << 0);
if ( len <= 0 )
throw new EOFException("stream is corrupted");
byte buffer[] = new byte[len]; // this could be reused !
while (len > 0) {
len -= in.read(buffer, buffer.length - len, len);
}
return getObjectInput(buffer).readObject();
}
/**
* @return a configzration sharing as much as possible state with the callee. Its valid to register
* different serializers at the derived configzration obtained.
*/
public FSTConfiguration deriveConfiguration() {
if ( fieldInfoCache == null ) {
fieldInfoCache = new ConcurrentHashMap<>();
}
FSTConfiguration derived = createConfiguration(type, shareReferences, fieldInfoCache);
// try to share as much as possible to save memory
// note: the creation of those objects in createConfiguration() is unnecessary,
// would need a specials lean creation method to avoid that (+init overhead)
//
// still no good test coverage. Problematic distribution of state and references all across the
// code (to reduce pointer chasing) makes it problematic to implement stuff like this (errors might occur on nasty edge cases)
derived.fieldInfoCache = fieldInfoCache;
// sharing does not work. need manual clean up
// derived.output = output;
// derived.input = input;
// cannot derive => hard link to conf in anonymous
// derived.streamCoderFactory = streamCoderFactory;
// derived.instantiator = instantiator;
// derived.lastResortResolver = lastResortResolver;
// avoid concurrent registering later on !
derived.minbinNames = minbinNames;
derived.minBinNamesBytez = minBinNamesBytez;
derived.minbinNamesReverse = minbinNamesReverse;
// errors with websockets ..
// derived.classRegistry = classRegistry;
return derived;
}
@Override
public String toString() {
return "FSTConfiguration{" +
"name='" + name + '\'' +
'}';
}
protected static class MinBinStreamCoderFactory implements StreamCoderFactory {
private final FSTConfiguration conf;
public MinBinStreamCoderFactory(FSTConfiguration conf) {
this.conf = conf;
}
@Override
public FSTEncoder createStreamEncoder() {
return new FSTMinBinEncoder(conf);
}
@Override
public FSTDecoder createStreamDecoder() {
return new FSTMinBinDecoder(conf);
}
static ThreadLocal input = new ThreadLocal();
static ThreadLocal output = new ThreadLocal();
@Override
public ThreadLocal getInput() {
return input;
}
@Override
public ThreadLocal getOutput() {
return output;
}
}
protected static class FSTDefaultStreamCoderFactory implements FSTConfiguration.StreamCoderFactory {
private FSTConfiguration fstConfiguration;
public FSTDefaultStreamCoderFactory(FSTConfiguration fstConfiguration) {this.fstConfiguration = fstConfiguration;}
@Override
public FSTEncoder createStreamEncoder() {
return new FSTStreamEncoder(fstConfiguration);
}
@Override
public FSTDecoder createStreamDecoder() {
return new FSTStreamDecoder(fstConfiguration);
}
static ThreadLocal input = new ThreadLocal();
static ThreadLocal output = new ThreadLocal();
@Override
public ThreadLocal getInput() {
return input;
}
@Override
public ThreadLocal getOutput() {
return output;
}
}
protected static class JSonStreamCoderFactory implements StreamCoderFactory {
protected final FSTConfiguration conf;
public JSonStreamCoderFactory(FSTConfiguration conf) {
this.conf = conf;
}
@Override
public FSTEncoder createStreamEncoder() {
return new FSTJsonEncoder(conf);
}
@Override
public FSTDecoder createStreamDecoder() {
return new FSTJsonDecoder(conf);
}
static ThreadLocal input = new ThreadLocal();
static ThreadLocal output = new ThreadLocal();
@Override
public ThreadLocal getInput() {
return input;
}
@Override
public ThreadLocal getOutput() {
return output;
}
}
protected static class FBinaryStreamCoderFactory implements StreamCoderFactory {
protected final FSTConfiguration conf;
public FBinaryStreamCoderFactory(FSTConfiguration conf) {
this.conf = conf;
}
@Override
public FSTEncoder createStreamEncoder() {
return new FSTBytezEncoder(conf, new HeapBytez(new byte[4096]));
}
@Override
public FSTDecoder createStreamDecoder() {
return new FSTBytezDecoder(conf);
}
static ThreadLocal input = new ThreadLocal();
static ThreadLocal output = new ThreadLocal();
@Override
public ThreadLocal getInput() {
return input;
}
@Override
public ThreadLocal getOutput() {
return output;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy