/*
 *  Copyright (c) 2012-2019, Jan Bernitt
 *
 *  Licensed under the Apache License, Version 2.0, http://www.apache.org/licenses/LICENSE-2.0
 */
package se.jbee.inject.bootstrap;

import static se.jbee.inject.Instance.anyOf;
import static se.jbee.inject.Type.raw;
import static se.jbee.inject.Utils.arrayIndex;
import static se.jbee.inject.Utils.arrayPrepand;
import static se.jbee.inject.Utils.isClassVirtual;
import static se.jbee.inject.bootstrap.BindingType.LINK;
import static se.jbee.inject.bootstrap.BindingType.METHOD;
import static se.jbee.inject.bootstrap.BindingType.PREDEFINED;
import static se.jbee.inject.bootstrap.Supply.constructor;
import static se.jbee.inject.bootstrap.Supply.method;
import static se.jbee.inject.bootstrap.Supply.parametrizedInstance;

import java.lang.reflect.Constructor;

import se.jbee.inject.DeclarationType;
import se.jbee.inject.InconsistentBinding;
import se.jbee.inject.Instance;
import se.jbee.inject.Parameter;
import se.jbee.inject.Resource;
import se.jbee.inject.Type;
import se.jbee.inject.container.Supplier;

/**
 * A immutable collection of {@link Macro}s each bound to a specific type
 * handled.
 *
 * @author Jan Bernitt (jan@jbee.se)
 */
public final class Macros {

	public static final Macro<Binding<?>> EXPAND = new AutoInheritanceMacro();
	public static final Macro<Class<?>> PARAMETRIZED_LINK = new TypeParametrizedLinkMacro();
	public static final Macro<Instance<?>> INSTANCE_LINK = new LinkMacro();
	public static final Macro<Parameter<?>[]> ARRAY = new ArrayElementsMacro();
	public static final Macro<BoundConstructor<?>> CONSTRUCTOR = new ConstructorMacro();
	public static final Macro<BoundMethod<?>> FACTORY_METHOD = new MethodMacro();
	public static final Macro<BoundConstant<?>> CONSTANT = new ConstantMacro();

	public static final Macros NONE = new Macros(new Class<?>[0],
			new Macro<?>[0]);

	public static final Macros DEFAULT = Macros.NONE.with(EXPAND).with(
			CONSTRUCTOR).with(CONSTANT).with(FACTORY_METHOD).with(
					INSTANCE_LINK).with(PARAMETRIZED_LINK).with(ARRAY);

	private final Class<?>[] types;
	private final Macro<?>[] macros;

	private Macros(Class<?>[] types, Macro<?>[] macros) {
		this.types = types;
		this.macros = macros;
	}

	/**
	 * Uses the given {@link Macro} and derives the {@link #with(Class, Macro)}
	 * type from its declaration. This is a utility method that can be used as
	 * long as the {@link Macro} implementation is not generic.
	 *
	 * @param macro No generic macro class (e.g. decorators)
	 * @return A set of {@link Macros} containing the given one for the type
	 *         derived from its type declaration.
	 */
	@SuppressWarnings("unchecked")
	public <T> Macros with(Macro<T> macro) {
		Class<?> type = Type.supertype(Macro.class,
				Type.raw(macro.getClass())).parameter(0).rawType;
		return with((Class<? super T>) type, macro);
	}

	/**
	 * Uses the given {@link Macro} for the given exact (no super-types!) type
	 * of values.
	 *
	 * @param type The type of value that should be passed to the {@link Macro}
	 *            as value
	 * @param macro The {@link Macro} expanding the type of value
	 * @return A set of {@link Macros} containing the given one
	 */
	public <T> Macros with(Class<T> type, Macro<? extends T> macro) {
		int index = index(type);
		if (index >= 0) {
			Macro<?>[] tmp = macros.clone();
			tmp[index] = macro;
			return new Macros(types, tmp);
		}
		return new Macros(arrayPrepand(type, types),
				arrayPrepand(macro, macros));
	}

	/**
	 * A generic version of {@link Macro#expand(Object, Binding, Bindings)} that
	 * uses the matching predefined {@link Macro} for the actual type of the
	 * value and expands it.
	 *
	 * @param binding The usually incomplete binding to expand (and add to
	 *            {@link Bindings})
	 * @param value Non-null value to expand via matching {@link Macro}
	 *
	 * @throws InconsistentBinding In case no {@link Macro} had been declared
	 *             for the type of value argument
	 */
	@SuppressWarnings("unchecked")
	public <T, V> void expandInto(Bindings bindings, Binding<T> binding,
			V value) {
		macroForValueOf((Class<? super V>) value.getClass()).expand(value,
				binding, bindings);
	}

	@SuppressWarnings("unchecked")
	private <V> Macro<? super V> macroForValueOf(
			final Class<? extends V> type) {
		int index = index(type);
		if (index < 0)
			throw new InconsistentBinding(
					"No macro for type:" + type.getCanonicalName());
		return (Macro<? super V>) macros[index];
	}

	private int index(final Class<?> type) {
		return arrayIndex(types, type, (a, b) -> a == b);
	}

	private static final class AutoInheritanceMacro
			implements Macro<Binding<?>> {

		AutoInheritanceMacro() {
			/* make visible */ }

		@Override
		public <T> void expand(Binding<?> untyped, Binding<T> binding,
				Bindings bindings) {
			bindings.add(binding);
			DeclarationType declarationType = binding.source.declarationType;
			if (declarationType != DeclarationType.AUTO
				&& declarationType != DeclarationType.PROVIDED)
				return;
			for (Type<? super T> supertype : binding.type().supertypes())
				// Object is of course a superclass but not indented when doing auto-binds
				if (supertype.rawType != Object.class)
					bindings.add(binding.typed(supertype));
		}
	}

	private static final class ArrayElementsMacro
			implements Macro<Parameter<?>[]> {

		ArrayElementsMacro() {
			/* make visible */ }

		@Override
		@SuppressWarnings({ "unchecked", "rawtypes" })
		public <T> void expand(Parameter<?>[] elements, Binding<T> incomplete,
				Bindings bindings) {
			bindings.expandInto(incomplete.complete(PREDEFINED,
					supplier((Type) incomplete.type(), elements)));
		}

		@SuppressWarnings("unchecked")
		static <E> Supplier<E> supplier(Type<E[]> array,
				Parameter<?>[] elements) {
			return (Supplier<E>) Supply.elements(array,
					(Parameter<? extends E>[]) elements);
		}

	}

	private static final class ConstructorMacro
			implements Macro<BoundConstructor<?>> {

		ConstructorMacro() {
			/* make visible */ }

		@Override
		public <T> void expand(BoundConstructor<?> constructor,
				Binding<T> incomplete, Bindings bindings) {
			bindings.expandInto(incomplete.complete(BindingType.CONSTRUCTOR,
					constructor(constructor.typed(incomplete.type()))));
		}
	}

	private static final class MethodMacro implements Macro<BoundMethod<?>> {

		MethodMacro() {
			/* make visible */ }

		@Override
		public <T> void expand(BoundMethod<?> method, Binding<T> incomplete,
				Bindings bindings) {
			bindings.expandInto(incomplete.complete(METHOD,
					method(method.typed(incomplete.type()))));
		}
	}

	private static final class TypeParametrizedLinkMacro
			implements Macro<Class<?>> {

		TypeParametrizedLinkMacro() {
			/* make visible */ }

		@Override
		public <T> void expand(Class<?> to, Binding<T> incomplete,
				Bindings bindings) {
			bindings.expandInto(incomplete.complete(LINK, parametrizedInstance(
					anyOf(raw(to).castTo(incomplete.type())))));
		}

	}

	private static final class ConstantMacro
			implements Macro<BoundConstant<?>> {

		ConstantMacro() {
			/* make visible */ }

		@Override
		public <T> void expand(BoundConstant<?> value, Binding<T> incomplete,
				Bindings bindings) {
			Supplier<T> supplier = Supply.constant(
					incomplete.type().rawType.cast(value.constant));
			bindings.expandInto(
					incomplete.complete(BindingType.PREDEFINED, supplier));
			if (incomplete.source.declarationType == DeclarationType.EXPLICIT) {
				if (incomplete.type().rawType != value.constant.getClass()) {
					@SuppressWarnings("unchecked")
					Class<T> type = (Class<T>) value.constant.getClass();
					bindings.expandInto(Binding.binding(
							incomplete.resource.typed(raw(type)),
							BindingType.PREDEFINED, supplier, incomplete.scope,
							incomplete.source.typed(DeclarationType.IMPLICIT)));
				}
			}
		}
	}

	private static final class LinkMacro implements Macro<Instance<?>> {

		LinkMacro() {
			/* make visible */ }

		@Override
		public <T> void expand(Instance<?> linked, Binding<T> binding,
				Bindings bindings) {
			Type<?> t = linked.type();
			if (t.isAssignableTo(raw(Supplier.class))
				&& !binding.type().isAssignableTo(raw(Supplier.class))) {
				@SuppressWarnings("unchecked")
				Class<? extends Supplier<? extends T>> supplier = (Class<? extends Supplier<? extends T>>) t.rawType;
				bindings.expandInto(
						binding.complete(LINK, Supply.reference(supplier)));
				implicitlyBindToConstructor(binding, linked, bindings);
				return;
			}
			final Type<? extends T> type = t.castTo(binding.type());
			final Instance<T> bound = binding.resource.instance;
			if (!bound.type().equalTo(type)
				|| !linked.name.isCompatibleWith(bound.name)) {
				bindings.expandInto(binding.complete(LINK,
						Supply.instance(linked.typed(type))));
				implicitlyBindToConstructor(binding, linked, bindings);
				return;
			}
			if (type.isInterface()) {
				throw new InconsistentBinding(
						"Interface type linked in a loop: " + bound + " > "
							+ linked);
			}
			bindToMirrorConstructor(bindings, binding, type.rawType);
		}

	}

	static <T> void implicitlyBindToConstructor(Binding<?> incomplete,
			Instance<T> instance, Bindings bindings) {
		Class<T> impl = instance.type().rawType;
		if (!isClassVirtual(impl)) {
			Binding<T> binding = Binding.binding(new Resource<>(instance),
					BindingType.CONSTRUCTOR, null, incomplete.scope,
					incomplete.source.typed(DeclarationType.IMPLICIT));
			bindToMirrorConstructor(bindings, binding, impl);
		}
	}

	static <T> void bindToMirrorConstructor(Bindings bindings,
			Binding<T> binding, Class<? extends T> impl) {
		Constructor<? extends T> c = bindings.mirrors.construction.reflect(
				impl);
		if (c != null) {
			bindings.macros.expandInto(bindings, binding,
					BoundConstructor.bind(c));
		}
	}
}


/*
 *  Copyright (c) 2012-2019, Jan Bernitt
 *
 *  Licensed under the Apache License, Version 2.0, http://www.apache.org/licenses/LICENSE-2.0
 */
package se.jbee.inject;

import static se.jbee.inject.Utils.arrayContains;
import static se.jbee.inject.Utils.arrayEquals;
import static se.jbee.inject.Utils.arrayFindFirst;
import static se.jbee.inject.Utils.arrayMap;
import static se.jbee.inject.Utils.newArray;

import java.io.Serializable;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

/**
 * A generic version of {@link Class} like {@link java.lang.reflect.Type} but
 * without a complex hierarchy. Instead all cases are represented as a general
 * model. The key difference is that this model just describes concrete types.
 * So there is no representation for a {@link TypeVariable}.
 *
 * There are some generic cases ({@code ? super X} types) that are not supported
 * right now because they haven't been needed.
 *
 * @author Jan Bernitt (jan@jbee.se)
 */
