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

package jodd.madvoc.component;

import jodd.cache.TypeCache;
import jodd.introspector.ClassDescriptor;
import jodd.introspector.ClassIntrospector;
import jodd.introspector.PropertyDescriptor;
import jodd.madvoc.MadvocException;
import jodd.madvoc.config.InjectionPoint;
import jodd.madvoc.config.ScopeData;
import jodd.madvoc.meta.In;
import jodd.madvoc.meta.Out;
import jodd.madvoc.meta.Scope;
import jodd.madvoc.scope.MadvocScope;
import jodd.petite.meta.PetiteInject;
import jodd.util.StringUtil;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * Inspector for {@link ScopeData}. It works with types and method parameters.
 */
public class ScopeDataInspector {

	@PetiteInject
	protected ScopeResolver scopeResolver;

	private final ScopeData NO_SCOPE_DATA;

	public ScopeDataInspector() {
		NO_SCOPE_DATA = new ScopeData(this, null, null);
	}

	// ---------------------------------------------------------------- detection

	/**
	 * Scans annotation and returns type of Madvoc annotations.
	 */
	public Class<? extends Annotation> detectAnnotationType(final Annotation[] annotations) {
		for (final Annotation annotation : annotations) {
			if (annotation instanceof In) {
				return annotation.annotationType();
			}
			else if (annotation instanceof Out) {
				return annotation.annotationType();
			}
		}
		return null;
	}

	// ---------------------------------------------------------------- inspect method

	/**
	 * Inspects {@link ScopeData} parameters for given method parameter information.
	 */
	public ScopeData inspectMethodParameterScopes(final String name, final Class type, final Annotation[] annotations) {
		In in = null;
		Out out = null;

		for (final Annotation annotation : annotations) {
			if (annotation instanceof In) {
				in = (In) annotation;
			} else if (annotation instanceof Out) {
				out = (Out) annotation;
			}
		}

		final Class<? extends MadvocScope> scope = resolveScopeClassFromAnnotations(annotations);

		int count = 0;

		InjectionPoint[] ins = null;
		InjectionPoint[] outs = null;

		if (in != null) {
			final InjectionPoint scopeDataIn = buildInjectionPoint(in.value(), name, type, scope);
			if (scopeDataIn != null) {
				count++;
				ins = new InjectionPoint[]{scopeDataIn};
			}
		}
		if (out != null) {
			final InjectionPoint scopeDataOut = buildInjectionPoint(out.value(), name, type, scope);
			if (scopeDataOut != null) {
				count++;
				outs = new InjectionPoint[]{scopeDataOut};
			}
		}

		if (count == 0) {
			return NO_SCOPE_DATA;
		}

		return new ScopeData(this, ins, outs);
	}

	protected Class<? extends MadvocScope> resolveScopeClassFromAnnotations(final Annotation[] annotations) {
		Class<? extends MadvocScope> scope = null;

		for (final Annotation annotation : annotations) {
			if (annotation instanceof Scope) {
				if (scope != null) {
					throw new MadvocException("Scope already defined: " + scope);
				}
				scope = ((Scope) annotation).value();
			} else {
				Class<? extends MadvocScope> annotationScope = null;

				final Annotation[] annotationAnnotations = annotation.annotationType().getAnnotations();
				for (final Annotation innerAnnotation : annotationAnnotations) {
					if (innerAnnotation instanceof Scope) {
						annotationScope = ((Scope) innerAnnotation).value();
					}
				}

				if (annotationScope != null) {
					if (scope == null) {
						scope = annotationScope;
					}
					else {
						throw new MadvocException("Scope already defined: " + scope);
					}
				}
			}
		}

		return scope;
	}

	// ---------------------------------------------------------------- inspect class

	/**
	 * Builds injection point.
	 */
	protected InjectionPoint buildInjectionPoint(
			final String annotationValue,
			final String propertyName,
			final Class propertyType,
			final Class<? extends MadvocScope> scope) {

		final String value = annotationValue.trim();
		final String name, targetName;

		if (StringUtil.isNotBlank(value)) {
			name = value;
			targetName = propertyName;
		}
		else {
			name = propertyName;
			targetName = null;
		}
		return new InjectionPoint(propertyType, name, targetName, scopeResolver.defaultOrScopeType(scope));
	}

	private TypeCache<ScopeData> scopeDataTypeCache = TypeCache.createDefault();

	/**
	 * Cached version of {@link #inspectClassScopes(Class)}. Use it in runtime when
	 * configuration is not known yet.
	 */
	public ScopeData inspectClassScopesWithCache(final Class actionClass) {
		return scopeDataTypeCache.get(actionClass, () -> inspectClassScopes(actionClass));
	}

	/**
	 * Inspects {@link ScopeData} for given class. The results are not cached, so it should
	 * be used only dyring configuration-time.
	 * For cached version, use {@link #inspectClassScopesWithCache(Class)}.
	 */
	public ScopeData inspectClassScopes(final Class actionClass) {
		ClassDescriptor cd = ClassIntrospector.get().lookup(actionClass);

		PropertyDescriptor[] allProperties = cd.getAllPropertyDescriptors();

		List<InjectionPoint> listIn = new ArrayList<>(allProperties.length);
		List<InjectionPoint> listOut = new ArrayList<>(allProperties.length);

		for (PropertyDescriptor pd : allProperties) {
			// collect annotations

			Class<? extends MadvocScope> scope = null;
			In in = null;
			Out out = null;

			if (pd.getFieldDescriptor() != null) {
				Field field = pd.getFieldDescriptor().getField();

				in = field.getAnnotation(In.class);
				out = field.getAnnotation(Out.class);
				scope = resolveScopeClassFromAnnotations(field.getAnnotations());
			}

			if (pd.getWriteMethodDescriptor() != null) {
				Method method = pd.getWriteMethodDescriptor().getMethod();
				if (in == null) {
					in = method.getAnnotation(In.class);
				}
				if (out == null) {
					out = method.getAnnotation(Out.class);
				}
				if (scope == null) {
					scope = resolveScopeClassFromAnnotations(method.getAnnotations());
				}
			}

			if (pd.getReadMethodDescriptor() != null) {
				Method method = pd.getReadMethodDescriptor().getMethod();
				if (in == null) {
					in = method.getAnnotation(In.class);
				}
				if (out == null) {
					out = method.getAnnotation(Out.class);
				}
				if (scope == null) {
					scope = resolveScopeClassFromAnnotations(method.getAnnotations());
				}
			}

			// inspect all

			final InjectionPoint ii = in == null ? null : buildInjectionPoint(in.value(), pd.getName(), pd.getType(), scope);
			if (ii != null) {
				listIn.add(ii);
			}

			final InjectionPoint oi = out == null ? null : buildInjectionPoint(out.value(), pd.getName(), pd.getType(), scope);
			if (oi != null) {
				listOut.add(oi);
			}
		}

		if ((listIn.isEmpty()) && (listOut.isEmpty())) {
			return NO_SCOPE_DATA;
		}

		InjectionPoint[] in = null;
		InjectionPoint[] out = null;

		if (!listIn.isEmpty()) {
			in = listIn.toArray(new InjectionPoint[0]);
		}
		if (!listOut.isEmpty()) {
			out = listOut.toArray(new InjectionPoint[0]);
		}

		return new ScopeData(this, in, out);
	}

}


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

import jodd.introspector.ClassDescriptor;
import jodd.introspector.ClassIntrospector;
import jodd.introspector.FieldDescriptor;
import jodd.introspector.Getter;
import jodd.introspector.PropertyDescriptor;
import jodd.json.meta.JsonAnnotationManager;
import jodd.json.meta.TypeData;

import java.lang.reflect.Modifier;

/**
 * Type's property visitor that follows JSON include/excludes rules.
 */
public abstract class TypeJsonVisitor {

	protected final JsonContext jsonContext;
	protected final boolean declared;
	protected final String classMetadataName;
	protected final Class type;

	protected int count;
	protected final TypeData typeData;

	public TypeJsonVisitor(final JsonContext jsonContext, final Class type) {
		this.jsonContext = jsonContext;
		this.count = 0;
		this.declared = false;
		this.classMetadataName = jsonContext.jsonSerializer.classMetadataName;

		this.type = type;

		this.typeData = JsonAnnotationManager.get().lookupTypeData(type);
	}

	/**
	 * Visits a type.
	 */
	public void visit() {
		ClassDescriptor classDescriptor = ClassIntrospector.get().lookup(type);

		if (classMetadataName != null) {
			// process first 'meta' fields 'class'
			onProperty(classMetadataName, null, false);
		}

		PropertyDescriptor[] propertyDescriptors = classDescriptor.getAllPropertyDescriptors();

		for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {

			Getter getter = propertyDescriptor.getGetter(declared);
			if (getter != null) {
				String propertyName = propertyDescriptor.getName();

				boolean isTransient = false;
				// check for transient flag
				FieldDescriptor fieldDescriptor = propertyDescriptor.getFieldDescriptor();

				if (fieldDescriptor != null) {
					isTransient = Modifier.isTransient(fieldDescriptor.getField().getModifiers());
				}

				onProperty(propertyName, propertyDescriptor, isTransient);
			}
		}
	}

	/**
	 * Invoked on each property. Properties are getting matched against the rules.
	 * If property passes all the rules, it will be processed in
	 * {@link #onSerializableProperty(String, jodd.introspector.PropertyDescriptor)}.
	 */
	protected void onProperty(
		String propertyName,
		final PropertyDescriptor propertyDescriptor,
		final boolean isTransient) {

		Class propertyType = propertyDescriptor == null ?  null : propertyDescriptor.getType();

		Path currentPath = jsonContext.path;

		currentPath.push(propertyName);

		// change name for properties

		if (propertyType != null) {
			propertyName = typeData.resolveJsonName(propertyName);
		}

		// determine if name should be included/excluded

		boolean include = !typeData.strict;

		// + don't include transient fields

		if (isTransient) {
			include = false;
		}

		// + all collections are not serialized by default

		include = jsonContext.matchIgnoredPropertyTypes(propertyType, true, include);

		// + annotations

		include = typeData.rules.apply(propertyName, true, include);

		// + path queries: excludes/includes

		include = jsonContext.matchPathToQueries(include);

		// done

		if (!include) {
			currentPath.pop();
			return;
		}

		onSerializableProperty(propertyName, propertyDescriptor);

		currentPath.pop();
	}

	/**
	 * Invoked on serializable properties, that have passed all the rules.
	 * Property descriptor may be null in special case when
	 * class meta data name is used.
	 */
	protected abstract void onSerializableProperty(String propertyName, PropertyDescriptor propertyDescriptor);

}


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

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.LinkedHashSet;
import java.util.Set;

@JavaIncompatible
public class ClassPathURLs {

	/**
	 * Returns urls for the classloader
	 *
	 * @param classLoader classloader in which to find urls
	 * @return list of urls or {@code null} if not found
	 */
	public static URL[] of(ClassLoader classLoader, Class clazz) {
		if (clazz == null) {
			clazz = ClassPathURLs.class;
		}
		if (classLoader == null) {
			classLoader = clazz.getClassLoader();
		}
		final Set<URL> urls = new LinkedHashSet<>();

		while (classLoader != null) {
			if (classLoader instanceof URLClassLoader) {
				final URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
				return urlClassLoader.getURLs();
			}

			final URL url = classModuleUrl(classLoader, clazz);

			if (url != null) {
				urls.add(url);
			}

			classLoader = classLoader.getParent();
		}

		return urls.toArray(new URL[0]);
	}

	private static URL classModuleUrl(final ClassLoader classLoader, final Class clazz) {
		if (clazz == null) {
			return null;
		}
		final String name = clazz.getName().replace('.', '/') + ".class";

		final URL url = classLoader.getResource(name);

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

		// use root
		String urlString = url.toString();
		final int ndx = urlString.indexOf(name);
		urlString = urlString.substring(0, ndx) + urlString.substring(ndx + name.length());

		try {
			return new URL(urlString);
		} catch (MalformedURLException ignore) {
			return null;
		}
	}

}


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

package jodd.bean;

import jodd.introspector.ClassDescriptor;
import jodd.introspector.ClassIntrospector;
import jodd.introspector.Getter;
import jodd.introspector.PropertyDescriptor;
import jodd.introspector.Setter;

import java.util.function.Supplier;

/**
 * Represents a bean named property. Contains two information:
 *

 *
Bean instance (and cached class descriptor)

 *
Property name

 *

 * Used only internally by {@link BeanUtil} and similar utils.
 */
class BeanProperty {

	BeanProperty(final BeanUtilBean beanUtilBean, final Object bean, final String propertyName) {
		this.introspector = beanUtilBean.introspector;
		setName(propertyName);
		updateBean(bean);
		this.last = true;
		this.first = true;
		this.fullName = bean.getClass().getSimpleName() + '#' + propertyName;
	}

	// ---------------------------------------------------------------- bean and descriptor

	final String fullName;  // initial name
	final ClassIntrospector introspector;
	Object bean;
	private ClassDescriptor cd;
	String name;        // property name
	boolean last;       // is it a last property (when nested)
	boolean first;		// is it first property (when nested)
	String indexString;	// indexString for index property

	/**
	 * Sets current property name.
	 */
	public void setName(final String name) {
		this.name = name;
		this.updateProperty = true;
	}

	/**
	 * Sets new bean instance.
	 */
	private void setBean(final Object bean) {
		this.bean = bean;
		this.cd = (bean == null ? null : introspector.lookup(bean.getClass()));
		this.first = false;
		this.updateProperty = true;
	}

	/**
	 * Updates the bean. Detects special case of suppliers.
	 */
	public void updateBean(final Object bean) {
		this.setBean(bean);

		if (this.cd != null && this.cd.isSupplier()) {
			final Object newBean = ((Supplier)this.bean).get();
			setBean(newBean);
		}
	}

	// ---------------------------------------------------------------- simple properties

	// indicates that property descriptor has to be updated
	// since there was some property-related change of BeanProperty state
	private boolean updateProperty = true;

	// most recent property descriptor
	private PropertyDescriptor propertyDescriptor;

	/**
	 * Loads property descriptor, if property was updated.
	 */
	private void loadPropertyDescriptor() {
		if (updateProperty) {
			if (cd == null) {
				propertyDescriptor = null;
			} else {
				propertyDescriptor = cd.getPropertyDescriptor(name, true);
			}
			updateProperty = false;
		}
	}

	/**
	 * Returns getter.
	 */
	public Getter getGetter(final boolean declared) {
		loadPropertyDescriptor();
		return propertyDescriptor != null ? propertyDescriptor.getGetter(declared) : null;
	}

	/**
	 * Returns setter.
	 */
	public Setter getSetter(final boolean declared) {
		loadPropertyDescriptor();
		return propertyDescriptor != null ? propertyDescriptor.getSetter(declared) : null;
	}

	/**
	 * Returns true if class is a map.
	 */
	public boolean isMap() {
		return cd != null && cd.isMap();
	}

	String index;

	// ---------------------------------------------------------------- toString

	@Override
	public String toString() {
		return fullName + " (" + (bean != null ? bean.getClass().getSimpleName() : "?") + '#' + name + ')';
	}
}


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

import jodd.db.connection.ConnectionProvider;
import jodd.db.oom.DbEntityManager;
import jodd.db.oom.DbOomConfig;
import jodd.db.oom.DbOomQuery;
import jodd.db.oom.DbSqlGenerator;
import jodd.db.oom.sqlgen.DbEntitySql;
import jodd.db.oom.sqlgen.DbSqlBuilder;
import jodd.db.pool.CoreConnectionPool;
import jodd.db.querymap.EmptyQueryMap;
import jodd.db.querymap.QueryMap;
import jodd.log.Logger;
import jodd.log.LoggerFactory;

/**
 * Starting class that all DBOOM starts from.
 * It encapsulate the database and works like a factory for all
 * db-related classes. This way you can have multiple connections.
 */
public class DbOom {

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

	/**
	 * The first DbOom is the default one, when only one is set.
	 * When you have two or more DbOom's the default dbOom is deregistered.
	 */
	private static DbOom defaultDbOom;

	/**
	 * Returns default DbOom instance. If multiple DbOoms are in use,
	 * an exception is thrown.
	 */
	public static DbOom get() {
		if (defaultDbOom == null) {
			throw new DbSqlException(
				"No default DbOom available. Use DbOom to create one.\n" +
				"If more then one DbOom is in use, there is no default instance.");
		}
		return defaultDbOom;
	}

	// ---------------------------------------------------------------- builder

	/**
	 * Creates a new DbOom builder.
	 */
	public static Builder create() {
		return new Builder();
	}

