// 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.log.Logger;
import jodd.log.LoggerFactory;
import jodd.util.ResourceBundleMessageResolver;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Locale;
import java.util.ResourceBundle;

/**
 * Central localization messages manager.
 * @see ResourceBundleMessageResolver
 */
public class LocalizationUtil {

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

	/**
	 * Central message bundle instance.
	 */
	public static final ResourceBundleMessageResolver MESSAGE_RESOLVER = new ResourceBundleMessageResolver() {
		@Override
		public ResourceBundle getBundle(final String bundleName, final Locale locale, final ClassLoader classLoader) {
			if (!isCacheResourceBundles()) {
				//ResourceBundle.clearCache(classLoader);
				clearResourceBundleCache();
			}
			return super.getBundle(bundleName, locale, classLoader);

		}
	};

	static {
		MESSAGE_RESOLVER.addDefaultBundle("messages");
		MESSAGE_RESOLVER.addDefaultBundle("validation");
	}

	// ---------------------------------------------------------------- session/request

	public static final String REQUEST_BUNDLE_NAME_ATTR = ResourceBundleMessageResolver.class.getName() + ".BUNDLE.";
	public static final String SESSION_LOCALE_ATTR = ResourceBundleMessageResolver.class.getName() + ".LOCALE.";

	/**
	 * Sets bundle name for provided servlet request.
	 */
	public static void setRequestBundleName(final ServletRequest request, final String bundleName) {
		if (log.isDebugEnabled()) {
			log.debug("Bundle name for this request: " + bundleName);
		}
		request.setAttribute(REQUEST_BUNDLE_NAME_ATTR, bundleName);
	}

	/**
	 * Saves Locale to HTTP session.
	 */
	public static void setSessionLocale(final HttpSession session, final String localeCode) {
		if (log.isDebugEnabled()) {
			log.debug("Locale stored to session: " + localeCode);
		}
		Locale locale = Locale.forLanguageTag(localeCode);
		session.setAttribute(SESSION_LOCALE_ATTR, locale);
	}

	/**
	 * Returns current locale from session.
s	 */
	public static Locale getSessionLocale(final HttpSession session) {
		Locale locale = (Locale) session.getAttribute(SESSION_LOCALE_ATTR);
		return locale == null ? MESSAGE_RESOLVER.getFallbackLocale() : locale;
	}

	// ---------------------------------------------------------------- delegates

	public static String findMessage(final HttpServletRequest request, final String key) {
		String bundleName = (String) request.getAttribute(REQUEST_BUNDLE_NAME_ATTR);
		Locale locale = (Locale) request.getSession().getAttribute(SESSION_LOCALE_ATTR);
		return MESSAGE_RESOLVER.findMessage(bundleName, locale, key);
	}

	public static String findMessage(final String bundleName, final HttpServletRequest request, final String key) {
		Locale locale = (Locale) request.getSession().getAttribute(SESSION_LOCALE_ATTR);
		return MESSAGE_RESOLVER.findMessage(bundleName, locale, key);
	}

	public static String findMessage(final HttpServletRequest request, final Locale locale, final String key) {
		String bundleName = (String) request.getAttribute(REQUEST_BUNDLE_NAME_ATTR);
		return MESSAGE_RESOLVER.findMessage(bundleName, locale, key);
	}

	public static String findDefaultMessage(final HttpServletRequest request, final String key) {
		Locale locale = (Locale) request.getSession().getAttribute(SESSION_LOCALE_ATTR);
		return MESSAGE_RESOLVER.findDefaultMessage(locale, key);
	}

	public String findMessage(final String bundleName, final Locale locale, final String key) {
		return MESSAGE_RESOLVER.findMessage(bundleName, locale, key);
	}

	public static String findDefaultMessage(final Locale locale, final String key) {
		return MESSAGE_RESOLVER.findDefaultMessage(locale, key);
	}

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

	/**
	 * Clears resource bundle caches.
	 */
	protected static void clearResourceBundleCache() {
		try {
			clearMap(ResourceBundle.class, null, "cacheList");
		} catch (Exception ex) {
			log.warn("Unable to clear resource bundle cache", ex);
		}
	}

	private static void clearMap(final Class mapClass, final Object map, final String fieldName) throws Exception {
		Field field = mapClass.getDeclaredField(fieldName);
		field.setAccessible(true);
		Object cache = field.get(map);
		synchronized (cache) {
			Class ccl = cache.getClass();
			Method clearMethod = ccl.getMethod("clear");
			clearMethod.invoke(cache);
		}
	}

}


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

package jodd.madvoc;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

/**
 * Creates {@link Madvoc} {@link WebApp}. It's an alternative way how to
 * start web application - before any other Jodd framework is used and called.
 */
public class MadvocContextListener implements ServletContextListener {

	protected Madvoc madvoc;

	/**
	 * Creates new {@link Madvoc} {@link WebApp} that is configured
	 * by context init parameters.
	 */
	@Override
	public void contextInitialized(final ServletContextEvent servletContextEvent) {
		ServletContext servletContext = servletContextEvent.getServletContext();

		madvoc = new Madvoc();

		madvoc.configureWith(servletContext);

		madvoc.startWebApplication(servletContext);
	}

	/**
	 * Stops Madvoc.
	 */
	@Override
	public void contextDestroyed(final ServletContextEvent servletContextEvent) {
		madvoc.stopWebApplication();
	}

}


// 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.introspector.ClassDescriptor;
import jodd.introspector.ClassIntrospector;
import jodd.introspector.FieldDescriptor;
import jodd.introspector.MethodDescriptor;
import jodd.introspector.PropertyDescriptor;
import jodd.petite.def.ValueInjectionPoint;
import jodd.petite.meta.PetiteValue;
import jodd.template.ContextTemplateParser;
import jodd.template.MapTemplateParser;
import jodd.util.PropertiesUtil;
import jodd.util.StringPool;

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

/**
 * Parameters storage and resolver. Parameters are injected into beans.
 */
public class ParamManager {

	protected final Map<String, Object> params;
	protected final ContextTemplateParser contextTemplateParser;

	public ParamManager() {
		params = new HashMap<>();
		contextTemplateParser =  new MapTemplateParser().of(params);
	}

	/**
	 * Adds a parameter.
	 */
	public void put(final String name, final Object value) {
		params.put(name, value);
	}

	/**
	 * Returns parameter for given name or null
	 * if not found.
	 */
	public Object get(final String name) {
		return params.get(name);
	}

	public String parseKeyTemplate(final String input) {
		return contextTemplateParser.parse(input);
	}

	/**
	 * Returns an array of param keys that belongs to provided bean.
	 * Optionally resolves the value of returned parameters.
	 */
	public String[] filterParametersForBeanName(String beanName, final boolean resolveReferenceParams) {
		beanName = beanName + '.';

		List<String> list = new ArrayList<>();
		for (Map.Entry<String, Object> entry : params.entrySet()) {
			String key = entry.getKey();
			if (!key.startsWith(beanName)) {
				continue;
			}
			list.add(key);
			if (!resolveReferenceParams) {
				continue;
			}
			// resolve all references
			String value = PropertiesUtil.resolveProperty(params, key);
			entry.setValue(value);
		}
		if (list.isEmpty()) {
			return StringPool.EMPTY_ARRAY;
		} else {
			return list.toArray(new String[0]);
		}
	}

	public ValueInjectionPoint[] resolveParamInjectionPoints(final Class type) {
		final ClassDescriptor cd = ClassIntrospector.get().lookup(type);

		final List<ValueInjectionPoint> valueInjectionPointList = new ArrayList<>();

		for (final PropertyDescriptor pd : cd.getAllPropertyDescriptors()) {
			final FieldDescriptor fd = pd.getFieldDescriptor();

			if (fd != null) {
				final PetiteValue petiteValue = fd.getField().getAnnotation(PetiteValue.class);

				if (petiteValue != null) {
					valueInjectionPointList.add(new ValueInjectionPoint(pd.getName(), petiteValue.value()));
					continue;
				}
			}

			MethodDescriptor md = pd.getWriteMethodDescriptor();
			if (md != null) {
				final PetiteValue petiteValue = md.getMethod().getAnnotation(PetiteValue.class);

				if (petiteValue != null) {
					valueInjectionPointList.add(new ValueInjectionPoint(pd.getName(), petiteValue.value()));
					continue;
				}
			}
		}

		return valueInjectionPointList.toArray(new ValueInjectionPoint[0]);
	}

}


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

/**
 * An {@link AnnotationVisitor} that generates a corresponding 'annotation' or 'type_annotation'
 * structure, as defined in the Java Virtual Machine Specification (JVMS). AnnotationWriter
 * instances can be chained in a doubly linked list, from which Runtime[In]Visible[Type]Annotations
 * attributes can be generated with the {@link #putAnnotations} method. Similarly, arrays of such
 * lists can be used to generate Runtime[In]VisibleParameterAnnotations attributes.
 *
 * @see JVMS
 *     4.7.16
 * @see JVMS
 *     4.7.20
 * @author Eric Bruneton
 * @author Eugene Kuleshov
 */
