// 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;

import jodd.net.URLDecoder;
import jodd.util.cl.ClassLoaderStrategy;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarFile;

/**
 * Class utilities.
 */
public class ClassUtil {

	/** Empty class array. */
	public static final Class[] EMPTY_CLASS_ARRAY = new Class[0];

	@SuppressWarnings("unchecked")
	public static <T> Class<T>[] emptyClassArray() {
		return EMPTY_CLASS_ARRAY;
	}

	public static final String METHOD_GET_PREFIX = "get";
	public static final String METHOD_IS_PREFIX = "is";
	public static final String METHOD_SET_PREFIX = "set";

	// ---------------------------------------------------------------- find method

	/**
	 * Returns method from an object, matched by name. This may be considered as
	 * a slow operation, since methods are matched one by one.
	 * Returns only accessible methods.
	 * Only first method is matched.
	 *
	 * @param c          class to examine
	 * @param methodName Full name of the method.
	 * @return null if method not found
	 */
	public static Method findMethod(final Class c, final String methodName) {
		return findDeclaredMethod(c, methodName, true);
	}

	/**
	 * @see #findMethod(Class, String)
	 */
	public static Method findDeclaredMethod(final Class c, final String methodName) {
		return findDeclaredMethod(c, methodName, false);
	}

	private static Method findDeclaredMethod(final Class c, final String methodName, final boolean publicOnly) {
		if ((methodName == null) || (c == null)) {
			return null;
		}
		Method[] ms = publicOnly ? c.getMethods() : c.getDeclaredMethods();
		for (Method m : ms) {
			if (m.getName().equals(methodName)) {
				return m;
			}
		}
		return null;
	}

	// ---------------------------------------------------------------- find ctor

	/**
	 * Finds constructor with given parameter types. First matched ctor is returned.
	 */
	public static <T> Constructor<T> findConstructor(final Class<T> clazz, final Class<?>... parameterTypes) {
		final Constructor<?>[] constructors = clazz.getConstructors();

		Class<?>[] pts;

		for (Constructor<?> constructor : constructors) {
			pts = constructor.getParameterTypes();

			if (isAllAssignableFrom(pts, parameterTypes)) {
				return (Constructor<T>) constructor;
			}
		}
		return null;
	}

	/**
	 * Returns {@code true} if all types are assignable from the other array of types.
	 */
	public static boolean isAllAssignableFrom(final Class<?>[] typesTarget, final Class<?>[] typesFrom) {
		if (typesTarget.length == typesFrom.length) {
			for (int i = 0; i < typesTarget.length; i++) {
				if (!typesTarget[i].isAssignableFrom(typesFrom[i])) {
					return false;
				}
			}
			return true;
		}
		return false;
	}

	// ---------------------------------------------------------------- classes

	/**
	 * Returns classes from array of objects. It accepts {@code null}
	 * values.
	 */
	public static Class[] getClasses(final Object... objects) {
		if (objects.length == 0) {
			return EMPTY_CLASS_ARRAY;
		}
		Class[] result = new Class[objects.length];
		for (int i = 0; i < objects.length; i++) {
			if (objects[i] != null) {
				result[i] = objects[i].getClass();
			}
		}
		return result;
	}

	// ---------------------------------------------------------------- match classes

	/**
	 * Safe version of isAssignableFrom method that
	 * returns false if one of the arguments is null.
	 */
	public static boolean isTypeOf(final Class<?> lookupClass, final Class<?> targetClass) {
		if (targetClass == null || lookupClass == null) {
			return false;
		}
		return targetClass.isAssignableFrom(lookupClass);
	}

	/**
	 * Safe version of isInstance, returns false
	 * if any of the arguments is null.
	 */
	public static boolean isInstanceOf(final Object object, final Class target) {
		if (object == null || target == null) {
			return false;
		}
		return target.isInstance(object);
	}

	/**
	 * Resolves all interfaces of a type. No duplicates are returned.
	 * Direct interfaces are prior the interfaces of subclasses in
	 * the returned array.
	 */
	public static Class[] resolveAllInterfaces(final Class type) {
		Set<Class> bag = new LinkedHashSet<>();
		_resolveAllInterfaces(type, bag);

		return bag.toArray(new Class[0]);
	}

	private static void _resolveAllInterfaces(final Class type, final Set<Class> bag) {
		// add types interfaces
		Class[] interfaces = type.getInterfaces();
		Collections.addAll(bag, interfaces);

		// resolve interfaces of each interface
		for (Class iface : interfaces) {
			_resolveAllInterfaces(iface, bag);
		}

		// continue with super type
		Class superClass = type.getSuperclass();

		if (superClass == null) {
			return;
		}

		if (superClass == Object.class) {
			return;
		}

		_resolveAllInterfaces(type.getSuperclass(), bag);
	}

	/**
	 * Resolves all super classes, from top (direct subclass) to down. Object
	 * class is not included in the list.
	 */
	public static Class[] resolveAllSuperclasses(Class type) {
		List<Class> list = new ArrayList<>();

		while (true) {
			type = type.getSuperclass();

			if ((type == null) || (type == Object.class)) {
				break;
			}
			list.add(type);
		}

		return list.toArray(new Class[0]);
	}

	// ---------------------------------------------------------------- accessible methods

	/**
	 * Returns array of all methods that are accessible from given class.
	 * @see #getAccessibleMethods(Class, Class)
	 */
	public static Method[] getAccessibleMethods(final Class clazz) {
		return getAccessibleMethods(clazz, Object.class);
	}

	/**
	 * Returns array of all methods that are accessible from given class, upto limit
	 * (usually Object.class). Abstract methods are ignored.
	 */
	public static Method[] getAccessibleMethods(Class clazz, final Class limit) {
		Package topPackage = clazz.getPackage();
		List<Method> methodList = new ArrayList<>();
		int topPackageHash = topPackage == null ? 0 : topPackage.hashCode();
		boolean top = true;
		do {
			if (clazz == null) {
				break;
			}
			Method[] declaredMethods = clazz.getDeclaredMethods();
			for (Method method : declaredMethods) {
				if (Modifier.isVolatile(method.getModifiers())) {
				    continue;
				}
//				if (Modifier.isAbstract(method.getModifiers())) {
//					continue;
//				}
				if (top) {				// add all top declared methods
					methodList.add(method);
					continue;
				}
				int modifier = method.getModifiers();
				if (Modifier.isPrivate(modifier)) {
					continue;										// ignore super private methods
				}
				if (Modifier.isAbstract(modifier)) {		// ignore super abstract methods
					continue;
				}
				if (Modifier.isPublic(modifier)) {
					addMethodIfNotExist(methodList, method);		// add super public methods
					continue;
				}
				if (Modifier.isProtected(modifier)) {
					addMethodIfNotExist(methodList, method);		// add super protected methods
					continue;
				}
				// add super default methods from the same package
				Package pckg = method.getDeclaringClass().getPackage();
				int pckgHash = pckg == null ? 0 : pckg.hashCode();
				if (pckgHash == topPackageHash) {
					addMethodIfNotExist(methodList, method);
				}
			}
			top = false;
		} while ((clazz = clazz.getSuperclass()) != limit);

		Method[] methods = new Method[methodList.size()];
		for (int i = 0; i < methods.length; i++) {
			methods[i] = methodList.get(i);
		}
		return methods;
	}

	private static void addMethodIfNotExist(final List<Method> allMethods, final Method newMethod) {
		for (Method m : allMethods) {
			if (compareSignatures(m, newMethod)) {
				return;
			}
		}
		allMethods.add(newMethod);
	}

	// ---------------------------------------------------------------- accessible fields

	public static Field[] getAccessibleFields(final Class clazz) {
		return getAccessibleFields(clazz, Object.class);
	}

	public static Field[] getAccessibleFields(Class clazz, final Class limit) {
		Package topPackage = clazz.getPackage();
		List<Field> fieldList = new ArrayList<>();
		int topPackageHash = topPackage == null ? 0 : topPackage.hashCode();
		boolean top = true;
		do {
			if (clazz == null) {
				break;
			}
			Field[] declaredFields = clazz.getDeclaredFields();
			for (Field field : declaredFields) {
				if (top) {				// add all top declared fields
					fieldList.add(field);
					continue;
				}
				int modifier = field.getModifiers();
				if (Modifier.isPrivate(modifier)) {
					continue;										// ignore super private fields
				}
				if (Modifier.isPublic(modifier)) {
					addFieldIfNotExist(fieldList, field);			// add super public methods
					continue;
				}
				if (Modifier.isProtected(modifier)) {
					addFieldIfNotExist(fieldList, field);			// add super protected methods
					continue;
				}
				// add super default methods from the same package
				Package pckg = field.getDeclaringClass().getPackage();
				int pckgHash = pckg == null ? 0 : pckg.hashCode();
				if (pckgHash == topPackageHash) {
					addFieldIfNotExist(fieldList, field);
				}
			}
			top = false;
		} while ((clazz = clazz.getSuperclass()) != limit);

		Field[] fields = new Field[fieldList.size()];
		for (int i = 0; i < fields.length; i++) {
			fields[i] = fieldList.get(i);
		}
		return fields;
	}

	private static void addFieldIfNotExist(final List<Field> allFields, final Field newField) {
		for (Field f : allFields) {
			if (compareSignatures(f, newField)) {
				return;
			}
		}
		allFields.add(newField);
	}

	// ---------------------------------------------------------------- supported methods

	public static Method[] getSupportedMethods(final Class clazz) {
		return getSupportedMethods(clazz, Object.class);
	}