	public static class Builder {
		private ConnectionProvider connectionProvider;
		private DbSessionProvider dbSessionProvider;
		private QueryMap queryMap;

		public Builder withSessionProvider(final DbSessionProvider sessionProvider) {
			this.dbSessionProvider = sessionProvider;
			return this;
		}

		public Builder withConnectionProvider(final ConnectionProvider connectionProvider) {
			this.connectionProvider = connectionProvider;
			return this;
		}

		public Builder withQueryMap(final QueryMap queryMap) {
			this.queryMap = queryMap;
			return this;
		}

		/**
		 * Creates new DbOom.
		 */
		public DbOom get() {
			if (connectionProvider == null) {
				connectionProvider = new CoreConnectionPool();
			}
			if (dbSessionProvider == null) {
				dbSessionProvider = new ThreadDbSessionProvider();
			}
			if (queryMap == null) {
				queryMap = new EmptyQueryMap();
			}
			return new DbOom(connectionProvider, dbSessionProvider, queryMap);
		}
	}

	private final ConnectionProvider connectionProvider;
	private final DbSessionProvider dbSessionProvider;
	private final QueryMap queryMap;

	private final DbQueryConfig dbQueryConfig;
	private final DbOomConfig dbOomConfig;
	private final DbEntityManager dbEntityManager;
	private final DbEntitySql dbEntitySql;

	// ---------------------------------------------------------------- class

	/**
	 * Creates new DbOom.
	 */
	public DbOom(
			final ConnectionProvider connectionProvider,
			final DbSessionProvider dbSessionProvider,
			final QueryMap queryMap) {
		this.connectionProvider = connectionProvider;
		this.dbSessionProvider = dbSessionProvider;
		this.queryMap = queryMap;

		this.dbOomConfig = new DbOomConfig();
		this.dbQueryConfig = new DbQueryConfig();
		this.dbEntityManager = new DbEntityManager(dbOomConfig);
		this.dbEntitySql = new DbEntitySql(this);

		// static init

		if (defaultDbOom == null) {
			log.info("Default DbOom is created.");
			defaultDbOom = this;
		}
		else {
			log.warn("Multiple DbOom detected.");
			defaultDbOom = null;
		}
	}

	/**
	 * Initializes the DbOom by connecting to the database. Database will be detected
	 * and DbOom will be configured to match it.
	 */
	public DbOom connect() {
		connectionProvider.init();

		final DbDetector dbDetector = new DbDetector();

		dbDetector.detectDatabaseAndConfigureDbOom(connectionProvider, dbOomConfig);

		return this;
	}

	/**
	 * Closes the DbOom.
	 */
	public void shutdown() {
		if (defaultDbOom == this) {
			defaultDbOom = null;
		}
		connectionProvider.close();
	}

	// ---------------------------------------------------------------- instances

	/**
	 * Returns DbOom configuration.
	 */
	public DbOomConfig config() {
		return dbOomConfig;
	}

	/**
	 * Returns default query configuration.
	 */
	public DbQueryConfig queryConfig() {
		return dbQueryConfig;
	}

	/**
	 * Returns DbOoom entity manager.
	 */
	public DbEntityManager entityManager() {
		return dbEntityManager;
	}

	/**
	 * Returns DbSession provider.
	 */
	public DbSessionProvider sessionProvider() {
		return dbSessionProvider;
	}

	/**
	 * Returns connection provider.
	 */
	public ConnectionProvider connectionProvider() {
		return connectionProvider;
	}

	/**
	 * Returns a query map.
	 */
	public QueryMap queryMap() {
		return queryMap;
	}

	// ---------------------------------------------------------------- entity

	/**
	 * Returns Entity SQL factory.
	 */
	public DbEntitySql entities() {
		return dbEntitySql;
	}

	// ---------------------------------------------------------------- factories

	/**
	 * Creates new T-SQL builder.
	 */
	public DbSqlBuilder sql(final String sql) {
		return new DbSqlBuilder(this, sql);
	}

	/**
	 * Creates new empty T-SQL builder.
	 */
	public DbSqlBuilder sql() {
		return new DbSqlBuilder(this);
	}

	/**
	 * Creates a query.
	 */
	public DbOomQuery query(final String sql) {
		return new DbOomQuery(this, sessionProvider().getDbSession(), sql);
	}

	/**
	 * Creates a query from a sql generator.
	 */
	public DbOomQuery query(final DbSqlGenerator sql) {
		return new DbOomQuery(this, sessionProvider().getDbSession(), sql);
	}

}


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

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

/**
 * Iterator that combines multiple iterators.
 */
public class CompositeIterator<T> implements Iterator<T> {

	protected final List<Iterator<T>> allIterators = new ArrayList<>();

	/**
	 * Creates new composite iterator.
	 * Iterators may be added using the {@link #add(Iterator)} method.
	 */
	public CompositeIterator() {
	}

	/**
	 * Creates new composite iterator with provided iterators.
	 */
	public CompositeIterator(final Iterator<T>... iterators) {
		for (Iterator<T> iterator : iterators) {
			add(iterator);
		}
	}

	/**
	 * Adds an iterator to this composite.
	 */
	public void add(final Iterator<T> iterator) {
		if (allIterators.contains(iterator)) {
			throw new IllegalArgumentException("Duplicate iterator");
		}
		allIterators.add(iterator);
	}

	// ---------------------------------------------------------------- interface

	protected int currentIterator = -1;

	/**
	 * Returns true if next element is available.
	 */
	@Override
	public boolean hasNext() {
		if (currentIterator == -1) {
			currentIterator = 0;
		}
		for (int i = currentIterator; i < allIterators.size(); i++) {
			Iterator iterator = allIterators.get(i);
			if (iterator.hasNext()) {
				currentIterator = i;
				return true;
			}
		}
		return false;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public T next() {
		if (!hasNext()) {
			throw new NoSuchElementException();
		}

		return allIterators.get(currentIterator).next();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void remove() {
		if (currentIterator == -1) {
			throw new IllegalStateException("next() has not yet been called");
		}

		allIterators.get(currentIterator).remove();
	}

}


// 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.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * Null-aware {@link SqlType sql types}.
 */
public abstract class NullAwareSqlType<T> extends SqlType<T> {

	/**
	 * Detects if there was a null reading and returns null if it was.
	 * Result set returns default value (e.g. 0) for many getters, therefore it detects if it was
	 * a null reading or it is a real value.
	 */
	@Override
	public <E> E readValue(final ResultSet rs, final int index, final Class<E> destinationType, final int dbSqlType) throws SQLException {
		T t = get(rs, index, dbSqlType);
		if ((t == null) || (rs.wasNull())) {
			return null;
		}
		return prepareGetValue(t, destinationType);
	}

	/**
	 * Detects null before storing the value into the database.
	 */
	@Override
	public void storeValue(final PreparedStatement st, final int index, final Object value, final int dbSqlType) throws SQLException {
		if (value == null) {
			st.setNull(index, dbSqlType);
			return;
		}
		super.storeValue(st, index, value, dbSqlType);
	}

}


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

import jodd.Jodd;
import jodd.chalk.Chalk256;
import jodd.log.Logger;
import jodd.log.LoggerFactory;
import jodd.log.LoggerProvider;
import jodd.log.impl.SimpleLogger;
import jodd.madvoc.WebApp;
import jodd.madvoc.petite.PetiteWebApp;
import jodd.petite.PetiteContainer;
import jodd.util.function.Consumers;

import javax.servlet.ServletContext;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class JoddJoy {

	/**
	 * System property: application folder.
	 */
	public static final String APP_DIR = "app.dir";

	private static JoddJoy joddJoy;

	public synchronized static JoddJoy get() {
		if (joddJoy == null) {
			joddJoy = new JoddJoy();
		}
		return joddJoy;
	}

	public JoddJoy() {
		appName = "joy";
		joyPaths = new JoyPaths();
		joyPaths.start();

		joyScanner = new JoyScanner();
		joyProps = new JoyProps(() -> appName);

		joyProxetta = new JoyProxetta();
		joyPetite =
			new JoyPetite(
				() -> appName,
				() -> joyProxetta,
				() -> joyProps,
				() -> joyScanner
			);

		joyDb = new JoyDb(
			() -> appName,
			() -> joyPetite,
			() -> joyProxetta,
			() -> joyScanner);

		joyMadvoc = new JoyMadvoc(
			() -> appName,
			() -> joyPetite,
			() -> joyProxetta,
			() -> joyProps,
			() -> joyScanner
		);
	}

	// ---------------------------------------------------------------- name

	private String appName;

	/**
	 * Sets default Joy application name used for various components.
	 */
	public JoddJoy setApplicationName(final String name) {
		Objects.requireNonNull(name);
		this.appName = name;
		return this;
	}

	// ---------------------------------------------------------------- logger

	private Supplier<LoggerProvider> loggerProviderSupplier;

	/**
	 * Configures the logger provider.
	 */
	public JoddJoy withLoggerProvider(final Supplier<LoggerProvider> loggerProviderSupplier) {
		this.loggerProviderSupplier = loggerProviderSupplier;
		return this;
	}

	// ---------------------------------------------------------------- paths

	private final JoyPaths joyPaths;

	// ---------------------------------------------------------------- props

	private JoyProps joyProps;

	private final Consumers<JoyPropsConfig> joyPropsConsumers = Consumers.empty();

	/**
	 * Configures Joy props before Joy is started.
	 */
	public JoddJoy withProps(final Consumer<JoyPropsConfig> propsConsumer) {
		joyPropsConsumers.add(propsConsumer);
		return this;
	}

	// ---------------------------------------------------------------- scanner

	private final JoyScanner joyScanner;

	/**
	 * Configures the Joy scanner.
	 */
	public JoddJoy withScanner(final Consumer<JoyScannerConfig> scannerConsumer) {
		scannerConsumer.accept(joyScanner);
		return this;
	}

	// ---------------------------------------------------------------- proxetta

	private JoyProxetta joyProxetta;
	private final Consumers<JoyProxettaConfig> joyProxettaConsumers = Consumers.empty();

	/**
	 * Configures the Joy proxetta.
	 */
	public JoddJoy withProxetta(final Consumer<JoyProxettaConfig> proxettaConsumer) {
		joyProxettaConsumers.add(proxettaConsumer);
		return this;
	}

	// ---------------------------------------------------------------- petite

	private JoyPetite joyPetite;

	private final Consumers<JoyPetiteConfig> joyPetiteConsumers = Consumers.empty();

	/**
	 * Configures the Joy petite.
	 */
	public JoddJoy withPetite(final Consumer<JoyPetiteConfig> petiteConsumer) {
		joyPetiteConsumers.add(petiteConsumer);
		return this;
	}

	// ---------------------------------------------------------------- db

	private JoyDb joyDb;

	private final Consumers<JoyDbConfig> joyDbConsumers = Consumers.empty();

	/**
	 * Configures the Joy db.
	 */
	public JoddJoy withDb(final Consumer<JoyDbConfig> dbConsumer) {
		joyDbConsumers.add(dbConsumer);
		return this;
	}

	// ---------------------------------------------------------------- madvoc

	private final JoyMadvoc joyMadvoc;

	public JoddJoy withWebApp(final Consumer<WebApp> webAppConsumer) {
		joyMadvoc.add(webAppConsumer);
		return this;
	}

	/**
	 * Defines custom {@link PetiteWebApp} implementation.
	 */
	public JoddJoy useWebApp(final Supplier<PetiteWebApp> webAppSupplier) {
		joyMadvoc.setWebAppSupplier(webAppSupplier);
		return this;
	}

	// ---------------------------------------------------------------- start

	private Logger log;

	/**
	 * Starts the Joy without the web application.
	 */
	public JoddJoyRuntime startOnlyBackend() {
		return start(null);
	}

	/**
	 * Starts the Joy. Returns the {@link JoddJoyRuntime runtime}, set of running
	 * Joy components.
	 */
	public JoddJoyRuntime start(final ServletContext servletContext) {
		LoggerProvider loggerProvider = null;

		if (loggerProviderSupplier != null) {
			loggerProvider = loggerProviderSupplier.get();
		}
		if (loggerProvider == null) {
			loggerProvider = SimpleLogger.PROVIDER;
		}

		LoggerFactory.setLoggerProvider(loggerProvider);
		log = LoggerFactory.getLogger(JoddJoy.class);

		printLogo();

		log.info("Ah, Joy!");
		log.info("Logging using: " + loggerProvider.getClass().getSimpleName());

		joyPropsConsumers.accept(joyProps);
		joyProxettaConsumers.accept(joyProxetta);
		joyDbConsumers.accept(joyDb);
		joyPetiteConsumers.accept(joyPetite);

		try {
			joyPaths.start();
			joyProps.start();
			joyProxetta.start();
			joyScanner.start();

			joyPetite.start();
			joyPetite.getPetiteContainer().addBean(appName + ".core",  this);
			joyPetite.getPetiteContainer().addBean(appName + ".scanner", joyScanner);

			joyDb.start();

			joyMadvoc.setServletContext(servletContext);
			joyMadvoc.start();

			runJoyInitBeans();

			// cleanup things we will not use

			joyScanner.stop();
		}
		catch (Exception ex) {
			if (log != null) {
				log.error(ex.toString(), ex);
			} else {
				System.out.println(ex.toString());
				ex.printStackTrace();
			}
			stop();
			throw ex;
		}

		joyPetite.printBeans(100);
		joyDb.printEntities(100);
		joyMadvoc.printRoutes(100);

		System.out.println(Chalk256.chalk().yellow().on("Joy") + " is up. Enjoy!");

		log.info("Joy is up. Enjoy!");

		if (joyDb.isDatabaseEnabled()) {
			return new JoddJoyRuntime(
				appName,
				joyPaths.getAppDir(),
				joyProps.getProps(),
				joyProxetta.getProxetta(),
				joyPetite.getPetiteContainer(),
				joyMadvoc.getWebApp(),
				joyDb.isDatabaseEnabled(),
				joyDb.getConnectionProvider(),
				joyDb.getJtxManager()
			);
		}
		else {
			return new JoddJoyRuntime(
				appName,
				joyPaths.getAppDir(),
				joyProps.getProps(),
				joyProxetta.getProxetta(),
				joyPetite.getPetiteContainer(),
				joyMadvoc.getWebApp()
			);
		}
	}

	/**
	 * Prints a logo.
	 */
	private void printLogo() {
		System.out.println(Chalk256.chalk().yellow().on(Jodd.JODD));
	}

	/**
	 * Stops the Joy.
	 */
	public void stop() {
		joyProps.stop();
		try {
			joyDb.stop();
			joyPetite.stop();
		}
		catch (Exception ignore) {
		}

		if (log != null) {
			log.info("Joy is down. Bye, bye!");
		}
	}

	protected void runJoyInitBeans() {
		final PetiteContainer pc = joyPetite.getPetiteContainer();
		pc.forEachBeanType(JoyInit.class, beanName -> {
			final JoyInit joyInit = pc.getBean(beanName);

			if (joyInit != null) {
				joyInit.onJoy();
			}
		});
	}

}


// 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.http.net;

import jodd.http.HttpConnection;
import jodd.http.HttpConnectionProvider;
import jodd.http.HttpException;
import jodd.http.HttpRequest;
import jodd.http.ProxyInfo;
import jodd.http.Sockets;
import jodd.util.StringUtil;

import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;

/**
 * Socket factory for HTTP proxy.
 */
public class SocketHttpConnectionProvider implements HttpConnectionProvider {

	protected ProxyInfo proxy = ProxyInfo.directProxy();
	protected String secureEnabledProtocols = System.getProperty("https.protocols");
	protected String sslProtocol = "TLSv1.1";

	/**
	 * Defines proxy to use for created sockets.
	 */
	@Override
	public void useProxy(final ProxyInfo proxyInfo) {
		proxy = proxyInfo;
	}

	/**
	 * CSV of default enabled secured protocols. By default the value is
	 * read from system property https.protocols.
	 */
	public void setSecuredProtocols(final String secureEnabledProtocols) {
		this.secureEnabledProtocols = secureEnabledProtocols;
	}

	/**
	 * Returns current SSL protocol used.
	 */
	public String getSslProtocol() {
		return sslProtocol;
	}

	/**
	 * Sets default SSL protocol to use. One of "SSL", "TLSv1.2", "TLSv1.1", "TLSv1".
	 */
	public SocketHttpConnectionProvider setSslProtocol(final String sslProtocol) {
		this.sslProtocol = sslProtocol;
		return this;
	}

	/**
	 * Creates new connection from current {@link jodd.http.HttpRequest request}.
	 *
	 * @see #createSocket(String, int, int)
	 */
	@Override
	public HttpConnection createHttpConnection(final HttpRequest httpRequest) throws IOException {
		final SocketHttpConnection httpConnection;

		final boolean https = httpRequest.protocol().equalsIgnoreCase("https");

		if (https) {
			SSLSocket sslSocket = createSSLSocket(
				httpRequest.host(),
				httpRequest.port(),
				httpRequest.connectionTimeout(),
				httpRequest.trustAllCertificates(),
				httpRequest.verifyHttpsHost()
			);

			httpConnection = new SocketHttpSecureConnection(sslSocket);
		}
		else {
			Socket socket = createSocket(httpRequest.host(), httpRequest.port(), httpRequest.connectionTimeout());

			httpConnection = new SocketHttpConnection(socket);
		}

		// prepare connection config

		httpConnection.setTimeout(httpRequest.timeout());

		try {
			// additional socket initialization

			httpConnection.init();
		}
		catch (Throwable throwable) {  			// @wjw_add
			httpConnection.close();

			throw new HttpException(throwable);
		}

		return httpConnection;
	}

	/**
	 * Creates a socket using socket factory.
	 */
	protected Socket createSocket(final String host, final int port, final int connectionTimeout) throws IOException {
		final SocketFactory socketFactory = getSocketFactory(proxy, false, false, connectionTimeout);

		if (connectionTimeout < 0) {
			return socketFactory.createSocket(host, port);
		}
		else {
			// creates unconnected socket
			Socket socket = socketFactory.createSocket();

			socket.connect(new InetSocketAddress(host, port), connectionTimeout);

			return socket;
		}
	}

	/**
	 * Creates a SSL socket. Enables default secure enabled protocols if specified.
	 */
	protected SSLSocket createSSLSocket(
		final String host, final int port, final int connectionTimeout,
		final boolean trustAll, final boolean verifyHttpsHost) throws IOException {

		final SocketFactory socketFactory = getSocketFactory(proxy, true, trustAll, connectionTimeout);

		final Socket socket;

		if (connectionTimeout < 0) {
			socket = socketFactory.createSocket(host, port);
		}
		else {
			// creates unconnected socket
			// unfortunately, this does not work always

//			sslSocket = (SSLSocket) socketFactory.createSocket();
//			sslSocket.connect(new InetSocketAddress(host, port), connectionTimeout);

			//
			// Note: SSLSocketFactory has several create() methods.
			// Those that take arguments all connect immediately
			// and have no options for specifying a connection timeout.
			//
			// So, we have to create a socket and connect it (with a
			// connection timeout), then have the SSLSocketFactory wrap
			// the already-connected socket.
			//
			socket = Sockets.connect(host, port, connectionTimeout);
			//sock.setSoTimeout(readTimeout);
			//socket.connect(new InetSocketAddress(host, port), connectionTimeout);

			// continue to wrap this plain socket with ssl socket...
		}

		// wrap plain socket in an SSL socket

		SSLSocket sslSocket;

		if (socket instanceof SSLSocket) {
			sslSocket = (SSLSocket) socket;
		}
		else {
			if (socketFactory instanceof SSLSocketFactory) {
				sslSocket = (SSLSocket) ((SSLSocketFactory)socketFactory).createSocket(socket, host, port, true);
			}
			else {
				sslSocket = (SSLSocket) (getDefaultSSLSocketFactory(trustAll)).createSocket(socket, host, port, true);
			}
		}

		// sslSocket is now ready

		if (secureEnabledProtocols != null) {
			final String[] values = StringUtil.splitc(secureEnabledProtocols, ',');

			StringUtil.trimAll(values);

			sslSocket.setEnabledProtocols(values);
		}

		// set SSL parameters to allow host name verifier

		if (verifyHttpsHost) {
			final SSLParameters sslParams = new SSLParameters();

			sslParams.setEndpointIdentificationAlgorithm("HTTPS");

			sslSocket.setSSLParameters(sslParams);
		}

		return sslSocket;
	}

	/**
	 * Returns default SSL socket factory allowing setting trust managers.
	 */
	protected SSLSocketFactory getDefaultSSLSocketFactory(final boolean trustAllCertificates) throws IOException {
		if (trustAllCertificates) {
			try {
				SSLContext sc = SSLContext.getInstance(sslProtocol);
				sc.init(null, TrustManagers.TRUST_ALL_CERTS, new java.security.SecureRandom());
				return sc.getSocketFactory();
			}
			catch (NoSuchAlgorithmException | KeyManagementException e) {
				throw new IOException(e);
			}
		} else {
			return (SSLSocketFactory) SSLSocketFactory.getDefault();
		}
	}

	/**
	 * Returns socket factory based on proxy type and SSL requirements.
	 */
	protected SocketFactory getSocketFactory(
			final ProxyInfo proxy,
			final boolean ssl,
			final boolean trustAllCertificates,
			final int connectionTimeout) throws IOException {

		switch (proxy.getProxyType()) {
			case NONE:
				if (ssl) {
					return getDefaultSSLSocketFactory(trustAllCertificates);
				}
				else {
					return SocketFactory.getDefault();
				}
			case HTTP:
				return new HTTPProxySocketFactory(proxy, connectionTimeout);
			case SOCKS4:
				return new Socks4ProxySocketFactory(proxy, connectionTimeout);
			case SOCKS5:
				return new Socks5ProxySocketFactory(proxy, connectionTimeout);
			default:
				return null;
		}
	}
}


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

package jodd.util;

import java.security.SecureRandom;

/**
 * Class that generates random strings.
 */
public class RandomString {

	protected static final char[] ALPHA_RANGE = new char[] {'A', 'Z', 'a', 'z'};
	protected static final char[] ALPHA_NUMERIC_RANGE = new char[] {'0', '9', 'A', 'Z', 'a', 'z'};

	protected final static RandomString INSTANCE = new RandomString();

	/**
	 * Returns default instance of RandomString.
	 */
	public static RandomString get() {
		return INSTANCE;
	}

	protected final SecureRandom rnd;

	/**
	 * Creates new random string.
	 */
	public RandomString() {
		this.rnd = new SecureRandom();
	}

	// ---------------------------------------------------------------- string

	/**
	 * Creates random string whose length is the number of characters specified.
	 * Characters are chosen from the set of characters specified.
	 */
	public String random(int count, final char[] chars) {
		if (count == 0) {
			return StringPool.EMPTY;
		}
		final char[] result = new char[count];
		while (count-- > 0) {
			result[count] = chars[rnd.nextInt(chars.length)];
		}
		return new String(result);
	}

	/**
	 * Creates random string whose length is the number of characters specified.
	 * Characters are chosen from the set of characters specified.
	 */
	public String random(final int count, final String chars) {
		return random(count, chars.toCharArray());
	}

	// ---------------------------------------------------------------- range

	/**
	 * Creates random string whose length is the number of characters specified.
	 * Characters are chosen from the provided range.
	 */
	public String random(int count, final char start, final char end) {
		if (count == 0) {
			return StringPool.EMPTY;
		}
		final char[] result = new char[count];
		final int len = end - start + 1;
		while (count-- > 0) {
			result[count] = (char) (rnd.nextInt(len) + start);
		}
		return new String(result);
	}

	/**
	 * Creates random string whose length is the number of characters specified.
	 * Characters are chosen from the set of characters whose
	 * ASCII value is between 32 and 126 (inclusive).
	 */
	public String randomAscii(final int count) {
		return random(count, (char) 32, (char) 126);
	}

	/**
	 * Creates random string that consist only of numbers.
	 */
	public String randomNumeric(final int count) {
		return random(count, '0', '9');
	}

	/**
	 * Creates random string whose length is the number of characters specified.
	 * Characters are chosen from the multiple sets defined by range pairs.
	 * All ranges must be in acceding order.
	 */
	public String randomRanges(int count, final char... ranges) {
		if (count == 0) {
			return StringPool.EMPTY;
		}
		int i = 0;
		int len = 0;
		final int[] lens = new int[ranges.length];
		while (i < ranges.length) {
			int gap = ranges[i + 1] - ranges[i] + 1;
			len += gap;
			lens[i] = len;
			i += 2;
		}

		final char[] result = new char[count];
		while (count-- > 0) {
			char c = 0;
			int r = rnd.nextInt(len);
			for (i = 0; i < ranges.length; i += 2) {
				if (r < lens[i]) {
					r += ranges[i];
					if (i != 0) {
						r -= lens[i - 2];
					}
					c = (char) r;
					break;
				}
			}
			result[count] = c;
		}
		return new String(result);
	}

	/**
	 * Creates random string of characters.
	 */
	public String randomAlpha(final int count) {
		return randomRanges(count, ALPHA_RANGE);
	}

	/**
	 * Creates random string of characters and digits.
	 */
	public String randomAlphaNumeric(final int count) {
		return randomRanges(count, ALPHA_NUMERIC_RANGE);
	}

	/**
	 * Creates random string that contains only Base64 characters.
	 */
	public String randomBase64(final int count) {
		return random(count, Base64.CHARS);
	}

}


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

package jodd.mail;

import jodd.core.JoddCore;
import jodd.io.StreamUtil;

import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;

/**
 * Developer-friendly class for parsing EML files.
 */
public class EMLParser extends EMLProperties<EMLParser> {

	public static EMLParser create() {
		return new EMLParser();
	}

	/**
	 * Parses EML with provided EML content.
	 *
	 * @param emlContent {@link String} with EML content.
	 * @param charset    String with charset.
	 * @return {@link ReceivedEmail}.
	 * @throws UnsupportedEncodingException if the named charset is not supported.
	 * @throws MessagingException           if {@link MimeMessage} cannot be created.
	 * @see #parse(byte[])
	 */
	public ReceivedEmail parse(final String emlContent, final String charset) throws
			UnsupportedEncodingException, MessagingException {

		final byte[] bytes = emlContent.getBytes(charset);
		return parse(bytes);
	}

	/**
	 * Parses EML with provided EML content.
	 *
	 * @param emlContent {@link String} with EML content.
	 * @return {@link ReceivedEmail}.
	 * @throws MessagingException if {@link MimeMessage} cannot be created.
	 * @see #parse(String, String)
	 */
	public ReceivedEmail parse(final String emlContent) throws MessagingException {
		try {
			return parse(emlContent, JoddCore.encoding);
		} catch (final UnsupportedEncodingException ignore) {
			return null;
		}
	}

	/**
	 * Parses EML with provided EML content.
	 *
	 * @param content byte[] with EML content.
	 * @return {@link ReceivedEmail}.
	 * @throws MessagingException if {@link MimeMessage} cannot be created.
	 * @see #parse(InputStream)
	 */
	public ReceivedEmail parse(final byte[] content) throws MessagingException {
		return parse(new ByteArrayInputStream(content));
	}

	/**
	 * Starts EML parsing with provided EML {@link File}.
	 *
	 * @param emlFile {@link File} with EML content.
	 * @return {@link ReceivedEmail}.
	 * @throws FileNotFoundException if emlFile cannot be found
	 * @throws MessagingException    if {@link MimeMessage} cannot be created.
	 * @see #parse(InputStream)
	 */
	public ReceivedEmail parse(final File emlFile) throws FileNotFoundException, MessagingException {
		final FileInputStream fileInputStream = new FileInputStream(emlFile);
		try {
			return parse(fileInputStream);
		} finally {
			StreamUtil.close(fileInputStream);
		}
	}

	/**
	 * Parses the EML content. If {@link Session} is not created, default one will be used.
	 *
	 * @param emlContentInputStream {@link InputStream} containing the EML content.
	 * @return {@link ReceivedEmail}.
	 * @throws MessagingException if {@link MimeMessage} cannot be created.
	 */
	protected ReceivedEmail parse(final InputStream emlContentInputStream) throws MessagingException {
		if (getSession() == null) {
			createSession(getProperties());
		}

		try {
			final MimeMessage message = new MimeMessage(getSession(), emlContentInputStream);
			return new ReceivedEmail(message, false, null);
		} finally {
			StreamUtil.close(emlContentInputStream);
		}
	}
}


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

import jodd.introspector.ClassDescriptor;
import jodd.introspector.ClassIntrospector;
import jodd.introspector.CtorDescriptor;
import jodd.introspector.PropertyDescriptor;
import jodd.introspector.Setter;
import jodd.typeconverter.TypeConverterManager;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;

/**
 * Just a base class of {@link jodd.json.JsonParser} that contains
 * various utilities, to reduce the size of a parser.
 */
public abstract class JsonParserBase {

	protected static final Supplier<Map> HASHMAP_SUPPLIER = LinkedHashMap::new;
	protected static final Supplier<Map> LAZYMAP_SUPPLIER = LazyMap::new;

	protected static final Supplier<List> ARRAYLIST_SUPPLIER = ArrayList::new;
	protected static final Supplier<List> LAZYLIST_SUPPLIER = LazyList::new;

	protected Supplier<Map> mapSupplier = HASHMAP_SUPPLIER;
	protected Supplier<List> listSupplier = ARRAYLIST_SUPPLIER;
	protected List<String> classnameWhitelist;
	protected boolean strictTypes;

	public JsonParserBase(final boolean strictTypes) {
		this.strictTypes = strictTypes;
	}

	/**
	 * Creates new instance of {@link jodd.json.MapToBean}.
	 */
	protected MapToBean createMapToBean(final String classMetadataName) {
		return new MapToBean(this, classMetadataName);
	}

	// ---------------------------------------------------------------- object tools

	/**
	 * Creates new type for JSON array objects.
	 * It returns a collection.
	 * Later, the collection will be converted into the target type.
	 */
	@SuppressWarnings("unchecked")
	protected Collection<Object> newArrayInstance(final Class targetType) {
		if (targetType == null ||
			targetType == List.class ||
			targetType == Collection.class ||
			targetType.isArray()) {

			return listSupplier.get();
		}

		if (targetType == Set.class) {
			return new HashSet<>();
		}

		try {
			return (Collection<Object>) targetType.getDeclaredConstructor().newInstance();
		} catch (Exception e) {
			throw new JsonException(e);
		}
	}

	/**
	 * Creates new object or a HashMap if type is not specified.
	 */
	protected Object newObjectInstance(final Class targetType) {
		if (targetType == null ||
			targetType == Map.class) {

			return mapSupplier.get();
		}

		final ClassDescriptor cd = ClassIntrospector.get().lookup(targetType);

		final CtorDescriptor ctorDescriptor = cd.getDefaultCtorDescriptor(true);
		if (ctorDescriptor == null) {
			throw new JsonException("Default ctor not found for: " + targetType.getName());
		}

		try {
//			return ClassUtil.newInstance(targetType);
			return ctorDescriptor.getConstructor().newInstance();
		} catch (Exception e) {
			throw new JsonException(e);
		}
	}

	/**
	 * Injects value into the targets property.
	 */
	protected void injectValueIntoObject(final Object target, final PropertyDescriptor pd, final Object value) {
		Object convertedValue = value;

		if (value != null) {
			Class targetClass = pd.getType();

			convertedValue = convertType(value, targetClass);
		}

		try {
			Setter setter = pd.getSetter(true);
			if (setter != null) {
				setter.invokeSetter(target, convertedValue);
			}
		} catch (Exception ex) {
			throw new JsonException(ex);
		}
	}

	/**
	 * Converts type of the given value.
	 */
	protected Object convertType(final Object value, final Class targetType) {
		final Class valueClass = value.getClass();

		if (valueClass == targetType) {
			return value;
		}

		try {
			return TypeConverterManager.get().convertType(value, targetType);
		}
		catch (Exception ex) {
			if (!strictTypes) {
				return null;
			}
			throw new JsonException("Type conversion failed", ex);
		}
	}

}


// 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 module. The methods of this class must be called in the following
 * order: ( {@code visitMainClass} | ( {@code visitPackage} | {@code visitRequire} | {@code
 * visitExport} | {@code visitOpen} | {@code visitUse} | {@code visitProvide} )* ) {@code visitEnd}.
 *
 * @author Remi Forax
 * @author Eric Bruneton
 */
public abstract class ModuleVisitor {
  /**
   * The ASM API version implemented by this visitor. The value of this field must be one of {@link
   * Opcodes#ASM6} or {@link Opcodes#ASM7}.
   */
  protected final int api;

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

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

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

  /**
   * Visit the main class of the current module.
   *
   * @param mainClass the internal name of the main class of the current module.
   */
  public void visitMainClass(final String mainClass) {
    if (mv != null) {
      mv.visitMainClass(mainClass);
    }
  }

  /**
   * Visit a package of the current module.
   *
   * @param packaze the internal name of a package.
   */
  public void visitPackage(final String packaze) {
    if (mv != null) {
      mv.visitPackage(packaze);
    }
  }

  /**
   * Visits a dependence of the current module.
   *
   * @param module the fully qualified name (using dots) of the dependence.
   * @param access the access flag of the dependence among {@code ACC_TRANSITIVE}, {@code
   *     ACC_STATIC_PHASE}, {@code ACC_SYNTHETIC} and {@code ACC_MANDATED}.
   * @param version the module version at compile time, or {@literal null}.
   */
  public void visitRequire(final String module, final int access, final String version) {
    if (mv != null) {
      mv.visitRequire(module, access, version);
    }
  }

  /**
   * Visit an exported package of the current module.
   *
   * @param packaze the internal name of the exported package.
   * @param access the access flag of the exported package, valid values are among {@code
   *     ACC_SYNTHETIC} and {@code ACC_MANDATED}.
   * @param modules the fully qualified names (using dots) of the modules that can access the public
   *     classes of the exported package, or {@literal null}.
   */
  public void visitExport(final String packaze, final int access, final String... modules) {
    if (mv != null) {
      mv.visitExport(packaze, access, modules);
    }
  }

  /**
   * Visit an open package of the current module.
   *
   * @param packaze the internal name of the opened package.
   * @param access the access flag of the opened package, valid values are among {@code
   *     ACC_SYNTHETIC} and {@code ACC_MANDATED}.
   * @param modules the fully qualified names (using dots) of the modules that can use deep
   *     reflection to the classes of the open package, or {@literal null}.
   */
  public void visitOpen(final String packaze, final int access, final String... modules) {
    if (mv != null) {
      mv.visitOpen(packaze, access, modules);
    }
  }

  /**
   * Visit a service used by the current module. The name must be the internal name of an interface
   * or a class.
   *
   * @param service the internal name of the service.
   */
  public void visitUse(final String service) {
    if (mv != null) {
      mv.visitUse(service);
    }
  }

  /**
   * Visit an implementation of a service.
   *
   * @param service the internal name of the service.
   * @param providers the internal names of the implementations of the service (there is at least
   *     one provider).
   */
  public void visitProvide(final String service, final String... providers) {
    if (mv != null) {
      mv.visitProvide(service, providers);
    }
  }

  /**
   * Visits the end of the module. This method, which is the last one to be called, is used to
   * inform the visitor that everything 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.db.type;

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

public class ShortSqlType extends NullAwareSqlType<Short> {

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

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

}


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

package jodd.proxetta.asm;

import jodd.asm.ClassAdapter;
import jodd.asm7.ClassVisitor;
import jodd.asm7.MethodVisitor;
import jodd.proxetta.InvokeAspect;

import java.util.ArrayList;
import java.util.List;

/**
 * Invocation replacer class adapter.
 */
public class InvokeClassBuilder extends ClassAdapter {

	protected final WorkData wd;
	protected final InvokeAspect[] aspects;
	protected final String suffix;
	protected final String reqProxyClassName;
	protected final TargetClassInfoReader targetClassInfo;

	public InvokeClassBuilder(final ClassVisitor dest, final InvokeAspect[] invokeAspects, final String suffix, final String reqProxyClassName, final TargetClassInfoReader targetClassInfoReader) {
		super(dest);
		this.aspects = invokeAspects;
		this.suffix = suffix;
		this.wd = new WorkData(dest);
		this.targetClassInfo = targetClassInfoReader;
		this.reqProxyClassName = reqProxyClassName;
	}

	/**
	 * Returns working data.
	 */
	public WorkData getWorkData() {
		return wd;
	}

	// ---------------------------------------------------------------- header

	@Override
	public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) {
		wd.init(name, superName, suffix, reqProxyClassName);

		// write destination class
		final int v = ProxettaAsmUtil.resolveJavaVersion(version);
		super.visit(v, access, wd.thisReference, signature, wd.superName, interfaces);
	}

	@Override
	public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
		final MethodSignatureVisitor msign = targetClassInfo.lookupMethodSignatureVisitor(access, name, desc, wd.superReference);

		if (msign == null) {
			super.visitMethod(access, name, desc, signature, exceptions);
			return null;
		}

		// check aspects that apply to this method
		List<InvokeAspect> applicableAspects = new ArrayList<>(aspects.length);
		for (InvokeAspect aspect : aspects) {
			if (aspect.apply(msign)) {
				applicableAspects.add(aspect);
			}
		}

		if (applicableAspects.isEmpty()) {
			super.visitMethod(access, name, desc, signature, exceptions);
			return null;
		}

		InvokeAspect[] nextAspects = new InvokeAspect[applicableAspects.size()];
		nextAspects = applicableAspects.toArray(nextAspects);

		return new InvokeReplacerMethodAdapter(
				super.visitMethod(access, name, desc, signature, exceptions), msign, wd, nextAspects);
	}

}


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

import java.util.AbstractMap;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * Types cache. Provides several implementations depending on what you need to be addressed.
 * There are two things you should take care off:
 *

