SerIteratorFactory.java

/*
 *  Copyright 2001-present Stephen Colebourne
 *
 *  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 org.joda.beans.ser;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

import org.joda.beans.JodaBeanUtils;
import org.joda.beans.MetaProperty;

/**
 * A factory used to create wrappers around collection-like objects.
 */
public class SerIteratorFactory {

    /**
     * Singleton instance.
     */
    public static final SerIteratorFactory INSTANCE = getInstance();
    private static SerIteratorFactory getInstance() {
        try {
            Class.forName("org.joda.collect.grid.Grid");
            return new CollectSerIteratorFactory();
        } catch (Exception | LinkageError ex) {
            try {
                Class.forName("com.google.common.collect.Multimap");
                return new GuavaSerIteratorFactory();
            } catch (Exception | LinkageError ex2) {
                return new SerIteratorFactory();
            }
        }
    }
    /**
     * An empty list of classes.
     */
    public static final List<Class<?>> EMPTY_VALUE_TYPES = Collections.emptyList();
    /**
     * Map of array types.
     */
    private static final Map<String, Class<?>> META_TYPE_MAP = new HashMap<>();
    static {
        META_TYPE_MAP.put("Object[]", Object.class);
        META_TYPE_MAP.put("String[]", String.class);
        META_TYPE_MAP.put("boolean[]", boolean.class);
        META_TYPE_MAP.put("char[]", char.class);
        META_TYPE_MAP.put("byte[]", byte.class);
        META_TYPE_MAP.put("short[]", short.class);
        META_TYPE_MAP.put("int[]", int.class);
        META_TYPE_MAP.put("long[]", long.class);
        META_TYPE_MAP.put("float[]", float.class);
        META_TYPE_MAP.put("double[]", double.class);
        META_TYPE_MAP.put("Object[][]", Object[].class);
        META_TYPE_MAP.put("String[][]", String[].class);
        META_TYPE_MAP.put("boolean[][]", boolean[].class);
        META_TYPE_MAP.put("char[][]", char[].class);
        META_TYPE_MAP.put("byte[][]", byte[].class);
        META_TYPE_MAP.put("short[][]", short[].class);
        META_TYPE_MAP.put("int[][]", int[].class);
        META_TYPE_MAP.put("long[][]", long[].class);
        META_TYPE_MAP.put("float[][]", float[].class);
        META_TYPE_MAP.put("double[][]", double[].class);
    }

    //-----------------------------------------------------------------------
    /**
     * Creates an iterator wrapper for a meta-property value.
     * 
     * @param value  the possible collection-like object, not null
     * @param prop  the meta-property defining the value, not null
     * @param beanClass  the class of the bean, not the meta-property, for better generics, not null
     * @param allowPrimitiveArrays  whether to allow primitive arrays
     * @return the iterator, null if not a collection-like type
     */
    public SerIterator create(Object value, MetaProperty<?> prop, Class<?> beanClass, boolean allowPrimitiveArrays) {
        if (allowPrimitiveArrays &&
                value.getClass().isArray() &&
                value.getClass().getComponentType().isPrimitive() &&
                value.getClass().getComponentType() != byte.class) {
            return arrayPrimitive(value, prop.propertyType(), value.getClass().getComponentType());
        }
        return create(value, prop, beanClass);
    }

    /**
     * Creates an iterator wrapper for a meta-property value.
     * 
     * @param value  the possible collection-like object, not null
     * @param prop  the meta-property defining the value, not null
     * @param beanClass  the class of the bean, not the meta-property, for better generics, not null
     * @return the iterator, null if not a collection-like type
     */
    public SerIterator create(Object value, MetaProperty<?> prop, Class<?> beanClass) {
        Class<?> declaredType = prop.propertyType();
        if (value instanceof Collection) {
            Class<?> valueType = defaultToObjectClass(JodaBeanUtils.collectionType(prop, beanClass));
            List<Class<?>> valueTypeTypes = JodaBeanUtils.collectionTypeTypes(prop, beanClass);
            return collection((Collection<?>) value, declaredType, valueType, valueTypeTypes);
        }
        if (value instanceof Map) {
            Class<?> keyType = defaultToObjectClass(JodaBeanUtils.mapKeyType(prop, beanClass));
            Class<?> valueType = defaultToObjectClass(JodaBeanUtils.mapValueType(prop, beanClass));
            List<Class<?>> valueTypeTypes = JodaBeanUtils.mapValueTypeTypes(prop, beanClass);
            return map((Map<?, ?>) value, declaredType, keyType, valueType, valueTypeTypes);
        }
        if (value.getClass().isArray() && value.getClass().getComponentType().isPrimitive() == false) {
            Object[] array = (Object[]) value;
            return array(array, declaredType, array.getClass().getComponentType());
        }
        return null;
    }

