// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.petite;

import jodd.cache.TypeCache;
import jodd.introspector.ClassDescriptor;
import jodd.introspector.ClassIntrospector;
import jodd.introspector.CtorDescriptor;
import jodd.introspector.MethodDescriptor;
import jodd.introspector.PropertyDescriptor;
import jodd.log.Logger;
import jodd.log.LoggerFactory;
import jodd.petite.def.BeanReferences;
import jodd.petite.def.CtorInjectionPoint;
import jodd.petite.def.DestroyMethodPoint;
import jodd.petite.def.InitMethodPoint;
import jodd.petite.def.MethodInjectionPoint;
import jodd.petite.def.PropertyInjectionPoint;
import jodd.petite.def.ProviderDefinition;
import jodd.petite.def.SetInjectionPoint;
import jodd.petite.meta.InitMethodInvocationStrategy;
import jodd.petite.resolver.ReferencesResolver;
import jodd.petite.scope.Scope;
import jodd.petite.scope.SingletonScope;
import jodd.props.Props;
import jodd.util.ClassUtil;
import jodd.util.StringPool;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

/**
 * Base layer of {@link PetiteContainer Petite Container}.
 * Holds beans and scopes definitions.
 */
public abstract class PetiteBeans {

	private static final Logger log = LoggerFactory.getLogger(PetiteBeans.class);

	/**
	 * Map of all beans definitions.
	 */
	protected final Map<String, BeanDefinition> beans = new HashMap<>();

	/**
	 * Map of alternative beans names.
	 */
	protected final Map<String, BeanDefinition> beansAlt = new HashMap<>();

	/**
	 * Map of all bean scopes.
	 */
	protected final TypeCache<Scope> scopes = TypeCache.createDefault();

	/**
	 * Map of all providers.
	 */
	protected final Map<String, ProviderDefinition> providers = new HashMap<>();

	/**
	 * Map of all bean collections.
	 */
	protected final TypeCache<String[]> beanCollections = TypeCache.createDefault();

	/**
	 * Cache used for storing the internals about the external types that are not
	 * registered withing the container.
	 */
	protected TypeCache<BeanDefinition> externalsCache = TypeCache.<BeanDefinition>create().noCache().get();

	/**
	 * Sets the type of cache used for storing the configurations for external types that
	 * are not part of the container. This affects usages of the methods
	 * like {@link PetiteContainer#wire(Object)} and {@link PetiteContainer#invokeMethod(Object, Method)}.
	 */
	public void setExternalsCache(final TypeCache<BeanDefinition> typeCacheImplementation) {
		this.externalsCache = typeCacheImplementation;
	}

	/**
	 * {@link PetiteConfig Petite configuration}.
	 */
	protected final PetiteConfig petiteConfig;

	/**
	 * {@link ReferencesResolver bean reference resolver}.
	 */
	protected final ReferencesResolver referencesResolver;

	/**
	 * {@link PetiteResolvers Petite resolvers}.
	 */
	protected final PetiteResolvers petiteResolvers;

	/**
	 * {@link ParamManager Parameters manager}.
	 */
	protected final ParamManager paramManager;

	protected final AnnotationResolver annotationResolver;

	protected PetiteBeans(final PetiteConfig petiteConfig) {
		this.petiteConfig = petiteConfig;
		this.referencesResolver = new ReferencesResolver(petiteConfig);
		this.petiteResolvers = new PetiteResolvers(referencesResolver);
		this.paramManager = new ParamManager();
		this.annotationResolver = new AnnotationResolver();
	}

	/**
	 * Returns parameter manager.
	 */
	public ParamManager paramManager() {
		return paramManager;
	}

	/**
	 * Returns {@link PetiteConfig Petite configuration}.
	 * All changes on config should be done before
	 * beans registration process starts.
	 */
	public PetiteConfig config() {
		return petiteConfig;
	}

	// ---------------------------------------------------------------- scopes

	/**
	 * Resolves and registers scope from a scope type.
	 */
	@SuppressWarnings("unchecked")
	public <S extends Scope> S resolveScope(final Class<S> scopeType) {
		S scope = (S) scopes.get(scopeType);
		if (scope == null) {

			try {
				scope = newInternalInstance(scopeType, (PetiteContainer) this);
			} catch (Exception ex) {
				throw new PetiteException("Invalid Petite scope: " + scopeType.getName(), ex);
			}

			registerScope(scopeType, scope);
			scopes.put(scopeType, scope);
		}
		return scope;
	}

	/**
	 * Creates new instance of given type. In the first try, it tries to use
	 * constructor with a {@link PetiteContainer}. If that files, uses default
	 * constructor to builds an instance.
	 */
	private <T> T newInternalInstance(final Class<T> type, final PetiteContainer petiteContainer) throws Exception {
		T t = null;

		// first try ctor(PetiteContainer)
		try {
			Constructor<T> ctor = type.getConstructor(PetiteContainer.class);
			t = ctor.newInstance(petiteContainer);
		} catch (NoSuchMethodException nsmex) {
			// ignore
		}

		// if first try failed, try default ctor
		if (t == null) {
			return ClassUtil.newInstance(type);
		}

		return t;
	}

	/**
	 * Registers new scope. It is not necessary to manually register scopes,
	 * since they become registered on first scope resolving.
	 * However, it is possible to pre-register some scopes, or to replace one scope
	 * type with another. Replacing may be important for testing purposes when
	 * using container-depended scopes.
	 */
	public void registerScope(final Class<? extends Scope> scopeType, final Scope scope) {
		scopes.put(scopeType, scope);
	}

	// ---------------------------------------------------------------- lookup beans

	/**
	 * Lookups for {@link BeanDefinition bean definition}.
	 * Returns null if bean name doesn't exist.
	 */
	public BeanDefinition lookupBeanDefinition(final String name) {
		BeanDefinition beanDefinition = beans.get(name);

		// try alt bean names
		if (beanDefinition == null) {
			if (petiteConfig.isUseAltBeanNames()) {
				beanDefinition = beansAlt.get(name);
			}
		}

		return beanDefinition;
	}

	/**
	 * Lookups for first founded {@link BeanDefinition bean definition}.
	 * Returns null if none of the beans is found.
	 */
	protected BeanDefinition lookupBeanDefinitions(final BeanReferences beanReferences) {
		final int total = beanReferences.size();
		for (int i = 0; i < total; i++) {
			final String name = beanReferences.name(i);

			BeanDefinition beanDefinition = lookupBeanDefinition(name);

			if (beanDefinition != null) {
				return beanDefinition;
			}
		}
		return null;
	}

	/**
	 * Lookups for existing {@link jodd.petite.BeanDefinition bean definition}.
	 * Throws exception if bean is not found.
	 */
	protected BeanDefinition lookupExistingBeanDefinition(final String name) {
		BeanDefinition beanDefinition = lookupBeanDefinition(name);
		if (beanDefinition == null) {
			throw new PetiteException("Bean not found: " + name);
		}
		return beanDefinition;
	}

	/**
	 * Returns true if bean name is registered.
	 */
	public boolean isBeanNameRegistered(final String name) {
		return lookupBeanDefinition(name) != null;
	}

	/**
	 * Resolves bean's name from bean annotation or type name. May be used for resolving bean name
	 * of base type during registration of bean subclass.
	 */
	public String resolveBeanName(final Class type) {
		return annotationResolver.resolveBeanName(type, petiteConfig.getUseFullTypeNames());
	}

	// ---------------------------------------------------------------- register beans

	/**
	 * Creates {@link jodd.petite.BeanDefinition} on
	 * {@link #registerPetiteBean(Class, String, Class, WiringMode, boolean, Consumer) bean registration}.
	 * This is a hook for modifying the bean data, like passing proxifed class etc.
	 * By default returns new instance of {@link jodd.petite.BeanDefinition}.
	 */
	protected <T> BeanDefinition createBeanDefinitionForRegistration(
			final String name,
			final Class<T> type,
			final Scope scope,
			final WiringMode wiringMode,
			final Consumer<T> consumer) {

		return new BeanDefinition<>(name, type, scope, wiringMode, consumer);
	}

	/**
	 * Creates {@link jodd.petite.BeanDefinition} for all external beans.
	 */
	protected <T> BeanDefinition createBeandDefinitionForExternalBeans(
			final Class<T> type,
			final WiringMode wiringMode) {

		final String name = resolveBeanName(type);
		return new BeanDefinition<>(name, type, null, wiringMode, null);
	}

	/**
	 * Registers a bean using provided class that is annotated.
	 */
	public BeanDefinition registerPetiteBean(final Class type) {
		return registerPetiteBean(type, null, null, null, false, null);
	}
	/**
	 * Registers a bean using provided class that is annotated.
	 */
	public <T> BeanDefinition<T> registerPetiteBean(final Class<T> type, final Consumer<T> consumer) {
		return registerPetiteBean(type, null, null, null, false, consumer);
	}

