// 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.bridge.Packages;
import jodd.introspector.Mapper;
import jodd.introspector.MapperFunction;
import jodd.introspector.MapperFunctionInstances;
import jodd.madvoc.ActionConfig;
import jodd.madvoc.ActionHandler;
import jodd.madvoc.MadvocException;
import jodd.madvoc.MadvocUtil;
import jodd.madvoc.config.ActionDefinition;
import jodd.madvoc.config.ActionNames;
import jodd.madvoc.config.ActionRuntime;
import jodd.madvoc.config.MethodParam;
import jodd.madvoc.config.ScopeData;
import jodd.madvoc.filter.ActionFilter;
import jodd.madvoc.interceptor.ActionInterceptor;
import jodd.madvoc.meta.Action;
import jodd.madvoc.meta.ActionAnnotationValues;
import jodd.madvoc.meta.Async;
import jodd.madvoc.meta.Auth;
import jodd.madvoc.meta.FilteredBy;
import jodd.madvoc.meta.InterceptedBy;
import jodd.madvoc.meta.MadvocAction;
import jodd.madvoc.meta.RenderWith;
import jodd.madvoc.meta.method.DELETE;
import jodd.madvoc.meta.method.GET;
import jodd.madvoc.meta.method.HEAD;
import jodd.madvoc.meta.method.OPTIONS;
import jodd.madvoc.meta.method.PATCH;
import jodd.madvoc.meta.method.POST;
import jodd.madvoc.meta.method.PUT;
import jodd.madvoc.meta.method.TRACE;
import jodd.madvoc.path.ActionNamingStrategy;
import jodd.madvoc.result.ActionResult;
import jodd.madvoc.result.NoneActionResult;
import jodd.petite.meta.PetiteInject;
import jodd.util.ArraysUtil;
import jodd.util.ClassUtil;
import jodd.util.StringPool;
import jodd.util.StringUtil;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;

/**
 * Creates {@link ActionRuntime action runtime} configuration from action java method.
 * Reads all annotations and builds action path (i.e. configuration).
 *

 * Invoked only during registration, so performance is not critical.
 */
public class ActionMethodParser {

	@SuppressWarnings("unchecked")
	private static final Class<? extends Annotation>[] METHOD_ANNOTATIONS = new Class[] {
		DELETE.class,
		GET.class,
		HEAD.class,
		POST.class,
		PUT.class,
		OPTIONS.class,
		TRACE.class,
		PATCH.class
	};

	@PetiteInject
	protected ContextInjectorComponent contextInjectorComponent;

	@PetiteInject
	protected ActionConfigManager actionConfigManager;

	@PetiteInject
	protected ActionsManager actionsManager;

	@PetiteInject
	protected InterceptorsManager interceptorsManager;

	@PetiteInject
	protected FiltersManager filtersManager;

	@PetiteInject
	protected RootPackages rootPackages;

	@PetiteInject
	protected ScopeDataInspector scopeDataInspector;

	@PetiteInject
	protected ActionMethodParamNameResolver actionMethodParamNameResolver;

	/**
	 * Parses action class and method and creates {@link ActionDefinition parsed action definition}.
	 */
	public ActionDefinition parseActionDefinition(final Class<?> actionClass, final Method actionMethod) {

		final ActionAnnotationValues annotationValues = detectActionAnnotationValues(actionMethod);

		final ActionConfig actionConfig = resolveActionConfig(annotationValues);

		final String[] packageActionNames = readPackageActionPath(actionClass);

		final String[] classActionNames = readClassActionPath(actionClass);

		final String[] methodActionNames = readMethodActionPath(actionMethod.getName(), annotationValues, actionConfig);

		final String method = readMethodHttpMethod(actionMethod);

		final ActionNames actionNames = new ActionNames(packageActionNames, classActionNames, methodActionNames, method);

		final ActionNamingStrategy namingStrategy;

		try {
			namingStrategy = ClassUtil.newInstance(actionConfig.getNamingStrategy());

			contextInjectorComponent.injectContext(namingStrategy);
		} catch (Exception ex) {
			throw new MadvocException(ex);
		}

		return namingStrategy.buildActionDef(actionClass, actionMethod, actionNames);
	}

	/**
	 * Parses java action method annotation and returns its action runtime.
	 *
	 * @param actionClass action class
	 * @param actionMethod action method
	 * @param actionDefinition optional action def, usually null so to be parsed
	 */
	public ActionRuntime parse(final Class<?> actionClass, final Method actionMethod, ActionDefinition actionDefinition) {
		final ActionAnnotationValues annotationValues = detectActionAnnotationValues(actionMethod);

		final ActionConfig actionConfig = resolveActionConfig(annotationValues);

		// interceptors
		ActionInterceptor[] actionInterceptors = parseActionInterceptors(actionClass, actionMethod, actionConfig);

		// filters
		ActionFilter[] actionFilters = parseActionFilters(actionClass, actionMethod, actionConfig);

		// build action definition when not provided
		if (actionDefinition == null) {
			actionDefinition = parseActionDefinition(actionClass, actionMethod);
		}

		detectAndRegisterAlias(annotationValues, actionDefinition);

		final boolean async = parseMethodAsyncFlag(actionMethod);

		final boolean auth = parseMethodAuthFlag(actionMethod);

		final Class<? extends ActionResult> actionResult = parseActionResult(actionMethod);
		final Class<? extends ActionResult> defaultActionResult = actionConfig.getActionResult();

		return createActionRuntime(
			null,
			actionClass,
			actionMethod,
			actionResult,
			defaultActionResult,
			actionFilters,
			actionInterceptors,
			actionDefinition,
			async,
			auth);
	}

	/**
	 * Resolves action config.
	 */
	protected ActionConfig resolveActionConfig(final ActionAnnotationValues annotationValues) {
		final Class<? extends Annotation> annotationType;

		if (annotationValues == null) {
			annotationType = Action.class;
		}
		else {
			annotationType = annotationValues.annotationType();
		}
		return actionConfigManager.lookup(annotationType);
	}

	/**
	 * Detects {@link jodd.madvoc.meta.ActionAnnotationValues}. Returns {@code null} if annotation does not exist.
	 */
	protected ActionAnnotationValues detectActionAnnotationValues(final Method actionMethod) {
		return actionConfigManager.readAnnotationValue(actionMethod);
	}