	/**
	 * Returns a Method array of the methods to which instances of the specified
	 * respond except for those methods defined in the class specified by limit
	 * or any of its superclasses. Note that limit is usually used to eliminate
	 * them methods defined by java.lang.Object. If limit is null then all
	 * methods are returned.
	 */
	public static Method[] getSupportedMethods(final Class clazz, final Class limit) {
		final ArrayList<Method> supportedMethods = new ArrayList<>();
		for (Class c = clazz; c != limit && c!= null; c = c.getSuperclass()) {
			final Method[] methods = c.getDeclaredMethods();
			for (final Method method : methods) {
				boolean found = false;
				for (final Method supportedMethod : supportedMethods) {
					if (compareSignatures(method, supportedMethod)) {
						found = true;
						break;
					}
				}
				if (!found) {
					supportedMethods.add(method);
				}
			}
		}
		return supportedMethods.toArray(new Method[0]);
	}

	public static Field[] getSupportedFields(final Class clazz) {
		return getSupportedFields(clazz, Object.class);
	}

	public static Field[] getSupportedFields(final Class clazz, final Class limit) {
		final ArrayList<Field> supportedFields = new ArrayList<>();
		for (Class c = clazz; c != limit && c!= null; c = c.getSuperclass()) {
			final Field[] fields = c.getDeclaredFields();
			for (final Field field : fields) {
				boolean found = false;
				for (final Field supportedField : supportedFields) {
					if (compareSignatures(field, supportedField)) {
						found = true;
						break;
					}
				}
				if (!found) {
					supportedFields.add(field);
				}
			}
		}
		return supportedFields.toArray(new Field[0]);
	}

	// ---------------------------------------------------------------- compare

	/**
	 * Compares method declarations: signature and return types.
	 */
	public static boolean compareDeclarations(final Method first, final Method second) {
		if (first.getReturnType() != second.getReturnType()) {
			return false;
		}
		return compareSignatures(first, second);
	}

	/**
	 * Compares method signatures: names and parameters.
	 */
	public static boolean compareSignatures(final Method first, final Method second) {
		if (!first.getName().equals(second.getName())) {
			return false;
		}
		return compareParameters(first.getParameterTypes(), second.getParameterTypes());
	}

	/**
	 * Compares constructor signatures: names and parameters.
	 */
	public static boolean compareSignatures(final Constructor first, final Constructor second) {
		if (!first.getName().equals(second.getName())) {
			return false;
		}
		return compareParameters(first.getParameterTypes(), second.getParameterTypes());
	}

	public static boolean compareSignatures(final Field first, final Field second) {
		return first.getName().equals(second.getName());
	}

	/**
	 * Compares classes, usually method or ctor parameters.
	 */
	public static boolean compareParameters(final Class[] first, final Class[] second) {
		if (first.length != second.length) {
			return false;
		}
		for (int i = 0; i < first.length; i++) {
			if (first[i] != second[i]) {
				return false;
			}
		}
		return true;
	}

	// ---------------------------------------------------------------- force

	/**
	 * Suppress access check against a reflection object. SecurityException is silently ignored.
	 * Checks first if the object is already accessible.
	 */
	public static void forceAccess(final AccessibleObject accObject) {
		try {
			if (System.getSecurityManager() == null)
				accObject.setAccessible(true);
			else {
				AccessController.doPrivileged((PrivilegedAction) () -> {
					accObject.setAccessible(true);
					return null;
				});
			}
		} catch (SecurityException sex) {
			// ignore
		}
	}

	// ---------------------------------------------------------------- is public

	/**
	 * Returns true if class member is public.
	 */
	public static boolean isPublic(final Member member) {
		return Modifier.isPublic(member.getModifiers());
	}

	/**
	 * Returns true if class member is public and if its declaring class is also public.
	 */
	public static boolean isPublicPublic(final Member member) {
		if (Modifier.isPublic(member.getModifiers())) {
			if (Modifier.isPublic(member.getDeclaringClass().getModifiers())) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Returns true if class is public.
	 */
	public static boolean isPublic(final Class c) {
		return Modifier.isPublic(c.getModifiers());
	}

	// ---------------------------------------------------------------- create

	/**
	 * Creates new instance of given class with given optional arguments.
	 */
	@SuppressWarnings("unchecked")
	public static <T> T newInstance(final Class<T> clazz, final Object... params) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
		if (params.length == 0) {
			return newInstance(clazz);
		}

		final Class<?>[] paramTypes = getClasses(params);

		final Constructor<?> constructor = findConstructor(clazz, paramTypes);

		if (constructor == null) {
			throw new InstantiationException("No constructor matched parameter types.");
		}

		return (T) constructor.newInstance(params);
	}

	/**
	 * Creates new instances including for common mutable classes that do not have a default constructor.
	 * more user-friendly. It examines if class is a map, list,
	 * String, Character, Boolean or a Number. Immutable instances are cached and not created again.
	 * Arrays are also created with no elements. Note that this bunch of if blocks
	 * is faster then using a HashMap.
	 */
	@SuppressWarnings("unchecked")
	public static <T> T newInstance(final Class<T> type) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
		if (type.isPrimitive()) {
			if (type == int.class) {
				return (T) Integer.valueOf(0);
			}
			if (type == long.class) {
				return (T) Long.valueOf(0);
			}
			if (type == boolean.class) {
				return (T) Boolean.FALSE;
			}
			if (type == float.class) {
				return (T) Float.valueOf(0);
			}
			if (type == double.class) {
				return (T) Double.valueOf(0);
			}
			if (type == byte.class) {
				return (T) Byte.valueOf((byte) 0);
			}
			if (type == short.class) {
				return (T) Short.valueOf((short) 0);
			}
			if (type == char.class) {
				return (T) Character.valueOf((char) 0);
			}
			throw new IllegalArgumentException("Invalid primitive: " + type);
		}

		if (type.getName().startsWith("java.")) {

			if (type == Integer.class) {
				return (T) Integer.valueOf(0);
			}
			if (type == String.class) {
				return (T) StringPool.EMPTY;
			}
			if (type == Long.class) {
				return (T) Long.valueOf(0);
			}
			if (type == Boolean.class) {
				return (T) Boolean.FALSE;
			}
			if (type == Float.class) {
				return (T) Float.valueOf(0);
			}
			if (type == Double.class) {
				return (T) Double.valueOf(0);
			}

			if (type == Map.class) {
				return (T) new HashMap();
			}
			if (type == List.class) {
				return (T) new ArrayList();
			}
			if (type == Set.class) {
				return (T) new HashSet();
			}
			if (type == Collection.class) {
				return (T) new ArrayList();
			}

			if (type == Byte.class) {
				return (T) Byte.valueOf((byte) 0);
			}
			if (type == Short.class) {
				return (T) Short.valueOf((short) 0);
			}
			if (type == Character.class) {
				return (T) Character.valueOf((char) 0);
			}
		}

		if (type.isEnum()) {
			return type.getEnumConstants()[0];
		}

		if (type.isArray()) {
			return (T) Array.newInstance(type.getComponentType(), 0);
		}

		Constructor<T> declaredConstructor = type.getDeclaredConstructor();

		forceAccess(declaredConstructor);

		return declaredConstructor.newInstance();
	}

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

	/**
	 * Returns true if the first member is accessible from second one.
	 */
	public static boolean isAssignableFrom(final Member member1, final Member member2) {
		return member1.getDeclaringClass().isAssignableFrom(member2.getDeclaringClass());
	}

	/**
	 * Returns all superclasses.
	 */
	public static Class[] getSuperclasses(final Class type) {
		int i = 0;
		for (Class x = type.getSuperclass(); x != null; x = x.getSuperclass()) {
			i++;
		}
		Class[] result = new Class[i];
		i = 0;
		for (Class x = type.getSuperclass(); x != null; x = x.getSuperclass()) {
			result[i] = x;
			i++;
		}
		return result;
	}

	/**
	 * Returns true if method is user defined and not defined in Object class.
	 */
	public static boolean isUserDefinedMethod(final Method method) {
		return method.getDeclaringClass() != Object.class;
	}

	/**
	 * Returns true if method defined in Object class.
	 */
	public static boolean isObjectMethod(final Method method) {
		return method.getDeclaringClass() == Object.class;
	}

	/**
	 * Returns true if method is a bean property.
	 */
	public static boolean isBeanProperty(final Method method) {
		if (isObjectMethod(method)) {
			return false;
		}
		String methodName = method.getName();
		Class returnType = method.getReturnType();
		Class[] paramTypes =  method.getParameterTypes();
		if (methodName.startsWith(METHOD_GET_PREFIX)) {		// getter method must starts with 'get' and it is not getClass()
			if ((returnType != null) && (paramTypes.length == 0)) {	// getter must have a return type and no arguments
				return true;
			}
		} else if (methodName.startsWith(METHOD_IS_PREFIX)) {		    // ister must starts with 'is'
			if ((returnType != null)  && (paramTypes.length == 0)) {	// ister must have return type and no arguments
				return true;
			}
		} else if (methodName.startsWith(METHOD_SET_PREFIX)) {	// setter must start with a 'set'
			if (paramTypes.length == 1) {				        // setter must have just one argument
				return true;
			}
		}
		return false;
	}

	/**
	 * Returns true if method is bean getter.
	 */
	public static boolean isBeanPropertyGetter(final Method method) {
		return getBeanPropertyGetterPrefixLength(method) != 0;
	}

	private static int getBeanPropertyGetterPrefixLength(final Method method) {
		if (isObjectMethod(method)) {
			return 0;
		}
		String methodName = method.getName();
		Class returnType = method.getReturnType();
		Class[] paramTypes =  method.getParameterTypes();
		if (methodName.startsWith(METHOD_GET_PREFIX)) {		        // getter method must starts with 'get' and it is not getClass()
			if ((returnType != null) && (paramTypes.length == 0)) {	// getter must have a return type and no arguments
				return 3;
			}
		} else if (methodName.startsWith(METHOD_IS_PREFIX)) {		    // ister must starts with 'is'
			if ((returnType != null)  && (paramTypes.length == 0)) {	// ister must have return type and no arguments
				return 2;
			}
		}
		return 0;
	}