	/**
	 * Registers or defines a bean.
	 *
	 * @param type bean type, must be specified
	 * @param name bean name, if null it will be resolved from the class (name or annotation)
	 * @param scopeType bean scope, if null it will be resolved from the class (annotation or default one)
	 * @param wiringMode wiring mode, if null it will be resolved from the class (annotation or default one)
	 * @param define when set to true bean will be defined - all injection points will be set to none
	 */
	public <T> BeanDefinition<T> registerPetiteBean(
		final Class<T> type, String name,
		Class<? extends Scope> scopeType,
		WiringMode wiringMode,
		final boolean define,
		final Consumer<T> consumer
	) {

		if (name == null) {
			name = resolveBeanName(type);
		}
		if (wiringMode == null) {
			wiringMode = annotationResolver.resolveBeanWiringMode(type);
		}
		if (wiringMode == WiringMode.DEFAULT) {
			wiringMode = petiteConfig.getDefaultWiringMode();
		}
		if (scopeType == null) {
			scopeType = annotationResolver.resolveBeanScopeType(type);
		}
		if (scopeType == null) {
			scopeType = SingletonScope.class;
		}

		// remove existing bean
		BeanDefinition existing = removeBean(name);
		if (existing != null) {
			if (petiteConfig.getDetectDuplicatedBeanNames()) {
				throw new PetiteException(
						"Duplicated bean name detected while registering class '" + type.getName() + "'. Petite bean class '" +
						existing.type.getName() + "' is already registered with the name: " + name);
			}
		}

		// check if type is valid
		if (type.isInterface()) {
			throw new PetiteException("PetiteBean can not be an interface: " + type.getName());
		}

		// registration
		if (log.isDebugEnabled()) {
			log.info("Petite bean: [" + name +
					"] --> " + type.getName() +
					" @ " + scopeType.getSimpleName() +
					":" + wiringMode.toString());
		}

		// register
		Scope scope = resolveScope(scopeType);
		BeanDefinition<T> beanDefinition = createBeanDefinitionForRegistration(name, type, scope, wiringMode, consumer);

		registerBean(name, beanDefinition);

		// providers
		ProviderDefinition[] providerDefinitions = petiteResolvers.resolveProviderDefinitions(type, name);

		if (providerDefinitions != null) {
			for (ProviderDefinition providerDefinition : providerDefinitions) {
				providers.put(providerDefinition.name, providerDefinition);
			}
		}

		// define
		if (define) {
			beanDefinition.ctor = petiteResolvers.resolveCtorInjectionPoint(beanDefinition.type());
			beanDefinition.properties = PropertyInjectionPoint.EMPTY;
			beanDefinition.methods = MethodInjectionPoint.EMPTY;
			beanDefinition.initMethods = InitMethodPoint.EMPTY;
			beanDefinition.destroyMethods = DestroyMethodPoint.EMPTY;
		}

		// return
		return beanDefinition;
	}

	/**
	 * Registers bean definition by putting it in the beans map. If bean does
	 * not have petite name explicitly defined, alternative bean names
	 * will be registered.
	 */
	protected void registerBean(final String name, final BeanDefinition beanDefinition) {
		beans.put(name, beanDefinition);

		if (!petiteConfig.isUseAltBeanNames()) {
			return;
		}

		Class type = beanDefinition.type();

		if (annotationResolver.beanHasAnnotationName(type)) {
			return;
		}

		Class[] interfaces = ClassUtil.resolveAllInterfaces(type);

		for (Class anInterface : interfaces) {
			String altName = annotationResolver.resolveBeanName(anInterface, petiteConfig.getUseFullTypeNames());

			if (name.equals(altName)) {
				continue;
			}

			if (beans.containsKey(altName)) {
				continue;
			}

			if (beansAlt.containsKey(altName)) {
				BeanDefinition existing = beansAlt.get(altName);

				if (existing != null) {
					beansAlt.put(altName, null);		// store null as value to mark that alt name is duplicate
				}
			}
			else {
				beansAlt.put(altName, beanDefinition);
			}
		}
	}

	/**
	 * Removes all petite beans of provided type. Bean name is not resolved from a type!
	 * Instead, all beans are iterated and only beans with equal types are removed.
	 * @see #removeBean(String)
	 */
	public void removeBean(final Class type) {
		// collect bean names
		Set<String> beanNames = new HashSet<>();

		for (BeanDefinition def : beans.values()) {
			if (def.type.equals(type)) {
				beanNames.add(def.name);
			}
		}

		// remove collected bean names
		for (String beanName : beanNames) {
			removeBean(beanName);
		}
	}

	/**
	 * Removes bean and returns definition of removed bean.
	 * All resolvers references are deleted, too.
	 * Returns bean definition of removed bean or null.
	 */
	public BeanDefinition removeBean(final String name) {
		BeanDefinition bd = beans.remove(name);
		if (bd == null) {
			return null;
		}
		bd.scopeRemove();
		return bd;
	}

	// ---------------------------------------------------------------- bean collections

	/**
	 * Resolves bean names for give type.
	 */
	protected String[] resolveBeanNamesForType(final Class type) {
		String[] beanNames = beanCollections.get(type);
		if (beanNames != null) {
			return beanNames;
		}

		ArrayList<String> list = new ArrayList<>();

		for (Map.Entry<String, BeanDefinition> entry : beans.entrySet()) {
			BeanDefinition beanDefinition = entry.getValue();

			if (ClassUtil.isTypeOf(beanDefinition.type, type)) {
				String beanName = entry.getKey();
				list.add(beanName);
			}
		}

		if (list.isEmpty()) {
			beanNames = StringPool.EMPTY_ARRAY;
		} else {
			beanNames = list.toArray(new String[0]);
		}

		beanCollections.put(type, beanNames);
		return beanNames;
	}

	// ---------------------------------------------------------------- injection points

	/**
	 * Registers constructor injection point.
	 *
	 * @param beanName bean name
	 * @param paramTypes constructor parameter types, may be null
	 * @param references references for arguments
	 */
	public void registerPetiteCtorInjectionPoint(final String beanName, final Class[] paramTypes, final String[] references) {
		BeanDefinition beanDefinition = lookupExistingBeanDefinition(beanName);

		ClassDescriptor cd = ClassIntrospector.get().lookup(beanDefinition.type);
		Constructor constructor = null;

		if (paramTypes == null) {
			CtorDescriptor[] ctors = cd.getAllCtorDescriptors();
			if (ctors != null && ctors.length > 0) {
				if (ctors.length > 1) {
					throw new PetiteException(ctors.length + " suitable constructor found as injection point for: " + beanDefinition.type.getName());
				}
				constructor = ctors[0].getConstructor();
			}
		} else {
			CtorDescriptor ctorDescriptor = cd.getCtorDescriptor(paramTypes, true);

			if (ctorDescriptor != null) {
				constructor = ctorDescriptor.getConstructor();
			}
		}

		if (constructor == null) {
			throw new PetiteException("Constructor not found: " + beanDefinition.type.getName());
		}

		BeanReferences[] ref = referencesResolver.resolveReferenceFromValues(constructor, references);

		beanDefinition.ctor = new CtorInjectionPoint(constructor, ref);
	}

	/**
	 * Registers property injection point.
	 *
	 * @param beanName bean name
	 * @param property property name
	 * @param reference explicit injection reference, may be null
	 */
	public void registerPetitePropertyInjectionPoint(final String beanName, final String property, final String reference) {
		BeanDefinition beanDefinition = lookupExistingBeanDefinition(beanName);

		ClassDescriptor cd = ClassIntrospector.get().lookup(beanDefinition.type);
		PropertyDescriptor propertyDescriptor = cd.getPropertyDescriptor(property, true);

		if (propertyDescriptor == null) {
			throw new PetiteException("Property not found: " + beanDefinition.type.getName() + '#' + property);
		}

		BeanReferences ref = referencesResolver.resolveReferenceFromValue(propertyDescriptor, reference);

		PropertyInjectionPoint pip = new PropertyInjectionPoint(propertyDescriptor, ref);

		beanDefinition.addPropertyInjectionPoint(pip);
	}

	/**
	 * Registers set injection point.
	 *
	 * @param beanName bean name
	 * @param property set property name
	 */
	public void registerPetiteSetInjectionPoint(final String beanName, final String property) {
		BeanDefinition beanDefinition = lookupExistingBeanDefinition(beanName);
		ClassDescriptor cd = ClassIntrospector.get().lookup(beanDefinition.type);

		PropertyDescriptor propertyDescriptor = cd.getPropertyDescriptor(property, true);

		if (propertyDescriptor == null) {
			throw new PetiteException("Property not found: " + beanDefinition.type.getName() + '#' + property);
		}

		SetInjectionPoint sip = new SetInjectionPoint(propertyDescriptor);

		beanDefinition.addSetInjectionPoint(sip);
	}

