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

import jodd.io.FileNameUtil;
import jodd.log.Logger;
import jodd.log.LoggerFactory;
import jodd.servlet.DispatcherUtil;
import jodd.servlet.wrapper.BufferResponseWrapper;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Lagarto servlet filter takes HTML content and invokes user defined parser on it.
 * This filter is a generic one and does not use Lagarto parser explicitly.
 * It just gives a placeholder where user can add it's own parsing mechanism.
 */
public abstract class LagartoServletFilter implements Filter {

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

	@Override
	public void init(final FilterConfig filterConfig) throws ServletException {
	}

	@Override
	public void destroy() {
	}

	/**
	 * Wraps the response and parse it using Lagarto parser.
	 * It first calls {@link #processActionPath(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, String)}
	 * to optionally consumes path, then {@link #acceptActionPath(javax.servlet.http.HttpServletRequest, String)} to
	 * check if path is accepted for processing.
	 */
	@Override
	public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;
		String actionPath = DispatcherUtil.getServletPath(request);

		if (processActionPath(request, response, actionPath)) {
			return;
		}

		if (!acceptActionPath(request, actionPath)) {
			filterChain.doFilter(servletRequest, servletResponse);
			return;
		}

		BufferResponseWrapper wrapper = new BufferResponseWrapper(response);
		filterChain.doFilter(servletRequest, wrapper);

		// reset servlet response content length AFTER the chain, since
		// servlet container may set it and we are changing the content.
		servletResponse.setContentLength(-1);

		char[] content = wrapper.getBufferContentAsChars();

		if ((content != null) && (content.length != 0)) {
			if (log.isDebugEnabled()) {
				log.debug("Lagarto is about to parse: " + actionPath);
			}
			try {
				content = parse(content, request);
			} catch (Exception ex) {
				log.error("Error parsing", ex);
				throw new ServletException(ex);
			}

			wrapper.writeContentToResponse(content);
		}
	}

	/**
	 * Manually process the action path and returns true if path is consumed.
	 * When path is consumed, filter chain is not continued.
	 * By default, it returns false.
	 */
	protected boolean processActionPath(
		final HttpServletRequest servletRequest,
		final HttpServletResponse servletResponse,
		final String actionPath) throws IOException {

		return false;
	}

	/**
	 * Accepts action path for further parsing. By default, only *.htm(l)
	 * requests are passed through and those without any extension.
	 */
	protected boolean acceptActionPath(final HttpServletRequest request, final String actionPath) {
		String extension = FileNameUtil.getExtension(actionPath);
		if (extension.length() == 0) {
			return true;
		}
		if (extension.equals("html") || extension.equals("htm")) {
			return true;
		}
		return false;
	}

	/**
	 * Main method that parses content. It can use Lagarto parser
	 * or any other custom parsing technology.
	 */
	protected abstract char[] parse(char[] content, HttpServletRequest request);

}


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

import jodd.madvoc.ActionRequest;
import jodd.madvoc.component.ResultMapper;
import jodd.madvoc.meta.In;
import jodd.madvoc.meta.scope.MadvocContext;
import jodd.util.StringPool;

/**
 * Process chain results. Chaining is very similar to forwarding, except it is done
 * by {@link jodd.madvoc.MadvocServletFilter} and not by container. Chaining to next action request
 * happens after the complete execution of current one: after all interceptors and this result has been
 * finished.
 */
public class ChainActionResult implements ActionResult {

	@In @MadvocContext
	protected ResultMapper resultMapper;

	/**
	 * Sets the {@link jodd.madvoc.ActionRequest#setNextActionPath(String) next action request} for the chain.
	 */
	@Override
	public void render(final ActionRequest actionRequest, final Object resultValue) {
		final Chain chainResult;

		if (resultValue == null) {
			chainResult = Chain.to(StringPool.EMPTY);
		} else {
			if (resultValue instanceof String) {
				chainResult = Chain.to((String)resultValue);
			}
			else {
				chainResult = (Chain) resultValue;
			}
		}

		final String resultBasePath = actionRequest.getActionRuntime().getResultBasePath();

		final String resultPath = resultMapper.resolveResultPathString(resultBasePath, chainResult.path());

		actionRequest.setNextActionPath(resultPath);
	}

}


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

import jodd.madvoc.ActionRequest;
import jodd.madvoc.component.FileUploader;
import jodd.madvoc.component.MadvocEncoding;
import jodd.madvoc.component.ScopeResolver;
import jodd.madvoc.config.Targets;
import jodd.madvoc.meta.In;
import jodd.madvoc.meta.scope.MadvocContext;
import jodd.servlet.ServletUtil;
import jodd.servlet.upload.MultipartRequestWrapper;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;

/**
 * Configures actions and applies some servlet configuration prior action execution.
 * This interceptor does the following:
 *

 *
uses multi-part request if needed

 *
performs the injection (using either default or specified injector)

 *
invokes the action

 *
performs the outjection.

 *

 */
public class ServletConfigInterceptor implements ActionInterceptor {

	@In @MadvocContext
	protected ScopeResolver scopeResolver;

	@In @MadvocContext
	protected MadvocEncoding madvocEncoding;

	@In @MadvocContext
	protected FileUploader fileUploader;

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Object intercept(final ActionRequest actionRequest) throws Exception {
		HttpServletRequest servletRequest = actionRequest.getHttpServletRequest();

		// detect multipart request
		if (ServletUtil.isMultipartRequest(servletRequest)) {
			servletRequest = new MultipartRequestWrapper(servletRequest, fileUploader.get(), madvocEncoding.getEncoding());
			actionRequest.bind(servletRequest);
		}

		// do it
		inject(actionRequest);

		final Object result = actionRequest.invoke();

		outject(actionRequest);

		return result;
	}

	/**
	 * Performs injection.
	 */
	protected void inject(final ActionRequest actionRequest) {
		final Targets targets = actionRequest.getTargets();
		final ServletContext servletContext = actionRequest.getHttpServletRequest().getServletContext();

		scopeResolver.forEachScope(madvocScope -> madvocScope.inject(servletContext, targets));
		scopeResolver.forEachScope(madvocScope -> madvocScope.inject(actionRequest, targets));

	}

