// 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.lagarto.dom;

import jodd.util.StringUtil;

import java.util.ArrayList;

/**
 * Simplified HTML foster rules for tables.
 */
public class HtmlFosterRules {

	public static final String[] FOSTER_TABLE_ELEMENTS = new String[] {
		"table", "tbody", "tfoot", "thead", "tr"
	};

	public static final String[] TABLE_ELEMENTS = new String[] {
		"table", "tbody", "tfoot", "thead", "th" ,"tr", "td", "caption", "colgroup", "col"
	};

	/**
	 * Returns true if provided element is one of the table-related elements.
	 */
	protected boolean isOneOfTableElements(final Element element) {
		String elementName = element.getNodeName().toLowerCase();

		return StringUtil.equalsOne(elementName, TABLE_ELEMENTS) != -1;
	}

	/**
	 * Returns true if given node is a table element.
	 */
	protected boolean isTableElement(final Node node) {
		if (node.getNodeType() != Node.NodeType.ELEMENT) {
			return false;
		}
		String elementName = node.getNodeName().toLowerCase();

		return elementName.equals("table");
	}

	/**
	 * Returns true if parent node is one of the table elements.
	 */
	protected boolean isParentNodeOneOfFosterTableElements(final Node parentNode) {
		if (parentNode == null) {
			return false;
		}
		if (parentNode.getNodeName() == null) {
			return false;
		}
		String nodeName = parentNode.getNodeName().toLowerCase();

		return StringUtil.equalsOne(nodeName, FOSTER_TABLE_ELEMENTS) != -1;
	}

	/**
	 * Finds the last table in stack of open elements.
	 */
	protected Element findLastTable(final Node node) {
		Node tableNode = node;

		while (tableNode != null) {
			if (tableNode.getNodeType() == Node.NodeType.ELEMENT) {
				String tableNodeName = tableNode.getNodeName().toLowerCase();

				if (tableNodeName.equals("table")) {
					break;
				}
			}
			tableNode = tableNode.getParentNode();
		}

		return (Element) tableNode;
	}

	// ---------------------------------------------------------------- core

	protected ArrayList<Element> lastTables = new ArrayList<>();
	protected ArrayList<Element> fosterElements = new ArrayList<>();
	protected ArrayList<Text> fosterTexts = new ArrayList<>();

	/**
	 * Fixes foster elements.
	 */
	public void fixFosterElements(final Document document) {
		findFosterNodes(document);
		fixElements();
		fixText();
	}

	/**
	 * Finds foster elements. Returns true if there was no change in
	 * DOM tree of the parent element. Otherwise, returns false
	 * meaning that parent will scan its childs again.
	 */
	protected boolean findFosterNodes(final Node node) {
		boolean isTable = false;

		if (!lastTables.isEmpty()) {
			// if inside table
			if (node.getNodeType() == Node.NodeType.TEXT) {
				String value = node.getNodeValue();
				if (!StringUtil.isBlank(value)) {
					if (isParentNodeOneOfFosterTableElements(node.getParentNode())) {
						fosterTexts.add((Text) node);
					}
				}
			}
		}

		if (node.getNodeType() == Node.NodeType.ELEMENT) {
			Element element = (Element) node;

			isTable = isTableElement(node);

			if (isTable) {
				// if node is a table, add it to the stack-of-last-tables
				lastTables.add(element);
			} else {
				// otherwise...

				// ...if inside the table
				if (!lastTables.isEmpty()) {
					// check this and parent
					Node parentNode = node.getParentNode();
					if (
							isParentNodeOneOfFosterTableElements(parentNode) &&
							!isOneOfTableElements(element)
							) {

						String elementNodeName = element.getNodeName().toLowerCase();
						if (elementNodeName.equals("form")) {
							if (element.getChildNodesCount() > 0) {
								// if form element, take all its child nodes
								// and add after the from element
								Node[] formChildNodes = element.getChildNodes();
								parentNode.insertAfter(formChildNodes, element);
								return false;
							} else {
								// empty form element, leave it where it is
								return true;
							}
						}

						if (elementNodeName.equals("input")) {
							String inputType = element.getAttribute("type");

							if (inputType.equals("hidden")) {
								// input hidden elements remains as they are
								return true;
							}
						}

						// foster element found, remember it to process it later
						fosterElements.add(element);
					}

				} else {
					// ...if not inside the table, just keep going
				}
			}
		}

		allchilds:
		while (true) {
			int childs = node.getChildNodesCount();
			for (int i = 0; i < childs; i++) {
				Node childNode = node.getChild(i);

				boolean done = findFosterNodes(childNode);
				if (!done) {
					continue allchilds;
				}
			}
			break;
		}

		if (isTable) {
			// remove last element
			int size = lastTables.size();
			if (size > 0) {
				lastTables.remove(size - 1);	// no array copy occurs when the last element is removed
			}
		}
		return true;
	}

	/**
	 * Performs the fix for elements.
	 */
	protected void fixElements() {
		for (Element fosterElement : fosterElements) {
			// find parent table
			Element lastTable = findLastTable(fosterElement);
			Node fosterElementParent = fosterElement.getParentNode();

			// filter our foster element
			Node[] fosterChilds = fosterElement.getChildNodes();
			for (Node fosterChild : fosterChilds) {
				if (fosterChild.getNodeType() == Node.NodeType.ELEMENT) {
					if (isOneOfTableElements((Element) fosterChild)) {
						// move all child table elements outside
						// the foster element
						fosterChild.detachFromParent();
						fosterElementParent.insertBefore(fosterChild, fosterElement);
					}
				}
			}

			// finally, move foster element above the table
			fosterElement.detachFromParent();
			lastTable.getParentNode().insertBefore(fosterElement, lastTable);
		}
	}

	protected void fixText() {
		for (Text fosterText : fosterTexts) {
			// find parent table
			Element lastTable = findLastTable(fosterText);

			// move foster element above the table
			fosterText.detachFromParent();

			Node tablesPreviousNode = lastTable.getPreviousSibling();
			if (tablesPreviousNode.getNodeType() == Node.NodeType.TEXT) {
				// append to previous text node
				Text textNode = (Text) tablesPreviousNode;

				String text = textNode.getNodeValue();

				textNode.setNodeValue(text + fosterText.getNodeValue());
			} else {
				// insert text node before the table
				lastTable.getParentNode().insertBefore(fosterText, lastTable);
			}
		}
	}

}


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

package jodd.cache;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Iterator;

/**
 * LRU (least recently used) cache.
 *
 *

 * Items are added to the cache as they are accessed; when the cache is full, the least recently used item is ejected.
 * This type of cache is typically implemented as a linked list, so that an item in cache, when it is accessed again,
 * can be moved back up to the head of the queue; items are ejected from the tail of the queue. Cache access overhead
 * is again constant time. This algorithm is simple and fast, and it has a significant advantage over FIFO in being
 * able to adapt somewhat to the data access pattern; frequently used items are less likely to be
 * ejected from the cache. The main disadvantage is that it can still get filled up with items that are
 * unlikely to be reaccessed soon; in particular, it can become useless in the face of scanning type accesses.
 * Nonetheless, this is by far the most frequently used caching algorithm.
 *

 * Implementation note: unfortunately, it was not possible to have onRemove callback method,
 * since LinkedHashMap has its removal methods private.
 *

 * Summary for LRU: fast, adaptive, not scan resistant.
 */
public class LRUCache<K, V> extends AbstractCacheMap<K, V> {

	public LRUCache(final int cacheSize) {
		this(cacheSize, 0);
	}

	/**
	 * Creates a new LRU cache.
	 */
	public LRUCache(final int cacheSize, final long timeout) {
		this.cacheSize = cacheSize;
		this.timeout = timeout;
		cacheMap = new LinkedHashMap<K, CacheObject<K,V>>(cacheSize + 1, 1.0f, true) {
			@Override
			protected boolean removeEldestEntry(final Map.Entry eldest) {
				return LRUCache.this.removeEldestEntry(size());
			}
		};
	}

	/**
	 * Removes the eldest entry if current cache size exceed cache size.
	 */
	protected boolean removeEldestEntry(final int currentSize) {
		if (cacheSize == 0) {
			return false;
		}
		return currentSize > cacheSize;
	}

	// ---------------------------------------------------------------- prune

	/**
	 * Prune only expired objects, LinkedHashMap will take care of LRU if needed.
	 */
	@Override
	protected int pruneCache() {
		if (!isPruneExpiredActive()) {
			return 0;
		}
        int count = 0;
		Iterator<CacheObject<K,V>> values = cacheMap.values().iterator();
		while (values.hasNext()) {
			CacheObject<K,V> co = values.next();
			if (co.isExpired()) {
				values.remove();
				onRemove(co.key, co.cachedObject);
				count++;
			}
		}
		return 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.petite.scope;

import jodd.petite.BeanData;
import jodd.petite.BeanDefinition;
import jodd.petite.PetiteContainer;
import jodd.petite.PetiteException;
import jodd.servlet.RequestContextListener;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
 * Session scope stores unique object instances per single http session.
 * Upon creation, new session listener is registered (dynamically) that will
 * keep track on active sessions. {@link RequestContextListener} is used for accessing
 * the request and the session.
 */
public class SessionScope extends ShutdownAwareScope {

	private final PetiteContainer pc;

	public SessionScope(final PetiteContainer pc) {
		this.pc = pc;
	}

	// ---------------------------------------------------------------- destory

	protected static final String SESSION_BEANS_NAME = SessionScope.class.getName() + ".SESSION_BEANS.";

	/**
	 * Registers new session destroy callback if not already registered.
	 */
	protected Map<String, BeanData> registerSessionBeans(final HttpSession httpSession) {
	    SessionBeans sessionBeans = new SessionBeans();
		httpSession.setAttribute(SESSION_BEANS_NAME, sessionBeans);
		return sessionBeans.getBeanMap();
	}

	/**
	 * Returns instance map from http session.
	 */
	@SuppressWarnings("unchecked")
	protected Map<String, BeanData> getSessionMap(final HttpSession session) {
		SessionBeans sessionBeans = (SessionBeans) session.getAttribute(SESSION_BEANS_NAME);
		if (sessionBeans == null) {
			return null;
		}
		return sessionBeans.getBeanMap();
	}

	/**
	 * Session beans holder and manager.
	 */
	public class SessionBeans implements HttpSessionBindingListener, Serializable {

		protected Map<String, BeanData> beanMap = new HashMap<>();

		/**
		 * Returns bean map used in this session.
		 */
		public Map<String, BeanData> getBeanMap() {
			return beanMap;
		}

		@Override
		public void valueBound(final HttpSessionBindingEvent event) {
			// do nothing
		}

		/**
		 * Session is destroyed.
		 */
		@Override
		public void valueUnbound(final HttpSessionBindingEvent event) {
			for (BeanData beanData : beanMap.values()) {
				destroyBean(beanData);
			}
		}
	}

	// ---------------------------------------------------------------- scope

	/**
	 * Shutdowns the Session scope. Calls destroyable methods on
	 * all destroyable beans available in this moment.
	 */
	@Override
	public void shutdown() {
		super.shutdown();
	}

	@Override
	public Object lookup(final String name) {
		HttpSession session = getCurrentHttpSession();
		Map<String, BeanData> map = getSessionMap(session);
		if (map == null) {
			return null;
		}

		BeanData beanData = map.get(name);
		if (beanData == null) {
			return null;
		}
		return beanData.bean();
	}

	@Override
	public void register(final BeanDefinition beanDefinition, final Object bean) {
		HttpSession session = getCurrentHttpSession();
		Map<String, BeanData> map = getSessionMap(session);
		if (map == null) {
			map = registerSessionBeans(session);
		}

		final BeanData beanData = new BeanData(pc, beanDefinition, bean);

		map.put(beanDefinition.name(), beanData);

		registerDestroyableBeans(beanData);
	}

	@Override
	public void remove(final String name) {
		if (totalRegisteredDestroyableBeans() == 0) {
			return;
		}
		HttpSession session = getCurrentHttpSession();
		Map<String, BeanData> map = getSessionMap(session);
		if (map != null) {
			map.remove(name);
		}
	}

	@Override
	public boolean accept(final Scope referenceScope) {
		Class<? extends Scope> refScopeType = referenceScope.getClass();

		if (refScopeType == ProtoScope.class) {
			return true;
		}

		if (refScopeType == SingletonScope.class) {
			return true;
		}

		if (refScopeType == SessionScope.class) {
			return true;
		}

		return false;
	}

	// ---------------------------------------------------------------- util

	/**
	 * Returns request from current thread.
	 */
	protected HttpSession getCurrentHttpSession() {
		HttpServletRequest request = RequestContextListener.getRequest();
		if (request == null) {
			throw new PetiteException("No HTTP request bound to the current thread. Is RequestContextListener registered?");
		}
		return request.getSession();
	}

}


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

package jodd.proxetta.asm;

import jodd.asm7.ClassVisitor;
import jodd.util.StringPool;

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

import static jodd.util.StringPool.DOT;

/**
 * Holds various information about the current process of making proxy.
 */
public final class WorkData {

	final ClassVisitor dest;

	WorkData(final ClassVisitor dest) {
		this.dest = dest;
	}

	// ---------------------------------------------------------------- data

	String targetPackage;
	String targetClassname;
	String nextSupername;
	String superName;
	String superReference;
	ProxyAspectData[] proxyAspects;
	String wrapperRef;
	String wrapperType;
	boolean wrapInterface;
	boolean allowFinalMethods;

	public String thisReference;
	public boolean proxyApplied;

	public boolean isWrapper() {
		return wrapperRef != null;
	}

	// ---------------------------------------------------------------- init

	/**
	 * Work data initialization.
	 */
	public void init(String name, final String superName, final String suffix, final String reqProxyClassName) {
		int lastSlash = name.lastIndexOf('/');
		this.targetPackage = lastSlash == -1 ? StringPool.EMPTY : name.substring(0, lastSlash).replace('/', '.');
		this.targetClassname = name.substring(lastSlash + 1);
		this.nextSupername = superName;
		this.superName = name;

		// create proxy name
		if (reqProxyClassName != null) {
			if (reqProxyClassName.startsWith(DOT)) {
				name = name.substring(0, lastSlash) + '/' + reqProxyClassName.substring(1);
			} else if (reqProxyClassName.endsWith(DOT)) {
				name = reqProxyClassName.replace('.', '/') + this.targetClassname;
			} else {
				name = reqProxyClassName.replace('.', '/');
			}
		}

		// add optional suffix
		if (suffix != null) {
			name += suffix;
		}
		this.thisReference = name;
		this.superReference = this.superName;
	}

	// ---------------------------------------------------------------- advice clinits

	List<String> adviceClinits;

	/**
	 * Saves used static initialization blocks (clinit) of advices.
	 */
	void addAdviceClinitMethod(final String name) {
		if (adviceClinits == null) {
			adviceClinits = new ArrayList<>();
		}
		adviceClinits.add(name);
	}

	// ---------------------------------------------------------------- advice inits

	List<String> adviceInits;

	/**
	 * Saves used constructors of advices.
	 */
	void addAdviceInitMethod(final String name) {
		if (adviceInits == null) {
			adviceInits = new ArrayList<>();
		}
		adviceInits.add(name);
	}

}


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

package jodd.db.type;

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

public class ByteSqlType extends NullAwareSqlType<Byte> {

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

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


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

import jodd.util.ArraysUtil;

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

import static jodd.htmlstapler.HtmlStaplerBundlesManager.Strategy.ACTION_MANAGED;

/**
 * Bundle action used during page parsing and resources collection.
 */
public class BundleAction {

	private static final String BUNDLE_ID_MARKER = "---jodd-bundle-id-marker---";
	private static final String UNSTAPLE_MARKER = "jodd-unstaple";

	protected final HtmlStaplerBundlesManager bundlesManager;
	protected final String bundleContentType;
	protected final boolean newAction;
	protected final String actionPath;
	protected final String contextPath;
	protected final HtmlStaplerBundlesManager.Strategy strategy;
	protected String bundleId;
	protected char[] bundleIdMark;

	protected List<String> sources;
	protected boolean firstScriptTag;

	/**
	 * Creates new bundle action.
	 */
	public BundleAction(final HtmlStaplerBundlesManager bundlesManager, final String servletPath, final String bundleContentType) {
		this.bundlesManager = bundlesManager;
		this.bundleContentType = bundleContentType;
		this.strategy = bundlesManager.getStrategy();

		String realActionPath = bundlesManager.resolveRealActionPath(servletPath);

		actionPath = realActionPath + '*' + bundleContentType;

		contextPath = bundlesManager.contextPath;

		if (strategy == ACTION_MANAGED) {
			bundleId = bundlesManager.lookupBundleId(actionPath);

			newAction = (bundleId == null);

			if (newAction) {
				sources = new ArrayList<>();
			}
		} else {
			bundleId = BUNDLE_ID_MARKER + bundleContentType;
			bundleIdMark = bundleId.toCharArray();
			newAction = true;
			sources = new ArrayList<>();
		}

		firstScriptTag = true;
	}

	/**
	 * Returns true if resource link  should be collected into the bundle. Returns
	 * false for resources that has to ignored or when no link existed (null).
	 *

	 * By default, ignores resource links that contains "jodd.unstaple"
	 * (usually set as dummy parameter name).
	 */
	public boolean acceptLink(final String src) {
		if (src == null) {
			return false;
		}
		return !src.contains(UNSTAPLE_MARKER);
	}

	/**
	 * Process links. Returns bundle link if this is the first resource
	 * of the same type. Otherwise, returns null indicating
	 * that collection is going on and the original link should be removed.
	 */
	public String processLink(final String src) {
		if (newAction) {
			if (bundleId == null) {
				bundleId = bundlesManager.registerNewBundleId();
				bundleId += '.' + bundleContentType;
			}
			sources.add(src);
		}

		if (firstScriptTag) {
			// this is the first tag, change the url to point to the bundle
			firstScriptTag = false;

			return buildStaplerUrl();
		} else {
			// ignore all other script tags
			return null;
		}
	}

	/**
	 * Builds stapler URL based on bundle action data.
	 */
	protected String buildStaplerUrl() {
		return contextPath + '/' + bundlesManager.getStaplerPath() + '/' + bundleId;
	}

	/**
	 * Called on end of parsing.
	 */
	public void end() {
		if (newAction) {
			bundleId = bundlesManager.registerBundle(contextPath, actionPath, bundleId, bundleContentType, sources);
		}
	}

	/**
	 * Replaces bundle marker with calculated bundle id.
	 * Used for RESOURCE_ONLY strategy.
	 */
	public char[] replaceBundleId(final char[] content) {
		if (strategy == ACTION_MANAGED || bundleId == null) {
			return content;
		}

		int index = ArraysUtil.indexOf(content, bundleIdMark);
		if (index == -1) {
			return content;
		}

		char[] bundleIdChars = bundleId.toCharArray();
		char[] result = new char[content.length - bundleIdMark.length + bundleIdChars.length];

		System.arraycopy(content, 0, result, 0, index);
		System.arraycopy(bundleIdChars, 0, result, index, bundleIdChars.length);
		System.arraycopy(content, index + bundleIdMark.length, result, index + bundleIdChars.length, content.length - bundleIdMark.length - index);

		return result;
	}

}


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

package jodd.props;

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

/**
 * Converter of Java Properties to Jodd Props format.
 */
public class PropsConverter {

	/**
	 * Convert Java Properties to Jodd Props format.
	 *
	 * @param writer     Writer to write Props formatted file content to
	 * @param properties Properties to convert to Props format
	 * @throws IOException On any I/O error when writing to the writer
	 */
	public static void convert(final Writer writer, final Properties properties) throws IOException {
		convert(writer, properties, Collections.emptyMap());
	}

	/**
	 * Convert Java Properties to Jodd Props format.
	 *
	 * @param writer     Writer to write Props formatted file content to
	 * @param properties Properties to convert to Props format
	 * @param profiles   Properties per profile to convert and add to the Props format
	 * @throws IOException On any I/O error when writing to the writer
	 */
	public static void convert(final Writer writer, final Properties properties, final Map<String, Properties> profiles)
			throws IOException {

		final PropertiesToProps toProps = new PropertiesToProps();
		toProps.convertToWriter(writer, properties, profiles);
	}

}


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

package jodd.props;

import jodd.util.CharUtil;
import jodd.util.StringPool;
import jodd.util.StringUtil;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * {@link Props} parser.
 */
public class PropsParser implements Cloneable {

	protected static final String PROFILE_LEFT = "<";

	protected static final String PROFILE_RIGHT = ">";

	protected final PropsData propsData;

	/**
	 * Value that will be inserted when escaping the new line.
	 */
	protected String escapeNewLineValue = StringPool.EMPTY;

	/**
	 * Trims left the value.
	 */
	protected boolean valueTrimLeft = true;

	/**
	 * Trims right the value.
	 */
	protected boolean valueTrimRight = true;

	/**
	 * Defines if starting whitespaces when value is split in the new line
	 * should be ignored or not.
	 */
	protected boolean ignorePrefixWhitespacesOnNewLine = true;

	/**
	 * Defines if multi-line values may be written using triple-quotes
	 * as in python.
	 */
	protected boolean multilineValues = true;

	/**
	 * Don't include empty properties.
	 */
	protected boolean skipEmptyProps = true;

	public PropsParser() {
		this.propsData = new PropsData();
	}

	public PropsParser(final PropsData propsData) {
		this.propsData = propsData;
	}

	public PropsData getPropsData() {
		return propsData;
	}

	@Override
	public PropsParser clone() {
		final PropsParser pp = new PropsParser(this.propsData.clone());

		pp.escapeNewLineValue = escapeNewLineValue;
		pp.valueTrimLeft = valueTrimLeft;
		pp.valueTrimRight = valueTrimRight;
		pp.ignorePrefixWhitespacesOnNewLine = ignorePrefixWhitespacesOnNewLine;
		pp.skipEmptyProps = skipEmptyProps;
		pp.multilineValues = multilineValues;

		return pp;
	}

	/**
	 * Parsing states.
	 */
	protected enum ParseState {
		TEXT,
		ESCAPE,
		ESCAPE_NEWLINE,
		COMMENT,
		VALUE
	}

	/**
	 * Different assignment operators.
	 */
	protected enum Operator {
		ASSIGN,
		QUICK_APPEND,
		COPY
	}

	/**
	 * Loads properties.
	 */
	public void parse(final String in) {
		ParseState state = ParseState.TEXT;
		ParseState stateOnEscape = null;

		boolean insideSection = false;
		String currentSection = null;
		String key = null;
		Operator operator = Operator.ASSIGN;
		final StringBuilder sb = new StringBuilder();

		final int len = in.length();
		int ndx = 0;
		while (ndx < len) {
			final char c = in.charAt(ndx);
			ndx++;

			if (state == ParseState.COMMENT) {
				// comment, skip to the end of the line
				if (c == '\r') {
					if ((ndx < len) && (in.charAt(ndx) == '\n')) {
						ndx++;
					}
					state = ParseState.TEXT;
				}
				else if (c == '\n') {
					state = ParseState.TEXT;
				}
			} else if (state == ParseState.ESCAPE) {
				state = stateOnEscape;  //ParseState.VALUE;
				switch (c) {
					case '\r':
						if ((ndx < len) && (in.charAt(ndx) == '\n')) {
							ndx++;
						}
					case '\n':
						// need to go 1 step back in order to escape
						// the current line ending in the follow-up state
						ndx--;
						state = ParseState.ESCAPE_NEWLINE;
						break;
					// encode UTF character
					case 'u':
						int value = 0;

						for (int i = 0; i < 4; i++) {
							final char hexChar = in.charAt(ndx++);
							if (CharUtil.isDigit(hexChar)) {
								value = (value << 4) + hexChar - '0';
							} else if (hexChar >= 'a' && hexChar <= 'f') {
								value = (value << 4) + 10 + hexChar - 'a';
							} else if (hexChar >= 'A' && hexChar <= 'F') {
								value = (value << 4) + 10 + hexChar - 'A';
							} else {
								throw new IllegalArgumentException("Malformed \\uXXXX encoding.");
							}
						}
						sb.append((char) value);
						break;
					case 't':
						sb.append('\t');
						break;
					case 'n':
						sb.append('\n');
						break;
					case 'r':
						sb.append('\r');
						break;
					case 'f':
						sb.append('\f');
						break;
					default:
						sb.append(c);
				}
			} else if (state == ParseState.TEXT) {
				switch (c) {
					case '\\':
						// escape char, take the next char as is
						stateOnEscape = state;
						state = ParseState.ESCAPE;
						break;

					// start section
					case '[':
						if (sb.length() > 0) {
							if (StringUtil.isNotBlank(sb)) {
								sb.append(c);
								// previous string is not blank, hence it's not the section
								break;
							}
						}
						sb.setLength(0);
						insideSection = true;
						break;

					// end section
					case ']':
						if (insideSection) {
							currentSection = sb.toString().trim();
							sb.setLength(0);
							insideSection = false;
							if (currentSection.length() == 0) {
								currentSection = null;
							}
						} else {
							sb.append(c);
						}
						break;

					case '#':
					case ';':
						state = ParseState.COMMENT;
						break;

					// copy operator
					case '<':
						if (ndx == len || in.charAt(ndx) != '=') {
							sb.append(c);
							break;
						}
						operator = Operator.COPY;
						//ndx++;
						continue;

					// assignment operator
					case '+':
						if (ndx == len || in.charAt(ndx) != '=') {
							sb.append(c);
							break;
						}
						operator = Operator.QUICK_APPEND;
						//ndx++;
						continue;
					case '=':
					case ':':
						if (key == null) {
							key = sb.toString().trim();
							sb.setLength(0);
						} else {
							sb.append(c);
						}
						state = ParseState.VALUE;
						break;

					case '\r':
					case '\n':
						add(currentSection, key, sb, true, operator);
						sb.setLength(0);
						key = null;
						operator = Operator.ASSIGN;
						break;

					case ' ':
					case '\t':
						// ignore whitespaces
						break;
					default:
						sb.append(c);
				}
			} else {
				switch (c) {
					case '\\':
						// escape char, take the next char as is
						stateOnEscape = state;
						state = ParseState.ESCAPE;
						break;

					case '\r':
						if ((ndx < len) && (in.charAt(ndx) == '\n')) {
							ndx++;
						}
					case '\n':
						if (state == ParseState.ESCAPE_NEWLINE) {
							sb.append(escapeNewLineValue);
							if (!ignorePrefixWhitespacesOnNewLine) {
								state = ParseState.VALUE;
							}
						} else {
							add(currentSection, key, sb, true, operator);
							sb.setLength(0);
							key = null;
							operator = Operator.ASSIGN;

							// end of value, continue to text
							state = ParseState.TEXT;
						}
						break;

					case ' ':
					case '\t':
						if (state == ParseState.ESCAPE_NEWLINE) {
							break;
						}
					default:
						sb.append(c);
						state = ParseState.VALUE;

						if (multilineValues) {
							if (sb.length() == 3) {

								// check for ''' beginning
								if (sb.toString().equals("'''")) {
									sb.setLength(0);
									int endIndex = in.indexOf("'''", ndx);
									if (endIndex == -1) {
										endIndex = in.length();
									}
									sb.append(in, ndx, endIndex);

									// append
									add(currentSection, key, sb, false, operator);
									sb.setLength(0);
									key = null;
									operator = Operator.ASSIGN;

									// end of value, continue to text
									state = ParseState.TEXT;
									ndx = endIndex + 3;
								}
							}
						}
				}
			}
		}

		if (key != null) {
			add(currentSection, key, sb, true, operator);
		}
	}

	// ---------------------------------------------------------------- add

	/**
	 * Adds accumulated value to key and current section.
	 */
	protected void add(
			final String section, final String key,
			final StringBuilder value, final boolean trim, final Operator operator) {

		// ignore lines without : or =
		if (key == null) {
			return;
		}
		String fullKey = key;

		if (section != null) {
			if (fullKey.length() != 0) {
				fullKey = section + '.' + fullKey;
			} else {
				fullKey = section;
			}
		}
		String v = value.toString();

		if (trim) {
			if (valueTrimLeft && valueTrimRight) {
				v = v.trim();
			} else if (valueTrimLeft) {
				v = StringUtil.trimLeft(v);
			} else {
				v = StringUtil.trimRight(v);
			}
		}

		if (v.length() == 0 && skipEmptyProps) {
			return;
		}

		extractProfilesAndAdd(fullKey, v, operator);
	}

	/**
	 * Extracts profiles from the key name and adds key-value to them.
	 */
	protected void extractProfilesAndAdd(final String key, final String value, final Operator operator) {
		String fullKey = key;
		int ndx = fullKey.indexOf(PROFILE_LEFT);
		if (ndx == -1) {
			justAdd(fullKey, value, null, operator);
			return;
		}

		// extract profiles
		ArrayList<String> keyProfiles = new ArrayList<>();

		while (true) {
			ndx = fullKey.indexOf(PROFILE_LEFT);
			if (ndx == -1) {
				break;
			}

			final int len = fullKey.length();

			int ndx2 = fullKey.indexOf(PROFILE_RIGHT, ndx + 1);
			if (ndx2 == -1) {
				ndx2 = len;
			}

			// remember profile
			final String profile = fullKey.substring(ndx + 1, ndx2);
			keyProfiles.add(profile);

			// extract profile from key
			ndx2++;
			final String right = (ndx2 == len) ? StringPool.EMPTY : fullKey.substring(ndx2);
			fullKey = fullKey.substring(0, ndx) + right;
		}

		if (fullKey.startsWith(StringPool.DOT)) {
			// check for special case when only profile is defined in section
			fullKey = fullKey.substring(1);
		}

		// add value to extracted profiles
		justAdd(fullKey, value, keyProfiles, operator);
	}

	/**
	 * Core key-value addition.
	 */
	protected void justAdd(final String key, final String value, final ArrayList<String> keyProfiles, final Operator operator) {
		if (operator == Operator.COPY) {
			HashMap<String,Object> target = new HashMap<>();

			String[] profiles = null;
			if (keyProfiles != null) {
				profiles = keyProfiles.toArray(new String[0]);
			}

			String[] sources = StringUtil.splitc(value, ',');
			for (String source : sources) {
				source = source.trim();

				// try to extract profile for parsing

				String[] lookupProfiles = profiles;
				String lookupProfilesString = null;

				int leftIndex = source.indexOf('<');
				if (leftIndex != -1) {
					int rightIndex = source.indexOf('>');

					lookupProfilesString = source.substring(leftIndex + 1, rightIndex);
					source = source.substring(0, leftIndex).concat(source.substring(rightIndex + 1));

					lookupProfiles = StringUtil.splitc(lookupProfilesString, ',');

					StringUtil.trimAll(lookupProfiles);
				}

				String[] wildcards = new String[] {source + ".*"};

				propsData.extract(target, lookupProfiles, wildcards, null);

				for (Map.Entry<String, Object> entry : target.entrySet()) {
					String entryKey = entry.getKey();
					String suffix = entryKey.substring(source.length());

					String newKey = key + suffix;

					String newValue = "${" + entryKey;
					if (lookupProfilesString != null) {
						newValue += "<" + lookupProfilesString + ">";
					}
					newValue += "}";

					if (profiles == null) {
						propsData.putBaseProperty(newKey, newValue, false);
					} else {
						for (final String p : profiles) {
							propsData.putProfileProperty(newKey, newValue, p, false);
						}
					}
				}
			}
			return;
		}

		boolean append = operator == Operator.QUICK_APPEND;
		if (keyProfiles == null) {
			propsData.putBaseProperty(key, value, append);
			return;
		}
		for (final String p : keyProfiles) {
			propsData.putProfileProperty(key, value, p, append);
		}

	}

}


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

package jodd.mutable;

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

	public static MutableShort of(final short value) {
		return new MutableShort(value);
	}

	public MutableShort() {
	}

	public MutableShort(final short value) {
		this.value = value;
	}

	public MutableShort(final String value) {
		this.value = Short.parseShort(value);
	}

	public MutableShort(final Number number) {
		this.value = number.shortValue();
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	/**
	 * Clones object.
	 */
	@Override
	public MutableShort clone() {
		return new MutableShort(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.asm;

import jodd.asm7.MethodVisitor;
import jodd.mutable.MutableInteger;

import static jodd.asm7.Opcodes.CHECKCAST;
import static jodd.asm7.Opcodes.INVOKESTATIC;
import static jodd.asm7.Opcodes.INVOKEVIRTUAL;

/**
 * Generic ASM utils.
 */
public class AsmUtil {

	// types

	public static final int TYPE_BYTE           = 'B';
	public static final int TYPE_CHAR           = 'C';
	public static final int TYPE_DOUBLE         = 'D';
	public static final int TYPE_FLOAT          = 'F';
	public static final int TYPE_INT            = 'I';
	public static final int TYPE_LONG           = 'J';
	public static final int TYPE_REFERENCE      = 'L';
	public static final int TYPE_SHORT          = 'S';
	public static final int TYPE_BOOLEAN        = 'Z';
	public static final int TYPE_VOID           = 'V';
	public static final int TYPE_ARRAY          = '[';
//	public static final int TYPE_STRING         = 's';
//	public static final int TYPE_ENUM           = 'e';
//	public static final int TYPE_CLASS          = 'c';
	public static final int TYPE_ANNOTATION     = '@';

	// access flags

	public static final int ACC_PUBLIC = 0x0001;        // class, field, method
	public static final int ACC_PRIVATE = 0x0002;       // class, field, method
	public static final int ACC_PROTECTED = 0x0004;     // class, field, method
	public static final int ACC_STATIC = 0x0008;        // field, method
	public static final int ACC_FINAL = 0x0010;         // class, field, method
	public static final int ACC_SUPER = 0x0020;         // class
	public static final int ACC_SYNCHRONIZED = 0x0020;  // method
	public static final int ACC_VOLATILE = 0x0040;      // field
	public static final int ACC_BRIDGE = 0x0040;        // method
	public static final int ACC_VARARGS = 0x0080;       // method
	public static final int ACC_TRANSIENT = 0x0080;     // field
	public static final int ACC_NATIVE = 0x0100;        // method
	public static final int ACC_INTERFACE = 0x0200;     // class
	public static final int ACC_ABSTRACT = 0x0400;      // class, method
	public static final int ACC_STRICT = 0x0800;        // method
	public static final int ACC_SYNTHETIC = 0x1000;     // class, field, method
	public static final int ACC_ANNOTATION = 0x2000;    // class
	public static final int ACC_ENUM = 0x4000;          // class(?) field inner

	// signatures

	public static final String SIGNATURE_JAVA_LANG_OBJECT 		= "java/lang/Object";
	public static final String SIGNATURE_JAVA_LANG_CLASS 		= "java/lang/Class";
	public static final String SIGNATURE_JAVA_LANG_BYTE 		= "java/lang/Byte";
	public static final String SIGNATURE_JAVA_LANG_CHARACTER 	= "java/lang/Character";
	public static final String SIGNATURE_JAVA_LANG_SHORT 		= "java/lang/Short";
	public static final String SIGNATURE_JAVA_LANG_INTEGER 		= "java/lang/Integer";
	public static final String SIGNATURE_JAVA_LANG_BOOLEAN 		= "java/lang/Boolean";
	public static final String SIGNATURE_JAVA_LANG_LONG 		= "java/lang/Long";
	public static final String SIGNATURE_JAVA_LANG_FLOAT 		= "java/lang/Float";
	public static final String SIGNATURE_JAVA_LANG_DOUBLE 		= "java/lang/Double";
	public static final String SIGNATURE_JAVA_LANG_VOID 		= "java/lang/Void";

	public static final String L_SIGNATURE_JAVA_LANG_OBJECT 	= "Ljava/lang/Object;";
	public static final String L_SIGNATURE_JAVA_LANG_STRING 	= "Ljava/lang/String;";
	public static final String L_SIGNATURE_JAVA_LANG_CLASS 		= "Ljava/lang/Class;";

	private static final String INVALID_BASE_TYPE = "Invalid base type: ";
	private static final String INVALID_TYPE_DESCRIPTION = "Invalid type description: ";

	// ---------------------------------------------------------------- class relates

	/**
	 * Converts bytecode-like description to java class name that can be loaded
	 * with a classloader. Uses less-known feature of class loaders for loading
	 * array classes.
	 *
	 * @see #typedescToSignature(String, jodd.mutable.MutableInteger)
	 */
	public static String typedesc2ClassName(final String desc) {
		String className = desc;
		switch (desc.charAt(0)) {
			case 'B':
			case 'C':
			case 'D':
			case 'F':
			case 'I':
			case 'J':
			case 'S':
			case 'Z':
			case 'V':
				if (desc.length() != 1) {
					throw new IllegalArgumentException(INVALID_BASE_TYPE + desc);
				}
				break;
			case 'L':
				className = className.substring(1, className.length() - 1); break;
			case '[':
				// uses less-known feature of class loaders for loading array types
				// using bytecode-like signatures.
				className = className.replace('/', '.');
				break;
			default: throw new IllegalArgumentException(INVALID_TYPE_DESCRIPTION + desc);
		}

		return className;
	}

	/**
	 * Converts type reference to java-name.
	 */
	public static String typeref2Name(final String desc) {
		if (desc.charAt(0) != TYPE_REFERENCE) {
			throw new IllegalArgumentException(INVALID_TYPE_DESCRIPTION + desc);
		}
		String name = desc.substring(1, desc.length() - 1);
		return name.replace('/', '.');
	}

	// ---------------------------------------------------------------- description

	/**
	 * Returns type-name to type char.
	 * Arrays are not supported.
	 */
	public static char typeNameToOpcode(final String typeName) {
		switch (typeName) {
			case "byte" : return 'B';
			case "char": return 'C';
			case "double": return 'D';
			case "float": return 'F';
			case "int": return 'I';
			case "long": return 'J';
			case "short": return 'S';
			case "boolean": return 'Z';
			case "void": return 'V';
			default: return 'L';
		}
	}

	/**
	 * Returns java-like signature of a bytecode-like description.
	 * @see #typedescToSignature(String, jodd.mutable.MutableInteger)
	 */
	public static String typedescToSignature(final String desc) {
		return typedescToSignature(desc, new MutableInteger());
	}

	/**
	 * Returns java-like signature of a bytecode-like description.
	 * Only first description is parsed.
	 *
	 * The field signature represents the value of an argument to a function or
	 * the value of a variable. It is a series of bytes generated by the
	 * following grammar:
	 *
	 *
{@code
	 * <field_signature> ::= <field_type>
	 * <field_type>      ::= <base_type>|<object_type>|<array_type>
	 * <base_type>       ::= B|C|D|F|I|J|S|Z
	 * <object_type>     ::= L<fullclassname>;
	 * <array_type>      ::= [<field_type>
	 *
	 * The meaning of the base types is as follows:
	 * B byte signed byte
	 * C char character
	 * D double double precision IEEE float
	 * F float single precision IEEE float
	 * I int integer
	 * J long long integer
	 * L<fullclassname>; ... an object of the given class
	 * S short signed short
	 * Z boolean true or false
	 * [<field sig> ... array
	 * }

	 *
	 * This method converts this string into a Java type declaration such as
	 * String[].
	 */
	public static String typedescToSignature(final String desc, final MutableInteger from) {
		int fromIndex = from.get();
		from.value++;	// default usage for most cases

		switch (desc.charAt(fromIndex)) {
			case 'B': return "byte";
			case 'C': return "char";
			case 'D': return "double";
			case 'F': return "float";
			case 'I': return "int";
			case 'J': return "long";
			case 'S': return "short";
			case 'Z': return "boolean";
			case 'V': return "void";

			case 'L':
				int index = desc.indexOf(';', fromIndex);
				if (index < 0) {
					throw new IllegalArgumentException(INVALID_TYPE_DESCRIPTION + desc);
				}
				from.set(index + 1);
				String str = desc.substring(fromIndex + 1, index);
				return str.replace('/', '.');

			case 'T':
				return desc.substring(from.value);

			case '[':
				StringBuilder brackets = new StringBuilder();
				int n = fromIndex;
				while (desc.charAt(n) == '[') {	// count opening brackets
					brackets.append("[]");
					n++;
				}
				from.value = n;
				String type = typedescToSignature(desc, from);	// the rest of the string denotes a `<field_type>'
				return type + brackets;

			default:
				if (from.value == 0) {
					throw new IllegalArgumentException(INVALID_TYPE_DESCRIPTION + desc);
				}
				// generics!
				return desc.substring(from.value);
		}
	}

	// ---------------------------------------------------------------- type

	/**
	 * Converts java-class name ("foo.Bar") to bytecode-signature ("foo/bar").
	 */
	public static String typeToSignature(final String className) {
		return className.replace('.', '/');
	}

	/**
	 * Converts java-class name ("foo.Bar") to bytecode-name ("foo/bar").
	 */
	public static String typeToSignature(final Class type) {
		return typeToSignature(type.getName());
	}

	/**
	 * Converts type to byteccode type ref.
	 */
	public static String typeToTyperef(final Class type) {
		if (!type.isArray()) {
			if (!type.isPrimitive()) {
				return 'L' + typeToSignature(type) + ';';
			}
			if (type == int.class) {
				return "I";
			}
			if (type == long.class) {
				return "J";
			}
			if (type == boolean.class) {
				return "Z";
			}
			if (type == double.class) {
				return "D";
			}
			if (type == float.class) {
				return "F";
			}
			if (type == short.class) {
				return "S";
			}
			if (type == void.class) {
				return "V";
			}
			if (type == byte.class) {
				return "B";
			}
			if (type == char.class) {
				return "C";
			}
		}

		return type.getName();
	}

	// ---------------------------------------------------------------- boxing

	/**
	 * Converts Integer object to an int.
	 */
	public static void intValue(final MethodVisitor mv) {
		mv.visitTypeInsn(CHECKCAST, SIGNATURE_JAVA_LANG_INTEGER);
		mv.visitMethodInsn(INVOKEVIRTUAL, SIGNATURE_JAVA_LANG_INTEGER, "intValue", "()I", false);
	}

	/**
	 * Converts Long object to a long.
	 */
	public static void longValue(final MethodVisitor mv) {
		mv.visitTypeInsn(CHECKCAST, SIGNATURE_JAVA_LANG_LONG);
		mv.visitMethodInsn(INVOKEVIRTUAL, SIGNATURE_JAVA_LANG_LONG, "longValue", "()J", false);
	}

	/**
	 * Converts Float object to a float.
	 */
	public static void floatValue(final MethodVisitor mv) {
		mv.visitTypeInsn(CHECKCAST, SIGNATURE_JAVA_LANG_FLOAT);
		mv.visitMethodInsn(INVOKEVIRTUAL, SIGNATURE_JAVA_LANG_FLOAT, "floatValue", "()F", false);
	}

	/**
	 * Converts Double object to a double.
	 */
	public static void doubleValue(final MethodVisitor mv) {
		mv.visitTypeInsn(CHECKCAST, SIGNATURE_JAVA_LANG_DOUBLE);
		mv.visitMethodInsn(INVOKEVIRTUAL, SIGNATURE_JAVA_LANG_DOUBLE, "doubleValue", "()D", false);
	}

	/**
	 * Converts Byte object to a byte.
	 */
	public static void byteValue(final MethodVisitor mv) {
		mv.visitTypeInsn(CHECKCAST, SIGNATURE_JAVA_LANG_BYTE);
		mv.visitMethodInsn(INVOKEVIRTUAL, SIGNATURE_JAVA_LANG_BYTE, "byteValue", "()B", false);
	}

	/**
	 * Converts Short object to a short.
	 */
	public static void shortValue(final MethodVisitor mv) {
		mv.visitTypeInsn(CHECKCAST, SIGNATURE_JAVA_LANG_SHORT);
		mv.visitMethodInsn(INVOKEVIRTUAL, SIGNATURE_JAVA_LANG_SHORT, "shortValue", "()S", false);
	}

	/**
	 * Converts Boolean object to a boolean.
	 */
	public static void booleanValue(final MethodVisitor mv) {
		mv.visitTypeInsn(CHECKCAST, SIGNATURE_JAVA_LANG_BOOLEAN);
		mv.visitMethodInsn(INVOKEVIRTUAL, SIGNATURE_JAVA_LANG_BOOLEAN, "booleanValue", "()Z", false);
	}

	/**
	 * Converts Character object to a char.
	 */
	public static void charValue(final MethodVisitor mv) {
		mv.visitTypeInsn(CHECKCAST, SIGNATURE_JAVA_LANG_CHARACTER);
		mv.visitMethodInsn(INVOKEVIRTUAL, SIGNATURE_JAVA_LANG_CHARACTER, "charValue", "()C", false);
	}

	public static void valueOfInteger(final MethodVisitor mv) {
		mv.visitMethodInsn(INVOKESTATIC, SIGNATURE_JAVA_LANG_INTEGER, "valueOf", "(I)Ljava/lang/Integer;", false);
	}

	public static void valueOfLong(final MethodVisitor mv) {
		mv.visitMethodInsn(INVOKESTATIC, SIGNATURE_JAVA_LANG_LONG, "valueOf", "(J)Ljava/lang/Long;", false);
	}

	public static void valueOfFloat(final MethodVisitor mv) {
		mv.visitMethodInsn(INVOKESTATIC, SIGNATURE_JAVA_LANG_FLOAT, "valueOf", "(F)Ljava/lang/Float;", false);
	}

	public static void valueOfDouble(final MethodVisitor mv) {
		mv.visitMethodInsn(INVOKESTATIC, SIGNATURE_JAVA_LANG_DOUBLE, "valueOf", "(D)Ljava/lang/Double;", false);
	}

	public static void valueOfByte(final MethodVisitor mv) {
		mv.visitMethodInsn(INVOKESTATIC, SIGNATURE_JAVA_LANG_BYTE, "valueOf", "(B)Ljava/lang/Byte;", false);
	}

	public static void valueOfShort(final MethodVisitor mv) {
		mv.visitMethodInsn(INVOKESTATIC, SIGNATURE_JAVA_LANG_SHORT, "valueOf", "(S)Ljava/lang/Short;", false);
	}

	public static void valueOfBoolean(final MethodVisitor mv) {
		mv.visitMethodInsn(INVOKESTATIC, SIGNATURE_JAVA_LANG_BOOLEAN, "valueOf", "(Z)Ljava/lang/Boolean;", false);
	}

	public static void valueOfCharacter(final MethodVisitor mv) {
		mv.visitMethodInsn(INVOKESTATIC, SIGNATURE_JAVA_LANG_CHARACTER, "valueOf", "(C)Ljava/lang/Character;", 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.util.function;

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

/**
 * An aggregate consumer.
 */
public class Consumers<T> implements Consumer<T> {

	private final List<Consumer<T>> consumerList = new ArrayList<>();
	private boolean parallel = false;

	public Consumers(final Consumer<T>... consumers) {
		Collections.addAll(consumerList, consumers);
	}
	public Consumers(final Iterable<Consumers<T>> consumers) {
		consumers.forEach(consumerList::add);
	}

	@SuppressWarnings("unchecked")
	public static <R> Consumers<R> empty() {
		return new Consumers<>();
	}

	/**
	 * Creates an aggregate from given consumers.
	 */
	public static <R> Consumers<R> of(final Consumer<R>... consumers) {
		return new Consumers<>(consumers);
	}

	@SuppressWarnings("unchecked")
	public static <R> Consumers<R> of(final Iterable<Consumer<R>> consumers) {
		return new Consumers(consumers);
	}

	/**
	 * Registers an additional consumer.
	 */
	public Consumers<T> add(final Consumer<T> consumer) {
		consumerList.add(consumer);
		return this;
	}

	/**
	 * Registers additional consumers.
	 */
	public Consumers<T> addAll(final Consumer<T>... consumers) {
		Collections.addAll(consumerList, consumers);
		return this;
	}

	/**
	 * Defines if consumer acceptance should be parallel.
	 */
	public Consumers<T> parallel(final boolean parallel) {
		this.parallel = parallel;
		return this;
	}

	/**
	 * Consumes all registered consumers. The are executed sequentially, in order of registration.
	 * If {@link #parallel(boolean)} flag is set, consumption is going to be parallel.
	 */
	@Override
	public void accept(final T t) {
		if (parallel) {
			consumerList.parallelStream().forEach(consumer -> consumer.accept(t));
		}
		else {
			consumerList.forEach(consumer -> consumer.accept(t));
		}
	}

	public boolean isEmpty() {
		return consumerList.isEmpty();
	}

	/**
	 * Clears the list of consumers.
	 */
	public Consumer clear() {
		consumerList.clear();
		return this;
	}

	/**
	 * Removes a consumer.
	 */
	public Consumers<T> remove(final Consumer<T> consumer) {
		consumerList.remove(consumer);
		return this;
	}
}


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

package jodd.typeconverter.impl;

import jodd.typeconverter.TypeConversionException;
import jodd.typeconverter.TypeConverter;
import jodd.typeconverter.TypeConverterManager;
import jodd.util.CsvUtil;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Collection converter.
 */
public class CollectionConverter<T> implements TypeConverter<Collection<T>> {

	protected final TypeConverterManager typeConverterManager;
	protected final Class<? extends Collection> collectionType;
	protected final Class<T> targetComponentType;

	public CollectionConverter(
		final Class<? extends Collection> collectionType,
		final Class<T> targetComponentType) {
		this(TypeConverterManager.get(), collectionType, targetComponentType);
	}

	public CollectionConverter(
		final TypeConverterManager typeConverterManager,
		final Class<? extends Collection> collectionType,
		final Class<T> targetComponentType) {

		this.typeConverterManager = typeConverterManager;
		this.collectionType = collectionType;
		this.targetComponentType = targetComponentType;
	}

	@Override
	public Collection<T> convert(final Object value) {
		if (value == null) {
			return null;
		}

		if (!(value instanceof Collection)) {
			// source is not an array
	        return convertValueToCollection(value);
		}

		// source is a collection
		return convertCollectionToCollection((Collection)value);
	}

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

	/**
	 * Creates new collection of target component type.
	 * Default implementation uses reflection to create
	 * an collection of target type. Override it for better performances.
	 */
	@SuppressWarnings("unchecked")
	protected Collection<T> createCollection(final int length) {
		if (collectionType.isInterface()) {
			if (collectionType == List.class) {
				if (length > 0) {
					return new ArrayList<>(length);
				} else {
					return new ArrayList<>();
				}
			}

			if (collectionType == Set.class) {
				if (length > 0) {
					return new HashSet<>(length);
				} else {
					return new HashSet<>();
				}
			}

			throw new TypeConversionException("Unknown collection: " + collectionType.getName());
		}
		if (length > 0) {
			try {
				Constructor<Collection<T>> ctor = (Constructor<Collection<T>>) collectionType.getConstructor(int.class);
				return ctor.newInstance(Integer.valueOf(length));
			} catch (Exception ex) {
				// ignore exception
			}
		}

		try {
			return collectionType.getDeclaredConstructor().newInstance();
		} catch (Exception ex) {
			throw new TypeConversionException(ex);
		}
	}

	/**
	 * Creates a collection with single element.
	 */
	protected Collection<T> convertToSingleElementCollection(final Object value) {
		Collection<T> collection = createCollection(0);

		//noinspection unchecked
		collection.add((T) value);

		return collection;
	}

	/**
	 * Converts non-collection value to collection.
 	 */
	protected Collection<T> convertValueToCollection(Object value) {
		if (value instanceof Iterable) {
			Iterable iterable = (Iterable) value;
			Collection<T> collection = createCollection(0);

			for (Object element : iterable) {
				collection.add(convertType(element));
			}
			return collection;
		}

		if (value instanceof CharSequence) {
			value = CsvUtil.toStringArray(value.toString());
		}

		Class type = value.getClass();

		if (type.isArray()) {
			// convert arrays
			Class componentType = type.getComponentType();

			if (componentType.isPrimitive()) {
				return convertPrimitiveArrayToCollection(value, componentType);
			} else {
				Object[] array = (Object[]) value;
				Collection<T> result = createCollection(array.length);
				for (Object a : array) {
					result.add(convertType(a));
				}
				return result;
			}
		}

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

	/**
	 * Converts collection value to target collection.
	 * Each element is converted to target component type.
	 */
	protected Collection<T> convertCollectionToCollection(final Collection value) {
		Collection<T> collection = createCollection(value.size());

		for (Object v : value) {
			collection.add(convertType(v));
		}

		return collection;
	}

	/**
	 * Converts primitive array to target collection.
	 */
	@SuppressWarnings("AutoBoxing")
	protected Collection<T> convertPrimitiveArrayToCollection(final Object value, final Class primitiveComponentType) {
		Collection<T> result = null;

		if (primitiveComponentType == int.class) {
			int[] array = (int[]) value;
			result = createCollection(array.length);
			for (int a : array) {
				result.add(convertType(a));
			}
		}
		else if (primitiveComponentType == long.class) {
			long[] array = (long[]) value;
			result = createCollection(array.length);
			for (long a : array) {
				result.add(convertType(a));
			}
		}
		else if (primitiveComponentType == float.class) {
			float[] array = (float[]) value;
			result = createCollection(array.length);
			for (float a : array) {
				result.add(convertType(a));
			}
		}
		else if (primitiveComponentType == double.class) {
			double[] array = (double[]) value;
			result = createCollection(array.length);
			for (double a : array) {
				result.add(convertType(a));
			}
		}
		else if (primitiveComponentType == short.class) {
			short[] array = (short[]) value;
			result = createCollection(array.length);
			for (short a : array) {
				result.add(convertType(a));
			}
		}
		else if (primitiveComponentType == byte.class) {
			byte[] array = (byte[]) value;
			result = createCollection(array.length);
			for (byte a : array) {
				result.add(convertType(a));
			}
		}
		else if (primitiveComponentType == char.class) {
			char[] array = (char[]) value;
			result = createCollection(array.length);
			for (char a : array) {
				result.add(convertType(a));
			}
		}
		else if (primitiveComponentType == boolean.class) {
			boolean[] array = (boolean[]) value;
			result = createCollection(array.length);
			for (boolean a : array) {
				result.add(convertType(a));
			}
		}
		return result;
	}

}


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

package jodd.proxetta.impl;

import jodd.asm7.ClassReader;
import jodd.proxetta.ProxettaFactory;
import jodd.proxetta.ProxyAspect;
import jodd.proxetta.asm.ProxettaClassBuilder;
import jodd.proxetta.asm.TargetClassInfoReader;
import jodd.proxetta.asm.WorkData;

import java.io.InputStream;

/**
 * Creates the proxy subclass using ASM library.
 */
public class ProxyProxettaFactory extends ProxettaFactory<ProxyProxettaFactory, ProxyProxetta> {

	public ProxyProxettaFactory(final ProxyProxetta proxyProxetta) {
		super(proxyProxetta);
	}

	@Override
	public ProxyProxettaFactory setTarget(final InputStream target) {
		return super.setTarget(target);
	}

	@Override
	public ProxyProxettaFactory setTarget(final String targetName) {
		return super.setTarget(targetName);
	}

	@Override
	public ProxyProxettaFactory setTarget(final Class target) {
		return super.setTarget(target);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected WorkData process(final ClassReader cr, final TargetClassInfoReader targetClassInfoReader) {

		ProxettaClassBuilder pcb = new ProxettaClassBuilder(
				destClassWriter,
				proxetta.getAspects(new ProxyAspect[0]),
				resolveClassNameSuffix(),
				requestedProxyClassName,
				targetClassInfoReader);

		cr.accept(pcb, 0);

		return pcb.getWorkData();
	}

}