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

package jodd.buffer;

import java.util.Arrays;

/**
 * Faster {@code short} buffer.
 */
public class FastShortBuffer {

	private short[] buffer;
	private int offset;

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

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

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

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

		buffer[offset++] = element;
	}

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

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

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

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

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

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

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

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

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

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

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

		return array;
	}

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

}


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

package jodd.proxetta.advice;

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

import java.lang.reflect.Method;

/**
 * Delegates calls to target using reflection. Offers
 * separation between between points where class is loaded
 * and class is used.
 *

 * Allows usage of classes loaded by different class loader.
 * For example, if you have an instance of class loaded by
 * parent-last class loader, delegate allows
 * to still call it using plain java. Under the hood,
 * each method will actually invoke target method
 * using reflection.
 */
public class DelegateAdvice implements ProxyAdvice {

	/**
	 * Target object.
	 */
	public Object _target;

	/**
	 * Looks up for method in target object and invokes it using reflection.
	 */
	public Object execute() throws Exception {
		String methodName = ProxyTarget.targetMethodName();
		Class[] argTypes = ProxyTarget.createArgumentsClassArray();
		Object[] args = ProxyTarget.createArgumentsArray();

		// lookup method on target object class (and not #targetClass!()
		Class type = _target.getClass();
		Method method = type.getMethod(methodName, argTypes);

		// remember context classloader
		ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();

		Object result;
		try {
			// change class loader
			Thread.currentThread().setContextClassLoader(type.getClassLoader());

			// invoke
			result = method.invoke(_target, args);
		}
		finally {
			// return context classloader
			Thread.currentThread().setContextClassLoader(contextClassLoader);

		}

		return ProxyTarget.returnValue(result);
	}
}


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

package jodd.joy;

import jodd.bridge.ClassPathURLs;
import jodd.io.findfile.ClassScanner;
import jodd.typeconverter.Converter;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Tiny JoyScanner kickstart.
 */
public class JoyScanner extends JoyBase implements JoyScannerConfig {

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

	/**
	 * Scanning entries that will be examined by various
	 * Jodd auto-magic tools.
	 */
	private List<String> includedEntries = new ArrayList<>();

	/**
	 * Included jars.
	 */
	private List<String> includedJars = new ArrayList<>();

	/**
	 * Excluded jars.
	 */
	private List<String> excludedJars = new ArrayList<>();

	/**
	 * List of APP classes.
	 */
	private List<Class> appClasses = new ArrayList<>();

	/**
	 * Should scanning ignore the exception.
	 */
	private boolean ignoreExceptions;

	@Override
	public JoyScanner setIncludedEntries(final String... includedEntries) {
		requireNotStarted(classScanner);
		Collections.addAll(this.includedEntries, includedEntries);
		return this;
	}

	@Override
	public JoyScanner setIncludedJars(final String... includedJars) {
		requireNotStarted(classScanner);
		Collections.addAll(this.includedJars, includedJars);
		return this;
	}

	@Override
	public JoyScanner setExcludedJars(final String... excludedJars) {
		requireNotStarted(classScanner);
		Collections.addAll(this.excludedJars, excludedJars);
		return this;
	}

	@Override
	public JoyScanner setIgnoreExceptions(final boolean ignoreExceptions) {
		requireNotStarted(classScanner);
		this.ignoreExceptions = ignoreExceptions;
		return this;
	}

	/**
	 * Defines class and it's classloader to scan. This is not required in Java8
	 * and would not hurt anything if called. However, for Java9, you should
	 * pass <i>any</i> user-application class, so Jodd can figure out the real
	 * class path to scan.
	 */
	@Override
	public JoyScanner scanClasspathOf(final Class applicationClass) {
		requireNotStarted(classScanner);
		appClasses.add(applicationClass);
		return this;
	}

	/**
	 * Shortcut for {@link #scanClasspathOf(Class)}.
	 */
	@Override
	public JoyScanner scanClasspathOf(final Object applicationObject) {
		requireNotStarted(classScanner);
		return scanClasspathOf(applicationObject.getClass());
	}

	// ---------------------------------------------------------------- runtime

	/**
	 * Returns class scanner.
	 */
	public ClassScanner getClassScanner() {
		return requireStarted(classScanner);
	}

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

	protected ClassScanner classScanner;

	/**
	 * Configures scanner class finder. Works for all three scanners:
	 * Petite, DbOom and Madvoc. All scanners by default include all jars,
	 * but exclude all entries.
	 */
	@Override
	public void start() {
		initLogger();

		log.info("SCANNER start ----------");

		classScanner = new ClassScanner() {
			@Override
			protected void scanJarFile(final File file) {
				log.debug("Scanning jar: " + file);
				super.scanJarFile(file);
			}

			@Override
			protected void scanClassPath(final File root) {
				log.debug("Scanning path: " + root);
				super.scanClassPath(root);
			}
		};

		if (log.isDebugEnabled()) {
			log.debug("Scan entries: " + Converter.get().toString(includedEntries));
			log.debug("Scan jars: " + Converter.get().toString(includedJars));
			log.debug("Scan exclude jars: " + Converter.get().toString(excludedJars));
			log.debug("Scan ignore exception: " + ignoreExceptions);
		}

		classScanner.excludeCommonEntries();
		classScanner.excludeCommonJars();
		classScanner.excludeJars(excludedJars.toArray(new String[0]));

		if (includedEntries.isEmpty() && includedJars.isEmpty()) {
			// nothing was explicitly included
			classScanner.excludeAllEntries(false);
		}
		else {
			// something was included by user
			classScanner.excludeAllEntries(true);
			includedEntries.add("jodd.*");
		}

		classScanner
			.detectEntriesMode(true)
			.includeEntries(includedEntries.toArray(new String[0]))
			.includeJars(includedJars.toArray(new String[0]))
			.ignoreException(ignoreExceptions)
			.scanDefaultClasspath();

		appClasses.forEach(clazz -> classScanner.scan(ClassPathURLs.of(null, clazz)));

		log.info("SCANNER OK!");
	}

	@Override
	public void stop() {
		classScanner = null;
	}

}


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

package jodd.htmlstapler;

import jodd.lagarto.Tag;
import jodd.lagarto.TagAdapter;
import jodd.lagarto.TagVisitor;
import jodd.util.CharArraySequence;
import jodd.util.CharSequenceUtil;
import jodd.util.Util;

/**
 * HTML Stapler tag adapter parses HTML page and collects all information
 * about linking resource files.
 */
public class HtmlStaplerTagAdapter extends TagAdapter {

	protected final HtmlStaplerBundlesManager bundlesManager;
	protected final BundleAction jsBundleAction;
	protected final BundleAction cssBundleAction;

	protected boolean insideConditionalComment;

	public HtmlStaplerTagAdapter(final HtmlStaplerBundlesManager bundlesManager, final String servletPath, final TagVisitor target) {
		super(target);

		this.bundlesManager = bundlesManager;

		jsBundleAction = bundlesManager.start(servletPath, "js");
		cssBundleAction = bundlesManager.start(servletPath, "css");

		insideConditionalComment = false;
	}

	// ---------------------------------------------------------------- javascripts

	@Override
	public void script(final Tag tag, final CharSequence body) {
		if (!insideConditionalComment) {
			String src = Util.toString(tag.getAttributeValue("src"));

			if (src == null) {
				super.script(tag, body);
				return;
			}

			if (jsBundleAction.acceptLink(src)) {
				String link = jsBundleAction.processLink(src);
				if (link != null) {
					tag.setAttributeValue("src", link);
					super.script(tag, body);
				}
				return;
			}
		}
		super.script(tag, body);
	}

	// ---------------------------------------------------------------- css

	private static final CharSequence T_LINK = CharArraySequence.of('l', 'i', 'n', 'k');

	@Override
	public void tag(final Tag tag) {
		if (!insideConditionalComment) {
			if (tag.nameEquals(T_LINK)) {
				CharSequence type = tag.getAttributeValue("type");

				if (type != null && CharSequenceUtil.equalsIgnoreCase(type, "text/css")) {
					String media = Util.toString(tag.getAttributeValue("media"));

					if (media == null || media.contains("screen")) {
						String href = Util.toString(tag.getAttributeValue("href"));

						if (cssBundleAction.acceptLink(href)) {
							String link = cssBundleAction.processLink(href);
							if (link != null) {
								tag.setAttribute("href", link);
								super.tag(tag);
							}
							return;
						}
					}
				}
			}
		}
		super.tag(tag);
	}

