/*
 * This file is part of Nuts Framework.
 * Copyright(C) 2009-2012 Nuts Develop Team.
 *
 * Nuts Framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License any later version.
 * 
 * Nuts Framework is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Nuts Framework. If not, see <http://www.gnu.org/licenses/>.
 */
package nuts.core.lang;

/**
 * <p>
 * Operations on char primitives and Character objects.
 * </p>
 * <p>
 * This class tries to handle {@code null} input gracefully. An exception will not be thrown for a
 * {@code null} input. Each method documents its behaviour in more detail.
 * </p>
 * <p>
 * #ThreadSafe#
 * </p>
 * 
 */
public abstract class Chars {
	/**
	 * BOM character
	 */
	public static final char BOM = '\uFEFF';

	/**
	 * TAB (\u0009)
	 */
	public static final char TAB = '\t';
	
	/**
	 * SPACE (\u0020)
	 */
	public static final char SPACE = ' ';
	
	/**
	 * line-feed LF ('\n') {@code '\u000a'}.
	 * 
	 * @see <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#101089">JLF:
	 *      Escape Sequences for Character and String Literals</a>
	 */
	public static final char LF = '\n';

	/**
	 * {@code '\u000d'} carriage return CR ('\r').
	 * 
	 * @see <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#101089">JLF:
	 *      Escape Sequences for Character and String Literals</a>
	 */
	public static final char CR = '\r';

	private static final String[] CHAR_STRING_ARRAY = new String[128];
	static {
		for (char c = 0; c < CHAR_STRING_ARRAY.length; c++) {
			CHAR_STRING_ARRAY[c] = String.valueOf(c);
		}
	}

	/**
	 * <p>
	 * {@code CharUtils} instances should NOT be constructed in standard programming. Instead, the
	 * class should be used as {@code CharUtils.toString('c');}.
	 * </p>
	 * <p>
	 * This constructor is public to permit tools that require a JavaBean instance to operate.
	 * </p>
	 */
	public Chars() {
		super();
	}

	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Converts the String to a Character using the first character, returning null for empty
	 * Strings.
	 * </p>
	 * <p>
	 * For ASCII 7 bit characters, this uses a cache that will return the same Character object each
	 * time.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.toCharacterObject(null) = null
	 *   CharUtils.toCharacterObject("")   = null
	 *   CharUtils.toCharacterObject("A")  = 'A'
	 *   CharUtils.toCharacterObject("BA") = 'B'
	 * </pre>
	 * 
	 * @param str the character to convert
	 * @return the Character value of the first letter of the String
	 */
	public static Character toCharacterObject(String str) {
		if (Strings.isEmpty(str)) {
			return null;
		}
		return Character.valueOf(str.charAt(0));
	}

	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Converts the Character to a char throwing an exception for {@code null}.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.toChar(' ')  = ' '
	 *   CharUtils.toChar('A')  = 'A'
	 *   CharUtils.toChar(null) throws IllegalArgumentException
	 * </pre>
	 * 
	 * @param ch the character to convert
	 * @return the char value of the Character
	 * @throws IllegalArgumentException if the Character is null
	 */
	public static char toChar(Character ch) {
		if (ch == null) {
			throw new IllegalArgumentException("The Character must not be null");
		}
		return ch.charValue();
	}

	/**
	 * <p>
	 * Converts the Character to a char handling {@code null}.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.toChar(null, 'X') = 'X'
	 *   CharUtils.toChar(' ', 'X')  = ' '
	 *   CharUtils.toChar('A', 'X')  = 'A'
	 * </pre>
	 * 
	 * @param ch the character to convert
	 * @param defaultValue the value to use if the Character is null
	 * @return the char value of the Character or the default if null
	 */
	public static char toChar(Character ch, char defaultValue) {
		if (ch == null) {
			return defaultValue;
		}
		return ch.charValue();
	}

	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Converts the String to a char using the first character, throwing an exception on empty
	 * Strings.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.toChar("A")  = 'A'
	 *   CharUtils.toChar("BA") = 'B'
	 *   CharUtils.toChar(null) throws IllegalArgumentException
	 *   CharUtils.toChar("")   throws IllegalArgumentException
	 * </pre>
	 * 
	 * @param str the character to convert
	 * @return the char value of the first letter of the String
	 * @throws IllegalArgumentException if the String is empty
	 */
	public static char toChar(String str) {
		if (Strings.isEmpty(str)) {
			throw new IllegalArgumentException("The String must not be empty");
		}
		return str.charAt(0);
	}