	/**
	 * Detects if alias is defined in annotation and registers it if so.
	 */
	protected void detectAndRegisterAlias(final ActionAnnotationValues annotationValues, final ActionDefinition actionDefinition) {
		final String alias = parseMethodAlias(annotationValues);

		if (alias != null) {
			String aliasPath = StringUtil.cutToIndexOf(actionDefinition.actionPath(), StringPool.HASH);
			actionsManager.registerPathAlias(alias, aliasPath);
		}
	}

	protected Class<? extends ActionResult> parseActionResult(final Method actionMethod) {
		final RenderWith renderWith = actionMethod.getAnnotation(RenderWith.class);

		if (renderWith != null) {
			return renderWith.value();
		}

		return null;
	}

	protected ActionInterceptor[] parseActionInterceptors(final Class<?> actionClass, final Method actionMethod, final ActionConfig actionConfig) {
		Class<? extends ActionInterceptor>[] interceptorClasses = readActionInterceptors(actionMethod);
		if (interceptorClasses == null) {
			interceptorClasses = readActionInterceptors(actionClass);
		}
		if (interceptorClasses == null) {
			interceptorClasses = actionConfig.getInterceptors();
		}

		return interceptorsManager.resolveAll(interceptorClasses);
	}

	protected ActionFilter[] parseActionFilters(final Class<?> actionClass, final Method actionMethod, final ActionConfig actionConfig) {
		Class<? extends ActionFilter>[] filterClasses = readActionFilters(actionMethod);
		if (filterClasses == null) {
			filterClasses = readActionFilters(actionClass);
		}
		if (filterClasses == null) {
			filterClasses = actionConfig.getFilters();
		}

		return filtersManager.resolveAll(filterClasses);
	}

	// ---------------------------------------------------------------- interceptors

	/**
	 * Reads class or method annotation for action interceptors.
	 */
	protected Class<? extends ActionInterceptor>[] readActionInterceptors(final AnnotatedElement actionClassOrMethod) {
		Class<? extends ActionInterceptor>[] result = null;
		InterceptedBy interceptedBy = actionClassOrMethod.getAnnotation(InterceptedBy.class);
		if (interceptedBy != null) {
			result = interceptedBy.value();
			if (result.length == 0) {
				result = null;
			}
		}
		return result;
	}

	// ---------------------------------------------------------------- filters

	/**
	 * Reads class or method annotation for action filters.
	 */
	protected Class<? extends ActionFilter>[] readActionFilters(final AnnotatedElement actionClassOrMethod) {
		Class<? extends ActionFilter>[] result = null;
		FilteredBy filteredBy = actionClassOrMethod.getAnnotation(FilteredBy.class);
		if (filteredBy != null) {
			result = filteredBy.value();
			if (result.length == 0) {
				result = null;
			}
		}
		return result;
	}

	// ---------------------------------------------------------------- readers

	/**
	 * Reads action path for package.
	 * If annotation is not set on package-level, class package will be used for
	 * package action path part.
	 */
	protected String[] readPackageActionPath(final Class actionClass) {
		Package actionPackage = actionClass.getPackage();

		final String actionPackageName = actionPackage.getName();

		// 1 - read annotations first

		String packageActionPathFromAnnotation;

		mainloop:
		while (true) {
			MadvocAction madvocActionAnnotation = actionPackage.getAnnotation(MadvocAction.class);

			packageActionPathFromAnnotation = madvocActionAnnotation != null ? madvocActionAnnotation.value().trim() : null;

			if (StringUtil.isEmpty(packageActionPathFromAnnotation)) {
				packageActionPathFromAnnotation = null;
			}

			if (packageActionPathFromAnnotation == null) {
				// next package
				String newPackage = actionPackage.getName();
				actionPackage = null;

				while (actionPackage == null) {
					final int ndx = newPackage.lastIndexOf('.');
					if (ndx == -1) {
						// end of hierarchy, nothing found
						break mainloop;
					}
					newPackage = newPackage.substring(0, ndx);
					actionPackage = Packages.of(actionClass.getClassLoader(), newPackage);
				}
			}
			else {
				// annotation found, register root
				rootPackages.addRootPackage(actionPackage.getName(), packageActionPathFromAnnotation);
				break;
			}
		}

		// 2 - read root package

		String packagePath = rootPackages.findPackagePathForActionPackage(actionPackageName);

		if (packagePath == null) {
			return ArraysUtil.array(null, null);
		}

		return ArraysUtil.array(
			StringUtil.stripChar(packagePath, '/'),
			StringUtil.surround(packagePath, StringPool.SLASH)
		);
	}

	/**
	 * Reads action path from class. If the class is annotated with {@link MadvocAction} annotation,
	 * class action path will be read from annotation value. Otherwise, action class path will be built from the
	 * class name. This is done by removing the package name and the last contained word
	 * (if there is more then one) from the class name. Such name is finally uncapitalized.
	 */
	protected String[] readClassActionPath(final Class actionClass) {
		// read class annotation
		MadvocAction madvocActionAnnotation = ((Class<?>)actionClass).getAnnotation(MadvocAction.class);

		String classActionPath = madvocActionAnnotation != null ? madvocActionAnnotation.value().trim() : null;

		if (StringUtil.isEmpty(classActionPath)) {
			classActionPath = null;
		}

		String actionClassName = actionClass.getSimpleName();
		actionClassName = StringUtil.uncapitalize(actionClassName);
		actionClassName = MadvocUtil.stripLastCamelWord(actionClassName);       // removes 'Action' from the class name

		if (classActionPath == null) {
			classActionPath = actionClassName;
		}

		return ArraysUtil.array(actionClassName, classActionPath);
	}

	/**
	 * Reads action path from the action method.
	 */
	protected String[] readMethodActionPath(final String methodName, final ActionAnnotationValues annotationValues, final ActionConfig actionConfig) {
		// read annotation
		String methodActionPath = annotationValues != null ? annotationValues.value() : null;

		if (methodActionPath == null) {
			methodActionPath = methodName;
		} else {
			if (methodActionPath.equals(Action.NONE)) {
				return ArraysUtil.array(null, null);
			}
		}

		// check for defaults
		for (String path : actionConfig.getActionMethodNames()) {
			if (methodActionPath.equals(path)) {
				methodActionPath = null;
				break;
			}
		}

		return ArraysUtil.array(methodName, methodActionPath);
	}