 *
synchronization - especially on storing items. If not synchronized, one instance of an item may be put
 *     more then once into the map. This is usually fine, as it happens only during the initialization and makes not
 *     harm if something is created twice

 *
weak - if your key classes are replaced during the runtime, you should use weak map, in order to automatically
 *     remove obsolete keys.

 *

 */
public class TypeCache<T> {

	public static class Defaults {
		/**
		 * Default {@link TypeCache} implementation.
		 */
		public static Supplier<TypeCache> implementation = () -> TypeCache.create().get();
	}

	// ---------------------------------------------------------------- builder

	/**
	 * Creates a type cache by using a builder.
	 */
	public static  Builder create() {
		return new Builder<>();
	}

	/**
	 * Creates default implementation of the type cache.
	 */
	@SuppressWarnings("unchecked")
	public static  TypeCache createDefault() {
		return (TypeCache)Defaults.implementation.get();
	}

	public static class Builder {
		private boolean threadsafe;
		private boolean weak;
		private boolean none;

		/**
		 * No cache will be used.
		 * Setting other properties will not have any affect.
		 */
		public Builder noCache() {
			none = true;
			return this;
		}

		/**
		 * Cache keys will be weak.
		 */
		public Builder weak(final boolean weak) {
			this.weak = weak;
			return this;
		}
		/**
		 * Cache will be thread-safe.
		 */
		public Builder threadsafe(final boolean threadsafe) {
			this.threadsafe = threadsafe;
			return this;
		}

