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

package jodd.util;

/**
 * Base32 encoding. Quite fast.
 */
public class Base32 {

	private static final String ERR_CANONICAL_LEN = "Invalid Base32 string length";
	private static final String ERR_CANONICAL_END = "Invalid end bits of Base32 string";
	private static final String ERR_INVALID_CHARS = "Invalid character in Base32 string";

	private static final char[] CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".toCharArray();

	private static final byte[] LOOKUP = {
			26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1,			// 0123456789:;<=>?
			-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,			// @ABCDEFGHIJKLMNO
			15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, // PQRSTUVWXYZ[\]^_
			-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,			// `abcdefghijklmno
			15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25						// pqrstuvwxyz
	};

	/**
	 * Encode an array of binary bytes into a Base32 string.
	 */
	public static String encode(final byte[] bytes) {
		StringBuilder base32 = new StringBuilder((bytes.length * 8 + 4) / 5);

		int currByte, digit, i = 0;
		while (i < bytes.length) {

			// STEP 0; insert new 5 bits, leave 3 bits
			currByte = bytes[i++] & 255;
			base32.append(CHARS[currByte >> 3]);
			digit = (currByte & 7) << 2;
			if (i >= bytes.length) {
				base32.append(CHARS[digit]);
				break;
			}

			// STEP 3: insert 2 new bits, then 5 bits, leave 1 bit
			currByte = bytes[i++] & 255;
			base32.append(CHARS[digit | (currByte >> 6)]);
			base32.append(CHARS[(currByte >> 1) & 31]);
			digit = (currByte & 1) << 4;
			if (i >= bytes.length) {
				base32.append(CHARS[digit]);
				break;
			}

			// STEP 1: insert 4 new bits, leave 4 bit
			currByte = bytes[i++] & 255;
			base32.append(CHARS[digit | (currByte >> 4)]);
			digit = (currByte & 15) << 1;
			if (i >= bytes.length) {
				base32.append(CHARS[digit]);
				break;
			}

			// STEP 4: insert 1 new bit, then 5 bits, leave 2 bits
			currByte = bytes[i++] & 255;
			base32.append(CHARS[digit | (currByte >> 7)]);
			base32.append(CHARS[(currByte >> 2) & 31]);
			digit = (currByte & 3) << 3;
			if (i >= bytes.length) {
				base32.append(CHARS[digit]);
				break;
			}

			// STEP 2: insert 3 new bits, then 5 bits, leave 0 bit
			currByte = bytes[i++] & 255;
			base32.append(CHARS[digit | (currByte >> 5)]);
			base32.append(CHARS[currByte & 31]);
		}
		return base32.toString();
	}

