// 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.system.SystemUtil;
import jodd.util.ClassUtil;

import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.util.EnumSet;
import java.util.Objects;

import static javax.servlet.DispatcherType.REQUEST;

/**
 * Joy starts here. You can register this class it in several ways:
 *

 *
Add it in the web.xml

 *
Make a subclass and annotate it with @WebListener

 *
Register it explicitly (using embedded containers)

 *

 */
public class JoyContextListener implements ServletContextListener {

	protected boolean decoraEnabled = false;
	protected String contextPath = "/*";
	protected EnumSet<DispatcherType> madvocDispatcherTypes = EnumSet.of(REQUEST);

	/**
	 * Alternative way for registering Joy listeners.
	 * Sometimes servlet container does not allow adding new listener
	 * from already added listener. This method therefore registers
	 * the listener before container actually called the
	 * callback methods.
	 */
	public static void registerInServletContext(
			final ServletContext servletContext,
			final Class<? extends JoyContextListener> joyContextListenerClass
	) {
		try {
			final JoyContextListener joyContextListener =
				ClassUtil.newInstance(joyContextListenerClass);

			joyContextListener.createJoyAndInitServletContext(servletContext);
		} catch (Exception e) {
			throw new JoyException(e);
		}

		servletContext.addListener(joyContextListenerClass);
	}

	@Override
	public void contextInitialized(final ServletContextEvent sce) {
		final ServletContext servletContext = sce.getServletContext();

		createJoyAndInitServletContext(servletContext).start(servletContext);
	}

	private JoddJoy createJoyAndInitServletContext(final ServletContext servletContext) {
		JoddJoy joy = (JoddJoy) servletContext.getAttribute(JoddJoy.class.getName());

		if (joy == null) {
			joy = createJoy();

			configureServletContext(servletContext);
		}

		servletContext.setAttribute(JoddJoy.class.getName(), joy);
		return joy;
	}

	/**
	 * Creates {@link JoddJoy}. This is a place where to configure the app.
	 */
	protected JoddJoy createJoy() {
		final JoddJoy joy = JoddJoy.get();

		if (SystemUtil.info().isAtLeastJavaVersion(9)) {
			joy.withScanner(joyScanner -> joyScanner.scanClasspathOf(this.getClass()));
		}

		return joy;
	}

	/**
	 * Enables Decora.
	 */
	protected void enableDecora() {
		decoraEnabled = true;
	}

	/**
	 * Defines Madvoc servlet context.
	 */
	protected void setMadvocContextPath(final String context) {
		Objects.requireNonNull(context);
		this.contextPath = context;
	}

	/**
	 * Defines enum set of dispatcher types for the filter.
	 */
	protected void mapMadvocFilterFor(final EnumSet<DispatcherType> dispatcherTypeEnumSet) {
		this.madvocDispatcherTypes = dispatcherTypeEnumSet;
	}

	/**
	 * Configures servlet context.
	 */
	private void configureServletContext(final ServletContext servletContext) {
		servletContext.addListener(jodd.servlet.RequestContextListener.class);

		if (decoraEnabled) {
			final FilterRegistration filter = servletContext.addFilter("decora", jodd.decora.DecoraServletFilter.class);
			filter.addMappingForUrlPatterns(null, true, contextPath);
		}

		final FilterRegistration filter = servletContext.addFilter("madvoc", jodd.madvoc.MadvocServletFilter.class);
		filter.addMappingForUrlPatterns(madvocDispatcherTypes, true, contextPath);
	}

	@Override
	public void contextDestroyed(final ServletContextEvent sce) {
		JoddJoy.get().stop();
	}
}


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

/**
 * Result of a CALL statement.
 */
public class DbCallResult {
	private final DbQueryParser query;
	private final CallableStatement statement;

	DbCallResult(final DbQueryParser parser, final CallableStatement callableStatement) {
		this.query = parser;
		this.statement = callableStatement;
	}

	// ---------------------------------------------------------------- integer

	public int getInteger(final int index) {
		try {
			return statement.getInt(index);
		} catch (SQLException sex) {
			throw newGetParamError(index, sex);
		}
	}

	public int getInteger(final String param) {
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			if (positions.length == 1) {
				return statement.getInt(positions[0]);
			}
			throw newGetParamError(param);
		} catch (SQLException sex) {
			throw newGetParamError(param, sex);
		}
	}

	public int[] getAllInteger(final String param) {
		final int[] positions = query.getNamedParameterIndices(param);
		final int[] result = new int[positions.length];
		try {
			for (int i = 0; i < positions.length; i++) {
				result[i] = statement.getInt(positions[i]);
			}
			return result;
		} catch (SQLException sex) {
			throw newGetParamError(param, sex);
		}
	}

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

	public boolean getBoolean(final int index) {
		try {
			return statement.getBoolean(index);
		} catch (SQLException sex) {
			throw newGetParamError(index, sex);
		}
	}

	public boolean getBoolean(final String param) {
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			if (positions.length == 1) {
				return statement.getBoolean(positions[0]);
			}
			throw newGetParamError(param);
		} catch (SQLException sex) {
			throw newGetParamError(param, sex);
		}
	}

	public boolean[] getAllBoolean(final String param) {
		final int[] positions = query.getNamedParameterIndices(param);
		final boolean[] result = new boolean[positions.length];
		try {
			for (int i = 0; i < positions.length; i++) {
				result[i] = statement.getBoolean(positions[i]);
			}
			return result;
		} catch (SQLException sex) {
			throw newGetParamError(param, sex);
		}
	}

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

	public byte getByte(final int index) {
		try {
			return statement.getByte(index);
		} catch (SQLException sex) {
			throw newGetParamError(index, sex);
		}
	}

	public byte getByte(final String param) {
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			if (positions.length == 1) {
				return statement.getByte(positions[0]);
			}
			throw newGetParamError(param);
		} catch (SQLException sex) {
			throw newGetParamError(param, sex);
		}
	}

	public byte[] getAllByte(final String param) {
		final int[] positions = query.getNamedParameterIndices(param);
		final byte[] result = new byte[positions.length];
		try {
			for (int i = 0; i < positions.length; i++) {
				result[i] = statement.getByte(positions[i]);
			}
			return result;
		} catch (SQLException sex) {
			throw newGetParamError(param, sex);
		}
	}

	// ---------------------------------------------------------------- doublw

	public double getDouble(final int index) {
		try {
			return statement.getDouble(index);
		} catch (SQLException sex) {
			throw newGetParamError(index, sex);
		}
	}

	public double getDouble(final String param) {
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			if (positions.length == 1) {
				return statement.getDouble(positions[0]);
			}
			throw newGetParamError(param);
		} catch (SQLException sex) {
			throw newGetParamError(param, sex);
		}
	}

	public double[] getAllDouble(final String param) {
		final int[] positions = query.getNamedParameterIndices(param);
		final double[] result = new double[positions.length];
		try {
			for (int i = 0; i < positions.length; i++) {
				result[i] = statement.getDouble(positions[i]);
			}
			return result;
		} catch (SQLException sex) {
			throw newGetParamError(param, sex);
		}
	}

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

	public float getFloat(final int index) {
		try {
			return statement.getFloat(index);
		} catch (SQLException sex) {
			throw newGetParamError(index, sex);
		}
	}

	public float getFloat(final String param) {
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			if (positions.length == 1) {
				return statement.getFloat(positions[0]);
			}
			throw newGetParamError(param);
		} catch (SQLException sex) {
			throw newGetParamError(param, sex);
		}
	}

	public float[] getAllFloat(final String param) {
		final int[] positions = query.getNamedParameterIndices(param);
		final float[] result = new float[positions.length];
		try {
			for (int i = 0; i < positions.length; i++) {
				result[i] = statement.getFloat(positions[i]);
			}
			return result;
		} catch (SQLException sex) {
			throw newGetParamError(param, sex);
		}
	}

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

	public String getString(final int index) {
		try {
			return statement.getString(index);
		} catch (SQLException sex) {
			throw newGetParamError(index, sex);
		}
	}

	public String getString(final String param) {
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			if (positions.length == 1) {
				return statement.getString(positions[0]);
			}
			throw newGetParamError(param);
		} catch (SQLException sex) {
			throw newGetParamError(param, sex);
		}
	}

	public String[] getAllString(final String param) {
		final int[] positions = query.getNamedParameterIndices(param);
		final String[] result = new String[positions.length];
		try {
			for (int i = 0; i < positions.length; i++) {
				result[i] = statement.getString(positions[i]);
			}
			return result;
		} catch (SQLException sex) {
			throw newGetParamError(param, sex);
		}
	}

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

	public long getLong(final int index) {
		try {
			return statement.getLong(index);
		} catch (SQLException sex) {
			throw newGetParamError(index, sex);
		}
	}

	public long getLong(final String param) {
		final int[] positions = query.getNamedParameterIndices(param);
		try {
			if (positions.length == 1) {
				return statement.getLong(positions[0]);
			}
			throw newGetParamError(param);
		} catch (SQLException sex) {
			throw newGetParamError(param, sex);
		}
	}

	public long[] getAllLong(final String param) {
		final int[] positions = query.getNamedParameterIndices(param);
		final long[] result = new long[positions.length];
		try {
			for (int i = 0; i < positions.length; i++) {
				result[i] = statement.getLong(positions[i]);
			}
			return result;
		} catch (SQLException sex) {
			throw newGetParamError(param, sex);
		}
	}

	// ---------------------------------------------------------------- exception

	private DbSqlException newGetParamError(final int index, final Exception ex) {
		return new DbSqlException("Invalid SQL parameter with index: " + index, ex);
	}
	private DbSqlException newGetParamError(final String param, final Exception ex) {
		return new DbSqlException("Invalid SQL parameter with name: " + param, ex);
	}
	private DbSqlException newGetParamError(final String param) {
		return new DbSqlException("Invalid number of parameter with name: " + param);
	}

}


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

