All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.dyuproject.protostuff.runtime.RuntimeSchema Maven / Gradle / Ivy

The newest version!
//========================================================================
//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 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;
                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();
                    if (name == null || name.isEmpty())
                        name = f.getName();
                }
                
                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 clazz, 
            boolean throwIfNone)
    {
        if (RuntimeEnv.MIX_MESSAGE_SCHEMA && 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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy