// 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.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.locks.StampedLock;

/**
 * Default implementation of timed and size cache map.
 * Implementations should:
 *

 *
create a new cache map

 *
implements own prune strategy

 *

 * Uses ReentrantReadWriteLock to synchronize access.
 * Since upgrading from a read lock to the write lock is not possible,
 * be careful withing {@link #get(Object)} method.
 */
public abstract class AbstractCacheMap<K,V> implements Cache<K,V> {

	class CacheObject<K2,V2> {
		CacheObject(final K2 key, final V2 object, final long ttl) {
			this.key = key;
			this.cachedObject = object;
			this.ttl = ttl;
			this.lastAccess = System.currentTimeMillis();
		}

		final K2 key;
		final V2 cachedObject;
		long lastAccess;		// time of last access
		long accessCount;		// number of accesses
		long ttl;				// objects timeout (time-to-live), 0 = no timeout

		boolean isExpired() {
			if (ttl == 0) {
				return false;
			}
			return lastAccess + ttl < System.currentTimeMillis();
		}
		V2 getObject() {
			lastAccess = System.currentTimeMillis();
			accessCount++;
			return cachedObject;
		}
    }

	protected Map<K,CacheObject<K,V>> cacheMap;
	private final StampedLock lock = new StampedLock();

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

	protected int cacheSize;      // max cache size, 0 = no limit

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int limit() {
		return cacheSize;
	}

	protected long timeout;     // default timeout, 0 = no timeout

	/**
	 * Returns default cache timeout or 0 if it is not set.
	 * Timeout can be set individually for each object.
	 */
	@Override
	public long timeout() {
		return timeout;
	}

	/**
	 * Identifies if objects has custom timeouts.
	 * Should be used to determine if prune for existing objects is needed.
	 */
	protected boolean existCustomTimeout;