final class AnnotationWriter extends AnnotationVisitor {

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

  /**
   * Whether values are named or not. AnnotationWriter instances used for annotation default and
   * annotation arrays use unnamed values (i.e. they generate an 'element_value' structure for each
   * value, instead of an element_name_index followed by an element_value).
   */
  private final boolean useNamedValues;

  /**
   * The 'annotation' or 'type_annotation' JVMS structure corresponding to the annotation values
   * visited so far. All the fields of these structures, except the last one - the
   * element_value_pairs array, must be set before this ByteVector is passed to the constructor
   * (num_element_value_pairs can be set to 0, it is reset to the correct value in {@link
   * #visitEnd()}). The element_value_pairs array is filled incrementally in the various visit()
   * methods.
   *
   *
Note: as an exception to the above rules, for AnnotationDefault attributes (which contain a
   * single element_value by definition), this ByteVector is initially empty when passed to the
   * constructor, and {@link #numElementValuePairsOffset} is set to -1.
   */
  private final ByteVector annotation;

  /**
   * The offset in {@link #annotation} where {@link #numElementValuePairs} must be stored (or -1 for
   * the case of AnnotationDefault attributes).
   */
  private final int numElementValuePairsOffset;

  /** The number of element value pairs visited so far. */
  private int numElementValuePairs;

  /**
   * The previous AnnotationWriter. This field is used to store the list of annotations of a
   * Runtime[In]Visible[Type]Annotations attribute. It is unused for nested or array annotations
   * (annotation values of annotation type), or for AnnotationDefault attributes.
   */
  private final AnnotationWriter previousAnnotation;

  /**
   * The next AnnotationWriter. This field is used to store the list of annotations of a
   * Runtime[In]Visible[Type]Annotations attribute. It is unused for nested or array annotations
   * (annotation values of annotation type), or for AnnotationDefault attributes.
   */
  private AnnotationWriter nextAnnotation;

  // -----------------------------------------------------------------------------------------------
  // Constructors
  // -----------------------------------------------------------------------------------------------

  /**
   * Constructs a new {@link AnnotationWriter}.
   *
   * @param symbolTable where the constants used in this AnnotationWriter must be stored.
   * @param useNamedValues whether values are named or not. AnnotationDefault and annotation arrays
   *     use unnamed values.
   * @param annotation where the 'annotation' or 'type_annotation' JVMS structure corresponding to
   *     the visited content must be stored. This ByteVector must already contain all the fields of
   *     the structure except the last one (the element_value_pairs array).
   * @param previousAnnotation the previously visited annotation of the
   *     Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or null in
   *     other cases (e.g. nested or array annotations).
   */
  AnnotationWriter(
      final SymbolTable symbolTable,
      final boolean useNamedValues,
      final ByteVector annotation,
      final AnnotationWriter previousAnnotation) {
    super(Opcodes.ASM7);
    this.symbolTable = symbolTable;
    this.useNamedValues = useNamedValues;
    this.annotation = annotation;
    // By hypothesis, num_element_value_pairs is stored in the last unsigned short of 'annotation'.
    this.numElementValuePairsOffset = annotation.length == 0 ? -1 : annotation.length - 2;
    this.previousAnnotation = previousAnnotation;
    if (previousAnnotation != null) {
      previousAnnotation.nextAnnotation = this;
    }
  }

  /**
   * Constructs a new {@link AnnotationWriter} using named values.
   *
   * @param symbolTable where the constants used in this AnnotationWriter must be stored.
   * @param annotation where the 'annotation' or 'type_annotation' JVMS structure corresponding to
   *     the visited content must be stored. This ByteVector must already contain all the fields of
   *     the structure except the last one (the element_value_pairs array).
   * @param previousAnnotation the previously visited annotation of the
   *     Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or null in
   *     other cases (e.g. nested or array annotations).
   */
  AnnotationWriter(
      final SymbolTable symbolTable,
      final ByteVector annotation,
      final AnnotationWriter previousAnnotation) {
    this(symbolTable, /* useNamedValues = */ true, annotation, previousAnnotation);
  }

  // -----------------------------------------------------------------------------------------------
  // Implementation of the AnnotationVisitor abstract class
  // -----------------------------------------------------------------------------------------------

