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

import jodd.bean.BeanUtil;
import jodd.log.Logger;
import jodd.log.LoggerFactory;
import jodd.petite.def.BeanReferences;
import jodd.petite.def.MethodInjectionPoint;
import jodd.petite.def.ProviderDefinition;
import jodd.petite.meta.InitMethodInvocationStrategy;
import jodd.petite.scope.Scope;
import jodd.petite.scope.SingletonScope;

import java.lang.reflect.Method;

/**
 * Petite IOC container.
 * @see PetiteRegistry for fluent java registration of Petite beans.
 */
public class PetiteContainer extends PetiteBeans {

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

	/**
	 * Petite container reference name.
	 * Used when container itself is added as its bean.
	 * @see #addSelf()
	 * @see #addSelf(String)
	 */
	public static final String PETITE_CONTAINER_REF_NAME = "petiteContainer";

	protected final ScopedProxyManager scopedProxyManager;

	/**
	 * Creates new Petite container using {@link PetiteConfig default configuration}.
	 */
	public PetiteContainer() {
		this(new PetiteConfig());
	}

	/**
	 * Creates new Petite container using {@link PetiteContainer provided configuration}.
	 */
	public PetiteContainer(final PetiteConfig config) {
		super(config);

		scopedProxyManager = new ScopedProxyManager();

		if (log.isDebugEnabled()) {
			log.debug("Petite container created");
		}
	}

	// ---------------------------------------------------------------- core

	protected Object lookupMixingScopedBean(final BeanDefinition def, final BeanReferences refNames) {
		final boolean mixing = petiteConfig.wireScopedProxy || petiteConfig.detectMixedScopes;

		Object value = null;

		if (mixing) {
			final BeanDefinition refBeanDefinition = lookupBeanDefinitions(refNames);

			if (refBeanDefinition != null) {
				value = scopedProxyManager.lookupValue(PetiteContainer.this, def, refBeanDefinition);
			}
		}

		if (value == null) {
			value = PetiteContainer.this.getBean(refNames);
		}

		return value;
	}

	// ---------------------------------------------------------------- get beans

	/**
	 * Returns Petite bean instance. Bean name will be resolved from provided type.
	 */
	@SuppressWarnings({"unchecked"})
	public <T> T getBean(final Class<T> type) {
		String name = resolveBeanName(type);
		return (T) getBean(name);
	}

	/**
	 * Returns Petite bean instance named as one of the provided names.
	 * Returns {@code null} if bean is not found.
	 */
	protected Object getBean(final BeanReferences beanReferences) {
		final int total = beanReferences.size();

		for (int i = 0; i < total; i++) {
			String name = beanReferences.name(i);

			if (name != null) {
				Object bean = getBean(name);
				if (bean != null) {
					return bean;
				}
			}
		}
		return null;
	}

	/**
	 * Returns Petite bean instance.
	 * Petite container will find the bean in corresponding scope and all its dependencies,
	 * either by constructor or property injection. When using constructor injection, cyclic dependencies
	 * can not be prevented, but at least they are detected.
	 *
	 * @see PetiteContainer#createBean(Class)
	 */
	public <T> T getBean(final String name) {

		// Lookup for registered bean definition.
		BeanDefinition def = lookupBeanDefinition(name);

		if (def == null) {

			// try provider
			ProviderDefinition providerDefinition = providers.get(name);

			if (providerDefinition != null) {
				return (T) invokeProvider(providerDefinition);
			}
			return null;
		}

		// Find the bean in its scope
		Object bean = def.scopeLookup();

		if (bean == null) {
			// Create new bean in the scope
			initBeanDefinition(def);
			final BeanData beanData = new BeanData(this, def);
			registerBeanAndWireAndInjectParamsAndInvokeInitMethods(beanData);
			bean = beanData.bean();
		}

		return (T) bean;
	}

	/**
	 * Resolves and initializes bean definition. May be called multiple times.
	 */
	protected void initBeanDefinition(final BeanDefinition def) {
		// init methods
		if (def.initMethods == null) {
			def.initMethods = petiteResolvers.resolveInitMethodPoint(def.type);
		}
		// destroy methods
		if (def.destroyMethods == null) {
			def.destroyMethods = petiteResolvers.resolveDestroyMethodPoint(def.type);
		}
		// properties
		if (def.properties == null) {
			def.properties = petiteResolvers.resolvePropertyInjectionPoint(def.type, def.wiringMode == WiringMode.AUTOWIRE);
		}
		// methods
		if (def.methods == null) {
			def.methods = petiteResolvers.resolveMethodInjectionPoint(def.type);
		}
		// ctors
		if (def.ctor == null) {
			def.ctor = petiteResolvers.resolveCtorInjectionPoint(def.type);
		}
		// values
		if (def.values == null) {
			def.values = paramManager.resolveParamInjectionPoints(def.type);
		}
		// sets
		if (def.sets == null) {
			def.sets = petiteResolvers.resolveSetInjectionPoint(def.type, def.wiringMode == WiringMode.AUTOWIRE);
		}
		// params
		if (def.params == null) {
			def.params = paramManager.filterParametersForBeanName(def.name, petiteConfig.getResolveReferenceParameters());
		}
	}

	/**
	 * Wires bean, injects parameters and invokes init methods.
	 * Such a loooong name :)
	 */
	protected void registerBeanAndWireAndInjectParamsAndInvokeInitMethods(final BeanData beanData) {
		initBeanDefinition(beanData.definition());

		beanData.scopeRegister();
		beanData.invokeInitMethods(InitMethodInvocationStrategy.POST_CONSTRUCT);
		beanData.wireBean();
		beanData.invokeInitMethods(InitMethodInvocationStrategy.POST_DEFINE);
		beanData.injectParams(paramManager, petiteConfig.isImplicitParamInjection());
		beanData.invokeInitMethods(InitMethodInvocationStrategy.POST_INITIALIZE);
		beanData.invokeConsumerIfRegistered();
	}

	// ---------------------------------------------------------------- wire

	/**
	 * Wires provided bean with the container using default wiring mode.
	 * Bean is not registered withing container.
	 */
	public void wire(final Object bean) {
		wire(bean, null);
	}

	/**
	 * Wires provided bean with the container and optionally invokes init methods.
	 * Bean is not registered withing container.
	 */
	public void wire(final Object bean, final WiringMode wiringMode) {
		final WiringMode finalWiringMode = petiteConfig.resolveWiringMode(wiringMode);

		final BeanDefinition def = externalsCache.get(
			bean.getClass(), () -> {
				final BeanDefinition beanDefinition = createBeandDefinitionForExternalBeans(bean.getClass(), finalWiringMode);
				initBeanDefinition(beanDefinition);
				return beanDefinition;
			});

		registerBeanAndWireAndInjectParamsAndInvokeInitMethods(new BeanData(this, def, bean));
	}

	/**
	 * Invokes the method of some bean with the container, when its parameters requires to be injected into.
	 * The bean is not registered within container.
	 */
	public <T> T invokeMethod(final Object bean, final Method method) {
		final WiringMode wiringMode = petiteConfig.resolveWiringMode(null);

		final BeanDefinition def = externalsCache.get(
			bean.getClass(), () -> {
				final BeanDefinition beanDefinition = createBeandDefinitionForExternalBeans(bean.getClass(), wiringMode);
				initBeanDefinition(beanDefinition);
				return beanDefinition;
			});

		final BeanData beanData = new BeanData(this, def, bean);

		for (MethodInjectionPoint methodInjectionPoint : def.methods) {
			if (methodInjectionPoint.method.equals(method)) {
				return (T) beanData.invokeMethodInjectionPoint(methodInjectionPoint);
			}
		}
		try {
			return (T) method.invoke(bean);
		} catch (Exception e) {
			throw new PetiteException(e);
		}
	}

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

	/**
	 * Creates and wires a bean within the container using default wiring mode and default init methods flag.
	 * Bean is not registered.
	 */
	public <E> E createBean(final Class<E> type) {
		return createBean(type, null);
	}

	/**
	 * Creates and wires a bean within the container and optionally invokes init methods. However, bean is
	 * not registered.
	 */
	@SuppressWarnings({"unchecked"})
	public <E> E createBean(final Class<E> type, final WiringMode wiringMode) {
		final WiringMode finalWiringMode = petiteConfig.resolveWiringMode(wiringMode);

		final BeanDefinition def = externalsCache.get(
			type, () -> {
				final BeanDefinition beanDefinition = createBeandDefinitionForExternalBeans(type, finalWiringMode);
				initBeanDefinition(beanDefinition);
				return beanDefinition;
			});

		final BeanData<E> beanData = new BeanData(this, def);
		registerBeanAndWireAndInjectParamsAndInvokeInitMethods(beanData);
		return beanData.bean();
	}