	/**
	 * Reads method's alias value.
	 */
	protected String parseMethodAlias(final ActionAnnotationValues annotationValues) {
		String alias = null;
		if (annotationValues != null) {
			alias = annotationValues.alias();
		}
		return alias;
	}

	/**
	 * Reads method's http method or {@code null} if not specified.
	 */
	private String readMethodHttpMethod(final Method actionMethod) {
		for (Class<? extends Annotation> methodAnnotation : METHOD_ANNOTATIONS) {
			if (actionMethod.getAnnotation(methodAnnotation) != null) {
				return methodAnnotation.getSimpleName();
			}
		}

		return null;
	}

	/**
	 * Reads method's async flag.
	 */
	private boolean parseMethodAsyncFlag(final Method actionMethod) {
		return actionMethod.getAnnotation(Async.class) != null;
	}

	private boolean parseMethodAuthFlag(final Method actionMethod) {
		if (actionMethod.getAnnotation(Auth.class) != null) {
			return true;
		}
		final Class declaringClass = actionMethod.getDeclaringClass();
		if (declaringClass.getAnnotation(Auth.class) != null) {
			return true;
		}
		if (declaringClass.getPackage().getAnnotation(Auth.class) != null) {
			return true;
		}
		return false;
	}

	// ---------------------------------------------------------------- create action runtime

	/**
	 * Creates new instance of action runtime configuration.
	 * Initialize caches.
	 */
	public ActionRuntime createActionRuntime(
		final ActionHandler actionHandler,
		final Class actionClass,
		final Method actionClassMethod,
		final Class<? extends ActionResult> actionResult,
		final Class<? extends ActionResult> defaultActionResult,
		final ActionFilter[] filters,
		final ActionInterceptor[] interceptors,
		final ActionDefinition actionDefinition,
		final boolean async,
		final boolean auth)
	{
		if (actionHandler != null) {

			return new ActionRuntime(
				actionHandler,
				actionClass,
				actionClassMethod,
				filters,
				interceptors,
				actionDefinition,
				NoneActionResult.class,
				NoneActionResult.class,
				async,
				auth,
				null,
				null);

		}

		final ScopeData scopeData = scopeDataInspector.inspectClassScopes(actionClass);

		// find ins and outs

		final Class[] paramTypes = actionClassMethod.getParameterTypes();
		final MethodParam[] params = new MethodParam[paramTypes.length];

		final Annotation[][] paramAnns = actionClassMethod.getParameterAnnotations();
		String[] methodParamNames = null;

		// for all elements: action and method arguments...
		for (int ndx = 0; ndx < paramTypes.length; ndx++) {
			Class paramType = paramTypes[ndx];

			// lazy init to postpone bytecode usage, when method has no arguments
			if (methodParamNames == null) {
				methodParamNames = actionMethodParamNameResolver.resolveParamNames(actionClassMethod);
			}

			final String paramName = methodParamNames[ndx];

			final Annotation[] parameterAnnotations = paramAnns[ndx];

			final ScopeData paramsScopeData = scopeDataInspector.inspectMethodParameterScopes(paramName, paramType, parameterAnnotations);

			MapperFunction mapperFunction = null;
			for (final Annotation annotation : parameterAnnotations) {
				if (annotation instanceof Mapper) {
					mapperFunction = MapperFunctionInstances.get().lookup(((Mapper) annotation).value());
					break;
				}
			}

			params[ndx] = new MethodParam(
				paramTypes[ndx],
				paramName,
				scopeDataInspector.detectAnnotationType(parameterAnnotations),
				paramsScopeData,
				mapperFunction
			);
		}

		return new ActionRuntime(
				null,
				actionClass,
				actionClassMethod,
				filters,
				interceptors,
				actionDefinition,
				actionResult,
				defaultActionResult,
				async,
				auth,
				scopeData,
				params);
	}

}


// 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.ResultSet;
import java.sql.SQLException;
import java.sql.PreparedStatement;
import java.sql.Time;

public class TimeSqlType extends SqlType<Time> {

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

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

}


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

package jodd.decora;

import jodd.servlet.wrapper.BufferResponseWrapper;
import jodd.servlet.wrapper.LastModifiedData;

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

/**
 * Decora response wrapper uses {@link DecoraManager decora manager} to determine
 * if buffering should be enabled or disabled.
 */
public class DecoraResponseWrapper extends BufferResponseWrapper {

	protected final DecoraManager decoraManager;
	protected final HttpServletRequest request;
	protected final HttpServletResponse response;

	public DecoraResponseWrapper(final HttpServletRequest originalRequest, final HttpServletResponse originalResponse, final LastModifiedData lastModifiedData, final DecoraManager decoraManager) {
		super(originalResponse, lastModifiedData);
		this.request = originalRequest;
		this.response = originalResponse;
		this.decoraManager = decoraManager;

		// reset servlet response content length AFTER the chain, since
		// servlet container may set it and we are changing the content.
		originalResponse.setContentLength(-1);
	}

	@Override
	protected boolean bufferContentType(final String contentType, final String mimeType, final String encoding) {
		return decoraManager.decorateContentType(contentType, mimeType, encoding);
	}

	@Override
	protected boolean bufferStatusCode(final int statusCode) {
		return decoraManager.decorateStatusCode(statusCode);
	}

	// todo move to BufferResponseWrapper ?
	@Override
	protected void preResponseCommit() {
		long lastModified = lastModifiedData.getLastModified();
		long ifModifiedSince = request.getDateHeader("If-Modified-Since");

		if (lastModified > -1 && !response.containsHeader("Last-Modified")) {
			if (ifModifiedSince < (lastModified / 1000 * 1000)) {
				response.setDateHeader("Last-Modified", lastModified);
			} else {
				response.reset();
				response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
			}
		}
	}
}


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

import java.math.BigDecimal;
import java.math.BigInteger;

/**
 * Just a convenient {@link TypeConverterManager} usage.
 */
public class Converter {

	private static final Converter CONVERTER = new Converter();

	/**
	 * Returns default instance.
	 */
	public static Converter get() {
		return CONVERTER;
	}

	// ---------------------------------------------------------------- boolean

	/**
	 * Converts value to Boolean.
	 */
	public Boolean toBoolean(final Object value) {
		final TypeConverter<Boolean> tc = TypeConverterManager.get().lookup(Boolean.class);
		return tc.convert(value);
	}

	/**
	 * Converts value to Boolean. Returns default value
	 * when conversion result is null
	 */
	public Boolean toBoolean(final Object value, final Boolean defaultValue) {
		final Boolean result = toBoolean(value);
		if (result == null) {
			return defaultValue;
		}
		return result;
	}

