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

package jodd.proxetta.asm;

import jodd.asm.AsmUtil;
import jodd.asm.EmptyAnnotationVisitor;
import jodd.asm7.AnnotationVisitor;
import jodd.proxetta.AnnotationInfo;

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

/**
 * Reads annotation inner data.
 */
@SuppressWarnings({"AnonymousClassVariableHidesContainingMethodVariable"})
public class AnnotationReader extends EmptyAnnotationVisitor implements AnnotationInfo {

	public static final AnnotationInfo[] NO_ANNOTATIONS = new AnnotationInfo[0];

	protected final String desc;
	protected final String className;
	protected final boolean visible;
	protected final Map<String, Object> elements;

	public AnnotationReader(final String desc, final boolean visible) {
		this.desc = desc;
		this.visible = visible;
		this.elements = new HashMap<>();
		this.className = AsmUtil.typeref2Name(desc);
	}

	// ---------------------------------------------------------------- info

	@Override
	public String getAnnotationClassname() {
		return className;
	}

	@Override
	public String getAnnotationSignature() {
		return desc;
	}

	@Override
	public boolean isVisible() {
		return visible;
	}

	@Override
	public Object getElement(final String name) {
		return elements.get(name);
	}

	@Override
	public Set<String> getElementNames() {
		return elements.keySet();
	}

	// ---------------------------------------------------------------- visitor

	@Override
	public void visit(final String name, final Object value) {
		elements.put(name, value);
	}

	@Override
	public void visitEnum(final String name, final String desc, final String value) {
		elements.put(name, new String[]{desc, value});
	}

	@Override
	public AnnotationVisitor visitAnnotation(final String name, final String desc) {
		AnnotationReader nestedAnnotation = new AnnotationReader(desc, true);
		elements.put(name, nestedAnnotation);
		return nestedAnnotation;
	}

	@Override
	public AnnotationVisitor visitArray(final String name) {
		final List<Object> array = new ArrayList<>();
		return new EmptyAnnotationVisitor() {

			@Override
			public void visit(final String name, final Object value) {
				array.add(value);
			}

			@Override
			public void visitEnd() {
				Object[] data = array.toArray(new Object[0]);
				elements.put(name, data);
			}
		};
	}

}


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

package jodd.madvoc;

import jodd.log.Logger;
import jodd.log.LoggerFactory;
import jodd.madvoc.component.ActionConfigManager;
import jodd.madvoc.component.ActionMethodParamNameResolver;
import jodd.madvoc.component.ActionMethodParser;
import jodd.madvoc.component.ActionPathRewriter;
import jodd.madvoc.component.ActionsManager;
import jodd.madvoc.component.AsyncActionExecutor;
import jodd.madvoc.component.ContextInjectorComponent;
import jodd.madvoc.component.FileUploader;
import jodd.madvoc.component.FiltersManager;
import jodd.madvoc.component.InterceptorsManager;
import jodd.madvoc.component.MadvocComponentLifecycle;
import jodd.madvoc.component.MadvocComponentLifecycle.Init;
import jodd.madvoc.component.MadvocComponentLifecycle.Ready;
import jodd.madvoc.component.MadvocComponentLifecycle.Start;
import jodd.madvoc.component.MadvocContainer;
import jodd.madvoc.component.MadvocController;
import jodd.madvoc.component.MadvocEncoding;
import jodd.madvoc.component.ResultMapper;
import jodd.madvoc.component.ResultsManager;
import jodd.madvoc.component.RootPackages;
import jodd.madvoc.component.ScopeDataInspector;
import jodd.madvoc.component.ScopeResolver;
import jodd.madvoc.component.ServletContextProvider;
import jodd.madvoc.meta.Action;
import jodd.madvoc.meta.RestAction;
import jodd.props.Props;
import jodd.util.ClassConsumer;
import jodd.util.function.Consumers;

import javax.servlet.ServletContext;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;

/**
 * Web application contains all configurations and holds all managers and controllers of one web application.
 * Custom implementations may override this class to enhance several different functionality.
 */
@SuppressWarnings({"UnusedDeclaration"})
public class WebApp {

	private static Logger log;

	private static final String WEBAPP_ATTR = WebApp.class.getName();

	public static WebApp createWebApp() {
		return new WebApp();
	}

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

	// ---------------------------------------------------------------- builder

	protected ServletContext servletContext;
	private List<Props> propsList = new ArrayList<>();
	private List<Map<String, Object>> paramsList = new ArrayList<>();
	private List<ClassConsumer> madvocComponents = new ArrayList<>();
	private List<Object> madvocComponentInstances = new ArrayList<>();
	private Consumers<MadvocRouter> madvocRouterConsumers = Consumers.empty();

	/**
	 * Defines params to load.
	 */
	public WebApp withParams(final Props props) {
		propsList.add(props);
		return this;
	}

	public WebApp withParams(final Map<String, Object> params) {
		paramsList.add(params);
		return this;
	}

	/**
	 * Defines servlet context. Must be called in the web environment.
	 */
	public WebApp bindServletContext(final ServletContext servletContext) {
		this.servletContext = servletContext;
		this.servletContext.setAttribute(WEBAPP_ATTR, this);
		return this;
	}

	/**
	 * Registers additional Madvoc components after the registration of default components.
	 */
	public WebApp registerComponent(final Class<?> madvocComponent) {
		Objects.requireNonNull(madvocComponent);
		madvocComponents.add(ClassConsumer.of(madvocComponent));
		return this;
	}

	public <T> WebApp registerComponent(final Class<T> madvocComponent, final Consumer<T> componentConsumer) {
		Objects.requireNonNull(madvocComponent);
		madvocComponents.add(ClassConsumer.of(madvocComponent, componentConsumer));
		return this;
	}

	/**
	 * Registers Madvoc component instance. Use with caution, as injection of
	 * components registered after this will fail.
	 */
	public WebApp registerComponent(final Object madvocComponent) {
		Objects.requireNonNull(madvocComponent);
		madvocComponentInstances.add(madvocComponent);
		return this;
	}

	/**
	 * Configures the action configurations.
	 */
	public  WebApp withActionConfig(final Class actionConfigType, final Consumer actionConfigConsumer) {
		withRegisteredComponent(ActionConfigManager.class, acm -> acm.with(actionConfigType, actionConfigConsumer));
		return this;
	}

	// ---------------------------------------------------------------- main components

	protected final MadvocContainer madvocContainer;
	protected Consumers<MadvocContainer> componentConfigs = Consumers.empty();

	public WebApp() {
		madvocContainer = new MadvocContainer();
		madvocContainer.registerComponentInstance(madvocContainer);
	}

	/**
	 * Returns {@link MadvocContainer Madvoc container} that maintain all Madvoc components.
	 */
	public MadvocContainer madvocContainer() {
		return madvocContainer;
	}

	/**
	 * Configures a component. While the signature is the same as for {@link #registerComponent(Class, Consumer)}
	 * this method does not register component, just operates on an already registered one.
	 */
	public <T> WebApp withRegisteredComponent(final Class<T> madvocComponent, final Consumer<T> componentConsumer) {
		if (componentConfigs == null) {
			// component is already configured
			final T component = madvocContainer.lookupComponent(madvocComponent);
			if (component == null) {
				throw new MadvocException("Component not found: " + madvocComponent.getName());
			}
			componentConsumer.accept(component);
		}
		else {
			componentConfigs.add(madvocContainer -> {
				final T component = madvocContainer.lookupComponent(madvocComponent);
				if (component == null) {
					throw new MadvocException("Component not found: " + madvocComponent.getName());
				}
				componentConsumer.accept(component);
			});
		}
		return this;
	}

	/**
	 * Defines a route manually using {@link MadvocRouter}.
	 */
	public WebApp router(final Consumer<MadvocRouter> madvocAppConsumer) {
		madvocRouterConsumers.add(madvocAppConsumer);
		return this;
	}

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

	/**
	 * Initializes and starts web application.
	 */
	public WebApp start(final Consumer<MadvocRouter> madvocRouterConsumer) {
		madvocRouterConsumers.add(madvocRouterConsumer);
		return start();
	}

	/**
	 * Initializes and starts web application.
	 */
	public WebApp start() {
		log = LoggerFactory.getLogger(WebApp.class);

		log.debug("Initializing Madvoc WebApp");

		//// params & props
		for (final Map<String, Object> params : paramsList) {
			madvocContainer.defineParams(params);
		}
		for (final Props props : propsList) {
			madvocContainer.defineParams(props);
		}
		propsList = null;

		//// components
		registerMadvocComponents();

		madvocComponents.forEach(
			madvocComponent -> madvocContainer.registerComponent(madvocComponent.type(), madvocComponent.consumer()));
		madvocComponents = null;

		madvocComponentInstances.forEach(madvocContainer::registerComponentInstance);
		madvocComponentInstances = null;

		configureDefaults();

		//// listeners
		madvocContainer.fireEvent(Init.class);

		//// component configuration
		componentConfigs.accept(madvocContainer);
		componentConfigs = null;

		initialized();

		madvocContainer.fireEvent(Start.class);

		if (!madvocRouterConsumers.isEmpty()) {

			final MadvocRouter madvocRouter = MadvocRouter.create();

			madvocContainer.registerComponentInstance(madvocRouter);

			madvocRouterConsumers.accept(madvocRouter);
		}
		madvocRouterConsumers = null;

		started();

		madvocContainer.fireEvent(Ready.class);

		ready();

		return this;
	}

