// ASM: a very small and fast Java bytecode manipulation framework
// Copyright (c) 2000-2011 INRIA, France Telecom
// 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.
// 3. Neither the name of the copyright holders nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// 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 OWNER 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.asm7;

/**
 * A {@link ModuleVisitor} that generates the corresponding Module, ModulePackages and
 * ModuleMainClass attributes, as defined in the Java Virtual Machine Specification (JVMS).
 *
 * @see JVMS
 *     4.7.25
 * @see JVMS
 *     4.7.26
 * @see JVMS
 *     4.7.27
 * @author Remi Forax
 * @author Eric Bruneton
 */
final class ModuleWriter extends ModuleVisitor {

  /** Where the constants used in this AnnotationWriter must be stored. */
  private final SymbolTable symbolTable;

  /** The module_name_index field of the JVMS Module attribute. */
  private final int moduleNameIndex;

  /** The module_flags field of the JVMS Module attribute. */
  private final int moduleFlags;

  /** The module_version_index field of the JVMS Module attribute. */
  private final int moduleVersionIndex;

  /** The requires_count field of the JVMS Module attribute. */
  private int requiresCount;

  /** The binary content of the 'requires' array of the JVMS Module attribute. */
  private final ByteVector requires;

  /** The exports_count field of the JVMS Module attribute. */
  private int exportsCount;

  /** The binary content of the 'exports' array of the JVMS Module attribute. */
  private final ByteVector exports;

  /** The opens_count field of the JVMS Module attribute. */
  private int opensCount;

  /** The binary content of the 'opens' array of the JVMS Module attribute. */
  private final ByteVector opens;

  /** The uses_count field of the JVMS Module attribute. */
  private int usesCount;

  /** The binary content of the 'uses_index' array of the JVMS Module attribute. */
  private final ByteVector usesIndex;

  /** The provides_count field of the JVMS Module attribute. */
  private int providesCount;

  /** The binary content of the 'provides' array of the JVMS Module attribute. */
  private final ByteVector provides;

  /** The provides_count field of the JVMS ModulePackages attribute. */
  private int packageCount;

  /** The binary content of the 'package_index' array of the JVMS ModulePackages attribute. */
  private final ByteVector packageIndex;

  /** The main_class_index field of the JVMS ModuleMainClass attribute, or 0. */
  private int mainClassIndex;

  ModuleWriter(final SymbolTable symbolTable, final int name, final int access, final int version) {
    super(Opcodes.ASM7);
    this.symbolTable = symbolTable;
    this.moduleNameIndex = name;
    this.moduleFlags = access;
    this.moduleVersionIndex = version;
    this.requires = new ByteVector();
    this.exports = new ByteVector();
    this.opens = new ByteVector();
    this.usesIndex = new ByteVector();
    this.provides = new ByteVector();
    this.packageIndex = new ByteVector();
  }

  @Override
  public void visitMainClass(final String mainClass) {
    this.mainClassIndex = symbolTable.addConstantClass(mainClass).index;
  }

  @Override
  public void visitPackage(final String packaze) {
    packageIndex.putShort(symbolTable.addConstantPackage(packaze).index);
    packageCount++;
  }

  @Override
  public void visitRequire(final String module, final int access, final String version) {
    requires
        .putShort(symbolTable.addConstantModule(module).index)
        .putShort(access)
        .putShort(version == null ? 0 : symbolTable.addConstantUtf8(version));
    requiresCount++;
  }

  @Override
  public void visitExport(final String packaze, final int access, final String... modules) {
    exports.putShort(symbolTable.addConstantPackage(packaze).index).putShort(access);
    if (modules == null) {
      exports.putShort(0);
    } else {
      exports.putShort(modules.length);
      for (String module : modules) {
        exports.putShort(symbolTable.addConstantModule(module).index);
      }
    }
    exportsCount++;
  }

  @Override
  public void visitOpen(final String packaze, final int access, final String... modules) {
    opens.putShort(symbolTable.addConstantPackage(packaze).index).putShort(access);
    if (modules == null) {
      opens.putShort(0);
    } else {
      opens.putShort(modules.length);
      for (String module : modules) {
        opens.putShort(symbolTable.addConstantModule(module).index);
      }
    }
    opensCount++;
  }

  @Override
  public void visitUse(final String service) {
    usesIndex.putShort(symbolTable.addConstantClass(service).index);
    usesCount++;
  }

  @Override
  public void visitProvide(final String service, final String... providers) {
    provides.putShort(symbolTable.addConstantClass(service).index);
    provides.putShort(providers.length);
    for (String provider : providers) {
      provides.putShort(symbolTable.addConstantClass(provider).index);
    }
    providesCount++;
  }

  @Override
  public void visitEnd() {
    // Nothing to do.
  }

  /**
   * Returns the number of Module, ModulePackages and ModuleMainClass attributes generated by this
   * ModuleWriter.
   *
   * @return the number of Module, ModulePackages and ModuleMainClass attributes (between 1 and 3).
   */
  int getAttributeCount() {
    return 1 + (packageCount > 0 ? 1 : 0) + (mainClassIndex > 0 ? 1 : 0);
  }

  /**
   * Returns the size of the Module, ModulePackages and ModuleMainClass attributes generated by this
   * ModuleWriter. Also add the names of these attributes in the constant pool.
   *
   * @return the size in bytes of the Module, ModulePackages and ModuleMainClass attributes.
   */
  int computeAttributesSize() {
    symbolTable.addConstantUtf8(Constants.MODULE);
    // 6 attribute header bytes, 6 bytes for name, flags and version, and 5 * 2 bytes for counts.
    int size =
        22 + requires.length + exports.length + opens.length + usesIndex.length + provides.length;
    if (packageCount > 0) {
      symbolTable.addConstantUtf8(Constants.MODULE_PACKAGES);
      // 6 attribute header bytes, and 2 bytes for package_count.
      size += 8 + packageIndex.length;
    }
    if (mainClassIndex > 0) {
      symbolTable.addConstantUtf8(Constants.MODULE_MAIN_CLASS);
      // 6 attribute header bytes, and 2 bytes for main_class_index.
      size += 8;
    }
    return size;
  }

  /**
   * Puts the Module, ModulePackages and ModuleMainClass attributes generated by this ModuleWriter
   * in the given ByteVector.
   *
   * @param output where the attributes must be put.
   */
  void putAttributes(final ByteVector output) {
    // 6 bytes for name, flags and version, and 5 * 2 bytes for counts.
    int moduleAttributeLength =
        16 + requires.length + exports.length + opens.length + usesIndex.length + provides.length;
    output
        .putShort(symbolTable.addConstantUtf8(Constants.MODULE))
        .putInt(moduleAttributeLength)
        .putShort(moduleNameIndex)
        .putShort(moduleFlags)
        .putShort(moduleVersionIndex)
        .putShort(requiresCount)
        .putByteArray(requires.data, 0, requires.length)
        .putShort(exportsCount)
        .putByteArray(exports.data, 0, exports.length)
        .putShort(opensCount)
        .putByteArray(opens.data, 0, opens.length)
        .putShort(usesCount)
        .putByteArray(usesIndex.data, 0, usesIndex.length)
        .putShort(providesCount)
        .putByteArray(provides.data, 0, provides.length);
    if (packageCount > 0) {
      output
          .putShort(symbolTable.addConstantUtf8(Constants.MODULE_PACKAGES))
          .putInt(2 + packageIndex.length)
          .putShort(packageCount)
          .putByteArray(packageIndex.data, 0, packageIndex.length);
    }
    if (mainClassIndex > 0) {
      output
          .putShort(symbolTable.addConstantUtf8(Constants.MODULE_MAIN_CLASS))
          .putInt(2)
          .putShort(mainClassIndex);
    }
  }
}


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

import jodd.core.JoddCore;
import jodd.util.StringUtil;

import java.io.ByteArrayOutputStream;

/**
 * URL decoder.
 */
public class URLDecoder {

	/**
	 * Decodes URL elements.
	 */
	public static String decode(final String url) {
		return decode(url, JoddCore.encoding, false);
	}

	/**
	 * Decodes URL elements. This method may be used for all
	 * parts of URL, except for the query parts, since it does
	 * not decode the '+' character.
	 * @see #decodeQuery(String, String)
	 */
	public static String decode(final String source, final String encoding) {
		return decode(source, encoding, false);
	}

	/**
	 * Decodes query name or value.
	 */
	public static String decodeQuery(final String source) {
		return decode(source, JoddCore.encoding, true);
	}

	/**
	 * Decodes query name or value.
	 */
	public static String decodeQuery(final String source, final String encoding) {
		return decode(source, encoding, true);
	}

	private static String decode(final String source, final String encoding, final boolean decodePlus) {
		int length = source.length();
		ByteArrayOutputStream bos = new ByteArrayOutputStream(length);

		boolean changed = false;

		for (int i = 0; i < length; i++) {
			int ch = source.charAt(i);
			switch (ch) {
				case '%':
					if ((i + 2) < length) {
						char hex1 = source.charAt(i + 1);
						char hex2 = source.charAt(i + 2);
						int u = Character.digit(hex1, 16);
						int l = Character.digit(hex2, 16);
						if (u == -1 || l == -1) {
							throw new IllegalArgumentException("Invalid sequence: " + source.substring(i));
						}
						bos.write((char) ((u << 4) + l));
						i += 2;
						changed = true;
					} else {
						throw new IllegalArgumentException("Invalid sequence: " + source.substring(i));
					}
					break;

				case '+':
					if (decodePlus) {
						ch = ' ';
						changed = true;
					}

				default:
					bos.write(ch);
			}
		}
		return changed ? StringUtil.newString(bos.toByteArray(), encoding) : source;
	}

}