	/**
	 * Returns property name from a getter method.
	 * Returns null if method is not a real getter.
	 */
	public static String getBeanPropertyGetterName(final Method method) {
		int prefixLength = getBeanPropertyGetterPrefixLength(method);
		if (prefixLength == 0) {
			return null;
		}
		String methodName = method.getName().substring(prefixLength);
		return StringUtil.decapitalize(methodName);
	}

	/**
	 * Returns true if method is bean setter.
	 */
	public static boolean isBeanPropertySetter(final Method method) {
		return getBeanPropertySetterPrefixLength(method) != 0;
	}

	private static int getBeanPropertySetterPrefixLength(final Method method) {
		if (isObjectMethod(method)) {
			return 0;
		}
		String methodName = method.getName();
		Class[] paramTypes =  method.getParameterTypes();
		if (methodName.startsWith(METHOD_SET_PREFIX)) {	        // setter must start with a 'set'
			if (paramTypes.length == 1) {				        // setter must have just one argument
				return 3;
			}
		}
		return 0;
	}

	/**
	 * Returns beans property setter name or null if method is not a real setter.
	 */
	public static String getBeanPropertySetterName(final Method method) {
		int prefixLength = getBeanPropertySetterPrefixLength(method);
		if (prefixLength == 0) {
			return null;
		}
		String methodName = method.getName().substring(prefixLength);
		return StringUtil.decapitalize(methodName);
	}

	// ---------------------------------------------------------------- generics

	/**
	 * Returns single component type. Index is used when type consist of many
	 * components. If negative, index will be calculated from the end of the
	 * returned array. Returns null if component type
	 * does not exist or if index is out of bounds.
	 *
	 * @see #getComponentTypes(java.lang.reflect.Type)
	 */
	public static Class getComponentType(final Type type, final int index) {
		return getComponentType(type, null, index);
	}

	/**
	 * Returns single component type for given type and implementation.
	 * Index is used when type consist of many
	 * components. If negative, index will be calculated from the end of the
	 * returned array.  Returns null if component type
	 * does not exist or if index is out of bounds.
	 *

	 *
	 * @see #getComponentTypes(java.lang.reflect.Type, Class)
	 */
	public static Class getComponentType(final Type type, final Class implClass, int index) {
		Class[] componentTypes = getComponentTypes(type, implClass);
		if (componentTypes == null) {
			return null;
		}

		if (index < 0) {
			index += componentTypes.length;
		}

		if (index >= componentTypes.length) {
			return null;
		}

		return componentTypes[index];
	}

	/**
	 * @see #getComponentTypes(java.lang.reflect.Type, Class)
	 */
	public static Class[] getComponentTypes(final Type type) {
		return getComponentTypes(type, null);
	}

	/**
	 * Returns all component types of the given type.
	 * For example the following types all have the
	 * component-type MyClass:
	 *

	 *
MyClass[]

	 *
List<MyClass>

	 *
Foo<? extends MyClass>

	 *
Bar<? super MyClass>

	 *
<T extends MyClass> T[]

	 *

	 */
	public static Class[] getComponentTypes(final Type type, final Class implClass) {
		if (type instanceof Class) {
			Class clazz = (Class) type;
			if (clazz.isArray()) {
				return new Class[] {clazz.getComponentType()};
			}
		}
		else if (type instanceof ParameterizedType) {
			ParameterizedType pt = (ParameterizedType) type;

			Type[] generics = pt.getActualTypeArguments();

			if (generics.length == 0) {
				return null;
			}

			Class[] types = new Class[generics.length];

			for (int i = 0; i < generics.length; i++) {
				types[i] = getRawType(generics[i], implClass);
			}
			return types;
		}
		else if (type instanceof GenericArrayType) {
			GenericArrayType gat = (GenericArrayType) type;

			Class rawType = getRawType(gat.getGenericComponentType(), implClass);
			if (rawType == null) {
				return null;
			}

			return new Class[] {rawType};
		}
		return null;
	}

	/**
	 * Shortcut for getComponentTypes(type.getGenericSuperclass()).
	 *
	 * @see #getComponentTypes(java.lang.reflect.Type)
	 */
	public static Class[] getGenericSupertypes(final Class type) {
		return getComponentTypes(type.getGenericSuperclass());
	}

	/**
	 * Shortcut for getComponentType(type.getGenericSuperclass()).
	 *
	 * @see #getComponentType(java.lang.reflect.Type, int)
	 */
	public static Class getGenericSupertype(final Class type, final int index) {
		return getComponentType(type.getGenericSuperclass(), index);
	}

	/**
	 * Returns raw class for given type. Use this method with both
	 * regular and generic types.
	 *
	 * @param type the type to convert
	 * @return the closest class representing the given type
	 * @see #getRawType(java.lang.reflect.Type, Class)
	 */
	public static Class getRawType(final Type type) {
		return getRawType(type, null);
	}

	/**
	 * Returns raw class for given type when implementation class is known
	 * and it makes difference.
	 * @see #resolveVariable(java.lang.reflect.TypeVariable, Class)
	 */
	public static Class<?> getRawType(final Type type, final Class implClass) {
		if (type instanceof Class) {
			return (Class) type;
		}
		if (type instanceof ParameterizedType) {
			ParameterizedType pType = (ParameterizedType) type;
			return getRawType(pType.getRawType(), implClass);
		}
		if (type instanceof WildcardType) {
			WildcardType wType = (WildcardType) type;

			Type[] lowerTypes = wType.getLowerBounds();
			if (lowerTypes.length > 0) {
				return getRawType(lowerTypes[0], implClass);
			}

			Type[] upperTypes = wType.getUpperBounds();
			if (upperTypes.length != 0) {
				return getRawType(upperTypes[0], implClass);
			}

			return Object.class;
		}
		if (type instanceof GenericArrayType) {
			Type genericComponentType = ((GenericArrayType) type).getGenericComponentType();
			Class<?> rawType = getRawType(genericComponentType, implClass);
			// this is sort of stupid, but there seems no other way (consider don't creating new instances each time)...
			return Array.newInstance(rawType, 0).getClass();
		}
		if (type instanceof TypeVariable) {
			TypeVariable<?> varType = (TypeVariable<?>) type;
			if (implClass != null) {
				Type resolvedType = resolveVariable(varType, implClass);
				if (resolvedType != null) {
					return getRawType(resolvedType, null);
				}
			}
			Type[] boundsTypes = varType.getBounds();
			if (boundsTypes.length == 0) {
				return Object.class;
			}
			return getRawType(boundsTypes[0], implClass);
		}
		return null;
	}

	/**
	 * Resolves TypeVariable with given implementation class.
	 */
	public static Type resolveVariable(final TypeVariable variable, final Class implClass) {
		final Class rawType = getRawType(implClass, null);

		int index = ArraysUtil.indexOf(rawType.getTypeParameters(), variable);
		if (index >= 0) {
			return variable;
		}

		final Class[] interfaces = rawType.getInterfaces();
		final Type[] genericInterfaces = rawType.getGenericInterfaces();

		for (int i = 0; i <= interfaces.length; i++) {
			Class rawInterface;

			if (i < interfaces.length) {
				rawInterface = interfaces[i];
			} else {
				rawInterface = rawType.getSuperclass();
				if (rawInterface == null) {
					continue;
				}
			}

			final Type resolved = resolveVariable(variable, rawInterface);
			if (resolved instanceof Class || resolved instanceof ParameterizedType) {
				return resolved;
			}

			if (resolved instanceof TypeVariable) {
				final TypeVariable typeVariable = (TypeVariable) resolved;
				index = ArraysUtil.indexOf(rawInterface.getTypeParameters(), typeVariable);

				if (index < 0) {
					throw new IllegalArgumentException("Invalid type variable:" + typeVariable);
				}

				final Type type = i < genericInterfaces.length ? genericInterfaces[i] : rawType.getGenericSuperclass();

				if (type instanceof Class) {
					return Object.class;
				}

				if (type instanceof ParameterizedType) {
					return ((ParameterizedType) type).getActualTypeArguments()[index];
				}

				throw new IllegalArgumentException("Unsupported type: " + type);
			}
		}
		return null;
	}

	/**
	 * Converts Type to a String. Supports successor interfaces:
	 *

	 *
java.lang.Class - represents usual class

	 *
java.lang.reflect.ParameterizedType - class with generic parameter (e.g. List)

	 *
java.lang.reflect.TypeVariable - generic type literal (e.g. List, T - type variable)

	 *
java.lang.reflect.WildcardType - wildcard type (List<? extends Number>, "? extends Number - wildcard type)

	 *
java.lang.reflect.GenericArrayType - type for generic array (e.g. T[], T - array type)

	 *

	 */
	public static String typeToString(final Type type) {
		StringBuilder sb = new StringBuilder();
		typeToString(sb, type, new HashSet<Type>());
		return sb.toString();
	}