public final class Type<T>
		implements Qualifying<Type<?>>, Parameter<T>, Serializable {

	public static final Type<Object> OBJECT = Type.raw(Object.class);
	public static final Type<Void> VOID = raw(Void.class);
	public static final Type<?> WILDCARD = OBJECT.asUpperBound();

	public static Type<?> fieldType(Field field) {
		return type(field.getGenericType());
	}

	public static Type<?> returnType(Method method) {
		return type(method.getGenericReturnType());
	}

	public static Type<?>[] parameterTypes(Executable methodOrConstructor) {
		return parameterTypes(methodOrConstructor.getGenericParameterTypes());
	}

	private static Type<?>[] parameterTypes(
			java.lang.reflect.Type[] genericParameterTypes) {
		Type<?>[] res = new Type<?>[genericParameterTypes.length];
		for (int i = 0; i < res.length; i++) {
			res[i] = type(genericParameterTypes[i]);
		}
		return res;
	}

	public static Type<?>[] wildcards(TypeVariable<?>... variables) {
		Type<?>[] wildcards = new Type<?>[variables.length];
		Arrays.fill(wildcards, WILDCARD);
		return wildcards;
	}

	public static <T> Type<T> raw(Class<T> type) {
		return new Type<>(type);
	}

	private static Type<?>[] types(java.lang.reflect.Type[] parameters,
			Map<String, Type<?>> actualTypeArguments) {
		Type<?>[] args = new Type<?>[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
			args[i] = type(parameters[i], actualTypeArguments);
		}
		return args;
	}

	private static Type<?> type(java.lang.reflect.Type type) {
		return type(type, Collections.emptyMap());
	}

	private static Type<?> type(java.lang.reflect.Type type,
			Map<String, Type<?>> actualTypeArguments) {
		if (type instanceof Class<?>) {
			return raw((Class<?>) type);
		}
		if (type instanceof ParameterizedType) {
			return parameterizedType((ParameterizedType) type,
					actualTypeArguments);
		}
		if (type instanceof TypeVariable<?>) {
			return actualTypeArguments.get(((TypeVariable<?>) type).getName());
		}
		if (type instanceof GenericArrayType) {
			GenericArrayType gat = (GenericArrayType) type;
			return type(gat.getGenericComponentType()).addArrayDimension();
		}
		if (type instanceof WildcardType) {
			WildcardType wt = (WildcardType) type;
			java.lang.reflect.Type[] upperBounds = wt.getUpperBounds();
			if (upperBounds.length == 1) {
				return type(upperBounds[0]).asUpperBound();
			}
		}
		throw new UnsupportedOperationException(
				"Type has no support yet: " + type);
	}

	private static <T> Type<T> parameterizedType(ParameterizedType type,
			Map<String, Type<?>> actualTypeArguments) {
		@SuppressWarnings("unchecked")
		Class<T> rawType = (Class<T>) type.getRawType();
		return new Type<>(rawType,
				types(type.getActualTypeArguments(), actualTypeArguments));
	}

	public final Class<T> rawType;
	private final Type<?>[] params;

	/**
	 * Used to model upper bound wildcard types like ? extends Foo
	 */
	private final boolean upperBound;

	private Type(boolean upperBound, Class<T> rawType, Type<?>[] parameters) {
		assert (rawType != null);
		this.rawType = primitiveAsWrapper(rawType);
		this.params = parameters;
		this.upperBound = upperBound;
	}

	private Type(Class<T> rawType, Type<?>[] parameters) {
		this(false, rawType, parameters);
	}

	private Type(Class<T> rawType) {
		this(false, rawType, new Type<?>[0]);
	}

	@Override
	public Type<T> type() {
		return this;
	}

	@Override
	public <E> Type<E> typed(Type<E> type) {
		return type;
	}

	@SuppressWarnings("unchecked")
	public <S> Type<? extends S> castTo(Type<S> supertype)
			throws ClassCastException {
		toSupertype(supertype);
		return (Type<S>) this;
	}

	public <S> Type<S> toSupertype(Type<S> supertype) {
		if (!isAssignableTo(supertype)) {
			throw new ClassCastException(
					"Cannot cast " + this + " to " + supertype);
		}
		return supertype;
	}

	public Type<? extends T> asUpperBound() {
		return upperBound(true);
	}

	public Type<? extends T> upperBound(boolean upperBound) {
		return this.upperBound == upperBound
			? this
			: new Type<>(upperBound, rawType, params);
	}

	public Type<? extends T> asExactType() {
		return upperBound(false);
	}

	@SuppressWarnings("unchecked")
	public Type<T[]> addArrayDimension() {
		Object proto = newArray(rawType, 0);
		return new Type<>(upperBound, (Class<T[]>) proto.getClass(), params);
	}

	public boolean equalTo(Type<?> other) {
		return this == other
			|| rawType == other.rawType && upperBound == other.upperBound
				&& arrayEquals(params, other.params, Type::equalTo);
	}

	@Override
	public int hashCode() {
		return rawType.hashCode();
	}

	@Override
	public boolean equals(Object obj) {
		return obj instanceof Type<?> && equalTo((Type<?>) obj);
	}

	/**
	 * @return in case of an array type the {@link Class#getComponentType()}
	 *         with the same type parameters as this type or otherwise this
	 *         type.
	 */
	@SuppressWarnings("unchecked")
	public  Type<?> baseType() {
		if (!rawType.isArray())
			return this;
		Class<?> baseType = rawType;
		while (baseType.isArray()) {
			baseType = baseType.getComponentType();
		}
		return new Type<>(upperBound, (Class) baseType, params);
	}

	/**
	 * @return The actual type parameters (arguments).
	 */
	public Type<?>[] parameters() {
		return params.clone();
	}

	public Type<?> parameter(int index) {
		if (index < 0 || index >= rawType.getTypeParameters().length)
			throw new IndexOutOfBoundsException("The type " + this
				+ " has no type parameter at index: " + index);
		return isRawType() ? WILDCARD : params[index];
	}

	public boolean isAssignableTo(Type<?> other) {
		if (!other.rawType.isAssignableFrom(rawType))
			return false;
		if (!isParameterized() || other.isRawType())
			return true; //raw type is ok - no parameters to check
		if (other.rawType == rawType) // both have the same rawType
			return allParametersAreAssignableTo(other);
		@SuppressWarnings("unchecked")
		Class<? super T> commonRawType = (Class<? super T>) other.rawType;
		Type<?> asOther = supertype(commonRawType, this);
		return asOther.allParametersAreAssignableTo(other);
	}

	private boolean allParametersAreAssignableTo(Type<?> other) {
		return arrayEquals(params, other.params, Type::asParameterAssignableTo);
	}

	private boolean asParameterAssignableTo(Type<?> other) {
		if (rawType == other.rawType)
			return !isParameterized() || allParametersAreAssignableTo(other);
		return other.isUpperBound() && isAssignableTo(other.asExactType());
	}

	public boolean isInterface() {
		return rawType.isInterface();
	}

	public boolean isAbstract() {
		return Modifier.isAbstract(rawType.getModifiers());
	}

	/**
	 * @return true if this type describes the upper bound of the required types
	 *         (a wildcard generic).
	 */
	public boolean isUpperBound() {
		return upperBound;
	}

	/**
	 * @see #hasTypeParameter() To check if the {@link Class} defines type
	 *      parameter.
	 * @return true if the type {@link #hasTypeParameter()} and parameters are
	 *         given.
	 */
	public boolean isParameterized() {
		return params.length > 0;
	}

	/**
	 * @see #isParameterized() To check if actual type parameters are given.
	 *
	 * @return true when the {@link Class} defines type parameters (generics).
	 */
	public boolean hasTypeParameter() {
		return rawType.getTypeParameters().length > 0;
	}

	public int arrayDimensions() {
		return arrayDimensions(rawType);
	}

	private static int arrayDimensions(Class<?> type) {
		return !type.isArray()
			? 0
			: 1 + arrayDimensions(type.getComponentType());
	}

	@Override
	public boolean moreQualiedThan(Type<?> other) {
		if (!rawType.isAssignableFrom(other.rawType))
			return true;
		if ((hasTypeParameter() && !isParameterized())
			|| (isUpperBound() && !other.isUpperBound()))
			return false; // equal or other is a subtype of this
		if ((other.hasTypeParameter() && !other.isParameterized())
			|| (!isUpperBound() && other.isUpperBound()))
			return true;
		if (rawType == other.rawType)
			return moreApplicableParametersThan(other);
		@SuppressWarnings("unchecked")
		Type<?> asOther = supertype(rawType, (Type<? extends T>) other);
		return moreApplicableParametersThan(asOther);
	}

	private boolean moreApplicableParametersThan(Type<?> other) {
		if (params.length == 1)
			return params[0].moreQualiedThan(other.params[0]);
		int morePrecise = 0;
		for (int i = 0; i < params.length; i++)
			if (params[i].moreQualiedThan(other.params[0]))
				morePrecise++;
		return morePrecise > params.length - morePrecise;
	}

	/**
	 * Example - typeOf
	 *
	 *

	 * Map<String,String> => Map<? extends String, ? extends String>
	 *

	 *
	 * @return A {@link Type} having all its type arguments
	 *         {@link #asUpperBound()}s. Use this to model <?> wildcard
	 *         generic.
	 */
	public Type<T> parametizedAsUpperBounds() {
		if (!isParameterized())
			return isRawType()
				? parametized(wildcards(rawType.getTypeParameters()))
				: this;
		if (areAllTypeParametersAreUpperBounds())
			return this;
		return new Type<>(upperBound, rawType,
				arrayMap(params, Type::asUpperBound));
	}

	/**
	 * @return true, in case this is a raw type - that is a generic type without
	 *         any generic type information available.
	 */
	public boolean isRawType() {
		return !isParameterized() && rawType.getTypeParameters().length > 0;
	}

	/**
	 * @return True when all type parameters are upper bounds.
	 */
	public boolean areAllTypeParametersAreUpperBounds() {
		return !arrayContains(params, p -> !p.isUpperBound());
	}

	public Type<T> parametized(Class<?>... typeParams) {
		return parametized(arrayMap(typeParams, Type.class, Type::raw));
	}

	public Type<T> parametized(Type<?>... params) {
		checkTypeParameters(params);
		return new Type<>(upperBound, rawType, params);
	}

	@Override
	public String toString() {
		return name(true);
	}

	void toString(StringBuilder b, boolean canonicalName) {
		if (isUpperBound()) {
			if (rawType == Object.class) {
				b.append("?");
				return;
			}
			b.append("? extends ");
		}
		String name = canonicalName
			? rawType.getCanonicalName()
			: rawType.getSimpleName();
		b.append(rawType.isArray()
			? name.substring(0, name.indexOf('['))
			: name);
		if (isParameterized()) {
			b.append('<');
			params[0].toString(b, canonicalName);
			for (int i = 1; i < params.length; i++) {
				b.append(',');
				params[i].toString(b, canonicalName);
			}
			b.append('>');
		}
		if (rawType.isArray()) {
			b.append(name.substring(name.indexOf('[')));
		}
	}

	public String simpleName() {
		return name(false);
	}

	private String name(boolean canonicalName) {
		StringBuilder b = new StringBuilder();
		toString(b, canonicalName);
		return b.toString();
	}

	private void checkTypeParameters(Type<?>... params) {
		if (params.length == 0)
			return; // is treated as raw-type
		if (arrayDimensions() > 0) {
			baseType().checkTypeParameters(params);
			return;
		}
		TypeVariable<Class<T>>[] vars = rawType.getTypeParameters();
		if (vars.length != params.length)
			throw new IllegalArgumentException(
					"Invalid nuber of type arguments - " + rawType
						+ " has type variables " + Arrays.toString(vars)
						+ " but got:" + Arrays.toString(params));
		for (int i = 0; i < vars.length; i++) {
			for (java.lang.reflect.Type t : vars[i].getBounds()) {
				Type<?> var = type(t, new HashMap<>());
				if (t != Object.class && !params[i].isAssignableTo(var)) {
					throw new IllegalArgumentException(params[i]
						+ " is not assignable to the type variable: " + var);
				}
			}
		}
	}

	public static <S> Type<? extends S> supertype(Class<S> supertype,
			Type<? extends S> type) {
		if (supertype.getTypeParameters().length == 0)
			return raw(supertype); // just for better performance
		@SuppressWarnings("unchecked")
		Type<? extends S> res = (Type<? extends S>) arrayFindFirst(
				type.supertypes(), s -> s.rawType == supertype);
		if (res == null)
			throw new IllegalArgumentException("`" + supertype
				+ "` is not a supertype of: `" + type + "`");
		return res;
	}

	/**
	 * @return a list of all super-classes and super-interfaces of this type
	 *         starting with the direct super-class followed by the direct
	 *         super-interfaces continuing by going up the type hierarchy.
	 */
	public Type<? super T>[] supertypes() {
		Set<Type<?>> res = new LinkedHashSet<>();
		Class<?> supertype = rawType;
		java.lang.reflect.Type genericSupertype = null;
		Type<?> type = this;
		Map<String, Type<?>> actualTypeArguments = actualTypeArguments(type);
		if (!isInterface())
			res.add(OBJECT);
		while (supertype != null) {
			if (genericSupertype != null) {
				type = type(genericSupertype, actualTypeArguments);
				res.add(type);
			}
			actualTypeArguments = actualTypeArguments(type);
			addSuperInterfaces(res, supertype, actualTypeArguments);
			genericSupertype = supertype.getGenericSuperclass();
			supertype = supertype.getSuperclass();
		}
		@SuppressWarnings("unchecked")
		Type<? super T>[] supertypes = (Type<? super T>[]) res.toArray(
				new Type<?>[0]);
		return supertypes;
	}

	private static <V> Map<String, Type<?>> actualTypeArguments(Type<V> type) {
		Map<String, Type<?>> actualTypeArguments = new HashMap<>();
		TypeVariable<Class<V>>[] typeParams = type.rawType.getTypeParameters();
		for (int i = 0; i < typeParams.length; i++) {
			// it would be correct to use the joint type of the bounds but since it is not possible to create a type with illegal parameters it is ok to just use Object since there is no way to model a joint type
			actualTypeArguments.put(typeParams[i].getName(), type.parameter(i));
		}
		return actualTypeArguments;
	}

	private void addSuperInterfaces(Set<Type<?>> res, Class<?> type,
			Map<String, Type<?>> actualTypeArguments) {
		Class<?>[] interfaces = type.getInterfaces();
		java.lang.reflect.Type[] genericInterfaces = type.getGenericInterfaces();
		for (int i = 0; i < interfaces.length; i++) {
			Type<?> interfaceType = Type.type(genericInterfaces[i],
					actualTypeArguments);
			if (!res.contains(interfaceType)) {
				res.add(interfaceType);
				addSuperInterfaces(res, interfaces[i],
						actualTypeArguments(interfaceType));
			}
		}
	}

	@SuppressWarnings("unchecked")
	public static <T> Class<T> primitiveAsWrapper(Class<T> type) {
		if (!type.isPrimitive())
			return type;
		if (type == int.class)
			return (Class<T>) Integer.class;
		if (type == boolean.class)
			return (Class<T>) Boolean.class;
		if (type == long.class)
			return (Class<T>) Long.class;
		if (type == char.class)
			return (Class<T>) Character.class;
		if (type == void.class)
			return (Class<T>) Void.class;
		if (type == float.class)
			return (Class<T>) Float.class;
		if (type == double.class)
			return (Class<T>) Double.class;
		if (type == byte.class)
			return (Class<T>) Byte.class;
		if (type == short.class)
			return (Class<T>) Short.class;
		throw new UnsupportedOperationException(
				"The primitive " + type + " cannot be wrapped yet!");
	}
}