	/**
	 * Converts value to boolean. Returns default value
	 * when conversion result is null.
	 */
	public boolean toBooleanValue(final Object value, final boolean defaultValue) {
		final Boolean result = toBoolean(value);
		if (result == null) {
			return defaultValue;
		}
		return result.booleanValue();
	}

	/**
	 * Converts value to boolean with common default value.
	 */
	public boolean toBooleanValue(final Object value) {
		return toBooleanValue(value, false);
	}

	// ---------------------------------------------------------------- integer

	/**
	 * Converts value to Integer.
	 */
	public Integer toInteger(final Object value) {
		final TypeConverter<Integer> tc = TypeConverterManager.get().lookup(Integer.class);
		return tc.convert(value);
	}

	/**
	 * Converts value to Integer. Returns default value
	 * when conversion result is null
	 */
	public Integer toInteger(final Object value, final Integer defaultValue) {
		final Integer result = toInteger(value);
		if (result == null) {
			return defaultValue;
		}
		return result;
	}

	/**
	 * Converts value to int. Returns default value
	 * when conversion result is null.
	 */
	public int toIntValue(final Object value, final int defaultValue) {
		final Integer result = toInteger(value);
		if (result == null) {
			return defaultValue;
		}
		return result.intValue();
	}

	/**
	 * Converts value to int with common default value.
	 */
	public int toIntValue(final Object value) {
		return toIntValue(value, 0);
	}

	// ---------------------------------------------------------------- long

	/**
	 * Converts value to Long.
	 */
	public Long toLong(final Object value) {
		final TypeConverter<Long> tc = TypeConverterManager.get().lookup(Long.class);
		return tc.convert(value);
	}

	/**
	 * Converts value to Long. Returns default value
	 * when conversion result is null
	 */
	public Long toLong(final Object value, final Long defaultValue) {
		final Long result = toLong(value);
		if (result == null) {
			return defaultValue;
		}
		return result;
	}

	/**
	 * Converts value to long. Returns default value
	 * when conversion result is null.
	 */
	public long toLongValue(final Object value, final long defaultValue) {
		final Long result = toLong(value);
		if (result == null) {
			return defaultValue;
		}
		return result.longValue();
	}

	/**
	 * Converts value to long with common default value.
	 */
	public long toLongValue(final Object value) {
		return toLongValue(value, 0);
	}

	// ---------------------------------------------------------------- float

	/**
	 * Converts value to Float.
	 */
	public Float toFloat(final Object value) {
		final TypeConverter<Float> tc = TypeConverterManager.get().lookup(Float.class);
		return tc.convert(value);
	}

	/**
	 * Converts value to Float. Returns default value
	 * when conversion result is null
	 */
	public Float toFloat(final Object value, final Float defaultValue) {
		final Float result = toFloat(value);
		if (result == null) {
			return defaultValue;
		}
		return result;
	}

	/**
	 * Converts value to float. Returns default value
	 * when conversion result is null.
	 */
	public float toFloatValue(final Object value, final float defaultValue) {
		final Float result = toFloat(value);
		if (result == null) {
			return defaultValue;
		}
		return result.floatValue();
	}

	/**
	 * Converts value to float with common default value.
	 */
	public float toFloatValue(final Object value) {
		return toFloatValue(value, 0);
	}

	// ---------------------------------------------------------------- double

	/**
	 * Converts value to Double.
	 */
	public Double toDouble(final Object value) {
		final TypeConverter<Double> tc = TypeConverterManager.get().lookup(Double.class);
		return tc.convert(value);
	}

	/**
	 * Converts value to Double. Returns default value
	 * when conversion result is null
	 */
	public Double toDouble(final Object value, final Double defaultValue) {
		final Double result = toDouble(value);
		if (result == null) {
			return defaultValue;
		}
		return result;
	}

	/**
	 * Converts value to double. Returns default value
	 * when conversion result is null.
	 */
	public double toDoubleValue(final Object value, final double defaultValue) {
		final Double result = toDouble(value);
		if (result == null) {
			return defaultValue;
		}
		return result.doubleValue();
	}

	/**
	 * Converts value to double with common default value.
	 */
	public double toDoubleValue(final Object value) {
		return toDoubleValue(value, 0);
	}

	// ---------------------------------------------------------------- short

	/**
	 * Converts value to Short.
	 */
	public Short toShort(final Object value) {
		final TypeConverter<Short> tc = TypeConverterManager.get().lookup(Short.class);
		return tc.convert(value);
	}

	/**
	 * Converts value to Short. Returns default value
	 * when conversion result is null
	 */
	public Short toShort(final Object value, final Short defaultValue) {
		final Short result = toShort(value);
		if (result == null) {
			return defaultValue;
		}
		return result;
	}

	/**
	 * Converts value to short. Returns default value
	 * when conversion result is null.
	 */
	public short toShortValue(final Object value, final short defaultValue) {
		final Short result = toShort(value);
		if (result == null) {
			return defaultValue;
		}
		return result.shortValue();
	}

	/**
	 * Converts value to short with common default value.
	 */
	public short toShortValue(final Object value) {
		return toShortValue(value, (short) 0);
	}

	// ---------------------------------------------------------------- character

	/**
	 * Converts value to Character.
	 */
	public Character toCharacter(final Object value) {
		final TypeConverter<Character> tc = TypeConverterManager.get().lookup(Character.class);
		return tc.convert(value);
	}

	/**
	 * Converts value to Character. Returns default value
	 * when conversion result is null
	 */
	public Character toCharacter(final Object value, final Character defaultValue) {
		final Character result = toCharacter(value);
		if (result == null) {
			return defaultValue;
		}
		return result;
	}

	/**
	 * Converts value to char. Returns default value
	 * when conversion result is null.
	 */
	public char toCharValue(final Object value, final char defaultValue) {
		final Character result = toCharacter(value);
		if (result == null) {
			return defaultValue;
		}
		return result.charValue();
	}

	/**
	 * Converts value to char with common default value.
	 */
	public char toCharValue(final Object value) {
		return toCharValue(value, (char) 0);
	}

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

	/**
	 * Converts value to Byte.
	 */
	public Byte toByte(final Object value) {
		final TypeConverter<Byte> tc = TypeConverterManager.get().lookup(Byte.class);
		return tc.convert(value);
	}

