/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2013-2014 sagyf Yang. The Four Group.
 */

package goja.core.kits.lang;

/**
 * Checks whether a string or path matches a given wildcard pattern.
 * Possible patterns allow to match single characters ('?') or any count of
 * characters ('*'). Wildcard characters can be escaped (by an '\').
 * When matching path, deep tree wildcard also can be used ('**').
 *

 * This method uses recursive matching, as in linux or windows. regexp works the same.
 * This method is very fast, comparing to similar implementations.
 */
public class Wildcard {

    protected static final String PATH_MATCH      = "**";
    protected static final String PATH_SEPARATORS = "/\\";

    /**
     * Checks whether a string matches a given wildcard pattern.
     *
     * @param string  input string
     * @param pattern pattern to match
     * @return true if string matches the pattern, otherwise false
     */
    public static boolean match(String string, String pattern) {
        return match(string, pattern, 0, 0);
    }

    // ---------------------------------------------------------------- utilities

    /**
     * Checks if two strings are equals or if they {@link #match(String, String)}.
     * Useful for cases when matching a lot of equal strings and speed is important.
     */
    public static boolean equalsOrMatch(String string, String pattern) {
        return string.equals(pattern) || match(string, pattern, 0, 0);
    }

    /**
     * Internal matching recursive function.
     */
    private static boolean match(String string, String pattern, int sNdx, int pNdx) {
        int pLen = pattern.length();
        if (pLen == 1) {
            if (pattern.charAt(0) == '*') {     // speed-up
                return true;
            }
        }
        int sLen = string.length();
        boolean nextIsNotWildcard = false;

        while (true) {

            // check if end of string and/or pattern occurred
            if ((sNdx >= sLen)) {        // end of string still may have pending '*' in pattern
                while ((pNdx < pLen) && (pattern.charAt(pNdx) == '*')) {
                    pNdx++;
                }
                return pNdx >= pLen;
            }
            if (pNdx >= pLen) {                    // end of pattern, but not end of the string
                return false;
            }
            char p = pattern.charAt(pNdx);        // pattern char

            // perform logic
            if (!nextIsNotWildcard) {

                if (p == '\\') {
                    pNdx++;
                    nextIsNotWildcard = true;
                    continue;
                }
                if (p == '?') {
                    sNdx++;
                    pNdx++;
                    continue;
                }
                if (p == '*') {
                    char pNext = 0;                        // next pattern char
                    if (pNdx + 1 < pLen) {
                        pNext = pattern.charAt(pNdx + 1);
                    }
                    if (pNext == '*') {                    // double '*' have the same effect as one '*'
                        pNdx++;
                        continue;
                    }
                    int i;
                    pNdx++;

                    // find recursively if there is any substring from the end of the
                    // line that matches the rest of the pattern !!!
                    for (i = string.length(); i >= sNdx; i--) {
                        if (match(string, pattern, i, pNdx)) {
                            return true;
                        }
                    }
                    return false;
                }
            } else {
                nextIsNotWildcard = false;
            }

            // check if pattern char and string char are equals
            if (p != string.charAt(sNdx)) {
                return false;
            }

            // everything matches for now, continue
            sNdx++;
            pNdx++;
        }
    }

    // ---------------------------------------------------------------- path