// 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.chalk.Chalk256;
import jodd.db.DbOom;
import jodd.db.DbSessionProvider;
import jodd.db.connection.ConnectionProvider;
import jodd.db.jtx.DbJtxSessionProvider;
import jodd.db.jtx.DbJtxTransactionManager;
import jodd.db.oom.AutomagicDbOomConfigurator;
import jodd.db.oom.DbEntityDescriptor;
import jodd.db.oom.DbEntityManager;
import jodd.db.pool.CoreConnectionPool;
import jodd.db.querymap.DbPropsQueryMap;
import jodd.db.querymap.QueryMap;
import jodd.jtx.JtxTransactionManager;
import jodd.jtx.proxy.AnnotationTxAdvice;
import jodd.jtx.proxy.AnnotationTxAdviceManager;
import jodd.jtx.proxy.AnnotationTxAdviceSupport;
import jodd.jtx.worker.LeanJtxWorker;
import jodd.petite.PetiteContainer;
import jodd.proxetta.MethodInfo;
import jodd.proxetta.ProxyAspect;
import jodd.proxetta.ProxyPointcut;
import jodd.proxetta.pointcuts.MethodWithAnnotationPointcut;
import jodd.util.ClassUtil;
import jodd.util.function.Consumers;

import java.lang.annotation.Annotation;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * Tiny JoyDb kickstarter.
 */
public class JoyDb extends JoyBase implements JoyDbConfig {

	protected final Supplier<String> appNameSupplier;
	protected final Supplier<JoyScanner> joyScannerSupplier;
	protected final Supplier<JoyProxetta> joyProxettaSupplier;
	protected final Supplier<JoyPetite> joyPetiteSupplier;

	protected DbOom dbOom;
	protected ConnectionProvider connectionProvider;
	protected JtxTransactionManager jtxManager;
	protected String jtxScopePattern;

	public JoyDb(
			final Supplier<String> appNameSupplier,
			final Supplier<JoyPetite> joyPetiteSupplier,
			final Supplier<JoyProxetta> joyProxettaSupplier,
			final Supplier<JoyScanner> joyScannerSupplier) {
		this.appNameSupplier = appNameSupplier;
		this.joyPetiteSupplier = joyPetiteSupplier;
		this.joyScannerSupplier = joyScannerSupplier;
		this.joyProxettaSupplier = joyProxettaSupplier;
	}

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

	/**
	 * Returns connection provider once when component is started.
	 */
	public ConnectionProvider getConnectionProvider() {
		return requireStarted(connectionProvider);
	}

	/**
	 * Returns JTX transaction manager once when component is started.
	 */
	public JtxTransactionManager getJtxManager() {
		return requireStarted(jtxManager);
	}

	/**
	 * Returns {@code true} if database usage is enabled.
	 */
	public boolean isDatabaseEnabled() {
		return databaseEnabled;
	}

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

	private boolean databaseEnabled = true;
	private boolean autoConfiguration = true;
	private Supplier<ConnectionProvider> connectionProviderSupplier;
	private Consumers<DbEntityManager> dbEntityManagerConsumers = Consumers.empty();

	@Override
	public JoyDb disableDatabase() {
		requireNotStarted(connectionProvider);
		databaseEnabled = false;
		return this;
	}

	@Override
	public JoyDb disableAutoConfiguration() {
		requireNotStarted(connectionProvider);
		autoConfiguration = false;
		return this;
	}

	@Override
	public JoyDb withEntityManager(final Consumer<DbEntityManager> dbEntityManagerConsumer) {
		requireNotStarted(connectionProvider);
		dbEntityManagerConsumers.add(dbEntityManagerConsumer);
		return this;
	}

	@Override
	public JoyDb withConnectionProvider(final Supplier<ConnectionProvider> connectionProviderSupplier) {
		requireNotStarted(connectionProvider);
		this.connectionProviderSupplier = connectionProviderSupplier;
		return this;
	}

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

	/**
	 * Initializes database. First, creates connection pool.
	 * and transaction manager. Then, Jodds DbEntityManager is
	 * configured. It is also configured automagically, by scanning
	 * the class path for entities.
	 */
	@Override
	@SuppressWarnings("unchecked")
	public void start() {
		initLogger();

		if (!databaseEnabled) {
			log.info("DB not enabled.");
			return;
		}

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

		final PetiteContainer petiteContainer = joyPetiteSupplier.get().getPetiteContainer();

		// connection pool
		connectionProvider = createConnectionProviderIfNotSupplied();

		petiteContainer.addBean(beanNamePrefix() + "pool", connectionProvider);

		if (connectionProvider instanceof CoreConnectionPool) {
			final CoreConnectionPool pool = (CoreConnectionPool) connectionProvider;
			if (pool.getDriver() == null) {
				databaseEnabled = false;
				log.warn("DB configuration not set (" + beanNamePrefix() + "pool.*). DB will be disabled.");
				return;
			}
		}
		connectionProvider.init();

		checkConnectionProvider();

		// transactions manager
		jtxManager = createJtxTransactionManager(connectionProvider);
		jtxManager.setValidateExistingTransaction(true);

		final AnnotationTxAdviceManager annTxAdviceManager = new AnnotationTxAdviceManager(new LeanJtxWorker(jtxManager), jtxScopePattern);
		AnnotationTxAdviceSupport.manager = annTxAdviceManager;

		// create proxy
		joyProxettaSupplier.get().getProxetta().withAspect(createTxProxyAspects(annTxAdviceManager.getAnnotations()));

		final DbSessionProvider sessionProvider = new DbJtxSessionProvider(jtxManager);

		// querymap
		final long startTime = System.currentTimeMillis();

		final QueryMap queryMap = new DbPropsQueryMap();

		log.debug("Queries loaded in " + (System.currentTimeMillis() - startTime) + "ms.");
		log.debug("Total queries: " + queryMap.size());

		// dboom
		dbOom = DbOom.create()
			.withConnectionProvider(connectionProvider)
			.withSessionProvider(sessionProvider)
			.withQueryMap(queryMap)
			.get();

		dbOom.connect();

		final DbEntityManager dbEntityManager = dbOom.entityManager();
		dbEntityManager.reset();

		petiteContainer.addBean(beanNamePrefix() + "query", dbOom.queryConfig());
		petiteContainer.addBean(beanNamePrefix() + "oom", dbOom.config());

		// automatic configuration
		if (autoConfiguration) {
			final AutomagicDbOomConfigurator automagicDbOomConfigurator =
				new AutomagicDbOomConfigurator(dbEntityManager, true);

			automagicDbOomConfigurator.registerAsConsumer(
				joyScannerSupplier.get().getClassScanner());
		}

		dbEntityManagerConsumers.accept(dbEntityManager);

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

	/**
	 * Creates JTX transaction manager.
	 */
	protected JtxTransactionManager createJtxTransactionManager(final ConnectionProvider connectionProvider) {
		return new DbJtxTransactionManager(connectionProvider);
	}

	/**
	 * Returns ConnectionProvider instance.
	 * Instance will be registered into the Petite context.
	 */
	protected ConnectionProvider createConnectionProviderIfNotSupplied() {
		if (connectionProviderSupplier != null) {
			return connectionProviderSupplier.get();
		}
		return new CoreConnectionPool();
	}

	/**
	 * Checks if connection provider can return a connection.
	 */
	protected void checkConnectionProvider() {
		final Connection connection = connectionProvider.getConnection();
		try {
			final DatabaseMetaData databaseMetaData = connection.getMetaData();
			String name = databaseMetaData.getDatabaseProductName();
			String version = databaseMetaData.getDatabaseProductVersion();

			if (log.isInfoEnabled()) {
				log.info("Connected to database: " + name + " v" + version);
			}
		} catch (SQLException sex) {
			log.error("DB connection failed: ", sex);
		} finally {
			connectionProvider.closeConnection(connection);
		}
	}

	protected ProxyAspect createTxProxyAspects(final Class<? extends Annotation>[] annotations) {
		return new ProxyAspect(
			AnnotationTxAdvice.class,
			((ProxyPointcut) MethodInfo::isPublicMethod)
				.and(MethodWithAnnotationPointcut.of(annotations))
		);
	}

	@Override
	public void stop() {
		if (!databaseEnabled) {
			return;
		}

		if (log != null) {
			log.info("DB stop");
		}

		if (jtxManager != null) {
			jtxManager.close();
		}
		jtxManager = null;

		if (connectionProvider != null) {
			connectionProvider.close();
		}
		connectionProvider = null;

		if (dbOom != null) {
			dbOom.shutdown();
		}
		dbOom = null;
	}

	protected String beanNamePrefix() {
		final String appName = appNameSupplier.get();
		return appName + ".db.";
	}

	// ---------------------------------------------------------------- print

	public void printEntities(final int width) {
		if (!databaseEnabled) {
			return;
		}

		final List<DbEntityDescriptor> list = new ArrayList<>();
		dbOom.entityManager().forEachEntity(list::add);

		if (list.isEmpty()) {
			return;
		}

		final Print print = new Print();
		print.line("Entities", width);

		list.stream()
			.sorted(Comparator.comparing(DbEntityDescriptor::getEntityName))
			.forEach(ded -> print.outLeftRightNewLine(
				Chalk256.chalk().yellow(), ded.getTableName(),
				Chalk256.chalk().blue(),   ClassUtil.getShortClassName(ded.getType(), 2),
				width));

		print.line(width);
	}
}


// 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.EmptyClassVisitor;
import jodd.asm.EmptyMethodVisitor;
import jodd.asm7.AnnotationVisitor;
import jodd.asm7.ClassReader;
import jodd.asm7.MethodVisitor;
import jodd.asm7.signature.SignatureReader;
import jodd.io.StreamUtil;
import jodd.proxetta.AnnotationInfo;
import jodd.proxetta.ClassInfo;
import jodd.proxetta.GenericsReader;
import jodd.proxetta.ProxettaException;
import jodd.util.ClassLoaderUtil;
import jodd.util.StringPool;

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

import static jodd.proxetta.asm.ProxettaAsmUtil.CLINIT;
import static jodd.proxetta.asm.ProxettaAsmUtil.INIT;

/**
 * Reads info from target class.
 */
@SuppressWarnings({"AnonymousClassVariableHidesContainingMethodVariable"})
public class TargetClassInfoReader extends EmptyClassVisitor implements ClassInfo {

