public class ArrayAdapter<T> implements TypeAdapter<T>
{
    @Override
    public void write(T array, Writer writer, Context context)
    {
        Class type = array.getClass();

        if (!type.isArray()) {
            throw new IllegalArgumentException(
                "Expected array, but got " + type.getName());
        }

        int length = Array.getLength(array);

        writer.writeArrayStart(length);

        for (int index = 0; index < length; index++) {
            writer.writeKey(index);

            context.write(Array.get(array, index), writer);
        }

        writer.writeArrayEnd();
    }
} public class BooleanAdapter implements TypeAdapter<Boolean>
{
    @Override
    public void write(Boolean value, Writer writer, Context context)
    {
        writer.writeBoolean(value);
    }
} public class CollectionAdapter<T> implements TypeAdapter<Collection<T>>
{
    @Override
    public void write(Collection<T> collection, Writer writer, Context context)
    {
        int size = collection.size();

        writer.writeArrayStart(size);

        int index = 0;
        for (Object value: collection) {
            writer.writeKey(index++);
            context.write(value, writer);
        }

        writer.writeArrayEnd();
    }
} public class DoubleAdapter implements TypeAdapter<Double>
{
    @Override
    public void write(Double value, Writer writer, Context context)
    {
        writer.writeFloat(value);
    }
} public class IntegerAdapter implements TypeAdapter<Integer>
{
    @Override
    public void write(Integer value, Writer writer, Context context)
    {
        writer.writeInteger(value);
    }
} public class LongAdapter implements TypeAdapter<Long>
{
    @Override
    public void write(Long value, Writer writer, Context context)
    {
        writer.writeInteger(value);
    }
} public class MapAdapter<K, V> implements TypeAdapter<Map<K, V>>
{
    @Override
    public void write(Map<K, V> map, Writer writer, Context context)
    {
        int size = map.size();

        writer.writeArrayStart(size);

        for (Map.Entry<?, ?> entry : map.entrySet()) {
            Object key = entry.getKey();

            if (key instanceof Integer) {
                writer.writeKey((Integer) key);
            } else {
                writer.writeKey(key.toString());
            }

            context.write(entry.getValue(), writer);
        }

        writer.writeArrayEnd();
    }
} public class ObjectAdapter<T> implements TypeAdapter<T>
{
    @Override
    public void write(T object, Writer writer, Context context)
    {
        Class type = object.getClass();
        Field[] fields = getFields(type, context.getExclusionStrategy());

        NamingStrategy namingStrategy = context.getNamingStrategy();
        String className = namingStrategy.getClassName(type);

        writer.writeObjectStart(className, fields.length);

        writeFields(object, fields, writer, context);

        writer.writeObjectEnd();
    }

    /**
     * Writes fields using the specified writer.
     *
     * @param object The object that owns the field.
     * @param fields The fields to be written.
     * @param writer The writer to use for writing the fields.
     * @param context The serialization context.
     */
    protected void writeFields(
        T object,
        Field[] fields,
        Writer writer,
        Context context
    )
    {
        NamingStrategy namingStrategy = context.getNamingStrategy();

        for (Field field : fields) {
            String name = namingStrategy.getFieldName(field);
            Class<?> declaringClass = field.getDeclaringClass();
            String fieldClass = namingStrategy.getClassName(declaringClass);

            writer.writeProperty(name, fieldClass, getModifiers(field));
            context.write(ReflectionUtils.getValue(object, field), writer);
        }
    }

    /**
     * Returns the access modifier for the specified field.
     *
     * This method can be overridden to implement custom logic for porting
     * property modifiers from Java to PHP.
     *
     * @param field The field to define the access modifier.
     *
     * @return The modifier.
     *
     * @see java.lang.reflect.Modifier
     */
    protected int getModifiers(Field field)
    {
        return field.getModifiers();
    }

    /**
     * Returns the list of fields of a class filtered according to the
     * specified exclusion strategy.
     *
     * @param type The class to collect the fields from.
     * @param exclusionStrategy The strategy for filtering fields.
     *
     * @return The modifier.
     *
     * @see java.lang.reflect.Modifier
     */
    protected Field[] getFields(
        Class type,
        FieldExclusionStrategy exclusionStrategy
    )
    {
        List<Field> filtered = new ArrayList<>();

        for (Field field : ReflectionUtils.getFields(type)) {
            if (exclusionStrategy.shouldSkipField(field)) {
                continue;
            }

            filtered.add(field);
        }

        return filtered.toArray(new Field[0]);
    }
} public class ReferableObjectAdapter<T> implements TypeAdapter<T>
{
    /**
     * The decorated adapter.
     */
    private TypeAdapter<T> adapter;

    /**
     * Decorates the specified adapter.
     *
     * @param adapter The adapter to decorate.
     */
    public ReferableObjectAdapter(TypeAdapter<T> adapter)
    {
        this.adapter = adapter;
    }

    @Override
    public void write(T object, Writer writer, Context context)
    {
        int pointer = context.getReference(object);

        if (pointer > -1) {
            writer.writeObjectReference(pointer);

            return;
        }

        context.setReference(writer.getPointer(), object);

        adapter.write(object, writer, context);
    }
} public class StringAdapter implements TypeAdapter<String>
{
    /**
     * The charset.
     */
    private Charset charset;

    /**
     * Creates a adapter for strings encoded in UTF-8.
     */
    public StringAdapter() {
        this(Charset.forName("UTF-8"));
    }

    /**
     * Creates a adapter for strings encoded with the specified charset.
     *
     * @param charset The charset to encode strings.
     */
    public StringAdapter(Charset charset) {
        this.charset = charset;
    }

    @Override
    public void write(String value, Writer writer, Context context)
    {
        writer.writeString(value, this.charset);
    }
} public class DisjunctionExclusionStrategy implements FieldExclusionStrategy
{
    /**
     * The underlying strategies.
     */
    private FieldExclusionStrategy[] strategies;

    /**
     * Creates a disjunction from the specified strategies.
     *
     * @param strategies The list of strategies.
     */
    public DisjunctionExclusionStrategy(FieldExclusionStrategy... strategies)
    {
        this.strategies = strategies;
    }

    @Override
    public boolean shouldSkipField(Field field)
    {
        for (FieldExclusionStrategy strategy : strategies) {
            if (strategy.shouldSkipField(field)) {
                return true;
            }
        }

        return false;
    }
} public class NoExclusionStrategy implements FieldExclusionStrategy
{
    @Override
    public boolean shouldSkipField(Field field)
    {
        return false;
    }
} public class PsrNamingStrategy implements NamingStrategy
{
    @Override
    public String getClassName(Class type)
    {
        String className = type.getCanonicalName();
        StringBuilder adapted = new StringBuilder();

        for (String part : className.split("\\.")) {
            adapted.append("\\");

            adapted.append(part.substring(0, 1).toUpperCase());
            adapted.append(part.substring(1));
        }

        return adapted.substring(1);
    }

    @Override
    public String getFieldName(Field field)
    {
        return field.getName();
    }
}
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.marcospassos.phpserializer.adapter.ArrayAdapter;
import com.marcospassos.phpserializer.adapter.BooleanAdapter;
import com.marcospassos.phpserializer.adapter.CollectionAdapter;
import com.marcospassos.phpserializer.adapter.DoubleAdapter;
import com.marcospassos.phpserializer.adapter.IntegerAdapter;
import com.marcospassos.phpserializer.adapter.LongAdapter;
import com.marcospassos.phpserializer.adapter.MapAdapter;
import com.marcospassos.phpserializer.adapter.ObjectAdapter;
import com.marcospassos.phpserializer.adapter.ReferableObjectAdapter;
import com.marcospassos.phpserializer.adapter.StringAdapter;
import com.marcospassos.phpserializer.exclusion.DisjunctionExclusionStrategy;
import com.marcospassos.phpserializer.exclusion.NoExclusionStrategy;
import com.marcospassos.phpserializer.naming.PsrNamingStrategy;
package com.marcospassos.phpserializer;