import jodd.proxetta.ProxyAdvice;
import jodd.proxetta.ProxyTarget;

import static jodd.proxetta.ProxyTarget.returnType;
import static jodd.proxetta.ProxyTarget.targetMethodName;

/**
 * Pathref advice applied on all methods.
 */
public class PathrefAdvice implements ProxyAdvice {

	Pathref pathref;

	/**
	 * Reads method name and appends it. Creates object for next call and
	 * returns that value. If next object is unsupported, it will return null;
	 */
	public Object execute() {
		String methodName = targetMethodName();

		Class returnType = returnType();

		Object next = pathref.continueWith(this, methodName, returnType);

		return ProxyTarget.returnValue(next);
	}

}


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

package jodd.introspector;

import jodd.util.ClassUtil;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;

/**
 * Collection of {@link FieldDescriptor field descriptors}.
 */
public class Fields {

	protected final ClassDescriptor classDescriptor;
	protected final Map<String, FieldDescriptor> fieldsMap;

	// cache
	private FieldDescriptor[] allFields;

	/**
	 * Creates new fields collection.
	 */
	public Fields(final ClassDescriptor classDescriptor) {
		this.classDescriptor = classDescriptor;
		this.fieldsMap = inspectFields();
	}

	/**
	 * Inspects fields and returns map of {@link FieldDescriptor field descriptors}.
	 */
	private Map<String, FieldDescriptor> inspectFields() {
		if (classDescriptor.isSystemClass()) {
			return emptyFields();
		}
		final boolean scanAccessible = classDescriptor.isScanAccessible();
		final Class type = classDescriptor.getType();

		final Field[] fields = scanAccessible ? ClassUtil.getAccessibleFields(type) : ClassUtil.getSupportedFields(type);

		final HashMap<String, FieldDescriptor> map = new HashMap<>(fields.length);

		for (final Field field : fields) {
			final String fieldName = field.getName();

			if (fieldName.equals("serialVersionUID")) {
				continue;
			}

			map.put(fieldName, createFieldDescriptor(field));
		}

		return map;
	}

	/**
	 * Defines empty fields for special cases.
	 */
	private Map<String, FieldDescriptor> emptyFields() {
		allFields = FieldDescriptor.EMPTY_ARRAY;
		return Collections.emptyMap();
	}

	/**
	 * Creates new {@code FieldDescriptor}.
	 */
	protected FieldDescriptor createFieldDescriptor(final Field field) {
		return new FieldDescriptor(classDescriptor, field);
	}

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

	/**
	 * Returns {@link FieldDescriptor field descriptor} for given field name
	 * or null if field does not exist.
	 */
	public FieldDescriptor getFieldDescriptor(final String name) {
		return fieldsMap.get(name);
	}

	/**
	 * Returns all fields of this collection. Returns empty array
	 * if no fields exist. Initialized lazy.
	 */
	public FieldDescriptor[] getAllFieldDescriptors() {
		if (allFields == null) {
			FieldDescriptor[] allFields = new FieldDescriptor[fieldsMap.size()];

			int index = 0;
			for (FieldDescriptor fieldDescriptor : fieldsMap.values()) {
				allFields[index] = fieldDescriptor;
				index++;
			}

			Arrays.sort(allFields, Comparator.comparing(fd -> fd.getField().getName()));

			this.allFields = allFields;
		}
		return allFields;
	}

}


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

package jodd.db.type;

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

public class BlobSqlType extends SqlType<Blob> {

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

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

}


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

package jodd.petite.proxetta;

import jodd.petite.BeanDefinition;
import jodd.petite.PetiteConfig;
import jodd.petite.PetiteContainer;
import jodd.petite.WiringMode;
import jodd.petite.scope.Scope;
import jodd.proxetta.Proxetta;
import jodd.proxetta.ProxettaFactory;
import jodd.proxetta.ProxyAspect;

import java.util.function.Consumer;

/**
 * Proxetta-aware Petite container that applies proxies on bean registration.
 */
public class ProxettaAwarePetiteContainer extends PetiteContainer {

	protected final Proxetta<?, ProxyAspect> proxetta;

	public ProxettaAwarePetiteContainer(final Proxetta<?, ProxyAspect> proxetta) {
		this.proxetta = proxetta;
	}
	public ProxettaAwarePetiteContainer(final Proxetta<?, ProxyAspect> proxetta, final PetiteConfig petiteConfig) {
		super(petiteConfig);
		this.proxetta = proxetta;
	}

	/**
	 * Applies proxetta on bean class before bean registration.
	 */
	@SuppressWarnings("unchecked")
	@Override
	protected <T> BeanDefinition<T> createBeanDefinitionForRegistration(
			final String name,
			Class<T> type,
			final Scope scope,
			final WiringMode wiringMode,
			final Consumer<T> consumer)
	{
		if (proxetta != null) {
			final Class originalType = type;

			final ProxettaFactory builder = proxetta.proxy();

			builder.setTarget(type);

			type = builder.define();

			return new ProxettaBeanDefinition(
				name,
				type,
				scope,
				wiringMode,
				originalType,
				proxetta.getAspects(new ProxyAspect[0]),
				consumer);
		}

		return super.createBeanDefinitionForRegistration(name, type, scope, wiringMode, consumer);
	}

}


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

import jodd.db.DbTransactionMode;
import jodd.jtx.JtxTransactionMode;

/**
 * Helpers for jtx db.
 */
public class JtxDbUtil {

	/**
	 * Converter JTX transaction mode to DB transaction mode.
	 */
	public static DbTransactionMode convertToDbMode(final JtxTransactionMode txMode) {
		final int isolation;
		switch (txMode.getIsolationLevel()) {
			case ISOLATION_DEFAULT: isolation = DbTransactionMode.ISOLATION_DEFAULT; break;
			case ISOLATION_NONE: isolation = DbTransactionMode.ISOLATION_NONE; break;
			case ISOLATION_READ_COMMITTED: isolation = DbTransactionMode.ISOLATION_READ_COMMITTED; break;
			case ISOLATION_READ_UNCOMMITTED: isolation = DbTransactionMode.ISOLATION_READ_UNCOMMITTED; break;
			case ISOLATION_REPEATABLE_READ: isolation = DbTransactionMode.ISOLATION_REPEATABLE_READ; break;
			case ISOLATION_SERIALIZABLE: isolation = DbTransactionMode.ISOLATION_SERIALIZABLE; break;
			default:
				throw new IllegalArgumentException();
		}
		return new DbTransactionMode(isolation, txMode.isReadOnly());
	}
}


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

package jodd.db.type;

import jodd.db.DbSqlException;

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

/**
 * Char SQL type.
 */
public class CharacterSqlType extends NullAwareSqlType<Character> {

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Character get(final ResultSet rs, final int index, final int dbSqlType) throws SQLException {
		if (TypesUtil.isIntegerType(dbSqlType)) {
			return Character.valueOf((char) rs.getInt(index));
		}
		String s = rs.getString(index);
		if (s == null) {
			return null;
		}
		if (s.length() > 1) {
			throw new DbSqlException("Char column size too long, should be 1");
		}
		return Character.valueOf(s.charAt(0));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void set(final PreparedStatement st, final int index, final Character value, final int dbSqlType) throws SQLException {
		if (TypesUtil.isIntegerType(dbSqlType)) {
			st.setInt(index, value.charValue());
			return;
		}
		st.setString(index, value.toString());
	}

}


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

package jodd.json;

import jodd.introspector.Getter;
import jodd.introspector.PropertyDescriptor;

/**
 * Bean visitor that serializes properties of a bean.
 * It analyzes the rules for inclusion/exclusion of a property.
 */
public class BeanSerializer extends TypeJsonVisitor {

	protected final Object source;

	public BeanSerializer(final JsonContext jsonContext, final Object bean) {
		super(jsonContext, bean.getClass());

		this.source = bean;
	}

	/**
	 * Serializes a bean.
	 */
	public void serialize() {
		visit();
	}

	/**
	 * Reads property value and {@link #onSerializableProperty(String, Class, Object) serializes it}.
	 */
	@Override
	protected final void onSerializableProperty(String propertyName, final PropertyDescriptor propertyDescriptor) {
		final Object value;

		if (propertyDescriptor == null) {
			// metadata - classname
			value = source.getClass().getName();
		} else {
			value = readProperty(source, propertyDescriptor);

			if ((value == null) && jsonContext.isExcludeNulls()) {
				return;
			}

			// change name for properties

			propertyName = typeData.resolveJsonName(propertyName);
		}

		onSerializableProperty(
				propertyName,
				propertyDescriptor == null ? null : propertyDescriptor.getType(),
				value);
	}