	/**
	 * Registers method injection point.
	 *
	 * @param beanName bean name
	 * @param methodName method name
	 * @param arguments method arguments, may be null
	 * @param references injection references
	 */
	public void registerPetiteMethodInjectionPoint(final String beanName, final String methodName, final Class[] arguments, final String[] references) {
		BeanDefinition beanDefinition = lookupExistingBeanDefinition(beanName);

		ClassDescriptor cd = ClassIntrospector.get().lookup(beanDefinition.type);

		Method method = null;
		if (arguments == null) {
			MethodDescriptor[] methods = cd.getAllMethodDescriptors(methodName);

			if (methods != null && methods.length > 0) {
				if (methods.length > 1) {
					throw new PetiteException(methods.length + " suitable methods found as injection points for: " + beanDefinition.type.getName() + '#' + methodName);
				}
				method = methods[0].getMethod();
			}
		} else {
			MethodDescriptor md = cd.getMethodDescriptor(methodName, arguments, true);
			if (md != null) {
				method = md.getMethod();
			}
		}
		if (method == null) {
			throw new PetiteException("Method not found: " + beanDefinition.type.getName() + '#' + methodName);
		}

		BeanReferences[] ref = referencesResolver.resolveReferenceFromValues(method, references);

		MethodInjectionPoint mip = new MethodInjectionPoint(method, ref);

		beanDefinition.addMethodInjectionPoint(mip);
	}

	/**
	 * Registers init method.
	 *
	 * @param beanName bean name
	 * @param invocationStrategy moment of invocation
	 * @param initMethodNames init method names
	 */
	public void registerPetiteInitMethods(final String beanName, final InitMethodInvocationStrategy invocationStrategy, String... initMethodNames) {
		BeanDefinition beanDefinition = lookupExistingBeanDefinition(beanName);

		ClassDescriptor cd = ClassIntrospector.get().lookup(beanDefinition.type);
		if (initMethodNames == null) {
			initMethodNames = StringPool.EMPTY_ARRAY;
		}

		int total = initMethodNames.length;
		InitMethodPoint[] initMethodPoints = new InitMethodPoint[total];

		int i;
		for (i = 0; i < initMethodNames.length; i++) {
			MethodDescriptor md = cd.getMethodDescriptor(initMethodNames[i], ClassUtil.EMPTY_CLASS_ARRAY, true);
			if (md == null) {
				throw new PetiteException("Init method not found: " + beanDefinition.type.getName() + '#' + initMethodNames[i]);
			}
			initMethodPoints[i] = new InitMethodPoint(md.getMethod(), i, invocationStrategy);
		}

		beanDefinition.addInitMethodPoints(initMethodPoints);
	}

	/**
	 * Registers destroy method.
	 *
	 * @param beanName bean name
	 * @param destroyMethodNames destroy method names
	 */
	public void registerPetiteDestroyMethods(final String beanName, String... destroyMethodNames) {
		BeanDefinition beanDefinition = lookupExistingBeanDefinition(beanName);

		ClassDescriptor cd = ClassIntrospector.get().lookup(beanDefinition.type);
		if (destroyMethodNames == null) {
			destroyMethodNames = StringPool.EMPTY_ARRAY;
		}

		int total = destroyMethodNames.length;
		DestroyMethodPoint[] destroyMethodPoints = new DestroyMethodPoint[total];

		int i;
		for (i = 0; i < destroyMethodNames.length; i++) {
			MethodDescriptor md = cd.getMethodDescriptor(destroyMethodNames[i], ClassUtil.EMPTY_CLASS_ARRAY, true);
			if (md == null) {
				throw new PetiteException("Destroy method not found: " + beanDefinition.type.getName() + '#' + destroyMethodNames[i]);
			}
			destroyMethodPoints[i] = new DestroyMethodPoint(md.getMethod());
		}

		beanDefinition.addDestroyMethodPoints(destroyMethodPoints);
	}

	// ---------------------------------------------------------------- providers

	/**
	 * Registers instance method provider.
	 *
	 * @param providerName provider name
	 * @param beanName bean name
	 * @param methodName instance method name
	 * @param arguments method argument types
	 */
	public void registerPetiteProvider(final String providerName, final String beanName, final String methodName, final Class[] arguments) {
		BeanDefinition beanDefinition = lookupBeanDefinition(beanName);

		if (beanDefinition == null) {
			throw new PetiteException("Bean not found: " + beanName);
		}

		Class beanType = beanDefinition.type;

		ClassDescriptor cd = ClassIntrospector.get().lookup(beanType);
		MethodDescriptor md = cd.getMethodDescriptor(methodName, arguments, true);

		if (md == null) {
			throw new PetiteException("Provider method not found: " + methodName);
		}

		ProviderDefinition providerDefinition = new ProviderDefinition(providerName, beanName, md.getMethod());

		providers.put(providerName, providerDefinition);
	}

	/**
	 * Registers static method provider.
	 *
	 * @param providerName provider name
	 * @param type class type
	 * @param staticMethodName static method name
	 * @param arguments method argument types
	 */
	public void registerPetiteProvider(final String providerName, final Class type, final String staticMethodName, final Class[] arguments) {
		ClassDescriptor cd = ClassIntrospector.get().lookup(type);
		MethodDescriptor md = cd.getMethodDescriptor(staticMethodName, arguments, true);

		if (md == null) {
			throw new PetiteException("Provider method not found: " + staticMethodName);
		}

		ProviderDefinition providerDefinition = new ProviderDefinition(providerName, md.getMethod());

		providers.put(providerName, providerDefinition);
	}

	// ---------------------------------------------------------------- statistics

	/**
	 * Returns total number of registered beans.
	 */
	public int beansCount() {
		return beans.size();
	}

	/**
	 * Returns total number of used scopes.
	 */
	public int scopesCount() {
		return scopes.size();
	}

	/**
	 * Returns set of all bean names. The returned set is a safe
	 * snapshot of all bean names.
	 */
	public Set<String> beanNames() {
		return new HashSet<>(beans.keySet());
	}

	/**
	 * Iterates all beans. Iteration occurs over the {@link #beanNames() snapshot of bean names}.
	 */
	public void forEachBean(final Consumer<BeanDefinition> beanDefinitionConsumer) {
		final Set<String> names = beanNames();
		for (String beanName : names) {
			BeanDefinition beanDefinition = lookupBeanDefinition(beanName);

			if (beanDefinition != null) {
				beanDefinitionConsumer.accept(beanDefinition);
			}
		}
	}

	/**
	 * Iterates all beans that are of given type.
	 */
	public void forEachBeanType(final Class type, final Consumer<String> beanNameConsumer) {
		forEachBean(bd -> {
			if (ClassUtil.isTypeOf(bd.type, type)) {
				beanNameConsumer.accept(bd.name);
			}
		});
	}

	// ---------------------------------------------------------------- params

	/**
	 * Defines new parameter. Parameters with same name will be replaced.
	 */
	public void defineParameter(final String name, final Object value) {
		paramManager.put(name, value);
	}

	/**
	 * Returns defined parameter.
	 */
	public Object getParameter(final String name) {
		return paramManager.get(name);
	}

	/**
	 * Defines many parameters at once.
	 */
	public void defineParameters(final Map<?, ?> properties) {
		for (Map.Entry<?, ?> entry : properties.entrySet()) {
			defineParameter(entry.getKey().toString(), entry.getValue());
		}
	}

	/**
	 * Defines many parameters at once from {@link jodd.props.Props}.
	 */
	public void defineParameters(final Props props) {
		Map<?, ?> map = new HashMap<>();
		props.extractProps(map);
		defineParameters(map);
	}

}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.util.cl;

import java.util.Map;
import java.util.WeakHashMap;

/**
 * This class provides classloader-local variables.
 *

 * It is designed to look very much like ThreadLocal.
 */
public class ClassLoaderLocal<T> {

	private final Map<ClassLoader, T> weakMap = new WeakHashMap<>();
	private T value;
	private boolean initialized;

	/**
	 * Returns the current classloader "initial value" for this classloader-local variable.
	 * This method will be invoked the first time it is accessed the get() method, unless
	 * the thread previously invoked the set(T) method, in which case the initialValue method
	 * will not be invoked for the classloader. Normally, this method is invoked at most once
	 * per classloader, but it may be invoked again in case of subsequent invocations of remove()
	 * followed by get().
	 *

	 * This implementation simply returns null; if the programmer desires classloader-local variables to
	 * have an initial value other than null, ClassLoaderLocal must be subclassed, and this method overridden.
	 * Typically, an anonymous inner class will be used.
	 */
	protected T initialValue() {
		return null;
	}

	/**
	 * Returns the value in the current classloader copy of this variable.
	 * If the variable has no value for the current classloader, it is first initialized to the value returned
	 * by an invocation of the initialValue() method.
	 */
	public synchronized T get() {
		ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();

		if (contextClassLoader != null) {
			T value = weakMap.get(contextClassLoader);

			if ((value == null) && !weakMap.containsKey(contextClassLoader)) {
				value = initialValue();
				weakMap.put(contextClassLoader, value);
			}

			return value;
		}

		if (!initialized) {
			value = initialValue();
			initialized = true;
		}

		return value;
	}

	/**
	 * Sets the current classloaders's copy of this variable to the specified value.
	 * Most subclasses will have no need to override this method, relying solely on the initialValue()
	 * method to set the values of classloader-locals.
	 */
	public synchronized void set(final T value) {

		ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();

		if (contextClassLoader != null) {
			weakMap.put(contextClassLoader, value);
			return;
		}

		this.value = value;
		this.initialized = true;
	}