    /**
     * Creates an iterator wrapper for a value retrieved from a parent iterator.
     * <p>
     * Allows the parent iterator to define the child iterator using generic type information.
     * This handles cases such as a {@code List} as the value in a {@code Map}.
     * 
     * @param value  the possible collection-like object, not null
     * @param parent  the parent iterator, not null
     * @return the iterator, null if not a collection-like type
     */
    public SerIterator createChild(Object value, SerIterator parent) {
        Class<?> declaredType = parent.valueType();
        List<Class<?>> childGenericTypes = parent.valueTypeTypes();
        if (value instanceof Collection) {
            if (childGenericTypes.size() == 1) {
                return collection((Collection<?>) value, declaredType, childGenericTypes.get(0), EMPTY_VALUE_TYPES);
            }
            return collection((Collection<?>) value, Object.class, Object.class, EMPTY_VALUE_TYPES);
        }
        if (value instanceof Map) {
            if (childGenericTypes.size() == 2) {
                return map((Map<?, ?>) value, declaredType, childGenericTypes.get(0), childGenericTypes.get(1), EMPTY_VALUE_TYPES);
            }
            return map((Map<?, ?>) value, Object.class, Object.class, Object.class, EMPTY_VALUE_TYPES);
        }
        if (value.getClass().isArray() && value.getClass().getComponentType().isPrimitive() == false) {
            Object[] array = (Object[]) value;
            return array(array, Object.class, value.getClass().getComponentType());
        }
        return null;
    }

    /**
     * Defaults input class to Object class.
     * 
     * @param type  the type, may be null
     * @return the type, not null
     */
    protected Class<?> defaultToObjectClass(Class<?> type) {
        return (type != null ? type : Object.class);
    }

    //-----------------------------------------------------------------------
    /**
     * Creates an iterator wrapper for a meta-type description.
     * 
     * @param metaTypeDescription  the description of the collection type, not null
     * @param settings  the settings object, not null
     * @param knownTypes  the known types map, null if not using known type shortening
     * @return the iterable, null if not a collection-like type
     */
    public SerIterable createIterable(String metaTypeDescription, JodaBeanSer settings, Map<String, Class<?>> knownTypes) {
        if (metaTypeDescription.equals("Set")) {
            return set(Object.class, EMPTY_VALUE_TYPES);
        }
        if (metaTypeDescription.equals("List")) {
            return list(Object.class, EMPTY_VALUE_TYPES);
        }
        if (metaTypeDescription.equals("Collection")) {
            return list(Object.class, EMPTY_VALUE_TYPES);
        }
        if (metaTypeDescription.equals("Map")) {
            return map(Object.class, Object.class, EMPTY_VALUE_TYPES);
        }
        if (metaTypeDescription.endsWith("[][][]")) {
            throw new IllegalArgumentException("Three-dimensional arrays cannot be parsed");
        }
        if (metaTypeDescription.endsWith("[][]")) {
            Class<?> type = META_TYPE_MAP.get(metaTypeDescription);
            if (type != null) {
                return array(type);
            }
            String clsStr = metaTypeDescription.substring(0, metaTypeDescription.length() - 4);
            try {
                Class<?> cls = SerTypeMapper.decodeType(clsStr, settings, null, knownTypes);
                String compound = "[L" + cls.getName() + ";";
                return array(Class.forName(compound));  // needs to be Class.forName
            } catch (ClassNotFoundException ex) {
                throw new RuntimeException(ex);
            }
        }
        if (metaTypeDescription.endsWith("[]")) {
            Class<?> type = META_TYPE_MAP.get(metaTypeDescription);
            if (type == null) {
                String clsStr = metaTypeDescription.substring(0, metaTypeDescription.length() - 2);
                try {
                    type = SerTypeMapper.decodeType(clsStr, settings, null, knownTypes);
                } catch (ClassNotFoundException ex) {
                    throw new RuntimeException(ex);
                }
            }
            return type.isPrimitive() ? arrayPrimitive(type) : array(type);
        }
        return null;
    }