	//protected ClassInfo classInfo;

	protected final Map<String, MethodSignatureVisitor> methodSignatures;
	protected final List<ClassReader> superClassReaders;					// list of all super class readers
	protected final Set<String> allMethodSignatures;
	protected final ClassLoader classLoader;

	public TargetClassInfoReader(final ClassLoader classLoader) {
		this.methodSignatures = new HashMap<>();
		this.superClassReaders = new ArrayList<>();
		this.allMethodSignatures = new HashSet<>();
		this.classLoader = classLoader;
	}

	// ---------------------------------------------------------------- some getters

	/**
	 * Returns method signature for some method. If signature is not found, returns null.
	 * Founded signatures means that those method can be proxyfied.
	 */
	public MethodSignatureVisitor lookupMethodSignatureVisitor(final int access, final String name, final String desc, final String className) {
		String key = ProxettaAsmUtil.createMethodSignaturesKey(access, name, desc, className);
		return methodSignatures.get(key);
	}

	// ---------------------------------------------------------------- information

	protected String targetPackage;
	protected String targetClassname;
	protected String superName;
	protected String thisReference;
	protected String nextSupername;
	protected String[] superClasses;
	protected AnnotationInfo[] annotations;
	protected List<AnnotationInfo> classAnnotations;
	protected boolean isTargetInterface;
	protected Set<String> nextInterfaces;
	protected Map<String, String> generics;

	// ---------------------------------------------------------------- class interface

	@Override
	public String getPackage() {
		return targetPackage;
	}

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

	@Override
	public String getSuperName() {
		return superName;
	}

	@Override
	public String getReference() {
		return thisReference;
	}

	@Override
	public String[] getSuperClasses() {
		return superClasses;
	}

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

//	@Override
//	public Map<String, String> getGenerics() {
//		return generics;
//	}

	// ---------------------------------------------------------------- visits

	@Override
	public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) {
		final int lastSlash = name.lastIndexOf('/');
		this.thisReference = name;
		this.superName = superName;
		this.nextSupername = superName;
		this.targetPackage = lastSlash == -1 ? StringPool.EMPTY : name.substring(0, lastSlash).replace('/', '.');
		this.targetClassname = name.substring(lastSlash + 1);

		this.isTargetInterface = (access & AsmUtil.ACC_INTERFACE) != 0;
		if (this.isTargetInterface) {
			nextInterfaces = new HashSet<>();
			if (interfaces != null) {
				Collections.addAll(nextInterfaces, interfaces);
			}
		}
		generics = new GenericsReader().parseSignatureForGenerics(signature, isTargetInterface);
	}

	@Override
	public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
		AnnotationReader ar = new AnnotationReader(desc, visible);
		if (classAnnotations == null) {
			classAnnotations = new ArrayList<>();
		}
		classAnnotations.add(ar);
		return ar;
	}

	/**
	 * Stores method signature for target method.
	 */
	@Override
	public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
//		if ((access & AsmUtil.ACC_FINAL) != 0) {
//			return null;	// skip finals
//		}
		MethodSignatureVisitor msign = createMethodSignature(access, name, desc, signature, exceptions, thisReference, this.generics);
		String key = ProxettaAsmUtil.createMethodSignaturesKey(access, name, desc, thisReference);
		methodSignatures.put(key, msign);
		allMethodSignatures.add(msign.getCleanSignature());
		return new MethodAnnotationReader(msign);
	}

	/**
	 * Stores signatures for all super public methods not already overridden by target class.
	 * All this methods will be accepted for proxyfication.
	 */
	@Override
	public void visitEnd() {

		// prepare class annotations
		if (classAnnotations != null) {
			annotations = classAnnotations.toArray(new AnnotationInfo[0]);
			classAnnotations = null;
		}

		List<String> superList = new ArrayList<>();

		Set<String> allInterfaces = new HashSet<>();

		if (nextInterfaces != null) {
			allInterfaces.addAll(nextInterfaces);
		}

		// check all public super methods that are not overridden in superclass
		while (nextSupername != null) {
			InputStream inputStream = null;
			ClassReader cr;

			try {
				inputStream = ClassLoaderUtil.getClassAsStream(nextSupername, classLoader);
				cr = new ClassReader(inputStream);
			} catch (IOException ioex) {
				throw new ProxettaException("Unable to inspect super class: " + nextSupername, ioex);
			} finally {
				StreamUtil.close(inputStream);
			}

			superList.add(nextSupername);
			superClassReaders.add(cr);	// remember the super class reader
			cr.accept(new SuperClassVisitor(), 0);

			if (cr.getInterfaces() != null) {
				Collections.addAll(allInterfaces, cr.getInterfaces());
			}
		}
		superClasses = superList.toArray(new String[0]);

		// check all interface methods that are not overridden in super-interface

		Set<String> todoInterfaces = new HashSet<>(allInterfaces);
		Set<String> newCollectedInterfaces = new HashSet<>();

		while (true) {

			for (String next : todoInterfaces) {
				InputStream inputStream = null;
				ClassReader cr;
				try {
					inputStream = ClassLoaderUtil.getClassAsStream(next, classLoader);
					cr = new ClassReader(inputStream);
				}
				catch (IOException ioex) {
					throw new ProxettaException("Unable to inspect super interface: " + next, ioex);
				}
				finally {
					StreamUtil.close(inputStream);
				}
				superClassReaders.add(cr);				// remember the super class reader
				cr.accept(new SuperClassVisitor(), 0);

				if (cr.getInterfaces() != null) {
					for (String newInterface : cr.getInterfaces()) {
						if (!allInterfaces.contains(newInterface) && !todoInterfaces.contains(newInterface)) {
							// new interface found
							newCollectedInterfaces.add(newInterface);
						}
					}
				}
			}

			// perform collection
			allInterfaces.addAll(todoInterfaces);

			if (newCollectedInterfaces.isEmpty()) {
				// no new interface found
				break;
			}
			todoInterfaces.clear();
			todoInterfaces.addAll(newCollectedInterfaces);

			newCollectedInterfaces.clear();
		}
	}

	/**
	 * Creates method signature from method name.
	 */
	protected MethodSignatureVisitor createMethodSignature(
			final int access,
			final String methodName,
			final String description,
			final String signature,
			final String[] exceptions,
			final String classname,
			final Map<String, String> declaredTypeGenerics) {
		MethodSignatureVisitor v = new MethodSignatureVisitor(methodName, access, classname, description, exceptions, signature, declaredTypeGenerics, this);
		new SignatureReader(signature != null ? signature : description).accept(v);
		return v;
	}

	// ---------------------------------------------------------------- util class

	/**
	 * Reads method annotations and stores to method info.
	 */
	static class MethodAnnotationReader extends EmptyMethodVisitor {

		final List<AnnotationInfo> methodAnns = new ArrayList<>();
		final List<AnnotationInfo>[] methodParamsAnns;

		final MethodSignatureVisitor msign;

		MethodAnnotationReader(final MethodSignatureVisitor msign) {
			this.msign = msign;
			this.methodParamsAnns = new ArrayList[msign.getAllArgumentsSize()];
		}

		@Override
		public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
			AnnotationReader ar = new AnnotationReader(desc, visible);
			methodAnns.add(ar);
			return ar;
		}

		@Override
		public AnnotationVisitor visitParameterAnnotation(final int parameter, final String desc, final boolean visible) {
			AnnotationReader ar = new AnnotationReader(desc, visible);
			if (methodParamsAnns[parameter] == null) {
				methodParamsAnns[parameter] = new ArrayList<>();
			}

			methodParamsAnns[parameter].add(ar);

			return ar;
		}

		@Override
		public void visitEnd() {
			if (!methodAnns.isEmpty()) {
				// method annotations
				msign.annotations = methodAnns.toArray(new AnnotationInfo[0]);
			}

			// arguments annotations

			for (int i = 0; i < methodParamsAnns.length; i++) {
				List<AnnotationInfo> methodParamsAnn = methodParamsAnns[i];

				if (methodParamsAnn != null) {
					msign.getArgument(i + 1).annotations = methodParamsAnn.toArray(new AnnotationInfo[0]);
				}
			}
		}
	}

	// ---------------------------------------------------------------- super class visitor

	private class SuperClassVisitor extends EmptyClassVisitor {

		String declaredClassName;
		Map<String, String> superGeneric;

		@Override
		public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) {
			nextSupername = superName;
			declaredClassName = name;

			// append inner interfaces
			if (nextInterfaces != null) {
				if (interfaces != null) {
					Collections.addAll(nextInterfaces, interfaces);
				}
			}

			final boolean isInterface = (access & AsmUtil.ACC_INTERFACE) != 0;
			this.superGeneric = new GenericsReader().parseSignatureForGenerics(signature, isInterface);
		}

		@Override
		public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
			if (name.equals(INIT) || name.equals(CLINIT)) {
				return null;
			}

			if ((access & AsmUtil.ACC_PUBLIC) == 0) {   		// skip non-public
				return null;
			}
			if ((access & AsmUtil.ACC_FINAL) != 0) {		// skip finals
				return null;
			}

			MethodSignatureVisitor msign = createMethodSignature(access, name, desc, signature, exceptions, thisReference, this.superGeneric);
			if (allMethodSignatures.contains(msign.getCleanSignature())) {		// skip overridden method by some in above classes
				return null;
			}

			msign.setDeclaredClassName(declaredClassName);		// indicates it is not a top level class
			String key = ProxettaAsmUtil.createMethodSignaturesKey(access, name, desc, declaredClassName);
			methodSignatures.put(key, msign);
			allMethodSignatures.add(msign.getCleanSignature());
			return new MethodAnnotationReader(msign);
		}
	}

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

	@Override
	public String toString() {
		return "target: " + this.targetPackage + '.' + this.targetClassname;
	}
}


