public interface Configurable {

} public interface PartialObjectFactory extends Configurable {

	public interface PartialObjectProxy {

		boolean $hasUndefinedProperties();
		boolean $isDefined(String propertyName);
		boolean $undefine(String propertyName);
		Property[] $getDefinedProperties();
	}
	
	public interface ExtendedPartialObjectProxy extends PartialObjectProxy {
		
		Class<?> $getActualClass();
	}
	
	public static class UndefinedPropertyException extends RuntimeException {

		private static final long serialVersionUID = 1L;

		public UndefinedPropertyException(String propertyName) {
			super("Property '" + propertyName + "' is undefined");
		}
	}

	Object instantiatePartial(SpearalContext context, Class<?> cls, Property[] partialProperties)
		throws InstantiationException, IllegalAccessException;
} public class SpearalContextImpl implements SpearalContext {
	
	private Introspector introspector;
	private TypeLoader loader;
	private Securizer securizer;
	private PartialObjectFactory partialObjectFactory;
	private AliasStrategy aliasStrategy;
	
	private final List<TypeInstantiatorProvider> typeInstantiatorProviders;
	private final CopyOnWriteMap<Type, Object, TypeInstantiator> typeInstantiatorsCache;

	private final List<PropertyInstantiatorProvider> propertyInstantiatorProviders;
	private final CopyOnWriteMap<Property, Object, PropertyInstantiator> propertyInstantiatorsCache;
	
	private final List<ConverterProvider> converterProviders;
	private final CopyOnWriteDualIdentityMap<Class<?>, Type, Converter<?>> convertersCache;
	
	private final List<CoderProvider> coderProviders;
	private final CopyOnWriteMap<Class<?>, Object, Coder> codersCache;
	
	private final List<UnfilterablePropertiesProvider> unfilterablePropertiesProviders;
	private final CopyOnWriteMap<Class<?>, Object, String[]> unfilterablePropertiesCache;
	
	private final List<PropertyFactory> propertyFactories;
	
	private final List<FilteredBeanDescriptorFactory> descriptorFactories;
	
	private static final String[] EMPTY_STRING_ARRAY = new String[] {};
	
	
	public SpearalContextImpl() {
		this.typeInstantiatorProviders = new ArrayList<TypeInstantiatorProvider>();
		this.typeInstantiatorsCache = new CopyOnWriteMap<Type, Object, TypeInstantiator>(true,
			new ValueProvider<Type, Object, TypeInstantiator>() {
				@Override
				public TypeInstantiator createValue(SpearalContext context, Type key, Object unused) {
					for (TypeInstantiatorProvider provider : typeInstantiatorProviders) {
						TypeInstantiator instantiator = provider.getInstantiator(key);
						if (instantiator != null)
							return instantiator;
					}
					throw new RuntimeException("Could not find any instantiator for: " + key);
				}
			}
		);

		this.propertyInstantiatorProviders = new ArrayList<PropertyInstantiatorProvider>();
		this.propertyInstantiatorsCache = new CopyOnWriteMap<Property, Object, PropertyInstantiator>(false,
			new ValueProvider<Property, Object, PropertyInstantiator>() {
				@Override
				public PropertyInstantiator createValue(SpearalContext context, Property key, Object unused) {
					for (PropertyInstantiatorProvider provider : propertyInstantiatorProviders) {
						PropertyInstantiator instantiator = provider.getInstantiator(key);
						if (instantiator != null)
							return instantiator;
					}
					throw new RuntimeException("Could not find any instantiator for: " + key);
				}
			}
		);
		
		this.converterProviders = new ArrayList<ConverterProvider>();
		this.convertersCache = new CopyOnWriteDualIdentityMap<Class<?>, Type, Converter<?>>(
			new DualIdentityMap.ValueProvider<Class<?>, Type, Converter<?>>() {
				@Override
				public Converter<?> createValue(SpearalContext context, Class<?> valueClass, Type targetType) {
					for (ConverterProvider provider : converterProviders) {
						Converter<?> converter = provider.getConverter(valueClass, targetType);
						if (converter != null)
							return converter;
					}
					throw new RuntimeException("No converter found from: " + valueClass + " to: " + targetType);
				}
			}
		);
		
		this.coderProviders = new ArrayList<CoderProvider>();
		this.codersCache = new CopyOnWriteMap<Class<?>, Object, Coder>(true,
			new ValueProvider<Class<?>, Object, Coder>() {
				@Override
				public Coder createValue(SpearalContext context, Class<?> key, Object param) {
					for (CoderProvider provider : coderProviders) {
						Coder coder = provider.getCoder(key);
						if (coder != null)
							return coder;
					}
					throw new RuntimeException("Not coder found for type: " + key);
				}
			}
		);
		
		this.unfilterablePropertiesProviders = new ArrayList<UnfilterablePropertiesProvider>();
		this.unfilterablePropertiesCache = new CopyOnWriteMap<Class<?>, Object, String[]>(true,
			new ValueProvider<Class<?>, Object, String[]>() {
				
				@Override
				public String[] createValue(SpearalContext context, Class<?> key, Object unused) {
					for (UnfilterablePropertiesProvider unfilterablePropertiesProvider : unfilterablePropertiesProviders) {
						String[] unfilterableProperties = unfilterablePropertiesProvider.getUnfilterableProperties(key);
						if (unfilterableProperties != null)
							return unfilterableProperties;
					}
					return EMPTY_STRING_ARRAY;
				}
			}
		);
		
		this.propertyFactories = new ArrayList<PropertyFactory>();
		
		this.descriptorFactories = new ArrayList<FilteredBeanDescriptorFactory>();
	}

	@Override
	public Securizer getSecurizer() {
		return securizer;
	}
	
	@Override
	public void configure(Configurable configurable) {
		configure(configurable, false);
	}
	
	@Override
	public void configure(Configurable configurable, boolean append) {
		boolean added = false;
		
		if (configurable instanceof Repeatable) {
			if (configurable instanceof TypeInstantiatorProvider) {
				typeInstantiatorProviders.add((append ? typeInstantiatorProviders.size() : 0), (TypeInstantiatorProvider)configurable);
				added = true;
			}
			
			if (configurable instanceof PropertyInstantiatorProvider) {
				propertyInstantiatorProviders.add((append ? propertyInstantiatorProviders.size() : 0), (PropertyInstantiatorProvider)configurable);
				added = true;
			}
			
			if (configurable instanceof ConverterProvider) {
				converterProviders.add((append ? converterProviders.size() : 0), (ConverterProvider)configurable);
				added = true;
			}
			
			if (configurable instanceof CoderProvider) {
				coderProviders.add((append ? coderProviders.size() : 0), (CoderProvider)configurable);
				added = true;
			}
			
			if (configurable instanceof UnfilterablePropertiesProvider) {
				unfilterablePropertiesProviders.add((append ? unfilterablePropertiesProviders.size() : 0), (UnfilterablePropertiesProvider)configurable);
				added = true;
			}
			
			if (configurable instanceof PropertyFactory) {
				propertyFactories.add((append ? propertyFactories.size() : 0), (PropertyFactory)configurable);
				added = true;
			}
			
			if (configurable instanceof FilteredBeanDescriptorFactory) {
				descriptorFactories.add((append ? descriptorFactories.size() : 0), (FilteredBeanDescriptorFactory)configurable);
				added = true;
			}
		}
		else {
			if (configurable instanceof AliasStrategy) {
				aliasStrategy = (AliasStrategy)configurable;
				added = true;
			}
			
			if (configurable instanceof Introspector) {
				introspector = (Introspector)configurable;
				added = true;
			}
			
			if (configurable instanceof TypeLoader) {
				loader = (TypeLoader)configurable;
				added = true;
			}
			
			if (configurable instanceof Securizer) {
				securizer = (Securizer)configurable;
				added = true;
			}
			
			if (configurable instanceof PartialObjectFactory) {
				partialObjectFactory = (PartialObjectFactory)configurable;
				added = true;
			}
		}
		
		if (!added)
			throw new RuntimeException("Unsupported configurable: " + configurable);
	}

	@Override
	public String alias(Class<?> cls) {
		return aliasStrategy.alias(cls);
	}

	@Override
	public String unalias(String aliasedClassName) {
		return aliasStrategy.unalias(aliasedClassName);
	}

	@Override
	public Class<?> loadClass(String classNames, Type target) throws SecurityException {
		return loader.loadClass(this, classNames, target);
	}

	@Override
	public Property[] getProperties(Class<?> cls) {
		return introspector.getProperties(this, cls);
	}
	
	@Override
	public Object newInstance(Class<?> cls) {
		if (cls.isInterface())
			cls = Proxy.getProxyClass(cls.getClassLoader(), cls, PartialObjectProxy.class);
        try {
			Property[] properties = getProperties(cls);
			return instantiatePartial(cls, properties);
        }
        catch (InstantiationException e) {
            throw new RuntimeException("Could not create instance of " + cls, e);
        }
        catch (IllegalAccessException e) {
            throw new RuntimeException("Could not create instance of " + cls, e);
        }
	}

	@Override
	public Object instantiate(Type type, Object param) throws InstantiationException {
		return typeInstantiatorsCache.getOrPutIfAbsent(this, type).instantiate(this, type, param);
	}

	@Override
	public Object instantiate(Property property, Object param) throws InstantiationException {
		return propertyInstantiatorsCache.getOrPutIfAbsent(this, property).instantiate(this, property, param);
	}

	@Override
	public Object instantiatePartial(Class<?> cls, Property[] partialProperties)
		throws InstantiationException, IllegalAccessException {

		return partialObjectFactory.instantiatePartial(this, cls, partialProperties);
	}

	@Override
	public Object convert(Object value, Type targetType) {
		Class<?> valueClass = (value != null ? value.getClass() : null);
		if (valueClass == targetType)
			return value;
		return convertersCache.getOrPutIfAbsent(this, valueClass, targetType)
			.convert(this, value, targetType);
	}
	
	@Override
	public Coder getCoder(Class<?> valueClass) {
		return codersCache.getOrPutIfAbsent(this, valueClass);
	}
	
	@Override
	public String[] getUnfilterableProperties(Class<?> valueClass) {
		return unfilterablePropertiesCache.getOrPutIfAbsent(this, valueClass);
	}
	
	@Override
	public Property createProperty(String name, Field field, Method getter, Method setter) {
		for (PropertyFactory factory : propertyFactories) {
			Property property = factory.createProperty(name, field, getter, setter);
			if (property != null)
				return property;
		}
		throw new UnsupportedOperationException(
			"Could not create property for: " + name + " {" +
				"field=" + field + ", " +
				"getter=" + getter + ", " +
				"setter=" + setter +
			"}"
		);
	}
	
	@Override
	public FilteredBeanDescriptor createDescriptor(SpearalPropertyFilter filter, Object value) {
		for (FilteredBeanDescriptorFactory factory : descriptorFactories) {
			FilteredBeanDescriptor descriptor = factory.createDescription(this, filter, value);
			if (descriptor != null)
				return descriptor;
		}
		throw new UnsupportedOperationException("Could not create bean descriptor for: " + value);
	}
} public class SpearalDecoderImpl implements ExtendedSpearalDecoder {

	private final List<String> sharedStrings;
	private final List<Object> sharedObjects;
	
	private final PathImpl path;
	private final PartialObjectMap partialObjectsMap;
	
	private final EqualityMap<String, Type, ClassDescriptor> descriptors;
	private final EqualityMap<String, Object, BigInteger> bigIntegers;
	private final EqualityMap<String, Object, BigDecimal> bigDecimals;

	private final SpearalContext context;
	
	private final InputStream in;
	private final byte[] buffer;
	private int position;
	private int size;

	public SpearalDecoderImpl(SpearalContext context, InputStream in) {
		this(context, in, 1024);
	}

	public SpearalDecoderImpl(final SpearalContext context, InputStream in, int capacity) {
		this.sharedStrings = new ArrayList<String>(64);
		this.sharedObjects = new ArrayList<Object>(64);
		
		this.path = new PathImpl();
		this.partialObjectsMap = new PartialObjectMap();
		
		this.descriptors = new EqualityMap<String, Type, ClassDescriptor>(new ValueProvider<String, Type, ClassDescriptor>() {
			@Override
			public ClassDescriptor createValue(SpearalContext context, String key, Type targetType) {
				return ClassDescriptor.forDescription(context, key, targetType);
			}
		});
		this.bigIntegers = new EqualityMap<String, Object, BigInteger>(new ValueProvider<String, Object, BigInteger>() {
			@Override
			public BigInteger createValue(SpearalContext context, String key, Object unsused) {
				final int exponentIndex = key.indexOf('E');

				BigInteger bigInteger;
				if (exponentIndex == -1)
					bigInteger = new BigInteger(key);
				else {
					bigInteger = new BigInteger(key.substring(0, exponentIndex));
					int exponent = Integer.parseInt(key.substring(exponentIndex + 1));
					bigInteger = bigInteger.multiply(BigInteger.TEN.pow(exponent));
				}
				return bigInteger;
			}
		});
		this.bigDecimals = new EqualityMap<String, Object, BigDecimal>(new ValueProvider<String, Object, BigDecimal>() {
			@Override
			public BigDecimal createValue(SpearalContext context, String key, Object unsused) {
				return new BigDecimal(key);
			}
		});

		this.context = context;
		this.in = in;
		this.buffer = new byte[capacity];
		this.position = 0;
		this.size = 0;
	}
	
	@Override
	public SpearalContext getContext() {
		return context;
	}

	public Path getPath() {
		return path;
	}

	@Override
	public boolean containsPartialObjects() {
		return !partialObjectsMap.isEmpty();
	}

	@Override
	public Map<Object, List<PathSegment>> getPartialObjectsMap() {
		return partialObjectsMap;
	}

	@Override
	public Object readAny() throws IOException {
		return readAny(readNextByte(), null);
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> T readAny(Type targetType) throws IOException {
		return (T)readAny(readNextByte(), targetType);
	}

	@Override
	public void skipAny()  throws IOException {
		readAny();
	}
	
	@Override
	public void printAny(SpearalPrinter printer) throws IOException {
		printAny(printer, readNextByte());
	}

	@Override
	public Object readAny(int parameterizedType) throws IOException {
		return readAny(parameterizedType, null);
	}

	@Override
	public Object readAny(int parameterizedType, Type targetType) throws IOException {

		targetType = TypeUtil.unwrapTypeVariable(targetType);
		
		Object value;
		boolean convert;
		
		switch (SpearalType.valueOf(parameterizedType)) {
		
		case NULL:
			value = null;
			convert = ((targetType instanceof Class<?>) && ((Class<?>)targetType).isPrimitive());
			break;
		
		case TRUE:
			value = Boolean.TRUE;
			convert = (targetType != null && targetType != boolean.class && targetType != Boolean.class);
			break;

		case FALSE:
			value = Boolean.FALSE;
			convert = (targetType != null && targetType != boolean.class && targetType != Boolean.class);
			break;

		case INTEGRAL:
			value = Long.valueOf(readIntegral(parameterizedType));
			convert = (targetType != null && targetType != long.class && targetType != Long.class);
			break;

		case BIG_INTEGRAL:
			value = readBigIntegral(parameterizedType);
			convert = (targetType != null && targetType != BigInteger.class);
			break;
			
		case FLOATING:
			value = Double.valueOf(readFloating(parameterizedType));
			convert = (targetType != null && targetType != double.class && targetType != Double.class);
			break;
		
		case BIG_FLOATING:
			value = readBigFloating(parameterizedType);
			convert = (targetType != null && targetType != BigDecimal.class);
			break;
			
		case STRING:
			value = readString(parameterizedType);
			convert = (targetType != null && targetType != String.class);
			break;
		
		case BYTE_ARRAY:
			value = readByteArray(parameterizedType);
			convert = (targetType != null && targetType != byte[].class);
			break;

		case DATE_TIME:
			value = readDateTime(parameterizedType);
			if (targetType == null) {
				targetType = Object.class;
				convert = true;
			}
			else
				convert = (targetType != SpearalDateTime.class);
			break;
			
		case COLLECTION:
			value = readCollection(parameterizedType, targetType);
			if (targetType == null)
				convert = false;
			else {
				Class<?> targetClass = TypeUtil.classOfType(targetType);
				convert = !(targetClass.isArray() || Collection.class.isAssignableFrom(targetClass));
			}
			break;
			
		case MAP:
			value = readMap(parameterizedType, targetType);
			convert = (targetType != null && !Map.class.isAssignableFrom(TypeUtil.classOfType(targetType)));
			break;
			
		case ENUM:
			value = readEnum(parameterizedType, targetType);
			convert = (targetType != null && targetType != value.getClass());
			break;
			
		case CLASS:
			value = readClass(parameterizedType, targetType);
			convert = (targetType != null && targetType != Class.class);
			break;
			
		case BEAN:
			value = readBean(parameterizedType, targetType);
			convert = (
				targetType != null &&
				targetType != value.getClass() &&
				!((targetType instanceof Class) && ((Class<?>)targetType).isAssignableFrom(value.getClass()))
			);
			break;
		
		default:
			throw new RuntimeException("Unexpected parameterized type: " + parameterizedType);
		}
		
		return (convert ? context.convert(value, targetType) : value);
	}

	@Override
	public void skipAny(int parameterizedType) throws IOException {
		readAny(parameterizedType);
	}

	public void printAny(SpearalPrinter printer, int parameterizedType) throws IOException {
		switch (SpearalType.valueOf(parameterizedType)) {
		
		case NULL:
			printer.printNull();
			return;

		case TRUE:
			printer.printBoolean(true);
			return;
		case FALSE:
			printer.printBoolean(false);
			return;

		case INTEGRAL:
			printer.printIntegral(readIntegral(parameterizedType));
			return;
		case BIG_INTEGRAL:
			printBigIntegral(printer, parameterizedType);
			return;
			
		case FLOATING:
			printer.printFloating(readFloating(parameterizedType));
			return;
		case BIG_FLOATING:
			printBigFloating(printer, parameterizedType);
			return;
			
		case STRING:
			printString(printer, parameterizedType);
			return;
		
		case BYTE_ARRAY:
			printByteArray(printer, parameterizedType);
			return;

		case DATE_TIME:
			printDateTime(printer, parameterizedType);
			return;
			
		case COLLECTION:
			printCollection(printer, parameterizedType);
			return;
		case MAP:
			printMap(printer, parameterizedType);
			return;
			
		case ENUM:
			printEnum(printer, parameterizedType);
			return;
		case CLASS:
			printClass(printer, parameterizedType);
			return;
		case BEAN:
			printBean(printer, parameterizedType);
			return;
		}
		
		throw new RuntimeException("Unexpected type: " + parameterizedType);
	}
	
	@Override
	public long readIntegral(int parameterizedType) throws IOException {
		final int length0 = (parameterizedType & 0x07);

		ensureAvailable(length0 + 1);

		final byte[] buffer = this.buffer;
		int position = this.position;
		
		long v = 0L;
		
		switch (length0) {
		case 7:
			v |= (buffer[position++] & 0xffL) << 56;
		case 6:
			v |= (buffer[position++] & 0xffL) << 48;
		case 5:
			v |= (buffer[position++] & 0xffL) << 40;
		case 4:
			v |= (buffer[position++] & 0xffL) << 32;
		case 3:
			v |= (buffer[position++] & 0xffL) << 24;
		case 2:
			v |= (buffer[position++] & 0xffL) << 16;
		case 1:
			v |= (buffer[position++] & 0xffL) << 8;
		case 0:
			v |= (buffer[position++] & 0xffL);
		}
		
		this.position = position;
		
		if ((parameterizedType & 0x08) != 0)
			v = -v;
		
		return v;
	}
	
	@Override
	public BigInteger readBigIntegral(int parameterizedType) throws IOException {
		final int indexOrLength = readIndexOrLength(parameterizedType);
		String representation;
		if (isStringReference(parameterizedType))
			representation = sharedStrings.get(indexOrLength);
		else
			representation = readBigNumberData(indexOrLength);
		return bigIntegers.putIfAbsent(context, representation);
	}
	
	void printBigIntegral(SpearalPrinter printer, int parameterizedType) throws IOException {
		final int indexOrLength = readIndexOrLength(parameterizedType);
		if (isStringReference(parameterizedType))
			printer.printBigIntegral(new StringData(sharedStrings.get(indexOrLength), indexOrLength, true));
		else
			printer.printBigIntegral(new StringData(readBigNumberData(indexOrLength), sharedStrings.size() - 1, false));
	}

	@Override
	public double readFloating(int parameterizedType) throws IOException {
		if ((parameterizedType & 0x08) != 0) {
			int length0 = (parameterizedType & 0x03);
			
			ensureAvailable(length0 + 1);
			
			final byte[] buffer = this.buffer;
			int position = this.position;
			
			long doubleAsLong = 0L;
			
			switch (length0) {
			case 3:
				doubleAsLong |= (buffer[position++] & 0xffL) << 24;
			case 2:
				doubleAsLong |= (buffer[position++] & 0xffL) << 16;
			case 1:
				doubleAsLong |= (buffer[position++] & 0xffL) << 8;
			case 0:
				doubleAsLong |= (buffer[position++] & 0xffL);
			}
			
			this.position = position;
			
			if ((parameterizedType & 0x04) != 0)
				doubleAsLong = -doubleAsLong;
			
			return (doubleAsLong / 1000.0);
		}
		
		ensureAvailable(8);
		return Double.longBitsToDouble(readLongData());
	}

	@Override
	public BigDecimal readBigFloating(int parameterizedType) throws IOException {
		final int indexOrLength = readIndexOrLength(parameterizedType);
		String representation;
		if (isStringReference(parameterizedType))
			representation = sharedStrings.get(indexOrLength);
		else
			representation = readBigNumberData(indexOrLength);
		return bigDecimals.putIfAbsent(context, representation);
	}
	
	void printBigFloating(SpearalPrinter printer, int parameterizedType) throws IOException {
		final int indexOrLength = readIndexOrLength(parameterizedType);
		if (isStringReference(parameterizedType))
			printer.printBigFloating(new StringData(sharedStrings.get(indexOrLength), indexOrLength, true));
		else
			printer.printBigFloating(new StringData(readBigNumberData(indexOrLength), sharedStrings.size() - 1, false));
	}

	@Override
	public String readString(int parameterizedType) throws IOException {
		return readStringData(parameterizedType);
	}
	
	private void printString(SpearalPrinter printer, int parameterizedType) throws IOException {
		printer.printString(getStringData(parameterizedType));
	}
	
	@Override
	public byte[] readByteArray(int parameterizedType) throws IOException {
		final int indexOrLength = readIndexOrLength(parameterizedType);

		if (isObjectReference(parameterizedType))
			return (byte[])sharedObjects.get(indexOrLength);
		
		byte[] bytes = new byte[indexOrLength];
		sharedObjects.add(bytes);
		readFully(bytes, 0, indexOrLength);
		return bytes;
	}
	
	private void printByteArray(SpearalPrinter printer, int parameterizedType) throws IOException {
		final int indexOrLength = readIndexOrLength(parameterizedType);

		if (isObjectReference(parameterizedType))
			printer.printByteArray((byte[])sharedObjects.get(indexOrLength), indexOrLength, true);
		else {
			byte[] bytes = new byte[indexOrLength];
			sharedObjects.add(bytes);
			readFully(bytes, 0, indexOrLength);
			printer.printByteArray(bytes, sharedObjects.size() - 1, false);
		}
	}
	
	@Override
	public SpearalDateTime readDateTime(int parameterizedType) throws IOException {

		boolean hasDate = ((parameterizedType & 0x08) != 0);
		boolean hasTime = ((parameterizedType & 0x04) != 0);
		
		int year = 0;
		int month = 0;
		int date = 0;
		int hours = 0;
		int minutes = 0;
		int seconds = 0;
		int nanoseconds = 0;
		
		if (hasDate) {
			ensureAvailable(2);
			month = (buffer[position++] & 0xff);
			date = (buffer[position++] & 0xff);
			
			int length0 = ((month >>> 4) & 0x03);
			boolean inverse = ((month & 0x80) != 0);
			
			month &= 0x0f;
			
			ensureAvailable(length0 + 1);
			year = readUnsignedIntegerValue(length0);
			if (inverse)
				year = -year;
			year += 2000;
		}
		
		if (hasTime) {
			ensureAvailable(3);
			hours = (buffer[position++] & 0xff);
			minutes = (buffer[position++] & 0xff);
			seconds = (buffer[position++] & 0xff);
			
			int subsecondsType = (parameterizedType & 0x03);
			if (subsecondsType != 0) {
				int length0 = (hours >>> 5);
				ensureAvailable(length0 + 1);
				nanoseconds = readUnsignedIntegerValue(length0);
				
				if (subsecondsType == 2)
					nanoseconds *= 1000;
				else if (subsecondsType == 3)
					nanoseconds *= 1000000;
			}
			
			hours &= 0x1f;
		}
		
		return new SpearalDateTime(year, month, date, hours, minutes, seconds, nanoseconds, hasDate, hasTime);
	}
	
	private void printDateTime(SpearalPrinter printer, int parameterizedType) throws IOException {
		printer.printDateTime(readDateTime(parameterizedType));
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public Object readCollection(int parameterizedType, Type targetType) throws IOException {
		final int indexOrLength = readIndexOrLength(parameterizedType);

		if (isObjectReference(parameterizedType))
			return sharedObjects.get(indexOrLength);
		
		Object value = null;
		Type elementType = null;
		if (targetType != null) {		
			try {
				value = context.instantiate(targetType, Integer.valueOf(indexOrLength));
			} 
			catch (Exception e) {
				throw new RuntimeException("Couldn't instantiate type: " + targetType, e);
			}
			elementType = TypeUtil.getElementType(targetType);
		}
		else
			value = new ArrayList<Object>(indexOrLength);
		
		sharedObjects.add(value);
		
		if (value.getClass().isArray()) {
			ArrayPathSegmentImpl segment = new ArrayPathSegmentImpl(value);
			path.push(segment);
			for (segment.index = 0; segment.index < indexOrLength; segment.index++)
				Array.set(value, segment.index, readAny(elementType));
			path.pop();
		}
		else {
			Collection<Object> collection = (Collection<Object>)value;
			CollectionPathSegmentImpl segment = new CollectionPathSegmentImpl(collection);
			path.push(segment);
			for (segment.index = 0; segment.index < indexOrLength; segment.index++)
				collection.add(readAny(elementType));
			path.pop();
		}

		return value;
	}
	
	private void printCollection(SpearalPrinter printer, int parameterizedType) throws IOException {
		final int indexOrLength = readIndexOrLength(parameterizedType);
		
		if (isObjectReference(parameterizedType))
			printer.printCollectionReference(indexOrLength);
		else {
			int index = sharedObjects.size();
			printer.printCollectionStart(index, indexOrLength);
			sharedObjects.add(null);
			
			for (int i = 0; i < indexOrLength; i++) {
				printer.printCollectionItemStart(index, i);
				printAny(printer);
				printer.printCollectionItemEnd(index, i);
			}
			
			printer.printCollectionEnd(index);
		}
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public void readCollection(int parameterizedType, Object holder, Property property)
		throws IOException, InstantiationException, IllegalAccessException, InvocationTargetException {
		
		final int indexOrLength = readIndexOrLength(parameterizedType);
		
		if (isObjectReference(parameterizedType)) {
			property.set(holder, sharedObjects.get(indexOrLength));
			return;
		}
		
		Collection<Object> value = (Collection<Object>)property.get(holder);
		if (value != null)
			value.clear();
		else
			value = (Collection<Object>)property.init(this, holder);
		sharedObjects.add(value);
		
		Type elementType = TypeUtil.getElementType(property.getGenericType());
		
		CollectionPathSegmentImpl segment = new CollectionPathSegmentImpl(value);
		path.push(segment);
		for (segment.index = 0; segment.index < indexOrLength; segment.index++)
			value.add(readAny(elementType));
		path.pop();
	}

	@SuppressWarnings("unchecked")
	@Override
	public Map<?, ?> readMap(int parameterizedType, Type targetType) throws IOException {
		final int indexOrLength = readIndexOrLength(parameterizedType);
		
		if (isObjectReference(parameterizedType))
			return (Map<?, ?>)sharedObjects.get(indexOrLength);
		
		Map<Object, Object> value = null;
		Type keyType = null;
		Type valType = null;
		if (targetType != null && Map.class.isAssignableFrom(TypeUtil.classOfType(targetType))) {		
			try {
				value = (Map<Object, Object>)context.instantiate(targetType, Integer.valueOf(indexOrLength));
			} 
			catch (Exception e) {
				throw new RuntimeException("Couldn't instantiate type: " + targetType, e);
			}
			Type[] keyValueTypes = TypeUtil.getKeyValueType(targetType);
			keyType = keyValueTypes[0];
			valType = keyValueTypes[1];
		}
		else
			value = new LinkedHashMap<Object, Object>(indexOrLength);
		
		sharedObjects.add(value);
		
		MapPathSegmentImpl segment = new MapPathSegmentImpl(value);
		path.push(segment);
		for (int i = 0; i < indexOrLength; i++) {
			segment.key = null;
			Object key = readAny(keyType);
			segment.key = key;
			Object val = readAny(valType);
			value.put(key, val);
		}
		path.pop();
		
		return value;
	}
	
	private void printMap(SpearalPrinter printer, int parameterizedType) throws IOException {
		final int indexOrLength = readIndexOrLength(parameterizedType);
		
		if (isObjectReference(parameterizedType))
			printer.printMapReference(indexOrLength);
		else {
			int index = sharedObjects.size();
			printer.printMapStart(index, indexOrLength);
			sharedObjects.add(null);
			
			for (int i = 0; i < indexOrLength; i++) {
				printer.printMapKeyStart(index, i);
				printAny(printer);
				printer.printMapKeyEnd(index, i);
				
				printer.printMapValueStart(index, i);
				printAny(printer);
				printer.printMapValueEnd(index, i);
			}
			
			printer.printMapEnd(index);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public void readMap(int parameterizedType, Object holder, Property property)
		throws IOException, InstantiationException, IllegalAccessException, InvocationTargetException {
		
		final int indexOrLength = readIndexOrLength(parameterizedType);
		
		if (isObjectReference(parameterizedType)) {
			property.set(holder, sharedObjects.get(indexOrLength));
			return;
		}
		
		Map<Object, Object> value = (Map<Object, Object>)property.get(holder);
		if (value != null)
			value.clear();
		else
			value = (Map<Object, Object>)property.init(this, holder);
		sharedObjects.add(value);

		Type[] keyValueTypes = TypeUtil.getKeyValueType(property.getGenericType());
		Type keyType = keyValueTypes[0];
		Type valType = keyValueTypes[1];
		
		MapPathSegmentImpl segment = new MapPathSegmentImpl(value);
		path.push(segment);
		for (int i = 0; i < indexOrLength; i++) {
			segment.key = null;
			Object key = readAny(keyType);
			segment.key = key;
			Object val = readAny(valType);
			value.put(key, val);
		}
		path.pop();
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public Enum<?> readEnum(int parameterizedType, Type targetType) throws IOException {
		String className = readStringData(parameterizedType);
		Class<? extends Enum> cls = (Class<? extends Enum>)context.loadClass(className, targetType);
		String value = readString(readNextByte());
		return Enum.valueOf(cls, value);
	}
	
	private void printEnum(SpearalPrinter printer, int parameterizedType) throws IOException {
		StringData className = getStringData(parameterizedType);
		parameterizedType = readNextByte();
		StringData value = getStringData(parameterizedType);
		printer.printEnum(className, value);
	}

	@Override
	public Class<?> readClass(int parameterizedType, Type targetType) throws IOException {
		String className = readStringData(parameterizedType);
		return context.loadClass(className, targetType);
	}
	
	private void printClass(SpearalPrinter printer, int parameterizedType) throws IOException {
		printer.printClass(getStringData(parameterizedType));
	}

	@Override
	public Object readBean(int parameterizedType, Type targetType) throws IOException {
		final int indexOrLength = readIndexOrLength(parameterizedType);
		
		if (isObjectReference(parameterizedType)) {
			Object value = sharedObjects.get(indexOrLength);
			partialObjectsMap.appendIfPresent(value, path.peek());
			return value;
		}
		
		String classDescription = readStringData(parameterizedType, indexOrLength);
		ClassDescriptor descriptor = descriptors.putIfAbsent(context, classDescription, targetType);

		try {
			Class<?> cls = descriptor.cls;
			
			Object value;
			if (cls == ClassNotFound.class)
				value = new ClassNotFound(classDescription);
			else if (!descriptor.partial)
				value = context.instantiate(cls, null);
			else {
				value = context.instantiatePartial(cls, descriptor.properties);
				partialObjectsMap.put(value, path.peek());
			}
			sharedObjects.add(value);
			
			BeanPathSegmentImpl segment = new BeanPathSegmentImpl(value);
			path.push(segment);
			for (Property property : descriptor.properties) {
				segment.property = property;
				int propertyType = readNextByte();
				if (property != null)
					property.read(this, value, propertyType);
				else
					skipAny(propertyType);
			}
			path.pop();
			
			return value;
		}
		catch (Exception e) {
			throw new IOException(e);
		}
	}
	
	private void printBean(SpearalPrinter printer, int parameterizedType) throws IOException {
		final int indexOrLength = readIndexOrLength(parameterizedType);
		
		if (isObjectReference(parameterizedType))
			printer.printBeanReference(indexOrLength);
		else {
			StringData classDescription = getStringData(parameterizedType, indexOrLength);
			String[] classNames = ClassDescriptionUtil.splitClassNames(classDescription.value);
			
			final int index = sharedObjects.size();
			sharedObjects.add(null);
			printer.printBeanStart(index, classDescription, classNames);
			
			String[] propertyNames = ClassDescriptionUtil.splitPropertyNames(classDescription.value);
			int propertyIndex = 0;
			for (String propertyName : propertyNames) {
				printer.printBeanPropertyStart(index, propertyName, propertyIndex);
				printAny(printer);
				printer.printBeanPropertyEnd(index, propertyIndex++);
			}
			
			printer.printBeanEnd(index);
		}
	}
	
	private int readNextByte() throws IOException {
		ensureAvailable(1);
		return (buffer[position++] & 0xff);
	}
	
	private int readIndexOrLength(int parameterizedType) throws IOException {
		int length0 = (parameterizedType & 0x03);
		ensureAvailable(length0 + 1);
		return readUnsignedIntegerValue(length0);
	}
	
	private int readUnsignedIntegerValue(int length0) {
		int v = 0;

		switch (length0) {
		case 3:
			v |= (buffer[position++] & 0xff) << 24;
		case 2:
			v |= (buffer[position++] & 0xff) << 16;
		case 1:
			v |= (buffer[position++] & 0xff) << 8;
		case 0:
			v |= (buffer[position++] & 0xff);
		}
		
		return v;
	}
	
	private String readStringData(int parameterizedType) throws IOException {
		int indexOrLength = readIndexOrLength(parameterizedType);
		return readStringData(parameterizedType, indexOrLength);
	}
	
	private String readStringData(int parameterizedType, int indexOrLength) throws IOException {
		if (isStringReference(parameterizedType))
			return sharedStrings.get(indexOrLength);
		
		if (indexOrLength == 0)
			return "";
		
		String value;
		if (indexOrLength <= buffer.length) {
			ensureAvailable(indexOrLength);
			value = new String(buffer, position, indexOrLength, UTF8);
			position += indexOrLength;
		}
		else {
			byte[] bytes = new byte[indexOrLength];
			readFully(bytes, 0, indexOrLength);
			value = new String(bytes, UTF8);
		}
		
		sharedStrings.add(value);
		
		return value;
	}
	
	private StringData getStringData(int parameterizedType) throws IOException {
		int indexOrLength = readIndexOrLength(parameterizedType);
		return getStringData(parameterizedType, indexOrLength);
	}
	
	private StringData getStringData(int parameterizedType, int indexOrLength) throws IOException {
		if (isStringReference(parameterizedType))
			return new StringData(sharedStrings.get(indexOrLength), indexOrLength, true);
		
		if (indexOrLength == 0)
			return new StringData("", -1, false);
		
		String value = readStringData(parameterizedType, indexOrLength);
		return new StringData(value, sharedStrings.size() - 1, false);
	}
	
	private String readBigNumberData(int length) throws IOException {
		char[] chars = new char[length];
		
		final byte[] buffer = this.buffer;
		final int bufferLength = buffer.length;
		int position = this.position;
		int size = this.size;
		
		final int count = (length / 2) + (length % 2);
		int iChar = 0;
		for (int i = 0; i < count; i++) {
			if (position == size) {
				this.position = position;
				fillBuffer(Math.min(count - i, bufferLength));
				position = 0;
				size = this.size;
			}
			int b = (buffer[position++] & 0xff);
			chars[iChar++] = BIG_NUMBER_ALPHA[(b & 0xf0) >>> 4];
			if (iChar == length)
				break;
			chars[iChar++] = BIG_NUMBER_ALPHA[b & 0x0f];
		}
		this.position = position;
		
		String representation = new String(chars);
		sharedStrings.add(representation);
		return representation;
	}

	private long readLongData() {
		final byte[] buffer = this.buffer;
		int position = this.position;
		
		long v =
			(buffer[position++] & 0xffL) << 56 |
			(buffer[position++] & 0xffL) << 48 |
			(buffer[position++] & 0xffL) << 40 |
			(buffer[position++] & 0xffL) << 32 |
			(buffer[position++] & 0xffL) << 24 |
			(buffer[position++] & 0xffL) << 16 |
			(buffer[position++] & 0xffL) <<  8 |
			(buffer[position++] & 0xffL);

		this.position = position;
		
		return v;
	}

	private void readFully(byte[] b, int off, int len) throws IOException {
		if (b == null)
			throw new NullPointerException();
		if (off < 0 || len < 0 || len > b.length - off)
			throw new IndexOutOfBoundsException();
		if (len == 0)
			return;

		final int left = size - position;
		if (len <= left) {
			System.arraycopy(buffer, position, b, off, len);
			position += len;
		}
		else {
			if (left > 0) {
				System.arraycopy(buffer, position, b, off, left);
				off += left;
				len -= left;
				position = size;
			}
			
			while (len > 0) {
				int count = in.read(b, off, len);
				if (count == len)
					return;
				if (count <= 0)
					throw new EOFException();
				off += count;
				len -= count;
			}
		}
	}
	
	private void ensureAvailable(int count) throws IOException {
		if (size - position < count)
			fillBuffer(count);
	}

	private void fillBuffer(int count) throws IOException {
		if (position > 0) {
			size -= position;
			System.arraycopy(buffer, position, buffer, 0, size);
			position = 0;
		}
		
		do {
			int read = in.read(buffer, size, buffer.length - size);
			if (read == -1)
				throw new EOFException();
			size += read;
		}
		while (size < count);
	}
	
	private static boolean isObjectReference(int parameterizedType) {
		return ((parameterizedType & 0x08) != 0);
	}
	
	private static boolean isStringReference(int parameterizedType) {
		return ((parameterizedType & 0x04) != 0);
	}
	
	private static class ClassDescriptor {
		
		public final Class<?> cls;
		public final Property[] properties;
		public final boolean partial;

		public static ClassDescriptor forDescription(SpearalContext context, String description, Type targetType) {
			String classNames = ClassDescriptionUtil.classNames(description);
			String[] propertyNames = ClassDescriptionUtil.splitPropertyNames(description);

			Class<?> cls = context.loadClass(classNames, targetType);
			
			if (cls == null) {
				Property[] classNotFoundProperties = new Property[propertyNames.length];
				for (int i = 0; i < propertyNames.length; i++) {
					String propertyName = propertyNames[i];
					classNotFoundProperties[i] = new ClassNotFoundProperty(propertyName);
				}
				return new ClassDescriptor(ClassNotFound.class, classNotFoundProperties, false);
			}

			
			Property[] properties = context.getProperties(cls);

			Property[] serializedProperties = new Property[propertyNames.length];
			boolean partial = false;
			
			propertiesLoop:
			for (Property property : properties) {
				String propertyName = property.getName();
				for (int i = 0; i < propertyNames.length; i++) {
					if (propertyName.equals(propertyNames[i])) {
						serializedProperties[i] = property;
						continue propertiesLoop;
					}
				}
				partial = true;
			}
			
			if (Proxy.isProxyClass(cls)) {
				partial = true;
				cls = context.loadClass(classNames + "," + PartialObjectProxy.class.getName(), targetType);
			}
			
			return new ClassDescriptor(cls, serializedProperties, partial);
		}
		
		public ClassDescriptor(Class<?> cls, Property[] properties, boolean partial) {
			this.cls = cls;
			this.properties = properties;
			this.partial = partial;
		}
	}
	
	public static class ClassNotFound extends HashMap<String, Object> {

		private static final long serialVersionUID = 1L;

		private final String classNotFoundDescription;
		
		public ClassNotFound(String classNotFoundDescription) {
			this.classNotFoundDescription = classNotFoundDescription;
		}

		public String getClassNotFoundDescription() {
			return classNotFoundDescription;
		}
	}
	
	private static class ClassNotFoundProperty implements Property {

		private final String name;
		
		public ClassNotFoundProperty(String name) {
			this.name = name;
		}

		@Override
		public String getName() {
			return name;
		}

		@Override
		public Class<?> getType() {
			return Object.class;
		}

		@Override
		public Type getGenericType() {
			return Object.class;
		}

		@Override
		public boolean hasField() {
			return false;
		}

		@Override
		public Field getField() {
			return null;
		}

		@Override
		public boolean hasGetter() {
			return false;
		}

		@Override
		public Method getGetter() {
			return null;
		}

		@Override
		public boolean hasSetter() {
			return false;
		}

		@Override
		public Method getSetter() {
			return null;
		}

		@Override
		public Class<?> getDeclaringClass() {
			return ClassNotFound.class;
		}

		@Override
		public Object init(ExtendedSpearalDecoder decoder, Object holder)
			throws InstantiationException, IllegalAccessException, InvocationTargetException {
			return null;
		}

		@Override
		public Object get(Object holder)
			throws IllegalAccessException, InvocationTargetException {
			return ((ClassNotFound)holder).get(name);
		}

		@Override
		public void set(Object holder, Object value)
			throws IllegalAccessException, InvocationTargetException {
			((ClassNotFound)holder).put(name, value);
		}

		@Override
		public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
			return false;
		}

		@Override
		public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
			return null;
		}

		@Override
		public boolean isReadOnly() {
			return false;
		}

		@Override
		public void write(ExtendedSpearalEncoder encoder, Object holder)
			throws IOException, IllegalAccessException, InvocationTargetException {
			throw new UnsupportedOperationException();
		}

		@Override
		public void read(ExtendedSpearalDecoder decoder, Object holder, int parameterizedType)
			throws IOException, InstantiationException, IllegalAccessException, InvocationTargetException {
			
			set(holder, decoder.readAny(parameterizedType));
		}
	}
	
	public static class CollectionPathSegmentImpl implements CollectionPathSegment {
		
		private final Collection<?> collection;
		private int index;
		
		public CollectionPathSegmentImpl(Collection<?> collection) {
			this.collection = collection;
		}

		public Collection<?> getCollection() {
			return collection;
		}

		public int getIndex() {
			return index;
		}

		@Override
		public CollectionPathSegment copy() {
			CollectionPathSegmentImpl copy = new CollectionPathSegmentImpl(collection);
			copy.index = index;
			return copy;
		}

		@Override
		public String toString() {
			return "[" + index + "]";
		}
	}
	
	public static class ArrayPathSegmentImpl implements ArrayPathSegment {
		
		private final Object array;
		private int index;
		
		public ArrayPathSegmentImpl(Object array) {
			this.array = array;
		}

		public Object getArray() {
			return array;
		}

		public int getIndex() {
			return index;
		}

		@Override
		public ArrayPathSegment copy() {
			ArrayPathSegmentImpl copy = new ArrayPathSegmentImpl(array);
			copy.index = index;
			return copy;
		}

		@Override
		public String toString() {
			return "[" + index + "]";
		}
	}
	
	public static class MapPathSegmentImpl implements MapPathSegment {
		
		private final Map<?, ?> map;
		private Object key;

		public MapPathSegmentImpl(Map<?, ?> map) {
			this.map = map;
		}

		public Map<?, ?> getMap() {
			return map;
		}

		public Object getKey() {
			return key;
		}

		@Override
		public MapPathSegment copy() {
			MapPathSegmentImpl copy = new MapPathSegmentImpl(map);
			copy.key = key;
			return copy;
		}

		@Override
		public String toString() {
			if (key == null)
				return "";
			try {
				return key.toString();
			}
			catch (Exception e) {
				return "[ERROR: " + e.getMessage() + "]";
			}
		}
	}
	
	public static class BeanPathSegmentImpl implements BeanPathSegment {
		
		private final Object bean;
		private Property property;

		public BeanPathSegmentImpl(Object bean) {
			this.bean = bean;
		}

		public Object getBean() {
			return bean;
		}

		public Property getProperty() {
			return property;
		}

		@Override
		public BeanPathSegment copy() {
			BeanPathSegmentImpl copy = new BeanPathSegmentImpl(bean);
			copy.property = property;
			return copy;
		}

		@Override
		public String toString() {
			return property.getName();
		}
	}
	
	private static class PathImpl implements Path {
		
		private static final int DEFAULT_SIZE_INCREMENT = 8;
		
		private PathSegment[] segments;
		private int size; 
		
		public PathImpl() {
			this.segments = new PathSegment[0];
			this.size = 0;
		}

		@Override
		public Collection<PathSegment> segments() {
			List<PathSegment> segmentsList = new ArrayList<PathSegment>(size);
			for (int i = 0; i < size; i++)
				segmentsList.add(segments[i]);
			return segmentsList;
		}

		public void push(PathSegment segment) {
			if (size == segments.length) {
				PathSegment[] newSegments = new PathSegment[segments.length + DEFAULT_SIZE_INCREMENT];
				if (segments.length > 0)
					System.arraycopy(segments, 0, newSegments, 0, segments.length);
				segments = newSegments;
			}
			segments[size++] = segment;
		}
		
		public void pop() {
			if (size == 0)
				throw new IndexOutOfBoundsException("Empty path");
			size--;
		}
		
		public PathSegment peek() {
			return (size == 0 ? null : segments[size - 1]);
		}

		@Override
		public String toString() {
			if (size == 0)
				return "";
			
			StringBuilder sb = new StringBuilder().append(segments[0]);
			for (int i = 1; i < size; i++)
				sb.append('.').append(segments[i]);
			return sb.toString();
		}
	}
	
	private static class PartialObjectMap extends IdentityHashMap<Object, List<PathSegment>> {

		private static final long serialVersionUID = 1L;
		
		public void put(Object partialObject, PathSegment segment) {
			List<PathSegment> segments = new ArrayList<PathSegment>();
			if (segment != null)
				segments.add(segment.copy());
			put(partialObject, segments);
		}
		
		public void appendIfPresent(Object partialObject, PathSegment segment) {
			if (segment != null) {
				List<PathSegment> segments = get(partialObject);
				if (segments != null)
					segments.add(segment.copy());
			}
		}
	}
} public class SpearalEncoderImpl implements ExtendedSpearalEncoder {

	private final SpearalContext context;
	private final SpearalPropertyFilter propertyFilter;
	private final OutputStream out;
	
	private final StringIndexMap sharedStrings;
	private final IdentityIndexMap sharedObjects;
	private final Map<Class<?>, FilteredBeanDescriptor> descriptors;
	
	private final byte[] buffer;
	private int position;
	
	private int depth;
	
	public SpearalEncoderImpl(SpearalContext context, OutputStream out) {
		this(context, null, out, 1024);
	}
	
	public SpearalEncoderImpl(SpearalContext context, OutputStream out, int capacity) {
		this(context, null, out, capacity);
	}
	
	public SpearalEncoderImpl(SpearalContext context, SpearalPropertyFilter request, OutputStream out) {
		this(context, request, out, 1024);
	}
	
	public SpearalEncoderImpl(final SpearalContext context, SpearalPropertyFilter propertyFilter, OutputStream out, int capacity) {
		this.context = context;
		this.propertyFilter = (propertyFilter != null ? propertyFilter : new SpearalPropertyFilterImpl(context));
		this.out = out;
		
		this.sharedStrings = new StringIndexMap();
		this.sharedObjects = new IdentityIndexMap();
		this.descriptors = new IdentityHashMap<Class<?>, FilteredBeanDescriptor>(32);

		this.buffer = new byte[capacity];
        this.position = 0;
        
        this.depth = 0;
	}
	
	@Override
	public SpearalContext getContext() {
		return context;
	}

	@Override
	public SpearalPropertyFilter getPropertyFilter() {
		return propertyFilter;
	}

	@Override
	public void writeAny(Object o) throws IOException {
		++depth;
		
		if (o == null)
			writeNull();
		else
			context.getCoder(o.getClass()).encode(this, o);
		
		if ((--depth) == 0)
			flushBuffer();
	}

	@Override
	public void writeNull() throws IOException {
		ensureCapacity(1);
		buffer[position++] = (byte)SpearalType.NULL.id();
	}
	
	@Override
	public void writeBoolean(boolean value) throws IOException {
		ensureCapacity(1);
		buffer[position++] = (byte)(value ? SpearalType.TRUE.id() : SpearalType.FALSE.id());
	}
	
	@Override
	public void writeDateTime(SpearalDateTime date) throws IOException {
		
		int parameters = 0x00;
		int subsecs = date.nanoseconds;
		
		if (date.hasDate)
			parameters |= 0x08;
		
		if (date.hasTime) {
			parameters |= 0x04;
			if (subsecs != 0) {
				if (subsecs % 1000 == 0) {
					if (subsecs % 1000000 == 0) {
						subsecs /= 1000000;
						parameters |= 0x03;
					}
					else {
						subsecs /= 1000;
						parameters |= 0x02;
					}
				}
				else
					parameters |= 0x01;
			}
		}
		
		ensureCapacity(1);
		buffer[position++] = (byte)(SpearalType.DATE_TIME.id() | parameters);
		
		if (date.hasDate) {
			int year = date.year - 2000;
			
			int inverse;
			if (year < 0) {
				inverse = 0x80;
				year = -year;
			}
			else
				inverse = 0x00;
			
			int length0 = unsignedIntLength0(year);
			
			ensureCapacity(length0 + 3);
			buffer[position++] = (byte)(inverse | (length0 << 4) | date.month);
			buffer[position++] = (byte)date.date;
			writeUnsignedIntValue(year, length0);
		}
		
		if (date.hasTime) {
			if (subsecs == 0) {
				ensureCapacity(3);
				buffer[position++] = (byte)date.hours;
				buffer[position++] = (byte)date.minutes;
				buffer[position++] = (byte)date.seconds;
			}
			else {
				int length0 = unsignedIntLength0(subsecs);
				ensureCapacity(length0 + 4);
				buffer[position++] = (byte)((length0 << 5) | date.hours);
				buffer[position++] = (byte)date.minutes;
				buffer[position++] = (byte)date.seconds;
				writeUnsignedIntValue(subsecs, length0);				
			}
		}
	}
	
	@Override
	public void writeByte(byte value) throws IOException {
		int inverse = 0;
		if (value < 0) {
			inverse = 0x08;
			if (value != Byte.MIN_VALUE)
				value = (byte)-value;
		}
		
		ensureCapacity(2);
		buffer[position++] = (byte)(SpearalType.INTEGRAL.id() | inverse);
		buffer[position++] = value;
	}
	
	@Override
	public void writeShort(short value) throws IOException {
		int inverse;
		int length0;
		if (value < 0) {
			inverse = 0x08;
			if (value == Short.MIN_VALUE)
				length0 = 1;
			else {
				value = (short)-value;
				length0 = (value <= 0xff ? 0 : 1);
			}
		}
		else {
			inverse = 0;
			length0 = (value <= 0xff ? 0 : 1);
		}
		
		ensureCapacity(length0 + 2);
		buffer[position++] = (byte)(SpearalType.INTEGRAL.id() | inverse | length0);
		
		if (length0 == 1)
			buffer[position++] = (byte)(value >>> 8);
		buffer[position++] = (byte)value;
	}
	
	@Override
	public void writeInt(int value) throws IOException {
		int inverse;
		int length0;
		if (value < 0) {
			inverse = 0x08;
			if (value == Integer.MIN_VALUE)
				length0 = 3;
			else {
				value = -value;
				length0 = unsignedIntLength0(value);
			}
		}
		else {
			inverse = 0;
			length0 = unsignedIntLength0(value);
		}
		
		ensureCapacity(length0 + 2);
		buffer[position++] = (byte)(SpearalType.INTEGRAL.id() | inverse | length0);
		writeUnsignedIntValue(value, length0);
	}
	
	@Override
	public void writeLong(long value) throws IOException {
		int inverse = 0;
		int length0;
		if (value < 0) {
			if (value == Long.MIN_VALUE)
				length0 = 7;
			else {
				inverse = 0x08;
				value = -value;
				length0 = unsignedLongLength0(value);
			}
		}
		else
			length0 = unsignedLongLength0(value);
		
		ensureCapacity(length0 + 2);
		
		final byte[] buffer = this.buffer;
		int position = this.position;
		
		buffer[position++] = (byte)(SpearalType.INTEGRAL.id() | inverse | length0);
		
		switch (length0) {
		case 7:
			buffer[position++] = (byte)(value >>> 56);
		case 6:
			buffer[position++] = (byte)(value >>> 48);
		case 5:
			buffer[position++] = (byte)(value >>> 40);
		case 4:
			buffer[position++] = (byte)(value >>> 32);
		case 3:
			buffer[position++] = (byte)(value >>> 24);
		case 2:
			buffer[position++] = (byte)(value >>> 16);
		case 1:
			buffer[position++] = (byte)(value >>> 8);
		case 0:
			buffer[position++] = (byte)value;
			break;
		default:
			throw new RuntimeException("Internal error: length0=" + length0);
		}
		
		this.position = position;
	}
	
	@Override
	public void writeBigInteger(BigInteger value) throws IOException {
		writeBigNumberData(SpearalType.BIG_INTEGRAL.id(), exponentize(value));
	}

	@Override
	public void writeFloat(float value) throws IOException {
		writeDouble(value);
	}

	@Override
	public void writeDouble(double value) throws IOException {

		long bits = Double.doubleToLongBits(value);
		
		// Not NaN, +/-Infinity or -0.0
		if ((bits & 0x7ff0000000000000L) != 0x7ff0000000000000L && bits != 0x8000000000000000L) {
			
			long doubleAsLong = (long)value;
			
			if (value == doubleAsLong) {
				// 6.5 bytes max (absolute value), i.e. max length of a IEEE 754 fraction part.
				if (doubleAsLong >= -0x000fffffffffffffL && doubleAsLong <= 0x000fffffffffffffL) {
					writeLong(doubleAsLong);
					return;
				}
			}
			else {
				doubleAsLong = (long)(value * 1000.0);

				if (value == (doubleAsLong / 1000.0) ||
					value == ((doubleAsLong += (doubleAsLong < 0 ? -1 : 1)) / 1000.0)) {
					
					// 4 bytes max (absolute value)
					if (doubleAsLong >= -0xffffffffL && doubleAsLong <= 0xffffffffL) {
						int inverse;
						
						if (doubleAsLong < 0) {
							doubleAsLong = -doubleAsLong;
							inverse = 0x04;
						}
						else
							inverse = 0x00;
						
						int length0 = unsignedLongLength0(doubleAsLong);
						
						ensureCapacity(length0 + 2);
	
						final byte[] buffer = this.buffer;
						int position = this.position;
						
						buffer[position++] = (byte)(SpearalType.FLOATING.id() | 0x08 | inverse | length0);
						
						switch (length0) {
						case 3:
							buffer[position++] = (byte)(doubleAsLong >>> 24);
						case 2:
							buffer[position++] = (byte)(doubleAsLong >>> 16);
						case 1:
							buffer[position++] = (byte)(doubleAsLong >>> 8);
						case 0:
							buffer[position++] = (byte)doubleAsLong;
							break;
						default:
							throw new RuntimeException("Internal error: length0=" + length0);
						}
						
						this.position = position;
						
						return;
					}
				}
			}
		}
		
		ensureCapacity(9);
		buffer[position++] = (byte)SpearalType.FLOATING.id();
		writeLongData(bits);
	}

	@Override
	public void writeBigDecimal(BigDecimal value) throws IOException {
		writeBigNumberData(SpearalType.BIG_FLOATING.id(), value.toString());
	}
	
	@Override
	public void writeChar(char value) throws IOException {
		writeStringData(SpearalType.STRING.id(), String.valueOf(value));
	}

	@Override
	public final void writeString(String value) throws IOException {
		writeStringData(SpearalType.STRING.id(), value);
	}

	@Override
	public void writeByteArray(byte[] value) throws IOException {
		if (!putAndWriteObjectReference(SpearalType.BYTE_ARRAY.id(), value)) {
			writeTypeUint(SpearalType.BYTE_ARRAY.id(), value.length);
			writeBytes(value);
		}
	}

	@Override
	public void writeArray(Object value) throws IOException {
		if (!putAndWriteObjectReference(SpearalType.COLLECTION.id(), value)) {
			final int size = Array.getLength(value);
			writeTypeUint(SpearalType.COLLECTION.id(), size);
			for (int i = 0; i < size; i++)
				writeAny(Array.get(value, i));
		}
	}

	@Override
	public void writeCollection(Collection<?> value) throws IOException {
		if (!putAndWriteObjectReference(SpearalType.COLLECTION.id(), value)) {
			final int size = value.size();
			writeTypeUint(SpearalType.COLLECTION.id(), size);
			for (Object item : value)
				writeAny(item);
		}
	}

	@Override
	public void writeMap(Map<?, ?> value) throws IOException {
		if (!putAndWriteObjectReference(SpearalType.MAP.id(), value)) {
			final int size = value.size();
			writeTypeUint(SpearalType.MAP.id(), size);
			for (Map.Entry<?, ?> entry : value.entrySet()) {
				writeAny(entry.getKey());
				writeAny(entry.getValue());
			}
		}
	}

	@Override
	public void writeEnum(Enum<?> value) throws IOException {
		writeStringData(SpearalType.ENUM.id(), value.getClass().getName());
		writeStringData(SpearalType.STRING.id(), value.name());
	}
	
	@Override
	public void writeClass(Class<?> value) throws IOException {
		writeStringData(SpearalType.CLASS.id(), value.getName());
	}

	@Override
	public void writeBean(Object value) throws IOException {
		if (!putAndWriteObjectReference(SpearalType.BEAN.id(), value)) {
			Class<?> cls = value.getClass();
			
			FilteredBeanDescriptor descriptor = descriptors.get(cls);
			if (descriptor == null) {
				descriptor = context.createDescriptor(propertyFilter, value);
				if (descriptor.isCacheable())
					descriptors.put(cls, descriptor);
			}
			
			writeStringData(SpearalType.BEAN.id(), descriptor.getDescription());
			
			for (Property property : descriptor.getProperties()) {
				if (property == null)
					continue;
				try {
					property.write(this, value);
				}
				catch (IOException e) {
					throw e;
				}
				catch (Exception e) {
					throw new IOException(e);
				}
			}
		}
	}
	
    private void writeStringData(int type, String s) throws IOException {
    	if (s.length() == 0) {
        	ensureCapacity(2);
    		buffer[position++] = (byte)type;
        	buffer[position++] = 0;
            return;
        }
    	
    	if (!putAndWriteStringReference(type, s)) {
        	byte[] bytes = s.getBytes(UTF8);
        	writeTypeUint(type, bytes.length);
        	writeBytes(bytes);
        }
    }
	
	private static String exponentize(BigInteger value) {
		String representation = value.toString(10);
		final int length = representation.length();
		if (length > 3) {
			int trailingZeros = 0;
			for (int i = length - 1; i > 0 && representation.charAt(i) == '0'; i--)
				trailingZeros++;
			if (trailingZeros > 2)
				representation = representation.substring(0, length - trailingZeros) + "E" + trailingZeros;
		}
		return representation;
	}
	
	private void writeBigNumberData(int type, String representation) throws IOException {
        if (!putAndWriteStringReference(type, representation)) {
        	final int length = representation.length();
        	writeTypeUint(type, length);
        	
	    	final byte[] buffer = this.buffer;
	    	final int bufferLength = buffer.length;
	    	int position = this.position;
	
	    	for (int i = 0; i < length; ) {
	    		if (position >= bufferLength) {
	    			this.position = bufferLength;
	    			flushBuffer();
	    			position = 0;
	    		}
	    		int b = (BIG_NUMBER_ALPHA_MIRROR[representation.charAt(i++)] << 4);
	    		if (i == length) {
	    			buffer[position++] = (byte)b;
	    			break;
	    		}
	    		b |= BIG_NUMBER_ALPHA_MIRROR[representation.charAt(i++)];
	    		buffer[position++] = (byte)b;
	    	}
	    	this.position = position;
        }
	}
	
	private boolean putAndWriteObjectReference(int type, Object o) throws IOException {
		int index = sharedObjects.putIfAbsent(o);
		if (index != -1) {
			writeTypeUint(type | 0x08, index);
			return true;
		}
		return false;
	}
	
	private boolean putAndWriteStringReference(int type, String s) throws IOException {
		int index = sharedStrings.putIfAbsent(s);
		if (index != -1) {
			writeTypeUint(type | 0x04, index);
			return true;
		}
		return false;
	}
	
	private void writeTypeUint(int type, int uint) throws IOException {
		final int length0 = unsignedIntLength0(uint);
		ensureCapacity(length0 + 2);
		buffer[position++] = (byte)(type | length0);
		writeUnsignedIntValue(uint, length0);
	}

	private static int unsignedLongLength0(long value) {
		if (value <= 0xffffffffL) {
			if (value <= 0xffffL)
				return (value <= 0xffL ? 0 : 1);
			return (value <= 0xffffffL ? 2 : 3);
		}
		if (value <= 0xffffffffffffL)
			return (value <= 0xffffffffffL ? 4 : 5);
		return (value <= 0xffffffffffffffL ? 6 : 7);
	}
	
	private void writeLongData(long value) {
		final byte[] buffer = this.buffer;
		int position = this.position;
		
		buffer[position++] = (byte)(value >>> 56);
		buffer[position++] = (byte)(value >>> 48);
		buffer[position++] = (byte)(value >>> 40);
		buffer[position++] = (byte)(value >>> 32);
		buffer[position++] = (byte)(value >>> 24);
		buffer[position++] = (byte)(value >>> 16);
		buffer[position++] = (byte)(value >>> 8);
		buffer[position++] = (byte)value;
		
		this.position = position;
	}

	private static int unsignedIntLength0(int value) {
		if (value <= 0xff)
			return 0;
		if (value <= 0xffff)
			return 1;
		return (value <= 0xffffff ? 2 : 3);
	}
	
	private void writeUnsignedIntValue(int value, int length0) {
		switch (length0) {
		case 3:
			buffer[position++] = (byte)(value >>> 24);
		case 2:
			buffer[position++] = (byte)(value >>> 16);
		case 1:
			buffer[position++] = (byte)(value >>> 8);
		case 0:
			buffer[position++] = (byte)value;
			break;
		default:
			throw new RuntimeException("Internal error: length0=" + length0);
		}
	}

    private void ensureCapacity(int capacity) throws IOException {
		if (buffer.length - position < capacity)
			flushBuffer();
	}
	
    private void flushBuffer() throws IOException {
		if (position > 0) {
			out.write(buffer, 0, position);
			position = 0;
		}
	}
    
    private void writeBytes(byte[] bytes) throws IOException {
    	if (bytes.length > 0) {
	    	if (buffer.length - position >= bytes.length) {
	    		System.arraycopy(bytes, 0, buffer, position, bytes.length);
	    		position += bytes.length;
	    	}
	    	else {
	    		flushBuffer();
	    		out.write(bytes, 0, bytes.length);
	    	}
    	}
    }
} @SuppressWarnings("boxing")
public class SpearalPrinterImpl implements SpearalPrinter {
	
	protected final PrintStream out;
	protected final int maxStringLength;
	protected final int maxByteArrayLength;
	
	protected int indent;

	public SpearalPrinterImpl(PrintStream out) {
		this(out, 256, 256);
	}
	
	public SpearalPrinterImpl(PrintStream out, int maxStringLength, int maxByteArrayLength) {
		if (out == null)
			throw new NullPointerException();
		
		this.out = out;
		this.maxStringLength = maxStringLength;
		this.maxByteArrayLength = maxByteArrayLength;
		
		this.indent = 0;
	}

	public PrintStream getOut() {
		return out;
	}

	public int getMaxStringLength() {
		return maxStringLength;
	}

	public int getMaxByteArrayLength() {
		return maxByteArrayLength;
	}

	@Override
	public void printNull() throws IOException {
		out.print("null");
	}

	@Override
	public void printBoolean(boolean value) throws IOException {
		out.print(value);
	}

	@Override
	public void printDateTime(SpearalDateTime value) throws IOException {
		out.print(value);
	}

	@Override
	public void printIntegral(long value) throws IOException {
		out.print(value);
	}

	@Override
	public void printBigIntegral(StringData value) throws IOException {
		out.print(value.value);
	}

	@Override
	public void printFloating(double value) throws IOException {
		out.print(value);
	}

	@Override
	public void printBigFloating(StringData value) throws IOException {
		out.print(value.value);
	}

	@Override
	public void printString(StringData value) throws IOException {
		out.printf("\"%s\"", escape(value.value));
	}

	@Override
	public void printByteArray(byte[] value, int index, boolean reference) throws IOException {
		if (reference)
			out.printf("(^%d)", index);
		else {
			out.printf("<#%d> [", index);
			for (int i = 0; i < value.length; i++) {
				if (i > 0)
					out.print(", ");
				out.printf("0x%02x", value[i] & 0xff);
				if (i >= maxByteArrayLength) {
					out.printf(" (and %d more...)", value.length - i);
					break;
				}
			}
			out.print(']');
		}
	}

	@Override
	public void printCollectionStart(int index, int length) throws IOException {
		out.printf("<#%d> [", index);
		indent++;
	}

	@Override
	public void printCollectionItemStart(int index, int itemIndex) throws IOException {
		if (index != 0)
			out.print(',');
		indent();
	}

	@Override
	public void printCollectionItemEnd(int index, int itemIndex) throws IOException {
	}

	@Override
	public void printCollectionEnd(int index) throws IOException {
		indent--;
		indent();
		out.print(']');
	}

	@Override
	public void printCollectionReference(int index) throws IOException {
		out.printf("(^%d)", index);
	}

	@Override
	public void printMapStart(int index, int length) throws IOException {
		out.printf("<#%d> {", index);
		indent++;
	}

	@Override
	public void printMapKeyStart(int index, int entryIndex) throws IOException {
		if (index != 0)
			out.print(',');
		indent();
	}

	@Override
	public void printMapKeyEnd(int index, int entryIndex) throws IOException {
		out.print(" -> ");
	}

	@Override
	public void printMapValueStart(int index, int entryIndex) throws IOException {
		indent++;
	}

	@Override
	public void printMapValueEnd(int index, int entryIndex) throws IOException {
		indent--;
	}

	@Override
	public void printMapEnd(int index) throws IOException {
		indent--;
		indent();
		out.print('}');
	}

	@Override
	public void printMapReference(int index) throws IOException {
		out.printf("(^%d)", index);
	}

	@Override
	public void printEnum(StringData className, StringData value) throws IOException {
		out.printf("%s.%s", className.value, value.value);
	}

	@Override
	public void printClass(StringData className) throws IOException {
		out.print(className.value);
	}

	@Override
	public void printBeanStart(int index, StringData classDescription, String[] classNames)
		throws IOException {
		boolean first = true;
		for (String className : classNames) {
			if (first)
				first = false;
			else
				out.print(", ");
			out.print(className);
		}
		
		out.printf(" <#%d> {", index);
		indent++;
	}

	@Override
	public void printBeanPropertyStart(int index, String propertyName, int propertyIndex) {
		if (propertyIndex > 0)
			out.print(',');
		
		indent();
		out.printf("%s: ", propertyName);
	}

	@Override
	public void printBeanPropertyEnd(int index, int propertyIndex) {
	}

	@Override
	public void printBeanEnd(int index) throws IOException {
		indent--;
		indent();
		out.print('}');
	}

	@Override
	public void printBeanReference(int index) throws IOException {
		out.printf("(^%d)", index);
	}
	
	protected void indent() {
		out.println();
		for (int i = 0; i < indent; i++)
			out.print("    ");
	}
	
	protected String escape(String s) {
		if (s == null || s.length() == 0)
			return s;
		
		StringBuilder sb = new StringBuilder(Math.min(s.length(), maxStringLength));
		final int max = s.length();
		for (int i = 0; i < max; i++) {
			char c = s.charAt(i);
			escape(c, sb);
			
			if (i >= maxStringLength) {
				sb.append(" (and ");
				sb.append(s.length() - 1);
				sb.append(" more...)");
				break;
			}
		}
		return sb.toString();
	}
	
	protected void escape(char c, StringBuilder sb) {
		if (c >= 0x20 && c <= 0x7F) {
			if (c == '"' || c == '\\')
				sb.append('\\');
			sb.append(c);
		}
		else {
			switch (c) {
				case '\b': sb.append("\\b"); break;
				case '\t': sb.append("\\t"); break;
				case '\n': sb.append("\\n"); break;
				case '\f': sb.append("\\f"); break;
				case '\r': sb.append("\\r"); break;
				default: {
					String hex = Integer.toHexString(c);
					switch (hex.length()) {
						case 1: sb.append("\\u000"); break;
						case 2: sb.append("\\u00"); break;
						case 3: sb.append("\\u0"); break;
						default: sb.append("\\u"); break;
					}
					sb.append(hex);
					break;
				}
			}
		}
	}
} public class AliasStrategyImpl implements AliasStrategy {

	@Override
	public String alias(Class<?> cls) {
		return cls.getName();
	}

	@Override
	public String unalias(String aliasedClassName) {
		return aliasedClassName;
	}
} public class ArrayCoder implements CoderProvider, Coder {

	@Override
	public Coder getCoder(Class<?> valueClass) {
		return (valueClass.isArray() ? this : null);
	}

	@Override
	public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
		encoder.writeArray(value);
	}
} public class BeanCoder implements CoderProvider, Coder {

	@Override
	public Coder getCoder(Class<?> valueClass) {
		return this;
	}

	@Override
	public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
		encoder.writeBean(value);
	}
} public class CollectionCoder implements CoderProvider, Coder {

	@Override
	public Coder getCoder(Class<?> valueClass) {
		return (Collection.class.isAssignableFrom(valueClass) ? this : null);
	}

	@Override
	public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
		encoder.writeCollection((Collection<?>)value);
	}
} public class EnumCoder implements CoderProvider, Coder {

	@Override
	public Coder getCoder(Class<?> valueClass) {
		return (Enum.class.isAssignableFrom(valueClass) ? this : null);
	}

	@Override
	public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
		encoder.writeEnum((Enum<?>)value);
	}
} public class MapCoder implements CoderProvider, Coder {

	@Override
	public Coder getCoder(Class<?> valueClass) {
		return (Map.class.isAssignableFrom(valueClass) ? this : null);
	}

	@Override
	public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
		encoder.writeMap((Map<?, ?>)value);
	}
} public class SimpleCodersProvider implements CoderProvider {

	private final Map<Class<?>, Coder> coders;
	
	public SimpleCodersProvider() {
		this.coders = new HashMap<Class<?>, Coder>();

		// Boolean
		
		coders.put(Boolean.class, new Coder() {
			@Override
			public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
				encoder.writeBoolean(((Boolean)value).booleanValue());
			}
		});

		// Numbers
		
		coders.put(Byte.class, new Coder() {
			@Override
			public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
				encoder.writeByte(((Byte)value).byteValue());
			}
		});
		
		coders.put(Short.class, new Coder() {
			@Override
			public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
				encoder.writeShort(((Short)value).shortValue());
			}
		});
		
		coders.put(Integer.class, new Coder() {
			@Override
			public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
				encoder.writeInt(((Integer)value).intValue());
			}
		});
		
		coders.put(Long.class, new Coder() {
			@Override
			public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
				encoder.writeLong(((Long)value).longValue());
			}
		});
		
		coders.put(BigInteger.class, new Coder() {
			@Override
			public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
				encoder.writeBigInteger(((BigInteger)value));
			}
		});
		
		coders.put(Float.class, new Coder() {
			@Override
			public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
				encoder.writeFloat(((Float)value).floatValue());
			}
		});
		
		coders.put(Double.class, new Coder() {
			@Override
			public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
				encoder.writeDouble(((Double)value).doubleValue());
			}
		});
		
		coders.put(BigDecimal.class, new Coder() {
			@Override
			public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
				encoder.writeBigDecimal(((BigDecimal)value));
			}
		});
		
		// String / Character
		
		coders.put(String.class, new Coder() {
			@Override
			public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
				encoder.writeString((String)value);
			}
		});
		
		coders.put(char[].class, new Coder() {
			@Override
			public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
				encoder.writeString(String.valueOf((char[])value));
			}
		});
		
		coders.put(Character.class, new Coder() {
			@Override
			public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
				encoder.writeString(String.valueOf(((Character)value).charValue()));
			}
		});
		
		// Byte array
		
		coders.put(byte[].class, new Coder() {
			@Override
			public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
				encoder.writeByteArray(((byte[])value));
			}
		});
		
		// Temporal types.
		
		coders.put(GregorianCalendar.class, new Coder() {
			@Override
			public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
				encoder.writeDateTime(SpearalDateTime.forGregorianCalendar((GregorianCalendar)value));
			}
		});
		
		coders.put(Date.class, new Coder() {
			@Override
			public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
				encoder.writeDateTime(SpearalDateTime.forDate((Date)value));
			}
		});
		
		coders.put(java.sql.Date.class, new Coder() {
			@Override
			public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
				encoder.writeDateTime(SpearalDateTime.forSQLDate((java.sql.Date)value));
			}
		});
		
		coders.put(Time.class, new Coder() {
			@Override
			public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
				encoder.writeDateTime(SpearalDateTime.forSQLTime((Time)value));
			}
		});
		
		coders.put(Timestamp.class, new Coder() {
			@Override
			public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
				encoder.writeDateTime(SpearalDateTime.forSQLTimestamp((Timestamp)value));
			}
		});
		
		// Class.
		
		coders.put(Class.class, new Coder() {
			@Override
			public void encode(ExtendedSpearalEncoder encoder, Object value) throws IOException {
				encoder.writeClass((Class<?>)value);
			}
		});
	}

	@Override
	public Coder getCoder(Class<?> valueClass) {
		return coders.get(valueClass);
	}
} public class EnumConverter implements ConverterProvider, Converter<Enum<?>> {

	@Override
	public Converter<?> getConverter(Class<?> valueClass, Type targetType) {
		return (TypeUtil.classOfType(targetType).isEnum() ? this : null);
	}

	@Override
	public Enum<?> convert(SpearalContext context, Object value, Type targetType) {
		if (value == null)
			return null;
		
		if (value.getClass() == targetType)
			return (Enum<?>)value;
		
		@SuppressWarnings({ "rawtypes", "unchecked" })
		Class<Enum> targetEnumType = (Class<Enum>)TypeUtil.classOfType(targetType);
		
		try {
			@SuppressWarnings("unchecked")
			Enum<?> enumValue = Enum.valueOf(targetEnumType, value.toString());
			return enumValue;
		}
		catch (Exception e) {
			return null;
		}
	}
} public class SimpleConvertersProvider implements ConverterProvider {
	
	private final Map<Class<?>, Converter<?>> converters;

	public SimpleConvertersProvider() {
		this.converters = new HashMap<Class<?>, Converter<?>>();
		
		// Boolean
		
		Converter<?> converter = new Converter<Boolean>() {
			@Override
			public Boolean convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return (targetType == boolean.class ? Boolean.FALSE : null);

				Class<?> cls = value.getClass();
				
				if (cls == Boolean.class)
					return (Boolean)value;
				if (cls == String.class)
					return Boolean.valueOf(Boolean.parseBoolean((String)value));
				if (Number.class.isAssignableFrom(cls))
					return Boolean.valueOf(((Number)value).intValue() != 0);
				if (cls == Character.class)
					return Boolean.valueOf(((Character)value).charValue() != '\0');

				return Boolean.TRUE;
			}
		};
		converters.put(Boolean.class, converter);
		converters.put(boolean.class, converter);
		
		// Number
		
		converter = new Converter<Byte>() {
			@Override
			public Byte convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return (targetType == byte.class ? Byte.valueOf((byte)0) : null);

				Class<?> cls = value.getClass();
				
				if (cls == Byte.class)
					return (Byte)value;
				if (Number.class.isAssignableFrom(cls))
					return Byte.valueOf(((Number)value).byteValue());
				if (cls == String.class) {
					try {
						return Byte.valueOf(Byte.parseByte((String)value));
					}
					catch (NumberFormatException e) {
						return Byte.valueOf((byte)0);
					}
				}
				if (cls == Boolean.class)
					return Byte.valueOf((byte)(((Boolean)value).booleanValue() ? 1 : 0));
				if (cls == Character.class)
					return Byte.valueOf((byte)((Character)value).charValue());

				return Byte.valueOf((byte)1);
			}
		};
		converters.put(Byte.class, converter);
		converters.put(byte.class, converter);
		
		converter = new Converter<Short>() {
			@Override
			public Short convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return (targetType == int.class ? Short.valueOf((short)0) : null);

				Class<?> cls = value.getClass();
				
				if (cls == Short.class)
					return (Short)value;
				if (Number.class.isAssignableFrom(cls))
					return Short.valueOf(((Number)value).shortValue());
				if (cls == String.class) {
					try {
						return Short.valueOf(Short.parseShort((String)value));
					}
					catch (NumberFormatException e) {
						return Short.valueOf((short)0);
					}
				}
				if (cls == Character.class)
					return Short.valueOf((short)((Character)value).charValue());
				if (cls == Boolean.class)
					return Short.valueOf((short)(((Boolean)value).booleanValue() ? 1 : 0));

				return Short.valueOf((short)1);
			}
		};
		converters.put(Short.class, converter);
		converters.put(short.class, converter);
		
		converter = new Converter<Integer>() {
			@Override
			public Integer convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return (targetType == int.class ? Integer.valueOf(0) : null);

				Class<?> cls = value.getClass();
				
				if (cls == Integer.class)
					return (Integer)value;
				if (Number.class.isAssignableFrom(cls))
					return Integer.valueOf(((Number)value).intValue());
				if (cls == String.class) {
					try {
						return Integer.valueOf(Integer.parseInt((String)value));
					}
					catch (NumberFormatException e) {
						return Integer.valueOf(0);
					}
				}
				if (cls == Character.class)
					return Integer.valueOf(((Character)value).charValue());
				if (cls == Boolean.class)
					return Integer.valueOf(((Boolean)value).booleanValue() ? 1 : 0);

				return Integer.valueOf(1);
			}
		};
		converters.put(Integer.class, converter);
		converters.put(int.class, converter);
		
		converter = new Converter<Long>() {
			@Override
			public Long convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return (targetType == long.class ? Long.valueOf(0L) : null);
				
				Class<?> cls = value.getClass();
				
				if (cls == Long.class)
					return (Long)value;
				if (Number.class.isAssignableFrom(cls))
					return Long.valueOf(((Number)value).longValue());
				if (cls == String.class) {
					try {
						return Long.valueOf(Long.parseLong((String)value));
					}
					catch (NumberFormatException e) {
						return Long.valueOf(0);
					}
				}
				if (cls == Character.class)
					return Long.valueOf(((Character)value).charValue());
				if (cls == Boolean.class)
					return Long.valueOf(((Boolean)value).booleanValue() ? 1L : 0L);

				return Long.valueOf(1L);
			}
		};
		converters.put(Long.class, converter);
		converters.put(long.class, converter);
		
		converter = new Converter<BigInteger>() {
			@Override
			public BigInteger convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return null;
				
				Class<?> cls = value.getClass();
				
				if (cls == BigInteger.class)
					return (BigInteger)value;
				if (Number.class.isAssignableFrom(cls))
					return BigInteger.valueOf(((Number)value).longValue());
				if (cls == String.class) {
					try {
						return new BigInteger((String)value);
					}
					catch (NumberFormatException e) {
						return BigInteger.ZERO;
					}
				}
				if (cls == Character.class)
					return BigInteger.valueOf(((Character)value).charValue());
				if (cls == Boolean.class)
					return (((Boolean)value).booleanValue() ? BigInteger.ONE : BigInteger.ZERO);

				return BigInteger.ONE;
			}
		};
		converters.put(BigInteger.class, converter);
		
		
		final Float FLOAT_ZERO = Float.valueOf(0.0f);
		final Float FLOAT_ONE = Float.valueOf(1.0f);
		
		converter = new Converter<Float>() {
			
			@Override
			public Float convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return (targetType == float.class ? Float.valueOf(0.0f) : null);
				
				Class<?> cls = value.getClass();
				
				if (cls == Float.class)
					return (Float)value;
				if (Number.class.isAssignableFrom(cls))
					return Float.valueOf(((Number)value).floatValue());
				if (cls == String.class) {
					try {
						return Float.valueOf(Float.parseFloat((String)value));
					}
					catch (NumberFormatException e) {
						return FLOAT_ZERO;
					}
				}
				if (cls == Character.class)
					return Float.valueOf(((Character)value).charValue());
				if (cls == Boolean.class)
					return (((Boolean)value).booleanValue() ? FLOAT_ONE : FLOAT_ZERO);

				return FLOAT_ONE;
			}
		};
		converters.put(Float.class, converter);
		converters.put(float.class, converter);
		
		final Double DOUBLE_ZERO = Double.valueOf(0.0);
		final Double DOUBLE_ONE = Double.valueOf(1.0);

		converter = new Converter<Double>() {
			@Override
			public Double convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return (targetType == double.class ? Double.valueOf(0.0) : null);
				
				Class<?> cls = value.getClass();
				
				if (cls == Double.class)
					return (Double)value;
				if (Number.class.isAssignableFrom(cls))
					return Double.valueOf(((Number)value).doubleValue());
				if (cls == String.class) {
					try {
						return Double.valueOf(Double.parseDouble((String)value));
					}
					catch (NumberFormatException e) {
						return DOUBLE_ZERO;
					}
				}
				if (cls == Character.class)
					return Double.valueOf(((Character)value).charValue());
				if (cls == Boolean.class)
					return (((Boolean)value).booleanValue() ? DOUBLE_ONE : DOUBLE_ZERO);

				return DOUBLE_ONE;
			}
		};
		converters.put(Double.class, converter);
		converters.put(double.class, converter);
		
		converter = new Converter<BigDecimal>() {
			@Override
			public BigDecimal convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return null;
				
				Class<?> cls = value.getClass();
				
				if (cls == BigDecimal.class)
					return (BigDecimal)value;
				if (Number.class.isAssignableFrom(cls))
					return BigDecimal.valueOf(((Number)value).doubleValue());
				if (cls == String.class) {
					try {
						return new BigDecimal((String)value);
					}
					catch (NumberFormatException e) {
						return BigDecimal.ZERO;
					}
				}
				if (cls == Character.class)
					return BigDecimal.valueOf(((Character)value).charValue());
				if (cls == Boolean.class)
					return (((Boolean)value).booleanValue() ? BigDecimal.ONE : BigDecimal.ZERO);

				return BigDecimal.ONE;
			}
		};
		converters.put(BigDecimal.class, converter);
		
		// String / Character
		
		converter = new Converter<String>() {
			@Override
			public String convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return null;
				
				Class<?> cls = value.getClass();
				
				if (cls == String.class)
					return (String)value;
				if (cls == char[].class)
					return String.valueOf((char[])value);
				if (cls == Character[].class) {
					Character[] characters = (Character[])value;
					StringBuilder sb = new StringBuilder(characters.length);
					for (Character c : characters)
						sb.append((c != null ? c.charValue() : '\0'));
					return sb.toString();
				}
				
				return value.toString();
			}
		};
		converters.put(String.class, converter);
		
		converter = new Converter<Character>() {
			@Override
			public Character convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return null;
				
				Class<?> cls = value.getClass();
				if (cls == Character.class)
					return (Character)value;
				if (cls == char[].class) {
					char[] chars = (char[])value;
					return (chars.length > 0 ? Character.valueOf(chars[0]) : null);
				}
				if (cls == Character[].class) {
					Character[] characters = (Character[])value;
					return (characters.length > 0 ? characters[0] : null);
				}
				
				String string = value.toString();
				return (string.length() > 0 ? Character.valueOf(string.charAt(0)) : null);
			}
		};
		converters.put(Character.class, converter);
		
		converter = new Converter<Character>() {
			@Override
			public Character convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return Character.valueOf('\0');
				
				Class<?> cls = value.getClass();
				if (cls == Character.class)
					return (Character)value;
				if (cls == char[].class) {
					char[] chars = (char[])value;
					return Character.valueOf(chars.length > 0 ? chars[0] : '\0');
				}
				if (cls == Character[].class) {
					Character[] characters = (Character[])value;
					return (characters.length > 0 && characters[0] != null ? characters[0] : Character.valueOf('\0'));
				}
				
				String string = value.toString();
				return (string.length() > 0 ? Character.valueOf(string.charAt(0)) : Character.valueOf('\0'));
			}
		};
		converters.put(char.class, converter);
		
		converter = new Converter<Character[]>() {
			@Override
			public Character[] convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return null;
				
				Class<?> cls = value.getClass();
				
				if (cls == Character[].class)
					return (Character[])value;
				
				if (cls != char[].class)
					value = value.toString().toCharArray();

				char[] chars = (char[])value;
				Character[] characters = new Character[chars.length];
				for (int i = 0; i < chars.length; i++)
					characters[i] = Character.valueOf(chars[i]);
				return characters;
			}
		};
		converters.put(Character[].class, converter);
		
		converter = new Converter<char[]>() {
			@Override
			public char[] convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return null;
				
				Class<?> cls = value.getClass();
				
				if (cls == char[].class)
					return (char[])value;
				if (cls == Character[].class) {
					Character[] characters = (Character[])value;
					char[] chars = new char[characters.length];
					for (int i = 0; i < characters.length; i++) {
						Character c = characters[i];
						chars[i] = (c != null ? c.charValue() : '\0');
					}
					return chars;
				}

				return value.toString().toCharArray();
			}
		};
		converters.put(char[].class, converter);
		
		// Byte array
		
		final Charset UTF8 = Charset.forName("UTF-8");
		
		converter = new Converter<byte[]>() {
			@Override
			public byte[] convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return null;
				
				Class<?> cls = value.getClass();
				
				if (cls == byte[].class)
					return (byte[])value;
				return value.toString().getBytes(UTF8);
			}
		};
		converters.put(byte[].class, converter);
		
		// Temporal types.
		
		final TimeZone UTC = TimeZone.getTimeZone("UTC");
		
		converter = new Converter<GregorianCalendar>() {
			@Override
			public GregorianCalendar convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return null;
				
				Class<?> cls = value.getClass();
				if (cls == SpearalDateTime.class)
					return ((SpearalDateTime)value).toGregorianCalendar();
				
				Date date;
				if (value instanceof Date)
					date = (Date)value;
				else if (value instanceof Number)
					date = new Date(((Number)value).longValue());
				else
					date = parseDate(value.toString());
				
				if (date != null) {
					GregorianCalendar calendar = new GregorianCalendar(UTC, Locale.US);
					calendar.setTime(date);
					return calendar;
				}
				
				return null;
			}
		};
		converters.put(Calendar.class, converter);
		converters.put(GregorianCalendar.class, converter);
		
		converter = new Converter<Date>() {
			@Override
			public Date convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return null;
				
				Class<?> cls = value.getClass();
				
				if (cls == SpearalDateTime.class)
					return ((SpearalDateTime)value).toDate();
				if (cls == Date.class)
					return (Date)value;
				if (value instanceof Number)
					return new Date(((Number)value).longValue());
				
				return parseDate(value.toString());
			}
		};
		converters.put(Date.class, converter);
		
		converter = new Converter<java.sql.Date>() {
			@Override
			public java.sql.Date convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return null;
				
				Class<?> cls = value.getClass();
				
				if (cls == SpearalDateTime.class)
					return ((SpearalDateTime)value).toSQLDate();
				if (cls == java.sql.Date.class)
					return (java.sql.Date)value;
				if (value instanceof Date)
					return new java.sql.Date(((Date)value).getTime());
				if (value instanceof Number)
					return new java.sql.Date(((Number)value).longValue());
				
				Date date = parseDate(value.toString());
				return (date != null ? new java.sql.Date(date.getTime()) : null);
			}
		};
		converters.put(java.sql.Date.class, converter);
		
		converter = new Converter<java.sql.Time>() {
			@Override
			public java.sql.Time convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return null;
				
				Class<?> cls = value.getClass();
				
				if (cls == SpearalDateTime.class)
					return ((SpearalDateTime)value).toSQLTime();
				if (cls == java.sql.Time.class)
					return (java.sql.Time)value;
				if (value instanceof Date)
					return new java.sql.Time(((Date)value).getTime());
				if (value instanceof Number)
					return new java.sql.Time(((Number)value).longValue());
				
				Date date = parseDate(value.toString());
				return (date != null ? new java.sql.Time(date.getTime()) : null);
			}
		};
		converters.put(java.sql.Time.class, converter);
		
		converter = new Converter<java.sql.Timestamp>() {
			@Override
			public java.sql.Timestamp convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return null;
				
				Class<?> cls = value.getClass();
				
				if (cls == SpearalDateTime.class)
					return ((SpearalDateTime)value).toSQLTimestamp();
				if (cls == java.sql.Timestamp.class)
					return (java.sql.Timestamp)value;
				if (value instanceof Date)
					return new java.sql.Timestamp(((Date)value).getTime());
				if (value instanceof Number)
					return new java.sql.Timestamp(((Number)value).longValue());
				
				Date date = parseDate(value.toString());
				return (date != null ? new java.sql.Timestamp(date.getTime()) : null);
			}
		};
		converters.put(java.sql.Timestamp.class, converter);
		
		// Class.
		
		converter = new Converter<Class<?>>() {
			@Override
			public Class<?> convert(SpearalContext context, Object value, Type targetType) {
				if (value == null)
					return null;
				
				if (value.getClass() == Class.class)
					return ((Class<?>)value);

				try {
					return context.loadClass(value.toString(), targetType);
				}
				catch (Exception e) {
					return null;
				}
			}
		};
		converters.put(Class.class, converter);
		
		// SpearalDateTime -> Object.
		
		converter = new Converter<Object>() {
			@Override
			public Object convert(SpearalContext context, Object value, Type targetType) {
				if (value != null && value.getClass() == SpearalDateTime.class)
					return ((SpearalDateTime)value).toDate();
				return value;
			}
		};
		converters.put(Object.class, converter);
	}

	@Override
	public Converter<?> getConverter(Class<?> valueClass, Type targetType) {
		return converters.get(targetType);
	}
	
	private static Date parseDate(String s) {
		// TODO: support ISO-8601 format.
		DateFormat format = SimpleDateFormat.getDateInstance(DateFormat.SHORT, Locale.US);
		try {
			return format.parse(s);
		}
		catch (Exception e) {
			return null;
		}
	}
} public class FilteredBeanDescriptorFactoryImpl implements FilteredBeanDescriptorFactory {
	
	private static class FilteredBeanDescriptorImpl implements FilteredBeanDescriptor {

		private final String description;
		private final Property[] properties;
		
		public FilteredBeanDescriptorImpl(String description, Property[] properties) {
			this.description = description;
			this.properties = properties;
		}

		@Override
		public String getDescription() {
			return description;
		}

		@Override
		public Property[] getProperties() {
			return properties;
		}

		@Override
		public boolean isCacheable() {
			return true;
		}
	}
	
	private static class FilteredPartialBeanDescriptorImpl extends FilteredBeanDescriptorImpl {

		public FilteredPartialBeanDescriptorImpl(String description, Property[] properties) {
			super(description, properties);
		}

		@Override
		public boolean isCacheable() {
			return false;
		}
	}

	@Override
	public FilteredBeanDescriptor createDescription(SpearalContext context, SpearalPropertyFilter filter, Object value) {
		Class<?> type = value.getClass();
		
		if (value instanceof PartialObjectProxy) {
			
			if (value instanceof ExtendedPartialObjectProxy)
				type = ((ExtendedPartialObjectProxy)value).$getActualClass();
			
			Property[] filteredProperties = filter.get(type).clone();
			Property[] partialProperties = ((PartialObjectProxy)value).$getDefinedProperties();
			
			filteredPropertiesLoop:
			for (int i = 0; i < filteredProperties.length; i++) {
				Property filteredProperty = filteredProperties[i];
				for (Property property : partialProperties) {
					if (property.equals(filteredProperty))
						continue filteredPropertiesLoop;
				}
				filteredProperties[i] = null;
			}
			
			String description = ClassDescriptionUtil.createAliasedDescription(context, type, filteredProperties);
			return new FilteredPartialBeanDescriptorImpl(description, filteredProperties);
		}
		
		Property[] properties = filter.get(type);
		String description = ClassDescriptionUtil.createAliasedDescription(context, type, properties);
		return new FilteredBeanDescriptorImpl(description, properties);
	}
} public class ArrayInstantiator implements
	TypeInstantiatorProvider, TypeInstantiator,
	PropertyInstantiatorProvider, PropertyInstantiator {

	@Override
	public TypeInstantiator getInstantiator(Type type) {
		return (canInstantiate(type) ? this : null);
	}

	@Override
	public Object instantiate(SpearalContext context, Type type, Object param) {
		Class<?> cls = TypeUtil.classOfType(type);
		
		if (!(param instanceof Integer))
			throw new IllegalArgumentException("param should be the length of the array: " + param);
		int length = ((Integer)param).intValue();
		
		Class<?> componentType = cls.getComponentType();
		if (!componentType.isArray())
			return Array.newInstance(componentType, length);
		
		int depth = 0;
		while (componentType.isArray()) {
			depth++;
			componentType = componentType.getComponentType();
		}
		
		int[] dimensions = new int[depth + 1];
		dimensions[0] = length;
		return Array.newInstance(componentType, dimensions);
	}

	@Override
	public PropertyInstantiator getInstantiator(Property property) {
		return (canInstantiate(property.getGenericType()) ? this : null);
	}

	@Override
	public Object instantiate(SpearalContext context, Property property, Object param) {
		return instantiate(context, property.getGenericType(), param);
	}

	private static boolean canInstantiate(Type type) {
		return TypeUtil.classOfType(type).isArray();
	}
} public class ClassInstantiator implements
	TypeInstantiatorProvider, TypeInstantiator,
	PropertyInstantiatorProvider, PropertyInstantiator {

	@Override
	public TypeInstantiator getInstantiator(Type type) {
		return (canInstantiate(type) ? this : null);
	}

	@Override
	public Object instantiate(SpearalContext context, Type type, Object param) {
		context.getSecurizer().checkDecodable(type);
		
		Class<?> cls = TypeUtil.classOfType(type);
        try {
			return cls.newInstance();
		}
        catch (Exception e) {
			throw new RuntimeException("Could not create instance of: " + cls, e);
		}
	}

	@Override
	public PropertyInstantiator getInstantiator(Property property) {
		return (canInstantiate(property.getGenericType()) ? this : null);
	}

	@Override
	public Object instantiate(SpearalContext context, Property property, Object param) {
		return instantiate(context, property.getGenericType(), param);
	}

	private static boolean canInstantiate(Type type) {
		Class<?> cls = TypeUtil.classOfType(type);
		return !(
			cls == Class.class ||
			cls.isInterface() ||
			cls.isPrimitive() ||
			cls.isEnum() ||
			cls.isAnnotation() ||
			Proxy.isProxyClass(cls)
		);
	}
} public class CollectionInstantiator implements
	TypeInstantiatorProvider, TypeInstantiator,
	PropertyInstantiatorProvider, PropertyInstantiator {

	@Override
	public TypeInstantiator getInstantiator(Type type) {
		return (canInstantiate(type) ? this : null);
	}

	@Override
	public Object instantiate(SpearalContext context, Type type, Object param) {
		Class<?> cls = TypeUtil.classOfType(type);
		int capacity = (param instanceof Integer ? ((Integer)param).intValue() : -1);

		if (cls.isInterface()) {
			if (cls.isAssignableFrom(ArrayList.class))
				return (capacity < 0 ? new ArrayList<Object>() : new ArrayList<Object>(capacity));
			if (cls.isAssignableFrom(HashSet.class))
				return (capacity < 0 ? new HashSet<Object>() : new HashSet<Object>(capacity));
			if (cls.isAssignableFrom(TreeSet.class))
				return new TreeSet<Object>();
            throw new IllegalArgumentException("Unsupported collection interface: " + cls);
        }
		
		context.getSecurizer().checkDecodable(type);
		
        try {
			return cls.newInstance();
		}
        catch (Exception e) {
			throw new RuntimeException("Could not create instance of: " + cls, e);
		}
	}

	@Override
	public PropertyInstantiator getInstantiator(Property property) {
		return (canInstantiate(property.getGenericType()) ? this : null);
	}

	@Override
	public Object instantiate(SpearalContext context, Property property, Object param) {
		return instantiate(context, property.getGenericType(), param);
	}

	private static boolean canInstantiate(Type type) {
		return Collection.class.isAssignableFrom(TypeUtil.classOfType(type));
	}
} public class MapInstantiator implements
	TypeInstantiatorProvider, TypeInstantiator,
	PropertyInstantiatorProvider, PropertyInstantiator {

	@Override
	public TypeInstantiator getInstantiator(Type type) {
		return (canInstantiate(type) ? this : null);
	}

	@Override
	public Object instantiate(SpearalContext context, Type type, Object param) {
		Class<?> cls = TypeUtil.classOfType(type);
		int capacity = (param instanceof Integer ? ((Integer)param).intValue() : -1);

		if (cls.isInterface()) {
			if (cls.isAssignableFrom(LinkedHashMap.class))
				return (capacity < 0 ? new LinkedHashMap<Object, Object>() : new LinkedHashMap<Object, Object>(capacity));
			if (cls.isAssignableFrom(TreeMap.class))
				return new TreeMap<Object, Object>();
            throw new IllegalArgumentException("Unsupported map interface: " + cls);
        }
		
		context.getSecurizer().checkDecodable(type);
		
        try {
			return cls.newInstance();
		}
        catch (Exception e) {
			throw new RuntimeException("Could not create instance of: " + cls, e);
		}
	}

	@Override
	public PropertyInstantiator getInstantiator(Property property) {
		return (canInstantiate(property.getGenericType()) ? this : null);
	}

	@Override
	public Object instantiate(SpearalContext context, Property property, Object param) {
		return instantiate(context, property.getGenericType(), param);
	}

	private static boolean canInstantiate(Type type) {
		return Map.class.isAssignableFrom(TypeUtil.classOfType(type));
	}
} public class ProxyInstantiator implements
	TypeInstantiatorProvider, TypeInstantiator,
	PropertyInstantiatorProvider, PropertyInstantiator {

	@Override
	public TypeInstantiator getInstantiator(Type type) {
		return (canInstantiate(type) ? this : null);
	}

	@Override
	public Object instantiate(SpearalContext context, Type type, Object param) {
		context.getSecurizer().checkDecodable(type);
		
        try {
    		Class<?> cls = (Class<?>)type;
    		Constructor<?> constructor = cls.getConstructor(new Class<?>[]{ InvocationHandler.class });
    		Property[] properties = context.getProperties(cls);
    		return constructor.newInstance(new PropertiesInvocationHandler(context, properties));
		}
        catch (Exception e) {
			throw new RuntimeException("Could not create instance of: " + type, e);
		}
	}
	
	public static Object instantiatePartial(SpearalContext context, Type type, Property[] serializedProperties) {
		context.getSecurizer().checkDecodable(type);
		
        try {
    		Class<?> cls = (Class<?>)type;
    		Constructor<?> constructor = cls.getConstructor(new Class<?>[]{ InvocationHandler.class });
    		Property[] properties = context.getProperties(cls);
    		return constructor.newInstance(new PropertiesInvocationHandler(context, properties, serializedProperties));
		}
        catch (Exception e) {
			throw new RuntimeException("Could not create instance of: " + type, e);
		}
	}

	@Override
	public PropertyInstantiator getInstantiator(Property property) {
		return (canInstantiate(property.getGenericType()) ? this : null);
	}

	@Override
	public Object instantiate(SpearalContext context, Property property, Object param) {
		return instantiate(context, property.getGenericType(), param);
	}

	private static boolean canInstantiate(Type type) {
		return (type instanceof Class<?> && Proxy.isProxyClass((Class<?>)type));
	}

	private static class PropertiesInvocationHandler implements InvocationHandler {
		
		private final SpearalContext context;
		private final Map<Method, String> methods;
		private final Map<String, Object> values;
		private final Property[] properties;
		private Map<String, Property> definedProperties = new HashMap<String, Property>();
		
		public PropertiesInvocationHandler(SpearalContext context, Property[] properties) {
			this(context, properties, null);
		}
		
		public PropertiesInvocationHandler(SpearalContext context, Property[] properties, Property[] definedProperties) {
			this.context = context;
			this.methods = new HashMap<Method, String>();
			this.values = new HashMap<String, Object>();
			this.properties = properties;
			
			for (Property property : properties) {
				methods.put(property.getGetter(), property.getName());
				if (property.getSetter() != null)
					methods.put(property.getSetter(), property.getName());
			}
			if (definedProperties != null) {
				for (Property property : definedProperties) {
					this.definedProperties.put(property.getName(), property);
					this.values.put(property.getName(), null);
				}
			}
		}

		@Override
		public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
			
			if (method.getDeclaringClass() == PartialObjectProxy.class) {
				if ("$hasUndefinedProperties".equals(method.getName()) && method.getParameterTypes().length == 0) {
					return properties.length > definedProperties.size();
				}
				else if ("$isDefined".equals(method.getName()) && method.getParameterTypes().length == 1 && method.getParameterTypes()[0] == String.class) {
					if (values.containsKey(args[0]))
						return true;
					if (definedProperties.containsKey(args[0]))
						return true;						
					return false;
				}
				else if ("$undefine".equals(method.getName()) && method.getParameterTypes().length == 1 && method.getParameterTypes()[0] == String.class) {
					values.remove(args[0]);
					return definedProperties.remove(args[0]);
				}
				else if ("$getDefinedProperties".equals(method.getName()) && method.getParameterTypes().length == 0)
					return definedProperties.values().toArray(new Property[definedProperties.size()]);
			}
			
			String propertyName = methods.get(method);
			if (propertyName != null) {
				if (method.getName().startsWith("set")) {
					values.put(propertyName, args[0]);
					
					Property property = null;
					for (Property p : properties) {
						if (p.getName().equals(propertyName)) {
							property = p;
							break;
						}
					}
					definedProperties.put(propertyName, property);
				}
				else {
					if (!values.containsKey(propertyName))
						throw new UndefinedPropertyException(method.toString());
					
					return context.convert(values.get(propertyName), method.getReturnType());
				}
			}
			
			return null;
		}
	}
} public class IntrospectorImpl implements Introspector {
	
	private static Logger logger = Logger.getLogger(IntrospectorImpl.class.getName());

	private final CopyOnWriteMap<Class<?>, Object, Property[]> cache;

	public IntrospectorImpl() {
		this.cache = new CopyOnWriteMap<Class<?>, Object, Property[]>(true,
			new ValueProvider<Class<?>, Object, Property[]>() {
				@Override
				public Property[] createValue(SpearalContext context, Class<?> key, Object unused) {
					return (
						Proxy.isProxyClass(key)
						? introspectProxyProperties(context, key)
						: introspectBeanProperties(context, key)
					);
				}
			}
		);
	}
	
	@Override
	public Property[] getProperties(SpearalContext context, Class<?> cls) {
		Property[] properties = cache.get(cls);
		if (properties == null)
			properties = cache.putIfAbsent(context, cls, null);
		return properties;
	}
	
	protected Property[] introspectBeanProperties(SpearalContext context, Class<?> cls) {
		if (cls == Object.class || cls == null)
			return ZERO_PROPERTIES;
		
		Field[] declaredFields = cls.getDeclaredFields();
		Method[] declaredMethods = cls.getDeclaredMethods();
		
		SortedMap<String, Property> propertiesMap = new TreeMap<String, Property>();
		for (Field field : declaredFields) {
			if ((field.getModifiers() & (STATIC | TRANSIENT)) == 0 &&
				!field.isAnnotationPresent(Exclude.class)) {
				
				field.setAccessible(true);
				
				String name = field.getName();
				Class<?> type = field.getType();
				
				Method getter = findGetter(declaredMethods, type, name);
				Method setter = findSetter(declaredMethods, type, name);

				Property property = context.createProperty(name, field, getter, setter);
				
				propertiesMap.put(name, property);
			}
		}
		
		for (Method getter : declaredMethods) {
			if ((getter.getModifiers() & (STATIC | PRIVATE | PROTECTED)) == 0 &&
				getter.getReturnType() != void.class &&
				getter.isAnnotationPresent(Include.class) &&
				getter.getParameterTypes().length == 0) {
				
				String name = null;

				String methodName = getter.getName();
				if (getter.getReturnType() == boolean.class && methodName.startsWith("is"))
					name = decapitalize(methodName.substring(2));
				else if (methodName.startsWith("get"))
					name = decapitalize(methodName.substring(3));
				
				if (name == null || name.length() == 0) {
					logger.warning("Ignoring method annotated with @Include (illegal property getter name): " + methodName);
					continue;
				}
				if (propertiesMap.containsKey(name))
					continue;
				
				Class<?> type = getter.getReturnType();
				Method setter = findSetter(declaredMethods, type, name);

				Property property = context.createProperty(name, null, getter, setter);
				
				propertiesMap.put(name, property);
			}
		}
		
		Property[] properties = propertiesMap.values().toArray(ZERO_PROPERTIES);
		
		Class<?> superCls = cls.getSuperclass();
		if (superCls == Object.class || superCls == null)
			return properties;
		
		Property[] superProperties = getProperties(context, superCls);
		return concat(superProperties, properties);
	}

	protected Method findGetter(Method[] methods, Class<?> type, String name) {
		final String isName = "is" + name;
		final String getName = "get" + name;

		Method looseMatch = null;
		
		for (Method method : methods) {
			if ((method.getModifiers() & (STATIC | PRIVATE | PROTECTED)) == 0 &&
				method.getReturnType() == type &&
				method.getParameterTypes().length == 0) {
				
				if (type == boolean.class && method.getName().equalsIgnoreCase(isName)) {
					if (type.equals(decapitalize(method.getName().substring(2))))
						return method;
					looseMatch = method;
				}
				else if (method.getName().equalsIgnoreCase(getName)) {
					if (name.equals(decapitalize(method.getName().substring(3))))
						return method;
					looseMatch = method;
				}
			}
		}
		
		return looseMatch;
	}
	
	protected Method findSetter(Method[] methods, Class<?> type, String name) {
		final String setName = "set" + name;
		
		Method looseMatch = null;
		
		for (Method method : methods) {
			if ((method.getModifiers() & (STATIC | PRIVATE | PROTECTED)) == 0 &&
				method.getReturnType() == void.class) {
				Class<?>[] parameterTypes = method.getParameterTypes();
				if (parameterTypes.length == 1 &&
					parameterTypes[0] == type &&
					method.getName().equalsIgnoreCase(setName)) {
					
					if (name.equals(decapitalize(method.getName().substring(3))))
						return method;
					looseMatch = method;
				}
			}
		}
		
		return looseMatch;
	}
	
	protected Property[] introspectProxyProperties(SpearalContext context, Class<?> cls) {
		if (cls == null)
			return ZERO_PROPERTIES;
		
		Map<String, Method> setters = new HashMap<String, Method>();
		Map<String, Method> getters = new HashMap<String, Method>();
		for (Class<?> inter : cls.getInterfaces()) {
			for (Method method : inter.getMethods()) {
				
				if (method.getReturnType() == void.class) {
					String name = method.getName();
					if (name.length() > 3 && name.startsWith("set")) {
						Class<?>[] parameterTypes = method.getParameterTypes();
						if (parameterTypes.length == 1)
							setters.put(name.substring(3).toLowerCase(), method);
					}
				}
				else if (method.getParameterTypes().length == 0) {
					String name = method.getName();
					if (name.length() > 3 && name.startsWith("get"))
						getters.put(name.substring(3).toLowerCase(), method);
					else if (method.getReturnType() == boolean.class &&
							 name.length() > 2 && name.startsWith("is"))
						getters.put(name.substring(2).toLowerCase(), method);
				}
			}
		}
		
		SortedMap<String, Property> propertiesMap = new TreeMap<String, Property>();
		for (Map.Entry<String, Method> nameGetter : getters.entrySet()) {
			Method getter = nameGetter.getValue();
			Method setter = setters.get(nameGetter.getKey());
			
			if ((setter != null && setter.getParameterTypes()[0] == getter.getReturnType()) ||
				getter.isAnnotationPresent(Include.class)) {

				String name = (
					getter.getName().startsWith("is")
					? decapitalize(getter.getName().substring(2))
					: decapitalize(getter.getName().substring(3))
				);
				
				Property property = context.createProperty(name, null, getter, setter);
				propertiesMap.put(name, property);
			}
			
		}

		return propertiesMap.values().toArray(ZERO_PROPERTIES);
	}
	
	protected static Property[] concat(Property[] properties1, Property[] properties2) {
		int length1 = properties1.length,
			length2 = properties2.length;
		
		if (length1 == 0)
			return properties2;
		if (length2 == 0)
			return properties1;
		
		Property[] properties = new Property[length1 + length2];
		System.arraycopy(properties1, 0, properties, 0, length1);
		System.arraycopy(properties2, 0, properties, length1, length2);
		return properties;
	}
	
    private static String decapitalize(String name) {
        if (name == null || name.length() == 0)
            return name;
        
        if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0)))
            return name;
        
        char chars[] = name.toCharArray();
        chars[0] = Character.toLowerCase(chars[0]);
        return new String(chars);
    }
} public class TypeLoaderImpl implements TypeLoader, ValueProvider<String, Type, Class<?>> {

	private final ClassLoader classLoader;
	private final CopyOnWriteMap<String, Type, Class<?>> classesCache;
	
	public TypeLoaderImpl() {
		this(TypeLoaderImpl.class.getClassLoader());
	}
	
	public TypeLoaderImpl(final ClassLoader classLoader) {
		this.classLoader = classLoader;
		this.classesCache = new CopyOnWriteMap<String, Type, Class<?>>(true, this);
	}

	@Override
	public Class<?> loadClass(SpearalContext context, String classNames, Type targetType) throws SecurityException {
		if (classNames == null || classNames.length() == 0)
			return null;
		
		Class<?> cls = classesCache.getOrPutIfAbsent(context, classNames, targetType);
		return (cls != ClassNotFound.class ? cls : null);
	}
	
	@Override
	public Class<?> createValue(SpearalContext context, String key, Type targetType) {
		Class<?> cls;
		
		String[] classNames = ClassDescriptionUtil.splitClassNames(key);
		if (classNames.length <= 1) {
			try {
				cls = Class.forName(context.unalias(key), true, classLoader);
				if (cls.isInterface())
					cls = Proxy.getProxyClass(classLoader, new Class<?>[]{ cls });
			}
			catch (ClassNotFoundException e) {
				cls = null;
			}
		}
		else {
			List<Class<?>> interfaces = new ArrayList<Class<?>>(classNames.length);
			
			for (int i = 0; i < classNames.length; i++) {
				if (isDuplicate(classNames, i))
					continue;
				
				Class<?> inter;
				try {
					inter = Class.forName(context.unalias(classNames[i]), true, classLoader);
				}
				catch (ClassNotFoundException e) {
					continue;
				}
				if (!inter.isInterface()) {
					throw new UnsupportedOperationException(
						"Cannot create Proxy for: " + Arrays.toString(classNames) +
						" (" + inter + " isn't an interface)"
					);
				}
				interfaces.add(inter);
			}
			
			if (interfaces.size() == 0)
				cls = null;
			else
				cls = Proxy.getProxyClass(classLoader, interfaces.toArray(new Class<?>[0]));
		}
		
		if (cls == null) {
			if (targetType == null)
				cls = ClassNotFound.class;
			else {
				cls = TypeUtil.classOfType(targetType);
				if (cls.isInterface())
					cls = Proxy.getProxyClass(cls.getClassLoader(), new Class<?>[]{ cls });
			}
			
		}
		
		return cls;
	}
	
	private static boolean isDuplicate(String[] classNames, int index) {
		String className = classNames[index];
		for (int i = 0; i < index; i++) {
			if (className.equals(classNames[i]))
				return true;
		}
		return false;
	}
	
	private interface ClassNotFound {
		
	}
} public class NoProxyPartialObjectFactory implements PartialObjectFactory, ValueProvider<Class<?>, Object, Class<?>> {
	
	public NoProxyPartialObjectFactory() {
	}

	@Override
	public Class<?> createValue(SpearalContext context, Class<?> key, Object unused) {
		context.getSecurizer().checkDecodable(key);
		
		return key;
	}

	@Override
	public Object instantiatePartial(SpearalContext context, Class<?> cls, Property[] partialProperties)
		throws InstantiationException, IllegalAccessException {
		
		if (Proxy.isProxyClass(cls))
			return ProxyInstantiator.instantiatePartial(context, cls, partialProperties);
		
		return context.instantiate(cls, null);
	}
} public class SimplePropertiesFactory implements PropertyFactory {
	
	@Override
	public Property createProperty(String name, Field field, Method getter, Method setter) {
		Class<?> type = AnyProperty.typeOf(field, getter);
		
		if (type.isPrimitive()) {
			if (type == boolean.class)
				return new PrimitiveBooleanProperty(name, field, getter, setter);
			if (type == int.class)
				return new PrimitiveIntProperty(name, field, getter, setter);
			if (type == long.class)
				return new PrimitiveLongProperty(name, field, getter, setter);
			if (type == double.class)
				return new PrimitiveDoubleProperty(name, field, getter, setter);
		}
		else {
			if (type == String.class)
				return new StringProperty(name, field, getter, setter);
			if (Collection.class.isAssignableFrom(type))
				return new CollectionProperty(name, field, getter, setter);
			if (Map.class.isAssignableFrom(type))
				return new MapProperty(name, field, getter, setter);
		}
		
		return new AnyProperty(name, field, getter, setter);
	}
} public class SecurizerImpl implements Securizer {

	@Override
	public void checkDecodable(Type type) throws SecurityException {
		if (!Serializable.class.isAssignableFrom(TypeUtil.classOfType(type)))
			throw new SecurityException("Not serializable: " + type);
	}

	@Override
	public void checkEncodable(Class<?> cls) throws SecurityException {
		if (!Serializable.class.isAssignableFrom(cls))
			throw new SecurityException("Not serializable: " + cls);
	}
} public class ServiceLoader<S> implements Iterable<S> {
	
	private static final Logger log = Logger.getLogger(ServiceLoader.class.getName());
	
	private final Class<S> service;
	private final ClassLoader loader;
	
	private List<String> serviceClassNames;
	
	private Class<?>[] constructorParameterTypes = new Class<?>[0];
	private Object[] constructorParameters = new Object[0];
	
	private ServiceLoader(Class<S> service, ClassLoader loader, List<String> servicesNames) {
		this.service = service;
		this.loader = loader;
		this.serviceClassNames = servicesNames;
	}
	
	public void setConstructorParameters(Class<?>[] constructorParameterTypes, Object[] constructorParameters) {		
		
		if (constructorParameterTypes == null)
			constructorParameterTypes = new Class<?>[0];
		if (constructorParameters == null)
			constructorParameters = new Object[0];
		
		if (constructorParameterTypes.length != constructorParameters.length)
			throw new IllegalArgumentException("constructor types and argurments must have the same size");

		this.constructorParameterTypes = constructorParameterTypes;
		this.constructorParameters = constructorParameters;
	}

	public ServicesIterator<S> iterator() {
		return new ServicesIterator<S>(loader, serviceClassNames.iterator(), constructorParameterTypes, constructorParameters);
	}
	
	public void reload() {
		ServiceLoader<S> serviceLoaderTmp = load(service, loader);
		this.serviceClassNames = serviceLoaderTmp.serviceClassNames;
	}
	
	public static <S> ServiceLoader<S> load(Class<S> service) {
		return load(service, Thread.currentThread().getContextClassLoader());
	}

	public static <S> ServiceLoader<S> load(final Class<S> service, final ClassLoader loader) {
		List<String> serviceClassNames = new ArrayList<String>();
		
		try {
			// Standard Java platforms.
			Enumeration<URL> en = loader.getResources("META-INF/services/" + service.getName());
			while (en.hasMoreElements())
				parse(en.nextElement(), serviceClassNames);
			
			// Android support (META-INF/** files are not included in APK files).
			en = loader.getResources("meta_inf/services/" + service.getName());
			while (en.hasMoreElements())
				parse(en.nextElement(), serviceClassNames);
			
			return new ServiceLoader<S>(service, loader, serviceClassNames);
		}
		catch (Exception e) {
			log.log(Level.SEVERE, "Could not load services of type " + service, e);
			throw new RuntimeException(e);
		}
	}
	
	private static void parse(URL serviceFile, List<String> serviceClassNames) {
		try {
		    InputStream is = serviceFile.openStream();
		    try {
		    	BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
	
		    	String serviceClassName = null;
		    	while ((serviceClassName = reader.readLine()) != null) {
		    		int comment = serviceClassName.indexOf('#');
		    		if (comment >= 0)
		    			serviceClassName = serviceClassName.substring(0, comment);
			        serviceClassName = serviceClassName.trim();
			        if (serviceClassName.length() > 0) {
			        	log.log(Level.FINE, "Adding service " + serviceClassName + " from " + serviceFile);
			        	serviceClassNames.add(serviceClassName);
			        }
		    	}
		    }
		    finally {
		    	is.close();
		    }
		}
		catch (Exception e) {
			log.log(Level.SEVERE, "Could not parse service file " + serviceFile, e);
		}
	}
	
	public static class ServicesIterator<S> implements Iterator<S> {
		
		private final ClassLoader loader;
		private final Iterator<String> serviceClassNames;
		private final Class<?>[] constructorParameterTypes;
		private final Object[] constructorParameters;

		private ServicesIterator(ClassLoader loader, Iterator<String> servicesNames, Class<?>[] constructorParameterTypes, Object[] constructorParameters) {
			this.loader = loader;
			this.serviceClassNames = servicesNames;
			this.constructorParameterTypes = constructorParameterTypes;
			this.constructorParameters = constructorParameters;
		}

		public boolean hasNext() {
			return serviceClassNames.hasNext();
		}

		public S next() {
			String serviceClassName = serviceClassNames.next();
			log.log(Level.FINE, "Loading service " + serviceClassName);
			try {
				@SuppressWarnings("unchecked")
				Class<? extends S> serviceClass = (Class<? extends S>)loader.loadClass(serviceClassName);
				return serviceClass.getConstructor(constructorParameterTypes).newInstance(constructorParameters);
			}
			catch (Throwable t) {
				log.log(Level.SEVERE, "Could not load service " + serviceClassName, t);
				throw new RuntimeException(t);
			}
		}

		public void remove() {
			throw new UnsupportedOperationException();
		}
	}	
}
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import org.spearal.configuration.Configurable;
import org.spearal.configuration.PartialObjectFactory;
import org.spearal.impl.SpearalContextImpl;
import org.spearal.impl.SpearalDecoderImpl;
import org.spearal.impl.SpearalEncoderImpl;
import org.spearal.impl.SpearalPrinterImpl;
import org.spearal.impl.alias.AliasStrategyImpl;
import org.spearal.impl.coder.ArrayCoder;
import org.spearal.impl.coder.BeanCoder;
import org.spearal.impl.coder.CollectionCoder;
import org.spearal.impl.coder.EnumCoder;
import org.spearal.impl.coder.MapCoder;
import org.spearal.impl.coder.SimpleCodersProvider;
import org.spearal.impl.converter.EnumConverter;
import org.spearal.impl.converter.SimpleConvertersProvider;
import org.spearal.impl.descriptor.FilteredBeanDescriptorFactoryImpl;
import org.spearal.impl.instantiator.ArrayInstantiator;
import org.spearal.impl.instantiator.ClassInstantiator;
import org.spearal.impl.instantiator.CollectionInstantiator;
import org.spearal.impl.instantiator.MapInstantiator;
import org.spearal.impl.instantiator.ProxyInstantiator;
import org.spearal.impl.introspector.IntrospectorImpl;
import org.spearal.impl.loader.TypeLoaderImpl;
import org.spearal.impl.partial.NoProxyPartialObjectFactory;
import org.spearal.impl.property.SimplePropertiesFactory;
import org.spearal.impl.security.SecurizerImpl;
import org.spearal.impl.util.ServiceLoader;
/**
 * == @Spearal ==>
 * 
 * Copyright (C) 2014 Franck WOLFF & William DRAI (http://www.spearal.io)
 *
 * 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.spearal;



/**
 * @author Franck WOLFF
 * @author William DRAI
 */
public class DefaultSpearalFactory implements SpearalFactory {
	
	private final SpearalContextImpl context;
	
	public DefaultSpearalFactory() {
		this(true);
	}
	
	public DefaultSpearalFactory(boolean loadServices) {
		
		context = new SpearalContextImpl();
		
		// Reflection / Security.
		
		context.configure(new IntrospectorImpl());
		context.configure(new TypeLoaderImpl());
		context.configure(new SecurizerImpl());
		context.configure(newDefaultPartialObjectFactory());
		
		// Converters.
		
		context.configure(new SimpleConvertersProvider(), true);
		context.configure(new EnumConverter(), true);

		// Instantiators.
		
		context.configure(new CollectionInstantiator(), true);
		context.configure(new ArrayInstantiator(), true);
		context.configure(new MapInstantiator(), true);
		context.configure(new ProxyInstantiator(), true);