You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by iv...@apache.org on 2012/02/02 03:12:31 UTC

[9/9] git commit: WICKET-4356 StringValueConversionException should not be thrown when requesting a conversion on StringValue with a default value

WICKET-4356 StringValueConversionException should not be thrown when requesting a conversion on StringValue with a default value

- exception handler was too generic and should not catch java.lang.Exception, only the expected StringValueConversionException should be caught
- removed StringValueConversionException from method signature when it's actually not thrown
- saved a temporary variable
- fixed javadoc


Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/abe41a90
Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/abe41a90
Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/abe41a90

Branch: refs/heads/sandbox/feedback
Commit: abe41a90f8bcf3e6ecd43fffce81c80da37da3ec
Parents: 557e435
Author: Peter Ertl <pe...@apache.org>
Authored: Tue Jan 31 23:19:08 2012 +0100
Committer: Peter Ertl <pe...@apache.org>
Committed: Tue Jan 31 23:23:44 2012 +0100

----------------------------------------------------------------------
 .../org/apache/wicket/util/string/StringValue.java | 1781 +++++++--------
 1 files changed, 884 insertions(+), 897 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/wicket/blob/abe41a90/wicket-util/src/main/java/org/apache/wicket/util/string/StringValue.java
----------------------------------------------------------------------
diff --git a/wicket-util/src/main/java/org/apache/wicket/util/string/StringValue.java b/wicket-util/src/main/java/org/apache/wicket/util/string/StringValue.java
index 2f9865c..bbad52b 100755
--- a/wicket-util/src/main/java/org/apache/wicket/util/string/StringValue.java
+++ b/wicket-util/src/main/java/org/apache/wicket/util/string/StringValue.java
@@ -1,897 +1,884 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.apache.wicket.util.string;
-
-import java.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
-import java.text.NumberFormat;
-import java.text.ParseException;
-import java.util.Locale;
-
-import org.apache.wicket.IClusterable;
-import org.apache.wicket.util.lang.Objects;
-import org.apache.wicket.util.time.Duration;
-import org.apache.wicket.util.time.Time;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-
-/**
- * Holds an immutable String value and optionally a Locale, with methods to convert to various
- * types. Also provides some handy parsing methods and a variety of static factory methods.
- * <p>
- * Objects can be constructed directly from Strings or by using the valueOf() static factory
- * methods. The repeat() static factory methods provide a way of generating a String value that
- * repeats a given char or String a number of times.
- * <p>
- * Conversions to a wide variety of types can be found in the to*() methods. A generic conversion
- * can be achieved with to(Class).
- * <P>
- * The beforeFirst(), afterFirst(), beforeLast() and afterLast() methods are handy for parsing
- * things like paths and filenames.
- * 
- * @author Jonathan Locke
- */
-public class StringValue implements IClusterable
-{
-	private static final long serialVersionUID = 1L;
-
-	private static final Logger LOG = LoggerFactory.getLogger(StringValue.class);
-
-	/** Locale to be used for formatting and parsing. */
-	private final Locale locale;
-
-	/** The underlying string. */
-	private final String text;
-
-	/**
-	 * @param times
-	 *            Number of times to repeat character
-	 * @param c
-	 *            Character to repeat
-	 * @return Repeated character string
-	 */
-	public static StringValue repeat(final int times, final char c)
-	{
-		final AppendingStringBuffer buffer = new AppendingStringBuffer(times);
-
-		for (int i = 0; i < times; i++)
-		{
-			buffer.append(c);
-		}
-
-		return valueOf(buffer);
-	}
-
-	/**
-	 * @param times
-	 *            Number of times to repeat string
-	 * @param s
-	 *            String to repeat
-	 * @return Repeated character string
-	 */
-	public static StringValue repeat(final int times, final String s)
-	{
-		final AppendingStringBuffer buffer = new AppendingStringBuffer(times);
-
-		for (int i = 0; i < times; i++)
-		{
-			buffer.append(s);
-		}
-
-		return valueOf(buffer);
-	}
-
-	/**
-	 * Converts the given input to an instance of StringValue.
-	 * 
-	 * @param value
-	 *            Double precision value
-	 * @return String value formatted with one place after decimal
-	 */
-	public static StringValue valueOf(final double value)
-	{
-		return valueOf(value, Locale.getDefault());
-	}
-
-	/**
-	 * Converts the given input to an instance of StringValue.
-	 * 
-	 * @param value
-	 *            Double precision value
-	 * @param places
-	 *            Number of places after decimal
-	 * @param locale
-	 *            Locale to be used for formatting
-	 * @return String value formatted with the given number of places after decimal
-	 */
-	public static StringValue valueOf(final double value, final int places, final Locale locale)
-	{
-		if (Double.isNaN(value) || Double.isInfinite(value))
-		{
-			return valueOf("N/A");
-		}
-		else
-		{
-			final DecimalFormat format = new DecimalFormat("#." + repeat(places, '#'),
-				new DecimalFormatSymbols(locale));
-			return valueOf(format.format(value));
-		}
-	}
-
-	/**
-	 * Converts the given input to an instance of StringValue.
-	 * 
-	 * @param value
-	 *            Double precision value
-	 * @param locale
-	 *            Locale to be used for formatting
-	 * @return String value formatted with one place after decimal
-	 */
-	public static StringValue valueOf(final double value, final Locale locale)
-	{
-		return valueOf(value, 1, locale);
-	}
-
-	/**
-	 * Converts the given input to an instance of StringValue.
-	 * 
-	 * @param object
-	 *            An object
-	 * @return String value for object
-	 */
-	public static StringValue valueOf(final Object object)
-	{
-		return valueOf(Strings.toString(object));
-	}
-
-	/**
-	 * Converts the given input to an instance of StringValue.
-	 * 
-	 * @param object
-	 *            An object
-	 * @param locale
-	 *            Locale to be used for formatting
-	 * @return String value for object
-	 */
-	public static StringValue valueOf(final Object object, final Locale locale)
-	{
-		return valueOf(Strings.toString(object), locale);
-	}
-
-	/**
-	 * Converts the given input to an instance of StringValue.
-	 * 
-	 * @param string
-	 *            A string
-	 * @return String value for string
-	 */
-	public static StringValue valueOf(final String string)
-	{
-		return new StringValue(string);
-	}
-
-	/**
-	 * Converts the given input to an instance of StringValue.
-	 * 
-	 * @param string
-	 *            A string
-	 * @param locale
-	 *            Locale to be used for formatting
-	 * @return String value for string
-	 */
-	public static StringValue valueOf(final String string, final Locale locale)
-	{
-		return new StringValue(string, locale);
-	}
-
-	/**
-	 * Converts the given input to an instance of StringValue.
-	 * 
-	 * @param buffer
-	 *            A string buffer
-	 * @return String value
-	 */
-	public static StringValue valueOf(final AppendingStringBuffer buffer)
-	{
-		return valueOf(buffer.toString());
-	}
-
-	/**
-	 * Private constructor to force use of static factory methods.
-	 * 
-	 * @param text
-	 *            The text for this string value
-	 */
-	protected StringValue(final String text)
-	{
-		this.text = text;
-		locale = Locale.getDefault();
-	}
-
-	/**
-	 * Private constructor to force use of static factory methods.
-	 * 
-	 * @param text
-	 *            The text for this string value
-	 * @param locale
-	 *            the locale for formatting and parsing
-	 */
-	protected StringValue(final String text, final Locale locale)
-	{
-		this.text = text;
-		this.locale = locale;
-	}
-
-	/**
-	 * Gets the substring after the first occurrence given char.
-	 * 
-	 * @param c
-	 *            char to scan for
-	 * @return the substring
-	 */
-	public final String afterFirst(final char c)
-	{
-		return Strings.afterFirst(text, c);
-	}
-
-	/**
-	 * Gets the substring after the last occurrence given char.
-	 * 
-	 * @param c
-	 *            char to scan for
-	 * @return the substring
-	 */
-	public final String afterLast(final char c)
-	{
-		return Strings.afterLast(text, c);
-	}
-
-	/**
-	 * Gets the substring before the first occurrence given char.
-	 * 
-	 * @param c
-	 *            char to scan for
-	 * @return the substring
-	 */
-	public final String beforeFirst(final char c)
-	{
-		return Strings.beforeFirst(text, c);
-	}
-
-	/**
-	 * Gets the substring before the last occurrence given char.
-	 * 
-	 * @param c
-	 *            char to scan for
-	 * @return the substring
-	 */
-	public final String beforeLast(final char c)
-	{
-		return Strings.afterLast(text, c);
-	}
-
-	/**
-	 * Replaces on this text.
-	 * 
-	 * @param searchFor
-	 *            What to search for
-	 * @param replaceWith
-	 *            What to replace with
-	 * @return This string value with searchFor replaces with replaceWith
-	 */
-	public final CharSequence replaceAll(final CharSequence searchFor,
-		final CharSequence replaceWith)
-	{
-		return Strings.replaceAll(text, searchFor, replaceWith);
-	}
-
-	/**
-	 * Converts this StringValue to a given type.
-	 * 
-	 * @param type
-	 *            The type to convert to
-	 * @return The converted value
-	 * @throws StringValueConversionException
-	 */
-	public final Object to(final Class<?> type) throws StringValueConversionException
-	{
-		if (type == String.class)
-		{
-			return toString();
-		}
-
-		if ((type == Integer.TYPE) || (type == Integer.class))
-		{
-			return toInteger();
-		}
-
-		if ((type == Long.TYPE) || (type == Long.class))
-		{
-			return toLongObject();
-		}
-
-		if ((type == Boolean.TYPE) || (type == Boolean.class))
-		{
-			return toBooleanObject();
-		}
-
-		if ((type == Double.TYPE) || (type == Double.class))
-		{
-			return toDoubleObject();
-		}
-
-		if ((type == Character.TYPE) || (type == Character.class))
-		{
-			return toCharacter();
-		}
-
-		if (type == Time.class)
-		{
-			return toTime();
-		}
-
-		if (type == Duration.class)
-		{
-			return toDuration();
-		}
-
-		throw new StringValueConversionException("Cannot convert '" + toString() + "'to type " +
-			type);
-	}
-
-	/**
-	 * Convert this text to a boolean.
-	 * 
-	 * @return This string value as a boolean
-	 * @throws StringValueConversionException
-	 */
-	public final boolean toBoolean() throws StringValueConversionException
-	{
-		return Strings.isTrue(text);
-	}
-
-	/**
-	 * Convert to primitive types, returning default value if text is null.
-	 * 
-	 * @param defaultValue
-	 *            the default value to return of text is null
-	 * @return the converted text as a primitive or the default if text is null
-	 * @throws StringValueConversionException
-	 */
-	public final boolean toBoolean(final boolean defaultValue)
-		throws StringValueConversionException
-	{
-		boolean result = defaultValue;
-		if (text != null)
-		{
-			try
-			{
-				result = toBoolean();
-			} catch (Exception x)
-			{
-				if (LOG.isDebugEnabled())
-				{
-					LOG.debug(String.format(
-							"An error occurred while converting '%s' to a boolean: %s", text, x.getMessage()), x);
-				}
-			}
-		}
-
-		return result;
-	}
-
-	/**
-	 * Convert this text to a boolean.
-	 * 
-	 * @return Converted text
-	 * @throws StringValueConversionException
-	 */
-	public final Boolean toBooleanObject() throws StringValueConversionException
-	{
-		return Strings.toBoolean(text);
-	}
-
-	/**
-	 * Convert this text to a char.
-	 * 
-	 * @return This string value as a character
-	 * @throws StringValueConversionException
-	 */
-	public final char toChar() throws StringValueConversionException
-	{
-		return Strings.toChar(text);
-	}
-
-	/**
-	 * Convert to primitive types, returning default value if text is null.
-	 * 
-	 * @param defaultValue
-	 *            the default value to return of text is null
-	 * @return the converted text as a primitive or the default if text is null
-	 * @throws StringValueConversionException
-	 */
-	public final char toChar(final char defaultValue) throws StringValueConversionException
-	{
-		char result = defaultValue;
-		if (text != null)
-		{
-			try
-			{
-				result = toChar();
-			} catch (Exception x)
-			{
-				if (LOG.isDebugEnabled())
-				{
-					LOG.debug(String.format(
-						"An error occurred while converting '%s' to a character: %s", text, x.getMessage()), x);
-				}
-			}
-		}
-
-		return result;
-	}
-
-	/**
-	 * Convert this text to a Character.
-	 * 
-	 * @return Converted text
-	 * @throws StringValueConversionException
-	 */
-	public final Character toCharacter() throws StringValueConversionException
-	{
-		return toChar();
-	}
-
-	/**
-	 * Convert this text to a double.
-	 * 
-	 * @return Converted text
-	 * @throws StringValueConversionException
-	 */
-	public final double toDouble() throws StringValueConversionException
-	{
-		try
-		{
-			return NumberFormat.getNumberInstance(locale).parse(text).doubleValue();
-		}
-		catch (ParseException e)
-		{
-			throw new StringValueConversionException("Unable to convert '" + text +
-				"' to a double value", e);
-		}
-	}
-
-	/**
-	 * Convert to primitive types, returning default value if text is null.
-	 * 
-	 * @param defaultValue
-	 *            the default value to return of text is null
-	 * @return the converted text as a primitive or the default if text is null
-	 * @throws StringValueConversionException
-	 */
-	public final double toDouble(final double defaultValue) throws StringValueConversionException
-	{
-		double result = defaultValue;
-		if (text != null)
-		{
-			try
-			{
-				result = toDouble();
-			} catch (Exception x)
-			{
-				if (LOG.isDebugEnabled())
-				{
-					LOG.debug(String.format(
-							"An error occurred while converting '%s' to a double: %s", text, x.getMessage()), x);
-				}
-			}
-		}
-
-		return result;
-	}
-
-	/**
-	 * Convert this text to a Double.
-	 * 
-	 * @return Converted text
-	 * @throws StringValueConversionException
-	 */
-	public final Double toDoubleObject() throws StringValueConversionException
-	{
-		return toDouble();
-	}
-
-	/**
-	 * Convert this text to a Duration instance.
-	 * 
-	 * @return Converted text
-	 * @throws StringValueConversionException
-	 */
-	public final Duration toDuration() throws StringValueConversionException
-	{
-		return Duration.valueOf(text, locale);
-	}
-
-	/**
-	 * Convert to primitive types, returning default value if text is null.
-	 * 
-	 * @param defaultValue
-	 *            the default value to return of text is null
-	 * @return the converted text as a primitive or the default if text is null
-	 * @throws StringValueConversionException
-	 */
-	public final Duration toDuration(final Duration defaultValue)
-		throws StringValueConversionException
-	{
-		Duration result = defaultValue;
-		if (text != null)
-		{
-			try
-			{
-				result = toDuration();
-			} catch (Exception x)
-			{
-				if (LOG.isDebugEnabled())
-				{
-					LOG.debug(String.format(
-							"An error occurred while converting '%s' to a Duration: %s", text, x.getMessage()), x);
-				}
-			}
-		}
-
-		return result;
-	}
-
-	/**
-	 * Convert this text to an int.
-	 * 
-	 * @return Converted text
-	 * @throws StringValueConversionException
-	 */
-	public final int toInt() throws StringValueConversionException
-	{
-		try
-		{
-			return Integer.parseInt(text);
-		}
-		catch (NumberFormatException e)
-		{
-			throw new StringValueConversionException("Unable to convert '" + text +
-				"' to an int value", e);
-		}
-	}
-
-	/**
-	 * Convert to primitive types, returning default value if text is null.
-	 * 
-	 * @param defaultValue
-	 *            the default value to return of text is null
-	 * @return the converted text as a primitive or the default if text is null
-	 * @throws StringValueConversionException
-	 */
-	public final int toInt(final int defaultValue) throws StringValueConversionException
-	{
-		int result = defaultValue;
-		if (text != null)
-		{
-			try
-			{
-				result = toInt();
-			} catch (Exception x)
-			{
-				if (LOG.isDebugEnabled())
-				{
-					LOG.debug(String.format(
-						"An error occurred while converting '%s' to an integer: %s", text, x.getMessage()), x);
-				}
-			}
-		}
-
-		return result;
-	}
-
-	/**
-	 * Convert this text to an Integer.
-	 * 
-	 * @return Converted text
-	 * @throws StringValueConversionException
-	 */
-	public final Integer toInteger() throws StringValueConversionException
-	{
-		try
-		{
-			return new Integer(text);
-		}
-		catch (NumberFormatException e)
-		{
-			throw new StringValueConversionException("Unable to convert '" + text +
-				"' to an Integer value", e);
-		}
-	}
-
-	/**
-	 * Convert this text to a long.
-	 * 
-	 * @return Converted text
-	 * @throws StringValueConversionException
-	 */
-	public final long toLong() throws StringValueConversionException
-	{
-		try
-		{
-			return Long.parseLong(text);
-		}
-		catch (NumberFormatException e)
-		{
-			throw new StringValueConversionException("Unable to convert '" + text +
-				"' to a long value", e);
-		}
-	}
-
-	/**
-	 * Convert to primitive types, returning default value if text is null.
-	 * 
-	 * @param defaultValue
-	 *            the default value to return of text is null
-	 * @return the converted text as a primitive or the default if text is null
-	 * @throws StringValueConversionException
-	 */
-	public final long toLong(final long defaultValue) throws StringValueConversionException
-	{
-		long result = defaultValue;
-		if (text != null)
-		{
-			try
-			{
-				result = toLong();
-			} catch (Exception x)
-			{
-				if (LOG.isDebugEnabled())
-				{
-					LOG.debug(String.format(
-							"An error occurred while converting '%s' to a long: %s", text, x.getMessage()), x);
-				}
-			}
-		}
-
-		return result;
-	}
-
-	/**
-	 * Convert this text to a Long.
-	 * 
-	 * @return Converted text
-	 * @throws StringValueConversionException
-	 */
-	public final Long toLongObject() throws StringValueConversionException
-	{
-		try
-		{
-			return new Long(text);
-		}
-		catch (NumberFormatException e)
-		{
-			throw new StringValueConversionException("Unable to convert '" + text +
-				"' to a Long value", e);
-		}
-	}
-
-	/**
-	 * Convert to object types, returning null if text is null or empty.
-	 * 
-	 * @return converted
-	 * @throws StringValueConversionException
-	 */
-	public final Boolean toOptionalBoolean() throws StringValueConversionException
-	{
-		return Strings.isEmpty(text) ? null : toBooleanObject();
-	}
-
-	/**
-	 * Convert to object types, returning null if text is null or empty.
-	 * 
-	 * @return converted
-	 * @throws StringValueConversionException
-	 */
-	public final Character toOptionalCharacter() throws StringValueConversionException
-	{
-		return Strings.isEmpty(text) ? null : toCharacter();
-	}
-
-	/**
-	 * Convert to object types, returning null if text is null or empty.
-	 * 
-	 * @return converted
-	 * @throws StringValueConversionException
-	 */
-	public final Double toOptionalDouble() throws StringValueConversionException
-	{
-		return Strings.isEmpty(text) ? null : toDoubleObject();
-	}
-
-	/**
-	 * Convert to object types, returning null if text is null or empty.
-	 * 
-	 * @return converted
-	 * @throws StringValueConversionException
-	 */
-	public final Duration toOptionalDuration() throws StringValueConversionException
-	{
-		return Strings.isEmpty(text) ? null : toDuration();
-	}
-
-	/**
-	 * Convert to object types, returning null if text is null or empty.
-	 * 
-	 * @return converted
-	 * @throws StringValueConversionException
-	 */
-	public final Integer toOptionalInteger() throws StringValueConversionException
-	{
-		return Strings.isEmpty(text) ? null : toInteger();
-	}
-
-	/**
-	 * Convert to object types, returning null if text is null or empty.
-	 * 
-	 * @return converted
-	 * @throws StringValueConversionException
-	 */
-	public final Long toOptionalLong() throws StringValueConversionException
-	{
-		return Strings.isEmpty(text) ? null : toLongObject();
-	}
-
-	/**
-	 * Convert to object types, returning null if text is null.
-	 * 
-	 * @return converted
-	 */
-	public final String toOptionalString()
-	{
-		return text;
-	}
-
-	/**
-	 * Convert to object types, returning null if text is null or empty.
-	 * 
-	 * @return converted
-	 * @throws StringValueConversionException
-	 */
-	public final Time toOptionalTime() throws StringValueConversionException
-	{
-		return Strings.isEmpty(text) ? null : toTime();
-	}
-
-	/**
-	 * @return The string value
-	 */
-	@Override
-	public final String toString()
-	{
-		return text;
-	}
-
-	/**
-	 * Convert to primitive types, returning default value if text is null.
-	 * 
-	 * @param defaultValue
-	 *            the default value to return of text is null
-	 * @return the converted text as a primitive or the default if text is null
-	 */
-	public final String toString(final String defaultValue)
-	{
-		return (text == null) ? defaultValue : text;
-	}
-
-	/**
-	 * Convert this text to a time instance.
-	 * 
-	 * @return Converted text
-	 * @throws StringValueConversionException
-	 */
-	public final Time toTime() throws StringValueConversionException
-	{
-		try
-		{
-			return Time.valueOf(text);
-		}
-		catch (ParseException e)
-		{
-			throw new StringValueConversionException("Unable to convert '" + text +
-				"' to a Time value", e);
-		}
-	}
-
-	/**
-	 * Convert to primitive types, returning default value if text is null.
-	 * 
-	 * @param defaultValue
-	 *            the default value to return of text is null
-	 * @return the converted text as a primitive or the default if text is null
-	 * @throws StringValueConversionException
-	 */
-	public final Time toTime(final Time defaultValue) throws StringValueConversionException
-	{
-		Time result = defaultValue;
-		if (text != null)
-		{
-			try
-			{
-				result = toTime();
-			} catch (Exception x)
-			{
-				if (LOG.isDebugEnabled())
-				{
-					LOG.debug(String.format(
-						"An error occurred while converting '%s' to a Time: %s", text, x.getMessage()), x);
-				}
-			}
-		}
-
-		return result;
-	}
-
-	/**
-	 * Returns whether the text is null.
-	 * 
-	 * @return <code>true</code> if the text is <code>null</code>, <code>false</code> otherwise.
-	 */
-	public boolean isNull()
-	{
-		return text == null;
-	}
-
-	/**
-	 * Returns whether the text is null or empty
-	 * 
-	 * @return <code>true</code> if the text is <code>null</code> or
-	 *         <code>.trim().length()==0</code>, <code>false</code> otherwise.
-	 */
-	public boolean isEmpty()
-	{
-		return Strings.isEmpty(text);
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-	@Override
-	public int hashCode()
-	{
-		return Objects.hashCode(locale, text);
-	}
-
-	/**
-	 * {@inheritDoc}
-	 */
-	@Override
-	public boolean equals(final Object obj)
-	{
-		if (obj instanceof StringValue)
-		{
-			StringValue stringValue = (StringValue)obj;
-			return Objects.isEqual(text, stringValue.text) &&
-				Objects.isEqual(locale, stringValue.locale);
-		}
-		else
-		{
-			return false;
-		}
-	}
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.wicket.util.string;
+
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.Locale;
+
+import org.apache.wicket.IClusterable;
+import org.apache.wicket.util.lang.Objects;
+import org.apache.wicket.util.time.Duration;
+import org.apache.wicket.util.time.Time;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Holds an immutable String value and optionally a Locale, with methods to convert to various
+ * types. Also provides some handy parsing methods and a variety of static factory methods.
+ * <p>
+ * Objects can be constructed directly from Strings or by using the valueOf() static factory
+ * methods. The repeat() static factory methods provide a way of generating a String value that
+ * repeats a given char or String a number of times.
+ * <p>
+ * Conversions to a wide variety of types can be found in the to*() methods. A generic conversion
+ * can be achieved with to(Class).
+ * <P>
+ * The beforeFirst(), afterFirst(), beforeLast() and afterLast() methods are handy for parsing
+ * things like paths and filenames.
+ * 
+ * @author Jonathan Locke
+ */
+public class StringValue implements IClusterable
+{
+	private static final long serialVersionUID = 1L;
+
+	private static final Logger LOG = LoggerFactory.getLogger(StringValue.class);
+
+	/** Locale to be used for formatting and parsing. */
+	private final Locale locale;
+
+	/** The underlying string. */
+	private final String text;
+
+	/**
+	 * @param times
+	 *            Number of times to repeat character
+	 * @param c
+	 *            Character to repeat
+	 * @return Repeated character string
+	 */
+	public static StringValue repeat(final int times, final char c)
+	{
+		final AppendingStringBuffer buffer = new AppendingStringBuffer(times);
+
+		for (int i = 0; i < times; i++)
+		{
+			buffer.append(c);
+		}
+
+		return valueOf(buffer);
+	}
+
+	/**
+	 * @param times
+	 *            Number of times to repeat string
+	 * @param s
+	 *            String to repeat
+	 * @return Repeated character string
+	 */
+	public static StringValue repeat(final int times, final String s)
+	{
+		final AppendingStringBuffer buffer = new AppendingStringBuffer(times);
+
+		for (int i = 0; i < times; i++)
+		{
+			buffer.append(s);
+		}
+
+		return valueOf(buffer);
+	}
+
+	/**
+	 * Converts the given input to an instance of StringValue.
+	 * 
+	 * @param value
+	 *            Double precision value
+	 * @return String value formatted with one place after decimal
+	 */
+	public static StringValue valueOf(final double value)
+	{
+		return valueOf(value, Locale.getDefault());
+	}
+
+	/**
+	 * Converts the given input to an instance of StringValue.
+	 * 
+	 * @param value
+	 *            Double precision value
+	 * @param places
+	 *            Number of places after decimal
+	 * @param locale
+	 *            Locale to be used for formatting
+	 * @return String value formatted with the given number of places after decimal
+	 */
+	public static StringValue valueOf(final double value, final int places, final Locale locale)
+	{
+		if (Double.isNaN(value) || Double.isInfinite(value))
+		{
+			return valueOf("N/A");
+		}
+		else
+		{
+			final DecimalFormat format = new DecimalFormat("#." + repeat(places, '#'),
+				new DecimalFormatSymbols(locale));
+			return valueOf(format.format(value));
+		}
+	}
+
+	/**
+	 * Converts the given input to an instance of StringValue.
+	 * 
+	 * @param value
+	 *            Double precision value
+	 * @param locale
+	 *            Locale to be used for formatting
+	 * @return String value formatted with one place after decimal
+	 */
+	public static StringValue valueOf(final double value, final Locale locale)
+	{
+		return valueOf(value, 1, locale);
+	}
+
+	/**
+	 * Converts the given input to an instance of StringValue.
+	 * 
+	 * @param object
+	 *            An object
+	 * @return String value for object
+	 */
+	public static StringValue valueOf(final Object object)
+	{
+		return valueOf(Strings.toString(object));
+	}
+
+	/**
+	 * Converts the given input to an instance of StringValue.
+	 * 
+	 * @param object
+	 *            An object
+	 * @param locale
+	 *            Locale to be used for formatting
+	 * @return String value for object
+	 */
+	public static StringValue valueOf(final Object object, final Locale locale)
+	{
+		return valueOf(Strings.toString(object), locale);
+	}
+
+	/**
+	 * Converts the given input to an instance of StringValue.
+	 * 
+	 * @param string
+	 *            A string
+	 * @return String value for string
+	 */
+	public static StringValue valueOf(final String string)
+	{
+		return new StringValue(string);
+	}
+
+	/**
+	 * Converts the given input to an instance of StringValue.
+	 * 
+	 * @param string
+	 *            A string
+	 * @param locale
+	 *            Locale to be used for formatting
+	 * @return String value for string
+	 */
+	public static StringValue valueOf(final String string, final Locale locale)
+	{
+		return new StringValue(string, locale);
+	}
+
+	/**
+	 * Converts the given input to an instance of StringValue.
+	 * 
+	 * @param buffer
+	 *            A string buffer
+	 * @return String value
+	 */
+	public static StringValue valueOf(final AppendingStringBuffer buffer)
+	{
+		return valueOf(buffer.toString());
+	}
+
+	/**
+	 * Private constructor to force use of static factory methods.
+	 * 
+	 * @param text
+	 *            The text for this string value
+	 */
+	protected StringValue(final String text)
+	{
+		this.text = text;
+		locale = Locale.getDefault();
+	}
+
+	/**
+	 * Private constructor to force use of static factory methods.
+	 * 
+	 * @param text
+	 *            The text for this string value
+	 * @param locale
+	 *            the locale for formatting and parsing
+	 */
+	protected StringValue(final String text, final Locale locale)
+	{
+		this.text = text;
+		this.locale = locale;
+	}
+
+	/**
+	 * Gets the substring after the first occurrence given char.
+	 * 
+	 * @param c
+	 *            char to scan for
+	 * @return the substring
+	 */
+	public final String afterFirst(final char c)
+	{
+		return Strings.afterFirst(text, c);
+	}
+
+	/**
+	 * Gets the substring after the last occurrence given char.
+	 * 
+	 * @param c
+	 *            char to scan for
+	 * @return the substring
+	 */
+	public final String afterLast(final char c)
+	{
+		return Strings.afterLast(text, c);
+	}
+
+	/**
+	 * Gets the substring before the first occurrence given char.
+	 * 
+	 * @param c
+	 *            char to scan for
+	 * @return the substring
+	 */
+	public final String beforeFirst(final char c)
+	{
+		return Strings.beforeFirst(text, c);
+	}
+
+	/**
+	 * Gets the substring before the last occurrence given char.
+	 * 
+	 * @param c
+	 *            char to scan for
+	 * @return the substring
+	 */
+	public final String beforeLast(final char c)
+	{
+		return Strings.afterLast(text, c);
+	}
+
+	/**
+	 * Replaces on this text.
+	 * 
+	 * @param searchFor
+	 *            What to search for
+	 * @param replaceWith
+	 *            What to replace with
+	 * @return This string value with searchFor replaces with replaceWith
+	 */
+	public final CharSequence replaceAll(final CharSequence searchFor,
+		final CharSequence replaceWith)
+	{
+		return Strings.replaceAll(text, searchFor, replaceWith);
+	}
+
+	/**
+	 * Converts this StringValue to a given type.
+	 * 
+	 * @param type
+	 *            The type to convert to
+	 * @return The converted value
+	 * @throws StringValueConversionException
+	 */
+	public final Object to(final Class<?> type) throws StringValueConversionException
+	{
+		if (type == String.class)
+		{
+			return toString();
+		}
+
+		if ((type == Integer.TYPE) || (type == Integer.class))
+		{
+			return toInteger();
+		}
+
+		if ((type == Long.TYPE) || (type == Long.class))
+		{
+			return toLongObject();
+		}
+
+		if ((type == Boolean.TYPE) || (type == Boolean.class))
+		{
+			return toBooleanObject();
+		}
+
+		if ((type == Double.TYPE) || (type == Double.class))
+		{
+			return toDoubleObject();
+		}
+
+		if ((type == Character.TYPE) || (type == Character.class))
+		{
+			return toCharacter();
+		}
+
+		if (type == Time.class)
+		{
+			return toTime();
+		}
+
+		if (type == Duration.class)
+		{
+			return toDuration();
+		}
+
+		throw new StringValueConversionException("Cannot convert '" + toString() + "'to type " +
+			type);
+	}
+
+	/**
+	 * Convert this text to a boolean.
+	 * 
+	 * @return This string value as a boolean
+	 * @throws StringValueConversionException
+	 */
+	public final boolean toBoolean() throws StringValueConversionException
+	{
+		return Strings.isTrue(text);
+	}
+
+	/**
+	 * Convert to boolean, returning default value if text is inconvertible.
+	 * 
+	 * @param defaultValue
+	 *            the default value
+	 * @return the converted text as a boolean or the default value if text is empty or inconvertible
+	 * @see Strings#isTrue(String) 
+	 */
+	public final boolean toBoolean(final boolean defaultValue)
+	{
+		if (text != null)
+		{
+			try
+			{
+				return toBoolean();
+			}
+			catch (StringValueConversionException x)
+			{
+				if (LOG.isDebugEnabled())
+				{
+					LOG.debug(String.format(
+						"An error occurred while converting '%s' to a boolean: %s", text, x.getMessage()), x);
+				}
+			}
+		}
+		return defaultValue;
+	}
+
+	/**
+	 * Convert this text to a boolean.
+	 * 
+	 * @return Converted text
+	 * @throws StringValueConversionException
+	 */
+	public final Boolean toBooleanObject() throws StringValueConversionException
+	{
+		return Strings.toBoolean(text);
+	}
+
+	/**
+	 * Convert this text to a char.
+	 * 
+	 * @return This string value as a character
+	 * @throws StringValueConversionException
+	 */
+	public final char toChar() throws StringValueConversionException
+	{
+		return Strings.toChar(text);
+	}
+
+	/**
+	 * Convert to character, returning default value if text is inconvertible.
+	 * 
+	 * @param defaultValue
+	 *            the default value
+	 * @return the converted text as a primitive char or the default value if text is not a single character
+	 */
+	public final char toChar(final char defaultValue)
+	{
+		if (text != null)
+		{
+			try
+			{
+				return toChar();
+			}
+			catch (StringValueConversionException x)
+			{
+				if (LOG.isDebugEnabled())
+				{
+					LOG.debug(String.format(
+						"An error occurred while converting '%s' to a character: %s", text, x.getMessage()), x);
+				}
+			}
+		}
+		return defaultValue;
+	}
+
+	/**
+	 * Convert this text to a Character.
+	 * 
+	 * @return Converted text
+	 * @throws StringValueConversionException
+	 */
+	public final Character toCharacter() throws StringValueConversionException
+	{
+		return toChar();
+	}
+
+	/**
+	 * Convert this text to a double.
+	 * 
+	 * @return Converted text
+	 * @throws StringValueConversionException
+	 */
+	public final double toDouble() throws StringValueConversionException
+	{
+		try
+		{
+			return NumberFormat.getNumberInstance(locale).parse(text).doubleValue();
+		}
+		catch (ParseException e)
+		{
+			throw new StringValueConversionException("Unable to convert '" + text +
+				"' to a double value", e);
+		}
+	}
+
+	/**
+	 * Convert to double, returning default value if text is inconvertible.
+	 * 
+	 * @param defaultValue
+	 *            the default value
+	 * @return the converted text as a double or the default value if text is empty or inconvertible
+	 */
+	public final double toDouble(final double defaultValue)
+	{
+		if (text != null)
+		{
+			try
+			{
+				return toDouble();
+			}
+			catch (Exception x)
+			{
+				if (LOG.isDebugEnabled())
+				{
+					LOG.debug(String.format(
+						"An error occurred while converting '%s' to a double: %s", text, x.getMessage()), x);
+				}
+			}
+		}
+		return defaultValue;
+	}
+
+	/**
+	 * Convert this text to a Double.
+	 * 
+	 * @return Converted text
+	 * @throws StringValueConversionException
+	 */
+	public final Double toDoubleObject() throws StringValueConversionException
+	{
+		return toDouble();
+	}
+
+	/**
+	 * Convert this text to a Duration instance.
+	 * 
+	 * @return Converted text
+	 * @throws StringValueConversionException
+	 * @see Duration#valueOf(String, java.util.Locale) 
+	 */
+	public final Duration toDuration() throws StringValueConversionException
+	{
+		return Duration.valueOf(text, locale);
+	}
+
+	/**
+	 * Convert to duration, returning default value if text is inconvertible.
+	 * 
+	 * @param defaultValue
+	 *            the default value
+	 * @return the converted text as a duration or the default value if text is empty or inconvertible
+	 * @see Duration#valueOf(String, java.util.Locale) 
+	 */
+	public final Duration toDuration(final Duration defaultValue)
+	{
+		if (text != null)
+		{
+			try
+			{
+				return toDuration();
+			}
+			catch (Exception x)
+			{
+				if (LOG.isDebugEnabled())
+				{
+					LOG.debug(String.format(
+						"An error occurred while converting '%s' to a Duration: %s", text, x.getMessage()), x);
+				}
+			}
+		}
+		return defaultValue;
+	}
+
+	/**
+	 * Convert this text to an int.
+	 * 
+	 * @return Converted text
+	 * @throws StringValueConversionException
+	 */
+	public final int toInt() throws StringValueConversionException
+	{
+		try
+		{
+			return Integer.parseInt(text);
+		}
+		catch (NumberFormatException e)
+		{
+			throw new StringValueConversionException("Unable to convert '" + text +
+				"' to an int value", e);
+		}
+	}
+
+	/**
+	 * Convert to integer, returning default value if text is inconvertible.
+	 * 
+	 * @param defaultValue
+	 *            the default value
+	 * @return the converted text as an integer or the default value if text is not an integer
+	 */
+	public final int toInt(final int defaultValue)
+	{
+		if (text != null)
+		{
+			try
+			{
+				return toInt();
+			}
+			catch (StringValueConversionException x)
+			{
+				if (LOG.isDebugEnabled())
+				{
+					LOG.debug(String.format(
+						"An error occurred while converting '%s' to an integer: %s", text, x.getMessage()), x);
+				}
+			}
+		}
+		return defaultValue;
+	}
+
+	/**
+	 * Convert this text to an Integer.
+	 * 
+	 * @return Converted text
+	 * @throws StringValueConversionException
+	 */
+	public final Integer toInteger() throws StringValueConversionException
+	{
+		try
+		{
+			return new Integer(text);
+		}
+		catch (NumberFormatException e)
+		{
+			throw new StringValueConversionException("Unable to convert '" + text +
+				"' to an Integer value", e);
+		}
+	}
+
+	/**
+	 * Convert this text to a long.
+	 * 
+	 * @return Converted text
+	 * @throws StringValueConversionException
+	 */
+	public final long toLong() throws StringValueConversionException
+	{
+		try
+		{
+			return Long.parseLong(text);
+		}
+		catch (NumberFormatException e)
+		{
+			throw new StringValueConversionException("Unable to convert '" + text +
+				"' to a long value", e);
+		}
+	}
+
+	/**
+	 * Convert to long integer, returning default value if text is inconvertible.
+	 * 
+	 * @param defaultValue
+	 *            the default value
+	 * @return the converted text as a long integer or the default value if text is empty or inconvertible
+	 */
+	public final long toLong(final long defaultValue)
+	{
+		if (text != null)
+		{
+			try
+			{
+				return toLong();
+			}
+			catch (StringValueConversionException x)
+			{
+				if (LOG.isDebugEnabled())
+				{
+					LOG.debug(String.format(
+						"An error occurred while converting '%s' to a long: %s", text, x.getMessage()), x);
+				}
+			}
+		}
+		return defaultValue;
+	}
+
+	/**
+	 * Convert this text to a Long.
+	 * 
+	 * @return Converted text
+	 * @throws StringValueConversionException
+	 */
+	public final Long toLongObject() throws StringValueConversionException
+	{
+		try
+		{
+			return new Long(text);
+		}
+		catch (NumberFormatException e)
+		{
+			throw new StringValueConversionException("Unable to convert '" + text +
+				"' to a Long value", e);
+		}
+	}
+
+	/**
+	 * Convert to object types, returning null if text is null or empty.
+	 * 
+	 * @return converted
+	 * @throws StringValueConversionException
+	 */
+	public final Boolean toOptionalBoolean() throws StringValueConversionException
+	{
+		return Strings.isEmpty(text) ? null : toBooleanObject();
+	}
+
+	/**
+	 * Convert to object types, returning null if text is null or empty.
+	 * 
+	 * @return converted
+	 * @throws StringValueConversionException
+	 */
+	public final Character toOptionalCharacter() throws StringValueConversionException
+	{
+		return Strings.isEmpty(text) ? null : toCharacter();
+	}
+
+	/**
+	 * Convert to object types, returning null if text is null or empty.
+	 * 
+	 * @return converted
+	 * @throws StringValueConversionException
+	 */
+	public final Double toOptionalDouble() throws StringValueConversionException
+	{
+		return Strings.isEmpty(text) ? null : toDoubleObject();
+	}
+
+	/**
+	 * Convert to object types, returning null if text is null or empty.
+	 * 
+	 * @return converted
+	 * @throws StringValueConversionException
+	 */
+	public final Duration toOptionalDuration() throws StringValueConversionException
+	{
+		return Strings.isEmpty(text) ? null : toDuration();
+	}
+
+	/**
+	 * Convert to object types, returning null if text is null or empty.
+	 * 
+	 * @return converted
+	 * @throws StringValueConversionException
+	 */
+	public final Integer toOptionalInteger() throws StringValueConversionException
+	{
+		return Strings.isEmpty(text) ? null : toInteger();
+	}
+
+	/**
+	 * Convert to object types, returning null if text is null or empty.
+	 * 
+	 * @return converted
+	 * @throws StringValueConversionException
+	 */
+	public final Long toOptionalLong() throws StringValueConversionException
+	{
+		return Strings.isEmpty(text) ? null : toLongObject();
+	}
+
+	/**
+	 * Convert to object types, returning null if text is null.
+	 * 
+	 * @return converted
+	 */
+	public final String toOptionalString()
+	{
+		return text;
+	}
+
+	/**
+	 * Convert to object types, returning null if text is null or empty.
+	 * 
+	 * @return converted
+	 * @throws StringValueConversionException
+	 */
+	public final Time toOptionalTime() throws StringValueConversionException
+	{
+		return Strings.isEmpty(text) ? null : toTime();
+	}
+
+	/**
+	 * @return The string value
+	 */
+	@Override
+	public final String toString()
+	{
+		return text;
+	}
+
+	/**
+	 * Convert to primitive types, returning default value if text is null.
+	 * 
+	 * @param defaultValue
+	 *            the default value to return of text is null
+	 * @return the converted text as a primitive or the default if text is null
+	 */
+	public final String toString(final String defaultValue)
+	{
+		return (text == null) ? defaultValue : text;
+	}
+
+	/**
+	 * Convert this text to a time instance.
+	 * 
+	 * @return Converted text
+	 * @throws StringValueConversionException
+	 */
+	public final Time toTime() throws StringValueConversionException
+	{
+		try
+		{
+			return Time.valueOf(text);
+		}
+		catch (ParseException e)
+		{
+			throw new StringValueConversionException("Unable to convert '" + text +
+				"' to a Time value", e);
+		}
+	}
+
+	/**
+	 * Convert to time, returning default value if text is inconvertible.
+	 * 
+	 * @param defaultValue
+	 *            the default value
+	 * @return the converted text as a time or the default value if text is inconvertible.
+	 */
+	public final Time toTime(final Time defaultValue)
+	{
+		if (text != null)
+		{
+			try
+			{
+				return toTime();
+			}
+			catch (StringValueConversionException x)
+			{
+				if (LOG.isDebugEnabled())
+				{
+					LOG.debug(String.format(
+						"An error occurred while converting '%s' to a Time: %s", text, x.getMessage()), x);
+				}
+			}
+		}
+		return defaultValue;
+	}
+
+	/**
+	 * Returns whether the text is null.
+	 * 
+	 * @return <code>true</code> if the text is <code>null</code>, <code>false</code> otherwise.
+	 */
+	public boolean isNull()
+	{
+		return text == null;
+	}
+
+	/**
+	 * Returns whether the text is null or empty
+	 * 
+	 * @return <code>true</code> if the text is <code>null</code> or
+	 *         <code>.trim().length()==0</code>, <code>false</code> otherwise.
+	 */
+	public boolean isEmpty()
+	{
+		return Strings.isEmpty(text);
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public int hashCode()
+	{
+		return Objects.hashCode(locale, text);
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public boolean equals(final Object obj)
+	{
+		if (obj instanceof StringValue)
+		{
+			StringValue stringValue = (StringValue)obj;
+			return Objects.isEqual(text, stringValue.text) &&
+				Objects.isEqual(locale, stringValue.locale);
+		}
+		else
+		{
+			return false;
+		}
+	}
+}