	/**
	 * Configure defaults.
	 */
	protected void configureDefaults() {
		final ActionConfigManager actionConfigManager =
			madvocContainer.lookupComponent(ActionConfigManager.class);

		actionConfigManager.registerAnnotation(Action.class);
		actionConfigManager.registerAnnotation(RestAction.class);
	}

	/**
	 * Registers default Madvoc components.
	 */
	protected void registerMadvocComponents() {
		if (madvocContainer == null) {
			throw new MadvocException("Madvoc WebApp not initialized.");
		}

		log.debug("Registering Madvoc WebApp components");

		madvocContainer.registerComponent(MadvocEncoding.class);

		madvocContainer.registerComponentInstance(new ServletContextProvider(servletContext));

		madvocContainer.registerComponent(ActionConfigManager.class);
		madvocContainer.registerComponent(ActionMethodParamNameResolver.class);
		madvocContainer.registerComponent(ActionMethodParser.class);
		madvocContainer.registerComponent(ActionPathRewriter.class);
		madvocContainer.registerComponent(ActionsManager.class);
		madvocContainer.registerComponent(ContextInjectorComponent.class);
		madvocContainer.registerComponent(InterceptorsManager.class);
		madvocContainer.registerComponent(FiltersManager.class);
		madvocContainer.registerComponent(MadvocController.class);
		madvocContainer.registerComponent(RootPackages.class);
		madvocContainer.registerComponent(ResultsManager.class);
		madvocContainer.registerComponent(ResultMapper.class);
		madvocContainer.registerComponent(ScopeResolver.class);
		madvocContainer.registerComponent(ScopeDataInspector.class);
		madvocContainer.registerComponent(AsyncActionExecutor.class);
		madvocContainer.registerComponent(FileUploader.class);
	}

	/**
	 * Called when Madvoc is initialized, at the end of the {@link Init INIT} phase.
	 * @see MadvocComponentLifecycle
	 */
	protected void initialized() {
	}

	/**
	 * Called when Madvoc is started, at the end of the {@link Start START} phase.
	 * @see MadvocComponentLifecycle
	 */
	protected void started() {
	}

	/**
	 * Called when Madvoc is ready, at the end of the {@link Ready READY} phase.
	 * @see MadvocComponentLifecycle
	 */
	protected void ready() {
	}