	private static void typeToString(final StringBuilder sb, final Type type, final Set<Type> visited) {
		if (type instanceof ParameterizedType) {
			ParameterizedType parameterizedType = (ParameterizedType) type;
			final Class<?> rawType = (Class<?>) parameterizedType.getRawType();
			sb.append(rawType.getName());
			boolean first = true;
			for (Type typeArg : parameterizedType.getActualTypeArguments()) {
				if (first) {
					first = false;
				} else {
					sb.append(", ");
				}
				sb.append('<');
				typeToString(sb, typeArg, visited);
				sb.append('>');
			}
		} else if (type instanceof WildcardType) {
			WildcardType wildcardType = (WildcardType) type;
			sb.append('?');

			// According to JLS(http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.5.1):
			// - Lower and upper can't coexist: (for instance, this is not allowed: <? extends List<String> & super MyInterface>)
			// - Multiple bounds are not supported (for instance, this is not allowed: <? extends List<String> & MyInterface>)

			final Type bound;
			if (wildcardType.getLowerBounds().length != 0) {
				sb.append(" super ");
				bound = wildcardType.getLowerBounds()[0];
			} else {
				sb.append(" extends ");
				bound = wildcardType.getUpperBounds()[0];
			}
			typeToString(sb, bound, visited);
		} else if (type instanceof TypeVariable<?>) {
			TypeVariable<?> typeVariable = (TypeVariable<?>) type;
			sb.append(typeVariable.getName());

			// prevent cycles in case: <T extends List<T>>

			if (!visited.contains(type)) {
				visited.add(type);
				sb.append(" extends ");
				boolean first = true;
				for (Type bound : typeVariable.getBounds()) {
					if (first) {
						first = false;
					} else {
						sb.append(" & ");
					}
					typeToString(sb, bound, visited);
				}
				visited.remove(type);
			}
		} else if (type instanceof GenericArrayType) {
			GenericArrayType genericArrayType = (GenericArrayType) type;
			typeToString(genericArrayType.getGenericComponentType());
			sb.append(genericArrayType.getGenericComponentType());
			sb.append("[]");
		} else if (type instanceof Class) {
			Class<?> typeClass = (Class<?>) type;
			sb.append(typeClass.getName());
		} else {
			throw new IllegalArgumentException("Unsupported type: " + type);
		}
	}

	// ---------------------------------------------------------------- annotations

	/**
	 * Reads annotation value. Returns null on error
	 * (e.g. when value name not found).
	 */
	public static Object readAnnotationValue(final Annotation annotation, final String name) {
		try {
			Method method  = annotation.annotationType().getDeclaredMethod(name);
			return method.invoke(annotation);
		} catch (Exception ignore) {
			return null;
		}
	}

	// ---------------------------------------------------------------- caller

	private static class ReflectUtilSecurityManager extends SecurityManager {
		public Class getCallerClass(final int callStackDepth) {
			return getClassContext()[callStackDepth + 1];
		}
	}

	private static ReflectUtilSecurityManager SECURITY_MANAGER;

	static {
		try {
			SECURITY_MANAGER = new ReflectUtilSecurityManager();
		} catch (Exception ex) {
			SECURITY_MANAGER = null;
		}
	}

	/**
	 * Emulates Reflection.getCallerClass using standard API.
	 * This implementation uses custom SecurityManager
	 * and it is the fastest. Other implementations are:
	 *

	 *
new Throwable().getStackTrace()[callStackDepth]

	 *
Thread.currentThread().getStackTrace()[callStackDepth] (the slowest)

	 *

	 *

	 * In case when usage of SecurityManager is not allowed,
	 * this method fails back to the second implementation.
	 *

	 * Note that original Reflection.getCallerClass is way faster
	 * then any emulation.
	 */
	public static Class getCallerClass(int framesToSkip) {
		if (SECURITY_MANAGER != null) {
			return SECURITY_MANAGER.getCallerClass(framesToSkip);
		}

		StackTraceElement[] stackTraceElements = new Throwable().getStackTrace();

		if (framesToSkip >= 2) {
			framesToSkip += 4;
		}

		String className = stackTraceElements[framesToSkip].getClassName();

		try {
			return Thread.currentThread().getContextClassLoader().loadClass(className);
		} catch (ClassNotFoundException cnfex) {
			throw new UnsupportedOperationException(className + " not found.");
		}
	}

	/**
	 * Smart variant of {@link #getCallerClass(int)} that skips all relevant Jodd calls.
	 * However, this one does not use the security manager.
	 */
	public static Class getCallerClass() {
		String className = null;
		StackTraceElement[] stackTraceElements = new Throwable().getStackTrace();

		for (StackTraceElement stackTraceElement : stackTraceElements) {
			className = stackTraceElement.getClassName();
			String methodName = stackTraceElement.getMethodName();

			if (methodName.equals("loadClass")) {
				if (className.contains(ClassLoaderStrategy.class.getSimpleName())) {
					continue;
				}
				if (className.equals(ClassLoaderUtil.class.getName())) {
					continue;
				}
			} else if (methodName.equals("getCallerClass")) {
				continue;
			}
			break;
		}

		try {
			return Thread.currentThread().getContextClassLoader().loadClass(className);
		} catch (ClassNotFoundException cnfex) {
			throw new UnsupportedOperationException(className + " not found.");
		}
	}

	// ---------------------------------------------------------------- enum

	/**
	 * Returns enum class or null if class is not an enum.
	 */
	public static Class findEnum(Class target) {
		if (target.isPrimitive()) {
			return null;
		}
		while (target != Object.class) {
			if (target.isEnum()) {
				return target;
			}

			target = target.getSuperclass();
		}

		return null;
	}

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

	/**
	 * Returns the class of the immediate subclass of the given parent class for
	 * the given object instance; or null if such immediate subclass cannot be
	 * uniquely identified for the given object instance.
	 */
	public static Class<?> childClassOf(final Class<?> parentClass, final Object instance) {

		if (instance == null || instance == Object.class) {
			return null;
		}

		if (parentClass != null) {
			if (parentClass.isInterface()) {
				return null;
			}
		}

		Class<?> childClass = instance.getClass();
		while (true) {
			Class<?> parent = childClass.getSuperclass();
			if (parent == parentClass) {
				return childClass;
			}
			if (parent == null) {
				return null;
			}
			childClass = parent;
		}
	}

	/**
	 * Returns the jar file from which the given class is loaded; or null
	 * if no such jar file can be located.
	 */
	public static JarFile jarFileOf(final Class<?> klass) {
		URL url = klass.getResource(
			"/" + klass.getName().replace('.', '/') + ".class");

		if (url == null) {
			return null;
		}

		String s = url.getFile();
		int beginIndex = s.indexOf("file:") + "file:".length();
		int endIndex = s.indexOf(".jar!");
		if (endIndex == -1) {
			return null;
		}

		endIndex += ".jar".length();
		String f = s.substring(beginIndex, endIndex);
		// decode URL string - it may contain encoded chars (e.g. whitespaces) which are not supported for file-instances
		f = URLDecoder.decode(f, "UTF-8");
		File file = new File(f);

		try {
			return file.exists() ? new JarFile(file) : null;
		} catch (IOException e) {
			throw new IllegalStateException(e);
		}
	}

	// ---------------------------------------------------------------- class names

	/**
	 * Resolves class file name from class name by replacing dot's with '/' separator
	 * and adding class extension at the end. If array, component type is returned.
	 */
	public static String convertClassNameToFileName(Class clazz) {
		if (clazz.isArray()) {
			clazz = clazz.getComponentType();
		}
		return convertClassNameToFileName(clazz.getName());
	}

	/**
	 * Resolves class file name from class name by replacing dot's with '/' separator.
	 */
	public static String convertClassNameToFileName(final String className) {
		return className.replace('.', '/') + ".class";
	}

	/**
	 * Returns short class name: packages are replaces with single letter.
	 */
	public static String getShortClassName(final Class clazz) {
		return getShortClassName(clazz, 1);
	}
	public static String getShortClassName(final Class clazz, final int shortUpTo) {
		final String[] chunks = StringUtil.splitc(clazz.getName(), '.');
		final StringBand stringBand = new StringBand(chunks.length);
		int ndx = chunks.length - shortUpTo;
		if (ndx < 0) {
			ndx = 0;
		}

		for (int i = 0; i < ndx; i++) {
			if (i > 0) {
				stringBand.append('.');
			}
			stringBand.append(chunks[i].charAt(0));
		}

		for (int i = ndx; i < chunks.length; i++) {
			if (i > 0) {
				stringBand.append('.');
			}
			stringBand.append(chunks[i]);
		}
		return stringBand.toString();
	}

	// ---------------------------------------------------------------- kotlin