	/**
	 * Invoked on serializable properties, that have passed all the rules.
	 * Property type is null for metadata class name property.
	 */
	protected void onSerializableProperty(final String propertyName, final Class propertyType, final Object value) {
		jsonContext.pushName(propertyName, count > 0);

		jsonContext.serialize(value);

		if (jsonContext.isNamePopped()) {
			count++;
		}
	}

	/**
	 * Reads property using property descriptor.
	 */
	private Object readProperty(final Object source, final PropertyDescriptor propertyDescriptor) {
		Getter getter = propertyDescriptor.getGetter(declared);

		if (getter != null) {
			try {
				return getter.invokeGetter(source);
			}
			catch (Exception ex) {
				throw new JsonException(ex);
			}
		}

		return null;
	}

}


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

package jodd.util;

import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;

/**
 * Some general utilities.
 * All methods are safe as possible and operates with as many types as possible.
 */
public class Util {

	/**
	 * Safely compares two objects just like equals() would, except
	 * it allows any of the 2 objects to be null.
	 *
	 * @return true if arguments are equal, otherwise false
	 */
	public static boolean equals(final Object obj1, final Object obj2) {
		return (obj1 != null) ? (obj1.equals(obj2)) : (obj2 == null);
	}

	/**
	 * Returns string representation of an object, while checking for null.
	 */
	public static String toString(final Object value) {
		if (value == null) {
			return null;
		}
		return value.toString();
	}

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

	/**
	 * Returns length of the object. Returns 0 for null.
	 * Returns -1 for objects without a length.
	 */
	public static int length(final Object obj) {
		if (obj == null) {
			return 0;
		}
		if (obj instanceof CharSequence) {
			return ((CharSequence) obj).length();
		}
		if (obj instanceof Collection) {
			return ((Collection) obj).size();
		}
		if (obj instanceof Map) {
			return ((Map) obj).size();
		}

		int count;
		if (obj instanceof Iterator) {
			Iterator iter = (Iterator) obj;
			count = 0;
			while (iter.hasNext()) {
				count++;
				iter.next();
			}
			return count;
		}
		if (obj instanceof Enumeration) {
			Enumeration enumeration = (Enumeration) obj;
			count = 0;
			while (enumeration.hasMoreElements()) {
				count++;
				enumeration.nextElement();
			}
			return count;
		}
		if (obj.getClass().isArray()) {
			return Array.getLength(obj);
		}
		return -1;
	}

	/**
	 * Returns true if first argument contains provided element.
	 * It works for strings, collections, maps and arrays.
s	 */
	public static boolean containsElement(final Object obj, final Object element) {
		if (obj == null) {
			return false;
		}
		if (obj instanceof String) {
			if (element == null) {
				return false;
			}
			return ((String) obj).contains(element.toString());
		}
		if (obj instanceof Collection) {
			return ((Collection) obj).contains(element);
		}
		if (obj instanceof Map) {
			return ((Map) obj).values().contains(element);
		}

		if (obj instanceof Iterator) {
			Iterator iter = (Iterator) obj;
			while (iter.hasNext()) {
				Object o = iter.next();
				if (equals(o, element)) {
					return true;
				}
			}
			return false;
		}
		if (obj instanceof Enumeration) {
			Enumeration enumeration = (Enumeration) obj;
			while (enumeration.hasMoreElements()) {
				Object o = enumeration.nextElement();
				if (equals(o, element)) {
					return true;
				}
			}
			return false;
		}
		if (obj.getClass().isArray()) {
			int len = Array.getLength(obj);
			for (int i = 0; i < len; i++) {
				Object o = Array.get(obj, i);
				if (equals(o, element)) {
					return true;
				}
			}
		}
		return false;
	}

}


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

package jodd.mail;

import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.URLName;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class EMLComposer extends EMLProperties<EMLComposer> {

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

	/**
	 * Creates EML string from given {@link Email}.
	 *
	 * @param email {@link Email} from which to create EML {@link String}.
	 * @return {@link String} with EML content.
	 */
	public String compose(final Email email) {
		if (getSession() == null) {
			createSession(getProperties());
		}

		final OutputStreamTransport ost = new OutputStreamTransport(getSession());

		final SendMailSession sendMailSession = new SendMailSession(getSession(), ost);

		sendMailSession.sendMail(email);

		return ost.getEml();
	}

	/**
	 * Creates EML string from given {@link ReceivedEmail}.
	 *
	 * @param receivedEmail {@link ReceivedEmail} from which to create EML {@link String}.
	 * @return {@link String} with EML content.
	 */
	public String compose(final ReceivedEmail receivedEmail) {
		Message msg = receivedEmail.originalMessage();

		final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

		try {
			msg.writeTo(outputStream);
		} catch (IOException | MessagingException e) {
			throw new MailException(e);
		}

		return outputStream.toString();
	}

	/**
	 * Special transport that writes message into the {@link OutputStream}.
	 */
	private static class OutputStreamTransport extends Transport {

		/**
		 * Creates a new {@link OutputStreamTransport}.
		 *
		 * @param session {@link Session}.
		 */
		public OutputStreamTransport(final Session session) {
			super(session, new URLName("JODD_MAIL_2_EML", null, -1, null, null, null));
		}

		/**
		 * Sends a message.
		 *
		 * @param msg       {@link Message} to send.
		 * @param addresses array of {@link Address}es to send to.
		 */
		@Override
		public void sendMessage(final Message msg, final Address[] addresses) {
			final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

			try {
				msg.writeTo(outputStream);
			} catch (IOException | MessagingException e) {
				throw new MailException(e);
			}

			eml = outputStream.toString();
		}

		/**
		 * Returns the EML content.
		 *
		 * @return EML content.
		 */
		public String getEml() {
			return eml;
		}

		/**
		 * String with EML content.
		 */
		private String eml;
	}
}


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

import java.util.Arrays;

/**
 * Faster {@code double} buffer.
 */
public class FastDoubleBuffer {

	private double[] buffer;
	private int offset;

	/**
	 * Creates a new {@code double} buffer. The buffer capacity is
	 * initially 64 doubles, though its size increases if necessary.
	 */
	public FastDoubleBuffer() {
		this.buffer = new double[64];
	}

	/**
	 * Creates a new {@code double} buffer, with a buffer capacity of
	 * the specified size.
	 *
	 * @param size the initial size.
	 * @throws IllegalArgumentException if size is negative.
	 */
	public FastDoubleBuffer(final int size) {
		this.buffer = new double[size];
	}

	/**
	 * Grows the buffer.
	 */
	private void grow(final int minCapacity) {
		final int oldCapacity = buffer.length;
		int newCapacity = oldCapacity << 1;
		if (newCapacity - minCapacity < 0) {
			// special case, min capacity is larger then a grow
			newCapacity = minCapacity + 512;
		}
		buffer = Arrays.copyOf(buffer, newCapacity);
	}

	/**
	 * Appends single {@code double} to buffer.
	 */
	public void append(final double element) {
		if (offset - buffer.length >= 0) {
			grow(offset);
		}

		buffer[offset++] = element;
	}

	/**
	 * Appends {@code double} array to buffer.
	 */
	public FastDoubleBuffer append(final double[] array, final int off, final int len) {
		if (offset + len - buffer.length > 0) {
			grow(offset + len);
		}

		System.arraycopy(array, off, buffer, offset, len);
		offset += len;
		return this;
	}

	/**
	 * Appends {@code double} array to buffer.
	 */
	public FastDoubleBuffer append(final double[] array) {
		return append(array, 0, array.length);
	}

	/**
	 * Appends another fast buffer to this one.
	 */
	public FastDoubleBuffer append(final FastDoubleBuffer buff) {
		if (buff.offset == 0) {
			return this;
		}
		append(buff.buffer, 0, buff.offset);
		return this;
	}

	/**
	 * Returns buffer size.
	 */
	public int size() {
		return offset;
	}

	/**
	 * Tests if this buffer has no elements.
	 */
	public boolean isEmpty() {
		return offset == 0;
	}

	/**
	 * Resets the buffer content.
	 */
	public void clear() {
		offset = 0;
	}

	/**
	 * Creates {@code double} array from buffered content.
	 */
	public double[] toArray() {
		return Arrays.copyOf(buffer, offset);
	}

	/**
	 * Creates {@code double} subarray from buffered content.
	 */
	public double[] toArray(final int start, final int len) {
		final double[] array = new double[len];

		if (len == 0) {
			return array;
		}

		System.arraycopy(buffer, start, array, 0, len);

		return array;
	}

	/**
	 * Returns {@code double} element at given index.
	 */
	public double get(final int index) {
		if (index >= offset) {
			throw new IndexOutOfBoundsException();
		}
		return buffer[index];
	}

}


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

package jodd.util.collection;

import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

/**
 * A Map that accepts int or Integer keys only. The implementation is based on
 * java.util.HashMap. IntHashMap is about 25% faster.
 *
 * @see java.util.HashMap
 */

public class IntHashMap extends AbstractMap implements Cloneable, Serializable {

	/**
	 * The hash table data.
	 */
	private transient Entry[] table;

	/**
	 * The total number of mappings in the hash table.
	 */
	private transient int count;

	/**
	 * The table is rehashed when its size exceeds this threshold. (The value of
	 * this field is (int)(capacity * loadFactor).)
	 */
	private int threshold;

	/**
	 * The load factor for the hashtable.
	 */
	private float loadFactor;