	/**
	 * Performs outjection.
	 */
	protected void outject(final ActionRequest actionRequest) {
		final Targets targets = actionRequest.getTargets();

		scopeResolver.forEachScope(madvocScope -> madvocScope.outject(actionRequest, targets));
	}

}


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

package jodd.http;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;

/**
 * Sockets factory.
 */
public class Sockets {

	/**
	 * Creates a socket.
	 */
	public static Socket connect(final String hostname, final int port) throws IOException {
		final Socket socket = new Socket();
		socket.connect(new InetSocketAddress(hostname, port));
		return socket;
	}

	/**
	 * Creates a socket with a timeout.
	 */
	public static Socket connect(final String hostname, final int port, final int connectionTimeout) throws IOException {
		final Socket socket = new Socket();
		if (connectionTimeout <= 0) {
			socket.connect(new InetSocketAddress(hostname, port));
		}
		else {
			socket.connect(new InetSocketAddress(hostname, port), connectionTimeout);
		}
		return socket;
	}
}


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

import jodd.util.ClassUtil;
import jodd.util.StringUtil;

import java.lang.reflect.Array;
import java.util.Arrays;

/**
 * Default Jodd class loader strategy.
 * Loads a class with a given name dynamically, more reliable then Class.forName.
 *

 * Class will be loaded using class loaders in the following order:
 *

 *
provided class loader (if any)

 *
Thread.currentThread().getContextClassLoader()}

 *
caller classloader

 *

 */
public class DefaultClassLoaderStrategy implements ClassLoaderStrategy {

	/**
	 * List of primitive type names.
	 */
	public static final String[] PRIMITIVE_TYPE_NAMES = new String[] {
			"boolean", "byte", "char", "double", "float", "int", "long", "short",
	};
	/**
	 * List of primitive types that matches names list.
	 */
	public static final Class[] PRIMITIVE_TYPES = new Class[] {
			boolean.class, byte.class, char.class, double.class, float.class, int.class, long.class, short.class,
	};
	/**
	 * List of primitive bytecode characters that matches names list.
	 */
	public static final char[] PRIMITIVE_BYTECODE_NAME = new char[] {
			'Z', 'B', 'C', 'D', 'F', 'I', 'J', 'S'
	};

	// ---------------------------------------------------------------- flags

	protected boolean loadArrayClassByComponentTypes = false;

	/**
	 * Returns arrays class loading strategy.
	 */
	public boolean isLoadArrayClassByComponentTypes() {
		return loadArrayClassByComponentTypes;
	}

	/**
	 * Defines arrays class loading strategy.
	 * If false (default), classes will be loaded by Class.forName.
	 * If true, classes will be loaded by reflection and component types.
	 */
	public void setLoadArrayClassByComponentTypes(final boolean loadArrayClassByComponentTypes) {
		this.loadArrayClassByComponentTypes = loadArrayClassByComponentTypes;
	}

	// ---------------------------------------------------------------- names

	/**
	 * Prepares classname for loading, respecting the arrays.
	 * Returns null if class name is not an array.
	 */
	public static String prepareArrayClassnameForLoading(String className) {
		int bracketCount = StringUtil.count(className, '[');

		if (bracketCount == 0) {
			// not an array
			return null;
		}

		String brackets = StringUtil.repeat('[', bracketCount);

		int bracketIndex = className.indexOf('[');
		className = className.substring(0, bracketIndex);

		int primitiveNdx = getPrimitiveClassNameIndex(className);
		if (primitiveNdx >= 0) {
			className = String.valueOf(PRIMITIVE_BYTECODE_NAME[primitiveNdx]);

			return brackets + className;
		} else {
			return brackets + 'L' + className + ';';
		}
	}

	/**
	 * Detects if provided class name is a primitive type.
	 * Returns >= 0 number if so.
	 */
	private static int getPrimitiveClassNameIndex(final String className) {
		int dotIndex = className.indexOf('.');
		if (dotIndex != -1) {
			return -1;
		}
		return Arrays.binarySearch(PRIMITIVE_TYPE_NAMES, className);
	}

	// ---------------------------------------------------------------- load