    /**
     * Creates an iterator wrapper for a child where there are second level generic parameters.
     * 
     * @param iterable  the parent iterable, not null
     * @return the iterable, null if not a collection-like type
     */
    public SerIterable createIterable(SerIterable iterable) {
        List<Class<?>> valueTypeTypes = iterable.valueTypeTypes();
        if (valueTypeTypes.size() > 0) {
            Class<?> valueType = iterable.valueType();
            if (NavigableSet.class.isAssignableFrom(valueType)) {
                return navigableSet(valueTypeTypes.get(0), EMPTY_VALUE_TYPES);
            }
            if (SortedSet.class.isAssignableFrom(valueType)) {
                return sortedSet(valueTypeTypes.get(0), EMPTY_VALUE_TYPES);
            }
            if (Set.class.isAssignableFrom(valueType)) {
                return set(valueTypeTypes.get(0), EMPTY_VALUE_TYPES);
            }
            if (Collection.class.isAssignableFrom(valueType)) {  // includes List
                return list(valueTypeTypes.get(0), EMPTY_VALUE_TYPES);
            }
            if (NavigableMap.class.isAssignableFrom(valueType)) {
                if (valueTypeTypes.size() == 2) {
                    return navigableMap(valueTypeTypes.get(0), valueTypeTypes.get(1), EMPTY_VALUE_TYPES);
                }
                return navigableMap(Object.class, Object.class, EMPTY_VALUE_TYPES);
            }
            if (SortedMap.class.isAssignableFrom(valueType)) {
                if (valueTypeTypes.size() == 2) {
                    return sortedMap(valueTypeTypes.get(0), valueTypeTypes.get(1), EMPTY_VALUE_TYPES);
                }
                return sortedMap(Object.class, Object.class, EMPTY_VALUE_TYPES);
            }
            if (Map.class.isAssignableFrom(valueType)) {
                if (valueTypeTypes.size() == 2) {
                    return map(valueTypeTypes.get(0), valueTypeTypes.get(1), EMPTY_VALUE_TYPES);
                }
                return map(Object.class, Object.class, EMPTY_VALUE_TYPES);
            }
            if (valueType.isArray()) {
                if (valueType.getComponentType().isPrimitive()) {
                    return arrayPrimitive(valueType.getComponentType());
                } else {
                    return array(valueType.getComponentType());
                }
            }
        }
        return null;
    }

    /**
     * Creates an iterator wrapper for a meta-property value.
     * 
     * @param prop  the meta-property defining the value, not null
     * @param beanClass  the class of the bean, not the meta-property, for better generics, not null
     * @param allowPrimitiveArrays  whether to allow primitive arrays
     * @return the iterable, null if not a collection-like type
     */
    public SerIterable createIterable(MetaProperty<?> prop, Class<?> beanClass, boolean allowPrimitiveArrays) {
        if (allowPrimitiveArrays &&
                prop.propertyType().isArray() &&
                prop.propertyType().getComponentType().isPrimitive() &&
                prop.propertyType().getComponentType() != byte.class) {
            return arrayPrimitive(prop.propertyType().getComponentType());
        }
        return createIterable(prop, beanClass);
    }