	/**
	 * <p>
	 * Converts the String to a char using the first character, defaulting the value on empty
	 * Strings.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.toChar(null, 'X') = 'X'
	 *   CharUtils.toChar("", 'X')   = 'X'
	 *   CharUtils.toChar("A", 'X')  = 'A'
	 *   CharUtils.toChar("BA", 'X') = 'B'
	 * </pre>
	 * 
	 * @param str the character to convert
	 * @param defaultValue the value to use if the Character is null
	 * @return the char value of the first letter of the String or the default if null
	 */
	public static char toChar(String str, char defaultValue) {
		if (Strings.isEmpty(str)) {
			return defaultValue;
		}
		return str.charAt(0);
	}

	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Converts the character to the Integer it represents, throwing an exception if the character
	 * is not numeric.
	 * </p>
	 * <p>
	 * This method coverts the char '1' to the int 1 and so on.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.toIntValue('3')  = 3
	 *   CharUtils.toIntValue('A')  throws IllegalArgumentException
	 * </pre>
	 * 
	 * @param ch the character to convert
	 * @return the int value of the character
	 * @throws IllegalArgumentException if the character is not ASCII numeric
	 */
	public static int toIntValue(char ch) {
		if (isAsciiNumeric(ch) == false) {
			throw new IllegalArgumentException("The character " + ch
					+ " is not in the range '0' - '9'");
		}
		return ch - 48;
	}

	/**
	 * <p>
	 * Converts the character to the Integer it represents, throwing an exception if the character
	 * is not numeric.
	 * </p>
	 * <p>
	 * This method coverts the char '1' to the int 1 and so on.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.toIntValue('3', -1)  = 3
	 *   CharUtils.toIntValue('A', -1)  = -1
	 * </pre>
	 * 
	 * @param ch the character to convert
	 * @param defaultValue the default value to use if the character is not numeric
	 * @return the int value of the character
	 */
	public static int toIntValue(char ch, int defaultValue) {
		if (isAsciiNumeric(ch) == false) {
			return defaultValue;
		}
		return ch - 48;
	}

	/**
	 * <p>
	 * Converts the character to the Integer it represents, throwing an exception if the character
	 * is not numeric.
	 * </p>
	 * <p>
	 * This method coverts the char '1' to the int 1 and so on.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.toIntValue('3')  = 3
	 *   CharUtils.toIntValue(null) throws IllegalArgumentException
	 *   CharUtils.toIntValue('A')  throws IllegalArgumentException
	 * </pre>
	 * 
	 * @param ch the character to convert, not null
	 * @return the int value of the character
	 * @throws IllegalArgumentException if the Character is not ASCII numeric or is null
	 */
	public static int toIntValue(Character ch) {
		if (ch == null) {
			throw new IllegalArgumentException("The character must not be null");
		}
		return toIntValue(ch.charValue());
	}

	/**
	 * <p>
	 * Converts the character to the Integer it represents, throwing an exception if the character
	 * is not numeric.
	 * </p>
	 * <p>
	 * This method coverts the char '1' to the int 1 and so on.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.toIntValue(null, -1) = -1
	 *   CharUtils.toIntValue('3', -1)  = 3
	 *   CharUtils.toIntValue('A', -1)  = -1
	 * </pre>
	 * 
	 * @param ch the character to convert
	 * @param defaultValue the default value to use if the character is not numeric
	 * @return the int value of the character
	 */
	public static int toIntValue(Character ch, int defaultValue) {
		if (ch == null) {
			return defaultValue;
		}
		return toIntValue(ch.charValue(), defaultValue);
	}

	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Converts the character to a String that contains the one character.
	 * </p>
	 * <p>
	 * For ASCII 7 bit characters, this uses a cache that will return the same String object each
	 * time.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.toString(' ')  = " "
	 *   CharUtils.toString('A')  = "A"
	 * </pre>
	 * 
	 * @param ch the character to convert
	 * @return a String containing the one specified character
	 */
	public static String toString(char ch) {
		if (ch < 128) {
			return CHAR_STRING_ARRAY[ch];
		}
		return new String(new char[] { ch });
	}