	// ---------------------------------------------------------------- providers

	/**
	 * Invokes provider to get a bean.
	 */
	protected Object invokeProvider(final ProviderDefinition provider) {
		if (provider.method != null) {

			final Object bean;
			if (provider.beanName != null) {
				// instance factory method
				bean = getBean(provider.beanName);
			} else {
				// static factory method
				bean = null;
			}
			try {
				return provider.method.invoke(bean);
			} catch (Exception ex) {
				throw new PetiteException("Invalid provider method: " + provider.method.getName(), ex);
			}
		}

		throw new PetiteException("Invalid provider");
	}

	// ---------------------------------------------------------------- add

	/**
	 * Adds object instance to the container as singleton bean using default
	 * wiring mode and default init method flag.
	 */
	public void addBean(final String name, final Object bean) {
		addBean(name, bean, null);
	}

	/**
	 * Adds object instance to the container as singleton bean.
	 */
	public void addBean(final String name, final Object bean, WiringMode wiringMode) {
		wiringMode = petiteConfig.resolveWiringMode(wiringMode);
		registerPetiteBean(bean.getClass(), name, SingletonScope.class, wiringMode, false, null);
		BeanDefinition def = lookupExistingBeanDefinition(name);
		registerBeanAndWireAndInjectParamsAndInvokeInitMethods(new BeanData(this, def, bean));
	}

	/**
	 * Adds self instance to the container so internal beans may fetch
	 * container for further usage. No wiring is used and no init methods are invoked.
	 */
	public void addSelf(final String name) {
		addBean(name, this, WiringMode.NONE);
	}

	/**
	 * Adds self instance to the container so internal beans may fetch
	 * container for further usage. No wiring is used and no init methods are invoked.
	 */
	public void addSelf() {
		addBean(PETITE_CONTAINER_REF_NAME, this, WiringMode.NONE);
	}

	// ---------------------------------------------------------------- property

	/**
	 * Sets petite bean property.
	 */
	public void setBeanProperty(final String name, final Object value) {
		Object bean = null;
		int ndx = name.length();

		while (true) {
			ndx = name.lastIndexOf('.', ndx);
			if (ndx == -1) {
				break;
			}

			String beanName = name.substring(0, ndx);
			bean = getBean(beanName);
			if (bean != null) {
				break;
			}
			ndx--;
		}

		if (bean == null) {
			throw new PetiteException("Invalid bean property: " + name);
		}

		try {
			BeanUtil.declared.setProperty(bean, name.substring(ndx + 1), value);
		} catch (Exception ex) {
			throw new PetiteException("Invalid bean property: " + name, ex);
		}
	}

	/**
	 * Returns petite bean property value.
	 */
	public Object getBeanProperty(final String name) {
		int ndx = name.indexOf('.');
		if (ndx == -1) {
			throw new PetiteException("Only bean name is specified, missing property name: " + name);
		}
		String beanName = name.substring(0, ndx);
		Object bean = getBean(beanName);
		if (bean == null) {
			throw new PetiteException("Bean doesn't exist: " + name);
		}
		try {
			return BeanUtil.declared.getProperty(bean, name.substring(ndx + 1));
		} catch (Exception ex) {
			throw new PetiteException("Invalid bean property: " + name, ex);
		}
	}

	// ---------------------------------------------------------------- registry

	/**
	 * Creates {@link PetiteRegistry} helper tool for this container.
	 */
	public PetiteRegistry createContainerRegistry() {
		return PetiteRegistry.of(this);
	}

	// ---------------------------------------------------------------- shutdown