    /**
     * Creates an iterator wrapper for a meta-property value.
     * 
     * @param prop  the meta-property defining the value, not null
     * @param beanClass  the class of the bean, not the meta-property, for better generics, not null
     * @return the iterable, null if not a collection-like type
     */
    public SerIterable createIterable(MetaProperty<?> prop, Class<?> beanClass) {
        if (NavigableSet.class.isAssignableFrom(prop.propertyType())) {
            Class<?> valueType = JodaBeanUtils.collectionType(prop, beanClass);
            List<Class<?>> valueTypeTypes = JodaBeanUtils.collectionTypeTypes(prop, beanClass);
            return navigableSet(valueType, valueTypeTypes);
        }
        if (SortedSet.class.isAssignableFrom(prop.propertyType())) {
            Class<?> valueType = JodaBeanUtils.collectionType(prop, beanClass);
            List<Class<?>> valueTypeTypes = JodaBeanUtils.collectionTypeTypes(prop, beanClass);
            return sortedSet(valueType, valueTypeTypes);
        }
        if (Set.class.isAssignableFrom(prop.propertyType())) {
            Class<?> valueType = JodaBeanUtils.collectionType(prop, beanClass);
            List<Class<?>> valueTypeTypes = JodaBeanUtils.collectionTypeTypes(prop, beanClass);
            return set(valueType, valueTypeTypes);
        }
        if (Collection.class.isAssignableFrom(prop.propertyType())) {  // includes List
            Class<?> valueType = JodaBeanUtils.collectionType(prop, beanClass);
            List<Class<?>> valueTypeTypes = JodaBeanUtils.collectionTypeTypes(prop, beanClass);
            return list(valueType, valueTypeTypes);
        }
        if (NavigableMap.class.isAssignableFrom(prop.propertyType())) {
            Class<?> keyType = JodaBeanUtils.mapKeyType(prop, beanClass);
            Class<?> valueType = JodaBeanUtils.mapValueType(prop, beanClass);
            List<Class<?>> valueTypeTypes = JodaBeanUtils.mapValueTypeTypes(prop, beanClass);
            return navigableMap(keyType, valueType, valueTypeTypes);
        }
        if (SortedMap.class.isAssignableFrom(prop.propertyType())) {
            Class<?> keyType = JodaBeanUtils.mapKeyType(prop, beanClass);
            Class<?> valueType = JodaBeanUtils.mapValueType(prop, beanClass);
            List<Class<?>> valueTypeTypes = JodaBeanUtils.mapValueTypeTypes(prop, beanClass);
            return sortedMap(keyType, valueType, valueTypeTypes);
        }
        if (Map.class.isAssignableFrom(prop.propertyType())) {
            Class<?> keyType = JodaBeanUtils.mapKeyType(prop, beanClass);
            Class<?> valueType = JodaBeanUtils.mapValueType(prop, beanClass);
            List<Class<?>> valueTypeTypes = JodaBeanUtils.mapValueTypeTypes(prop, beanClass);
            return map(keyType, valueType, valueTypeTypes);
        }
        if (prop.propertyType().isArray() && prop.propertyType().getComponentType().isPrimitive() == false) {
            return array(prop.propertyType().getComponentType());
        }
        return null;
    }

    //-----------------------------------------------------------------------
    /**
     * Gets an iterable wrapper for {@code List}.
     * 
     * @param valueType  the value type, not null
     * @param valueTypeTypes  the generic parameters of the value type
     * @return the iterable, not null
     */
    public static final SerIterable list(
            final Class<?> valueType, final List<Class<?>> valueTypeTypes) {
        final List<Object> coll = new ArrayList<>();
        return new SerIterable() {
            @Override
            public SerIterator iterator() {
                return collection(coll, Object.class, valueType, valueTypeTypes);
            }
            @Override
            public void add(Object key, Object column, Object value, int count) {
                if (key != null) {
                    throw new IllegalArgumentException("Unexpected key");
                }
                for (int i = 0; i < count; i++) {
                    coll.add(value);
                }
            }
            @Override
            public Object build() {
                return coll;
            }
            @Override
            public Class<?> valueType() {
                return valueType;
            }
            @Override
            public List<Class<?>> valueTypeTypes() {
                return valueTypeTypes;
            }
        };
    }

    /**
     * Gets an iterable wrapper for {@code Set}.
     * 
     * @param valueType  the value type, not null
     * @param valueTypeTypes  the generic parameters of the value type
     * @return the iterable, not null
     */
    public static final SerIterable set(final Class<?> valueType, final List<Class<?>> valueTypeTypes) {
        final Set<Object> coll = new HashSet<>();
        return set(valueType, valueTypeTypes, coll);
    }

