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

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

The newest version!
package com.dyuproject.protostuff.runtime;

import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;

import com.dyuproject.protostuff.CollectionSchema;
import com.dyuproject.protostuff.GraphInput;
import com.dyuproject.protostuff.Input;
import com.dyuproject.protostuff.MapSchema;
import com.dyuproject.protostuff.MapSchema.MapWrapper;
import com.dyuproject.protostuff.Message;
import com.dyuproject.protostuff.Output;
import com.dyuproject.protostuff.Pipe;
import com.dyuproject.protostuff.ProtostuffException;
import com.dyuproject.protostuff.Schema;
import com.dyuproject.protostuff.runtime.MappedSchema.Field;

/***
 * This base class handles all the IO for reading and writing polymorphic fields.
 * When a field's type is polymorphic/dynamic (e.g interface/abstract/object), 
 * the type (id) needs to be written (ahead) before its value/content to be able to 
 * deserialize it correctly.
 * 
 * The underlying impl will determine how the type (id) should be written.
 * 
 * An {@link IdStrategy} is standalone if the {@link #primaryGroup} is not set.
 * 
 * @author Leo Romanoff
 * @author David Yu
 * 
 */
public abstract class IdStrategy
{
    
    public final IdStrategy primaryGroup;
    public final int groupId;
    
    protected IdStrategy(IdStrategy primaryGroup, int groupId)
    {
        if(primaryGroup != null)
        {
            if(groupId <= 0 || 0 != (groupId & (groupId-1)))
            {
                throw new RuntimeException(
                        "The groupId must be a power of two (1,2,4,8,etc).");
            }
        }
        else if(groupId != 0)
        {
            throw new RuntimeException("An IdStrategy without a primaryGroup " +
            		"(standalone) must have a groupId of zero.");
        }
        
        this.primaryGroup = primaryGroup;
        this.groupId = groupId;
    }
    
    /**
     * Generates a schema from the given class.  If this strategy is part of a group, 
     * the existing fields of that group's schema will be re-used.
     */
    protected  Schema newSchema(Class typeClass)
    {
        // check if this is part of a group
        if (primaryGroup == null)
            return RuntimeSchema.createFrom(typeClass, this);
        
        // only pojos created by runtime schema support groups
        final Schema s = primaryGroup.getSchemaWrapper(typeClass, true).getSchema();
        if (!(s instanceof RuntimeSchema))
            return s;
        
        final RuntimeSchema rs = (RuntimeSchema)s;
        
        final ArrayList> fields = new ArrayList>(
                rs.fields.length);
        
        int copyCount = 0;
        for (Field f : rs.fields)
        {
            final int groupFilter = f.groupFilter;
            if (groupFilter != 0)
            {
                final int set; // set for exclusion
                if (groupFilter > 0)
                {
                    // inclusion
                    set = ~groupFilter & 0x7FFFFFFF;
                }
                else
                {
                    // exclusion
                    set = -groupFilter;
                }
                
                if (0 != (groupId & set))
                {
                    // this field is excluded on the current group id
                    continue;
                }
            }
            
            if (f != (f = f.copy(this)))
                copyCount++;
            
            fields.add(f);
        }
        
        final int size = fields.size();
        if (size == 0)
        {
            throw new RuntimeException("All fields were excluded for " + 
                    rs.messageFullName() + " on group " + groupId);
        }
        
        return copyCount == 0 && size == rs.fields.length ? rs : 
                new RuntimeSchema(typeClass, fields, 
                // the last field
                fields.get(size-1).number, rs.instantiator);
    }
    
    /**
     * Thrown when a type is not known by the IdStrategy.
     * The DefaultIdStrategy will never throw this exception though.
     */
    public static class UnknownTypeException extends RuntimeException
    {
        private static final long serialVersionUID = 1L;
        
        public UnknownTypeException(String msg)
        {
            super(msg);
        }
    }
    
    /**
     * Responsible for instantiating custom {@link IdStrategy} impls.
     */
    public interface Factory
    {
        /**
         * Creates a new {@link IdStrategy} instance (impl).
         */
        public IdStrategy create();
        
        /**
         * Called after the method {@link #create()} has been called.
         * This is used to prevent classloader issues.
         * RuntimeEnv's {@link RuntimeEnv#ID_STRATEGY} need to be set first.
         */
        public void postCreate();
    }
    
    /**
     * Returns true if there is a {@link Delegate} explicitly registered for the 
     * {@code typeClass}.
     */
    public abstract boolean isDelegateRegistered(Class typeClass);

    
    /**
     * Returns the {@link Delegate delegate}.
     */
    public abstract  HasDelegate getDelegateWrapper(Class typeClass);

    /**
     * Returns the {@link Delegate delegate}.
     */
    public abstract  Delegate getDelegate(Class typeClass);
    
    
    /**
     * Returns true if the {@code typeClass} is explicitly registered.
     */
    public abstract boolean isRegistered(Class typeClass);
    
    /**
     * Returns the wrapper for the registered schema.
     */
    public abstract  HasSchema getRegistered(Class typeClass);
    
    /**
     * Returns the {@link HasSchema schema wrapper}.
     * The caller is responsible that the typeClass is a pojo (e.g not an enum/array/etc).
     */
    public abstract  HasSchema getSchemaWrapper(Class typeClass, 
            boolean create);
    
    /**
     * Returns the {@link EnumIO}.
     * The callers (internal field factories) are responsible that the class provided 
     * is an enum class.
     */
    protected abstract EnumIO> getEnumIO(Class enumClass);
    
    /**
     * Returns the {@link CollectionSchema.MessageFactory}.
     * The callers (internal field factories) are responsible that the class provided 
     * implements {@link Collection}.
     */
    protected abstract CollectionSchema.MessageFactory getCollectionFactory(Class clazz);
    