// 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.ArrayList;
import java.util.List;

/**
 * Helps with CSV strings.
 * See: http://en.wikipedia.org/wiki/Comma-separated_values
 */
public class CsvUtil {

	protected static final char FIELD_SEPARATOR = ',';
	protected static final char FIELD_QUOTE = '"';
	protected static final String DOUBLE_QUOTE = "\"\"";
	protected static final String SPECIAL_CHARS = "\r\n";

	/**
	 * Parse fields as csv string,
	 */
	public static String toCsvString(final Object... elements) {
		StringBuilder line = new StringBuilder();
		int last = elements.length -1;
		for (int i = 0; i < elements.length; i++) {
			if (elements[i] == null) {
				if (i != last) {
					line.append(FIELD_SEPARATOR);
				}
				continue;
			}
			String field = elements[i].toString();

			// check for special cases
			int ndx = field.indexOf(FIELD_SEPARATOR);
			if (ndx == -1) {
				ndx = field.indexOf(FIELD_QUOTE);
			}
			if (ndx == -1) {
				if (field.startsWith(StringPool.SPACE) || field.endsWith(StringPool.SPACE)) {
					ndx = 1;
				}
			}
			if (ndx == -1) {
				ndx = StringUtil.indexOfChars(field, SPECIAL_CHARS);
			}

			// add field
			if (ndx != -1) {
				line.append(FIELD_QUOTE);
			}
			field = StringUtil.replace(field, StringPool.QUOTE, DOUBLE_QUOTE);
			line.append(field);
			if (ndx != -1) {
				line.append(FIELD_QUOTE);
			}

			// last
			if (i != last) {
				line.append(FIELD_SEPARATOR);
			}
		}
		return line.toString();
	}

	/**
	 * Converts CSV line to string array.
	 */
	public static String[] toStringArray(final String line) {
		List<String> row = new ArrayList<>();

        boolean inQuotedField = false;
        int fieldStart = 0;

        final int len = line.length();
        for (int i = 0; i < len; i++) {
            char c = line.charAt(i);
            if (c == FIELD_SEPARATOR) {
                if (!inQuotedField) {	// ignore we are quoting
                    addField(row, line, fieldStart, i, inQuotedField);
                    fieldStart = i + 1;
                }
            } else if (c == FIELD_QUOTE) {
                if (inQuotedField) {
                    if (i + 1 == len || line.charAt(i + 1) == FIELD_SEPARATOR) {	// we are already quoting - peek to see if this is the end of the field
                        addField(row, line, fieldStart, i, inQuotedField);
                        fieldStart = i + 2;
                        i++; // and skip the comma
                        inQuotedField = false;
                    }
                } else if (fieldStart == i) {
                    inQuotedField = true;	// this is a beginning of a quote
                    fieldStart++;			// move field start
                }
            }
        }
        // add last field - but only if string was not empty
        if (len > 0 && fieldStart <= len) {
            addField(row, line, fieldStart, len, inQuotedField);
        }
        return row.toArray(new String[0]);
	}

	private static void addField(final List<String> row, final String line, final int startIndex, final int endIndex, final boolean inQuoted) {
        String field = line.substring(startIndex, endIndex);
		if (inQuoted) {
			field = StringUtil.replace(field, DOUBLE_QUOTE, "\"");
		}
        row.add(field);
    }

}


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

package jodd.petite.resolver;

import jodd.introspector.ClassDescriptor;
import jodd.introspector.ClassIntrospector;
import jodd.introspector.CtorDescriptor;
import jodd.petite.PetiteException;
import jodd.petite.def.BeanReferences;
import jodd.petite.def.CtorInjectionPoint;

import java.lang.reflect.Constructor;

/**
 * Resolver for constructor injection points.
 */
public class CtorResolver {

	protected final ReferencesResolver referencesResolver;

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

	/**
	 * Resolves constructor injection point from type. Looks for single annotated constructor.
	 * If no annotated constructors found, the total number of constructors will be checked.
	 * If there is only one constructor, that one will be used as injection point. If more
	 * constructors exist, the default one will be used as injection point. Otherwise, exception
	 * is thrown.
	 */
	public CtorInjectionPoint resolve(final Class type, final boolean useAnnotation) {
		// lookup methods
		ClassDescriptor cd = ClassIntrospector.get().lookup(type);
		CtorDescriptor[] allCtors = cd.getAllCtorDescriptors();
		Constructor foundedCtor = null;
		Constructor defaultCtor = null;
		BeanReferences[] references = null;

		for (CtorDescriptor ctorDescriptor : allCtors) {
			Constructor<?> ctor = ctorDescriptor.getConstructor();

			Class<?>[] paramTypes = ctor.getParameterTypes();
			if (paramTypes.length == 0) {
				defaultCtor = ctor;     // detects default ctors
			}

			if (!useAnnotation) {
				continue;
			}

			BeanReferences[] ctorReferences = referencesResolver.readAllReferencesFromAnnotation(ctor);

			if (ctorReferences == null) {
				continue;
			}
			if (foundedCtor != null) {
				throw new PetiteException("Two or more constructors are annotated as injection points in the bean: " + type.getName());
			}

			foundedCtor = ctor;
			references = ctorReferences;
		}

		if (foundedCtor == null) {
			// there is no annotated constructor
			if (allCtors.length == 1) {
				foundedCtor = allCtors[0].getConstructor();
			} else {
				foundedCtor = defaultCtor;
			}

			if (foundedCtor == null) {
				// no matching ctor found
				// still this is not an error if bean is already instantiated.
				return CtorInjectionPoint.EMPTY;
			}

			references = referencesResolver.readAllReferencesFromAnnotation(foundedCtor);

			if (references == null) {
				references = new BeanReferences[0];
			}
		}

		return new CtorInjectionPoint(foundedCtor, references);
	}

}


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

package jodd.proxetta.asm;

import jodd.asm.AsmUtil;
import jodd.asm.EmptyClassVisitor;
import jodd.asm.EmptyMethodVisitor;
import jodd.asm.MethodAdapter;
import jodd.asm7.ClassReader;
import jodd.asm7.FieldVisitor;
import jodd.asm7.Label;
import jodd.asm7.MethodVisitor;
import jodd.cache.TypeCache;
import jodd.io.StreamUtil;
import jodd.proxetta.MethodInfo;
import jodd.proxetta.ProxettaException;
import jodd.proxetta.ProxettaNames;
import jodd.proxetta.ProxyAdvice;
import jodd.proxetta.ProxyAspect;
import jodd.proxetta.ProxyPointcut;
import jodd.util.ClassLoaderUtil;

import java.io.IOException;
import java.io.InputStream;

import static jodd.asm7.Opcodes.ALOAD;
import static jodd.asm7.Opcodes.INVOKEINTERFACE;
import static jodd.asm7.Opcodes.INVOKESPECIAL;
import static jodd.asm7.Opcodes.INVOKESTATIC;
import static jodd.asm7.Opcodes.INVOKEVIRTUAL;
import static jodd.proxetta.asm.ProxettaAsmUtil.CLINIT;
import static jodd.proxetta.asm.ProxettaAsmUtil.DESC_VOID;
import static jodd.proxetta.asm.ProxettaAsmUtil.INIT;
import static jodd.proxetta.asm.ProxettaAsmUtil.adviceFieldName;
import static jodd.proxetta.asm.ProxettaAsmUtil.adviceMethodName;
import static jodd.proxetta.asm.ProxettaAsmUtil.isStoreOpcode;

/**
 * Data of single aspect.
 */
@SuppressWarnings({"AnonymousClassVariableHidesContainingMethodVariable"})
final class ProxyAspectData {

	final ClassReader adviceClassReader;
	final ProxyAspect aspect;
	final Class<? extends ProxyAdvice> advice;
	final ProxyPointcut pointcut;

	final int aspectIndex;
	final WorkData wd;    // destination class writer

	String adviceReference;     // advice reference
	boolean ready;              // is advice ready for manipulation?
	int maxLocalVarOffset;      // first next local var offset

	ProxyAspectData(final WorkData wd, final ProxyAspect aspect, final int aspectIndex) {
		this.aspect = aspect;
		this.advice = aspect.advice();
		this.pointcut = aspect.pointcut();
		this.aspectIndex = aspectIndex;
		this.wd = wd;
		adviceClassReader = getCachedAdviceClassReader(advice);
		readAdviceData();
	}

	/**
	 * Delegates to aspects pointcut.
	 */
	boolean apply(final MethodInfo msign) {
		return pointcut.apply(msign);
	}