    /**
     * Gets an iterable wrapper for {@code SortedSet}.
     * 
     * @param valueType  the value type, not null
     * @param valueTypeTypes  the generic parameters of the value type
     * @return the iterable, not null
     */
    public static final SerIterable sortedSet(final Class<?> valueType, final List<Class<?>> valueTypeTypes) {
        final SortedSet<Object> coll = new TreeSet<>();
        return set(valueType, valueTypeTypes, coll);
    }

    /**
     * Gets an iterable wrapper for {@code NavigableSet}.
     * 
     * @param valueType  the value type, not null
     * @param valueTypeTypes  the generic parameters of the value type
     * @return the iterable, not null
     */
    public static final SerIterable navigableSet(final Class<?> valueType, final List<Class<?>> valueTypeTypes) {
        final NavigableSet<Object> coll = new TreeSet<>();
        return set(valueType, valueTypeTypes, coll);
    }

    private static SerIterable set(
            final Class<?> valueType, final List<Class<?>> valueTypeTypes, final Set<Object> coll) {
        return new SerIterable() {
            @Override
            public SerIterator iterator() {
                return collection(coll, Object.class, valueType, valueTypeTypes);
            }
            @Override
            public void add(Object key, Object column, Object value, int count) {
                if (key != null) {
                    throw new IllegalArgumentException("Unexpected key");
                }
                for (int i = 0; i < count; i++) {
                    coll.add(value);
                }
            }
            @Override
            public Object build() {
                return coll;
            }
            @Override
            public Class<?> valueType() {
                return valueType;
            }
            @Override
            public List<Class<?>> valueTypeTypes() {
                return valueTypeTypes;
            }
        };
    }

    /**
     * Gets an iterator wrapper for {@code Collection}.
     * 
     * @param coll  the collection, not null
     * @param declaredType  the declared type, not null
     * @param valueType  the value type, not null
     * @param valueTypeTypes  the generic parameters of the value type
     * @return the iterator, not null
     */
    @SuppressWarnings("rawtypes")
    public static final SerIterator collection(
            final Collection<?> coll, final Class<?> declaredType, final Class<?> valueType, final List<Class<?>> valueTypeTypes) {
        return new SerIterator() {
            private final Iterator it = coll.iterator();
            private Object current;

            @Override
            public String metaTypeName() {
                if (coll instanceof Set) {
                    return "Set";
                }
                if (coll instanceof List) {
                    return "List";
                }
                return "Collection";
            }
            @Override
            public boolean metaTypeRequired() {
                if (coll instanceof Set) {
                    return Set.class.isAssignableFrom(declaredType) == false;
                }
                if (coll instanceof List) {
                    return List.class.isAssignableFrom(declaredType) == false;
                }
                return Collection.class.isAssignableFrom(declaredType) == false;
            }
            @Override
            public int size() {
                return coll.size();
            }
            @Override
            public boolean hasNext() {
                return it.hasNext();
            }
            @Override
            public void next() {
                current = it.next();
            }
            @Override
            public Class<?> valueType() {
                return valueType;
            }
            @Override
            public List<Class<?>> valueTypeTypes() {
                return valueTypeTypes;
            }
            @Override
            public Object value() {
                return current;
            }
        };
    }

    //-----------------------------------------------------------------------
    /**
     * Gets an iterable wrapper for {@code Map}.
     * 
     * @param keyType  the value type, not null
     * @param valueType  the value type, not null
     * @param valueTypeTypes  the generic parameters of the value type
     * @return the iterable, not null
     */
    public static final SerIterable map(final Class<?> keyType, final Class<?> valueType, final List<Class<?>> valueTypeTypes) {
        final Map<Object, Object> map = new HashMap<>();
        return map(keyType, valueType, valueTypeTypes, map);
    }

    /**
     * Gets an iterable wrapper for {@code SortedMap}.
     * 
     * @param keyType  the value type, not null
     * @param valueType  the value type, not null
     * @param valueTypeTypes  the generic parameters of the value type
     * @return the iterable, not null
     */
    public static final SerIterable sortedMap(final Class<?> keyType, final Class<?> valueType, final List<Class<?>> valueTypeTypes) {
        final SortedMap<Object, Object> map = new TreeMap<>();
        return map(keyType, valueType, valueTypeTypes, map);
    }