    /**
     * Returns the {@link MapSchema.MessageFactory}.
     * The callers (internal field factories}) are responsible that the class provided 
     * implements {@Map}.
     */
    protected abstract MapSchema.MessageFactory getMapFactory(Class clazz);
    
    // collection
    
    protected abstract void writeCollectionIdTo(Output output, int fieldNumber, 
            Class clazz) throws IOException;
    
    protected abstract void transferCollectionId(Input input, Output output, 
            int fieldNumber) throws IOException;
    
    protected abstract CollectionSchema.MessageFactory resolveCollectionFrom(
            Input input) throws IOException;
    
    // map
    
    protected abstract void writeMapIdTo(Output output, int fieldNumber, 
            Class clazz) throws IOException;
    
    protected abstract void transferMapId(Input input, Output output, 
            int fieldNumber) throws IOException;
    
    protected abstract MapSchema.MessageFactory resolveMapFrom(
            Input input) throws IOException;
    
    // enum
    
    protected abstract void writeEnumIdTo(Output output, int fieldNumber, 
            Class clazz) throws IOException;
    
    protected abstract EnumIO transferEnumId(Input input, Output output, 
            int fieldNumber) throws IOException;
    
    protected abstract EnumIO resolveEnumFrom(Input input) throws IOException;
    
    // pojo
    
    protected abstract  HasSchema tryWritePojoIdTo(Output output,
            int fieldNumber, Class clazz, boolean registered) throws IOException;
    
    protected abstract  void writePojoIdTo(Output output, int fieldNumber, 
            Class clazz, HasSchema hs) throws IOException;
    
    protected abstract  HasSchema writePojoIdTo(Output output, int fieldNumber, 
            Class clazz) throws IOException;
    
    protected abstract  HasSchema transferPojoId(Input input, Output output, 
            int fieldNumber) throws IOException;
    
    protected abstract  HasSchema resolvePojoFrom(Input input, int fieldNumber) 
            throws IOException;
    
    protected abstract  Schema writeMessageIdTo(Output output, int fieldNumber, 
            Message message) throws IOException;
    
    // delegate
    
    /**
     * If this method returns null, the clazz was not registered as a delegate.
     */
    protected abstract  HasDelegate tryWriteDelegateIdTo(Output output, 
            int fieldNumber, Class clazz) throws IOException;
    
    protected abstract  HasDelegate transferDelegateId(Input input, Output output, 
            int fieldNumber) throws IOException;
    
    protected abstract  HasDelegate resolveDelegateFrom(Input input) throws IOException;
    
    // array
    
    protected abstract void writeArrayIdTo(Output output, Class componentType) 
            throws IOException;
    
    protected abstract void transferArrayId(Input input, Output output, int fieldNumber, 
            boolean mapped) throws IOException;
    
    protected abstract Class resolveArrayComponentTypeFrom(Input input, 
            boolean mapped) throws IOException;
    
    // class
    
    protected abstract void writeClassIdTo(Output output, Class componentType, 
            boolean array) throws IOException;
    
    protected abstract void transferClassId(Input input, Output output, int fieldNumber, 
            boolean mapped, boolean array) throws IOException;
    
    protected abstract Class resolveClassFrom(Input input, 
            boolean mapped, boolean array) throws IOException;
    
    // polymorphic requirements
    
    final DerivativeSchema POLYMORPHIC_POJO_ELEMENT_SCHEMA = 
            new DerivativeSchema(this)
    {
        @SuppressWarnings("unchecked")
        protected void doMergeFrom(Input input, Schema derivedSchema, 
                Object owner) throws IOException
        {
            final Object value = derivedSchema.newMessage();
            
            if (input instanceof GraphInput)
            {
                // update the actual reference.
                ((GraphInput)input).updateLast(value, owner);
            }
            
            derivedSchema.mergeFrom(input, value);
            
            if (MapWrapper.class == owner.getClass())
                ((MapWrapper)owner).setValue(value);
            else
                ((Collection)owner).add(value);
        }
    };
    
    // object polymorphic schema requirements
    
    final ArraySchema ARRAY_ELEMENT_SCHEMA = new ArraySchema(this)
    {
        @SuppressWarnings("unchecked")
        protected void setValue(Object value, Object owner)
        {
            if(MapWrapper.class == owner.getClass())
                ((MapWrapper)owner).setValue(value);
            else
                ((Collection)owner).add(value);
        }
    };
    
    final NumberSchema NUMBER_ELEMENT_SCHEMA = new NumberSchema(this)
    {
        @SuppressWarnings("unchecked")
        protected void setValue(Object value, Object owner)
        {
            if(MapWrapper.class == owner.getClass())
                ((MapWrapper)owner).setValue(value);
            else
                ((Collection)owner).add(value);
        }
    };
    
    final ClassSchema CLASS_ELEMENT_SCHEMA = new ClassSchema(this)
    {
        @SuppressWarnings("unchecked")
        protected void setValue(Object value, Object owner)
        {
            if(MapWrapper.class == owner.getClass())
                ((MapWrapper)owner).setValue(value);
            else
                ((Collection)owner).add(value);
        }
    };
    
    final PolymorphicEnumSchema POLYMORPHIC_ENUM_ELEMENT_SCHEMA = 
            new PolymorphicEnumSchema(this)
    {
        @SuppressWarnings("unchecked")
        protected void setValue(Object value, Object owner)
        {
            if(MapWrapper.class == owner.getClass())
                ((MapWrapper)owner).setValue(value);
            else
                ((Collection)owner).add(value);
        }
    };
    
