com.dyuproject.protostuff.runtime.RuntimeSchema Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of protostuff-runtime Show documentation
Show all versions of protostuff-runtime Show documentation
protobuf serialization for pre-existing objects
//========================================================================
//Copyright 2007-2009 David Yu [email protected]
//------------------------------------------------------------------------
//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 com.dyuproject.protostuff.runtime;
import static com.dyuproject.protostuff.runtime.RuntimeEnv.loadClass;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.dyuproject.protostuff.Message;
import com.dyuproject.protostuff.Pipe;
import com.dyuproject.protostuff.Schema;
import com.dyuproject.protostuff.runtime.RuntimeEnv.DefaultInstantiator;
import com.dyuproject.protostuff.runtime.RuntimeEnv.Instantiator;
/**
* A schema that can be generated and cached at runtime for objects that have no schema.
* This is particularly useful for pojos from 3rd party libraries.
*
* @author David Yu
* @created Nov 9, 2009
*/
public final class RuntimeSchema extends MappedSchema
{
private static final ConcurrentHashMap> __schemaWrappers =
new ConcurrentHashMap>();
private static final Set NO_EXCLUSIONS = Collections.emptySet();
/**
* Maps the {@code baseClass} to a specific non-interface/non-abstract
* {@code typeClass} and registers it (this must be done on application startup).
*
* With this approach, there is no overhead of writing the type metadata if a
* {@code baseClass} field is serialized.
*
* Returns true if the baseClass does not exist.
*
* @throws IllegalArgumentException if the {@code typeClass} is an interface or
* an abstract class.
*/
public static boolean map(Class super T> baseClass, Class typeClass)
{
assert baseClass != null && typeClass != null;
if(typeClass.isInterface() || Modifier.isAbstract(typeClass.getModifiers()))
{
throw new IllegalArgumentException(typeClass +
" cannot be an interface/abstract class.");
}
final HasSchema> last = __schemaWrappers.putIfAbsent(baseClass.getName(),
new Mapped(baseClass, typeClass));
return last == null || (last instanceof Mapped> &&
((Mapped>)last).typeClass == typeClass);
}
/**
* Returns true if this there is no existing one or the same schema
* has already been registered (this must be done on application startup).
*/
public static boolean register(Class typeClass, Schema schema)
{
assert typeClass != null && schema != null;
final HasSchema> last = __schemaWrappers.putIfAbsent(typeClass.getName(),
new Registered(schema));
return last == null || (last instanceof Registered> &&
((Registered>)last).schema == schema);
}
/**
* Returns true if the {@code typeClass} has already been registered/mapped.
*/
public static boolean isRegistered(Class> typeClass)
{
final HasSchema> last = __schemaWrappers.get(typeClass.getName());
return last != null && !(last instanceof Lazy>);
}
/**
* Gets the schema that was either registered or lazily initialized at runtime.
*/
@SuppressWarnings("unchecked")
public static Schema getSchema(Class typeClass)
{
HasSchema hs = (HasSchema)__schemaWrappers.get(typeClass.getName());
if(hs == null)
{
hs = new Lazy(typeClass);
final HasSchema last = (HasSchema)__schemaWrappers.putIfAbsent(
typeClass.getName(), hs);
if(last != null)
hs = last;
}
return hs.getSchema();
}
/**
* Gets the schema that was either registered or lazily initialized at runtime (if
* the boolean arg {@code load} is true).
*/
@SuppressWarnings("unchecked")
public static Schema getSchema(String className, boolean load)
{
HasSchema hs = (HasSchema)__schemaWrappers.get(className);
if(hs == null)
{
if(!load)
return null;
final Class typeClass = loadClass(className);
hs = new Lazy(typeClass);
final HasSchema last = (HasSchema)__schemaWrappers.putIfAbsent(
typeClass.getName(), hs);
if(last != null)
hs = last;
}
return hs.getSchema();
}
/**
* Returns the schema wrapper.
*/
@SuppressWarnings("unchecked")
static HasSchema getSchemaWrapper(Class typeClass)
{
HasSchema hs = (HasSchema)__schemaWrappers.get(typeClass.getName());
if(hs == null)
{
hs = new Lazy(typeClass);
final HasSchema last = (HasSchema)__schemaWrappers.putIfAbsent(
typeClass.getName(), hs);
if(last != null)
hs = last;
}
return hs;
}
/**
* Gets the schema wrapper that was either registered or lazily initialized
* at runtime (if the boolean arg {@code load} is true).
*/
@SuppressWarnings("unchecked")
static HasSchema getSchemaWrapper(String className, boolean load)
{
HasSchema hs = (HasSchema)__schemaWrappers.get(className);
if(hs == null)
{
if(!load)
return null;
final Class typeClass = loadClass(className);
hs = new Lazy(typeClass);
final HasSchema last = (HasSchema)__schemaWrappers.putIfAbsent(
typeClass.getName(), hs);
if(last != null)
hs = last;
}
return hs;
}
/**
* Generates a schema from the given class.
*/
public static RuntimeSchema createFrom(Class typeClass)
{
return createFrom(typeClass, NO_EXCLUSIONS);
}
/**
* Generates a schema from the given class with the exclusion of certain fields.
*/
public static RuntimeSchema createFrom(Class typeClass, String[] exclusions)
{
HashSet set = new HashSet();
for(String exclusion : exclusions)
set.add(exclusion);
return createFrom(typeClass, set);
}
/**
* Generates a schema from the given class with the exclusion of certain fields.
*/
public static RuntimeSchema createFrom(Class typeClass,
Set exclusions)
{
if(typeClass.isInterface() || Modifier.isAbstract(typeClass.getModifiers()))
{
throw new RuntimeException("The root object can neither be an abstract " +
"class nor interface: \"" + typeClass.getName());
}
final Map fieldMap = findInstanceFields(typeClass);
final ArrayList> fields = new ArrayList>(fieldMap.size());
int i = 0;
for(java.lang.reflect.Field f : fieldMap.values())
{
if(!exclusions.contains(f.getName()))
{
if(f.getAnnotation(Deprecated.class) != null)
{
// this field is deprecated and should be skipped.
// preserve its field number for backward-forward compat
i++;
continue;
}
final Field field = RuntimeFieldFactory.getFieldFactory(
f.getType()).create(++i, f.getName(), f);
fields.add(field);
}
}
if(fields.isEmpty())
{
throw new RuntimeException("Not able to map any fields from " +
typeClass + ". All fields are either transient/static.");
}
return new RuntimeSchema(typeClass, fields, i,
RuntimeEnv.newInstantiator(typeClass));
}
/**
* Generates a schema from the given class with the declared fields (inclusive) based
* from the given Map.
* The value of a the Map's entry will be the name used for the field (which enables aliasing).
*/
public static RuntimeSchema createFrom(Class typeClass,
Map declaredFields)
{
if(typeClass.isInterface() || Modifier.isAbstract(typeClass.getModifiers()))
{
throw new RuntimeException("The root object can neither be an abstract " +
"class nor interface: \"" + typeClass.getName());
}
final ArrayList> fields = new ArrayList>(declaredFields.size());
int i = 0;
for(Map.Entry entry : declaredFields.entrySet())
{
final java.lang.reflect.Field f;
try
{
f = typeClass.getDeclaredField(entry.getKey());
}
catch (Exception e)
{
throw new IllegalArgumentException("Exception on field: " + entry.getKey(), e);
}
final int mod = f.getModifiers();
if(!Modifier.isStatic(mod) && !Modifier.isTransient(mod))
{
final Field field = RuntimeFieldFactory.getFieldFactory(
f.getType()).create(++i, entry.getValue(), f);
fields.add(field);
}
}
if(fields.isEmpty())
{
throw new RuntimeException("Not able to map any fields from " +
typeClass + ". All fields are either transient/static.");
}
return new RuntimeSchema(typeClass, fields, i,
RuntimeEnv.newInstantiator(typeClass));
}
static Map findInstanceFields(Class> typeClass)
{
LinkedHashMap fieldMap =
new LinkedHashMap();
fill(fieldMap, typeClass);
return fieldMap;
}
static void fill(Map fieldMap, Class> typeClass)
{
if(Object.class!=typeClass.getSuperclass())
fill(fieldMap, typeClass.getSuperclass());
for(java.lang.reflect.Field f : typeClass.getDeclaredFields())
{
int mod = f.getModifiers();
if(!Modifier.isStatic(mod) && !Modifier.isTransient(mod))
fieldMap.put(f.getName(), f);
}
}
final Instantiator instantiator;
public RuntimeSchema(Class typeClass, Collection> fields,
int lastFieldNumber, Constructor constructor)
{
this(typeClass, fields, lastFieldNumber,
new DefaultInstantiator(constructor));
}
public RuntimeSchema(Class typeClass, Collection> fields,
int lastFieldNumber, Instantiator instantiator)
{
super(typeClass, fields, lastFieldNumber);
this.instantiator = instantiator;
}
/**
* Always returns true, everything is optional.
*/
public boolean isInitialized(T message)
{
return true;
}
public T newMessage()
{
return instantiator.newInstance();
}
/**
* The schema wrapper.
*/
public static abstract class HasSchema
{
/**
* Gets the schema.
*/
public abstract Schema getSchema();
/**
* Gets the pipe schema.
*/
abstract Pipe.Schema getPipeSchema();
}
static final class Registered extends HasSchema
{
final Schema schema;
private volatile Pipe.Schema pipeSchema;
Registered(Schema schema)
{
this.schema = schema;
}
public Schema getSchema()
{
return schema;
}
Pipe.Schema getPipeSchema()
{
Pipe.Schema pipeSchema = this.pipeSchema;
if(pipeSchema == null)
{
synchronized(this)
{
if((pipeSchema = this.pipeSchema) == null)
{
this.pipeSchema = pipeSchema = resolvePipeSchema(
schema, schema.typeClass(), true);
}
}
}
return pipeSchema;
}
}
static final class Lazy extends HasSchema
{
final Class typeClass;
private volatile Schema schema;
private volatile Pipe.Schema pipeSchema;
Lazy(Class typeClass)
{
this.typeClass = typeClass;
}
@SuppressWarnings("unchecked")
public Schema getSchema()
{
Schema schema = this.schema;
if(schema==null)
{
synchronized(this)
{
if((schema = this.schema) == null)
{
if(Message.class.isAssignableFrom(typeClass))
{
// use the message's schema.
try
{
final Message m = (Message)typeClass.newInstance();
this.schema = schema = m.cachedSchema();
}
catch (InstantiationException e)
{
throw new RuntimeException(e);
}
catch (IllegalAccessException e)
{
throw new RuntimeException(e);
}
}
else
{
// create new
this.schema = schema = createFrom(typeClass);
}
}
}
}
return schema;
}
Pipe.Schema getPipeSchema()
{
Pipe.Schema pipeSchema = this.pipeSchema;
if(pipeSchema == null)
{
synchronized(this)
{
if((pipeSchema = this.pipeSchema) == null)
{
this.pipeSchema = pipeSchema = RuntimeSchema.resolvePipeSchema(
getSchema(), typeClass, true);
}
}
}
return pipeSchema;
}
}
static final class Mapped extends HasSchema
{
final Class super T> baseClass;
final Class typeClass;
private volatile HasSchema wrapper;
Mapped(Class super T> baseClass, Class typeClass)
{
this.baseClass = baseClass;
this.typeClass = typeClass;
}
public Schema getSchema()
{
HasSchema wrapper = this.wrapper;
if(wrapper == null)
{
synchronized(this)
{
if((wrapper = this.wrapper) == null)
{
this.wrapper = wrapper = getSchemaWrapper(typeClass);
}
}
}
return wrapper.getSchema();
}
Pipe.Schema getPipeSchema()
{
HasSchema wrapper = this.wrapper;
if(wrapper == null)
{
synchronized(this)
{
if((wrapper = this.wrapper) == null)
{
this.wrapper = wrapper = getSchemaWrapper(typeClass);
}
}
}
return wrapper.getPipeSchema();
}
}
/**
* Invoked only when applications are having pipe io operations.
*/
@SuppressWarnings("unchecked")
static Pipe.Schema resolvePipeSchema(Schema schema, Class super T> clazz,
boolean throwIfNone)
{
if(Message.class.isAssignableFrom(clazz))
{
try
{
// use the pipe schema of code-generated messages if available.
java.lang.reflect.Method m = clazz.getDeclaredMethod("getPipeSchema",
new Class[]{});
return (Pipe.Schema)m.invoke(null, new Object[]{});
}
catch(Exception e)
{
// ignore
}
}
if(MappedSchema.class.isAssignableFrom(schema.getClass()))
return ((MappedSchema)schema).pipeSchema;
if(throwIfNone)
throw new RuntimeException("No pipe schema for: " + clazz);
return null;
}
}