/**
 * Provides a friendly API for creating instances of {@link Serializer}.
 *
 * @author Marcos Passos
 * @since 1.0
 */
public class SerializerBuilder
{
    /**
     * The naming strategy.
     */
    private NamingStrategy namingStrategy;

    /**
     * The exclusion strategies.
     */
    private List<FieldExclusionStrategy> exclusionStrategies;

    /**
     * The map of adapters.
     */
    private Map<Class, TypeAdapter> adapterMap;

    /**
     * The serializer factory.
     */
    private SerializerFactory factory;

    /**
     * Creates a builder with the specified factory.
     *
     * @param factory The factory to create the serializer.
     */
    public SerializerBuilder(SerializerFactory factory)
    {
        this.adapterMap = new HashMap<>();
        this.exclusionStrategies = new ArrayList<>();
        this.factory = factory;
    }

    /**
     * Creates a builder using an internal factory.
     */
    public SerializerBuilder()
    {
        this(new SerializerFactory());
    }

    /**
     * Creates a {@link Serializer} instance based on the current configuration.
     *
     * This method is free of side-effects to this instance and hence can be
     * called multiple times.
     *
     * @return An instance of {@link Serializer} configured with the options
     * currently set in this builder.
     */
    public Serializer build()
    {
        AdapterRegistry adapterRegistry = getAdapterRegistry();
        FieldExclusionStrategy exclusionStrategy = getExclusionStrategy();
        NamingStrategy namingStrategy = getNamingStrategy();

        return factory.create(
            namingStrategy,
            exclusionStrategy,
            adapterRegistry
        );
    }

