// 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.bean.BeanUtil;
import jodd.db.type.SqlType;
import jodd.db.type.SqlTypeManager;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.Date;
import java.sql.Ref;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Map;

/**
 * Encapsulates {@link Statement} and all its operations.
 *

 * It may be:
 *

 *
used in an un-managed way, created directly from connection;

 *
managed by {@link DbSession};

 *
managed by {@link jodd.db.DbThreadSession} i.e. {@link ThreadDbSessionHolder}.

 *

 *

 * Query life-cycle states:
 *

 *
1) created:     statement == null, initialized == false

 *
2) initialized: statement != null, initialized == true

 *
3) closed:      statement == null, initialized == true

 *

 */
public class DbQuery<Q extends DbQuery> extends DbQueryBase<Q> {

	/**
	 * Creates new query.
	 */
	public DbQuery(final DbOom dbOom, final Connection conn, final String sqlString) {
		super(dbOom);
		this.connection = conn;
		this.sqlString = sqlString;
	}

	public static DbQuery query(final Connection conn, final String sqlString) {
		return new DbQuery(DbOom.get(), conn, sqlString);
	}

	/**
	 * Creates a new query from {@link DbSession}.
	 */
	public DbQuery(final DbOom dbOom, final DbSession session, final String sqlString) {
		super(dbOom);
		initSession(session);
		this.session.attachQuery(this);
		this.sqlString = sqlString;
	}

	public static DbQuery query(final DbSession session, final String sqlString) {
		return new DbQuery(DbOom.get(), session, sqlString);
	}

	/**
	 * Creates a new query using default session provider.
	 */
	public DbQuery(final DbOom dbOom, final String sqlString) {
		this(dbOom, (DbSession)null, sqlString);
	}

	public static DbQuery query(final String sqlString) {
		return new DbQuery(DbOom.get(), sqlString);
	}

	// ---------------------------------------------------------------- additional statement parameters

	/**
	 * Clears the current parameter values immediately.
	 *

	 * In general, parameter values remain in force for repeated use of a
	 * statement. Setting a parameter value automatically clears its
	 * previous value. However, in some cases it is useful to immediately
	 * release the resources used by the current parameter values; this can
	 * be done by calling the method clearParameters.
	 */
	public Q clearParameters() {
		init();
		if (preparedStatement == null) {
			return _this();
		}
		try {
			preparedStatement.clearParameters();
		} catch (SQLException sex) {
			throw new DbSqlException(sex);
		}
		return _this();
	}

	// ---------------------------------------------------------------- methods for setting statement parameters

	private void throwSetParamError(final int index, final Exception ex) {
		throw new DbSqlException(this, "Invalid SQL parameter with index: " + index, ex);
	}

	private void throwSetParamError(final String param, final Exception ex) {
		throw new DbSqlException(this, "Invalid SQL parameter with name: " + param, ex);
	}

	// ---------------------------------------------------------------- null

	public Q setNull(final int index, final int type) {
		initPrepared();
		try {
			preparedStatement.setNull(index, type);
		} catch (SQLException sex) {
			throw new DbSqlException(this, "Failed to set null to parameter: " + index, sex);
		}
		return _this();
	}

