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

package jodd.proxetta.impl;

import jodd.asm7.ClassReader;
import jodd.proxetta.InvokeAspect;
import jodd.proxetta.ProxettaFactory;
import jodd.proxetta.asm.InvokeClassBuilder;
import jodd.proxetta.asm.TargetClassInfoReader;
import jodd.proxetta.asm.WorkData;

import java.io.InputStream;

/**
 * Invocation replacer class processor.
 */
public class InvokeProxettaFactory extends ProxettaFactory<InvokeProxettaFactory, InvokeProxetta> {

	public InvokeProxettaFactory(final InvokeProxetta invokeProxetta) {
		super(invokeProxetta);
	}

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

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

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

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

		InvokeClassBuilder icb = new InvokeClassBuilder(
				destClassWriter,
				proxetta.getAspects(new InvokeAspect[0]),
				resolveClassNameSuffix(),
				requestedProxyClassName,
				targetClassInfoReader);

		cr.accept(icb, 0);

		return icb.getWorkData();
	}

}


// 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.madvoc.config.ScopeData;
import jodd.madvoc.config.Targets;
import jodd.madvoc.scope.ParamsScope;
import jodd.petite.meta.PetiteInject;

import javax.servlet.ServletContext;

/**
 * Context injector for all singleton Madvoc elements, like results and interceptors.
 */
public class ContextInjectorComponent {

	@PetiteInject
	protected ScopeDataInspector scopeDataInspector;

	@PetiteInject
	protected ScopeResolver scopeResolver;

	@PetiteInject
	protected MadvocController madvocController;