		/**
		 * Builds a type cache.
		 */
		public TypeCache get() {
			final Map<Class<?>, A> map;
			if (none) {
				map = new AbstractMap<Class<?>, A>() {
					@Override
					public A put(final Class<?> key, final A value) {
						return null;
					}

					@Override
					public A get(final Object key) {
						return null;
					}

					@Override
					public Set<Entry<Class<?>, A>> entrySet() {
						return Collections.emptySet();
					}
				};
			}
			else if (weak) {
				if (threadsafe) {
					map = Collections.synchronizedMap(new WeakHashMap<>());
				} else {
					map = new WeakHashMap<>();
				}
			} else {
				if (threadsafe) {
					map = new ConcurrentHashMap<>();
				} else {
					map = new IdentityHashMap<>();
				}
			}

			return new TypeCache<>(map);
		}
	}

	// ---------------------------------------------------------------- map

	private final Map<Class<?>, T> map;

	private TypeCache(final Map<Class<?>, T> backedMap) {
		this.map = backedMap;
	}

	/**
	 * Add values to the map.
	 */
	public T put(final Class<?> type, final T value) {
		return map.put(type, value);
	}

	/**
	 * Returns value from the map or {@code null} if value does not exist.
	 */
	public T get(final Class<?> key) {
		return map.get(key);
	}