	/**
	 * Returns true if prune of expired objects should be invoked.
	 * For internal use.
	 */
	protected boolean isPruneExpiredActive() {
		return (timeout != 0) || existCustomTimeout;
	}

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void put(final K key, final V object) {
		put(key, object, timeout);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void put(final K key, final V object, final long timeout) {
		Objects.requireNonNull(object);

		final long stamp = lock.writeLock();

		try {
			CacheObject<K,V> co = new CacheObject<>(key, object, timeout);
			if (timeout != 0) {
				existCustomTimeout = true;
			}
			if (isReallyFull(key)) {
				pruneCache();
			}
			cacheMap.put(key, co);
		}
		finally {
			lock.unlockWrite(stamp);
		}
	}

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

	protected int hitCount;
	protected int missCount;

	/**
	 * Returns hit count.
	 */
	public int getHitCount() {
		return hitCount;
	}

	/**
	 * Returns miss count.
	 */
	public int getMissCount() {
		return missCount;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public V get(final K key) {
		long stamp = lock.readLock();

		try {
			CacheObject<K,V> co = cacheMap.get(key);
			if (co == null) {
				missCount++;
				return null;
			}
			if (co.isExpired()) {
				final long newStamp = lock.tryConvertToWriteLock(stamp);

				if (newStamp != 0L) {
					stamp = newStamp;
					// lock is upgraded to write lock
				}
				else {
					// manually upgrade lock to write lock
					lock.unlockRead(stamp);
					stamp = lock.writeLock();
				}

				CacheObject<K,V> removedCo = cacheMap.remove(key);
				if (removedCo != null) {
					onRemove(removedCo.key, removedCo.cachedObject);
				}

				missCount++;
				return null;
			}

			hitCount++;
			return co.getObject();
		}
		finally {
			lock.unlock(stamp);
		}
	}

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

	/**
	 * Prune implementation.
	 */
	protected abstract int pruneCache();

	/**
	 * {@inheritDoc}
	 */
	@Override
	public final int prune() {
		final long stamp = lock.writeLock();
		try {
			return pruneCache();
		}
		finally {
			lock.unlockWrite(stamp);
		}
	}

	// ---------------------------------------------------------------- common

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean isFull() {
		if (cacheSize == 0) {
			return false;
		}
		return cacheMap.size() >= cacheSize;
	}

	protected boolean isReallyFull(final K key) {
		if (cacheSize == 0) {
			return false;
		}
		if (cacheMap.size() >= cacheSize) {
			return !cacheMap.containsKey(key);
		}
		else {
			return false;
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public V remove(final K key) {
		V removedValue = null;
		final long stamp = lock.writeLock();
		try {
			CacheObject<K,V> co = cacheMap.remove(key);

			if (co != null) {
				onRemove(co.key, co.cachedObject);
				removedValue = co.cachedObject;
			}
		}
		finally {
			lock.unlockWrite(stamp);
		}
		return removedValue;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void clear() {
		final long stamp = lock.writeLock();
		try {
			cacheMap.clear();
		}
		finally {
			lock.unlockWrite(stamp);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int size() {
		return cacheMap.size();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean isEmpty() {
		return size() == 0;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Map<K, V> snapshot() {
		final long stamp = lock.writeLock();
		try {
			Map<K, V> map = new HashMap<>(cacheMap.size());
			cacheMap.forEach((key, cacheValue) -> map.put(key, cacheValue.getObject()));
			return map;
		}
		finally {
			lock.unlockWrite(stamp);
		}
	}

	// ---------------------------------------------------------------- protected

	/**
	 * Callback called on item removal. The cache is still locked.
	 */
	protected void onRemove(final K key, final V cachedObject) {
	}

}


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

package jodd.madvoc;

import jodd.log.Logger;
import jodd.log.LoggerFactory;
import jodd.props.Props;
import jodd.typeconverter.Converter;
import jodd.util.ClassLoaderUtil;
import jodd.util.ClassUtil;

import javax.servlet.ServletContext;

/**
 * Maintain the lifecycle of a Madvoc {@link WebApp}.
 */
public class Madvoc {

	/**
	 * Web application.
	 */
	public static final String PARAM_MADVOC_WEBAPP = "madvoc.webapp";
	/**
	 * Madvoc configurator.
	 */
	public static final String PARAM_MADVOC_CONFIGURATOR = "madvoc.configurator";
	/**
	 * List of Madvoc params and properties files to be found on classpath.
	 */
	public static final String PARAM_MADVOC_PARAMS = "madvoc.params";

	private static Logger log;

	// ---------------------------------------------------------------- statics

	private static final String MADVOC_ATTR = Madvoc.class.getName();

	/**
	 * Returns Madvoc instance from servlet context.
	 * May return null indicating Madvoc
	 * is not yet initialized.
	 */
	public static Madvoc get(final ServletContext servletContext) {
		return (Madvoc) servletContext.getAttribute(MADVOC_ATTR);
	}

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

	protected String webAppClassName;
	protected Class webAppClass = WebApp.class;
	protected String[] paramsFiles;
	protected String madvocConfiguratorClassName;
	protected Class madvocConfiguratorClass = AutomagicMadvocConfigurator.class;

	/**
	 * Sets {@link WebApp} class name.
	 */
	public void setWebAppClassName(final String webAppClass) {
		this.webAppClassName = webAppClass;
		this.webAppClass = null;
	}

	/**
	 * Sets {@link WebApp} class.
	 */
	public void setWebAppClass(final Class webAppClass) {
		this.webAppClass = webAppClass;
		this.webAppClassName = null;
	}

	/**
	 * Sets the name of the class that is going to be used for configuration of user actions.
	 */
	public void setMadvocConfiguratorClassName(final String madvocConfiguratorClassName) {
		this.madvocConfiguratorClassName = madvocConfiguratorClassName;
		this.madvocConfiguratorClass = null;
	}

	/**
	 * Sets class that will be used for configuring the user actions.
	 */
	public void setMadvocConfiguratorClass(final Class madvocConfiguratorClass) {
		this.madvocConfiguratorClass = madvocConfiguratorClass;
		this.madvocConfiguratorClassName = null;
	}

	public void setParamsFiles(final String[] paramsFiles) {
		this.paramsFiles = paramsFiles;
	}

	/**
	 * Configures Madvoc by reading context init parameters.
	 */
	public void configureWith(final ServletContext servletContext) {
		webAppClassName = servletContext.getInitParameter(PARAM_MADVOC_WEBAPP);
		paramsFiles = Converter.get().toStringArray(servletContext.getInitParameter(PARAM_MADVOC_PARAMS));
		madvocConfiguratorClassName = servletContext.getInitParameter(PARAM_MADVOC_CONFIGURATOR);
	}

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

	protected WebApp webapp;
	protected ServletContext servletContext;

	/**
	 * Returns web application once it is started.
	 */
	public WebApp webapp() {
		return webapp;
	}

	/**
	 * Creates and starts new Madvoc web application.
	 * Madvoc instance is stored in servlet context.
	 * Important: servletContext may be null,
	 * when web application is run out from container.
	 */
	@SuppressWarnings("InstanceofCatchParameter")
	public WebApp startWebApplication(final ServletContext servletContext) {
		try {
			WebApp webApp = _start(servletContext);

			log.info("Madvoc is up and running.");

			return webApp;
		}
		catch (Exception ex) {
			if (log != null) {
				log.error("Madvoc startup failure.", ex);
			} else {
				ex.printStackTrace();
			}
			if (ex instanceof MadvocException) {
				throw (MadvocException) ex;
			}
			throw new MadvocException(ex);
		}
	}

	private WebApp _start(final ServletContext servletContext) {
		if (servletContext != null) {
			this.servletContext = servletContext;

			servletContext.setAttribute(MADVOC_ATTR, this);
		}

		webapp = createWebApplication();

		// init logger

		log = LoggerFactory.getLogger(Madvoc.class);

		// configure webapp

		webapp.bindServletContext(servletContext);

		if (paramsFiles != null) {
			final Props params = loadMadvocParams(paramsFiles);

			webapp.withParams(params);
		}

		resolveMadvocConfigClass();

		if (madvocConfiguratorClass != null) {
			webapp.registerComponent(madvocConfiguratorClass);
		}

		// initialize

		log.info("Madvoc starting...");

		webapp.start();

		return webapp;
	}

	/**
	 * Stops Madvoc web application.
	 */
	public void stopWebApplication() {
		log.info("Madvoc shutting down...");

		if (servletContext != null) {
			servletContext.removeAttribute(MADVOC_ATTR);
		}

		webapp.shutdown();

		webapp = null;
	}

	// ---------------------------------------------------------------- loading configuration

	/**
	 * Creates {@link WebApp}.
	 */
	protected WebApp createWebApplication() {
		if ((webAppClassName == null) && (webAppClass == null)) {
			return new WebApp();
		}

		final WebApp webApp;
		try {
			if (webAppClassName != null) {
				webAppClass = ClassLoaderUtil.loadClass(webAppClassName);
			}
			webApp = (WebApp) ClassUtil.newInstance(webAppClass);
		}
		catch (Exception ex) {
			throw new MadvocException("Unable to load Madvoc web application class: " + webAppClassName, ex);
		}

		return webApp;
	}

	/**
	 * Loads Madvoc parameters. New {@link Props} is created from the classpath.
	 */
	protected Props loadMadvocParams(final String[] patterns) {
		if (log.isInfoEnabled()) {
			log.info("Loading Madvoc parameters from: " + Converter.get().toString(patterns));
		}
		try {
			return new Props().loadFromClasspath(patterns);
		} catch (Exception ex) {
			throw new MadvocException("Unable to load Madvoc parameters from: " +
					Converter.get().toString(patterns) + ".properties': " + ex.toString(), ex);
		}
	}

	/**
	 * Loads Madvoc component that will be used for configuring the user actions.
	 * If class name is null, default {@link AutomagicMadvocConfigurator}
	 * will be used.
	 */
	protected void resolveMadvocConfigClass() {
		if ((madvocConfiguratorClassName == null) && (madvocConfiguratorClass == null)) {
			return;
		}

		try {
			if (madvocConfiguratorClassName != null) {
				madvocConfiguratorClass = ClassLoaderUtil.loadClass(madvocConfiguratorClassName);
			}

			log.info("Configuring Madvoc using: " + madvocConfiguratorClass.getName());
		} catch (Exception ex) {
			throw new MadvocException("Unable to load Madvoc configurator class: " + madvocConfiguratorClassName, ex);
		}
	}

}


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

package jodd.bridge;

import jodd.util.ClassLoaderUtil;

import java.lang.reflect.Method;

@JavaIncompatible
public class DefineClass {

	/**
	 * Defines a class from byte array into the specified class loader.
	 * Warning: this is a hack!
	 * @param className optional class name, may be null
	 * @param classData bytecode data
	 * @param classLoader classloader that will load class
	 */
	public static Class of(final String className, final byte[] classData, ClassLoader classLoader) {
		if (classLoader == null) {
			classLoader = ClassLoaderUtil.getDefaultClassLoader();
		}
		try {
			final Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
			defineClassMethod.setAccessible(true);
			return (Class) defineClassMethod.invoke(classLoader, className, classData, 0, classData.length);
		} catch (Throwable th) {
			throw new RuntimeException("Define class failed: " + className, th);
		}
	}

}


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

package jodd.json;

import jodd.introspector.ClassDescriptor;
import jodd.introspector.ClassIntrospector;
import jodd.introspector.PropertyDescriptor;
import jodd.json.meta.JsonAnnotationManager;
import jodd.json.meta.TypeData;
import jodd.util.CharArraySequence;
import jodd.util.CharUtil;
import jodd.util.StringPool;
import jodd.util.UnsafeUtil;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

/**
 * Simple, developer-friendly JSON parser. It focuses on easy usage
 * and type mappings. Uses Jodd's type converters, so it is natural
 * companion for Jodd projects.
 *

 * This JSON parser also works in {@link #lazy(boolean)} mode. This
 * mode is for top performance usage: parsing is done very, very lazy.
 * While you can use all the mappings and other tools, for best performance
 * the lazy mode should be used only with maps and lists (no special mappings).
 * Also, the performance has it's price: more memory consumption, because the
 * original input is hold until the result is in use.
 *

 * See: http://www.ietf.org/rfc/rfc4627.txt
 */
public class JsonParser extends JsonParserBase {

	public static class Defaults {

		public static final String DEFAULT_CLASS_METADATA_NAME = "__class";

		/**
		 * Flag for enabling the lazy mode.
		 */
		public static boolean lazy = false;
		/**
		 * Defines if parser will use extended paths information
		 * and path matching.
		 */
		public static boolean useAltPathsByParser = false;
		/**
		 * Default value for loose mode.
		 */
		public static boolean loose = false;

		/**
		 * Specifies if 'class' metadata is used and its value. When set, class metadata
		 * is used by {@link jodd.json.JsonSerializer} and all objects
		 * will have additional field with the class type in the resulting JSON.
		 * {@link jodd.json.JsonParser} will also consider this flag to build
		 * correct object type. If null, class information is not used.
		 */
		public static String classMetadataName = null;

		public static boolean strictTypes = true;
	}

	/**
	 * Static ctor.
	 */
	public static JsonParser create() {
		return new JsonParser();
	}

	/**
	 * Creates a lazy implementation of the JSON parser.
	 */
	public static JsonParser createLazyOne() {
		return new JsonParser().lazy(true);
	}

	private static final char[] T_RUE = new char[] {'r', 'u', 'e'};
	private static final char[] F_ALSE = new char[] {'a', 'l', 's', 'e'};
	private static final char[] N_ULL = new char[] {'u', 'l', 'l'};

	/**
	 * Map keys.
	 */
	public static final String KEYS = "keys";
	/**
	 * Array or map values.
	 */
	public static final String VALUES = "values";

	protected int ndx = 0;
	protected char[] input;
	protected int total;
	protected Path path;
	protected boolean useAltPaths = Defaults.useAltPathsByParser;
	protected boolean lazy = Defaults.lazy;
	protected boolean looseMode = Defaults.loose;
	protected Class rootType;
	protected MapToBean mapToBean;
	private boolean notFirstObject;

	private final JsonAnnotationManager jsonAnnotationManager;

	public JsonParser() {
		super(Defaults.strictTypes);
		this.text = new char[512];
		this.jsonAnnotationManager = JsonAnnotationManager.get();
	}

	/**
	 * Resets JSON parser, so it can be reused.
	 */
	protected void reset() {
		this.ndx = 0;
		this.textLen = 0;
		this.path = new Path();
		this.notFirstObject = false;
		if (useAltPaths) {
			path.altPath = new Path();
		}

		if (classMetadataName != null) {
			mapToBean = createMapToBean(classMetadataName);
		}
	}

	/**
	 * Enables usage of additional paths.
	 */
	public JsonParser useAltPaths() {
		this.useAltPaths = true;
		return this;
	}

	/**
	 * Enables 'loose' mode for parsing. When 'loose' mode is enabled,
	 * JSON parsers swallows also invalid JSONs:
	 *

	 *
invalid escape character sequence is simply added to the output

	 *
strings can be quoted with single-quotes

	 *
strings can be unquoted, but may not contain escapes

	 *

	 */
	public JsonParser looseMode(final boolean looseMode) {
		this.looseMode = looseMode;
		return this;
	}

	/**
	 * Defines if type conversion is strict. If not, all exceptions will be
	 * caught and replaced with {@code null}.
	 */
	public JsonParser strictTypes(final boolean strictTypes) {
		this.strictTypes = strictTypes;
		return this;
	}

	/**
	 * Defines how JSON parser works. In non-lazy mode, the whole JSON is parsed as it is.
	 * In the lazy mode, not everything is parsed, but some things are left lazy.
	 * This way we gain performance, especially on partial usage of the whole JSON.
	 * However, be aware that parser holds the input memory until the returned
	 * objects are disposed.
	 */
	public JsonParser lazy(final boolean lazy) {
		this.lazy = lazy;
		this.mapSupplier = lazy ? LAZYMAP_SUPPLIER : HASHMAP_SUPPLIER;
		this.listSupplier = lazy ? LAZYLIST_SUPPLIER : ARRAYLIST_SUPPLIER;
		return this;
	}

	// ---------------------------------------------------------------- mappings

	protected Map<Path, Class> mappings;

	/**
	 * Maps a class to JSONs root.
	 */
	public JsonParser map(final Class target) {
		rootType = target;
		return this;
	}

	/**
	 * Maps a class to given path. For arrays, append values
	 * to the path to specify component type (if not specified by
	 * generics).
	 */
	public JsonParser map(final String path, final Class target) {
		if (path == null) {
			rootType = target;
			return this;
		}
		if (mappings == null) {
			mappings = new HashMap<>();
		}
		mappings.put(Path.parse(path), target);
		return this;
	}

	/**
	 * Replaces type with mapped type for current path.
	 */
	protected Class replaceWithMappedTypeForPath(final Class target) {
		if (mappings == null) {
			return target;
		}

		Class newType;

		// first try alt paths

		Path altPath = path.getAltPath();

		if (altPath != null) {
			if (!altPath.equals(path)) {
				newType = mappings.get(altPath);

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

		// now check regular paths

		newType = mappings.get(path);

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

		return target;
	}

	// ---------------------------------------------------------------- converters

	protected Map<Path, ValueConverter> convs;

	/**
	 * Defines {@link jodd.json.ValueConverter} to use on given path.
	 */
	public JsonParser withValueConverter(final String path, final ValueConverter valueConverter) {
		if (convs == null) {
			convs = new HashMap<>();
		}
		convs.put(Path.parse(path), valueConverter);
		return this;
	}

	/**
	 * Lookups for value converter for current path.
	 */
	protected ValueConverter lookupValueConverter() {
		if (convs == null) {
			return null;
		}
		return convs.get(path);
	}

	// ---------------------------------------------------------------- class meta data name

	protected String classMetadataName = Defaults.classMetadataName;

	/**
	 * Sets local class meta-data name.
	 *

	 * Note that by using the class meta-data name you may expose a security hole in case untrusted source
	 * manages to specify a class that is accessible through class loader and exposes set of methods and/or fields,
	 * access of which opens an actual security hole. Such classes are known as “deserialization gadget”s.
	 *
	 * Because of this, use of "default typing" is not encouraged in general, and in particular is recommended against
	 * if the source of content is not trusted. Conversely, default typing may be used for processing content in
	 * cases where both ends (sender and receiver) are controlled by same entity.
	 */
	public JsonParser setClassMetadataName(final String name) {
		classMetadataName = name;
		return this;
	}

	/**
	 * Sets usage of default class meta-data name.
	 * Using it may introduce a security hole, see {@link #setClassMetadataName(String)} for more details.
	 * @see #setClassMetadataName(String)
	 */
	public JsonParser withClassMetadata(final boolean useMetadata) {
		if (useMetadata) {
			classMetadataName = Defaults.DEFAULT_CLASS_METADATA_NAME;
		}
		else {
			classMetadataName = null;
		}
		return this;
	}

	/**
	 * Adds a {@link jodd.util.Wildcard wildcard} pattern for white-listing classes.
	 * @see #setClassMetadataName(String)
	 */
	public JsonParser allowClass(final String classPattern) {
		if (super.classnameWhitelist == null) {
			super.classnameWhitelist = new ArrayList<>();
		}
		classnameWhitelist.add(classPattern);
		return this;
	}

	/**
	 * Removes the whitelist of allowed classes.
	 * @see #setClassMetadataName(String)
	 */
	public JsonParser allowAllClasses() {
		classnameWhitelist = null;
		return this;
	}

	// ---------------------------------------------------------------- parse

	/**
	 * Parses input JSON as given type.
	 */
	@SuppressWarnings("unchecked")
	public <T> T parse(final String input, final Class<T> targetType) {
		rootType = targetType;
		return _parse(UnsafeUtil.getChars(input));
	}

	/**
	 * Parses input JSON to {@link JsonObject}, special case of {@link #parse(String, Class)}.
	 */
	public JsonObject parseAsJsonObject(final String input) {
		return new JsonObject(parse(input));
	}

	/**
	 * Parses input JSON to {@link JsonArray}, special case of parsing.
	 */
	public JsonArray parseAsJsonArray(final String input) {
		return new JsonArray(parse(input));
	}

	/**
	 * Parses input JSON to a list with specified component type.
	 */
	public <T> List<T> parseAsList(final String string, final Class<T> componentType) {
		return new JsonParser()
			.map(JsonParser.VALUES, componentType)
			.parse(string);
	}

	/**
	 * Parses input JSON to a list with specified key and value types.
	 */
	public <K, V> Map<K, V> parseAsMap(
		final String string, final Class<K> keyType, final Class<V> valueType) {

		return new JsonParser()
			.map(JsonParser.KEYS, keyType)
			.map(JsonParser.VALUES, valueType)
			.parse(string);
	}

	/**
	 * Parses input JSON string.
	 */
	public <T> T parse(final String input) {
		return _parse(UnsafeUtil.getChars(input));
	}

	/**
	 * Parses input JSON as given type.
	 */
	@SuppressWarnings("unchecked")
	public <T> T parse(final char[] input, final Class<T> targetType) {
		rootType = targetType;
		return _parse(input);
	}

	/**
	 * Parses input JSON char array.
	 */
	public <T> T parse(final char[] input) {
		return _parse(input);
	}

	private <T> T _parse(final char[] input) {
		this.input = input;
		this.total = input.length;

		reset();

		skipWhiteSpaces();

		Object value;

		try {
			value = parseValue(rootType, null, null);
		}
		catch (IndexOutOfBoundsException iofbex) {
			syntaxError("End of JSON");
			return null;
		}

		skipWhiteSpaces();

		if (ndx != total) {
			syntaxError("Trailing chars");
			return null;
		}

		if (lazy) {
			// lets resolve root lazy values
			value = resolveLazyValue(value);
		}

		// convert map to target type

		if (classMetadataName != null && rootType == null) {
			if (value instanceof Map) {
				Map map = (Map) value;

				value = mapToBean.map2bean(map, null);
			}
		}

		return (T) value;
	}

	// ---------------------------------------------------------------- parser

	/**
	 * Parses a JSON value.
	 * @param targetType target type to convert, may be null
	 * @param componentType component type for maps and arrays, may be null
	 */
	protected Object parseValue(final Class targetType, final Class keyType, final Class componentType) {
		final ValueConverter valueConverter;

		final char c = input[ndx];

		switch (c) {
			case '\'':
				if (!looseMode) {
					break;
				}
			case '"':
				ndx++;
				Object string = parseStringContent(c);

				valueConverter = lookupValueConverter();
				if (valueConverter != null) {
					return valueConverter.convert(string);
				}

				if (targetType != null && targetType != String.class) {
					string = convertType(string, targetType);
				}
				return string;

			case '{':
				ndx++;
				if (lazy) {
					if (notFirstObject) {
						final Object value = new ObjectParser(this, targetType, keyType, componentType);

						skipObject();

						return value;
					}
					else {
						notFirstObject = true;
					}
				}

				return parseObjectContent(targetType, keyType, componentType);

			case '[':
				ndx++;
				return parseArrayContent(targetType, componentType);

			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
			case '-':
				Object number = parseNumber();

				valueConverter = lookupValueConverter();
				if (valueConverter != null) {
					return valueConverter.convert(number);
				}

				if (targetType != null) {
					number = convertType(number, targetType);
				}
				return number;

			case 'n':
				ndx++;
				if (match(N_ULL)) {
					valueConverter = lookupValueConverter();
					if (valueConverter != null) {
						return valueConverter.convert(null);
					}
					return null;
				}
				break;

			case 't':
				ndx++;
				if (match(T_RUE)) {
					Object value = Boolean.TRUE;

					valueConverter = lookupValueConverter();
					if (valueConverter != null) {
						return valueConverter.convert(value);
					}

					if (targetType != null) {
						value = convertType(value, targetType);
					}
					return value;
				}
				break;

			case 'f':
				ndx++;
				if (match(F_ALSE)) {
					Object value = Boolean.FALSE;

					valueConverter = lookupValueConverter();
					if (valueConverter != null) {
						return valueConverter.convert(value);
					}

					if (targetType != null) {
						value = convertType(value, targetType);
					}
					return value;
				}
				break;
		}

		if (looseMode) {
			// try to parse unquoted string
			Object string = parseUnquotedStringContent();

			valueConverter = lookupValueConverter();
			if (valueConverter != null) {
				return valueConverter.convert(string);
			}

			if (targetType != null && targetType != String.class) {
				string = convertType(string, targetType);
			}
			return string;
		}

		syntaxError("Invalid char: " + input[ndx]);
		return null;
	}

	// ---------------------------------------------------------------- lazy

	/**
	 * Resolves lazy value during the parsing runtime.
	 */
	private Object resolveLazyValue(Object value) {
		if (value instanceof Supplier) {
			value = ((Supplier)value).get();
		}
		return value;
	}

	/**
	 * Skips over complete object. It is not parsed, just skipped. It will be
	 * parsed later, but only if required.
	 */
	private void skipObject() {
		int bracketCount = 1;
		boolean insideString = false;

		while (ndx < total) {
			final char c = input[ndx];

			if (insideString) {
				if (c == '\"' && notPrecededByEvenNumberOfBackslashes()) {
					insideString = false;
				}
			} else if (c == '\"') {
				insideString = true;
			} else if (c == '{') {
				bracketCount++;
			} else if (c == '}') {
				bracketCount--;
				if (bracketCount == 0) {
					ndx++;
					return;
				}
			}
			ndx++;
		}
	}

	private boolean notPrecededByEvenNumberOfBackslashes() {
		int pos = ndx;
		int count = 0;
		while (pos > 0 && input[pos - 1] == '\\') {
			count++;
			pos--;
		}
		return count  % 2 == 0;
	}

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

	protected char[] text;
	protected int textLen;

	/**
	 * Parses a string.
	 */
	protected String parseString() {
		char quote = '\"';
		if (looseMode) {
			quote = consumeOneOf('\"', '\'');
			if (quote == 0) {
				return parseUnquotedStringContent();
			}
		} else {
			consume(quote);
		}

		return parseStringContent(quote);
	}

	/**
	 * Parses string content, once when starting quote has been consumed.
	 */
	protected String parseStringContent(final char quote) {
		final int startNdx = ndx;

		// roll-out until the end of the string or the escape char
		while (true) {
			final char c = input[ndx];

			if (c == quote) {
				// no escapes found, just use existing string
				ndx++;
				return new String(input, startNdx, ndx - 1 - startNdx);
			}

			if (c == '\\') {
				break;
			}

			ndx++;
		}

		// escapes found, proceed differently

		textLen = ndx - startNdx;

		growEmpty();

//		for (int i = startNdx, j = 0; j < textLen; i++, j++) {
//			text[j] = input[i];
//		}
		System.arraycopy(input, startNdx, text, 0, textLen);

		// escape char, process everything until the end
		while (true) {
			char c = input[ndx];

			if (c == quote) {
				// done
				ndx++;
				final String str = new String(text, 0, textLen);
				textLen = 0;
				return str;
			}

			if (c == '\\') {
				// escape char found
				ndx++;

				c = input[ndx];

				switch (c) {
					case '\"' : c = '\"'; break;
					case '\\' : c = '\\'; break;
					case '/' : c = '/'; break;
					case 'b' : c = '\b'; break;
					case 'f' : c = '\f'; break;
					case 'n' : c = '\n'; break;
					case 'r' : c = '\r'; break;
					case 't' : c = '\t'; break;
					case 'u' :
						ndx++;
						c = parseUnicode();
						break;
					default:
						if (looseMode) {
							if (c != '\'') {
								c = '\\';
								ndx--;
							}
						}
						else {
							syntaxError("Invalid escape char: " + c);
						}
				}
			}

			text[textLen] = c;

			textLen++;

			growAndCopy();

			ndx++;
		}
	}

	/**
	 * Grows empty text array.
	 */
	protected void growEmpty() {
		if (textLen >= text.length) {
			int newSize = textLen << 1;

			text = new char[newSize];
		}
	}

	/**
	 * Grows text array when {@code text.length == textLen}.
	 */
	protected void growAndCopy() {
		if (textLen == text.length) {
			int newSize = text.length << 1;

			char[] newText = new char[newSize];

			if (textLen > 0) {
				System.arraycopy(text, 0, newText, 0, textLen);
			}

			text = newText;
		}
	}

	/**
	 * Parses 4 characters and returns unicode character.
	 */
	protected char parseUnicode() {
		int i0 = CharUtil.hex2int(input[ndx++]);
		int i1 = CharUtil.hex2int(input[ndx++]);
		int i2 = CharUtil.hex2int(input[ndx++]);
		int i3 = CharUtil.hex2int(input[ndx]);

		return (char) ((i0 << 12) + (i1 << 8) + (i2 << 4) + i3);
	}

	// ---------------------------------------------------------------- un-quoted

	private final static char[] UNQUOTED_DELIMETERS = ",:[]{}\\\"'".toCharArray();

	/**
	 * Parses un-quoted string content.
	 */
	protected String parseUnquotedStringContent() {
		final int startNdx = ndx;

		while (true) {
			final char c = input[ndx];

			if (c <= ' ' || CharUtil.equalsOne(c, UNQUOTED_DELIMETERS)) {
				final int currentNdx = ndx;

				// done
				skipWhiteSpaces();

				return new String(input, startNdx, currentNdx - startNdx);
			}

			ndx++;
		}
	}

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

	/**
	 * Parses JSON numbers.
	 */
	protected Number parseNumber() {
		final int startIndex = ndx;

		char c = input[ndx];

		boolean isDouble = false;
		boolean isExp = false;

		if (c == '-') {
			ndx++;
		}

		while (true) {
			if (isEOF()) {
				break;
			}

			c = input[ndx];

			if (c >= '0' && c <= '9') {
				ndx++;
				continue;
			}
			if (c <= 32) {		// white space
				break;
			}
			if (c == ',' || c == '}' || c == ']') {	// delimiter
				break;
			}

			if (c == '.') {
				isDouble = true;
			}
			else if (c == 'e' || c == 'E') {
				isExp = true;
			}
			ndx++;
		}

		final String value = new String(input, startIndex, ndx - startIndex);

		if (isDouble) {
			return Double.valueOf(value);
		}

		long longNumber;

		if (isExp) {
			longNumber = Double.valueOf(value).longValue();
		}
		else {
			if (value.length() >= 19) {
				// if string is 19 chars and longer, it can be over the limit
				BigInteger bigInteger = new BigInteger(value);

				if (isGreaterThanLong(bigInteger)) {
					return bigInteger;
				}
				longNumber = bigInteger.longValue();
			}
			else {
				longNumber = Long.parseLong(value);
			}
		}

		if ((longNumber >= Integer.MIN_VALUE) && (longNumber <= Integer.MAX_VALUE)) {
			return (int) longNumber;
		}
		return longNumber;
	}

	private static boolean isGreaterThanLong(final BigInteger bigInteger) {
		return bigInteger.compareTo(MAX_LONG) > 0 || bigInteger.compareTo(MIN_LONG) < 0;
	}

	private static final BigInteger MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE);
	private static final BigInteger MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE);

	// ---------------------------------------------------------------- array

	/**
	 * Parses arrays, once when open bracket has been consumed.
	 */
	protected Object parseArrayContent(Class targetType, Class componentType) {
		// detect special case

		if (targetType == Object.class) {
			targetType = List.class;
		}

		// continue

		targetType = replaceWithMappedTypeForPath(targetType);

		if (componentType == null && targetType != null && targetType.isArray()) {
			componentType = targetType.getComponentType();
		}

		path.push(VALUES);

		componentType = replaceWithMappedTypeForPath(componentType);

		Collection<Object> target = newArrayInstance(targetType);

		boolean koma = false;

		mainloop:
		while (true) {
			skipWhiteSpaces();

			char c = input[ndx];

			if (c == ']') {
				if (koma) {
					syntaxError("Trailing comma");
				}

				ndx++;
				path.pop();
				return target;
			}

			Object value = parseValue(componentType, null, null);

			target.add(value);

			skipWhiteSpaces();

			c = input[ndx];

			switch (c) {
				case ']': ndx++; break mainloop;
				case ',': ndx++; koma = true; break;
				default: syntaxError("Invalid char: expected ] or ,");
			}

		}

		path.pop();

		if (targetType != null) {
			return convertType(target, targetType);
		}

		return target;
	}

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

	/**
	 * Parses object, once when open bracket has been consumed.
	 */
	protected Object parseObjectContent(Class targetType, Class valueKeyType, Class valueType) {
		// detect special case

		if (targetType == Object.class) {
			targetType = Map.class;
		}

		// continue

		targetType = replaceWithMappedTypeForPath(targetType);

		Object target;
		boolean isTargetTypeMap = true;
		boolean isTargetRealTypeMap = true;
		ClassDescriptor targetTypeClassDescriptor = null;
		TypeData typeData = null;

		if (targetType != null) {
			targetTypeClassDescriptor = ClassIntrospector.get().lookup(targetType);

			// find if the target is really a map
			// because when classMetadataName != null we are forcing
			// map usage locally in this method

			isTargetRealTypeMap = targetTypeClassDescriptor.isMap();

			typeData = jsonAnnotationManager.lookupTypeData(targetType);
		}

		if (isTargetRealTypeMap) {
			// resolve keys only for real maps
			path.push(KEYS);
			valueKeyType = replaceWithMappedTypeForPath(valueKeyType);
			path.pop();
		}

		if (classMetadataName == null) {
			// create instance of target type, no 'class' information
			target = newObjectInstance(targetType);

			isTargetTypeMap = isTargetRealTypeMap;
		} else {
			// all beans will be created first as a map
			target = mapSupplier.get();
		}

		boolean koma = false;

		mainloop:
		while (true) {
			skipWhiteSpaces();

			char c = input[ndx];

			if (c == '}') {
				if (koma) {
					syntaxError("Trailing comma");
				}

				ndx++;
				break;
			}

			koma = false;

			String key = parseString();
			String keyOriginal = key;

			skipWhiteSpaces();

			consume(':');

			skipWhiteSpaces();

			// read the type of the simple property

			PropertyDescriptor pd = null;
			Class propertyType = null;
			Class keyType = null;
			Class componentType = null;

			// resolve simple property

			if (!isTargetRealTypeMap) {
				// replace key with real property value
				key = jsonAnnotationManager.resolveRealName(targetType, key);
			}

			if (!isTargetTypeMap) {
				pd = targetTypeClassDescriptor.getPropertyDescriptor(key, true);

				if (pd != null) {
					propertyType = pd.getType();
					keyType = pd.resolveKeyType(true);
					componentType = pd.resolveComponentType(true);
				}
			}

			Object value;

			if (!isTargetTypeMap) {
				// *** inject into bean
					path.push(key);

					value = parseValue(propertyType, keyType, componentType);

					path.pop();

				if (typeData.rules.match(keyOriginal, !typeData.strict)) {

					if (pd != null) {
						if (lazy) {
							// need to resolve lazy value before injecting objects into it
							value = resolveLazyValue(value);
						}

						// only inject values if target property exist
						injectValueIntoObject(target, pd, value);
					}
				}
			}
			else {
				Object keyValue = key;

				if (valueKeyType != null) {
					keyValue = convertType(key, valueKeyType);
				}

				// *** add to map
				if (isTargetRealTypeMap) {
					path.push(VALUES, key);

					valueType = replaceWithMappedTypeForPath(valueType);
				} else {
					path.push(key);
				}

				value = parseValue(valueType, null, null);

				path.pop();

				((Map) target).put(keyValue, value);
			}

			skipWhiteSpaces();

			c = input[ndx];

			switch (c) {
				case '}': ndx++; break mainloop;
				case ',': ndx++; koma = true; break;
				default: syntaxError("Invalid char: expected } or ,");
			}
		}

		// done

		// convert Map to target type
		if (classMetadataName != null) {
			target = mapToBean.map2bean((Map) target, targetType);
		}

		return target;
	}

	// ---------------------------------------------------------------- scanning tools

	/**
	 * Consumes char at current position. If char is different, throws the exception.
	 */
	protected void consume(final char c) {
		if (input[ndx] != c) {
			syntaxError("Invalid char: expected " + c);
		}

		ndx++;
	}

	/**
	 * Consumes one of the allowed char at current position.
	 * If char is different, return 0.
	 * If matched, returns matched char.
	 */
	protected char consumeOneOf(final char c1, final char c2) {
		char c = input[ndx];

		if ((c != c1) && (c != c2)) {
			return 0;
		}

		ndx++;

		return c;
	}

	/**
	 * Returns true if scanning is at the end.
	 */
	protected boolean isEOF() {
		return ndx >= total;
	}

	/**
	 * Skips whitespaces. For the simplification, whitespaces are
	 * considered any characters less or equal to 32 (space).
	 */
	protected final void skipWhiteSpaces() {
		while (true) {
			if (isEOF()) {
				return;
			}
			if (input[ndx] > 32) {
				return;
			}
			ndx++;
		}
    }

	/**
	 * Matches char buffer with content on given location.
	 */
	protected final boolean match(final char[] target) {
		for (char c : target) {
			if (input[ndx] != c) {
				return false;
			}
			ndx++;
		}

		return true;
	}

	// ---------------------------------------------------------------- error

	/**
	 * Throws {@link jodd.json.JsonException} indicating a syntax error.
	 */
	protected void syntaxError(final String message) {
		String left = "...";
		String right = "...";
		int offset = 10;

		int from = ndx - offset;
		if (from < 0) {
			from = 0;
			left = StringPool.EMPTY;
		}

		int to = ndx + offset;
		if (to > input.length) {
			to = input.length;
			right = StringPool.EMPTY;
		}

		final CharSequence str = CharArraySequence.of(input, from, to - from);

		throw new JsonException(
				"Syntax error! " + message + "\n" +
				"offset: " + ndx + " near: \"" + left + str + right + "\"");
	}

}


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

package jodd.proxetta.asm;

import jodd.asm.AsmUtil;
import jodd.asm7.ClassVisitor;
import jodd.asm7.FieldVisitor;
import jodd.asm7.MethodVisitor;
import jodd.asm7.Opcodes;
import jodd.proxetta.ProxyAspect;

import java.lang.reflect.Modifier;
import java.util.List;

import static jodd.asm7.Opcodes.ACC_ABSTRACT;
import static jodd.asm7.Opcodes.ACC_NATIVE;
import static jodd.asm7.Opcodes.ALOAD;
import static jodd.asm7.Opcodes.GETFIELD;
import static jodd.asm7.Opcodes.INVOKEINTERFACE;
import static jodd.asm7.Opcodes.INVOKEVIRTUAL;
import static jodd.proxetta.asm.ProxettaAsmUtil.CLINIT;
import static jodd.proxetta.asm.ProxettaAsmUtil.INIT;
import static jodd.proxetta.asm.ProxettaAsmUtil.loadVirtualMethodArguments;
import static jodd.proxetta.asm.ProxettaAsmUtil.visitReturn;

public class ProxettaWrapperClassBuilder extends ProxettaClassBuilder {

	protected final Class targetClassOrInterface;
	protected final Class targetInterface;
	protected final String targetFieldName;
	protected final boolean createTargetInDefaultCtor;

	public ProxettaWrapperClassBuilder(
		final Class targetClassOrInterface,
		final Class targetInterface,
		final String targetFieldName,
		final ClassVisitor dest,
		final ProxyAspect[] aspects,
		final String suffix,
		final String reqProxyClassName,
		final TargetClassInfoReader targetClassInfoReader,
		final boolean createTargetInDefaultCtor
		) {

		super(dest, aspects, suffix, reqProxyClassName, targetClassInfoReader);
		this.targetClassOrInterface = targetClassOrInterface;
		this.targetInterface = targetInterface;
		this.targetFieldName = targetFieldName;
		this.createTargetInDefaultCtor = createTargetInDefaultCtor;

		wd.allowFinalMethods = true;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(final int version, int access, final String name, final String signature, final String superName, String[] interfaces) {

		wd.init(name, superName, this.suffix, this.reqProxyClassName);

		// no superclass
		wd.superName = AsmUtil.SIGNATURE_JAVA_LANG_OBJECT;

		// change access of destination
		access &= ~AsmUtil.ACC_ABSTRACT;
		access &= ~AsmUtil.ACC_INTERFACE;

		// write destination class
		if (targetClassOrInterface.isInterface()) {
			// target is interface
			wd.wrapInterface = true;

			interfaces = new String[] {targetClassOrInterface.getName().replace('.', '/')};
		} else {
			// target is class
			wd.wrapInterface = false;

			if (targetInterface != null) {
				// interface provided
				interfaces = new String[] {targetInterface.getName().replace('.', '/')};
			} else {
				// no interface provided, use all
				//interfaces = null;
			}
		}
		final int v = ProxettaAsmUtil.resolveJavaVersion(version);
		wd.dest.visit(v, access, wd.thisReference, signature, wd.superName, interfaces);

		wd.proxyAspects = new ProxyAspectData[aspects.length];
		for (int i = 0; i < aspects.length; i++) {
			wd.proxyAspects[i] = new ProxyAspectData(wd, aspects[i], i);
		}

		// create new field wrapper field and store it's reference into work-data
		wd.wrapperRef = targetFieldName;
		wd.wrapperType = 'L' + name + ';';

		if (createTargetInDefaultCtor) {
			// create private, final field
			final FieldVisitor fv = wd.dest.visitField(AsmUtil.ACC_PRIVATE | AsmUtil.ACC_FINAL, wd.wrapperRef, wd.wrapperType, null, null);
			fv.visitEnd();

			createEmptyCtorThatCreatesTarget();
		}
		else {
			// create public, non-final field
			final FieldVisitor fv = wd.dest.visitField(AsmUtil.ACC_PUBLIC, wd.wrapperRef, wd.wrapperType, null, null);
			fv.visitEnd();

			createEmptyCtor();
		}
	}

	/**
	 * Created empty default constructor.
	 */
	protected void createEmptyCtor() {
		final MethodVisitor mv = wd.dest.visitMethod(AsmUtil.ACC_PUBLIC, INIT, "()V", null, null);
		mv.visitCode();
		mv.visitVarInsn(Opcodes.ALOAD, 0);
		mv.visitMethodInsn(
			Opcodes.INVOKESPECIAL,
			AsmUtil.SIGNATURE_JAVA_LANG_OBJECT,
			INIT, "()V",
			false);
		mv.visitInsn(Opcodes.RETURN);
		mv.visitMaxs(1, 1);
		mv.visitEnd();
	}

	protected void createEmptyCtorThatCreatesTarget() {
		final MethodVisitor mv = wd.dest.visitMethod(AsmUtil.ACC_PUBLIC, "<init>", "()V", null, null);
		mv.visitCode();
		mv.visitVarInsn(ALOAD, 0);
		mv.visitMethodInsn(
			Opcodes.INVOKESPECIAL,
			AsmUtil.SIGNATURE_JAVA_LANG_OBJECT,
			INIT, "()V",
			false);
		mv.visitVarInsn(ALOAD, 0);

		mv.visitTypeInsn(Opcodes.NEW, wd.superReference);
		mv.visitInsn(Opcodes.DUP);
		mv.visitMethodInsn(
			Opcodes.INVOKESPECIAL,
			wd.superReference,
			INIT, "()V",
			false);

		mv.visitFieldInsn(Opcodes.PUTFIELD,
			wd.thisReference,
			wd.wrapperRef,
			wd.wrapperType);

		mv.visitInsn(Opcodes.RETURN);
		mv.visitMaxs(3, 1);
		mv.visitEnd();
	}

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

		// ignore all destination constructors
		if (name.equals(INIT)) {
			return null;
		}
		// ignore all destination static block
		if (name.equals(CLINIT)) {
			return null;
		}

		// skip all static methods
		if (Modifier.isStatic(access)) {
			return null;
		}

		return applyProxy(msign);
	}

	@Override
	protected ProxettaMethodBuilder applyProxy(final MethodSignatureVisitor msign) {
		List<ProxyAspectData> aspectList = matchMethodPointcuts(msign);

		if (aspectList == null) {
			wd.proxyApplied = true;
			createSimpleMethodWrapper(msign);
			return null;
		}

		wd.proxyApplied = true;
		return new ProxettaMethodBuilder(msign, wd, aspectList);

	}

	/**
	 * Creates simple method wrapper without proxy.
	 */
	protected void createSimpleMethodWrapper(final MethodSignatureVisitor msign) {

		int access = msign.getAccessFlags();

		access &= ~ACC_ABSTRACT;
		access &= ~ACC_NATIVE;

		MethodVisitor mv = wd.dest.visitMethod(
				access, msign.getMethodName(), msign.getDescription(), msign.getAsmMethodSignature(), msign.getExceptions());
		mv.visitCode();
		mv.visitVarInsn(ALOAD, 0);
		mv.visitFieldInsn(GETFIELD, wd.thisReference, wd.wrapperRef, wd.wrapperType);
		loadVirtualMethodArguments(mv, msign);

		if (wd.wrapInterface) {
			mv.visitMethodInsn(
				INVOKEINTERFACE,
				wd.wrapperType.substring(1, wd.wrapperType.length() - 1),
				msign.getMethodName(),
				msign.getDescription(),
				true);
		} else {
			mv.visitMethodInsn(
				INVOKEVIRTUAL,
				wd.wrapperType.substring(1, wd.wrapperType.length() - 1),
				msign.getMethodName(),
				msign.getDescription(),
				false);
		}

		ProxettaAsmUtil.prepareReturnValue(mv, msign, 0);
		visitReturn(mv, msign, true);
		mv.visitMaxs(0, 0);
		mv.visitEnd();
	}

	@Override
	public void visitEnd() {
		makeStaticInitBlock();

		processSuperMethods();

		wd.dest.visitEnd();

	}
}


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

package jodd.proxetta.asm;

import jodd.asm.AsmUtil;
import jodd.asm.TraceSignatureVisitor;
import jodd.asm7.Opcodes;
import jodd.asm7.signature.SignatureVisitor;
import jodd.buffer.FastIntBuffer;
import jodd.proxetta.AnnotationInfo;
import jodd.proxetta.ClassInfo;
import jodd.proxetta.GenericsReader;
import jodd.proxetta.MethodInfo;
import jodd.proxetta.ProxettaException;
import jodd.proxetta.TypeInfo;
import jodd.util.StringPool;
import jodd.util.StringUtil;

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

import static jodd.proxetta.asm.AnnotationReader.NO_ANNOTATIONS;

/**
 * Resolves method signature and holds all information. Uses {@link jodd.asm.TraceSignatureVisitor} from ASM library.
 * <pre>
 * MethodSignature = ( visitFormalTypeParameter visitClassBound? visitInterfaceBound* )* ( visitParameterType* visitReturnType visitExceptionType* )
 * </pre>
 */
public class MethodSignatureVisitor extends TraceSignatureVisitor implements MethodInfo {

	protected final String classname;
	protected final String methodName;
	protected final String[] exceptionsArray;
	protected final boolean isStatic;
	protected final boolean isFinal;
	protected final ClassInfo targetClassInfo;
	protected final FastIntBuffer argumentsOffset;
	protected final List<TypeInfoImpl> arguments;
	protected final int access;
	protected final String description;

	protected TypeInfo returnType;
	protected String signature;
	protected int argumentsCount;
	protected int argumentsWords;
	protected String asmMethodSignature;
	protected AnnotationInfo[] annotations;
	protected String declaredClassName;
	protected final Map<String, String> generics;
	protected final Map<String, String> declaredTypeGeneric;

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

	public MethodSignatureVisitor(
			final String methodName,
			final int access,
			final String classname,
			final String description,
			final String[] exceptions,
			final String signature,
			final Map<String, String> declaredTypeGenerics,
			final ClassInfo targetClassInfo) {

		super(new StringBuilder(), (access & Opcodes.ACC_INTERFACE) != 0);
//		this.isInterface = (access & Opcodes.ACC_INTERFACE) != 0;
		this.isStatic = (access & Opcodes.ACC_STATIC) != 0;
		this.isFinal = (access & Opcodes.ACC_FINAL) != 0;
		this.methodName = methodName;
		this.access = access;
		this.classname = classname;
		this.description = description;
		this.targetClassInfo = targetClassInfo;
		this.asmMethodSignature = signature;
		this.generics = new GenericsReader().parseSignatureForGenerics(signature, isInterface);
		this.exceptionsArray = exceptions;
		this.declaredTypeGeneric = declaredTypeGenerics;

		this.arguments = new ArrayList<>();
		this.arguments.add(new TypeInfoImpl('L', null, null, null));

		this.argumentsOffset = new FastIntBuffer();
		this.argumentsOffset.append(0);

		this.annotations = NO_ANNOTATIONS;
	}

	// ---------------------------------------------------------------- method-info signature

	@Override
	public String getSignature() {
		if (signature == null) {
			String decl = getDeclaration();

			int ndx = decl.indexOf(')');
			ndx++;
			String retType = decl.substring(ndx);

			StringBuilder methodDeclaration = new StringBuilder(50);
			methodDeclaration.append(retType).append(' ').append(methodName).append(decl, 0, ndx);

			String exceptionsAsString = getExceptionsAsString();
			if (exceptionsAsString != null) {
				methodDeclaration.append(" throws ").append(exceptionsAsString);
			}

			signature = methodDeclaration.toString();
		}
		return signature;
	}

	@Override
	public String getCleanSignature() {
		return methodName + '#' + getDescription();
	}

	public String getAsmMethodSignature() {
		return asmMethodSignature;
	}

	@Override
	public String getMethodName() {
		return methodName;
	}

	@Override
	public int getArgumentsCount() {
		return argumentsCount;
	}

	@Override
	public TypeInfoImpl getArgument(final int ndx) {
		return arguments.get(ndx);
	}

	@Override
	public int getArgumentOffset(final int index) {
		return argumentsOffset.get(index);
	}

	@Override
	public int getAllArgumentsSize() {
		return argumentsWords;
	}

	@Override
	public TypeInfo getReturnType() {
		return returnType;
	}

	@Override
	public int getAccessFlags() {
		return access;
	}

	@Override
	public String getClassname() {
		return classname;
	}

	@Override
	public String getDescription() {
		return description;
	}

	@Override
	public AnnotationInfo[] getAnnotations() {
		return annotations;
	}

	@Override
	public String getDeclaredClassName() {
		if (declaredClassName == null) {
			return classname;
		}
		return declaredClassName;
	}

	public void setDeclaredClassName(final String declaredClassName) {
		this.declaredClassName = declaredClassName;
	}

	@Override
	public boolean isTopLevelMethod() {
		return declaredClassName == null;
	}

	@Override
	public ClassInfo getClassInfo() {
		return targetClassInfo;
	}

	@Override
	public String[] getExceptions() {
		return exceptionsArray;
	}

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

	private boolean visitingArgument;
	private boolean visitingReturnType;
	private boolean visitingArray;
	private int declarationTypeOffset;

	@Override
	public SignatureVisitor visitParameterType() {
		super.visitParameterType();

		visitingArgument = true;

		return this;
	}

	@Override
	public SignatureVisitor visitReturnType() {
		super.visitReturnType();

		visitingReturnType = true;

		return this;
	}

	@Override
	public SignatureVisitor visitArrayType() {
		visitingArray = true;
		return super.visitArrayType();
	}

	@Override
	public void visitBaseType(final char descriptor) {
		if (isTopLevelType()) {
			// mark type start
			declarationTypeOffset = declaration.length();
		}

		super.visitBaseType(descriptor);
	}

	@Override
	public void visitClassType(final String name) {
		if (isTopLevelType()) {
			// mark type start
			declarationTypeOffset = declaration.length();
		}

		super.visitClassType(name);
	}

	@Override
	protected void startType() {
		super.startType();

		if (isTopLevelType()) {
			// mark type start
			declarationTypeOffset = declaration.length();
		}
	}

	@Override
	protected void endType() {
		super.endType();

		String type = declaration.subSequence(declarationTypeOffset, declaration.length()).toString();

		maybeUseType(type);
	}

	private void maybeUseType(final String typeName) {
		if (!isTopLevelType()) {
			return;
		}

		char type;
		String bytecodeName;

		if (visitingArray) {
			type = '[';

			int arrayCount = StringUtil.count(typeName, '[');
			String arrayDepth = StringUtil.repeat('[', arrayCount);

			int ndx = typeName.indexOf('[');
			bytecodeName = typeName.substring(0, ndx);

			char arrayType = AsmUtil.typeNameToOpcode(bytecodeName);
			if (arrayType != 'L') {
				bytecodeName = String.valueOf(arrayType);
			}
			else {
				bytecodeName = resolveBytecodeName(bytecodeName);
			}

			bytecodeName = arrayDepth  + bytecodeName;
		}
		else {
			type = AsmUtil.typeNameToOpcode(typeName);

			if (type != 'L') {
				bytecodeName = String.valueOf(type);
			}
			else {
				bytecodeName = resolveBytecodeName(typeName);
			}
		}

		final TypeInfoImpl typeInfo = new TypeInfoImpl(
			type,
			typeName,
			bytecodeName,
			resolveRawTypeName(bytecodeName));

		if (visitingArgument) {
			if (type == 'V') {
				throw new ProxettaException("Method argument can't be void");
			}

			arguments.add(typeInfo);

			argumentsCount++;
			argumentsOffset.append(argumentsWords + 1);

			if ((type == 'D') || (type == 'J')) {
				argumentsWords += 2;
			} else {
				argumentsWords++;
			}
		}
		else if (visitingReturnType) {
			returnType = typeInfo;
		}

		visitingReturnType = false;
		visitingArgument = false;
		visitingArray = false;
	}

	/**
	 * Returns {@code true} if we are scanning the top-level type and not
	 * the inner ones, like generics.
	 */
	private boolean isTopLevelType() {
		return argumentStack == 0;
	}

	private String resolveBytecodeName(String typeName) {
		int ndx = 0;
		int genericsStartNdx = -1;
		int bracketCount = 0;

		while (ndx < typeName.length()) {
			final char c = typeName.charAt(ndx);

			if (c == '<') {
				if (bracketCount == 0) {
					genericsStartNdx = ndx;
				}
				bracketCount++;
				ndx++;
				continue;
			}

			if (c == '>') {
				bracketCount--;
				if (bracketCount == 0) {
					break;
				}
			}

			ndx++;
		}

		if (genericsStartNdx != -1) {
			typeName = typeName.substring(0, genericsStartNdx) + typeName.substring(ndx + 1);
		}

		if (isGenericType(typeName)) {
			return typeName;
		}

		return 'L' + typeName.replace('.', '/') + ';';
	}

	/**
	 * Resolves raw type name using the generics information from the class
	 * or method information.
	 */
	private String resolveRawTypeName(String typeName) {
		if (typeName == null) {
			return null;
		}

		boolean isArray = typeName.startsWith(StringPool.LEFT_SQ_BRACKET);
		if (isArray) {
			typeName = typeName.substring(1);
		}

		String rawTypeName;

		if (generics.containsKey(typeName)) {
			rawTypeName = generics.get(typeName);
		}
		else {
			rawTypeName = declaredTypeGeneric.getOrDefault(typeName, typeName);
		}

		if (isArray) {
			rawTypeName = '[' + rawTypeName;
		}

		return rawTypeName;
	}

	private boolean isGenericType(final String typeName) {
		if (generics.containsKey(typeName)) {
			return true;
		}
		return declaredTypeGeneric.containsKey(typeName);
	}

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

	@Override
	public String toString() {
		return getDeclaredClassName() + '#' + getMethodName() + getDescription();
	}

}


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

import jodd.introspector.FieldDescriptor;
import jodd.introspector.MethodDescriptor;
import jodd.introspector.PropertyDescriptor;
import jodd.paramo.MethodParameter;
import jodd.paramo.Paramo;
import jodd.petite.PetiteConfig;
import jodd.petite.PetiteException;
import jodd.petite.PetiteReferenceType;
import jodd.petite.def.BeanReferences;
import jodd.petite.meta.PetiteInject;
import jodd.typeconverter.Converter;
import jodd.util.StringUtil;

import java.lang.reflect.Executable;
import java.lang.reflect.Parameter;

/**
 * Annotation reader for methods and constructors.
 */
public class ReferencesResolver {

	private final PetiteConfig petiteConfig;

	public ReferencesResolver(final PetiteConfig petiteConfig) {
		this.petiteConfig = petiteConfig;
	}

	/**
	 * Resolves reference from given values. Returns bean reference of given value or defaults
	 * if given name is blank.
	 */
	public BeanReferences resolveReferenceFromValue(final PropertyDescriptor propertyDescriptor, final String refName) {
		BeanReferences references;

		if (refName == null || refName.isEmpty()) {
			references = buildDefaultReference(propertyDescriptor);
		}
		else {
			references = BeanReferences.of(refName);
		}

		references = references.removeDuplicateNames();

		return references;
	}

	/**
	 * Takes given parameters references and returns reference set for given method or constructor.
	 */
	public BeanReferences[] resolveReferenceFromValues(final Executable methodOrCtor, final String... parameterReferences) {
		BeanReferences[] references = convertRefToReferences(parameterReferences);

		if (references == null || references.length == 0) {
			references = buildDefaultReferences(methodOrCtor);
		}

		if (methodOrCtor.getParameterTypes().length != references.length) {
			throw new PetiteException("Different number of method parameters and references for: " +
				methodOrCtor.getDeclaringClass().getName() + '#' + methodOrCtor.getName());
		}

		removeAllDuplicateNames(references);

		return references;
	}

	/**
	 * Extracts references for given property. Returns {@code null} if property is not marked with an
	 * annotation.
	 */
	public BeanReferences readReferenceFromAnnotation(final PropertyDescriptor propertyDescriptor) {
		final MethodDescriptor writeMethodDescriptor = propertyDescriptor.getWriteMethodDescriptor();
		final FieldDescriptor fieldDescriptor = propertyDescriptor.getFieldDescriptor();

		PetiteInject ref = null;
		if (writeMethodDescriptor != null) {
			ref = writeMethodDescriptor.getMethod().getAnnotation(PetiteInject.class);
		}
		if (ref == null && fieldDescriptor != null) {
			ref = fieldDescriptor.getField().getAnnotation(PetiteInject.class);
		}

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

		BeanReferences reference = null;

		String name = ref.value().trim();
		if (name.length() != 0) {
			reference = BeanReferences.of(name);
		}

		reference = updateReferencesWithDefaultsIfNeeded(propertyDescriptor, reference);

		reference = reference.removeDuplicateNames();

		return reference;
	}

	public BeanReferences readReferenceFromAnnotation(final FieldDescriptor fieldDescriptor) {
		final PetiteInject ref = fieldDescriptor.getField().getAnnotation(PetiteInject.class);

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

		BeanReferences reference = null;

		String name = ref.value().trim();
		if (name.length() != 0) {
			reference = BeanReferences.of(name);
		}

		//reference = updateReferencesWithDefaultsIfNeeded(propertyDescriptor, reference);

		reference = reference.removeDuplicateNames();

		return reference;
	}

	/**
	 * Extracts references from method or constructor annotation.
	 */
	public BeanReferences[] readAllReferencesFromAnnotation(final Executable methodOrCtor) {
		PetiteInject petiteInject = methodOrCtor.getAnnotation(PetiteInject.class);

		final Parameter[] parameters = methodOrCtor.getParameters();

		BeanReferences[] references;

		final boolean hasAnnotationOnMethodOrCtor;

		if (petiteInject != null) {
			references = convertAnnValueToReferences(petiteInject.value());

			hasAnnotationOnMethodOrCtor = true;
		}
		else {
			references = new BeanReferences[parameters.length];

			hasAnnotationOnMethodOrCtor = false;
		}

		int parametersWithAnnotationCount = 0;

		for (int i = 0; i < parameters.length; i++) {
			Parameter parameter = parameters[i];

			petiteInject = parameter.getAnnotation(PetiteInject.class);

			if (petiteInject == null) {
				// no annotation on argument
				continue;
			}

			// there is annotation on argument, override values
			String annotationValue = readAnnotationValue(petiteInject);

			if (annotationValue != null) {
				references[i] = BeanReferences.of(annotationValue);
			}

			parametersWithAnnotationCount++;
		}

		if (!hasAnnotationOnMethodOrCtor) {
			if (parametersWithAnnotationCount == 0) {
				return null;
			}
			if (parametersWithAnnotationCount != parameters.length) {
				throw new PetiteException("All arguments must be annotated with PetiteInject");
			}
		}

		references = updateReferencesWithDefaultsIfNeeded(methodOrCtor, references);

		removeAllDuplicateNames(references);

		return references;
	}

	/**
	 * Reads annotation value and returns {@code null} if value is empty.
	 */
	private String readAnnotationValue(final PetiteInject annotation) {
		String value = annotation.value().trim();

		if (value.isEmpty()) {
			return null;
		}
		return value;
	}

	private BeanReferences[] updateReferencesWithDefaultsIfNeeded(final Executable methodOrCtor, BeanReferences[] references) {
		BeanReferences[] defaultReferences = buildDefaultReferences(methodOrCtor);

		if (references == null || references.length == 0) {
			references = defaultReferences;
		}

		if (methodOrCtor.getParameterTypes().length != references.length) {
			throw new PetiteException(
				"Different number of parameters and references for: " + methodOrCtor.getName());
		}

		// apply default parameters
		for (int i = 0; i < references.length; i++) {
			BeanReferences parameterReferences = references[i];

			if (parameterReferenceIsNotSet(parameterReferences)) {
				references[i] = defaultReferences[i];
			}
		}

		return references;
	}

	private BeanReferences updateReferencesWithDefaultsIfNeeded(final PropertyDescriptor propertyDescriptor, BeanReferences references) {
		if (references == null || references.isEmpty()) {
			references = buildDefaultReference(propertyDescriptor);
		}

		return references;
	}

	/**
	 * Returns {@code true} if given parameter references is not set.
	 */
	private boolean parameterReferenceIsNotSet(final BeanReferences parameterReferences) {
		if (parameterReferences == null) {
			return true;
		}
		return parameterReferences.isEmpty();
	}

	/**
	 * Builds default method references.
	 */
	private BeanReferences[] buildDefaultReferences(final Executable methodOrCtor) {
		final boolean useParamo = petiteConfig.getUseParamo();
		final PetiteReferenceType[] lookupReferences = petiteConfig.getLookupReferences();
		MethodParameter[] methodParameters = null;

		if (useParamo) {
			methodParameters = Paramo.resolveParameters(methodOrCtor);
		}

		final Class[] paramTypes = methodOrCtor.getParameterTypes();
		final BeanReferences[] references = new BeanReferences[paramTypes.length];

		for (int j = 0; j < paramTypes.length; j++) {
			String[] ref = new String[lookupReferences.length];
			references[j] = BeanReferences.of(ref);

			for (int i = 0; i < ref.length; i++) {
				switch (lookupReferences[i]) {
					case NAME:
						ref[i] = methodParameters != null ? methodParameters[j].getName() : null;
						break;
					case TYPE_SHORT_NAME:
						ref[i] = StringUtil.uncapitalize(paramTypes[j].getSimpleName());
						break;
					case TYPE_FULL_NAME:
						ref[i] = paramTypes[j].getName();
						break;
				}
			}
		}

		return references;
	}

	/**
	 * Builds default field references.
	 */
	public BeanReferences buildDefaultReference(final PropertyDescriptor propertyDescriptor) {
		final PetiteReferenceType[] lookupReferences = petiteConfig.getLookupReferences();

		final String[] references = new String[lookupReferences.length];

		for (int i = 0; i < references.length; i++) {
			switch (lookupReferences[i]) {
				case NAME:
					references[i] = propertyDescriptor.getName();
					break;
				case TYPE_SHORT_NAME:
					references[i] = StringUtil.uncapitalize(propertyDescriptor.getType().getSimpleName());
					break;
				case TYPE_FULL_NAME:
					references[i] = propertyDescriptor.getType().getName();
					break;
			}
		}

		return BeanReferences.of(references);
	}

	/**
	 * Removes duplicate names from bean references.
	 */
	private void removeAllDuplicateNames(final BeanReferences[] allBeanReferences) {
		for (int i = 0; i < allBeanReferences.length; i++) {
			BeanReferences references = allBeanReferences[i];
			allBeanReferences[i] = references.removeDuplicateNames();
		}
	}

	/**
	 * Converts single string array to an array of bean references.
	 */
	private BeanReferences[] convertRefToReferences(final String[] references) {
		if (references == null) {
			return null;
		}
		BeanReferences[] ref = new BeanReferences[references.length];

		for (int i = 0; i < references.length; i++) {
			ref[i] = BeanReferences.of(references[i]);
		}
		return ref;
	}

	/**
	 * Converts comma-separated string into array of Bean references.
	 */
	private BeanReferences[] convertAnnValueToReferences(String value) {
		if (value == null) {
			return null;
		}

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

		String[] refNames = Converter.get().toStringArray(value);

		BeanReferences[] references = new BeanReferences[refNames.length];
		for (int i = 0; i < refNames.length; i++) {
			references[i] = BeanReferences.of(refNames[i].trim());
		}
		return references;
	}

}


// 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.jtx.JtxPropagationBehavior;
import jodd.jtx.JtxTransaction;
import jodd.jtx.JtxTransactionManager;
import jodd.jtx.JtxTransactionMode;

import java.util.function.Consumer;

/**
 * Standalone runner for Joy web application.
 */
public class StandaloneJoddJoy {

	/**
	 * Runs JOY in standalone mode, with only backend.
	 */
	public void runJoy(final Consumer<JoddJoyRuntime> consumer) {
		final JoddJoy joddJoy = new JoddJoy();

		final JoddJoyRuntime joyRuntime = joddJoy.startOnlyBackend();

		joddJoy.withDb(joyDb -> setJtxManager(joyRuntime.getJtxManager()));

		final JtxTransaction tx = startRwTx();
		final Print print = new Print();
		try {
			print.line("START", 80);
			print.newLine();

			consumer.accept(joyRuntime);

			print.newLine();
			print.line("END", 80);

			if (tx != null) {
				tx.commit();
			}
		} catch (Throwable throwable) {
			throwable.printStackTrace();
			if (tx != null) {
				tx.rollback();
			}
		}

		joddJoy.stop();
	}

	// ---------------------------------------------------------------- jtx

	protected JtxTransactionManager jtxManager;

	/**
	 * Sets transaction manager.
	 */
	private void setJtxManager(final JtxTransactionManager jm) {
		jtxManager = jm;
	}

	/**
	 * Starts new read/write transaction in PROPAGATION_REQUIRED mode.
	 */
	private JtxTransaction startRwTx() {
		if (jtxManager == null) {
			return null;
		}
		return jtxManager.requestTransaction(new JtxTransactionMode(JtxPropagationBehavior.PROPAGATION_REQUIRED, false));
	}

}