	/**
	 * Converts value to Byte. Returns default value
	 * when conversion result is null
	 */
	public Byte toByte(final Object value, final Byte defaultValue) {
		final Byte result = toByte(value);
		if (result == null) {
			return defaultValue;
		}
		return result;
	}

	/**
	 * Converts value to byte. Returns default value
	 * when conversion result is null.
	 */
	public byte toByteValue(final Object value, final byte defaultValue) {
		final Byte result = toByte(value);
		if (result == null) {
			return defaultValue;
		}
		return result.byteValue();
	}

	/**
	 * Converts value to byte with common default value.
	 */
	public byte toByteValue(final Object value) {
		return toByteValue(value, (byte) 0);
	}

	// ---------------------------------------------------------------- array

	/**
	 * Converts value to boolean[].
	 */
	public boolean[] toBooleanArray(final Object value) {
		final TypeConverter<boolean[]> tc = TypeConverterManager.get().lookup(boolean[].class);
		return tc.convert(value);
	}

	/**
	 * Converts value to int[].
	 */
	public int[] toIntegerArray(final Object value) {
		final TypeConverter<int[]> tc = TypeConverterManager.get().lookup(int[].class);
		return tc.convert(value);

	}

	/**
	 * Converts value to long[].
	 */
	public long[] toLongArray(final Object value) {
		final TypeConverter<long[]> tc = TypeConverterManager.get().lookup(long[].class);
		return tc.convert(value);

	}

	/**
	 * Converts value to float[].
	 */
	public float[] toFloatArray(final Object value) {
		final TypeConverter<float[]> tc = TypeConverterManager.get().lookup(float[].class);
		return tc.convert(value);
	}

	/**
	 * Converts value to double[].
	 */
	public double[] toDoubleArray(final Object value) {
		final TypeConverter<double[]> tc = TypeConverterManager.get().lookup(double[].class);
		return tc.convert(value);
	}

	/**
	 * Converts value to short[].
	 */
	public short[] toShortArray(final Object value) {
		final TypeConverter<short[]> tc = TypeConverterManager.get().lookup(short[].class);
		return tc.convert(value);
	}

	/**
	 * Converts value to char[].
	 */
	public char[] toCharacterArray(final Object value) {
		final TypeConverter<char[]> tc = TypeConverterManager.get().lookup(char[].class);
		return tc.convert(value);
	}

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

	/**
	 * Converts value to String.
	 */
	public String toString(final Object value) {
		final TypeConverter<String> tc = TypeConverterManager.get().lookup(String.class);
		return tc.convert(value);
	}

	/**
	 * Converts value to String. Returns default value
	 * when conversion result is null
	 */
	public String toString(final Object value, final String defaultValue) {
		final String result = toString(value);
		if (result == null) {
			return defaultValue;
		}
		return result;
	}

	/**
	 * Converts value to String[].
	 */
	public String[] toStringArray(final Object value) {
		final TypeConverter<String[]> tc = TypeConverterManager.get().lookup(String[].class);
		return tc.convert(value);
	}

	// ---------------------------------------------------------------- class

	/**
	 * Converts value to Class.
	 */
	public Class toClass(final Object value) {
		final TypeConverter<Class> tc = TypeConverterManager.get().lookup(Class.class);
		return tc.convert(value);
	}

	/**
	 * Converts value to Class[].
	 */
	public Class[] toClassArray(final Object value) {
		final TypeConverter<Class[]> tc = TypeConverterManager.get().lookup(Class[].class);
		return tc.convert(value);
	}

	// ---------------------------------------------------------------- bigs

	/**
	 * Converts value to BigInteger.
	 */
	public BigInteger toBigInteger(final Object value) {
		final TypeConverter<BigInteger> tc = TypeConverterManager.get().lookup(BigInteger.class);
		return tc.convert(value);
	}

	/**
	 * Converts value to BigInteger. Returns default value
	 * when conversion result is null
	 */
	public BigInteger toBigInteger(final Object value, final BigInteger defaultValue) {
		final BigInteger result = toBigInteger(value);
		if (result == null) {
			return defaultValue;
		}
		return result;
	}

	/**
	 * Converts value to BigDecimal.
	 */
	public BigDecimal toBigDecimal(final Object value) {
		final TypeConverter<BigDecimal> tc = TypeConverterManager.get().lookup(BigDecimal.class);
		return tc.convert(value);
	}

	/**
	 * Converts value to BigDecimal. Returns default value
	 * when conversion result is null
	 */
	public BigDecimal toBigDecimal(final Object value, final BigDecimal defaultValue) {
		final BigDecimal result = toBigDecimal(value);
		if (result == null) {
			return defaultValue;
		}
		return result;
	}
}


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

package jodd.vtor.constraint;

import jodd.typeconverter.Converter;
import jodd.vtor.ValidationConstraint;
import jodd.vtor.ValidationConstraintContext;

public class RangeConstraint implements ValidationConstraint<Range> {

	public RangeConstraint() {
	}

	public RangeConstraint(final double min, final double max) {
		this.min = min;
		this.max = max;
	}

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

	protected double min;
	protected double max;

	public double getMin() {
		return min;
	}

	public void setMin(final double min) {
		this.min = min;
	}

	public double getMax() {
		return max;
	}

	public void setMax(final double max) {
		this.max = max;
	}

	// ---------------------------------------------------------------- configure

	@Override
	public void configure(final Range annotation) {
		this.min = annotation.min();
		this.max = annotation.max();
	}

	// ---------------------------------------------------------------- valid

	@Override
	public boolean isValid(final ValidationConstraintContext vcc, final Object value) {
		return validate(value, min, max);
	}

	public static boolean validate(final Object value, final double min, final double max) {
		if (value == null) {
			return true;
		}
		double val = Converter.get().toDoubleValue(value);
		return val >= min && val <= max;
	}

}


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

import java.util.function.Consumer;

/**
 * Auto-magically configures Petite container by analyzing the classpath.
 * <p>
 * Scans all classes on classpath and in jar files, and scans for {@link jodd.petite.meta.PetiteBean}
 * annotation (not by loading the class!). If annotation is founded, class will be loaded and
 * registered as Petite bean.
 */
public class AutomagicPetiteConfigurator {