	/**
	 * Returns {@code true} if type is a Kotlin class.
	 */
	public static boolean isKotlinClass(final Class type) {
		final Annotation[] annotations = type.getAnnotations();
		for (Annotation annotation : annotations) {
			if (annotation.annotationType().getName().equals("kotlin.Metadata")) {
				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.db.type;

import java.sql.Types;

/**
 * Some java.sql.Types utilities.
 */
public class TypesUtil {

	/**
	 * Returns true if type is some integer-like type: INTEGER, SMALLINT, TINYINT, BIT.
	 */
	public static boolean isIntegerType(final int type) {
		return (type == Types.INTEGER) || (type == Types.SMALLINT) || (type == Types.TINYINT) || (type == Types.BIT);
	}

	/**
	 * Returns true if type is some string-like type: CHAR, VARCHAR.
	 */
	public static boolean isStringType(final int type) {
		return (type == Types.VARCHAR) || (type == Types.CHAR);
	}
}


// 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;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Thread utilities.
 */
public class ThreadUtil {

	/**
	 * Puts a thread to sleep, without throwing an InterruptedException.
	 *
	 * @param ms     the length of time to sleep in milliseconds
	 */
	public static void sleep(final long ms) {
		try {
			Thread.sleep(ms);
		} catch (InterruptedException iex) {
			Thread.currentThread().interrupt();
		}
	}

	/**
	 * Puts a thread to sleep forever.
	 */
	public static void sleep() {
		try {
			Thread.sleep(Long.MAX_VALUE);
		} catch (InterruptedException iex) {
			Thread.currentThread().interrupt();
		}
	}

	// ---------------------------------------------------------------- synchronization

	/**
	 * Waits for a object for synchronization purposes.
	 */
	public static void wait(final Object obj) {
		synchronized (obj) {
			try {
				obj.wait();
			} catch (InterruptedException inex) {
				Thread.currentThread().interrupt();
			}
		}
	}

	/**
	 * Waits for a object or a timeout for synchronization purposes.
	 */
	public static void wait(final Object obj, final long timeout) {
		synchronized (obj) {
			try {
				obj.wait(timeout);
			} catch (InterruptedException inex) {
				Thread.currentThread().interrupt();
			}
		}
	}

	/**
	 * Notifies an object for synchronization purposes.
	 */
	public static void notify(final Object obj){
		synchronized (obj) {
			obj.notify();
		}
	}

	/**
	 * Notifies an object for synchronization purposes.
	 */
	public static void notifyAll(final Object obj){
		synchronized (obj) {
			obj.notifyAll();
		}
	}

	// ---------------------------------------------------------------- join

	public static void join(final Thread thread) {
		try {
			thread.join();
		} catch (InterruptedException inex) {
			Thread.currentThread().interrupt();
		}
	}

	public static void join(final Thread thread, final long millis) {
		try {
			thread.join(millis);
		} catch (InterruptedException inex) {
			Thread.currentThread().interrupt();
		}
	}

	public static void join(final Thread thread, final long millis, final int nanos) {
		try {
			thread.join(millis, nanos);
		} catch (InterruptedException inex) {
			Thread.currentThread().interrupt();
		}
	}

	// ---------------------------------------------------------------- pool

	/**
	 * Creates new daemon thread factory.
	 */
	public static ThreadFactory daemonThreadFactory(final String name) {
		return daemonThreadFactory(name, Thread.NORM_PRIORITY);
	}
	/**
	 * Creates new daemon thread factory.
	 */
	public static ThreadFactory daemonThreadFactory(final String name, final int priority) {
		return new ThreadFactory() {
			private AtomicInteger count = new AtomicInteger();

			@Override
			public Thread newThread(final Runnable r) {
				Thread thread = new Thread(r);
				thread.setName(name + '-' + count.incrementAndGet());
				thread.setDaemon(true);
				thread.setPriority(priority);
				return thread;
			}
		};
	}

}


// 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.mutable;

import java.util.function.Supplier;

/**
 * Holder of a value that is computed lazy.
 */
public class LazyValue<T> implements Supplier<T> {

	private Supplier<T> supplier;
	private volatile boolean initialized;
	private T value;

	/**
	 * Creates new instance of LazyValue.
	 */
	public static <T> LazyValue<T> of(final Supplier<T> supplier) {
		return new LazyValue<>(supplier);
	}

	private LazyValue(final Supplier<T> supplier) {
		this.supplier = supplier;
	}

	/**
	 * Returns the value. Value will be computed on first call.
	 */
	@Override
	public T get() {
		if (!initialized) {
			synchronized (this) {
				if (!initialized) {
					final T t = supplier.get();
					value = t;
					initialized = true;
					supplier = null;
					return t;
				}
			}
		}
		return value;
	}

}


// ASM: a very small and fast Java bytecode manipulation framework
// Copyright (c) 2000-2011 INRIA, France Telecom
// 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.
// 3. Neither the name of the copyright holders nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// 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 OWNER 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.asm7;

/**
 * A visitor to visit a Java method. The methods of this class must be called in the following
 * order: ( {@code visitParameter} )* [ {@code visitAnnotationDefault} ] ( {@code visitAnnotation} |
 * {@code visitAnnotableParameterCount} | {@code visitParameterAnnotation} {@code
 * visitTypeAnnotation} | {@code visitAttribute} )* [ {@code visitCode} ( {@code visitFrame} |
 * {@code visitXInsn} | {@code visitLabel} | {@code visitInsnAnnotation} | {@code
 * visitTryCatchBlock} | {@code visitTryCatchAnnotation} | {@code visitLocalVariable} | {@code
 * visitLocalVariableAnnotation} | {@code visitLineNumber} )* {@code visitMaxs} ] {@code visitEnd}.
 * In addition, the {@code visitXInsn} and {@code visitLabel} methods must be called in the
 * sequential order of the bytecode instructions of the visited code, {@code visitInsnAnnotation}
 * must be called after the annotated instruction, {@code visitTryCatchBlock} must be called
 * before the labels passed as arguments have been visited, {@code
 * visitTryCatchBlockAnnotation} must be called after the corresponding try catch block has
 * been visited, and the {@code visitLocalVariable}, {@code visitLocalVariableAnnotation} and {@code
 * visitLineNumber} methods must be called after the labels passed as arguments have been
 * visited.
 *
 * @author Eric Bruneton
 */
public abstract class MethodVisitor {

  private static final String REQUIRES_ASM5 = "This feature requires ASM5";

  /**
   * The ASM API version implemented by this visitor. The value of this field must be one of {@link
   * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
   */
  protected final int api;

  /** The method visitor to which this visitor must delegate method calls. May be null. */
  protected MethodVisitor mv;

  /**
   * Constructs a new {@link MethodVisitor}.
   *
   * @param api the ASM API version implemented by this visitor. Must be one of {@link
   *     Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
   */
  public MethodVisitor(final int api) {
    this(api, null);
  }

  /**
   * Constructs a new {@link MethodVisitor}.
   *
   * @param api the ASM API version implemented by this visitor. Must be one of {@link
   *     Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
   * @param methodVisitor the method visitor to which this visitor must delegate method calls. May
   *     be null.
   */
  public MethodVisitor(final int api, final MethodVisitor methodVisitor) {
    if (api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4 && api != Opcodes.ASM7) {
      throw new IllegalArgumentException();
    }
    this.api = api;
    this.mv = methodVisitor;
  }

  // -----------------------------------------------------------------------------------------------
  // Parameters, annotations and non standard attributes
  // -----------------------------------------------------------------------------------------------

  /**
   * Visits a parameter of this method.
   *
   * @param name parameter name or null if none is provided.
   * @param access the parameter's access flags, only {@code ACC_FINAL}, {@code ACC_SYNTHETIC}
   *     or/and {@code ACC_MANDATED} are allowed (see {@link Opcodes}).
   */
  public void visitParameter(final String name, final int access) {
    if (api < Opcodes.ASM5) {
      throw new UnsupportedOperationException(REQUIRES_ASM5);
    }
    if (mv != null) {
      mv.visitParameter(name, access);
    }
  }

  /**
   * Visits the default value of this annotation interface method.
   *
   * @return a visitor to the visit the actual default value of this annotation interface method, or
   *     {@literal null} if this visitor is not interested in visiting this default value. The
   *     'name' parameters passed to the methods of this annotation visitor are ignored. Moreover,
   *     exacly one visit method must be called on this annotation visitor, followed by visitEnd.
   */
  public AnnotationVisitor visitAnnotationDefault() {
    if (mv != null) {
      return mv.visitAnnotationDefault();
    }
    return null;
  }

  /**
   * Visits an annotation of this method.
   *
   * @param descriptor the class descriptor of the annotation class.
   * @param visible {@literal true} if the annotation is visible at runtime.
   * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
   *     interested in visiting this annotation.
   */
  public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
    if (mv != null) {
      return mv.visitAnnotation(descriptor, visible);
    }
    return null;
  }

  /**
   * Visits an annotation on a type in the method signature.
   *
   * @param typeRef a reference to the annotated type. The sort of this type reference must be
   *     {@link TypeReference#METHOD_TYPE_PARAMETER}, {@link
   *     TypeReference#METHOD_TYPE_PARAMETER_BOUND}, {@link TypeReference#METHOD_RETURN}, {@link
   *     TypeReference#METHOD_RECEIVER}, {@link TypeReference#METHOD_FORMAL_PARAMETER} or {@link
   *     TypeReference#THROWS}. See {@link TypeReference}.
   * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
   *     static inner type within 'typeRef'. May be {@literal null} if the annotation targets
   *     'typeRef' as a whole.
   * @param descriptor the class descriptor of the annotation class.
   * @param visible {@literal true} if the annotation is visible at runtime.
   * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
   *     interested in visiting this annotation.
   */
  public AnnotationVisitor visitTypeAnnotation(
      final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
    if (api < Opcodes.ASM5) {
      throw new UnsupportedOperationException(REQUIRES_ASM5);
    }
    if (mv != null) {
      return mv.visitTypeAnnotation(typeRef, typePath, descriptor, visible);
    }
    return null;
  }

  /**
   * Visits the number of method parameters that can have annotations. By default (i.e. when this
   * method is not called), all the method parameters defined by the method descriptor can have
   * annotations.
   *
   * @param parameterCount the number of method parameters than can have annotations. This number
   *     must be less or equal than the number of parameter types in the method descriptor. It can
   *     be strictly less when a method has synthetic parameters and when these parameters are
   *     ignored when computing parameter indices for the purpose of parameter annotations (see
   *     https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.18).
   * @param visible {@literal true} to define the number of method parameters that can have
   *     annotations visible at runtime, {@literal false} to define the number of method parameters
   *     that can have annotations invisible at runtime.
   */
  public void visitAnnotableParameterCount(final int parameterCount, final boolean visible) {
    if (mv != null) {
      mv.visitAnnotableParameterCount(parameterCount, visible);
    }
  }

  /**
   * Visits an annotation of a parameter this method.
   *
   * @param parameter the parameter index. This index must be strictly smaller than the number of
   *     parameters in the method descriptor, and strictly smaller than the parameter count
   *     specified in {@link #visitAnnotableParameterCount}. Important note: a parameter index i
   *     is not required to correspond to the i'th parameter descriptor in the method
   *     descriptor, in particular in case of synthetic parameters (see
   *     https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.18).
   * @param descriptor the class descriptor of the annotation class.
   * @param visible {@literal true} if the annotation is visible at runtime.
   * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
   *     interested in visiting this annotation.
   */
  public AnnotationVisitor visitParameterAnnotation(
      final int parameter, final String descriptor, final boolean visible) {
    if (mv != null) {
      return mv.visitParameterAnnotation(parameter, descriptor, visible);
    }
    return null;
  }

  /**
   * Visits a non standard attribute of this method.
   *
   * @param attribute an attribute.
   */
  public void visitAttribute(final Attribute attribute) {
    if (mv != null) {
      mv.visitAttribute(attribute);
    }
  }

  /** Starts the visit of the method's code, if any (i.e. non abstract method). */
  public void visitCode() {
    if (mv != null) {
      mv.visitCode();
    }
  }

  /**
   * Visits the current state of the local variables and operand stack elements. This method must(*)
   * be called just before any instruction i that follows an unconditional branch
   * instruction such as GOTO or THROW, that is the target of a jump instruction, or that starts an
   * exception handler block. The visited types must describe the values of the local variables and
   * of the operand stack elements just before i is executed.

   *

   * (*) this is mandatory only for classes whose version is greater than or equal to {@link
   * Opcodes#V1_6}.

   *

   * The frames of a method must be given either in expanded form, or in compressed form (all frames
   * must use the same format, i.e. you must not mix expanded and compressed frames within a single
   * method):
   *
   *

   *
In expanded form, all frames must have the F_NEW type.
   *
In compressed form, frames are basically "deltas" from the state of the previous frame:
   *

   *
{@link Opcodes#F_SAME} representing frame with exactly the same locals as the
   *             previous frame and with the empty stack.
   *
{@link Opcodes#F_SAME1} representing frame with exactly the same locals as the
   *             previous frame and with single value on the stack ( numStack is 1 and
   *             stack[0] contains value for the type of the stack item).
   *
{@link Opcodes#F_APPEND} representing frame with current locals are the same as the
   *             locals in the previous frame, except that additional locals are defined (
   *             numLocal is 1, 2 or 3 and local elements contains values
   *             representing added types).
   *
{@link Opcodes#F_CHOP} representing frame with current locals are the same as the
   *             locals in the previous frame, except that the last 1-3 locals are absent and with
   *             the empty stack (numLocal is 1, 2 or 3).
   *
{@link Opcodes#F_FULL} representing complete frame data.
   *

   *

   *
   *

   * In both cases the first frame, corresponding to the method's parameters and access flags, is
   * implicit and must not be visited. Also, it is illegal to visit two or more frames for the same
   * code location (i.e., at least one instruction must be visited between two calls to visitFrame).
   *
   * @param type the type of this stack map frame. Must be {@link Opcodes#F_NEW} for expanded
   *     frames, or {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND}, {@link Opcodes#F_CHOP}, {@link
   *     Opcodes#F_SAME} or {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for compressed frames.
   * @param numLocal the number of local variables in the visited frame.
   * @param local the local variable types in this frame. This array must not be modified. Primitive
   *     types are represented by {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link
   *     Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL} or
   *     {@link Opcodes#UNINITIALIZED_THIS} (long and double are represented by a single element).
   *     Reference types are represented by String objects (representing internal names), and
   *     uninitialized types by Label objects (this label designates the NEW instruction that
   *     created this uninitialized value).
   * @param numStack the number of operand stack elements in the visited frame.
   * @param stack the operand stack types in this frame. This array must not be modified. Its
   *     content has the same format as the "local" array.
   * @throws IllegalStateException if a frame is visited just after another one, without any
   *     instruction between the two (unless this frame is a Opcodes#F_SAME frame, in which case it
   *     is silently ignored).
   */
  public void visitFrame(
      final int type,
      final int numLocal,
      final Object[] local,
      final int numStack,
      final Object[] stack) {
    if (mv != null) {
      mv.visitFrame(type, numLocal, local, numStack, stack);
    }
  }

  // -----------------------------------------------------------------------------------------------
  // Normal instructions
  // -----------------------------------------------------------------------------------------------

  /**
   * Visits a zero operand instruction.
   *
   * @param opcode the opcode of the instruction to be visited. This opcode is either NOP,
   *     ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5,
   *     LCONST_0, LCONST_1, FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD, LALOAD,
   *     FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IASTORE, LASTORE, FASTORE, DASTORE,
   *     AASTORE, BASTORE, CASTORE, SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2,
   *     SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL, DMUL, IDIV, LDIV,
   *     FDIV, DDIV, IREM, LREM, FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR, IUSHR,
   *     LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I,
   *     D2L, D2F, I2B, I2C, I2S, LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN,
   *     DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER, or MONITOREXIT.
   */
  public void visitInsn(final int opcode) {
    if (mv != null) {
      mv.visitInsn(opcode);
    }
  }

  /**
   * Visits an instruction with a single int operand.
   *
   * @param opcode the opcode of the instruction to be visited. This opcode is either BIPUSH, SIPUSH
   *     or NEWARRAY.
   * @param operand the operand of the instruction to be visited.

   *     When opcode is BIPUSH, operand value should be between Byte.MIN_VALUE and Byte.MAX_VALUE.
   *

   *     When opcode is SIPUSH, operand value should be between Short.MIN_VALUE and Short.MAX_VALUE.
   *

   *     When opcode is NEWARRAY, operand value should be one of {@link Opcodes#T_BOOLEAN}, {@link
   *     Opcodes#T_CHAR}, {@link Opcodes#T_FLOAT}, {@link Opcodes#T_DOUBLE}, {@link Opcodes#T_BYTE},
   *     {@link Opcodes#T_SHORT}, {@link Opcodes#T_INT} or {@link Opcodes#T_LONG}.
   */
  public void visitIntInsn(final int opcode, final int operand) {
    if (mv != null) {
      mv.visitIntInsn(opcode, operand);
    }
  }

  /**
   * Visits a local variable instruction. A local variable instruction is an instruction that loads
   * or stores the value of a local variable.
   *
   * @param opcode the opcode of the local variable instruction to be visited. This opcode is either
   *     ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET.
   * @param var the operand of the instruction to be visited. This operand is the index of a local
   *     variable.
   */
  public void visitVarInsn(final int opcode, final int var) {
    if (mv != null) {
      mv.visitVarInsn(opcode, var);
    }
  }

  /**
   * Visits a type instruction. A type instruction is an instruction that takes the internal name of
   * a class as parameter.
   *
   * @param opcode the opcode of the type instruction to be visited. This opcode is either NEW,
   *     ANEWARRAY, CHECKCAST or INSTANCEOF.
   * @param type the operand of the instruction to be visited. This operand must be the internal
   *     name of an object or array class (see {@link Type#getInternalName()}).
   */
  public void visitTypeInsn(final int opcode, final String type) {
    if (mv != null) {
      mv.visitTypeInsn(opcode, type);
    }
  }

  /**
   * Visits a field instruction. A field instruction is an instruction that loads or stores the
   * value of a field of an object.
   *
   * @param opcode the opcode of the type instruction to be visited. This opcode is either
   *     GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD.
   * @param owner the internal name of the field's owner class (see {@link Type#getInternalName()}).
   * @param name the field's name.
   * @param descriptor the field's descriptor (see {@link Type}).
   */
  public void visitFieldInsn(
      final int opcode, final String owner, final String name, final String descriptor) {
    if (mv != null) {
      mv.visitFieldInsn(opcode, owner, name, descriptor);
    }
  }

  /**
   * Visits a method instruction. A method instruction is an instruction that invokes a method.
   *
   * @param opcode the opcode of the type instruction to be visited. This opcode is either
   *     INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE.
   * @param owner the internal name of the method's owner class (see {@link
   *     Type#getInternalName()}).
   * @param name the method's name.
   * @param descriptor the method's descriptor (see {@link Type}).
   * @deprecated use {@link #visitMethodInsn(int, String, String, String, boolean)} instead.
   */
  @Deprecated
  public void visitMethodInsn(
      final int opcode, final String owner, final String name, final String descriptor) {
    if (api >= Opcodes.ASM5) {
      boolean isInterface = opcode == Opcodes.INVOKEINTERFACE;
      visitMethodInsn(opcode, owner, name, descriptor, isInterface);
      return;
    }
    if (mv != null) {
      mv.visitMethodInsn(opcode, owner, name, descriptor);
    }
  }

  /**
   * Visits a method instruction. A method instruction is an instruction that invokes a method.
   *
   * @param opcode the opcode of the type instruction to be visited. This opcode is either
   *     INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE.
   * @param owner the internal name of the method's owner class (see {@link
   *     Type#getInternalName()}).
   * @param name the method's name.
   * @param descriptor the method's descriptor (see {@link Type}).
   * @param isInterface if the method's owner class is an interface.
   */
  public void visitMethodInsn(
      final int opcode,
      final String owner,
      final String name,
      final String descriptor,
      final boolean isInterface) {
    if (api < Opcodes.ASM5) {
      if (isInterface != (opcode == Opcodes.INVOKEINTERFACE)) {
        throw new IllegalArgumentException("INVOKESPECIAL/STATIC on interfaces requires ASM5");
      }
      visitMethodInsn(opcode, owner, name, descriptor);
      return;
    }
    if (mv != null) {
      mv.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
    }
  }

  /**
   * Visits an invokedynamic instruction.
   *
   * @param name the method's name.
   * @param descriptor the method's descriptor (see {@link Type}).
   * @param bootstrapMethodHandle the bootstrap method.
   * @param bootstrapMethodArguments the bootstrap method constant arguments. Each argument must be
   *     an {@link Integer}, {@link Float}, {@link Long}, {@link Double}, {@link String}, {@link
   *     Type}, {@link Handle} or {@link ConstantDynamic} value. This method is allowed to modify
   *     the content of the array so a caller should expect that this array may change.
   */
  public void visitInvokeDynamicInsn(
      final String name,
      final String descriptor,
      final Handle bootstrapMethodHandle,
      final Object... bootstrapMethodArguments) {
    if (api < Opcodes.ASM5) {
      throw new UnsupportedOperationException(REQUIRES_ASM5);
    }
    if (mv != null) {
      mv.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
    }
  }

  /**
   * Visits a jump instruction. A jump instruction is an instruction that may jump to another
   * instruction.
   *
   * @param opcode the opcode of the type instruction to be visited. This opcode is either IFEQ,
   *     IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT,
   *     IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL.
   * @param label the operand of the instruction to be visited. This operand is a label that
   *     designates the instruction to which the jump instruction may jump.
   */
  public void visitJumpInsn(final int opcode, final Label label) {
    if (mv != null) {
      mv.visitJumpInsn(opcode, label);
    }
  }

  /**
   * Visits a label. A label designates the instruction that will be visited just after it.
   *
   * @param label a {@link Label} object.
   */
  public void visitLabel(final Label label) {
    if (mv != null) {
      mv.visitLabel(label);
    }
  }

  // -----------------------------------------------------------------------------------------------
  // Special instructions
  // -----------------------------------------------------------------------------------------------

  /**
   * Visits a LDC instruction. Note that new constant types may be added in future versions of the
   * Java Virtual Machine. To easily detect new constant types, implementations of this method
   * should check for unexpected constant types, like this:
   *
   *

   * if (cst instanceof Integer) {
   *     // ...
   * } else if (cst instanceof Float) {
   *     // ...
   * } else if (cst instanceof Long) {
   *     // ...
   * } else if (cst instanceof Double) {
   *     // ...
   * } else if (cst instanceof String) {
   *     // ...
   * } else if (cst instanceof Type) {
   *     int sort = ((Type) cst).getSort();
   *     if (sort == Type.OBJECT) {
   *         // ...
   *     } else if (sort == Type.ARRAY) {
   *         // ...
   *     } else if (sort == Type.METHOD) {
   *         // ...
   *     } else {
   *         // throw an exception
   *     }
   * } else if (cst instanceof Handle) {
   *     // ...
   * } else if (cst instanceof ConstantDynamic) {
   *     // ...
   * } else {
   *     // throw an exception
   * }
   *

   *
   * @param value the constant to be loaded on the stack. This parameter must be a non null {@link
   *     Integer}, a {@link Float}, a {@link Long}, a {@link Double}, a {@link String}, a {@link
   *     Type} of OBJECT or ARRAY sort for {@code .class} constants, for classes whose version is
   *     49, a {@link Type} of METHOD sort for MethodType, a {@link Handle} for MethodHandle
   *     constants, for classes whose version is 51 or a {@link ConstantDynamic} for a constant
   *     dynamic for classes whose version is 55.
   */
  public void visitLdcInsn(final Object value) {
    if (api < Opcodes.ASM5
        && (value instanceof Handle
            || (value instanceof Type && ((Type) value).getSort() == Type.METHOD))) {
      throw new UnsupportedOperationException(REQUIRES_ASM5);
    }
    if (api != Opcodes.ASM7 && value instanceof ConstantDynamic) {
      throw new UnsupportedOperationException("This feature requires ASM7");
    }
    if (mv != null) {
      mv.visitLdcInsn(value);
    }
  }

  /**
   * Visits an IINC instruction.
   *
   * @param var index of the local variable to be incremented.
   * @param increment amount to increment the local variable by.
   */
  public void visitIincInsn(final int var, final int increment) {
    if (mv != null) {
      mv.visitIincInsn(var, increment);
    }
  }

  /**
   * Visits a TABLESWITCH instruction.
   *
   * @param min the minimum key value.
   * @param max the maximum key value.
   * @param dflt beginning of the default handler block.
   * @param labels beginnings of the handler blocks. {@code labels[i]} is the beginning of the
   *     handler block for the {@code min + i} key.
   */
  public void visitTableSwitchInsn(
      final int min, final int max, final Label dflt, final Label... labels) {
    if (mv != null) {
      mv.visitTableSwitchInsn(min, max, dflt, labels);
    }
  }

  /**
   * Visits a LOOKUPSWITCH instruction.
   *
   * @param dflt beginning of the default handler block.
   * @param keys the values of the keys.
   * @param labels beginnings of the handler blocks. {@code labels[i]} is the beginning of the
   *     handler block for the {@code keys[i]} key.
   */
  public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
    if (mv != null) {
      mv.visitLookupSwitchInsn(dflt, keys, labels);
    }
  }

  /**
   * Visits a MULTIANEWARRAY instruction.
   *
   * @param descriptor an array type descriptor (see {@link Type}).
   * @param numDimensions the number of dimensions of the array to allocate.
   */
  public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) {
    if (mv != null) {
      mv.visitMultiANewArrayInsn(descriptor, numDimensions);
    }
  }

  /**
   * Visits an annotation on an instruction. This method must be called just after the
   * annotated instruction. It can be called several times for the same instruction.
   *
   * @param typeRef a reference to the annotated type. The sort of this type reference must be
   *     {@link TypeReference#INSTANCEOF}, {@link TypeReference#NEW}, {@link
   *     TypeReference#CONSTRUCTOR_REFERENCE}, {@link TypeReference#METHOD_REFERENCE}, {@link
   *     TypeReference#CAST}, {@link TypeReference#CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link
   *     TypeReference#METHOD_INVOCATION_TYPE_ARGUMENT}, {@link
   *     TypeReference#CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link
   *     TypeReference#METHOD_REFERENCE_TYPE_ARGUMENT}. See {@link TypeReference}.
   * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
   *     static inner type within 'typeRef'. May be {@literal null} if the annotation targets
   *     'typeRef' as a whole.
   * @param descriptor the class descriptor of the annotation class.
   * @param visible {@literal true} if the annotation is visible at runtime.
   * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
   *     interested in visiting this annotation.
   */
  public AnnotationVisitor visitInsnAnnotation(
      final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
    if (api < Opcodes.ASM5) {
      throw new UnsupportedOperationException(REQUIRES_ASM5);
    }
    if (mv != null) {
      return mv.visitInsnAnnotation(typeRef, typePath, descriptor, visible);
    }
    return null;
  }

  // -----------------------------------------------------------------------------------------------
  // Exceptions table entries, debug information, max stack and max locals
  // -----------------------------------------------------------------------------------------------

  /**
   * Visits a try catch block.
   *
   * @param start the beginning of the exception handler's scope (inclusive).
   * @param end the end of the exception handler's scope (exclusive).
   * @param handler the beginning of the exception handler's code.
   * @param type the internal name of the type of exceptions handled by the handler, or {@literal
   *     null} to catch any exceptions (for "finally" blocks).
   * @throws IllegalArgumentException if one of the labels has already been visited by this visitor
   *     (by the {@link #visitLabel} method).
   */
  public void visitTryCatchBlock(
      final Label start, final Label end, final Label handler, final String type) {
    if (mv != null) {
      mv.visitTryCatchBlock(start, end, handler, type);
    }
  }

  /**
   * Visits an annotation on an exception handler type. This method must be called after the
   * {@link #visitTryCatchBlock} for the annotated exception handler. It can be called several times
   * for the same exception handler.
   *
   * @param typeRef a reference to the annotated type. The sort of this type reference must be
   *     {@link TypeReference#EXCEPTION_PARAMETER}. See {@link TypeReference}.
   * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
   *     static inner type within 'typeRef'. May be {@literal null} if the annotation targets
   *     'typeRef' as a whole.
   * @param descriptor the class descriptor of the annotation class.
   * @param visible {@literal true} if the annotation is visible at runtime.
   * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
   *     interested in visiting this annotation.
   */
  public AnnotationVisitor visitTryCatchAnnotation(
      final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
    if (api < Opcodes.ASM5) {
      throw new UnsupportedOperationException(REQUIRES_ASM5);
    }
    if (mv != null) {
      return mv.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible);
    }
    return null;
  }