	/**
	 * Loads class by name.
	 */
	@Override
	public Class loadClass(final String className, final ClassLoader classLoader) throws ClassNotFoundException {
		String arrayClassName = prepareArrayClassnameForLoading(className);

		if ((className.indexOf('.') == -1) && (arrayClassName == null)) {
			// maybe a primitive
			int primitiveNdx = getPrimitiveClassNameIndex(className);
			if (primitiveNdx >= 0) {
				return PRIMITIVE_TYPES[primitiveNdx];
			}
		}

		// try #1 - using provided class loader
		if (classLoader != null) {
			Class klass = loadClass(className, arrayClassName, classLoader);

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

		// try #2 - using thread class loader
		ClassLoader currentThreadClassLoader = Thread.currentThread().getContextClassLoader();

		if ((currentThreadClassLoader != null) && (currentThreadClassLoader != classLoader)) {
			Class klass = loadClass(className, arrayClassName, currentThreadClassLoader);

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

		// try #3 - using caller classloader, similar as Class.forName()
		//Class callerClass = ReflectUtil.getCallerClass(2);
		Class callerClass = ClassUtil.getCallerClass();
		ClassLoader callerClassLoader = callerClass.getClassLoader();

		if ((callerClassLoader != classLoader) && (callerClassLoader != currentThreadClassLoader)) {
			Class klass = loadClass(className, arrayClassName, callerClassLoader);

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

		// try #4 - everything failed, try alternative array loader
		if (arrayClassName != null) {
			try {
				return loadArrayClassByComponentType(className, classLoader);
			} catch (ClassNotFoundException ignore) {
			}
		}

		throw new ClassNotFoundException("Class not found: " + className);
	}

	/**
	 * Loads a class using provided class loader.
	 * If class is an array, it will be first loaded using the Class.forName!
	 * We must use this since for JDK {@literal >=} 6 arrays will be not loaded using classloader,
	 * but only with forName method. However, array loading strategy can be
	 * {@link #setLoadArrayClassByComponentTypes(boolean) changed}.
	 */
	protected Class loadClass(final String className, final String arrayClassName, final ClassLoader classLoader) {
		if (arrayClassName != null) {
			try {
				if (loadArrayClassByComponentTypes) {
					return loadArrayClassByComponentType(className, classLoader);
				} else {
					return Class.forName(arrayClassName, true, classLoader);
				}
			} catch (ClassNotFoundException ignore) {
			}
		}

		try {
			return classLoader.loadClass(className);
		} catch (ClassNotFoundException ignore) {
		}

		return null;
	}

	/**
	 * Loads array class using component type.
	 */
	protected Class loadArrayClassByComponentType(final String className, final ClassLoader classLoader) throws ClassNotFoundException {
		int ndx = className.indexOf('[');
		int multi = StringUtil.count(className, '[');

		String componentTypeName = className.substring(0, ndx);

		Class componentType = loadClass(componentTypeName, classLoader);

		if (multi == 1) {
			return Array.newInstance(componentType, 0).getClass();
		}

		int[] multiSizes;

		if (multi == 2) {
			multiSizes = new int[] {0, 0};
		} else if (multi == 3) {
			multiSizes = new int[] {0, 0, 0};
		} else {
			multiSizes = (int[]) Array.newInstance(int.class, multi);
		}

		return Array.newInstance(componentType, multiSizes).getClass();
	}

}


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

/**
 * Singleton scope pools all bean instances so they will be created only once in
 * the container context.
 */
public class SingletonScope implements Scope {

	private final PetiteContainer pc;

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

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

	@Override
	public Object lookup(final String name) {
		BeanData beanData = instances.get(name);
		if (beanData == null) {
			return null;
		}
		return beanData.bean();
	}

	@Override
	public void register(final BeanDefinition beanDefinition, final Object bean) {
		instances.put(beanDefinition.name(), new BeanData(pc, beanDefinition, bean));
	}

	@Override
	public void remove(final String name) {
		instances.remove(name);
	}

	/**
	 * Allows only singleton scoped beans to be injected into the target singleton bean.
	 */
	@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;
		}

		return false;
	}

	/**
	 * Iterate all beans and invokes registered destroy methods.
	 */
	@Override
	public void shutdown() {
		for (final BeanData beanData : instances.values()) {
			beanData.callDestroyMethods();
		}
		instances.clear();
	}
}


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

package jodd.util;

import java.util.Comparator;
import java.util.List;

/**
 * Binary search wrapper over any type of user-defined collection.
 * It provides a finder for given element, but also finder of first
 * and last index in range of equal elements.
 */
public abstract class BinarySearch<E> {

	/**
	 * Creates binary search wrapper over a list of comparable elements.
	 */
	public static <T extends Comparable> BinarySearch<T> forList(final List<T> list) {
		return new BinarySearch<T>() {
			@Override
			@SuppressWarnings( {"unchecked"})
			protected int compare(final int index, final T element) {
				return list.get(index).compareTo(element);
			}

			@Override
			protected int getLastIndex() {
				return list.size() - 1;
			}
		};
	}

	/**
	 * Creates binary search wrapper over a list with given comparator.
	 */
	public static <T> BinarySearch<T> forList(final List<T> list, final Comparator<T> comparator) {
		return new BinarySearch<T>() {
			@Override
			@SuppressWarnings( {"unchecked"})
			protected int compare(final int index, final T element) {
				return comparator.compare(list.get(index), element);
			}

			@Override
			protected int getLastIndex() {
				return list.size() - 1;
			}
		};
	}

	/**
	 * Creates binary search wrapper over an array.
	 */
	public static <T extends Comparable> BinarySearch<T> forArray(final T[] array) {
		return new BinarySearch<T>() {
			@Override
			@SuppressWarnings( {"unchecked"})
			protected int compare(final int index, final T element) {
				return array[index].compareTo(element);
			}

			@Override
			protected int getLastIndex() {
				return array.length - 1;
			}
		};
	}

	/**
	 * Creates binary search wrapper over an array with given comparator.
	 */
	public static <T> BinarySearch<T> forArray(final T[] array, final Comparator<T> comparator) {
		return new BinarySearch<T>() {
			@Override
			@SuppressWarnings( {"unchecked"})
			protected int compare(final int index, final T element) {
				return comparator.compare(array[index], element);
			}

			@Override
			protected int getLastIndex() {
				return array.length - 1;
			}
		};
	}

	// ---------------------------------------------------------------- abstract

	/**
	 * Compares element at index position with given object.
	 */
	protected abstract int compare(int index, E element);

	/**
	 * Returns index of last element in wrapped collection.
	 */
	protected abstract int getLastIndex();

	// ---------------------------------------------------------------- find

	/**
	 * Finds index of given element or negative value if element is not found.
	 */
	public int find(final E element) {
		return find(element, 0, getLastIndex());
	}

	/**
	 * Finds index of given element in inclusive index range. Returns negative
	 * value if element is not found.
	 */
	public int find(final E element, int low, int high) {
		while (low <= high) {
			int mid = (low + high) >>> 1;
			int delta = compare(mid, element);

			if (delta < 0) {
				low = mid + 1;
			} else if (delta > 0) {
				high = mid - 1;
			} else {
				return mid;
			}
		}
		// not found
		return -(low + 1);
	}

	// ---------------------------------------------------------------- first

	/**
	 * Finds very first index of given element or negative value if element is not found.
	 */
	public int findFirst(final E o) {
		return findFirst(o, 0, getLastIndex());
	}

	/**
	 * Finds very first index of given element in inclusive index range. Returns negative
	 * value if element is not found.
	 */
	public int findFirst(final E o, int low, int high) {

		int ndx = -1;
		while (low <= high) {
			int mid = (low + high) >>> 1;
			int delta = compare(mid, o);

			if (delta < 0) {
				low = mid + 1;
			} else {
				if (delta == 0) {
					ndx = mid;
				}
				high = mid - 1;
			}
		}

		if (ndx == -1) {
			return -(low + 1);
		}

		return ndx;
	}

	// ---------------------------------------------------------------- last

	/**
	 * Finds very last index of given element or negative value if element is not found.
	 */
	public int findLast(final E o) {
		return findLast(o, 0, getLastIndex());
	}