	// ---------------------------------------------------------------- conditional comments

	@Override
	public void condComment(final CharSequence expression, final boolean isStartingTag, final boolean isHidden, final boolean isHiddenEndTag) {
		insideConditionalComment = isStartingTag;
		super.condComment(expression, isStartingTag, isHidden, isHiddenEndTag);
	}

	// ---------------------------------------------------------------- end

	@Override
	public void end() {
		jsBundleAction.end();
		cssBundleAction.end();
		super.end();
	}

	/**
	 * Post process final content. Required for RESOURCE_ONLY strategy.
	 */
	public char[] postProcess(char[] content) {
		content = jsBundleAction.replaceBundleId(content);
		content = cssBundleAction.replaceBundleId(content);
		return content;
	}
}


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

import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.UncheckedIOException;
import java.util.concurrent.Callable;

/**
 * Unchecked exception and also a wrapper for checked exceptions.
 */
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter"})
public class UncheckedException extends RuntimeException {

	protected final Throwable cause;

	/**
	 * Divider between causes printouts.
	 */
	protected static final String CAUSE_DIV = "---[cause]------------------------------------------------------------------------";

	/**
	 * If set to true stack trace will be enhanced with cause's stack traces.
	 */
	protected final boolean showCauseDetails;

	// ---------------------------------------------------------------- constructors

	public UncheckedException(final Throwable t) {
		super(t.getMessage());
		cause = t;
		this.showCauseDetails = true;
	}

	public UncheckedException(final Throwable t, final boolean showCauseDetails) {
		super(t.getMessage());
		cause = t;
		this.showCauseDetails = showCauseDetails;
	}

	public UncheckedException() {
		super();
		cause = null;
		this.showCauseDetails = false;
	}

	public UncheckedException(final String message) {
		super(message);
		cause = null;
		this.showCauseDetails = false;
	}

	public UncheckedException(final String message, final Throwable t) {
		super(message, t);
		cause = t;
		this.showCauseDetails = true;
	}

	public UncheckedException(final String message, final Throwable t, final boolean showCauseDetails) {
		super(message, t);
		cause = t;
		this.showCauseDetails = showCauseDetails;
	}

	// ---------------------------------------------------------------- stack trace

	@Override
	public void printStackTrace() {
		printStackTrace(System.err);
	}

	@Override
	public void printStackTrace(final PrintStream ps) {
		synchronized (ps) {
			super.printStackTrace(ps);
			if ((cause != null) && showCauseDetails) {
				Throwable rootCause = ExceptionUtil.getRootCause(cause);
				ps.println(CAUSE_DIV);
				rootCause.printStackTrace(ps);
			}
		}
	}

	@Override
	public void printStackTrace(final PrintWriter pw) {
		synchronized (pw) {
			super.printStackTrace(pw);
			if ((cause != null) && showCauseDetails) {
				Throwable rootCause = ExceptionUtil.getRootCause(cause);
				pw.println(CAUSE_DIV);
				rootCause.printStackTrace(pw);
			}
		}
	}

	// ---------------------------------------------------------------- txt

	/**
	 * Returns the detail message, including the message from the nested exception if there is one.
	 */
	@Override
	public String getMessage() {
		return ExceptionUtil.buildMessage(super.getMessage(), cause);
	}

	// ---------------------------------------------------------------- wrap

	/**
	 * Wraps checked exceptions in a UncheckedException.
	 * Unchecked exceptions are not wrapped.
	 */
	public static <V> V callAndWrapException(final Callable<V> callable) {
		try {
			return callable.call();
		}
		catch (IOException ioex) {
			throw new UncheckedIOException(ioex);
		}
		catch (RuntimeException rtex) {
			throw rtex;
		}
		catch (Exception t) {
			throw new UncheckedException(t);
		}
	}

	@FunctionalInterface
	public interface CallableVoid {
		public void call() throws Exception;
	}

	/**
	 * Wraps checked exceptions in a UncheckedException.
	 * Unchecked exceptions are not wrapped.
	 */
	public static void runAndWrapException(final CallableVoid callable) {
		try {
			callable.call();
		}
		catch (IOException ioex) {
			throw new UncheckedIOException(ioex);
		}
		catch (RuntimeException rtex) {
			throw rtex;
		}
		catch (Exception t) {
			throw new UncheckedException(t);
		}
	}

	/**
	 * Wraps all exceptions in a UncheckedException
	 */
	public static RuntimeException wrap(final Throwable t) {
		return new UncheckedException(t);
	}

	/**
	 * Wraps all exceptions in a UncheckedException
	 */
	public static RuntimeException wrap(final Throwable t, final String message) {
		return new UncheckedException(message, t);
	}

	// ---------------------------------------------------------------- cause

	/**
	 * Re-throws cause if exists.
	 */
	public void rethrow() throws Throwable {
		if (cause == null) {
			return;
		}
		throw cause;
	}

	/**
	 * Returns exception cause.
	 */
	@Override
	public Throwable getCause() {
		return cause;
	}

}


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

package jodd.lagarto;

import jodd.util.CharArraySequence;
import jodd.util.CharUtil;

/**
 * Utility scanner over a char buffer.
 */
class Scanner {

	protected char[] input;
	protected int ndx = 0;
	protected int total;

	Scanner() { }

	/**
	 * Initializes scanner.
	 */
	protected void initialize(final char[] input) {
		this.input = input;
		this.ndx = -1;
		this.total = input.length;
	}

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

	/**
	 * Finds a character in some range and returns its index.
	 * Returns -1 if character is not found.
	 */
	protected final int find(final char target, int from, final int end) {
		while (from < end) {
			if (input[from] == target) {
				break;
			}
			from++;
		}

		return (from == end) ? -1 : from;
	}

	/**
	 * Finds character buffer in some range and returns its index.
	 * Returns -1 if character is not found.
	 */
	protected final int find(final char[] target, int from, final int end) {
		while (from < end) {
			if (match(target, from)) {
				break;
			}
			from++;
		}

		return (from == end) ? -1 : from;
	}

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