	// ---------------------------------------------------------------- advice reader cache

	private static TypeCache<ClassReader> adviceClassReaderCache;

	/**
	 * Creates advice's class reader.
	 */
	private ClassReader createAdviceClassReader(final Class<? extends ProxyAdvice> advice) {
		InputStream inputStream = null;
		try {
			inputStream = ClassLoaderUtil.getClassAsStream(advice);
			return new ClassReader(inputStream);
		} catch (IOException ioex) {
			throw new ProxettaException(ioex);
		} finally {
			StreamUtil.close(inputStream);
		}
	}

	/**
	 * Returns class reader for advice.
	 */
	private ClassReader getCachedAdviceClassReader(final Class<? extends ProxyAdvice> advice) {
		if (adviceClassReaderCache == null) {
			adviceClassReaderCache = TypeCache.createDefault();
		}
		ClassReader adviceReader = adviceClassReaderCache.get(advice);
		if (adviceReader == null) {
			adviceReader = createAdviceClassReader(advice);
			adviceClassReaderCache.put(advice, adviceReader);
		}
		return adviceReader;
	}

	/**
	 * Returns class reader for advice.
	 */
	ClassReader getAdviceClassReader() {
		return adviceClassReader;
	}

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

	/**
	 * Parse advice class to gather some advice data. Should be called before any advice use.
	 * Must be called only *once* per advice.
	 */
	private void readAdviceData() {
		if (ready) {
			return;
		}

		adviceClassReader.accept(new EmptyClassVisitor() {

			/**
			 * Stores advice reference.
			 */
			@Override
			public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) {
				adviceReference = name;
				super.visit(version, access, name, signature, superName, interfaces);
			}

			/**
			 * Prevents advice to have inner classes.
			 */
			@Override
			public void visitInnerClass(final String name, final String outerName, final String innerName, final int access) {
				if (outerName.equals(adviceReference)) {
					throw new ProxettaException("Proxetta doesn't allow inner classes in/for advice: " + advice.getName());
				}
				super.visitInnerClass(name, outerName, innerName, access);
			}

			/**
			 * Clones advices fields to destination.
			 */
			@Override
			public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, final Object value) {
				wd.dest.visitField(access, adviceFieldName(name, aspectIndex), desc, signature, value);     // [A5]
				return super.visitField(access, name, desc, signature, value);
			}

			/**
			 * Copies advices methods to destination.
			 */
			@Override
			public MethodVisitor visitMethod(int access, String name, final String desc, final String signature, final String[] exceptions) {
				if (name.equals(CLINIT)) {              // [A6]
					if (!desc.equals(DESC_VOID)) {
						throw new ProxettaException("Invalid static initialization block description for advice: " + advice.getName());
					}
					name = ProxettaNames.clinitMethodName + ProxettaNames.methodDivider + aspectIndex;
					access |= AsmUtil.ACC_PRIVATE | AsmUtil.ACC_FINAL;
					wd.addAdviceClinitMethod(name);
					return new MethodAdapter(wd.dest.visitMethod(access, name, desc, signature, exceptions)) {

						@Override
						public void visitLocalVariable(final String name, final String desc, final String signature, final Label start, final Label end, final int index) {
						}

						@Override
						public void visitLineNumber(final int line, final Label start) {
						}

						@Override
						public void visitMethodInsn(final int opcode, String owner, String name, final String desc, final boolean isInterface) {
							if (opcode == INVOKESTATIC) {
								if (owner.equals(adviceReference)) {
									owner = wd.thisReference;
									name = adviceMethodName(name, aspectIndex);
								}
							}
							super.visitMethodInsn(opcode, owner, name, desc, isInterface);
						}

						@Override
						public void visitFieldInsn(final int opcode, String owner, String name, final String desc) { // [F6]
							if (owner.equals(adviceReference)) {
								owner = wd.thisReference;              // [F5]
								name = adviceFieldName(name, aspectIndex);
							}
							super.visitFieldInsn(opcode, owner, name, desc);
						}
					};
				} else

				if (name.equals(INIT)) { // [A7]
					if (!desc.equals(DESC_VOID)) {
						throw new ProxettaException("Advices can have only default constructors. Invalid advice: " + advice.getName());
					}

					name = ProxettaNames.initMethodName + ProxettaNames.methodDivider + aspectIndex;
					access = ProxettaAsmUtil.makePrivateFinalAccess(access);
					wd.addAdviceInitMethod(name);
					return new MethodAdapter(wd.dest.visitMethod(access, name, desc, signature, exceptions)) {
						@Override
						public void visitLocalVariable(final String name, final String desc, final String signature, final Label start, final Label end, final int index) {
						}

						@Override
						public void visitLineNumber(final int line, final Label start) {
						}

						int state; // used to detect and to ignore the first super call()

						@Override
						public void visitVarInsn(final int opcode, final int var) {                      // [F7]
							if ((state == 0) && (opcode == ALOAD) && (var == 0)) {
								state++;
								return;
							}
							super.visitVarInsn(opcode, var);
						}

						@Override
						public void visitMethodInsn(final int opcode, String owner, String name, final String desc, final boolean isInterface) {
							if ((state == 1) && (opcode == INVOKESPECIAL)) {
							    state++;
								return;
							}
							if ((opcode == INVOKEVIRTUAL) || (opcode == INVOKEINTERFACE)) {
								if (owner.equals(adviceReference)) {
									owner = wd.thisReference;
									name = adviceMethodName(name, aspectIndex);
								}
							} else

							if (opcode == INVOKESTATIC) {
								if (owner.equals(adviceReference)) {
									owner = wd.thisReference;
									name = adviceMethodName(name, aspectIndex);
								}
							}
							super.visitMethodInsn(opcode, owner, name, desc, isInterface);
						}

						@Override
						public void visitFieldInsn(final int opcode, String owner, String name, final String desc) { // [F7]
							if (owner.equals(adviceReference)) {
								owner = wd.thisReference;              // [F5]
								name = adviceFieldName(name, aspectIndex);
							}
							super.visitFieldInsn(opcode, owner, name, desc);
						}
					};

				} else

				// other methods
				if (!name.equals(ProxettaNames.executeMethodName)) {
					name = adviceMethodName(name, aspectIndex);
					return new MethodAdapter(wd.dest.visitMethod(access, name, desc, signature, exceptions)) {

						@Override
						public void visitLocalVariable(final String name, final String desc, final String signature, final Label start, final Label end, final int index) {
						}

						@Override
						public void visitLineNumber(final int line, final Label start) {
						}

						@Override
						public void visitMethodInsn(final int opcode, String owner, String name, final String desc, final boolean isInterface) {
							if ((opcode == INVOKEVIRTUAL) || (opcode == INVOKEINTERFACE)) {
								if (owner.equals(adviceReference)) {
									owner = wd.thisReference;
									name = adviceMethodName(name, aspectIndex);
								}
							} else

							if (opcode == INVOKESTATIC || opcode == INVOKESPECIAL) {
								if (owner.equals(adviceReference)) {
									owner = wd.thisReference;
									name = adviceMethodName(name, aspectIndex);
								}
							}
							super.visitMethodInsn(opcode, owner, name, desc, isInterface);
						}

						@Override
						public void visitFieldInsn(final int opcode, String owner, String name, final String desc) {        // replace field references
							if (owner.equals(adviceReference)) {
								owner = wd.thisReference;
								name = adviceFieldName(name, aspectIndex);
							}
							super.visitFieldInsn(opcode, owner, name, desc);
						}
					};
				}

				// Parse EXECUTE method, just to gather some info, real parsing will come later
				//return new MethodAdapter(new EmptyMethodVisitor()) {		// toask may we replace this with the following code?
				return new EmptyMethodVisitor() {
					@Override
					public void visitVarInsn(final int opcode, final int var) {
						if (isStoreOpcode(opcode)) {
							if (var > maxLocalVarOffset) {
								maxLocalVarOffset = var;          // find max local var offset
							}
						}
						super.visitVarInsn(opcode, var);
					}
				};
//					return super.visitMethod(access, name, desc, signature, exceptions);
			}
		}, 0);
		maxLocalVarOffset += 2;       // increment offset by 2 because var on last index may be a dword value
		ready = 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.crypt;

import jodd.util.Base64;
import jodd.util.StringUtil;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.AlgorithmParameters;
import java.security.SecureRandom;
import java.security.spec.KeySpec;

import static jodd.util.StringPool.UTF_8;

/**
 * Symmetric de/encryptor that uses PBE With MD5 And Triple DES.
 * Note: Requires Java8 u151 or installed Unlimited Strength Jurisdiction Policy Files.
 */
public class PBKDF2Encryptor {

	protected final Cipher ecipher;
	protected final Cipher dcipher;
	protected final int iterationCount;

	public PBKDF2Encryptor(final String passPhrase) {
		this(passPhrase, SecureRandom.getSeed(8), 65536, 256);
	}