	/**
	 * Finds very last index of given element in inclusive index range. Returns negative
	 * value if element is not found.
	 */
	public int findLast(final E o, int low, int high) {
		int ndx = -1;
		while (low <= high) {
			int mid = (low + high) >>> 1;
			int delta = compare(mid, o);

			if (delta > 0) {
				high = mid - 1;
			} else {
				if (delta == 0) {
					ndx = mid;
				}
				low = mid + 1;
			}
		}

		if (ndx == -1) {
			return -(low + 1);
		}

		return ndx;
	}

}


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

package jodd.bean;

/**
 * Default implementation of {@link BeanVisitor} for just setting
 * the properties in fluent way.
 */
public abstract class BeanVisitorImplBase<T> extends BeanVisitor {

	@SuppressWarnings("unchecked")
	protected T _this() {
		return (T) this;
	}

	/**
	 * Excludes all properties, i.e. enables blacklist mode.
	 */
	public T excludeAll() {
		blacklist = false;
		return _this();
	}

	/**
	 * Defines excluded property names.
	 */
	public T exclude(final String... excludes) {
		for (String ex : excludes) {
			rules.exclude(ex);
		}
		return _this();
	}

	/**
	 * Exclude a property.
	 */
	public T exclude(final String exclude) {
		rules.exclude(exclude);
		return _this();
	}

	/**
	 * Defines included property names.
	 */
	public T include(final String... includes) {
		for (String in : includes) {
			rules.include(in);
		}
		return _this();
	}

	/**
	 * Include a property.
	 */
	public T include(final String include) {
		rules.include(include);
		return _this();
	}

	/**
	 * Defines included property names as public properties
	 * of given template class. Sets to black list mode.
	 */
	public T includeAs(final Class template) {
		blacklist = false;

		String[] properties = getAllBeanPropertyNames(template, false);

		include(properties);

		return _this();
	}

	/**
	 * Defines if null values should be ignored.
	 */
	public T ignoreNulls(final boolean ignoreNulls) {
		this.ignoreNullValues = ignoreNulls;

		return _this();
	}

	/**
	 * Defines if empty string should be ignored.
	 */
	public T ignoreEmptyString(final boolean ignoreEmptyString) {
		this.ignoreEmptyString = ignoreEmptyString;
		return _this();
	}

	/**
	 * Defines if all properties should be copied (when set to true)
	 * or only public (when set to false, default).
	 */
	public T declared(final boolean declared) {
		this.declared = declared;
		return _this();
	}