    final PolymorphicThrowableSchema POLYMORPHIC_THROWABLE_ELEMENT_SCHEMA = 
            new PolymorphicThrowableSchema(this)
    {
        @SuppressWarnings("unchecked")
        protected void setValue(Object value, Object owner)
        {
            if(MapWrapper.class == owner.getClass())
                ((MapWrapper)owner).setValue(value);
            else
                ((Collection)owner).add(value);
        }
    };
    
    final ObjectSchema OBJECT_ELEMENT_SCHEMA = new ObjectSchema(this)
    {
        @SuppressWarnings("unchecked")
        protected void setValue(Object value, Object owner)
        {
            if(MapWrapper.class == owner.getClass())
                ((MapWrapper)owner).setValue(value);
            else
                ((Collection)owner).add(value);
        }
    };
    
    // object dynamic schema requirements
    
    final Schema DYNAMIC_VALUE_SCHEMA = new Schema()
    {
        public String getFieldName(int number)
        {
            return ObjectSchema.name(number);
        }

        public int getFieldNumber(String name)
        {
            return ObjectSchema.number(name);
        }

        public boolean isInitialized(Object owner)
        {
            return true;
        }

        public String messageFullName()
        {
            return Object.class.getName();
        }

        public String messageName()
        {
            return Object.class.getSimpleName();
        }

        public Object newMessage()
        {
            // cannot instantiate because the type is dynamic.
            throw new UnsupportedOperationException();
        }

        public Class typeClass()
        {
            return Object.class;
        }

        @SuppressWarnings("unchecked")
        public void mergeFrom(Input input, Object owner) throws IOException
        {
            if(PMapWrapper.class == owner.getClass())
            {
                // called from ENTRY_SCHEMA
                ((PMapWrapper)owner).setValue(ObjectSchema.readObjectFrom(input, 
                        this, owner, IdStrategy.this));
            }
            else
            {
                // called from COLLECTION_SCHEMA
                ((Collection)owner).add(ObjectSchema.readObjectFrom(input, 
                        this, owner, IdStrategy.this));
            }
        }

        public void writeTo(Output output, Object message) throws IOException
        {
            ObjectSchema.writeObjectTo(output, message, this, IdStrategy.this);
        }
    };
    
    final Pipe.Schema DYNAMIC_VALUE_PIPE_SCHEMA = 
        new Pipe.Schema(DYNAMIC_VALUE_SCHEMA)
    {
        protected void transfer(Pipe pipe, Input input, Output output) throws IOException
        {
            ObjectSchema.transferObject(this, pipe, input, output, IdStrategy.this);
        }
    };
    
    final Schema> COLLECTION_SCHEMA = 
        new Schema>()
    {
        public String getFieldName(int number)
        {
            return number == 1 ? CollectionSchema.FIELD_NAME_VALUE : null;
        }

        public int getFieldNumber(String name)
        {
            return name.length() == 1 && name.charAt(0) == 'v' ? 1 : 0;
        }

        public boolean isInitialized(Collection owner)
        {
            return true;
        }

        public String messageFullName()
        {
            return Collection.class.getName();
        }

        public String messageName()
        {
            return Collection.class.getSimpleName();
        }

        public Collection newMessage()
        {
            throw new UnsupportedOperationException();
        }

        public Class> typeClass()
        {
            return Collection.class;
        }

        public void mergeFrom(Input input, Collection message) throws IOException
        {
            for(int number = input.readFieldNumber(this);;
                    number = input.readFieldNumber(this))
            {
                switch(number)
                {
                    case 0:
                        return;
                    case 1:
                        final Object value = input.mergeObject(message, 
                                DYNAMIC_VALUE_SCHEMA);
                        if(input instanceof GraphInput 
                                && ((GraphInput)input).isCurrentMessageReference())
                        {
                            // a reference from polymorphic+cyclic graph deser
                            message.add(value);
                        }
                        break;
                    default:
                        throw new ProtostuffException("Corrupt input.");
                }
            }
        }

        public void writeTo(Output output, Collection message) throws IOException
        {
            for(Object value : message)
            {
                if(value != null)
                    output.writeObject(1, value, DYNAMIC_VALUE_SCHEMA, true);
            }
        }
    };
    
    final Pipe.Schema> COLLECTION_PIPE_SCHEMA = 
        new Pipe.Schema>(COLLECTION_SCHEMA)
    {
        protected void transfer(Pipe pipe, Input input, Output output) throws IOException
        {
            for(int number = input.readFieldNumber(wrappedSchema);; 
                    number = input.readFieldNumber(wrappedSchema))
            {
                switch(number)
                {
                    case 0:
                        return;
                    case 1:
                        output.writeObject(number, pipe, DYNAMIC_VALUE_PIPE_SCHEMA, true);
                        break;
                    default:
                        throw new ProtostuffException("The collection was incorrectly " + 
                                "serialized.");
                }
            }
        }
    };
    
    final Schema ARRAY_SCHEMA = new Schema()
    {
        public String getFieldName(int number)
        {
            return number == 1 ? CollectionSchema.FIELD_NAME_VALUE : null;
        }

        public int getFieldNumber(String name)
        {
            return name.length() == 1 && name.charAt(0) == 'v' ? 1 : 0;
        }

        public boolean isInitialized(Object owner)
        {
            return true;
        }

        public String messageFullName()
        {
            return Array.class.getName();
        }

        public String messageName()
        {
            return Array.class.getSimpleName();
        }

        public Object newMessage()
        {
            throw new UnsupportedOperationException();
        }

        public Class typeClass()
        {
            return Object.class;
        }

        public void mergeFrom(Input input, Object message) throws IOException
        {
            // using COLLECTION_SCHEMA instead.
            throw new UnsupportedOperationException();
        }

        public void writeTo(Output output, Object message) throws IOException
        {
            final Object[] array = (Object[])message;
            for (int i = 0, len = array.length; i < len; i++)
            {
                final Object value = array[i];
                if(value != null)
                {
                    output.writeObject(1, value, DYNAMIC_VALUE_SCHEMA, true);
                }
            }
        }
    };
    
    final Pipe.Schema ARRAY_PIPE_SCHEMA = 
        new Pipe.Schema(ARRAY_SCHEMA)
    {
        protected void transfer(Pipe pipe, Input input, Output output) throws IOException
        {
            for(int number = input.readFieldNumber(wrappedSchema);; 
                    number = input.readFieldNumber(wrappedSchema))
            {
                switch(number)
                {
                    case 0:
                        return;
                    case 1:
                        output.writeObject(number, pipe, DYNAMIC_VALUE_PIPE_SCHEMA, true);
                        break;
                    default:
                        throw new ProtostuffException("The array was incorrectly " + 
                                "serialized.");
                }
            }
        }
    };
    
    final Schema> MAP_SCHEMA = 
        new Schema>()
    {
        public final String getFieldName(int number)
        {
            return number == 1 ? MapSchema.FIELD_NAME_ENTRY : null;
        }

        public final int getFieldNumber(String name)
        {
            return name.length() == 1 && name.charAt(0) == 'e' ? 1 : 0; 
        }

        public boolean isInitialized(Map owner)
        {
            return true;
        }

        public String messageFullName()
        {
            return Map.class.getName();
        }

        public String messageName()
        {
            return Map.class.getSimpleName();
        }

        public Map newMessage()
        {
            throw new UnsupportedOperationException();
        }

        public Class> typeClass()
        {
            return Map.class;
        }

        public void mergeFrom(Input input, Map message) throws IOException
        {
            PMapWrapper entry = null;
            for(int number = input.readFieldNumber(this);; 
                    number = input.readFieldNumber(this))
            {
                switch(number)
                {
                    case 0:
                        return;
                    case 1:
                        if(entry == null)
                        {
                            // lazy initialize
                            entry = new PMapWrapper(message);
                        }
                        
                        if(entry != input.mergeObject(entry, ENTRY_SCHEMA))
                        {
                            // an entry will always be unique
                            // it can never be a reference.
                            throw new IllegalStateException(
                                    "A Map.Entry will always be " +
                                    "unique, hence it cannot be a reference " +
                                    "obtained from " + input.getClass().getName());
                        }
                        break;
                    default:
                        throw new ProtostuffException("The map was incorrectly serialized.");
                }
            }
        }

        public void writeTo(Output output, Map message) throws IOException
        {
            for(Map.Entry entry : message.entrySet())
            {
                output.writeObject(1, entry, ENTRY_SCHEMA, true);
            }
        }
    };
    
    final Pipe.Schema> MAP_PIPE_SCHEMA = 
        new Pipe.Schema>(MAP_SCHEMA)
    {
        protected void transfer(Pipe pipe, Input input, Output output) throws IOException
        {
            for(int number = input.readFieldNumber(wrappedSchema);; 
                    number = input.readFieldNumber(wrappedSchema))
            {
                switch(number)
                {
                    case 0:
                        return;
                    case 1:
                        output.writeObject(number, pipe, ENTRY_PIPE_SCHEMA, true);
                        break;
                    default:
                        throw new ProtostuffException("The map was incorrectly " + 
                                "serialized.");
                }
            }
        }
    };
    
    final Schema> ENTRY_SCHEMA = 
        new Schema>()
    {
        public final String getFieldName(int number)
        {
            switch(number)
            {
                case 1:
                    return MapSchema.FIELD_NAME_KEY;
                case 2:
                    return MapSchema.FIELD_NAME_VALUE;
                default:
                    return null;
            }
        }

        public final int getFieldNumber(String name)
        {
            if(name.length() != 1)
                return 0;
            
            switch(name.charAt(0))
            {
                case 'k':
                    return 1;
                case 'v':
                    return 2;
                default:
                    return 0;
            }
        }

        public boolean isInitialized(Entry message)
        {
            return true;
        }

        public String messageFullName()
        {
            return Entry.class.getName();
        }

        public String messageName()
        {
            return Entry.class.getSimpleName();
        }

        public Entry newMessage()
        {
            throw new UnsupportedOperationException();
        }

        public Class> typeClass()
        {
            return Entry.class;
        }
        
        public void mergeFrom(Input input, Entry message) 
        throws IOException
        {
            // Nobody else calls this except MAP_SCHEMA.mergeFrom
            final PMapWrapper entry = (PMapWrapper)message;
            
            Object key = null, value = null;
            for(int number = input.readFieldNumber(this);; 
                    number = input.readFieldNumber(this))
            {
                switch(number)
                {
                    case 0:
                        entry.map.put(key, value);
                        return;
                    case 1:
                        if(key != null)
                        {
                            throw new ProtostuffException("The map was incorrectly " + 
                                    "serialized.");
                        }
                        key = input.mergeObject(entry, DYNAMIC_VALUE_SCHEMA);
                        if(entry != key)
                        {
                            // a reference.
                            assert key != null;
                        }
                        else
                        {
                            // entry held the key
                            key = entry.setValue(null);
                            assert key != null;
                        }
                        break;
                    case 2:
                        if(value != null)
                        {
                            throw new ProtostuffException("The map was incorrectly " + 
                                    "serialized.");
                        }
                        value = input.mergeObject(entry, DYNAMIC_VALUE_SCHEMA);
                        if(entry != value)
                        {
                            // a reference.
                            assert value != null;
                        }
                        else
                        {
                            // entry held the value
                            value = entry.setValue(null);
                            assert value != null;
                        }
                        break;
                    default:
                        throw new ProtostuffException("The map was incorrectly " + 
                                    "serialized.");
                }
            }
        }
        
        public void writeTo(Output output, Entry entry) 
        throws IOException
        {
            if(entry.getKey() != null)
                output.writeObject(1, entry.getKey(), DYNAMIC_VALUE_SCHEMA, false);
            
            if(entry.getValue() != null)
                output.writeObject(2, entry.getValue(), DYNAMIC_VALUE_SCHEMA, false);
        }
    };
    
    final Pipe.Schema> ENTRY_PIPE_SCHEMA = 
        new Pipe.Schema>(ENTRY_SCHEMA)
    {
        protected void transfer(Pipe pipe, Input input, Output output) throws IOException
        {
            for(int number = input.readFieldNumber(wrappedSchema);; 
                    number = input.readFieldNumber(wrappedSchema))
            {
                switch(number)
                {
                    case 0:
                        return;
                    case 1:
                        output.writeObject(number, pipe, DYNAMIC_VALUE_PIPE_SCHEMA, false);
                        break;
                    case 2:
                        output.writeObject(number, pipe, DYNAMIC_VALUE_PIPE_SCHEMA, false);
                        break;
                    default:
                        throw new ProtostuffException("The map was incorrectly " +
                                        "serialized.");
                }
            }
        }
    };
    
    final Schema OBJECT_SCHEMA = new Schema()
    {
        public String getFieldName(int number)
        {
            return ObjectSchema.name(number);
        }

        public int getFieldNumber(String name)
        {
            return ObjectSchema.number(name);
        }

        public boolean isInitialized(Object owner)
        {
            return true;
        }

        public String messageFullName()
        {
            return Object.class.getName();
        }

        public String messageName()
        {
            return Object.class.getSimpleName();
        }

        public Object newMessage()
        {
            // cannot instantiate because the type is dynamic.
            throw new UnsupportedOperationException();
        }

        public Class typeClass()
        {
            return Object.class;
        }

        public void mergeFrom(Input input, Object owner) throws IOException
        {
            ((Wrapper)owner).value = ObjectSchema.readObjectFrom(input, this, owner, 
                    IdStrategy.this);
        }

        public void writeTo(Output output, Object message) throws IOException
        {
            ObjectSchema.writeObjectTo(output, message, this, IdStrategy.this);
        }
    };
    
    final Pipe.Schema OBJECT_PIPE_SCHEMA = 
        new Pipe.Schema(OBJECT_SCHEMA)
    {
        protected void transfer(Pipe pipe, Input input, Output output) throws IOException
        {
            ObjectSchema.transferObject(this, pipe, input, output, IdStrategy.this);
        }
    };
    
    
    final Schema CLASS_SCHEMA = new Schema()
    {
        public String getFieldName(int number)
        {
            return ClassSchema.name(number);
        }

        public int getFieldNumber(String name)
        {
            return ClassSchema.number(name);
        }

        public boolean isInitialized(Object owner)
        {
            return true;
        }

        public String messageFullName()
        {
            return Class.class.getName();
        }

        public String messageName()
        {
            return Class.class.getSimpleName();
        }

        public Object newMessage()
        {
            // cannot instantiate because the type is dynamic.
            throw new UnsupportedOperationException();
        }

        public Class typeClass()
        {
            return Object.class;
        }

        public void mergeFrom(Input input, Object owner) throws IOException
        {
            ((Wrapper)owner).value = ClassSchema.readObjectFrom(input, this, owner, 
                    IdStrategy.this);
        }

        public void writeTo(Output output, Object message) throws IOException
        {
            ClassSchema.writeObjectTo(output, message, this, IdStrategy.this);
        }
    };
    
    final Pipe.Schema CLASS_PIPE_SCHEMA = 
        new Pipe.Schema(CLASS_SCHEMA)
    {
        protected void transfer(Pipe pipe, Input input, Output output) throws IOException
        {
            ClassSchema.transferObject(this, pipe, input, output, IdStrategy.this);
        }
    };
    
    final Schema POLYMORPHIC_COLLECTION_SCHEMA = new Schema()
    {
        public String getFieldName(int number)
        {
            return PolymorphicCollectionSchema.name(number);
        }

        public int getFieldNumber(String name)
        {
            return PolymorphicCollectionSchema.number(name);
        }

        public boolean isInitialized(Object owner)
        {
            return true;
        }

        public String messageFullName()
        {
            return Collection.class.getName();
        }

        public String messageName()
        {
            return Collection.class.getSimpleName();
        }

        public Object newMessage()
        {
            // cannot instantiate because the type is dynamic.
            throw new UnsupportedOperationException();
        }

        public Class typeClass()
        {
            return Object.class;
        }

        public void mergeFrom(Input input, Object owner) throws IOException
        {
            ((Wrapper)owner).value = PolymorphicCollectionSchema.readObjectFrom(input, 
                    this, owner, IdStrategy.this);
        }

        public void writeTo(Output output, Object message) throws IOException
        {
            PolymorphicCollectionSchema.writeObjectTo(output, message, this, 
                    IdStrategy.this);
        }
    };
    
    final Pipe.Schema POLYMORPHIC_COLLECTION_PIPE_SCHEMA = 
        new Pipe.Schema(POLYMORPHIC_COLLECTION_SCHEMA)
    {
        protected void transfer(Pipe pipe, Input input, Output output) throws IOException
        {
            PolymorphicCollectionSchema.transferObject(this, pipe, input, output, 
                    IdStrategy.this);
        }
    };
    
    final Schema POLYMORPHIC_MAP_SCHEMA = new Schema()
    {
        public String getFieldName(int number)
        {
            return PolymorphicMapSchema.name(number);
        }

        public int getFieldNumber(String name)
        {
            return PolymorphicMapSchema.number(name);
        }

        public boolean isInitialized(Object owner)
        {
            return true;
        }

        public String messageFullName()
        {
            return Map.class.getName();
        }

        public String messageName()
        {
            return Map.class.getSimpleName();
        }

        public Object newMessage()
        {
            // cannot instantiate because the type is dynamic.
            throw new UnsupportedOperationException();
        }

        public Class typeClass()
        {
            return Object.class;
        }

        public void mergeFrom(Input input, Object owner) throws IOException
        {
            ((Wrapper)owner).value = PolymorphicMapSchema.readObjectFrom(input, 
                    this, owner, IdStrategy.this);
        }

        public void writeTo(Output output, Object message) throws IOException
        {
            PolymorphicMapSchema.writeObjectTo(output, message, this, 
                    IdStrategy.this);
        }
    };
    
    final Pipe.Schema POLYMORPHIC_MAP_PIPE_SCHEMA = 
        new Pipe.Schema(POLYMORPHIC_MAP_SCHEMA)
    {
        protected void transfer(Pipe pipe, Input input, Output output) throws IOException
        {
            PolymorphicMapSchema.transferObject(this, pipe, input, output, 
                    IdStrategy.this);
        }
    };
    
    // array element schema
    
    final ArraySchemas.BoolArray ARRAY_BOOL_PRIMITIVE_SCHEMA = 
            new ArraySchemas.BoolArray(this, null, true);
    final ArraySchemas.BoolArray ARRAY_BOOL_BOXED_SCHEMA = 
            new ArraySchemas.BoolArray(this, null, false);
    final ArraySchemas.BoolArray ARRAY_BOOL_DERIVED_SCHEMA = 
            new ArraySchemas.BoolArray(this, null, false)
    {
        @Override
        public Object readFrom(Input input, Object owner) throws IOException
        {
            if (ArraySchemas.ID_ARRAY_LEN != input.readFieldNumber(this))
                throw new ProtostuffException("Corrupt input.");
            
            int len = input.readInt32();
            return len >= 0 ? readPrimitiveFrom(input, owner, len) : 
                    readBoxedFrom(input, owner, -len - 1);
        }

        @Override
        protected void writeLengthTo(Output output, int len, boolean primitive)
                throws IOException
        {
            if (primitive)
                output.writeInt32(ArraySchemas.ID_ARRAY_LEN, len, false);
            else
                output.writeInt32(ArraySchemas.ID_ARRAY_LEN, -(len + 1), false);
        }
        
        @Override
        public void writeTo(Output output, Object value) throws IOException
        {
            writeTo(output, value, value.getClass().getComponentType().isPrimitive());
        }

        @Override
        @SuppressWarnings("unchecked")
        protected void setValue(Object value, Object owner)
        {
            if (MapWrapper.class == owner.getClass())
                ((MapWrapper) owner).setValue(value);
            else
                ((Collection) owner).add(value);
        }
    };
    
    final ArraySchemas.CharArray ARRAY_CHAR_PRIMITIVE_SCHEMA = 
            new ArraySchemas.CharArray(this, null, true);
    final ArraySchemas.CharArray ARRAY_CHAR_BOXED_SCHEMA = 
            new ArraySchemas.CharArray(this, null, false);
    final ArraySchemas.CharArray ARRAY_CHAR_DERIVED_SCHEMA = 
            new ArraySchemas.CharArray(this, null, false)
    {
        @Override
        public Object readFrom(Input input, Object owner) throws IOException
        {
            if (ArraySchemas.ID_ARRAY_LEN != input.readFieldNumber(this))
                throw new ProtostuffException("Corrupt input.");
            
            int len = input.readInt32();
            return len >= 0 ? readPrimitiveFrom(input, owner, len) : 
                    readBoxedFrom(input, owner, -len - 1);
        }

        @Override
        protected void writeLengthTo(Output output, int len, boolean primitive)
                throws IOException
        {
            if (primitive)
                output.writeInt32(ArraySchemas.ID_ARRAY_LEN, len, false);
            else
                output.writeInt32(ArraySchemas.ID_ARRAY_LEN, -(len + 1), false);
        }
        
        @Override
        public void writeTo(Output output, Object value) throws IOException
        {
            writeTo(output, value, value.getClass().getComponentType().isPrimitive());
        }

        @Override
        @SuppressWarnings("unchecked")
        protected void setValue(Object value, Object owner)
        {
            if (MapWrapper.class == owner.getClass())
                ((MapWrapper) owner).setValue(value);
            else
                ((Collection) owner).add(value);
        }
    };
    
    final ArraySchemas.ShortArray ARRAY_SHORT_PRIMITIVE_SCHEMA = 
            new ArraySchemas.ShortArray(this, null, true);
    final ArraySchemas.ShortArray ARRAY_SHORT_BOXED_SCHEMA = 
            new ArraySchemas.ShortArray(this, null, false);
    final ArraySchemas.ShortArray ARRAY_SHORT_DERIVED_SCHEMA = 
            new ArraySchemas.ShortArray(this, null, false)
    {
        @Override
        public Object readFrom(Input input, Object owner) throws IOException
        {
            if (ArraySchemas.ID_ARRAY_LEN != input.readFieldNumber(this))
                throw new ProtostuffException("Corrupt input.");
            
            int len = input.readInt32();
            return len >= 0 ? readPrimitiveFrom(input, owner, len) : 
                    readBoxedFrom(input, owner, -len - 1);
        }

        @Override
        protected void writeLengthTo(Output output, int len, boolean primitive)
                throws IOException
        {
            if (primitive)
                output.writeInt32(ArraySchemas.ID_ARRAY_LEN, len, false);
            else
                output.writeInt32(ArraySchemas.ID_ARRAY_LEN, -(len + 1), false);
        }
        
        @Override
        public void writeTo(Output output, Object value) throws IOException
        {
            writeTo(output, value, value.getClass().getComponentType().isPrimitive());
        }

        @Override
        @SuppressWarnings("unchecked")
        protected void setValue(Object value, Object owner)
        {
            if (MapWrapper.class == owner.getClass())
                ((MapWrapper) owner).setValue(value);
            else
                ((Collection) owner).add(value);
        }
    };
    
    final ArraySchemas.Int32Array ARRAY_INT32_PRIMITIVE_SCHEMA = 
            new ArraySchemas.Int32Array(this, null, true);
    final ArraySchemas.Int32Array ARRAY_INT32_BOXED_SCHEMA = 
            new ArraySchemas.Int32Array(this, null, false);
    final ArraySchemas.Int32Array ARRAY_INT32_DERIVED_SCHEMA = 
            new ArraySchemas.Int32Array(this, null, false)
    {
        @Override
        public Object readFrom(Input input, Object owner) throws IOException
        {
            if (ArraySchemas.ID_ARRAY_LEN != input.readFieldNumber(this))
                throw new ProtostuffException("Corrupt input.");
            
            int len = input.readInt32();
            return len >= 0 ? readPrimitiveFrom(input, owner, len) : 
                    readBoxedFrom(input, owner, -len - 1);
        }

        @Override
        protected void writeLengthTo(Output output, int len, boolean primitive)
                throws IOException
        {
            if (primitive)
                output.writeInt32(ArraySchemas.ID_ARRAY_LEN, len, false);
            else
                output.writeInt32(ArraySchemas.ID_ARRAY_LEN, -(len + 1), false);
        }
        
        @Override
        public void writeTo(Output output, Object value) throws IOException
        {
            writeTo(output, value, value.getClass().getComponentType().isPrimitive());
        }

        @Override
        @SuppressWarnings("unchecked")
        protected void setValue(Object value, Object owner)
        {
            if (MapWrapper.class == owner.getClass())
                ((MapWrapper) owner).setValue(value);
            else
                ((Collection) owner).add(value);
        }
    };
    
    final ArraySchemas.Int64Array ARRAY_INT64_PRIMITIVE_SCHEMA = 
            new ArraySchemas.Int64Array(this, null, true);
    final ArraySchemas.Int64Array ARRAY_INT64_BOXED_SCHEMA = 
            new ArraySchemas.Int64Array(this, null, false);
    final ArraySchemas.Int64Array ARRAY_INT64_DERIVED_SCHEMA = 
            new ArraySchemas.Int64Array(this, null, false)
    {
        @Override
        public Object readFrom(Input input, Object owner) throws IOException
        {
            if (ArraySchemas.ID_ARRAY_LEN != input.readFieldNumber(this))
                throw new ProtostuffException("Corrupt input.");
            
            int len = input.readInt32();
            return len >= 0 ? readPrimitiveFrom(input, owner, len) : 
                    readBoxedFrom(input, owner, -len - 1);
        }

        @Override
        protected void writeLengthTo(Output output, int len, boolean primitive)
                throws IOException
        {
            if (primitive)
                output.writeInt32(ArraySchemas.ID_ARRAY_LEN, len, false);
            else
                output.writeInt32(ArraySchemas.ID_ARRAY_LEN, -(len + 1), false);
        }
        
        @Override
        public void writeTo(Output output, Object value) throws IOException
        {
            writeTo(output, value, value.getClass().getComponentType().isPrimitive());
        }

        @Override
        @SuppressWarnings("unchecked")
        protected void setValue(Object value, Object owner)
        {
            if (MapWrapper.class == owner.getClass())
                ((MapWrapper) owner).setValue(value);
            else
                ((Collection) owner).add(value);
        }
    };
    
    final ArraySchemas.FloatArray ARRAY_FLOAT_PRIMITIVE_SCHEMA = 
            new ArraySchemas.FloatArray(this, null, true);
    final ArraySchemas.FloatArray ARRAY_FLOAT_BOXED_SCHEMA = 
            new ArraySchemas.FloatArray(this, null, false);
    final ArraySchemas.FloatArray ARRAY_FLOAT_DERIVED_SCHEMA = 
            new ArraySchemas.FloatArray(this, null, false)
    {
        @Override
        public Object readFrom(Input input, Object owner) throws IOException
        {
            if (ArraySchemas.ID_ARRAY_LEN != input.readFieldNumber(this))
                throw new ProtostuffException("Corrupt input.");
            
            int len = input.readInt32();
            return len >= 0 ? readPrimitiveFrom(input, owner, len) : 
                    readBoxedFrom(input, owner, -len - 1);
        }

        @Override
        protected void writeLengthTo(Output output, int len, boolean primitive)
                throws IOException
        {
            if (primitive)
                output.writeInt32(ArraySchemas.ID_ARRAY_LEN, len, false);
            else
                output.writeInt32(ArraySchemas.ID_ARRAY_LEN, -(len + 1), false);
        }
        
        @Override
        public void writeTo(Output output, Object value) throws IOException
        {
            writeTo(output, value, value.getClass().getComponentType().isPrimitive());
        }

        @Override
        @SuppressWarnings("unchecked")
        protected void setValue(Object value, Object owner)
        {
            if (MapWrapper.class == owner.getClass())
                ((MapWrapper) owner).setValue(value);
            else
                ((Collection) owner).add(value);
        }
    };
    
    final ArraySchemas.DoubleArray ARRAY_DOUBLE_PRIMITIVE_SCHEMA = 
            new ArraySchemas.DoubleArray(this, null, true);
    final ArraySchemas.DoubleArray ARRAY_DOUBLE_BOXED_SCHEMA = 
            new ArraySchemas.DoubleArray(this, null, false);
    final ArraySchemas.DoubleArray ARRAY_DOUBLE_DERIVED_SCHEMA = 
            new ArraySchemas.DoubleArray(this, null, false)
    {
        @Override
        public Object readFrom(Input input, Object owner) throws IOException
        {
            if (ArraySchemas.ID_ARRAY_LEN != input.readFieldNumber(this))
                throw new ProtostuffException("Corrupt input.");
            
            int len = input.readInt32();
            return len >= 0 ? readPrimitiveFrom(input, owner, len) : 
                    readBoxedFrom(input, owner, -len - 1);
        }

        @Override
        protected void writeLengthTo(Output output, int len, boolean primitive)
                throws IOException
        {
            if (primitive)
                output.writeInt32(ArraySchemas.ID_ARRAY_LEN, len, false);
            else
                output.writeInt32(ArraySchemas.ID_ARRAY_LEN, -(len + 1), false);
        }
        
        @Override
        public void writeTo(Output output, Object value) throws IOException
        {
            writeTo(output, value, value.getClass().getComponentType().isPrimitive());
        }

        @Override
        @SuppressWarnings("unchecked")
        protected void setValue(Object value, Object owner)
        {
            if (MapWrapper.class == owner.getClass())
                ((MapWrapper) owner).setValue(value);
            else
                ((Collection) owner).add(value);
        }
    };
    
    final ArraySchemas.StringArray ARRAY_STRING_SCHEMA = 
            new ArraySchemas.StringArray(this, null)
    {
        @Override
        @SuppressWarnings("unchecked")
        protected void setValue(Object value, Object owner)
        {
            if (MapWrapper.class == owner.getClass())
                ((MapWrapper) owner).setValue(value);
            else
                ((Collection) owner).add(value);
        }
    };
    
    final ArraySchemas.ByteStringArray ARRAY_BYTESTRING_SCHEMA = 
            new ArraySchemas.ByteStringArray(this, null)
    {
        @Override
        @SuppressWarnings("unchecked")
        protected void setValue(Object value, Object owner)
        {
            if (MapWrapper.class == owner.getClass())
                ((MapWrapper) owner).setValue(value);
            else
                ((Collection) owner).add(value);
        }
    };
    
    final ArraySchemas.ByteArrayArray ARRAY_BYTEARRAY_SCHEMA = 
            new ArraySchemas.ByteArrayArray(this, null)
    {
        @Override
        @SuppressWarnings("unchecked")
        protected void setValue(Object value, Object owner)
        {
            if (MapWrapper.class == owner.getClass())
                ((MapWrapper) owner).setValue(value);
            else
                ((Collection) owner).add(value);
        }
    };
    
    final ArraySchemas.BigDecimalArray ARRAY_BIGDECIMAL_SCHEMA = 
            new ArraySchemas.BigDecimalArray(this, null)
    {
        @Override
        @SuppressWarnings("unchecked")
        protected void setValue(Object value, Object owner)
        {
            if (MapWrapper.class == owner.getClass())
                ((MapWrapper) owner).setValue(value);
            else
                ((Collection) owner).add(value);
        }
    };
    
    final ArraySchemas.BigIntegerArray ARRAY_BIGINTEGER_SCHEMA = 
            new ArraySchemas.BigIntegerArray(this, null)
    {
        @Override
        @SuppressWarnings("unchecked")
        protected void setValue(Object value, Object owner)
        {
            if (MapWrapper.class == owner.getClass())
                ((MapWrapper) owner).setValue(value);
            else
                ((Collection) owner).add(value);
        }
    };
    
    final ArraySchemas.DateArray ARRAY_DATE_SCHEMA = 
            new ArraySchemas.DateArray(this, null)
    {
        @Override
        @SuppressWarnings("unchecked")
        protected void setValue(Object value, Object owner)
        {
            if (MapWrapper.class == owner.getClass())
                ((MapWrapper) owner).setValue(value);
            else
                ((Collection) owner).add(value);
        }
    };
    
    private static final class PMapWrapper implements Entry
    {

        final Map map;
        private Object value;

        PMapWrapper(Map map)
        {
            this.map = map;
        }

        @Override
        public Object getKey()
        {
            throw new UnsupportedOperationException();
        }

        @Override
        public Object getValue()
        {
            return value;
        }

        @Override
        public Object setValue(Object value)
        {
            final Object last = this.value;
            this.value = value;
            return last;
        }

    }
    
    static final class Wrapper
    {
        Object value;
    }

    protected static  T createMessageInstance(Class clazz)
    {
        try
        {
            return clazz.newInstance();
        }
        catch (InstantiationException e)
        {
            throw new RuntimeException(e);
        }
        catch (IllegalAccessException e)
        {
            // ignore
        }
        
        try
        {
            Constructor constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            return constructor.newInstance();
        }
        catch (NoSuchMethodException e)
        {
            throw new RuntimeException(e);
        }
        catch (InstantiationException e)
        {
            throw new RuntimeException(e);
        }
        catch (InvocationTargetException e)
        {
            throw new RuntimeException(e);
        }
        catch (IllegalAccessException e)
        {
            throw new RuntimeException(e);
        }
    }
}