	/**
	 * The number of times this IntHashMap has been structurally modified
	 * Structural modifications are those that change the number of mappings in
	 * the IntHashMap or otherwise modify its internal structure (e.g., rehash).
	 * This field is used to make iterators on Collection-views of the IntHashMap
	 * fail-fast.
	 */
	private transient int modCount;

	/**
	 * Constructs a new, empty map with the specified initial
	 * capacity and the specified load factor.
	 *
	 * @param initialCapacity the initial capacity of the IntHashMap.
	 * @param loadFactor the load factor of the IntHashMap
	 * @throws IllegalArgumentException if the initial capacity is less
	 * than zero, or if the load factor is non-positive.
	 */
	public IntHashMap(int initialCapacity, final float loadFactor) {
		if (initialCapacity < 0) {
			throw new IllegalArgumentException("Invalid initial capacity: " + initialCapacity);
		}
		if (loadFactor <= 0) {
			throw new IllegalArgumentException("Invalid load factor: " + loadFactor);
		}
		if (initialCapacity == 0) {
			initialCapacity = 1;
		}
		this.loadFactor = loadFactor;
		table = new Entry[initialCapacity];
		threshold = (int) (initialCapacity * loadFactor);
	}

	/**
	 * Constructs a new, empty map with the specified initial capacity
	 * and default load factor, which is 0.75.
	 *
	 * @param initialCapacity the initial capacity of the IntHashMap.
	 * @throws IllegalArgumentException if the initial capacity is less
	 * than zero.
	 */
	public IntHashMap(final int initialCapacity) {
		this(initialCapacity, 0.75f);
	}

	/**
	 * Constructs a new, empty map with a default capacity and load
	 * factor, which is 0.75.
	 */
	public IntHashMap() {
		this(101, 0.75f);
	}

	/**
	 * Constructs a new map with the same mappings as the given map.  The
	 * map is created with a capacity of twice the number of mappings in
	 * the given map or 11 (whichever is greater), and a default load factor,
	 * which is 0.75.
	 */
	public IntHashMap(final Map t) {
		this(Math.max(2 * t.size(), 11), 0.75f);
		putAll(t);
	}

	/**
	 * Returns the number of key-value mappings in this map.
	 *
	 * @return the number of key-value mappings in this map.
	 */
	@Override
	public int size() {
		return count;
	}

	/**
	 * Returns true if this map contains no key-value mappings.
	 *
	 * @return true if this map contains no key-value mappings.
	 */
	@Override
	public boolean isEmpty() {
		return count == 0;
	}

	/**
	 * Returns true if this map maps one or more keys to the
	 * specified value.
	 *
	 * @param value value whose presence in this map is to be tested.
	 * @return true if this map maps one or more keys to the
	 * specified value.
	 */
	@Override
	public boolean containsValue(final Object value) {
		Entry[] tab = table;
		if (value == null) {
			for (int i = tab.length; i-- > 0; ) {
				for (Entry e = tab[i]; e != null; e = e.next) {
					if (e.value == null) {
						return true;
					}
				}
			}
		}
		else {
			for (int i = tab.length; i-- > 0; ) {
				for (Entry e = tab[i]; e != null; e = e.next) {
					if (value.equals(e.value)) {
						return true;
					}
				}
			}
		}
		return false;
	}

	/**
	 * Returns true if this map contains a mapping for the specified
	 * key.
	 *
	 * @param key key whose presence in this Map is to be tested.
	 * @return true if this map contains a mapping for the specified
	 * key.
	 */
	@Override
	public boolean containsKey(final Object key) {
		if (key instanceof Number) {
			return containsKey(((Number) key).intValue());
		}
		return false;
	}