  /**
   * Visits a local variable declaration.
   *
   * @param name the name of a local variable.
   * @param descriptor the type descriptor of this local variable.
   * @param signature the type signature of this local variable. May be {@literal null} if the local
   *     variable type does not use generic types.
   * @param start the first instruction corresponding to the scope of this local variable
   *     (inclusive).
   * @param end the last instruction corresponding to the scope of this local variable (exclusive).
   * @param index the local variable's index.
   * @throws IllegalArgumentException if one of the labels has not already been visited by this
   *     visitor (by the {@link #visitLabel} method).
   */
  public void visitLocalVariable(
      final String name,
      final String descriptor,
      final String signature,
      final Label start,
      final Label end,
      final int index) {
    if (mv != null) {
      mv.visitLocalVariable(name, descriptor, signature, start, end, index);
    }
  }

  /**
   * Visits an annotation on a local variable type.
   *
   * @param typeRef a reference to the annotated type. The sort of this type reference must be
   *     {@link TypeReference#LOCAL_VARIABLE} or {@link TypeReference#RESOURCE_VARIABLE}. See {@link
   *     TypeReference}.
   * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
   *     static inner type within 'typeRef'. May be {@literal null} if the annotation targets
   *     'typeRef' as a whole.
   * @param start the fist instructions corresponding to the continuous ranges that make the scope
   *     of this local variable (inclusive).
   * @param end the last instructions corresponding to the continuous ranges that make the scope of
   *     this local variable (exclusive). This array must have the same size as the 'start' array.
   * @param index the local variable's index in each range. This array must have the same size as
   *     the 'start' array.
   * @param descriptor the class descriptor of the annotation class.
   * @param visible {@literal true} if the annotation is visible at runtime.
   * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
   *     interested in visiting this annotation.
   */
  public AnnotationVisitor visitLocalVariableAnnotation(
      final int typeRef,
      final TypePath typePath,
      final Label[] start,
      final Label[] end,
      final int[] index,
      final String descriptor,
      final boolean visible) {
    if (api < Opcodes.ASM5) {
      throw new UnsupportedOperationException(REQUIRES_ASM5);
    }
    if (mv != null) {
      return mv.visitLocalVariableAnnotation(
          typeRef, typePath, start, end, index, descriptor, visible);
    }
    return null;
  }