  @Override
  public void visit(final String name, final Object value) {
    // Case of an element_value with a const_value_index, class_info_index or array_index field.
    // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1.
    ++numElementValuePairs;
    if (useNamedValues) {
      annotation.putShort(symbolTable.addConstantUtf8(name));
    }
    if (value instanceof String) {
      annotation.put12('s', symbolTable.addConstantUtf8((String) value));
    } else if (value instanceof Byte) {
      annotation.put12('B', symbolTable.addConstantInteger(((Byte) value).byteValue()).index);
    } else if (value instanceof Boolean) {
      int booleanValue = ((Boolean) value).booleanValue() ? 1 : 0;
      annotation.put12('Z', symbolTable.addConstantInteger(booleanValue).index);
    } else if (value instanceof Character) {
      annotation.put12('C', symbolTable.addConstantInteger(((Character) value).charValue()).index);
    } else if (value instanceof Short) {
      annotation.put12('S', symbolTable.addConstantInteger(((Short) value).shortValue()).index);
    } else if (value instanceof Type) {
      annotation.put12('c', symbolTable.addConstantUtf8(((Type) value).getDescriptor()));
    } else if (value instanceof byte[]) {
      byte[] byteArray = (byte[]) value;
      annotation.put12('[', byteArray.length);
      for (byte byteValue : byteArray) {
        annotation.put12('B', symbolTable.addConstantInteger(byteValue).index);
      }
    } else if (value instanceof boolean[]) {
      boolean[] booleanArray = (boolean[]) value;
      annotation.put12('[', booleanArray.length);
      for (boolean booleanValue : booleanArray) {
        annotation.put12('Z', symbolTable.addConstantInteger(booleanValue ? 1 : 0).index);
      }
    } else if (value instanceof short[]) {
      short[] shortArray = (short[]) value;
      annotation.put12('[', shortArray.length);
      for (short shortValue : shortArray) {
        annotation.put12('S', symbolTable.addConstantInteger(shortValue).index);
      }
    } else if (value instanceof char[]) {
      char[] charArray = (char[]) value;
      annotation.put12('[', charArray.length);
      for (char charValue : charArray) {
        annotation.put12('C', symbolTable.addConstantInteger(charValue).index);
      }
    } else if (value instanceof int[]) {
      int[] intArray = (int[]) value;
      annotation.put12('[', intArray.length);
      for (int intValue : intArray) {
        annotation.put12('I', symbolTable.addConstantInteger(intValue).index);
      }
    } else if (value instanceof long[]) {
      long[] longArray = (long[]) value;
      annotation.put12('[', longArray.length);
      for (long longValue : longArray) {
        annotation.put12('J', symbolTable.addConstantLong(longValue).index);
      }
    } else if (value instanceof float[]) {
      float[] floatArray = (float[]) value;
      annotation.put12('[', floatArray.length);
      for (float floatValue : floatArray) {
        annotation.put12('F', symbolTable.addConstantFloat(floatValue).index);
      }
    } else if (value instanceof double[]) {
      double[] doubleArray = (double[]) value;
      annotation.put12('[', doubleArray.length);
      for (double doubleValue : doubleArray) {
        annotation.put12('D', symbolTable.addConstantDouble(doubleValue).index);
      }
    } else {
      Symbol symbol = symbolTable.addConstant(value);
      annotation.put12(".s.IFJDCS".charAt(symbol.tag), symbol.index);
    }
  }

  @Override
  public void visitEnum(final String name, final String descriptor, final String value) {
    // Case of an element_value with an enum_const_value field.
    // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1.
    ++numElementValuePairs;
    if (useNamedValues) {
      annotation.putShort(symbolTable.addConstantUtf8(name));
    }
    annotation
        .put12('e', symbolTable.addConstantUtf8(descriptor))
        .putShort(symbolTable.addConstantUtf8(value));
  }

  @Override
  public AnnotationVisitor visitAnnotation(final String name, final String descriptor) {
    // Case of an element_value with an annotation_value field.
    // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1.
    ++numElementValuePairs;
    if (useNamedValues) {
      annotation.putShort(symbolTable.addConstantUtf8(name));
    }
    // Write tag and type_index, and reserve 2 bytes for num_element_value_pairs.
    annotation.put12('@', symbolTable.addConstantUtf8(descriptor)).putShort(0);
    return new AnnotationWriter(symbolTable, annotation, null);
  }