	public PBKDF2Encryptor(final String passPhrase, final byte[] salt, final int iterationCount, final int i1) {
		this.iterationCount = iterationCount;
		try {
			// create the key
			KeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray(), salt, iterationCount, i1);
			SecretKey tmp = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(keySpec);
			SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

			// encryptor
			ecipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
			ecipher.init(Cipher.ENCRYPT_MODE, secret);
			AlgorithmParameters params = ecipher.getParameters();
			byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();

			// decryptor
			dcipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
			dcipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
		}
		catch (Exception ex) {
			throw new IllegalArgumentException(ex);
		}
	}

	/**
	 * Symmetrically encrypts the string.
	 */
	public String encrypt(final String str) {
		try {
			byte[] utf8 = StringUtil.getBytes(str);		// encode the string into bytes using utf-8
			byte[] enc = ecipher.doFinal(utf8); 	// encrypt
			return Base64.encodeToString(enc);		// encode bytes to base64 to get a string
		} catch (Throwable ignore) {
			return null;
		}
	}

	public byte[] encrypt(final byte[] input) {
		try {
			return ecipher.doFinal(input);
		} catch (Throwable ignore) {
			return null;
		}
	}

	/**
	 * Symmetrically decrypts the string.
	 */
	public String decrypt(String str) {
		try {
			str = StringUtil.replaceChar(str, ' ', '+');	// replace spaces with chars.
			byte[] dec = Base64.decode(str);    	// decode base64 to get bytes
			byte[] utf8 = dcipher.doFinal(dec);     // decrypt
			return new String(utf8, UTF_8);			// decode using utf-8
		} catch (Throwable ignore) {
			return null;
		}
	}

	public byte[] decrypt(final byte[] bytes) {
		try {
			return dcipher.doFinal(bytes);
		} catch (Throwable 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.log;

import jodd.log.impl.NOPLogger;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

/**
 * Logger factory.
 */
public final class LoggerFactory {

	static {
		setLoggerProvider(NOPLogger.PROVIDER);
	}

	private static Function<String, Logger> loggerProvider;

	private static Map<String, Logger> loggers = new HashMap<>();

	/**
	 * Sets {@link LoggerProvider} instance used for creating new {@link Logger}s.
	 */
	public static void setLoggerProvider(final LoggerProvider loggerProvider) {
		LoggerFactory.loggerProvider = loggerProvider::createLogger;
		if (loggers != null) {
			loggers.clear();
		}
	}

	/**
	 * Returns logger for given class by simply using the class name.
	 * @see #getLogger(String)
	 */
	public static Logger getLogger(final Class clazz) {
		return getLogger(clazz.getName());
	}

	/**
	 * Enables cache. Previous cache is removed.
	 */
	public static void enableCache() {
		loggers = new HashMap<>();
	}

	/**
	 * Disables the cache.
	 */
	public static void disableCache() {
		loggers = null;
	}

	/**
	 * Returns logger for given name. Repeated calls to this method with the
	 * same argument should return the very same instance of the logger.
	 */
	public static Logger getLogger(final String name) {
		if (loggers == null) {
			return loggerProvider.apply(name);
		}
		return loggers.computeIfAbsent(name, loggerProvider);
	}

}


// 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.util.StringUtil;

/**
 * Cookie object. Simple cookie data holder, cookie header parser and generator.
 */
public class Cookie {

	// the value of the cookie itself

	private String name;
	private String value;

	// attributes encoded in the header's cookie fields

	private String comment;				// ;Comment=VALUE ... describes cookie's use
										// ;Discard ... implied by maxAge < 0
	private String domain;				// ;Domain=VALUE ... domain that sees cookie
	private Integer maxAge;				// ;Max-Age=VALUE ... cookies auto-expire
	private String expires;				// ;Expires= ... expires values
	private String path;				// ;Path=VALUE ... URLs that see the cookie
	private boolean secure;				// ;Secure ... e.g. use SSL
	private Integer version; 			// ;Version=1 ... means RFC 2109++ style
	private boolean httpOnly;			// ;HttpOnly

	/**
	 * Creates cookie with specified name and value.
	 *

	 * The name must conform to RFC 2109. That means it can contain
	 * only ASCII alphanumeric characters and cannot contain commas,
	 * semicolons, or white space or begin with a $ character.
	 *

	 * The value can be anything the server chooses to send.
	 */
	public Cookie(final String name, final String value) {
		setName(name);
		setValue(value);
	}

	/**
	 * Parses cookie data from given user-agent string.
	 */
	public Cookie(String cookie) {
		int from = 0;
		int ndx = 0;

		cookie = cookie.trim();

		while (ndx < cookie.length()) {
			ndx = cookie.indexOf(';', from);

			if (ndx == -1) {
				// last chunk
				ndx = cookie.length();
			}
			int ndx2 = cookie.indexOf('=', from);

			String name;
			String value;
			if (ndx2 != -1 && ndx2 < ndx) {
				name = cookie.substring(from, ndx2).trim();
				value = cookie.substring(ndx2 + 1, ndx).trim();
			} else {
				if (from == ndx) {
					ndx++;
					continue;
				}
				name = cookie.substring(from, ndx).trim();
				value = null;
			}

			if (value != null && name.equalsIgnoreCase("Max-Age")) {
				setMaxAge(Integer.parseInt(value));
			} else if (name.equalsIgnoreCase("Comment")) {
				setComment(value);
			} else if (name.equalsIgnoreCase("Domain")) {
				setDomain(value);
			} else if (name.equalsIgnoreCase("Path")) {
				setPath(value);
			} else if (name.equalsIgnoreCase("Secure")) {
				setSecure(true);
			} else if (value != null && name.equalsIgnoreCase("Version")) {
				setVersion(Integer.parseInt(value));
			} else if (name.equalsIgnoreCase("HttpOnly")) {
				setHttpOnly(true);
			} else if (name.equalsIgnoreCase("Expires")) {
				setExpires(value);
			} else if (this.name == null && !StringUtil.isBlank(name)) {
				setName(name);
				setValue(value);
			}

			// continue
			from = ndx + 1;
		}
	}

	/**
	 * Sets the cookie name and checks for validity.
	 */
	private void setName(final String name) {
		if (name.contains(";") || name.contains(",") || name.startsWith("$")) {
			throw new IllegalArgumentException("Invalid cookie name:" + name);
		}

		for (int n = 0; n < name.length(); n++) {
			char c = name.charAt(n);
			if (c <= 0x20 || c >= 0x7f) {
				throw new IllegalArgumentException("Invalid cookie name:" + name);
			}
		}
		this.name = name;
	}

	/**
	 * Returns the comment describing the purpose of this cookie, or
	 * null if the cookie has no comment.
	 */
	public String getComment() {
		return comment;
	}

	/**
	 * Specifies a comment that describes a cookie's purpose.
	 * The comment is useful if the browser presents the cookie
	 * to the user.
	 */
	public Cookie setComment(final String purpose) {
		comment = purpose;
		return this;
	}

	/**
	 * Returns the domain name set for this cookie. The form of
	 * the domain name is set by RFC 2109.
	 */

	public String getDomain() {
		return domain;
	}

	/**
	 * Specifies the domain within which this cookie should be presented.
	 *

	 * The form of the domain name is specified by RFC 2109. A domain
	 * name begins with a dot (.foo.com) and means that
	 * the cookie is visible to servers in a specified Domain Name System
	 * (DNS) zone (for example, www.foo.com, but not
	 * a.b.foo.com). By default, cookies are only returned
	 * to the server that sent them.
	 */

	public Cookie setDomain(final String pattern) {
		domain = pattern.toLowerCase();    // IE allegedly needs this
		return this;
	}

	/**
	 * Returns the maximum age of the cookie, specified in seconds,
	 * By default, -1 indicating the cookie will persist
	 * until browser shutdown.
	 */

	public Integer getMaxAge() {
		return maxAge;
	}

	/**
	 * Sets the maximum age of the cookie in seconds.
	 *

	 * A positive value indicates that the cookie will expire
	 * after that many seconds have passed. Note that the value is
	 * the maximum age when the cookie will expire, not the cookie's
	 * current age.
	 *

	 * A negative value means
	 * that the cookie is not stored persistently and will be deleted
	 * when the Web browser exits. A zero value causes the cookie
	 * to be deleted.
	 */

	public Cookie setMaxAge(final int expiry) {
		maxAge = Integer.valueOf(expiry);
		return this;
	}

	/**
	 * Returns the path on the server
	 * to which the browser returns this cookie. The
	 * cookie is visible to all subpaths on the server.
	 */
	public String getPath() {
		return path;
	}

	/**
	 * Specifies a path for the cookie
	 * to which the client should return the cookie.
	 *

	 * The cookie is visible to all the pages in the directory
	 * you specify, and all the pages in that directory's subdirectories.
	 * A cookie's path must include the servlet that set the cookie,
	 * for example, /catalog, which makes the cookie
	 * visible to all directories on the server under /catalog.
	 *
	 *
Consult RFC 2109 (available on the Internet) for more
	 * information on setting path names for cookies.
	 */
	public Cookie setPath(final String uri) {
		path = uri;
		return this;
	}

	/**
	 * Returns true if the browser is sending cookies
	 * only over a secure protocol, or false if the
	 * browser can send cookies using any protocol.
	 */
	public boolean isSecure() {
		return secure;
	}

	/**
	 * Indicates to the browser whether the cookie should only be sent
	 * using a secure protocol, such as HTTPS or SSL.
	 */
	public Cookie setSecure(final boolean flag) {
		secure = flag;
		return this;
	}

	/**
	 * Returns the name of the cookie. The name cannot be changed after
	 * creation.
	 */
	public String getName() {
		return name;
	}

	/**
	 * Returns the value of the cookie.
	 */
	public String getValue() {
		return value;
	}

	/**
	 * Assigns a new value to a cookie after the cookie is created.
	 * If you use a binary value, you may want to use BASE64 encoding.
	 */
	public Cookie setValue(final String newValue) {
		value = newValue;
		return this;
	}

	/**
	 * Returns the version of the protocol this cookie complies
	 * with. Version 1 complies with RFC 2109,
	 * and version 0 complies with the original
	 * cookie specification drafted by Netscape. Cookies provided
	 * by a browser use and identify the browser's cookie version.
	 */
	public Integer getVersion() {
		return version;
	}

	/**
	 * Sets the version of the cookie protocol this cookie complies
	 * with. Version 0 complies with the original Netscape cookie
	 * specification. Version 1 complies with RFC 2109.
	 */
	public Cookie setVersion(final int version) {
		this.version = Integer.valueOf(version);
		return this;
	}

	public boolean isHttpOnly() {
		return httpOnly;
	}

	public Cookie setHttpOnly(final boolean httpOnly) {
		this.httpOnly = httpOnly;
		return this;
	}

	public String getExpires() {
		return expires;
	}

	public Cookie setExpires(final String expires) {
		this.expires = expires;
		return this;
	}

	@Override
	public String toString() {
		StringBuilder cookie = new StringBuilder();

		cookie.append(name).append('=').append(value);

		if (maxAge != null) {
			cookie.append("; Max-Age=").append(maxAge);
		}
		if (expires != null) {
			cookie.append("; Expires=").append(expires);
		}
		if (comment != null) {
			cookie.append("; Comment=").append(comment);
		}
		if (domain != null) {
			cookie.append("; Domain=").append(domain);
		}
		if (path != null) {
			cookie.append("; Path=").append(path);
		}
		if (secure) {
			cookie.append("; Secure");
		}
		if (version != null) {
			cookie.append("; Version=").append(version);
		}
		if (httpOnly) {
			cookie.append("; HttpOnly");
		}

		return cookie.toString();
	}

}


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

package jodd.madvoc.component;

import jodd.log.Logger;
import jodd.log.LoggerFactory;
import jodd.madvoc.ActionRequest;
import jodd.madvoc.MadvocException;
import jodd.madvoc.MadvocUtil;
import jodd.madvoc.config.ActionRuntime;
import jodd.madvoc.result.ActionResult;
import jodd.petite.meta.PetiteInject;
import jodd.servlet.ServletUtil;
import jodd.util.ClassUtil;

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

/**
 * Madvoc controller invokes actions for action path and renders action results.
 * It also builds action objects and result paths. It handles initialization of
 * interceptors and results.
 */
public class MadvocController extends MadvocControllerCfg implements MadvocComponentLifecycle.Ready{

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

	@PetiteInject
	protected MadvocEncoding madvocEncoding;

	@PetiteInject
	protected ActionsManager actionsManager;

	@PetiteInject
	protected ActionPathRewriter actionPathRewriter;

	@PetiteInject
	protected ResultsManager resultsManager;

	@PetiteInject
	protected ServletContextProvider servletContextProvider;

	@PetiteInject
	protected AsyncActionExecutor asyncActionExecutor;

	@Override
	public void ready() {
		if (actionsManager.isAsyncModeOn()) {
			asyncActionExecutor.start();
		}
	}

	/**
	 * Returns application context set during the initialization.
	 */
	public ServletContext getApplicationContext() {
		return servletContextProvider.get();
	}

	// ---------------------------------------------------------------- invoke

	/**
	 * Invokes action registered to provided action path, Provides action chaining, by invoking the next action request.
	 * Returns null if action path is consumed and has been invoked by this controller; otherwise
	 * the action path string is returned (it might be different than original one, provided in arguments).
	 * On first invoke, initializes the action runtime before further proceeding.
	 */
	public String invoke(String actionPath, final HttpServletRequest servletRequest, final HttpServletResponse servletResponse) throws Exception {
		final String originalActionPath = actionPath;
		boolean characterEncodingSet = false;

		while (actionPath != null) {
			// build action path
			final String httpMethod = servletRequest.getMethod().toUpperCase();

			if (log.isDebugEnabled()) {
				log.debug("Action path: " + httpMethod + " " + actionPath);
			}

			actionPath = actionPathRewriter.rewrite(servletRequest, actionPath, httpMethod);

			String[] actionPathChunks = MadvocUtil.splitPathToChunks(actionPath);

			// resolve action runtime
			ActionRuntime actionRuntime = actionsManager.lookup(httpMethod, actionPathChunks);

			if (actionRuntime == null) {

				// special case!
				if (actionPath.endsWith(welcomeFile)) {
					actionPath = actionPath.substring(0, actionPath.length() - (welcomeFile.length() - 1));
					actionPathChunks = MadvocUtil.splitPathToChunks(actionPath);
					actionRuntime = actionsManager.lookup(httpMethod, actionPathChunks);
				}
				if (actionRuntime == null) {
					return originalActionPath;
				}
			}
			if (log.isDebugEnabled()) {
				log.debug("Invoke action for '" + actionPath + "' using " + actionRuntime.createActionString());
			}

			// set character encoding
			if (!characterEncodingSet && applyCharacterEncoding) {

				final String encoding = madvocEncoding.getEncoding();

				if (encoding != null) {
					servletRequest.setCharacterEncoding(encoding);
					servletResponse.setCharacterEncoding(encoding);
				}

				characterEncodingSet = true;
			}

			// create action object
			final Object action;

			if (actionRuntime.isActionHandlerDefined()) {
				action = actionRuntime.getActionHandler();
			}
			else {
				action = createAction(actionRuntime.getActionClass());
			}

			final ActionRequest actionRequest = createActionRequest(
				actionPath,
				actionPathChunks,
				actionRuntime,
				action,
				servletRequest,
				servletResponse);

			// invoke and render
			if (actionRuntime.isAsync()) {
				asyncActionExecutor.invoke(actionRequest);
			} else {
				actionRequest.invoke();
			}

			actionPath = actionRequest.getNextActionPath();
		}
		return null;
	}

	// ---------------------------------------------------------------- render

	/**
	 * Invokes a result after the action invocation.
	 *

	 * Results may be objects that specify which action result will be used
	 * to render the result.
	 *

	 * Result value may consist of two parts: type and value. Result type is optional and, if exists, it is separated
	 * by semi-colon from the value. If type is not specified
	 * then the default result type if still not defined. Result type defines which
	 * {@link ActionResult} should be used for rendering the value.
	 *

	 * Result value is first checked against aliased values. Then, it is resolved and then passed
	 * to the founded {@link ActionResult}.
	 *
	 * @see ActionResult#render(jodd.madvoc.ActionRequest, Object)
	 */
	@SuppressWarnings("unchecked")
	public void render(final ActionRequest actionRequest, final Object resultObject) throws Exception {
		final ActionResult actionResult = resultsManager.lookup(actionRequest, resultObject);

		if (actionResult == null) {
			throw new MadvocException("Action result not found");
		}

		if (preventCaching) {
			ServletUtil.preventCaching(actionRequest.getHttpServletResponse());
		}

		log.debug(() -> "Result type: " + actionResult.getClass().getSimpleName());

		actionResult.render(actionRequest, actionRequest.getActionResult());
	}

	// ---------------------------------------------------------------- create

	/**
	 * Creates new action object from {@link ActionRuntime} using default constructor.
	 */
	protected Object createAction(final Class actionClass) {
		try {
			return ClassUtil.newInstance(actionClass);
		} catch (Exception ex) {
			throw new MadvocException("Invalid Madvoc action", ex);
		}
	}

	/**
	 * Creates new action request.
	 * @param actionPath		action path
	 * @param actionRuntime		action runtime
	 * @param action			action object
	 * @param servletRequest	http request
	 * @param servletResponse	http response
	 * @return action request
	 */
	protected ActionRequest createActionRequest(
		final String actionPath,
		final String[] actionPathChunks,
		final ActionRuntime actionRuntime,
		final Object action,
		final HttpServletRequest servletRequest,
		final HttpServletResponse servletResponse) {

		return new ActionRequest(this, actionPath, actionPathChunks, actionRuntime, action, servletRequest, servletResponse);
	}

}


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

package jodd.madvoc.petite;

import jodd.madvoc.component.FiltersManager;
import jodd.madvoc.filter.ActionFilter;
import jodd.petite.PetiteContainer;
import jodd.petite.meta.PetiteInject;

/**
 * Petite-aware filters manager.
 */
public class PetiteFilterManager extends FiltersManager {

	@PetiteInject
	protected PetiteContainer petiteContainer;

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

}


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

package jodd.petite.scope;

import jodd.petite.BeanData;
import jodd.petite.def.DestroyMethodPoint;

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

/**
 * Base {@link Scope} class for scopes that collect
 * destroyable beans and implement shutdown routine.
 */
public abstract class ShutdownAwareScope implements Scope {

	protected List<BeanData> destroyableBeans;

	/**
	 * Returns true if bean is destroyable.
	 */
	protected boolean isBeanDestroyable(final BeanData beanData) {
		DestroyMethodPoint[] dmp = beanData.definition().destroyMethodPoints();
		return dmp != null && dmp.length != 0;
	}

	/**
	 * Checks if bean data is destroyable (has destroy methods) and
	 * registers it for later {@link #shutdown()}.
	 */
	protected void registerDestroyableBeans(final BeanData beanData) {
		if (!isBeanDestroyable(beanData)) {
			return;
		}
		if (destroyableBeans == null) {
			destroyableBeans = new ArrayList<>();
		}
		destroyableBeans.add(beanData);
	}

	/**
	 * Returns number of destroyable beans that have been registered.
	 */
	protected int totalRegisteredDestroyableBeans() {
		if (destroyableBeans == null) {
			return 0;
		}
		return destroyableBeans.size();
	}

	/**
	 * Removes destroyable bean from the list and calls it destroy methods.
	 * If bean is not destroyable, does nothing. Bean gets destroyed only once.
	 */
	protected void destroyBean(final BeanData beanData) {
		if (destroyableBeans == null) {
			return;
		}
		if (!isBeanDestroyable(beanData)) {
			return;
		}
		if (destroyableBeans.remove(beanData)) {
			beanData.callDestroyMethods();
		}
	}

	/**
	 * Shutdowns the scope and calls all collected destroyable beans.
	 */
	@Override
	public void shutdown() {
		if (destroyableBeans == null) {
			return;
		}

		for (final BeanData destroyableBean : destroyableBeans) {
			destroyableBean.callDestroyMethods();
		}

		destroyableBeans.clear();
	}

}


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

package jodd.mail;

import javax.mail.Authenticator;
import javax.mail.Session;
import java.io.File;
import java.util.Objects;
import java.util.Properties;

public abstract class MailServer<MailSessionImpl extends MailSession> {

	public static final String MAIL_HOST = "mail.host";
	public static final String MAIL_SMTP_HOST = "mail.smtp.host";
	public static final String MAIL_SMTP_PORT = "mail.smtp.port";
	public static final String MAIL_SMTP_AUTH = "mail.smtp.auth";
	public static final String MAIL_TRANSPORT_PROTOCOL = "mail.transport.protocol";
	//public static final String MAIL_SMTP_FROM = "mail.smtp.from";

	public static final String MAIL_SMTP_CONNECTIONTIMEOUT = "mail.smtp.connectiontimeout";
	public static final String MAIL_SMTP_TIMEOUT = "mail.smtp.timeout";
	public static final String MAIL_SMTP_WRITETIMEOUT = "mail.smtp.writetimeout";
	public static final String MAIL_DEBUG = "mail.debug";
	public static final String MAIL_MIME_ADDRESS_STRICT = "mail.mime.address.strict";

	public static final String MAIL_IMAP_CONNECTIONTIMEOUT = "mail.imap.connectiontimeout";
	public static final String MAIL_IMAP_TIMEOUT = "mail.imap.timeout";
	public static final String MAIL_IMAP_PORT = "mail.imap.port";
	public static final String MAIL_IMAP_HOST = "mail.imap.host";
	public static final String MAIL_IMAP_PARTIALFETCH = "mail.imap.partialfetch";

	public static final String MAIL_IMAP_SOCKET_FACTORY_PORT = "mail.imap.socketFactory.port";
	public static final String MAIL_IMAP_SOCKET_FACTORY_CLASS = "mail.imap.socketFactory.class";
	public static final String MAIL_IMAP_SOCKET_FACTORY_FALLBACK = "mail.imap.socketFactory.fallback";

	public static final String MAIL_SMTP_STARTTLS_REQUIRED = "mail.smtp.starttls.required";
	public static final String MAIL_SMTP_STARTTLS_ENABLE = "mail.smtp.starttls.enable";
	public static final String MAIL_SMTP_SOCKET_FACTORY_PORT = "mail.smtp.socketFactory.port";
	public static final String MAIL_SMTP_SOCKET_FACTORY_CLASS = "mail.smtp.socketFactory.class";
	public static final String MAIL_SMTP_SOCKET_FACTORY_FALLBACK = "mail.smtp.socketFactory.fallback";

	public static final String MAIL_POP3_PORT = "mail.pop3.port";
	public static final String MAIL_POP3_HOST = "mail.pop3.host";
	public static final String MAIL_POP3_AUTH = "mail.pop3.auth";
	public static final String MAIL_POP3_CONNECTIONTIMEOUT = "mail.pop3.connectiontimeout";
	public static final String MAIL_POP3_TIMEOUT = "mail.pop3.timeout";

	public static final String MAIL_POP3_SOCKET_FACTORY_PORT = "mail.pop3.socketFactory.port";
	public static final String MAIL_POP3_SOCKET_FACTORY_CLASS = "mail.pop3.socketFactory.class";
	public static final String MAIL_POP3_SOCKET_FACTORY_FALLBACK = "mail.pop3.socketFactory.fallback";

	/**
	 * The host.
	 */
	protected final String host;

	/**
	 * The port.
	 */
	protected final int port;

	/**
	 * The {@link Authenticator}.
	 */
	protected final Authenticator authenticator;

	protected final File attachmentStorage;

	protected final boolean debugMode;

	/**
	 * Whether strict address checking is turned on.
	 */
	protected final boolean strictAddress;

	/**
	 * Connection timeout.
	 */
	protected final int timeout;

	protected final Properties customProperties;

	/**
	 * {@link MailServer} defined with its host, port and {@link Authenticator}.
	 */
	protected MailServer(final Builder builder, final int defaultPort) {
		Objects.requireNonNull(builder.host, "Host cannot be null");

		this.host = builder.host;
		this.port = builder.port == -1 ? defaultPort : builder.port;
		this.authenticator = builder.authenticator;
		this.attachmentStorage = builder.attachmentStorage;
		this.timeout = builder.timeout;
		this.strictAddress = builder.strictAddress;
		this.debugMode = builder.debug;
		this.customProperties = builder.customProperties;
	}

	/**
	 * Creates new mail session.
	 *
	 * @return {@link MailSession} or an implementing class such as {@link ReceiveMailSession}
	 * or {@link SendMailSession}. The {@link Session} properties must be set <b>before</b>
	 * the {@link Session} is created.
	 */
	public abstract MailSessionImpl createSession();

	/**
	 * Creates {@link MailSession} {@link Properties}.
	 *
	 * @return session {@link Properties}
	 */
	protected Properties createSessionProperties() {
		final Properties props = new Properties();

		props.putAll(customProperties);

		if (debugMode) {
			props.put(MAIL_DEBUG, "true");
		}

		if (!strictAddress) {
			props.put(MAIL_MIME_ADDRESS_STRICT, "false");
		}

		return props;
	}

	/**
	 * Returns new mail server builder.
	 */
	public static Builder create() {
		return new Builder();
	}

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

	/**
	 * Used to create implementing instances of {@link MailServer}.
	 *
	 * @see ImapServer
	 * @see ImapSslServer
	 * @see Pop3Server
	 * @see Pop3SslServer
	 * @see SmtpServer
	 * @see SmtpSslServer
	 */
	public static class Builder {
		private String host = null;
		private int port = -1;
		private boolean ssl = false;
		private Authenticator authenticator;
		private File attachmentStorage;
		private boolean debug;
		private int timeout = 0;
		private boolean strictAddress = true;
		private Properties customProperties = new Properties();

		/**
		 * Sets the host.
		 *
		 * @param host The host to set.
		 * @return this
		 */
		public Builder host(final String host) {
			this.host = host;
			return this;
		}

		/**
		 * Sets the port.
		 *
		 * @param port The port to set.
		 * @return this
		 *
		 */
		public Builder port(final int port) {
			this.port = port;
			return this;
		}

		/**
		 * Sets the SSL implementation of the Mail server.
		 *
		 * @param ssl SSL flag
		 * @return this
		 */
		public Builder ssl(final boolean ssl) {
			this.ssl = ssl;
			return this;
		}

		/**
		 * Defines attachment storage, a folder where attachments will be saved.
		 */
		public Builder storeAttachmentsIn(final File attachmentStorage) {
			this.attachmentStorage = attachmentStorage;
			return this;
		}

		/**
		 * Sets authenticator as {@link SimpleAuthenticator} using username and password.
		 *
		 * @param username The username to use.
		 * @param password The password to use.
		 * @return this
		 */
		public Builder auth(final String username, final String password) {
			Objects.requireNonNull(username, "Username cannot be null");
			Objects.requireNonNull(password, "Password cannot be null");

			return auth(new SimpleAuthenticator(username, password));
		}

		/**
		 * Sets the authenticator.
		 *
		 * @param authenticator {@link Authenticator} to set.
		 * @return this
		 */
		public Builder auth(final Authenticator authenticator) {
			this.authenticator = authenticator;
			return this;
		}

		/**
		 * Enable or disable debug mode.
		 *
		 * @param debug {@code true} to turn on debugging. By default, this is {@code false}.
		 * @return this
		 */
		public Builder debugMode(final boolean debug) {
			this.debug = debug;
			return this;
		}

		/**
		 * Defines timeout value in milliseconds for all mail-related operations.
		 *
		 * @param timeout timeout value in milliseconds.
		 * @return this
		 */
		public Builder timeout(final int timeout) {
			this.timeout = timeout;
			return this;
		}

		/**
		 * Disables the strict address.
		 *
		 * @param strictAddress {@code true} if strict address checking should be be turned on. By default, this is {@code true}.
		 * @return this
		 */
		public Builder strictAddress(final boolean strictAddress) {
			this.strictAddress = strictAddress;
			return this;
		}

		public Builder property(final String name, final String value) {
			this.customProperties.put(name, value);
			return this;
		}

		// ---------------------------------------------------------------- build

		/**
		 * Create a {@link ImapServer} from current data.
		 *
		 * @return {@link ImapServer} from current data.
		 */
		public ImapServer buildImapMailServer() {
			if (ssl) {
				return new ImapSslServer(this);
			}
			return new ImapServer(this);
		}

		/**
		 * Create a {@link Pop3Server} from current data.
		 *
		 * @return {@link Pop3Server} from current data.
		 *
		 */
		public Pop3Server buildPop3MailServer() {
			if (ssl) {
				return new Pop3SslServer(this);
			}
			return new Pop3Server(this);
		}

		/**
		 * Create a {@link SmtpServer} from current data.
		 *
		 * @return {@link SmtpServer} from current data.
		 *
		 */
		public SmtpServer buildSmtpMailServer() {
			if (ssl) {
				return new SmtpSslServer(this);
			}
			return new SmtpServer(this);
		}
	}
}