	private static final Logger log = LoggerFactory.getLogger(AutomagicPetiteConfigurator.class);
	private final static byte[] PETITE_BEAN_ANNOTATION_BYTES = ClassScanner.bytecodeSignatureOfType(PetiteBean.class);
	private final PetiteContainer container;
	private final Consumers<ClassScanner> classScannerConsumers = new Consumers<>();

	public AutomagicPetiteConfigurator(final PetiteContainer petiteContainer) {
		this.container = petiteContainer;
	}

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

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

		final ClassScanner classScanner = new ClassScanner();

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

		registerAsConsumer(classScanner);

		try {
			classScanner.start();
		} catch (Exception ex) {
			throw new PetiteException("Scan classpath error", ex);
		}
		elapsed = System.currentTimeMillis() - elapsed;
		log.info("Petite configured in " + elapsed + " ms. Total beans: " + container.beansCount());
	}

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

			final Class<?> beanClass;

			try {
				beanClass = classPathEntry.loadClass();
			} catch (ClassNotFoundException cnfex) {
				throw new PetiteException("Unable to load class: " + cnfex, cnfex);
			}

			if (beanClass == null) {
				return;
			}

			final PetiteBean petiteBean = beanClass.getAnnotation(PetiteBean.class);

			if (petiteBean == null) {
				return;
			}
			container.registerPetiteBean(beanClass, null, null, null, false, 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.proxetta.advice;

import jodd.cache.TypeCache;
import jodd.proxetta.MethodInfo;
import jodd.proxetta.Proxetta;
import jodd.proxetta.ProxettaException;
import jodd.proxetta.ProxyAspect;
import jodd.proxetta.impl.ProxyProxetta;
import jodd.util.ClassUtil;

import java.lang.reflect.Field;

/**
 * Utilities for applying {@link DelegateAdvice} to target.
 */
public class DelegateAdviceUtil {

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

	/**
	 * Proxy Proxetta, applied on all public methods of the target class.
	 */
	private static final ProxyProxetta PROXY_PROXETTA =
		Proxetta
			.proxyProxetta()
			.withAspect(ProxyAspect.of(DelegateAdvice.class, MethodInfo::isPublicMethod));

	/**
	 * Applies advice on given target class and returns proxy instance.
	 */
	public static <T> T applyAdvice(final Class<T> targetClass) {
		Class adviceClass = cache.get(targetClass);

		if (adviceClass == null) {
			// advice not yet created

			adviceClass = PROXY_PROXETTA.proxy().setTarget(targetClass).define();

			cache.put(targetClass, adviceClass);
		}

		// create new advice instance and injects target instance to it

		try {
			Object advice = ClassUtil.newInstance(adviceClass);

			Field field = adviceClass.getField("$___target$0");

			field.set(advice, targetClass);

			return (T) advice;
		} catch (Exception ex) {
			throw new ProxettaException(ex);
		}
	}

	/**
	 * Injects target into proxy.
	 */
	public static void injectTargetIntoProxy(final Object proxy, final Object target) {
		Class proxyClass = proxy.getClass();

		try {
			Field field = proxyClass.getField("$___target$0");

			field.set(proxy, target);
		} catch (Exception ex) {
			throw new ProxettaException(ex);
		}
	}
}


// 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 visitor to visit a Java field. The methods of this class must be called in the following order:
 * ( {@code visitAnnotation} | {@code visitTypeAnnotation} | {@code visitAttribute} )* {@code
 * visitEnd}.
 *
 * @author Eric Bruneton
 */
public abstract class FieldVisitor {

  /**
   * The ASM API version implemented by this visitor. The value of this field must be one of {@link
   * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
   */
  protected final int api;

  /** The field visitor to which this visitor must delegate method calls. May be null. */
  protected FieldVisitor fv;

  /**
   * Constructs a new {@link FieldVisitor}.
   *
   * @param api the ASM API version implemented by this visitor. Must be one of {@link
   *     Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
   */
  public FieldVisitor(final int api) {
    this(api, null);
  }

  /**
   * Constructs a new {@link FieldVisitor}.
   *
   * @param api the ASM API version implemented by this visitor. Must be one of {@link
   *     Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
   * @param fieldVisitor the field visitor to which this visitor must delegate method calls. May be
   *     null.
   */
  public FieldVisitor(final int api, final FieldVisitor fieldVisitor) {
    if (api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4 && api != Opcodes.ASM7) {
      throw new IllegalArgumentException();
    }
    this.api = api;
    this.fv = fieldVisitor;
  }

  /**
   * Visits an annotation of the field.
   *
   * @param descriptor the class descriptor of the annotation class.
   * @param visible {@literal true} if the annotation is visible at runtime.
   * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
   *     interested in visiting this annotation.
   */
  public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
    if (fv != null) {
      return fv.visitAnnotation(descriptor, visible);
    }
    return null;
  }

  /**
   * Visits an annotation on the type of the field.
   *
   * @param typeRef a reference to the annotated type. The sort of this type reference must be
   *     {@link TypeReference#FIELD}. See {@link TypeReference}.
   * @param typePath the path to the annotated type argument, wildcard bound, array element type, or
   *     static inner type within 'typeRef'. May be {@literal null} if the annotation targets
   *     'typeRef' as a whole.
   * @param descriptor the class descriptor of the annotation class.
   * @param visible {@literal true} if the annotation is visible at runtime.
   * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not
   *     interested in visiting this annotation.
   */
  public AnnotationVisitor visitTypeAnnotation(
      final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
    if (api < Opcodes.ASM5) {
      throw new UnsupportedOperationException("This feature requires ASM5");
    }
    if (fv != null) {
      return fv.visitTypeAnnotation(typeRef, typePath, descriptor, visible);
    }
    return null;
  }

  /**
   * Visits a non standard attribute of the field.
   *
   * @param attribute an attribute.
   */
  public void visitAttribute(final Attribute attribute) {
    if (fv != null) {
      fv.visitAttribute(attribute);
    }
  }

  /**
   * Visits the end of the field. This method, which is the last one to be called, is used to inform
   * the visitor that all the annotations and attributes of the field have been visited.
   */
  public void visitEnd() {
    if (fv != null) {
      fv.visitEnd();
    }
  }
}


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

package jodd.json.meta;

import jodd.inex.InExRules;
import jodd.util.ArraysUtil;

import java.util.List;

/**
 * Type information read from annotations.
 */
public class TypeData {
	public final InExRules<String, String, String> rules;
	public final boolean strict;

	public final String[] jsonNames;
	public final String[] realNames;