	/**
	 * Shutdowns container. After container is down, it can't be used anymore.
	 */
	public void shutdown() {
		scopes.forEachValue(Scope::shutdown);

		externalsCache.clear();
		beans.clear();
		beansAlt.clear();
		scopes.clear();
		providers.clear();
		beanCollections.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.NoSuchProviderException;
import javax.mail.Session;
import javax.mail.Transport;
import java.util.Properties;

import static jodd.util.StringPool.TRUE;

/**
 * Represents simple plain SMTP server for sending emails.
 */
public class SmtpServer extends MailServer<SendMailSession> {

	protected static final String PROTOCOL_SMTP = "smtp";

	/**
	 * Default SMTP port
	 */
	protected static final int DEFAULT_SMTP_PORT = 25;

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

	public SmtpServer(final Builder builder) {
		super(builder, DEFAULT_SMTP_PORT);
	}
	protected SmtpServer(final Builder builder, final int defaultPort) {
		super(builder, defaultPort);
	}

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

	@Override
	protected Properties createSessionProperties() {
		final Properties props = super.createSessionProperties();

		props.setProperty(MAIL_TRANSPORT_PROTOCOL, PROTOCOL_SMTP);
		props.setProperty(MAIL_HOST, host);
		props.setProperty(MAIL_SMTP_HOST, host);
		props.setProperty(MAIL_SMTP_PORT, String.valueOf(port));

		if (authenticator != null) {
			props.setProperty(MAIL_SMTP_AUTH, TRUE);
		}

		if (timeout > 0) {
			final String timeoutValue = String.valueOf(timeout);
			props.put(MAIL_SMTP_CONNECTIONTIMEOUT, timeoutValue);
			props.put(MAIL_SMTP_TIMEOUT, timeoutValue);
			props.put(MAIL_SMTP_WRITETIMEOUT, timeoutValue);
		}

		return props;
	}

	/**
	 * {@inheritDoc}
	 *
	 * @return {@link SendMailSession}
	 */
	@Override
	public SendMailSession createSession() {
		final Session session = Session.getInstance(createSessionProperties(), authenticator);
		final Transport mailTransport;
		try {
			mailTransport = getTransport(session);
		} catch (final NoSuchProviderException nspex) {
			throw new MailException(nspex);
		}
		return new SendMailSession(session, mailTransport);
	}

	/**
	 * Get the {@link Transport} for {@link Session}.
	 *
	 * @param session The {@link SendMailSession}.
	 * @return SMTP {@link Transport}.
	 * @throws NoSuchProviderException If provider for the given protocol is not found.
	 */
	protected Transport getTransport(final Session session) throws NoSuchProviderException {
		return session.getTransport(PROTOCOL_SMTP);
	}

}


// 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.system.SystemUtil;
import jodd.util.ClassLoaderUtil;
import jodd.util.StringUtil;

import java.net.MalformedURLException;
import java.net.URL;

import static jodd.joy.JoddJoy.APP_DIR;

public class JoyPaths extends JoyBase {

	protected String appDir;

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

	/**
	 * Returns resolved app dir.
	 */
	public String getAppDir() {
		return requireStarted(appDir);
	}

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

	@Override
	public void start() {
		initLogger();

		final String resourceName = StringUtil.replaceChar(JoyPaths.class.getName(), '.', '/') + ".class";

		URL url = ClassLoaderUtil.getResourceUrl(resourceName);

		if (url == null) {
			throw new JoyException("Failed to resolve app dir, missing: " + resourceName);
		}
		final String protocol = url.getProtocol();

		if (!protocol.equals("file")) {
			try {
				url = new URL(url.getFile());
			} catch (MalformedURLException ignore) {
			}
		}

		appDir = url.getFile();

		final int ndx = appDir.indexOf("WEB-INF");

		appDir = (ndx > 0) ? appDir.substring(0, ndx) : SystemUtil.info().getWorkingDir();

		System.setProperty(APP_DIR, appDir);

		log.info("Application folder: " + appDir);
	}

	@Override
	public void stop() {
	}
}


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

import jodd.log.Logger;
import jodd.log.LoggerProvider;

/**
 * JDK logger.
 */
public class JDKLogger implements Logger {

	public static final LoggerProvider<JDKLogger> PROVIDER =
		name -> new JDKLogger(java.util.logging.Logger.getLogger(name));

	final java.util.logging.Logger logger;

	public JDKLogger(final java.util.logging.Logger logger) {
		this.logger = logger;
	}

	/**
	 * Converts Jodd logging level to JDK.
	 */
	private java.util.logging.Level jodd2jdk(final Level level) {
		switch (level) {
			case TRACE: return java.util.logging.Level.FINER;
			case DEBUG: return java.util.logging.Level.FINE;
			case INFO:	return java.util.logging.Level.INFO;
			case WARN:	return java.util.logging.Level.WARNING;
			case ERROR:	return java.util.logging.Level.SEVERE;
			default:
				throw new IllegalArgumentException();
		}
	}

	@Override
	public String getName() {
		return logger.getName();
	}

	@Override
	public boolean isEnabled(final Level level) {
		return logger.isLoggable(jodd2jdk(level));
	}

	@Override
	public void log(final Level level, final String message) {
		logger.log(jodd2jdk(level), message);
	}

	@Override
	public void log(final Level level, final String message, final Throwable throwable) {
		logger.log(jodd2jdk(level), message, throwable);
	}

	@Override
	public void setLevel(final Level level) {
		logger.setLevel(jodd2jdk(level));
	}

	@Override
	public boolean isTraceEnabled() {
		return logger.isLoggable(java.util.logging.Level.FINER);
	}

	@Override
	public void trace(final String message) {
		logger.log(java.util.logging.Level.FINER, message);
	}

	@Override
	public boolean isDebugEnabled() {
		return logger.isLoggable(java.util.logging.Level.FINE);
	}

	@Override
	public void debug(final String message) {
		logger.log(java.util.logging.Level.FINE, message);
	}

	@Override
	public boolean isInfoEnabled() {
		return logger.isLoggable(java.util.logging.Level.INFO);
	}

	@Override
	public void info(final String message) {
		logger.log(java.util.logging.Level.INFO, message);
	}

	@Override
	public boolean isWarnEnabled() {
		return logger.isLoggable(java.util.logging.Level.WARNING);
	}

	@Override
	public void warn(final String message) {
		logger.log(java.util.logging.Level.WARNING, message);
	}

	@Override
	public void warn(final String message, final Throwable throwable) {
		logger.log(java.util.logging.Level.WARNING, message, throwable);
	}

	@Override
	public boolean isErrorEnabled() {
		return logger.isLoggable(java.util.logging.Level.SEVERE);
	}

	@Override
	public void error(final String message) {
		logger.log(java.util.logging.Level.SEVERE, message);
	}

	@Override
	public void error(final String message, final Throwable throwable) {
		logger.log(java.util.logging.Level.SEVERE, message, throwable);
	}

}


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

package jodd.db.type;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class DoubleSqlType extends NullAwareSqlType<Double> {

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Double get(final ResultSet rs, final int index, final int dbSqlType) throws SQLException {
		return Double.valueOf(rs.getDouble(index));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void set(final PreparedStatement st, final int index, final Double value, final int dbSqlType) throws SQLException {
		st.setDouble(index, value.doubleValue());
	}
}


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

import jodd.asm7.ClassReader;
import jodd.io.StreamUtil;
import jodd.util.ClassLoaderUtil;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
 * Extracts method or constructor parameter names from bytecode debug information in runtime.
 */
public class Paramo {

	protected static final String CTOR_METHOD = "<init>";

	/**
	 * Resolves method parameters from a method or constructor.
	 * Returns an empty array when target does not contain any parameter.
	 * No caching is involved in this process, i.e. class bytecode
	 * is examined every time this method is called.
	 */
	public static MethodParameter[] resolveParameters(final AccessibleObject methodOrCtor) {
		Class[] paramTypes;
		Class declaringClass;
		String name;

		if (methodOrCtor instanceof Method) {
			Method method = (Method) methodOrCtor;
			paramTypes = method.getParameterTypes();
			name = method.getName();
			declaringClass = method.getDeclaringClass();
		} else {
			Constructor constructor = (Constructor) methodOrCtor;
			paramTypes = constructor.getParameterTypes();
			declaringClass = constructor.getDeclaringClass();
			name = CTOR_METHOD;
		}

		if (paramTypes.length == 0) {
			return MethodParameter.EMPTY_ARRAY;
		}

		InputStream stream;
		try {
			stream = ClassLoaderUtil.getClassAsStream(declaringClass);
		} catch (IOException ioex) {
			throw new ParamoException("Failed to read class bytes: " + declaringClass.getName(), ioex);
		}

		if (stream == null) {
			throw new ParamoException("Class not found: " + declaringClass);
		}

		try {
			ClassReader reader = new ClassReader(stream);
			MethodFinder visitor = new MethodFinder(declaringClass, name, paramTypes);
			reader.accept(visitor, 0);
			return visitor.getResolvedParameters();
		}
		catch (IOException ioex) {
			throw new ParamoException(ioex);
		}
		finally {
			StreamUtil.close(stream);
		}
	}

}


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

package jodd.lagarto.form;

import jodd.bean.BeanUtil;
import jodd.lagarto.LagartoParser;
import jodd.servlet.ServletUtil;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.BodyTagSupport;
import java.io.IOException;

/**
 * Magic form tag populates HTML form with values.
 */
public class FormTag extends BodyTagSupport {

	/**
	 * Starts the tag.
	 */
	@Override
	public int doStartTag() {
		return EVAL_BODY_AGAIN;
	}

	/**
	 * Performs smart form population.
	 */
	@Override
	public int doAfterBody() throws JspException {
		BodyContent body = getBodyContent();
		JspWriter out = body.getEnclosingWriter();

		String bodytext = populateForm(body.getString(), name -> value(name, pageContext));

		try {
			out.print(bodytext);
		} catch (IOException ioex) {
			throw new JspException(ioex);
		}
		return SKIP_BODY;
	}

	protected Object value(final String name, final PageContext pageContext) {
		String thisRef = BeanUtil.pojo.extractThisReference(name);
		Object value = ServletUtil.value(pageContext, thisRef);
		if (value == null) {
			return ServletUtil.value(pageContext, name);
		}

		if (thisRef.equals(name)) {
			return value;
		}

		String propertyName = name.substring(thisRef.length() + 1);

		return BeanUtil.declaredSilent.getProperty(value, propertyName);
	}

	/**
	 * Ends the tag.
	 */
	@Override
	public int doEndTag() {
		return EVAL_PAGE;
	}

	protected String populateForm(final String formHtml, final FormFieldResolver resolver) {
		LagartoParser lagartoParser = new LagartoParser(formHtml);
		StringBuilder result = new StringBuilder();

		lagartoParser.parse(new FormProcessorVisitor(result, resolver));

		return result.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.props;

/**
 * Holds props value.
 */
public class PropsEntry {

	/**
	 * Original value.
	 */
	protected final String value;

	protected PropsEntry next;

	protected final String key;

	protected final String profile;

	protected final boolean hasMacro;

	protected final PropsData propsData;

	public PropsEntry(final String key, final String value, final String profile, final PropsData propsData) {
		this.value = value;
		this.key = key;
		this.profile = profile;
		this.hasMacro = value.contains("${");
		this.propsData = propsData;
	}

	/**
	 * Returns the raw value. Macros are not replaced.
	 */
	public String getValue() {
		return value;
	}

	/**
	 * Returns the property value, with replaced macros.
	 */
	public String getValue(final String... profiles) {
		if (hasMacro) {
			return propsData.resolveMacros(value, profiles);
		}
		return value;
	}

	/**
	 * Returns property key.
	 */
	public String getKey() {
		return key;
	}

	/**
	 * Returns property profile or null if this is a base property.
	 */
	public String getProfile() {
		return profile;
	}

	/**
	 * Returns true if value has a macro to resolve.
	 */
	public boolean hasMacro() {
		return hasMacro;
	}

	@Override
	public String toString() {
		return "PropsEntry{" + key + (profile != null ? '<' + profile + '>' : "") + '=' + value + '}';
	}

}


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

package jodd.lagarto.dom;

import jodd.lagarto.Doctype;
import jodd.lagarto.Tag;
import jodd.lagarto.TagType;
import jodd.lagarto.TagVisitor;
import jodd.log.Logger;
import jodd.log.LoggerFactory;
import jodd.util.CharSequenceUtil;
import jodd.util.StringPool;
import jodd.util.Util;

/**
 * Lagarto tag visitor that builds DOM tree.
 * It (still) does not build the tree fully by the HTML specs,
 * however, it works good enough for any sane HTML out there.
 * In the default mode, the tree builder does not change
 * the order of the elements, so the returned tree reflects
 * the input. So if the input contains crazy stuff, the tree will
 * be weird, too :)
 *

 * In experimental html-plus mode we do have some
 * further HTML5 rules implemented, that according to some rules
 * may change the node position. However, not all rules are
 * implemented (yet) and this is still just experimental.
 */
public class LagartoDOMBuilderTagVisitor implements TagVisitor {

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

	protected final LagartoDOMBuilder domBuilder;
	protected final HtmlImplicitClosingRules implRules = new HtmlImplicitClosingRules();
	protected HtmlVoidRules htmlVoidRules;

	protected Document rootNode;
	protected Node parentNode;

	/**
	 * While enabled, nodes will be added to the DOM tree.
	 * Useful for skipping some tags.
	 */
	protected boolean enabled;

	public LagartoDOMBuilderTagVisitor(final LagartoDOMBuilder domBuilder) {
		this.domBuilder = domBuilder;
	}

	/**
	 * Returns root {@link Document document} node of parsed DOM tree.
	 */
	public Document getDocument() {
		return rootNode;
	}

	// ---------------------------------------------------------------- start/end

	/**
	 * Starts with DOM building.
	 * Creates root {@link jodd.lagarto.dom.Document} node.
	 */
	@Override
	public void start() {
		log.debug("DomTree builder started");

		if (rootNode == null) {
			rootNode = new Document(domBuilder.config);
		}
		parentNode = rootNode;
		enabled = true;

		if (domBuilder.config.isEnabledVoidTags()) {
			htmlVoidRules = new HtmlVoidRules();
		}
	}

	/**
	 * Finishes the tree building. Closes unclosed tags.
	 */
	@Override
	public void end() {
		if (parentNode != rootNode) {

			Node thisNode = parentNode;

			while (thisNode != rootNode) {
				if (domBuilder.config.isImpliedEndTags()) {
					if (implRules.implicitlyCloseTagOnEOF(thisNode.getNodeName())) {
						thisNode = thisNode.getParentNode();
						continue;
					}
				}

				error("Unclosed tag closed: <" + thisNode.getNodeName() + ">");

				thisNode = thisNode.getParentNode();
			}
		}

		// remove whitespaces
		if (domBuilder.config.isIgnoreWhitespacesBetweenTags()) {
			removeLastChildNodeIfEmptyText(parentNode, true);
		}

		// foster
		if (domBuilder.config.isUseFosterRules()) {
			HtmlFosterRules fosterRules = new HtmlFosterRules();
			fosterRules.fixFosterElements(rootNode);
		}

		// elapsed
		rootNode.end();

		if (log.isDebugEnabled()) {
			log.debug("LagartoDom tree created in " + rootNode.getElapsedTime() + " ms");
		}
	}

	// ---------------------------------------------------------------- tag

	/**
	 * Creates new element with correct configuration.
	 */
	protected Element createElementNode(final Tag tag) {
		boolean hasVoidTags = htmlVoidRules != null;

		boolean isVoid = false;
		boolean selfClosed = false;

		if (hasVoidTags) {
			isVoid = htmlVoidRules.isVoidTag(tag.getName());

			// HTML and XHTML
			if (isVoid) {
				// it's void tag, lookup the flag
				selfClosed = domBuilder.config.isSelfCloseVoidTags();
			}
		} else {
			// XML, no voids, lookup the flag
			selfClosed = domBuilder.config.isSelfCloseVoidTags();
		}

		return new Element(rootNode, tag, isVoid, selfClosed);
	}

	/**
	 * Visits tags.
	 */
	@Override
	public void tag(final Tag tag) {
		if (!enabled) {
			return;
		}

		TagType tagType = tag.getType();
		Element node;

		switch (tagType) {
			case START:
				if (domBuilder.config.isIgnoreWhitespacesBetweenTags()) {
					removeLastChildNodeIfEmptyText(parentNode, false);
				}

				node = createElementNode(tag);

				if (domBuilder.config.isImpliedEndTags()) {
					while (true) {
						String parentNodeName = parentNode.getNodeName();
						if (!implRules.implicitlyCloseParentTagOnNewTag(parentNodeName, node.getNodeName())) {
							break;
						}
						parentNode = parentNode.getParentNode();

						if (log.isDebugEnabled()) {
							log.debug("Implicitly closed tag <" + node.getNodeName() + "> ");
						}
					}
				}

				parentNode.addChild(node);

				if (!node.isVoidElement()) {
					parentNode = node;
				}
				break;

			case END:
				if (domBuilder.config.isIgnoreWhitespacesBetweenTags()) {
					removeLastChildNodeIfEmptyText(parentNode, true);
				}

				String tagName = tag.getName().toString();

				Node matchingParent = findMatchingParentOpenTag(tagName);

				if (matchingParent == parentNode) {		// regular situation
					parentNode = parentNode.getParentNode();
					break;
				}

				if (matchingParent == null) {			// matching open tag not found, remove it
					error("Orphan closed tag ignored: </" + tagName + "> " + tag.getTagPosition());
					break;
				}

				// try to close it implicitly
				if (domBuilder.config.isImpliedEndTags()) {
					boolean fixed = false;

					while (implRules.implicitlyCloseParentTagOnTagEnd(parentNode.getNodeName(), tagName)) {
						parentNode = parentNode.getParentNode();

						if (log.isDebugEnabled()) {
							log.debug("Implicitly closed tag <" + tagName + ">");
						}

						if (parentNode == matchingParent) {
							parentNode = matchingParent.parentNode;
							fixed = true;
							break;
						}
					}
					if (fixed) {
						break;
					}
				}

				// matching tag found, but it is not a regular situation
				// therefore close all unclosed tags in between
				fixUnclosedTagsUpToMatchingParent(tag, matchingParent);

				break;

			case SELF_CLOSING:
				if (domBuilder.config.isIgnoreWhitespacesBetweenTags()) {
					removeLastChildNodeIfEmptyText(parentNode, false);
				}

				node = createElementNode(tag);
				parentNode.addChild(node);
				break;
		}
	}

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

	/**
	 * Removes last child node if contains just empty text.
	 */
	protected void removeLastChildNodeIfEmptyText(final Node parentNode, final boolean closedTag) {
		if (parentNode == null) {
			return;
		}

		Node lastChild = parentNode.getLastChild();
		if (lastChild == null) {
			return;
		}

		if (lastChild.getNodeType() != Node.NodeType.TEXT) {
			return;
		}

		if (closedTag) {
			if (parentNode.getChildNodesCount() == 1) {
				return;
			}
		}

		Text text = (Text) lastChild;

		if (text.isBlank()) {
			lastChild.detachFromParent();
		}
	}

	/**
	 * Finds matching parent open tag or null if not found.
	 */
	protected Node findMatchingParentOpenTag(String tagName) {
		Node parent = parentNode;

		if (!rootNode.config.isCaseSensitive()) {
			tagName = tagName.toLowerCase();
		}

		while (parent != null) {
			String parentNodeName = parent.getNodeName();

			if (parentNodeName != null) {
				if (!rootNode.config.isCaseSensitive()) {
					parentNodeName = parentNodeName.toLowerCase();
				}
			}

			if (tagName.equals(parentNodeName)) {
				return parent;
			}
			parent = parent.getParentNode();
		}
		return null;
	}

	/**
	 * Fixes all unclosed tags up to matching parent. Missing end tags will be added
	 * just before parent tag is closed, making the whole inner content as its tag body.
	 *

	 * Tags that can be closed implicitly are checked and closed.
	 *

	 * There is optional check for detecting orphan tags inside the
	 * table or lists. If set, tags can be closed beyond the border of the
	 * table and the list and it is reported as orphan tag.
	 *

	 * This is just a generic solutions, closest to the rules.
	 */
	protected void fixUnclosedTagsUpToMatchingParent(final Tag tag, final Node matchingParent) {
		if (domBuilder.config.isUnclosedTagAsOrphanCheck()) {
			Node thisNode = parentNode;

			if (!CharSequenceUtil.equalsIgnoreCase(tag.getName(), "table")) {

				// check if there is table or list between this node
				// and matching parent
				while (thisNode != matchingParent) {
					String thisNodeName = thisNode.getNodeName().toLowerCase();

					if (thisNodeName.equals("table") || thisNodeName.equals("ul") || thisNodeName.equals("ol")) {

						String positionString = tag.getPosition();
						if (positionString == null) {
							positionString = StringPool.EMPTY;
						}

						error("Orphan closed tag ignored: </" + tag.getName() + "> " + positionString);
						return;
					}
					thisNode = thisNode.getParentNode();
				}
			}
		}

		while (true) {
			if (parentNode == matchingParent) {
				parentNode = parentNode.getParentNode();
				break;
			}

			Node parentParentNode = parentNode.getParentNode();

			if (domBuilder.config.isImpliedEndTags()) {
				if (implRules.implicitlyCloseParentTagOnNewTag(
						parentParentNode.getNodeName(), parentNode.getNodeName())) {
					// break the tree: detach this node and append it after parent

					parentNode.detachFromParent();

					parentParentNode.getParentNode().addChild(parentNode);
				}
			}

			// debug message

			error("Unclosed tag closed: <" + parentNode.getNodeName() + ">");

			// continue looping
			parentNode = parentParentNode;
		}
	}

	// ---------------------------------------------------------------- tree

	@Override
	public void script(final Tag tag, final CharSequence body) {
		if (!enabled) {
			return;
		}

		Element node = createElementNode(tag);

		parentNode.addChild(node);

		if (body.length() != 0) {
			Node text = new Text(rootNode, body.toString());
			node.addChild(text);
		}
	}

	@Override
	public void comment(final CharSequence comment) {
		if (!enabled) {
			return;
		}

		if (domBuilder.config.isIgnoreWhitespacesBetweenTags()) {
			removeLastChildNodeIfEmptyText(parentNode, false);
		}

		if (domBuilder.config.isIgnoreComments()) {
			return;
		}

		Node node = new Comment(rootNode, comment.toString());

		parentNode.addChild(node);
	}

	@Override
	public void text(final CharSequence text) {
		if (!enabled) {
			return;
		}

		String textValue = text.toString();

		Node node = new Text(rootNode, textValue);

		parentNode.addChild(node);
	}

	@Override
	public void cdata(final CharSequence cdata) {
		if (!enabled) {
			return;
		}

		CData cdataNode = new CData(rootNode, cdata.toString());

		parentNode.addChild(cdataNode);
	}

	@Override
	public void xml(final CharSequence version, final CharSequence encoding, final CharSequence standalone) {
		if (!enabled) {
			return;
		}

		XmlDeclaration xmlDeclaration = new XmlDeclaration(rootNode, version, encoding, standalone);

		parentNode.addChild(xmlDeclaration);
	}

	@Override
	public void doctype(final Doctype doctype) {
		if (!enabled) {
			return;
		}

		DocumentType documentType = new DocumentType(rootNode,
				Util.toString(doctype.getName()),
				Util.toString(doctype.getPublicIdentifier()),
				Util.toString(doctype.getSystemIdentifier())
		);

		parentNode.addChild(documentType);
	}

	@Override
	public void condComment(final CharSequence expression, final boolean isStartingTag, final boolean isHidden, final boolean isHiddenEndTag) {
		String expressionString = expression.toString().trim();

		if (expressionString.equals("endif")) {
			enabled = true;
			return;
		}

		if (expressionString.equals("if !IE")) {
			enabled = false;
			return;
		}

		float ieVersion = domBuilder.config.getCondCommentIEVersion();

		if (htmlCCommentExpressionMatcher == null) {
			htmlCCommentExpressionMatcher = new HtmlCCommentExpressionMatcher();
		}

		enabled = htmlCCommentExpressionMatcher.match(ieVersion, expressionString);
	}

	protected HtmlCCommentExpressionMatcher htmlCCommentExpressionMatcher;

	// ---------------------------------------------------------------- error

	@Override
	public void error(final String message) {
		rootNode.addError(message);
		log.log(domBuilder.config.getParsingErrorLogLevel(), message);
	}

}


// 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.MethodAdapter;
import jodd.asm7.Label;
import jodd.asm7.MethodVisitor;
import jodd.proxetta.ProxettaException;

import static jodd.asm7.Opcodes.BIPUSH;
import static jodd.asm7.Opcodes.ICONST_0;
import static jodd.asm7.Opcodes.ICONST_1;
import static jodd.asm7.Opcodes.ICONST_2;
import static jodd.asm7.Opcodes.ICONST_3;
import static jodd.asm7.Opcodes.ICONST_4;
import static jodd.asm7.Opcodes.ICONST_5;
import static jodd.asm7.Opcodes.SIPUSH;

/**
 * Method adapter that tracks history of previous instructions.
 *

 *
that remembers previous opcode of 'insn' and 'intInsn' instructions.
  * Used to detect single (last) int argument value of a method call.

 *
Stores last two arguments as strings

 *

 */
abstract class HistoryMethodAdapter extends MethodAdapter {

	protected HistoryMethodAdapter(final MethodVisitor methodVisitor) {
		super(methodVisitor);
	}

	// ---------------------------------------------------------------- history

	protected int opcode;
	protected int operand;
	protected boolean isPrevious;       // true only if previous opcode is of the correct type
	protected boolean traceNext;        // true only to trace very next opcode
	protected String[] strArgs = new String[2];

	// ---------------------------------------------------------------- get index

	/**
	 * Returns argument index from the history.
	 * Must POP value from the stack after the execution.
	 */
	protected int getArgumentIndex() {
		if (!isPrevious) {
			throw new ProxettaException("Unexpected previous instruction type used for setting argument index");
		}
		int argIndex;
		switch (opcode) {
			case ICONST_0: argIndex = 0; break;
			case ICONST_1: argIndex = 1; break;
			case ICONST_2: argIndex = 2; break;
			case ICONST_3: argIndex = 3; break;
			case ICONST_4: argIndex = 4; break;
			case ICONST_5: argIndex = 5; break;
			case BIPUSH:
			case SIPUSH:
				argIndex = operand; break;
			default:
				throw new ProxettaException("Unexpected previous instruction used for setting argument index");
		}
		return argIndex;
	}

	/**
	 * Returns last two string arguments.
	 */
	public String[] getLastTwoStringArguments() {
		return strArgs;
	}

	/**
	 * Adds last LDC arguments to {@link #getLastTwoStringArguments() string arguments}.
	 */
	private void keepStringArgument(final Object value) {
		strArgs[0] = strArgs[1];
		strArgs[1] = value.toString();
	}

	// ---------------------------------------------------------------- visitors

	@Override
	public void visitInsn(final int opcode) {
		this.opcode = opcode;
		isPrevious = true;
		traceNext = false;
		super.visitInsn(opcode);
	}

	@Override
	public void visitIntInsn(final int opcode, final int operand) {
		this.opcode = opcode;
		this.operand = operand;
		isPrevious = true;
		traceNext = false;
		super.visitIntInsn(opcode, operand);
	}

	@Override
	public void visitVarInsn(final int opcode, final int var) {
		isPrevious = false;
		traceNext = false;
		super.visitVarInsn(opcode, var);
	}

	@Override
	public void visitTypeInsn(final int opcode, final String type) {
		isPrevious = false;
		traceNext = false;
		super.visitTypeInsn(opcode, type);
	}

	@Override
	public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) {
		isPrevious = false;
		traceNext = false;
		super.visitFieldInsn(opcode, owner, name, desc);
	}

	@Override
	public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc, final boolean isInterface) {
		isPrevious = false;
		traceNext = false;
		super.visitMethodInsn(opcode, owner, name, desc, isInterface);
	}

	@Override
	public void visitJumpInsn(final int opcode, final Label label) {
		isPrevious = false;
		traceNext = false;
		super.visitJumpInsn(opcode, label);
	}

	@Override
	public void visitLdcInsn(final Object cst) {
		isPrevious = false;
		traceNext = false;

		keepStringArgument(cst);

		super.visitLdcInsn(cst);
	}

	@Override
	public void visitIincInsn(final int var, final int increment) {
		isPrevious = false;
		traceNext = false;
		super.visitIincInsn(var, increment);
	}

	@Override
	public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label[] labels) {
		isPrevious = false;
		traceNext = false;
		super.visitTableSwitchInsn(min, max, dflt, labels);
	}

	@Override
	public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
		isPrevious = false;
		traceNext = false;
		super.visitLookupSwitchInsn(dflt, keys, labels);
	}

	@Override
	public void visitMultiANewArrayInsn(final String desc, final int dims) {
		isPrevious = false;
		traceNext = false;
		super.visitMultiANewArrayInsn(desc, dims);
	}

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

	@Override
	public void visitLineNumber(final int line, final Label start) {
		//super.visitLineNumber(line, start);
	}

}


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

import jodd.asm.AsmUtil;
import jodd.asm7.MethodVisitor;
import jodd.asm7.Opcodes;
import jodd.asm7.Type;
import jodd.proxetta.asm.ProxettaAsmUtil;
import jodd.util.ClassLoaderUtil;

import java.lang.reflect.Array;
import java.lang.reflect.Method;

import static jodd.asm7.Opcodes.AASTORE;
import static jodd.asm7.Opcodes.ANEWARRAY;
import static jodd.asm7.Opcodes.DUP;
import static jodd.asm7.Opcodes.POP;
import static jodd.proxetta.asm.ProxettaAsmUtil.checkArgumentIndex;
import static jodd.proxetta.asm.ProxettaAsmUtil.loadMethodArgumentAsObject;
import static jodd.proxetta.asm.ProxettaAsmUtil.loadMethodArgumentClass;
import static jodd.proxetta.asm.ProxettaAsmUtil.pushInt;

/**
 * Replacements methods for {@link jodd.proxetta.ProxyTarget} methods.
 */
public class ProxyTargetReplacement {