    /**
     * Matches string to at least one pattern.
     * Returns index of matched pattern, or -1 otherwise.
     *
     * @see #match(String, String)
     */
    public static int matchOne(String src, String[] patterns) {
        for (int i = 0; i < patterns.length; i++) {
            if (match(src, patterns[i])) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Matches path to at least one pattern.
     * Returns index of matched pattern or -1 otherwise.
     *
     * @see #matchPath(String, String)
     */
    public static int matchPathOne(String path, String[] patterns) {
        for (int i = 0; i < patterns.length; i++) {
            if (matchPath(path, patterns[i])) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Matches path against pattern using *, ? and ** wildcards.
     * Both path and the pattern are tokenized on path separators (both \ and /).
     * '**' represents deep tree wildcard, as in Ant.
     */
    public static boolean matchPath(String path, String pattern) {
        String[] pathElements = CharKit.splitc(path, PATH_SEPARATORS);
        String[] patternElements = CharKit.splitc(pattern, PATH_SEPARATORS);
        return matchTokens(pathElements, patternElements);
    }

    /**
     * Match tokenized string and pattern.
     */
    protected static boolean matchTokens(String[] tokens, String[] patterns) {
        int patNdxStart = 0;
        int patNdxEnd = patterns.length - 1;
        int tokNdxStart = 0;
        int tokNdxEnd = tokens.length - 1;

        while ((patNdxStart <= patNdxEnd) && (tokNdxStart <= tokNdxEnd)) {    // find first **
            String patDir = patterns[patNdxStart];
            if (patDir.equals(PATH_MATCH)) {
                break;
            }
            if (!match(tokens[tokNdxStart], patDir)) {
                return false;
            }
            patNdxStart++;
            tokNdxStart++;
        }
        if (tokNdxStart > tokNdxEnd) {
            for (int i = patNdxStart; i <= patNdxEnd; i++) {    // string is finished
                if (!patterns[i].equals(PATH_MATCH)) {
                    return false;
                }
            }
            return true;
        }
        if (patNdxStart > patNdxEnd) {
            return false;    // string is not finished, but pattern is
        }

        while ((patNdxStart <= patNdxEnd) && (tokNdxStart <= tokNdxEnd)) {    // to the last **
            String patDir = patterns[patNdxEnd];
            if (patDir.equals(PATH_MATCH)) {
                break;
            }
            if (!match(tokens[tokNdxEnd], patDir)) {
                return false;
            }
            patNdxEnd--;
            tokNdxEnd--;
        }
        if (tokNdxStart > tokNdxEnd) {
            for (int i = patNdxStart; i <= patNdxEnd; i++) {    // string is finished
                if (!patterns[i].equals(PATH_MATCH)) {
                    return false;
                }
            }
            return true;
        }

        while ((patNdxStart != patNdxEnd) && (tokNdxStart <= tokNdxEnd)) {
            int patIdxTmp = -1;
            for (int i = patNdxStart + 1; i <= patNdxEnd; i++) {
                if (patterns[i].equals(PATH_MATCH)) {
                    patIdxTmp = i;
                    break;
                }
            }
            if (patIdxTmp == patNdxStart + 1) {
                patNdxStart++;    // skip **/** situation
                continue;
            }
            // find the pattern between padIdxStart & padIdxTmp in str between strIdxStart & strIdxEnd
            int patLength = (patIdxTmp - patNdxStart - 1);
            int strLength = (tokNdxEnd - tokNdxStart + 1);
            int ndx = -1;
            strLoop:
            for (int i = 0; i <= strLength - patLength; i++) {
                for (int j = 0; j < patLength; j++) {
                    String subPat = patterns[patNdxStart + j + 1];
                    String subStr = tokens[tokNdxStart + i + j];
                    if (!match(subStr, subPat)) {
                        continue strLoop;
                    }
                }

                ndx = tokNdxStart + i;
                break;
            }

            if (ndx == -1) {
                return false;
            }

            patNdxStart = patIdxTmp;
            tokNdxStart = ndx + patLength;
        }

        for (int i = patNdxStart; i <= patNdxEnd; i++) {
            if (!patterns[i].equals(PATH_MATCH)) {
                return false;
            }
        }

        return true;
    }
}


/*
 * Copyright (C) 2011-2013 Eiichiro Uchiumi. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.eiichiro.bootleg;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.sql.Timestamp;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.Stack;
import java.util.TreeSet;
import java.util.Vector;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.SynchronousQueue;

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

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.Converter;
import org.apache.commons.beanutils.converters.DateConverter;
import org.apache.commons.lang.ClassUtils;

/**
 * {@code Types} is a type system utility for Bootleg built-in components.
 *
 * @author Eiichiro Uchiumi
 */
public abstract class Types {

	private Types() {}

	private static Set<Class<?>> builtins = new CopyOnWriteArraySet<Class<?>>();

	private static Set<Class<?>> collections = new CopyOnWriteArraySet<Class<?>>();

	private static Set<Class<?>> values = new CopyOnWriteArraySet<Class<?>>();

	private static Map<Class<?>, Class<?>> implementations = new ConcurrentHashMap<Class<?>, Class<?>>();

	private static Map<Class<?>, Collection<?>> empties = new ConcurrentHashMap<Class<?>, Collection<?>>();

	static {
		// Built-in types.
		builtins.add(WebContext.class);
		builtins.add(HttpServletRequest.class);
		builtins.add(HttpServletResponse.class);
		builtins.add(HttpSession.class);
		builtins.add(ServletContext.class);
		builtins.add(Request.class);

		// Interfaces.
		collections.add(Collection.class);
		collections.add(List.class);
		collections.add(Set.class);
		collections.add(SortedSet.class);
		collections.add(NavigableSet.class);
		collections.add(Queue.class);
		collections.add(Deque.class);

		// Implementations.
		collections.add(ArrayList.class);
		collections.add(LinkedList.class);
		collections.add(CopyOnWriteArrayList.class);
		collections.add(Stack.class);
		collections.add(Vector.class);
		collections.add(HashSet.class);
		collections.add(LinkedHashSet.class);
		collections.add(CopyOnWriteArraySet.class);
		collections.add(TreeSet.class);
		collections.add(ConcurrentSkipListSet.class);
		collections.add(PriorityQueue.class);
		collections.add(ConcurrentLinkedQueue.class);
		collections.add(DelayQueue.class);
		collections.add(ArrayBlockingQueue.class);
		collections.add(LinkedBlockingQueue.class);
		collections.add(PriorityBlockingQueue.class);
		collections.add(SynchronousQueue.class);
		collections.add(ArrayDeque.class);
		collections.add(LinkedBlockingDeque.class);

		// Value types.
		values.add(Boolean.TYPE);
		values.add(Byte.TYPE);
		values.add(Character.TYPE);
		values.add(Double.TYPE);
		values.add(Float.TYPE);
		values.add(Integer.TYPE);
		values.add(Long.TYPE);
		values.add(Short.TYPE);
		values.add(BigDecimal.class);
		values.add(BigInteger.class);
		values.add(Boolean.class);
		values.add(Byte.class);
		values.add(Character.class);
		values.add(Double.class);
		values.add(Float.class);
		values.add(Integer.class);
		values.add(Long.class);
		values.add(Short.class);
		values.add(String.class);
		values.add(Class.class);
		values.add(java.util.Date.class);
		values.add(Calendar.class);
		values.add(File.class);
		values.add(java.sql.Date.class);
		values.add(java.sql.Time.class);
		values.add(Timestamp.class);
		values.add(URL.class);
		values.add(Object.class);

		// XXX: :-/
		DateConverter converter = new DateConverter();
		converter.setUseLocaleFormat(true);
		ConvertUtils.register(converter, Date.class);

		// Default collection implementations.
		implementations.put(Collection.class, ArrayList.class);
		implementations.put(List.class, ArrayList.class);
		implementations.put(Set.class, HashSet.class);
		implementations.put(SortedSet.class, TreeSet.class);
		implementations.put(NavigableSet.class, TreeSet.class);
		implementations.put(Queue.class, PriorityQueue.class);
		implementations.put(Deque.class, ArrayDeque.class);

		// Empty collections.
		empties.put(Collection.class, Collections.EMPTY_LIST);
		empties.put(List.class, Collections.EMPTY_LIST);
		empties.put(Set.class, Collections.EMPTY_SET);
		empties.put(SortedSet.class, new TreeSet<Object>());
		empties.put(NavigableSet.class, new TreeSet<Object>());
		empties.put(Queue.class, new PriorityQueue<Object>());
		empties.put(Deque.class, new ArrayDeque<Object>(0));
		empties.put(ArrayList.class, new ArrayList<Object>(0));
		empties.put(LinkedList.class, new LinkedList<Object>());
		empties.put(CopyOnWriteArrayList.class, new CopyOnWriteArrayList<Object>());
		empties.put(Stack.class, new Stack<Object>());
		empties.put(Vector.class, new Vector<Object>(0));
		empties.put(HashSet.class, new HashSet<Object>(0));
		empties.put(LinkedHashSet.class, new LinkedHashSet<Object>(0));
		empties.put(CopyOnWriteArraySet.class, new CopyOnWriteArraySet<Object>());
		empties.put(TreeSet.class, new TreeSet<Object>());
		empties.put(ConcurrentSkipListSet.class, new ConcurrentSkipListSet<Object>());
		empties.put(PriorityQueue.class, new PriorityQueue<Object>());
		empties.put(ConcurrentLinkedQueue.class, new ConcurrentLinkedQueue<Object>());
		empties.put(DelayQueue.class, new DelayQueue<Delayed>());
		empties.put(ArrayBlockingQueue.class, new ArrayBlockingQueue<Object>(1));
		empties.put(LinkedBlockingQueue.class, new LinkedBlockingDeque<Object>());
		empties.put(PriorityBlockingQueue.class, new PriorityBlockingQueue<Object>());
		empties.put(SynchronousQueue.class, new SynchronousQueue<Object>());
		empties.put(ArrayDeque.class, new ArrayDeque<Object>());
		empties.put(LinkedBlockingDeque.class, new LinkedBlockingDeque<Object>());
	}

	/**
	 * Returns true if the specified type is a
	 * built-in type.
	 *
	 * @param type The type to be tested.
	 * @return true if the specified type is a
	 * built-in type.
	 */
	public static boolean isBuiltinType(Type type) {
		Class<?> rawType = getRawType(type);
		return (rawType == null) ? false : builtins.contains(rawType);
	}

	/**
	 * Returns true if the specified type is an
	 * instance of {@code Collection}.
	 *
	 * @param type The type to be tested.
	 * @return true if the specified type is an
	 * instance of {@code Collection}.
	 */
	public static boolean isCollection(Type type) {
		Class<?> rawType = getRawType(type);
		return (rawType == null) ? false
				: (rawType.equals(Collection.class)
						|| ClassUtils.getAllInterfaces(rawType).contains(Collection.class));
	}

	/**
	 * Returns true if the specified type is an
	 * instance of supported {@code Collection}.

	 *

	 * Supported collections are:
	 *

	 * java.util.Collection
	 * java.util.List
	 * java.util.Set
	 * java.util.SortedSet
	 * java.util.NavigableSet
	 * java.util.Queue
	 * java.util.Deque
	 *
	 * java.util.ArrayList
	 * java.util.LinkedList
	 * java.util.concurrent.CopyOnWriteArrayList
	 * java.util.Stack
	 * java.util.Vector
	 * java.util.HashSet
	 * java.util.LinkedHashSet
	 * java.util.concurrent.CopyOnWriteArraySet
	 * java.util.TreeSet
	 * java.util.concurrent.ConcurrentSkipListSet
	 * java.util.PriorityQueue
	 * java.util.concurrent.ConcurrentLinkedQueue
	 * java.util.concurrent.PriorityBlockingQueue
	 * java.util.concurrent.DelayQueue
	 * java.util.concurrent.LinkedBlockingQueue
	 * java.util.concurrent.PriorityBlockingQueue
	 * java.util.ArrayDeque
	 * java.util.concurrent.LinkedBlockingDeque
	 * java.util.concurrent.SynchronousQueue
	 *

	 *
	 * @param type The type to be tested.
	 * @return true if the specified type is an
	 * instance of supported {@code Collection}.

	 */
	public static boolean isSupportedCollection(Type type) {
		Class<?> rawType = getRawType(type);
		return (rawType == null) ? false : collections.contains(rawType);
	}

	/**
	 * Returns true if the specified type is an array
	 * type.
	 *
	 * @param type The type to be tested.
	 * @return true if the specified type is an array
	 * type.
	 */
	public static boolean isArray(Type type) {
		if (type instanceof GenericArrayType) {
			return true;
		} else if (type instanceof Class<?>) {
			return ((Class<?>) type).isArray();
		} else {
			return false;
		}
	}

	/**
	 * Returns the element type of the specified collection type.
	 * The specified type must be collection or array. To make it sure, use
	 * {@code #isCollection(Type)} method or {@code #isArray(Type)} method.
	 *
	 * @param type Collection or array type.
	 * @return The element type of the specified collection or array.
	 * @throws IllegalArgumentException If the specified 'type' is not a
	 * collection or array.
	 */
	public static Class<?> getElementType(Type type) {
		if (isCollection(type)) {
			if (type instanceof ParameterizedType) {
				ParameterizedType parameterizedType = (ParameterizedType) type;
				return (Class<?>) parameterizedType.getActualTypeArguments()[0];
			} else {
				return Object.class;
			}

		} else if (isArray(type)) {
			if (type instanceof GenericArrayType) {
				GenericArrayType genericArrayType = (GenericArrayType) type;
				return (Class<?>) genericArrayType.getGenericComponentType();
			} else {
				Class<?> clazz = (Class<?>) type;
				return clazz.getComponentType();
			}

		} else {
			throw new IllegalArgumentException("'type' must be a collection or array");
		}
	}

	/**
	 * Returns the default implementation type of the specified collection
	 * interface type.
	 * The specified type must be an supported collection. To make it sure, use
	 * {@code #isSupportedCollection(Type)}. If the specified collection type is
	 * not an interface, this method returns the specified implementation
	 * type directly.

	 *

	 * The default implementations are:
	 *

	 * java.util.Collection -> java.util.ArrayList
	 * java.util.List -> java.util.ArrayList
	 * java.util.Set -> java.util.HashSet
	 * java.util.SortedSet -> java.util.TreeSet
	 * java.util.NavigableSet -> java.util.TreeSet
	 * java.util.Queue -> java.util.PriorityQueue
	 * java.util.Deque -> java.util.ArrayDeque
	 *

	 *
	 * @param type Collection type (must be supported type).
	 * @return The default implementation type of the specified collection
	 * interface type.
	 */
	public static Class<?> getDefaultImplementationType(Type type) {
		Class<?> clazz = getRawType(type);
		return (clazz.isInterface()) ? implementations.get(clazz) : clazz;
	}

	/**
	 * Returns the empty instance of the specified collection type.
	 *
	 * @param type The collection type (must be supported type).
	 * @return The empty instance of the specified collection type.
	 */
	public static Collection<?> getEmptyCollection(Type type) {
		return empties.get(getRawType(type));
	}

	/**
	 * Returns true if the specified type is a
	 * supported core value type.

	 *

	 * Supported core value types are:
	 *

	 * boolean
	 * byte
	 * char
	 * double
	 * float
	 * int
	 * long
	 * short
	 * java.math.BigDecimal
	 * java.math.BigInteger
	 * Boolean
	 * Byte
	 * Character
	 * Double
	 * Float
	 * Integer
	 * Long
	 * Short
	 * String
	 * Class
	 * java.util.Date
	 * java.util.Calendar
	 * java.io.File
	 * java.sql.Date
	 * java.sql.Time
	 * java.sql.Timestamp
	 * java.net.URL
	 * Object
	 *

	 *
	 * @param type The type to be tested.
	 * @return true if the specified type is supported core value
	 * type.
	 */
	public static boolean isCoreValueType(Type type) {
		Class<?> rawType = getRawType(type);
		return (rawType == null) ? false : values.contains(rawType);
	}

	/**
	 * Returns true if the specified type is an user define value
	 * type.
	 * User defined value type must satisfy either of the following condition.
	 *

	 *
(The class) has a public constructor that takes one String.class parameter.

	 *
Has a public static factory method that named 'valueOf' and takes one
	 * String.class parameter.

	 *

	 *
	 * @param type The type to be tested.
	 * @return true if the specified type is an user define value
	 * type.
	 */
	public static boolean isUserDefinedValueType(Type type) {
		Class<?> rawType = getRawType(type);

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

		for (Constructor<?> constructor : rawType.getConstructors()) {
			Class<?>[] parameterTypes = constructor.getParameterTypes();

			if (parameterTypes.length == 1
					&& parameterTypes[0].equals(String.class)) {
				return true;
			}
		}

		for (Method method : rawType.getMethods()) {
			if (method.getName().equals("valueOf")
					&& Modifier.isStatic(method.getModifiers())) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Adds supported core value type.
	 *
	 * @param clazz The core value type.
	 * @param converter Converter for the specified core value type.
	 */
	public static void addCoreValueType(Class<?> clazz, Converter converter) {
		ConvertUtils.register(converter, clazz);
		values.add(clazz);
	}

	/**
	 * Returns true if the specified type is a
	 * primitive type.
	 *
	 * @param type The type to be tested.
	 * @return true if the specified type is a primitive type.
	 */
	public static boolean isPrimitive(Type type) {
		return (type instanceof Class<?>) ? ((Class<?>) type).isPrimitive() : false;
	}

	/**
	 * Returns the raw type of the specified type.
	 * This method supports {@code ParameterizedType} or raw {@code Class}
	 * (just returns it directly). The other types such as array type are not
	 * supported. If the specified 'type' is not supported, this method returns
	 * null.
	 *
	 * @param type The type to be tested.
	 * @return The raw type of the specified type.
	 */
	public static Class<?> getRawType(Type type) {
		if (type instanceof ParameterizedType) {
			ParameterizedType parameterizedType = (ParameterizedType) type;
			return (Class<?>) parameterizedType.getRawType();
		} else if (type instanceof Class<?>) {
			Class<?> clazz = (Class<?>) type;
			return (clazz.isArray()) ? null : clazz;
		} else {
			return null;
		}
	}

}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2013-2014 sagyf Yang. The Four Group.
 */

package goja.core;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.regex.Pattern;

/**
 * <p> 验证帮助. </p>
 *
 * @author sagyf yang
 * @version 1.0 2014-02-22 15:09
 * @since JDK 1.6
 */
public class Validator {

    /**
     * Matches the regular expression
     *
     * @param regex regex
     * @param value value
     * @return boolean
     */
    public static boolean match(String regex, String value) {
        Pattern pattern = Pattern.compile(regex);
        return pattern.matcher(value).find();
    }

    /**
     * Matching the regular expression is case-sensitive
     *
     * @param regex regex
     * @param flags flags
     * @param value value
     * @return boolean
     */
    public static boolean match(String regex, int flags, String value) {
        Pattern pattern = Pattern.compile(regex, flags);
        return pattern.matcher(value).find();
    }

    /**
     * Email validation
     *
     * @param value value
     * @return boolean
     */
    public static boolean isEmail(String value) {
        String check =
                "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$";
        return match(check, Pattern.CASE_INSENSITIVE, value);
    }

    /**
     * Phone number verification
     *
     * @param value value
     * @return boolean
     */
    public static boolean isMobile(String value) {
        String check = "^(((13[0-9]{1})|(15[0-9]{1})|(18[0-9]{1}))+\\d{8})$";
        return match(check, Pattern.CASE_INSENSITIVE, value);
    }

    /**
     * Phone verification
     *
     * @param value value
     * @return boolean
     */
    public static boolean isTel(String value) {
        String check = "^\\d{3,4}-?\\d{7,9}$";
        return match(check, Pattern.CASE_INSENSITIVE, value);
    }

    /**
     * Telephone number, including mobile phones and landlines
     *
     * @param value value
     * @return boolean
     */
    public static boolean isPhone(String value) {
        String telcheck = "^\\d{3,4}-?\\d{7,9}$";
        String mobilecheck = "^(((13[0-9]{1})|(15[0-9]{1})|(18[0-9]{1}))+\\d{8})$";
        return match(telcheck, Pattern.CASE_INSENSITIVE, value) || match(mobilecheck,
                Pattern.CASE_INSENSITIVE, value);
    }

    /**
     * @param value Input limited to English letters, numbers and underscores
     * @return boolean
     */
    public static boolean isGeneral(String value) {
        String check = "^\\w+$";
        return match(check, Pattern.CASE_INSENSITIVE, value);
    }

    /**
     * @param value Input limited to English letters, numbers and underscores
     * @param min   Minimum length
     * @param max   Maximum length
     * @return boolean
     */
    public static boolean isGeneral(String value, int min, int max) {
        String check = "^\\w{" + min + "," + max + "}$";
        return match(check, Pattern.CASE_INSENSITIVE, value);
    }

    /**
     * Judge whether it is birthday
     *
     * @param value value
     * @return boolean
     */
    public static boolean isBirthDay(String value) {
        String check = "(\\d{4})(/|-|\\.)(\\d{1,2})(/|-|\\.)(\\d{1,2})$";

        if (match(check, Pattern.CASE_INSENSITIVE, value)) {
            int year = Integer.parseInt(value.substring(0, 4));
            int month = Integer.parseInt(value.substring(5, 7));
            int day = Integer.parseInt(value.substring(8, 10));

            if (month < 1 || month > 12) {
                return false;
            }

            if (day < 1 || day > 31) {
                return false;
            }

            if ((month == 4 || month == 6 || month == 9 || month == 11)
                    && day == 31) {
                return false;
            }

            if (month == 2) {
                boolean isleap = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
                if (day > 29 || (day == 29 && !isleap)) {
                    return false;
                }
            }
            return true;
        } else {
            return false;
        }
    }

    /**
     * Identity verification
     *
     * @param value value
     * @return boolean
     */
    public static boolean isIdentityCard(String value) {
        String check = "(^\\d{15}$)|(^\\d{17}([0-9]|X)$)";
        return match(check, Pattern.CASE_INSENSITIVE, value);
    }

    /**
     * Postal Code
     *
     * @param value value
     * @return boolean
     */
    public static boolean isZipCode(String value) {
        String check = "^[0-9]{6}$";
        return match(check, Pattern.CASE_INSENSITIVE, value);
    }

    /**
     * Currency validation
     *
     * @param value value
     * @return boolean
     */
    public static boolean isCurrency(String value) {
        String check = "^(\\d+(?:\\.\\d{1,2})?)$";
        return match(check, Pattern.CASE_INSENSITIVE, value);
    }

    /**
     * @param value Enter the numbers
     * @return boolean
     */
    public static boolean isNumber(String value) {
        String check = "^(\\+|\\-)?\\d+$";
        return match(check, Pattern.CASE_INSENSITIVE, value);
    }

    /**
     * @param value Enter the numbers
     * @param min   Minimum length
     * @param max   Maximum length
     * @return boolean
     */
    public static boolean isNumber(String value, int min, int max) {
        String check = "^(\\+|\\-)?\\d{" + min + "," + max + "}$";
        return match(check, Pattern.CASE_INSENSITIVE, value);
    }

    /**
     * @param value A positive integer
     * @return boolean
     */
    public static boolean isPositiveNumber(String value) {
        String check = "^\\d+$";
        return match(check, Pattern.CASE_INSENSITIVE, value);
    }

    /**
     * @param value A positive integer
     * @param min   Minimum length
     * @param max   Maximum length
     * @return boolean
     */
    public static boolean isPositiveNumber(String value, int min, int max) {
        String check = "^\\d{" + min + "," + max + "}$";
        return match(check, Pattern.CASE_INSENSITIVE, value);
    }

    /**
     * Chinese
     *
     * @param value value
     * @return boolean
     */
    public static boolean isChinese(String value) {
        String check = "^[\\u2E80-\\u9FFF]+$";
        return match(check, Pattern.CASE_INSENSITIVE, value);
    }

    public static boolean isChinese(String value, int min, int max) {
        String check = "^[\\u2E80-\\u9FFF]{" + min + "," + max + "}$";
        return match(check, Pattern.CASE_INSENSITIVE, value);
    }

    /**
     * @param value Chinese characters, English letters, numbers and underscores
     * @return boolean
     */
    public static boolean isString(String value) {
        String check = "^[\\u0391-\\uFFE5\\w]+$";
        return match(check, Pattern.CASE_INSENSITIVE, value);
    }

    /**
     * @param value Chinese characters, English letters, numbers and underscores
     * @param min   Minimum length
     * @param max   Maximum length
     * @return boolean
     */
    public static boolean isString(String value, int min, int max) {
        String check = "^[\\u0391-\\uFFE5\\w]{" + min + "," + max + "}$";
        return match(check, Pattern.CASE_INSENSITIVE, value);
    }

    /**
     * @param value UUID
     * @return boolean
     */
    public static boolean isUUID(String value) {
        String check = "^[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}$";
        return match(check, Pattern.CASE_INSENSITIVE, value);
    }

    /**
     * Matches are linked
     *
     * @param value value
     * @return boolean
     */
    public static boolean isUrl(String value) {
        String check =
                "^((https?|ftp):\\/\\/)?(((([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(%[\\da-f]{2})|[!\\$&'\\(\\)\\*\\+,;=]|:)*@)?(((\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5]))|((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.?)(:\\d*)?)(\\/((([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(%[\\da-f]{2})|[!\\$&'\\(\\)\\*\\+,;=]|:|@)+(\\/(([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(%[\\da-f]{2})|[!\\$&'\\(\\)\\*\\+,;=]|:|@)*)*)?)?(\\?((([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(%[\\da-f]{2})|[!\\$&'\\(\\)\\*\\+,;=]|:|@)|[\\uE000-\\uF8FF]|\\/|\\?)*)?(\\#((([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(%[\\da-f]{2})|[!\\$&'\\(\\)\\*\\+,;=]|:|@)|\\/|\\?)*)?$";
        return match(check, Pattern.CASE_INSENSITIVE, value);
    }

    /**
     * Tell the time
     *
     * @param value value
     * @return boolean
     */
    public static boolean isDateTime(String value) {
        String check =
                "^(\\d{4})(/|-|\\.|年)(\\d{1,2})(/|-|\\.|月)(\\d{1,2})(日)?(\\s+\\d{1,2}(:|时)\\d{1,2}(:|分)?(\\d{1,2}(秒)?)?)?$";// check = "^(\\d{4})(/|-|\\.)(\\d{1,2})(/|-|\\.)(\\d{1,2})$";
        return match(check, Pattern.CASE_INSENSITIVE, value);
    }

    /**
     * Blank
     *
     * @param value value
     * @return boolean
     */
    public static boolean isBlank(Object value) {
        if (value instanceof Collection) {
            return ((Collection) value).isEmpty();
        } else if (value instanceof String) {
            return "".equals(value.toString().trim());
        } else {
            return value == null;
        }
    }

    public static boolean isNotBlank(Object value) {
        return !isBlank(value);
    }

    public static boolean isLength(String value, int min, int max) {
        int length = isBlank(value) ? 0 : value.length();
        return length >= min && length <= max;
    }

    public static boolean compareDate(String date1, String date2, String df) {
        SimpleDateFormat sdf = new SimpleDateFormat(df);
        try {
            Date d1 = sdf.parse(date1);
            Date d2 = sdf.parse(date2);
            return d1.compareTo(d2) > 0;
        } catch (ParseException e) {
            return false;
        }
    }

    public static boolean compareDate(String date1, String date2) {
        return compareDate(date1, date2, "yyyy-MM-dd HH:mm:ss");
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2013-2014 sagyf Yang. The Four Group.
 */

package goja.rapid.qrcode;

import com.google.common.base.Strings;
import com.google.common.io.Files;
import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeReader;
import com.google.zxing.qrcode.QRCodeWriter;

import javax.imageio.ImageIO;

import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;

/**
 * <p> . </p>
 *
 * @author sagyf yang
 * @version 1.0 2014-08-27 15:27
 * @since JDK 1.6
 */
public final class QRCode {

    /**
     * QRCode 生成器格式
     */
    private QRCodeFormat format = null;

    /**
     * 生成的 QRCode 图像对象
     */
    private BufferedImage qrcodeImage = null;

    /**
     * 生成的 QRCode 图片文件
     */
    private File qrcodeFile = null;

    private QRCode() {

    }

    /**
     * 使用带默认值的「QRCode 生成器格式」来创建一个 QRCode 处理器。
     *
     * @param content 所要生成 QRCode 的内容
     * @return QRCode 处理器
     */
    public static QRCode create(final String content) {
        return create(content, QRCodeFormat.NEW());
    }

    /**
     * 使用指定的「QRCode 生成器格式」来创建一个 QRCode 处理器。
     *
     * @param content 所要生成 QRCode 的内容
     * @param format  QRCode 生成器格式
     * @return QRCode 处理器
     */
    public static QRCode create(final String content, QRCodeFormat format) {
        QRCode qrcode = new QRCode();
        qrcode.format = format;
        qrcode.qrcodeImage = toQRCode(content, format);
        return qrcode;
    }

    /**
     * 使用带默认值的「QRCode 生成器格式」，把指定的内容生成为一个 QRCode 的图像对象。
     *
     * @param content 所需生成 QRCode 的内容
     * @return QRCode 的图像对象
     */
    public static BufferedImage toQRCode(String content) {
        return toQRCode(content, null);
    }

    /**
     * 使用指定的「QRCode生成器格式」，把指定的内容生成为一个 QRCode 的图像对象。
     *
     * @param content 所需生成 QRCode 的内容
     * @return QRCode 的图像对象
     */
    public static BufferedImage toQRCode(String content, QRCodeFormat format) {
        if (format == null) {
            format = QRCodeFormat.NEW();
        }

        content = new String(content.getBytes(Charset.forName(format.getEncode())));
        BitMatrix matrix;
        try {
            matrix = new QRCodeWriter().encode(content,
                    BarcodeFormat.QR_CODE,
                    format.getSize(),
                    format.getSize(),
                    format.getHints());
        } catch (WriterException e) {
            throw new RuntimeException(e);
        }

        int width = matrix.getWidth();
        int height = matrix.getHeight();
        int fgColor = format.getForeGroundColor().getRGB();
        int bgColor = format.getBackGroundColor().getRGB();
        BufferedImage image = new BufferedImage(width,
                height,
                ColorSpace.TYPE_RGB);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                image.setRGB(x, y, matrix.get(x, y) ? fgColor : bgColor);
            }
        }
        return image;
    }

    /**
     * 从指定的 QRCode 图片文件中解析出其内容。
     *
     * @param qrcodeFile QRCode 文件
     * @return QRCode 中的内容
     */
    public static String from(String qrcodeFile) {
        if (qrcodeFile.startsWith("http://")
                || qrcodeFile.startsWith("https://")) {
            try {
                return from(new URL(qrcodeFile));
            } catch (MalformedURLException e) {
                throw new RuntimeException(e);
            }
        } else {
            return from(new File(qrcodeFile));
        }
    }

    /**
     * 从指定的 QRCode 图片文件中解析出其内容。
     *
     * @param qrcodeFile QRCode 图片文件
     * @return QRCode 中的内容
     */
    public static String from(File qrcodeFile) {
        if (!qrcodeFile.exists()) {
            return null;
        }
        try {
            BufferedImage image = ImageIO.read(qrcodeFile);
            return from(image);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 从指定的 QRCode 图片链接中解析出其内容。
     *
     * @param qrcodeUrl QRCode 图片链接
     * @return QRCode 中的内容
     */
    public static String from(URL qrcodeUrl) {
        try {
            BufferedImage image = ImageIO.read(qrcodeUrl);
            return from(image);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 从指定的 QRCode 图像对象中解析出其内容。
     *
     * @param qrcodeImage QRCode 图像对象
     * @return QRCode 中的内容
     */
    public static String from(BufferedImage qrcodeImage) {
        final LuminanceSource source = new BufferedImageLuminanceSource(qrcodeImage);
        final BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
        String content;
        try {
            Result result = new QRCodeReader().decode(bitmap);
            content = result.getText();
        } catch (NotFoundException e) {
            throw new RuntimeException(e);
        } catch (ChecksumException e) {
            throw new RuntimeException(e);
        } catch (FormatException e) {
            throw new RuntimeException(e);
        }
        return content;
    }

    /**
     * 返回生成的 QRCode 图像对象
     *
     * @return 生成的 QRCode 图像对象
     */
    public BufferedImage getQrcodeImage() {
        return qrcodeImage;
    }

    /**
     * 返回生成的 QRCode 图片文件
     *
     * @return 生成的 QRCode 图片文件
     */
    public File getQrcodeFile() {
        return qrcodeFile;
    }

    /**
     * 把指定的内容生成为一个 QRCode 的图片，之后保存到指定的文件中。
     *
     * @param f 指定的文件
     * @return QRCode 处理器
     */
    public QRCode toFile(String f) {
        return toFile(new File(f));
    }

    /**
     * 把指定的内容生成为一个 QRCode 的图片，之后保存到指定的文件中。
     *
     * @param qrcodeFile 指定的文件
     * @return QRCode 处理器
     */
    public QRCode toFile(File qrcodeFile) {
        try {
            if (!qrcodeFile.exists()) {
                Files.createParentDirs(qrcodeFile);
                qrcodeFile.createNewFile();
            }

            if (!ImageIO.write(this.qrcodeImage,
                    getSuffixName(qrcodeFile),
                    qrcodeFile)) {
                throw new RuntimeException();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        this.qrcodeFile = qrcodeFile;
        return this;
    }

    /**
     * 把指定的内容生成为一个 QRCode 的图片，并在该图片中间添加上指定的图片；之后保存到指定的文件内。
     *
     * @param qrcodeFile QRCode 图片生成的指定的文件
     * @param appendFile 需要添加的图片
     * @return QRCode 处理器
     */
    public QRCode toFile(String qrcodeFile, String appendFile) {
        return toFile(new File(qrcodeFile), new File(appendFile));
    }

    /**
     * 把指定的内容生成为一个 QRCode 的图片，并在该图片中间添加上指定的图片；之后保存到指定的文件内。
     *
     * @param qrcodeFile QRCode 图片生成的指定的文件
     * @param appendFile 需要添加的图片
     * @return QRCode 处理器
     */
    public QRCode toFile(File qrcodeFile, File appendFile) {
        try {
            if (!qrcodeFile.exists()) {
                Files.createParentDirs(qrcodeFile);

                qrcodeFile.createNewFile();
            }

            appendImage(this.qrcodeImage, ImageIO.read(appendFile), this.format);
            if (!ImageIO.write(this.qrcodeImage,
                    getSuffixName(qrcodeFile),
                    qrcodeFile)) {
                throw new RuntimeException("Unexpected error writing image");
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        this.qrcodeFile = qrcodeFile;
        return this;
    }

    private void appendImage(BufferedImage baseImage,
                             BufferedImage appendImage,
                             QRCodeFormat format) {
        Graphics gc = baseImage.getGraphics();
        gc.drawImage(appendImage,
                (baseImage.getWidth() - appendImage.getWidth()) / 2,
                (baseImage.getHeight() - appendImage.getHeight()) / 2,
                appendImage.getWidth(),
                appendImage.getHeight(),
                format.getBackGroundColor(),
                null);
    }

    private String getSuffixName(File file) {
        String path = file.getAbsolutePath();

        if (Strings.isNullOrEmpty(path)) {
            return this.format.getImageFormat();
        }
        int pos = path.lastIndexOf('.');
        if (-1 == pos) {
            return this.format.getImageFormat();
        }
        return path.substring(pos + 1).toUpperCase();
    }
}


/*
 * Copyright (C) 2011-2013 Eiichiro Uchiumi. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.eiichiro.bootleg.json;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import org.apache.commons.lang.ClassUtils;
import org.eiichiro.bootleg.AbstractRequest;
import org.eiichiro.bootleg.Types;
import org.eiichiro.bootleg.WebContext;
import org.eiichiro.reverb.lang.UncheckedException;

import com.google.common.base.Function;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;

/**
 * {@code JSONRequest} is a JSON-based implementation of {@code Request}.
 * If the requested MIME media type is 'application/json', Bootleg uses this
 * implementation by default.
 *
 * @author Eiichiro Uchiumi
 */
public class JSONRequest extends AbstractRequest {

	private JsonElement element;

	/**
	 * Constructs a new {@code JSONRequest} from the current {@code WebContext}.
	 *
	 * @param context The current {@code WebContext}.
	 */
	public void from(WebContext context) {
		try {
			element = new JsonParser().parse(context.request().getReader());
		} catch (Exception e) {
			logger.warn("Cannot parse JSON string into a parse tree", e);
			throw new UncheckedException(e);
		}

		super.from(context);
	}

	/**
	 * Returns the Web endpoint method parameter value from the current HTTP
	 * request body sent as JSON format.
	 * This method supports the following parameter declaration independently.
	 * Other than them are the same as {@link AbstractRequest}.
	 *

	 *
Named collection of user-defined object type

	 *
Named user-defined object type

	 *
No-named collection of core value type

	 *
No-named collection of user-defined value type

	 *
No-named collection of user-defined object type

	 *

	 *
	 * @param type Web endpoint method parameter type.
	 * @param name Web endpoint method parameter name.
	 * @return Web endpoint method parameter value.
	 */
	@Override
	protected Object body(Type type, String name) {
		return body(type, name, element);
	}

	@SuppressWarnings("unchecked")
	private Object body(Type type, String name, final JsonElement element) {
		Function<String, Object> value = new Function<String, Object>() {

			public Object apply(String name) {
				if (element instanceof JsonObject) {
					JsonObject jsonObject = (JsonObject) element;
					return jsonObject.get(name);
				} else {
					return null;
				}
			}

		};
		Function<String, Collection<Object>> values = new Function<String, Collection<Object>>() {

			public Collection<Object> apply(String name) {
				if (element instanceof JsonObject) {
					JsonObject jsonObject = (JsonObject) element;
					JsonElement jsonElement = jsonObject.get(name);

					if (jsonElement != null && jsonElement.isJsonArray()) {
						Collection<Object> collection = new ArrayList<Object>();
						Iterator<JsonElement> iterator = jsonElement.getAsJsonArray().iterator();

						while (iterator.hasNext()) {
							collection.add(iterator.next());
						}

						return (collection.isEmpty()) ? null : collection;
					} else {
						logger.warn("Element [" + name + "] is not a JSON array");
						return null;
					}

				} else if ((name == null || name.isEmpty()) && element instanceof JsonArray) {
					JsonArray array = (JsonArray) element;
					Collection<Object> collection = new ArrayList<Object>();
					Iterator<JsonElement> iterator = array.iterator();

					while (iterator.hasNext()) {
						collection.add(iterator.next());
					}

					return (collection.isEmpty()) ? null : collection;
				} else {
					return null;
				}
			}

		};

		if (name != null && !name.isEmpty()) {
			if (Types.isCollection(type)) {
				if (Types.isSupportedCollection(type)) {
					Class<?> elementType = Types.getElementType(type);

					if (!Types.isCoreValueType(elementType)
							&& !Types.isUserDefinedValueType(elementType)) {
						// Named collection of user-defined object type.
						Collection<Object> jsonElements = values.apply(name);

						if (jsonElements == null) {
							logger.debug("Collection named [" + name + "] not found");
							return jsonElements;
						}

						try {
							Class<?> implementationType = Types.getDefaultImplementationType(type);
							Collection<Object> collection = (Collection<Object>) implementationType.newInstance();

							try {
								for (Object jsonElement : jsonElements) {
									Object instance = elementType.newInstance();

									for (Field field : elementType.getDeclaredFields()) {
										Object object = body(field.getGenericType(), field.getName(), (JsonElement) jsonElement);

										if (object != null) {
											field.setAccessible(true);
											field.set(instance, object);
										}
									}

									collection.add(instance);
								}

								return (collection.isEmpty()) ? null : collection;
							} catch (Exception e) {
								logger.warn("Cannot instantiate [" + elementType
										+ "] (Collection element type of [" + type
										+ "])", e);
							}

						} catch (Exception e) {
							logger.debug("Cannot instantiate ["
									+ Types.getDefaultImplementationType(type)
									+ "] (Default implementation type of ["
									+ type + "])", e);
						}

						return null;
					}
				}

			} else if (!Types.isCoreValueType(type)
					&& !Types.isUserDefinedValueType(type)) {
				// Named user-defined object type.
				Class<?> rawType = Types.getRawType(type);
				Object jsonElement = value.apply(name);

				try {
					Object instance = rawType.newInstance();

					for (Field field : rawType.getDeclaredFields()) {
						Object object = body(field.getGenericType(), field.getName(), (JsonElement) jsonElement);

						if (object != null) {
							field.setAccessible(true);
							field.set(instance, object);
						}
					}

					return instance;
				} catch (Exception e) {
					logger.warn("Cannot instantiate [" + type + "]", e);
				}

				return null;
			}

		} else {
			if (Types.isCollection(type)) {
				if (Types.isSupportedCollection(type)) {
					Collection<Object> objects = values.apply(name);

					if (objects == null) {
						logger.debug("Posted JSON element is not a JSON array");
						return objects;
					}

					try {
						Class<?> implementationType = Types.getDefaultImplementationType(type);
						Collection<Object> collection = (Collection<Object>) implementationType.newInstance();
						Class<?> elementType = Types.getElementType(type);
						boolean coreValueType = Types.isCoreValueType(elementType);

						try {
							if (coreValueType || Types.isUserDefinedValueType(elementType)) {
								// No-named collection of core value type.
								// No-named collection of user-defined value type.
								for (Object object : objects) {
									Object convert = (coreValueType) ? convert(object, elementType)
											: convertUserDefinedValueType(object, elementType);

									if (convert != null
											&& ClassUtils.primitiveToWrapper(elementType).isAssignableFrom(convert.getClass())) {
										collection.add(convert);
									} else {
										logger.debug("Parameter [" + convert
												+ "] cannot be converted to ["
												+ elementType + "]");
									}
								}

							} else {
								// No-named collection of user-defined object type.
								for (Object jsonElement : objects) {
									Object instance = elementType.newInstance();

									for (Field field : elementType.getDeclaredFields()) {
										Object object = body(field.getGenericType(), field.getName(), (JsonElement) jsonElement);

										if (object != null) {
											field.setAccessible(true);
											field.set(instance, object);
										}
									}

									collection.add(instance);
								}
							}

							return (collection.isEmpty()) ? null : collection;
						} catch (Exception e) {
							logger.warn("Cannot instantiate [" + elementType
									+ "] (Collection element type of [" + type
									+ "])", e);
						}

					} catch (Exception e) {
						logger.debug("Cannot instantiate ["
								+ Types.getDefaultImplementationType(type)
								+ "] (Default implementation type of ["
								+ type + "])", e);
					}
				}

				return null;
			}
		}

		return parameter(type, name, value, values);
	}

	/**
	 * Converts the specified object to the specified type.
	 * The object to be converted must be a {@code JsonPrimitive}, or this
	 * method return null.
	 *
	 * @param object The object to be converted
	 * @param type The type to which the specified object be converted.
	 * @return The converted object.
	 */
	@Override
	protected Object convert(Object object, Class<?> type) {
		if (object instanceof JsonPrimitive) {
			JsonPrimitive primitive = (JsonPrimitive) object;
			return super.convert(primitive.getAsString(), type);
		} else {
			logger.warn("Parameter [" + object + "] cannot be converted to ["
					+ type + "]; Converted 'object' must be a ["
					+ JsonPrimitive.class + "]");
			return null;
		}
	}

	/**
	 * Converts the specified object to the specified user-defined value type.
	 * The object to be converted must be a {@code JsonPrimitive}, or this
	 * method return null.
	 *
	 * @param object The object to be converted
	 * @param type The user-defined value type to which the specified object be
	 * converted.
	 * @return The converted object.
	 */
	@Override
	protected Object convertUserDefinedValueType(Object object, Class<?> type) {
		if (object instanceof JsonPrimitive) {
			JsonPrimitive primitive = (JsonPrimitive) object;
			return super.convertUserDefinedValueType(primitive.getAsString(), type);
		} else {
			logger.warn("Parameter [" + object + "] cannot be converted to ["
					+ type + "]; Converted 'object' must be a ["
					+ JsonPrimitive.class + "]");
			return null;
		}
	}

}


/*
 * Copyright (C) 2011-2013 Eiichiro Uchiumi. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.eiichiro.bootleg;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * {@code BootlegFilter} is a central Servlet {@code Filter} to process Web
 * request in Bootleg framework.
 * You need to setup this filter in web.xml in order to use Bootleg, as the
 * following:
 *

 * <filter>
 * 	<filter-name>bootleg</filter-name>
 * 	<filter-class>org.eiichiro.bootleg.BootlegFilter</filter-class>
 * </filter>
 * <filter-mapping>
 * 	<filter-name>bootleg</filter-name>
 * 	<url-pattern>/*</url-pattern>
 * </filter-mapping>
 *

 *
 * @author Eiichiro Uchiumi
 */
public class BootlegFilter implements Filter {

	/** Servlet context parameter key to get custom {@code Configuration} class. */
	public static final String CONFIGURATION = "org.eiichiro.bootleg.configuration";

	private static Logger logger = LoggerFactory.getLogger(BootlegFilter.class);

	private Configuration configuration;

	private Pipeline<WebContext> pipeline;

	static {
		Logger logger = LoggerFactory.getLogger(Version.class);
		logger.info("Bootleg " + Version.MAJOR + "." + Version.MINER + "." + Version.BUILD);
//		logger.info("Copyright (C) 2011-2013 Eiichiro Uchiumi. All Rights Reserved.");
	}

	/**
	 * Initializes this filter with the specified configuration.
	 * Constructs the {@code Configuration} and sets up the HTTP request processing
	 * pipeline. If the custom configuration class has not been specified,
	 * {@link DefaultConfiguration} is used by default.
	 *
	 * @param filterConfig Servlet filter configuration.
	 */
	public void init(FilterConfig filterConfig) throws ServletException {
		logger.info("Starting Bootleg on the Servlet ["
				+ filterConfig.getServletContext().getMajorVersion() + "."
				+ filterConfig.getServletContext().getMinorVersion() + "] environment");
		Configuration configuration = configuration(filterConfig);

		if (configuration == null) {
			configuration = new DefaultConfiguration();
			configuration.init(filterConfig.getServletContext());
			logger.info("Default configuration [" + DefaultConfiguration.class + "] loaded");
		}

		Pipeline<WebContext> pipeline = configuration.pipeline();

		if (pipeline == null) {
			throw new ServletException("Pipeline must not be [" + pipeline
					+ "]: Configuration [" + configuration + "]");
		} else {
			logger.debug("HTTP request processing pipeline " + pipeline + " constructed");
		}

		this.configuration = configuration;
		this.pipeline = pipeline;
	}

	/**
	 * Processes the Web request in Bootleg.
	 *
	 * @param request HTTP request.
	 * @param response HTTP response.
	 * @throws IOException If any I/O error has occurred.
	 * @throws ServletException If any exception has occurred in processing the
	 * request.
	 */
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		try {
			pipeline.apply(new WebContext(configuration, (HttpServletRequest) request, (HttpServletResponse) response, chain));
		} catch (Exception e) {
			logger.warn("Failed to process HTTP request", e);
			((HttpServletResponse) response).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
		}
	}

	/** Destroys this fileter. */
	public void destroy() {
		logger.info("Bootleg stopped");
	}

	/**
	 * Constructs the {@code Configuration}.
	 * This method attempts to get custom configuration class from {@code ServletContext}
	 * init parameter and instantiate and initialize it. If the custom configuration
	 * class has not been specified as the {@code ServletContext} init parameter,
	 * this method returns null.
	 *
	 * @param config Servlet filter configuration.
	 * @return {@code Configuration} constructed.
	 * @throws ServletException If the custom configuration loading is failed.
	 */
	protected Configuration configuration(FilterConfig config) throws ServletException {
		ServletContext context = config.getServletContext();
		String clazz = context.getInitParameter(CONFIGURATION);
		Configuration configuration = null;

		if (clazz != null) {
			try {
				configuration = (Configuration) Class.forName(clazz).newInstance();
				configuration.init(config.getServletContext());
				logger.info("Custom configuration [" + clazz + "] loaded");
			} catch (Exception e) {
				logger.error("Failed to load custom configuration [" + clazz + "]", e);
				throw new ServletException("Failed to load custom configuration [" + clazz + "]", e);
			}
		}

		return configuration;
	}

}


package goja.initialize;

/**
 * <p> </p>
 *
 * @author sog
 * @version 1.0
 * @since JDK 1.6
 */
public final class GojaBootVersion {

    private GojaBootVersion() {
    }

    /**
     * Return the full version string of the present Goja Boot codebase, or {@code null}
     * if it cannot be determined.
     * @return the version of Goja Boot or {@code null}
     * @see Package#getImplementationVersion()
     */
    public static String getVersion() {
        Package pkg = GojaBootVersion.class.getPackage();
        return (pkg != null ? pkg.getImplementationVersion() : null);
    }

}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2013-2014 sagyf Yang. The Four Group.
 */

package goja.mvc.render.ftl.layout;

import goja.core.StringPool;
import goja.mvc.render.ftl.kit.DirectiveKit;

import freemarker.cache.TemplateCache;
import freemarker.core.Environment;
import freemarker.template.Configuration;
import freemarker.template.SimpleScalar;
import freemarker.template.Template;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;

import java.io.IOException;
import java.util.Map;

/**
 * <p> 定义模板继承的自定义指令. </p>
 *
 * @author poplar.yfyang
 * @version 1.0 2012-04-20 上午7:36
 * @since JDK 1.5
 */
public class ExtendsDirective implements TemplateDirectiveModel {
    /**
     * 自定义指令名称
     */
    public final static String DIRECTIVE_NAME = "extends";

    @SuppressWarnings("rawtypes")
    @Override
    public void execute(Environment env, Map params,
                        TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException {
        String name = DirectiveKit.getRequiredParam(params, "name");
        params.remove("name");

        if (!name.endsWith(".ftl")) {
            name = name + ".ftl";
        }
        String encoding = DirectiveKit.getParam(params, "encoding", StringPool.UTF_8);
        String includeTemplateName = TemplateCache.getFullTemplatePath(env, getTemplatePath(env), name);
        Configuration configuration = env.getConfiguration();
        final Template template = configuration.getTemplate(includeTemplateName, env.getLocale(), encoding, true);
        for (Object key : params.keySet()) {
            TemplateModel paramModule = new SimpleScalar(params.get(key).toString());
            env.setVariable(key.toString(), paramModule);
        }
        env.include(template);
    }

    /**
     * 取得模板路径的地址
     *
     * @param env Freemarker的运行环境
     * @return 模板路径地址
     */
    private String getTemplatePath(Environment env) {
        String templateName = env.getTemplate().getName();
        return templateName.lastIndexOf('/') == -1 ? "" : templateName.substring(0, templateName.lastIndexOf('/') + 1);
    }

}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2013-2014 sagyf Yang. The Four Group.
 */

package goja.core.libs;

import goja.core.StringPool;
import goja.core.exceptions.UnexpectedException;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.util.UUID;

/**
 * <p> . </p>
 *
 * @author sagyf yang
 * @version 1.0 2014-04-04 10:22
 * @since JDK 1.6
 */
public class Codec {

    /**
     * @return an UUID String
     */
    public static String UUID() {
        return UUID.randomUUID().toString();
    }

    /**
     * Encode a String to base64
     *
     * @param value The plain String
     * @return The base64 encoded String
     */
    public static String encodeBASE64(String value) {
        try {
            return new String(Base64.encodeBase64(value.getBytes(StringPool.UTF_8)));
        } catch (UnsupportedEncodingException ex) {
            throw new UnexpectedException(ex);
        }
    }

    /**
     * Encode binary data to base64
     *
     * @param value The binary data
     * @return The base64 encoded String
     */
    public static String encodeBASE64(byte[] value) {
        return new String(Base64.encodeBase64(value));
    }

    /**
     * Decode a base64 value
     *
     * @param value The base64 encoded String
     * @return decoded binary data
     */
    public static byte[] decodeBASE64(String value) {
        try {
            return Base64.decodeBase64(value.getBytes(StringPool.UTF_8));
        } catch (UnsupportedEncodingException ex) {
            throw new UnexpectedException(ex);
        }
    }

    /**
     * Build an hexadecimal MD5 hash for a String
     *
     * @param value The String to hash
     * @return An hexadecimal Hash
     */
    public static String hexMD5(String value) {
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            messageDigest.reset();
            messageDigest.update(value.getBytes("utf-8"));
            byte[] digest = messageDigest.digest();
            return byteToHexString(digest);
        } catch (Exception ex) {
            throw new UnexpectedException(ex);
        }
    }

    /**
     * Build an hexadecimal SHA1 hash for a String
     *
     * @param value The String to hash
     * @return An hexadecimal Hash
     */
    public static String hexSHA1(String value) {
        try {
            MessageDigest md;
            md = MessageDigest.getInstance("SHA-1");
            md.update(value.getBytes("utf-8"));
            byte[] digest = md.digest();
            return byteToHexString(digest);
        } catch (Exception ex) {
            throw new UnexpectedException(ex);
        }
    }

    /**
     * Write a byte array as hexadecimal String.
     */
    public static String byteToHexString(byte[] bytes) {
        return String.valueOf(Hex.encodeHex(bytes));
    }

    /**
     * Transform an hexadecimal String to a byte array.
     */
    public static byte[] hexStringToByte(String hexString) {
        try {
            return Hex.decodeHex(hexString.toCharArray());
        } catch (DecoderException e) {
            throw new UnexpectedException(e);
        }
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2013-2014 sagyf Yang. The Four Group.
 */

package goja.core.libs;

import goja.core.StringPool;
import goja.core.exceptions.UnexpectedException;
import goja.core.kits.io.OrderSafeProperties;

import org.apache.commons.io.IOUtils;

import java.io.*;
import java.util.List;
import java.util.Properties;

/**
 * <p> IO utils. </p>
 *
 * @author sagyf yang
 * @version 1.0 2014-04-04 10:18
 * @since JDK 1.6
 */
public class IO {

    /**
     * Read a properties file with the utf-8 encoding
     *
     * @param is Stream to properties file
     * @return The Properties object
     */
    public static Properties readUtf8Properties(InputStream is) {
        Properties properties = new OrderSafeProperties();
        try {
            properties.load(is);
            is.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return properties;
    }

    /**
     * Read the Stream content as a string (use utf-8)
     *
     * @param is The stream to read
     * @return The String content
     */
    public static String readContentAsString(InputStream is) {
        return readContentAsString(is, StringPool.UTF_8);
    }

    /**
     * Read the Stream content as a string
     *
     * @param is The stream to read
     * @return The String content
     */
    public static String readContentAsString(InputStream is, String encoding) {
        String res = null;
        try {
            res = IOUtils.toString(is, encoding);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                is.close();
            } catch (Exception e) {
                //
            }
        }
        return res;
    }

    /**
     * Read file content to a String (always use utf-8)
     *
     * @param file The file to read
     * @return The String content
     */
    public static String readContentAsString(File file) {
        return readContentAsString(file, "utf-8");
    }

    /**
     * Read file content to a String
     *
     * @param file The file to read
     * @return The String content
     */
    public static String readContentAsString(File file, String encoding) {
        InputStream is = null;
        try {
            is = new FileInputStream(file);
            StringWriter result = new StringWriter();
            PrintWriter out = new PrintWriter(result);
            BufferedReader reader = new BufferedReader(new InputStreamReader(is, encoding));
            String line = null;
            while ((line = reader.readLine()) != null) {
                out.println(line);
            }
            return result.toString();
        } catch (IOException e) {
            throw new UnexpectedException(e);
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (Exception e) {
                    //
                }
            }
        }
    }

    public static List<String> readLines(InputStream is) {
        List<String> lines = null;
        try {
            lines = IOUtils.readLines(is);
        } catch (IOException ex) {
            throw new UnexpectedException(ex);
        }
        return lines;
    }

    public static List<String> readLines(File file, String encoding) {
        List<String> lines = null;
        InputStream is = null;
        try {
            is = new FileInputStream(file);
            lines = IOUtils.readLines(is, encoding);
        } catch (IOException ex) {
            throw new UnexpectedException(ex);
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (Exception e) {
                    //
                }
            }
        }
        return lines;
    }

    public static List<String> readLines(File file) {
        return readLines(file, "utf-8");
    }

    /**
     * Read binary content of a file (warning does not use on large file !)
     *
     * @param file The file te read
     * @return The binary data
     */
    public static byte[] readContent(File file) {
        InputStream is = null;
        try {
            is = new FileInputStream(file);
            byte[] result = new byte[(int) file.length()];
            is.read(result);
            return result;
        } catch (IOException e) {
            throw new UnexpectedException(e);
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (Exception e) {
                    //
                }
            }
        }
    }

    /**
     * Read binary content of a stream (warning does not use on large file !)
     *
     * @param is The stream to read
     * @return The binary data
     */
    public static byte[] readContent(InputStream is) {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int read = 0;
            byte[] buffer = new byte[8096];
            while ((read = is.read(buffer)) > 0) {
                baos.write(buffer, 0, read);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            throw new UnexpectedException(e);
        }
    }

    /**
     * Write String content to a stream (always use utf-8)
     *
     * @param content The content to write
     * @param os      The stream to write
     */
    public static void writeContent(CharSequence content, OutputStream os) {
        writeContent(content, os, "utf-8");
    }

    /**
     * Write String content to a stream (always use utf-8)
     *
     * @param content The content to write
     * @param os      The stream to write
     */
    public static void writeContent(CharSequence content, OutputStream os, String encoding) {
        try {
            PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(os, encoding));
            printWriter.println(content);
            printWriter.flush();
            os.flush();
        } catch (IOException e) {
            throw new UnexpectedException(e);
        } finally {
            try {
                os.close();
            } catch (Exception e) {
                //
            }
        }
    }

    /**
     * Write String content to a file (always use utf-8)
     *
     * @param content The content to write
     * @param file    The file to write
     */
    public static void writeContent(CharSequence content, File file) {
        writeContent(content, file, "utf-8");
    }

    /**
     * Write String content to a file (always use utf-8)
     *
     * @param content The content to write
     * @param file    The file to write
     */
    public static void writeContent(CharSequence content, File file, String encoding) {
        OutputStream os = null;
        try {
            os = new FileOutputStream(file);
            PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(os, encoding));
            printWriter.println(content);
            printWriter.flush();
            os.flush();
        } catch (IOException e) {
            throw new UnexpectedException(e);
        } finally {
            try {
                if (os != null) os.close();
            } catch (Exception e) {
                //
            }
        }
    }

    /**
     * Write binay data to a file
     *
     * @param data The binary data to write
     * @param file The file to write
     */
    public static void write(byte[] data, File file) {
        OutputStream os = null;
        try {
            os = new FileOutputStream(file);
            os.write(data);
            os.flush();
        } catch (IOException e) {
            throw new UnexpectedException(e);
        } finally {
            try {
                if (os != null) os.close();
            } catch (Exception e) {
                //
            }
        }
    }

    /**
     * Copy an stream to another one.
     */
    public static void copy(InputStream is, OutputStream os) {
        try {
            int read = 0;
            byte[] buffer = new byte[8096];
            while ((read = is.read(buffer)) > 0) {
                os.write(buffer, 0, read);
            }
        } catch (IOException e) {
            throw new UnexpectedException(e);
        } finally {
            try {
                is.close();
            } catch (Exception e) {
                //
            }
        }
    }

    /**
     * Copy an stream to another one.
     */
    public static void write(InputStream is, OutputStream os) {
        try {
            int read = 0;
            byte[] buffer = new byte[8096];
            while ((read = is.read(buffer)) > 0) {
                os.write(buffer, 0, read);
            }
        } catch (IOException e) {
            throw new UnexpectedException(e);
        } finally {
            try {
                is.close();
            } catch (Exception e) {
                //
            }
            try {
                os.close();
            } catch (Exception e) {
                //
            }
        }
    }

    /**
     * Copy an stream to another one.
     */
    public static void write(InputStream is, File f) {
        OutputStream os = null;
        try {
            os = new FileOutputStream(f);
            int read = 0;
            byte[] buffer = new byte[8096];
            while ((read = is.read(buffer)) > 0) {
                os.write(buffer, 0, read);
            }
        } catch (IOException e) {
            throw new UnexpectedException(e);
        } finally {
            try {
                is.close();
            } catch (Exception e) {
                //
            }
            try {
                if (os != null) os.close();
            } catch (Exception e) {
                //
            }
        }
    }

    // If targetLocation does not exist, it will be created.
    public static void copyDirectory(File source, File target) {
        if (source.isDirectory()) {
            if (!target.exists()) {
                target.mkdir();
            }
            for (String child : source.list()) {
                copyDirectory(new File(source, child), new File(target, child));
            }
        } else {
            try {
                write(new FileInputStream(source), new FileOutputStream(target));
            } catch (IOException e) {
                throw new UnexpectedException(e);
            }
        }
    }
}


package co.omkar.utility;

import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import java.util.Arrays;

import co.omkar.utility.opermission.RequestPermission;
import co.omkar.utility.opermission.annotation.DeniedPermission;
import co.omkar.utility.opermission.annotation.GrantedPermission;
import co.omkar.utility.opermission.annotation.OPermission;
import co.omkar.utility.opermission.annotation.Result;
import co.omkar.utility.opermission.bean.PermBean;
import co.omkar.utility.opermission.bean.Permission;

/**
 * Basic Activity example.
 * Created on 11/09/16.
 *
 * @author Omkar Todkar.
 */
public class ExampleActivity extends AppCompatActivity {

    private static final String TAG = "Example";

    private BroadcastReceiver mReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            Bundle extras = intent.getExtras();
            String[] grantedPermissions = extras.getStringArray(RequestPermission.GRANTED);
            String[] deniedPermissions = extras.getStringArray(RequestPermission.DENIED);

            if (grantedPermissions != null) {
                Log.e(TAG, "onReceive: Granted Permissions - " + Arrays.toString(grantedPermissions));
            }
            if (deniedPermissions != null) {
                Log.e(TAG, "onReceive: Denied Permissions - " + Arrays.toString(deniedPermissions));
            }
        }
    };

    private TextView message;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_example);
        message = (TextView) findViewById(R.id.text);
        message.setText("Asking permission...");

        /* Multiple permission example. */
        PermBean permBean = new PermBean();

        // wrapper of permissions.
        permBean.put(Permission.READ_PHONE_STATE, "To process some features application required permissions to access your phone state.");
        permBean.put(Permission.WRITE_EXTERNAL_STORAGE, "To store some of your important data related application we required permission to access your External Storage.");

        String locationMessage = "To know your location application required permission to access your GPS";
        permBean.put(Permission.ACCESS_FINE_LOCATION, locationMessage);
        permBean.put(Permission.ACCESS_COARSE_LOCATION, locationMessage);

        // prepare permission request.
        RequestPermission.on(this).with(PermBean.create(Permission.READ_PHONE_STATE))
                .request();
    }

    @Override
    protected void onStart() {
        super.onStart();
        LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver,
                new IntentFilter(RequestPermission.PERMISSION_RESULT_BROADCAST));
    }

    @Override
    protected void onStop() {
        super.onStop();
        LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
    }

    public void updateView(String data) {
        message.append(data);
    }

    /**
     *
Executes this method on permission result.
     * Get boolean result as method parameter.
     * Granted = True && Denied = False.

     *
     * @param isGranted boolean on permission result.
     */
    @OPermission(value = Permission.WRITE_EXTERNAL_STORAGE)
    void onStoragePermission(@Result boolean isGranted) {
        if (isGranted) {
            updateView("\n Write Storage Granted");
        } else {
            updateView("\n Write Storage Denied");
        }
    }

    /**
     *
Executes this method on permission results.
     * Get boolean result as method parameter.

     *
     * @param isGranted True only if all permissions are granted else False.
     */
    @OPermission(values = {Permission.ACCESS_COARSE_LOCATION,
            Permission.ACCESS_FINE_LOCATION})
    void onLocationPermission(@Result boolean isGranted) {
        if (isGranted) {
            updateView("\n Location status Granted");
        } else {
            updateView("\n Location status Denied");
        }
    }

    /**
     * Executes method on READ_PHONE_STATE method is granted.
     */
    @GrantedPermission(permission = Manifest.permission.READ_PHONE_STATE)
    void onReadPhonePermissionGranted() {
        updateView("\n Read Phone status Granted");
    }

    /**
     * Executes method on READ_PHONE_STATE method is denied.
     */
    @DeniedPermission(permission = Manifest.permission.READ_PHONE_STATE)
    void onReadPhonePermissionDenied() {
        updateView("\n Read Phone status Denied");
    }

    public void switchActivity(View view) {
        startActivity(new Intent(this, MVPExample.class));
        finish();
    }
}


package goja.core.kits;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Currency;

/**
 * 单币种货币类，处理货币算术、币种和取整。

 货币类中封装了货币金额和币种。目前金额在内部是long类型表示， 单位是所属币种的最小货币单位（对人民币是分）。

 * 目前，货币实现了以下主要功能：

支持货币对象与double(float)/long(int)/String/BigDecimal之间相互转换。
 *
货币类在运算中提供与JDK中的BigDecimal类似的运算接口， BigDecimal的运算接口支持任意指定精度的运算功能，能够支持各种 可能的财务规则。
 *
货币类在运算中也提供一组简单运算接口，使用这组运算接口，则在 精度处理上使用缺省的处理规则。
推荐使用Money，不建议直接使用BigDecimal的原因之一在于，
 * 使用BigDecimal，同样金额和币种的货币使用BigDecimal存在多种可能 的表示，例如：new BigDecimal("10.5")与new BigDecimal("10.50")
 * 不相等，因为scale不等。使得Money类，同样金额和币种的货币只有 一种表示方式，new Money("10.5")和new Money("10.50")应该是相等的。
 *
提供基本的格式化功能。
Money类中不包含与业务相关的统计功能和格式化功能。业务相关的功能 建议使用utility类来实现。
 *
Money类实现了Serializable接口，支持作为远程调用的参数和返回值。
Money类实现了equals和hashCode方法。

 */
@SuppressWarnings("UnusedDeclaration")
public class MoneyKit implements Serializable, Comparable<MoneyKit> {
    /**
     * 缺省的币种代码，为CNY（人民币）。
     */
    public static final  String   DEFAULT_CURRENCY_CODE = "CNY";
    /**
     * 缺省的取整模式，为BigDecimal.ROUND_HALF_EVEN （四舍五入，当小数为0.5时，则取最近的偶数）。
     */
    public static final  int      DEFAULT_ROUNDING_MODE = BigDecimal.ROUND_HALF_EVEN;
    /**
     * 0元,静态常量
     */
    public static final  MoneyKit ZERO                  = new MoneyKit() {
        private static final long serialVersionUID = 1662793805847514821L;

        public void setCent(long cent) {
            throw new UnsupportedOperationException();
        }
    };
    private static final long     serialVersionUID      = -118437905184548616L;
    /**
     * 一组可能的元/分换算比例。

 此处，“分”是指货币的最小单位，“元”是货币的最常用单位， 不同的币种有不同的元/分换算比例，如人民币是100，而日元为1。
     */
    private static final int[]    centFactors           = new int[]{1, 10, 100, 1000};
    /**
     * 金额，以分为单位。
     */
    private long cent;

    /**
     * 币种。
     */
    private Currency currency;

    // 构造器 ====================================================

    /**
     * 缺省构造器。

 创建一个具有缺省金额（0）和缺省币种的货币对象。
     */
    public MoneyKit() {
        this(0);
    }

    /**
     * 构造器。

 创建一个具有金额yuan元cent</cent>分和缺省币种的货币对象。
     *
     * @param yuan 金额元数。
     * @param cent 金额分数。
     */
    public MoneyKit(long yuan, int cent) {
        this(yuan, cent, Currency.getInstance(DEFAULT_CURRENCY_CODE));
    }

    /**
     * 构造器。

 创建一个具有金额yuan元cent分和指定币种的货币对象。
     *
     * @param yuan 金额元数。
     * @param cent 金额分数。
     */
    public MoneyKit(long yuan, int cent, Currency currency) {
        this.currency = currency;

        this.cent = (yuan * getCentFactor()) + (cent % getCentFactor());
    }

    /**
     * 构造器。

 创建一个具有金额amount元和缺省币种的货币对象。
     *
     * @param amount 金额，以元为单位。
     */
    public MoneyKit(String amount) {
        this(amount, Currency.getInstance(DEFAULT_CURRENCY_CODE));
    }

    /**
     * 构造器。

 创建一个具有金额amount元和指定币种currency的货币对象。
     *
     * @param amount   金额，以元为单位。
     * @param currency 币种。
     */
    public MoneyKit(String amount, Currency currency) {
        this(new BigDecimal(amount), currency);
    }

    /**
     * 构造器。

 创建一个具有金额amount元和指定币种currency的货币对象。
     * 如果金额不能转换为整数分，则使用指定的取整模式roundingMode取整。
     *
     * @param amount       金额，以元为单位。
     * @param currency     币种。
     * @param roundingMode 取整模式。
     */
    public MoneyKit(String amount, Currency currency, int roundingMode) {
        this(new BigDecimal(amount), currency, roundingMode);
    }

    /**
     * 构造器。

 创建一个具有参数amount指定金额和缺省币种的货币对象。 如果金额不能转换为整数分，则使用四舍五入方式取整。

     * 注意：由于double类型运算中存在误差，使用四舍五入方式取整的 结果并不确定，因此，应尽量避免使用double类型创建货币类型。 例：  assertEquals(999,
     * Math.round(9.995 * 100)); assertEquals(1000, Math.round(999.5)); money = new Money((9.995));
     * assertEquals(999, money.getCent()); money = new Money(10.005); assertEquals(1001,
     * money.getCent());
     *
     * @param amount 金额，以元为单位。
     */
    public MoneyKit(double amount) {
        this(amount, Currency.getInstance(DEFAULT_CURRENCY_CODE));
    }

    /**
     * 构造器。

 创建一个具有金额amount和指定币种的货币对象。 如果金额不能转换为整数分，则使用四舍五入方式取整。

     * 注意：由于double类型运算中存在误差，使用四舍五入方式取整的 结果并不确定，因此，应尽量避免使用double类型创建货币类型。 例：  assertEquals(999,
     * Math.round(9.995 * 100)); assertEquals(1000, Math.round(999.5)); money = new Money((9.995));
     * assertEquals(999, money.getCent()); money = new Money(10.005); assertEquals(1001,
     * money.getCent());
     *
     * @param amount   金额，以元为单位。
     * @param currency 币种。
     */
    public MoneyKit(double amount, Currency currency) {
        this.currency = currency;
        this.cent = Math.round(amount * getCentFactor());
    }

    /**
     * 构造器。

 创建一个具有金额amount和缺省币种的货币对象。 如果金额不能转换为整数分，则使用缺省取整模式DEFAULT_ROUNDING_MODE取整。
     *
     * @param amount 金额，以元为单位。
     */
    public MoneyKit(BigDecimal amount) {
        this(amount, Currency.getInstance(DEFAULT_CURRENCY_CODE));
    }

    /**
     * 构造器。

 创建一个具有参数amount指定金额和缺省币种的货币对象。 如果金额不能转换为整数分，则使用指定的取整模式roundingMode取整。
     *
     * @param amount       金额，以元为单位。
     * @param roundingMode 取整模式
     */
    public MoneyKit(BigDecimal amount, int roundingMode) {
        this(amount, Currency.getInstance(DEFAULT_CURRENCY_CODE), roundingMode);
    }

    /**
     * 构造器。

 创建一个具有金额amount和指定币种的货币对象。 如果金额不能转换为整数分，则使用缺省的取整模式DEFAULT_ROUNDING_MODE进行取整。
     *
     * @param amount   金额，以元为单位。
     * @param currency 币种
     */
    public MoneyKit(BigDecimal amount, Currency currency) {
        this(amount, currency, DEFAULT_ROUNDING_MODE);
    }

    /**
     * 构造器。

 创建一个具有金额amount和指定币种的货币对象。 如果金额不能转换为整数分，则使用指定的取整模式roundingMode取整。
     *
     * @param amount       金额，以元为单位。
     * @param currency     币种。
     * @param roundingMode 取整模式。
     */
    public MoneyKit(BigDecimal amount, Currency currency, int roundingMode) {
        this.currency = currency;
        this.cent = rounding(amount.movePointRight(currency.getDefaultFractionDigits()),
                roundingMode);
    }

    // Bean方法 ====================================================

    /**
     * 获取本货币对象代表的金额数。
     *
     * @return 金额数，以元为单位。
     */
    public BigDecimal getAmount() {
        return BigDecimal.valueOf(cent, currency.getDefaultFractionDigits());
    }

    /**
     * 获取本货币对象代表的金额数。
     *
     * @return 金额数，以分为单位。
     */
    public long getCent() {
        return cent;
    }

    /**
     * 设置本货币对象代表的金额数。
     *
     * @param cent 金额数，以分为单位。
     */
    public void setCent(long cent) {
        this.cent = cent;
    }

    /**
     * 获取本货币对象代表的币种。
     *
     * @return 本货币对象所代表的币种。
     */
    public Currency getCurrency() {
        return currency;
    }

    /**
     * 获取本货币币种的元/分换算比率。
     *
     * @return 本货币币种的元/分换算比率。
     */
    public int getCentFactor() {
        return centFactors[currency.getDefaultFractionDigits()];
    }

    // 基本对象方法 ===================================================

    /**
     * 判断本货币对象与另一对象是否相等。

 本货币对象与另一对象相等的充分必要条件是：

另一对象也属货币对象类。
金额相同。
币种相同。
     *

     *
     * @param other 待比较的另一对象。
     * @return true表示相等，false表示不相等。
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object other) {
        return (other instanceof MoneyKit) && equals((MoneyKit) other);
    }

    /**
     * 判断本货币对象与另一货币对象是否相等。

 本货币对象与另一货币对象相等的充分必要条件是：

金额相同。
币种相同。

     *
     * @param other 待比较的另一货币对象。
     * @return true表示相等，false表示不相等。
     */
    public boolean equals(MoneyKit other) {
        return currency.equals(other.currency) && (cent == other.cent);
    }

    /**
     * 计算本货币对象的杂凑值。
     *
     * @return 本货币对象的杂凑值。
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
        return (int) (cent ^ (cent >>> 32));
    }

    // Comparable接口 ========================================

    /**
     * 货币比较。

 比较本货币对象与另一货币对象的大小。 如果待比较的两个货币对象的币种不同，则抛出java.lang.IllegalArgumentException。
     * 如果本货币对象的金额少于待比较货币对象，则返回-1。 如果本货币对象的金额等于待比较货币对象，则返回0。 如果本货币对象的金额大于待比较货币对象，则返回1。
     *
     * @param other 另一对象。
     * @return -1表示小于，0表示等于，1表示大于。
     * @throws IllegalArgumentException 待比较货币对象与本货币对象的币种不同。
     */
    public int compareTo(MoneyKit other) {
        assertSameCurrencyAs(other);

        if (cent < other.cent) {
            return -1;
        } else if (cent == other.cent) {
            return 0;
        } else {
            return 1;
        }
    }

    /**
     * 货币大于比较。

 判断本货币对象是否大于另一货币对象。 如果待比较的两个货币对象的币种不同，则抛出java.lang.IllegalArgumentException。
     * 如果本货币对象的金额大于待比较货币对象，则返回true，否则返回false。
     *
     * @param other 另一对象。
     * @return true表示大于，false表示不大于（小于等于）。
     * @throws IllegalArgumentException 待比较货币对象与本货币对象的币种不同。
     */
    public boolean greaterThan(MoneyKit other) {
        return compareTo(other) > 0;
    }

    /**
     * 货币大于等于比较。

 判断本货币对象是否大于等于另一货币对象。 如果待比较的两个货币对象的币种不同，则抛出java.lang.IllegalArgumentException。
     * 如果本货币对象的金额大于等于待比较货币对象，则返回true，否则返回false。
     *
     * @param other 另一对象。
     * @return true表示大于等于，false表示小于。
     * @throws IllegalArgumentException 待比较货币对象与本货币对象的币种不同。
     */
    public boolean greaterThanEquals(MoneyKit other) {
        return compareTo(other) >= 0;
    }

    /**
     * 货币小于比较。

 判断本货币对象是否小于另一货币对象。 如果待比较的两个货币对象的币种不同，则抛出java.lang.IllegalArgumentException。
     * 如果本货币对象的金额小于待比较货币对象，则返回true，否则返回false。
     *
     * @param other 另一对象。
     * @return true表示小于，false表示不小于（小于等于）。
     * @throws IllegalArgumentException 待比较货币对象与本货币对象的币种不同。
     */
    public boolean lessThan(MoneyKit other) {
        return compareTo(other) < 0;
    }

    /**
     * 货币小于等于比较。

 判断本货币对象是否小于等于另一货币对象。 如果待比较的两个货币对象的币种不同，则抛出java.lang.IllegalArgumentException。
     * 如果本货币对象的金额小于等于待比较货币对象，则返回true，否则返回false。
     *
     * @param other 另一对象。
     * @return true表示小于等于，false表示大于。
     * @throws IllegalArgumentException 待比较货币对象与本货币对象的币种不同。
     */
    public boolean lessThanEquals(MoneyKit other) {
        return compareTo(other) <= 0;
    }

    // 货币算术 ==========================================

    /**
     * 货币加法。

 如果两货币币种相同，则返回一个新的相同币种的货币对象，其金额为 两货币对象金额之和，本货币对象的值不变。
     * 如果两货币对象币种不同，抛出java.lang.IllegalArgumentException。
     *
     * @param other 作为加数的货币对象。
     * @return 相加后的结果。
     * @throws IllegalArgumentException 如果本货币对象与另一货币对象币种不同。
     */
    public MoneyKit add(MoneyKit other) {
        assertSameCurrencyAs(other);

        return newMoneyWithSameCurrency(cent + other.cent);
    }

    /**
     * 货币减法。

 如果两货币币种相同，则返回一个新的相同币种的货币对象，其金额为 本货币对象的金额减去参数货币对象的金额。本货币对象的值不变。
     * 如果两货币币种不同，抛出java.lang.IllegalArgumentException。
     *
     * @param other 作为减数的货币对象。
     * @return 相减后的结果。
     * @throws IllegalArgumentException 如果本货币对象与另一货币对象币种不同。
     */
    public MoneyKit subtract(MoneyKit other) {
        assertSameCurrencyAs(other);

        return newMoneyWithSameCurrency(cent - other.cent);
    }

    /**
     * 货币乘法。

 返回一个新的货币对象，币种与本货币对象相同，金额为本货币对象的金额乘以乘数。 本货币对象的值不变。
     *
     * @param val 乘数
     * @return 乘法后的结果。
     */
    public MoneyKit multiply(long val) {
        return newMoneyWithSameCurrency(cent * val);
    }

    /**
     * 货币乘法。

 返回一个新的货币对象，币种与本货币对象相同，金额为本货币对象的金额乘以乘数。 本货币对象的值不变。如果相乘后的金额不能转换为整数分，则四舍五入。
     *
     * @param val 乘数
     * @return 相乘后的结果。
     */
    public MoneyKit multiply(double val) {
        return newMoneyWithSameCurrency(Math.round(cent * val));
    }

    /**
     * 货币乘法。

 返回一个新的货币对象，币种与本货币对象相同，金额为本货币对象的金额乘以乘数。 本货币对象的值不变。如果相乘后的金额不能转换为整数分，使用缺省的取整模式
     * DEFUALT_ROUNDING_MODE进行取整。
     *
     * @param val 乘数
     * @return 相乘后的结果。
     */
    public MoneyKit multiply(BigDecimal val) {
        return multiply(val, DEFAULT_ROUNDING_MODE);
    }

    /**
     * 货币乘法。

 返回一个新的货币对象，币种与本货币对象相同，金额为本货币对象的金额乘以乘数。 本货币对象的值不变。如果相乘后的金额不能转换为整数分，使用指定的取整方式
     * roundingMode进行取整。
     *
     * @param val          乘数
     * @param roundingMode 取整方式
     * @return 相乘后的结果。
     */
    public MoneyKit multiply(BigDecimal val, int roundingMode) {
        BigDecimal newCent = BigDecimal.valueOf(cent).multiply(val);

        return newMoneyWithSameCurrency(rounding(newCent, roundingMode));
    }

    /**
     * 货币除法。

 返回一个新的货币对象，币种与本货币对象相同，金额为本货币对象的金额除以除数。 本货币对象的值不变。如果相除后的金额不能转换为整数分，使用四舍五入方式取整。
     *
     * @param val 除数
     * @return 相除后的结果。
     */
    public MoneyKit divide(double val) {
        return newMoneyWithSameCurrency(Math.round(cent / val));
    }

    /**
     * 货币除法。

 返回一个新的货币对象，币种与本货币对象相同，金额为本货币对象的金额除以除数。 本货币对象的值不变。如果相除后的金额不能转换为整数分，使用缺省的取整模式
     * DEFAULT_ROUNDING_MODE进行取整。
     *
     * @param val 除数
     * @return 相除后的结果。
     */
    public MoneyKit divide(BigDecimal val) {
        return divide(val, DEFAULT_ROUNDING_MODE);
    }

    /**
     * 货币除法。

 返回一个新的货币对象，币种与本货币对象相同，金额为本货币对象的金额除以除数。 本货币对象的值不变。如果相除后的金额不能转换为整数分，使用指定的取整模式
     * roundingMode进行取整。
     *
     * @param val          除数
     * @param roundingMode 取整
     * @return 相除后的结果。
     */
    public MoneyKit divide(BigDecimal val, int roundingMode) {
        BigDecimal newCent = BigDecimal.valueOf(cent).divide(val, roundingMode);

        return newMoneyWithSameCurrency(newCent.longValue());
    }

    /**
     * 货币分配。

 将本货币对象尽可能平均分配成targets份。 如果不能平均分配尽，则将零头放到开始的若干份中。分配
     * 运算能够确保不会丢失金额零头。
     *
     * @param targets 待分配的份数
     * @return 货币对象数组，数组的长度与分配份数相同，数组元素 从大到小排列，所有货币对象的金额最多只相差1分。
     */
    public MoneyKit[] allocate(int targets) {
        MoneyKit[] results = new MoneyKit[targets];

        MoneyKit lowResult = newMoneyWithSameCurrency(cent / targets);
        MoneyKit highResult = newMoneyWithSameCurrency(lowResult.cent + 1);

        int remainder = (int) cent % targets;

        for (int i = 0; i < remainder; i++) {
            results[i] = highResult;
        }

        for (int i = remainder; i < targets; i++) {
            results[i] = lowResult;
        }

        return results;
    }

    /**
     * 货币分配。

 将本货币对象按照规定的比例分配成若干份。分配所剩的零头 从第一份开始顺序分配。分配运算确保不会丢失金额零头。
     *
     * @param ratios 分配比例数组，每一个比例是一个长整型，代表 相对于总数的相对数。
     * @return 货币对象数组，数组的长度与分配比例数组的长度相同。
     */
    public MoneyKit[] allocate(long[] ratios) {
        MoneyKit[] results = new MoneyKit[ratios.length];

        long total = 0;

        for (long ratio : ratios) {
            total += ratio;
        }

        long remainder = cent;

        for (int i = 0; i < results.length; i++) {
            results[i] = newMoneyWithSameCurrency((cent * ratios[i]) / total);
            remainder -= results[i].cent;
        }

        for (int i = 0; i < remainder; i++) {
            results[i].cent++;
        }

        return results;
    }

    // 格式化方法 =================================================

    /**
     * 生成本对象的缺省字符串表示
     */
    public String toString() {
        return getAmount().toString();
    }

    // 内部方法 ===================================================

    /**
     * 断言本货币对象与另一货币对象是否具有相同的币种。

 如果本货币对象与另一货币对象具有相同的币种，则方法返回。 否则抛出运行时异常java.lang.IllegalArgumentException。
     *
     * @param other 另一货币对象
     * @throws IllegalArgumentException 如果本货币对象与另一货币对象币种不同。
     */
    protected void assertSameCurrencyAs(MoneyKit other) {
        if (!currency.equals(other.currency)) {
            throw new IllegalArgumentException("Money math currency mismatch.");
        }
    }

    /**
     * 对BigDecimal型的值按指定取整方式取整。
     *
     * @param val          待取整的BigDecimal值
     * @param roundingMode 取整方式
     * @return 取整后的long型值
     */
    protected long rounding(BigDecimal val, int roundingMode) {
        return val.setScale(0, roundingMode).longValue();
    }

    /**
     * 创建一个币种相同，具有指定金额的货币对象。
     *
     * @param cent 金额，以分为单位
     * @return 一个新建的币种相同，具有指定金额的货币对象
     */
    protected MoneyKit newMoneyWithSameCurrency(long cent) {
        MoneyKit money = new MoneyKit(0, currency);

        money.cent = cent;

        return money;
    }

    // 调试方式 ==================================================

    /**
     * 生成本对象内部变量的字符串表示，用于调试。
     *
     * @return 本对象内部变量的字符串表示。
     */
    public String dump() {
        String lineSeparator = System.getProperty("line.separator");

        return "cent = " + cent + lineSeparator + "currency = " + currency;
    }

    /**
     * 将Money根据汇率表转换成为另外一种货币Money
     *
     * @param currency 币种
     * @return 另外一种货币Money
     */
    public MoneyKit convertTo(Currency currency) {
        throw new UnsupportedOperationException();
    }

    /**
     * 将Money变为负数
     *
     * @return 负数
     */
    public MoneyKit negate() {
        return newMoneyWithSameCurrency(-cent);
    }
}