Skip to content

Instantly share code, notes, and snippets.

@dragon66
Created September 25, 2014 15:14
Show Gist options
  • Select an option

  • Save dragon66/e9b8fb13c657e12b7145 to your computer and use it in GitHub Desktop.

Select an option

Save dragon66/e9b8fb13c657e12b7145 to your computer and use it in GitHub Desktop.

Revisions

  1. dragon66 created this gist Sep 25, 2014.
    213 changes: 213 additions & 0 deletions DateTime
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,213 @@
    /**
    * Copyright (c) 2014 by Wen Yu.
    * All rights reserved. This program and the accompanying materials
    * are made available under the terms of the Eclipse Public License v1.0
    * which accompanies this distribution, and is available at
    * http://www.eclipse.org/legal/epl-v10.html
    *
    * Any modifications to this file must keep this entire header intact.
    */

    package cafe.date;

    import java.util.Calendar;
    import java.util.Date;
    import java.util.Locale;
    import java.util.TimeZone;
    import java.text.DateFormat;
    import java.text.SimpleDateFormat;

    /**
    * A wrapper for the Date class with information for TimeZone, Locale as well
    * as a calendar associated with it
    *
    * @author Wen Yu, [email protected]
    * @version 1.0 02/15/2013
    */
    public class DateTime
    {
    private Date date;
    private TimeZone timeZone;
    private Locale locale;
    private Calendar calendar;

    public DateTime(Date date) {
    this(date, TimeZone.getDefault(), Locale.getDefault());
    }

    public DateTime(Date date, TimeZone timeZone, Locale locale) {
    this.date = date;
    this.timeZone = timeZone;
    this.locale = locale;
    this.calendar = Calendar.getInstance(timeZone, locale);

    calendar.setTime(date);
    }

    public DateTime(Date date, TimeZone timeZone) {
    this(date, timeZone, Locale.getDefault());
    }

    public boolean after(DateTime that) {
    return this.getUTCTimeInMillis() > that.getUTCTimeInMillis();
    }

    public boolean before(DateTime that) {
    return this.getUTCTimeInMillis() < that.getUTCTimeInMillis();
    }

    /**
    * Simple representation of the current date for the default TimeZone
    * and Locale.
    *
    * @return a DateTime object for the default TimeZone and Locale.
    */
    public static DateTime currentDate() {
    TimeZone tz = TimeZone.getDefault();
    Locale locale = Locale.getDefault();

    return currentDate(tz, locale);
    }

    public static DateTime currentDate(TimeZone timeZone) {
    Locale locale = Locale.getDefault();

    return currentDate(timeZone, locale);
    }

    public static DateTime currentDate(TimeZone timeZone, Locale locale) {
    Date date = new Date();

    return new DateTime(date, timeZone, locale);
    }

    public static DateTime currentDateUTC() {
    return currentDate(TimeZone.getTimeZone("UTC"));
    }

    public static DateTime currentDateUTC(Locale locale) {
    return currentDate(TimeZone.getTimeZone("UTC"), locale);
    }

    public DateTime dateAfter(int years, int months) {
    Calendar clone = (Calendar)calendar.clone();
    clone.add(Calendar.YEAR, years);
    clone.add(Calendar.MONTH, months);

    return new DateTime(clone.getTime(), timeZone, locale);
    }

    public DateTime dateAfter(int years, int months, int days) {
    Calendar clone = (Calendar)calendar.clone();
    clone.add(Calendar.YEAR, years);
    clone.add(Calendar.MONTH, months);
    clone.add(Calendar.DAY_OF_MONTH, days);

    return new DateTime(clone.getTime(), timeZone, locale);
    }

    public DateTime dateAfter(int years, int months, int days, int hours, int minutes, int seconds, int millis) {
    Calendar clone = (Calendar)calendar.clone();
    clone.add(Calendar.YEAR, years);
    clone.add(Calendar.MONTH, months);
    clone.add(Calendar.DAY_OF_MONTH, days);
    clone.add(Calendar.HOUR, hours);
    clone.add(Calendar.MINUTE, minutes);
    clone.add(Calendar.SECOND, seconds);
    clone.add(Calendar.MILLISECOND, millis);

    return new DateTime(clone.getTime(), timeZone, locale);
    }

    public DateTime daysAfter(int days) {

    return new DateTime(new Date(date.getTime() + 1000L*3600*24*days), timeZone, locale);
    }

    public boolean equals(Object that) {
    if (!(that instanceof DateTime))
    return false;
    return this.getUTCTimeInMillis() == ((DateTime)that).getUTCTimeInMillis();
    }

    public String format(DateFormat df) {
    return df.format(date);
    }

    public String format(String format) {
    return format(format, Locale.getDefault());
    }

    public String format(String format, Locale locale) {

    DateFormat df = new SimpleDateFormat(format, (locale == null)?Locale.getDefault():locale);
    df.setTimeZone(timeZone);
    String dateString = df.format(date);

    return dateString;
    }

    /**
    * Formats a DateTime to a ISO8601 string with a second fraction part of up to 3 digits.
    */
    public String formatISO8601() {
    return ISO8601DateUtils.formatISO8601(date, timeZone);
    }

    public Date getDate() {
    return (Date)date.clone();
    }

    public int getDayOfMonth() {
    return calendar.get(Calendar.DAY_OF_MONTH);
    }

    public int getDayOfWeek() {
    return calendar.get(Calendar.DAY_OF_WEEK);
    }

    public int getMonth() {
    return calendar.get(Calendar.MONTH);
    }

    public long getUTCTimeInMillis() {
    long time = date.getTime();
    int zoneOffset = timeZone.getOffset(time);

    return time + zoneOffset;
    }

    public int getYear() {
    return calendar.get(Calendar.YEAR);
    }

    public int hashCode() {
    return Long.valueOf(getUTCTimeInMillis()).hashCode();
    }

    public DateTime hoursAfter(int hours) {
    return new DateTime(new Date(date.getTime() + 1000L*3600*hours), timeZone, locale);
    }

    public DateTime monthsAfter(int months) {
    Calendar clone = (Calendar)calendar.clone();
    clone.add(Calendar.MONTH, months);

    return new DateTime(clone.getTime(), timeZone, locale);
    }

    public String toString() {

    DateFormat df = DateFormat.getInstance();
    df.setTimeZone(timeZone);

    return df.format(this.date) + " " + timeZone.getDisplayName();
    }

    public DateTime yearsAfter(int years) {
    Calendar clone = (Calendar)calendar.clone();
    clone.add(Calendar.YEAR, years);

    return new DateTime(clone.getTime(), timeZone, locale);
    }
    }
    170 changes: 170 additions & 0 deletions ISO8601DateUtils
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,170 @@
    /**
    * Copyright (c) 2014 by Wen Yu.
    * All rights reserved. This program and the accompanying materials
    * are made available under the terms of the Eclipse Public License v1.0
    * which accompanies this distribution, and is available at
    * http://www.eclipse.org/legal/epl-v10.html
    *
    * Any modifications to this file must keep this entire header intact.
    */

    package cafe.date;

    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.TimeZone;

    import cafe.date.DateTime;
    import cafe.string.StringUtils;

    /**
    * http://www.w3.org/TR/NOTE-datetime
    *
    * The formats are as follows. Exactly the components shown here must be
    * present, with exactly this punctuation. Note that the "T" appears literally
    * in the string, to indicate the beginning of the time element, as specified in
    * ISO 8601.
    *
    * Year:
    * YYYY (eg 1997)
    * Year and month:
    * YYYY-MM (eg 1997-07)
    * Complete date:
    * YYYY-MM-DD (eg 1997-07-16)
    * Complete date plus hours and minutes:
    * YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
    * Complete date plus hours, minutes and seconds:
    * YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
    * Complete date plus hours, minutes, seconds and a decimal fraction of a second
    * YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
    *
    * where:
    *
    * YYYY = four-digit year
    * MM = two-digit month (01=January, etc.)
    * DD = two-digit day of month (01 through 31)
    * hh = two digits of hour (00 through 23) (am/pm NOT allowed)
    * mm = two digits of minute (00 through 59)
    * ss = two digits of second (00 through 59)
    * s = one or more digits representing a decimal fraction of a second
    * TZD = time zone designator (Z or +hh:mm or -hh:mm)
    */
    /**
    * ISO 8601 date utilities.
    *
    * Supports up to 3 digits fraction of a second.
    *
    * @author Wen Yu, [email protected]
    * @version 1.0 03/04/2013
    */
    public class ISO8601DateUtils {

    private ISO8601DateUtils() {}

    /**
    * Formats a DateTime to a ISO8601 string with a second fraction part of up to 3 digits.
    */
    public static String formatISO8601(Date date, TimeZone timeZone) {

    SimpleDateFormat df = null;

    if (timeZone != null) {
    df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'.'SSSZ");
    df.setTimeZone(timeZone);

    String result = df.format(date);

    if (result.endsWith("0000"))
    return result.replaceAll("[+-]0000$", "Z");

    return result.replaceAll("(\\d{2})(\\d{2})$", "$1:$2");
    }

    df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");

    return df.format(date);
    }

    /**
    * Parses a ISO8601 string into a DateTime to retain time zone information.
    *
    * The pattern is dynamically created in accordance with the input date string.
    * Time zone is optional.
    *
    * The fraction part of the second if exists is cut down to 3 digits, i.e to the
    * precision of millisecond.
    */
    public static DateTime parse(String input) throws ParseException {

    TimeZone timeZone = null;
    StringBuilder sb = new StringBuilder(30);
    String[] dateParts = {"yyyy", "yyyy-MM", "yyyy-MM-dd"};
    String[] timeParts = {"HH", "HH:mm", "HH:mm:ss"};
    String[] inputs = input.split("-");

    sb.append(dateParts[Math.min(inputs.length - 1, 2)]);

    int timeIndex = input.indexOf("T");

    // If we have time parts
    if (timeIndex > 0)
    {
    input = input.replace("Z", "+00:00");
    boolean hasTimeZone = false;
    int timeZoneLen = 0;

    String timeStr = input.substring(timeIndex);
    // If we have time zone
    //if (StringUtils.contains(timeStr, "[+-]\\d{2}:\\d{2}$"))
    if (timeStr.indexOf("+") > 0 || timeStr.indexOf("-") > 0)
    {
    hasTimeZone = true;
    timeZoneLen = 5;
    timeZone = TimeZone.getTimeZone("GMT" + input.substring(input.length() - 6));
    input = StringUtils.replaceLast(input, ":", "");
    }

    String[] timeInputs = input.split(":");
    sb.append("'T'");
    sb.append(timeParts[timeInputs.length - 1]);

    int indexOfDot = input.indexOf(".");

    if (indexOfDot > 0) {

    sb.append("'.'");

    int secondFractionLen = input.length() - indexOfDot - 1 - timeZoneLen;

    // cut to 3 digits, we avoid round up which may cause problem in case of carrying
    if (secondFractionLen > 3)
    {
    String secondFractionStr = input.substring(indexOfDot, indexOfDot + secondFractionLen + 1);
    input = input.replace(secondFractionStr, input.substring(indexOfDot, indexOfDot + 4));
    }

    for (int j = secondFractionLen; j > 0; j--)
    {
    sb.append("S");
    }
    }
    if (hasTimeZone)
    sb.append("Z");
    }

    SimpleDateFormat df = new SimpleDateFormat(sb.toString());

    return new DateTime(df.parse(input), timeZone);
    }

    public static String format(Date date, String format) {
    SimpleDateFormat df = new SimpleDateFormat(format);

    return df.format(date);
    }

    public static String format(Date date) {
    return format(date, "yyyy-MM-dd'T'HH:mm:ss.SSS");
    }
    }
    807 changes: 807 additions & 0 deletions StringUtils
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,807 @@
    /**
    * Copyright (c) 2014 by Wen Yu.
    * All rights reserved. This program and the accompanying materials
    * are made available under the terms of the Eclipse Public License v1.0
    * which accompanies this distribution, and is available at
    * http://www.eclipse.org/legal/epl-v10.html
    *
    * Any modifications to this file must keep this entire header intact.
    */

    package cafe.string;

    import java.io.PrintWriter;
    import java.io.StringWriter;
    import java.io.UnsupportedEncodingException;
    import java.net.URLEncoder;
    import java.util.Iterator;
    import java.util.NoSuchElementException;
    import java.util.regex.*;
    /**
    * String utility class
    *
    * @author Wen Yu, [email protected]
    * @version 1.0 09/18/2012
    */
    public class StringUtils
    {

    private static final char[] HEXES = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};

    /**
    * Formats byte array.
    *
    * @param bytes an array of byte.
    * @return a hex string representation of the byte array.
    */
    public static String byteArrayToHexString(byte [] bytes) {

    if ( bytes == null ) {
    return null;
    }

    StringBuilder hex = new StringBuilder(5*bytes.length + 2);
    hex.append("[");

    if (bytes.length > 0)
    {
    for (byte b : bytes ) {
    hex.append("0x").append(HEXES[(b & 0xf0) >> 4])
    .append(HEXES[b & 0x0f]).append(",");
    }
    hex.deleteCharAt(hex.length()-1);
    }

    hex.append("]");

    return hex.toString();
    }

    public static String byteToHexString(byte b) {
    return String.format("0x%02X ", b);
    }

    /**
    * Capitalizes the first character of the words in a string.
    *
    * @param s the input string
    * @return a string with the first character of all words capitalized
    */
    public static String capitalize(String s)
    {
    StringBuffer myStringBuffer = new StringBuffer();
    Pattern p = Pattern.compile("\\b(\\w)(\\w*)");
    Matcher m = p.matcher(s);

    while (m.find()) {
    if(!Character.isUpperCase(m.group().charAt(0)))
    m.appendReplacement(myStringBuffer, m.group(1).toUpperCase()+"$2");
    }

    return m.appendTail(myStringBuffer).toString();
    }

    public static String capitalizeFully(String s)
    {
    return capitalize(s.toLowerCase());
    }

    public static String concat(Iterable<? extends CharSequence> strings, String delimiter)
    {
    int capacity = 0;
    int delimLength = delimiter.length();

    Iterator<? extends CharSequence> iter = strings.iterator();

    while (iter.hasNext()) {
    CharSequence next = iter.next();

    if(!isNullOrEmpty(next))
    capacity += next.length() + delimLength;
    }

    StringBuilder buffer = new StringBuilder(capacity);
    iter = strings.iterator();

    while (iter.hasNext()) {
    CharSequence next = iter.next();

    if(!isNullOrEmpty(next)) {
    buffer.append(next);
    buffer.append(delimiter);
    }
    }

    int lastIndexOfDelimiter = buffer.lastIndexOf(delimiter);
    buffer.delete(lastIndexOfDelimiter, buffer.length());

    return buffer.toString();
    }

    public static String concat(String first, String second)
    {
    if(first == null) return second;
    if(second == null) return first;

    StringBuilder sb = new StringBuilder(first.length() + second.length());
    sb.append(first);
    sb.append(second);

    return sb.toString();
    }

    public static String concat(String first, String... strings)
    {
    StringBuilder sb;

    if(first != null) sb = new StringBuilder(first);
    else sb = new StringBuilder();

    for (String s: strings) {
    if(!isNullOrEmpty(s))
    sb.append(s);
    }

    return sb.toString();
    }

    public static <T extends CharSequence> String concat(T[] strings, String delimiter)
    {
    int capacity = 0;
    int delimLength = delimiter.length();

    for (T value : strings) {
    if(!isNullOrEmpty(value))
    capacity += value.length() + delimLength;
    }

    StringBuilder buffer = new StringBuilder(capacity);

    for (T value : strings) {
    if(!isNullOrEmpty(value)) {
    buffer.append(value);
    buffer.append(delimiter);
    }
    }

    int lastIndexOfDelimiter = buffer.lastIndexOf(delimiter);
    buffer.delete(lastIndexOfDelimiter, buffer.length());

    return buffer.toString();
    }

    /**
    * Regular expression version of the String contains method.
    * If used with a match from start or match from end regular expression,
    * it becomes the regular expression version of the {@link String#
    * startsWith(String prefix)} or {@link String#endsWith(String suffix)}
    * methods.
    *
    * @param input the input string
    * @param regex the regular expression to which this string is to be matched
    * @return true if a match is found, otherwise false
    */
    public static boolean contains(String input, String regex)
    {
    Pattern p = Pattern.compile(regex);
    Matcher m = p.matcher(input);

    if (m.find()) {
    return true;
    }

    return false;
    }

    /**
    * From www.javapractices.com EscapeChars.java
    *
    * @param url URL string to be encoded
    * @return a encoded URL string
    */
    public static String encodeURL(String url)
    {
    String result = null;

    try {
    result = URLEncoder.encode(url, "UTF-8");
    }
    catch (UnsupportedEncodingException ex){
    throw new RuntimeException("UTF-8 not supported", ex);
    }

    return result;
    }

    /**
    * Escapes HTML reserved characters and other characters which might cause Cross Site Scripting
    * (XSS) hacks
    *
    * The following table comes from www.javapractice.com EscapeChars.java
    *
    * <P>The following characters are replaced with corresponding HTML character entities:
    *
    * <table border='1' cellpadding='3' cellspacing='0'>
    * <tr><th> Character </th><th>Replacement</th></tr>
    * <tr><td> < </td><td> &lt; </td></tr>
    * <tr><td> > </td><td> &gt; </td></tr>
    * <tr><td> & </td><td> &amp; </td></tr>
    * <tr><td> " </td><td> &quot;</td></tr>
    * <tr><td> \t </td><td> &#009;</td></tr>
    * <tr><td> ! </td><td> &#033;</td></tr>
    * <tr><td> # </td><td> &#035;</td></tr>
    * <tr><td> $ </td><td> &#036;</td></tr>
    * <tr><td> % </td><td> &#037;</td></tr>
    * <tr><td> ' </td><td> &#039;</td></tr>
    * <tr><td> ( </td><td> &#040;</td></tr>
    * <tr><td> ) </td><td> &#041;</td></tr>
    * <tr><td> * </td><td> &#042;</td></tr>
    * <tr><td> + </td><td> &#043; </td></tr>
    * <tr><td> , </td><td> &#044; </td></tr>
    * <tr><td> - </td><td> &#045; </td></tr>
    * <tr><td> . </td><td> &#046; </td></tr>
    * <tr><td> / </td><td> &#047; </td></tr>
    * <tr><td> : </td><td> &#058;</td></tr>
    * <tr><td> ; </td><td> &#059;</td></tr>
    * <tr><td> = </td><td> &#061;</td></tr>
    * <tr><td> ? </td><td> &#063;</td></tr>
    * <tr><td> @ </td><td> &#064;</td></tr>
    * <tr><td> [ </td><td> &#091;</td></tr>
    * <tr><td> \ </td><td> &#092;</td></tr>
    * <tr><td> ] </td><td> &#093;</td></tr>
    * <tr><td> ^ </td><td> &#094;</td></tr>
    * <tr><td> _ </td><td> &#095;</td></tr>
    * <tr><td> ` </td><td> &#096;</td></tr>
    * <tr><td> { </td><td> &#123;</td></tr>
    * <tr><td> | </td><td> &#124;</td></tr>
    * <tr><td> } </td><td> &#125;</td></tr>
    * <tr><td> ~ </td><td> &#126;</td></tr>
    * </table>
    *
    * @return a string with the specified characters replaced by HTML entities
    */
    public static String escapeHTML(String input)
    {
    Iterator<Character> itr = stringIterator(input);
    StringBuilder result = new StringBuilder();

    while (itr.hasNext())
    {
    Character c = itr.next();

    switch (c)
    {
    case '<':
    result.append("&lt;");
    break;
    case '>':
    result.append("&gt;");
    break;
    case '&':
    result.append("&amp;");
    break;
    case '"':
    result.append("&quot;");
    break;
    case '\t':
    result.append("&#009;");
    break;
    case '!':
    result.append("&#033;");
    break;
    case '#':
    result.append("&#035;");
    break;
    case '$':
    result.append("&#036;");
    break;
    case '%':
    result.append("&#037;");
    break;
    case '\'':
    result.append("&#039;");
    break;
    case '(':
    result.append("&#040;");
    break;
    case ')':
    result.append("&#041;");
    break;
    case '*':
    result.append("&#042;");
    break;
    case '+':
    result.append("&#043;");
    break;
    case ',':
    result.append("&#044;");
    break;
    case '-':
    result.append("&#045;");
    break;
    case '.':
    result.append("&#046;");
    break;
    case '/':
    result.append("&#047;");
    break;
    case ':':
    result.append("&#058;");
    break;
    case ';':
    result.append("&#059;");
    break;
    case '=':
    result.append("&#061;");
    break;
    case '?':
    result.append("&#063;");
    break;
    case '@':
    result.append("&#064;");
    break;
    case '[':
    result.append("&#091;");
    break;
    case '\\':
    result.append("&#092;");
    break;
    case ']':
    result.append("&#093;");
    break;
    case '^':
    result.append("&#094;");
    break;
    case '_':
    result.append("&#095;");
    break;
    case '`':
    result.append("&#096;");
    break;
    case '{':
    result.append("&#123;");
    break;
    case '|':
    result.append("&#124;");
    break;
    case '}':
    result.append("&#125;");
    break;
    case '~':
    result.append("&#126;");
    break;
    default:
    result.append(c);
    }
    }

    return result.toString();
    }

    /**
    * Replaces "&" with its entity "&amp;" to make it a valid HTML link
    *
    * @param queryString a URL string with a query string attached
    * @return a valid URL string to be used as a link
    */
    public static String escapeQueryStringAmp(String queryString)
    {
    return queryString.replace("&", "&amp;");
    }

    public static String escapeRegex(String input)
    {
    Iterator<Character> itr = stringIterator(input);
    StringBuilder result = new StringBuilder();

    while (itr.hasNext())
    {
    Character c = itr.next();

    switch (c)
    {
    case '.':
    case '^':
    case '$':
    case '*':
    case '+':
    case '?':
    case '(':
    case ')':
    case '[':
    case '{':
    result.append("\\").append(c);
    break;
    case '\\':
    result.append("\\\\");
    break;
    default:
    result.append(c);
    }
    }

    return result.toString();
    }

    public static String escapeXML(String input)
    {
    Iterator<Character> itr = stringIterator(input);
    StringBuilder result = new StringBuilder();

    while (itr.hasNext())
    {
    Character c = itr.next();

    switch (c)
    {
    case '"':
    result.append("&quot;");
    break;
    case '\'':
    result.append("&apos;");
    break;
    case '<':
    result.append("&lt;");
    break;
    case '>':
    result.append("&gt;");
    break;
    case '&':
    result.append("&amp;");
    break;
    default:
    result.append(c);
    }
    }

    return result.toString();
    }

    public static String intToHexString(int value) {
    StringBuilder buffer = new StringBuilder(10);

    buffer.append("0x");

    buffer.append(HEXES[(value & 0x0000000F)]);
    buffer.append(HEXES[(value & 0x000000F0) >>> 4]);
    buffer.append(HEXES[(value & 0x00000F00) >>> 8]);
    buffer.append(HEXES[(value & 0x0000F000) >>> 12]);
    buffer.append(HEXES[(value & 0x000F0000) >>> 16]);
    buffer.append(HEXES[(value & 0x00F00000) >>> 20]);
    buffer.append(HEXES[(value & 0x0F000000) >>> 24]);
    buffer.append(HEXES[(value & 0xF0000000) >>> 28]);

    return buffer.toString();
    }

    public static String intToHexStringMM(int value) {

    StringBuilder buffer = new StringBuilder(10);

    buffer.append("0x");

    buffer.append(HEXES[(value & 0xF0000000) >>> 28]);
    buffer.append(HEXES[(value & 0x0F000000) >>> 24]);
    buffer.append(HEXES[(value & 0x00F00000) >>> 20]);
    buffer.append(HEXES[(value & 0x000F0000) >>> 16]);
    buffer.append(HEXES[(value & 0x0000F000) >>> 12]);
    buffer.append(HEXES[(value & 0x00000F00) >>> 8]);
    buffer.append(HEXES[(value & 0x000000F0) >>> 4]);
    buffer.append(HEXES[(value & 0x0000000F)]);

    return buffer.toString();
    }

    /**
    * Checks if a string is null, empty, or consists only of white spaces
    *
    * @param str the input CharSequence to check
    * @return true if the input string is null, empty, or contains only white
    * spaces, otherwise false
    */
    public static boolean isNullOrEmpty(CharSequence str)
    {
    return ((str == null) || (str.length() == 0));
    }

    /**
    * Formats TIFF long data field.
    *
    * @param data an array of int.
    * @param unsigned true if the int value should be treated as unsigned,
    * otherwise false
    * @return a string representation of the int array.
    */
    public static String longArrayToString(int[] data, boolean unsigned) {
    StringBuilder longs = new StringBuilder();
    longs.append("[");

    for (int i=0; i<data.length; i++)
    {
    if(unsigned) {
    // Convert it to unsigned integer
    longs.append(data[i]&0xffffffffL);
    } else {
    longs.append(data[i]);
    }
    longs.append(",");
    }

    longs.deleteCharAt(longs.length()-1);
    longs.append("]");

    return longs.toString();
    }

    public static boolean parseBoolean(String s) {
    return Boolean.parseBoolean(s);
    }

    public static byte parseByte(String s) {
    return Byte.parseByte(s);
    }

    public static byte parseByte(String s, int radix) {
    return Byte.parseByte(s, radix);
    }

    public static double parseDouble(String s) {
    return Double.parseDouble(s);
    }

    public static float parseFloat(String s) {
    return Float.parseFloat(s);
    }

    public static int parseInt(String s) {
    return Integer.parseInt(s);
    }

    public static int parseInt(String s, int radix) {
    return Integer.parseInt(s, radix);
    }

    public static long parseLong(String s) {
    return Long.parseLong(s);
    }

    public static long parseLong(String s, int radix) {
    return Long.parseLong(s, radix);
    }

    public static short parseShort(String s) {
    return Short.parseShort(s);
    }

    public static short parseShort(String s, int radix) {
    return Short.parseShort(s, radix);
    }

    public static String quoteRegexReplacement(String replacement)
    {
    return Matcher.quoteReplacement(replacement);
    }

    /**
    * Formats TIFF rational data field.
    *
    * @param data an array of int.
    * @param unsigned true if the int value should be treated as unsigned,
    * otherwise false
    * @return a string representation of the int array.
    */
    public static String rationalArrayToString(int[] data, boolean unsigned)
    {
    if(data.length%2 != 0)
    throw new IllegalArgumentException("Data length is odd number, expect even!");

    StringBuilder rational = new StringBuilder();
    rational.append("[");

    for (int i=0; i<data.length; i+=2)
    {
    long numerator = data[i], denominator = data[i+1];

    if (unsigned) {
    // Converts it to unsigned integer
    numerator = (data[i]&0xffffffffL);
    denominator = (data[i+1]&0xffffffffL);
    }

    rational.append(numerator);
    rational.append("/");
    rational.append(denominator);

    rational.append(",");
    }

    rational.deleteCharAt(rational.length()-1);
    rational.append("]");

    return rational.toString();
    }

    /**
    * Replaces the last occurrence of the string represented by the regular expression
    *
    * @param input input string
    * @param regex the regular expression to which this string is to be matched
    * @param replacement the string to be substituted for the match
    * @return the resulting String
    */
    public static String replaceLast(String input, String regex, String replacement)
    {
    return input.replaceAll(regex+"(?!.*"+regex+")", replacement); // Using negative look ahead
    }

    public static String reverse(String s)
    {
    int i, len = s.length();
    StringBuilder dest = new StringBuilder(len);

    for (i = (len - 1); i >= 0; i--)
    dest.append(s.charAt(i));

    return dest.toString();
    }

    public static String reverse(String str, String delimiter)
    {
    if(isNullOrEmpty(delimiter)) {
    return str;
    }

    StringBuilder sb = new StringBuilder(str.length());
    reverseIt(str, delimiter, sb);

    return sb.toString();
    }

    public static String reverse2(String str, String delimiter)
    {
    if(isNullOrEmpty(delimiter) || isNullOrEmpty(str) || (str.trim().length() == 0) || (str.indexOf(delimiter) < 0)) {
    return str;
    }

    String escaptedDelimiter = escapeRegex(delimiter);
    // Keep the trailing white spaces by setting limit to -1
    String[] stringArray = str.split(escaptedDelimiter, -1);
    StringBuilder sb = new StringBuilder(str.length() + delimiter.length());

    for (int i = stringArray.length-1; i >= 0; i--)
    {
    sb.append(stringArray[i]).append(delimiter);
    }

    return sb.substring(0, sb.lastIndexOf(delimiter));
    }

    private static void reverseIt(String str, String delimiter, StringBuilder sb)
    {
    if(isNullOrEmpty(str) || (str.trim().length() == 0) || str.indexOf(delimiter) < 0) {
    sb.append(str);
    return;
    }
    // Recursion
    reverseIt(str.substring(str.indexOf(delimiter)+delimiter.length()), delimiter, sb);
    sb.append(delimiter);
    sb.append(str.substring(0, str.indexOf(delimiter)));
    }

    public static String reverseWords(String s)
    {
    String[] stringArray = s.split("\\b");
    StringBuilder sb = new StringBuilder(s.length());

    for (int i = stringArray.length-1; i >= 0; i--)
    {
    sb.append(stringArray[i]);
    }

    return sb.toString();
    }

    /**
    * Formats TIFF short data field.
    *
    * @param data an array of short.
    * @param unsigned true if the short value should be treated as unsigned,
    * otherwise false
    * @return a string representation of the short array.
    */
    public static String shortArrayToString(short[] data, boolean unsigned)
    {
    StringBuilder shorts = new StringBuilder();
    shorts.append("[");

    for (int i=0; i<data.length; i++)
    {
    if(unsigned) {
    // Convert it to unsigned short
    shorts.append(data[i]&0xffff);
    } else {
    shorts.append(data[i]);
    }
    shorts.append(",");
    }

    shorts.deleteCharAt(shorts.length()-1);
    shorts.append("]");

    return shorts.toString();
    }

    public static String shortToHexString(short value) {
    StringBuilder buffer = new StringBuilder(6);

    buffer.append("0x");

    buffer.append(HEXES[(value & 0x000F)]);
    buffer.append(HEXES[(value & 0x00F0) >>> 4]);
    buffer.append(HEXES[(value & 0x0F00) >>> 8]);
    buffer.append(HEXES[(value & 0xF000) >>> 12]);

    return buffer.toString();
    }

    public static String shortToHexStringMM(short value) {

    StringBuilder buffer = new StringBuilder(6);

    buffer.append("0x");

    buffer.append(HEXES[(value & 0xF000) >>> 12]);
    buffer.append(HEXES[(value & 0x0F00) >>> 8]);
    buffer.append(HEXES[(value & 0x00F0) >>> 4]);
    buffer.append(HEXES[(value & 0x000F)]);

    return buffer.toString();
    }

    /**
    * Converts stack trace to string
    */
    public static String stackTraceToString(Throwable e) {
    StringWriter sw = new StringWriter();
    e.printStackTrace(new PrintWriter(sw));

    return sw.toString();
    }

    /**
    * A read-only String iterator from stackoverflow.com
    *
    * @param string input string to be iterated
    * @return an iterator for the input string
    */
    public static Iterator<Character> stringIterator(final String string)
    {
    // Ensure the error is found as soon as possible.
    if (string == null)
    throw new NullPointerException();

    return new Iterator<Character>() {
    private int index = 0;

    public boolean hasNext() {
    return index < string.length();
    }

    public Character next() {
    /*
    * Throw NoSuchElementException as defined by the Iterator contract,
    * not IndexOutOfBoundsException.
    */
    if (!hasNext())
    throw new NoSuchElementException();
    return string.charAt(index++);
    }

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

    private StringUtils(){} // Prevents instantiation
    }