	/**
	 * Removes the current classloader's value for this variable.
	 */
	public synchronized void remove() {
		ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
		weakMap.remove(contextClassLoader);
	}

}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.db.type;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.PreparedStatement;

public class FloatSqlType extends NullAwareSqlType<Float> {

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Float get(final ResultSet rs, final int index, final int dbSqlType) throws SQLException {
		return Float.valueOf(rs.getFloat(index));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void set(final PreparedStatement st, final int index, final Float value, final int dbSqlType) throws SQLException {
		st.setFloat(index, value.floatValue());
	}

}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.db.type;

import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class SqlDateSqlType extends SqlType<Date>{

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Date get(final ResultSet rs, final int index, final int dbSqlType) throws SQLException {
		return rs.getDate(index);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void set(final PreparedStatement st, final int index, final Date value, final int dbSqlType) throws SQLException {
		st.setDate(index, value);
	}

}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.madvoc.interceptor;

import jodd.madvoc.ActionRequest;
import jodd.util.StringUtil;

/**
 * Simple interceptor that measures time and prints out information about invoked actions.
 * User may inherit it and change the way message is printed.
 */
public class EchoInterceptor implements ActionInterceptor {

	protected String prefixIn = "-----> ";
	protected String prefixOut = "<----- ";

	public void setPrefixIn(final String prefixIn) {
		this.prefixIn = prefixIn;
	}

	public void setPrefixOut(final String prefixOut) {
		this.prefixOut = prefixOut;
	}

	// ---------------------------------------------------------------- code

	/**
	 * Measure action invocation time.
	 */
	@Override
	public Object intercept(final ActionRequest actionRequest) throws Exception {
		printBefore(actionRequest);
		long startTime = System.currentTimeMillis();
		Object result = null;
		try {
			result = actionRequest.invoke();
		} catch (Exception ex) {
			result = "<exception>";
			throw ex;
		} catch (Throwable th) {
			result = "<throwable>";
			throw new Exception(th);
		} finally {
			long executionTime = System.currentTimeMillis() - startTime;
			printAfter(actionRequest, executionTime, result);
		}
		return result;
	}

	/**
	 * Prints out the message. User can override this method and modify the way
	 * the message is printed.
	 */
	protected void printBefore(final ActionRequest request) {
		StringBuilder message = new StringBuilder(prefixIn);

		message.append(request.getActionPath()).append("   [").append(request.getActionRuntime().createActionString()).append(']');
		out(message.toString());
	}

	/**
	 * Prints out the message. User can override this method and modify the way
	 * the message is printed.
	 */
	protected void printAfter(final ActionRequest request, final long executionTime, final Object result) {
		StringBuilder message = new StringBuilder(prefixOut);

		String resultString = StringUtil.toSafeString(result);
		if (resultString.length() > 70) {
			resultString = resultString.substring(0, 70);
			resultString += "...";
		}
		message.append(request.getActionPath()).append("  (")
				.append(resultString).append(") in ").append(executionTime)
				.append("ms.");
		out(message.toString());
	}

	/**
	 * Outputs info message. By default, it outputs it to console.
	 */
	protected void out(final String message) {
		System.out.println(message);
	}

}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.proxetta.asm;

import jodd.asm.AsmUtil;
import jodd.asm7.Label;
import jodd.asm7.MethodVisitor;
import jodd.asm7.Type;
import jodd.proxetta.MethodInfo;
import jodd.proxetta.ProxettaException;
import jodd.proxetta.ProxettaNames;
import jodd.proxetta.TypeInfo;
import jodd.system.SystemUtil;
import jodd.util.ClassUtil;
import jodd.util.StringBand;
import jodd.util.StringPool;

import java.lang.reflect.Method;

import static jodd.asm7.Opcodes.AASTORE;
import static jodd.asm7.Opcodes.ACONST_NULL;
import static jodd.asm7.Opcodes.ALOAD;
import static jodd.asm7.Opcodes.ANEWARRAY;
import static jodd.asm7.Opcodes.ARETURN;
import static jodd.asm7.Opcodes.ASTORE;
import static jodd.asm7.Opcodes.BASTORE;
import static jodd.asm7.Opcodes.BIPUSH;
import static jodd.asm7.Opcodes.CASTORE;
import static jodd.asm7.Opcodes.CHECKCAST;
import static jodd.asm7.Opcodes.DASTORE;
import static jodd.asm7.Opcodes.DCONST_0;
import static jodd.asm7.Opcodes.DLOAD;
import static jodd.asm7.Opcodes.DRETURN;
import static jodd.asm7.Opcodes.DSTORE;
import static jodd.asm7.Opcodes.DUP;
import static jodd.asm7.Opcodes.FASTORE;
import static jodd.asm7.Opcodes.FCONST_0;
import static jodd.asm7.Opcodes.FLOAD;
import static jodd.asm7.Opcodes.FRETURN;
import static jodd.asm7.Opcodes.FSTORE;
import static jodd.asm7.Opcodes.GETSTATIC;
import static jodd.asm7.Opcodes.IASTORE;
import static jodd.asm7.Opcodes.ICONST_0;
import static jodd.asm7.Opcodes.IFNONNULL;
import static jodd.asm7.Opcodes.ILOAD;
import static jodd.asm7.Opcodes.IRETURN;
import static jodd.asm7.Opcodes.ISTORE;
import static jodd.asm7.Opcodes.LASTORE;
import static jodd.asm7.Opcodes.LCONST_0;
import static jodd.asm7.Opcodes.LLOAD;
import static jodd.asm7.Opcodes.LRETURN;
import static jodd.asm7.Opcodes.LSTORE;
import static jodd.asm7.Opcodes.NEWARRAY;
import static jodd.asm7.Opcodes.POP;
import static jodd.asm7.Opcodes.RETURN;
import static jodd.asm7.Opcodes.SASTORE;
import static jodd.asm7.Opcodes.SIPUSH;
import static jodd.asm7.Opcodes.T_BOOLEAN;
import static jodd.asm7.Opcodes.T_BYTE;
import static jodd.asm7.Opcodes.T_CHAR;
import static jodd.asm7.Opcodes.T_DOUBLE;
import static jodd.asm7.Opcodes.T_FLOAT;
import static jodd.asm7.Opcodes.T_INT;
import static jodd.asm7.Opcodes.T_LONG;
import static jodd.asm7.Opcodes.T_SHORT;
import static jodd.util.StringPool.COLON;

/**
 * Various ASM utilities used by {@link jodd.proxetta.Proxetta}.
 * For more generic ASM tools, see {@link jodd.asm.AsmUtil}.
 */
public class ProxettaAsmUtil {

	public static final String INIT = "<init>";
	public static final String CLINIT = "<clinit>";
	public static final String DESC_VOID = "()V";

	// ---------------------------------------------------------------- versions

	/**
	 * Resolves Java version from current version.
	 */
	public static int resolveJavaVersion(final int version) {
		final int javaVersionNumber = SystemUtil.info().getJavaVersionNumber();
		final int platformVersion = javaVersionNumber - 8 + 52;

		return version > platformVersion ? version : platformVersion;
	}

	// ---------------------------------------------------------------- misc

	/**
	 * Pushes int value in an optimal way.
	 */
	public static void pushInt(final MethodVisitor mv, final int value) {
		if (value <= 5) {
			mv.visitInsn(ICONST_0 + value);
		} else if (value <= Byte.MAX_VALUE) {
			mv.visitIntInsn(BIPUSH, value);
		}  else {
			mv.visitIntInsn(SIPUSH, value);
		}
	}

	/**
	 * Changes method access to private and final.
	 */
	public static int makePrivateFinalAccess(final int access) {
		return (access & 0xFFFFFFF0) | AsmUtil.ACC_PRIVATE | AsmUtil.ACC_FINAL;
	}

	/**
	 * Validates argument index.
	 */
	public static void checkArgumentIndex(final MethodInfo methodInfo, final int argIndex) {
		if ((argIndex < 1) || (argIndex > methodInfo.getArgumentsCount())) {
			throw new ProxettaException("Invalid argument index: " + argIndex);
		}
	}

	/**
	 * Builds advice field name.
	 */
	public static String adviceFieldName(final String name, final int index) {
		return ProxettaNames.fieldPrefix + name + ProxettaNames.fieldDivider + index;
	}

	/**
	 * Builds advice method name.
	 */
	public static String adviceMethodName(final String name, final int index) {
		return ProxettaNames.methodPrefix + name + ProxettaNames.methodDivider + index;
	}

	// ---------------------------------------------------------------- load

	public static void loadMethodArgumentClass(final MethodVisitor mv, final MethodInfo methodInfo, final int index) {
		TypeInfo argument = methodInfo.getArgument(index);
		loadClass(mv, argument.getOpcode(), argument.getRawName());
	}

	public static void loadClass(final MethodVisitor mv, final int type, final String typeName) {
		switch (type) {
			case 'V':
				mv.visitFieldInsn(GETSTATIC, AsmUtil.SIGNATURE_JAVA_LANG_VOID, "TYPE", AsmUtil.L_SIGNATURE_JAVA_LANG_CLASS);
				break;
			case 'B':
				mv.visitFieldInsn(GETSTATIC, AsmUtil.SIGNATURE_JAVA_LANG_BYTE, "TYPE", AsmUtil.L_SIGNATURE_JAVA_LANG_CLASS);
				break;
			case 'C':
				mv.visitFieldInsn(GETSTATIC, AsmUtil.SIGNATURE_JAVA_LANG_CHARACTER, "TYPE", AsmUtil.L_SIGNATURE_JAVA_LANG_CLASS);
				break;
			case 'S':
				mv.visitFieldInsn(GETSTATIC, AsmUtil.SIGNATURE_JAVA_LANG_SHORT, "TYPE", AsmUtil.L_SIGNATURE_JAVA_LANG_CLASS);
				break;
			case 'I':
				mv.visitFieldInsn(GETSTATIC, AsmUtil.SIGNATURE_JAVA_LANG_INTEGER, "TYPE", AsmUtil.L_SIGNATURE_JAVA_LANG_CLASS);
				break;
			case 'Z':
				mv.visitFieldInsn(GETSTATIC, AsmUtil.SIGNATURE_JAVA_LANG_BOOLEAN, "TYPE", AsmUtil.L_SIGNATURE_JAVA_LANG_CLASS);
				break;
			case 'J':
				mv.visitFieldInsn(GETSTATIC, AsmUtil.SIGNATURE_JAVA_LANG_LONG, "TYPE", AsmUtil.L_SIGNATURE_JAVA_LANG_CLASS);
				break;
			case 'F':
				mv.visitFieldInsn(GETSTATIC, AsmUtil.SIGNATURE_JAVA_LANG_FLOAT, "TYPE", AsmUtil.L_SIGNATURE_JAVA_LANG_CLASS);
				break;
			case 'D':
				mv.visitFieldInsn(GETSTATIC, AsmUtil.SIGNATURE_JAVA_LANG_DOUBLE, "TYPE", AsmUtil.L_SIGNATURE_JAVA_LANG_CLASS);
				break;
			default:
				mv.visitLdcInsn(Type.getType(typeName));
				break;
		}

	}

	/**
	 * Loads all method arguments before INVOKESPECIAL call.
	 */
	public static void loadSpecialMethodArguments(final MethodVisitor mv, final MethodInfo methodInfo) {
		mv.visitVarInsn(ALOAD, 0);
		for (int i = 1; i <= methodInfo.getArgumentsCount(); i++) {
			loadMethodArgument(mv, methodInfo, i);
		}
	}

	/**
	 * Loads all method arguments before INVOKESTATIC call.
	 */
	public static void loadStaticMethodArguments(final MethodVisitor mv, final MethodInfo methodInfo) {
		for (int i = 0; i < methodInfo.getArgumentsCount(); i++) {
			loadMethodArgument(mv, methodInfo, i);
		}
	}

	/**
	 * Loads all method arguments before INVOKEVIRTUAL call.
	 */
	public static void loadVirtualMethodArguments(final MethodVisitor mv, final MethodInfo methodInfo) {
		for (int i = 1; i <= methodInfo.getArgumentsCount(); i++) {
			loadMethodArgument(mv, methodInfo, i);
		}
	}

	/**
	 * Loads one argument. Index is 1-based. No conversion occurs.
	 */
	public static void loadMethodArgument(final MethodVisitor mv, final MethodInfo methodInfo, final int index) {
		int offset = methodInfo.getArgumentOffset(index);
		int type = methodInfo.getArgument(index).getOpcode();
		switch (type) {
			case 'V':
				break;
			case 'B':
			case 'C':
			case 'S':
			case 'I':
			case 'Z':
				mv.visitVarInsn(ILOAD, offset);
				break;
			case 'J':
				mv.visitVarInsn(LLOAD, offset);
				break;
			case 'F':
				mv.visitVarInsn(FLOAD, offset);
				break;
			case 'D':
				mv.visitVarInsn(DLOAD, offset);
				break;
			default:
				mv.visitVarInsn(ALOAD, offset);
		}
	}

	public static void loadMethodArgumentAsObject(final MethodVisitor mv, final MethodInfo methodInfo, final int index) {
		int offset = methodInfo.getArgumentOffset(index);
		int type = methodInfo.getArgument(index).getOpcode();
		switch (type) {
			case 'V':
				break;
			case 'B':
				mv.visitVarInsn(ILOAD, offset);
				AsmUtil.valueOfByte(mv);
				break;
			case 'C':
				mv.visitVarInsn(ILOAD, offset);
				AsmUtil.valueOfCharacter(mv);
				break;
			case 'S':
				mv.visitVarInsn(ILOAD, offset);
				AsmUtil.valueOfShort(mv);
				break;
			case 'I':
				mv.visitVarInsn(ILOAD, offset);
				AsmUtil.valueOfInteger(mv);
				break;
			case 'Z':
				mv.visitVarInsn(ILOAD, offset);
				AsmUtil.valueOfBoolean(mv);
				break;
			case 'J':
				mv.visitVarInsn(LLOAD, offset);
				AsmUtil.valueOfLong(mv);
				break;
			case 'F':
				mv.visitVarInsn(FLOAD, offset);
				AsmUtil.valueOfFloat(mv);
				break;
			case 'D':
				mv.visitVarInsn(DLOAD, offset);
				AsmUtil.valueOfDouble(mv);
				break;
			default:
				mv.visitVarInsn(ALOAD, offset);
		}
	}

	// ---------------------------------------------------------------- store

	/**
	 * Stores one argument. Index is 1-based. No conversion occurs.
	 */
	public static void storeMethodArgument(final MethodVisitor mv, final MethodInfo methodInfo, final int index) {
		int offset = methodInfo.getArgumentOffset(index);
		int type = methodInfo.getArgument(index).getOpcode();
		switch (type) {
			case 'V':
				break;
			case 'B':
			case 'C':
			case 'S':
			case 'I':
			case 'Z':
				mv.visitVarInsn(ISTORE, offset); break;
			case 'J':
				mv.visitVarInsn(LSTORE, offset); break;
			case 'F':
				mv.visitVarInsn(FSTORE, offset); break;
			case 'D':
				mv.visitVarInsn(DSTORE, offset); break;
			default:
				mv.visitVarInsn(ASTORE, offset);
		}
	}

	/**
	 * Returns true if opcode is xSTORE.
	 */
	public static boolean isStoreOpcode(final int opcode) {
		return (opcode == ISTORE)
				|| (opcode == LSTORE)
				|| (opcode == FSTORE)
				|| (opcode == DSTORE)
				|| (opcode == ASTORE);
	}

	public static void storeMethodArgumentFromObject(final MethodVisitor mv, final MethodInfo methodInfo, final int index) {
		int type = methodInfo.getArgument(index).getOpcode();
		int offset = methodInfo.getArgumentOffset(index);
		storeValue(mv, offset, type);
	}

	public static void storeValue(final MethodVisitor mv, final int offset, final int type) {
		switch (type) {
			case 'V':
				break;
			case 'B':
				AsmUtil.byteValue(mv);
				mv.visitVarInsn(ISTORE, offset);
				break;
			case 'C':
				AsmUtil.charValue(mv);
				mv.visitVarInsn(ISTORE, offset);
				break;
			case 'S':
				AsmUtil.shortValue(mv);
				mv.visitVarInsn(ISTORE, offset);
				break;
			case 'I':
				AsmUtil.intValue(mv);
				mv.visitVarInsn(ISTORE, offset);
				break;
			case 'Z':
				AsmUtil.booleanValue(mv);
				mv.visitVarInsn(ISTORE, offset);
				break;
			case 'J':
				AsmUtil.longValue(mv);
				mv.visitVarInsn(LSTORE, offset);
				break;
			case 'F':
				AsmUtil.floatValue(mv);
				mv.visitVarInsn(FSTORE, offset);
				break;
			case 'D':
				AsmUtil.doubleValue(mv);
				mv.visitVarInsn(DSTORE, offset);
				break;
			default:
				mv.visitVarInsn(ASTORE, offset);
		}
	}

	// ---------------------------------------------------------------- return

	/**
	 * Visits return opcodes.
	 */
	public static void visitReturn(final MethodVisitor mv, final MethodInfo methodInfo, final boolean isLast) {
		switch (methodInfo.getReturnType().getOpcode()) {
			case 'V':
				if (isLast) {
					mv.visitInsn(POP);
				}
				mv.visitInsn(RETURN);
				break;

			case 'B':
				if (isLast) {
					mv.visitInsn(DUP);
					Label label = new Label();
					mv.visitJumpInsn(IFNONNULL, label);
						mv.visitInsn(POP);
						mv.visitInsn(ICONST_0);
						mv.visitInsn(IRETURN);
					mv.visitLabel(label);

					AsmUtil.byteValue(mv);
				}
				mv.visitInsn(IRETURN);
				break;

			case 'C':
				if (isLast) {
					mv.visitInsn(DUP);
					Label label = new Label();
					mv.visitJumpInsn(IFNONNULL, label);
						mv.visitInsn(POP);
						mv.visitInsn(ICONST_0);
						mv.visitInsn(IRETURN);
					mv.visitLabel(label);

					AsmUtil.charValue(mv);
				}
				mv.visitInsn(IRETURN);
				break;

			case 'S':
				if (isLast) {
					mv.visitInsn(DUP);
					Label label = new Label();
					mv.visitJumpInsn(IFNONNULL, label);
						mv.visitInsn(POP);
						mv.visitInsn(ICONST_0);
						mv.visitInsn(IRETURN);
					mv.visitLabel(label);

					AsmUtil.shortValue(mv);
				}
				mv.visitInsn(IRETURN);
				break;

			case 'I':
				if (isLast) {
					mv.visitInsn(DUP);
					Label label = new Label();
					mv.visitJumpInsn(IFNONNULL, label);
						mv.visitInsn(POP);
						mv.visitInsn(ICONST_0);
						mv.visitInsn(IRETURN);
					mv.visitLabel(label);

					AsmUtil.intValue(mv);
				}
				mv.visitInsn(IRETURN);
				break;

			case 'Z':
				if (isLast) {
					mv.visitInsn(DUP);
					Label label = new Label();
					mv.visitJumpInsn(IFNONNULL, label);
						mv.visitInsn(POP);
						mv.visitInsn(ICONST_0);
						mv.visitInsn(IRETURN);
					mv.visitLabel(label);

					AsmUtil.booleanValue(mv);
				}
				mv.visitInsn(IRETURN);
				break;

			case 'J':
				if (isLast) {
					mv.visitInsn(DUP);
					Label label = new Label();
					mv.visitJumpInsn(IFNONNULL, label);
						mv.visitInsn(POP);
						mv.visitInsn(LCONST_0);
						mv.visitInsn(LRETURN);
					mv.visitLabel(label);

					AsmUtil.longValue(mv);
				}
				mv.visitInsn(LRETURN);
				break;

			case 'F':
				if (isLast) {
					mv.visitInsn(DUP);
					Label label = new Label();
					mv.visitJumpInsn(IFNONNULL, label);
						mv.visitInsn(POP);
						mv.visitInsn(FCONST_0);
						mv.visitInsn(FRETURN);
					mv.visitLabel(label);

					AsmUtil.floatValue(mv);
				}
				mv.visitInsn(FRETURN);
				break;

			case 'D':
				if (isLast) {
					mv.visitInsn(DUP);
					Label label = new Label();
					mv.visitJumpInsn(IFNONNULL, label);
						mv.visitInsn(POP);
						mv.visitInsn(DCONST_0);
						mv.visitInsn(DRETURN);
					mv.visitLabel(label);

					AsmUtil.doubleValue(mv);
				}
				mv.visitInsn(DRETURN);
				break;

			default:
				mv.visitInsn(ARETURN);
				break;
		}
	}

	/**
	 * Prepares return value.
	 */
	public static void prepareReturnValue(final MethodVisitor mv, final MethodInfo methodInfo, int varOffset) {
		varOffset += methodInfo.getAllArgumentsSize();
		switch (methodInfo.getReturnType().getOpcode()) {
			case 'V':
				mv.visitInsn(ACONST_NULL);
				break;
			case 'B':
				AsmUtil.valueOfByte(mv);
				break;
			case 'C':
				AsmUtil.valueOfCharacter(mv);
				break;
			case 'S':
				AsmUtil.valueOfShort(mv);
				break;
			case 'I':
				AsmUtil.valueOfInteger(mv);
				break;
			case 'Z':
				AsmUtil.valueOfBoolean(mv);
				break;
			case 'J':
				AsmUtil.valueOfLong(mv);
				break;
			case 'F':
				AsmUtil.valueOfFloat(mv);
				break;
			case 'D':
				AsmUtil.valueOfDouble(mv);
				break;

		}
	}

	public static void castToReturnType(final MethodVisitor mv, final MethodInfo methodInfo) {
		final String returnType;

		char returnOpcodeType = methodInfo.getReturnType().getOpcode();

		switch (returnOpcodeType) {
			case 'I':
				returnType = AsmUtil.SIGNATURE_JAVA_LANG_INTEGER;
				break;
			case 'J':
				returnType = AsmUtil.SIGNATURE_JAVA_LANG_LONG;
				break;
			case 'S':
				returnType = AsmUtil.SIGNATURE_JAVA_LANG_SHORT;
				break;
			case 'B':
				returnType = AsmUtil.SIGNATURE_JAVA_LANG_BYTE;
				break;
			case 'Z':
				returnType = AsmUtil.SIGNATURE_JAVA_LANG_BOOLEAN;
				break;
			case 'F':
				returnType = AsmUtil.SIGNATURE_JAVA_LANG_FLOAT;
				break;
			case 'D':
				returnType = AsmUtil.SIGNATURE_JAVA_LANG_DOUBLE;
				break;
			case 'C':
				returnType = AsmUtil.SIGNATURE_JAVA_LANG_CHARACTER;
				break;
			case '[':
				returnType = methodInfo.getReturnType().getRawName();
				break;
			default:
				String rtname = methodInfo.getReturnType().getRawName();
				returnType = rtname.length() == 0 ?
					AsmUtil.typeToSignature(methodInfo.getReturnType().getType()) :
					AsmUtil.typedesc2ClassName(rtname);
				break;
		}

		mv.visitTypeInsn(CHECKCAST, returnType);
	}

	// ---------------------------------------------------------------- method signature

	/**
	 * Creates unique key for method signatures map.
	 */
	public static String createMethodSignaturesKey(final int access, final String methodName, final String description, final String className) {
		return new StringBand(7)
			.append(access)
			.append(COLON)
			.append(description)
			.append(StringPool.UNDERSCORE)
			.append(className)
			.append(StringPool.HASH)
			.append(methodName)
			.toString();
	}

	// ---------------------------------------------------------------- annotation work

	/**
	 * Visits non-array element value for annotation. Returns true
	 * if value is successfully processed.
	 */
	public static void visitElementValue(final MethodVisitor mv, final Object elementValue, final boolean boxPrimitives) {
		if (elementValue instanceof String) {	// string
			mv.visitLdcInsn(elementValue);
			return;
		}
		if (elementValue instanceof Type) {		// class
			mv.visitLdcInsn(elementValue);
			return;
		}
		if (elementValue instanceof Class) {
			mv.visitLdcInsn(Type.getType((Class) elementValue));
			return;
		}

		// primitives

		if (elementValue instanceof Integer) {
			mv.visitLdcInsn(elementValue);
			if (boxPrimitives) {
				AsmUtil.valueOfInteger(mv);
			}
			return;
		}
		if (elementValue instanceof Long) {
			mv.visitLdcInsn(elementValue);
			if (boxPrimitives) {
				AsmUtil.valueOfLong(mv);
			}
			return;
		}
		if (elementValue instanceof Short) {
			mv.visitLdcInsn(elementValue);
			if (boxPrimitives) {
				AsmUtil.valueOfShort(mv);
			}
			return;
		}
		if (elementValue instanceof Byte) {
			mv.visitLdcInsn(elementValue);
			if (boxPrimitives) {
				AsmUtil.valueOfByte(mv);
			}
			return;
		}
		if (elementValue instanceof Float) {
			mv.visitLdcInsn(elementValue);
			if (boxPrimitives) {
				AsmUtil.valueOfFloat(mv);
			}
			return;
		}
		if (elementValue instanceof Double) {
			mv.visitLdcInsn(elementValue);
			if (boxPrimitives) {
				AsmUtil.valueOfDouble(mv);
			}
			return;
		}
		if (elementValue instanceof Character) {
			mv.visitLdcInsn(elementValue);
			if (boxPrimitives) {
				AsmUtil.valueOfCharacter(mv);
			}
			return;
		}
		if (elementValue instanceof Boolean) {
			mv.visitLdcInsn(elementValue);
			if (boxPrimitives) {
				AsmUtil.valueOfBoolean(mv);
			}
			return;
		}

		// enum

		Class elementValueClass = elementValue.getClass();
		Class enumClass = ClassUtil.findEnum(elementValueClass);

		if (enumClass != null) {
			try {
				String typeRef = AsmUtil.typeToTyperef(enumClass);
				String typeSignature = AsmUtil.typeToSignature(enumClass);

				// invoke
				Method nameMethod = elementValue.getClass().getMethod("name");
				String name = (String) nameMethod.invoke(elementValue);

				mv.visitFieldInsn(GETSTATIC, typeSignature, name, typeRef);

				return;
			} catch (Exception ignore) {
			}
		}

		throw new ProxettaException("Unsupported annotation type: " + elementValue.getClass());
	}

	// ---------------------------------------------------------------- array

	/**
	 * Creates new array.
	 */
	public static void newArray(final MethodVisitor mv, final Class componentType) {
		if (componentType == int.class) {
			mv.visitIntInsn(NEWARRAY, T_INT);
			return;
		}
		if (componentType == long.class) {
			mv.visitIntInsn(NEWARRAY, T_LONG);
			return;
		}
		if (componentType == float.class) {
			mv.visitIntInsn(NEWARRAY, T_FLOAT);
			return;
		}
		if (componentType == double.class) {
			mv.visitIntInsn(NEWARRAY, T_DOUBLE);
			return;
		}
		if (componentType == byte.class) {
			mv.visitIntInsn(NEWARRAY, T_BYTE);
			return;
		}
		if (componentType == short.class) {
			mv.visitIntInsn(NEWARRAY, T_SHORT);
			return;
		}
		if (componentType == boolean.class) {
			mv.visitIntInsn(NEWARRAY, T_BOOLEAN);
			return;
		}
		if (componentType == char.class) {
			mv.visitIntInsn(NEWARRAY, T_CHAR);
			return;
		}

		mv.visitTypeInsn(ANEWARRAY, AsmUtil.typeToSignature(componentType));
	}

	/**
	 * Stores element on stack into an array.
	 */
	public static void storeIntoArray(final MethodVisitor mv, final Class componentType) {
		if (componentType == int.class) {
			mv.visitInsn(IASTORE);
			return;
		}
		if (componentType == long.class) {
			mv.visitInsn(LASTORE);
			return;
		}
		if (componentType == float.class) {
			mv.visitInsn(FASTORE);
			return;
		}
		if (componentType == double.class) {
			mv.visitInsn(DASTORE);
			return;
		}
		if (componentType == byte.class) {
			mv.visitInsn(BASTORE);
			return;
		}
		if (componentType == short.class) {
			mv.visitInsn(SASTORE);
			return;
		}
		if (componentType == boolean.class) {
			mv.visitInsn(BASTORE);
			return;
		}
		if (componentType == char.class) {
			mv.visitInsn(CASTORE);
			return;
		}

		mv.visitInsn(AASTORE);
	}

	// ---------------------------------------------------------------- detect advice macros

	public static boolean isInvokeMethod(final String name, final String desc) {
		if (name.equals("invoke")) {
			if (desc.equals("()Ljava/lang/Object;")) {
				return true;
			}
		}
		return false;
	}

	public static boolean isArgumentsCountMethod(final String name, final String desc) {
		if (name.equals("argumentsCount")) {
			if (desc.equals("()I")) {
				return true;
			}
		}
		return false;
	}

	public static boolean isArgumentTypeMethod(final String name, final String desc) {
		if (name.equals("argumentType")) {
			if (desc.equals("(I)Ljava/lang/Class;")) {
				return true;
			}
		}
		return false;
	}

	public static boolean isArgumentMethod(final String name, final String desc) {
		if (name.equals("argument")) {
			if (desc.equals("(I)Ljava/lang/Object;")) {
				return true;
			}
		}
		return false;
	}

	public static boolean isSetArgumentMethod(final String name, final String desc) {
		if (name.equals("setArgument")) {
			if (desc.equals("(Ljava/lang/Object;I)V")) {
				return true;
			}
		}
		return false;
	}

	public static boolean isCreateArgumentsArrayMethod(final String name, final String desc) {
		if (name.equals("createArgumentsArray")) {
			if (desc.equals("()[Ljava/lang/Object;")) {
				return true;
			}
		}
		return false;
	}

	public static boolean isCreateArgumentsClassArrayMethod(final String name, final String desc) {
		if (name.equals("createArgumentsClassArray")) {
			if (desc.equals("()[Ljava/lang/Class;")) {
				return true;
			}
		}
		return false;
	}

	public static boolean isReturnTypeMethod(final String name, final String desc) {
		if (name.equals("returnType")) {
			if (desc.equals("()Ljava/lang/Class;")) {
				return true;
			}
		}
		return false;
	}

	public static boolean isTargetMethod(final String name, final String desc) {
		if (name.equals("target")) {
			if (desc.equals("()Ljava/lang/Object;")) {
				return true;
			}
		}
		return false;
	}

	public static boolean isTargetClassMethod(final String name, final String desc) {
		if (name.equals("targetClass")) {
			if (desc.equals("()Ljava/lang/Class;")) {
				return true;
			}
		}
		return false;
	}

	public static boolean isTargetMethodNameMethod(final String name, final String desc) {
		if (name.equals("targetMethodName")) {
			if (desc.equals("()Ljava/lang/String;")) {
				return true;
			}
		}
		return false;
	}

	public static boolean isTargetMethodSignatureMethod(final String name, final String desc) {
		if (name.equals("targetMethodSignature")) {
			if (desc.equals("()Ljava/lang/String;")) {
				return true;
			}
		}
		return false;
	}

	public static boolean isTargetMethodDescriptionMethod(final String name, final String desc) {
		if (name.equals("targetMethodDescription")) {
			if (desc.equals("()Ljava/lang/String;")) {
				return true;
			}
		}
		return false;
	}

	public static boolean isReturnValueMethod(final String name, final String desc) {
		if (name.equals("returnValue")) {
			if (desc.equals("(Ljava/lang/Object;)Ljava/lang/Object;")) {
				return true;
			}
		}
		return false;
	}

	public static boolean isInfoMethod(final String name, final String desc) {
		if (name.equals("info")) {
			if (desc.equals("()Ljodd/proxetta/ProxyTargetInfo;")) {
				return true;
			}
		}
		return false;
	}

	public static boolean isTargetMethodAnnotationMethod(final String name, final String desc) {
		if (name.equals("targetMethodAnnotation")) {
			if (desc.equals("(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;")) {
				return true;
			}
		}
		return false;
	}

	public static boolean isTargetClassAnnotationMethod(final String name, final String desc) {
		if (name.equals("targetClassAnnotation")) {
			if (desc.equals("(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;")) {
				return true;
			}
		}
		return false;
	}

}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.decora;

import jodd.io.FileUtil;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * Decora manager defines if some request should be decorated and resolves decorators paths.
 */
public class DecoraManager {

	private static DecoraManager DECORA_MANAGER;

	/**
	 * Returns Decora manager.
	 */
	public static DecoraManager get() {
		return Objects.requireNonNull(DECORA_MANAGER, "Only one instance of DecoraManager should be used.");
	}

	public static final String DEFAULT_DECORATOR = "/decora/main.jsp";

	public DecoraManager() {
		DECORA_MANAGER = this;
	}

	// ---------------------------------------------------------------- properties

	protected boolean decorateErrorPages;

	public boolean isDecorateErrorPages() {
		return decorateErrorPages;
	}

	public void setDecorateErrorPages(final boolean decorateErrorPages) {
		this.decorateErrorPages = decorateErrorPages;
	}

	// ---------------------------------------------------------------- cache

	protected Map<String, char[]> contentMap;
	protected Map<String, File> filesMap;

	public DecoraManager registerDecorator(final String path, final char[] content) {
		if (contentMap == null) {
			contentMap = new HashMap<>();
		}
		contentMap.put(path, content);
		return this;
	}
	public DecoraManager registerDecorator(final String path, final File decorator) {
		if (filesMap == null) {
			filesMap = new HashMap<>();
		}
		filesMap.put(path, decorator);
		return this;
	}

	/**
	 * Lookups the decorator for given decorator path.
	 * Returns {@code null} if decorator is not registered, indicating that content should be
	 * read using the dispatcher.
	 */
	public char[] lookupDecoratorContent(final String path) {
		if (contentMap != null) {
			final char[] data = contentMap.get(path);
			if (data != null) {
				return data;
			}
			final File file = filesMap.get(path);
			if (file != null) {
				try {
					return FileUtil.readChars(file);
				} catch (IOException e) {
					throw new DecoraException("Unable to read Decrator files", e);
				}
			}
		}
		return null;
	}

	// ---------------------------------------------------------------- check

	/**
	 * Determines if a request should be decorated.
	 * By default returns true.
	 */
	public boolean decorateRequest(final HttpServletRequest request) {
		return true;
	}

	/**
	 * Determines if some content type should be decorated.
	 * By default returns true.
	 */
	public boolean decorateContentType(final String contentType, final String mimeType, final String encoding) {
		return true;
	}

	/**
	 * Determines if buffering should be used for some HTTP status code.
	 * By default returns true for status code 200 and, optionally,
	 * for error pages (status code {@literal >=} 400).
	 */
	public boolean decorateStatusCode(final int statusCode) {
		return (statusCode == 200) || (decorateErrorPages && statusCode >= 400);
	}

	// ---------------------------------------------------------------- find

	/**
	 * Resolves decorator path based on request and action path.
	 * If decorator is not found, returns null.
	 * By default applies decorator on all *.html pages.
	 */
	public String resolveDecorator(final HttpServletRequest request, final String actionPath) {
		if (actionPath.endsWith(".html")) {
			return DEFAULT_DECORATOR;
		}
		return null;
	}

}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.mail;

import jodd.core.JoddCore;
import jodd.util.CharUtil;
import jodd.util.StringPool;

import javax.mail.*;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeUtility;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.Properties;

/**
 * Email utilities.
 */
public class EmailUtil {

	protected static final String ATTR_CHARSET = "charset=";
	static final String NO_NAME = "no-name";

	/**
	 * Extracts MIME type from content type.
	 *
	 * @param contentType MIME type.
	 * @return MIME type for the given content type.
	 */
	//TODO: should this always return lowercase or always uppercase?
	public static String extractMimeType(final String contentType) {
		final int ndx = contentType.indexOf(';');
		final String mime;
		if (ndx != -1) {
			mime = contentType.substring(0, ndx);
		} else {
			mime = contentType;
		}
		return mime;
	}

	/**
	 * Extracts encoding from a given content type.
	 *
	 * @param contentType content type.
	 * @return Encoding from the content type. May return {@code null} if encoding is not specified in content type.
	 */
	//TODO: should this always return lowercase or always uppercase?
	public static String extractEncoding(final String contentType) {
		int ndx = contentType.indexOf(';');
		final String charset = ndx != -1 ? contentType.substring(ndx + 1) : StringPool.EMPTY;
		String encoding = null;

		ndx = charset.indexOf(ATTR_CHARSET);
		if (ndx != -1) {
			ndx += ATTR_CHARSET.length();
			final int len = charset.length();

			if (charset.charAt(ndx) == '"') {
				ndx++;
			}
			final int start = ndx;

			while (ndx < len) {
				final char c = charset.charAt(ndx);
				if ((c == '"') || (CharUtil.isWhitespace(c)) || (c == ';')) {
					break;
				}
				ndx++;
			}
			encoding = charset.substring(start, ndx);
		}
		return encoding;
	}

	/**
	 * Extracts encoding from a given content type.
	 *
	 * @param contentType     content type.
	 * @param defaultEncoding Default encoding to be used if extract returns {@code null}.
	 *                        If defaultEncoding is {@code null}, {@link JoddCore#encoding} will be used.
	 * @return Encoding from the content type.
	 * @see #extractEncoding(String)
	 */
	public static String extractEncoding(final String contentType, String defaultEncoding) {
		String encoding = extractEncoding(contentType);

		if (encoding == null) {
			if (defaultEncoding == null) {
				defaultEncoding = JoddCore.encoding;
			}
			encoding = defaultEncoding;
		}
		return encoding;
	}

	/**
	 * Correctly resolves file name from the message part.
	 * Thanx to: Flavio Pompermaier
	 *
	 * @param part {@link Part} to decode file name from.
	 * @return String containing file name.
	 */
	public static String resolveFileName(final Part part) throws MessagingException {
		if (!(part instanceof MimeBodyPart)) {
			return part.getFileName();
		}

		final String contentType = part.getContentType();
		String ret;

		try {
			ret = MimeUtility.decodeText(part.getFileName());
		} catch (final Exception ex) {
			// String[] contentId = part.getHeader("Content-ID");
			// if (contentId != null && contentId.length > 0) {
			final String contentId = ((MimeBodyPart) part).getContentID();
			if (contentId != null) {
				ret = contentId + contentTypeForFileName(contentType);
			} else {
				ret = defaultFileName(contentType);
			}
		}

		return ret;
	}

	private static String contentTypeForFileName(final String contentType) {
		return StringPool.DOT + contentType.substring(contentType.lastIndexOf("/") + 1, contentType.length());
	}

	private static String defaultFileName(final String contentType) {
		return NO_NAME + contentTypeForFileName(contentType);
	}

	/**
	 * @param protocol          Protocol such as {@link ImapServer#PROTOCOL_IMAP} or {@link Pop3Server#PROTOCOL_POP3}.
	 * @param sessionProperties Session properties to use.
	 * @param authenticator     Authenticator which contains necessary authentication for server.
	 * @return {@link ReceiveMailSession}.
	 */
	public static ReceiveMailSession createSession(final String protocol, final Properties sessionProperties, final Authenticator authenticator, final File attachmentStorage) {
		final Session session = Session.getInstance(sessionProperties, authenticator);
		final Store store;
		try {
			store = session.getStore(protocol);
		} catch (final NoSuchProviderException nspex) {
			final String errMsg = String.format("Failed to create %s session", protocol);
			throw new MailException(errMsg, nspex);
		}
		return new ReceiveMailSession(session, store, attachmentStorage);
	}

	/**
	 * Check whether flags is a empty flags
	 * @param flags a flags of message to check
	 * @return whether the flags is empty
	 */
	public static boolean isEmptyFlags(Flags flags) {
		if (flags == null) return true;
		Flags.Flag[] systemFlags = flags.getSystemFlags();
		if (systemFlags != null && systemFlags.length > 0) {
			return false;
		}
		String[] userFlags = flags.getUserFlags();
		if (userFlags != null && userFlags.length > 0) {
			return false;
		}

		return true;
	}

}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.typeconverter.impl;

import jodd.typeconverter.TypeConverter;
import jodd.typeconverter.TypeConverterManager;

import java.util.ArrayList;
import java.util.Collection;

/**
 * Converts given object to char[].
 */
public class CharacterArrayConverter implements TypeConverter<char[]> {

	protected final TypeConverterManager typeConverterManager;

	public CharacterArrayConverter(final TypeConverterManager typeConverterManager) {
		this.typeConverterManager = typeConverterManager;
	}

	@Override
	public char[] convert(final Object value) {
		if (value == null) {
			return null;
		}

		final Class valueClass = value.getClass();

		if (!valueClass.isArray()) {
			// source is not an array
			return convertValueToArray(value);
		}

		// source is an array
		return convertArrayToArray(value);
	}

	/**
	 * Converts type using type converter manager.
	 */
	protected char convertType(final Object value) {
		return typeConverterManager.convertType(value, char.class).charValue();
	}

	/**
	 * Creates an array with single element.
	 */
	protected char[] convertToSingleElementArray(final Object value) {
		return new char[] {convertType(value)};
	}

	/**
	 * Converts non-array value to array. Detects various
	 * collection types and iterates them to make conversion
	 * and to create target array.
 	 */
	protected char[] convertValueToArray(final Object value) {
		if (value instanceof Collection) {
			final Collection collection = (Collection) value;
			final char[] target = new char[collection.size()];

			int i = 0;
			for (final Object element : collection) {
				target[i] = convertType(element);
				i++;
			}

			return target;
		}

		if (value instanceof Iterable) {
			final Iterable iterable = (Iterable) value;

			final ArrayList<Character> charArrayList = new ArrayList<>();

			for (final Object element : iterable) {
				final char convertedValue = convertType(element);
				charArrayList.add(Character.valueOf(convertedValue));
			}

			final char[] array = new char[charArrayList.size()];

			for (int i = 0; i < charArrayList.size(); i++) {
				final Character c = charArrayList.get(i);
				array[i] = c.charValue();
			}

			return array;

		}

		if (value instanceof CharSequence) {
			final CharSequence charSequence = (CharSequence) value;

			final char[] result = new char[charSequence.length()];

			for (int i = 0; i < result.length; i++) {
				result[i] = charSequence.charAt(i);
			}

			return result;
		}

		// everything else:
		return convertToSingleElementArray(value);
	}

	/**
	 * Converts array value to array.
	 */
	protected char[] convertArrayToArray(final Object value) {
		final Class valueComponentType = value.getClass().getComponentType();

		final char[] result;

		if (valueComponentType.isPrimitive()) {
			result = convertPrimitiveArrayToArray(value, valueComponentType);
		} else {
			// convert object array to target array
			final Object[] array = (Object[]) value;
			result = new char[array.length];

			for (int i = 0; i < array.length; i++) {
				result[i] = convertType(array[i]);
			}
		}

		return result;
	}

	/**
	 * Converts primitive array to target array.
	 */
	protected char[] convertPrimitiveArrayToArray(final Object value, final Class primitiveComponentType) {
		char[] result = null;

		if (primitiveComponentType == char.class) {
			return (char[]) value;
		}

		if (primitiveComponentType == int.class) {
			final int[] array = (int[]) value;
			result = new char[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = (char) array[i];
			}
		}
		else if (primitiveComponentType == long.class) {
			final long[] array = (long[]) value;
			result = new char[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = (char) array[i];
			}
		}
		else if (primitiveComponentType == float.class) {
			final float[] array = (float[]) value;
			result = new char[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = (char) array[i];
			}
		}
		else if (primitiveComponentType == double.class) {
			final double[] array = (double[]) value;
			result = new char[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = (char) array[i];
			}
		}
		else if (primitiveComponentType == short.class) {
			final short[] array = (short[]) value;
			result = new char[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = (char) array[i];
			}
		}
		else if (primitiveComponentType == byte.class) {
			final byte[] array = (byte[]) value;
			result = new char[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = (char) array[i];
			}
		}
		else if (primitiveComponentType == boolean.class) {
			final boolean[] array = (boolean[]) value;
			result = new char[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = (char) (array[i] ? 1 : 0);
			}
		}
		return result;
	}

}