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.ID_STRATEGY;
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 com.dyuproject.protostuff.Message;
import com.dyuproject.protostuff.Pipe;
import com.dyuproject.protostuff.Schema;
import com.dyuproject.protostuff.Tag;
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 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.
*
* NOTE: This is only supported when {@link RuntimeEnv#ID_STRATEGY} is
* {@link DefaultIdStrategy}.
*
* @throws IllegalArgumentException if the {@code typeClass} is an interface or
* an abstract class.
*/
public static boolean map(Class super T> baseClass, Class typeClass)
{
if(ID_STRATEGY instanceof DefaultIdStrategy)
return ((DefaultIdStrategy)ID_STRATEGY).map(baseClass, typeClass);
throw new RuntimeException("RuntimeSchema.map is only supported on DefaultIdStrategy");
}
/**
* Returns true if this there is no existing one or the same schema
* has already been registered (this must be done on application startup).
*
* NOTE: This is only supported when {@link RuntimeEnv#ID_STRATEGY} is
* {@link DefaultIdStrategy}.
*/
public static boolean register(Class typeClass)
{
if (ID_STRATEGY instanceof DefaultIdStrategy)
return ((DefaultIdStrategy)ID_STRATEGY).registerPojo(typeClass);
throw new RuntimeException("RuntimeSchema.register is only supported on DefaultIdStrategy");
}
/**
* Returns true if this there is no existing one or the same schema
* has already been registered (this must be done on application startup).
*
* NOTE: This is only supported when {@link RuntimeEnv#ID_STRATEGY} is
* {@link DefaultIdStrategy}.
*/
public static boolean register(Class typeClass, Schema schema)
{
if(ID_STRATEGY instanceof DefaultIdStrategy)
return ((DefaultIdStrategy)ID_STRATEGY).registerPojo(typeClass, schema);
throw new RuntimeException("RuntimeSchema.register is only supported on DefaultIdStrategy");
}
/**
* Returns true if the {@code typeClass} was not lazily created.
*
* Method overload for backwards compatibility.
*/
public static boolean isRegistered(Class> typeClass)
{
return isRegistered(typeClass, ID_STRATEGY);
}
/**
* Returns true if the {@code typeClass} was not lazily created.
*/
public static boolean isRegistered(Class> typeClass, IdStrategy strategy)
{
return strategy.isRegistered(typeClass);
}
/**
* Gets the schema that was either registered or lazily initialized at runtime.
*
* Method overload for backwards compatibility.
*/
public static Schema getSchema(Class typeClass)
{
return getSchema(typeClass, ID_STRATEGY);
}
/**
* Gets the schema that was either registered or lazily initialized at runtime.
*/
public static Schema getSchema(Class typeClass, IdStrategy strategy)
{
return strategy.getSchemaWrapper(typeClass, true).getSchema();
}
/**
* Returns the schema wrapper.
*
* Method overload for backwards compatibility.
*/
static HasSchema getSchemaWrapper(Class typeClass)
{
return getSchemaWrapper(typeClass, ID_STRATEGY);
}
/**
* Returns the schema wrapper.
*/
static HasSchema getSchemaWrapper(Class typeClass, IdStrategy strategy)
{
return strategy.getSchemaWrapper(typeClass, true);
}
/**
* Generates a schema from the given class.
*
* Method overload for backwards compatibility.
*/
public static RuntimeSchema createFrom(Class typeClass)
{
return createFrom(typeClass, NO_EXCLUSIONS, ID_STRATEGY);
}
/**
* Generates a schema from the given class.
*/
public static RuntimeSchema createFrom(Class typeClass,
IdStrategy strategy)
{
return createFrom(typeClass, NO_EXCLUSIONS, strategy);
}
/**
* Generates a schema from the given class with the exclusion of certain fields.
*/
public static RuntimeSchema createFrom(Class typeClass,
String[] exclusions, IdStrategy strategy)
{
HashSet set = new HashSet();
for(String exclusion : exclusions)
set.add(exclusion);
return createFrom(typeClass, set, strategy);
}
/**
* Generates a schema from the given class with the exclusion of certain fields.
*/
public static RuntimeSchema createFrom(Class typeClass,
Set exclusions, IdStrategy strategy)
{
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;
int maxFieldMapping = 0;
boolean annotated = false;
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 Tag tag = f.getAnnotation(Tag.class);
final int fieldMapping;
final String name;
if(tag == null)
{
// Fields gets assigned mapping tags according to their definition order
if(annotated)
{
throw new RuntimeException(typeClass.getName() + "::" + f.getName() +
"is not annotated with @" + Tag.class.getSimpleName());
}
fieldMapping = ++i;
name = f.getName();
}
else
{
// Fields gets assigned mapping tags according to their annotation
if(!annotated && !fields.isEmpty())
{
throw new RuntimeException(
"When using annotation-based mapping, " +
"all fields must be annotated with @" + Tag.class.getSimpleName() +
" <- " + typeClass.getName());
}
annotated = true;
fieldMapping = tag.value();
if(fieldMapping < 1)
{
throw new RuntimeException("Invalid field number: " +
fieldMapping + " on " + typeClass);
}
name = tag.alias().isEmpty() ? f.getName() : tag.alias();
}
final Field field = RuntimeFieldFactory.getFieldFactory(
f.getType(), strategy).create(fieldMapping, name, f, strategy);
fields.add(field);
maxFieldMapping = Math.max(maxFieldMapping, fieldMapping);
}
}
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, maxFieldMapping,
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, IdStrategy strategy)
{
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(), strategy).create(++i, entry.getValue(), f, strategy);
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);
}
}
public 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();
}
/**
* 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;
}
}