	/**
	 * Returns true if this map contains a mapping for the specified
	 * key.
	 *
	 * @param key key whose presence in this Map is to be tested.
	 * @return true if this map contains a mapping for the specified
	 * key.
	 */
	public boolean containsKey(final int key) {
		Entry[] tab = table;

		int index = (key & 0x7FFFFFFF) % tab.length;
		for (Entry e = tab[index]; e != null; e = e.next) {
			if (e.key == key) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Returns the value to which this map maps the specified key. Returns
	 * null if the map contains no mapping for this key. A return
	 * value of null does not necessarily indicate that the
	 * map contains no mapping for the key; it's also possible that the map
	 * explicitly maps the key to null. The containsKey
	 * operation may be used to distinguish these two cases.
	 *
	 * @param key key whose associated value is to be returned.
	 * @return the value to which this map maps the specified key.
	 */
	@Override
	public Object get(final Object key) {
		if (key instanceof Number) {
			return get(((Number) key).intValue());
		}
		return null;
	}

	/**
	 * Returns the value to which this map maps the specified key. Returns
	 * null if the map contains no mapping for this key. A return
	 * value of null does not necessarily indicate that the
	 * map contains no mapping for the key; it's also possible that the map
	 * explicitly maps the key to null. The containsKey
	 * operation may be used to distinguish these two cases.
	 *
	 * @param key key whose associated value is to be returned.
	 * @return the value to which this map maps the specified key.
	 */
	public Object get(final int key) {
		Entry[] tab = table;

		int index = (key & 0x7FFFFFFF) % tab.length;
		for (Entry e = tab[index]; e != null; e = e.next) {
			if (e.key == key) {
				return e.value;
			}
		}

		return null;
	}

	/**
	 * Rehashes the contents of this map into a new IntHashMap
	 * instance with a larger capacity. This method is called automatically when
	 * the number of keys in this map exceeds its capacity and load factor.
	 */
	private void rehash() {
		int oldCapacity = table.length;
		Entry[] oldMap = table;

		int newCapacity = (oldCapacity << 1) + 1;
		Entry[] newMap = new Entry[newCapacity];

		modCount++;
		threshold = (int) (newCapacity * loadFactor);
		table = newMap;

		for (int i = oldCapacity; i-- > 0; ) {
			for (Entry old = oldMap[i]; old != null; ) {
				Entry e = old;
				old = old.next;

				int index = (e.key & 0x7FFFFFFF) % newCapacity;
				e.next = newMap[index];
				newMap[index] = e;
			}
		}
	}

	/**
	 * Associates the specified value with the specified key in this map. If the
	 * map previously contained a mapping for this key, the old value is
	 * replaced.
	 *
	 * @param key key with which the specified value is to be associated.
	 * @param value value to be associated with the specified key.
	 * @return previous value associated with specified key, or null if
	 * there was no mapping for key. A null return can also indicate
	 * that the IntHashMap previously associated null with the
	 * specified key.
	 */
	@Override
	public Object put(final Object key, final Object value) {
		if (key instanceof Number) {
			return put(((Number) key).intValue(), value);
		}
		throw new UnsupportedOperationException("IntHashMap key must be a Number");
	}

	/**
	 * Associates the specified value with the specified key in this map. If the
	 * map previously contained a mapping for this key, the old value is
	 * replaced.
	 *
	 * @param key key with which the specified value is to be associated.
	 * @param value value to be associated with the specified key.
	 * @return previous value associated with specified key, or null if
	 * there was no mapping for key. A null return can also indicate
	 * that the IntHashMap previously associated null with the
	 * specified key.
	 */
	public Object put(final int key, final Object value) {
		// makes sure the key is not already in the IntHashMap.
		Entry[] tab = table;

		int index = (key & 0x7FFFFFFF) % tab.length;
		for (Entry e = tab[index]; e != null; e = e.next) {
			if (e.key == key) {
				Object old = e.value;
				e.value = value;
				return old;
			}
		}

		modCount++;
		if (count >= threshold) {
			// rehash the table if the threshold is exceeded
			rehash();

			tab = table;
			index = (key & 0x7FFFFFFF) % tab.length;
		}

		// creates the new entry.
		tab[index] = new Entry(key, value, tab[index]);
		count++;
		return null;
	}

	/**
	 * Removes the mapping for this key from this map if present.
	 *
	 * @param key key whose mapping is to be removed from the map.
	 * @return previous value associated with specified key, or null if
	 * there was no mapping for key. A null return can also indicate
	 * that the map previously associated null with the specified
	 * key.
	 */
	@Override
	public Object remove(final Object key) {
		if (key instanceof Number) {
			return remove(((Number) key).intValue());
		}
		return null;
	}

	/**
	 * Removes the mapping for this key from this map if present.
	 *
	 * @param key key whose mapping is to be removed from the map.
	 * @return previous value associated with specified key, or null if
	 * there was no mapping for key. A null return can also indicate
	 * that the map previously associated null with the specified
	 * key.
	 */
	public Object remove(final int key) {
		Entry[] tab = table;

		int index = (key & 0x7FFFFFFF) % tab.length;

		for (Entry e = tab[index], prev = null; e != null;
			 prev = e, e = e.next) {

			if (e.key == key) {
				modCount++;
				if (prev != null) {
					prev.next = e.next;
				}
				else {
					tab[index] = e.next;
				}

				count--;
				Object oldValue = e.value;
				e.value = null;
				return oldValue;
			}
		}

		return null;
	}

	/**
	 * Copies all of the mappings from the specified map to this one.
	 * These mappings replace any mappings that this map had for any of the
	 * keys currently in the specified Map.
	 *
	 * @param t Mappings to be stored in this map.
	 */
	@Override
	public void putAll(final Map t) {
		for (Object o : t.entrySet()) {
			Map.Entry e = (Map.Entry) o;
			put(e.getKey(), e.getValue());
		}
	}

	/**
	 * Removes all mappings from this map.
	 */
	@Override
	public void clear() {
		Entry[] tab = table;
		modCount++;
		for (int index = tab.length; --index >= 0; ) {
			tab[index] = null;
		}
		count = 0;
	}

	/**
	 * Returns a shallow copy of this IntHashMap instance: the keys and
	 * values themselves are not cloned.
	 *
	 * @return a shallow copy of this map.
	 */
	@Override
	public IntHashMap clone() {
		try {
			IntHashMap t = (IntHashMap) super.clone();
			t.table = new Entry[table.length];
			for (int i = table.length; i-- > 0; ) {
				t.table[i] = (table[i] != null)
						? (Entry) table[i].clone() : null;
			}
			t.keySet = null;
			t.entrySet = null;
			t.values = null;
			t.modCount = 0;
			return t;
		} catch (CloneNotSupportedException e) {
			// this shouldn't happen, since we are Cloneable
			throw new InternalError();
		}
	}

	// views
	private transient Set keySet;
	private transient Set entrySet;
	private transient Collection values;

	/**
	 * Returns a set view of the keys contained in this map. The set is backed by
	 * the map, so changes to the map are reflected in the set, and vice-versa.
	 * The set supports element removal, which removes the corresponding mapping
	 * from this map, via the Iterator.remove,
	 * Set.remove, removeAll, retainAll,
	 * and clear operations. It does not support the
	 * add or addAll operations.
	 *
	 * @return a set view of the keys contained in this map.
	 */
	@Override
	public Set<Integer> keySet() {
		if (keySet == null) {
			keySet = new AbstractSet<Integer>() {
				@Override
				public Iterator<Integer> iterator() {
					return new IntHashIterator(KEYS);
				}

				@Override
				public int size() {
					return count;
				}

				@Override
				public boolean contains(final Object o) {
					return containsKey(o);
				}

				@Override
				public boolean remove(final Object o) {
					return IntHashMap.this.remove(o) != null;
				}

				@Override
				public void clear() {
					IntHashMap.this.clear();
				}
			};
		}
		return keySet;
	}

	/**
	 * Returns a collection view of the values contained in this map. The
	 * collection is backed by the map, so changes to the map are reflected in
	 * the collection, and vice-versa. The collection supports element removal,
	 * which removes the corresponding mapping from this map, via the
	 * Iterator.remove, Collection.remove,
	 * removeAll, retainAll, and clear
	 * operations. It does not support the add or
	 * addAll operations.
	 *
	 * @return a collection view of the values contained in this map.
	 */
	@Override
	public Collection values() {
		if (values == null) {
			values = new AbstractCollection() {
				@Override
				public Iterator iterator() {
					return new IntHashIterator(VALUES);
				}

				@Override
				public int size() {
					return count;
				}

				@Override
				public boolean contains(final Object o) {
					return containsValue(o);
				}

				@Override
				public void clear() {
					IntHashMap.this.clear();
				}
			};
		}
		return values;
	}

	/**
	 * Returns a collection view of the mappings contained in this map. Each
	 * element in the returned collection is a Map.Entry. The
	 * collection is backed by the map, so changes to the map are reflected in
	 * the collection, and vice-versa. The collection supports element removal,
	 * which removes the corresponding mapping from the map, via the
	 * Iterator.remove, Collection.remove,
	 * removeAll, retainAll, and clear
	 * operations. It does not support the add or
	 * addAll operations.
	 *
	 * @return a collection view of the mappings contained in this map.
	 *
	 * @see java.util.Map.Entry
	 */
	@Override
	public Set<Map.Entry<Integer, Object>> entrySet() {
		if (entrySet == null) {
			entrySet = new AbstractSet() {
				@Override
				public Iterator iterator() {
					return new IntHashIterator(ENTRIES);
				}

				@Override
				public boolean contains(final Object o) {
					if (!(o instanceof Map.Entry)) {
						return false;
					}
					Map.Entry entry = (Map.Entry) o;
					Object key = entry.getKey();
					Entry[] tab = table;
					int hash = (key == null ? 0 : key.hashCode());
					int index = (hash & 0x7FFFFFFF) % tab.length;

					for (Entry e = tab[index]; e != null; e = e.next) {
						if (e.key == hash && e.equals(entry)) {
							return true;
						}
					}
					return false;
				}

				@Override
				public boolean remove(final Object o) {
					if (!(o instanceof Map.Entry)) {
						return false;
					}
					Map.Entry entry = (Map.Entry) o;
					Object key = entry.getKey();
					Entry[] tab = table;
					int hash = (key == null ? 0 : key.hashCode());
					int index = (hash & 0x7FFFFFFF) % tab.length;

					for (Entry e = tab[index], prev = null; e != null;
						 prev = e, e = e.next) {
						if (e.key == hash && e.equals(entry)) {
							modCount++;
							if (prev != null) {
								prev.next = e.next;
							}
							else {
								tab[index] = e.next;
							}

							count--;
							e.value = null;
							return true;
						}
					}
					return false;
				}

				@Override
				public int size() {
					return count;
				}

				@Override
				public void clear() {
					IntHashMap.this.clear();
				}
			};
		}

		return entrySet;
	}

	/**
	 * IntHashMap collision list entry.
	 */
	private static class Entry implements Map.Entry<Integer, Object>, Cloneable {
		int key;
		Object value;
		Entry next;
		private Integer objectKey;

		Entry(final int key, final Object value, final Entry next) {
			this.key = key;
			this.value = value;
			this.next = next;
		}

		@Override
		protected Object clone() {
			return new Entry(key, value,
					(next == null ? null : (Entry) next.clone()));
		}

		// Map.Entry Ops

		@Override
		public Integer getKey() {
			if (objectKey == null) {
				objectKey = Integer.valueOf(key);
			}
			return objectKey;
		}

		@Override
		public Object getValue() {
			return value;
		}

		@Override
		public Object setValue(final Object value) {
			Object oldValue = this.value;
			this.value = value;
			return oldValue;
		}

		@Override
		public boolean equals(final Object o) {
			if (this.getClass() != o.getClass()) {
				return false;
			}
			Map.Entry e = (Map.Entry) o;

			return (getKey().equals(e.getKey())) &&
					(value == null ? e.getValue() == null : value.equals(e.getValue()));
		}

		@Override
		public int hashCode() {
			return key ^ (value == null ? 0 : value.hashCode());
		}

		@Override
		public String toString() {
			return Integer.toString(key) + '=' + value;
		}
	}

	// types of Iterators
	private static final int KEYS = 0;
	private static final int VALUES = 1;
	private static final int ENTRIES = 2;

	private class IntHashIterator implements Iterator {
		Entry[] _table = IntHashMap.this.table;
		int index = _table.length;
		Entry entry;
		Entry lastReturned;
		int type;

		/**
		 * The modCount value that the iterator believes that the backing
		 * List should have.  If this expectation is violated, the iterator
		 * has detected concurrent modification.
		 */
		private int expectedModCount = modCount;

		IntHashIterator(final int type) {
			this.type = type;
		}

		@Override
		public boolean hasNext() {
			while (entry == null && index > 0) {
				entry = _table[--index];
			}

			return entry != null;
		}

		@Override
		public Object next() {
			if (modCount != expectedModCount) {
				throw new ConcurrentModificationException();
			}

			while (entry == null && index > 0) {
				entry = _table[--index];
			}

			if (entry != null) {
				Entry e = lastReturned = entry;
				entry = e.next;
				return type == KEYS ? e.getKey() :
						(type == VALUES ? e.value : e);
			}
			throw new NoSuchElementException();
		}

		@Override
		public void remove() {
			if (lastReturned == null) {
				throw new IllegalStateException();
			}
			if (modCount != expectedModCount) {
				throw new ConcurrentModificationException();
			}

			Entry[] tab = IntHashMap.this.table;
			int ndx = (lastReturned.key & 0x7FFFFFFF) % tab.length;

			for (Entry e = tab[ndx], prev = null; e != null;
				 prev = e, e = e.next) {
				if (e == lastReturned) {
					modCount++;
					expectedModCount++;
					if (prev == null) {
						tab[ndx] = e.next;
					}
					else {
						prev.next = e.next;
					}
					count--;
					lastReturned = null;
					return;
				}
			}
			throw new ConcurrentModificationException();
		}
	}

}


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

import java.util.Arrays;

/**
 * Faster {@code long} buffer.
 */
public class FastLongBuffer {

	private long[] buffer;
	private int offset;

	/**
	 * Creates a new {@code long} buffer. The buffer capacity is
	 * initially 64 longs, though its size increases if necessary.
	 */
	public FastLongBuffer() {
		this.buffer = new long[64];
	}

	/**
	 * Creates a new {@code long} buffer, with a buffer capacity of
	 * the specified size.
	 *
	 * @param size the initial size.
	 * @throws IllegalArgumentException if size is negative.
	 */
	public FastLongBuffer(final int size) {
		this.buffer = new long[size];
	}

	/**
	 * Grows the buffer.
	 */
	private void grow(final int minCapacity) {
		final int oldCapacity = buffer.length;
		int newCapacity = oldCapacity << 1;
		if (newCapacity - minCapacity < 0) {
			// special case, min capacity is larger then a grow
			newCapacity = minCapacity + 512;
		}
		buffer = Arrays.copyOf(buffer, newCapacity);
	}

	/**
	 * Appends single {@code long} to buffer.
	 */
	public void append(final long element) {
		if (offset - buffer.length >= 0) {
			grow(offset);
		}

		buffer[offset++] = element;
	}

	/**
	 * Appends {@code long} array to buffer.
	 */
	public FastLongBuffer append(final long[] array, final int off, final int len) {
		if (offset + len - buffer.length > 0) {
			grow(offset + len);
		}

		System.arraycopy(array, off, buffer, offset, len);
		offset += len;
		return this;
	}

	/**
	 * Appends {@code long} array to buffer.
	 */
	public FastLongBuffer append(final long[] array) {
		return append(array, 0, array.length);
	}

	/**
	 * Appends another fast buffer to this one.
	 */
	public FastLongBuffer append(final FastLongBuffer buff) {
		if (buff.offset == 0) {
			return this;
		}
		append(buff.buffer, 0, buff.offset);
		return this;
	}

	/**
	 * Returns buffer size.
	 */
	public int size() {
		return offset;
	}

	/**
	 * Tests if this buffer has no elements.
	 */
	public boolean isEmpty() {
		return offset == 0;
	}

	/**
	 * Resets the buffer content.
	 */
	public void clear() {
		offset = 0;
	}

	/**
	 * Creates {@code long} array from buffered content.
	 */
	public long[] toArray() {
		return Arrays.copyOf(buffer, offset);
	}

	/**
	 * Creates {@code long} subarray from buffered content.
	 */
	public long[] toArray(final int start, final int len) {
		final long[] array = new long[len];

		if (len == 0) {
			return array;
		}

		System.arraycopy(buffer, start, array, 0, len);

		return array;
	}

	/**
	 * Returns {@code long} element at given index.
	 */
	public long get(final int index) {
		if (index >= offset) {
			throw new IndexOutOfBoundsException();
		}
		return buffer[index];
	}

}


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

package jodd.http;

import jodd.http.up.Uploadable;
import jodd.io.StreamUtil;
import jodd.util.StringPool;
import jodd.buffer.FastByteBuffer;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.LinkedList;

/**
 * Holds request/response content until it is actually send.
 * File content (i.e. {@link jodd.http.up.Uploadable}) is
 * <b>not</b> read until it is really used.
 */
public class Buffer {

	protected LinkedList<Object> list = new LinkedList<>();
	protected FastByteBuffer last;
	protected int size;

	/**
	 * Appends string content to buffer.
	 */
	public Buffer append(final String string) {
		ensureLast();

		try {
			byte[] bytes = string.getBytes(StringPool.ISO_8859_1);

			last.append(bytes);

			size += bytes.length;
		} catch (UnsupportedEncodingException ignore) {
		}

		return this;
	}

	/**
	 * Appends a char.
	 */
	public Buffer append(final char c) {
		append(Character.toString(c));
		return this;
	}

	/**
	 * Appends a number.
	 */
	public Buffer append(final int number) {
		append(Integer.toString(number));
		return this;
	}

	/**
	 * Appends {@link jodd.http.up.Uploadable} to buffer.
	 */
	public Buffer append(final Uploadable uploadable) {
		list.add(uploadable);
		size += uploadable.getSize();
		last = null;
		return this;
	}

	/**
	 * Appends other buffer to this one.
	 */
	public Buffer append(final Buffer buffer) {
		if (buffer.list.isEmpty()) {
			// nothing to append
			return buffer;
		}
		list.addAll(buffer.list);
		last = buffer.last;
		size += buffer.size;
		return this;
	}

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

	/**
	 * Ensures that last buffer exist.
	 */
	private void ensureLast() {
		if (last == null) {
			last = new FastByteBuffer();
			list.add(last);
		}
	}

	// ---------------------------------------------------------------- write

	/**
	 * Writes content to the writer.
	 */
	public void writeTo(final Writer writer) throws IOException {
		for (Object o : list) {
			if (o instanceof FastByteBuffer) {
				FastByteBuffer fastByteBuffer = (FastByteBuffer) o;

				byte[] array = fastByteBuffer.toArray();

				writer.write(new String(array, StringPool.ISO_8859_1));
			}
			else if (o instanceof Uploadable) {
				Uploadable uploadable = (Uploadable) o;

				InputStream inputStream = uploadable.openInputStream();

				try {
					StreamUtil.copy(inputStream, writer, StringPool.ISO_8859_1);
				}
				finally {
					StreamUtil.close(inputStream);
				}
			}
		}
	}

	/**
	 * Writes content to the output stream.
	 */
	public void writeTo(final OutputStream out) throws IOException {
		for (Object o : list) {
			if (o instanceof FastByteBuffer) {
				FastByteBuffer fastByteBuffer = (FastByteBuffer) o;

				out.write(fastByteBuffer.toArray());
			}
			else if (o instanceof Uploadable) {
				Uploadable uploadable = (Uploadable) o;

				InputStream inputStream = uploadable.openInputStream();

				try {
					StreamUtil.copy(inputStream, out);
				}
				finally {
					StreamUtil.close(inputStream);
				}
			}
		}
	}

	/**
	 * Writes content to the output stream, using progress listener to track the sending progress.
	 */
	public void writeTo(final OutputStream out, final HttpProgressListener progressListener) throws IOException {

		// start

		final int size = size();
		final int callbackSize = progressListener.callbackSize(size);
		int count = 0;		// total count
		int step = 0;		// step is offset in current chunk

		progressListener.transferred(count);

		// loop

		for (Object o : list) {
			if (o instanceof FastByteBuffer) {
				FastByteBuffer fastByteBuffer = (FastByteBuffer) o;
				byte[] bytes = fastByteBuffer.toArray();

				int offset = 0;

				while (offset < bytes.length) {
					// calc the remaining sending chunk size
					int chunk = callbackSize - step;

					// check if this chunk size fits the bytes array
					if (offset + chunk > bytes.length) {
						chunk = bytes.length - offset;
					}

					// writes the chunk
					out.write(bytes, offset, chunk);

					offset += chunk;
					step += chunk;
					count += chunk;

					// listener
					if (step >= callbackSize) {
						progressListener.transferred(count);
						step -= callbackSize;
					}
				}
			}
			else if (o instanceof Uploadable) {
				Uploadable uploadable = (Uploadable) o;

				InputStream inputStream = uploadable.openInputStream();

				int remaining = uploadable.getSize();

				try {
					while (remaining > 0) {
						// calc the remaining sending chunk size
						int chunk = callbackSize - step;

						// check if this chunk size fits the remaining size
						if (chunk > remaining) {
							chunk = remaining;
						}

						// writes remaining chunk
						StreamUtil.copy(inputStream, out, chunk);

						remaining -= chunk;
						step += chunk;
						count += chunk;

						// listener
						if (step >= callbackSize) {
							progressListener.transferred(count);
							step -= callbackSize;
						}
					}
				}
				finally {
					StreamUtil.close(inputStream);
				}
			}
		}

		// end

		if (step != 0) {
			progressListener.transferred(count);
		}
	}

}


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

import jodd.util.StringPool;

/**
 * Encodes text and URL strings in various ways resulting HTML-safe text.
 * All methods are null safe.
 * Invalid HTML chars are not checked with these methods, they are just
 * passed as they are.
 */
public class HtmlEncoder {

	private static final int LEN = 0xA1;
	private static final int LEN_XML = 0x40;
	private static final char[][] TEXT = new char[LEN][];
	private static final char[][] ATTR_SQ = new char[LEN][];
	private static final char[][] ATTR_DQ = new char[LEN][];
	private static final char[][] TEXT_XML = new char[LEN_XML][];

	private static final char[] AMP = "&".toCharArray();
	private static final char[] QUOT = """.toCharArray();
	private static final char[] APOS = "'".toCharArray();
	private static final char[] LT = "<".toCharArray();
	private static final char[] GT = ">".toCharArray();
	private static final char[] NBSP = " ".toCharArray();

	/*
	 * Creates HTML lookup tables for faster encoding.
	 */
	static {
		for (int i = 0; i < LEN_XML; i++) {
			TEXT_XML[i] = TEXT[i] = ATTR_SQ[i] = ATTR_DQ[i] = new char[] {(char) i};
		}
		for (int i = LEN_XML; i < LEN; i++) {
			TEXT[i] = ATTR_SQ[i] = ATTR_DQ[i] = new char[] {(char) i};
		}

		// HTML characters
		TEXT['&']	= AMP;		// ampersand
		TEXT['<']	= LT;	    // less than
		TEXT['>']	= GT;		// greater than
		TEXT[0xA0]	= NBSP;

		// SINGLE QUOTE
		ATTR_SQ['&']	= AMP;		// ampersand
		ATTR_SQ['\'']	= APOS;		// single quote
		ATTR_SQ[0xA0]	= NBSP;

		// DOUBLE QUOTE
		ATTR_DQ['&']	= AMP;		// ampersand
		ATTR_DQ['\"']	= QUOT;		// double quote
		ATTR_DQ[0xA0]	= NBSP;

		// XML characters
		TEXT_XML['&']	= AMP;		// ampersand
		TEXT_XML['\"']	= QUOT;		// double-quote
		TEXT_XML['\'']	= APOS;		// single-quote (' is not working for all browsers)
		TEXT_XML['<']	= LT;	    // less than
		TEXT_XML['>']	= GT;	    // greater than
	}

	// ---------------------------------------------------------------- encode text

	/**
	 * Encodes attribute value that will be double quoted.
	 * In this case, only these entities are encoded:
	 *

	 *
& with &amp;

	 *
" with &quot;

	 *
&

	 *

	 */
	public static String attributeDoubleQuoted(final CharSequence value) {
		return encode(value, ATTR_DQ, LEN);
	}

	/**
	 * Encodes attribute value that will be single quoted.
	 * In this case, only two entities are encoded:
	 *

	 *
& with &amp;

	 *
' with &#39;

	 *
&

	 *

	 */
	public static String attributeSingleQuoted(final CharSequence value) {
		return encode(value, ATTR_SQ, LEN);
	}

	/**
	 * Encodes a string to HTML-safe text. The following characters are replaced:
	 *

	 *
& with &amp;

	 *
< with &lt;

	 *
> with &gt;

	 *
\u00A0 with

	 *

	 */
	public static String text(final CharSequence text) {
		return encode(text, TEXT, LEN);
	}

	/**
	 * Encodes XML string. In XML there are only 5 predefined character entities.
	 */
	public static String xml(final CharSequence text) {
		return encode(text, TEXT_XML, LEN_XML);
	}

	// ---------------------------------------------------------------- private

	private static String encode(final CharSequence text, final char[][] buff, final int bufflen) {
		int len;
		if ((text == null) || ((len = text.length()) == 0)) {
			return StringPool.EMPTY;
		}

		StringBuilder buffer = new StringBuilder(len + (len >> 2));

		for (int i = 0; i < len; i++) {
			char c = text.charAt(i);

			if (c < bufflen) {
				buffer.append(buff[c]);
			} else {
				buffer.append(c);
			}
		}
		return buffer.toString();
	}

}


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

package jodd.json;

import jodd.util.collection.MapEntry;

import java.util.Base64;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;

/**
 * Representation of JSON object.
 * @see JsonArray
 */
public class JsonObject implements Iterable<Map.Entry<String, Object>> {

	private Map<String, Object> map;

	/**
	 * Create a new, empty instance.
	 */
	public JsonObject() {
		map = new LinkedHashMap<>();
	}

	/**
	 * Create an instance from a Map. The Map is not copied.
	 */
	public JsonObject(final Map<String, Object> map) {
		this.map = map;
	}

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

	/**
	 * Returns the string value with the specified key.
	 */
	public String getString(final String key) {
		CharSequence cs = (CharSequence) map.get(key);
		return cs == null ? null : cs.toString();
	}

	/**
	 * returns the integer value with the specified key.
	 */
	public Integer getInteger(final String key) {
		Number number = (Number) map.get(key);

		if (number == null) {
			return null;
		}
		if (number instanceof Integer) {
			return (Integer) number;
		}
		return number.intValue();
	}

	/**
	 * Returns the long value with the specified key.
	 */
	public Long getLong(final String key) {
		Number number = (Number) map.get(key);

		if (number == null) {
			return null;
		}
		if (number instanceof Long) {
			return (Long) number;
		}
		return number.longValue();
	}

	/**
	 * Returns the double value with the specified key.
	 */
	public Double getDouble(final String key) {
		Number number = (Number) map.get(key);

		if (number == null) {
			return null;
		}
		if (number instanceof Double) {
			return (Double) number;
		}
		return number.doubleValue();
	}

	/**
	 * Returns the float value with the specified key.
	 */
	public Float getFloat(final String key) {
		Number number = (Number) map.get(key);

		if (number == null) {
			return null;
		}
		if (number instanceof Float) {
			return (Float) number;
		}
		return number.floatValue();
	}

	/**
	 * Returns the boolean value with the specified key.
	 */
	public Boolean getBoolean(final String key) {
		return (Boolean) map.get(key);
	}

	/**
	 * Returns the {@code JsonObject} value with the specified key.
	 */
	public JsonObject getJsonObject(final String key) {
		Object val = map.get(key);

		if (val instanceof Map) {
			val = new JsonObject((Map) val);
		}
		return (JsonObject) val;
	}

	/**
	 * Returns the {@link JsonArray} value with the specified key
	 */
	public JsonArray getJsonArray(final String key) {
		Object val = map.get(key);

		if (val instanceof List) {
			val = new JsonArray((List) val);
		}
		return (JsonArray) val;
	}

	/**
	 * Returns the binary value with the specified key.
	 *

	 * JSON itself has no notion of a binary. This extension complies to the RFC-7493.
	 * THe byte array is Base64 encoded binary.
	 */
	public byte[] getBinary(final String key) {
		String encoded = (String) map.get(key);
		return encoded == null ? null : Base64.getDecoder().decode(encoded);
	}

	/**
	 * Returns the value with the specified key, as an object.
	 */
	@SuppressWarnings("unchecked")
	public <T> T getValue(final String key) {
		T val = (T) map.get(key);

		if (val instanceof Map) {
			return (T) new JsonObject((Map) val);
		}
		if (val instanceof List) {
			return (T) new JsonArray((List) val);
		}
		return val;
	}

	// ---------------------------------------------------------------- get + default

	/**
	 * Like {@link #getString(String)} but specifies a default value to return if there is no entry.
	 */
	public String getString(final String key, final String def) {
		String val = getString(key);

		if (val == null) {
			if (map.containsKey(key)) {
				return null;
			}
			return def;
		}

		return val;
	}

	/**
	 * Like {@link #getInteger(String)} but specifies a default value to return if there is no entry.
	 */
	public Integer getInteger(final String key, final Integer def) {
		Integer val = getInteger(key);

		if (val == null) {
			if (map.containsKey(key)) {
				return null;
			}
			return def;
		}

		return val;
	}

	/**
	 * Like {@link #getLong(String)} but specifies a default value to return if there is no entry.
	 */
	public Long getLong(final String key, final Long def) {
		Long val = getLong(key);

		if (val == null) {
			if (map.containsKey(key)) {
				return null;
			}
			return def;
		}

		return val;
	}

	/**
	 * Like {@link #getDouble(String)} but specifies a default value to return if there is no entry.
	 */
	public Double getDouble(final String key, final Double def) {
		Double val = getDouble(key);

		if (val == null) {
			if (map.containsKey(key)) {
				return null;
			}
			return def;
		}

		return val;
	}

	/**
	 * Like {@link #getFloat(String)} but specifies a default value to return if there is no entry.
	 */
	public Float getFloat(final String key, final Float def) {
		Float val = getFloat(key);

		if (val == null) {
			if (map.containsKey(key)) {
				return null;
			}
			return def;
		}

		return val;
	}

	/**
	 * Like {@link #getBoolean(String)} but specifies a default value to return if there is no entry.
	 */
	public Boolean getBoolean(final String key, final Boolean def) {
		Boolean val = getBoolean(key);

		if (val == null) {
			if (map.containsKey(key)) {
				return null;
			}
			return def;
		}

		return val;
	}

	/**
	 * Like {@link #getJsonObject(String)} but specifies a default value to return if there is no entry.
	 */
	public JsonObject getJsonObject(final String key, final JsonObject def) {
		JsonObject val = getJsonObject(key);

		if (val == null) {
			if (map.containsKey(key)) {
				return null;
			}
			return def;
		}

		return val;
	}

	/**
	 * Like {@link #getJsonArray(String)} but specifies a default value to return if there is no entry.
	 */
	public JsonArray getJsonArray(final String key, final JsonArray def) {
		JsonArray val = getJsonArray(key);

		if (val == null) {
			if (map.containsKey(key)) {
				return null;
			}
			return def;
		}

		return val;

	}

	/**
	 * Like {@link #getBinary(String)} but specifies a default value to return if there is no entry.
	 */
	public byte[] getBinary(final String key, final byte[] def) {
		byte[] val = getBinary(key);

		if (val == null) {
			if (map.containsKey(key)) {
				return null;
			}
			return def;
		}

		return val;

	}

	/**
	 * Like {@link #getValue(String)} but specifies a default value to return if there is no entry.
	 */
	public <T> T getValue(final String key, final T def) {
		T val = getValue(key);

		if (val == null) {
			if (map.containsKey(key)) {
				return null;
			}
			return def;
		}

		return val;
	}

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

	/**
	 * Returns {@code true} if the JSON object contain the specified key.
	 */
	public boolean containsKey(final String key) {
		return map.containsKey(key);
	}

	/**
	 * Return the set of field names in the JSON objects.
	 */
	public Set<String> fieldNames() {
		return map.keySet();
	}

	// ---------------------------------------------------------------- put

	/**
	 * Puts an Enum into the JSON object with the specified key.
	 *

	 * JSON has no concept of encoding Enums, so the Enum will be converted to a String using the {@code java.lang.Enum#name}
	 * method and the value put as a String.
	 */
	public JsonObject put(final String key, final Enum value) {
		Objects.requireNonNull(key);
		map.put(key, value == null ? null : value.name());
		return this;
	}

	/**
	 * Puts an {@code CharSequence} into the JSON object with the specified key.
	 */
	public JsonObject put(final String key, final CharSequence value) {
		Objects.requireNonNull(key);
		map.put(key, value == null ? null : value.toString());
		return this;
	}

	/**
	 * Puts a string into the JSON object with the specified key.
	 */
	public JsonObject put(final String key, final String value) {
		Objects.requireNonNull(key);
		map.put(key, value);
		return this;
	}

	/**
	 * Puts an integer into the JSON object with the specified key.
	 */
	public JsonObject put(final String key, final Integer value) {
		Objects.requireNonNull(key);
		map.put(key, value);
		return this;
	}

	/**
	 * Puts a long into the JSON object with the specified key.
	 */
	public JsonObject put(final String key, final Long value) {
		Objects.requireNonNull(key);
		map.put(key, value);
		return this;
	}

	/**
	 * Puts a double into the JSON object with the specified key.
	 */
	public JsonObject put(final String key, final Double value) {
		Objects.requireNonNull(key);
		map.put(key, value);
		return this;
	}

	/**
	 * Puts a float into the JSON object with the specified key.
	 */
	public JsonObject put(final String key, final Float value) {
		Objects.requireNonNull(key);
		map.put(key, value);
		return this;
	}

	/**
	 * Puts a boolean into the JSON object with the specified key.
	 */
	public JsonObject put(final String key, final Boolean value) {
		Objects.requireNonNull(key);
		map.put(key, value);
		return this;
	}

	/**
	 * Puts a {@code null} value into the JSON object with the specified key.
	 */
	public JsonObject putNull(final String key) {
		Objects.requireNonNull(key);
		map.put(key, null);
		return this;
	}

	/**
	 * Puts another JSON object into the JSON object with the specified key.
	 */
	public JsonObject put(final String key, final JsonObject value) {
		Objects.requireNonNull(key);
		map.put(key, value);
		return this;
	}

	/**
	 * Puts a {@link JsonArray} into the JSON object with the specified key.
	 */
	public JsonObject put(final String key, final JsonArray value) {
		Objects.requireNonNull(key);
		map.put(key, value);
		return this;
	}

	/**
	 * Puts a {@code byte[]} into the JSON object with the specified key.
	 *

	 * Follows JSON extension RFC7493, where binary will first be Base64
	 * encoded before being put as a String.
	 */
	public JsonObject put(final String key, final byte[] value) {
		Objects.requireNonNull(key);
		map.put(key, value == null ? null : Base64.getEncoder().encodeToString(value));
		return this;
	}

	/**
	 * Puts an object into the JSON object with the specified key.
	 */
	public JsonObject put(final String key, Object value) {
		Objects.requireNonNull(key);

		value = resolveValue(value);

		map.put(key, value);
		return this;
	}

	@SuppressWarnings("StatementWithEmptyBody")
	static Object resolveValue(Object value) {
		if (value == null) {
			// OK
		}
		else if (value instanceof Number) {
			// OK
		}
		else if (value instanceof Boolean) {
			// OK
		}
		else if (value instanceof String) {
			// OK
		}
		else if (value instanceof Character) {
			// OK
		}
		else if (value instanceof CharSequence) {
			value = value.toString();
		}
		else if (value instanceof JsonObject) {
			// OK
		}
		else if (value instanceof JsonArray) {
			// OK
		}
		else if (value instanceof Map) {
			value = new JsonObject((Map) value);
		}
		else if (value instanceof List) {
			value = new JsonArray((List) value);
		}
		else if (value instanceof byte[]) {
			value = Base64.getEncoder().encodeToString((byte[]) value);
		}
		else {
			throw new JsonException("Illegal JSON type: " + value.getClass());
		}

		return value;
	}

	// ---------------------------------------------------------------- remove

	/**
	 * Removes an entry from this object.
	 */
	public Object remove(final String key) {
		return map.remove(key);
	}

	// ---------------------------------------------------------------- merge

	/**
	 * Merges in another JSON object.
	 *

	 * This is the equivalent of putting all the entries of the other JSON object into this object. This is not a deep
	 * merge, entries containing (sub) JSON objects will be replaced entirely.
	 */
	public JsonObject mergeIn(final JsonObject other) {
		return mergeIn(other, 1);
	}

	/**
	 * Merges in another JSON object.
	 * A deep merge (recursive) matches (sub) JSON objects in the existing tree and replaces all
	 * matching entries. JsonArrays are treated like any other entry, i.e. replaced entirely.
	 */
	public JsonObject mergeInDeep(final JsonObject other) {
		return mergeIn(other, Integer.MAX_VALUE);
	}

	/**
	 * Merges in another JSON object.
	 * The merge is deep (recursive) to the specified level. If depth is 0, no merge is performed,
	 * if depth is greater than the depth of one of the objects, a full deep merge is performed.
	 */
	@SuppressWarnings("unchecked")
	public JsonObject mergeIn(final JsonObject other, final int depth) {
		if (depth < 1) {
			return this;
		}
		if (depth == 1) {
			map.putAll(other.map);
			return this;
		}

		for (Map.Entry<String, Object> e : other.map.entrySet()) {
			map.merge(e.getKey(), e.getValue(), (oldVal, newVal) -> {
				if (oldVal instanceof Map) {
					oldVal = new JsonObject((Map) oldVal);
				}
				if (newVal instanceof Map) {
					newVal = new JsonObject((Map) newVal);
				}
				if (oldVal instanceof JsonObject && newVal instanceof JsonObject) {
					return ((JsonObject) oldVal).mergeIn((JsonObject) newVal, depth - 1);
				}
				return newVal;
			});
		}
		return this;
	}

	// ---------------------------------------------------------------- encode

	/**
	 * Returns the underlying {@code Map} as is.
	 */
	public Map<String, Object> map() {
		return map;
	}

	/**
	 * Returns a stream of the entries in the JSON object.
	 */
	public Stream<Map.Entry<String, Object>> stream() {
		return map.entrySet().stream();
	}

	/**
	 * Returns an iterator of the entries in the JSON object.
	 */
	@Override
	public Iterator<Map.Entry<String, Object>> iterator() {
		return new Iter(map.entrySet().iterator());
	}

	/**
	 * Returns the number of entries in the JSON object.
	 */
	public int size() {
		return map.size();
	}

	/**
	 * Removes all the entries in this JSON object.
	 */
	public JsonObject clear() {
		map.clear();
		return this;
	}

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

	/**
	 * Returns JSON serialized by default {@link JsonSerializer}.
	 */
	@Override
	public String toString() {
		return JsonSerializer.create().deep(true).serialize(map);
	}

	// ---------------------------------------------------------------- equals/hash

	@Override
	public boolean equals(final Object o) {
		if (this == o) {
			return true;
		}
		if (o == null || getClass() != o.getClass()) {
			return false;
		}
		return objectEquals(map, o);
	}

	static boolean objectEquals(final Map<?, ?> m1, final Object o2) {
		Map<?, ?> m2;
		if (o2 instanceof JsonObject) {
			m2 = ((JsonObject) o2).map;
		} else if (o2 instanceof Map<?, ?>) {
			m2 = (Map<?, ?>) o2;
		} else {
			return false;
		}
		if (m1.size() != m2.size()) {
			return false;
		}
		for (Map.Entry<?, ?> entry : m1.entrySet()) {
			Object val = entry.getValue();
			if (val == null) {
				if (m2.get(entry.getKey()) != null) {
					return false;
				}
			} else {
				if (!elementEquals(entry.getValue(), m2.get(entry.getKey()))) {
					return false;
				}
			}
		}
		return true;
	}

	static boolean elementEquals(final Object o1, final Object o2) {
		if (o1 == o2) {
			return true;
		}
		if (o1 instanceof JsonObject) {
			return objectEquals(((JsonObject) o1).map, o2);
		}
		if (o1 instanceof Map<?, ?>) {
			return objectEquals((Map<?, ?>) o1, o2);
		}
		if (o1 instanceof JsonArray) {
			return JsonArray.arrayEquals(((JsonArray) o1).list(), o2);
		}
		if (o1 instanceof List<?>) {
			return JsonArray.arrayEquals((List<?>) o1, o2);
		}
		if (o1 instanceof Number && o2 instanceof Number && o1.getClass() != o2.getClass()) {
			Number n1 = (Number) o1;
			Number n2 = (Number) o2;
			if (o1 instanceof Float || o1 instanceof Double || o2 instanceof Float || o2 instanceof Double) {
				return n1.doubleValue() == n2.doubleValue();
			} else {
				return n1.longValue() == n2.longValue();
			}
		}
		return o1.equals(o2);
	}

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

	// ---------------------------------------------------------------- iterator

	/**
	 * Iterator over values that handles maps and lists and wraps them in the JSON objects.
	 */
	private class Iter implements Iterator<Map.Entry<String, Object>> {

		final Iterator<Map.Entry<String, Object>> mapIterator;

		Iter(final Iterator<Map.Entry<String, Object>> mapIterator) {
			this.mapIterator = mapIterator;
		}

		@Override
		public boolean hasNext() {
			return mapIterator.hasNext();
		}

		@Override
		public Map.Entry<String, Object> next() {
			Map.Entry<String, Object> entry = mapIterator.next();

			if (entry.getValue() instanceof Map) {
				return MapEntry.createUnmodifiable(entry.getKey(), new JsonObject((Map) entry.getValue()));
			}
			if (entry.getValue() instanceof List) {
				return MapEntry.createUnmodifiable(entry.getKey(), new JsonArray((List) entry.getValue()));
			}

			return entry;
		}

		@Override
		public void remove() {
			mapIterator.remove();
		}
	}
}