  /**
   * Visits a line number declaration.
   *
   * @param line a line number. This number refers to the source file from which the class was
   *     compiled.
   * @param start the first instruction corresponding to this line number.
   * @throws IllegalArgumentException if {@code start} has not already been visited by this visitor
   *     (by the {@link #visitLabel} method).
   */
  public void visitLineNumber(final int line, final Label start) {
    if (mv != null) {
      mv.visitLineNumber(line, start);
    }
  }

  /**
   * Visits the maximum stack size and the maximum number of local variables of the method.
   *
   * @param maxStack maximum stack size of the method.
   * @param maxLocals maximum number of local variables for the method.
   */
  public void visitMaxs(final int maxStack, final int maxLocals) {
    if (mv != null) {
      mv.visitMaxs(maxStack, maxLocals);
    }
  }

  /**
   * Visits the end of the method. This method, which is the last one to be called, is used to
   * inform the visitor that all the annotations and attributes of the method have been visited.
   */
  public void visitEnd() {
    if (mv != null) {
      mv.visitEnd();
    }
  }
}


// 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.props;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Map;
import java.util.Properties;

/**
 * Converter for Java Properties to Jodd Props format.
 */
class PropertiesToProps {

	/**
	 * Convert Java Properties to Jodd Props format
	 *
	 * @param writer     Writer to write Props formatted file content to
	 * @param properties Properties to convert to Props format
	 * @param profiles   Properties per profile to convert and add to the Props format
	 * @throws IOException On any I/O error when writing to the writer
	 */
	void convertToWriter(final Writer writer, final Properties properties, final Map<String, Properties> profiles)
			throws IOException {
		final BufferedWriter bw = getBufferedWriter(writer);
		writeBaseAndProfileProperties(bw, properties, profiles);
		writeProfilePropertiesThatAreNotInTheBase(bw, properties, profiles);
		bw.flush();
	}