	public Q setNull(final String param, final int type) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setNull(position, type);
			}
		} catch (SQLException sex) {
			throw new DbSqlException(this, "Failed to set null to parameter: " + param, sex);
		}
		return _this();
	}

	public Q setNull(final int index, final int type, final String typeName) {
		initPrepared();
		try {
			preparedStatement.setNull(index, type, typeName);
		} catch (SQLException sex) {
			throw new DbSqlException(this, "Failed to set null to parameter: " + index, sex);
		}
		return _this();
	}

	public Q setNull(final String param, final int value, final String typeName) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setNull(position, value, typeName);
			}
		} catch (SQLException sex) {
			throw new DbSqlException(this, "Failed to set null to parameter: " + param, sex);
		}
		return _this();
	}

	// ---------------------------------------------------------------- int

	public Q setInteger(final int index, final int value) {
		initPrepared();
		try {
			preparedStatement.setInt(index, value);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	public Q setInteger(final String param, final int value) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setInt(position, value);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	public Q outInteger(final int index) {
		return registerOutParameter(index, Types.INTEGER);
	}
	public Q outInteger(final String param) {
		return registerOutParameter(param, Types.INTEGER);
	}

	// ---------------------------------------------------------------- Integer

	public Q setInteger(final int index, final Number value) {
		if (value == null) {
			setNull(index, Types.INTEGER);
		}
		else {
			setInteger(index, value.intValue());
		}
		return _this();
	}

	public Q setInteger(final String param, final Number value) {
		if (value == null) {
			setNull(param, Types.INTEGER);
		}
		else {
			setInteger(param, value.intValue());
		}
		return _this();
	}

	// ---------------------------------------------------------------- boolean

	public Q setBoolean(final int index, final boolean value) {
		initPrepared();
		try {
			preparedStatement.setBoolean(index, value);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	public Q setBoolean(final String param, final boolean value) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setBoolean(position, value);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	public Q outBoolean(final int index) {
		return registerOutParameter(index, Types.BOOLEAN);
	}
	public Q outBoolean(final String param) {
		return registerOutParameter(param, Types.BOOLEAN);
	}

	// ---------------------------------------------------------------- Boolean

	public Q setBoolean(final int index, final Boolean value) {
		if (value == null) {
			setNull(index, Types.BOOLEAN);
		}
		else {
			setBoolean(index, value.booleanValue());
		}
		return _this();
	}

	public Q setBoolean(final String param, final Boolean value) {
		if (value == null) {
			setNull(param, Types.BOOLEAN);
		}
		else {
			setBoolean(param, value.booleanValue());
		}
		return _this();
	}

	// ---------------------------------------------------------------- long

	public Q setLong(final int index, final long value) {
		initPrepared();
		try {
			preparedStatement.setLong(index, value);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	public Q setLong(final String param, final long value) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setLong(position, value);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	// ---------------------------------------------------------------- Long

	public Q setLong(final int index, final Number value) {
		if (value == null) {
			setNull(index, Types.BIGINT);
		}
		else {
			setLong(index, value.longValue());
		}
		return _this();
	}

	public Q setLong(final String param, final Number value) {
		if (value == null) {
			setNull(param, Types.BIGINT);
		}
		else {
			setLong(param, value.longValue());
		}
		return _this();
	}

	public Q outLong(final int index) {
		return registerOutParameter(index, Types.BIGINT);
	}
	public Q outLong(final String param) {
		return registerOutParameter(param, Types.BIGINT);
	}

	// ---------------------------------------------------------------- byte

	public Q setByte(final int index, final byte value) {
		initPrepared();
		try {
			preparedStatement.setByte(index, value);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	public Q setByte(final String param, final byte value) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setByte(position, value);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	public Q outByte(final int index) {
		return registerOutParameter(index, Types.SMALLINT);
	}
	public Q outByte(final String param) {
		return registerOutParameter(param, Types.SMALLINT);
	}

	// ---------------------------------------------------------------- Byte

	public Q setByte(final int index, final Number value) {
		if (value == null) {
			setNull(index, Types.SMALLINT);
		}
		else {
			setByte(index, value.byteValue());
		}
		return _this();
	}

	public Q setByte(final String param, final Number value) {
		if (value == null) {
			setNull(param, Types.SMALLINT);
		}
		else {
			setByte(param, value.byteValue());
		}
		return _this();
	}

	// ---------------------------------------------------------------- bytes[]

	public Q setBytes(final int index, final byte[] value) {
		initPrepared();
		try {
			preparedStatement.setBytes(index, value);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	public Q setBytes(final String param, final byte[] value) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setBytes(position, value);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	// ---------------------------------------------------------------- double

	public Q setDouble(final int index, final double value) {
		initPrepared();
		try {
			preparedStatement.setDouble(index, value);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	public Q setDouble(final String param, final double value) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setDouble(position, value);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	public Q outDouble(final int index) {
		return registerOutParameter(index, Types.DOUBLE);
	}
	public Q outDouble(final String param) {
		return registerOutParameter(param, Types.DOUBLE);
	}

	// ---------------------------------------------------------------- Double

	public Q setDouble(final int index, final Number value) {
		if (value == null) {
			setNull(index, Types.DOUBLE);
		}
		else {
			setDouble(index, value.doubleValue());
		}
		return _this();
	}

	public Q setDouble(final String param, final Number value) {
		if (value == null) {
			setNull(param, Types.DOUBLE);
		}
		else {
			setDouble(param, value.doubleValue());
		}
		return _this();
	}

	// ---------------------------------------------------------------- float

	public Q setFloat(final int index, final float value) {
		initPrepared();
		try {
			preparedStatement.setFloat(index, value);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	public Q setFloat(final String param, final float value) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setFloat(position, value);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	public Q outFloat(final int index) {
		return registerOutParameter(index, Types.FLOAT);
	}
	public Q outFloat(final String param) {
		return registerOutParameter(param, Types.FLOAT);
	}

	// ---------------------------------------------------------------- Float

	public Q setFloat(final int index, final Number value) {
		if (value == null) {
			setNull(index, Types.FLOAT);
		}
		else {
			setFloat(index, value.floatValue());
		}
		return _this();
	}

	public Q setFloat(final String param, final Number value) {
		if (value == null) {
			setNull(param, Types.FLOAT);
		}
		else {
			setFloat(param, value.floatValue());
		}
		return _this();
	}

	// ---------------------------------------------------------------- short

	public Q setShort(final int index, final short value) {
		initPrepared();
		try {
			preparedStatement.setShort(index, value);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	public Q setShort(final String param, final short value) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				final int index = position;
				preparedStatement.setShort(index, value);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	// ---------------------------------------------------------------- Short

	public Q setShort(final int index, final Number value) {
		if (value == null) {
			setNull(index, Types.SMALLINT);
		}
		else {
			setShort(index, value.shortValue());
		}
		return _this();
	}

	public Q setShort(final String param, final Number value) {
		if (value == null) {
			setNull(param, Types.SMALLINT);
		}
		else {
			setShort(param, value.shortValue());
		}
		return _this();
	}

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

	public Q setString(final int index, final String value) {
		initPrepared();
		try {
			preparedStatement.setString(index, value);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	public Q setString(final String param, final String value) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setString(position, value);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	public Q outString(final int index) {
		return registerOutParameter(index, Types.VARCHAR);
	}
	public Q outString(final String param) {
		return registerOutParameter(param, Types.VARCHAR);
	}

	// ---------------------------------------------------------------- date

	public Q setDate(final int index, final Date value) {
		initPrepared();
		try {
			preparedStatement.setDate(index, value);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	public Q setDate(final String param, final Date value) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setDate(position, value);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	// ---------------------------------------------------------------- time

	public Q setTime(final int index, final Time value) {
		initPrepared();
		try {
			preparedStatement.setTime(index, value);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	public Q setTime(final String param, final Time value) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setTime(position, value);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	// ---------------------------------------------------------------- timestamp

	public Q setTimestamp(final int index, final Timestamp value) {
		initPrepared();
		try {
			preparedStatement.setTimestamp(index, value);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	public Q setTimestamp(final String param, final Timestamp value) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setTimestamp(position, value);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	// ---------------------------------------------------------------- big decimal

	public Q setBigDecimal(final int index, final BigDecimal value) {
		initPrepared();
		try {
			preparedStatement.setBigDecimal(index, value);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	public Q setBigDecimal(final String param, final BigDecimal value) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setBigDecimal(position, value);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	// ---------------------------------------------------------------- big integer

	public Q setBigInteger(final int index, final BigInteger value) {
		if (value == null) {
			setNull(index, Types.NUMERIC);
		}
		else {
			setLong(index, value.longValue());
		}
		return _this();
	}

	public Q setBigInteger(final String param, final BigInteger value) {
		if (value == null) {
			setNull(param, Types.NUMERIC);
		}
		else {
			setLong(param, value.longValue());
		}
		return _this();
	}

	// ---------------------------------------------------------------- URL

	public Q setURL(final int index, final URL value) {
		initPrepared();
		try {
			preparedStatement.setURL(index, value);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	public Q setURL(final String param, final URL value) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setURL(position, value);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	// ---------------------------------------------------------------- BLOB

	public Q setBlob(final int index, final Blob value) {
		initPrepared();
		try {
			preparedStatement.setBlob(index, value);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	public Q setBlob(final String param, final Blob value) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setBlob(position, value);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	// ---------------------------------------------------------------- CLOB

	public Q setClob(final int index, final Clob value) {
		initPrepared();
		try {
			preparedStatement.setClob(index, value);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	public Q setClob(final String param, final Clob value) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setClob(position, value);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	// ---------------------------------------------------------------- Array

	public Q setArray(final int index, final Array value) {
		initPrepared();
		try {
			preparedStatement.setArray(index, value);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	public Q setArray(final String param, final Array value) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setArray(position, value);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	// ---------------------------------------------------------------- Ref

	public Q setRef(final int index, final Ref value) {
		initPrepared();
		try {
			preparedStatement.setRef(index, value);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	public Q setRef(final String param, final Ref value) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setRef(position, value);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	// ---------------------------------------------------------------- ascii streams

	public Q setAsciiStream(final int index, final InputStream stream) {
		initPrepared();
		try {
			preparedStatement.setAsciiStream(index, stream, stream.available());
		} catch (IOException | SQLException ioex) {
			throwSetParamError(index, ioex);
		}
		return _this();
	}

	public Q setAsciiStream(final String param, final InputStream stream) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setAsciiStream(position, stream, stream.available());
			}
		} catch (IOException | SQLException ioex) {
			throwSetParamError(param, ioex);
		}
		return _this();
	}

	public Q setAsciiStream(final int index, final InputStream stream, final int length) {
		initPrepared();
		try {
			preparedStatement.setAsciiStream(index, stream, length);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	public Q setAsciiStream(final String param, final InputStream stream, final int length) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setAsciiStream(position, stream, length);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	public Q setCharacterStream(final int index, final Reader reader, final int length) {
		initPrepared();
		try {
			preparedStatement.setCharacterStream(index, reader, length);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	public Q setCharacterStream(final String param, final Reader reader, final int length) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setCharacterStream(position, reader, length);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	// ---------------------------------------------------------------- bean

	/**
	 * Sets bean parameters from bean. Non-existing bean properties are ignored.
	 */
	public Q setBean(final String beanName, final Object bean) {
		if (bean == null) {
			return _this();
		}
		init();
		final String beanNamePrefix = beanName + '.';

		query.forEachNamedParameter(p -> {
			final String paramName = p.name;

			if (paramName.startsWith(beanNamePrefix)) {
				final String propertyName = paramName.substring(beanNamePrefix.length());

				if (BeanUtil.declared.hasRootProperty(bean, propertyName)) {
					final Object value = BeanUtil.declared.getProperty(bean, propertyName);
					setObject(paramName, value);
				}
			}

		});

		return _this();
	}

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

	/**
	 * Sets properties from the map.
	 */
	public Q setMap(final Map parameters) {
		if (parameters == null) {
			return _this();
		}
		init();
		query.forEachNamedParameter(p -> {
			final String paramName = p.name;
			setObject(paramName, parameters.get(paramName));
		});
		return _this();
	}

	// ---------------------------------------------------------------- objects

	/**
	 * Sets the value of the designated parameter with the given object.
	 * This method is like the method setObject
	 * above, except that it assumes a scale of zero.
	 */
	public Q setObject(final int index, final Object object, final int targetSqlType) {
		initPrepared();
		try {
			preparedStatement.setObject(index, object, targetSqlType);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	/**
	 * Sets the value of the designated parameter with the given object.
	 * This method is like the method setObject
	 * above, except that it assumes a scale of zero.
	 */
	public Q setObject(final String param, final Object object, final int targetSqlType) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setObject(position, object, targetSqlType);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	/**
	 * Sets the value of the designated parameter with the given object.
	 * This method is like the method setObject
	 * above, except that it assumes a scale of zero.
	 */
    void setObject(final int index, final Object object, final int targetSqlType, final int scale) {
		initPrepared();
	    try {
		    preparedStatement.setObject(index, object, targetSqlType, scale);
	    } catch (SQLException sex) {
			throwSetParamError(index, sex);
	    }
    }

	/**
	 * Sets the value of the designated parameter with the given object.
	 * This method is like the method setObject
	 * above, except that it assumes a scale of zero.
	 */
    void setObject(final String param, final Object object, final int targetSqlType, final int scale) {
		initPrepared();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				preparedStatement.setObject(position, object, targetSqlType, scale);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
	}

	// ---------------------------------------------------------------- object ex

	/**
	 * @see #setObject(String, Object, Class, int)
	 */
	public Q setObject(final int index, final Object value) {
		setObject(index, value, null, Types.OTHER);
		return _this();
	}

	/**
	 * Sets object parameter in an advanced way.
	 *

	 * First, it checks if object is null and invokes setNull if so.
	 * If object is not null, it tries to resolve {@link SqlType sql type} (by looking up
	 * or using provided class) and use it for setting data.
	 * If sql type is not found, default setObject is invoked.
	 */
	@SuppressWarnings({"unchecked"})
	public Q setObject(final int index, final Object value, final Class<? extends SqlType> sqlTypeClass, final int dbSqlType) {
		init();
		if (value == null) {
			setNull(index, Types.NULL);
			return _this();
		}
		final SqlType sqlType;
		if (sqlTypeClass != null) {
			sqlType = SqlTypeManager.get().lookupSqlType(sqlTypeClass);
		} else {
			sqlType = SqlTypeManager.get().lookup(value.getClass());
		}
		try {
			if ((sqlType != null) && (dbSqlType != SqlType.DB_SQLTYPE_NOT_AVAILABLE)) {
				sqlType.storeValue(preparedStatement, index, value, dbSqlType);
			} else {
				DbUtil.setPreparedStatementObject(preparedStatement, index, value, dbSqlType);
			}
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}

	/**
	 * @see #setObject(String, Object, Class, int)
	 */
	public Q setObject(final String param, final Object value) {
		setObject(param, value, null, Types.OTHER);
		return _this();
	}

	/**
	 * @see #setObject(String, Object, Class, int)
	 */
	public Q setObject(final String param, final Object value, final Class<? extends SqlType> sqlTypeClass, final int dbSqlType) {
		init();
		final int[] positions = query.getNamedParameterIndices(param);
		for (final int position : positions) {
			setObject(position, value, sqlTypeClass, dbSqlType);
		}
		return _this();
	}

	// ---------------------------------------------------------------- set object shortcuts

	/**
	 * Sets an array of objects parameters in given order.
	 */
	public Q setObjects(final Object... objects) {
		int index = 1;
		for (final Object object : objects) {
			setObject(index++, object);
		}
		return _this();
	}

	/**
	 * Sets sql parameters from two arrays: names and values.
	 */
	public Q setObjects(final String[] names, final Object[] values) {
		init();
		if (names.length != values.length) {
			throw new DbSqlException(this, "Different number of parameter names and values");
		}
		for (int i = 0; i < names.length; i++) {
			setObject(names[i], values[i]);
		}
		return _this();
	}

	// ---------------------------------------------------------------- batch

	/**
	 * Sets batch parameters with given array of values.
	 */
	public Q setBatch(final String name, final int[] array, int startingIndex) {
		init();
		final int batchSize = query.getBatchParameterSize(name);

		for (int i = 1; i <= batchSize; i++) {
			final String paramName = name + '.' + i;

			if (startingIndex < array.length) {
				setInteger(paramName, array[startingIndex]);
			} else {
				setNull(paramName, Types.INTEGER);
			}
			startingIndex++;
		}
		return _this();
	}
	/**
	 * Sets batch parameters with given array of values.
	 */
	public Q setBatch(final String name, final long[] array, int startingIndex) {
		init();
		final int batchSize = query.getBatchParameterSize(name);

		for (int i = 1; i <= batchSize; i++) {
			final String paramName = name + '.' + i;

			if (startingIndex < array.length) {
				setLong(paramName, array[startingIndex]);
			} else {
				setNull(paramName, Types.INTEGER);
			}
			startingIndex++;
		}
		return _this();
	}

	/**
	 * Sets batch parameters with given array of values.
	 */
	public Q setBatch(final String name, final Object[] array, int startingIndex) {
		init();
		final int batchSize = query.getBatchParameterSize(name);

		for (int i = 1; i <= batchSize; i++) {
			final String paramName = name + '.' + i;

			if (startingIndex < array.length) {
				setObject(paramName, array[startingIndex]);
			} else {
				setObject(paramName, null);
			}
			startingIndex++;
		}
		return _this();
	}

	// ---------------------------------------------------------------- utils

	private void initPrepared() {
		init();
		if (preparedStatement == null) {
			throw new DbSqlException("Prepared statement not initialized.");
		}
	}
	private void initCallable() {
		init();
		if (callableStatement == null) {
			throw new DbSqlException("Callable statement not initialized.");
		}
	}

	private Q registerOutParameter(final int index, final int type) {
		initCallable();
		try {
			callableStatement.registerOutParameter(index,type);
		} catch (SQLException sex) {
			throwSetParamError(index, sex);
		}
		return _this();
	}
	private Q registerOutParameter(final String param, final int type) {
		initCallable();
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			for (final int position : positions) {
				callableStatement.registerOutParameter(position, type);
			}
		} catch (SQLException sex) {
			throwSetParamError(param, sex);
		}
		return _this();
	}

	// ---------------------------------------------------------------- close

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Q autoClose() {
		super.autoClose();
		return _this();
	}

}


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

package jodd.db.oom.sqlgen.chunks;

import jodd.db.oom.DbEntityColumnDescriptor;
import jodd.db.oom.DbEntityDescriptor;
import jodd.db.oom.DbEntityManager;
import jodd.db.oom.sqlgen.DbSqlBuilderException;
import jodd.db.oom.sqlgen.TemplateData;
import jodd.util.CharUtil;
import jodd.util.StringUtil;

/**
 * SQL chunk defines part of the SQL query that can be processed.
 */
public abstract class SqlChunk {

	public static final int COLS_NA = 0;                // using explicit reference.
	public static final int COLS_ONLY_EXISTING = 1;     // using only existing columns i.e. that are not-null
	public static final int COLS_ONLY_IDS = 2;          // using only identity columns
	public static final int COLS_ALL = 3;               // using all available columns
	public static final int COLS_ALL_BUT_ID = 4;        // using all available columns except the identity column
	public static final int COLS_NA_MULTI = 0;          // using explicit reference.

	public static final int CHUNK_RAW = -1;
	public static final int CHUNK_SELECT_COLUMNS = 1;
	public static final int CHUNK_TABLE = 2;
	public static final int CHUNK_REFERENCE = 3;
	public static final int CHUNK_MATCH = 4;
	public static final int CHUNK_VALUE = 5;
	public static final int CHUNK_INSERT = 5;
	public static final int CHUNK_UPDATE = 6;

	private final int chunkType;
	private final DbEntityManager dbEntityManager;

	protected SqlChunk(final DbEntityManager dbEntityManager, final int chunkType) {
		this.dbEntityManager = dbEntityManager;
		this.chunkType = chunkType;
	}

	// ---------------------------------------------------------------- linked list

	protected SqlChunk previousChunk;

	/**
	 * Returns previous chunk.
	 */
	public SqlChunk getPreviousChunk() {
		return previousChunk;
	}

	protected SqlChunk nextChunk;

	/**
	 * Returns next chunk.
	 */
	public SqlChunk getNextChunk() {
		return nextChunk;
	}

	/**
	 * Appends chunk to previous one and maintains the double-linked list of the previous chunk.
	 * Current surrounding connections of this chunk will be cut-off.
	 */
	public void insertChunkAfter(final SqlChunk previous) {
		SqlChunk next = previous.nextChunk;
		previous.nextChunk = this;
		this.previousChunk = previous;
		if (next != null) {
			next.previousChunk = this;
			this.nextChunk = next;
		}
	}

	/**
	 * Returns true if previous chunk is of provided type.
	 */
	public boolean isPreviousChunkOfType(final int type) {
		if (previousChunk == null) {
			return false;
		}
		return previousChunk.chunkType == type;
	}

	/**
	 * Returns true if previous chunk is of the same type.
	 */
	public boolean isPreviousChunkOfSameType() {
		if (previousChunk == null) {
			return false;
		}
		return previousChunk.chunkType == chunkType;
	}

	/**
	 * Returns true if previous chunk is not raw.
	 */
	public boolean isPreviousMacroChunk() {
		if (previousChunk == null) {
			return false;
		}
		return previousChunk.chunkType != CHUNK_RAW;
	}

	public boolean isPreviousRawChunk() {
		if (previousChunk == null) {
			return false;
		}
		return previousChunk.chunkType == CHUNK_RAW;
	}

	// ---------------------------------------------------------------- process

	protected TemplateData templateData;      // working template context

	/**
	 * Initializes chunk. Assigns {@link jodd.db.oom.sqlgen.TemplateData} to chunk.
	 * If chunk needs some pre-processing, they should be done here.
	 */
	public void init(final TemplateData templateData) {
		this.templateData = templateData;
	}

	/**
	 * Process the chunk and appends data to the output.
	 */
	public abstract void process(StringBuilder out);

	// ---------------------------------------------------------------- lookup

	/**
	 * Lookups for entity name and throws exception if entity name not found.
	 */
	protected DbEntityDescriptor lookupName(final String entityName) {
		DbEntityDescriptor ded = dbEntityManager.lookupName(entityName);
		if (ded == null) {
			throw new DbSqlBuilderException("Entity name not registered: " + entityName);
		}
		return ded;
	}

	/**
	 * Lookups for entity name and throws an exception if entity type is invalid.
	 */
	protected DbEntityDescriptor lookupType(final Class entity) {
		final DbEntityDescriptor ded = dbEntityManager.lookupType(entity);
		if (ded == null) {
			throw new DbSqlBuilderException("Invalid or not-persistent entity: " + entity.getName());
		}
		return ded;
	}

	/**
	 * Lookups for table reference and throws an exception if table reference not found.
	 */
	protected DbEntityDescriptor lookupTableRef(final String tableRef) {
		return lookupTableRef(tableRef, true);
	}

	/**
	 * Lookups for table reference and optionally throws an exception if table reference not found.
	 */
	protected DbEntityDescriptor lookupTableRef(final String tableRef, final boolean throwExceptionIfNotFound) {
		DbEntityDescriptor ded = templateData.getTableDescriptor(tableRef);
		if (ded == null) {
			if (throwExceptionIfNotFound) {
				throw new DbSqlBuilderException("Invalid table reference: " + tableRef);
			}
		}
		return ded;
	}

	/**
	 * Finds a table that contains given column.
	 */
	protected DbEntityDescriptor findColumnRef(final String columnRef) {
		DbEntityDescriptor ded = templateData.findTableDescriptorByColumnRef(columnRef);
		if (ded == null) {
			throw new DbSqlBuilderException("Invalid column reference: [" + columnRef + "]");
		}
		return ded;
	}

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

	/**
	 * Resolves table name or alias that will be used in the query.
	 */
	protected String resolveTable(final String tableRef, final DbEntityDescriptor ded) {
		String tableAlias = templateData.getTableAlias(tableRef);
		if (tableAlias != null) {
			return tableAlias;
		}
		return ded.getTableNameForQuery();
	}

	/**
	 * Defines parameter with name and its value.
	 */
	protected void defineParameter(final StringBuilder query, String name, final Object value, final DbEntityColumnDescriptor dec) {
		if (name == null) {
			name = templateData.getNextParameterName();
		}
		query.append(':').append(name);
		templateData.addParameter(name, value, dec);
	}

	/**
	 * Resolves object to a class.
	 */
	protected static Class resolveClass(final Object object) {
		Class type = object.getClass();
		return type == Class.class ? (Class) object : type;
	}

	/**
	 * Returns true if a value is considered empty i.e. not existing.
	 */
	protected boolean isEmptyColumnValue(final DbEntityColumnDescriptor dec, final Object value) {
		if (value == null) {
			return true;
		}

		// special case for ID column
		if (dec.isId() && value instanceof Number) {
			final double d = ((Number) value).doubleValue();
			if (d == 0.0d) {
				return true;
			}
		}

		// special case for primitives
		if (dec.getPropertyType().isPrimitive()) {
			if (char.class == dec.getPropertyType()) {
				final Character c = ((Character) value);
				if ('\u0000' == c.charValue()) {
					return true;
				}
			} else {
				final double d = ((Number) value).doubleValue();
				if (d == 0) {
					return true;
				}
			}
		}

		// special case for strings
		if (value instanceof CharSequence) {
			if (StringUtil.isBlank((CharSequence) value)) {
				return true;
			}
		}

		return false;
	}

	// ---------------------------------------------------------------- separation

	/**
	 * Appends missing space if the output doesn't end with whitespace.
	 */
	protected void appendMissingSpace(final StringBuilder out) {
		int len = out.length();
		if (len == 0) {
			return;
		}
		len--;
		if (!CharUtil.isWhitespace(out.charAt(len))) {
			out.append(' ');
		}
	}

	/**
	 * Separates from previous chunk by comma if is of the same type.
	 */
	protected void separateByCommaOrSpace(final StringBuilder out) {
		if (isPreviousChunkOfSameType()) {
			out.append(',').append(' ');
		} else {
			appendMissingSpace(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.vtor.constraint;

import jodd.bean.BeanException;
import jodd.bean.BeanUtil;
import jodd.vtor.ValidationConstraint;
import jodd.vtor.ValidationConstraintContext;
import jodd.vtor.VtorException;

public class EqualToDeclaredFieldConstraint implements ValidationConstraint<EqualToDeclaredField> {

	public EqualToDeclaredFieldConstraint() {
	}

	public EqualToDeclaredFieldConstraint(final String fieldName) {
		this.fieldName = fieldName;
	}

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

	protected String fieldName;

	public String getFieldName() {
		return fieldName;
	}

	public void setFieldName(final String fieldName) {
		this.fieldName = fieldName;
	}

	// ---------------------------------------------------------------- configure

	public void configure(final EqualToDeclaredField annotation) {
		this.fieldName = annotation.value();
	}

	// ---------------------------------------------------------------- valid

	public boolean isValid(final ValidationConstraintContext vcc, final Object value) {
		return validate(vcc.getTarget(), value, fieldName);
	}

	public static boolean validate(final Object target, final Object value, final String fieldName) {
		if (value == null) {
			return true;
		}
		Object valueToCompare;
		try {
			valueToCompare = BeanUtil.declared.getProperty(target, fieldName);
		} catch (BeanException bex) {
			throw new VtorException("Invalid value: " + fieldName, bex);
		}
		if (valueToCompare == null) {
			return false;
		}
		return value.equals(valueToCompare);
	}
}


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

import jodd.db.DbSqlException;

import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * Connection pool data source {@link jodd.db.connection.ConnectionProvider}
 */
public class ConnectionPoolDataSourceConnectionProvider implements ConnectionProvider {
	// ---------------------------------------------------------------- properties

	private ConnectionPoolDataSource cpds;

	public ConnectionPoolDataSourceConnectionProvider(final ConnectionPoolDataSource cpds) {
		this.cpds = cpds;
	}

	// ---------------------------------------------------------------- init/close

	@Override
	public void init() {
	}

	@Override
	public void close() {
		cpds = null;
	}

	// ---------------------------------------------------------------- get/free

	@Override
	public Connection getConnection() {
		PooledConnection pconn;
		try {
			pconn = cpds.getPooledConnection();
		} catch (SQLException sex) {
			throw new DbSqlException("Invalid pooled connection", sex);
		}
		try {
			return pconn.getConnection();
		} catch (SQLException sex) {
			throw new DbSqlException("Invalid pooled connection", sex);
		}
	}

	@Override
	public void closeConnection(final Connection connection) {
		try {
			if (connection != null) {
				connection.close();
			}
		} catch (SQLException sex) {
			// ignore
		}
	}
}


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

import jodd.io.upload.FileUpload;
import jodd.io.upload.FileUploadFactory;
import jodd.servlet.ServletUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * Multi-part HTTP servlet request wrapper.
 *
 * @see MultipartRequest
 */
public class MultipartRequestWrapper extends HttpServletRequestWrapper {

	// ---------------------------------------------------------------- construction

	MultipartRequest mreq;
	HttpServletRequest req;

	public MultipartRequestWrapper(final HttpServletRequest request, final FileUploadFactory fileUploadFactory, final String encoding) throws IOException {
		super(request);
		req = request;
		if (ServletUtil.isMultipartRequest(request)) {
			mreq = MultipartRequest.getInstance(request, fileUploadFactory, encoding);
		}
	}

	public MultipartRequestWrapper(final HttpServletRequest request, final FileUploadFactory fileUploadFactory) throws IOException {
		super(request);
		if (ServletUtil.isMultipartRequest(request)) {
			mreq = MultipartRequest.getInstance(request, fileUploadFactory, null);
		}
	}

	public MultipartRequestWrapper(final MultipartRequest mpreq) {
		super(mpreq.getServletRequest());
		mreq = mpreq;
	}

	/**
	 * Returns {@link MultipartRequest} instance or null if request is not multi-part.
	 */
	public MultipartRequest getMultipartRequest() {
		return mreq;
	}

	/**
	 * Returns true if request is multi-part.
	 */
	public boolean isMultipart() {
		return mreq != null;
	}

	// ---------------------------------------------------------------- methods

	/**
	 * Get an enumeration of the parameter names for uploaded files
	 */
	public Enumeration<String> getFileParameterNames() {
		if (mreq == null) {
			return null;
		}
		return Collections.enumeration(mreq.getFileParameterNames());
	}

	/**
	 * Get a {@link FileUpload} array for the given input field name.
	 *
	 * @param fieldName input field name
	 * @return a File[] object for files associated with the specified input field name
	 */
	public FileUpload[] getFiles(final String fieldName) {
		if (mreq == null) {
			return null;
		}
		return mreq.getFiles(fieldName);
	}

	public FileUpload getFile(final String fieldName) {
		if (mreq == null) {
			return null;
		}
		return mreq.getFile(fieldName);
	}

	/**
	 * @see javax.servlet.http.HttpServletRequest#getParameter(String)
	 */
	@Override
	public String getParameter(final String name) {
		if (mreq == null) {
			return super.getParameter(name);
		}
		return mreq.getParameter(name);
	}

	/**
	 * @see javax.servlet.http.HttpServletRequest#getParameterMap()
	 */
	@Override
	public Map<String, String[]> getParameterMap() {
		if (mreq == null) {
			return super.getParameterMap();
		}
		Map<String, String[]> map = new HashMap<>();
		Enumeration enumeration = getParameterNames();
		while (enumeration.hasMoreElements()) {
			String name = (String) enumeration.nextElement();
			map.put(name, this.getParameterValues(name));
		}
		return map;
	}

	/**
	 * @see javax.servlet.http.HttpServletRequest#getParameterNames()
	 */
	@Override
	@SuppressWarnings({"unchecked"})
	public Enumeration<String> getParameterNames() {
		if (mreq == null) {
			return super.getParameterNames();
		}
		return Collections.enumeration(mreq.getParameterNames());
	}

	/**
	 * @see javax.servlet.http.HttpServletRequest#getParameterValues(String)
	 */
	@Override
	public String[] getParameterValues(final String name) {
		if (mreq == null) {
			return super.getParameterValues(name);
		}
		return mreq.getParameterValues(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.mutable;

/**
 * A mutable long wrapper.
 */
public final class MutableLong extends Number implements Comparable<MutableLong>, Cloneable {

	public static MutableLong of(final long value) {
		return new MutableLong(value);
	}

	public MutableLong() {
	}

	public MutableLong(final long value) {
		this.value = value;
	}

	public MutableLong(final String value) {
		this.value = Long.parseLong(value);
	}

	public MutableLong(final Number number) {
		this.value = number.longValue();
	}

	// ---------------------------------------------------------------- value

	/**
	 * The mutable value.
	 */
	public long value;

	/**
	 * Returns mutable value.
	 */
	public long get() {
		return value;
	}

	/**
	 * Sets mutable value.
	 */
	public void set(final long value) {
		this.value = value;
	}

	/**
	 * Sets mutable value from a Number.
	 */
	public void set(final Number value) {
		this.value = value.longValue();
	}

	// ---------------------------------------------------------------- object

	/**
	 * Stringify the value.
	 */
	@Override
	public String toString() {
		return Long.toString(value);
	}

	/**
	 * Returns a hashcode for this value.
	 */
	@Override
	public int hashCode() {
		return (int) (value ^ (value >>> 32));
	}

	/**
	 * Compares this object to the specified object.
	 *
	 * @param obj the object to compare with.
	 * @return true if the objects are the same;
	 *         false otherwise.
	 */
	@Override
	public boolean equals(final Object obj) {
		if (obj != null) {
			if ( ((Long)this.value).getClass() == obj.getClass() ) {
				return value == ((Long) obj).longValue();
			}
			if (this.getClass() == obj.getClass()) {
				return value == ((MutableLong) obj).value;
			}
		}
		return false;
	}

	// ---------------------------------------------------------------- number

	/**
	 * Returns the value as a int.
	 */
	@Override
	public int intValue() {
		return (int) value;
	}

	/**
	 * Returns the value as a long.
	 */
	@Override
	public long longValue() {
		return value;
	}

	/**
	 * Returns the value as a float.
	 */
	@Override
	public float floatValue() {
		return value;
	}

	/**
	 * Returns the value as a double.
	 */
	@Override
	public double doubleValue() {
		return value;
	}

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

	/**
	 * Compares value of two same instances.
	 */
	@Override
	public int compareTo(final MutableLong other) {
		return value < other.value ? -1 : (value == other.value ? 0 : 1);
	}

	// ---------------------------------------------------------------- clone

	/**
	 * Clones object.
	 */
	@Override
	public MutableLong clone() {
		return new MutableLong(value);
	}

}


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

package jodd.servlet;

import jodd.util.StringPool;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Request utilities for path, uri, query strings etc..
 */
public class DispatcherUtil {

	// ---------------------------------------------------------------- include

	/**
	 * Include page which path is relative to the current HTTP request.
	 */
	public static boolean include(final ServletRequest request, final ServletResponse response, final String page) throws IOException, ServletException {
		RequestDispatcher dispatcher = request.getRequestDispatcher(page);
		if (dispatcher != null) {
			dispatcher.include(request, response);
			return true;
		}
		return false;
	}

	/**
	 * Include named resource.
	 */
	public static boolean includeNamed(final HttpServletRequest request, final ServletResponse response, final String resource) throws IOException, ServletException {
		return includeNamed(request.getServletContext(), request, response, resource);
	}

	/**
	 * Include named resource.
	 */
	public static boolean includeNamed(final ServletContext context, final ServletRequest request, final ServletResponse response, final String page) throws IOException, ServletException {
		RequestDispatcher dispatcher = context.getNamedDispatcher(page);
		if (dispatcher != null) {
			dispatcher.include(request, response);
			return true;
		}
		return false;
	}

	/**
	 * Include page which path relative to the root of the ServletContext.
	 */
	public static boolean includeAbsolute(final HttpServletRequest request, final HttpServletResponse response, final String page) throws IOException, ServletException {
		return includeAbsolute(request.getServletContext(), request, response, page);
	}

	/**
	 * Include page which path relative to the root of the ServletContext.
	 */
	public static boolean includeAbsolute(final ServletContext context, final ServletRequest request, final HttpServletResponse response, final String page) throws IOException, ServletException {
		RequestDispatcher dispatcher = context.getRequestDispatcher(page);
		if (dispatcher != null) {
			dispatcher.include(request, response);
			return true;
		}
		return false;
	}

	// ---------------------------------------------------------------- forward

	/**
	 * Forward to page which path is relative to the current HTTP request.
	 */
	public static boolean forward(final ServletRequest request, final ServletResponse response, final String page) throws IOException, ServletException {
		RequestDispatcher dispatcher = request.getRequestDispatcher(page);
		if (dispatcher != null) {
			dispatcher.forward(request, response);
			return true;
		}
		return false;
	}

	/**
	 * Forward to named resource.
	 */
	public static boolean forwardNamed(final HttpServletRequest request, final ServletResponse response, final String resource) throws IOException, ServletException {
		return forwardNamed(request.getServletContext(), request, response, resource);
	}

	/**
	 * Forward to named resource.
	 */
	public static boolean forwardNamed(final ServletContext context, final ServletRequest request, final ServletResponse response, final String resource) throws IOException, ServletException {
		RequestDispatcher dispatcher = context.getNamedDispatcher(resource);
		if (dispatcher != null) {
			dispatcher.forward(request, response);
			return true;
		}
		return false;
	}

	/**
	 * Forward to page path relative to the root of the ServletContext.
	 */
	public static boolean forwardAbsolute(final HttpServletRequest request, final ServletResponse response, final String page) throws IOException, ServletException {
		return forwardAbsolute(request.getServletContext(), request, response, page);
	}

	/**
	 * Forward to page path relative to the root of the ServletContext.
	 */
	public static boolean forwardAbsolute(final ServletContext context, final ServletRequest request, final ServletResponse response, final String resource) throws IOException, ServletException {
		RequestDispatcher dispatcher = context.getRequestDispatcher(resource);
		if (dispatcher != null) {
			dispatcher.forward(request, response);
			return true;
		}
		return false;
	}

	// ---------------------------------------------------------------- redirect

	/**
	 * Performs redirection (302) to specified url.
	 */
	public static void redirect(final HttpServletRequest request, final HttpServletResponse response, String url) throws IOException {
		if (url.startsWith(StringPool.SLASH)) {
			url = ServletUtil.getContextPath(request) + url;
		}
		response.sendRedirect(response.encodeRedirectURL(url));
	}

	/**
	 * Performs permanent redirection (301) to specified url.
	 */
	public static void redirectPermanent(final HttpServletRequest request, final HttpServletResponse response, String url) {
		if (url.startsWith(StringPool.SLASH)) {
			url = ServletUtil.getContextPath(request) + url;
		}
		response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
		response.setHeader("Location", url);
	}

	// ---------------------------------------------------------------- full urls

	/**
	 * Returns full URL: uri + query string, including the context path.
	 */
	public static String getFullUrl(final HttpServletRequest request) {
		String url = request.getRequestURI();
		String query = request.getQueryString();
		if ((query != null) && (query.length() != 0)) {
			url += '?' + query;
		}
		return url;
	}

	/**
	 * Returns url, without context path, convenient for request dispatcher.
	 */
	public static String getUrl(final HttpServletRequest request) {
		String servletPath = request.getServletPath();
		String query = request.getQueryString();
		if ((query != null) && (query.length() != 0)) {
			servletPath += '?' + query;
		}
		return servletPath;
	}

	// ---------------------------------------------------------------- include

	public static final String INCLUDE_REQUEST_URI = "javax.servlet.include.request_uri";
	public static final String INCLUDE_CONTEXT_PATH = "javax.servlet.include.context_path";
	public static final String INCLUDE_SERVLET_PATH = "javax.servlet.include.servlet_path";
	public static final String INCLUDE_PATH_INFO = "javax.servlet.include.path_info";
	public static final String INCLUDE_QUERY_STRING = "javax.servlet.include.query_string";

	public static String getIncludeRequestUri(final HttpServletRequest request) {
		return (String) request.getAttribute(INCLUDE_REQUEST_URI);
	}

	public static String getIncludeContextPath(final HttpServletRequest request) {
		return (String) request.getAttribute(INCLUDE_CONTEXT_PATH);
	}

	public static String getIncludeServletPath(final HttpServletRequest request) {
		return (String) request.getAttribute(INCLUDE_SERVLET_PATH);
	}

	public static String getIncludePathInfo(final HttpServletRequest request) {
		return (String) request.getAttribute(INCLUDE_PATH_INFO);
	}

	public static String getIncludeQueryString(final HttpServletRequest request) {
		return (String) request.getAttribute(INCLUDE_QUERY_STRING);
	}

	// ---------------------------------------------------------------- forward

	public static final String FORWARD_REQUEST_URI = "javax.servlet.forward.request_uri";
	public static final String FORWARD_CONTEXT_PATH = "javax.servlet.forward.context_path";
	public static final String FORWARD_SERVLET_PATH = "javax.servlet.forward.servlet_path";
	public static final String FORWARD_PATH_INFO = "javax.servlet.forward.path_info";
	public static final String FORWARD_QUERY_STRING = "javax.servlet.forward.query_string";

	public static String getForwardRequestUri(final HttpServletRequest request) {
		return (String) request.getAttribute(FORWARD_REQUEST_URI);
	}

	public static String getForwardContextPath(final HttpServletRequest request) {
		return (String) request.getAttribute(FORWARD_CONTEXT_PATH);
	}

	public static String getForwardServletPath(final HttpServletRequest request) {
		return (String) request.getAttribute(FORWARD_SERVLET_PATH);
	}

	public static String getForwardPathInfo(final HttpServletRequest request) {
		return (String) request.getAttribute(FORWARD_PATH_INFO);
	}

	public static String getForwardQueryString(final HttpServletRequest request) {
		return (String) request.getAttribute(FORWARD_QUERY_STRING);
	}

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

	/**
	 * Returns true if current page is included.
	 */
	public static boolean isPageIncluded(final HttpServletRequest request, final HttpServletResponse response) {
		return (response.isCommitted() || (getIncludeServletPath(request) != null));
	}

	/**
	 * Returns true if request is a top-level one, i.e. previously
	 * not included or forwarded.
	 */
	public static boolean isTopLevelRequest(final HttpServletRequest request) {
		return (getForwardRequestUri(request) == null) && (getIncludeRequestUri(request) == null);
	}

	// ---------------------------------------------------------------- smarter info

	/**
	 * Returns the base (top-level) uri.
	 */
	public static String getBaseRequestUri(final HttpServletRequest request) {
		String result = getForwardRequestUri(request);
		if (result == null) {
			result = request.getRequestURI();
		}
		return result;
	}
	/**
	 * Get current request uri.
	 */
	public static String getRequestUri(final HttpServletRequest request) {
		String result = getIncludeRequestUri(request);
		if (result == null) {
			result = request.getRequestURI();
		}
		return result;
	}

	public static String getBaseContextPath(final HttpServletRequest request) {
		String result = getForwardContextPath(request);
		if (result == null) {
			result = ServletUtil.getContextPath(request);
		}
		return result;
	}
	public static String getContextPath(final HttpServletRequest request) {
		String result = getIncludeContextPath(request);
		if (result == null) {
			result = ServletUtil.getContextPath(request);
		}
		return result;
	}

	public static String getBaseServletPath(final HttpServletRequest request) {
		String result = getForwardServletPath(request);
		if (result == null) {
			result = request.getServletPath();
		}
		return result;
	}
	public static String getServletPath(final HttpServletRequest request) {
		String result = getIncludeServletPath(request);
		if (result == null) {
			result = request.getServletPath();
		}
		return result;
	}

	public static String getBasePathInfo(final HttpServletRequest request) {
		String result = getForwardPathInfo(request);
		if (result == null) {
			result = request.getPathInfo();
		}
		return result;
	}
	public static String getPathInfo(final HttpServletRequest request) {
		String result = getIncludePathInfo(request);
		if (result == null) {
			result = request.getPathInfo();
		}
		return result;
	}

	public static String getBaseQueryString(final HttpServletRequest request) {
		String result = getForwardQueryString(request);
		if (result == null) {
			result = request.getQueryString();
		}
		return result;
	}
	public static String getQueryString(final HttpServletRequest request) {
		String result = getIncludeQueryString(request);
		if (result == null) {
			result = request.getQueryString();
		}
		return result;
	}
}


// 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.paramo.MethodParameter;
import jodd.paramo.Paramo;

import java.lang.reflect.Method;

/**
 * Resolves method parameter names.
 * Does not cache anything, as it is going to be called once per each method.
 * One method will and should not be passed twice to this class!
 */
public class ActionMethodParamNameResolver {

	/**
	 * Returns method parameter names.
	 */
	public String[] resolveParamNames(final Method actionClassMethod) {
		MethodParameter[] methodParameters = Paramo.resolveParameters(actionClassMethod);

		String[] names = new String[methodParameters.length];

		for (int i = 0; i < methodParameters.length; i++) {
			names[i] = methodParameters[i].getName();
		}

		return names;
	}

}


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

import jodd.madvoc.MadvocException;
import jodd.madvoc.component.ActionsManager;
import jodd.madvoc.macro.PathMacros;
import jodd.util.ClassUtil;
import jodd.util.StringPool;
import jodd.util.StringUtil;

/**
 * Utility that deals with routes and route chunks.
 */
public class Routes {
	private static final String ANY_METHOD = StringPool.STAR;

	private final RouteChunk root;
	private final ActionsManager actionsManager;
	private RouteChunk anyMethodChunk;

	public Routes(final ActionsManager actionsManager) {
		this.root = new RouteChunk(this, null, StringPool.EMPTY);
		this.actionsManager = actionsManager;
	}

	public RouteChunk registerPath(String method, String path) {
		if (method == null) {
			method = ANY_METHOD;
		}
		else {
			method = method.toUpperCase();
		}

		RouteChunk chunk = root.findOrCreateChild(method);

		if (method.equals(ANY_METHOD)) {
			// cache common root chunk
			anyMethodChunk = chunk;
		}

		path = StringUtil.cutSurrounding(path, StringPool.SLASH);

		String[] pathChunks = StringUtil.splitc(path, '/');

		for (String pathChunk : pathChunks) {
			chunk = chunk.findOrCreateChild(pathChunk);
		}

		return chunk;
	}

	public ActionRuntime lookup(final String method, final String[] pathChunks) {
		while (true) {
			final ActionRuntime actionRuntime = _lookup(method, pathChunks);
			if (actionRuntime != null) {
				return actionRuntime;
			}

			if (actionsManager.isStrictRoutePaths()) {
				return null;
			}

			// special case
			final String lastPath = pathChunks[pathChunks.length - 1];
			final int lastNdx = lastPath.lastIndexOf('.');
			if (lastNdx == -1) {
				return null;
			}
			final String pathExtension = lastPath.substring(lastNdx + 1);

			if (StringUtil.equalsOne(pathExtension, actionsManager.getPathExtensionsToStrip()) == -1) {
				return null;
			}

			pathChunks[pathChunks.length - 1] = lastPath.substring(0, lastNdx);
		}
	}

	private ActionRuntime _lookup(String method, final String[] pathChunks) {

		// 1 - match method
		if (method != null) {
			method = method.toUpperCase();
			final RouteChunk methodChunk = root.findOrCreateChild(method);
			final ActionRuntime actionRuntime = lookupFrom(methodChunk, pathChunks);
			if (actionRuntime != null) {
				return actionRuntime;
			}
		}

		// 2 - match all methods
		if (anyMethodChunk != null) {
			final ActionRuntime actionRuntime = lookupFrom(anyMethodChunk, pathChunks);
			if (actionRuntime != null) {
				return actionRuntime;
			}
		}

		// nothing found
		return null;
	}

	private ActionRuntime lookupFrom(final RouteChunk chunk, final String[] path) {
		// matched, scan children
		RouteChunk[] children = chunk.children();

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

		for (RouteChunk child : children) {
			ActionRuntime matched = match(child, path, 0);

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

		return null;
	}

	private ActionRuntime match(final RouteChunk chunk, final String[] path, final int ndx) {
		final int maxDeep = path.length - 1;
		if (ndx > maxDeep) {
			// too deep, don't go any further
			return null;
		}

		if (!chunk.match(path[ndx])) {
			// no match, continue
			return null;
		}

		if (ndx == maxDeep) {
			// end of the path
			if (chunk.isEndpoint()) {
				return chunk.value();
			}
			return null;
		}

		// matched, scan children
		final RouteChunk[] children = chunk.children();

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

		ActionRuntime matchedRuntime = null;

		for (final RouteChunk child : children) {
			final ActionRuntime match = match(child, path, ndx + 1);

			if (match != null) {
				if (matchedRuntime == null) {
					matchedRuntime = match;
				}
				else {
					// already matched
					if (matchedRuntime.getRouteChunk().hasMacrosOnPath()) {
						if (!match.getRouteChunk().hasMacrosOnPath()) {
							// previous match IS macro; and current one is NOT
							matchedRuntime = match;
						}
					}
				}
			}
		}

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

		return null;
	}

	// ---------------------------------------------------------------- path macros

	/**
	 * Builds {@link PathMacros action path macros} from given action
	 * path chunks. Returns either null, if
	 * no action path contains no macros, or instance of the PathMacro
	 * implementations.
	 */
	public PathMacros buildActionPathMacros(final String actionPath) {
		if (actionPath.isEmpty()) {
			return null;
		}

		PathMacros pathMacros = createPathMacroInstance();

		if (!pathMacros.init(actionPath, actionsManager.getPathMacroSeparators())) {
			return null;
		}

		return pathMacros;
	}

	/**
	 * Creates new PathMacro instance.
	 */
	private PathMacros createPathMacroInstance() {
		try {
			return ClassUtil.newInstance(actionsManager.getPathMacroClass());
		} catch (Exception ex) {
			throw new MadvocException(ex);
		}
	}

}


// 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.cache.TypeCache;
import jodd.chalk.Chalk256;
import jodd.petite.AutomagicPetiteConfigurator;
import jodd.petite.BeanDefinition;
import jodd.petite.PetiteContainer;
import jodd.petite.proxetta.ProxettaAwarePetiteContainer;
import jodd.petite.scope.SessionScope;
import jodd.petite.scope.SingletonScope;
import jodd.util.ClassUtil;
import jodd.util.StringUtil;
import jodd.util.function.Consumers;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class JoyPetite extends JoyBase implements JoyPetiteConfig {

	protected final Supplier<String> appNameSupplier;
	protected final Supplier<JoyScanner> joyScannerSupplier;
	protected final Supplier<JoyProps> joyPropsSupplier;
	protected final Supplier<JoyProxetta> joyProxettaSupplier;

	protected PetiteContainer petiteContainer;
	protected boolean isWebApplication = true;

	public JoyPetite(
			final Supplier<String> appNameSupplier,
			final Supplier<JoyProxetta> joyProxettaSupplier,
			final Supplier<JoyProps> joyPropsSupplier,
			final Supplier<JoyScanner> joyScannerSupplier) {
		this.appNameSupplier = appNameSupplier;
		this.joyProxettaSupplier = joyProxettaSupplier;
		this.joyScannerSupplier = joyScannerSupplier;
		this.joyPropsSupplier = joyPropsSupplier;
	}

	// ---------------------------------------------------------------- runtime

	/**
	 * Returns PetiteContainer once when it is created.
	 */
	public PetiteContainer getPetiteContainer() {
		return requireStarted(petiteContainer);
	}

	// ---------------------------------------------------------------- config

	private boolean autoConfiguration = true;
	private boolean externalsCache = true;
	private Consumers<PetiteContainer> petiteContainerConsumers = Consumers.empty();

	@Override
	public JoyPetite disableAutoConfiguration() {
		requireNotStarted(petiteContainer);
		autoConfiguration = false;
		return this;
	}

	@Override
	public JoyPetite disableExternalsCache() {
		requireNotStarted(petiteContainer);
		externalsCache = true;
		return this;
	}

	@Override
	public JoyPetite withPetite(final Consumer<PetiteContainer> petiteContainerConsumer) {
		requireNotStarted(petiteContainer);
		petiteContainerConsumers.add(petiteContainerConsumer);
		return this;
	}

	// ---------------------------------------------------------------- lifecycle

	/**
	 * Creates and initializes Petite container.
	 * It will be auto-magically configured by scanning the classpath.
	 */
	@Override
	public void start() {
		initLogger();

		log.info("PETITE start  ----------");

		petiteContainer = createPetiteContainer();

		if (externalsCache) {
			petiteContainer.setExternalsCache(TypeCache.createDefault());
		}

		log.info("Web application? " + isWebApplication);

		if (!isWebApplication) {
			// make session scope to act as singleton scope
			// if this is not a web application (and http session is not available).
			petiteContainer.registerScope(SessionScope.class, new SingletonScope(petiteContainer));
		}

		// load parameters from properties files
		petiteContainer.defineParameters(joyPropsSupplier.get().getProps());

		// automagic configuration
		if (autoConfiguration) {
			final AutomagicPetiteConfigurator automagicPetiteConfigurator =
				new AutomagicPetiteConfigurator(petiteContainer);

			automagicPetiteConfigurator.registerAsConsumer(joyScannerSupplier.get().getClassScanner());
		}

		petiteContainerConsumers.accept(this.petiteContainer);

		log.info("PETITE OK!");
	}

	protected ProxettaAwarePetiteContainer createPetiteContainer() {
		return new ProxettaAwarePetiteContainer(joyProxettaSupplier.get().getProxetta());
	}

	/**
	 * Stops Petite container.
	 */
	@Override
	public void stop() {
		if (log != null) {
			log.info("PETITE stop");
		}
		if (petiteContainer != null) {
			petiteContainer.shutdown();
		}
		petiteContainer = null;
	}

	// ---------------------------------------------------------------- print

	public void printBeans(final int width) {
		final Print print = new Print();

		print.line("Beans", width);

		final List<BeanDefinition> beanDefinitionList = new ArrayList<>();
		final String appName = appNameSupplier.get();
		final String prefix = appName + ".";

		petiteContainer.forEachBean(beanDefinitionList::add);

		beanDefinitionList.stream()
			.sorted((bd1, bd2) -> {
				if (bd1.name().startsWith(prefix)) {
					if (bd2.name().startsWith(prefix)) {
						return bd1.name().compareTo(bd2.name());
					}
					return 1;
				}
				if (bd2.name().startsWith(prefix)) {
					if (bd1.name().startsWith(prefix)) {
						return bd1.name().compareTo(bd2.name());
					}
					return -1;
				}
				return bd1.name().compareTo(bd2.name());
			})
			.forEach(beanDefinition -> {
				print.out(Chalk256.chalk().yellow(), scopeName(beanDefinition), 10);
				print.space();

				print.outLeftRightNewLine(
					Chalk256.chalk().green(), beanDefinition.name(),
					Chalk256.chalk().blue(), ClassUtil.getShortClassName(beanDefinition.type(), 2),
					width - 10 - 1
				);
			});

		print.line(width);
	}

	private String scopeName(final BeanDefinition beanDefinition) {
		String scopeName = beanDefinition.scope().getSimpleName();

		scopeName = StringUtil.cutSuffix(scopeName, "Scope");

		return scopeName.toLowerCase();
	}
}


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

/**
 * Information about the input stack map frame at the "current" instruction of a method. This is
 * implemented as a Frame subclass for a "basic block" containing only one instruction.
 *
 * @author Eric Bruneton
 */
final class CurrentFrame extends Frame {

  CurrentFrame(final Label owner) {
    super(owner);
  }

  /**
   * Sets this CurrentFrame to the input stack map frame of the next "current" instruction, i.e. the
   * instruction just after the given one. It is assumed that the value of this object when this
   * method is called is the stack map frame status just before the given instruction is executed.
   */
  @Override
  void execute(
      final int opcode, final int arg, final Symbol symbolArg, final SymbolTable symbolTable) {
    super.execute(opcode, arg, symbolArg, symbolTable);
    Frame successor = new Frame(null);
    merge(symbolTable, successor, 0);
    copyFrom(successor);
  }
}


// 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 javax.mail.Flags;
import javax.mail.Flags.Flag;
import javax.mail.Header;
import javax.mail.Message.RecipientType;
import javax.mail.search.AndTerm;
import javax.mail.search.BodyTerm;
import javax.mail.search.FlagTerm;
import javax.mail.search.FromStringTerm;
import javax.mail.search.HeaderTerm;
import javax.mail.search.MessageIDTerm;
import javax.mail.search.MessageNumberTerm;
import javax.mail.search.NotTerm;
import javax.mail.search.OrTerm;
import javax.mail.search.ReceivedDateTerm;
import javax.mail.search.RecipientStringTerm;
import javax.mail.search.SearchTerm;
import javax.mail.search.SentDateTerm;
import javax.mail.search.SizeTerm;
import javax.mail.search.SubjectTerm;
import java.util.Date;

/**
 * EmailFilter helps in building boolean queries of search terms.
 * There are two ways how it can be used, both can be combined.
 *

 * First way is constructing boolean expression using groups.
 * Just use {@link #and(EmailFilter...)}, {@link #or(EmailFilter...)}
 * and {@link #not(EmailFilter)} methods that takes any number
 * of filters that will be joined with chosen boolean operator.
 *

 * Second way is more fluent. It may not be used to express some
 * complex queries, but for every-day use it would be enough.
 * Use methods {@link #and()} and {@link #or()} to define
 * how all next terms will be joined. Method {@link #not()}
 * marks the one next term to be added as NOT term.
 */
public class EmailFilter {

	boolean operatorAnd = true;
	boolean nextIsNot;

	/**
	 * Creates new Email filter.
	 */
	public static EmailFilter filter() {
		return new EmailFilter();
	}

	/**
	 * The {@link SearchTerm} to be used.
	 */
	protected SearchTerm searchTerm;

	/**
	 * Defines filter for SUBJECT field.
	 *
	 * @param subject The SUBJECT.
	 * @return this
	 */
	public EmailFilter subject(final String subject) {
		final SearchTerm subjectTerm = new SubjectTerm(subject);
		concat(subjectTerm);
		return this;
	}

	/**
	 * Defines filter for message id.
	 *
	 * @param messageId The message ID.
	 * @return this
	 */
	public EmailFilter messageId(final String messageId) {
		final SearchTerm msgIdTerm = new MessageIDTerm(messageId);
		concat(msgIdTerm);
		return this;
	}

	/**
	 * Defines filteer for message number.
	 *
	 * @param messageNumber The message number.
	 * @return this
	 */
	public EmailFilter messageNumber(final int messageNumber) {
		final SearchTerm msgIdTerm = new MessageNumberTerm(messageNumber);
		concat(msgIdTerm);
		return this;
	}

	/**
	 * Defines filter for FROM field.
	 *
	 * @param fromAddress The FROM address
	 * @return this
	 */
	public EmailFilter from(final String fromAddress) {
		final SearchTerm fromTerm = new FromStringTerm(fromAddress);
		concat(fromTerm);
		return this;
	}

	/**
	 * Defines filter for TO field.
	 *
	 * @param toAddress The TO address.
	 * @return this
	 */
	public EmailFilter to(final String toAddress) {
		final SearchTerm toTerm = new RecipientStringTerm(RecipientType.TO, toAddress);
		concat(toTerm);
		return this;
	}

	/**
	 * Defines filter for CC field.
	 *
	 * @param ccAddress CC addreses.
	 * @return this
	 */
	public EmailFilter cc(final String ccAddress) {
		final SearchTerm toTerm = new RecipientStringTerm(RecipientType.CC, ccAddress);
		concat(toTerm);
		return this;
	}

	/**
	 * Defines filter for BCC field.
	 *
	 * @param bccAddress BCC address.
	 * @return this
	 */
	public EmailFilter bcc(final String bccAddress) {
		final SearchTerm toTerm = new RecipientStringTerm(RecipientType.BCC, bccAddress);
		concat(toTerm);
		return this;
	}

	/**
	 * Defines filter for many flags at once.
	 *
	 * @param flags The {@link Flags} to filter on.
	 * @param value The {@link Flag} setting to check for.
	 * @return this
	 */
	public EmailFilter flags(final Flags flags, final boolean value) {
		final SearchTerm flagTerm = new FlagTerm(flags, value);
		concat(flagTerm);
		return this;
	}

	/**
	 * Defines filter for single flag.
	 *
	 * @param flag  The flag to filter on.
	 * @param value The {@link Flag} setting to check for.
	 * @return this
	 */
	public EmailFilter flag(final Flag flag, final boolean value) {
		final Flags flags = new Flags();
		flags.add(flag);
		return flags(flags, value);
	}

	/**
	 * Defines filter for received date.
	 *
	 * @return this
	 */
	public EmailFilter receivedDate(final Operator operator, final long milliseconds) {
		final SearchTerm term = new ReceivedDateTerm(operator.value, new Date(milliseconds));
		concat(term);
		return this;
	}

	/**
	 * Defines filter for sent date.
	 *
	 * @param operator     {@link Operator} to use.
	 * @param milliseconds the milliseconds since January 1, 1970, 00:00:00 GMT.
	 * @return this
	 */
	public EmailFilter sentDate(final Operator operator, final long milliseconds) {
		final SearchTerm term = new SentDateTerm(operator.value, new Date(milliseconds));
		concat(term);
		return this;
	}

	/**
	 * Defines filter on a message body.
	 * All parts of the message that are of MIME type "text/*" are searched.
	 *
	 * @param pattern String pattern use in body.
	 * @return this
	 */
	public EmailFilter text(final String pattern) {
		final SearchTerm term = new BodyTerm(pattern);
		concat(term);
		return this;
	}

	/**
	 * Defines filter for {@link Header}.
	 *
	 * @param headerName The name of the {@link Header}.
	 * @param pattern    String pattern to use for headerName.
	 * @return this
	 */
	public EmailFilter header(final String headerName, final String pattern) {
		final SearchTerm term = new HeaderTerm(headerName, pattern);
		concat(term);
		return this;
	}

	/**
	 * Defines filter for message size.
	 *
	 * @param comparison {@link Operator}.
	 * @param size       size of message.
	 * @return this
	 */
	public EmailFilter size(final Operator comparison, final int size) {
		final SearchTerm term = new SizeTerm(comparison.value, size);
		concat(term);
		return this;
	}

	/**
	 * Comparison operator.
	 */
	public enum Operator {
		EQ(javax.mail.search.ComparisonTerm.EQ),
		GE(javax.mail.search.ComparisonTerm.GE),
		GT(javax.mail.search.ComparisonTerm.GT),
		LE(javax.mail.search.ComparisonTerm.LE),
		LT(javax.mail.search.ComparisonTerm.LT),
		NE(javax.mail.search.ComparisonTerm.NE);

		private final int value;

		Operator(final int value) {
			this.value = value;
		}
	}

	// ---------------------------------------------------------------- boolean

	/**
	 * Changes concatenation mode to AND.
	 *
	 * @return this
	 */
	public EmailFilter and() {
		this.operatorAnd = true;
		return this;
	}

	/**
	 * Changes concatenation mode to OR.
	 *
	 * @return this
	 */
	public EmailFilter or() {
		this.operatorAnd = false;
		return this;
	}

	/**
	 * Marks next condition to be NOT.
	 *
	 * @return this
	 */
	public EmailFilter not() {
		this.nextIsNot = true;
		return this;
	}

	/**
	 * Defines AND group of filters.
	 *
	 * @param emailFilters array of {@link EmailFilter}s to AND.
	 * @return this
	 */
	public EmailFilter and(final EmailFilter... emailFilters) {
		final SearchTerm[] searchTerms = new SearchTerm[emailFilters.length];

		for (int i = 0; i < emailFilters.length; i++) {
			searchTerms[i] = emailFilters[i].searchTerm;
		}

		concat(new AndTerm(searchTerms));
		return this;
	}

	/**
	 * Defines OR group of filters.
	 *
	 * @param emailFilters array of {@link EmailFilter}s to OR.
	 * @return this
	 */
	public EmailFilter or(final EmailFilter... emailFilters) {
		final SearchTerm[] searchTerms = new SearchTerm[emailFilters.length];

		for (int i = 0; i < emailFilters.length; i++) {
			searchTerms[i] = emailFilters[i].searchTerm;
		}

		concat(new OrTerm(searchTerms));
		return this;
	}

	/**
	 * Appends single filter as NOT.
	 *
	 * @param emailFilter {@link EmailFilter} to append.
	 * @return this
	 */
	public EmailFilter not(final EmailFilter emailFilter) {
		final SearchTerm searchTerm = new NotTerm(emailFilter.searchTerm);
		concat(searchTerm);
		return this;
	}

	// ---------------------------------------------------------------- concat

	/**
	 * Concatenates last search term with new one.
	 *
	 * @param searchTerm searchTerm {@link SearchTerm} concatenate.
	 * @see #and(SearchTerm)
	 * @see #or(SearchTerm)
	 */
	protected void concat(SearchTerm searchTerm) {
		if (nextIsNot) {
			searchTerm = new NotTerm(searchTerm);
			nextIsNot = false;
		}
		if (operatorAnd) {
			and(searchTerm);
		} else {
			or(searchTerm);
		}
	}

	/**
	 * Sets {@link AndTerm} as searchTerm.
	 *
	 * @param searchTerm {@link SearchTerm} to set as AND.
	 */
	protected void and(final SearchTerm searchTerm) {
		if (this.searchTerm == null) {
			this.searchTerm = searchTerm;
			return;
		}

		this.searchTerm = new AndTerm(this.searchTerm, searchTerm);
	}

	/**
	 * Sets {@link OrTerm} searchTerm.
	 *
	 * @param searchTerm {@link SearchTerm} to set as OR.
	 */
	protected void or(final SearchTerm searchTerm) {
		if (this.searchTerm == null) {
			this.searchTerm = searchTerm;
			return;
		}

		this.searchTerm = new OrTerm(this.searchTerm, searchTerm);
	}

	// ---------------------------------------------------------------- term

	/**
	 * Returns search term.
	 *
	 * @return {@link SearchTerm}.
	 */
	public SearchTerm getSearchTerm() {
		return searchTerm;
	}

}