	public static final String PROXY_TARGET_INFO = "jodd/proxetta/ProxyTargetInfo";

	/**
	 * Visits replacement code for {@link ProxyTarget#argumentsCount()}.
	 */
	public static void argumentsCount(final MethodVisitor mv, final MethodInfo methodInfo) {
		int argsCount = methodInfo.getArgumentsCount();
		pushInt(mv, argsCount);
	}

	/**
	 * Visits replacement code for {@link ProxyTarget#argumentType(int)}.
	 */
	public static void argumentType(final MethodVisitor mv, final MethodInfo methodInfo, final int argIndex) {
		checkArgumentIndex(methodInfo, argIndex);
		mv.visitInsn(POP);
		loadMethodArgumentClass(mv, methodInfo, argIndex);
	}

	/**
	 * Visits replacement code for {@link ProxyTarget#argument(int)}.
	 */
	public static void argument(final MethodVisitor mv, final MethodInfo methodInfo, final int argIndex) {
		checkArgumentIndex(methodInfo, argIndex);
		mv.visitInsn(POP);
		loadMethodArgumentAsObject(mv, methodInfo, argIndex);
	}

	/**
	 * Visits replacement code for {@link ProxyTarget#createArgumentsArray()}.
	 */
	public static void createArgumentsArray(final MethodVisitor mv, final MethodInfo methodInfo) {
		int argsCount = methodInfo.getArgumentsCount();
		pushInt(mv, argsCount);
		mv.visitTypeInsn(ANEWARRAY, AsmUtil.SIGNATURE_JAVA_LANG_OBJECT);
		for (int i = 0; i < argsCount; i++) {
			mv.visitInsn(DUP);
			pushInt(mv, i);
			loadMethodArgumentAsObject(mv, methodInfo, i + 1);
			mv.visitInsn(AASTORE);
		}
	}