/*
 *  Copyright (c) 2012-2019, Jan Bernitt
 *
 *  Licensed under the Apache License, Version 2.0, http://www.apache.org/licenses/LICENSE-2.0
 */
package se.jbee.inject;

import static java.lang.System.arraycopy;
import static java.lang.reflect.Array.newInstance;
import static java.lang.reflect.Modifier.isPrivate;
import static java.lang.reflect.Modifier.isProtected;
import static java.lang.reflect.Modifier.isPublic;
import static java.util.Arrays.copyOf;
import static se.jbee.inject.Type.raw;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Map;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;

import se.jbee.inject.UnresolvableDependency.NoMethodForDependency;

/**
 * Language level utility methods for the library.
 *
 * @author Jan Bernitt (jan@jbee.se)
 */
public final class Utils {

	/**
	 * Function that determines of two elements of the same type are equal.
	 *
	 * @param <T> type of the elements compared
	 */
	public interface Eq<T> extends BiPredicate<T, T> {
	}

	/* Arrays */

	public static <A> A[] arrayFilter(A[] arr, Predicate<A> accept) {
		if (arr == null || arr.length == 0)
			return arr;
		A[] accepted = newArray(arr, arr.length);
		int j = 0;
		for (int i = 0; i < arr.length; i++)
			if (accept.test(arr[i]))
				accepted[j++] = arr[i];
		return j == arr.length ? arr : copyOf(accepted, j);
	}

	public static <A> A[] arrayAppend(A[] arr, A e) {
		A[] copy = copyOf(arr, arr.length + 1);
		copy[arr.length] = e;
		return copy;
	}

	public static <A> A[] arrayPrepand(A e, A[] arr) {
		A[] copy = newArray(arr, arr.length + 1);
		arraycopy(arr, 0, copy, 1, arr.length);
		copy[0] = e;
		return copy;
	}

	public static <A> A[] arrayInsert(A e, A[] arr, Eq<A> eq) {
		if (arr.length == 0)
			return arrayPrepand(e, arr);
		int i = arrayIndex(arr, e, eq);
		if (i >= 0) {
			if (e == arr[i]) // already very same
				return arr;
			A[] tmp = arr.clone();
			tmp[i] = e;
			return tmp;
		}
		return arrayPrepand(e, arr);
	}

	public static <A> A[] arrayDropTail(A[] arr, int n) {
		if (arr.length <= n)
			return newArray(arr, 0);
		return copyOf(arr, arr.length - n);
	}

	/**
	 * This is a special form of flatmap where the mapping does not return zero
	 * to n Bs but always returns a B. If {@code null} is returned this means
	 * "zero" or remove the B. So length of {@code B[]} is always {@code <=}
	 * length of {@code A[]}.
	 */
	public static <A, B> B[] arrayFlatmap(A[] as, Class<B> to,
			Function<A, B> flatmapOp) {
		B[] bs = newArray(to, as.length);
		int j = 0;
		for (int i = 0; i < as.length; i++) {
			B b = flatmapOp.apply(as[i]);
			if (b != null)
				bs[j++] = b;
		}
		return j == as.length ? bs : copyOf(bs, j);
	}

	public static <A, B> B[] arrayMap(A[] as, Class<B> to,
			Function<A, B> mapOp) {
		B[] bs = newArray(to, as.length);
		for (int i = 0; i < as.length; i++)
			bs[i] = mapOp.apply(as[i]);
		return bs;
	}

	public static <A> A[] arrayMap(A[] arr, UnaryOperator<A> mapOp) {
		if (arr == null || arr.length == 0)
			return arr;
		A[] mapped = newArray(arr, arr.length);
		for (int i = 0; i < arr.length; i++)
			mapped[i] = mapOp.apply(arr[i]);
		return mapped;
	}

	public static <A> int arrayIndex(A[] arr, A e, Eq<A> eq) {
		if (arr == null || arr.length == 0)
			return -1;
		for (int i = 0; i < arr.length; i++) {
			if (eq.test(e, arr[i])) {
				return i;
			}
		}
		return -1;
	}

	public static <A> A arrayFindFirst(A[] arr, Predicate<A> test) {
		if (arr == null || arr.length == 0)
			return null;
		for (int i = 0; i < arr.length; i++)
			if (test.test(arr[i]))
				return arr[i];
		return null;
	}

	public static <A> boolean arrayContains(A[] arr, Predicate<A> test) {
		return arrayFindFirst(arr, test) != null;
	}

	public static <A> boolean arrayContains(A[] arr, A e, Eq<A> eq) {
		if (arr == null || arr.length == 0)
			return false;
		for (int i = 0; i < arr.length; i++)
			if (eq.test(arr[i], e))
				return true;
		return false;
	}

	public static <A> boolean arrayEquals(A[] a, A[] b, Eq<A> eq) {
		if (a == b)
			return true;
		if (b == null || a == null || a.length != b.length)
			return false;
		for (int i = 0; i < a.length; i++)
			if (!eq.test(a[i], b[i]))
				return false;
		return true;
	}

	@SuppressWarnings("unchecked")
	public static <A> A[] newArray(A[] proto, int length) {
		return (A[]) newArray(proto.getClass().getComponentType(), length);
	}

	@SuppressWarnings("unchecked")
	public static <A> A[] newArray(Class<A> componentType, int length) {
		return (A[]) newInstance(componentType, length);
	}

	public static <A> A[] arrayOf(Collection<? extends A> list, Class<A> type) {
		return list.toArray(newArray(type, list.size()));
	}

	/* Annotations */

	public static Method annotationPropertyByType(Class<?> type,
			Class<? extends Annotation> annotation) {
		return arrayFindFirst(annotation.getDeclaredMethods(),
				m -> m.getReturnType() == type);
	}

	public static Name annotatedName(Method nameProperty, Annotation obj) {
		if (obj == null)
			return null;
		try {
			String name = (String) nameProperty.invoke(obj);
			if (!name.isEmpty() && !name.equals(nameProperty.getDefaultValue()))
				return Name.named(name);
		} catch (Exception e) {
			// fall through
		}
		return null;
	}

	public static <A extends Annotation> A annotation(Class<A> type,
			AnnotatedElement obj) {
		A res = obj.getAnnotation(type);
		if (res != null)
			return res;
		if (!isAllowedOnAnnotations(type))
			return null;
		do {
			for (Annotation a : obj.getAnnotations()) {
				res = annotation(type, a.annotationType());
				if (res != null)
					return res;
			}
		} while (obj instanceof Class && obj != Object.class);
		return null;
	}

	public static boolean isAllowedOnAnnotations(
			Class<? extends Annotation> type) {
		return type.isAnnotationPresent(Target.class)
			&& arrayContains(type.getAnnotation(Target.class).value(),
					e -> e == ElementType.ANNOTATION_TYPE);
	}

	/* Classes / Types */

	/**
	 * @return the given object made accessible.
	 */
	public static <T extends AccessibleObject> T accessible(T obj) {
		obj.setAccessible(true);
		return obj;
	}

	/**
	 * @return A {@link Class} counts as "virtual" when it is known that its not
	 *         a type handled by an {@link Injector} context, either because it
	 *         cannot be constructed at all or it does not make sense to let the
	 *         {@link Injector} take care of it. This includes value types,
	 *         enums, collection types (including arrays) or any type than
	 *         cannot be instantiated by its nature (abstract types).
	 *
	 *         Note that this method just covers JRE types.
	 */
	public static boolean isClassVirtual(Class<?> cls) {
		return cls == null || cls.isInterface() || cls.isEnum()
			|| cls.isAnnotation() || cls.isAnonymousClass() || cls.isPrimitive()
			|| cls.isArray() || Modifier.isAbstract(cls.getModifiers())
			|| cls == String.class || Number.class.isAssignableFrom(cls)
			|| cls == Boolean.class || cls == Void.class || cls == Class.class
			|| Collection.class.isAssignableFrom(cls)
			|| Map.class.isAssignableFrom(cls);
	}