	private void writeProfilePropertiesThatAreNotInTheBase(final BufferedWriter bw, final Properties baseProperties,
														   final Map<String, Properties> profiles) throws IOException {

		for (final Map.Entry<String, Properties> entry : profiles.entrySet()) {
			final String profileName = entry.getKey();
			final Properties profileProperties = entry.getValue();

			for (final Object key : profileProperties.keySet()) {
				if (baseProperties.containsKey(key)) {
					continue;
				}

				final String keyString = key.toString();
				writeProfileProperty(bw, profileName, keyString, profileProperties.getProperty(keyString));
			}
		}
	}

	private BufferedWriter getBufferedWriter(final Writer writer) {
		final BufferedWriter bw;
		if (writer instanceof BufferedWriter) {
			bw = (BufferedWriter) writer;
		} else {
			bw = new BufferedWriter(writer);
		}
		return bw;
	}

	private void writeBaseAndProfileProperties(final BufferedWriter bw, final Properties baseProperties,
											   final Map<String, Properties> profiles) throws IOException {
		for (final Object key : baseProperties.keySet()) {
			final String keyString = key.toString();

			final String value = baseProperties.getProperty(keyString);
			writeBaseProperty(bw, keyString, value);
			writeProfilePropertiesOfKey(bw, keyString, profiles);
		}
	}

	private void writeProfilePropertiesOfKey(final BufferedWriter bw, final String key,
											 final Map<String, Properties> profiles) throws IOException {
		for (final Map.Entry<String, Properties> entry : profiles.entrySet()) {
			final Properties profileProperties = entry.getValue();
			if (!profileProperties.containsKey(key)) {
				continue;
			}
			final String profileName = entry.getKey();
			writeProfileProperty(bw, profileName, key, profileProperties.getProperty(key));
		}
	}

	private void writeProfileProperty(final BufferedWriter bw, final String profileName,
									  final String key, final String value)
			throws IOException {
		bw.write(key + '<' + profileName + '>' + '=' + value);
		bw.newLine();
	}

	private void writeBaseProperty(final BufferedWriter bw, final String key, final String value)
			throws IOException {
		bw.write(key + '=' + value);
		bw.newLine();
	}
}


// 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.io.upload.impl;

import jodd.io.FastByteArrayOutputStream;
import jodd.io.upload.FileUpload;
import jodd.io.upload.MultipartRequestInputStream;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * {@link FileUpload} that stores uploaded files in memory byte array.
 */
public class MemoryFileUpload extends FileUpload {

	MemoryFileUpload(final MultipartRequestInputStream input, final int maxFileSize) {
		super(input, maxFileSize);
	}

	// ---------------------------------------------------------------- logic

	protected byte[] data;

	/**
	 * Returns byte array containing uploaded file data.
	 */
	@Override
	public byte[] getFileContent() {
		return data;
	}

	/**
	 * Returns true as uploaded file is stored in memory.
	 */
	@Override
	public boolean isInMemory() {
		return true;
	}

	/**
	 * Returns byte array input stream.
	 */
	@Override
	public InputStream getFileInputStream() {
		return new ByteArrayInputStream(data);
	}

	/**
	 * Reads data from input stream into byte array and stores file size.
	 */
	@Override
	public void processStream() throws IOException {
		FastByteArrayOutputStream out = new FastByteArrayOutputStream();
		size = 0;
		if (maxFileSize == -1) {
			size += input.copyAll(out);
		} else {
			size += input.copyMax(out, maxFileSize + 1);		// one more byte to detect larger files
			if (size > maxFileSize) {
				fileTooBig = true;
				valid = false;
				input.skipToBoundary();
				return;
			}
		}
		data = out.toByteArray();
		size = data.length;
		valid = 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.db.oom;

import jodd.db.oom.mapper.ResultSetMapper;

import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * Internal result set iterator.
 */
class DbListIterator<T> implements Iterator<T> {

	protected final DbOomQuery query;
	protected final boolean entityAwareMode;
	protected final ResultSetMapper resultSetMapper;
	protected final Class[] types;
	protected final boolean closeOnEnd;

	// ---------------------------------------------------------------- ctors

	DbListIterator(final DbOomQuery query, final Class[] types, final boolean closeOnEnd) {
		this.query = query;
		this.resultSetMapper = query.executeAndBuildResultSetMapper();
		this.entityAwareMode = query.entityAwareMode;
		this.types = (types == null ? resultSetMapper.resolveTables() : types);
		this.closeOnEnd = closeOnEnd;
	}

	DbListIterator(final DbOomQuery query, final Class[] types, final ResultSetMapper resultSetMapper, final boolean closeOnEnd) {
		this.query = query;
		this.resultSetMapper = resultSetMapper;
		this.entityAwareMode = query.entityAwareMode;
		this.types = (types == null ? resultSetMapper.resolveTables() : types);
		this.closeOnEnd = closeOnEnd;
	}

	// ---------------------------------------------------------------- iterate

	protected T previousElement;
	protected T newElement;
	protected int count;
	protected boolean last;
	protected Boolean hasNext;

	@Override
	public void remove() {
		throw new UnsupportedOperationException();
	}

	/**
	 * Returns true if there is {@link #next() next} parsed object
	 * available.
	 */
	@Override
	public boolean hasNext() {
		if (hasNext == null) {
			hasNext = Boolean.valueOf(moveToNext());
		}
		return hasNext.booleanValue();
	}

	/**
	 * Returns next mapped object.
	 */
	@Override
	public T next() {
		if (hasNext == null) {
			hasNext = Boolean.valueOf(moveToNext());
		}
		if (hasNext == false) {
			throw new NoSuchElementException();
		}

		if (!entityAwareMode) {
			hasNext = null;
			return newElement;
		}

		count++;

		T result = previousElement;

		previousElement = newElement;

		hasNext = null;
		return result;
	}

	/**
	 * Moves to next element.
	 */
	private boolean moveToNext() {
		if (last) {
			// last has been set to true, so no more rows to iterate - close everything

			if (closeOnEnd) {
				query.close();
			} else {
				query.closeResultSet(resultSetMapper.getResultSet());
			}

			return false;
		}

		while (true) {

			if (!resultSetMapper.next()) {
				// no more rows, no more parsing, previousElement is the last one to iterate
				last = true;
				return entityAwareMode;
			}

			// parse row

			Object[] objects = resultSetMapper.parseObjects(types);

			Object row = query.resolveRowResults(objects);

			newElement = (T) row;

			if (entityAwareMode) {

				if (count == 0 && previousElement == null) {
					previousElement = newElement;

					continue;
				}

				if (previousElement != null && newElement != null) {
					boolean equals;

					if (newElement.getClass().isArray()) {
						equals = Arrays.equals((Object[]) previousElement, (Object[]) newElement);
					} else {
						equals = previousElement.equals(newElement);
					}

					if (equals) {
						continue;
					}
				}
			}

			break;
		}

		return true;
	}

}