    /**
     * Gets an iterable wrapper for {@code NavigableMap}.
     * 
     * @param keyType  the value type, not null
     * @param valueType  the value type, not null
     * @param valueTypeTypes  the generic parameters of the value type
     * @return the iterable, not null
     */
    public static final SerIterable navigableMap(final Class<?> keyType, final Class<?> valueType, final List<Class<?>> valueTypeTypes) {
        final NavigableMap<Object, Object> map = new TreeMap<>();
        return map(keyType, valueType, valueTypeTypes, map);
    }

    static SerIterable map(
            final Class<?> keyType, final Class<?> valueType,
            final List<Class<?>> valueTypeTypes, final Map<Object, Object> map) {
        return new SerIterable() {
            @Override
            public SerIterator iterator() {
                return map(map, Object.class, keyType, valueType, valueTypeTypes);
            }
            @Override
            public void add(Object key, Object column, Object value, int count) {
                if (key == null) {
                    throw new IllegalArgumentException("Missing key");
                }
                if (count != 1) {
                    throw new IllegalArgumentException("Unexpected count");
                }
                map.put(key, value);
            }
            @Override
            public Object build() {
                return map;
            }
            @Override
            public SerCategory category() {
                return SerCategory.MAP;
            }
            @Override
            public Class<?> keyType() {
                return keyType;
            }
            @Override
            public Class<?> valueType() {
                return valueType;
            }
            @Override
            public List<Class<?>> valueTypeTypes() {
                return valueTypeTypes;
            }
        };
    }

    /**
     * Gets an iterator wrapper for {@code Map}.
     * 
     * @param map  the collection, not null
     * @param declaredType  the declared type, not null
     * @param keyType  the value type, not null
     * @param valueType  the value type, not null
     * @param valueTypeTypes  the generic parameters of the value type
     * @return the iterator, not null
     */
    @SuppressWarnings("rawtypes")
    public static final SerIterator map(
            final Map<?, ?> map, final Class<?> declaredType,
            final Class<?> keyType, final Class<?> valueType, final List<Class<?>> valueTypeTypes) {
        return new SerIterator() {
            private final Iterator it = map.entrySet().iterator();
            private Entry current;

            @Override
            public String metaTypeName() {
                return "Map";
            }
            @Override
            public boolean metaTypeRequired() {
                return Map.class.isAssignableFrom(declaredType) == false;
            }
            @Override
            public SerCategory category() {
                return SerCategory.MAP;
            }
            @Override
            public int size() {
                return map.size();
            }
            @Override
            public boolean hasNext() {
                return it.hasNext();
            }
            @Override
            public void next() {
                current = (Entry) it.next();
            }
            @Override
            public Class<?> keyType() {
                return keyType;
            }
            @Override
            public Object key() {
                return current.getKey();
            }
            @Override
            public Class<?> valueType() {
                return valueType;
            }
            @Override
            public List<Class<?>> valueTypeTypes() {
                return valueTypeTypes;
            }
            @Override
            public Object value() {
                return current.getValue();
            }
        };
    }

    //-----------------------------------------------------------------------
    /**
     * Gets an iterable wrapper for an object array.
     * 
     * @param valueType  the value type, not null
     * @return the iterable, not null
     */
    public static final SerIterable array(final Class<?> valueType) {
        final List<Object> list = new ArrayList<>();
        return new SerIterable() {
            @Override
            public SerIterator iterator() {
                return array(build(), Object.class, valueType);
            }
            @Override
            public void add(Object key, Object column, Object value, int count) {
                if (key != null) {
                    throw new IllegalArgumentException("Unexpected key");
                }
                if (count != 1) {
                    throw new IllegalArgumentException("Unexpected count");
                }
                for (int i = 0; i < count; i++) {
                    list.add(value);
                }
            }
            @Override
            public Object[] build() {
                Object[] array = (Object[]) Array.newInstance(valueType, list.size());
                return list.toArray(array);
            }
            @Override
            public Class<?> valueType() {
                return valueType;
            }
            @Override
            public List<Class<?>> valueTypeTypes() {
                return EMPTY_VALUE_TYPES;
            }
        };
    }