	/**
	 * @return A {@link Class} is monomodal if it there is just a single
	 *         possible initial state. All newly created instances can just have
	 *         this similar initial state but due to internal state they could
	 *         (not necessarily must) develop (behave) different later on.
	 *
	 *         The opposite of monomodal is multimodal.
	 */
	public static boolean isClassMonomodal(Class<?> cls) {
		if (cls.isInterface())
			return false;
		if (cls == Object.class)
			return true;
		for (Field f : cls.getDeclaredFields())
			if (!Modifier.isStatic(f.getModifiers()))
				return false;
		for (Constructor<?> c : cls.getDeclaredConstructors())
			// maybe arguments are passed to super-type so we check it too
			if (c.getParameterTypes().length > 0)
				return isClassMonomodal(cls.getSuperclass());
		return true;
	}

	/* Members */

	public static <T extends Member> T moreVisible(T a, T b) {
		int am = a.getModifiers();
		int bm = b.getModifiers();
		if (isPublic(am))
			return a;
		if (isPublic(bm))
			return b;
		if (isProtected(am))
			return a;
		if (isProtected(bm))
			return b;
		if (isPrivate(bm))
			return a;
		if (isPrivate(am))
			return b;
		return a; // same
	}

	/**
	 * Returns the constructor with most visible visibility and longest argument
	 * list. Self-referencing constructors are ignored.
	 *
	 * @param type constructed type
	 * @return The highest visibility constructor with the most parameters that
	 *         does not have the declaring class itself as parameter type (some
	 *         compiler seam to generate such a synthetic constructor)
	 * @throws NoMethodForDependency in case the type is not constructible (has
	 *             no constructors at all)
	 */
	public static <T> Constructor<T> commonConstructor(Class<T> type)
			throws NoMethodForDependency {
		Constructor<?>[] cs = type.getDeclaredConstructors();
		if (cs.length == 0)
			throw new NoMethodForDependency(raw(type));
		Constructor<?> mostParamsConstructor = null;
		for (Constructor<?> c : cs) {
			if (!arrayContains(c.getParameterTypes(), type, (a, b) -> a == b) // avoid self referencing constructors (synthetic) as they cause endless loop
				&& (mostParamsConstructor == null //
					|| (moreVisible(c, mostParamsConstructor) == c
						&& (moreVisible(mostParamsConstructor, c) == c
							|| c.getParameterCount() > mostParamsConstructor.getParameterCount())))) {
				mostParamsConstructor = c;
			}
		}
		if (mostParamsConstructor == null)
			throw new NoMethodForDependency(raw(type));
		@SuppressWarnings("unchecked")
		Constructor<T> c = (Constructor<T>) mostParamsConstructor;
		return c;
	}

	public static <T> Constructor<T> commonConstructorOrNull(Class<T> type) {
		try {
			return commonConstructor(type);
		} catch (RuntimeException e) {
			return null;
		}
	}

	public static <T> Constructor<T> noArgsConstructor(Class<T> type) {
		if (type.isInterface())
			throw new NoMethodForDependency(raw(type));
		try {
			return type.getDeclaredConstructor();
		} catch (Exception e) {
			if (e instanceof RuntimeException)
				throw (RuntimeException) e;
			throw new RuntimeException(e);
		}
	}

	/* Sequences */

	public static boolean seqRegionEquals(CharSequence s1, CharSequence s2,
			int length) {
		if (s1.length() < length || s2.length() < length)
			return false;
		if (s1 == s2)
			return true;
		for (int i = length - 1; i > 0; i--)
			if (s1.charAt(i) != s2.charAt(i))
				return false;
		return true;
	}

	public static int seqCount(CharSequence seq, char match) {
		int c = 0;
		for (int i = seq.length() - 1; i > 0; i--)
			if (seq.charAt(i) == match)
				c++;
		return c;
	}

	/* Exception Handling */

	public static <T> T orElse(T defaultValue, Provider<T> src) {
		try {
			return src.provide();
		} catch (UnresolvableDependency e) {
			return defaultValue;
		}
	}
}


/*
 *  Copyright (c) 2012-2019, Jan Bernitt
 *
 *  Licensed under the Apache License, Version 2.0, http://www.apache.org/licenses/LICENSE-2.0
 */
package se.jbee.inject;

import static se.jbee.inject.Utils.arrayContains;
import static se.jbee.inject.Utils.arrayMap;
import static se.jbee.inject.Utils.seqCount;
import static se.jbee.inject.Utils.seqRegionEquals;

import java.io.Serializable;
import java.util.Arrays;

/**
 * A set of {@link Package}s described one or more root packages (on the same
 * hierarchy level/depth) with or without their sub-packages.
 *
 * @author Jan Bernitt (jan@jbee.se)
 */
public final class Packages implements Qualifying<Packages>, Serializable {

	/**
	 * Contains all packages including the (default) package.
	 */
	public static final Packages ALL = new Packages(new String[0], true);

	/**
	 * The (default) package.
	 */
	public static final Packages DEFAULT = new Packages(new String[0], false);

	public static Packages packageAndSubPackagesOf(Class<?> type) {
		return new Packages(packageNameOf(type), true);
	}

	public static Packages packageAndSubPackagesOf(Class<?> type,
			Class<?>... types) {
		commonPackageDepth(type, types);
		return new Packages(packageNamesOf(type, "", types), true);
	}

	public static Packages packageOf(Class<?> type) {
		return new Packages(packageNameOf(type), false);
	}

	public static Packages packageOf(Class<?> type, Class<?>... types) {
		return new Packages(packageNamesOf(type, "", types), false);
	}

	public static Packages subPackagesOf(Class<?> type) {
		return new Packages(packageNameOf(type) + ".", true);
	}

	public static Packages subPackagesOf(Class<?> type, Class<?>... types) {
		commonPackageDepth(type, types);
		return new Packages(packageNamesOf(type, ".", types), true);
	}

	private static String[] packageNamesOf(Class<?> packageOf, String suffix,
			Class<?>... packagesOf) {
		String[] names = new String[packagesOf.length + 1];
		names[0] = packageNameOf(packageOf) + suffix;
		for (int i = 1; i <= packagesOf.length; i++)
			names[i] = packageNameOf(packagesOf[i - 1]) + suffix;
		return names;
	}

	private static String packageNameOf(Class<?> packageOf) {
		Package pkg = packageOf.getPackage();
		return pkg == null ? "(default)" : pkg.getName();
	}

	private static String packageNameOf(Type<?> packageOf) {
		return packageOf.isUpperBound()
			? "-NONE-"
			: packageNameOf(packageOf.rawType);
	}

	private final String[] roots;
	private final boolean includingSubpackages;
	private final int rootDepth;

	private Packages(String root, boolean includingSubpackages) {
		this(new String[] { root }, includingSubpackages);
	}

	private Packages(String[] roots, boolean includingSubpackages) {
		this.roots = roots;
		this.includingSubpackages = includingSubpackages;
		this.rootDepth = rootDepth(roots);
	}

	public Packages parents() {
		if (rootDepth == 0)
			return this;
		if (rootDepth == 1)
			return includingSubpackages ? ALL : DEFAULT;
		return new Packages(arrayMap(roots, Packages::parent),
				includingSubpackages);
	}

	/**
	 * <pre>
	 * foo.bar.baz -> foo.bar
	 * foo.bar. -> foo.
	 * </pre>
	 */
	private static String parent(String root) {
		return root.substring(0, root.lastIndexOf('.', root.length() - 2)
			+ (root.endsWith(".") ? 1 : 0));
	}

	private static void commonPackageDepth(Class<?> type, Class<?>[] types) {
		if (arrayContains(types, type, (a, b) -> dotsIn(
				a.getPackage().getName()) != dotsIn(b.getPackage().getName())))
			throw new IllegalArgumentException(
					"All classes of a packages set have to be on same depth level.");
	}

	private static int rootDepth(String[] roots) {
		return roots.length == 0 ? 0 : dotsIn(roots[0]);
	}

	private static int dotsIn(String s) {
		return seqCount(s, '.');
	}

	public boolean contains(Type<?> type) {
		if (includesAll())
			return true;
		final String packageNameOfType = packageNameOf(type);
		return arrayContains(roots,
				root -> seqRegionEquals(root, packageNameOfType,
						includingSubpackages
							? root.length()
							: packageNameOfType.length()));
	}

	public boolean includesAll() {
		return roots.length == 0 && includingSubpackages;
	}

	@Override
	public boolean equals(Object obj) {
		return obj instanceof Packages && equalTo(((Packages) obj));
	}

	@Override
	public int hashCode() {
		return Arrays.hashCode(roots);
	}

	@Override
	public boolean moreQualiedThan(Packages other) {
		if (includingSubpackages != other.includingSubpackages)
			return !includingSubpackages;
		return rootDepth != other.rootDepth && rootDepth > other.rootDepth;
	}

	@Override
	public String toString() {
		if (roots.length == 0)
			return includingSubpackages ? "*" : "(default)";
		if (roots.length == 1)
			return toString(roots[0]);
		StringBuilder b = new StringBuilder();
		for (int i = 0; i < roots.length; i++)
			b.append('+').append(toString(roots[i]));
		return b.substring(1);
	}

	private String toString(String root) {
		return root + (includingSubpackages ? "*" : "");
	}

	public boolean equalTo(Packages other) {
		return other.includingSubpackages == includingSubpackages //
			&& other.rootDepth == rootDepth
			&& other.roots.length == roots.length
			&& Arrays.equals(roots, other.roots);
	}

}


/*
 *  Copyright (c) 2012-2019, Jan Bernitt
 *
 *  Licensed under the Apache License, Version 2.0, http://www.apache.org/licenses/LICENSE-2.0
 */
package se.jbee.inject;

import static java.util.Arrays.asList;
import static se.jbee.inject.Instance.defaultInstanceOf;
import static se.jbee.inject.Name.pluginFor;
import static se.jbee.inject.Type.raw;
import static se.jbee.inject.Utils.arrayAppend;
import static se.jbee.inject.Utils.arrayContains;
import static se.jbee.inject.Utils.arrayDropTail;
import static se.jbee.inject.Utils.arrayEquals;
import static se.jbee.inject.Utils.arrayFindFirst;
import static se.jbee.inject.Utils.arrayMap;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Iterator;

import se.jbee.inject.UnresolvableDependency.DependencyCycle;
import se.jbee.inject.UnresolvableDependency.UnstableDependency;

/**
 * Describes what is wanted/needed as parameter to construct a instance of T.
 *
 * @author Jan Bernitt (jan@jbee.se)
 */