	/**
	 * <p>
	 * Converts the character to a String that contains the one character.
	 * </p>
	 * <p>
	 * For ASCII 7 bit characters, this uses a cache that will return the same String object each
	 * time.
	 * </p>
	 * <p>
	 * If {@code null} is passed in, {@code null} will be returned.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.toString(null) = null
	 *   CharUtils.toString(' ')  = " "
	 *   CharUtils.toString('A')  = "A"
	 * </pre>
	 * 
	 * @param ch the character to convert
	 * @return a String containing the one specified character
	 */
	public static String toString(Character ch) {
		if (ch == null) {
			return null;
		}
		return toString(ch.charValue());
	}

	// --------------------------------------------------------------------------
	/**
	 * <p>
	 * Converts the string to the Unicode format '\u0020'.
	 * </p>
	 * <p>
	 * This format is the Java source code format.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.unicodeEscaped(' ') = "\u0020"
	 *   CharUtils.unicodeEscaped('A') = "\u0041"
	 * </pre>
	 * 
	 * @param ch the character to convert
	 * @return the escaped Unicode string
	 */
	public static String unicodeEscaped(char ch) {
		if (ch < 0x10) {
			return "\\u000" + Integer.toHexString(ch);
		}
		else if (ch < 0x100) {
			return "\\u00" + Integer.toHexString(ch);
		}
		else if (ch < 0x1000) {
			return "\\u0" + Integer.toHexString(ch);
		}
		return "\\u" + Integer.toHexString(ch);
	}

	/**
	 * <p>
	 * Converts the string to the Unicode format '\u0020'.
	 * </p>
	 * <p>
	 * This format is the Java source code format.
	 * </p>
	 * <p>
	 * If {@code null} is passed in, {@code null} will be returned.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.unicodeEscaped(null) = null
	 *   CharUtils.unicodeEscaped(' ')  = "\u0020"
	 *   CharUtils.unicodeEscaped('A')  = "\u0041"
	 * </pre>
	 * 
	 * @param ch the character to convert, may be null
	 * @return the escaped Unicode string, null if null input
	 */
	public static String unicodeEscaped(Character ch) {
		if (ch == null) {
			return null;
		}
		return unicodeEscaped(ch.charValue());
	}

	// --------------------------------------------------------------------------
	/**
	 * <p>
	 * Checks whether the character is ASCII 7 bit.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.isAscii('a')  = true
	 *   CharUtils.isAscii('A')  = true
	 *   CharUtils.isAscii('3')  = true
	 *   CharUtils.isAscii('-')  = true
	 *   CharUtils.isAscii('\n') = true
	 *   CharUtils.isAscii('&copy;') = false
	 * </pre>
	 * 
	 * @param ch the character to check
	 * @return true if less than 128
	 */
	public static boolean isAscii(char ch) {
		return ch < 128;
	}

	/**
	 * <p>
	 * Checks whether the character is ASCII 7 bit printable.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.isAsciiPrintable('a')  = true
	 *   CharUtils.isAsciiPrintable('A')  = true
	 *   CharUtils.isAsciiPrintable('3')  = true
	 *   CharUtils.isAsciiPrintable('-')  = true
	 *   CharUtils.isAsciiPrintable('\n') = false
	 *   CharUtils.isAsciiPrintable('&copy;') = false
	 * </pre>
	 * 
	 * @param ch the character to check
	 * @return true if between 32 and 126 inclusive
	 */
	public static boolean isAsciiPrintable(char ch) {
		return ch >= 32 && ch < 127;
	}

