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

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

There is a newer version: 1.3.1
Show 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.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 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 baseClass;
        final Class typeClass;
        private volatile HasSchema wrapper;
        
        Mapped(Class 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 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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy