private static <T> Field<T> createCollectionEnumV(
      int number,
      String name,
      final java.lang.reflect.Field f,
      final MessageFactory messageFactory,
      final Class<Object> genericType,
      IdStrategy strategy) {
    final EnumIO<?> eio = strategy.getEnumIO(genericType);
    return new Field<T>(FieldType.ENUM, number, name, true, f.getAnnotation(Tag.class)) {
      {
        f.setAccessible(true);
      }

      @SuppressWarnings("unchecked")
      protected void mergeFrom(Input input, T message) throws IOException {
        final Enum<?> value = eio.readFrom(input);
        try {
          final Collection<Enum<?>> existing = (Collection<Enum<?>>) f.get(message);
          if (existing == null) {
            final Collection<Enum<?>> collection = messageFactory.newMessage();
            collection.add(value);
            f.set(message, collection);
          } else existing.add(value);
        } catch (IllegalArgumentException e) {
          throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
          throw new RuntimeException(e);
        }
      }

      @SuppressWarnings("unchecked")
      protected void writeTo(Output output, T message) throws IOException {
        final Collection<Enum<?>> collection;
        try {
          collection = (Collection<Enum<?>>) f.get(message);
        } catch (IllegalArgumentException e) {
          throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
          throw new RuntimeException(e);
        }

        if (collection != null && !collection.isEmpty()) {
          for (Enum<?> en : collection) EnumIO.writeTo(output, number, true, en);
        }
      }

      protected void transfer(Pipe pipe, Input input, Output output, boolean repeated)
          throws IOException {
        EnumIO.transfer(pipe, input, output, number, repeated);
      }
    };
  }
  @SuppressWarnings("unchecked")
  static void writeObjectTo(
      Output output, Object value, Schema<?> currentSchema, IdStrategy strategy)
      throws IOException {
    if (Collections.class == value.getClass().getDeclaringClass()) {
      writeNonPublicCollectionTo(output, value, currentSchema, strategy);
      return;
    }

    if (EnumSet.class.isAssignableFrom(value.getClass())) {
      strategy.writeEnumIdTo(output, ID_ENUM_SET, EnumIO.getElementTypeFromEnumSet(value));

      // TODO optimize
    } else {
      strategy.writeCollectionIdTo(output, ID_COLLECTION, value.getClass());
    }

    if (output instanceof StatefulOutput) {
      // update using the derived schema.
      ((StatefulOutput) output).updateLast(strategy.COLLECTION_SCHEMA, currentSchema);
    }

    strategy.COLLECTION_SCHEMA.writeTo(output, (Collection<Object>) value);
  }
        @SuppressWarnings("unchecked")
        public <T> Field<T> create(
            int number, String name, final java.lang.reflect.Field f, IdStrategy strategy) {
          if (null != f.getAnnotation(Morph.class)) {
            // can be used to override the configured system property:
            // RuntimeEnv.COLLECTION_SCHEMA_ON_REPEATED_FIELDS

            // In this context, Morph annotation will force using a
            // collection
            // schema only for this particular field.
            return RuntimeCollectionFieldFactory.getFactory().create(number, name, f, strategy);
          }

          if (EnumSet.class.isAssignableFrom(f.getType())) {
            final Class<Object> enumType = (Class<Object>) getGenericType(f, 0);
            if (enumType == null) {
              // still handle the serialization of EnumSets even without
              // generics
              return RuntimeFieldFactory.OBJECT.create(number, name, f, strategy);
            }

            return createCollectionEnumV(
                number,
                name,
                f,
                strategy.getEnumIO(enumType).getEnumSetFactory(),
                enumType,
                strategy);
          }

          final MessageFactory messageFactory = strategy.getCollectionFactory(f.getType());

          final Class<Object> genericType = (Class<Object>) getGenericType(f, 0);
          if (genericType == null) {
            // the value is not a simple parameterized type.
            return createCollectionObjectV(
                number,
                name,
                f,
                messageFactory,
                genericType,
                PolymorphicSchemaFactories.OBJECT,
                strategy);
          }

          final Delegate<Object> inline = getDelegateOrInline(genericType, strategy);
          if (inline != null)
            return createCollectionInlineV(number, name, f, messageFactory, inline);

          if (Message.class.isAssignableFrom(genericType))
            return createCollectionPojoV(number, name, f, messageFactory, genericType, strategy);

          if (genericType.isEnum())
            return createCollectionEnumV(number, name, f, messageFactory, genericType, strategy);

          final PolymorphicSchema.Factory factory =
              PolymorphicSchemaFactories.getFactoryFromRepeatedValueGenericType(genericType);
          if (factory != null) {
            return createCollectionObjectV(
                number, name, f, messageFactory, genericType, factory, strategy);
          }

          if (pojo(genericType, f.getAnnotation(Morph.class), strategy))
            return createCollectionPojoV(number, name, f, messageFactory, genericType, strategy);

          if (genericType.isInterface()) {
            return createCollectionObjectV(
                number,
                name,
                f,
                messageFactory,
                genericType,
                PolymorphicSchemaFactories.OBJECT,
                strategy);
          }

          return createCollectionPolymorphicV(
              number, name, f, messageFactory, genericType, strategy);
        }
  private static <T> Field<T> createCollectionPojoV(
      int number,
      String name,
      final java.lang.reflect.Field f,
      final MessageFactory messageFactory,
      final Class<Object> genericType,
      IdStrategy strategy) {
    return new RuntimeMessageField<T, Object>(
        genericType,
        strategy.getSchemaWrapper(genericType, true),
        FieldType.MESSAGE,
        number,
        name,
        true,
        f.getAnnotation(Tag.class)) {

      {
        f.setAccessible(true);
      }

      @SuppressWarnings("unchecked")
      protected void mergeFrom(Input input, T message) throws IOException {
        final Object value = input.mergeObject(null, getSchema());
        try {
          final Collection<Object> existing = (Collection<Object>) f.get(message);
          if (existing == null) {
            final Collection<Object> collection = messageFactory.newMessage();
            collection.add(value);
            f.set(message, collection);
          } else existing.add(value);
        } catch (IllegalArgumentException e) {
          throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
          throw new RuntimeException(e);
        }
      }

      @SuppressWarnings("unchecked")
      protected void writeTo(Output output, T message) throws IOException {
        final Collection<Object> collection;
        try {
          collection = (Collection<Object>) f.get(message);
        } catch (IllegalArgumentException e) {
          throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
          throw new RuntimeException(e);
        }

        if (collection != null && !collection.isEmpty()) {
          final Schema<Object> schema = getSchema();
          for (Object o : collection) {
            if (o != null) output.writeObject(number, o, schema, true);
          }
        }
      }

      protected void transfer(Pipe pipe, Input input, Output output, boolean repeated)
          throws IOException {
        output.writeObject(number, pipe, getPipeSchema(), repeated);
      }
    };
  }
  @SuppressWarnings("unchecked")
  static Object readObjectFrom(
      Input input, Schema<?> schema, Object owner, IdStrategy strategy, final int number)
      throws IOException {
    final boolean graph = input instanceof GraphInput;
    Object ret = null;
    switch (number) {
      case ID_EMPTY_SET:
        if (0 != input.readUInt32()) throw new ProtostuffException("Corrupt input.");

        if (graph) {
          // update the actual reference.
          ((GraphInput) input).updateLast(Collections.EMPTY_SET, owner);
        }

        ret = Collections.EMPTY_SET;
        break;

      case ID_EMPTY_LIST:
        if (0 != input.readUInt32()) throw new ProtostuffException("Corrupt input.");

        if (graph) {
          // update the actual reference.
          ((GraphInput) input).updateLast(Collections.EMPTY_LIST, owner);
        }

        ret = Collections.EMPTY_LIST;
        break;

      case ID_SINGLETON_SET:
        {
          if (0 != input.readUInt32()) throw new ProtostuffException("Corrupt input.");

          final Object collection = iSingletonSet.newInstance();
          if (graph) {
            // update the actual reference.
            ((GraphInput) input).updateLast(collection, owner);
          }

          final int next = input.readFieldNumber(schema);
          if (next == 0) {
            // null element
            return collection;
          }

          if (next != 1) throw new ProtostuffException("Corrupt input");

          final Wrapper wrapper = new Wrapper();
          Object element = input.mergeObject(wrapper, strategy.OBJECT_SCHEMA);
          if (!graph || !((GraphInput) input).isCurrentMessageReference()) element = wrapper.value;

          try {
            fSingletonSet_element.set(collection, element);
          } catch (Exception e) {
            throw new RuntimeException(e);
          }

          ret = collection;
          break;
        }

      case ID_SINGLETON_LIST:
        {
          if (0 != input.readUInt32()) throw new ProtostuffException("Corrupt input.");

          final Object collection = iSingletonList.newInstance();
          if (graph) {
            // update the actual reference.
            ((GraphInput) input).updateLast(collection, owner);
          }

          final int next = input.readFieldNumber(schema);
          if (next == 0) {
            // null element
            return collection;
          }

          if (next != 1) throw new ProtostuffException("Corrupt input.");

          final Wrapper wrapper = new Wrapper();
          Object element = input.mergeObject(wrapper, strategy.OBJECT_SCHEMA);
          if (!graph || !((GraphInput) input).isCurrentMessageReference()) element = wrapper.value;

          try {
            fSingletonList_element.set(collection, element);
          } catch (Exception e) {
            throw new RuntimeException(e);
          }

          ret = collection;
          break;
        }

      case ID_SET_FROM_MAP:
        {
          final Object collection = iSetFromMap.newInstance();
          if (graph) {
            // update the actual reference.
            ((GraphInput) input).updateLast(collection, owner);
          }

          final Wrapper wrapper = new Wrapper();
          Object m = input.mergeObject(wrapper, strategy.POLYMORPHIC_MAP_SCHEMA);
          if (!graph || !((GraphInput) input).isCurrentMessageReference()) m = wrapper.value;

          try {
            fSetFromMap_m.set(collection, m);
            fSetFromMap_s.set(collection, ((Map<?, ?>) m).keySet());
          } catch (Exception e) {
            throw new RuntimeException(e);
          }

          ret = collection;
          break;
        }

      case ID_COPIES_LIST:
        {
          if (0 != input.readUInt32()) throw new ProtostuffException("Corrupt input.");

          final Object collection = iCopiesList.newInstance();
          if (graph) {
            // update the actual reference.
            ((GraphInput) input).updateLast(collection, owner);
          }

          if (1 != input.readFieldNumber(schema)) throw new ProtostuffException("Corrupt input.");

          final int n = input.readUInt32(), next = input.readFieldNumber(schema);

          if (next == 0) {
            // null element
            try {
              fCopiesList_n.setInt(collection, n);
            } catch (Exception e) {
              throw new RuntimeException(e);
            }

            return collection;
          }

          if (next != 2) throw new ProtostuffException("Corrupt input.");

          final Wrapper wrapper = new Wrapper();
          Object element = input.mergeObject(wrapper, strategy.OBJECT_SCHEMA);
          if (!graph || !((GraphInput) input).isCurrentMessageReference()) element = wrapper.value;

          try {
            fCopiesList_n.setInt(collection, n);
            fCopiesList_element.set(collection, element);
          } catch (Exception e) {
            throw new RuntimeException(e);
          }

          ret = collection;
          break;
        }

      case ID_UNMODIFIABLE_COLLECTION:
        ret =
            readUnmodifiableCollectionFrom(
                input,
                schema,
                owner,
                strategy,
                graph,
                iUnmodifiableCollection.newInstance(),
                false,
                false);
        break;
      case ID_UNMODIFIABLE_SET:
        ret =
            readUnmodifiableCollectionFrom(
                input,
                schema,
                owner,
                strategy,
                graph,
                iUnmodifiableSet.newInstance(),
                false,
                false);
        break;
      case ID_UNMODIFIABLE_SORTED_SET:
        ret =
            readUnmodifiableCollectionFrom(
                input,
                schema,
                owner,
                strategy,
                graph,
                iUnmodifiableSortedSet.newInstance(),
                true,
                false);
        break;
      case ID_UNMODIFIABLE_LIST:
        ret =
            readUnmodifiableCollectionFrom(
                input,
                schema,
                owner,
                strategy,
                graph,
                iUnmodifiableList.newInstance(),
                false,
                true);
        break;
      case ID_UNMODIFIABLE_RANDOM_ACCESS_LIST:
        ret =
            readUnmodifiableCollectionFrom(
                input,
                schema,
                owner,
                strategy,
                graph,
                iUnmodifiableRandomAccessList.newInstance(),
                false,
                true);
        break;

      case ID_SYNCHRONIZED_COLLECTION:
        ret =
            readSynchronizedCollectionFrom(
                input,
                schema,
                owner,
                strategy,
                graph,
                iSynchronizedCollection.newInstance(),
                false,
                false);
        break;
      case ID_SYNCHRONIZED_SET:
        ret =
            readSynchronizedCollectionFrom(
                input,
                schema,
                owner,
                strategy,
                graph,
                iSynchronizedSet.newInstance(),
                false,
                false);
        break;
      case ID_SYNCHRONIZED_SORTED_SET:
        ret =
            readSynchronizedCollectionFrom(
                input,
                schema,
                owner,
                strategy,
                graph,
                iSynchronizedSortedSet.newInstance(),
                true,
                false);
        break;
      case ID_SYNCHRONIZED_LIST:
        ret =
            readSynchronizedCollectionFrom(
                input,
                schema,
                owner,
                strategy,
                graph,
                iSynchronizedList.newInstance(),
                false,
                true);
        break;
      case ID_SYNCHRONIZED_RANDOM_ACCESS_LIST:
        ret =
            readSynchronizedCollectionFrom(
                input,
                schema,
                owner,
                strategy,
                graph,
                iSynchronizedRandomAccessList.newInstance(),
                false,
                true);
        break;

      case ID_CHECKED_COLLECTION:
        ret =
            readCheckedCollectionFrom(
                input,
                schema,
                owner,
                strategy,
                graph,
                iCheckedCollection.newInstance(),
                false,
                false);
        break;
      case ID_CHECKED_SET:
        ret =
            readCheckedCollectionFrom(
                input, schema, owner, strategy, graph, iCheckedSet.newInstance(), false, false);
        break;
      case ID_CHECKED_SORTED_SET:
        ret =
            readCheckedCollectionFrom(
                input,
                schema,
                owner,
                strategy,
                graph,
                iCheckedSortedSet.newInstance(),
                true,
                false);
        break;
      case ID_CHECKED_LIST:
        ret =
            readCheckedCollectionFrom(
                input, schema, owner, strategy, graph, iCheckedList.newInstance(), false, true);
        break;
      case ID_CHECKED_RANDOM_ACCESS_LIST:
        ret =
            readCheckedCollectionFrom(
                input,
                schema,
                owner,
                strategy,
                graph,
                iCheckedRandomAccessList.newInstance(),
                false,
                true);
        break;

      case ID_ENUM_SET:
        {
          final Collection<?> es = strategy.resolveEnumFrom(input).newEnumSet();

          if (graph) {
            // update the actual reference.
            ((GraphInput) input).updateLast(es, owner);
          }

          // TODO enum schema
          strategy.COLLECTION_SCHEMA.mergeFrom(input, (Collection<Object>) es);
          return es;
        }

      case ID_COLLECTION:
        {
          final Collection<Object> collection = strategy.resolveCollectionFrom(input).newMessage();

          if (graph) {
            // update the actual reference.
            ((GraphInput) input).updateLast(collection, owner);
          }

          strategy.COLLECTION_SCHEMA.mergeFrom(input, collection);

          return collection;
        }

      default:
        throw new ProtostuffException("Corrupt input.");
    }

    if (0 != input.readFieldNumber(schema)) throw new ProtostuffException("Corrupt input.");

    return ret;
  }
  static void transferObject(
      Pipe.Schema<Object> pipeSchema,
      Pipe pipe,
      Input input,
      Output output,
      IdStrategy strategy,
      final int number)
      throws IOException {
    switch (number) {
      case ID_EMPTY_SET:
        output.writeUInt32(number, input.readUInt32(), false);
        break;

      case ID_EMPTY_LIST:
        output.writeUInt32(number, input.readUInt32(), false);
        break;

      case ID_SINGLETON_SET:
      case ID_SINGLETON_LIST:
        {
          output.writeUInt32(number, input.readUInt32(), false);

          final int next = input.readFieldNumber(pipeSchema.wrappedSchema);
          if (next == 0) {
            // null element
            return;
          }

          if (next != 1) throw new ProtostuffException("Corrupt input.");

          output.writeObject(1, pipe, strategy.OBJECT_PIPE_SCHEMA, false);
          break;
        }
      case ID_SET_FROM_MAP:
        output.writeObject(number, pipe, strategy.POLYMORPHIC_MAP_PIPE_SCHEMA, false);
        break;

      case ID_COPIES_LIST:
        {
          output.writeUInt32(number, input.readUInt32(), false);

          if (1 != input.readFieldNumber(pipeSchema.wrappedSchema))
            throw new ProtostuffException("Corrupt input.");

          // size
          output.writeUInt32(1, input.readUInt32(), false);

          final int next = input.readFieldNumber(pipeSchema.wrappedSchema);
          if (next == 0) {
            // null element
            return;
          }

          if (next != 2) throw new ProtostuffException("Corrupt input.");

          output.writeObject(2, pipe, strategy.OBJECT_PIPE_SCHEMA, false);
          break;
        }
      case ID_UNMODIFIABLE_COLLECTION:
      case ID_UNMODIFIABLE_SET:
      case ID_UNMODIFIABLE_SORTED_SET:
      case ID_UNMODIFIABLE_LIST:
      case ID_UNMODIFIABLE_RANDOM_ACCESS_LIST:
        output.writeObject(number, pipe, strategy.POLYMORPHIC_COLLECTION_PIPE_SCHEMA, false);
        break;

      case ID_SYNCHRONIZED_COLLECTION:
      case ID_SYNCHRONIZED_SET:
      case ID_SYNCHRONIZED_SORTED_SET:
      case ID_SYNCHRONIZED_LIST:
      case ID_SYNCHRONIZED_RANDOM_ACCESS_LIST:
        output.writeObject(number, pipe, strategy.POLYMORPHIC_COLLECTION_PIPE_SCHEMA, false);
        break;

      case ID_CHECKED_COLLECTION:
      case ID_CHECKED_SET:
      case ID_CHECKED_SORTED_SET:
      case ID_CHECKED_LIST:
      case ID_CHECKED_RANDOM_ACCESS_LIST:
        output.writeObject(number, pipe, strategy.POLYMORPHIC_COLLECTION_PIPE_SCHEMA, false);

        if (1 != input.readFieldNumber(pipeSchema.wrappedSchema))
          throw new ProtostuffException("Corrupt input.");

        output.writeObject(1, pipe, strategy.CLASS_PIPE_SCHEMA, false);
        break;

      case ID_ENUM_SET:
        strategy.transferEnumId(input, output, number);

        if (output instanceof StatefulOutput) {
          // update using the derived schema.
          ((StatefulOutput) output).updateLast(strategy.COLLECTION_PIPE_SCHEMA, pipeSchema);
        }

        // TODO use enum schema
        Pipe.transferDirect(strategy.COLLECTION_PIPE_SCHEMA, pipe, input, output);
        return;
      case ID_COLLECTION:
        strategy.transferCollectionId(input, output, number);

        if (output instanceof StatefulOutput) {
          // update using the derived schema.
          ((StatefulOutput) output).updateLast(strategy.COLLECTION_PIPE_SCHEMA, pipeSchema);
        }

        Pipe.transferDirect(strategy.COLLECTION_PIPE_SCHEMA, pipe, input, output);
        return;
      default:
        throw new ProtostuffException("Corrupt input.");
    }

    if (0 != input.readFieldNumber(pipeSchema.wrappedSchema))
      throw new ProtostuffException("Corrupt input.");
  }