	/**
	 * Returns existing value or add default supplied one.
	 * Use this method instead of {@code get-nullcheck-put} block when
	 * thread-safety is of importance.
	 */
	public T get(final Class<?> key, final Supplier<T> valueSupplier) {
		return map.computeIfAbsent(key, aClass -> valueSupplier.get());
	}

	/**
	 * Removes element from the type cache.
	 */
	public T remove(final Class<?> type) {
		return map.remove(type);
	}

	/**
	 * Clears complete cache.
	 */
	public void clear() {
		map.clear();
	}

	/**
	 * Returns cache size.
	 */
	public int size() {
		return map.size();
	}

	/**
	 * Returns {@code true} if cache is empty.
	 */
	public boolean isEmpty() {
		return map.isEmpty();
	}

	/**
	 * Iterates all cached values.
	 */
	public void forEachValue(final Consumer<? super T> valueConsumer) {
		map.values().forEach(valueConsumer);
	}

}


// 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.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class LongSqlType extends NullAwareSqlType<Long> {

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

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

}


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

import jodd.util.ArraysUtil;
import jodd.util.ClassUtil;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;

/**
 * Methods collection.
 */
public class Methods {

	protected final ClassDescriptor classDescriptor;
	protected final HashMap<String, MethodDescriptor[]> methodsMap;