	/**
	 * Visits replacement code for {@link ProxyTarget#createArgumentsClassArray()}.
	 */
	public static void createArgumentsClassArray(final MethodVisitor mv, final MethodInfo methodInfo) {
		int argsCount = methodInfo.getArgumentsCount();
		pushInt(mv, argsCount);
		mv.visitTypeInsn(ANEWARRAY, AsmUtil.SIGNATURE_JAVA_LANG_CLASS);
		for (int i = 0; i < argsCount; i++) {
			mv.visitInsn(DUP);
			pushInt(mv, i);
			loadMethodArgumentClass(mv, methodInfo, i + 1);
			mv.visitInsn(AASTORE);
		}
	}

	/**
	 * Visits replacement code for {@link ProxyTarget#returnType()}.
	 */
	public static void returnType(final MethodVisitor mv, final MethodInfo methodInfo) {
		ProxettaAsmUtil.loadClass(mv, methodInfo.getReturnType().getOpcode(), methodInfo.getReturnType().getName());
	}

	/**
	 * Visits replacement code for {@link ProxyTarget#targetMethodName()}.
	 */
	public static void targetMethodName(final MethodVisitor mv, final MethodInfo methodInfo) {
		mv.visitLdcInsn(methodInfo.getMethodName());
	}

	/**
	 * Visits replacement code for {@link ProxyTarget#targetMethodSignature()}.
	 */
	public static void targetMethodSignature(final MethodVisitor mv, final MethodInfo methodInfo) {
		mv.visitLdcInsn(methodInfo.getSignature());
	}