	public TypeData(final List<String> includes, final List<String> excludes, final boolean strict, final String[] jsonNames, final String[] realNames) {
		rules = new InExRules<>();

		for (String include : includes) {
			rules.include(include);
		}
		for (String exclude : excludes) {
			rules.exclude(exclude);
		}

		this.strict = strict;
		this.jsonNames = jsonNames;
		this.realNames = realNames;
	}

	/**
	 * Resolves real name from JSON name.
	 */
	public String resolveRealName(final String jsonName) {
		if (jsonNames == null) {
			return jsonName;
		}
		int jsonIndex = ArraysUtil.indexOf(jsonNames, jsonName);
		if (jsonIndex == -1) {
			return jsonName;
		}
		return realNames[jsonIndex];
	}

	/**
	 * Resolves JSON name from real name.
	 */
	public String resolveJsonName(final String realName) {
		if (realNames == null) {
			return realName;
		}
		int realIndex = ArraysUtil.indexOf(realNames, realName);
		if (realIndex == -1) {
			return realName;
		}
		return jsonNames[realIndex];
	}
}


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

package jodd.json.meta;

import jodd.cache.TypeCache;
import jodd.introspector.ClassDescriptor;
import jodd.introspector.ClassIntrospector;
import jodd.introspector.FieldDescriptor;
import jodd.introspector.MethodDescriptor;
import jodd.introspector.PropertyDescriptor;
import jodd.util.annotation.AnnotationParser;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;

/**
 * Cached includes and excludes annotation data per type.
 */
public class JsonAnnotationManager {

	private static final JsonAnnotationManager JSON_ANNOTATION_MANAGER = new JsonAnnotationManager();

	/**
	 * Returns instance of this class.
	 */
	public static JsonAnnotationManager get() {
		return JSON_ANNOTATION_MANAGER;
	}

	private final TypeCache<TypeData> typeDataMap = TypeCache.createDefault();
	private boolean serializationSubclassAware;
	private Class<? extends Annotation> jsonAnnotation;

	@SuppressWarnings("unchecked")
	public JsonAnnotationManager() {
		reset();
	}

	/**
	 * Returns all includes for given type. Returns an empty array
	 * when no includes are defined.
	 */
	public TypeData lookupTypeData(final Class type) {
		TypeData typeData = typeDataMap.get(type);

		if (typeData == null) {
			if (serializationSubclassAware) {
				typeData = findSubclassTypeData(type);
			}

			if (typeData == null) {
				typeData = scanClassForAnnotations(type);
				typeDataMap.put(type, typeData);
			}
		}

		return typeData;
	}

	/**
	 * Lookups type data and creates one if missing.
	 */
	protected TypeData _lookupTypeData(final Class type) {
		TypeData typeData = typeDataMap.get(type);

		if (typeData == null) {
			typeData = scanClassForAnnotations(type);
			typeDataMap.put(type, typeData);
		}

		return typeData;
	}

	/**
	 * Finds type data of first annotated superclass or interface.
	 */
	protected TypeData findSubclassTypeData(final Class type) {
		final Class<? extends Annotation> defaultAnnotation = jsonAnnotation;

		if (type.getAnnotation(defaultAnnotation) != null) {
			// current type has annotation, don't find anything, let type data be created
			return null;
		}

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

		// lookup superclasses

		Class[] superClasses = cd.getAllSuperclasses();

		for (Class superClass : superClasses) {
			if (superClass.getAnnotation(defaultAnnotation) != null) {
				// annotated subclass founded!
				return _lookupTypeData(superClass);
			}
		}

		Class[] interfaces = cd.getAllInterfaces();

		for (Class interfaze : interfaces) {
			if (interfaze.getAnnotation(defaultAnnotation) != null) {
				// annotated subclass founded!
				return _lookupTypeData(interfaze);
			}
		}

		return null;
	}

	/**
	 * Returns different name of a property if set by annotation.
	 */
	public String resolveJsonName(final Class type, final String name) {
		TypeData typeData = lookupTypeData(type);

		return typeData.resolveJsonName(name);
	}

	/**
	 * Returns real property name for given JSON property.
	 */
	public String resolveRealName(final Class type, final String jsonName) {
		TypeData typeData = lookupTypeData(type);

		return typeData.resolveRealName(jsonName);
	}

	/**
	 * Scans class for annotations and returns {@link TypeData}.
	 */
	private TypeData scanClassForAnnotations(final Class type) {
		ClassDescriptor cd = ClassIntrospector.get().lookup(type);

		PropertyDescriptor[] pds = cd.getAllPropertyDescriptors();

		ArrayList<String> includedList = new ArrayList<>();
		ArrayList<String> excludedList = new ArrayList<>();
		ArrayList<String> jsonNames = new ArrayList<>();
		ArrayList<String> realNames = new ArrayList<>();

		AnnotationParser annotationParser = JSONAnnotationValues.parserFor(jsonAnnotation);

		for (PropertyDescriptor pd : pds) {
			JSONAnnotationValues data = null;
			{
				MethodDescriptor md = pd.getReadMethodDescriptor();

				if (md != null) {
					Method method = md.getMethod();
					data = JSONAnnotationValues.of(annotationParser, method);
				}
			}

			if (data == null) {
				MethodDescriptor md = pd.getWriteMethodDescriptor();

				if (md != null) {
					Method method = md.getMethod();
					data = JSONAnnotationValues.of(annotationParser, method);
				}
			}

			if (data == null) {
				FieldDescriptor fd = pd.getFieldDescriptor();

				if (fd != null) {
					Field field = fd.getField();
					data = JSONAnnotationValues.of(annotationParser, field);
				}
			}

			if (data != null) {
				// annotation found
				String propertyName = pd.getName();

				String newPropertyName = data.name();
				if (newPropertyName != null) {
					realNames.add(propertyName);
					jsonNames.add(newPropertyName);

					propertyName = newPropertyName;
				}

				if (data.include()) {
					includedList.add(propertyName);
				} else {
					excludedList.add(propertyName);
				}
			}
		}

		String[] reals = null;

		if (!realNames.isEmpty()) {
			reals = realNames.toArray(new String[0]);
		}

		String[] jsons = null;

		if (!jsonNames.isEmpty()) {
			jsons = jsonNames.toArray(new String[0]);
		}

		// type

		JSONAnnotationValues data = JSONAnnotationValues.of(annotationParser, type);

		return new TypeData(includedList, excludedList, data != null && data.strict(), jsons, reals);
	}