  @Override
  public AnnotationVisitor visitArray(final String name) {
    // Case of an element_value with an array_value field.
    // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1
    ++numElementValuePairs;
    if (useNamedValues) {
      annotation.putShort(symbolTable.addConstantUtf8(name));
    }
    // Write tag, and reserve 2 bytes for num_values. Here we take advantage of the fact that the
    // end of an element_value of array type is similar to the end of an 'annotation' structure: an
    // unsigned short num_values followed by num_values element_value, versus an unsigned short
    // num_element_value_pairs, followed by num_element_value_pairs { element_name_index,
    // element_value } tuples. This allows us to use an AnnotationWriter with unnamed values to
    // visit the array elements. Its num_element_value_pairs will correspond to the number of array
    // elements and will be stored in what is in fact num_values.
    annotation.put12('[', 0);
    return new AnnotationWriter(symbolTable, /* useNamedValues = */ false, annotation, null);
  }

  @Override
  public void visitEnd() {
    if (numElementValuePairsOffset != -1) {
      byte[] data = annotation.data;
      data[numElementValuePairsOffset] = (byte) (numElementValuePairs >>> 8);
      data[numElementValuePairsOffset + 1] = (byte) numElementValuePairs;
    }
  }

  // -----------------------------------------------------------------------------------------------
  // Utility methods
  // -----------------------------------------------------------------------------------------------

  /**
   * Returns the size of a Runtime[In]Visible[Type]Annotations attribute containing this annotation
   * and all its predecessors (see {@link #previousAnnotation}. Also adds the attribute name
   * to the constant pool of the class (if not null).
   *
   * @param attributeName one of "Runtime[In]Visible[Type]Annotations", or null.
   * @return the size in bytes of a Runtime[In]Visible[Type]Annotations attribute containing this
   *     annotation and all its predecessors. This includes the size of the attribute_name_index and
   *     attribute_length fields.
   */
  int computeAnnotationsSize(final String attributeName) {
    if (attributeName != null) {
      symbolTable.addConstantUtf8(attributeName);
    }
    // The attribute_name_index, attribute_length and num_annotations fields use 8 bytes.
    int attributeSize = 8;
    AnnotationWriter annotationWriter = this;
    while (annotationWriter != null) {
      attributeSize += annotationWriter.annotation.length;
      annotationWriter = annotationWriter.previousAnnotation;
    }
    return attributeSize;
  }

  /**
   * Puts a Runtime[In]Visible[Type]Annotations attribute containing this annotations and all its
   * predecessors (see {@link #previousAnnotation} in the given ByteVector. Annotations are
   * put in the same order they have been visited.
   *
   * @param attributeNameIndex the constant pool index of the attribute name (one of
   *     "Runtime[In]Visible[Type]Annotations").
   * @param output where the attribute must be put.
   */
  void putAnnotations(final int attributeNameIndex, final ByteVector output) {
    int attributeLength = 2; // For num_annotations.
    int numAnnotations = 0;
    AnnotationWriter annotationWriter = this;
    AnnotationWriter firstAnnotation = null;
    while (annotationWriter != null) {
      // In case the user forgot to call visitEnd().
      annotationWriter.visitEnd();
      attributeLength += annotationWriter.annotation.length;
      numAnnotations++;
      firstAnnotation = annotationWriter;
      annotationWriter = annotationWriter.previousAnnotation;
    }
    output.putShort(attributeNameIndex);
    output.putInt(attributeLength);
    output.putShort(numAnnotations);
    annotationWriter = firstAnnotation;
    while (annotationWriter != null) {
      output.putByteArray(annotationWriter.annotation.data, 0, annotationWriter.annotation.length);
      annotationWriter = annotationWriter.nextAnnotation;
    }
  }