	/**
	 * Visits replacement code for {@link ProxyTarget#targetMethodDescription()}.
	 */
	public static void targetMethodDescription(final MethodVisitor mv, final MethodInfo methodInfo) {
		mv.visitLdcInsn(methodInfo.getDescription());
	}

	/**
	 * Visits replacement code for {@link ProxyTarget#targetClass()}.
	 */
	public static void targetClass(final MethodVisitor mv, final MethodInfo methodInfo) {
		ClassInfo classInfo = methodInfo.getClassInfo();
		mv.visitLdcInsn(Type.getType('L' + classInfo.getReference() + ';'));
	}

	/**
	 * Visits replacement code for {@link ProxyTarget#info()}.
	 */
	public static void info(final MethodVisitor mv, final MethodInfo methodInfo, final int argsOff) {
		mv.visitTypeInsn(Opcodes.NEW, PROXY_TARGET_INFO);
		mv.visitInsn(DUP);
		mv.visitMethodInsn(Opcodes.INVOKESPECIAL, PROXY_TARGET_INFO, "<init>", "()V", false);

//		int argsOff = methodInfo.getAllArgumentsSize();
//		argsOff++;

		mv.visitVarInsn(Opcodes.ASTORE, argsOff);

		// argument count
		mv.visitVarInsn(Opcodes.ALOAD, argsOff);
		argumentsCount(mv, methodInfo);
		mv.visitFieldInsn(Opcodes.PUTFIELD, PROXY_TARGET_INFO, "argumentCount", "I");

		// arguments class
		mv.visitVarInsn(Opcodes.ALOAD, argsOff);
		createArgumentsClassArray(mv, methodInfo);
		mv.visitFieldInsn(Opcodes.PUTFIELD, PROXY_TARGET_INFO, "argumentsClasses", "[Ljava/lang/Class;");

		// arguments
		mv.visitVarInsn(Opcodes.ALOAD, argsOff);
		createArgumentsArray(mv, methodInfo);
		mv.visitFieldInsn(Opcodes.PUTFIELD, PROXY_TARGET_INFO, "arguments", "[Ljava/lang/Object;");

		// return type
		mv.visitVarInsn(Opcodes.ALOAD, argsOff);
		returnType(mv, methodInfo);
		mv.visitFieldInsn(Opcodes.PUTFIELD, PROXY_TARGET_INFO, "returnType", AsmUtil.L_SIGNATURE_JAVA_LANG_CLASS);

		// target method name
		mv.visitVarInsn(Opcodes.ALOAD, argsOff);
		targetMethodName(mv, methodInfo);
		mv.visitFieldInsn(Opcodes.PUTFIELD, PROXY_TARGET_INFO, "targetMethodName", AsmUtil.L_SIGNATURE_JAVA_LANG_STRING);

		// target method name
		mv.visitVarInsn(Opcodes.ALOAD, argsOff);
		targetMethodDescription(mv, methodInfo);
		mv.visitFieldInsn(Opcodes.PUTFIELD, PROXY_TARGET_INFO, "targetMethodDescription", AsmUtil.L_SIGNATURE_JAVA_LANG_STRING);

		// target method name
		mv.visitVarInsn(Opcodes.ALOAD, argsOff);
		targetMethodSignature(mv, methodInfo);
		mv.visitFieldInsn(Opcodes.PUTFIELD, PROXY_TARGET_INFO, "targetMethodSignature", AsmUtil.L_SIGNATURE_JAVA_LANG_STRING);

		// target class
		mv.visitVarInsn(Opcodes.ALOAD, argsOff);
		targetClass(mv, methodInfo);
		mv.visitFieldInsn(Opcodes.PUTFIELD, PROXY_TARGET_INFO, "targetClass", AsmUtil.L_SIGNATURE_JAVA_LANG_CLASS);

		// the end
		mv.visitVarInsn(Opcodes.ALOAD, argsOff);
	}

	/**
	 * Visits replacement code for {@link ProxyTarget#targetMethodAnnotation(String, String)}.
	 */
	public static void targetMethodAnnotation(final MethodVisitor mv, final MethodInfo methodInfo, final String[] args) {
		AnnotationInfo[] anns = methodInfo.getAnnotations();

		if (anns != null) {
			targetAnnotation(mv, anns, args);
		}
	}

	/**
	 * Visits replacement code for {@link ProxyTarget#targetClassAnnotation(String, String)}.
	 */
	public static void targetClassAnnotation(final MethodVisitor mv, final ClassInfo classInfo, final String[] args) {
		AnnotationInfo[] anns = classInfo.getAnnotations();

		if (anns != null) {
			targetAnnotation(mv, anns, args);
		} else {
			mv.visitInsn(Opcodes.ACONST_NULL);
		}
	}

	private static void targetAnnotation(final MethodVisitor mv, final AnnotationInfo[] anns, final String[] args) {
		for (AnnotationInfo ann : anns) {
			String annotationSignature = ann.getAnnotationSignature();
			Method annotationMethod = null;

			if (annotationSignature.equals(args[0])) {
				String elementName = args[1];
				Object elementValue = ann.getElement(elementName);

				if (elementValue == null) {
					// read default annotation
					String annotationClass = ann.getAnnotationClassname();

					try {
						Class annotation = ClassLoaderUtil.loadClass(annotationClass);

						annotationMethod = annotation.getMethod(elementName);

						elementValue = annotationMethod.getDefaultValue();
					}
					catch (Exception ignore) {
						elementValue = null;
					}

					if (elementValue == null) {
						mv.visitInsn(Opcodes.ACONST_NULL);
						return;
					}
				}

				Class elementValueClass = elementValue.getClass();

				if (!elementValueClass.isArray()) {
					// non-arrays
					ProxettaAsmUtil.visitElementValue(mv, elementValue, true);
					return;
				}
				else {
					// arrays
					Class componentType = elementValueClass.getComponentType();

					String annotationClass = ann.getAnnotationClassname();

					try {
						if (annotationMethod == null) {
							Class annotation = ClassLoaderUtil.loadClass(annotationClass);

							annotationMethod = annotation.getMethod(elementName);
						}

						componentType = annotationMethod.getReturnType().getComponentType();
					}
					catch (Exception ignore) {
					}

					int size = Array.getLength(elementValue);

					ProxettaAsmUtil.pushInt(mv, size);

					ProxettaAsmUtil.newArray(mv, componentType);

					for (int i = 0; i < size; i++) {
						mv.visitInsn(DUP);

						ProxettaAsmUtil.pushInt(mv, i);

						Object value = Array.get(elementValue, i);
						ProxettaAsmUtil.visitElementValue(mv, value, false);

						ProxettaAsmUtil.storeIntoArray(mv, componentType);
					}

					return;
				}
			}
		}
	}
}


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

package jodd.db.debug;

import jodd.typeconverter.Converter;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.StringTokenizer;

public abstract class BaseLoggableStatement<S extends Statement> implements Statement {