	/**
	 * Matches char buffer with content on given location.
	 */
	protected final boolean match(final char[] target, final int ndx) {
		if (ndx + target.length >= total) {
			return false;
		}

		int j = ndx;

		for (int i = 0; i < target.length; i++, j++) {
			if (input[j] != target[i]) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Matches char buffer with content at current location case-sensitive.
	 */
	public final boolean match(final char[] target) {
		return match(target, ndx);
	}

	/**
	 * Matches char buffer given in uppercase with content at current location, that will
	 * be converted to upper case to make case-insensitive matching.
	 */
	public final boolean matchUpperCase(final char[] uppercaseTarget) {
		if (ndx + uppercaseTarget.length > total) {
			return false;
		}

		int j = ndx;

		for (int i = 0; i < uppercaseTarget.length; i++, j++) {
			final char c = CharUtil.toUpperAscii(input[j]);

			if (c != uppercaseTarget[i]) {
				return false;
			}
		}

		return true;
	}

	// ---------------------------------------------------------------- char sequences

	/**
	 * Creates char sub-sequence from the input.
	 */
	protected final CharSequence charSequence(final int from, final int to) {
		if (from == to) {
			return CharArraySequence.EMPTY;
		}
		return CharArraySequence.of(input, from, to - from);
	}

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

	private int lastOffset = -1;
	private int lastLine;
	private int lastLastNewLineOffset;

	/**
	 * Returns true if EOF.
	 */
	protected final boolean isEOF() {
		return ndx >= total;
	}

	/**
	 * Calculates {@link Position current position}: offset, line and column.
	 */
	protected Position position(final int position) {
		int line;
		int offset;
		int lastNewLineOffset;

		if (position > lastOffset) {
			line = 1;
			offset = 0;
			lastNewLineOffset = 0;
		} else {
			line = lastLine;
			offset = lastOffset;
			lastNewLineOffset = lastLastNewLineOffset;
		}

		while (offset < position) {
			final char c = input[offset];

			if (c == '\n') {
				line++;
				lastNewLineOffset = offset + 1;
			}

			offset++;
		}

		lastOffset = offset;
		lastLine = line;
		lastLastNewLineOffset = lastNewLineOffset;

		return new Position(position, line, position - lastNewLineOffset + 1);
	}

	/**
	 * Current position.
	 */
	public static class Position {

		private final int offset;
		private final int line;
		private final int column;

		public Position(final int offset, final int line, final int column) {
			this.offset = offset;
			this.line = line;
			this.column = column;
		}

		public Position(final int offset) {
			this.offset = offset;
			this.line = -1;
			this.column = -1;
		}

		@Override
		public String toString() {
			if (offset == -1) {
				return "[" + line + ':' + column + ']';
			}
			if (line == -1) {
				return "[@" + offset + ']';
			}
			return "[" + line + ':' + column + " @" + offset + ']';
		}
	}

}


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

import jodd.jtx.JtxTransaction;
import jodd.jtx.JtxTransactionManager;
import jodd.jtx.JtxTransactionMode;
import jodd.log.Logger;
import jodd.log.LoggerFactory;

/**
 * Lean transaction worker helps dealing transactions when they were requested
 * in several places, usually in separated methods. This worker knows when requested transaction is
 * the same as current one, or completely new. It might be useful for aspects.
 */
public class LeanJtxWorker {

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

	protected final JtxTransactionManager txManager;

	public LeanJtxWorker(final JtxTransactionManager txManager) {
		this.txManager = txManager;
	}

	/**
	 * Returns transaction manager.
	 */
	public JtxTransactionManager getTransactionManager() {
		return txManager;
	}

	/**
	 * Returns current transaction or null if there is no transaction at the moment.
	 */
	public JtxTransaction getCurrentTransaction() {
		return txManager.getTransaction();
	}

	/**
	 * Requests for transaction and returns non-null value only when new transaction
	 * is created! When null is returned, transaction may be get by
	 * {@link #getCurrentTransaction()}.
	 *
	 * @see jodd.jtx.JtxTransactionManager#requestTransaction(jodd.jtx.JtxTransactionMode)
	 */
	public JtxTransaction maybeRequestTransaction(final JtxTransactionMode txMode, final Object scope) {
		if (txMode == null) {
			return null;
		}
		JtxTransaction currentTx = txManager.getTransaction();
		JtxTransaction requestedTx = txManager.requestTransaction(txMode, scope);
		if (currentTx == requestedTx) {
			return null;
		}
		return requestedTx;
	}

	/**
	 * Commits transaction if created in the same level where this method is invoked.
	 * Returns true if transaction was actually committed or false
	 * if transaction was not created on this level.
	 */
	public boolean maybeCommitTransaction(final JtxTransaction tx) {
		if (tx == null) {
			return false;
		}
		log.debug("commit tx");

		tx.commit();
		return true;
	}

	/**
	 * Rollbacks transaction if created in the same scope where this method is invoked.
	 * If not, current transaction is marked for rollback.
	 * Returns true if transaction was actually roll backed.
	 */
	public boolean markOrRollbackTransaction(JtxTransaction tx, final Throwable cause) {
		if (tx == null) {
			tx = getCurrentTransaction();
			if (tx == null) {
				return false;
			}
			log.debug("set rollback only tx");

			tx.setRollbackOnly(cause);
			return false;
		}
		log.debug("rollback tx");

		tx.rollback();
		return true;
	}
}


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

package jodd.bridge;

import java.lang.module.ModuleDescriptor;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;

public class ClassPathURLs {

	private static final String MANIFEST = "META-INF/MANIFEST.MF";

	/**
	 * Returns urls for the classloader.
	 *
	 * @param classLoader classloader in which to find urls
	 * @return list of urls or {@code null} if not found
	 */
	public static URL[] of(ClassLoader classLoader, Class clazz) {
		if (clazz == null) {
			clazz = ClassPathURLs.class;
		}
		if (classLoader == null) {
			classLoader = clazz.getClassLoader();
		}
		final Set<URL> urls = new LinkedHashSet<>();

		while (classLoader != null) {
			if (classLoader instanceof URLClassLoader) {
				URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
				URL[] allURLS = urlClassLoader.getURLs();
				Collections.addAll(urls, allURLS);
				break;
			}

			URL classUrl = classModuleUrl(classLoader, clazz);
			if (classUrl != null) {
				urls.add(classUrl);
			}
			classUrl = classModuleUrl(classLoader, ClassPathURLs.class);
			if (classUrl != null) {
				urls.add(classUrl);
			}

			ModuleDescriptor moduleDescriptor = clazz.getModule().getDescriptor();

			if (moduleDescriptor != null) {
				moduleDescriptor.requires().forEach(req -> {
					ModuleLayer.boot()
						.findModule(req.name())
						.ifPresent(mod -> {
							ClassLoader moduleClassLoader = mod.getClassLoader();
							if (moduleClassLoader != null) {
								URL url = moduleClassLoader.getResource(MANIFEST);
								if (url != null) {
									url = fixManifestUrl(url);
									urls.add(url);
								}
							}
						});
				});
			}

			classLoader = classLoader.getParent();
		}

		return urls.toArray(new URL[0]);
	}

	private static URL fixManifestUrl(URL url) {
		String urlString = url.toString();
		int ndx = urlString.indexOf(MANIFEST);
		urlString = urlString.substring(0, ndx) + urlString.substring(ndx + MANIFEST.length());

		try {
			return new URL(urlString);
		} catch (MalformedURLException ignore) {
			return null;
		}
	}

	private static URL classModuleUrl(ClassLoader classLoader, Class clazz) {
		if (clazz == null) {
			return null;
		}
		final String name = clazz.getName().replace('.', '/') + ".class";

		return resourceModuleUrl(classLoader, name);
	}

	private static URL resourceModuleUrl(ClassLoader classLoader, String resourceClassPath) {
		URL url = classLoader.getResource(resourceClassPath);

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

		// use root
		String urlString = url.toString();
		int ndx = urlString.indexOf(resourceClassPath);
		urlString = urlString.substring(0, ndx) + urlString.substring(ndx + resourceClassPath.length());

		try {
			return new URL(urlString);
		}
		catch (MalformedURLException ignore) {
			return null;
		}
	}

}


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

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;

import java.io.UnsupportedEncodingException;

/**
 * Benchmark for encoding and decoding base32 - data.

 * Following <tt>encode</tt>-methods will be compared:
 *

 *
{@link org.apache.commons.codec.binary.Base32#encodeToString(byte[])}

 *
{@link Base32#encode(byte[])}

 *

 *
 * And following <tt>decode</tt>-methods will be compared:
 *

 *
{@link org.apache.commons.codec.binary.Base32#decode(String)}

 *
{@link Base32#decode(String)}

 *

 *
 *

 * Run:
 *
 * gw :jodd-core:Base32Benchmark
 *
 *

 *
 * Results:
 *

 * Benchmark                                                    Mode  Cnt          Score       Error    Units
 * Base32Benchmark.decode_Apache_Base32                        thrpt   10    174665,214 ±     6591,054  ops/s
 * Base32Benchmark.decode_Jodd_Base32                          thrpt   10    823002,724 ±    52784,837  ops/s
 * Base32Benchmark.encode_Apache_Base32                        thrpt   10    216608,267 ±     7159,349  ops/s
 * Base32Benchmark.encode_Jodd_Base32                          thrpt   10    373314,481 ±    19277,663  ops/s
 *

 */
@Fork(1)
@Warmup(iterations = 2)
@Measurement(iterations = 10)
@State(Scope.Benchmark)
public class Base32Benchmark {

    private byte[] to_be_encoded ;
    private String to_be_decoded ;

	@Setup
	public void prepare() throws UnsupportedEncodingException {
		to_be_encoded = "Jodd is set of Java microframeworks, tools and utilities, under 1.7 MB. We believe in common sense to make things simple, but not simpler. Get things done! Make your ideas! Kickstart your startup! And enjoy the coding.".getBytes("ISO-8859-1");
		to_be_decoded = "JJXWIZBANFZSA43FOQQG6ZRAJJQXMYJANVUWG4TPMZZGC3LFO5XXE23TFQQHI33PNRZSAYLOMQQHK5DJNRUXI2LFOMWCA5LOMRSXEIBRFY3SATKCFYQFOZJAMJSWY2LFOZSSA2LOEBRW63LNN5XCA43FNZZWKIDUN4QG2YLLMUQHI2DJNZTXGIDTNFWXA3DFFQQGE5LUEBXG65BAONUW24DMMVZC4ICHMV2CA5DINFXGO4ZAMRXW4ZJBEBGWC23FEB4W65LSEBUWIZLBOMQSAS3JMNVXG5DBOJ2CA6LPOVZCA43UMFZHI5LQEEQEC3TEEBSW42TPPEQHI2DFEBRW6ZDJNZTS4";
	}

    // ----------------------------------------------------------------------- Apache Commons Codec - Base32

    @Benchmark
    public String encode_Apache_Base32() {
        return new org.apache.commons.codec.binary.Base32(false).encodeAsString(to_be_encoded);
    }

	@Benchmark
	public byte[] decode_Apache_Base32() {
		return new org.apache.commons.codec.binary.Base32(false).decode(to_be_decoded);
	}

    // ----------------------------------------------------------------------- Jodd Base32

    @Benchmark
    public String encode_Jodd_Base32() {
		return Base32.encode(to_be_encoded);
	}

	@Benchmark
	public byte[] decode_Jodd_Base32() {
		return Base32.decode(to_be_decoded);
	}

}


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

package jodd.mail;

import jodd.util.ArraysUtil;

import javax.mail.Address;
import java.util.Date;

/**
 * E-mail holds all parts of an email and handle attachments.
 */
public class Email extends CommonEmail<Email> {

	/**
	 * Static constructor for fluent interface.
	 */
	public static Email create() {
		return new Email();
	}

	@Override
	public Email clone() {
		return create()

			// from / reply-to
			.from(from())
			.replyTo(replyTo())

			// recipients
			.to(to())
			.cc(cc())
			.bcc(bcc())

			// subject
			.subject(subject(), subjectEncoding())

			// dates
			.sentDate(sentDate())

			// headers - includes priority
			.headers(headers())

			// content / attachments
			.storeAttachments(attachments())
			.message(messages());
	}

	// ---------------------------------------------------------------- date

	/**
	 * Sets current date as the sent date.
	 *
	 * @return this
	 * @see #sentDate(Date)
	 */
	public Email currentSentDate() {
		return sentDate(new Date());
	}

	// ---------------------------------------------------------------- bcc

	/**
	 * BCC address.
	 */
	private EmailAddress[] bcc = EmailAddress.EMPTY_ARRAY;

	/**
	 * Appends BCC address.
	 *
	 * @param to {@link EmailAddress} to add.
	 * @return this
	 */
	public Email bcc(final EmailAddress to) {
		this.bcc = ArraysUtil.append(this.bcc, to);
		return _this();
	}

	/**
	 * Appends BCC address.
	 *
	 * @param bcc Address may be specified with personal name like this: {@code Jenny Doe <email@foo.com>}.
	 * @return this
	 * @see #bcc(EmailAddress)
	 */
	public Email bcc(final String bcc) {
		return bcc(EmailAddress.of(bcc));
	}

	/**
	 * Appends BCC address.
	 *
	 * @param personalName personal name.
	 * @param bcc          email address.
	 * @return this
	 * @see #bcc(EmailAddress)
	 */
	public Email bcc(final String personalName, final String bcc) {
		return bcc(new EmailAddress(personalName, bcc));
	}

	/**
	 * Appends BCC address.
	 *
	 * @param bcc {@link Address} to add.
	 * @return this
	 * @see #bcc(EmailAddress)
	 */
	public Email bcc(final Address bcc) {
		return bcc(EmailAddress.of(bcc));
	}

	/**
	 * Appends BCC address.
	 *
	 * @param bccs array of {@link String}s to set.
	 * @return this
	 * @see #bcc(EmailAddress...)
	 */
	public Email bcc(final String... bccs) {
		return bcc(EmailAddress.of(bccs));
	}

	/**
	 * Appends BCC address.
	 *
	 * @param bccs array of {@link Address}es to set.
	 * @return this
	 * @see #bcc(EmailAddress...)
	 */
	public Email bcc(final Address... bccs) {
		return bcc(EmailAddress.of(bccs));
	}

	/**
	 * Appends one or more BCC addresses.
	 *
	 * @param bccs vararg of {@link EmailAddress}es to set.
	 * @return this
	 */
	public Email bcc(final EmailAddress... bccs) {
		this.bcc = ArraysUtil.join(this.bcc, valueOrEmptyArray(bccs));
		return _this();
	}

	/**
	 * Returns BCC addresses.
	 */
	public EmailAddress[] bcc() {
		return bcc;
	}

	/**
	 * Resets BCC addresses.
	 */
	public Email resetBcc() {
		this.bcc = EmailAddress.EMPTY_ARRAY;
		return _this();
	}

}


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

package jodd.io.upload.impl;

import jodd.io.upload.FileUpload;
import jodd.io.upload.FileUploadFactory;
import jodd.io.upload.MultipartRequestInputStream;

import java.io.File;

/**
 *
 * Factory for {@link AdaptiveFileUpload}.
 */
public class AdaptiveFileUploadFactory implements FileUploadFactory {

	protected int memoryThreshold = 8192;
	protected File uploadPath;
	protected int maxFileSize = 102400;
	protected boolean breakOnError;
	protected String[] fileExtensions;
	protected boolean allowFileExtensions = true;

	/**
	 * {@inheritDoc}
	 */
	@Override
	public FileUpload create(final MultipartRequestInputStream input) {
		return new AdaptiveFileUpload(input, memoryThreshold, uploadPath, maxFileSize, breakOnError, fileExtensions, allowFileExtensions);
	}

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

	public int getMemoryThreshold() {
		return memoryThreshold;
	}
	/**
	 * Specifies per file memory limit for keeping uploaded files in the memory.
	 */
	public AdaptiveFileUploadFactory setMemoryThreshold(final int memoryThreshold) {
		if (memoryThreshold >= 0) {
			this.memoryThreshold = memoryThreshold;
		}
		return this;
	}

	public File getUploadPath() {
		return uploadPath;
	}

	/**
	 * Specifies the upload path. If set to null default
	 * system TEMP path will be used.
	 */
	public AdaptiveFileUploadFactory  setUploadPath(final File uploadPath) {
		this.uploadPath = uploadPath;
		return this;
	}

	public int getMaxFileSize() {
		return maxFileSize;
	}

	/**
	 * Sets maximum file upload size. Setting to -1
	 * disables this constraint.
	 */
	public AdaptiveFileUploadFactory setMaxFileSize(final int maxFileSize) {
		this.maxFileSize = maxFileSize;
		return this;
	}

	public boolean isBreakOnError() {
		return breakOnError;
	}

	public AdaptiveFileUploadFactory setBreakOnError(final boolean breakOnError) {
		this.breakOnError = breakOnError;
		return this;
	}

	/**
	 * Specifies if upload should break on error.
	 */
	public AdaptiveFileUploadFactory breakOnError(final boolean breakOnError) {
		this.breakOnError = breakOnError;
		return this;
	}

	/**
	 * Allow or disallow set of file extensions. Only one rule can be active at time,
	 * which means user can only specify extensions that are either allowed or disallowed.
	 * Setting this value to null will turn this feature off.
	 */
	public AdaptiveFileUploadFactory setFileExtensions(final String[] fileExtensions, final boolean allow) {
		this.fileExtensions = fileExtensions;
		this.allowFileExtensions = allow;
		return this;
	}

}


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

package jodd.json;

import jodd.cache.TypeCache;
import jodd.introspector.ClassDescriptor;
import jodd.introspector.ClassIntrospector;
import jodd.json.impl.ArraysJsonSerializer;
import jodd.json.impl.BooleanArrayJsonSerializer;
import jodd.json.impl.BooleanJsonSerializer;
import jodd.json.impl.ByteArrayJsonSerializer;
import jodd.json.impl.CalendarJsonSerializer;
import jodd.json.impl.CharSequenceJsonSerializer;
import jodd.json.impl.CharacterJsonSerializer;
import jodd.json.impl.ClassJsonSerializer;
import jodd.json.impl.DateJsonSerializer;
import jodd.json.impl.DoubleArrayJsonSerializer;
import jodd.json.impl.DoubleJsonSerializer;
import jodd.json.impl.EnumJsonSerializer;
import jodd.json.impl.FileJsonSerializer;
import jodd.json.impl.FloatArrayJsonSerializer;
import jodd.json.impl.FloatJsonSerializer;
import jodd.json.impl.IntArrayJsonSerializer;
import jodd.json.impl.IterableJsonSerializer;
import jodd.json.impl.JsonArraySerializer;
import jodd.json.impl.JsonObjectSerializer;
import jodd.json.impl.JulianDateSerializer;
import jodd.json.impl.LocalDateSerializer;
import jodd.json.impl.LocalDateTimeSerializer;
import jodd.json.impl.LocalTimeSerializer;
import jodd.json.impl.LongArrayJsonSerializer;
import jodd.json.impl.MapJsonSerializer;
import jodd.json.impl.NumberJsonSerializer;
import jodd.json.impl.ObjectJsonSerializer;
import jodd.json.impl.UUIDJsonSerializer;
import jodd.time.JulianDate;

import java.io.File;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.UUID;

/**
 * Map of {@link jodd.json.TypeJsonSerializer json type serializers}.
 */
public class TypeJsonSerializerMap {

	private static final TypeJsonSerializerMap TYPE_JSON_SERIALIZER_MAP = new TypeJsonSerializerMap();

	/**
	 * Returns default instance.
	 */
	public static TypeJsonSerializerMap get() {
		return TYPE_JSON_SERIALIZER_MAP;
	}

	private final TypeJsonSerializerMap defaultSerializerMap;

	/**
	 * Creates new serializers map and registers defaults.
	 */
	public TypeJsonSerializerMap() {
		registerDefaults();
		defaultSerializerMap = null;
	}

	/**
	 * Creates new empty serializer map with given defaults map.
	 */
	public TypeJsonSerializerMap(final TypeJsonSerializerMap defaultSerializerMap) {
		this.defaultSerializerMap = defaultSerializerMap;
	}

	protected final TypeCache<TypeJsonSerializer> map = TypeCache.createDefault();
	protected final TypeCache<TypeJsonSerializer> cache = TypeCache.createDefault();

	/**
	 * Registers default set of {@link jodd.json.TypeJsonSerializer serializers}.
	 */
	public void registerDefaults() {

		// main

		map.put(Object.class, new ObjectJsonSerializer());
		map.put(Map.class, new MapJsonSerializer());
		map.put(Iterable.class, new IterableJsonSerializer());

		map.put(JsonObject.class, new JsonObjectSerializer());
		map.put(JsonArray.class, new JsonArraySerializer());

		// arrays
		map.put(int[].class, new IntArrayJsonSerializer());
		map.put(long[].class, new LongArrayJsonSerializer());
		map.put(double[].class, new DoubleArrayJsonSerializer());
		map.put(float[].class, new FloatArrayJsonSerializer());
		map.put(boolean[].class, new BooleanArrayJsonSerializer());
		map.put(byte[].class, new ByteArrayJsonSerializer());

		map.put(Integer[].class, new ArraysJsonSerializer<Integer>() {
			@Override
			protected int getLength(final Integer[] array) {
				return array.length;
			}

			@Override
			protected Integer get(final Integer[] array, final int index) {
				return array[index];
			}
		});
		map.put(Long[].class, new ArraysJsonSerializer<Long>() {
			@Override
			protected int getLength(final Long[] array) {
				return array.length;
			}

			@Override
			protected Long get(final Long[] array, final int index) {
				return array[index];
			}
		});
		map.put(Arrays.class, new ArraysJsonSerializer());

		// strings

		TypeJsonSerializer jsonSerializer = new CharSequenceJsonSerializer();

		map.put(String.class, jsonSerializer);
		map.put(StringBuilder.class, jsonSerializer);
		map.put(CharSequence.class, jsonSerializer);

		// number

		jsonSerializer = new NumberJsonSerializer();

		map.put(Number.class, jsonSerializer);

		map.put(Integer.class, jsonSerializer);
		map.put(int.class, jsonSerializer);

		map.put(Long.class, jsonSerializer);
		map.put(long.class, jsonSerializer);

		DoubleJsonSerializer doubleJsonSerializer = new DoubleJsonSerializer();
		map.put(Double.class, doubleJsonSerializer);
		map.put(double.class, doubleJsonSerializer);

		FloatJsonSerializer floatJsonSerializer = new FloatJsonSerializer();
		map.put(Float.class, floatJsonSerializer);
		map.put(float.class, floatJsonSerializer);

		map.put(BigInteger.class, jsonSerializer);
		map.put(BigDecimal.class, jsonSerializer);

		// other

		map.put(Boolean.class, new BooleanJsonSerializer());
		map.put(boolean.class, new BooleanJsonSerializer());
		map.put(Date.class, new DateJsonSerializer());
		map.put(Calendar.class, new CalendarJsonSerializer());
		map.put(JulianDate.class, new JulianDateSerializer());
		map.put(LocalDateTime.class, new LocalDateTimeSerializer());
		map.put(LocalDate.class, new LocalDateSerializer());
		map.put(LocalTime.class, new LocalTimeSerializer());
		map.put(Enum.class, new EnumJsonSerializer());
		map.put(File.class, new FileJsonSerializer(FileJsonSerializer.Type.PATH));

		//map.putUnsafe();(Collection.class, new CollectionJsonSerializer());

		jsonSerializer = new CharacterJsonSerializer();

		map.put(Character.class, jsonSerializer);
		map.put(char.class, jsonSerializer);

		map.put(UUID.class, new UUIDJsonSerializer());

		map.put(Class.class, new ClassJsonSerializer());

		// clear cache
		cache.clear();
	}

	/**
	 * Registers new serializer.
	 */
	public void register(final Class type, final TypeJsonSerializer typeJsonSerializer) {
		map.put(type, typeJsonSerializer);
		cache.clear();
	}

	/**
	 * Lookups for the {@link jodd.json.TypeJsonSerializer serializer} for given type.
	 * If serializer not found, then all interfaces and subclasses of the type are checked.
	 * Finally, if no serializer is found, object's serializer is returned.
	 */
	public TypeJsonSerializer lookup(final Class type) {
		return cache.get(type, () -> _lookup(type));
	}

	/**
	 * Get type serializer from map. First the current map is used.
	 * If element is missing, default map will be used, if exist.
	 */
	protected TypeJsonSerializer lookupSerializer(final Class type) {
		TypeJsonSerializer tjs = map.get(type);

		if (tjs == null) {
			if (defaultSerializerMap != null) {
				tjs = defaultSerializerMap.map.get(type);
			}
		}

		return tjs;
	}

	protected TypeJsonSerializer _lookup(final Class type) {
		synchronized (map) {
			TypeJsonSerializer tjs = lookupSerializer(type);

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

			ClassDescriptor cd = ClassIntrospector.get().lookup(type);

			// check array

			if (cd.isArray()) {
				return lookupSerializer(Arrays.class);
			}

			// now iterate interfaces

			Class[] interfaces = cd.getAllInterfaces();

			for (Class interfaze : interfaces) {
				tjs = lookupSerializer(interfaze);

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

			// now iterate all superclases

			Class[] superclasses = cd.getAllSuperclasses();

			for (Class clazz : superclasses) {
				tjs = lookupSerializer(clazz);

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

			// nothing found, go with the Object

			return lookupSerializer(Object.class);
		}
	}

}


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

import jodd.io.FastByteArrayOutputStream;
import jodd.io.upload.impl.MemoryFileUploadFactory;
import jodd.util.ArraysUtil;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * Generic, serlvets-free multipart request input stream parser.
 */
public class MultipartStreamParser {

	protected FileUploadFactory fileUploadFactory;
	protected Map<String, String[]> requestParameters;
	protected Map<String, FileUpload[]> requestFiles;

	public MultipartStreamParser() {
		this(null);
	}

	public MultipartStreamParser(final FileUploadFactory fileUploadFactory) {
		this.fileUploadFactory = (fileUploadFactory == null ? new MemoryFileUploadFactory() : fileUploadFactory);
	}

	private boolean parsed;

	/**
	 * Sets the loaded flag that indicates that input stream is loaded and parsed.
	 * Throws an exception if stream already loaded.
	 */
	protected void setParsed() throws IOException {
		if (parsed) {
			throw new IOException("Multi-part request already parsed");
		}
		parsed = true;
	}

	/**
	 * Returns true if multi-part request is already loaded.
	 */
	public boolean isParsed() {
		return parsed;
	}

	// ---------------------------------------------------------------- load and extract

	protected void putFile(final String name, final FileUpload value) {
		if (requestFiles == null) {
			requestFiles = new HashMap<>();
		}

		FileUpload[] fileUploads = requestFiles.get(name);

		if (fileUploads != null) {
			fileUploads = ArraysUtil.append(fileUploads, value);
		} else {
			fileUploads = new FileUpload[] {value};
		}

		requestFiles.put(name, fileUploads);
	}

	protected void putParameters(final String name, final String[] values) {
		if (requestParameters == null) {
			requestParameters = new HashMap<>();
		}
		requestParameters.put(name, values);
	}

	protected void putParameter(final String name, final String value) {
		if (requestParameters == null) {
			requestParameters = new HashMap<>();
		}

		String[] params = requestParameters.get(name);

		if (params != null) {
			params = ArraysUtil.append(params, value);
		} else {
			params = new String[] {value};
		}

		requestParameters.put(name, params);
	}

	/**
	 * Extracts uploaded files and parameters from the request data.
	 */
	public void parseRequestStream(final InputStream inputStream, final String encoding) throws IOException {
		setParsed();

		MultipartRequestInputStream input = new MultipartRequestInputStream(inputStream);
		input.readBoundary();
		while (true) {
			FileUploadHeader header = input.readDataHeader(encoding);
			if (header == null) {
				break;
			}

			if (header.isFile) {
				String fileName = header.fileName;
				if (fileName.length() > 0) {
					if (header.contentType.indexOf("application/x-macbinary") > 0) {
						input.skipBytes(128);
					}
				}
				FileUpload newFile = fileUploadFactory.create(input);
				newFile.processStream();
				if (fileName.length() == 0) {
					// file was specified, but no name was provided, therefore it was not uploaded
					if (newFile.getSize() == 0) {
						newFile.size = -1;
					}
				}
				putFile(header.formFieldName, newFile);
			} else {
				// no file, therefore it is regular form parameter.
				FastByteArrayOutputStream fbos = new FastByteArrayOutputStream();
				input.copyAll(fbos);
				String value = encoding != null ? new String(fbos.toByteArray(), encoding) : new String(fbos.toByteArray());
				putParameter(header.formFieldName, value);
			}

			input.skipBytes(1);
			input.mark(1);

			// read byte, but may be end of stream
			int nextByte = input.read();
			if (nextByte == -1 || nextByte == '-') {
				input.reset();
				break;
			}
			input.reset();
		}
	}

	// ---------------------------------------------------------------- parameters

	/**
	 * Returns single value of a parameter. If parameter name is used for
	 * more then one parameter, only the first one will be returned.
	 *
	 * @return parameter value, or null if not found
	 */
	public String getParameter(final String paramName) {
		if (requestParameters == null) {
			return null;
		}
		String[] values = requestParameters.get(paramName);
		if ((values != null) && (values.length > 0)) {
			return values[0];
		}
		return null;
	}

	/**
	 * Returns the names of the parameters contained in this request.
	 */
	public Set<String> getParameterNames() {
		if (requestParameters == null) {
			return Collections.emptySet();
		}
		return requestParameters.keySet();
	}

	/**
	 * Returns all values all of the values the given request parameter has.
	 */
	public String[] getParameterValues(final String paramName) {
		if (requestParameters == null) {
			return null;
		}
		return requestParameters.get(paramName);
	}

	/**
	 * Returns uploaded file.
	 * @param paramName parameter name of the uploaded file
	 * @return uploaded file or null if parameter name not found
	 */
	public FileUpload getFile(final String paramName) {
		if (requestFiles == null) {
			return null;
		}
		FileUpload[] values = requestFiles.get(paramName);
		if ((values != null) && (values.length > 0)) {
			return values[0];
		}
		return null;
	}

	/**
	 * Returns all uploaded files the given request parameter has.
	 */
	public FileUpload[] getFiles(final String paramName) {
		if (requestFiles == null) {
			return null;
		}
		return requestFiles.get(paramName);
	}

	/**
	 * Returns parameter names of all uploaded files.
	 */
	public Set<String> getFileParameterNames() {
		if (requestFiles == null) {
			return Collections.emptySet();
		}
		return requestFiles.keySet();
	}

}


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

import jodd.core.JoddCore;
import jodd.typeconverter.Converter;

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

/**
 * Example filter that sets the character encoding to be used in parsing the
 * incoming request, either unconditionally or only if the client did not
 * specify a character encoding.  Configuration of this filter is based on
 * the following initialization parameters:
 *
 *

 *
encoding - The character encoding to be configured
 *     for this request, either conditionally or unconditionally based on
 *     the ignore initialization parameter.  This parameter
 *     is required, so there is no default.

 *
ignore - If set to "true", any character encoding
 *     specified by the client is ignored, and the value returned by the
 *     selectEncoding() method is set.  If set to "false,
 *     selectEncoding() is called only if the
 *     client has not already specified an encoding.  By default, this
 *     parameter is set to "true".

 *

 *
 *
Although this filter can be used unchanged, it is also easy to
 * subclass it and make the selectEncoding() method more
 * intelligent about what encoding to choose, based on characteristics of
 * the incoming request (such as the values of the Accept-Language
 * and User-Agent headers, or a value stashed in the current
 * user's session.
 */

public class CharacterEncodingFilter implements Filter {

	/**
	 * The default character encoding to set for requests that pass through
	 * this filter.
	 */
	protected String encoding;

	/**
	 * The filter configuration object we are associated with.  If this value
	 * is null, this filter instance is not currently configured.
	 */
	protected FilterConfig filterConfig;

	/**
	 * Should a character encoding specified by the client be ignored?
	 */
	protected boolean ignore = true;

	/**
	 * Take this filter out of service.
	 */
	@Override
	public void destroy() {
		this.encoding = null;
		this.filterConfig = null;
	}

	/**
	 * Select and set (if specified) the character encoding to be used to
	 * interpret request parameters for this request.
	 *
	 * @param request  servlet request we are processing
	 * @param response servlet response we are creating
	 * @param chain    filter chain we are processing
	 *
	 * @exception IOException
	 *                   if an input/output error occurs
	 * @exception ServletException
	 *                   if a servlet error occurs
	 */
	@Override
	public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {

		// Conditionally select and set the character encoding to be used
		if (ignore || (request.getCharacterEncoding() == null)) {
			String encodingStr = selectEncoding(request);
			if (encodingStr != null) {
				request.setCharacterEncoding(encodingStr);
				response.setCharacterEncoding(encodingStr);
			}
		}
		chain.doFilter(request, response);
	}

	/**
	 * Place this filter into service.
	 *
	 * @param filterConfig The filter configuration object
	 */
	@Override
	public void init(final FilterConfig filterConfig) {

		this.filterConfig = filterConfig;
		this.encoding = filterConfig.getInitParameter("encoding");
		if (this.encoding == null) {
			this.encoding = JoddCore.encoding;
		}
		this.ignore = Converter.get().toBooleanValue(filterConfig.getInitParameter("ignore"), true);
	}

	/**
	 * Select an appropriate character encoding to be used, based on the
	 * characteristics of the current request and/or filter initialization
	 * parameters.  If no character encoding should be set, return
	 * null.
	 *

	 * The default implementation unconditionally returns the value configured
	 * by the encoding initialization parameter for this
	 * filter.
	 *
	 * @param request The servlet request we are processing
	 */
	protected String selectEncoding(final ServletRequest request) {
		return this.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.db.oom.sqlgen;

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

import static jodd.util.CharUtil.isAlpha;
import static jodd.util.CharUtil.isDigit;

/**
 * Internal template parser.
 */
class TemplateParser {

	// ---------------------------------------------------------------- parsing

	protected static final char ESCAPE_CHARACTER = '\\';
	protected static final String MACRO_TABLE = "$T{";
	protected static final String MACRO_COLUMN = "$C{";
	protected static final String MACRO_MATCH = "$M{";
	protected static final String MACRO_VALUE = "$V{";

	/**
	 * Parses template and returns generated sql builder.
	 */
	public void parse(final DbSqlBuilder sqlBuilder, final String template) {
		int length = template.length();
		int last = 0;
		while (true) {
			int mark = template.indexOf('$', last);
			if (mark == -1) {
				if (last < length) {
					sqlBuilder.appendRaw(template.substring(last));
				}
				break;
			}

			int escapesCount = countEscapes(template, mark);                // check if escaped
			if (escapesCount > 0) {
				boolean isEscaped = escapesCount % 2 != 0;
				int escapesToAdd = escapesCount >> 1;
				sqlBuilder.appendRaw(template.substring(last, mark - escapesCount + escapesToAdd) + '$');
				if (isEscaped) {
					last = mark + 1;
					continue;
				}
			} else {
				sqlBuilder.appendRaw(template.substring(last, mark));
			}

			int end;

			if (template.startsWith(MACRO_TABLE, mark)) {
				mark += MACRO_TABLE.length();
				end = findMacroEnd(template, mark);
				onTable(sqlBuilder, template.substring(mark, end));
			} else if (template.startsWith(MACRO_COLUMN, mark)) {
				mark += MACRO_COLUMN.length();
				end = findMacroEnd(template, mark);
				onColumn(sqlBuilder, template.substring(mark, end));
			} else if (template.startsWith(MACRO_MATCH, mark)) {
				mark += MACRO_MATCH.length();
				end = findMacroEnd(template, mark);
				onMatch(sqlBuilder, template.substring(mark, end));
			} else if (template.startsWith(MACRO_VALUE, mark)) {
				mark += MACRO_VALUE.length();
				end = findMacroEnd(template, mark);
				onValue(sqlBuilder, template.substring(mark, end));
			} else {
				mark++;           // reference found
				end = mark;       // find macro end
				while (end < length) {
					if (!isReferenceChar(template, end)) {
						break;
					}
					end++;
				}
				onReference(sqlBuilder, template.substring(mark, end));
				end--;
			}
			end++;
			last = end;
		}
	}

	protected static boolean isReferenceChar(final String template, final int index) {
		char c = template.charAt(index);
		if ((c == '+') && (template.charAt(index - 1) == '.')) {
			return true;
		}
		return isDigit(c) || isAlpha(c) || (c == '_') || (c == '.');
	}

	/**
	 * Finds macros end.
	 */
	protected int findMacroEnd(final String template, final int fromIndex) {
		int endIndex = template.indexOf('}', fromIndex);
		if (endIndex == -1) {
			throw new DbSqlBuilderException("Template syntax error, some macros are not closed. Error at: '..." + template.substring(fromIndex));
		}
		return endIndex;
	}

	/**
	 * Count escapes to the left.
	 */
	protected int countEscapes(final String template, int macroIndex) {
		macroIndex--;
		int escapeCount = 0;
		while (macroIndex >= 0) {
			if (template.charAt(macroIndex) != ESCAPE_CHARACTER) {
				break;
			}
			escapeCount++;
			macroIndex--;
		}
		return escapeCount;
	}

	// ---------------------------------------------------------------- handlers

	protected void onTable(final DbSqlBuilder sqlBuilder, final String allTables) {
		String[] tables = StringUtil.split(allTables, StringPool.COMMA);
		for (String table : tables) {
			sqlBuilder.table(table);
		}
	}

	protected void onColumn(final DbSqlBuilder sqlBuilder, final String allColumns) {
		int len = allColumns.length();
		int lastNdx = 0;

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

			if (c == ',') {
				sqlBuilder.column(allColumns.substring(lastNdx, i));
				lastNdx = i + 1;
				continue;
			}

			if (c == '[') {
				i = allColumns.indexOf(']', i) + 1;
				if (i == 0) {
					i = len;
				}
			}
		}

		sqlBuilder.column(allColumns.substring(lastNdx));
	}

	protected void onReference(final DbSqlBuilder sqlBuilder, final String reference) {
		sqlBuilder.ref(reference);
	}

	protected void onMatch(final DbSqlBuilder sqlBuilder, final String expression) {
		sqlBuilder.match(expression);
	}

	protected void onValue(final DbSqlBuilder sqlBuilder, final String expression) {
		sqlBuilder.columnValue(expression);
	}

}


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

package jodd.http;

import jodd.net.URLCoder;
import jodd.net.URLDecoder;
import jodd.util.StringBand;
import jodd.util.StringPool;
import jodd.util.StringUtil;

import java.util.Map;

/**
 * Few HTTP utilities.
 */
public class HttpUtil {

	// ---------------------------------------------------------------- query

	/**
	 * Builds a query string from given query map.
	 */
	public static String buildQuery(final HttpMultiMap<?> queryMap, final String encoding) {
		if (queryMap.isEmpty()) {
			return StringPool.EMPTY;
		}

		int queryMapSize = queryMap.size();

		StringBand query = new StringBand(queryMapSize * 4);

		int count = 0;
		for (Map.Entry<String, ?> entry : queryMap) {
			String key = entry.getKey();
			key = URLCoder.encodeQueryParam(key, encoding);

			Object value = entry.getValue();

			if (value == null) {
				if (count != 0) {
					query.append('&');
				}

				query.append(key);
				count++;
			} else {
				if (count != 0) {
					query.append('&');
				}

				query.append(key);
				count++;
				query.append('=');

				String valueString = URLCoder.encodeQueryParam(value.toString(), encoding);
				query.append(valueString);
			}
		}

		return query.toString();
	}

	/**
	 * Parses query from give query string. Values are optionally decoded.
	 */
	public static HttpMultiMap<String> parseQuery(final String query, final boolean decode) {

		final HttpMultiMap<String> queryMap = HttpMultiMap.newCaseInsensitiveMap();

		if (StringUtil.isBlank(query)) {
			return queryMap;
		}

		int lastNdx = 0;
		while (lastNdx < query.length()) {
			int ndx = query.indexOf('&', lastNdx);
			if (ndx == -1) {
				ndx = query.length();
			}

			final String paramAndValue = query.substring(lastNdx, ndx);

			ndx = paramAndValue.indexOf('=');

			if (ndx == -1) {
				queryMap.add(paramAndValue, null);
			}
			else {
				String name = paramAndValue.substring(0, ndx);
				if (decode) {
					name = URLDecoder.decodeQuery(name);
				}

				String value = paramAndValue.substring(ndx + 1);

				if (decode) {
					value = URLDecoder.decodeQuery(value);
				}

				queryMap.add(name, value);
			}
			lastNdx += paramAndValue.length() + 1;
		}

		return queryMap;
	}

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

	/**
	 * Makes nice header names.
	 */
	public static String prepareHeaderParameterName(final String headerName) {

		// special cases

		if (headerName.equals("etag")) {
			return HttpBase.HEADER_ETAG;
		}

		if (headerName.equals("www-authenticate")) {
			return "WWW-Authenticate";
		}

		char[] name = headerName.toCharArray();

		boolean capitalize = true;

		for (int i = 0; i < name.length; i++) {
			char c = name[i];

			if (c == '-') {
				capitalize = true;
				continue;
			}

			if (capitalize) {
				name[i] = Character.toUpperCase(c);
				capitalize = false;
			} else {
				name[i] = Character.toLowerCase(c);
			}
		}

		return new String(name);
	}

	// ---------------------------------------------------------------- content type

	/**
	 * Extracts media-type from value of "Content Type" header.
	 */
	public static String extractMediaType(final String contentType) {
		int index = contentType.indexOf(';');

		if (index == -1) {
			return contentType;
		}

		return contentType.substring(0, index);
	}

	/**
	 * @see #extractHeaderParameter(String, String, char)
	 */
	public static String extractContentTypeCharset(final String contentType) {
		return extractHeaderParameter(contentType, "charset", ';');
	}

	// ---------------------------------------------------------------- keep-alive

	/**
	 * Extract keep-alive timeout.
	 */
	public static String extractKeepAliveTimeout(final String keepAlive) {
		return extractHeaderParameter(keepAlive, "timeout", ',');
	}

	public static String extractKeepAliveMax(final String keepAlive) {
		return extractHeaderParameter(keepAlive, "max", ',');
	}

	// ---------------------------------------------------------------- header

	/**
	 * Extracts header parameter. Returns null
	 * if parameter not found.
	 */
	public static String extractHeaderParameter(final String header, final String parameter, final char separator) {
		int index = 0;

		while (true) {
			index = header.indexOf(separator, index);

			if (index == -1) {
				return null;
			}

			index++;

			// skip whitespaces
			while (index < header.length() && header.charAt(index) == ' ') {
				index++;
			}

			int eqNdx = header.indexOf('=', index);

			if (eqNdx == -1) {
				return null;
			}

			String paramName = header.substring(index, eqNdx);

			eqNdx++;

			if (!paramName.equalsIgnoreCase(parameter)) {
				index = eqNdx;
				continue;
			}

			int endIndex = header.indexOf(';', eqNdx);

			if (endIndex == -1) {
				return header.substring(eqNdx);
			} else {
				return header.substring(eqNdx, endIndex);
			}
		}
	}

}


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

package jodd.db;

import jodd.db.type.SqlType;
import jodd.typeconverter.Converter;
import jodd.typeconverter.TypeConverterManager;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.sql.Types;

/**
 * Various DB utilities.
 */
public class DbUtil {

	/**
	 * Closes statement safely without throwing an exception.
	 */
	public static void close(final Statement statement) {
		if (statement == null) {
			return;
		}

		try {
			statement.close();
		} catch (SQLException sex) {
			// ignore
		}
	}

	/**
	 * Closes result set safely without throwing an exception.
	 */
	public static void close(final ResultSet resultSet) {
		if (resultSet == null) {
			return;
		}
		try {
			resultSet.close();
		} catch (SQLException sex) {
			// ignore
		}
	}

	/**
	 * Returns long value of very first column in result set.
	 */
	public static long getFirstLong(final ResultSet resultSet) throws SQLException {
		if (resultSet.next()) {
			return resultSet.getLong(1);
		}
		return -1;
	}

	/**
	 * Returns int value of very first column in result set.
	 */
	public static int getFirstInt(final ResultSet resultSet) throws SQLException {
		if (resultSet.next()) {
			return resultSet.getInt(1);
		}
		return -1;
	}

	public static Object getFirstObject(final ResultSet resultSet) throws SQLException {
		if (resultSet.next()) {
			return resultSet.getObject(1);
		}
		return null;
	}

	/**
	 * Sets prepared statement object using target SQL type.
	 * Here Jodd makes conversion and not JDBC driver.
	 * See: http://www.tutorialspoint.com/jdbc/jdbc-data-types.htm
	 */
	public static void setPreparedStatementObject(final PreparedStatement preparedStatement, final int index, final Object value, final int targetSqlType) throws SQLException {
		if (value == null) {
			preparedStatement.setNull(index, Types.NULL);
			return;
		}

		switch (targetSqlType) {
			case Types.VARCHAR:
			case Types.LONGVARCHAR:
			case Types.CHAR:
				preparedStatement.setString(index, Converter.get().toString(value));
				break;

			case Types.INTEGER:
			case Types.SMALLINT:
			case Types.TINYINT:
				preparedStatement.setInt(index, Converter.get().toIntValue(value));
				break;

			case Types.BIGINT:
				preparedStatement.setLong(index, Converter.get().toLongValue(value));
				break;

			case Types.BOOLEAN:
			case Types.BIT:
				preparedStatement.setBoolean(index, Converter.get().toBooleanValue(value));
				break;

			case Types.DATE:
				preparedStatement.setDate(index, TypeConverterManager.get().convertType(value, java.sql.Date.class));
				break;

			case Types.NUMERIC:
			case Types.DECIMAL:
				preparedStatement.setBigDecimal(index, Converter.get().toBigDecimal(value));
				break;

			case Types.DOUBLE:
				preparedStatement.setDouble(index, Converter.get().toDoubleValue(value));
				break;

			case Types.REAL:
			case Types.FLOAT:
				preparedStatement.setFloat(index, Converter.get().toFloatValue(value));
			    break;

			case Types.TIME:
				preparedStatement.setTime(index, TypeConverterManager.get().convertType(value, java.sql.Time.class));
				break;

			case Types.TIMESTAMP:
				preparedStatement.setTimestamp(index, TypeConverterManager.get().convertType(value, Timestamp.class));
				break;

			case Types.BINARY:
			case Types.VARBINARY:
				preparedStatement.setBytes(index, TypeConverterManager.get().convertType(value, byte[].class));
				break;

			default:
				if (targetSqlType != SqlType.DB_SQLTYPE_NOT_AVAILABLE) {
					preparedStatement.setObject(index, value, targetSqlType);
				} else {
					preparedStatement.setObject(index, value);
				}
		}

	}

}


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

package jodd.madvoc.macro;

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

/**
 * Common class for PathMacro implementations.
 * Assume that macros are defined in the following way:
 * prefix{name:pattern}suffix.
 * Pattern is optional, and if missing all values are matched.
 */
public abstract class BasePathMacros implements PathMacros {

	protected int macrosCount;
	protected String[] names;		// macros names
	protected String[] patterns;	// macros patterns, if defined, elements may be null
	protected String[] fixed;		// array of fixed strings surrounding macros

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean init(final String actionPath, final String[] separators) {
		String prefix = separators[0];
		String split = separators[1];
		String suffix = separators[2];

		macrosCount = StringUtil.count(actionPath, prefix);

		if (macrosCount == 0) {
			return false;
		}

		names = new String[macrosCount];
		patterns = new String[macrosCount];
		fixed = new String[macrosCount + 1];

		int offset = 0;
		int i = 0;

		while (true) {
			int[] ndx = StringUtil.indexOfRegion(actionPath, prefix, suffix, offset);

			if (ndx == null) {
				break;
			}

			fixed[i] = actionPath.substring(offset, ndx[0]);

			String name = actionPath.substring(ndx[1], ndx[2]);

			// name:pattern
			String pattern = null;

			int colonNdx = name.indexOf(split);
			if (colonNdx != -1) {
				pattern = name.substring(colonNdx + 1).trim();

				name = name.substring(0, colonNdx).trim();
			}

			this.patterns[i] = pattern;
			this.names[i] = name;

			// iterate
			offset = ndx[3];
			i++;
		}

		if (offset < actionPath.length()) {
			fixed[i] = actionPath.substring(offset);
		} else {
			fixed[i] = StringPool.EMPTY;
		}

		return true;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String[] names() {
		return names;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String[] patterns() {
		return patterns;
	}

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int match(final String actionPath) {
		String[] values = process(actionPath, true);

		if (values == null) {
			return -1;
		}

		int macroChars = 0;
		for (String value : values) {
			if (value != null) {
				macroChars += value.length();
			}
		}

		return actionPath.length() - macroChars;
	}

	/**
	 * Matches the stripped value with the pattern.
	 * Pattern at provided macroIndex is not null!
	 */
	protected abstract boolean matchValue(int macroIndex, String value);

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String[] extract(final String actionPath) {
		return process(actionPath, false);
	}

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

	/**
	 * Process action path in two modes: matching mode and extracting mode.
	 * @return string array of extracted macro values (null element is allowed) or null
	 */
	private String[] process(final String actionPath, final boolean match) {
		// first check the first fixed as a prefix
		if (match && !actionPath.startsWith(fixed[0])) {
			return null;
		}

		String[] values = new String[macrosCount];

		int offset = fixed[0].length();
		int i = 0;

		while (i < macrosCount) {
			int nexti = i;

			// defines next fixed string to match
			String nextFixed;
			while (true) {
				nexti++;
				if (nexti > macrosCount) {
					nextFixed = null;	// match to the end of line
					break;
				}
				nextFixed = fixed[nexti];
				if (nextFixed.length() != 0) {
					break;
				}
				// next fixed is an empty string, so skip the next macro.
			}

			// find next fixed string
			int ndx;

			if (nextFixed != null) {
				ndx = actionPath.indexOf(nextFixed, offset);
			} else {
				ndx = actionPath.length();
			}

			if (ndx == -1) {
				return null;
			}

			String macroValue = actionPath.substring(offset, ndx);
			values[i] = macroValue;

			if (match && patterns[i] != null) {
				if (!matchValue(i, macroValue)) {
					return null;
				}
			}

			if (nextFixed == null) {
				offset = ndx;
				break;
			}

			// iterate
			int nextFixedLength = nextFixed.length();
			offset = ndx + nextFixedLength;

			i = nexti;
		}

		if (offset != actionPath.length()) {
			// action path is not consumed fully during this matching
			return null;
		}

		return values;
	}

}


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

package jodd.lagarto.dom;

/**
 * Renderer is a factory for {@link jodd.lagarto.dom.NodeVisitor} that
 * renders DOM tree to string.
 */
public class LagartoHtmlRenderer {

	/**
	 * Renders node to appendable.
	 */
	public String toHtml(final Node node, final Appendable appendable) {
		NodeVisitor renderer = createRenderer(appendable);

		node.visit(renderer);

		return appendable.toString();
	}

	/**
	 * Renders node children to appendable.
	 */
	public String toInnerHtml(final Node node, final Appendable appendable) {
		NodeVisitor renderer = createRenderer(appendable);

		node.visitChildren(renderer);

		return appendable.toString();
	}

	protected NodeVisitor createRenderer(final Appendable appendable) {
		return new LagartoHtmlRendererNodeVisitor(appendable);
	}

}


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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

/**
 * Special HttpServletRequestWrapper allows filtering of the HTTP headers.
 */
public class DecoraRequestWrapper extends HttpServletRequestWrapper {

	public DecoraRequestWrapper(final HttpServletRequest httpServletRequest) {
		super(httpServletRequest);
	}

	/**
	 * Returns null for excluded HTTP headers.
	 */
	@Override
	public String getHeader(final String header) {
		if (isExcluded(header)) {
			return null;
		}
		return super.getHeader(header);
	}

	/**
	 * Returns -1 for excluded HTTP headers.
	 */
	@Override
	public long getDateHeader(final String header) {
		if (isExcluded(header)) {
			return -1;
		}
		return super.getDateHeader(header);
	}

	/**
	 * Checks if header name is excluded.
	 */
	protected boolean isExcluded(final String header) {
		return "If-Modified-Since".equalsIgnoreCase(header);
	}

}