  /**
   * Returns the size of a Runtime[In]VisibleParameterAnnotations attribute containing all the
   * annotation lists from the given AnnotationWriter sub-array. Also adds the attribute name to the
   * constant pool of the class.
   *
   * @param attributeName one of "Runtime[In]VisibleParameterAnnotations".
   * @param annotationWriters an array of AnnotationWriter lists (designated by their last
   *     element).
   * @param annotableParameterCount the number of elements in annotationWriters to take into account
   *     (elements [0..annotableParameterCount[ are taken into account).
   * @return the size in bytes of a Runtime[In]VisibleParameterAnnotations attribute corresponding
   *     to the given sub-array of AnnotationWriter lists. This includes the size of the
   *     attribute_name_index and attribute_length fields.
   */
  static int computeParameterAnnotationsSize(
      final String attributeName,
      final AnnotationWriter[] annotationWriters,
      final int annotableParameterCount) {
    // Note: attributeName is added to the constant pool by the call to computeAnnotationsSize
    // below. This assumes that there is at least one non-null element in the annotationWriters
    // sub-array (which is ensured by the lazy instantiation of this array in MethodWriter).
    // The attribute_name_index, attribute_length and num_parameters fields use 7 bytes, and each
    // element of the parameter_annotations array uses 2 bytes for its num_annotations field.
    int attributeSize = 7 + 2 * annotableParameterCount;
    for (int i = 0; i < annotableParameterCount; ++i) {
      AnnotationWriter annotationWriter = annotationWriters[i];
      attributeSize +=
          annotationWriter == null ? 0 : annotationWriter.computeAnnotationsSize(attributeName) - 8;
    }
    return attributeSize;
  }

  /**
   * Puts a Runtime[In]VisibleParameterAnnotations attribute containing all the annotation lists
   * from the given AnnotationWriter sub-array in the given ByteVector.
   *
   * @param attributeNameIndex constant pool index of the attribute name (one of
   *     Runtime[In]VisibleParameterAnnotations).
   * @param annotationWriters an array of AnnotationWriter lists (designated by their last
   *     element).
   * @param annotableParameterCount the number of elements in annotationWriters to put (elements
   *     [0..annotableParameterCount[ are put).
   * @param output where the attribute must be put.
   */
  static void putParameterAnnotations(
      final int attributeNameIndex,
      final AnnotationWriter[] annotationWriters,
      final int annotableParameterCount,
      final ByteVector output) {
    // The num_parameters field uses 1 byte, and each element of the parameter_annotations array
    // uses 2 bytes for its num_annotations field.
    int attributeLength = 1 + 2 * annotableParameterCount;
    for (int i = 0; i < annotableParameterCount; ++i) {
      AnnotationWriter annotationWriter = annotationWriters[i];
      attributeLength +=
          annotationWriter == null ? 0 : annotationWriter.computeAnnotationsSize(null) - 8;
    }
    output.putShort(attributeNameIndex);
    output.putInt(attributeLength);
    output.putByte(annotableParameterCount);
    for (int i = 0; i < annotableParameterCount; ++i) {
      AnnotationWriter annotationWriter = annotationWriters[i];
      AnnotationWriter firstAnnotation = null;
      int numAnnotations = 0;
      while (annotationWriter != null) {
        // In case user the forgot to call visitEnd().
        annotationWriter.visitEnd();
        numAnnotations++;
        firstAnnotation = annotationWriter;
        annotationWriter = annotationWriter.previousAnnotation;
      }
      output.putShort(numAnnotations);
      annotationWriter = firstAnnotation;
      while (annotationWriter != null) {
        output.putByteArray(
            annotationWriter.annotation.data, 0, annotationWriter.annotation.length);
        annotationWriter = annotationWriter.nextAnnotation;
      }
    }
  }
}


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

import jodd.util.CollectionUtil;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.Consumer;

/**
 * Props iterator builder. Should be used with: {@link jodd.props.Props#entries()}.
 */
public final class PropsEntries {

	private final PropsIterator propsIterator;
	private final Props props;

	public PropsEntries(final Props props) {
		this.props = props;
		this.propsIterator = new PropsIterator();
	}

	/**
	 * Enables profile to iterate.
	 */
	public PropsEntries profile(final String profile) {
		addProfiles(profile);
		return this;
	}
	/**
	 * Enables profiles to iterate.
	 */
	public PropsEntries profile(final String... profiles) {
		if (profiles == null) {
			return this;
		}
		for (String profile : profiles) {
			addProfiles(profile);
		}
		return this;
	}

	/**
	 * Enables active profiles to iterate over.
	 */
	public PropsEntries activeProfiles() {
		profile(props.activeProfiles);
		return this;
	}

	private void addProfiles(final String profile) {
		if (propsIterator.profiles == null) {
			propsIterator.profiles = new ArrayList<>();
		}
		propsIterator.profiles.add(profile);
	}

	/**
	 * Enables section to iterate.
	 */
	public PropsEntries section(final String section) {
		addSection(section);
		return this;
	}
	/**
	 * Enables sections to iterate.
	 */
	public PropsEntries section(final String... section) {
		for (String s : section) {
			addSection(s);
		}
		return this;
	}