	protected final S wrappedStatement;
	protected final String sqlTemplate;

	public BaseLoggableStatement(final S wrappedStatement, final String sqlTemplate) {
		this.wrappedStatement = wrappedStatement;
		this.sqlTemplate = sqlTemplate;
	}

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

	protected ArrayList<String> parameterValues;

	/**
	 * Returns the query string.
	 */
	public String getQueryString() {
		if (sqlTemplate == null) {
			return toString();
		}
		if (parameterValues == null) {
			return sqlTemplate;
		}

		final StringBuilder sb = new StringBuilder();

		int qMarkCount = 0;
		final StringTokenizer tok = new StringTokenizer(sqlTemplate + ' ', "?");

		while (tok.hasMoreTokens()) {
			final String oneChunk = tok.nextToken();
			sb.append(oneChunk);
			try {
				Object value = null;
				if (parameterValues.size() > 1 + qMarkCount) {
					value = parameterValues.get(1 + qMarkCount);
					qMarkCount++;
				} else {
					if (!tok.hasMoreTokens()) {
						value = "";
					}
				}
				if (value == null) {
					value = "?";
				}
				sb.append(value);
			} catch (Throwable th) {
				sb.append("--- Building query failed: ").append(th.toString());
			}
		}
		return sb.toString().trim();
	}

	/**
	 * Saves the parameter value obj for the specified
	 * position for use in logging output.
	 *
	 * @param position position (starting at 1) of the parameter to save
	 * @param obj      java.lang.Object the parameter value to save
	 */
	protected void saveQueryParamValue(final int position, final Object obj) {
		final String strValue;
		if (obj instanceof String || obj instanceof Date) {
			strValue = "'" + obj + '\''; // if we have a String or Date, include '' in the saved value
		} else if (obj instanceof LocalDateTime || obj instanceof LocalDate || obj instanceof LocalTime) {
			strValue = "'" + Converter.get().toString(obj) + '\''; // time as string with '
		} else if (obj == null) {
			strValue = "<null>"; // convert null to the string null
		} else {
			strValue = Converter.get().toString(obj); // all other objects (includes all Numbers, arrays, etc)
		}

		// if we are setting a position larger than current size of parameterValues,
		// first make it larger
		if (parameterValues == null) {
			parameterValues = new ArrayList<>();
		}

		while (position >= parameterValues.size()) {
			parameterValues.add(null);
		}
		parameterValues.set(position, strValue);
	}

	// ---------------------------------------------------------------- statement

	@Override
	public ResultSet executeQuery(String sql) throws SQLException {
		return wrappedStatement.executeQuery(sql);
	}

	@Override
	public int executeUpdate(String sql) throws SQLException {
		return wrappedStatement.executeUpdate(sql);
	}

	@Override
	public void close() throws SQLException {
		wrappedStatement.close();
	}

	@Override
	public int getMaxFieldSize() throws SQLException {
		return wrappedStatement.getMaxFieldSize();
	}

	@Override
	public void setMaxFieldSize(int max) throws SQLException {
		wrappedStatement.setMaxFieldSize(max);
	}

	@Override
	public int getMaxRows() throws SQLException {
		return wrappedStatement.getMaxRows();
	}

	@Override
	public void setMaxRows(int max) throws SQLException {
		wrappedStatement.setMaxRows(max);
	}

	@Override
	public void setEscapeProcessing(boolean enable) throws SQLException {
		wrappedStatement.setEscapeProcessing(enable);
	}

	@Override
	public int getQueryTimeout() throws SQLException {
		return wrappedStatement.getQueryTimeout();
	}

	@Override
	public void setQueryTimeout(int seconds) throws SQLException {
		wrappedStatement.setQueryTimeout(seconds);
	}

	@Override
	public void cancel() throws SQLException {
		wrappedStatement.cancel();
	}

	@Override
	public SQLWarning getWarnings() throws SQLException {
		return wrappedStatement.getWarnings();
	}

	@Override
	public void clearWarnings() throws SQLException {
		wrappedStatement.clearWarnings();
	}

	@Override
	public void setCursorName(String name) throws SQLException {
		wrappedStatement.setCursorName(name);
	}

	@Override
	public boolean execute(String sql) throws SQLException {
		return wrappedStatement.execute(sql);
	}

	@Override
	public ResultSet getResultSet() throws SQLException {
		return wrappedStatement.getResultSet();
	}

	@Override
	public int getUpdateCount() throws SQLException {
		return wrappedStatement.getUpdateCount();
	}

	@Override
	public boolean getMoreResults() throws SQLException {
		return wrappedStatement.getMoreResults();
	}

	@Override
	public void setFetchDirection(int direction) throws SQLException {
		wrappedStatement.setFetchDirection(direction);
	}

	@Override
	public int getFetchDirection() throws SQLException {
		return wrappedStatement.getFetchDirection();
	}

	@Override
	public void setFetchSize(int rows) throws SQLException {
		wrappedStatement.setFetchDirection(rows);
	}

	@Override
	public int getFetchSize() throws SQLException {
		return wrappedStatement.getFetchSize();
	}

	@Override
	public int getResultSetConcurrency() throws SQLException {
		return wrappedStatement.getResultSetConcurrency();
	}

	@Override
	public int getResultSetType() throws SQLException {
		return wrappedStatement.getResultSetType();
	}

	@Override
	public void addBatch(String sql) throws SQLException {
		wrappedStatement.addBatch(sql);
	}

	@Override
	public void clearBatch() throws SQLException {
		wrappedStatement.clearBatch();
	}

	@Override
	public int[] executeBatch() throws SQLException {
		return wrappedStatement.executeBatch();
	}

	@Override
	public Connection getConnection() throws SQLException {
		return wrappedStatement.getConnection();
	}

	@Override
	public boolean getMoreResults(int current) throws SQLException {
		return wrappedStatement.getMoreResults(current);
	}

	@Override
	public ResultSet getGeneratedKeys() throws SQLException {
		return wrappedStatement.getGeneratedKeys();
	}

	@Override
	public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
		return wrappedStatement.executeUpdate(sql, autoGeneratedKeys);
	}

	@Override
	public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
		return wrappedStatement.executeUpdate(sql, columnIndexes);
	}

	@Override
	public int executeUpdate(String sql, String[] columnNames) throws SQLException {
		return wrappedStatement.executeUpdate(sql, columnNames);
	}

	@Override
	public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
		return wrappedStatement.execute(sql, autoGeneratedKeys);
	}

	@Override
	public boolean execute(String sql, int[] columnIndexes) throws SQLException {
		return wrappedStatement.execute(sql, columnIndexes);
	}

	@Override
	public boolean execute(String sql, String[] columnNames) throws SQLException {
		return wrappedStatement.execute(sql, columnNames);
	}

	@Override
	public int getResultSetHoldability() throws SQLException {
		return wrappedStatement.getResultSetHoldability();
	}

	@Override
	public boolean isClosed() throws SQLException {
		return wrappedStatement.isClosed();
	}

	@Override
	public void setPoolable(boolean poolable) throws SQLException {
		wrappedStatement.setPoolable(poolable);
	}

	@Override
	public boolean isPoolable() throws SQLException {
		return wrappedStatement.isPoolable();
	}

	@Override
	public void closeOnCompletion() throws SQLException {
		wrappedStatement.closeOnCompletion();
	}

	@Override
	public boolean isCloseOnCompletion() throws SQLException {
		return wrappedStatement.isCloseOnCompletion();
	}

	@Override
	public <T> T unwrap(Class<T> iface) throws SQLException {
		return wrappedStatement.unwrap(iface);
	}

	@Override
	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		return wrappedStatement.isWrapperFor(iface);
	}
}


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