	/**
	 * Shutdows the web application. Triggers the STOP event.
	 * @see MadvocComponentLifecycle
	 */
	public void shutdown() {
		log.info("Madvoc shutting down...");

		madvocContainer.fireEvent(MadvocComponentLifecycle.Stop.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.findfile;

import jodd.inex.InExRules;
import jodd.io.FileNameUtil;
import jodd.io.FileUtil;
import jodd.io.StreamUtil;
import jodd.io.ZipUtil;
import jodd.util.ArraysUtil;
import jodd.util.ClassLoaderUtil;
import jodd.util.StringUtil;
import jodd.util.function.Consumers;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Consumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import static jodd.inex.InExRuleMatcher.WILDCARD_PATH_RULE_MATCHER;
import static jodd.inex.InExRuleMatcher.WILDCARD_RULE_MATCHER;

/**
 * Convenient class path scanner.
 */
public class ClassScanner {

	private static final String CLASS_FILE_EXT = ".class";
	private static final String JAR_FILE_EXT = ".jar";

	/**
	 * Create new class scanner.
	 */
	public static ClassScanner create() {
		return new ClassScanner();
	}

	public ClassScanner() {
		this.rulesJars = new InExRules<>(WILDCARD_PATH_RULE_MATCHER);
		this.rulesEntries = new InExRules<>(WILDCARD_RULE_MATCHER);

		excludeJars(SYSTEM_JARS);
	}

	// ---------------------------------------------------------------- excluded jars

	/**
	 * Array of system jars that are excluded from the search.
	 * By default, these paths are common for linux, windows and mac.
	 */
	protected static final String[] SYSTEM_JARS = new String[] {
		"**/jre/lib/*.jar",
		"**/jre/lib/ext/*.jar",
		"**/Java/Extensions/*.jar",
		"**/Classes/*.jar"
	};

	protected static final String[] COMMONLY_EXCLUDED_JARS = new String[] {
		"**/tomcat*",
		"**/jetty*",
		"**/javafx*",
		"**/junit*",
		"**/javax*",
		"**/org.eclipse.*",
		"**/ant*",
		"**/idea_rt.jar",
	};

	protected final InExRules<String, String, String> rulesJars;

	/**
	 * Specify excluded jars.
	 */
	public ClassScanner excludeJars(final String... excludedJars) {
		for (final String excludedJar : excludedJars) {
			rulesJars.exclude(excludedJar);
		}
		return this;
	}

	/**
	 * Exclude some commonly unused jars.
	 */
	public ClassScanner excludeCommonJars() {
		return excludeJars(COMMONLY_EXCLUDED_JARS);
	}

	/**
	 * Specify included jars.
	 */
	public ClassScanner includeJars(final String... includedJars) {
		for (final String includedJar : includedJars) {
			rulesJars.include(includedJar);
		}
		return this;
	}

	/**
	 * Sets white/black list mode for jars.
	 */
	public ClassScanner includeAllJars(final boolean blacklist) {
		if (blacklist) {
			rulesJars.blacklist();
		} else {
			rulesJars.whitelist();
		}
		return this;
	}

	/**
	 * Sets white/black list mode for jars.
	 */
	public ClassScanner excludeAllJars(final boolean whitelist) {
		if (whitelist) {
			rulesJars.whitelist();
		} else {
			rulesJars.blacklist();
		}
		return this;
	}

	// ---------------------------------------------------------------- included entries

	protected static final String[] COMMONLY_EXCLUDED_ENTRIES = new String[] {
		"java.*",
		"ch.qos.logback.*",
		"sun.*",
		"com.sun.*",
		"org.eclipse.*",
	};

	protected final InExRules<String, String, String> rulesEntries;
	protected boolean detectEntriesMode = false;

	/**
	 * Sets included set of names that will be considered during configuration.
	 * @see InExRules
	 */
	public ClassScanner includeEntries(final String... includedEntries) {
		for (final String includedEntry : includedEntries) {
			rulesEntries.include(includedEntry);
		}
		return this;
	}

	/**
	 * Sets white/black list mode for entries.
	 */
	public ClassScanner includeAllEntries(final boolean blacklist) {
		if (blacklist) {
			rulesEntries.blacklist();
		} else {
			rulesEntries.whitelist();
		}
		return this;
	}
	/**
	 * Sets white/black list mode for entries.
	 */
	public ClassScanner excludeAllEntries(final boolean whitelist) {
		if (whitelist) {
			rulesEntries.whitelist();
		} else {
			rulesEntries.blacklist();
		}
		return this;
	}

	/**
	 * Sets excluded names that narrows included set of packages.
	 * @see InExRules
	 */
	public ClassScanner excludeEntries(final String... excludedEntries) {
		for (final String excludedEntry : excludedEntries) {
			rulesEntries.exclude(excludedEntry);
		}
		return this;
	}

	/**
	 * Excludes some commonly skipped packages.
	 */
	public ClassScanner excludeCommonEntries() {
		return excludeEntries(COMMONLY_EXCLUDED_ENTRIES);
	}

	public ClassScanner detectEntriesMode(final boolean detectMode) {
		this.detectEntriesMode = detectMode;
		return this;
	}

	// ---------------------------------------------------------------- implementation

	/**
	 * If set to true all files will be scanned and not only classes.
	 */
	protected boolean includeResources;
	/**
	 * If set to true exceptions for entry scans are ignored.
	 */
	protected boolean ignoreException;

	public ClassScanner includeResources(final boolean includeResources) {
		this.includeResources = includeResources;
		return this;
	}

	/**
	 * Sets if exceptions during scanning process should be ignored or not.
	 */
	public ClassScanner ignoreException(final boolean ignoreException) {
		this.ignoreException = ignoreException;
		return this;
	}

	// ---------------------------------------------------------------- scan

	/**
	 * Returns true if some JAR file has to be accepted.
	 */
	protected boolean acceptJar(final File jarFile) {
		String path = jarFile.getAbsolutePath();
		path = FileNameUtil.separatorsToUnix(path);

		return rulesJars.match(path);
	}

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

	/**
	 * Scans classes inside single JAR archive. Archive is scanned as a zip file.
	 * @see #onEntry(ClassPathEntry)
	 */
	protected void scanJarFile(final File file) {
		final ZipFile zipFile;
		try {
			zipFile = new ZipFile(file);
		} catch (IOException ioex) {
			if (!ignoreException) {
				throw new FindFileException("Invalid zip: " + file.getName(), ioex);
			}
			return;
		}
		final Enumeration entries = zipFile.entries();
		while (entries.hasMoreElements()) {
			final ZipEntry zipEntry = (ZipEntry) entries.nextElement();
			final String zipEntryName = zipEntry.getName();
			try {
				if (StringUtil.endsWithIgnoreCase(zipEntryName, CLASS_FILE_EXT)) {
					final String entryName = prepareEntryName(zipEntryName, true);
					final ClassPathEntry classPathEntry = new ClassPathEntry(entryName, zipFile, zipEntry);
					try {
						scanEntry(classPathEntry);
					} finally {
						classPathEntry.closeInputStream();
					}
				} else if (includeResources) {
					final String entryName = prepareEntryName(zipEntryName, false);
					final ClassPathEntry classPathEntry = new ClassPathEntry(entryName, zipFile, zipEntry);
					try {
						scanEntry(classPathEntry);
					} finally {
						classPathEntry.closeInputStream();
					}
				}
			} catch (RuntimeException rex) {
				if (!ignoreException) {
					ZipUtil.close(zipFile);
					throw rex;
				}
			}
		}
		ZipUtil.close(zipFile);
	}

	/**
	 * Scans single classpath directory.
	 * @see #onEntry(ClassPathEntry)
	 */
	protected void scanClassPath(final File root) {
		String rootPath = root.getAbsolutePath();
		if (!rootPath.endsWith(File.separator)) {
			rootPath += File.separatorChar;
		}

		final FindFile ff = FindFile.create().includeDirs(false).recursive(true).searchPath(rootPath);
		File file;
		while ((file = ff.nextFile()) != null) {
			final String filePath = file.getAbsolutePath();
			try {
				if (StringUtil.endsWithIgnoreCase(filePath, CLASS_FILE_EXT)) {
					scanClassFile(filePath, rootPath, file, true);
				} else if (includeResources) {
					scanClassFile(filePath, rootPath, file, false);
				}
			} catch (RuntimeException rex) {
				if (!ignoreException) {
					throw rex;
				}
			}
		}
	}

	protected void scanClassFile(final String filePath, final String rootPath, final File file, final boolean isClass) {
		if (StringUtil.startsWithIgnoreCase(filePath, rootPath)) {
			final String entryName = prepareEntryName(filePath.substring(rootPath.length()), isClass);
			final ClassPathEntry classPathEntry = new ClassPathEntry(entryName, file);
			try {
				scanEntry(classPathEntry);
			} finally {
				classPathEntry.closeInputStream();
			}
		}
	}

	/**
	 * Prepares resource and class names. For classes, it strips '.class' from the end and converts
	 * all (back)slashes to dots. For resources, it replaces all backslashes to slashes.
	 */
	protected String prepareEntryName(final String name, final boolean isClass) {
		String entryName = name;
		if (isClass) {
			entryName = name.substring(0, name.length() - 6);		// 6 == ".class".length()
			entryName = StringUtil.replaceChar(entryName, '/', '.');
			entryName = StringUtil.replaceChar(entryName, '\\', '.');
		} else {
			entryName = '/' + StringUtil.replaceChar(entryName, '\\', '/');
		}
		return entryName;
	}

	/**
	 * Returns true if some entry name has to be accepted.
	 * @see #prepareEntryName(String, boolean)
	 * @see #scanEntry(ClassPathEntry)
	 */
	protected boolean acceptEntry(final String entryName) {
		return rulesEntries.match(entryName);
	}

	/**
	 * If entry name is {@link #acceptEntry(String) accepted} invokes {@link #onEntry(ClassPathEntry)} a callback}.
	 */
	protected void scanEntry(final ClassPathEntry classPathEntry) {
		if (!acceptEntry(classPathEntry.name())) {
			return;
		}
		try {
			onEntry(classPathEntry);
		} catch (Exception ex) {
			throw new FindFileException("Scan entry error: " + classPathEntry, ex);
		}
	}

	// ---------------------------------------------------------------- callback

	private Consumers<ClassPathEntry> entryDataConsumers = Consumers.empty();

	/**
	 * Registers a {@link ClassPathEntry class path entry} consumer.
	 * It will be called on each loaded entry.
	 */
	public ClassScanner registerEntryConsumer(final Consumer<ClassPathEntry> entryDataConsumer) {
		entryDataConsumers.add(entryDataConsumer);
		return this;
	}

	/**
	 * Called during classpath scanning when class or resource is found.
	 *

	 *
Class name is java-alike class name (pk1.pk2.class) that may be immediately used
	 * for dynamic loading.

	 *
Resource name starts with '\' and represents either jar path (\pk1/pk2/res) or relative file path (\pk1\pk2\res).

	 *

	 *
	 * InputStream is provided by InputStreamProvider and opened lazy.
	 * Once opened, input stream doesn't have to be closed - this is done by this class anyway.
	 */
	protected void onEntry(final ClassPathEntry classPathEntry) {
		entryDataConsumers.accept(classPathEntry);
	}

	// ---------------------------------------------------------------- utilities

	/**
	 * Returns type signature bytes used for searching in class file.
	 */
	public static byte[] bytecodeSignatureOfType(final Class type) {
		final String name = 'L' + type.getName().replace('.', '/') + ';';
		return name.getBytes();
	}

	// ---------------------------------------------------------------- provider

	/**
	 * Provides input stream on demand. Input stream is not open until get().
	 */
	public class ClassPathEntry {

		private final File file;
		private final ZipFile zipFile;
		private final ZipEntry zipEntry;
		private final String name;

		ClassPathEntry(final String name, final ZipFile zipFile, final ZipEntry zipEntry) {
			this.name = name;
			this.zipFile = zipFile;
			this.zipEntry = zipEntry;
			this.file = null;
			this.inputStream = null;
		}
		ClassPathEntry(final String name, final File file) {
			this.name = name;
			this.file = file;
			this.zipEntry = null;
			this.zipFile = null;
			this.inputStream = null;
		}

		private InputStream inputStream;
		private byte[] inputStreamBytes;

		/**
		 * Returns entry name.
		 */
		public String name() {
			return name;
		}

		/**
		 * Returns true if archive.
		 */
		public boolean isArchive() {
			return zipFile != null;
		}

		/**
		 * Returns archive name or null if entry is not inside archived file.
		 */
		public String archiveName() {
			if (zipFile != null) {
				return zipFile.getName();
			}
			return null;
		}

		/**
		 * Returns true if class contains {@link #bytecodeSignatureOfType(Class) type signature}.
		 * It searches the class content for bytecode signature. This is the fastest way of finding if come
		 * class uses some type. Please note that if signature exists it still doesn't means that class uses
		 * it in expected way, therefore, class should be loaded to complete the scan.
		 */
		public boolean isTypeSignatureInUse(final byte[] bytes) {
			try {
				final byte[] data = readBytes();
				final int index = ArraysUtil.indexOf(data, bytes);
				return index != -1;
			} catch (IOException ioex) {
				throw new FindFileException("Read error", ioex);
			}
		}

		/**
		 * Reads stream bytes. Since stream can be read only once, the byte content
		 * is cached.
		 */
		public byte[] readBytes() throws IOException {
			openInputStream();

			if (inputStreamBytes == null) {
				inputStreamBytes = StreamUtil.readBytes(inputStream);
			}
			return inputStreamBytes;
		}

		/**
		 * Opens zip entry or plain file and returns its input stream.
		 */
		public InputStream openInputStream() {
			if (inputStream != null) {
				return inputStream;
			}
			if (zipFile != null && zipEntry != null) {
				try {
					inputStream = zipFile.getInputStream(zipEntry);
					return inputStream;
				} catch (IOException ioex) {
					throw new FindFileException("Input stream error: '" + zipFile.getName()
						+ "', entry: '" + zipEntry.getName() + "'." , ioex);
				}
			}
			if (file != null) {
				try {
					inputStream = new FileInputStream(file);
					return inputStream;
				} catch (FileNotFoundException fnfex) {
					throw new FindFileException("Unable to open: " + file.getAbsolutePath(), fnfex);
				}
			}
			throw new FindFileException("Unable to open stream: " + name());
		}

		/**
		 * Closes input stream if opened.
		 */
		public void closeInputStream() {
			if (inputStream == null) {
				return;
			}
			StreamUtil.close(inputStream);
			inputStream = null;
			inputStreamBytes = null;
		}

		/**
		 * Loads class by its name. If {@link #ignoreException} is set,
		 * no exception is thrown, but null is returned.
		 */
		public Class loadClass() throws ClassNotFoundException {
			try {
				return ClassLoaderUtil.loadClass(name);
			} catch (ClassNotFoundException | Error cnfex) {
				if (ignoreException) {
					return null;
				}
				throw cnfex;
			}
		}

		@Override
		public String toString() {
			return "ClassPathEntry{" + name + '\'' +'}';
		}
	}

	// ---------------------------------------------------------------- public scanning

	private Set<File> filesToScan = new LinkedHashSet<>();

	/**
	 * Scans URLs. If (#ignoreExceptions} is set, exceptions
	 * per one URL will be ignored and loops continues.
	 */
	public ClassScanner scan(final URL... urls) {
		for (final URL url : urls) {
			final File file = FileUtil.toContainerFile(url);
			if (file == null) {
				if (!ignoreException) {
					throw new FindFileException("URL is not a valid file: " + url);
				}
			}
			else {
				filesToScan.add(file);
			}
		}
		return this;
	}

	/**
	 * Scans {@link jodd.util.ClassLoaderUtil#getDefaultClasspath() default class path}.
	 */
	public ClassScanner scanDefaultClasspath() {
		return scan(ClassLoaderUtil.getDefaultClasspath());
	}

	/**
	 * Scans provided paths.
	 */
	public ClassScanner scan(final File... paths) {
		filesToScan.addAll(Arrays.asList(paths));
		return this;
	}

	/**
	 * Scans provided paths.
	 */
	public ClassScanner scan(final String... paths) {
		for (final String path : paths) {
			filesToScan.add(new File(path));
		}
		return this;
	}

	/**
	 * Starts with the scanner.
	 */
	public void start() {
		if (detectEntriesMode) {
			rulesEntries.detectMode();
		}

		filesToScan.forEach(file -> {
			final String path = file.getAbsolutePath();
			if (StringUtil.endsWithIgnoreCase(path, JAR_FILE_EXT)) {
				if (!acceptJar(file)) {
					return;
				}
				scanJarFile(file);
			} else if (file.isDirectory()) {
				scanClassPath(file);
			}
		});
	}

}


// 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 jodd.bridge.JavaIncompatible;
import jodd.core.JoddCore;
import jodd.system.SystemUtil;

/**
 * Few methods using infamous java.misc.Unsafe, mostly for private use.
 * See: http://mishadoff.github.io/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/
 *
 * Thanx to Gatling (http://gatling-tool.org)!
 */
@JavaIncompatible
public class UnsafeUtil {

	// IMPORTANT - the order of declaration here is important! we need to detect
	// first the Android, and then to check for the unsafe field.

	private static final boolean IS_ANDROID = SystemUtil.info().isAndroid();
	private static final boolean HAS_UNSAFE = !IS_ANDROID && UnsafeInternal.hasUnsafe();

	/**
	 * Returns true if system has the Unsafe.
	 */
	public static boolean hasUnsafe() {
		return HAS_UNSAFE;
	}

	/**
	 * Returns String characters in most performing way.
	 * If possible, the inner char[] will be returned.
	 * If not, toCharArray() will be called.
	 * Returns null when argument is null.
	 */
	public static char[] getChars(final String string) {
		if (string == null) {
			return null;
		}

		if (!HAS_UNSAFE || !JoddCore.unsafeUsageEnabled) {
			return string.toCharArray();
		}

		return UnsafeInternal.unsafeGetChars(string);
	}

}


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

package jodd.db.jtx;

import jodd.db.connection.ConnectionProvider;
import jodd.jtx.JtxTransaction;
import jodd.jtx.JtxTransactionManager;
import jodd.jtx.JtxTransactionMode;

/**
 * {@link jodd.jtx.JtxTransactionManager} that uses only <b>one</b> JTX db resource type.
 * Usually, applications have only one transactional resource type - the database.
 * This manager just simplifies the usage, nothing more.
 * @see jodd.jtx.JtxTransactionManager
 */
public class DbJtxTransactionManager extends JtxTransactionManager {

	/**
	 * Creates db jtx manager and registers provided {@link DbJtxResourceManager}.
	 */
	public DbJtxTransactionManager(final DbJtxResourceManager resourceManager) {
		setMaxResourcesPerTransaction(1);
		setSingleResourceManager(true);
		super.registerResourceManager(resourceManager);

	}

	/**
	 * Creates db jtx manager and registers new {@link DbJtxResourceManager}.
	 */
	public DbJtxTransactionManager(final ConnectionProvider connectionProvider) {
		this(new DbJtxResourceManager(connectionProvider));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public DbJtxTransaction requestTransaction(final JtxTransactionMode mode) {
		return (DbJtxTransaction) super.requestTransaction(mode, null);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public DbJtxTransaction requestTransaction(final JtxTransactionMode mode, final Object scope) {
		return (DbJtxTransaction) super.requestTransaction(mode, scope);
	}

	/**
	 * Builds new transaction instance.
	 */
	@Override
	protected JtxTransaction createNewTransaction(final JtxTransactionMode tm, final Object scope, final boolean active) {
		return new DbJtxTransaction(this, tm, scope, active);
	}

}


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

package jodd.madvoc.component;

import jodd.madvoc.MadvocException;
import jodd.madvoc.scope.MadvocScope;
import jodd.madvoc.scope.RequestScope;
import jodd.petite.PetiteContainer;
import jodd.petite.meta.PetiteInject;

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

/**
 * Scope resolver maintains scope instances and provides lookups.
 */
public class ScopeResolver {

	@PetiteInject
	protected PetiteContainer madpc;

	protected List<MadvocScope> allScopes = new ArrayList<>();

	/**
	 * Lookups the scope instance of given scope annotation.
	 * If instance does not exist, it will be created, cached and returned.
	 */
	@SuppressWarnings("unchecked")
	public <S extends MadvocScope> S defaultOrScopeType(final Class<S> scopeClass) {
		if (scopeClass == null) {
			return (S) getOrInitScope(RequestScope.class);
		}

		return (S) getOrInitScope(scopeClass);
	}

	/**
	 * Performs search for the scope class and returns it's instance.
	 */
	protected MadvocScope getOrInitScope(final Class<? extends MadvocScope> madvocScopeType) {
		for (final MadvocScope s : allScopes) {
			if (s.getClass().equals(madvocScopeType)) {
				return s;
			}
		}

		// new scope detected
		final MadvocScope newScope;
		try {
			newScope = madpc.createBean(madvocScopeType);
		} catch (Exception ex) {
			throw new MadvocException("Unable to create scope: " + madvocScopeType, ex);
		}

		allScopes.add(newScope);

		return newScope;
	}

	// ---------------------------------------------------------------- iteration

	/**
	 * Iterates over all scope instances.
	 */
	public void forEachScope(final Consumer<MadvocScope> madvocScopeConsumer) {
		allScopes.forEach(madvocScopeConsumer);
	}

	/**
	 * Finds a given scope and consumes it.
	 */
	public void forScope(final Class<? extends MadvocScope> scopeType, final Consumer<MadvocScope> madvocScopeConsumer) {
		final MadvocScope scope = getOrInitScope(scopeType);
		madvocScopeConsumer.accept(scope);
	}

}


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

import javax.mail.Address;
import javax.mail.Flags;
import javax.mail.Flags.Flag;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimePart;
import javax.mail.util.ByteArrayDataSource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Received email.
 */
public class ReceivedEmail extends CommonEmail<ReceivedEmail> {

	public static final ReceivedEmail[] EMPTY_ARRAY = new ReceivedEmail[0];
	private File attachmentStorage;

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

	@Override
	public ReceivedEmail clone() {
		return create()
			//original message
			.originalMessage(originalMessage())

			// flags
			.flags(flags())

			// message number and id
			.messageNumber(messageNumber())
			.messageId(messageId())

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

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

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

			// dates
			.receivedDate(receivedDate())
			.sentDate(sentDate())

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

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

	/**
	 * Creates an empty {@link ReceivedEmail}.
	 */
	private ReceivedEmail() {
	}

	/**
	 * Creates a {@link ReceivedEmail} from a given {@link Message}.
	 *
	 * @param msg {@link Message}
	 * @param envelope flag if this is an envelope
	 */
	public ReceivedEmail(final Message msg, final boolean envelope, final File attachmentStorage) {
		this.attachmentStorage = attachmentStorage;
		this.originalMessage = msg;
		try {
			parseMessage(msg, envelope);
		} catch (final Exception ex) {
			throw new MailException("Message parsing failed", ex);
		}
	}

	/**
	 * Parses {@link Message} and extracts all data for the received message.
	 *
	 * @param msg {@link Message} to parse.
	 * @throws IOException        if there is an error with the content
	 * @throws MessagingException if there is an error.
	 */
	protected void parseMessage(final Message msg, final boolean envelope) throws MessagingException, IOException {
		// flags
		flags(msg.getFlags());

		// message number
		messageNumber(msg.getMessageNumber());

		if (msg instanceof MimeMessage) {
			messageId(((MimeMessage) msg).getMessageID());
		}

		// single from
		final Address[] addresses = msg.getFrom();

		if (addresses != null && addresses.length > 0) {
			from(addresses[0]);
		}

		// reply-to
		replyTo(msg.getReplyTo());

		// recipients
		to(msg.getRecipients(Message.RecipientType.TO));
		cc(msg.getRecipients(Message.RecipientType.CC));
		// no BCC because this will always be empty

		// subject
		subject(msg.getSubject());

		// dates
		receivedDate(msg.getReceivedDate());
		sentDate(msg.getSentDate());

		// headers
		headers(msg.getAllHeaders());

		// content
		if (!envelope) {
			processPart(msg);
		}
	}

	/**
	 * Process part of the received message. All parts are simply added to the {@link ReceivedEmail},
	 * i.e. hierarchy is not saved.
	 *
	 * @param part {@link Part} of received message
	 * @throws IOException        if there is an error with the content.
	 * @throws MessagingException if there is an error.
	 */
	protected void processPart(final Part part) throws MessagingException, IOException {
		final Object content = part.getContent();

		if (content instanceof String) {
			addStringContent(part, (String) content);
		} else if (content instanceof Multipart) {
			processMultipart((Multipart) content);
		} else if (content instanceof InputStream) {
			addAttachment(part, (InputStream) content, attachmentStorage);
		} else if (content instanceof MimeMessage) {
			final MimeMessage mimeMessage = (MimeMessage) content;
			attachedMessage(new ReceivedEmail(mimeMessage, false, attachmentStorage));
		} else {
			addAttachment(part, part.getInputStream(), attachmentStorage);
		}
	}

	/**
	 * Process the {@link Multipart}.
	 *
	 * @param mp {@link Multipart}
	 * @throws MessagingException if there is a failure.
	 * @throws IOException        if there is an issue with the {@link Multipart}.
	 */
	private void processMultipart(final Multipart mp) throws MessagingException, IOException {
		final int count = mp.getCount();
		for (int i = 0; i < count; i++) {
			final Part innerPart = mp.getBodyPart(i);
			processPart(innerPart);
		}
	}

	/**
	 * Adds String content as either {@link EmailAttachment} or as {@link EmailMessage}.
	 *
	 * @param part    {@link Part}
	 * @param content Content as {@link String}
	 * @throws MessagingException           if there is a failure.
	 * @throws UnsupportedEncodingException if the named charset is not supported.
	 * @see #message(String, String, String)
	 */
	private void addStringContent(final Part part, final String content) throws MessagingException, UnsupportedEncodingException {
		final String contentType = part.getContentType();
		final String encoding = EmailUtil.extractEncoding(contentType, StringPool.US_ASCII);

		final String disposition = part.getDisposition();

		if (disposition != null && disposition.equalsIgnoreCase(Part.ATTACHMENT)) {
			addAttachment(part, content.getBytes(encoding));
		} else {
			final String mimeType = EmailUtil.extractMimeType(contentType);
			message(content, mimeType, encoding);
		}
	}

	/**
	 * Returns the Content-ID of this {@link Part}. Returns {@code null} if none present.
	 *
	 * @param part {@link Part} the Part to parse.
	 * @return String containing content ID.
	 * @throws MessagingException if there is a failure.
	 * @see MimePart#getContentID()
	 */
	protected static String parseContentId(final Part part) throws MessagingException {
		if (part instanceof MimePart) {
			final MimePart mp = (MimePart) part;
			return mp.getContentID();
		} else {
			return null;
		}
	}

	/**
	 * Returns {@code true} if the {@link Part} is inline.
	 *
	 * @param part {@link Part} to parse.
	 * @return {@code true} if the {@link Part} is inline.
	 * @throws MessagingException if there is a failure.
	 */
	protected static boolean parseInline(final Part part) throws MessagingException {
		if (part instanceof MimePart) {
			final String dispositionId = part.getDisposition();
			return dispositionId != null && dispositionId.equalsIgnoreCase("inline");
		}
		return false;
	}

	// ---------------------------------------------------------------- original message

	/**
	 * {@link Message} for this {@link ReceivedEmail}.
	 */
	private Message originalMessage;

	/**
	 * @return {@link Message}
	 */
	public Message originalMessage() {
		return originalMessage;
	}

	/**
	 * Sets the original message.
	 *
	 * @param originalMessage {@link Message} to set.
	 */
	public ReceivedEmail originalMessage(final Message originalMessage) {
		this.originalMessage = originalMessage;
		return this;
	}

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

	/**
	 * {@link Flags} for this {@link ReceivedEmail}.
	 */
	private Flags flags;

	/**
	 * @return {@link Flags}
	 */
	public Flags flags() {
		return flags;
	}

	/**
	 * Sets the flags.
	 *
	 * @param flags {@link Flags} to set.
	 */
	public ReceivedEmail flags(final Flags flags) {
		this.flags = flags;
		return this;
	}

	/**
	 * Returns {@code true} if message is answered.
	 *
	 * @return {@code true} if message is answered.
	 */
	public boolean isAnswered() {
		return flags.contains(Flag.ANSWERED);
	}

	/**
	 * Returns {@code true} if message is deleted.
	 *
	 * @return {@code true} if message is deleted.
	 */
	public boolean isDeleted() {
		return flags.contains(Flag.DELETED);
	}

	/**
	 * Returns {@code true} if message is draft.
	 */
	public boolean isDraft() {
		return flags.contains(Flag.DRAFT);
	}

	/**
	 * Returns {@code true} is message is flagged.
	 *
	 * @return {@code true} is message is flagged.
	 */
	public boolean isFlagged() {
		return flags.contains(Flag.FLAGGED);
	}

	/**
	 * Returns {@code true} if message is recent.
	 *
	 * @return {@code true} if message is recent.
	 */
	public boolean isRecent() {
		return flags.contains(Flag.RECENT);
	}

	/**
	 * Returns {@code true} if message is seen.
	 *
	 * @return {@code true} if message is seen.
	 */
	public boolean isSeen() {
		return flags.contains(Flag.SEEN);
	}

	// ---------------------------------------------------------------- additional properties

	private int messageNumber;
	private String messageId;

	/**
	 * Returns message number.
	 *
	 * @return message number
	 */
	public int messageNumber() {
		return messageNumber;
	}

	/**
	 * Returns message ID if set by server.
	 */
	public String messageId() {
		return messageId;
	}

	/**
	 * Sets message number.
	 *
	 * @param messageNumber The message number to set.
	 * @return this
	 */
	public ReceivedEmail messageNumber(final int messageNumber) {
		this.messageNumber = messageNumber;
		return this;
	}

	/**
	 * Sets message ID.
	 */
	public ReceivedEmail messageId(final String messageId) {
		this.messageId = messageId;
		return this;
	}

	private Date receivedDate;

	/**
	 * Sets email's received {@link Date}.
	 *
	 * @param date The received {@link Date} to set.
	 * @return this
	 */
	public ReceivedEmail receivedDate(final Date date) {
		receivedDate = date;
		return this;
	}

	/**
	 * Returns email's received {@link Date}.
	 *
	 * @return The email's received {@link Date}.
	 */
	public Date receivedDate() {
		return receivedDate;
	}

	// ---------------------------------------------------------------- attachments

	/**
	 * Adds received attachment.
	 *
	 * @param part    {@link Part}.
	 * @param content Content as {@link InputStream}.
	 * @return this
	 * @see #attachment(EmailAttachment)
	 */
	private ReceivedEmail addAttachment(final Part part, final InputStream content, final File attachmentStorage) throws MessagingException, IOException {
		final EmailAttachmentBuilder builder = addAttachmentInfo(part);
		builder.content(content, part.getContentType());
		if (attachmentStorage != null) {
			String name = messageId + "-" + (this.attachments().size() + 1);
			return storeAttachment(builder.buildFileDataSource(name, attachmentStorage));
		}
		return storeAttachment(builder.buildByteArrayDataSource());
	}

	/**
	 * Adds received attachment.
	 *
	 * @param part    {@link Part}.
	 * @param content Content as byte array.
	 * @return this
	 * @see #attachment(EmailAttachment)
	 */
	private ReceivedEmail addAttachment(final Part part, final byte[] content) throws MessagingException {
		final EmailAttachmentBuilder builder = addAttachmentInfo(part);
		builder.content(content, part.getContentType());
		final EmailAttachment<ByteArrayDataSource> attachment = builder.buildByteArrayDataSource();
		attachment.setSize(content.length);
		return storeAttachment(attachment);
	}

	/**
	 * Creates {@link EmailAttachmentBuilder} from {@link Part} and sets Content ID, inline and name.
	 *
	 * @param part {@link Part}.
	 * @return this
	 * @see #attachment(EmailAttachment)
	 */
	private static EmailAttachmentBuilder addAttachmentInfo(final Part part) throws MessagingException {

		final String fileName = EmailUtil.resolveFileName(part);
		final String contentId = parseContentId(part);
		final boolean isInline = parseInline(part);

		return new EmailAttachmentBuilder()
			.name(fileName)
			.contentId(contentId)
			.inline(isInline);
	}

	// ---------------------------------------------------------------- inner messages

	/**
	 * {@link List} of attached {@link ReceivedEmail}s.
	 */
	private final List<ReceivedEmail> attachedMessages = new ArrayList<>();

	/**
	 * Adds attached {@link ReceivedEmail}s.
	 *
	 * @param emails {@link List} of {@link ReceivedEmail}s to attach.
	 */
	public ReceivedEmail attachedMessages(final List<ReceivedEmail> emails) {
		attachedMessages.addAll(emails);
		return this;
	}

	/**
	 * Adds attached {@link ReceivedEmail}.
	 *
	 * @param email {@link ReceivedEmail} to attach.
	 * @return this
	 */
	public ReceivedEmail attachedMessage(final ReceivedEmail email) {
		attachedMessages.add(email);
		return this;
	}

	/**
	 * Returns the {@link List} of attached messages.
	 * If no attached message is available, returns an empty {@link List}.
	 *
	 * @return {@link List} of {@link ReceivedEmail}s.
	 */
	public List<ReceivedEmail> attachedMessages() {
		return attachedMessages;
	}

}


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

package jodd.util;

import java.util.Arrays;

/**
 * The fastest Base64 encoder/decoder implementations.
 * Base64 is defined in RFC 2045.
 *

 * Encoding supports two modes - with or without line separator.
 * When line separator flag is on, result will have lines with
 * max size of 76 chars, as per spec.
 *

 * When decoding, input must be valid, without illegal characters.
 * If input contains lines, they must be 76 chars long. Lines must
 * end with CRLF ("\r\n"), as per spec.
 */
public class Base64 {

	public static final char[] CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
	private static final int[] INV = new int[256];

	static {
		Arrays.fill(INV, -1);
		for (int i = 0, iS = CHARS.length; i < iS; i++) {
			INV[CHARS[i]] = i;
		}
		INV['='] = 0;
	}

	// ---------------------------------------------------------------- char

	/**
	 * Encodes a raw byte array into a BASE64 char[].
	 * @param lineSeparator optional CRLF after 76 chars, unless EOF.
	 */
	public static char[] encodeToChar(final byte[] arr, final boolean lineSeparator) {
		int len = arr != null ? arr.length : 0;
		if (len == 0) {
			return new char[0];
		}

		int evenlen = (len / 3) * 3;
		int cnt = ((len - 1) / 3 + 1) << 2;
		int destLen = cnt + (lineSeparator ? (cnt - 1) / 76 << 1 : 0);
		char[] dest = new char[destLen];

		for (int s = 0, d = 0, cc = 0; s < evenlen;) {
			int i = (arr[s++] & 0xff) << 16 | (arr[s++] & 0xff) << 8 | (arr[s++] & 0xff);

			dest[d++] = CHARS[(i >>> 18) & 0x3f];
			dest[d++] = CHARS[(i >>> 12) & 0x3f];
			dest[d++] = CHARS[(i >>> 6) & 0x3f];
			dest[d++] = CHARS[i & 0x3f];

			if (lineSeparator && (++cc == 19) && (d < (destLen - 2))) {
				dest[d++] = '\r';
				dest[d++] = '\n';
				cc = 0;
			}
		}

		int left = len - evenlen; // 0 - 2.
		if (left > 0) {
			int i = ((arr[evenlen] & 0xff) << 10) | (left == 2 ? ((arr[len - 1] & 0xff) << 2) : 0);

			dest[destLen - 4] = CHARS[i >> 12];
			dest[destLen - 3] = CHARS[(i >>> 6) & 0x3f];
			dest[destLen - 2] = left == 2 ? CHARS[i & 0x3f] : '=';
			dest[destLen - 1] = '=';
		}
		return dest;
	}

	/**
	 * Decodes a BASE64 encoded char array.
	 */
	public static byte[] decode(final char[] arr) {
		int length = arr.length;
		if (length == 0) {
			return new byte[0];
		}

		int sndx = 0, endx = length - 1;
		int pad = arr[endx] == '=' ? (arr[endx - 1] == '=' ? 2 : 1) : 0;
		int cnt = endx - sndx + 1;
		int sepCnt = length > 76 ? (arr[76] == '\r' ? cnt / 78 : 0) << 1 : 0;
		int len = ((cnt - sepCnt) * 6 >> 3) - pad;
		byte[] dest = new byte[len];

		int d = 0;
		for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
			int i = INV[arr[sndx++]] << 18 | INV[arr[sndx++]] << 12 | INV[arr[sndx++]] << 6 | INV[arr[sndx++]];

			dest[d++] = (byte) (i >> 16);
			dest[d++] = (byte) (i >> 8);
			dest[d++] = (byte) i;

			if (sepCnt > 0 && ++cc == 19) {
				sndx += 2;
				cc = 0;
			}
		}

		if (d < len) {
			int i = 0;
			for (int j = 0; sndx <= endx - pad; j++) {
				i |= INV[arr[sndx++]] << (18 - j * 6);
			}
			for (int r = 16; d < len; r -= 8) {
				dest[d++] = (byte) (i >> r);
			}
		}

		return dest;
	}

	// ---------------------------------------------------------------- byte

	public static byte[] encodeToByte(final String s) {
		return encodeToByte(StringUtil.getBytes(s), false);
	}

	public static byte[] encodeToByte(final String s, final boolean lineSep) {
		return encodeToByte(StringUtil.getBytes(s), lineSep);
	}

	public static byte[] encodeToByte(final byte[] arr) {
		return encodeToByte(arr, false);
	}

	/**
	 * Encodes a raw byte array into a BASE64 char[].
	 * @param lineSep optional CRLF after 76 chars, unless EOF.
	 */
	public static byte[] encodeToByte(final byte[] arr, final boolean lineSep) {
		int len = arr != null ? arr.length : 0;
		if (len == 0) {
			return new byte[0];
		}

		int evenlen = (len / 3) * 3;
		int cnt = ((len - 1) / 3 + 1) << 2;
		int destlen = cnt + (lineSep ? (cnt - 1) / 76 << 1 : 0);
		byte[] dest = new byte[destlen];

		for (int s = 0, d = 0, cc = 0; s < evenlen;) {
			int i = (arr[s++] & 0xff) << 16 | (arr[s++] & 0xff) << 8 | (arr[s++] & 0xff);

			dest[d++] = (byte) CHARS[(i >>> 18) & 0x3f];
			dest[d++] = (byte) CHARS[(i >>> 12) & 0x3f];
			dest[d++] = (byte) CHARS[(i >>> 6) & 0x3f];
			dest[d++] = (byte) CHARS[i & 0x3f];

			if (lineSep && ++cc == 19 && d < destlen - 2) {
				dest[d++] = '\r';
				dest[d++] = '\n';
				cc = 0;
			}
		}

		int left = len - evenlen;
		if (left > 0) {
			int i = ((arr[evenlen] & 0xff) << 10) | (left == 2 ? ((arr[len - 1] & 0xff) << 2) : 0);

			dest[destlen - 4] = (byte) CHARS[i >> 12];
			dest[destlen - 3] = (byte) CHARS[(i >>> 6) & 0x3f];
			dest[destlen - 2] = left == 2 ? (byte) CHARS[i & 0x3f] : (byte) '=';
			dest[destlen - 1] = '=';
		}
		return dest;
	}

	public static String decodeToString(final byte[] arr) {
		return StringUtil.newString(decode(arr));
	}

	/**
	 * Decodes BASE64 encoded byte array.
	 */
	public static byte[] decode(final byte[] arr) {
		int length = arr.length;
		if (length == 0) {
			return new byte[0];
		}

		int sndx = 0, endx = length - 1;
		int pad = arr[endx] == '=' ? (arr[endx - 1] == '=' ? 2 : 1) : 0;
		int cnt = endx - sndx + 1;
		int sepCnt = length > 76 ? (arr[76] == '\r' ? cnt / 78 : 0) << 1 : 0;
		int len = ((cnt - sepCnt) * 6 >> 3) - pad;
		byte[] dest = new byte[len];

		int d = 0;
		for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
			int i = INV[arr[sndx++]] << 18 | INV[arr[sndx++]] << 12 | INV[arr[sndx++]] << 6 | INV[arr[sndx++]];

			dest[d++] = (byte) (i >> 16);
			dest[d++] = (byte) (i >> 8);
			dest[d++] = (byte) i;

			if (sepCnt > 0 && ++cc == 19) {
				sndx += 2;
				cc = 0;
			}
		}

		if (d < len) {
			int i = 0;
			for (int j = 0; sndx <= endx - pad; j++) {
				i |= INV[arr[sndx++]] << (18 - j * 6);
			}
			for (int r = 16; d < len; r -= 8) {
				dest[d++] = (byte) (i >> r);
			}
		}

		return dest;
	}

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

	public static String encodeToString(final String s) {
		return new String(encodeToChar(StringUtil.getBytes(s), false));
	}

	public static String encodeToString(final String s, final boolean lineSep) {
		return new String(encodeToChar(StringUtil.getBytes(s), lineSep));
	}

	public static String encodeToString(final byte[] arr) {
		return new String(encodeToChar(arr, false));
	}

	/**
	 * Encodes a raw byte array into a BASE64 String.
	 */
	public static String encodeToString(final byte[] arr, final boolean lineSep) {
		return new String(encodeToChar(arr, lineSep));
	}

	public static String decodeToString(final String s) {
		return StringUtil.newString(decode(s));
	}

	/**
	 * Decodes a BASE64 encoded string.
	 */
	public static byte[] decode(final String s) {
		int length = s.length();
		if (length == 0) {
			return new byte[0];
		}

		int sndx = 0, endx = length - 1;
		int pad = s.charAt(endx) == '=' ? (s.charAt(endx - 1) == '=' ? 2 : 1) : 0;
		int cnt = endx - sndx + 1;
		int sepCnt = length > 76 ? (s.charAt(76) == '\r' ? cnt / 78 : 0) << 1 : 0;
		int len = ((cnt - sepCnt) * 6 >> 3) - pad;
		byte[] dest = new byte[len];

		int d = 0;
		for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
			int i = INV[s.charAt(sndx++)] << 18 | INV[s.charAt(sndx++)] << 12 | INV[s.charAt(sndx++)] << 6 | INV[s.charAt(sndx++)];

			dest[d++] = (byte) (i >> 16);
			dest[d++] = (byte) (i >> 8);
			dest[d++] = (byte) i;

			if (sepCnt > 0 && ++cc == 19) {
				sndx += 2;
				cc = 0;
			}
		}

		if (d < len) {
			int i = 0;
			for (int j = 0; sndx <= endx - pad; j++) {
				i |= INV[s.charAt(sndx++)] << (18 - j * 6);
			}
			for (int r = 16; d < len; r -= 8) {
				dest[d++] = (byte) (i >> r);
			}
		}

		return dest;
	}

}


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

import jodd.util.RandomString;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.jsp.PageContext;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * Shields against CSRF attacks.
 */
public class CsrfShield {

	public static final String CSRF_TOKEN_NAME = "_csrf_token";
	public static final String CSRF_TOKEN_SET = "_csrf_token_set";

	protected static int timeToLive = 600;
	protected static int maxTokensPerSession = 20;

	/**
	 * Sets time to live for tokens in seconds.
	 * By setting negative value or 0 token leaves forever.
	 */
	public static void setTimeToLive(final int periodInSeconds) {
		timeToLive = periodInSeconds;
	}

	/**
	 * Sets max number of tokens that will be stored for single session.
	 * It is actually the number of CSRF validation that may occur in the
	 * same time. Limit prevents from malicious growing of the set.
	 */
	public static void setMaxTokensPerSession(final int maxTokensPerSession) {
		CsrfShield.maxTokensPerSession = maxTokensPerSession;
	}

	// ---------------------------------------------------------------- prepare

	/**
	 * @see #prepareCsrfToken(javax.servlet.http.HttpSession, int)
	 */
	public static String prepareCsrfToken(final PageContext pageContext) {
		return prepareCsrfToken(pageContext.getSession());
	}

	/**
	 * @see #prepareCsrfToken(javax.servlet.http.HttpSession, int)
	 */
	public static String prepareCsrfToken(final HttpSession session) {
		return prepareCsrfToken(session, timeToLive);
	}

	/**
	 * Generates new CSRF token and puts it in the session. Returns generated token value.
	 */
	@SuppressWarnings({"unchecked"})
	public static String prepareCsrfToken(final HttpSession session, final int timeToLive) {
		Set<Token> tokenSet = (Set<Token>) session.getAttribute(CSRF_TOKEN_SET);
		if (tokenSet == null) {
			tokenSet = new HashSet<>();
			session.setAttribute(CSRF_TOKEN_SET, tokenSet);
		}
		String value;
		boolean unique;
		do {
			value = RandomString.get().randomAlphaNumeric(32);
			assureSize(tokenSet);
			unique = tokenSet.add(new Token(value, timeToLive));
		} while (!unique);
		return value;
	}

	/**
	 * Removes expired tokens if token set is full.
	 * @see #setMaxTokensPerSession(int)
	 */
	protected static void assureSize(final Set<Token> tokenSet) {
		if (tokenSet.size() < maxTokensPerSession) {
			return;
		}
		long validUntilMin = Long.MAX_VALUE;
		Token tokenToRemove = null;
		Iterator<Token> iterator = tokenSet.iterator();
		while (iterator.hasNext()) {
			Token token = iterator.next();
			if (token.isExpired()) {
				iterator.remove();
				continue;
			}
			if (token.validUntil < validUntilMin) {
				validUntilMin = token.validUntil;
				tokenToRemove = token;
			}
		}
		if ((tokenToRemove != null) && (tokenSet.size() >= maxTokensPerSession)) {
			tokenSet.remove(tokenToRemove);
		}
	}

	// ---------------------------------------------------------------- check

	/**
	 * @see #checkCsrfToken(javax.servlet.http.HttpServletRequest, String)
	 */
	public static boolean checkCsrfToken(final HttpServletRequest request) {
		return checkCsrfToken(request, CSRF_TOKEN_NAME);
	}

	/**
	 * Checks if {@link jodd.servlet.tag.CsrfTokenTag CSRF token} is valid.
	 * Returns false if token was requested, but not found.
	 * Otherwise, it returns true.
	 */
	@SuppressWarnings({"unchecked"})
	public static boolean checkCsrfToken(final HttpServletRequest request, final String tokenName) {
		String tokenValue = request.getParameter(tokenName);
		return checkCsrfToken(request.getSession(), tokenValue);
	}

	/**
	 * Checks token value.
C	 */
	@SuppressWarnings({"unchecked"})
	public static boolean checkCsrfToken(final HttpSession session, final String tokenValue) {
		Set<Token> tokenSet = (Set<Token>) session.getAttribute(CSRF_TOKEN_SET);
		if ((tokenSet == null) && (tokenValue == null)) {
			return true;
		}
		if ((tokenSet == null) || (tokenValue == null)) {
			return false;
		}
		boolean found = false;
		Iterator<Token> it = tokenSet.iterator();
		while (it.hasNext()) {
			Token t = it.next();
			if (t.isExpired()) {
				it.remove();
				continue;
			}
			if (t.getValue().equals(tokenValue)) {
				it.remove();
				found = true;
			}
		}
		return found;
	}

	/**
	 * CSRF Token.
	 */
	public static class Token implements Serializable {

		protected final String value;
		protected final long validUntil;

		public Token(final String value) {
			this(value, 0);
		}

		public Token(final String value, final long timeToLive) {
			this.value = value;
			this.validUntil = timeToLive <= 0 ? Long.MAX_VALUE : (System.currentTimeMillis() + timeToLive * 1000);
		}

		/**
		 * Returns true if token is expired.
		 */
		public boolean isExpired() {
			return System.currentTimeMillis() > validUntil;
		}

		/**
		 * Returns token value.
		 */
		public String getValue() {
			return value;
		}

		@Override
		public boolean equals(final Object o) {
			if (this == o) {
				return true;
			}
			if (o == null || getClass() != o.getClass()) {
				return false;
			}
			Token token = (Token) o;
			return value.equals(token.value);

		}

		@Override
		public int hashCode() {
			return value.hashCode();
		}
	}

}


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

package jodd.bean;

import java.util.Map;

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

/**
 * Powerful tool for copying properties from one bean into another.
 * BeanCopy works with POJO beans, but also with Map.
 *
 * @see BeanVisitor
 */
public class BeanCopy extends BeanVisitorImplBase<BeanCopy> {

	protected Object destination;
	protected boolean forced;
	protected boolean declaredTarget;
	protected boolean isTargetMap;

	// ---------------------------------------------------------------- ctor

	/**
	 * Creates new BeanCopy process between the source and the destination.
	 * Both source and destination can be a POJO object or a Map.
	 */
	public BeanCopy(final Object source, final Object destination) {
		this.source = source;
		this.destination = destination;
	}

	private BeanCopy(final Object source) {
		this.source = source;
	}

	/**
	 * Simple static factory for BeanCopy.
	 * @see #BeanCopy(Object, Object)
	 */
	public static BeanCopy beans(final Object source, final Object destination) {
		return new BeanCopy(source, destination);
	}

	/**
	 * Creates BeanCopy with given POJO bean as a source.
	 */
	public static BeanCopy fromBean(final Object source) {
		return new BeanCopy(source);
	}

	/**
	 * Creates BeanCopy with given Map as a source.
	 */
	public static BeanCopy fromMap(final Map source) {
		BeanCopy beanCopy = new BeanCopy(source);

		beanCopy.isSourceMap = true;

		return beanCopy;
	}

	/**
	 * Defines source, detects a map.
	 */
	public static BeanCopy from(final Object source) {
		BeanCopy beanCopy = new BeanCopy(source);

		beanCopy.isSourceMap = source instanceof Map;

		return beanCopy;
	}

	// ---------------------------------------------------------------- destination

	/**
	 * Defines destination bean.
	 */
	public BeanCopy toBean(final Object destination) {
		this.destination = destination;
		return this;
	}

	/**
	 * Defines destination map.
	 */
	public BeanCopy toMap(final Map destination) {
		this.destination = destination;

		isTargetMap = true;

		return this;
	}

	/**
	 * Defines destination, detects a map.
	 */
	public BeanCopy to(final Object destination) {
		this.destination = destination;

		this.isTargetMap = destination instanceof Map;

		return this;
	}

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

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

	/**
	 * Fine-tuning of the declared behaviour.
	 */
	public BeanCopy declared(final boolean declaredSource, final boolean declaredTarget) {
		this.declared = declaredSource;
		this.declaredTarget = declaredTarget;
		return this;
	}

	public BeanCopy forced(final boolean forced) {
		this.forced = forced;
		return this;
	}

	// ---------------------------------------------------------------- visitor

	protected BeanUtil beanUtil;

	/**
	 * Performs the copying.
	 */
	public void copy() {
		beanUtil = new BeanUtilBean()
						.declared(declared)
						.forced(forced)
						.silent(true);
		visit();
	}

	/**
	 * Copies single property to the destination.
	 * Exceptions are ignored, so copying continues if
	 * destination does not have some of the sources properties.
	 */
	@Override
	protected boolean visitProperty(String name, final Object value) {
		if (isTargetMap) {
			name = LEFT_SQ_BRACKET + name + RIGHT_SQ_BRACKET;
		}

		beanUtil.setProperty(destination, name, value);

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

import jodd.io.FastCharArrayWriter;
import jodd.lagarto.LagartoParser;
import jodd.lagarto.TagAdapter;
import jodd.lagarto.TagWriter;

import javax.servlet.http.HttpServletRequest;

/**
 * Simple version of {@link LagartoServletFilter} that uses Lagarto.
 * May be used when set of adapters does not overlap each other during
 * single content parsing i.e. visiting. Otherwise, use more generic
 * {@link LagartoServletFilter}.
 */
public abstract class SimpleLagartoServletFilter extends LagartoServletFilter {

	/**
	 * Parses content using Lagarto and {@link LagartoParsingProcessor}.
	 */
	@Override
	protected final char[] parse(final char[] content, final HttpServletRequest request) {

		LagartoParsingProcessor lpp = createParsingProcessor();

		if (lpp == null) {
			return content;
		}

		lpp.init(content);

		return lpp.parse(request);
	}

	/**
	 * Returns custom {@link LagartoParsingProcessor parsing processor}.
	 * Returns null when content does not have to be parsed.
	 */
	protected abstract LagartoParsingProcessor createParsingProcessor();

	/**
	 * Wrapper over Lagarto parsing process.
	 */
	protected abstract static class LagartoParsingProcessor {

		protected LagartoParser lagartoParser;
		protected FastCharArrayWriter fastCharArrayWriter;
		protected TagWriter tagWriter;

		/**
		 * Initialize processor by creating new Lagarto and root TagWriter.
		 */
		public void init(final char[] content) {
			// create Lagarto
			lagartoParser = new LagartoParser(content);

			// prepare root tag writer
			fastCharArrayWriter = new FastCharArrayWriter();
			tagWriter = new TagWriter(fastCharArrayWriter);
		}

		/**
		 * Parses given and return adapted content.
		 * Delegates call to {@link #parse(jodd.lagarto.TagWriter, javax.servlet.http.HttpServletRequest)}
		 */
		public char[] parse(final HttpServletRequest request) {
			return parse(tagWriter, request);
		}

		/**
		 * Creates set of nested adapters and {@link #invokeLagarto(jodd.lagarto.TagAdapter) invokes lagarto parsing}.
		 * May be used if modifications does not overlap so everything can
		 * be done within single visit (i.e. within just one parsing).
		 * Returns parsed content so it may be modified afterwards.
		 */
		protected abstract char[] parse(TagWriter rootTagWriter, HttpServletRequest request);

		/**
		 * Invokes Lagarto parser with provided set of adapters
		 * and returns processed content.
		 */
		public char[] invokeLagarto(final TagAdapter tagAdapter) {
			lagartoParser.parse(tagAdapter);
			return fastCharArrayWriter.toCharArray();
		}

	}

}


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

import jodd.db.oom.DbEntityDescriptor;
import jodd.db.oom.DbEntityManager;
import jodd.db.oom.sqlgen.TemplateData;

/**
 * Table chunk resolves table name, optional table alias and defines table references.
 *

 * If previous chunk is also a table, comma separator will be added between two table definitions, otherwise,
 * regular space will be added if needed to separate from previous chunk.
 *

 * Tables must be processed first, before all other chunks processing, since other chunks depends on it.
 */
public class TableChunk extends SqlChunk {

	protected final Class entity;
	protected final String entityName;
	protected final String tableAlias;
	protected final String tableReference;

	public TableChunk(final DbEntityManager dbEntityManager, final Object entity) {
		super(dbEntityManager, CHUNK_TABLE);
		this.entity = resolveClass(entity);
		this.entityName = null;
		this.tableAlias = this.entity.getSimpleName();
		this.tableReference = null;
	}

	public TableChunk(final DbEntityManager dbEntityManager, final Object entity, final String alias) {
		super(dbEntityManager, CHUNK_TABLE);
		this.entity = resolveClass(entity);
		this.entityName = null;
		this.tableAlias = alias;
		this.tableReference = null;
	}

	public TableChunk(final DbEntityManager dbEntityManager, final Object entity, final String alias, final String tableReference) {
		super(dbEntityManager, CHUNK_TABLE);
		this.entity = resolveClass(entity);
		this.entityName = null;
		this.tableAlias = alias;
		this.tableReference = tableReference;
	}

	public TableChunk(final DbEntityManager dbEntityManager, final String entityName, final String alias) {
		this(dbEntityManager, null, entityName, alias, null);
	}

	private TableChunk(final DbEntityManager dbEntityManager, final Class entity, final String entityName, final String tableAlias, final String tableReference) {
		super(dbEntityManager, CHUNK_TABLE);
		this.entity = entity;
		this.entityName = entityName;
		this.tableAlias = tableAlias;
		this.tableReference = tableReference;
	}

	public TableChunk(final DbEntityManager dbEntityManager, String tableRef) {
		super(dbEntityManager, CHUNK_TABLE);
		tableRef = tableRef.trim();
		int spaceNdx = tableRef.indexOf(' ');
		this.entity = null;
		if (spaceNdx != -1) {
			this.entityName = tableRef.substring(0, spaceNdx);
			String alias = tableRef.substring(spaceNdx + 1).trim();
			tableAlias = alias.length() == 0 ? null : alias;
		} else {
			this.entityName = tableRef;
			this.tableAlias = null;
		}
		this.tableReference = null;
	}

	// ---------------------------------------------------------------- process

	protected DbEntityDescriptor ded;

	/**
	 * Resolves and registers table references.
	 */
	@Override
	public void init(final TemplateData templateData) {
		super.init(templateData);
		if (entity != null) {
			ded = lookupType(entity);
		} else {
			Object object = templateData.getObjectReference(entityName);
			if (object != null) {
				ded = lookupType(resolveClass(object));
			} else {
				ded = lookupName(entityName);
			}
		}
		String tableReference = this.tableReference;

		if (tableReference == null) {
			tableReference = tableAlias;
		}
		if (tableReference == null) {
			tableReference = entityName;
		}
		if (tableReference == null) {
			tableReference = ded.getEntityName();
		}
		templateData.registerTableReference(tableReference, ded, tableAlias);
	}

	@Override
	public void process(final StringBuilder out) {
		separateByCommaOrSpace(out);
		out.append(ded.getTableNameForQuery());
		if (tableAlias != null) {
			out.append(' ').append(tableAlias);
		}
	}

}