	/**
	 * Defines if fields without getters should be copied too.
	 */
	public T includeFields(final boolean includeFields) {
		this.includeFields = includeFields;
		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.lagarto;

import jodd.util.ArraysUtil;
import jodd.util.CharSequenceUtil;
import jodd.net.HtmlEncoder;

import java.io.IOException;

/**
 * Reusable, parsed {@link Tag tag} implementation.
 */
class ParsedTag implements Tag {

	private static final CharSequence ATTR_NAME_ID = "id";

	// flags
	private boolean caseSensitive;
	private boolean rawTag;

	// tag info
	private CharSequence name;
	private int idNdx;
	private TagType type;

	// attributes
	private int attributesCount;
	private CharSequence[] attrNames = new CharSequence[16];
	private CharSequence[] attrValues = new CharSequence[16];

	private int tagStartIndex;
	private int tagLength;
	private String position;

	// state
	private int deepLevel;
	private boolean modified;

	// ---------------------------------------------------------------- internal

	/**
	 * Initializes the instance.
	 */
	public void init(final boolean caseSensitive) {
		this.caseSensitive = caseSensitive;
	}

	/**
	 * Starts the tag with the index of first '<'.
	 * Resets all tag data.
	 */
	public void start(final int startIndex) {
		this.tagStartIndex = startIndex;
		this.name = null;
		this.idNdx = -1;
		this.attributesCount = 0;
		this.tagLength = 0;
		this.modified = false;
		this.type = TagType.START;
		this.rawTag = false;
	}

	/**
	 * Defines tag end with index of last '>'.
	 * Sets the modification flag to false.
	 */
	void end(final int endIndex) {
		this.tagLength = endIndex - tagStartIndex;
		this.modified = false;
	}

	void increaseDeepLevel() {
		deepLevel++;
	}

	void decreaseDeepLevel() {
		deepLevel--;
	}

	// ---------------------------------------------------------------- flags

	@Override
	public boolean isCaseSensitive() {
		return caseSensitive;
	}

	@Override
	public boolean isRawTag() {
		return rawTag;
	}

	public void setRawTag(final boolean isRawTag) {
		this.rawTag = isRawTag;
	}

	// ---------------------------------------------------------------- read

	@Override
	public CharSequence getName() {
		return name;
	}

	@Override
	public CharSequence getId() {
		if (idNdx == -1) {
			return null;
		}
		return attrValues[idNdx];
	}

	@Override
	public TagType getType() {
		return type;
	}

	@Override
	public int getDeepLevel() {
		return deepLevel;
	}

	@Override
	public int getAttributeCount() {
		return attributesCount;
	}

	@Override
	public CharSequence getAttributeName(final int index) {
		if (index >= attributesCount) {
			throw new IndexOutOfBoundsException();
		}
		return attrNames[index];
	}

	@Override
	public CharSequence getAttributeValue(final int index) {
		if (index >= attributesCount) {
			throw new IndexOutOfBoundsException();
		}
		return attrValues[index];
	}

	@Override
	public CharSequence getAttributeValue(final CharSequence name) {
		for (int i = 0; i < attributesCount; i++) {
			final CharSequence current = attrNames[i];
			if (caseSensitive ? current.equals(name) : CharSequenceUtil.equalsIgnoreCase(current, name)) {
				return attrValues[i];
			}
		}
		return null;
	}

	@Override
	public int getAttributeIndex(final CharSequence name) {
		for (int i = 0; i < attributesCount; i++) {
			final CharSequence current = attrNames[i];
			if (caseSensitive ? current.equals(name) : CharSequenceUtil.equalsIgnoreCase(current, name)) {
				return i;
			}
		}
		return -1;
	}

	@Override
	public boolean hasAttribute(final CharSequence name) {
		return getAttributeIndex(name) > -1;
	}

	// ---------------------------------------------------------------- position

	@Override
	public int getTagPosition() {
		return tagStartIndex;
	}

	@Override
	public int getTagLength() {
		return tagLength;
	}

	@Override
	public String getPosition() {
		return position;
	}

	public void setPosition(final Scanner.Position position) {
		this.position = position.toString();
	}

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

	@Override
	public void setName(final CharSequence tagName) {
		this.name = tagName;
		modified = true;
	}

	@Override
	public void setType(final TagType type) {
		this.type = type;
		modified = true;
	}

	@Override
	public void addAttribute(final CharSequence name, final CharSequence value) {
		ensureLength();
		attrNames[attributesCount] = name;
		setAttrVal(attributesCount, name, value);
		attributesCount++;
		modified = true;
	}

	@Override
	public void setAttribute(final CharSequence name, final CharSequence value) {
		final int index = getAttributeIndex(name);
		if (index == -1) {
			addAttribute(name, value);
		} else {
			setAttrVal(index, name, value);
		}
		modified = true;
	}

	@Override
	public void setAttributeValue(final int index, final CharSequence value) {
		if (index >= attributesCount) {
			throw new IndexOutOfBoundsException();
		}
		setAttrVal(index, value);
		modified = true;
	}

	@Override
	public void setAttributeValue(final CharSequence name, final CharSequence value) {
		final int index = getAttributeIndex(name);
		if (index != -1) {
			setAttrVal(index, name, value);
			modified = true;
		}
	}

	@Override
	public void setAttributeName(final int index, final CharSequence name) {
		if (index >= attributesCount) {
			throw new IndexOutOfBoundsException();
		}
		attrNames[index] = name;
		modified = true;
	}

	@Override
	public void removeAttribute(final int index) {
		if (index >= attributesCount) {
			throw new IndexOutOfBoundsException();
		}
		System.arraycopy(attrNames, index + 1, attrNames, index, attributesCount - index);
		System.arraycopy(attrValues, index + 1, attrValues, index, attributesCount - index);

		attributesCount--;
		modified = true;
	}

	@Override
	public void removeAttribute(final CharSequence name) {
		final int index = getAttributeIndex(name);
		if (index != -1) {
			removeAttribute(index);
		}
		modified = true;
	}

	@Override
	public void removeAttributes() {
		attributesCount = 0;
	}

	@Override
	public boolean isModified() {
		return modified;
	}

	// ---------------------------------------------------------------- match

	@Override
	public boolean nameEquals(final CharSequence charSequence) {
		return caseSensitive ? CharSequenceUtil.equals(name, charSequence) : CharSequenceUtil.equalsIgnoreCase(name, charSequence);
	}

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

	private void ensureLength() {
		if (attributesCount + 1 >= attrNames.length) {
			attrNames = ArraysUtil.resize(attrNames, attributesCount * 2);
			attrValues = ArraysUtil.resize(attrValues, attributesCount * 2);
		}
	}

	private void setAttrVal(final int index, final CharSequence name, final CharSequence value) {
		if (idNdx == -1) {
			if (CharSequenceUtil.equalsToLowercase(name, ATTR_NAME_ID)) {
				idNdx = index;
			}
		}
		attrValues[index] = value;
	}

	private void setAttrVal(final int index, final CharSequence value) {
		attrValues[index] = value;
	}

	// ---------------------------------------------------------------- output

	private void appendTo(final Appendable out) {
		try {
			out.append(type.getStartString());

			out.append(name);

			if (attributesCount > 0) {
				for (int i = 0; i < attributesCount; i++) {
					out.append(' ');
					out.append(attrNames[i]);
					final CharSequence value = attrValues[i];
					if (value != null) {
						out.append('=').append('"');
						out.append(HtmlEncoder.attributeDoubleQuoted(value));
						out.append('"');
					}
				}
			}

			out.append(type.getEndString());
		} catch (IOException ioex) {
			throw new LagartoException(ioex);
		}
	}

	@Override
	public void writeTo(final Appendable out) {
		appendTo(out);
	}

	@Override
	public String toString() {
		final StringBuilder sb = new StringBuilder();
		appendTo(sb);
		return sb.toString();
	}

}


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

package jodd.madvoc.component;

import jodd.log.Logger;
import jodd.log.LoggerFactory;
import jodd.madvoc.MadvocException;
import jodd.mutable.MutableInteger;
import jodd.petite.PetiteContainer;
import jodd.props.Props;

import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

/**
 * Madvoc container. Works internally with {@link jodd.petite.PetiteContainer}.
 */
public class MadvocContainer {

	public static final String MADVOC_PETITE_CONTAINER_NAME = "madpc";

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

	private final PetiteContainer madpc;

	public MadvocContainer() {
		madpc = new PetiteContainer();
		madpc.addSelf(MADVOC_PETITE_CONTAINER_NAME);
	}

	/**
	 * Returns Petite container used for the Madvoc.
	 */
	public PetiteContainer getPetiteContainer() {
		return madpc;
	}

	/**
	 * Registers component using its {@link #resolveBaseComponentName(Class) base name}.
	 * Previously defined component will be removed.
	 *
	 * @see #registerComponentInstance(Object)
	 */
	public void registerComponent(final Class component) {
		String name = resolveBaseComponentName(component);
		registerComponent(name, component);
	}

	public <T> void registerComponent(final Class<T> component, final Consumer<T> consumer) {
		String name = resolveBaseComponentName(component);
		registerComponent(name, component, consumer);
	}

	/**
	 * Registers Madvoc component with given name.
	 */
	public void registerComponent(final String name, final Class component) {
		log.debug(() -> "Madvoc WebApp component: [" + name + "] --> " + component.getName());

		madpc.removeBean(name);
		madpc.registerPetiteBean(component, name, null, null, false, null);
	}

	/**
	 * Registers Madvoc component with given name.
	 */
	public <T> void registerComponent(final String name, final Class<T> component, final Consumer<T> consumer) {
		log.debug(() -> "Madvoc WebApp component: [" + name + "] --> " + component.getName());

		madpc.removeBean(name);
		madpc.registerPetiteBean(component, name, null, null, false, consumer);
	}

	/**
	 * Registers component instance using its {@link #resolveBaseComponentName(Class) base name}.
	 * Previously defined component will be removed.
	 *
	 * @see #registerComponentInstance(String, Object)
	 */
	public void registerComponentInstance(final Object componentInstance) {
		Class component = componentInstance.getClass();
		String name = resolveBaseComponentName(component);
		registerComponentInstance(name, componentInstance);
	}

	/**
	 * Registers component instance and wires it with internal container.
	 * Warning: in this moment we can not guarantee that all other components
	 * are registered, replaced or configuration is update; therefore DO NOT
	 * USE injection, unless you are absolutely sure it works.
	 */
	public void registerComponentInstance(final String name, final Object componentInstance) {
		log.debug(() -> "Madvoc WebApp component: [" + name + "] --> " + componentInstance.getClass().getName());

		madpc.removeBean(name);
		madpc.addBean(name, componentInstance);
	}

	// ---------------------------------------------------------------- listeners

	/**
	 * Fires the Madvoc event.
	 * Warning: since event handlers may register more handlers, we
	 * must collect first the list of components that matches the type
	 * and then to execute.
	 */
	public void fireEvent(final Class listenerType) {
		final Set<String> existing = new HashSet<>();

		while (true) {
			MutableInteger newCount = MutableInteger.of(0);

			madpc.forEachBeanType(listenerType, name -> {
				if (existing.add(name)) {
					// name not found, fire!
					newCount.value++;

					Object listener = lookupComponent(name);
					if (listener != null) {
						MadvocComponentLifecycle.invoke(listener, listenerType);
					}
				}
			});

			if (newCount.value == 0) {
				break;
			}
		}
	}

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

	/**
	 * Returns registered component or {@code null} if component is not registered.
	 */
	@SuppressWarnings({"unchecked"})
	public <T> T lookupComponent(final Class<T> component) {
		String name = resolveBaseComponentName(component);
		return (T) madpc.getBean(name);
	}

	/**
	 * Returns existing component. Throws an exception if component is not registered.
	 */
	public <T> T requestComponent(final Class<T> component) {
		T existingComponent = lookupComponent(component);
		if (existingComponent == null) {
			throw new MadvocException("Madvoc component not found: " + component.getName());
		}
		return existingComponent;
	}

	/**
	 * Returns existing component. Throws an exception if component is not registered.
	 */
	public <T> T requestComponent(final String componentName) {
		T existingComponent = (T) lookupComponent(componentName);
		if (existingComponent == null) {
			throw new MadvocException("Madvoc component not found: " + componentName);
		}
		return existingComponent;
	}

	/**
	 * Returns registered component or {@code null} if component does not exist.
	 */
	public Object lookupComponent(final String componentName) {
		return madpc.getBean(componentName);
	}

	/**
	 * Resolves the name of the last base non-abstract subclass for provided component.
	 * It iterates all subclasses up to the <code>Object</cde> and declares the last
	 * non-abstract class as base component. Component name will be resolved from the
	 * founded base component.
	 */
	private String resolveBaseComponentName(Class component) {
		Class lastComponent = component;
		while (true) {
			Class superClass = component.getSuperclass();
			if (superClass.equals(Object.class)) {
				break;
			}
			component = superClass;
			if (!Modifier.isAbstract(component.getModifiers())) {
				lastComponent = component;
			}
		}
		return madpc.resolveBeanName(lastComponent);
	}

	// ---------------------------------------------------------------- params

	public void defineParams(final Props props) {
		madpc.defineParameters(props);
	}

	public void defineParams(final Map params) {
		madpc.defineParameters(params);
	}

}


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

package jodd.db.oom;

import jodd.bean.BeanUtil;
import jodd.db.DbOom;
import jodd.db.DbQuery;
import jodd.db.DbSession;
import jodd.db.DbUtil;
import jodd.db.oom.mapper.DefaultResultSetMapper;
import jodd.db.oom.mapper.ResultSetMapper;
import jodd.db.oom.sqlgen.ParameterValue;
import jodd.db.type.SqlType;
import jodd.log.Logger;
import jodd.log.LoggerFactory;
import jodd.util.CharUtil;
import jodd.util.StringUtil;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * A simple ORM extension for {@link DbQuery}.
 *

 * OOM extension may map results to objects in two ways:
 *

 *
auto mode - when result set is mapped to provided types, and

 *
mapped mode - requires explicit mapping definitions.

 *

 */
public class DbOomQuery extends DbQuery<DbOomQuery> {

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

	// ---------------------------------------------------------------- default ctors

	public DbOomQuery(final DbOom dbOom, final Connection conn, final String sqlString) {
		super(dbOom, conn, sqlString);
		this.sqlgen = null;
		init(dbOom);
	}

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

	public DbOomQuery(final DbOom dbOom, final DbSession session, final String sqlString) {
		super(dbOom, session, sqlString);
		this.sqlgen = null;
		init(dbOom);
	}

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

	public DbOomQuery(final DbOom dbOom, final String sqlString) {
		super(dbOom, sqlString);
		this.sqlgen = null;
		init(dbOom);
	}

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

	// ---------------------------------------------------------------- sqlgen ctors

	protected final DbSqlGenerator sqlgen;

	public DbOomQuery(final DbOom dbOom, final Connection conn, final DbSqlGenerator sqlgen) {
		super(dbOom, conn, sqlgen.generateQuery());
		this.sqlgen = sqlgen;
		init(dbOom);
	}
	public static DbOomQuery query(final Connection conn, final DbSqlGenerator sqlgen) {
		return new DbOomQuery(DbOom.get(), conn, sqlgen);
	}

	public DbOomQuery(final DbOom dbOom, final DbSession session, final DbSqlGenerator sqlgen) {
		super(dbOom, session, sqlgen.generateQuery());
		this.sqlgen = sqlgen;
		init(dbOom);
	}
	public static DbOomQuery query(final DbSession session, final DbSqlGenerator sqlgen) {
		return new DbOomQuery(DbOom.get(), session, sqlgen);
	}

	public DbOomQuery(final DbOom dbOom, final DbSqlGenerator sqlgen) {
		super(dbOom, sqlgen.generateQuery());
		this.sqlgen = sqlgen;
		init(dbOom);
	}
	public static DbOomQuery query(final DbSqlGenerator sqlgen) {
		return new DbOomQuery(DbOom.get(), sqlgen);
	}

	protected void init(final DbOom dbOom) {
		this.sqlString = preprocessSql(sqlString);
		this.cacheEntities = dbOom.config().isCacheEntitiesInResultSet();
		this.entityAwareMode = dbOom.config().isEntityAwareMode();
	}

	// ---------------------------------------------------------------- initialization

	/**
	 * Prepares the query after initialization. Besides default work, it checks if sql generator
	 * is used, and if so, generator hints and query parameters will be used for this query.
	 * Note regarding hints: since hints can be added manually, generators hints will be ignored
	 * if there exists some manually set hints.
	 */
	@Override
	protected void prepareQuery() {
		super.prepareQuery();
		if (sqlgen == null) {
			return;
		}
		if (hints == null) {
			String[] joinHints = sqlgen.getJoinHints();
			if (joinHints != null) {
				withHints(joinHints);
			}
		}
		// insert parameters
		Map<String, ParameterValue> parameters = sqlgen.getQueryParameters();
		if (parameters == null) {
			return;
		}
		for (Map.Entry<String, ParameterValue> entry : parameters.entrySet()) {
			String paramName = entry.getKey();
			ParameterValue param = entry.getValue();
			DbEntityColumnDescriptor dec = param.getColumnDescriptor();
			if (dec == null) {
				setObject(paramName, param.getValue());
			} else {
				resolveColumnDbSqlType(connection, dec);
				setObject(paramName, param.getValue(), dec.getSqlTypeClass(), dec.getDbSqlType());
			}
		}
	}

	/**
	 * Resolves column db sql type and populates it in column descriptor if missing.
	 */
	protected void resolveColumnDbSqlType(final Connection connection, final DbEntityColumnDescriptor dec) {
		if (dec.dbSqlType != SqlType.DB_SQLTYPE_UNKNOWN) {
			return;
		}
		ResultSet rs = null;
		DbEntityDescriptor ded = dec.getDbEntityDescriptor();
		try {
			DatabaseMetaData dmd = connection.getMetaData();
			rs = dmd.getColumns(null, ded.getSchemaName(), ded.getTableName(), dec.getColumnName());
			if (rs.next()) {
				dec.dbSqlType = rs.getInt("DATA_TYPE");
			} else {
				dec.dbSqlType = SqlType.DB_SQLTYPE_NOT_AVAILABLE;
				if (log.isWarnEnabled()) {
					log.warn("Column SQL type not available: " + ded.toString() + '.' + dec.getColumnName());
				}
			}
		} catch (SQLException sex) {
			dec.dbSqlType = SqlType.DB_SQLTYPE_NOT_AVAILABLE;
			if (log.isWarnEnabled()) {
				log.warn("Column SQL type not resolved: " + ded.toString() + '.' + dec.getColumnName(), sex);
			}
		} finally {
			DbUtil.close(rs);
		}
	}

	/**
	 * Pre-process SQL before using it. If string starts with a non-ascii char
	 * or it has no spaces, it will be loaded from the query map.
	 */
	protected String preprocessSql(String sqlString) {
		// detects callable statement
		if (sqlString.charAt(0) == '{') {
			return sqlString;
		}

		// quickly detect if SQL string is a key
		if (!CharUtil.isAlpha(sqlString.charAt(0))) {
			sqlString = sqlString.substring(1);
		}
		else if (sqlString.indexOf(' ') != -1) {
			return sqlString;
		}

		final String sqlFromMap = dbOom.queryMap().getQuery(sqlString);

		if (sqlFromMap != null) {
			sqlString = sqlFromMap.trim();
		}

		return sqlString;
	}

	// ---------------------------------------------------------------- join hints

	protected JoinHintResolver hintResolver;

	protected String[] hints;

	/**
	 * Specifies hints for the query. Provided string is
	 * split on ',' separator.
	 */
	public DbOomQuery withHints(final String hint) {
		this.hints = StringUtil.splitc(hint, ',');
		return this;
	}

	/**
	 * Specifies multiple hints for the query.
	 */
	public DbOomQuery withHints(final String... hints) {
		this.hints = hints;
		return this;
	}

	/**
	 * Prepares a row (array of rows mapped object) using hints.
	 * Returns either single object or objects array.
	 */
	protected Object resolveRowResults(Object[] row) {
		if (hintResolver == null) {
			hintResolver = new JoinHintResolver();
		}
		row = hintResolver.join(row, hints);
		return row.length == 1 ? row[0] : row;
	}

	// ---------------------------------------------------------------- result set

	protected boolean cacheEntities;

	/**
	 * Defines if entities should be cached in {@link ResultSetMapper}.
	 * Overrides default value in {@link DbEntityManager}.
	 */
	public DbOomQuery cacheEntities(final boolean cacheEntities) {
		this.cacheEntities = cacheEntities;
		return this;
	}

	/**
	 * Executes the query and returns {@link #createResultSetMapper(java.sql.ResultSet) builded ResultSet mapper}.
	 */
	protected ResultSetMapper executeAndBuildResultSetMapper() {
		ResultSet resultSet = execute();

		return createResultSetMapper(resultSet);
	}

	/**
	 * Factory for result sets mapper.
	 */
	protected ResultSetMapper createResultSetMapper(final ResultSet resultSet) {
		final Map<String, ColumnData> columnAliases = sqlgen != null ? sqlgen.getColumnData() : null;

		return new DefaultResultSetMapper(dbOom, resultSet, columnAliases, cacheEntities, this);
	}

	// ---------------------------------------------------------------- db list

	protected boolean entityAwareMode;

	/**
	 * Defines entity-aware mode for entities tracking in result collection.
	 */
	public DbOomQuery entityAwareMode(final boolean entityAware) {
		if (entityAware) {
			this.cacheEntities = true;
		}
		this.entityAwareMode = entityAware;
		return this;
	}

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

	public <T> Iterator<T> iterate(final Class... types) {
		return iterate(types, autoClose);
	}
	public <T> Iterator<T> iterate() {
		return iterate(null, autoClose);
	}
	protected <T> Iterator<T> iterate(final Class[] types, final boolean close) {
		return new DbListIterator<>(this, types, close);
	}

	// ---------------------------------------------------------------- list

	public <T> List<T> list(final Class... types) {
		return list(types, -1, autoClose);
	}
	public <T> List<T> list() {
		return list(null, -1, autoClose);
	}
	public <T> List<T> list(final int max, final Class... types) {
		return list(types, max, autoClose);
	}
	public <T> List<T> list(final int max) {
		return list(null, max, autoClose);
	}
	/**
	 * Iterates result set, maps rows to classes and populates resulting array list.
	 * @param types mapping types
	 * @param max max number of rows to collect, -1 for all
	 * @param close true if query is closed at the end, otherwise false.
	 * @return list of mapped entities or array of entities
	 */
	@SuppressWarnings({"unchecked"})
	protected <T> List<T> list(Class[] types, final int max, final boolean close) {
		List<T> result = new ArrayList<>(initialCollectionSize(max));

		ResultSetMapper rsm = executeAndBuildResultSetMapper();
		if (types == null) {
			types = rsm.resolveTables();
		}

		Object previousElement = null;

		while (rsm.next()) {
			Object[] objects = rsm.parseObjects(types);
			Object row = resolveRowResults(objects);

			int size = result.size();

			T newElement = (T) row;

			if (entityAwareMode && size > 0) {
				if (previousElement != null && newElement != null) {
					boolean equals;

					if (newElement.getClass().isArray()) {
						equals = Arrays.equals((Object[]) previousElement, (Object[]) newElement);
					} else {
						equals = previousElement.equals(newElement);
					}

					if (equals) {
						continue;
					}
				}
			}

			if (size == max) {
				break;
			}

			result.add(newElement);
			previousElement = newElement;
		}

		close(rsm, close);
		return result;
	}

	/**
	 * Returns initial collections size when max
	 * value is provided.
	 */
	private static int initialCollectionSize(final int max) {
		return max > 0 ? max : 10;
	}

	// ---------------------------------------------------------------- set

	public <T> Set<T> listSet(final Class... types) {
		return listSet(types, -1, autoClose);
	}
	public <T> Set<T> listSet() {
		return listSet(null, -1, autoClose);
	}
	public <T> Set<T> listSet(final int max, final Class... types) {
		return listSet(types, max, autoClose);
	}
	public <T> Set<T> listSet(final int max) {
		return listSet(null, max, autoClose);
	}
	@SuppressWarnings({"unchecked"})
	protected <T> Set<T> listSet(Class[] types, final int max, final boolean close) {
		final Set<T> result = new LinkedHashSet<>(initialCollectionSize(max));

		ResultSetMapper rsm = executeAndBuildResultSetMapper();
		if (types == null) {
			types = rsm.resolveTables();
		}

		Object previousElement = null;

		while (rsm.next()) {
			Object[] objects = rsm.parseObjects(types);
			Object row = resolveRowResults(objects);

			int size = result.size();

			T newElement = (T) row;

			if (entityAwareMode && size > 0) {
				if (previousElement != null && newElement != null) {
					boolean equals;

					if (newElement.getClass().isArray()) {
						equals = Arrays.equals((Object[]) previousElement, (Object[]) newElement);
					} else {
						equals = previousElement.equals(newElement);
					}

					if (equals) {
						continue;
					}
				}
			}

			if (size == max) {
				break;
			}

			result.add(newElement);
			previousElement = newElement;
		}

		close(rsm, close);
		return result;
	}

	// ---------------------------------------------------------------- find

	public <T> T find(final Class... types) {
		return find(types, autoClose, null);
	}
	public <T> T find() {
		return find(null, autoClose, null);
	}
	protected <T> T find(final Class[] types, final boolean close, ResultSet resultSet) {
		if (resultSet == null) {
			resultSet = execute();
		}
		ResultSetMapper rsm = createResultSetMapper(resultSet);

		Iterator<T> iterator = new DbListIterator<>(this, types, rsm, false);

		T result = null;

		if (iterator.hasNext()) {
			result = iterator.next();
		}

		close(rsm, close);
		return result;
	}

	// ---------------------------------------------------------------- generated columns

	/**
	 * Finds generated key column of given type.
	 */
	public <T> T findGeneratedKey(final Class<T> type) {
		return find(new Class[] {type}, false, getGeneratedColumns());
	}

	/**
	 * Finds generated columns.
	 */
	public Object findGeneratedColumns(final Class... types) {
		return find(types, false, getGeneratedColumns());
	}

	/**
	 * Populates entity with generated column values from executed query.
	 */
	public void populateGeneratedKeys(final Object entity) {

		final String[] generatedColumns = getGeneratedColumnNames();
		if (generatedColumns == null) {
			return;
		}
		DbEntityDescriptor ded = dbOom.entityManager().lookupType(entity.getClass());

		// prepare key types
		Class[] keyTypes = new Class[generatedColumns.length];
		String[] properties = new String[generatedColumns.length];
		for (int i = 0; i < generatedColumns.length; i++) {
			String column = generatedColumns[i];
			DbEntityColumnDescriptor decd = ded.findByColumnName(column);
			if (decd != null) {
				keyTypes[i] = decd.getPropertyType();
				properties[i] = decd.getPropertyName();
			}
		}

		final Object keyValues = findGeneratedColumns(keyTypes);
		if (!keyValues.getClass().isArray()) {
			BeanUtil.declared.setProperty(entity, properties[0], keyValues);
		} else {
			for (int i = 0; i < properties.length; i++) {
				BeanUtil.declared.setProperty(entity, properties[i], ((Object[]) keyValues)[i]);
			}
		}
	}

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public DbOomQuery autoClose() {
		super.autoClose();
		return this;
	}

	/**
	 * Closes results set or whole query.
	 */
	protected void close(final ResultSetMapper rsm, final boolean closeQuery) {
		if (closeQuery) {
			close();
		} else {
			closeResultSet(rsm.getResultSet());
		}
	}

}


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

import jodd.madvoc.component.InterceptorsManager;
import jodd.madvoc.interceptor.ActionInterceptor;
import jodd.petite.PetiteContainer;
import jodd.petite.meta.PetiteInject;

/**
 * Petite-aware interceptors manager.
 */
public class PetiteInterceptorManager extends InterceptorsManager {

	@PetiteInject
	protected PetiteContainer petiteContainer;

	/**
	 * Acquires interceptor from Petite container.
	 */
	@Override
	protected <R extends ActionInterceptor> R createWrapper(final Class<R> wrapperClass) {
		return petiteContainer.createBean(wrapperClass);
	}
}