public final class Dependency<T>
		implements Parameter<T>, Iterable<Injection>, Serializable {

	/**
	 * A empty {@link Injection} hierarchy. It is used whenever the
	 * {@link Dependency} does not depend on the actual hierarchy. This is the
	 * default.
	 */
	private static final Injection[] UNTARGETED = new Injection[0];

	public static Dependency<Class<?>[]> pluginsFor(Class<?> pluginPoint) {
		return pluginsFor(pluginPoint, Name.ANY.toString());
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static Dependency<Class<?>[]> pluginsFor(Class<?> pluginPoint,
			String property) {
		return (Dependency) dependency(raw(Class[].class)).named(
				pluginFor(pluginPoint, property));
	}
	//TODO maybe extract a Plugins Extension

	public static <T> Dependency<T> dependency(Class<T> type) {
		return dependency(raw(type));
	}

	public static <T> Dependency<T> dependency(Type<T> type) {
		return dependency(type, UNTARGETED);
	}

	public static <T> Dependency<T> dependency(Instance<T> instance) {
		return dependency(instance, UNTARGETED);
	}

	private static <T> Dependency<T> dependency(Type<T> type,
			Injection[] hierarchy) {
		return dependency(Instance.instance(Name.ANY, type), hierarchy);
	}

	private static <T> Dependency<T> dependency(Instance<T> instance,
			Injection[] hierarchy) {
		return new Dependency<>(instance, hierarchy);
	}

	private final Injection[] hierarchy;
	public final Instance<T> instance;

	private Dependency(Instance<T> instance, Injection... hierarchy) {
		this.instance = instance;
		this.hierarchy = hierarchy;
	}

	@Override
	public boolean equals(Object obj) {
		return obj instanceof Dependency && equalTo((Dependency<?>) obj);
	}

	@Override
	public int hashCode() {
		return instance.hashCode() ^ Arrays.hashCode(hierarchy);
	}

	public boolean equalTo(Dependency<?> other) {
		// cheapest first...
		return instance.equalTo(other.instance)
			&& arrayEquals(hierarchy, other.hierarchy, Injection::equalTo);
	}

	@Override
	public String toString() {
		StringBuilder b = new StringBuilder();
		for (Injection i : hierarchy)
			b.append(i.target).append(" -> ");
		b.append(instance);
		return b.toString();
	}

	@Override
	public Type<T> type() {
		return instance.type;
	}

	@Override
	public <E> Dependency<E> typed(Type<E> type) {
		return instanced(instance.typed(type));
	}

	public Dependency<?> onTypeParameter() {
		return typed(type().parameter(0));
	}

	public Dependency<T> named(String name) {
		return named(Name.named(name));
	}

	public Dependency<T> named(Name name) {
		return instanced(instance.named(name));
	}

	public <E> Dependency<E> instanced(Instance<E> instance) {
		return dependency(instance, hierarchy);
	}

	public Dependency<T> untargeted() {
		return dependency(instance, UNTARGETED);
	}

	public Dependency<T> ignoredScoping() {
		return hierarchy.length == 0
			? this
			: dependency(instance,
					arrayMap(hierarchy, Injection::ignoredScoping));
	}

	public boolean isUntargeted() {
		return hierarchy.length == 0;
	}

	public Instance<?> target() {
		return target(0);
	}

	public Instance<?> target(int level) {
		return level >= hierarchy.length
			? Instance.ANY
			: hierarchy[hierarchy.length - 1 - level].target.instance;
	}

	public int injectionDepth() {
		return hierarchy.length;
	}

	/**
	 * Means we inject into the argument target class.
	 */
	public Dependency<T> injectingInto(Class<?> target)
			throws DependencyCycle, UnstableDependency {
		return injectingInto(raw(target));
	}

	public Dependency<T> injectingInto(Type<?> target)
			throws DependencyCycle, UnstableDependency {
		return injectingInto(defaultInstanceOf(target));
	}

	public <I> Dependency<T> injectingInto(Instance<I> target)
			throws DependencyCycle, UnstableDependency {
		return injectingInto(new Resource<>(target), Scoping.ignore);
	}

	public Dependency<T> injectingInto(Resource<?> target, Scoping scoping)
			throws DependencyCycle, UnstableDependency {
		Injection injection = new Injection(instance, target, scoping);
		if (hierarchy.length == 0)
			return new Dependency<>(instance, injection);
		ensureStableScopeNesting(injection);
		ensureNoDependencyCycle(injection);
		return new Dependency<>(instance, arrayAppend(hierarchy, injection));
	}

	public Dependency<T> uninject() {
		return hierarchy.length <= 1
			? untargeted()
			: new Dependency<>(instance, arrayDropTail(hierarchy, 1));
	}

	private void ensureNoDependencyCycle(Injection injection)
			throws DependencyCycle {
		if (arrayContains(hierarchy, injection, Injection::equalTo))
			throw new DependencyCycle(this, injection.target);
	}

	private void ensureStableScopeNesting(Injection injection)
			throws UnstableDependency {
		Injection unstable = arrayFindFirst(hierarchy,
				e -> !injection.scoping.isStableIn(e.scoping));
		if (unstable != null)
			throw new UnstableDependency(unstable, injection);
	}

	@Override
	public Iterator<Injection> iterator() {
		return asList(hierarchy).iterator();
	}
}


package com.stanfy.enroscar.rest.request.binary;

import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;

import com.stanfy.enroscar.beans.BeansManager;
import com.stanfy.enroscar.io.BuffersPool;
import com.stanfy.enroscar.io.IoUtils;
import com.stanfy.enroscar.net.operation.RequestDescription;
import com.stanfy.enroscar.rest.request.net.multipart.Part;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * Binary data info.
 * @param <T> binary data
 * @author Roman Mazur (Stanfy - http://stanfy.com)
 */
public abstract class BinaryData<T extends Parcelable> implements Parcelable {

  /** Logging tag. */
  protected static final String TAG = RequestDescription.TAG;

  /** Binary data name. */
  private String name;
  /** Binary data content name. */
  private String contentName;
  /** Binary data. */
  private T data;
  /** Content type. */
  private String contentType;

  public BinaryData() {
    // nothing
  }

  public BinaryData(final Parcel source) {
    this.name = source.readString();
    this.contentName = source.readString();
    this.data = readData(source);
  }

  @Override
  public int describeContents() { return data != null ? data.describeContents() : 0; }

  @Override
  public void writeToParcel(final Parcel dest, final int flags) {
    dest.writeString(name);
    dest.writeString(contentName);
    writeData(dest, data, flags);
  }

  /** @param binaryDataName string used to name binary data */
  public void setName(final String binaryDataName) { this.name = binaryDataName; }
  /** @return string used to name binary data */
  public String getName() {
    return TextUtils.isEmpty(name) ? RequestDescription.BINARY_NAME_DEFAULT : name;
  }

  public void setContentName(final String contentName) { this.contentName = contentName; }
  /** @return file name of the binary data */
  public String getContentName() { return contentName; }

  /**
   * @param contentType content type value
   */
  public void setContentType(final String contentType) {
    this.contentType = contentType;
  }
  /**
   * @return content type
   */
  public String getContentType() {
    return contentType;
  }

  /** @param data internal data object */
  protected void setData(final T data) { this.data = data; }
  public T getData() { return data; }

  public void clear() {
    this.data = null;
    this.name = null;
    this.contentName = null;
  }

  /** @return part instance */
  public abstract Part createHttpPart(final Context context) throws IOException;

  /**
   * @param stream output
   * @throws IOException if I/O error happens
   */
  public abstract void writeContentTo(final Context context, final OutputStream stream) throws IOException;

  /**
   * @param dest destination parcel
   * @param data internal data object to write to the parcel
   * @param flags write options
   * @see Parcel#writeParcelable(Parcelable, int)
   */
  protected void writeData(final Parcel dest, final T data, final int flags) {
    dest.writeParcelable(data, flags);
  }

  /**
   * @param source source parcel
   * @return internal data object read from the parcel
   */
  protected T readData(final Parcel source) {
    return source.readParcelable(getClass().getClassLoader());
  }

  /**
   * Utility method for transferring input stream to output.
   * @param context application context (needed to retrieve a buffers pool)
   * @param source input stream
   * @param output output stream
   * @throws IOException if I/O error happens
   */
  protected static void writeInputStreamToOutput(final Context context, final InputStream source, final OutputStream output) throws IOException {
    BuffersPool pool = BeansManager.get(context).getContainer().getBean(BuffersPool.class);
    IoUtils.transfer(source, output, pool);
  }

}


package com.stanfy.enroscar.net.cache;

import android.annotation.TargetApi;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.text.TextUtils;
import android.util.Log;

import com.jakewharton.disklrucache.DiskLruCache.Editor;
import com.stanfy.enroscar.io.IoUtils;
import com.stanfy.enroscar.net.UrlConnectionWrapper;
import com.stanfy.enroscar.utils.Time;

import java.io.EOFException;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.CacheRequest;
import java.net.CacheResponse;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLConnection;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.TreeMap;

/** Cache entry. */
public class CacheEntry {

  /** Logging tag. */
  private static final String TAG = "CacheEntry";

  /** This comparator is required to add null keys to the {@link TreeMap}. */
  private static final Comparator<String> STRINGS_COMPARATOR = new Comparator<String>() {
    @Override
    public int compare(final String lhs, final String rhs) {
      if (lhs == null) { return 0; }
      if (rhs == null) { return 1; }
      return lhs.compareTo(rhs);
    }
  };

  /** We respect our own time rules, so give rather big max age to HTTP engine. Measured in seconds. */
  private static final long MAX_AGE_TIME = 30 * Time.DAYS;

  /** Format for the 'Date header'. */
  private static final ThreadLocal<DateFormat> STANDARD_DATE_FORMAT = new ThreadLocal<DateFormat>() {
    @Override
    protected DateFormat initialValue() {
      final DateFormat rfc1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
      rfc1123.setTimeZone(TimeZone.getTimeZone("UTC"));
      return rfc1123;
    }
  };

  /** Request URI. */
  private String uri;
  /** Request method. */
  private String requestMethod;

  /** Time stamp. */
  private long timestamp;

  /** Content encoding. */
  private String encoding;

  /** Response status line. */
  private String statusLine;

  /** Listener. */
  private CacheEntryListener listener;

  /** Time rules. */
  private CacheTimeRule[] timeRules;

  public void setTimeRules(final CacheTimeRule[] timeRules) {
    this.timeRules = timeRules;
  }

  void renewTimestamp() {
    this.timestamp = System.currentTimeMillis();
  }
  public long getTimestamp() { return timestamp; }

  public void setListener(final CacheEntryListener listener) {
    this.listener = listener;
  }

  public void set(final URI uri, final String requestMethod, final Map<String, List<String>> headers) {
    this.uri = uri.toString();
    this.requestMethod = requestMethod;
    renewTimestamp();
  }

  public void setFrom(final URLConnection conn) {
    final URLConnection connection = UrlConnectionWrapper.unwrap(conn);
    try {
      this.uri = connection.getURL().toURI().toString();
      if (connection instanceof HttpURLConnection) {
        this.requestMethod = ((HttpURLConnection) connection).getRequestMethod();
      }
      renewTimestamp();
    } catch (final URISyntaxException e) {
      Log.e(TAG, "Cannot convert URL to URI", e);
    }
  }

  public void setResponseData(final URLConnection conn) {
    try {
      this.statusLine = conn.getHeaderField(null);
    } catch (final Exception e) {
      // :( old Android versions have bad URLConnection implementations
      final URLConnection connection = UrlConnectionWrapper.unwrap(conn);
      if (connection instanceof HttpURLConnection) {
        final HttpURLConnection http = (HttpURLConnection) connection;
        try {
          this.statusLine = "HTTP/1.1 " + http.getResponseCode() + " " + http.getResponseMessage();
        } catch (final IOException ioe) {
          Log.e(TAG, "Cannot set status line", ioe);
        }
      }
    }
    this.encoding = conn.getContentEncoding();
  }

  public String getUri() { return uri; }
  public String getRequestMethod() { return requestMethod; }
  public String getEncoding() { return encoding; }

  /**
   * @param uri URI for this cache entry
   */
  protected void setUri(final String uri) { this.uri = uri; }
  /**
   * @param requestMethod request method for this cache entry
   */
  protected void setRequestMethod(final String requestMethod) { this.requestMethod = requestMethod; }

  public String getCacheKey() { return Md5.getMd5(uri); }

  public boolean canBeCached() {
    return !TextUtils.isEmpty(uri) && !TextUtils.isEmpty(requestMethod) && uri.startsWith("http") && isRequestMethodCacheable();
  }

  /**
   * Override this method if you want other methods than GET to be cached.
   * @return true if method of the request binded to this entry allows to cache response
   */
  protected boolean isRequestMethodCacheable() {
    return "GET".equalsIgnoreCase(requestMethod);
  }

  public final void readFrom(final InputStream in) throws IOException {
    try {
      uri = readString(in);
      requestMethod = readString(in);
      timestamp = readLong(in);
      encoding = readString(in);
      statusLine = readString(in);
      if (encoding.length() == 0) {
        this.encoding = null;
      }
      readMetaData(in);
    } finally {
      in.close();
    }
  }

  /**
   * Read some special metadata about this cache entry.
   * @param in cache entry input stream
   * @throws IOException if error happens
   */
  protected void readMetaData(final InputStream in) throws IOException {
    // nothing
  }

  public final void writeTo(final OutputStream out) throws IOException {
    final Writer writer = new OutputStreamWriter(out, IoUtils.UTF_8);
    writeString(writer, uri);
    writeString(writer, requestMethod);
    writeLong(writer, timestamp);
    writeString(writer, encoding);
    writeString(writer, statusLine);
    writeMetaData(writer);
    writer.close();
  }

  /**
   * Write some special metadata for this entry.
   * @param writer writer for this cache entry
   * @throws IOException if error happens
   */
  protected void writeMetaData(final Writer writer) throws IOException {
    // nothing
  }

  /**
   * @param in input stream
   * @return next line from the stream parsed as an integer
   * @throws IOException if error i/O happens or line has bad format (int cannot be parsed)
   */
  protected static int readInt(final InputStream in) throws IOException {
    final String intString = readString(in);
    try {
      return Integer.parseInt(intString);
    } catch (final NumberFormatException e) {
      throw new IOException("expected an int but was \"" + intString + "\"");
    }
  }
  /**
   * @param in input stream
   * @return next line from the stream parsed as a long integer
   * @throws IOException if error i/O happens or line has bad format (long cannot be parsed)
   */
  protected final long readLong(final InputStream in) throws IOException {
    final String longString = readString(in);
    try {
      return Long.parseLong(longString);
    } catch (final NumberFormatException e) {
      throw new IOException("expected a long but was \"" + longString + "\"");
    }
  }

  /**
   * Write next line as an int.
   * @param writer output writer
   * @param value integer value
   * @throws IOException if error happens
   */
  protected static void writeInt(final Writer writer, final int value) throws IOException {
    writer.write(new StringBuilder().append(value).append('\n').toString());
  }
  /**
   * Write next line as a long.
   * @param writer output writer
   * @param value integer value
   * @throws IOException if error happens
   */
  protected static void writeLong(final Writer writer, final long value) throws IOException {
    writer.write(new StringBuilder().append(value).append('\n').toString());
  }

  /**
   * @param in input stream
   * @return next line
   * @throws IOException if error happens
   */
  protected static String readString(final InputStream in) throws IOException {
    // TODO support UTF-8 here instead

    final StringBuilder result = new StringBuilder(80);
    while (true) {
      final int c = in.read();
      if (c == -1) {
        throw new EOFException();
      } else if (c == '\n') {
        break;
      }

      result.append((char) c);
    }
    final int length = result.length();
    if (length > 0 && result.charAt(length - 1) == '\r') {
      result.setLength(length - 1);
    }
    return result.toString();
  }

  /**
   * Write next line to the output.
   * @param writer output writer
   * @param line line to write (if null just a new empty line is inserted)
   * @throws IOException if error happens
   */
  protected final void writeString(final Writer writer, final String line) throws IOException {
    if (line != null) {
      writer.write(line + '\n');
    } else {
      writer.write('\n');
    }
  }

  public boolean matches(final CacheEntry requestInfo) {
    return this.uri.equals(requestInfo.uri) && this.requestMethod.equals(requestInfo.requestMethod);
  }

  public boolean canBeUsed() {
    if (timeRules == null) { return true; }
    for (final CacheTimeRule rule : timeRules) {
      if (rule.matches(this)) { return rule.isActual(timestamp); }
    }
    return true;
  }

  public CacheResponse newCacheResponse(final InputStream in) { return new CacheEntryResponse(in); }

  public CacheRequest newCacheRequest(final OutputStream output, final Editor editor) {
    return VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD
        ? new CacheEntryRequest(output, editor, listener)
        : new OldApiCacheEntryRequest(output, editor, listener);
  }

  /**
   * Cache response.
   */
  protected class CacheEntryResponse extends CacheResponse {

    /** Input stream. */
    private final InputStream in;

    /**
     * @param in cache entry data input stream
     */
    public CacheEntryResponse(final InputStream in) {
      this.in = in;
    }

    @Override
    public InputStream getBody() throws IOException { return in; }
    @Override
    public Map<String, List<String>> getHeaders() throws IOException {
      final TreeMap<String, List<String>> result = new TreeMap<String, List<String>>(STRINGS_COMPARATOR);

      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
        result.put(null, Collections.singletonList(statusLine));
      }

      // NB! use lower case for header names: for old Android versions
      if (encoding != null) {
        result.put("content-encoding", Collections.singletonList(encoding));
      }
      // force HTTP engine use cache response
      result.put("date", Collections.singletonList(STANDARD_DATE_FORMAT.get().format(new Date())));
      result.put("cache-control", Collections.singletonList(
          "max-age=" + Time.asSeconds(System.currentTimeMillis() + MAX_AGE_TIME)
      ));

      return result;
    }

  }

  /**
   * Cache request.
   */
  @TargetApi(VERSION_CODES.GINGERBREAD)
  protected class CacheEntryRequest extends CacheRequest {

    /** Events listener. */
    private final CacheEntryListener listener;

    /** Cache output. */
    private final OutputStream cacheOut;
    /** Output stream. */
    final OutputStream body;
    /** Cache editor. */
    private final Editor cacheEditor;

    /** Close flag. */
    private boolean done;

    /**
     * @param cacheOut cache entry data output stream
     * @param cacheEditor cache entry editor
     * @param listener entry listener
     */
    public CacheEntryRequest(final OutputStream cacheOut, final Editor cacheEditor, final CacheEntryListener listener) {
      this.cacheEditor = cacheEditor;
      this.cacheOut = cacheOut;
      this.listener = listener;
      this.body = new FilterOutputStream(cacheOut) {
        @Override
        public void write(final byte[] buffer, final int offset, final int length) throws IOException {
          out.write(buffer, offset, length);
        }
        @Override
        public void close() throws IOException {
          synchronized (CacheEntryRequest.this) {
            if (done) { return; }
            done = true;
          }
          super.close();
          cacheEditor.commit();
          if (listener != null) { listener.onCacheEntryWriteSuccess(CacheEntryRequest.this); }
        }
      };
    }

    @Override
    public OutputStream getBody() throws IOException { return body; }

    @Override
    public void abort() {
      synchronized (CacheEntryRequest.this) {
        if (done) { return; }
        done = true;
      }
      IoUtils.closeQuietly(this.cacheOut);
      try {
        cacheEditor.abort();
      } catch (final IOException ignored) {
        Log.w(TAG, "Cannot abort editor", ignored);
      }
      if (listener != null) { listener.onCacheEntryWriteAbort(this); }
    }

  }

  /**
   * Cache request implementation for Eclair and Froyo.
   * @author Roman Mazur (Stanfy - http://stanfy.com)
   */
  @TargetApi(VERSION_CODES.FROYO)
  protected class OldApiCacheEntryRequest extends CacheEntryRequest {

    /**
     * @param cacheOut cache entry data output stream
     * @param cacheEditor cache entry editor
     * @param listener entry listener
     */
    public OldApiCacheEntryRequest(final OutputStream cacheOut, final Editor cacheEditor, final CacheEntryListener listener) {
      super(cacheOut, cacheEditor, listener);
    }

    @Override
    public void abort() {
      // old implementations call abort when body stream should have been close
      IoUtils.closeQuietly(body);
    }

  }

  /** Cache entry listener. */
  public interface CacheEntryListener {
    void onCacheEntryWriteSuccess(CacheEntryRequest request);
    void onCacheEntryWriteAbort(CacheEntryRequest request);
  }

}


package com.stanfy.enroscar.rest.request.net;

import android.content.Context;
import android.os.Build;
import android.util.Log;

import com.stanfy.enroscar.beans.BeansManager;
import com.stanfy.enroscar.io.BuffersPool;
import com.stanfy.enroscar.io.IoUtils;
import com.stanfy.enroscar.net.UrlConnectionWrapper;
import com.stanfy.enroscar.net.operation.RequestDescription;
import com.stanfy.enroscar.rest.request.binary.BinaryData;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.URLConnection;
import java.util.ArrayList;

/**
 * Make a POST request with a payload.
 * @author Roman Mazur (Stanfy - http://stanfy.com)
 */
public class PayloadPostConverter extends PostConverter {

  /** Converter factory. */
  public static final ConverterFactory FACTORY = new ConverterFactory() {
    @Override
    public BaseRequestDescriptionConverter createConverter(final RequestDescription requestDescription, final Context context) {
      return new PayloadPostConverter(requestDescription, context);
    }
  };

  public PayloadPostConverter(final RequestDescription requestDescription, final Context context) {
    super(requestDescription, context, "text/plain");
  }

  @Override
  public URLConnection prepareConnectionInstance() throws IOException {
    URLConnection connection = super.prepareConnectionInstance();
    final int chunkSize = 8192;
    asHttp(connection).setChunkedStreamingMode(chunkSize);
    return connection;
  }

  @Override
  public void sendRequest(final URLConnection connection) throws IOException {
    ArrayList<BinaryData<?>> binaryData = getRequestDescription().getBinaryData();
    if (binaryData != null) {
      final BuffersPool buffersPool = BeansManager.get(getContext()).getContainer().getBean(BuffersPool.class);
      final OutputStream out = buffersPool.bufferize(connection.getOutputStream());

      try {
        int size = binaryData.size();
        //noinspection ForLoopReplaceableByForEach
        for (int i = 0; i < size; i++) {
          binaryData.get(i).writeContentTo(getContext(), out);
        }
        doSendWorkarounds(UrlConnectionWrapper.unwrap(connection));
      } finally {
        IoUtils.closeQuietly(out);
      }
    }
  }

  /**
   * Make workarounds for POST requests via {@link URLConnection} on older Android versions.
   * Override this method in order to disable them.
   * @param connection connection object instance
   */
  protected void doSendWorkarounds(final URLConnection connection) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD && connection instanceof HttpURLConnection) {
      try {
        Field resHeaderField = connection.getClass().getDeclaredField("resHeader");
        resHeaderField.setAccessible(true);
        resHeaderField.set(connection, null);
      } catch (Exception e) {
        Log.w("Workaround", "Failed to make a wrokaround for Android 2.2", e);
      }
    }
  }

}