	private void addSection(final String section) {
		if (propsIterator.sections == null) {
			propsIterator.sections = new ArrayList<>();
		}
		propsIterator.sections.add(section + '.');
	}

	/**
	 * Skips duplicate keys (defined in different profiles) which value is not
	 * used for setting current key value.
	 */
	public PropsEntries skipDuplicatesByValue() {
		propsIterator.skipDuplicatesByValue = true;
		propsIterator.skipDuplicatesByPosition = false;
		return this;
	}

	/**
	 * Skips all keys after first definition, even if value is set later.
	 */
	public PropsEntries skipDuplicatesByPosition() {
		propsIterator.skipDuplicatesByPosition = true;
		propsIterator.skipDuplicatesByValue = false;
		return this;
	}

	/**
	 * Returns populated iterator.
	 */
	public Iterator<PropsEntry> iterator() {
		return propsIterator;
	}

	/**
	 * Consumer all properties.
	 */
	public void forEach(final Consumer<PropsEntry> propsDataConsumer) {
		CollectionUtil.streamOf(propsIterator).forEach(propsDataConsumer);
	}

	// ---------------------------------------------------------------- iterator

	/**
	 * Props iterator.
	 */
	private class PropsIterator implements Iterator<PropsEntry> {
		private PropsEntry next = props.data.first;
		private boolean firstTime = true;
		private List<String> profiles;
		private List<String> sections;
		private boolean skipDuplicatesByValue;
		private boolean skipDuplicatesByPosition;
		private Set<String> keys;

		@Override
		public boolean hasNext() {
			if (firstTime) {
				start();
			}
			return next != null;
		}

		/**
		 * Starts with the iterator.
		 */
		private void start() {
			firstTime = false;

			while (!accept(next)) {
				if (next == null) {
					break;
				}
				next = next.next;		// funny :)))
			}
		}

		/**
		 * Accepts an entry and returns true
		 * if entry should appear in this iteration.
		 */
		private boolean accept(final PropsEntry entry) {
			if (entry == null) {
				return false;
			}
			if (profiles != null) {
				if (entry.getProfile() != null) {
					boolean found = false;
					for (String profile : profiles) {
						if (entry.getProfile().equals(profile)) {
							found = true;
							break;
						}
					}
					if (!found) {
						return false;
					}
				}
			} else {
				// ignore all profile keys if only a base profile is active
				if (entry.getProfile() != null) {
					return false;
				}
			}

			if (sections != null) {
				boolean found = false;
				for (String section : sections) {
					if (entry.getKey().startsWith(section)) {
						found = true;
						break;
					}
				}
				if (!found) {
					return false;
				}
			}

			if (profiles != null) {
				if (skipDuplicatesByValue) {
					String thisProfile = entry.getProfile();

					// iterate all profiles before this one
					for (String profile : profiles) {
						if (profile.equals(thisProfile)) {
							// if we came to this point, there is no
							// property defined in higher profile
							// therefore this one is the most important
							return true;
						}

						// check if key exist in higher profile
						Map<String, PropsEntry> profileMap = props.data.profileProperties.get(profile);
						if (profileMap == null) {
							continue;
						}
						if (profileMap.containsKey(entry.getKey())) {
							// duplicate key exist in higher profile, therefore this one is less important
							return false;
						}
					}
				}
				if (skipDuplicatesByPosition) {
					if (keys == null) {
						keys = new HashSet<>();
					}
					if (!keys.add(entry.getKey())) {
						return false;		// the key was already there
					}
				}
			}

			return true;
		}

		@Override
		public PropsEntry next() {
			if (firstTime) {
				start();
			}

			if (next == null) {
				throw new NoSuchElementException();
			}

			PropsEntry returnValue =  next;

			while (next != null) {
				next = next.next;

				if (accept(next)) {
					break;
				}
			}

			return returnValue;
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}
	}

}


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

package jodd.crypt;

import jodd.util.StringUtil;

/**
 * MurmurHash3 128bits.
 * Adaptation of http://github.com/yonik/java_util for future updates to this file.
 */
public class MurmurHash3 {

	/**
	 * 128 bits of state.
	 */
	public static final class HashValue {
		public final long val1;
		public final long val2;

		public HashValue(final long val1, final long val2) {
			this.val1 = val1;
			this.val2 = val2;
		}

		@Override
		public String toString() {
			return Long.toHexString(val1) + Long.toHexString(val2);
		}
	}