	// cache
	private MethodDescriptor[] allMethods;

	public Methods(final ClassDescriptor classDescriptor) {
		this.classDescriptor = classDescriptor;
		this.methodsMap = inspectMethods();
	}

	/**
	 * Inspects types methods and return map of {@link MethodDescriptor method descriptors}.
	 */
	protected HashMap<String, MethodDescriptor[]> inspectMethods() {
		boolean scanAccessible = classDescriptor.isScanAccessible();

		if (classDescriptor.isSystemClass()) {
			scanAccessible = false;
		}

		final Class type = classDescriptor.getType();

		final Method[] methods = scanAccessible ? ClassUtil.getAccessibleMethods(type) : ClassUtil.getSupportedMethods(type);

		final HashMap<String, MethodDescriptor[]> map = new HashMap<>(methods.length);

		for (final Method method : methods) {
			final String methodName = method.getName();

			MethodDescriptor[] mds = map.get(methodName);

			if (mds == null) {
				mds = new MethodDescriptor[1];
			} else {
				mds = ArraysUtil.resize(mds, mds.length + 1);
			}

			map.put(methodName, mds);

			mds[mds.length - 1] = createMethodDescriptor(method);
		}

		return map;
	}

	/**
	 * Creates new {@code MethodDescriptor}.
	 */
	protected MethodDescriptor createMethodDescriptor(final Method method) {
		return new MethodDescriptor(classDescriptor, method);
	}

	// ---------------------------------------------------------------- get

	/**
	 * Returns a method that matches given name and parameter types.
	 * Returns null if method is not found.
	 */
	public MethodDescriptor getMethodDescriptor(final String name, final Class[] paramTypes) {
		final MethodDescriptor[] methodDescriptors = methodsMap.get(name);
		if (methodDescriptors == null) {
			return null;
		}
		for (MethodDescriptor methodDescriptor : methodDescriptors) {
			final Method m = methodDescriptor.getMethod();
			if (ClassUtil.compareParameters(m.getParameterTypes(), paramTypes)) {
				return methodDescriptor;
			}
		}
		return null;
	}

	/**
	 * Returns method descriptor for given name. If more then one methods with
	 * the same name exists, one method will be returned (not determined which one).
	 * Returns null if no method exist in this collection by given name.
	 * @see #getMethodDescriptor(String, Class[])
	 */
	public MethodDescriptor getMethodDescriptor(final String name) {
		final MethodDescriptor[] methodDescriptors = methodsMap.get(name);
		if (methodDescriptors == null) {
			return null;
		}
		if (methodDescriptors.length != 1) {
			throw new IllegalArgumentException("Method name not unique: " + name);
		}
		return methodDescriptors[0];
	}

	/**
	 * Returns all methods for given name. Returns null if method not found.
	 */
	public MethodDescriptor[] getAllMethodDescriptors(final String name) {
		return methodsMap.get(name);
	}

	/**
	 * Returns all methods. Cached. Lazy.
	 */
	public MethodDescriptor[] getAllMethodDescriptors() {
		if (allMethods == null) {
			final List<MethodDescriptor> allMethodsList = new ArrayList<>();

			for (MethodDescriptor[] methodDescriptors : methodsMap.values()) {
				Collections.addAll(allMethodsList, methodDescriptors);
			}

			final MethodDescriptor[] allMethods = allMethodsList.toArray(new MethodDescriptor[0]);

			Arrays.sort(allMethods, Comparator.comparing(md -> md.getMethod().getName()));

			this.allMethods = allMethods;
		}
		return allMethods;
	}

}


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

import jodd.util.StringUtil;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.StandardOpenOption;

/**
 * Network utilities.
 */
public class NetUtil {

	public static final String LOCAL_HOST = "localhost";
	public static final String LOCAL_IP = "127.0.0.1";
	public static final String DEFAULT_MASK = "255.255.255.0";
	public static final int INT_VALUE_127_0_0_1 = 0x7f000001;

	/**
	 * Resolves IP address from a hostname.
	 */
	public static String resolveIpAddress(final String hostname) {
		try {
			InetAddress netAddress;

			if (hostname == null || hostname.equalsIgnoreCase(LOCAL_HOST)) {
				netAddress = InetAddress.getLocalHost();
			} else {
				netAddress = Inet4Address.getByName(hostname);
			}
			return netAddress.getHostAddress();
		} catch (UnknownHostException ignore) {
			return null;
		}
	}

	/**
	 * Returns IP address as integer.
	 */
	public static int getIpAsInt(final String ipAddress) {
		int ipIntValue = 0;
		String[] tokens = StringUtil.splitc(ipAddress, '.');
		for (String token : tokens) {
			if (ipIntValue > 0) {
				ipIntValue <<= 8;
			}
			ipIntValue += Integer.parseInt(token);
		}
		return ipIntValue;
	}

	public static int getMaskAsInt(String mask) {
		if (!validateAgaintIPAdressV4Format(mask)) {
			mask = DEFAULT_MASK;
		}
		return getIpAsInt(mask);
	}

	public static boolean isSocketAccessAllowed(final int localIp, final int socketIp, final int mask) {
		boolean _retVal = false;

		if (socketIp == INT_VALUE_127_0_0_1 || (localIp & mask) == (socketIp & mask)) {
			_retVal = true;
		}
		return _retVal;
	}

	/**
	 * Checks given string against IP address v4 format.
	 *
	 * @param input an ip address - may be null
	 * @return <tt>true</tt> if param has a valid ip v4 format <tt>false</tt> otherwise
	 * @see ip address v4
	 */
	public static boolean validateAgaintIPAdressV4Format(final String input) {
		if (input == null) {
			return false;
		}

		int hitDots = 0;
		char[] data = input.toCharArray();
		for (int i = 0; i < data.length; i++) {
			char c = data[i];
			int b = 0;
			do {
				if (c < '0' || c > '9') {
					return false;
				}
				b = (b * 10 + c) - 48;
				if (++i >= data.length) {
					break;
				}
				c = data[i];
			} while (c != '.');

			if (b > 255) {
				return false;
			}
			hitDots++;
		}

		return hitDots == 4;
	}

	/**
	 * Resolves host name from IP address bytes.
	 */
	public static String resolveHostName(final byte[] ip) {
		try {
			InetAddress address = InetAddress.getByAddress(ip);
			return address.getHostName();
		} catch (UnknownHostException ignore) {
			return null;
		}
	}

	// ---------------------------------------------------------------- download

	/**
	 * Downloads resource as byte array.
	 */
	public static byte[] downloadBytes(final String url) throws IOException {
		try (InputStream inputStream = new URL(url).openStream()) {
			return StreamUtil.readBytes(inputStream);
		}
	}

	/**
	 * Downloads resource as String.
	 */
	public static String downloadString(final String url, final String encoding) throws IOException {
		try (InputStream inputStream = new URL(url).openStream()) {
			return new String(StreamUtil.readChars(inputStream, encoding));
		}
	}

	/**
	 * Downloads resource as String.
	 */
	public static String downloadString(final String url) throws IOException {
		try (InputStream inputStream = new URL(url).openStream()) {
			return new String(StreamUtil.readChars(inputStream));
		}
	}

	/**
	 * Downloads resource to a file, potentially very efficiently.
	 */
	public static void downloadFile(final String url, final File file) throws IOException {
		try (
			InputStream inputStream = new URL(url).openStream();
			ReadableByteChannel rbc = Channels.newChannel(inputStream);
			FileChannel fileChannel = FileChannel.open(
				file.toPath(),
				StandardOpenOption.CREATE,
				StandardOpenOption.TRUNCATE_EXISTING,
				StandardOpenOption.WRITE)
		) {
			fileChannel.transferFrom(rbc, 0, Long.MAX_VALUE);
		}
	}
}