    /**
     * Gets an iterable wrapper for a primitive array.
     * 
     * @param valueType  the value type, not null
     * @return the iterable, not null
     */
    static final SerIterable arrayPrimitive(final Class<?> valueType) {
        final List<Object> list = new ArrayList<>();
        return new SerIterable() {
            @Override
            public SerIterator iterator() {
                return arrayPrimitive(build(), Object.class, valueType);
            }
            @Override
            public void add(Object key, Object column, Object value, int count) {
                if (key != null) {
                    throw new IllegalArgumentException("Unexpected key");
                }
                if (count != 1) {
                    throw new IllegalArgumentException("Unexpected count");
                }
                for (int i = 0; i < count; i++) {
                    list.add(value);
                }
            }
            @Override
            public Object build() {
                Object array = Array.newInstance(valueType, list.size());
                for (int i = 0; i < list.size(); i++) {
                    Array.set(array, i, list.get(i));
                }
                return array;
            }
            @Override
            public Class<?> valueType() {
                return valueType;
            }
            @Override
            public List<Class<?>> valueTypeTypes() {
                return EMPTY_VALUE_TYPES;
            }
        };
    }

    /**
     * Gets an iterator wrapper for an object array.
     * 
     * @param array  the array, not null
     * @param declaredType  the declared type, not null
     * @param valueType  the value type, not null
     * @return the iterator, not null
     */
    public static final SerIterator array(
            final Object[] array, final Class<?> declaredType, final Class<?> valueType) {
        return new SerIterator() {
            private int index = -1;

            @Override
            public String metaTypeName() {
                return metaTypeNameBase(valueType);
            }
            private String metaTypeNameBase(Class<?> arrayType) {
                if (arrayType.isArray()) {
                    return metaTypeNameBase(arrayType.getComponentType()) + "[]";
                }
                if (arrayType == Object.class) {
                    return "Object[]";
                }
                if (arrayType == String.class) {
                    return "String[]";
                }
                return arrayType.getName() + "[]";
            }
            @Override
            public boolean metaTypeRequired() {
                if (valueType == Object.class) {
                    return Object[].class.isAssignableFrom(declaredType) == false;
                }
                if (valueType == String.class) {
                    return String[].class.isAssignableFrom(declaredType) == false;
                }
                return true;
            }
            @Override
            public int size() {
                return array.length;
            }
            @Override
            public boolean hasNext() {
                return (index + 1) < array.length;
            }
            @Override
            public void next() {
                index++;
            }
            @Override
            public Class<?> valueType() {
                return valueType;
            }
            @Override
            public List<Class<?>> valueTypeTypes() {
                return Collections.emptyList();
            }
            @Override
            public Object value() {
                return array[index];
            }
        };
    }

    /**
     * Gets an iterator wrapper for a primitive array.
     * 
     * @param array  the array, not null
     * @param declaredType  the declared type, not null
     * @param valueType  the value type, not null
     * @return the iterator, not null
     */
    static final SerIterator arrayPrimitive(
            final Object array, final Class<?> declaredType, final Class<?> valueType) {
        final int arrayLength = Array.getLength(array);
        return new SerIterator() {
            private int index = -1;

            @Override
            public String metaTypeName() {
                return metaTypeNameBase(valueType);
            }
            private String metaTypeNameBase(Class<?> arrayType) {
                if (arrayType.isArray()) {
                    return metaTypeNameBase(arrayType.getComponentType()) + "[]";
                }
                if (arrayType == Object.class) {
                    return "Object[]";
                }
                if (arrayType == String.class) {
                    return "String[]";
                }
                return arrayType.getName() + "[]";
            }
            @Override
            public boolean metaTypeRequired() {
                if (valueType == Object.class) {
                    return Object[].class.isAssignableFrom(declaredType) == false;
                }
                if (valueType == String.class) {
                    return String[].class.isAssignableFrom(declaredType) == false;
                }
                return true;
            }
            @Override
            public int size() {
                return arrayLength;
            }
            @Override
            public boolean hasNext() {
                return (index + 1) < arrayLength;
            }
            @Override
            public void next() {
                index++;
            }
            @Override
            public Class<?> valueType() {
                return valueType;
            }
            @Override
            public List<Class<?>> valueTypeTypes() {
                return Collections.emptyList();
            }
            @Override
            public Object value() {
                return Array.get(array, index);
            }
        };
    }

}