import jodd.madvoc.ActionRequest;
import jodd.madvoc.interceptor.ActionInterceptor;
import jodd.proxetta.ProxettaUtil;

import javax.servlet.http.HttpServletRequest;

/**
 * Prepares bundle name for current request.
 */
public class I18nInterceptor implements ActionInterceptor {

	@Override
	public Object intercept(final ActionRequest actionRequest) throws Exception {
		HttpServletRequest request = actionRequest.getHttpServletRequest();

		// defines request bundle of this http request
		LocalizationUtil.setRequestBundleName(request, getActionClassName(actionRequest.getAction()));

		return actionRequest.invoke();
	}

	/**
	 * Returns correct action class name. Detects Proxetta classes.
	 */
	protected String getActionClassName(final Object action) {
		Class clazz = action.getClass();

		clazz = ProxettaUtil.resolveTargetClass(clazz);

		return clazz.getName();
	}

}


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

package jodd.servlet.tag;

import jodd.typeconverter.Converter;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.util.Collection;
import java.util.Iterator;

/**
 * Iterator tag for iterating collections.
 *
 * @see jodd.servlet.tag.IteratorStatus
 */
public class IteratorTag extends SimpleTagSupport {

	protected Object items;
	protected String var;
	protected String status;
	protected int modulus = 2;
	protected String scope;
	protected int from;
	protected int count = -1;

	protected IteratorStatus iteratorStatus;

	/**
	 * Specifies item collection.
	 */
	public void setItems(final Object items) {
		this.items = items;
	}

	/**
	 * Specifies variable name that will be used for item during iteration.
	 */
	public void setVar(final String var) {
		this.var = var;
	}

	/**
	 * Specifies status variable name. If omitted, status will not be used.
	 */
	public void setStatus(final String status) {
		this.status = status;
	}

	/**
	 * Specifies modulus value for the iterator status
	 */
	public void setModulus(final int modulus) {
		this.modulus = modulus;
	}

	/**
	 * Sets scope for all variables.
	 */
	public void setScope(final String scope) {
		this.scope = scope;
	}

	/**
	 * Sets starting index.
	 */
	public void setFrom(final int from) {
		this.from = from;
	}

	/**
	 * Sets count as total number of items to iterate.
	 */
	public void setCount(final int count) {
		this.count = count;
	}

	@Override
	public void doTag() throws JspException {
		if (items == null) {
			return;
		}
		JspFragment body = getJspBody();
		if (body == null) {
			return;
		}
		PageContext pageContext = (PageContext) getJspContext();

		// create an iterator status if the status attribute was set.
		if (status != null) {
			iteratorStatus = new IteratorStatus(this.modulus);
			TagUtil.setScopeAttribute(status, iteratorStatus, scope, pageContext);
		}

		if (items instanceof Collection) {
			iterateCollection((Collection) items, from, count, pageContext);
		} else if (items.getClass().isArray()) {
			iterateArray((Object[]) items, from, count, pageContext);
		} else if (items instanceof String) {
			iterateArray(Converter.get().toStringArray(items), from, count, pageContext);
		} else {
			throw new JspException("Provided items are not iterable");
		}

		// cleanup
		if (status != null) {
			TagUtil.removeScopeAttribute(status, scope, pageContext);
		}
		TagUtil.removeScopeAttribute(var, scope, pageContext);
	}

	/**
	 * Calculates 'TO'.
	 */
	protected int calculateTo(final int from, final int count, final int size) {
		int to = size;
		if (count != -1) {
			to = from + count;
			if (to > size) {
				to = size;
			}
		}
		return to;
	}

	/**
	 * Iterates collection.
	 */
	protected void iterateCollection(final Collection collection, final int from, final int count, final PageContext pageContext) throws JspException {
		JspFragment body = getJspBody();
		Iterator iter = collection.iterator();
		int i = 0;
		int to = calculateTo(from, count, collection.size());
		while (i < to) {
			Object item = iter.next();
			if (i >= from) {
				if (status != null) {
					iteratorStatus.next(!iter.hasNext());
				}
				TagUtil.setScopeAttribute(var, item, scope, pageContext);
				TagUtil.invokeBody(body);
			}
			i++;
		}
	}

	/**
	 * Iterates arrays.
	 */
	protected void iterateArray(final Object[] array, final int from, final int count, final PageContext pageContext) throws JspException {
		JspFragment body = getJspBody();
		int len = array.length;
		int to = calculateTo(from, count, len);
		int last = to - 1;
		for (int i = from; i < to; i++) {
			Object item = array[i];
			if (status != null) {
				iteratorStatus.next(i == last);
			}
			TagUtil.setScopeAttribute(var, item, scope, pageContext);
			TagUtil.invokeBody(body);
		}
	}

}


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

package jodd.db.oom;

import jodd.bean.BeanUtil;
import jodd.introspector.ClassDescriptor;
import jodd.introspector.ClassIntrospector;
import jodd.util.ArraysUtil;
import jodd.util.ClassUtil;
import jodd.util.StringUtil;

import java.lang.reflect.Array;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Joins an array of objects using provided hints.
 * If hint is not available, methods returns the very same object array instance.
 */
public class JoinHintResolver {

	public Object[] join(final Object[] data, final String hints) {
		if (hints == null) {
			return data;
		}
		return join(data, StringUtil.splitc(hints, ','));
	}

	/**
	 * Joins entity array using provided string hints.
	 */
	public Object[] join(final Object[] data, final String[] hints) {
		if (hints == null) {
			return data;
		}
		// build context
		Map<String, Object> context = new HashMap<>(hints.length);
		for (int i = 0; i < hints.length; i++) {
			hints[i] = hints[i].trim();
			String hint = hints[i];
			if (hint.indexOf('.') == -1) {
				context.put(hint, data[i]);
			}
		}

		// no joining hints found
		if (context.size() == data.length) {
			return data;
		}

		// joining
		Object[] result = new Object[context.size()];
		int count = 0;
		for (int i = 0; i < hints.length; i++) {

			String hint = hints[i];
			int ndx = hint.indexOf('.');
			if (ndx != -1) {
				String key = hint.substring(0, ndx);
				Object value = context.get(key);
				if (value == null) {
					throw new DbOomException("Hint value missing: " + key);
				}

				// don't merge nulls
				if (data[i] == null) {
					continue;
				}

				String hintPropertyName = hint.substring(ndx + 1);
				Class hintPropertyType = BeanUtil.pojo.getPropertyType(value, hintPropertyName);

				if (hintPropertyType != null) {
					ClassDescriptor cd = ClassIntrospector.get().lookup(hintPropertyType);

					if (cd.isCollection()) {
						// add element to collection
						try {
							Collection collection = BeanUtil.declared.getProperty(value, hintPropertyName);

							if (collection == null) {
								collection = (Collection) ClassUtil.newInstance(hintPropertyType);
								BeanUtil.declaredSilent.setProperty(value, hintPropertyName, collection);
							}

							collection.add(data[i]);
						} catch (Exception ex) {
							throw new DbOomException(ex);
						}
					} else if (cd.isArray()) {
						// add element to array
						try {
							Object[] array = BeanUtil.declared.getProperty(value, hintPropertyName);

							if (array == null) {
								array = (Object[]) Array.newInstance(hintPropertyType.getComponentType(), 1);

								BeanUtil.declaredSilent.setProperty(value, hintPropertyName, array);

								array[0] = data[i];
							} else {
								Object[] newArray = ArraysUtil.append(array, data[i]);

								if (newArray != array) {
									BeanUtil.declaredSilent.setProperty(value, hintPropertyName, newArray);
								}
							}
						} catch (Exception ex) {
							throw new DbOomException(ex);
						}
					} else {
						// set value
						BeanUtil.declaredSilent.setProperty(value, hintPropertyName, data[i]);
					}
				}
				else {
					// special case - the property probably contains the collection in the way

					int lastNdx = hintPropertyName.lastIndexOf('.');

					String name = hintPropertyName.substring(0, lastNdx);

					Object target = resolveValueInSpecialCase(value, name);

					if (target != null) {
						String targetSimpleName = hintPropertyName.substring(lastNdx + 1);

						BeanUtil.declaredForcedSilent.setProperty(target, targetSimpleName, data[i]);
					}
				}

			} else {
				result[count] = data[i];
				count++;
			}
		}
		return result;
	}

	protected Object resolveValueInSpecialCase(Object value, final String name) {
		String[] elements = StringUtil.splitc(name, '.');

		for (String element : elements) {
			value = BeanUtil.declaredSilent.getProperty(value, element);

			if (value instanceof List) {
				List list = (List) value;

				value = list.get(list.size() - 1);
			}
		}

		return value;
	}
}