	/**
	 * Decode a Base32 string into an array of binary bytes.
	 */
	public static byte[] decode(final String base32) throws IllegalArgumentException {
		switch (base32.length() % 8) {
			case 1:
			case 3:
			case 6:
				throw new IllegalArgumentException(ERR_CANONICAL_LEN);
		}

		byte[] bytes = new byte[base32.length() * 5 / 8];
		int offset = 0, i = 0, lookup;
		byte nextByte, digit;

		while (i < base32.length()) {
			lookup = base32.charAt(i++) - '2';
			if (lookup < 0 || lookup >= LOOKUP.length) {
				throw new IllegalArgumentException(ERR_INVALID_CHARS);
			}
			digit = LOOKUP[lookup];
			if (digit == -1) {
				throw new IllegalArgumentException(ERR_INVALID_CHARS);
			}

			// STEP n = 0: leave 5 bits
			nextByte = (byte) (digit << 3);
			lookup = base32.charAt(i++) - '2';
			if (lookup < 0 || lookup >= LOOKUP.length) {
				throw new IllegalArgumentException(ERR_INVALID_CHARS);
			}
			digit = LOOKUP[lookup];
			if (digit == -1) {
				throw new IllegalArgumentException(ERR_INVALID_CHARS);
			}

			// STEP n = 5: insert 3 bits, leave 2 bits
			bytes[offset++] = (byte) (nextByte | (digit >> 2));
			nextByte = (byte) ((digit & 3) << 6);
			if (i >= base32.length()) {
				if (nextByte != (byte) 0) {
					throw new IllegalArgumentException(ERR_CANONICAL_END);
				}
				break;
			}
			lookup = base32.charAt(i++) - '2';
			if (lookup < 0 || lookup >= LOOKUP.length) {
				throw new IllegalArgumentException(ERR_INVALID_CHARS);
			}
			digit = LOOKUP[lookup];
			if (digit == -1) {
				throw new IllegalArgumentException(ERR_INVALID_CHARS);
			}

			// STEP n = 2: leave 7 bits
			nextByte |= (byte) (digit << 1);
			lookup = base32.charAt(i++) - '2';
			if (lookup < 0 || lookup >= LOOKUP.length) {
				throw new IllegalArgumentException(ERR_INVALID_CHARS);
			}
			digit = LOOKUP[lookup];
			if (digit == -1) {
				throw new IllegalArgumentException(ERR_INVALID_CHARS);
			}

			// STEP n = 7: insert 1 bit, leave 4 bits
			bytes[offset++] = (byte) (nextByte | (digit >> 4));
			nextByte = (byte) ((digit & 15) << 4);
			if (i >= base32.length()) {
				if (nextByte != (byte) 0) {
					throw new IllegalArgumentException(ERR_CANONICAL_END);
				}
				break;
			}
			lookup = base32.charAt(i++) - '2';
			if (lookup < 0 || lookup >= LOOKUP.length) {
				throw new IllegalArgumentException(ERR_INVALID_CHARS);
			}
			digit = LOOKUP[lookup];
			if (digit == -1) {
				throw new IllegalArgumentException(ERR_INVALID_CHARS);
			}

			// STEP n = 4: insert 4 bits, leave 1 bit
			bytes[offset++] = (byte) (nextByte | (digit >> 1));
			nextByte = (byte) ((digit & 1) << 7);
			if (i >= base32.length()) {
				if (nextByte != (byte) 0) {
					throw new IllegalArgumentException(ERR_CANONICAL_END);
				}
				break;
			}
			lookup = base32.charAt(i++) - '2';
			if (lookup < 0 || lookup >= LOOKUP.length) {
				throw new IllegalArgumentException(ERR_INVALID_CHARS);
			}
			digit = LOOKUP[lookup];
			if (digit == -1) {
				throw new IllegalArgumentException(ERR_INVALID_CHARS);
			}

			// STEP n = 1: leave 6 bits
			nextByte |= (byte) (digit << 2);
			lookup = base32.charAt(i++) - '2';
			if (lookup < 0 || lookup >= LOOKUP.length) {
				throw new IllegalArgumentException(ERR_INVALID_CHARS);
			}
			digit = LOOKUP[lookup];
			if (digit == -1) {
				throw new IllegalArgumentException(ERR_INVALID_CHARS);
			}

			// STEP n = 6: insert 2 bits, leave 3 bits
			bytes[offset++] = (byte) (nextByte | (digit >> 3));
			nextByte = (byte) ((digit & 7) << 5);
			if (i >= base32.length()) {
				if (nextByte != (byte) 0) {
					throw new IllegalArgumentException(ERR_CANONICAL_END);
				}
				break;
			}
			lookup = base32.charAt(i++) - '2';
			if (lookup < 0 || lookup >= LOOKUP.length) {
				throw new IllegalArgumentException(ERR_INVALID_CHARS);
			}
			digit = LOOKUP[lookup];
			if (digit == -1) {
				throw new IllegalArgumentException(ERR_INVALID_CHARS);
			}

			// STEP n = 3: insert 5 bits, leave 0 bit
			bytes[offset++] = (byte) (nextByte | digit);
		}
		return bytes;
	}

}


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

import jodd.io.FileUtil;
import jodd.typeconverter.TypeConversionException;
import jodd.typeconverter.TypeConverter;
import jodd.typeconverter.TypeConverterManager;
import jodd.util.StringUtil;

import java.io.File;
import java.io.IOException;
import java.sql.Blob;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;

/**
 * Converts given object to byte[].
 */
public class ByteArrayConverter implements TypeConverter<byte[]> {

	protected final TypeConverterManager typeConverterManager;

	public ByteArrayConverter(final TypeConverterManager typeConverterManager) {
		this.typeConverterManager = typeConverterManager;
	}

	@Override
	public byte[] convert(final Object value) {
		if (value == null) {
			return null;
		}

		final Class valueClass = value.getClass();

		if (!valueClass.isArray()) {
			// source is not an array
			return convertValueToArray(value);
		}

		// source is an array
		return convertArrayToArray(value);
	}

	/**
	 * Converts type using type converter manager.
	 */
	protected byte convertType(final Object value) {
		return typeConverterManager.convertType(value, byte.class).byteValue();
	}

	/**
	 * Creates an array with single element.
	 */
	protected byte[] convertToSingleElementArray(final Object value) {
		return new byte[] {convertType(value)};
	}

	/**
	 * Converts non-array value to array. Detects various
	 * types and collections, iterates them to make conversion
	 * and to create target array.
 	 */
	protected byte[] convertValueToArray(final Object value) {
		if (value instanceof Blob) {
			final Blob blob = (Blob) value;
			try {
				final long length = blob.length();
				if (length > Integer.MAX_VALUE) {
					throw new TypeConversionException("Blob is too big.");
				}
				return blob.getBytes(1, (int) length);
			} catch (SQLException sex) {
				throw new TypeConversionException(value, sex);
			}
		}

		if (value instanceof File) {
			try {
				return FileUtil.readBytes((File) value);
			} catch (IOException ioex) {
				throw new TypeConversionException(value, ioex);
			}
		}

		if (value instanceof Collection) {
			final Collection collection = (Collection) value;
			final byte[] target = new byte[collection.size()];

			int i = 0;
			for (final Object element : collection) {
				target[i] = convertType(element);
				i++;
			}

			return target;
		}

		if (value instanceof Iterable) {
			final Iterable iterable = (Iterable) value;

			final ArrayList<Byte> byteArrayList = new ArrayList<>();

			for (final Object element : iterable) {
				final byte convertedValue = convertType(element);
				byteArrayList.add(Byte.valueOf(convertedValue));
			}

			final byte[] array = new byte[byteArrayList.size()];

			for (int i = 0; i < byteArrayList.size(); i++) {
				final Byte b = byteArrayList.get(i);
				array[i] = b.byteValue();
			}

			return array;
		}

		if (value instanceof CharSequence) {
			final String[] strings = StringUtil.splitc(value.toString(), ArrayConverter.NUMBER_DELIMITERS);
			return convertArrayToArray(strings);
		}

		// everything else:
		return convertToSingleElementArray(value);
	}