package com.stanfy.enroscar.io;

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * Source is based on BufferedOutputStream.
 * @see BuffersPool
 * @author Roman Mazur - Stanfy (http://www.stanfy.com)
 */
class PoolableBufferedOutputStream extends FilterOutputStream {

  /** Buffers pool. */
  private final BuffersPool pool;

  /**
   * The buffer containing the bytes to be written to the target stream.
   */
  private byte[] buf;

  /**
   * The total number of bytes inside the byte array {@code buf}.
   */
  private int count;

  /** Call stack. */
  private Throwable stack;

  /**
   * Constructs a new {@code BufferedOutputStream}, providing {@code out} with a buffer
   * of 8192 bytes.
   *
   * @param out the {@code OutputStream} the buffer writes to.
   * @param buffersPool buffers pool instance
   */
  public PoolableBufferedOutputStream(final OutputStream out, final BuffersPool buffersPool) {
    this(out, IoUtils.BUFFER_SIZE_8K, buffersPool);
  }

  /**
   * Constructs a new {@code BufferedOutputStream}, providing {@code out} with {@code size} bytes
   * of buffer.
   *
   * @param out the {@code OutputStream} the buffer writes to.
   * @param size the size of buffer in bytes.
   * @param buffersPool buffers pool instance
   * @throws IllegalArgumentException if {@code size <= 0}.
   */
  public PoolableBufferedOutputStream(final OutputStream out, final int size, final BuffersPool buffersPool) {
    super(out);
    if (size <= 0) {
      throw new IllegalArgumentException("size <= 0");
    }
    this.pool = buffersPool;
    buf = buffersPool.get(size);
    if (buf == null) {
      buf = new byte[size];
    }
    this.stack = new Throwable();
  }

  /**
   * Flushes this stream to ensure all pending data is written out to the
   * target stream. In addition, the target stream is flushed.
   *
   * @throws IOException
   *             if an error occurs attempting to flush this stream.
   */
  @Override
  public synchronized void flush() throws IOException {
    checkNotClosed();
    flushInternal();
    out.flush();
  }

  private void checkNotClosed() throws IOException {
    if (buf == null) {
      throw new IOException("BufferedOutputStream is closed");
    }
  }

  /**
   * Writes {@code count} bytes from the byte array {@code buffer} starting at
   * {@code offset} to this stream. If there is room in the buffer to hold the
   * bytes, they are copied in. If not, the buffered bytes plus the bytes in
   * {@code buffer} are written to the target stream, the target is flushed,
   * and the buffer is cleared.
   *
   * @param buffer
   *            the buffer to be written.
   * @param offset
   *            the start position in {@code buffer} from where to get bytes.
   * @param length
   *            the number of bytes from {@code buffer} to write to this
   *            stream.
   * @throws IndexOutOfBoundsException
   *             if {@code offset < 0} or {@code length < 0}, or if
   *             {@code offset + length} is greater than the size of
   *             {@code buffer}.
   * @throws IOException
   *             if an error occurs attempting to write to this stream.
   * @throws NullPointerException
   *             if {@code buffer} is {@code null}.
   * @throws ArrayIndexOutOfBoundsException
   *             If offset or count is outside of bounds.
   */
  @Override
  public synchronized void write(final byte[] buffer, final int offset, final int length) throws IOException {
    checkNotClosed();

    if (buffer == null) {
      throw new NullPointerException("buffer == null");
    }

    final byte[] internalBuffer = buf;
    if (length >= internalBuffer.length) {
      flushInternal();
      out.write(buffer, offset, length);
      return;
    }

    if ((offset | length) < 0 || offset > buffer.length || buffer.length - offset < length) {
      throw new ArrayIndexOutOfBoundsException("length=" + buffer.length + "; regionStart=" + offset
          + "; regionLength=" + length);
    }

    // flush the internal buffer first if we have not enough space left
    if (length > (internalBuffer.length - count)) {
      flushInternal();
    }

    System.arraycopy(buffer, offset, internalBuffer, count, length);
    count += length;
  }

  @Override
  public synchronized void close() throws IOException {
    if (buf == null) {
      return;
    }

    try {
      super.close();
    } finally {
      pool.release(buf);
      buf = null;
    }
  }

  /**
   * Writes one byte to this stream. Only the low order byte of the integer
   * {@code oneByte} is written. If there is room in the buffer, the byte is
   * copied into the buffer and the count incremented. Otherwise, the buffer
   * plus {@code oneByte} are written to the target stream, the target is
   * flushed, and the buffer is reset.
   *
   * @param oneByte
   *            the byte to be written.
   * @throws IOException
   *             if an error occurs attempting to write to this stream.
   */
  @Override
  public synchronized void write(final int oneByte) throws IOException {
    checkNotClosed();
    if (count == buf.length) {
      out.write(buf, 0, count);
      count = 0;
    }
    buf[count++] = (byte) oneByte;
  }

  /**
   * Flushes only internal buffer.
   */
  private void flushInternal() throws IOException {
    if (count > 0) {
      out.write(buf, 0, count);
      count = 0;
    }
  }

  @Override
  protected void finalize() throws Throwable {
    super.finalize();
    if (pool.strictMode && buf != null) {
      System.err.println("WARNING: " + this + " was not closed, buffer was not released");
      stack.printStackTrace();
    }
  }

}


package com.stanfy.enroscar.net.operation;

import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.support.v4.content.Loader;
import android.util.Log;

import com.stanfy.enroscar.beans.BeansManager;
import com.stanfy.enroscar.content.loader.ResponseData;
import com.stanfy.enroscar.rest.EntityTypeToken;
import com.stanfy.enroscar.rest.RemoteServerApiConfiguration;
import com.stanfy.enroscar.net.operation.executor.RequestExecutor;
import com.stanfy.enroscar.rest.Utils;
import com.stanfy.enroscar.rest.loader.RequestBuilderLoader;
import com.stanfy.enroscar.rest.request.binary.AssetFdBinaryData;
import com.stanfy.enroscar.rest.request.binary.BitmapBinaryData;
import com.stanfy.enroscar.rest.request.binary.ContentUriBinaryData;
import com.stanfy.enroscar.rest.request.binary.EmptyBinaryData;

import java.io.File;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

/**
 * Base class for request builders.
 * @param <MT> model type
 * @author Roman Mazur - Stanfy (http://www.stanfy.com)
 */
public abstract class BaseRequestBuilder<MT> implements RequestBuilder<MT> {

  /** Invalid request identifier. */
  public static final int INVALID_REQUEST_IDENTIFIER = -1;

  /** Logging tag. */
  private static final String TAG = "RequestBuilder";

  /** Date format. */
  private final SimpleDateFormat dateFormat = new SimpleDateFormat(getDateTimeFormat(), Locale.US);

  /** Configuration. */
  private final RemoteServerApiConfiguration config;
  /** Result object. */
  private final RequestDescription result;

  /** Context. */
  private final Context context;

  /** Class of the expected model. */
  private final EntityTypeToken expectedModelType;

  /** Performer. */
  private RequestExecutor executor;

  public BaseRequestBuilder(final Context context) {
    this.config = BeansManager.get(context).getContainer().getBean(RemoteServerApiConfiguration.BEAN_NAME, RemoteServerApiConfiguration.class);
    if (this.config == null) {
      throw new IllegalStateException("RemoteServerApiConfiguration bean is not added to the container");
    }

    this.context = context.getApplicationContext();
    this.result = config.createRequestDescription();

    result.simpleParameters = new ParametersGroup();
    result.simpleParameters.name = "stub";
    result.contentLanguage = Locale.getDefault().getLanguage();

    this.expectedModelType = EntityTypeToken.fromClassParameter(getClass());
    result.modelType = this.expectedModelType;
  }

  /**
   * Override this method in order to provide custom dates format for {@link #parseDate(String)} and {@link #formatDate(Date)}.
   * @return date format
   */
  protected String getDateTimeFormat() { return "yyyy-MM-dd HH:mm:ss Z"; }

  /**
   * @param d date instance
   * @return formatted string (see {@link #getDateTimeFormat()})
   */
  protected String formatDate(final Date d) { return d != null ? dateFormat.format(d) : null; }
  /**
   * @param d date string
   * @return date instance parsed with {@link #getDateTimeFormat()} format
   */
  protected Date parseDate(final String d) {
    if (d == null) { return null; }
    try {
      return dateFormat.parse(d);
    } catch (final ParseException e) {
      Log.e(TAG, "Cannot parse date " + d, e);
      return null;
    }
  }

  /**
   * @param type type token describing the data type for the request
   */
  protected void setModelType(final Type type) {
    result.modelType = EntityTypeToken.fromEntityType(type);
  }

  @Override
  public BaseRequestBuilder<MT> setExecutor(final RequestExecutor executor) {
    this.executor = executor;
    return this;
  }

  /**
   * @param url URL to set
   */
  protected void setTargetUrl(final String url) {
    result.url = url;
  }

  /**
   * @param operationType operation type
   * @see OperationType
   */
  protected void setRequestOperationType(final int operationType) {
    result.operationType = operationType;
  }

  /**
   * @param name cache manager bean name
   */
  protected void setRequestCacheName(final String name) {
    result.cacheName = name;
  }
  /**
   * @param name content handler name
   */
  protected void setRequestContentHandler(final String name) {
    result.contentHandler = name;
  }

  /**
   * Set network stats tag. Converts string tag to integer one.
   * @param tag string tag
   */
  protected void setConvertedTrafficStatsTag(final String tag) {
    result.statsTag = Utils.getTrafficStatsTag(tag);
    if (config.isDebugRest()) {
      Log.d(TAG, "TrafficStats tag <" + tag + ">=" + Integer.toHexString(result.statsTag));
    }
  }

  /**
   * Setup binary content from the local file. Parameter name will be equal to {@link RequestDescription#BINARY_NAME_DEFAULT}.
   * @param data content URI
   * @param contentType content MIME-type
   */
  protected void addBinaryContent(final Uri data, final String contentType) {
    addBinaryContent(null, data, contentType);
  }

  /**
   * Setup binary content with the local file.
   * @param name parameter name
   * @param data content URI
   * @param contentType content MIME-type
   */
  protected void addBinaryContent(final String name, final Uri data, final String contentType) {
    String contentName = RequestDescription.BINARY_NAME_DEFAULT;
    if (ContentResolver.SCHEME_FILE.equals(data.getScheme())) {
      try {
        contentName = new File(new URI(data.toString())).getName();
      } catch (final URISyntaxException e) {
        Log.e(TAG, "Bad file URI: " + data, e);
      }
    }
    addBinaryContent(name, contentName, data, contentType);
  }

  /**
   * Setup binary content with the local file.
   * @param name parameter name
   * @param contentName content name
   * @param data content URI
   * @param contentType content MIME-type
   */
  protected void addBinaryContent(final String name, final String contentName, final Uri data, final String contentType) {
    final ContentUriBinaryData bdata = new ContentUriBinaryData();
    bdata.setName(name);
    bdata.setContentUri(data, contentName);
    bdata.setContentType(contentType);
    result.addBinaryData(bdata);
  }

  /**
   * Setup binary content with the bitmap.
   * @param name parameter name
   * @param bitmap bitmap object
   * @param fileName file name
   */
  protected void addBitmap(final String name, final Bitmap bitmap, final String fileName) {
    final BitmapBinaryData bdata = new BitmapBinaryData();
    bdata.setName(name);
    bdata.setContentName(fileName);
    bdata.setBitmap(bitmap);
    result.addBinaryData(bdata);
  }

  /**
   * Setup binary content with the file descriptor.
   * @param name parameter name
   * @param fd file descriptor
   * @param contentType content MIME-type
   * @param fileName file name
   */
  protected void addFileDescriptor(final String name, final AssetFileDescriptor fd, final String contentType, final String fileName) {
    final AssetFdBinaryData bdata = new AssetFdBinaryData();
    bdata.setFileDescriptor(fileName, fd);
    bdata.setName(name);
    bdata.setContentType(contentType);
    result.addBinaryData(bdata);
  }

  /**
   * @param name name for empty binary type
   */
  protected void addEmptyBinary(final String name) {
    final EmptyBinaryData bdata = new EmptyBinaryData();
    bdata.setName(name);
    result.addBinaryData(bdata);
  }

  /**
   * @param name parameter name
   * @param value parameter value
   * @return added parameter instance
   */
  protected ParameterValue addSimpleParameter(final String name, final long value) {
    return addSimpleParameter(name, String.valueOf(value));
  }
  /**
   * @param name parameter name
   * @param value parameter value
   * @return added parameter instance
   */
  protected ParameterValue addSimpleParameter(final String name, final int value) {
    return addSimpleParameter(name, String.valueOf(value));
  }
  /**
   * @param name parameter name
   * @param value parameter value
   * @return added parameter instance
   */
  protected ParameterValue addSimpleParameter(final String name, final boolean value) {
    return addSimpleParameter(name, value ? "1" : "0");
  }
  /**
   * @param name parameter name
   * @param value parameter value
   * @return added parameter instance
   */
  protected ParameterValue addSimpleParameter(final String name, final String value) {
    return result.simpleParameters.addSimpleParameter(name, value);
  }

  /**
   * @param p parameter to add to the request description
   */
  protected void addParameter(final Parameter p) {
    result.simpleParameters.addParameter(p);
  }

  /**
   * Remove parameter with the specified name from request description.
   * @param name parameter name
   * @return removed parameter instance, null if no parameter was found
   */
  protected Parameter removeParameter(final String name) {
    if (name == null) { throw new IllegalArgumentException("Parameter name cannot be null"); }
    final Iterator<Parameter> iter = result.simpleParameters.getChildren().iterator();
    while (iter.hasNext()) {
      final Parameter p = iter.next();
      if (name.equals(p.name)) {
        iter.remove();
        return p;
      }
    }
    return null;
  }

  /**
   * Add header to request description.
   * @param name header name
   * @param value header value
   * @return this for chaining
   */
  protected BaseRequestBuilder<MT> addHeader(final String name, final String value) {
    result.addHeader(name, value);
    return this;
  }

  /**
   * Remove header.
   * @param name header name
   */
  protected void removeHeader(final String name) {
    result.removeHeader(name);
  }

  /**
   * @param contentAnalyzer bean name of {@link com.stanfy.enroscar.rest.response.ContentAnalyzer} instance
   */
  protected void defineContentAnalyzer(final String contentAnalyzer) {
    result.setContentAnalyzer(contentAnalyzer);
  }

  /** @return request description object */
  protected RequestDescription getResult() { return result; }

  /** @return the context */
  @Override
  public Context getContext() { return context; }

  /**
   * Clear the builder.
   */
  public void clear() {
    final RequestDescription result = this.result;
    result.simpleParameters.children.clear();
    result.clearBinaryData();
    result.contentType = null;
    result.clearHeaders();
  }

  public BaseRequestBuilder<?> setParallel(final boolean value) {
    result.parallelMode = value;
    return this;
  }

  public BaseRequestBuilder<?> setTaskQueueName(final String taskQueue) {
    result.parallelMode = false;
    result.taskQueueName = taskQueue;
    return this;
  }

  @Override
  public EntityTypeToken getExpectedModelType() { return expectedModelType; }

  @Override
  public void execute() {
    if (result.url == null) {
      throw new IllegalStateException("URL is not specified!");
    }
    if (result.modelType == null) {
      throw new IllegalStateException("Model is not specified!");
    }

    if (result.contentHandler == null) {
      result.contentHandler = config.getDefaultContentHandlerName();
    }
    if (result.contentHandler == null) {
      throw new IllegalStateException("Content handler is not specified");
    }

    if (result.cacheName == null) {
      result.cacheName = config.getDefaultCacheBeanName();
    }

    result.setCanceled(false);

    if (executor != null) {
      executor.performRequest(result);
    } else {
      Log.w(TAG, "Don't know how to perform operation " + result.getUrl());
    }
  }

  /**
   * Create an appropriate loader instance.
   * Basic usage:
   * <pre>
   * public Loader onCreateLoader(int id, Bundle args) {
   *   return new RequestBuilder(this)
   *     .addParam("aaa", "bbb")
   *     .getLoader();
   * }
   * </pre>
   * @return loader instance that uses this request builder
   */
  @Override
  public Loader<ResponseData<MT>> getLoader() {
    return new RequestBuilderLoader<MT>(this);
  }

  /**
   * @param <T> list element type
   * @param <LT> list type
   * @return list request builder wrapper instance
   */
  protected <T, LT extends List<T>> ListRequestBuilderWrapper<LT, T> createLoadMoreListWrapper() {
    return new ListRequestBuilderWrapper<LT, T>(this) { };
  }

  public <T, LT extends List<T>> ListRequestBuilderWrapper<LT, T> asLoadMoreList(final String offset, final String limit) {
    final ListRequestBuilderWrapper<LT, T> wrapper = createLoadMoreListWrapper();
    if (offset != null) {
      wrapper.setOffsetParamName(offset);
    }
    if (limit != null) {
      wrapper.setLimitParamName(limit);
    }
    return wrapper;
  }
  public <T, LT extends List<T>> ListRequestBuilderWrapper<LT, T> asLoadMoreList() {
    return asLoadMoreList(ListRequestBuilderWrapper.PARAM_OFFSET, ListRequestBuilderWrapper.PARAM_LIMIT);
  }

}


package com.stanfy.audio;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

import com.stanfy.DebugFlags;

/**
 * Base class for audio streaming service controllers. Use to implement GUI of audio players.
 * @author Roman Mazur (Stanfy - http://www.stanfy.com)
 */
public abstract class StreamingPlaybackController {

  /** Logging tag. */
  protected static final String TAG = "StreamingController";
  /** Debug flag. */
  private static final boolean DEBUG = DebugFlags.DEBUG_GUI;

  /** Context. */
  private final Context context;

  /** Streaming playback. */
  private StreamingPlayback streamingPlayback;

  /** Flag about requested binding. */
  private boolean bindRequested = false;

  /** State flags. */
  private boolean playing, preparing, hasInfo, paused;

  /** Current audio info. */
  private final Bundle currentAudioInfo = new Bundle();

  /** Streaming playback listener. */
  private final StreamingPlaybackListener streamingPlaybackListener = new StreamingPlaybackListener.Stub() {
    @Override
    public void onVolumeChanged(final int volume) throws RemoteException { StreamingPlaybackController.this.onVolumeChanged(volume); }
    @Override
    public void onError() throws RemoteException { StreamingPlaybackController.this.onError(); }
    @Override
    public void onPreparing() throws RemoteException {
      preparing = true;
      StreamingPlaybackController.this.onPreparingChanged();
    }
    @Override
    public void onPrepared() throws RemoteException {
      preparing = false;
      StreamingPlaybackController.this.onPreparingChanged();
      playing = true;
      StreamingPlaybackController.this.onPlayingChanged();
    }
    @Override
    public void onAudioInfo(final String title, final String author, final String album) throws RemoteException {
      currentAudioInfo.putString(StreamingPlaybackService.EXTRA_TRACK_TITLE, title);
      currentAudioInfo.putString(StreamingPlaybackService.EXTRA_TRACK_ALBUM, album);
      currentAudioInfo.putString(StreamingPlaybackService.EXTRA_TRACK_AUTHOR, author);
      StreamingPlaybackController.this.onAudioInfoChanged();
    }
    @Override
    public void onCompleted() throws RemoteException {
      playing = false;
      onPlayingChanged();
      StreamingPlaybackController.this.onCompleted();
    }
  };

  /** Service connection. */
  private final ServiceConnection serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(final ComponentName name, final IBinder service) {
      streamingPlayback = StreamingPlayback.Stub.asInterface(service);
      try {
        streamingPlayback.registerListener(streamingPlaybackListener);
        final boolean oldPlaying = playing, oldPaused = paused;
        hasInfo = streamingPlayback.getCurrentUrl() != null;
        if (hasInfo) {
          final Bundle currentInfo = streamingPlayback.getCurrentTrackInfo();
          currentAudioInfo.putAll(currentInfo);
        }
        playing = streamingPlayback.isPlaying();
        paused = streamingPlayback.isPaused();
        if (oldPlaying != playing || oldPaused != paused) { onPlayingChanged(); }
        StreamingPlaybackController.this.onServiceConnected();
      } catch (final RemoteException e) {
        if (DEBUG) { Log.e(TAG, "Cannot call playback service", e); }
      }
    }
    @Override
    public void onServiceDisconnected(final ComponentName name) {
      dropListener();
      streamingPlayback = null;
      bindRequested = false;
    }
  };

  public StreamingPlaybackController(final Context context) {
    this.context = context;
  }

  /** @return the playing */
  public boolean isPlaying() { return playing; }
  /** @return the preparing */
  public boolean isPreparing() { return preparing; }
  /** @return the hasInfo */
  public boolean isHasInfo() { return hasInfo; }
  /** @return the paused */
  public boolean isPaused() { return paused; }

  /** @param playing the playing to set */
  protected void setPlaying(final boolean playing) { this.playing = playing; }
  /** @param paused the paused to set */
  protected void setPaused(final boolean paused) { this.paused = paused; }
  /** @param preparing the preparing to set */
  protected void setPreparing(final boolean preparing) { this.preparing = preparing; }

  public void start() {
    if (context instanceof Activity) {
      ((Activity)context).setVolumeControlStream(AudioManager.STREAM_MUSIC);
    }
    bind();
  }

  public void stop() {
    unbind();
    setPaused(false);
    setPlaying(false);
    onPlayingChanged();
  }

  /** @return the context */
  public final Context getContext() { return context; }
  /** @return the streamingPlayback */
  public final StreamingPlayback getStreamingPlayback() { return streamingPlayback; }

  /** @return the currentAudioInfo */
  public Bundle getCurrentAudioInfo() { return currentAudioInfo; }

  /** @return steaming service class */
  protected Class<? extends StreamingPlaybackService> getServiceClass() { return StreamingPlaybackService.class; }
  /** @return intent to bind to the steaming service */
  protected Intent createBindIntent() { return new Intent(context, getServiceClass()); }
  /** @return intent to call 'play' on the steaming service */
  protected Intent createPlayIntent() { return new Intent(context, getServiceClass()).setAction(StreamingPlaybackService.ACTION_PLAY); }
  /** @return intent to call 'stop' on the steaming service */
  protected Intent createStopIntent() { return new Intent(context, getServiceClass()).setAction(StreamingPlaybackService.ACTION_STOP); }

  /** Play the track. */
  protected void callPlay(final Uri uri, final int volume, final Bundle params) {
    playing = true;
    paused = false;
    context.startService(
        createPlayIntent().setData(uri)
        .putExtra(StreamingPlaybackService.EXTRA_VOLUME, volume)
        .putExtra(StreamingPlaybackService.EXTRA_TRACK_INFO, params)
    );
    bind();
    onPlayingChanged();
    preparing = true;
    onPreparingChanged();
  }

  /** Stop playback. */
  protected void callStop() {
    playing = false;
    paused = false;
    context.startService(createStopIntent());
    onPlayingChanged();
    preparing = false;
    onPreparingChanged();
  }

  protected void callResume() {
    if (streamingPlayback != null) {
      try {
        streamingPlayback.resume();
        playing = true;
        paused = false;
        onPlayingChanged();
      } catch (final RemoteException e) {
        Log.e(TAG, "Cannot call resume", e);
      }
    }
  }

  protected void callPause() {
    if (streamingPlayback != null) {
      try {
        streamingPlayback.pause();
        playing = false;
        paused = true;
        onPlayingChanged();
      } catch (final RemoteException e) {
        Log.e(TAG, "Cannot call pause", e);
      }
    }
  }

  /**
   * Bind to the streaming service.
   */
  protected final void bind() {
    if (bindRequested) { return; }
    final boolean result = context.bindService(createBindIntent(), serviceConnection, 0);
    if (DEBUG) { Log.v(TAG, "Bind to streaming service: " + result); }
    bindRequested = result;
    onBind(result);
  }

  /**
   * Unbind from the streaming service.
   */
  protected final void unbind() {
    if (streamingPlayback != null) { dropListener(); }
    try {
      context.unbindService(serviceConnection);
    } catch (final RuntimeException e) {
      if (DEBUG) { Log.w(TAG, "Cannot unbind radio", e); }
    }
    bindRequested = false;
  }

  /**
   * Drop streaming service listener.
   */
  protected final void dropListener() {
    if (DEBUG) { Log.v(TAG, "Drop listener"); }
    if (streamingPlayback != null) {
      try {
        streamingPlayback.removeListener();
      } catch (final RemoteException e) {
        Log.e(TAG, "Cannot remove listener", e);
      }
      bindRequested = false;
    }
  }

  protected void onCompleted() { /* nothing */ }

  protected void onPlayingChanged() { /* nothing */ }

  protected void onError() { /* nothing */ }

  protected void onAudioInfoChanged() { /* nothing */ }

  protected void onVolumeChanged(final int value) { /* nothing */ }

  protected void onPreparingChanged() { /* nothing */ }

  protected void onServiceConnected() { /* nothing */ }

  protected void onBind(final boolean result) { /* nothing */ }

}