    /**
     * Configures the serializer to use the specified charset for
     * serializing strings.
     *
     * Calling this method will override any registered adapter for
     * {@code String}.
     *
     * @param charset The default charset to serialize strings.
     *
     * @return The current builder.
     */
    public SerializerBuilder setCharset(Charset charset)
    {
        registerAdapter(String.class, new StringAdapter(charset));

        return this;
    }

    /**
     * Configures the serializer to apply a specific naming policy strategy to
     * objects during serialization.
     *
     * @param strategy The naming strategy to apply to objects.
     *
     * @return The current builder.
     */
    public SerializerBuilder setNamingStrategy(NamingStrategy strategy)
    {
        this.namingStrategy = strategy;

        return this;
    }

    /**
     * Configures the serializer to apply the specified exclusion strategy
     * during serialization.
     *
     * If this method is invoked numerous times with different exclusion
     * strategies then the exclusion strategies that were added will be
     * merged into a disjunction strategy. This means that if at least one of
     * the added exclusion strategies suggests that a field (or class) should
     * be skipped then that field (or object) is skipped during its
     * deserialization.
     *
     * @param strategy The exclusion strategy.
     *
     * @return The current builder.
     */
    public SerializerBuilder addExclusionStrategy(FieldExclusionStrategy strategy)
    {
        if (!this.exclusionStrategies.contains(strategy)) {
            this.exclusionStrategies.add(strategy);
        }

        return this;
    }

    /**
     * Registers the provided adapter for the specified type.
     *
     * This registers the type specified and all subclasses. For example,
     * an adapter registered for {@code Number.class} will be also applied to
     * {@code Integer.class}.
     *
     * If a type adapter was previously registered for the specified type, it
     * is overwritten.
     *
     * @param type The base class or interface supported by the adapter.
     * @param adapter The type adapter.
     *
     * @return The current builder.
     */
    public <T> SerializerBuilder registerAdapter(Class type, TypeAdapter adapter)
    {
        adapterMap.put(type, adapter);

        return this;
    }

    /**
     * Registers all builtin adapters.
     *
     * Calling this method will override any adapter already registered for
     * the types handled by the builtin adapters.
     *
     * Strings are serialized in UTF-8 by default.
     *
     * @return The current builder.
     */
    public SerializerBuilder registerBuiltinAdapters()
    {
        registerAdapter(Object[].class, new ArrayAdapter<>());
        registerAdapter(Map.class, new MapAdapter<>());
        registerAdapter(Collection.class, new CollectionAdapter<>());
        registerAdapter(Boolean.class, new BooleanAdapter());
        registerAdapter(Double.class, new DoubleAdapter());
        registerAdapter(Integer.class, new IntegerAdapter());
        registerAdapter(Long.class, new LongAdapter());
        registerAdapter(String.class, new StringAdapter());
        registerAdapter(Object.class, new ReferableObjectAdapter<>(