	/**
	 * Converts array value to array.
	 */
	protected byte[] convertArrayToArray(final Object value) {
		final Class valueComponentType = value.getClass().getComponentType();

		final byte[] result;

		if (valueComponentType.isPrimitive()) {
			result = convertPrimitiveArrayToArray(value, valueComponentType);
		} else {
			// convert object array to target array
			final Object[] array = (Object[]) value;
			result = new byte[array.length];

			for (int i = 0; i < array.length; i++) {
				result[i] = convertType(array[i]);
			}
		}

		return result;
	}

	/**
	 * Converts primitive array to target array.
	 */
	protected byte[] convertPrimitiveArrayToArray(final Object value, final Class primitiveComponentType) {
		byte[] result = null;

		if (primitiveComponentType == byte.class) {
			return (byte[]) value;
		}

		if (primitiveComponentType == int.class) {
			final int[] array = (int[]) value;
			result = new byte[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = (byte) array[i];
			}
		}
		else if (primitiveComponentType == long.class) {
			final long[] array = (long[]) value;
			result = new byte[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = (byte) array[i];
			}
		}
		else if (primitiveComponentType == float.class) {
			final float[] array = (float[]) value;
			result = new byte[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = (byte) array[i];
			}
		}
		else if (primitiveComponentType == double.class) {
			final double[] array = (double[]) value;
			result = new byte[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = (byte) array[i];
			}
		}
		else if (primitiveComponentType == short.class) {
			final short[] array = (short[]) value;
			result = new byte[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = (byte) array[i];
			}
		}
		else if (primitiveComponentType == char.class) {
			final char[] array = (char[]) value;
			result = new byte[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = (byte) array[i];
			}
		}
		else if (primitiveComponentType == boolean.class) {
			final boolean[] array = (boolean[]) value;
			result = new byte[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = (byte) (array[i] ? 1 : 0);
			}
		}
		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.servlet.filter;

import jodd.io.FileNameUtil;
import jodd.servlet.ServletUtil;
import jodd.typeconverter.Converter;
import jodd.typeconverter.TypeConversionException;
import jodd.util.StringPool;
import jodd.util.StringUtil;
import jodd.util.Wildcard;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Compresses output with GZIP, for browsers that supports it.
 *

 * Configuration is based on the following initialization parameters:
 *
 *

 *
threshold - min number of bytes for compressing
 * or 0 for no compression at all. By defaults is 0. Good value is 128.

 *
 *
match - comma separated string patterns to be found
 * in the uri for using gzip. Only uris that match these patterns will be gzipped.
 * Use '*' to enable default matching using just extensions.
 * extensions (ignoring the wildcards value)

 *
 *
extensions - when match is set to all resources,
 * this parameter defines list of URI extensions that should be gzipped.
 * By default set to: html, htm, css, js. Use '*' to
 * match all extensions.
 *

 *
 *
exclude - comma separated string patterns to be excluded
 * if found in uri for using gzip. It is applied only if all urls are matched.

 *
 *
wildcards - boolean that specifies wildcard matching for string patterns.
 * by default false. URL is matched as {@link Wildcard#matchPathOne(String, String[]) paths}.

 *
 *
requestParameterName - name of request parameter that can override GZipping.
 * Default value is gzip. Set it to an empty string to turn this feature off.
 *

 *
 *

 *
 * All matching is done in lowercase. You can override this class for finer control.
 */
public class GzipFilter implements Filter {

	/**
	 * If browser supports gzip, sets the Content-Encoding response header and
	 * invoke resource with a wrapped response that collects all the output.
	 * Extracts the output and write it into a gzipped byte array. Finally, write
	 * that array to the client's output stream.
	 *

	 * If browser does not support gzip, invokes resource normally.
	 */
	@Override
	public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws ServletException, IOException {
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse res = (HttpServletResponse) response;

		if (
				(threshold == 0) ||
				(!ServletUtil.isGzipSupported(req)) ||
				(!isGzipEligible(req))
		) {
			chain.doFilter(request, response);
			return;
		}

		GzipResponseWrapper wrappedResponse = new GzipResponseWrapper(res);
		wrappedResponse.setCompressionThreshold(threshold);

		try {
			chain.doFilter(request, wrappedResponse);
		} finally {
			wrappedResponse.finishResponse();
		}
	}

    protected int threshold;			// the threshold number to compress, (0 == no compression).
	protected String[] matches;
	protected String[] excludes;
	protected boolean wildcards;
	protected String requestParameterName;
	protected String[] extensions;

	/**
	 * Filter initialization.
	 */
	@Override
	public void init(final FilterConfig config) {

		try {
			wildcards = Converter.get().toBooleanValue(config.getInitParameter("wildcards"), false);
		} catch (TypeConversionException ignore) {
			wildcards = false;
		}

		// min size
		try {
			threshold = Converter.get().toIntValue(config.getInitParameter("threshold"), 0);
		} catch (TypeConversionException ignore) {
			threshold = 0;
		}

		// match string
		String uriMatch = config.getInitParameter("match");

		if ((uriMatch != null) && (!uriMatch.equals(StringPool.STAR))) {
			matches = StringUtil.splitc(uriMatch, ',');
			for (int i = 0; i < matches.length; i++) {
				matches[i] = matches[i].trim();
			}
		}

		// exclude string
		String uriExclude = config.getInitParameter("exclude");

		if (uriExclude != null) {
			excludes = StringUtil.splitc(uriExclude, ',');
			for (int i = 0; i < excludes.length; i++) {
				excludes[i] = excludes[i].trim();
			}
		}

		// request parameter name
		requestParameterName = config.getInitParameter("requestParameterName");

		if (requestParameterName == null) {
			requestParameterName = "gzip";
		}

		requestParameterName = requestParameterName.trim();

		// allowed extensions

		String urlExtensions = config.getInitParameter("extensions");

		if (urlExtensions != null) {
			if (urlExtensions.equals(StringPool.STAR)) {
				extensions = null;
			} else {
				extensions = StringUtil.splitc(urlExtensions, ", ");
			}
		} else {
			extensions = new String[] {"html", "htm", "js", "css"};
		}

	}

	@Override
	public void destroy() {
	}

	/**
	 * Determine if request is eligible for GZipping.
	 */
	protected boolean isGzipEligible(final HttpServletRequest request) {
		// request parameter name

		if (requestParameterName.length() != 0) {
			String forceGzipString = request.getParameter(requestParameterName);

			if (forceGzipString != null) {
				return Converter.get().toBooleanValue(forceGzipString, false);
			}
		}

		// extract uri

		String uri = request.getRequestURI();

		if (uri == null) {
			return false;
		}

		uri = uri.toLowerCase();

		boolean result = false;

		// check uri

		if (matches == null) {					// match == *
			if (extensions == null) {			// extensions == *
				return true;
			}
			// extension
			String extension = FileNameUtil.getExtension(uri);

			if (extension.length() > 0) {
				extension = extension.toLowerCase();

				if (StringUtil.equalsOne(extension, extensions) != -1) {
					result = true;
				}
			}
		} else {
			if (wildcards) {
				result = Wildcard.matchPathOne(uri, matches) != -1;
			} else {
				for (String match : matches) {
					if (uri.contains(match)) {
						result = true;
						break;
					}
				}
			}
		}

		if ((result) && (excludes != null)) {
			if (wildcards) {
				if (Wildcard.matchPathOne(uri, excludes) != -1) {
					result = false;
				}
			} else {
				for (String exclude : excludes) {
					if (uri.contains(exclude)) {
						result = false;						// excludes founded
						break;
					}
				}
			}
		}

		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;

import jodd.bean.BeanUtil;
import jodd.util.StringUtil;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

/**
 * Vtor validator.
 */
public class Vtor {

	/**
	 * Static constructor for fluent usage.
	 */
	public static Vtor create() {
		return new Vtor();
	}

	public static final String DEFAULT_PROFILE = "default";
	public static final String ALL_PROFILES = "*";

	// ---------------------------------------------------------------- violations

	protected List<Violation> violations;

	/**
	 * Adds new {@link Violation violation}. Violations are added during {@link #validate(ValidationContext, Object, String) validation}.
	 * They can be added after the validation as well, with null check (and constraint).
	 */
	public void addViolation(final Violation v) {
		if (v == null) {
			return;
		}
		if (violations == null) {
			violations = new ArrayList<>();
		}
		violations.add(v);
	}

	/**
	 * Resets list of all violations.
	 */
	public void resetViolations() {
		violations = null;
	}

	// ---------------------------------------------------------------- validation

	/**
	 * Validate object using context from the annotations.
	 */
	public List<Violation> validate(final Object target) {
		return validate(ValidationContext.resolveFor(target.getClass()), target);
	}

	/**
	 * @see #validate(ValidationContext, Object, String)
	 */
	public List<Violation> validate(final ValidationContext vctx, final Object target) {
		return validate(vctx, target, null);
	}

	/**
	 * Performs validation of provided validation context and appends violations.
	 */
	public List<Violation> validate(final ValidationContext ctx, final Object target, final String targetName) {
		for (Map.Entry<String, List<Check>> entry : ctx.map.entrySet()) {
			String name = entry.getKey();
			Object value = BeanUtil.declaredSilent.getProperty(target, name);
			String valueName = targetName != null ? (targetName + '.' + name) : name;		// move up
			ValidationConstraintContext vcc = new ValidationConstraintContext(this, target, valueName);

			for (Check check : entry.getValue()) {
				String[] checkProfiles = check.getProfiles();
				if (!matchProfiles(checkProfiles)) {
					continue;
				}
				if (check.getSeverity() < severity) {
					continue;
				}
				ValidationConstraint constraint = check.getConstraint();
				if (!constraint.isValid(vcc, value)) {
					addViolation(new Violation(valueName, target, value, check));
				}
			}
		}

		return getViolations();
	}

	// ---------------------------------------------------------------- severity

	protected int severity;

	/**
	 * Set validation severity. Only checks with equal and higher severity
	 * will be checked.
	 */
	public void setSeverity(final int severity) {
		this.severity = severity;
	}

	// ---------------------------------------------------------------- profiles

	protected HashSet<String> enabledProfiles;

	protected boolean validateAllProfilesByDefault;

	public boolean isValidateAllProfilesByDefault() {
		return validateAllProfilesByDefault;
	}

	/**
	 * Specifies how to validate when no profiles is specified.
	 * If set to true, then all profiles will be validated;
	 * otherwise, only default profiles will be validated.
	 */
	public void setValidateAllProfilesByDefault(final boolean validateAllProfilesByDefault) {
		this.validateAllProfilesByDefault = validateAllProfilesByDefault;
	}

	/**
	 * Enables single profile.
	 */
	public void useProfile(final String profile) {
		if (profile == null) {
			return;
		}
		if (this.enabledProfiles == null) {
			this.enabledProfiles = new HashSet<>();
		}
		this.enabledProfiles.add(profile);
	}

	/**
	 * Enables list of profiles.
	 */
	public void useProfiles(final String... enabledProfiles) {
		if (enabledProfiles == null) {
			return;
		}
		if (this.enabledProfiles == null) {
			this.enabledProfiles = new HashSet<>();
		}
		Collections.addAll(this.enabledProfiles, enabledProfiles);
	}

	/**
	 * Reset profiles by clearing all enabled profiles
	 * and setting to default state.
	 * @see #setValidateAllProfilesByDefault(boolean)
	 */
	public void resetProfiles() {
		enabledProfiles = null;
	}

	/**
	 * Determine if any of checks profiles is among enabled profiles.
	 */
	protected boolean matchProfiles(final String[] checkProfiles) {
		// test for all profiles
		if ((checkProfiles != null) && (checkProfiles.length == 1) && checkProfiles[0].equals(ALL_PROFILES)) {
			return true;
		}
		if (enabledProfiles == null || enabledProfiles.isEmpty()) {
			if (validateAllProfilesByDefault) {
				return true;	// all profiles are considered as enabled
			}
			// only default profile is enabled
			if ((checkProfiles == null) || (checkProfiles.length == 0)) {
				return true;
			}
			for (String profile : checkProfiles) {
				if (StringUtil.isEmpty(profile)) {
					return true;	// default profile
				}
				if (profile.equals(DEFAULT_PROFILE)) {
					return true;
				}
			}
			return false;
		}
		// there are enabled profiles
		if ((checkProfiles == null) || (checkProfiles.length == 0)) {
			return enabledProfiles.contains(DEFAULT_PROFILE);
		}
		boolean result = false;
		for (String profile : checkProfiles) {
			boolean b = true;
			boolean must = false;
			if (StringUtil.isEmpty(profile)) {
				profile = DEFAULT_PROFILE;
			} else if (profile.charAt(0) == '-') {
				profile = profile.substring(1);
				b = false;
			} else if (profile.charAt(0) == '+') {
				profile = profile.substring(1);
				must = true;
			}

			if (enabledProfiles.contains(profile)) {
				if (!b) {
					return false;
				}
				result = true;
			} else {
				if (must) {
					return false;
				}
			}
		}
		return result;
	}

	// ---------------------------------------------------------------- after validation

	/**
	 * Returns the list of validation violations or null if validation is successful.
	 */
	public List<Violation> getViolations() {
		return violations;
	}

	/**
	 * Returns true if there are validations.
	 */
	public boolean hasViolations() {
		return violations != 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.madvoc.scope;

import jodd.json.JsonParser;
import jodd.madvoc.ActionRequest;
import jodd.madvoc.config.Targets;
import jodd.util.StringUtil;

import javax.servlet.ServletContext;

/**
 * JSON body scope.
 * Assumes that body is a JSON and parses it into the target type.
 * If target is a String, RAW body is copied.
 */
public class JsonBodyScope implements MadvocScope {

	@Override
	public void inject(final ActionRequest actionRequest, final Targets targets) {
		final String body = actionRequest.readRequestBody();
		if (StringUtil.isEmpty(body)) {
			return;
		}

		targets.forEachTargetAndIn(this, (target, in) -> {
			if (in.type() == String.class) {
				target.writeValue(in, body, true);
			}
			else {
				final Object value = parseRequestBody(body, in.type());

				target.writeValue(in, value, true);
			}
		});
	}

	@Override
	public void inject(final ServletContext servletContext, final Targets targets) {

	}

	@Override
	public void inject(final Targets targets) {

	}

	@Override
	public void outject(final ActionRequest actionRequest, final Targets targets) {

	}

	/**
	 * Parses request body into the target type.
	 */
	protected Object parseRequestBody(final String body, final Class targetType) {
		return JsonParser.create().parse(body, targetType);
	}

}


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

import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Objects;

import static jodd.time.TimeUtil.MILLIS_IN_DAY;

/**
 * Julian Date stamp, for high precision calculations. Julian date is a real
 * number and it basically consist of two parts: integer and fraction. Integer
 * part carries date information, fraction carries time information.
 *
 *

 * The Julian day or Julian day number (JDN) is the (integer) number of days that
 * have elapsed since Monday, January 1, 4713 BC in the proleptic Julian calendar 1.
 * That day is counted as Julian day zero. Thus the multiples of 7 are Mondays.
 * Negative values can also be used.
 *
 *

 * The Julian Date (JD) is the number of days (with decimal fraction of the day) that
 * have elapsed since 12 noon Greenwich Mean Time (UT or TT) of that day.
 * Rounding to the nearest integer gives the Julian day number.
 *

 * For calculations that will have time precision of 1e-3 seconds, both
 * fraction and integer part must have enough digits in it. The problem is
 * that integer part is big and, on the other hand fractional is small, and
 * since final julian date is a sum of this two values, some fraction
 * numerals may be lost. Therefore, for higher precision both
 * fractional and integer part of julian date real number has to be
 * preserved.
 *

 * This class stores the unmodified fraction part, but not all digits
 * are significant! For 1e-3 seconds precision, only 8 digits after
 * the decimal point are significant.
 *
 * @see TimeUtil
 */
public class JulianDate implements Serializable, Cloneable {
	/**
	 * Julian Date for 1970-01-01T00:00:00 (Thursday).
	 */
	public static final JulianDate JD_1970 = new JulianDate(2440587, 0.5);

	/**
	 * Julian Date for 2001-01-01T00:00:00 (Monday).
	 */
	public static final JulianDate JD_2001 = new JulianDate(2451910, 0.5);

	public static JulianDate of(final double value) {
		return new JulianDate(value);
	}

	public static JulianDate of(final LocalDateTime localDateTime) {
		return of(localDateTime.getYear(),
			localDateTime.getMonth().getValue(),
			localDateTime.getDayOfMonth(),
			localDateTime.getHour(),
			localDateTime.getMinute(),
			localDateTime.getSecond(),
			localDateTime.getNano() / 1_000_000);
	}

	public static JulianDate of(final LocalDate localDate) {
		return of(localDate.getYear(),
			localDate.getMonth().getValue(),
			localDate.getDayOfMonth(),
			0, 0, 0, 0);
	}

	public static JulianDate of(final long milliseconds) {
		int integer = (int) (milliseconds / MILLIS_IN_DAY);
		double fraction = (double)(milliseconds % MILLIS_IN_DAY) / MILLIS_IN_DAY;
		integer += JD_1970.integer;
		fraction += JD_1970.fraction;
		return new JulianDate(integer, fraction);
	}

	public static JulianDate of(final int i, final double f) {
		return new JulianDate(i, f);
	}

	public static JulianDate of(int year, int month, int day, final int hour, final int minute, final int second, final int millisecond) {
		// month range fix
		if ((month > 12) || (month < -12)) {
			month--;
			int delta = month / 12;
			year += delta;
			month -= delta * 12;
			month++;
		}
		if (month < 0) {
			year--;
			month += 12;
		}

		// decimal day fraction
		double frac = (hour / 24.0) + (minute / 1440.0) + (second / 86400.0) + (millisecond / 86400000.0);
		if (frac < 0) {		// negative time fix
			int delta = ((int)(-frac)) + 1;
			frac += delta;
			day -= delta;
		}
		//double gyr = year + (0.01 * month) + (0.0001 * day) + (0.0001 * frac) + 1.0e-9;
		double gyr = year + (0.01 * month) + (0.0001 * (day + frac)) + 1.0e-9;

		// conversion factors
		int iy0;
		int im0;
		if (month <= 2) {
			iy0 = year - 1;
			im0 = month + 12;
		} else {
			iy0 = year;
			im0 = month;
		}
		int ia = iy0 / 100;
		int ib = 2 - ia + (ia >> 2);

		// calculate julian date
		int jd;
		if (year <= 0) {
			jd = (int)((365.25 * iy0) - 0.75) + (int)(30.6001 * (im0 + 1)) + day + 1720994;
		} else {
			jd = (int)(365.25 * iy0) + (int)(30.6001 * (im0 + 1)) + day + 1720994;
		}
		if (gyr >= 1582.1015) {						// on or after 15 October 1582
			jd += ib;
		}
		//return  jd + frac + 0.5;

		return new JulianDate(jd, frac + 0.5);
	}

	/**
	 * Integer part of the Julian Date (JD).
	 */
	protected int integer;

	/**
	 * Returns integer part of the Julian Date (JD).
	 */
	public int getInteger() {
		return integer;
	}

	/**
	 * Fraction part of the Julian Date (JD).
	 * Should be always in [0.0, 1.0) range.
	 */
	protected double fraction;

	/**
	 * Returns the fraction part of Julian Date (JD).
	 * Returned value is always in [0.0, 1.0) range.
	 */
	public double getFraction() {
		return fraction;
	}

	/**
	 * Calculates and returns significant fraction only as an int.
	 */
	public int getSignificantFraction() {
		return (int) (fraction * 100_000_000);
	}

	/**
	 * Returns JDN. Note that JDN is not equal to {@link #integer}. It is calculated by
	 * rounding to the nearest integer.
	 */
	public int getJulianDayNumber() {
		if (fraction >= 0.5) {
			return integer + 1;
		}
		return integer;
	}

	/**
	 * Creates JD from a double.
	 */
	public JulianDate(final double jd) {
		integer = (int) jd;
		fraction = jd - (double)integer;
	}

	/**
	 * Creates JD from both integer and fractional part using normalization.
	 * Normalization occurs when fractional part is out of range.
	 *
	 * @see #set(int, double)
	 *
	 * @param i      integer part
	 * @param f      fractional part should be in range [0.0, 1.0)
	 */
	public JulianDate(final int i, final double f) {
		set(i, f);
	}

	/**
	 * Creates JD from BigDecimal.
	 */
	public JulianDate(final BigDecimal bd) {
		double d = bd.doubleValue();
		integer = (int) d;
		fraction = bd.subtract(new BigDecimal(integer)).doubleValue();
	}

	// ---------------------------------------------------------------- conversion

	/**
	 * Returns double value of JD.
	 * CAUTION: double values may not be suit for precision math due to
	 * loss of precision.
	 */
	public double doubleValue() {
		return (double)integer + fraction;
	}

	/**
	 * Returns BigDecimal value of JD.
	 */
	public BigDecimal toBigDecimal() {
		return new BigDecimal(integer).add(new BigDecimal(fraction));
	}

	/**
	 * Returns string representation of JD.
	 *
	 * @return julian integer as string
	 */
	@Override
	public String toString() {
		String s = Double.toString(fraction);
		int i = s.indexOf('.');
		s = s.substring(i);
		return integer + s;
	}

	/**
	 * Converts to milliseconds.
	 */
	public long toMilliseconds() {
		double then = (fraction - JD_1970.fraction) * MILLIS_IN_DAY;
		then += (integer - JD_1970.integer) * MILLIS_IN_DAY;
		then += then > 0 ? 1.0e-6 : -1.0e-6;
		return (long) then;
	}

	public LocalDateTime toLocalDateTime() {
		int year, month, day;
		double frac;
		int jd, ka, kb, kc, kd, ke, ialp;

		//double JD = jds.doubleValue();//jdate;
		//jd = (int)(JD + 0.5);							// integer julian date
		//frac = JD + 0.5 - (double)jd + 1.0e-10;		// day fraction

		ka = (int)(fraction + 0.5);
		jd = integer + ka;
		frac = fraction + 0.5 - ka + 1.0e-10;

		ka = jd;
		if (jd >= 2299161) {
			ialp = (int)(((double)jd - 1867216.25) / (36524.25));
			ka = jd + 1 + ialp - (ialp >> 2);
		}
		kb = ka + 1524;
		kc =  (int)(((double)kb - 122.1) / 365.25);
		kd = (int)((double)kc * 365.25);
		ke = (int)((double)(kb - kd) / 30.6001);
		day = kb - kd - ((int)((double)ke * 30.6001));
		if (ke > 13) {
			month = ke - 13;
		} else {
			month = ke - 1;
		}
		if ((month == 2) && (day > 28)){
			day = 29;
		}
		if ((month == 2) && (day == 29) && (ke == 3)) {
			year = kc - 4716;
		} else if (month > 2) {
			year = kc - 4716;
		} else {
			year = kc - 4715;
		}

		final int time_year = year;
		final int time_month = month;
		final int time_day = day;

		// hour with minute and second included as fraction
		double d_hour = frac * 24.0;
		final int time_hour = (int) d_hour;				// integer hour

		// minute with second included as a fraction
		double d_minute = (d_hour - (double)time_hour) * 60.0;
		final int time_minute = (int) d_minute;			// integer minute

		double d_second = (d_minute - (double)time_minute) * 60.0;
		final int time_second = (int) d_second;			// integer seconds

		double d_millis = (d_second - (double)time_second) * 1000.0;

		// fix calculation errors
		final int time_millisecond = (int) (((d_millis * 10) + 0.5) / 10);

		return LocalDateTime.of(time_year, time_month, time_day, time_hour, time_minute, time_second, time_millisecond * 1_000_000);
	}

	// ---------------------------------------------------------------- math

	/**
	 * Adds a JD to current instance.
	 */
	public JulianDate add(final JulianDate jds) {
		int i = this.integer + jds.integer;
		double f = this.fraction + jds.fraction;
		return new JulianDate(i, f);
	}

	/**
	 * Adds a double to current instance.
	 */
	public JulianDate add(final double delta) {
		return new JulianDate(this.integer, this.fraction + delta);
	}

	/**
	 * Subtracts a JD from current instance.
	 */
	public JulianDate sub(final JulianDate jds) {
		int i = this.integer - jds.integer;
		double f = this.fraction -jds.fraction;
		return new JulianDate(i, f);
	}

	/**
	 * Subtracts a double from current instance.
	 */
	public JulianDate sub(final double delta) {
		return new JulianDate(this.integer, this.fraction - delta);
	}

	/**
	 * Sets integer and fractional part with normalization.
	 * Normalization means that if double is out of range,
	 * values will be correctly fixed.
	 */
	private void set(final int i, double f) {
		integer = i;
		int fi = (int) f;
		f -= fi;
		integer += fi;
		if (f < 0) {
			f += 1;
			integer--;
		}
		this.fraction = f;
	}

	// ---------------------------------------------------------------- between

	/**
	 * Calculates the number of days between two dates. Returned value is always positive.
	 */
	public int daysBetween(final JulianDate otherDate) {
		int difference = daysSpan(otherDate);
		return difference >= 0 ? difference : -difference;
	}

	/**
	 * Returns span between two days. Returned value may be positive (when this date
	 * is after the provided one) or negative (when comparing to future date).
	 */
	public int daysSpan(final JulianDate otherDate) {
		int now = getJulianDayNumber();
		int then = otherDate.getJulianDayNumber();
		return now - then;
	}

	// ---------------------------------------------------------------- equals & hashCode

	@Override
	public boolean equals(final Object object) {
		if (this == object) {
			return true;
		}
		if (this.getClass() != object.getClass()) {
			return false;
		}
		JulianDate stamp = (JulianDate) object;
		return  (stamp.integer == this.integer) &&
				(Double.compare(stamp.fraction, this.fraction) == 0);
	}

	@Override
	public int hashCode() {
		return Objects.hash(integer, fraction);
	}

	// ---------------------------------------------------------------- clone

	@Override
	protected JulianDate clone() {
		return new JulianDate(this.integer, this.fraction);
	}

	// ---------------------------------------------------------------- conversion

	/**
	 * Returns Reduced Julian Date (RJD), used by astronomers.
	 * RJD = JD − 2400000
	 */
	public JulianDate getReducedJulianDate() {
		return new JulianDate(integer - 2400000, fraction);
	}

	/**
	 * Returns Modified Julian Date (MJD), where date starts from midnight rather than noon.
	 * RJD = JD − 2400000.5
	 */
	public JulianDate getModifiedJulianDate() {
		return new JulianDate(integer - 2400000, fraction - 0.5);
	}

	/**
	 * Returns Truncated Julian Day (TJD), introduced by NASA for the space program.
	 * TJD began at midnight at the beginning of May 24, 1968 (Friday).
	 */
	public JulianDate getTruncatedJulianDate() {
		return new JulianDate(integer - 2440000, fraction - 0.5);
	}

}


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

/**
 * Chalk256 allows you to color output going to ansi-256 console.
 * @see Chalk
 */
public class Chalk256 extends Chalk<Chalk256> {

	private static final String[] FG_CODES = new String[256];
	private static final String[] BG_CODES = new String[256];

	static {
		for (int i = 0; i < FG_CODES.length; i++) {
			FG_CODES[i] = "38;5;" + i;
		}
		for (int i = 0; i < BG_CODES.length; i++) {
			BG_CODES[i] = "48;5;" + i;
		}
	}

	public static Chalk256 chalk() {
		return new Chalk256();
	}

	// ---------------------------------------------------------------- fg codes

	public Chalk256 standard(final int index) {
		startSequence(FG_CODES[index(index, 0, 8)]);
		endSequence(RESET);
		return _this();
	}
	public Chalk256 bright(final int index) {
		startSequence(FG_CODES[index(index, 8, 16)]);
		endSequence(RESET);
		return _this();
	}
	public Chalk256 rgb(final int index) {
		startSequence(FG_CODES[index(index, 16, 232)]);
		endSequence(RESET);
		return _this();
	}

	/**
	 * Colors with red-green-blue value, in the range 0 to 6.
	 */
	public Chalk256 rgb(final int r, final int b, final int g) {
		startSequence(FG_CODES[index(36*r + 6*g + b,16, 232)]);
		endSequence(RESET);
		return _this();
	}
	public Chalk256 grayscale(final int index) {
		startSequence(FG_CODES[index(index, 232, 256)]);
		endSequence(RESET);
		return _this();
	}
	// ---------------------------------------------------------------- bg codes

	public Chalk256 bgStandard(final int index) {
		startSequence(BG_CODES[index(index, 0, 8)]);
		endSequence(RESET);
		return _this();
	}
	public Chalk256 bgBright(final int index) {
		startSequence(BG_CODES[index(index, 8, 16)]);
		endSequence(RESET);
		return _this();
	}
	public Chalk256 bgRgb(final int index) {
		startSequence(BG_CODES[index(index, 16, 232)]);
		endSequence(RESET);
		return _this();
	}

	/**
	 * Colors with red-green-blue value, in the range 0 to 6.
	 */
	public Chalk256 bgRgb(final int r, final int b, final int g) {
		startSequence(BG_CODES[index(36*r + 6*g + b,16, 232)]);
		endSequence(RESET);
		return _this();
	}
	public Chalk256 bgGrayscale(final int index) {
		startSequence(BG_CODES[index(index, 232, 256)]);
		endSequence(RESET);
		return _this();
	}

	// ---------------------------------------------------------------- bgcolors

	private int index(int index, final int from, final int to) {
		index += from;
		if ((index < from) || (index >= to)) {
			throw new IllegalArgumentException("Color index not in range: [0, " + (to - from) + "]");
		}
		return index;
	}

}