	/**
	 * Inject context into target.
	 */
	public void injectContext(final Object targetObject) {
		final Class targetType = targetObject.getClass();

		final ScopeData scopeData = scopeDataInspector.inspectClassScopesWithCache(targetType);

		final Targets targets = new Targets(targetObject, scopeData);

		// inject no context
		scopeResolver.forEachScope(madvocScope -> madvocScope.inject(targets));

		// inject special case
		scopeResolver.forScope(ParamsScope.class, scope -> scope.inject(targets));

		// inject servlet context
		final ServletContext servletContext = madvocController.getApplicationContext();
		if (servletContext != null) {
			scopeResolver.forEachScope(madvocScope -> madvocScope.inject(servletContext, 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.csselly;

import jodd.util.StringUtil;
import jodd.util.UnsafeUtil;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * CSS selector parser. Works with one query, i.e. does not support groups
 * of selectors (selectors separated by a comma). To parse selectors group,
 * manually split the group query into single queries and parse each.
 * See: http://www.w3.org/TR/css3-selectors/#w3cselgrammar
 */
public class CSSelly {

	protected final CSSellyLexer lexer;

	public CSSelly(final String input) {
		this(UnsafeUtil.getChars(input));
	}

	public CSSelly(final char[] input) {
		this.lexer = new CSSellyLexer(input);
	}

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

	/**
	 * Parses selector string.
	 * Returns null if no selector can be parsed.
	 */
	public List<CssSelector> parse() {
		try {
			lexer.yylex();
			if (lexer.selectors.isEmpty()) {
				return null;
			}

			// fixes last combinator
			CssSelector last = lexer.selectors.get(lexer.selectors.size() - 1);
			if (last.getCombinator() == Combinator.DESCENDANT) {
				last.setCombinator(null);
			}

			// set previous css selector
			CssSelector prevCssSelector = null;
			for (CssSelector cssSelector : lexer.selectors) {
				if (prevCssSelector != null) {
					cssSelector.setPrevCssSelector(prevCssSelector);
				}
				prevCssSelector = cssSelector;
			}

			return lexer.selectors;
		} catch (IOException ioex) {
			throw new CSSellyException(ioex);
		}
	}

	/**
	 * Parses string of selectors (separated with ,). Returns
	 * list of {@link CssSelector} lists in the same order.
	 */
	public static List<List<CssSelector>> parse(final String query) {
		String[] singleQueries = StringUtil.splitc(query, ',');
		List<List<CssSelector>> selectors = new ArrayList<>(singleQueries.length);

		for (String singleQuery: singleQueries) {
			selectors.add(new CSSelly(singleQuery).parse());
		}

		return selectors;
	}

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

	/**
	 * Returns string representation of given list of selectors.
	 */
	public static String toString(final List<CssSelector> selectors) {
		StringBuilder out = new StringBuilder();
		for (CssSelector s : selectors) {
			out.append(s.toString());
		}
		return out.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.csselly.selector;

import jodd.csselly.CSSellyException;
import jodd.csselly.Selector;
import jodd.lagarto.dom.Node;
import jodd.lagarto.dom.NodeFilter;
import jodd.lagarto.dom.NodeListFilter;
import jodd.util.ClassUtil;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Pseudo class selector.
 * A pseudo-class always consists of a "colon" (:) followed by
 * the name of the pseudo-class and optionally by a value between parentheses.
 *

 * Selectors introduces the concept of structural pseudo-classes to permit
 * selection based on extra information that lies in the document tree
 * but cannot be represented by other simple selectors or combinators.
 *

 * Standalone text and other non-element nodes are not counted when calculating
 * the position of an element in the list of children of its parent.
 * When calculating the position of an element in the list of children
 * of its parent, the index numbering starts at 1.
 */
public class PseudoClassSelector extends Selector implements NodeFilter, NodeListFilter {

	protected static final Map<String, PseudoClass> PSEUDO_CLASS_MAP;

	static {
		PSEUDO_CLASS_MAP = new HashMap<>(26);

		registerPseudoClass(PseudoClass.EMPTY.class);
		registerPseudoClass(PseudoClass.FIRST_CHILD.class);
		registerPseudoClass(PseudoClass.FIRST_OF_TYPE.class);
		registerPseudoClass(PseudoClass.LAST_CHILD.class);
		registerPseudoClass(PseudoClass.LAST_OF_TYPE.class);
		registerPseudoClass(PseudoClass.ONLY_CHILD.class);
		registerPseudoClass(PseudoClass.ONLY_OF_TYPE.class);
		registerPseudoClass(PseudoClass.ROOT.class);

		registerPseudoClass(PseudoClass.FIRST.class);
		registerPseudoClass(PseudoClass.LAST.class);
		registerPseudoClass(PseudoClass.BUTTON.class);
		registerPseudoClass(PseudoClass.CHECKBOX.class);
		registerPseudoClass(PseudoClass.FILE.class);
		registerPseudoClass(PseudoClass.IMAGE.class);
		registerPseudoClass(PseudoClass.INPUT.class);
		registerPseudoClass(PseudoClass.HEADER.class);
		registerPseudoClass(PseudoClass.PARENT.class);
		registerPseudoClass(PseudoClass.PASSWORD.class);
		registerPseudoClass(PseudoClass.RADIO.class);
		registerPseudoClass(PseudoClass.RESET.class);
		registerPseudoClass(PseudoClass.SELECTED.class);
		registerPseudoClass(PseudoClass.CHECKED.class);
		registerPseudoClass(PseudoClass.SUBMIT.class);
		registerPseudoClass(PseudoClass.TEXT.class);
		registerPseudoClass(PseudoClass.EVEN.class);
		registerPseudoClass(PseudoClass.ODD.class);
	}

	/**
	 * Registers pseudo class.
	 */
	public static void registerPseudoClass(final Class<? extends PseudoClass> pseudoClassType) {
		PseudoClass pseudoClass;
		try {
			pseudoClass = ClassUtil.newInstance(pseudoClassType);
		} catch (Exception ex) {
			throw new CSSellyException(ex);
		}
		PSEUDO_CLASS_MAP.put(pseudoClass.getPseudoClassName(), pseudoClass);
	}

	/**
	 * Lookups pseudo class for given pseudo class name.
	 */
	public static PseudoClass lookupPseudoClass(final String pseudoClassName) {
		PseudoClass pseudoClass = PSEUDO_CLASS_MAP.get(pseudoClassName);
		if (pseudoClass == null) {
			throw new CSSellyException("Unsupported pseudo class: " + pseudoClassName);
		}
		return pseudoClass;
	}

	// ---------------------------------------------------------------- selector

	protected final PseudoClass pseudoClass;

	public PseudoClassSelector(final String pseudoClassName) {
		super(Type.PSEUDO_CLASS);
		this.pseudoClass = lookupPseudoClass(pseudoClassName.trim());
	}

	/**
	 * Returns {@link PseudoClass pseudo class} value.
	 */
	public PseudoClass getPseudoClass() {
		return pseudoClass;
	}

	/**
	 * Matches node to this selector.
	 */
	@Override
	public boolean accept(final Node node) {
		return pseudoClass.match(node);
	}

	/**
	 * Accepts node within selected results. Invoked after results are matched.
	 */
	@Override
	public boolean accept(final List<Node> currentResults, final Node node, final int index) {
		return pseudoClass.match(currentResults, node, index);
	}

}


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

package jodd.madvoc.component;

import jodd.exception.ExceptionUtil;
import jodd.log.Logger;
import jodd.log.LoggerFactory;
import jodd.madvoc.ActionRequest;
import jodd.madvoc.MadvocException;

import javax.servlet.AsyncContext;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Thread pool and executor for Async actions.
 */
public class AsyncActionExecutor extends AsyncActionExecutorCfg {

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

	protected ExecutorService executorService;

	public void start() {
		executorService = new ThreadPoolExecutor(
			corePoolSize,
			maximumPoolSize,
			keepAliveTimeMillis,
			TimeUnit.MILLISECONDS,
			new LinkedBlockingQueue<>(queueCapacity));
	}

	/**
	 * Invokes an action asynchronously by submitting it to the thread pool.
	 */
	public void invoke(final ActionRequest actionRequest) {
		if (executorService == null) {
			throw new MadvocException("No action is marked as async!");
		}

		final HttpServletRequest servletRequest = actionRequest.getHttpServletRequest();

		log.debug(() -> "Async call to: " + actionRequest);

		final AsyncContext asyncContext = servletRequest.startAsync();

		executorService.submit(() -> {
			try {
				actionRequest.invoke();
			} catch (Exception ex) {
				log.error("Invoking async action path failed: " , ExceptionUtil.unwrapThrowable(ex));
			} finally {
				asyncContext.complete();
			}
		});
	}
}


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

import jodd.core.JoddCore;

import javax.activation.DataSource;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;

/**
 * Optimized byte and character stream utilities.
 */
public class StreamUtil {

	private static final int ZERO = 0;
	private static final int NEGATIVE_ONE = -1;
	private static final int ALL = -1;

	// ---------------------------------------------------------------- silent close

	/**
	 * Closes silently the closable object. If it is {@link Flushable}, it
	 * will be flushed first. No exception will be thrown if an I/O error occurs.
	 */
	public static void close(final Closeable closeable) {
		if (closeable != null) {
			if (closeable instanceof Flushable) {
				try {
					((Flushable) closeable).flush();
				} catch (IOException ignored) {
				}
			}

			try {
				closeable.close();
			} catch (IOException ignored) {
			}
		}
	}

	// ---------------------------------------------------------------- copy

	/**
	 * Copies bytes from {@link Reader} to {@link Writer} using buffer.
	 * {@link Reader} and {@link Writer} don't have to be wrapped to buffered, since copying is already optimized.
	 *
	 * @param input  {@link Reader} to read.
	 * @param output {@link Writer} to write to.
	 * @return The total number of characters read.
	 * @throws IOException if there is an error reading or writing.
	 */
	public static int copy(final Reader input, final Writer output) throws IOException {
		int numToRead = bufferSize();
		char[] buffer = new char[numToRead];

		int totalRead = ZERO;
		int read;

		while ((read = input.read(buffer, ZERO, numToRead)) >= ZERO) {
			output.write(buffer, ZERO, read);
			totalRead = totalRead + read;
		}

		output.flush();
		return totalRead;
	}

	/**
	 * Copies bytes from {@link InputStream} to {@link OutputStream} using buffer.
	 * {@link InputStream} and {@link OutputStream} don't have to be wrapped to buffered,
	 * since copying is already optimized.
	 *
	 * @param input  {@link InputStream} to read.
	 * @param output {@link OutputStream} to write to.
	 * @return The total number of bytes read.
	 * @throws IOException if there is an error reading or writing.
	 */
	public static int copy(final InputStream input, final OutputStream output) throws IOException {
		int numToRead = bufferSize();
		byte[] buffer = new byte[numToRead];

		int totalRead = ZERO;
		int read;

		while ((read = input.read(buffer, ZERO, numToRead)) >= ZERO) {
			output.write(buffer, ZERO, read);
			totalRead = totalRead + read;
		}

		output.flush();
		return totalRead;
	}

	/**
	 * Copies specified number of characters from {@link Reader} to {@link Writer} using buffer.
	 * {@link Reader} and {@link Writer} don't have to be wrapped to buffered, since copying is already optimized.
	 *
	 * @param input  {@link Reader} to read.
	 * @param output {@link Writer} to write to.
	 * @param count  The number of characters to read.
	 * @return The total number of characters read.
	 * @throws IOException if there is an error reading or writing.
	 */
	public static int copy(final Reader input, final Writer output, final int count) throws IOException {
		if (count == ALL) {
			return copy(input, output);
		}

		int numToRead = count;
		char[] buffer = new char[numToRead];

		int totalRead = ZERO;
		int read;

		while (numToRead > ZERO) {
			read = input.read(buffer, ZERO, bufferSize(numToRead));
			if (read == NEGATIVE_ONE) {
				break;
			}
			output.write(buffer, ZERO, read);

			numToRead = numToRead - read;
			totalRead = totalRead + read;
		}

		output.flush();
		return totalRead;
	}

	/**
	 * Copies specified number of bytes from {@link InputStream} to {@link OutputStream} using buffer.
	 * {@link InputStream} and {@link OutputStream} don't have to be wrapped to buffered, since copying is already optimized.
	 *
	 * @param input  {@link InputStream} to read.
	 * @param output {@link OutputStream} to write to.
	 * @param count  The number of bytes to read.
	 * @return The total number of bytes read.
	 * @throws IOException if there is an error reading or writing.
	 */
	public static int copy(final InputStream input, final OutputStream output, final int count) throws IOException {
		if (count == ALL) {
			return copy(input, output);
		}

		int numToRead = count;
		byte[] buffer = new byte[numToRead];

		int totalRead = ZERO;
		int read;

		while (numToRead > ZERO) {
			read = input.read(buffer, ZERO, bufferSize(numToRead));
			if (read == NEGATIVE_ONE) {
				break;
			}
			output.write(buffer, ZERO, read);

			numToRead = numToRead - read;
			totalRead = totalRead + read;
		}

		output.flush();
		return totalRead;
	}

	// ---------------------------------------------------------------- read bytes

	/**
	 * Reads all available bytes from {@link InputStream} as a byte array.
	 * Uses {@link InputStream#available()} to determine the size of input stream.
	 * This is the fastest method for reading {@link InputStream} to byte array, but
	 * depends on {@link InputStream} implementation of {@link InputStream#available()}.
	 *
	 * @param input {@link InputStream} to read.
	 * @return byte[]
	 * @throws IOException if total read is less than {@link InputStream#available()};
	 */
	public static byte[] readAvailableBytes(final InputStream input) throws IOException {
		int numToRead = input.available();
		byte[] buffer = new byte[numToRead];

		int totalRead = ZERO;
		int read;

		while ((totalRead < numToRead) && (read = input.read(buffer, totalRead, numToRead - totalRead)) >= ZERO) {
			totalRead = totalRead + read;
		}

		if (totalRead < numToRead) {
			throw new IOException("Failed to completely read InputStream");
		}

		return buffer;
	}

	// ---------------------------------------------------------------- copy to OutputStream

	/**
	 * @see #copy(Reader, OutputStream, String)
	 */
	public static <T extends OutputStream> T copy(final Reader input, final T output) throws IOException {
		return copy(input, output, encoding());
	}

	/**
	 * @see #copy(Reader, OutputStream, String, int)
	 */
	public static <T extends OutputStream> T copy(final Reader input, final T output, final int count) throws IOException {
		return copy(input, output, encoding(), count);
	}

	/**
	 * @see #copy(Reader, OutputStream, String, int)
	 */
	public static <T extends OutputStream> T copy(final Reader input, final T output, final String encoding) throws IOException {
		return copy(input, output, encoding, ALL);
	}

	/**
	 * Copies {@link Reader} to {@link OutputStream} using buffer and specified encoding.
	 *
	 * @see #copy(Reader, Writer, int)
	 */
	public static <T extends OutputStream> T copy(final Reader input, final T output, final String encoding, final int count) throws IOException {
		try (Writer out = outputStreamWriterOf(output, encoding)) {
			copy(input, out, count);
			return output;
		}
	}

	/**
	 * Copies data from {@link DataSource} to a new {@link FastByteArrayOutputStream} and returns this.
	 *
	 * @param input {@link DataSource} to copy from.
	 * @return new {@link FastByteArrayOutputStream} with data from input.
	 * @see #copyToOutputStream(InputStream)
	 */
	public static FastByteArrayOutputStream copyToOutputStream(final DataSource input) throws IOException {
		return copyToOutputStream(input.getInputStream());
	}

	/**
	 * @see #copyToOutputStream(InputStream, int)
	 */
	public static FastByteArrayOutputStream copyToOutputStream(final InputStream input) throws IOException {
		return copyToOutputStream(input, ALL);
	}

	/**
	 * Copies {@link InputStream} to a new {@link FastByteArrayOutputStream} using buffer and specified encoding.
	 *
	 * @see #copy(InputStream, OutputStream, int)
	 */
	public static FastByteArrayOutputStream copyToOutputStream(final InputStream input, final int count) throws IOException {
		try (FastByteArrayOutputStream output = createFastByteArrayOutputStream()) {
			copy(input, output, count);
			return output;
		}
	}

	/**
	 * @see #copyToOutputStream(Reader, String)
	 */
	public static FastByteArrayOutputStream copyToOutputStream(final Reader input) throws IOException {
		return copyToOutputStream(input, encoding());
	}

	/**
	 * @see #copyToOutputStream(Reader, String, int)
	 */
	public static FastByteArrayOutputStream copyToOutputStream(final Reader input, final String encoding) throws IOException {
		return copyToOutputStream(input, encoding, ALL);
	}

	/**
	 * @see #copyToOutputStream(Reader, String, int)
	 */
	public static FastByteArrayOutputStream copyToOutputStream(final Reader input, final int count) throws IOException {
		return copyToOutputStream(input, encoding(), count);
	}

	/**
	 * Copies {@link Reader} to a new {@link FastByteArrayOutputStream} using buffer and specified encoding.
	 *
	 * @see #copy(Reader, OutputStream, String, int)
	 */
	public static FastByteArrayOutputStream copyToOutputStream(final Reader input, final String encoding, final int count) throws IOException {
		try (FastByteArrayOutputStream output = createFastByteArrayOutputStream()) {
			copy(input, output, encoding, count);
			return output;
		}
	}

	// ---------------------------------------------------------------- copy to Writer

	/**
	 * @see #copy(InputStream, Writer, String)
	 */
	public static <T extends Writer> T copy(final InputStream input, final T output) throws IOException {
		return copy(input, output, encoding());
	}

	/**
	 * @see #copy(InputStream, Writer, String, int)
	 */
	public static <T extends Writer> T copy(final InputStream input, final T output, final int count) throws IOException {
		return copy(input, output, encoding(), count);
	}

	/**
	 * @see #copy(InputStream, Writer, String, int)
	 */
	public static <T extends Writer> T copy(final InputStream input, final T output, final String encoding) throws IOException {
		return copy(input, output, encoding, ALL);
	}

	/**
	 * Copies {@link InputStream} to {@link Writer} using buffer and specified encoding.
	 *
	 * @see #copy(Reader, Writer, int)
	 */
	public static <T extends Writer> T copy(final InputStream input, final T output, final String encoding, final int count) throws IOException {
		copy(inputStreamReadeOf(input, encoding), output, count);
		return output;
	}

	/**
	 * @see #copy(InputStream, String)
	 */
	public static FastCharArrayWriter copy(final InputStream input) throws IOException {
		return copy(input, encoding());
	}

	/**
	 * @see #copy(InputStream, String, int)
	 */
	public static FastCharArrayWriter copy(final InputStream input, final int count) throws IOException {
		return copy(input, encoding(), count);
	}

	/**
	 * @see #copy(InputStream, String, int)
	 */
	public static FastCharArrayWriter copy(final InputStream input, final String encoding) throws IOException {
		return copy(input, encoding, ALL);
	}

	/**
	 * Copies {@link InputStream} to a new {@link FastCharArrayWriter} using buffer and specified encoding.
	 *
	 * @see #copy(InputStream, Writer, String, int)
	 */
	public static FastCharArrayWriter copy(final InputStream input, final String encoding, final int count) throws IOException {
		try (FastCharArrayWriter output = createFastCharArrayWriter()) {
			copy(input, output, encoding, count);
			return output;
		}
	}

	/**
	 * @see #copy(Reader, int)
	 */
	public static FastCharArrayWriter copy(final Reader input) throws IOException {
		return copy(input, ALL);
	}

	/**
	 * Copies {@link Reader} to a new {@link FastCharArrayWriter} using buffer and specified encoding.
	 *
	 * @see #copy(Reader, Writer, int)
	 */
	public static FastCharArrayWriter copy(final Reader input, final int count) throws IOException {
		try (FastCharArrayWriter output = createFastCharArrayWriter()) {
			copy(input, output, count);
			return output;
		}
	}

	/**
	 * Copies data from {@link DataSource} to a new {@link FastCharArrayWriter} and returns this.
	 *
	 * @param input {@link DataSource} to copy from.
	 * @return new {@link FastCharArrayWriter} with data from input.
	 * @see #copy(InputStream)
	 */
	public static FastCharArrayWriter copy(final DataSource input) throws IOException {
		return copy(input.getInputStream());
	}

	// ---------------------------------------------------------------- read bytes

	/**
	 * @see #readBytes(InputStream, int)
	 */
	public static byte[] readBytes(final InputStream input) throws IOException {
		return readBytes(input, ALL);
	}

	/**
	 * @see #copyToOutputStream(InputStream, int)
	 */
	public static byte[] readBytes(final InputStream input, final int count) throws IOException {
		return copyToOutputStream(input, count).toByteArray();
	}

	/**
	 * @see #readBytes(Reader, String)
	 */
	public static byte[] readBytes(final Reader input) throws IOException {
		return readBytes(input, encoding());
	}

	/**
	 * @see #readBytes(Reader, String, int)
	 */
	public static byte[] readBytes(final Reader input, final int count) throws IOException {
		return readBytes(input, encoding(), count);
	}

	/**
	 * @see #readBytes(Reader, String, int)
	 */
	public static byte[] readBytes(final Reader input, final String encoding) throws IOException {
		return readBytes(input, encoding, ALL);
	}

	/**
	 * @see #copyToOutputStream(Reader, String, int)
	 */
	public static byte[] readBytes(final Reader input, final String encoding, final int count) throws IOException {
		return copyToOutputStream(input, encoding, count).toByteArray();
	}

	// ---------------------------------------------------------------- read chars

	/**
	 * @see #readChars(Reader, int)
	 */
	public static char[] readChars(final Reader input) throws IOException {
		return readChars(input, ALL);
	}

	/**
	 * @see #copy(Reader, int)
	 */
	public static char[] readChars(final Reader input, final int count) throws IOException {
		return copy(input, count).toCharArray();
	}

	/**
	 * @see #readChars(InputStream, int)
	 */
	public static char[] readChars(final InputStream input) throws IOException {
		return readChars(input, ALL);
	}

	/**
	 * @see #readChars(InputStream, String, int)
	 */
	public static char[] readChars(final InputStream input, final String encoding) throws IOException {
		return readChars(input, encoding, ALL);
	}

	/**
	 * @see #readChars(InputStream, String, int)
	 */
	public static char[] readChars(final InputStream input, final int count) throws IOException {
		return readChars(input, encoding(), count);
	}

	/**
	 * @see #copy(InputStream, String, int)
	 */
	public static char[] readChars(final InputStream input, final String encoding, final int count) throws IOException {
		return copy(input, encoding, count).toCharArray();
	}

	// ---------------------------------------------------------------- compare content

	/**
	 * Compares the content of two byte streams ({@link InputStream}s).
	 *
	 * @return {@code true} if the content of the first {@link InputStream} is equal
	 * to the content of the second {@link InputStream}.
	 */
	public static boolean compare(InputStream input1, InputStream input2) throws IOException {
		if (!(input1 instanceof BufferedInputStream)) {
			input1 = new BufferedInputStream(input1);
		}
		if (!(input2 instanceof BufferedInputStream)) {
			input2 = new BufferedInputStream(input2);
		}
		int ch = input1.read();
		while (ch != NEGATIVE_ONE) {
			int ch2 = input2.read();
			if (ch != ch2) {
				return false;
			}
			ch = input1.read();
		}
		int ch2 = input2.read();
		return (ch2 == NEGATIVE_ONE);
	}

	/**
	 * Compares the content of two character streams ({@link Reader}s).
	 *
	 * @return {@code true} if the content of the first {@link Reader} is equal
	 * to the content of the second {@link Reader}.
	 */
	public static boolean compare(Reader input1, Reader input2) throws IOException {
		if (!(input1 instanceof BufferedReader)) {
			input1 = new BufferedReader(input1);
		}
		if (!(input2 instanceof BufferedReader)) {
			input2 = new BufferedReader(input2);
		}

		int ch = input1.read();
		while (ch != NEGATIVE_ONE) {
			int ch2 = input2.read();
			if (ch != ch2) {
				return false;
			}
			ch = input1.read();
		}
		int ch2 = input2.read();
		return (ch2 == NEGATIVE_ONE);
	}

	// ---------------------------------------------------------------- defaults

	/**
	 * Returns default IO buffer size.
	 *
	 * @return default IO buffer size.
	 */
	private static int bufferSize() {
		return JoddCore.ioBufferSize;
	}

	/**
	 * Returns either count or default IO buffer size (whichever is smaller).
	 *
	 * @param count Number of characters or bytes to retrieve.
	 * @return buffer size (either count or default IO buffer size, whichever is smaller).
	 */
	private static int bufferSize(final int count) {
		final int ioBufferSize = JoddCore.ioBufferSize;
		if (count < ioBufferSize) {
			return count;
		} else {
			return ioBufferSize;
		}
	}

	/**
	 * Returns default encoding.
	 * @return default encoding.
	 */
	private static String encoding() {
		return JoddCore.encoding;
	}

	// ---------------------------------------------------------------- wrappers

	/**
	 * Returns new {@link FastCharArrayWriter} using default IO buffer size.
	 *
	 * @return new {@link FastCharArrayWriter} using default IO buffer size.
	 */
	private static FastCharArrayWriter createFastCharArrayWriter() {
		return new FastCharArrayWriter(bufferSize());
	}

	/**
	 * Returns new {@link FastByteArrayOutputStream} using default IO buffer size.
	 *
	 * @return new {@link FastByteArrayOutputStream} using default IO buffer size.
	 */
	private static FastByteArrayOutputStream createFastByteArrayOutputStream() {
		return new FastByteArrayOutputStream(bufferSize());
	}

	/**
	 * @see #inputStreamReadeOf(InputStream, String)
	 */
	public static InputStreamReader inputStreamReadeOf(final InputStream input) throws UnsupportedEncodingException {
		return inputStreamReadeOf(input, encoding());
	}

	/**
	 * Returns new {@link InputStreamReader} using specified {@link InputStream} and encoding.
	 *
	 * @param input    {@link InputStream}
	 * @param encoding Encoding as {@link String} to use for {@link InputStreamReader}.
	 * @return new {@link InputStreamReader}
	 * @throws UnsupportedEncodingException if encoding is not valid.
	 */
	public static InputStreamReader inputStreamReadeOf(final InputStream input, final String encoding) throws UnsupportedEncodingException {
		return new InputStreamReader(input, encoding);
	}

	/**
	 * @see #outputStreamWriterOf(OutputStream, String)
	 */
	public static OutputStreamWriter outputStreamWriterOf(final OutputStream output) throws UnsupportedEncodingException {
		return outputStreamWriterOf(output, encoding());
	}

	/**
	 * Returns new {@link OutputStreamWriter} using specified {@link OutputStream} and encoding.
	 *
	 * @param output   {@link OutputStream}
	 * @param encoding Encoding as {@link String} to use for {@link OutputStreamWriter}.
	 * @return new {@link OutputStreamWriter}
	 * @throws UnsupportedEncodingException if encoding is not valid.
	 */
	public static OutputStreamWriter outputStreamWriterOf(final OutputStream output, final String encoding) throws UnsupportedEncodingException {
		return new OutputStreamWriter(output, encoding);
	}
}


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

import jodd.inex.InExRuleMatcher;
import jodd.inex.InExRules;
import jodd.introspector.ClassDescriptor;
import jodd.introspector.ClassIntrospector;
import jodd.introspector.FieldDescriptor;
import jodd.introspector.MethodDescriptor;
import jodd.introspector.PropertyDescriptor;
import jodd.util.StringUtil;

import java.util.ArrayList;
import java.util.Map;
import java.util.Set;

import static jodd.util.StringPool.LEFT_SQ_BRACKET;
import static jodd.util.StringPool.RIGHT_SQ_BRACKET;

/**
 * Visitor for bean properties. It extracts properties names
 * from the source bean and then visits one by one.
 *
 * @see BeanVisitorImplBase
 */
public abstract class BeanVisitor implements InExRuleMatcher<String, String> {

	/**
	 * Source bean.
	 */
	protected Object source;
	/**
	 * Include/exclude rules.
	 */
	protected InExRules<String, String, String> rules = new InExRules<>(this);
	/**
	 * Flag for enabling declared properties, or just public ones.
	 */
	protected boolean declared;
	/**
	 * Defines if null values should be ignored.
	 */
	protected boolean ignoreNullValues;
	/**
	 * Defines if empty string should be ignored.
	 */
	protected boolean ignoreEmptyString;
	/**
	 * Defines if fields should be included.
	 */
	protected boolean includeFields;
	/**
	 * Initial matching mode.
	 */
	protected boolean blacklist = true;
	/**
	 * Indicates the the source is a Map.
	 */
	protected boolean isSourceMap = false;

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

	/**
	 * Returns all bean property names.
	 */
	protected String[] getAllBeanPropertyNames(final Class type, final boolean declared) {
		ClassDescriptor classDescriptor = ClassIntrospector.get().lookup(type);

		PropertyDescriptor[] propertyDescriptors = classDescriptor.getAllPropertyDescriptors();

		ArrayList<String> names = new ArrayList<>(propertyDescriptors.length);

		for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
			MethodDescriptor getter = propertyDescriptor.getReadMethodDescriptor();
			if (getter != null) {
				if (getter.matchDeclared(declared)) {
					names.add(propertyDescriptor.getName());
				}
			}
			else if (includeFields) {
				FieldDescriptor field = propertyDescriptor.getFieldDescriptor();
				if (field != null) {
					if (field.matchDeclared(declared)) {
						names.add(field.getName());
					}
				}
			}
		}

		return names.toArray(new String[0]);
	}

	/**
	 * Returns an array of bean properties. If bean is a Map,
	 * all its keys will be returned.
	 */
	protected String[] resolveProperties(final Object bean, final boolean declared) {
		String[] properties;

		if (bean instanceof Map) {
			Set keys = ((Map) bean).keySet();

			properties = new String[keys.size()];
			int ndx = 0;
			for (Object key : keys) {
				properties[ndx] = key.toString();
				ndx++;
			}
		} else {
			properties = getAllBeanPropertyNames(bean.getClass(), declared);
		}

		return properties;
	}

	/**
	 * Starts visiting properties.
	 */
	public void visit() {
		String[] properties = resolveProperties(source, declared);

		for (String name : properties) {
			if (name == null) {
				continue;
			}

			if (!rules.match(name, blacklist)) {
				continue;
			}

			Object value;

			String propertyName = name;

			if (isSourceMap) {
				propertyName = LEFT_SQ_BRACKET + name + RIGHT_SQ_BRACKET;
			}

			if (declared) {
				value = BeanUtil.declared.getProperty(source, propertyName);
			} else {
				value = BeanUtil.pojo.getProperty(source, propertyName);
			}

			if (value == null && ignoreNullValues) {
				continue;
			}

			if (value instanceof String && StringUtil.isEmpty((String) value)) {
				continue;
			}

			visitProperty(name, value);
		}
	}

	/**
	 * Invoked for each visited property. Returns true if
	 * visiting should continue, otherwise false to stop.
	 */
	protected abstract boolean visitProperty(String name, Object value);

	/**
	 * Compares property name to the rules.
	 */
	@Override
	public boolean accept(final String propertyName, final String rule, final boolean include) {
		return propertyName.equals(rule);
	}
}


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

package jodd.introspector;

import jodd.cache.TypeCache;

/**
 * Default {@link jodd.introspector.ClassIntrospector introspector} that caches all class descriptors.
 * It can examine either accessible or supported fields/methods/constructors.
 *

 * It simply caches all class descriptors.
 */
public class CachingIntrospector implements ClassIntrospector {

	protected final TypeCache<ClassDescriptor> cache;
	protected final boolean scanAccessible;
	protected final boolean enhancedProperties;
	protected final boolean includeFieldsAsProperties;
	protected final String[] propertyFieldPrefix;

	/**
	 * Default constructor.
	 */
	public CachingIntrospector() {
		this(true, true, true, null);
	}

	/**
	 * Creates new caching {@link ClassIntrospector}. It may scan
	 * accessible or supported fields, methods or
	 * constructors.
	 */
	public CachingIntrospector(final boolean scanAccessible, final boolean enhancedProperties, final boolean includeFieldsAsProperties, final String[] propertyFieldPrefix) {
		this.cache = TypeCache.createDefault();
		this.scanAccessible = scanAccessible;
		this.enhancedProperties = enhancedProperties;
		this.includeFieldsAsProperties = includeFieldsAsProperties;
		this.propertyFieldPrefix = propertyFieldPrefix;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public ClassDescriptor lookup(final Class type) {
		return cache.get(type, () ->
			new ClassDescriptor(
				type,
				scanAccessible,
				enhancedProperties,
				includeFieldsAsProperties,
				propertyFieldPrefix));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void reset() {
		cache.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.petite.resolver;

import jodd.introspector.ClassDescriptor;
import jodd.introspector.ClassIntrospector;
import jodd.introspector.MethodDescriptor;
import jodd.petite.def.BeanReferences;
import jodd.petite.def.MethodInjectionPoint;
import jodd.util.ClassUtil;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * Methods injection points resolver.
 */
public class MethodResolver {

	protected final ReferencesResolver referencesResolver;

	public MethodResolver(final ReferencesResolver referencesResolver) {
		this.referencesResolver = referencesResolver;
	}

	/**
	 * Resolve method injection points in given class.
	 */
	public MethodInjectionPoint[] resolve(final Class type) {
		// lookup methods
		ClassDescriptor cd = ClassIntrospector.get().lookup(type);
		List<MethodInjectionPoint> list = new ArrayList<>();
		MethodDescriptor[] allMethods = cd.getAllMethodDescriptors();

		for (MethodDescriptor methodDescriptor : allMethods) {
			Method method = methodDescriptor.getMethod();

			if (ClassUtil.isBeanPropertySetter(method)) {
				// ignore setters
				continue;
			}

			if (method.getParameterTypes().length == 0) {
				// ignore methods with no argument
				continue;
			}

			BeanReferences[] references = referencesResolver.readAllReferencesFromAnnotation(method);

			if (references != null) {
				MethodInjectionPoint methodInjectionPoint = new MethodInjectionPoint(method, references);

				list.add(methodInjectionPoint);
			}
		}

		final MethodInjectionPoint[] methodInjectionPoints;

		if (list.isEmpty()) {
			methodInjectionPoints = MethodInjectionPoint.EMPTY;
		} else {
			methodInjectionPoints = list.toArray(new MethodInjectionPoint[0]);
		}

		return methodInjectionPoints;
	}

}


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

package jodd.htmlstapler;

import jodd.bean.BeanUtil;
import jodd.io.StreamUtil;
import jodd.lagarto.TagVisitor;
import jodd.lagarto.TagWriter;
import jodd.lagarto.adapter.StripHtmlTagAdapter;
import jodd.lagarto.filter.SimpleLagartoServletFilter;
import jodd.log.Logger;
import jodd.log.LoggerFactory;
import jodd.servlet.DispatcherUtil;
import jodd.servlet.ServletUtil;
import jodd.util.StringPool;
import jodd.time.TimeUtil;
import jodd.net.MimeTypes;

import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

import static jodd.htmlstapler.HtmlStaplerBundlesManager.Strategy;

/**
 * HtmlStapler filter.
 * Part of the parameters are here, the other part is in the
 * {@link #createBundleManager(javax.servlet.ServletContext, jodd.htmlstapler.HtmlStaplerBundlesManager.Strategy)}  bundle manager}.
 */
public class HtmlStaplerFilter extends SimpleLagartoServletFilter {

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

	protected HtmlStaplerBundlesManager bundlesManager;

	protected boolean enabled = true;
	protected boolean stripHtml = true;
	protected boolean resetOnStart = true;
	protected boolean useGzip;
	protected int cacheMaxAge = TimeUtil.SECONDS_IN_DAY * 30;
	protected Strategy staplerStrategy = Strategy.RESOURCES_ONLY;

	@Override
	public void init(final FilterConfig filterConfig) throws ServletException {
		super.init(filterConfig);

		bundlesManager = createBundleManager(filterConfig.getServletContext(), staplerStrategy);

		readFilterConfigParameters(filterConfig, this,
				"enabled",
				"stripHtml",
				"resetOnStart",
				"useGzip",
				"cacheMaxAge"
		);

		String staplerStrategyName = filterConfig.getInitParameter("strategy");
		if (staplerStrategyName != null) {
			if (staplerStrategyName.equalsIgnoreCase("ACTION_MANAGED")) {
				staplerStrategy = Strategy.ACTION_MANAGED;
			}
		}

		readFilterConfigParameters(filterConfig, bundlesManager,
				"bundleFolder",
				"downloadLocal",
				"localAddressAndPort",
				"localFilesEncoding",
				"notFoundExceptionEnabled",
				"sortResources",
				"staplerPath",
				"randomDigestChars"
		);

		if (resetOnStart) {
			bundlesManager.reset();
		}
	}

	/**
	 * Reads filter config parameters and set into destination target.
	 */
	protected void readFilterConfigParameters(final FilterConfig filterConfig, final Object target, final String... parameters) {
		for (String parameter : parameters) {
			String value = filterConfig.getInitParameter(parameter);

			if (value != null) {
				BeanUtil.declared.setProperty(target, parameter, value);
			}
		}
	}

	/**
	 * Creates {@link HtmlStaplerBundlesManager} instance.
	 */
	protected HtmlStaplerBundlesManager createBundleManager(final ServletContext servletContext, final Strategy strategy) {
		String webRoot = servletContext.getRealPath(StringPool.EMPTY);

		String contextPath = ServletUtil.getContextPath(servletContext);

		return new HtmlStaplerBundlesManager(contextPath, webRoot, strategy);
	}

	@Override
	protected LagartoParsingProcessor createParsingProcessor() {
		if (!enabled) {
			return null;
		}

		return new LagartoParsingProcessor() {
			@Override
			protected char[] parse(final TagWriter rootTagWriter, final HttpServletRequest request) {

				TagVisitor visitor = rootTagWriter;

				if (stripHtml) {

					visitor = new StripHtmlTagAdapter(rootTagWriter) {
						@Override
						public void end() {
							super.end();
							if (log.isDebugEnabled()) {
								log.debug("Stripped: " + getStrippedCharsCount() + " chars");
							}
						}
					};
				}

				String servletPath = DispatcherUtil.getServletPath(request);

				HtmlStaplerTagAdapter htmlStaplerTagAdapter =
						new HtmlStaplerTagAdapter(bundlesManager, servletPath, visitor);

				// todo add more adapters

				char[] content = invokeLagarto(htmlStaplerTagAdapter);

				return htmlStaplerTagAdapter.postProcess(content);
			}
		};
	}

	@Override
	protected boolean processActionPath(final HttpServletRequest servletRequest, final HttpServletResponse servletResponse, final String actionPath) throws IOException {

		String bundlePath = '/' + bundlesManager.getStaplerPath() + '/';

		if (!actionPath.startsWith(bundlePath)) {
			return false;
		}

		String bundleId = actionPath.substring(bundlePath.length());

		File file = bundlesManager.lookupBundleFile(bundleId);

		if (log.isDebugEnabled()) {
			log.debug("bundle: " + bundleId);
		}

		int ndx = bundleId.lastIndexOf('.');
		String extension = bundleId.substring(ndx + 1);

		String contentType = MimeTypes.getMimeType(extension);
		servletResponse.setContentType(contentType);

		if (useGzip && ServletUtil.isGzipSupported(servletRequest)) {
			file = bundlesManager.lookupGzipBundleFile(file);

			servletResponse.setHeader("Content-Encoding", "gzip");
		}

		if (!file.exists()) {
			throw new IOException("bundle not found: " + bundleId);
		}

		servletResponse.setHeader("Content-Length", String.valueOf(file.length()));
		servletResponse.setHeader("Last-Modified", TimeUtil.formatHttpDate(file.lastModified()));

		if (cacheMaxAge > 0) {
			servletResponse.setHeader("Cache-Control", "max-age=" + cacheMaxAge);
		}

		sendBundleFile(servletResponse, file);

		return true;
	}

	/**
	 * Outputs bundle file to the response.
	 */
	protected void sendBundleFile(final HttpServletResponse resp, final File bundleFile) throws IOException {
		OutputStream out = resp.getOutputStream();
		FileInputStream fileInputStream = new FileInputStream(bundleFile);
		try {
			StreamUtil.copy(fileInputStream, out);
		}
		finally {
			StreamUtil.close(fileInputStream);
		}
	}

}


// 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.db.oom.meta.DbTable;
import jodd.io.findfile.ClassScanner;
import jodd.log.Logger;
import jodd.log.LoggerFactory;
import jodd.util.function.Consumers;

import java.util.function.Consumer;

/**
 * Auto-magically scans classpath for domain objects annotated with DbOom annotations.
 */
public class AutomagicDbOomConfigurator {

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

	protected final static byte[] DB_TABLE_ANNOTATION_BYTES = ClassScanner.bytecodeSignatureOfType(DbTable.class);
	protected final boolean registerAsEntities;
	protected final DbEntityManager dbEntityManager;
	private final Consumers<ClassScanner> classScannerConsumers = new Consumers<>();

	public AutomagicDbOomConfigurator(final DbEntityManager dbEntityManager, final boolean registerAsEntities) {
		this.dbEntityManager = dbEntityManager;
		this.registerAsEntities = registerAsEntities;
	}

	public AutomagicDbOomConfigurator withScanner(final Consumer<ClassScanner> scannerConsumer) {
		classScannerConsumers.add(scannerConsumer);
		return this;
	}

	/**
	 * Configures {@link DbEntityManager} with specified class path.
	 */
	public void configure() {
		long elapsed = System.currentTimeMillis();

		final ClassScanner classScanner = new ClassScanner();

		classScanner.detectEntriesMode(true);
		classScanner.scanDefaultClasspath();

		classScannerConsumers.accept(classScanner);

		registerAsConsumer(classScanner);

		try {
			classScanner.start();
		} catch (Exception ex) {
			throw new DbOomException("Scan classpath error", ex);
		}
		elapsed = System.currentTimeMillis() - elapsed;
		if (log.isInfoEnabled()) {
			log.info("DbEntityManager configured in " + elapsed + "ms. Total entities: " + dbEntityManager.getTotalNames());
		}
	}

	/**
	 * Registers a class consumer that registers only those annotated with {@link DbTable}.
	 * Because of performance purposes, classes are not dynamically loaded; instead, their
	 * file content is examined.
	 */
	public void registerAsConsumer(final ClassScanner classScanner) {
		classScanner.registerEntryConsumer(classPathEntry -> {
			if (!classPathEntry.isTypeSignatureInUse(DB_TABLE_ANNOTATION_BYTES)) {
				return;
			}

			final Class<?> beanClass;
			try {
				beanClass = classPathEntry.loadClass();
			} catch (ClassNotFoundException cnfex) {
				throw new DbOomException("Entry class not found: " + classPathEntry.name(), cnfex);
			}

			if (beanClass == null) {
				return;
			}

			final DbTable dbTable = beanClass.getAnnotation(DbTable.class);

			if (dbTable == null) {
				return;
			}

			if (registerAsEntities) {
				dbEntityManager.registerEntity(beanClass);
			} else {
				dbEntityManager.registerType(beanClass);
			}
		});
	}

}


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

package jodd.htmlstapler;

import jodd.crypt.DigestEngine;
import jodd.io.FileNameUtil;
import jodd.io.FileUtil;
import jodd.io.NetUtil;
import jodd.io.ZipUtil;
import jodd.io.findfile.FindFile;
import jodd.log.Logger;
import jodd.log.LoggerFactory;
import jodd.system.SystemUtil;
import jodd.util.Base32;
import jodd.util.CharUtil;
import jodd.util.RandomString;
import jodd.util.StringBand;
import jodd.util.StringPool;
import jodd.util.StringUtil;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * HTML resources bundles manager.
 */
public class HtmlStaplerBundlesManager {

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

	protected int bundleCount;		// counter for new bundles

	protected Map<String, String> actionBundles; 			// action -> bundleId/digest
	protected Map<String, String> mirrors;					// temp id -> bundleId

	protected final String webRoot;
	protected final String contextPath;
	protected final Strategy strategy;

	// parameters
	protected String localFilesEncoding = StringPool.UTF_8;
	protected String bundleFolder;
	protected String staplerPath = "jodd-bundle";
	protected String localAddressAndPort = "http://localhost:8080";
	protected boolean downloadLocal;
	protected boolean sortResources;
	protected boolean notFoundExceptionEnabled = true;
	protected int randomDigestChars = 0;

	private static String uniqueDigestKey;

	// ---------------------------------------------------------------- strategy

	public enum Strategy {

		/**
		 * For each action manager stores the bundle id.
		 * This gives top performances, as the links are collected only
		 * once per page and bundle id (digest) is calculated only once
		 * as well, only first time page is accessed. However, if there
		 * are dynamic REST-alike links, there might be a large or infinite
		 * number of action links in the application. This can be handled
		 * in the code by overriding method resolveRealActionPath().
		 */
		ACTION_MANAGED,

		/**
		 * Pragmatic strategy that collects all links and builds  bundle id
		 * (digest) every time when page is processed. This gives slightly
		 * slower performances, but there is no additional memory consumption.
		 */
		RESOURCES_ONLY
	}

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

	/**
	 * Creates new instance and initialize it.
	 */
	public HtmlStaplerBundlesManager(final String contextPath, final String webRoot, final Strategy strategy) {
		this.contextPath = contextPath;
		this.webRoot = webRoot;
		this.strategy = strategy;
		this.bundleFolder = SystemUtil.info().getTempDir();

		if (strategy == Strategy.ACTION_MANAGED) {
			actionBundles = new HashMap<>();
			mirrors = new HashMap<>();
		}
	}

	/**
	 * Starts bundle usage by creating new {@link BundleAction}.
	 */
	public BundleAction start(final String servletPath, final String bundleName) {
		return new BundleAction(this, servletPath, bundleName);
	}

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

	/**
	 * Returns current {@link Strategy strategy}.
	 */
	public Strategy getStrategy() {
		return strategy;
	}

	/**
	 * Returns true if resources are sorted before
	 * bundle id (a digest) is created. When sorting is enabled,
	 * two pages will share the same bundle even if they list
	 * resources in different order.
	 */
	public boolean isSortResources() {
		return sortResources;
	}

	/**
	 * Sets the resources sorting before bundle id (i.e. a digest)
	 * is created.
	 */
	public void setSortResources(final boolean sortResources) {
		this.sortResources = sortResources;
	}

	/**
	 * Returns current web root.
	 */
	public String getWebRoot() {
		return webRoot;
	}

	/**
	 * Returns bundles folder. By default, it is a system temp folder.
	 */
	public String getBundleFolder() {
		return bundleFolder;
	}

	/**
	 * Sets bundle folder.
	 */
	public void setBundleFolder(final String bundleFolder) {
		this.bundleFolder = bundleFolder;
	}

	/**
	 * Returns stapler path. It is both the file system folder
	 * name and the web folder name.
	 */
	public String getStaplerPath() {
		return staplerPath;
	}

	/**
	 * Sets stapler path.
	 */
	public void setStaplerPath(final String staplerPath) {
		this.staplerPath = staplerPath;
	}

	/**
	 * Returns local files encoding. By default its UTF8.
	 */
	public String getLocalFilesEncoding() {
		return localFilesEncoding;
	}

	/**
	 * Sets local files encoding.
	 */
	public void setLocalFilesEncoding(final String localFilesEncoding) {
		this.localFilesEncoding = localFilesEncoding;
	}

	/**
	 * Returns local address and port for downloading
	 * local resources.
	 */
	public String getLocalAddressAndPort() {
		return localAddressAndPort;
	}

	/**
	 * Specifies local address and port for downloading
	 * local resources. By default its "http://localhost:8080".
	 */
	public void setLocalAddressAndPort(final String localAddressAndPort) {
		this.localAddressAndPort = localAddressAndPort;
	}

	/**
	 * Returns true if local resource files are downloaded
	 * and not loaded from file system.
	 */
	public boolean isDownloadLocal() {
		return downloadLocal;
	}

	/**
	 * Sets if local resource files should be downloaded or loaded from file system.
	 */
	public void setDownloadLocal(final boolean downloadLocal) {
		this.downloadLocal = downloadLocal;
	}

	/**
	 * Returns true if exception will be thrown when
	 * resource is not found.
	 */
	public boolean isNotFoundExceptionEnabled() {
		return notFoundExceptionEnabled;
	}

	/**
	 * Sets if exception should be thrown when some resource is not found.
	 * If not enabled, the error will be logged as a warning.
	 */
	public void setNotFoundExceptionEnabled(final boolean notFoundExceptionEnabled) {
		this.notFoundExceptionEnabled = notFoundExceptionEnabled;
	}

	/**
	 * Returns the number of random digest chars.
	 */
	public int getRandomDigestChars() {
		return randomDigestChars;
	}

	/**
	 * Sets the number of random characters that will be appended to the
	 * {@link #createDigest(String) digest}. When it is set to 0, nothing
	 * will be appended to the digest. Otherwise, a random string will be
	 * generated (containing only letters and digits) and appended to the
	 * digest.
	 *

	 * Random digest chars is a unique key per one VM!
	 * This key is initialized only once.
	 * This is useful to automatically expire any cache that browsers may have in
	 * JS and CSS files, so that changes in those files will be downloaded by the
	 * browser.
	 */
	public void setRandomDigestChars(final int randomDigestChars) {
		this.randomDigestChars = randomDigestChars;

		if (randomDigestChars == 0) {
			uniqueDigestKey = null;
		}
		else {
			uniqueDigestKey = new RandomString().randomAlphaNumeric(randomDigestChars);
		}
	}

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

	/**
	 * Creates bundle file in bundleFolder/staplerPath. Only file object
	 * is created, not the file content.
	 */
	protected File createBundleFile(final String bundleId) {
		File folder = new File(bundleFolder, staplerPath);
		if (!folder.exists()) {
			folder.mkdirs();
		}
		return new File(folder, bundleId);
	}

	/**
	 * Lookups for bundle file.
	 */
	public File lookupBundleFile(String bundleId) {
		if ((mirrors != null) && (!mirrors.isEmpty())) {
			String realBundleId = mirrors.remove(bundleId);

			if (realBundleId != null) {
				bundleId = realBundleId;
			}
		}
		return createBundleFile(bundleId);
	}

	/**
	 * Locates gzipped version of bundle file. If gzip file
	 * does not exist, it will be created.
	 */
	public File lookupGzipBundleFile(final File file) throws IOException {
		String path = file.getPath() + ZipUtil.GZIP_EXT;
		File gzipFile = new File(path);

		if (!gzipFile.exists()) {
			if (log.isDebugEnabled()) {
				log.debug("gzip bundle to " + path);
			}
			ZipUtil.gzip(file);
		}

		return gzipFile;
	}

	/**
	 * Lookups for a bundle id for a given action.
	 * Returns null if action still has no bundle.
	 * Returns an empty string if action has an empty bundle.
	 */
	public String lookupBundleId(final String actionPath) {
		return actionBundles.get(actionPath);
	}

	// ---------------------------------------------------------------- register

	/**
	 * Registers new, temporary bundle id for given action path.
	 * This id is used on first bundle usage, later it will be replaces with
	 * real bundle id.
	 */
	public String registerNewBundleId() {
		bundleCount++;
		return String.valueOf(bundleCount);
	}

	/**
	 * Registers new bundle that consist of provided list of source paths.
	 * Returns the real bundle id, as provided one is just a temporary bundle id.
	 */
	public synchronized String registerBundle(final String contextPath, final String actionPath, final String tempBundleId, final String bundleContentType, final List<String> sources) {

		if (tempBundleId == null || sources.isEmpty()) {
			if (strategy == Strategy.ACTION_MANAGED) {
				// page does not include any resource source file
				actionBundles.put(actionPath, StringPool.EMPTY);
			}
			return null;
		}

		// create unique digest from the collected sources
		String[] sourcesArray = sources.toArray(new String[0]);
		for (int i = 0, sourcesArrayLength = sourcesArray.length; i < sourcesArrayLength; i++) {
			sourcesArray[i] = sourcesArray[i].trim().toLowerCase();
		}
		if (sortResources) {
			Arrays.sort(sourcesArray);
		}

		StringBand sb = new StringBand(sourcesArray.length);
		for (String src : sourcesArray) {
			sb.append(src);
		}
		String sourcesString = sb.toString();

		String bundleId = createDigest(sourcesString);
		bundleId += '.' + bundleContentType;

		// bundle appears for the first time, create the bundle
		if (strategy == Strategy.ACTION_MANAGED) {
			actionBundles.put(actionPath, bundleId);
			mirrors.put(tempBundleId, bundleId);
		}
		try {
			createBundle(contextPath, actionPath, bundleId, sources);
		} catch (IOException ioex) {
			throw new HtmlStaplerException("Can't create bundle", ioex);
		}
		return bundleId;
	}

	/**
	 * Creates digest i.e. bundle id from given string.
	 * Returned digest must be filename safe, for all platforms.
	 */
	protected String createDigest(final String source) {
		final DigestEngine digestEngine = DigestEngine.sha256();

		final byte[] bytes = digestEngine.digest(CharUtil.toSimpleByteArray(source));

		String digest = Base32.encode(bytes);

		if (uniqueDigestKey != null) {
			digest += uniqueDigestKey;
		}

		return digest;
	}

	/**
	 * Creates bundle file by loading resource files content. If bundle file already
	 * exist it will not be recreated!
	 */
	protected void createBundle(final String contextPath, final String actionPath, final String bundleId, final List<String>sources) throws IOException {
		final File bundleFile = createBundleFile(bundleId);
		if (bundleFile.exists()) {
			return;
		}

		StringBand sb = new StringBand(sources.size() * 2);
		for (String src : sources) {
			if (sb.length() != 0) {
				sb.append(StringPool.NEWLINE);
			}
			String content;
			if (isExternalResource(src)) {
				content = downloadString(src);
			} else {
				if (!downloadLocal) {
					// load local resource from file system
					String localFile = webRoot;

					if (src.startsWith(contextPath + '/')) {
						src = src.substring(contextPath.length());
					}

					if (src.startsWith(StringPool.SLASH)) {
						// absolute path
						localFile += src;
					} else {
						// relative path
						localFile += '/' + FileNameUtil.getPathNoEndSeparator(actionPath) + '/' + src;
					}

					// trim link parameters, if any
					int qmndx = localFile.indexOf('?');
					if (qmndx != -1) {
						localFile = localFile.substring(0, qmndx);
					}

					try {
						content = FileUtil.readString(localFile);
					} catch (IOException ioex) {
						if (notFoundExceptionEnabled) {
							throw ioex;
						}
						if (log.isWarnEnabled()) {
							log.warn(ioex.getMessage());
						}
						content = null;
					}
				} else {
					// download local resource
					String localUrl = localAddressAndPort;

					if (src.startsWith(StringPool.SLASH)) {
						localUrl += contextPath + src;
					} else {
						localUrl += contextPath + FileNameUtil.getPath(actionPath) + '/' + src;
					}

					content = downloadString(localUrl);
				}

				if (content != null) {
					if (isCssResource(src)) {
						content = fixCssRelativeUrls(content, src);
					}
				}
			}

			if (content != null) {
				content = onResourceContent(content);
				sb.append(content);
			}
		}

		FileUtil.writeString(bundleFile, sb.toString());

		if (log.isInfoEnabled()) {
			log.info("Bundle created: " + bundleId);
		}
	}

	private String downloadString(final String localUrl) throws IOException {
		String content;
		try {
			content = NetUtil.downloadString(localUrl, localFilesEncoding);
		} catch (IOException ioex) {
			if (notFoundExceptionEnabled) {
				throw ioex;
			}
			if (log.isWarnEnabled()) {
				log.warn("Download failed: " + localUrl + "; " + ioex.getMessage());
			}
			content = null;
		}
		return content;
	}

	/**
	 * Returns true if resource link has to be downloaded.
	 * By default, if resource link starts with "http://" or with "https://"
	 * it will be considered as external resource.
	 */
	protected boolean isExternalResource(final String link) {
		return link.startsWith("http://") || (link.startsWith("https://"));
	}

	/**
	 * Invoked before resource content is stored in the bundle.
	 * May be us used for additional resource processing, such as
	 * compressing, cleaning etc. By default it just returns unmodified
	 * content.
	 */
	protected String onResourceContent(final String content) {
		return content;
	}

	// ---------------------------------------------------------------- url rewriting

	/**
	 * Resolves real action path for given one.
	 * When some URLs are dynamically created, many different links points
	 * to the same page. Use this to prevent memory leaking.
	 */
	protected String resolveRealActionPath(final String actionPath) {
		return actionPath;
	}

	// ---------------------------------------------------------------- reset

	/**
	 * Clears all settings and removes all created bundle files from file system.
	 */
	public synchronized void reset() {
		if (strategy == Strategy.ACTION_MANAGED) {
			actionBundles.clear();
			mirrors.clear();
		}

		final FindFile ff = new FindFile();
		ff.includeDirs(false);
		ff.searchPath(new File(bundleFolder, staplerPath));

		File f;
		int count = 0;
		while ((f = ff.nextFile()) != null) {
			f.delete();
			count++;
		}
		if (log.isInfoEnabled()) {
			log.info("reset: " + count + " bundle files deleted.");
		}
	}

	// ---------------------------------------------------------------- css related

	/**
	 * Returns true if resource is CSS, so the
	 * CSS urls can be fixed.
	 */
	protected boolean isCssResource(final String src) {
		return src.endsWith(".css");
	}

	private static final Pattern CSS_URL_PATTERN = Pattern.compile("url\\s*\\(\\s*([^\\)\\s]*)\\s*\\)", Pattern.CASE_INSENSITIVE);

	/**
	 * Returns the content with all relative URLs fixed.
	 */
	protected String fixCssRelativeUrls(final String content, final String src) {
		final String path = FileNameUtil.getPath(src);

		final Matcher matcher = CSS_URL_PATTERN.matcher(content);

		final StringBuilder sb = new StringBuilder(content.length());

		int start = 0;

		while (matcher.find()) {
			sb.append(content, start, matcher.start());

			final String matchedUrl = StringUtil.removeChars(matcher.group(1), "'\"");

			final String url;
			if (matchedUrl.startsWith("https://") || matchedUrl.startsWith("http://") || matchedUrl.startsWith("data:")) {
				url = "url('" + matchedUrl + "')";
			}
			else {
				url = fixRelativeUrl(matchedUrl, path);
			}

			sb.append(url);

			start = matcher.end();
		}

		sb.append(content.substring(start));

		return sb.toString();
	}

	/**
	 * For a given URL (optionally quoted), produces CSS URL
	 * where relative paths are fixed and prefixed with offsetPath.
	 */
	protected String fixRelativeUrl(final String url, final String offsetPath) {
		final StringBuilder res = new StringBuilder();
		res.append("url('");

		if (!url.startsWith(StringPool.SLASH)) {
			res
				.append("../")
				.append(offsetPath);
		}

		res.append(url).append("')");

		return res.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.db.oom.sqlgen;

import jodd.bean.BeanUtil;
import jodd.db.DbOom;
import jodd.db.oom.DbEntityDescriptor;
import jodd.db.oom.DbEntityManager;
import jodd.db.oom.DbOomConfig;
import jodd.util.StringPool;

import static jodd.db.oom.sqlgen.DbSqlBuilder.sql;
import static jodd.util.StringPool.EQUALS;
import static jodd.util.StringPool.SPACE;
import static jodd.util.StringUtil.capitalize;
import static jodd.util.StringUtil.uncapitalize;

/**
 * Useful {@link DbSqlBuilder} generators.
 */
public class DbEntitySql {

	private static final String DELETE_FROM = "delete from ";
	private static final String WHERE = " where ";
	private static final String UPDATE = "update ";
	private static final String SELECT = "select ";
	private static final String FROM = " from ";
	private static final String SET = " set ";
	private static final String SELECT_COUNT_1_FROM = "select count(*) from ";

	private final DbEntityManager entityManager;
	private final DbOomConfig dbOomConfig;

	public DbEntitySql(final DbOom dbOom) {
		this.entityManager = dbOom.entityManager();
		this.dbOomConfig = dbOom.config();
	}

	public DbEntitySql() {
		this(DbOom.get());
	}

	// ---------------------------------------------------------------- insert

	/**
	 * Creates INSERT query for the entity.
	 */
	public DbSqlBuilder insert(final Object entity) {
		return sql().insert(entity);
	}

	// ---------------------------------------------------------------- truncate

	/**
	 * Creates DELETE query that truncates all table data.
	 */
	public DbSqlBuilder truncate(final Object entity) {
		return sql().$(DELETE_FROM).table(entity, null);
	}

	// ---------------------------------------------------------------- update

	/**
	 * Creates UPDATE query that updates all non-null values of an entity that is matched by id.
	 */
	public DbSqlBuilder update(final Object entity) {
		String tableRef = createTableRefName(entity);

		if (!dbOomConfig.isUpdateAcceptsTableAlias()) {
			tableRef = null;
		}

		return sql().$(UPDATE).table(entity, tableRef).set(tableRef, entity).$(WHERE).matchIds(tableRef, entity);
	}

	/**
	 * Creates UPDATE query that updates all values of an entity that is matched by id.
	 */
	public DbSqlBuilder updateAll(final Object entity) {
		String tableRef = createTableRefName(entity);

		if (!dbOomConfig.isUpdateAcceptsTableAlias()) {
			tableRef = null;
		}

		return sql().$(UPDATE).table(entity, tableRef).setAll(tableRef, entity).$(WHERE).matchIds(tableRef, entity);
	}

	/**
	 * Creates UPDATE query for single column of an entity that is matched by id.
	 */
	public DbSqlBuilder updateColumn(final Object entity, final String columnRef, final Object value) {
		String tableRef = createTableRefName(entity);

		if (!dbOomConfig.isUpdateAcceptsTableAlias()) {
			tableRef = null;
		}

		return sql().$(UPDATE).table(entity, tableRef).$(SET).ref(null, columnRef).$(EQUALS).columnValue(value).$(WHERE).matchIds(tableRef, entity);
	}

	/**
	 * Reads property value and updates the DB.
	 */
	public DbSqlBuilder updateColumn(final Object entity, final String columnRef) {
		final Object value = BeanUtil.pojo.getProperty(entity, columnRef);
		return updateColumn(entity, columnRef, value);
	}

	// ---------------------------------------------------------------- delete

	/**
	 * Creates DELETE query that deletes entity matched by non-null values.
	 */
	public DbSqlBuilder delete(final Object entity) {
		final String tableRef = createTableRefName(entity);
		return sql().$(DELETE_FROM).table(entity, null, tableRef).$(WHERE).match(tableRef, entity);
	}

	/**
	 * Creates DELETE query that deletes entity matched by all values.
	 */
	public DbSqlBuilder deleteByAll(final Object entity) {
		final String tableRef = createTableRefName(entity);
		return sql().$(DELETE_FROM).table(entity, null, tableRef).$(WHERE).matchAll(tableRef, entity);
	}

	// ---------------------------------------------------------------- delete by id

	/**
	 * Creates DELETE query that deletes entity by ID.
	 */
	public DbSqlBuilder deleteById(final Object entity) {
		final String tableRef = createTableRefName(entity);
		return sql().$(DELETE_FROM).table(entity, null, tableRef).$(WHERE).matchIds(tableRef, entity);
	}

	/**
	 * Creates DELETE query that deletes entity by ID.
	 */
	public DbSqlBuilder deleteById(final Object entityType, final Object id) {
		final String tableRef = createTableRefName(entityType);
		return sql().
			$(DELETE_FROM).table(entityType, null, tableRef).
			$(WHERE).refId(tableRef).$(EQUALS).columnValue(id);
	}

	// ---------------------------------------------------------------- from

	/**
	 * Creates 'SELECT all FROM entity' part of the SQL query that can be easily extended.
	 * Entity is referred with its simple class name.
	 */
	public DbSqlBuilder from(final Object entity) {
		return from(entity, createTableRefName(entity));
	}

	public DbSqlBuilder from(final Object entity, final String tableRef) {
		return sql().$(SELECT).column(tableRef).$(FROM).table(entity, tableRef).$(SPACE);
	}

	public DbSqlBuilder from(final Class entityType) {
		return from(entityType, createTableRefName(entityType));
	}

	public DbSqlBuilder from(final Class entityType, final String tableRef) {
		return sql().$(SELECT).column(tableRef).$(FROM).table(entityType, tableRef).$(SPACE);
	}

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

	/**
	 * Creates SELECT criteria for the entity matched by non-null values.
	 */
	public DbSqlBuilder find(final Class target, final Object matchEntity) {
		final String tableRef = createTableRefName(target);
		return sql().$(SELECT).column(tableRef).$(FROM).table(target, tableRef).$(WHERE).match(tableRef, matchEntity);
	}

	/**
	 * Creates SELECT criteria for the entity matched by non-null values.
	 */
	public DbSqlBuilder find(final Object entity) {
		final String tableRef = createTableRefName(entity);
		return sql().$(SELECT).column(tableRef).$(FROM).table(entity, tableRef).$(WHERE).match(tableRef, entity);
	}

	/**
	 * Creates SELECT criteria for the entity matched by all values.
	 */
	public DbSqlBuilder findByAll(final Object entity) {
		final String tableRef = createTableRefName(entity);
		return sql().$(SELECT).column(tableRef).$(FROM).table(entity, tableRef).$(WHERE).matchAll(tableRef, entity);
	}

	/**
	 * Creates SELECT criteria for the entity matched by column name
	 */
	public DbSqlBuilder findByColumn(final Class entity, final String column, final Object value) {
		final String tableRef = createTableRefName(entity);
		return sql().$(SELECT).column(tableRef).$(FROM).table(entity, tableRef).$(WHERE).ref(tableRef, column).$(EQUALS).columnValue(value);
	}

	/**
	 * Creates SELECT criteria for the entity matched by foreign key.
	 * Foreign key is created by concatenating foreign table name and column name.
	 */
	public DbSqlBuilder findForeign(final Class entity, final Object value) {
		final String tableRef = createTableRefName(entity);

		final DbEntityDescriptor dedFk = entityManager.lookupType(value.getClass());

		final String tableName = dbOomConfig.getTableNames().convertTableNameToEntityName(dedFk.getTableName());
		final String columnName = dbOomConfig.getColumnNames().convertColumnNameToPropertyName(dedFk.getIdColumnName());

		final String fkColumn = uncapitalize(tableName) + capitalize(columnName);
		final Object idValue = BeanUtil.pojo.getProperty(value, dedFk.getIdPropertyName());
		return sql().$(SELECT).column(tableRef).$(FROM).table(entity, tableRef).$(WHERE).ref(tableRef, fkColumn).$(EQUALS).columnValue(idValue);
	}

	// ---------------------------------------------------------------- ALL

	/**
	 * Returns all records for given type.
	 */
	public DbSqlBuilder findAll(final Object entity) {
		final String tableRef = createTableRefName(entity);
		return sql().$(SELECT).column(tableRef).$(FROM).table(entity, tableRef);
	}

	// ---------------------------------------------------------------- find by Id

	/**
	 * Creates SELECT criteria for the entity matched by id.
	 */
	public DbSqlBuilder findById(final Object entity) {
		final String tableRef = createTableRefName(entity);
		return sql().$(SELECT).column(tableRef).$(FROM).table(entity, tableRef).$(WHERE).matchIds(tableRef, entity);
	}

	/**
	 * Creates SELECT criteria for the entity matched by id.
	 */
	public DbSqlBuilder findById(final Object entityType, final Object id) {
		final String tableRef = createTableRefName(entityType);
		return sql().$(SELECT).column(tableRef).$(FROM).table(entityType, tableRef)
				.$(WHERE).refId(tableRef).$(EQUALS).columnValue(id);
	}

	// ---------------------------------------------------------------- count

	/**
	 * Creates SELECT COUNT criteria for the entity matched by non-null values.
	 */
	public DbSqlBuilder count(final Object entity) {
		final String tableRef = createTableRefName(entity);
		return sql().$(SELECT_COUNT_1_FROM).table(entity, tableRef).$(WHERE).match(tableRef, entity);
	}

	/**
	 * Creates SELECT COUNT all query.
	 */
	public DbSqlBuilder count(final Class entityType) {
		final String tableRef = createTableRefName(entityType);
		return sql().$(SELECT_COUNT_1_FROM).table(entityType, tableRef);
	}

	/**
	 * Creates SELECT COUNT criteria for the entity matched by all values.
	 */
	public DbSqlBuilder countAll(final Object entity) {
		final String tableRef = createTableRefName(entity);
		return sql().$(SELECT_COUNT_1_FROM).table(entity, tableRef).$(WHERE).matchAll(tableRef, entity);
	}

	// ---------------------------------------------------------------- increase

	/**
	 * Creates UPDATE that increases/decreases column by some delta value.
	 */
	public DbSqlBuilder increaseColumn(final Class entity, final Object id, final String columnRef, final Number delta, final boolean increase) {
		final String tableRef = createTableRefName(entity);

		return sql().$(UPDATE).table(entity, null, tableRef).$(SET)
				.ref(null, columnRef).$(EQUALS).ref(null, columnRef)
				.$(increase ? StringPool.PLUS : StringPool.DASH)
				.columnValue(delta).$(WHERE).refId(tableRef).$(EQUALS).columnValue(id);
	}

	// ---------------------------------------------------------------- resolve tableRef

	/**
	 * Creates table reference name from entity type.
	 * Always appends an underscore to reference name in order
	 * to circumvent SQL compatibility issues when entity class name
	 * equals to a reserved word.
	 */
	protected static String createTableRefName(final Object entity) {
		Class type = entity.getClass();
		type = (type == Class.class ? (Class) entity : type);
		return (type.getSimpleName() + '_');
	}

}


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

package jodd.db.jtx;

import jodd.db.DbSession;
import jodd.db.DbSessionProvider;
import jodd.db.DbSqlException;
import jodd.jtx.JtxTransactionManager;
import jodd.log.Logger;
import jodd.log.LoggerFactory;

/**
 * Returns session from the db {@link JtxTransactionManager transaction manager},
 * like {@link DbJtxTransactionManager}.
 */
public class DbJtxSessionProvider implements DbSessionProvider {

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

	protected final JtxTransactionManager jtxTxManager;

	/**
	 * Creates new JTX session provider.
	 */
	public DbJtxSessionProvider(final JtxTransactionManager txManager) {
		this.jtxTxManager = txManager;
	}

	/**
	 * Returns session from JTX transaction manager and started transaction.
	 */
	@Override
	public DbSession getDbSession() {
		log.debug("Requesting db TX manager session");

		final DbJtxTransaction jtx = (DbJtxTransaction) jtxTxManager.getTransaction();

		if (jtx == null) {
			throw new DbSqlException(
					"No transaction is in progress and DbSession can't be provided. " +
					"It seems that transaction manager is not used to begin a transaction.");
		}

		return jtx.requestResource();
	}

}


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

import jodd.cache.TypeCache;
import jodd.proxetta.ProxettaUtil;
import jodd.util.ClassUtil;

import java.lang.reflect.Field;

/**
 * Super tool for getting method references (names) in compile-time.
 */
@SuppressWarnings({"UnusedDeclaration"})
public class Methref<C> {

	public static TypeCache<Class> cache = TypeCache.createDefault();

	private static final MethrefProxetta proxetta = new MethrefProxetta();

	private final C instance;

	/**
	 * Creates new proxified instance of target.
	 * Proxy classes are cached. If given target is also
	 * proxified, it's real target will be used.
	 */
	@SuppressWarnings({"unchecked"})
	public Methref(Class<C> target) {
		target = ProxettaUtil.resolveTargetClass(target);

		Class proxyClass = cache.get(target);

		if (proxyClass == null) {
			proxyClass = proxetta.defineProxy(target);

			cache.put(target, proxyClass);
		}

		C proxy;

		try {
			proxy = (C) ClassUtil.newInstance(proxyClass);
		} catch (Exception ex) {
			throw new MethrefException(ex);
		}

        this.instance = proxy;
	}

	// ---------------------------------------------------------------- use

	/**
	 * Static factory, for convenient use.
	 */
	public static <T> Methref<T> on(final Class<T> target) {
		return new Methref<>(target);
	}

	/**
	 * Static factory that immediately returns {@link #to() method picker}.
	 */
	public static <T> T onto(final Class<T> target) {
		return new Methref<>(target).to();
	}

	/**
	 * Returns proxy instance of target class, so methods can be called
	 * immediately after (fluent interface).
	 */
	public C to() {
		return instance;
	}

	// ---------------------------------------------------------------- ref

	public String ref(final int dummy) {
		return ref(null);
	}
	public String ref(final short dummy) {
		return ref(null);
	}
	public String ref(final byte dummy) {
		return ref(null);
	}
	public String ref(final char dummy) {
		return ref(null);
	}
	public String ref(final long dummy) {
		return ref(null);
	}
	public String ref(final float dummy) {
		return ref(null);
	}
	public String ref(final double dummy) {
		return ref(null);
	}
	public String ref(final boolean dummy) {
		return ref(null);
	}

	/**
	 * Resolves method name of method reference. Argument is used so {@link #to()}
	 * can be called in convenient way. For methods that returns string,
	 * value will be returned immediately.
	 */
	public String ref(final Object dummy) {
		if (dummy != null) {
			if (dummy instanceof String) {
				return (String) dummy;
			}
			throw new MethrefException("Target method not collected");
		}
		return ref();
	}

	/**
	 * Returns name of method reference. Target {@link #on(Class) method} has
	 * to be {@link #to() called} before it can return its reference.
	 */
	public String ref() {
		if (instance == null) {
			return null;
		}
		try {
			Field f = instance.getClass().getDeclaredField("$__methodName$0");
			f.setAccessible(true);
			Object name = f.get(instance);
			if (name == null) {
				throw new MethrefException("Target method not collected");
			}
			return name.toString();
		} catch (Exception ex) {
			if (ex instanceof MethrefException) {
				throw ((MethrefException) ex);
			}
			throw new MethrefException("Methref field not found", ex);
		}
	}

}