	public static long fmix64(long k) {
		k ^= k >>> 33;
		k *= 0xff51afd7ed558ccdL;
		k ^= k >>> 33;
		k *= 0xc4ceb9fe1a85ec53L;
		k ^= k >>> 33;
		return k;
	}

	/**
	 * Gets a long from a byte buffer in little endian byte order.
	 */
	public static long getLongLittleEndian(final byte[] buf, final int offset) {
		return ((long) buf[offset + 7] << 56)   // no mask needed
			| ((buf[offset + 6] & 0xffL) << 48)
			| ((buf[offset + 5] & 0xffL) << 40)
			| ((buf[offset + 4] & 0xffL) << 32)
			| ((buf[offset + 3] & 0xffL) << 24)
			| ((buf[offset + 2] & 0xffL) << 16)
			| ((buf[offset + 1] & 0xffL) << 8)
			| ((buf[offset] & 0xffL));        // no shift needed
	}

	public static HashValue murmurhash3_x64_128(final String str, final int seed) {
		byte[] bytes = StringUtil.getBytes(str);
		return murmurhash3_x64_128(bytes, 0, bytes.length, seed);
	}

	/**
	 * Returns the MurmurHash3_x64_128 hash.
	 */
	public static HashValue murmurhash3_x64_128(final byte[] key, final int offset, final int len, final int seed) {
		// The original algorithm does have a 32 bit unsigned seed.
		// We have to mask to match the behavior of the unsigned types and prevent sign extension.
		long h1 = seed & 0x00000000FFFFFFFFL;
		long h2 = seed & 0x00000000FFFFFFFFL;

		final long c1 = 0x87c37b91114253d5L;
		final long c2 = 0x4cf5ad432745937fL;

		int roundedEnd = offset + (len & 0xFFFFFFF0);  // round down to 16 byte block
		for (int i = offset; i < roundedEnd; i += 16) {
			long k1 = getLongLittleEndian(key, i);
			long k2 = getLongLittleEndian(key, i + 8);
			k1 *= c1;
			k1 = Long.rotateLeft(k1, 31);
			k1 *= c2;
			h1 ^= k1;
			h1 = Long.rotateLeft(h1, 27);
			h1 += h2;
			h1 = h1 * 5 + 0x52dce729;
			k2 *= c2;
			k2 = Long.rotateLeft(k2, 33);
			k2 *= c1;
			h2 ^= k2;
			h2 = Long.rotateLeft(h2, 31);
			h2 += h1;
			h2 = h2 * 5 + 0x38495ab5;
		}

		long k1 = 0;
		long k2 = 0;

		switch (len & 15) {
			case 15:
				k2 = (key[roundedEnd + 14] & 0xffL) << 48;
			case 14:
				k2 |= (key[roundedEnd + 13] & 0xffL) << 40;
			case 13:
				k2 |= (key[roundedEnd + 12] & 0xffL) << 32;
			case 12:
				k2 |= (key[roundedEnd + 11] & 0xffL) << 24;
			case 11:
				k2 |= (key[roundedEnd + 10] & 0xffL) << 16;
			case 10:
				k2 |= (key[roundedEnd + 9] & 0xffL) << 8;
			case 9:
				k2 |= (key[roundedEnd + 8] & 0xffL);
				k2 *= c2;
				k2 = Long.rotateLeft(k2, 33);
				k2 *= c1;
				h2 ^= k2;
			case 8:
				k1 = ((long) key[roundedEnd + 7]) << 56;
			case 7:
				k1 |= (key[roundedEnd + 6] & 0xffL) << 48;
			case 6:
				k1 |= (key[roundedEnd + 5] & 0xffL) << 40;
			case 5:
				k1 |= (key[roundedEnd + 4] & 0xffL) << 32;
			case 4:
				k1 |= (key[roundedEnd + 3] & 0xffL) << 24;
			case 3:
				k1 |= (key[roundedEnd + 2] & 0xffL) << 16;
			case 2:
				k1 |= (key[roundedEnd + 1] & 0xffL) << 8;
			case 1:
				k1 |= (key[roundedEnd] & 0xffL);
				k1 *= c1;
				k1 = Long.rotateLeft(k1, 31);
				k1 *= c2;
				h1 ^= k1;
		}

		//----------
		// finalization

		h1 ^= len;
		h2 ^= len;

		h1 += h2;
		h2 += h1;

		h1 = fmix64(h1);
		h2 = fmix64(h2);

		h1 += h2;
		h2 += h1;

		return new HashValue(h1, h2);
	}

}