	/**
	 * When set searches for first annotated class or interface and use it's data.
	 */
	public JsonAnnotationManager setSerializationSubclassAware(final boolean serializationSubclassAware) {
		this.serializationSubclassAware = serializationSubclassAware;
		return this;
	}

	/**
	 * Sets different annotation.
	 */
	public JsonAnnotationManager setJsonAnnotation(final Class<? extends Annotation> jsonAnnotation) {
		this.jsonAnnotation = jsonAnnotation;
		return this;
	}

	public void reset() {
		typeDataMap.clear();
		serializationSubclassAware = true;
		jsonAnnotation = JSON.class;
	}
}


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

package jodd.io.upload.impl;

import jodd.core.JoddCore;
import jodd.io.FastByteArrayOutputStream;
import jodd.io.FileNameUtil;
import jodd.io.FileUtil;
import jodd.io.StreamUtil;
import jodd.io.upload.FileUpload;
import jodd.io.upload.MultipartRequestInputStream;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Smart {@link FileUpload} implementation that defer the action of what to do with uploaded file
 * for later. Internally, it stores uploaded file either in memory if it is small, or, in all
 * other cases, it stores them in TEMP folder.
 */
public class AdaptiveFileUpload extends FileUpload {

	protected static final String TMP_FILE_SUFFIX = ".upload.tmp";

	protected final int memoryThreshold;
	protected final File uploadPath;
	protected final boolean breakOnError;
	protected final String[] fileExtensions;
	protected final boolean allowFileExtensions;

	AdaptiveFileUpload(final MultipartRequestInputStream input, final int memoryThreshold, final File uploadPath, final int maxFileSize, final boolean breakOnError, final String[] extensions, final boolean allowed) {
		super(input, maxFileSize);
		this.memoryThreshold = memoryThreshold;
		this.uploadPath = uploadPath;
		this.breakOnError = breakOnError;
		this.fileExtensions = extensions;
		this.allowFileExtensions = allowed;
	}

	// ---------------------------------------------------------------- settings

	public int getMemoryThreshold() {
		return memoryThreshold;
	}

	public File getUploadPath() {
		return uploadPath;
	}

	public boolean isBreakOnError() {
		return breakOnError;
	}

	public String[] getFileExtensions() {
		return fileExtensions;
	}

	public boolean isAllowFileExtensions() {
		return allowFileExtensions;
	}

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

	protected File tempFile;
	protected byte[] data;

	/**
	 * Returns true if file upload resides in memory.
	 */
	@Override
	public boolean isInMemory() {
		return data != null;
	}

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

	protected boolean matchFileExtension() throws IOException {
		String fileNameExtension = FileNameUtil.getExtension(getHeader().getFileName());
		for (String fileExtension : fileExtensions) {
			if (fileNameExtension.equalsIgnoreCase(fileExtension)) {
				if (!allowFileExtensions) {	// extension matched and it is not allowed
					if (breakOnError) {
						throw new IOException("Upload filename extension not allowed: " + fileNameExtension);
					}
					size = input.skipToBoundary();
					return false;
				}
				return true;		// extension matched and it is allowed.
			}
		}
		if (allowFileExtensions) {	// extension is not one of the allowed ones.
			if (breakOnError) {
				throw new IOException("Upload filename extension not allowed: " + fileNameExtension);
			}
			size = input.skipToBoundary();
			return false;
		}
		return true;
	}

	/**
	 * Determines if upload is allowed.
	 */
	protected boolean checkUpload() throws IOException {
		if (fileExtensions != null) {
			if (!matchFileExtension()) {
				return false;
			}
		}
		return true;
	}

	@Override
	protected void processStream() throws IOException {
		if (!checkUpload()) {
			return;
		}
		size = 0;
		if (memoryThreshold > 0) {
			FastByteArrayOutputStream fbaos = new FastByteArrayOutputStream(memoryThreshold + 1);
			int written = input.copyMax(fbaos, memoryThreshold + 1);
			data = fbaos.toByteArray();
			if (written <= memoryThreshold) {
				size = data.length;
				valid = true;
				return;
			}
		}

		tempFile = FileUtil.createTempFile(JoddCore.tempFilePrefix, TMP_FILE_SUFFIX, uploadPath);
		BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile));
		if (data != null) {
			size = data.length;
			out.write(data);
			data = null;	// not needed anymore
		}
		boolean deleteTempFile = false;
		try {
			if (maxFileSize == -1) {
				size += input.copyAll(out);
			} else {
				size += input.copyMax(out, maxFileSize - size + 1);		// one more byte to detect larger files
				if (size > maxFileSize) {
					deleteTempFile = true;
					fileTooBig = true;
					valid = false;
					if (breakOnError) {
						throw new IOException("File upload (" + header.getFileName() + ") too big, > " + maxFileSize);
					}
					input.skipToBoundary();
					return;
				}
			}
			valid = true;
		} finally {
			StreamUtil.close(out);
			if (deleteTempFile) {
				tempFile.delete();
				tempFile = null;
			}
		}
	}

	// ---------------------------------------------------------------- operations

	/**
	 * Deletes file uploaded item from disk or memory.
	 */
	public void delete() {
		if (tempFile != null) {
			tempFile.delete();
		}
		if (data != null) {
			data = null;
		}
	}

	/**
	 * Writes file uploaded item.
	 */
	public File write(final String destination) throws IOException {
		return write(new File(destination));
	}

	/**
	 * Writes file upload item to destination folder or to destination file.
	 * Returns the destination file.
	 */
	public File write(File destination) throws IOException {
		if (destination.isDirectory()) {
			destination = new File(destination, this.header.getFileName());
		}
		if (data != null) {
			FileUtil.writeBytes(destination,  data);
		} else {
			if (tempFile != null) {
				FileUtil.move(tempFile, destination);
			}
		}
		return destination;
	}

	/**
	 * Returns the content of file upload item.
	 */
	@Override
	public byte[] getFileContent() throws IOException {
		if (data != null) {
			return data;
		}
		if (tempFile != null) {
			return FileUtil.readBytes(tempFile);
		}
		return null;
	}

	@Override
	public InputStream getFileInputStream() throws IOException {
		if (data != null) {
			return new BufferedInputStream(new ByteArrayInputStream(data));
		}
		if (tempFile != null) {
			return new BufferedInputStream(new FileInputStream(tempFile));
		}
		return null;
	}

}