	/**
	 * <p>
	 * Checks whether the character is ASCII 7 bit control.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.isAsciiControl('a')  = false
	 *   CharUtils.isAsciiControl('A')  = false
	 *   CharUtils.isAsciiControl('3')  = false
	 *   CharUtils.isAsciiControl('-')  = false
	 *   CharUtils.isAsciiControl('\n') = true
	 *   CharUtils.isAsciiControl('&copy;') = false
	 * </pre>
	 * 
	 * @param ch the character to check
	 * @return true if less than 32 or equals 127
	 */
	public static boolean isAsciiControl(char ch) {
		return ch < 32 || ch == 127;
	}

	/**
	 * <p>
	 * Checks whether the character is ASCII 7 bit alphabetic.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.isAsciiAlpha('a')  = true
	 *   CharUtils.isAsciiAlpha('A')  = true
	 *   CharUtils.isAsciiAlpha('3')  = false
	 *   CharUtils.isAsciiAlpha('-')  = false
	 *   CharUtils.isAsciiAlpha('\n') = false
	 *   CharUtils.isAsciiAlpha('&copy;') = false
	 * </pre>
	 * 
	 * @param ch the character to check
	 * @return true if between 65 and 90 or 97 and 122 inclusive
	 */
	public static boolean isAsciiAlpha(char ch) {
		return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');
	}

	/**
	 * <p>
	 * Checks whether the character is ASCII 7 bit alphabetic upper case.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.isAsciiAlphaUpper('a')  = false
	 *   CharUtils.isAsciiAlphaUpper('A')  = true
	 *   CharUtils.isAsciiAlphaUpper('3')  = false
	 *   CharUtils.isAsciiAlphaUpper('-')  = false
	 *   CharUtils.isAsciiAlphaUpper('\n') = false
	 *   CharUtils.isAsciiAlphaUpper('&copy;') = false
	 * </pre>
	 * 
	 * @param ch the character to check
	 * @return true if between 65 and 90 inclusive
	 */
	public static boolean isAsciiAlphaUpper(char ch) {
		return ch >= 'A' && ch <= 'Z';
	}

	/**
	 * <p>
	 * Checks whether the character is ASCII 7 bit alphabetic lower case.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.isAsciiAlphaLower('a')  = true
	 *   CharUtils.isAsciiAlphaLower('A')  = false
	 *   CharUtils.isAsciiAlphaLower('3')  = false
	 *   CharUtils.isAsciiAlphaLower('-')  = false
	 *   CharUtils.isAsciiAlphaLower('\n') = false
	 *   CharUtils.isAsciiAlphaLower('&copy;') = false
	 * </pre>
	 * 
	 * @param ch the character to check
	 * @return true if between 97 and 122 inclusive
	 */
	public static boolean isAsciiAlphaLower(char ch) {
		return ch >= 'a' && ch <= 'z';
	}

	/**
	 * <p>
	 * Checks whether the character is ASCII 7 bit numeric.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.isAsciiNumeric('a')  = false
	 *   CharUtils.isAsciiNumeric('A')  = false
	 *   CharUtils.isAsciiNumeric('3')  = true
	 *   CharUtils.isAsciiNumeric('-')  = false
	 *   CharUtils.isAsciiNumeric('\n') = false
	 *   CharUtils.isAsciiNumeric('&copy;') = false
	 * </pre>
	 * 
	 * @param ch the character to check
	 * @return true if between 48 and 57 inclusive
	 */
	public static boolean isAsciiNumeric(char ch) {
		return ch >= '0' && ch <= '9';
	}

	/**
	 * <p>
	 * Checks whether the character is ASCII 7 bit numeric.
	 * </p>
	 * 
	 * <pre>
	 *   CharUtils.isAsciiAlphanumeric('a')  = true
	 *   CharUtils.isAsciiAlphanumeric('A')  = true
	 *   CharUtils.isAsciiAlphanumeric('3')  = true
	 *   CharUtils.isAsciiAlphanumeric('-')  = false
	 *   CharUtils.isAsciiAlphanumeric('\n') = false
	 *   CharUtils.isAsciiAlphanumeric('&copy;') = false
	 * </pre>
	 * 
	 * @param ch the character to check
	 * @return true if between 48 and 57 or 65 and 90 or 97 and 122 inclusive
	 */
	public static boolean isAsciiAlphanumeric(char ch) {
		return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9');
	}

}
