Habe vor einige Zeit eine Klasse geschrieben, mit der sich beliebige Objekte zu anderen “cast” lassen (sofern unterstützt). Vielleicht für den einen oder anderen ganz hilfreich. Vor allen Dingen würden mich aber eure Meinung und evtl. Verbesserungs-Vorschläge Interessieren.
Die Nutzung ist denkbar einfach:
Date date = CastUtil.cast(Date.class, "2014-12-01 13:00:00");
Falls der Cast nicht unterstützt wird, fliegt eine ClassCastException. Mit isSupported
kann geprüft werden, ob der Cast von einem Typ zu einem anderen untersützt wird.
Im Moment wird es Klassen-Intern geregelt, welche Casts möglich sind (static-Block). Ein Umbau, dass man auch von außen gewisse Castings einfügt, wäre aber denkbar und leicht einzubauen. Im Moment ist es nur eine Klasse, damit ich sie leicht von Projekt zu Projekt kopieren kann, ohne irgendwelche Abhängigkeiten oder Overhead (in Form eines .jar) mitschleppen zu müssen.
(Quellcode im nächsten Post, aufgrund der maximalen Zeichenbegrenzung)
*** Edit ***
CastUtil.java
[spoiler]```import java.io.Serializable;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
@SuppressWarnings(“rawtypes”)
public final class CastUtil {
/**Used value when a {@code null} value should be casted to a primitive {@code boolean}*/
private static final Boolean NULL_BOOLEAN = Boolean.FALSE;
/**Used value when a {@code null} value should be casted to a primitive {@code byte}*/
private static final Byte NULL_BYTE = Byte.valueOf((byte) 0);
/**Used value when a {@code null} value should be casted to a primitive {@code char}*/
private static final Character NULL_CHARACTER = Character.valueOf((char) 0);
/**Used value when a {@code null} value should be casted to a primitive {@code double}*/
private static final Double NULL_DOUBLE = Double.valueOf(0.0);
/**Used value when a {@code null} value should be casted to a primitive {@code float}*/
private static final Float NULL_FLOAT = Float.valueOf(0.0F);
/**Used value when a {@code null} value should be casted to a primitive {@code int}*/
private static final Integer NULL_INTEGER = Integer.valueOf(0);
/**Used value when a {@code null} value should be casted to a primitive {@code long}*/
private static final Long NULL_LONG = Long.valueOf(0);
/**Used value when a {@code null} value should be casted to a primitive {@code short}*/
private static final Short NULL_SHORT = Short.valueOf((short) 0);
// -----------------------------
// Private Attributes
private static final Object[] EMPTY_ARRAY = new Object[0];
/** All {@link String}-literals which are considered as {@code false} */
private static final Pattern PATTERN_FALSE = Pattern.compile("0|f|n|no|false", Pattern.CASE_INSENSITIVE);
/** All {@link String}s which could be very likely considered as {@code boolean} */
private static final Pattern PATTERN_BOOLEAN = Pattern.compile("1|t|y|yes|true" + "|" + PATTERN_FALSE.pattern(), Pattern.CASE_INSENSITIVE);
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
/** Casts the given value to itself. */
private static final Cast<Object, Object> SELF_CAST = new Cast<Object, Object>(Object.class, Object.class) {
@Override
public Object cast(final Object value) throws RuntimeException {
return value;
}
@Override
public boolean isNoop() {
return true;
}
};
/** Instance to indicate a {@link Cast} could not be found. Throws an {@link IllegalArgumentException} if used. */
private static final Cast<Object, Object> INVALID_CAST = new Cast<Object, Object>(Object.class, Object.class) {
@Override
public Object cast(final Object value) throws RuntimeException {
throw new IllegalArgumentException("Invalid value for casting: " + value);
}
@Override
public boolean isNoop() {
return true;
}
};
private static final HashMap<Class<?>, HashMap<Class<?>, Cast<?, ?>>> castings = new HashMap<Class<?>, HashMap<Class<?>, Cast<?, ?>>>();
static {// supported autocasts - Note that a later added cast will not
// override existing ones
addCast(new CastStringToNumber());
addCast(new Cast<String, Integer>(String.class, Integer.class) {
@Override
public Integer cast(final String value) {
if (value == null) {
return null;
}
if (couldBeBoolean(value)) {
// Some Magic: Cast "boolean"-Strings to corresponding integer
return asBoolean(value) ? Integer.valueOf(1) : Integer.valueOf(0);
}
return Integer.valueOf(value.replaceAll(" ", ""));
}
});
addCast(new Cast<String, Boolean>(String.class, Boolean.class) {
@Override
public Boolean cast(final String value) {
return Boolean.valueOf(asBoolean(value));
}
});
addCast(new Cast<String, Double>(String.class, Double.class) {
@Override
public Double cast(final String value) {
return value == null ? null : Double.valueOf(value.replaceAll(" ", ""));
}
});
addCast(new Cast<String, Float>(String.class, Float.class) {
@Override
public Float cast(final String value) {
return value == null ? null : Float.valueOf(value.replaceAll(" ", ""));
}
});
addCast(new Cast<String, Short>(String.class, Short.class) {
@Override
public Short cast(final String value) {
return value == null ? null : Short.valueOf(value.replaceAll(" ", ""));
}
});
addCast(new Cast<String, Long>(String.class, Long.class) {
@Override
public Long cast(final String value) {
return value == null ? null : Long.valueOf(value.replaceAll(" ", ""));
}
});
addCast(new Cast<String, Byte>(String.class, Byte.class) {
@Override
public Byte cast(final String value) {
return value == null ? null : Byte.valueOf(value);
}
});
addCast(new Cast<String, BigInteger>(String.class, BigInteger.class) {
@Override
public BigInteger cast(final String value) {
return value == null ? null : new BigInteger(value.replaceAll(" ", ""));
}
});
addCast(new Cast<String, BigDecimal>(String.class, BigDecimal.class) {
@Override
public BigDecimal cast(final String value) {
return value == null ? null : new BigDecimal(value.replaceAll(" ", ""));
}
});
addCast(new Cast<String, Date>(String.class, Date.class) {
@Override
public Date cast(final String value) throws ParseException {
return getDateFormat().parse(value);
}
});
addCast(new Cast<Number, Integer>(Number.class, Integer.class) {
@Override
public Integer cast(final Number value) {
return value == null ? null : Integer.valueOf(value.intValue());
}
});
addCast(new Cast<Number, Boolean>(Number.class, Boolean.class) {
@Override
public Boolean cast(final Number value) {
if (value == null) {
return Boolean.FALSE;
}
// else {
final double d = value.doubleValue();
return Boolean.valueOf((d != 0.0) && !Double.isNaN(d));
// }
}
});
addCast(new Cast<Number, Double>(Number.class, Double.class) {
@Override
public Double cast(final Number value) {
return value == null ? null : Double.valueOf(value.doubleValue());
}
});
addCast(new Cast<Number, Float>(Number.class, Float.class) {
@Override
public Float cast(final Number value) {
return value == null ? null : Float.valueOf(value.floatValue());
}
});
addCast(new Cast<Number, Short>(Number.class, Short.class) {
@Override
public Short cast(final Number value) {
return value == null ? null : Short.valueOf(value.shortValue());
}
});
addCast(new Cast<Number, Long>(Number.class, Long.class) {
@Override
public Long cast(final Number value) {
return value == null ? null : Long.valueOf(value.longValue());
}
});
addCast(new Cast<Number, Byte>(Number.class, Byte.class) {
@Override
public Byte cast(final Number value) {
return value == null ? null : Byte.valueOf(value.byteValue());
}
});
addCast(new Cast<Number, String>(Number.class, String.class) {
@Override
public String cast(final Number value) {
return value == null ? null : value.toString();
}
});
addCast(new Cast<Boolean, Integer>(Boolean.class, Integer.class) {
@Override
public Integer cast(final Boolean value) {
return Integer.valueOf(Boolean.TRUE.equals(value) ? 1 : 0);
}
});
addCast(new Cast<Boolean, Double>(Boolean.class, Double.class) {
@Override
public Double cast(final Boolean value) {
return Double.valueOf(Boolean.TRUE.equals(value) ? 1 : 0);
}
});
addCast(new Cast<Boolean, Float>(Boolean.class, Float.class) {
@Override
public Float cast(final Boolean value) {
return Float.valueOf(Boolean.TRUE.equals(value) ? 1 : 0);
}
});
addCast(new Cast<Boolean, Short>(Boolean.class, Short.class) {
@Override
public Short cast(final Boolean value) {
return Short.valueOf((short) (Boolean.TRUE.equals(value) ? 1 : 0));
}
});
addCast(new Cast<Boolean, Long>(Boolean.class, Long.class) {
@Override
public Long cast(final Boolean value) {
return Long.valueOf(Boolean.TRUE.equals(value) ? 1 : 0);
}
});
addCast(new Cast<Boolean, Byte>(Boolean.class, Byte.class) {
@Override
public Byte cast(final Boolean value) {
return Byte.valueOf((byte) (Boolean.TRUE.equals(value) ? 1 : 0));
}
});
addCast(new Cast<Boolean, String>(Boolean.class, String.class) {
@Override
public String cast(final Boolean value) {
return value == null ? null : value.toString();
}
});
addCast(new Cast<Date, String>(Date.class, String.class) {
@Override
public String cast(final Date value) {
return getDateFormat().format(value);
}
});
addCast(new Cast<Date, Long>(Date.class, Long.class) {
@Override
public Long cast(final Date value) {
return Long.valueOf(value.getTime());
}
});
addCast(new Cast<Long, Date>(Long.class, Date.class) {
@Override
public Date cast(final Long value) throws Exception {
return value == null ? null : new Date(value.longValue());
}
});
addCast(new Cast<Collection, Object[]>(Collection.class, Object[].class) {
@Override
public Object[] cast(final Collection value) {
return value == null ? getEmptyArray() : value.toArray();
}
});
addCast(new Cast<Object[], ArrayList>(Object[].class, ArrayList.class) {
@Override
public ArrayList cast(final Object[] value) {
final ArrayList<Object> list = new ArrayList<Object>();
if (value != null) {
list.addAll(Arrays.asList(value));
}
return list;
}
});
addCast(new Cast<Object, ArrayList>(Object.class, ArrayList.class) {
@Override
public ArrayList<Object> cast(final Object value) {
final ArrayList<Object> list = new ArrayList<Object>();
list.add(value);
return list;
}
});
addCast(new Cast<Object, Object[]>(Object.class, Object[].class) {
@Override
public Object[] cast(final Object value) {
final Object[] array = (value == null) ? new Object[1] : (Object[]) Array.newInstance(value.getClass(), 1);
array[0] = value;
return array;
}
});
addCast(new Cast<Object, String>(Object.class, String.class) {
@Override
public String cast(final Object value) {
return value == null ? null : value.toString();
}
});
}
// -----------------------------
// Constructor
private CastUtil() {
super();
}
// -----------------------------
// Public Methods
/**
* Casts the given {@code value} to the given {@link Class} if possible. Returns the casted value, if a cast was
* possible. Throws an {@link IllegalArgumentException} if casting the value to the given {@link Class} failed. Use
* {@link #isSupported(Class, Class)} to check if the requested cast is supported.
*
* @param value The value to cast
* @param target The target type, the value should be casted to.
* @return The casted value, if the cast was successful.
* @throws IllegalArgumentException If casting the value fails.
*/
public static <T> T cast(final Object value, final Class<T> target) {
if ((value == null) && isPrimitive(target)) {
return castNullToPrimitive(target);
}
final Class<?> from = getType(value);
final Object casted;
if ((from == String.class) && target.isEnum()) {
casted = castEnum((String) value, target.asSubclass(Enum.class));
}
else {
casted = castObject(value, from, target);
}
@SuppressWarnings("unchecked")
final T result = (T) casted;
return result;
}
/**
* Returns {@code true} if casting from the given {@link Class} '{@code from} ' to the given {@link Class} '
* {@code to} ' is supported. Returns {@code false} if not.
*
* @param from The source type
* @param to The target type
* @return {@code true} if a casting from the source type to the target type is supported. {@code false} if not.
*/
public static boolean isSupported(final Class<?> from, final Class<?> to) {
return ((from == String.class) && to.isEnum()) || (!getCast(from, to).isNoop());
}
// -----------------------------
// Private Methods
// --- Add Cast ---
/**
* Will register the given {@link Cast} for its {@link Cast#getInputType()} and {@link Cast#getOutputType()}.<br>
* Will also register this {@link Cast} for all target types, which are interfaces or supertypes of this cast's
* output type and not implemted/extended by the the cast's input type..
* <p>
* Example: A {@code Cast} converts {@link String} to {@link Integer}. Such an instance will be:
* <ul>
* <li><span style="color:green">Used</span> to cast {@link String} to {@link Integer}
* <li><span style="color:green">Used</span> to cast {@link String} to {@link Number} (superclass of {@link Integer})
* <li><span style="color:red">Not</span> used for {@link String} to {@link Comparable} (cause {@link String} is
* already {@link Comparable})
* <li><span style="color:red">Not</span> used for {@link String} to {@link Serializable} (cause {@link String} is
* already {@link Serializable})
* <li><span style="color:red">Not</span> used for {@link String} to {@link Object} (cause {@link String} is already
* an {@link Object})
* </ul>
* Later registered {@link Cast}s will not override already existing ones:<br>
* Assuming a {@link String}-to-{@link Integer}-instance is already added. An later added {@link String}-to-
* {@link Double}-instance will not be used to cast {@link String} to {@link Number}, cause this is already possible
* using the {@link Integer}-to-{@link String}-instance added before.
*
* @param cast The {@link Cast}-instance to add
*/
private static void addCast(final Cast<?, ?> cast) {
final Class<?> from = cast.getInputType();
final Class<?> to = cast.getOutputType();
if (isSupported(from, to)) {// check for explicit override
throw new IllegalStateException("Cast-instance to cast values from " + from.getCanonicalName() + " to " + to.getCanonicalName() + " already exist: " + getCast(from, to));
}
HashMap<Class<?>, Cast<?, ?>> casts = castings.get(from);
if (casts == null) {
casts = new HashMap<Class<?>, Cast<?, ?>>();
castings.put(from, casts);
}
for (final Class<?> out : getTypesNeedingCast(from, to)) {
if (!casts.containsKey(out)) {// avoid implicit override
casts.put(out, cast);
}
}
}
/**
* Returns all {@link Class}-Instances, which are implemented or extended by the given {@code target}, which are
* <i>not</i> also implemented or extended by the given {@code source}.<br>
* Example: getOutputTypes({@link Integer}.class, {@link String}.class):
* <p>
* <b>Returned:</b> {@link Integer}.class, {@link Number}.class (This classes/interfaces are extended/implemented by
* every {@link Integer}.)<br>
* <b>Not returned:</b> {@link Serializable}.class, {@link Comparable} .class, {@link Object}.class (This
* classe/interfacses are extended/implemented by {@link Integer} <i>and</i> {@link String})
*
* @param source The values origin {@link Class}.
* @param target The {@link Class} a value should be casted to.
* @return An array of {@link Class}-instances, containing all types which are in the type-hierarchie of the
* {@code target} and <i>not</i> in the type-hierarchie of the {@code source}.
*/
private static Collection<Class<?>> getTypesNeedingCast(final Class<?> source, final Class<?> target) {
final Collection<Class<?>> sourceTypes = getTypes(source);
final ArrayList<Class<?>> list = new ArrayList<Class<?>>();
Class<?> temp = target;
while (temp != null) {
if (!sourceTypes.contains(temp) && (temp != Object.class)) {
list.add(temp);
}
for (final Class<?> type : temp.getInterfaces()) {
if (!sourceTypes.contains(type)) {
list.add(type);
}
}
temp = temp.getSuperclass();
}
return list;
}
/**
* Returns a {@link List} of all {@link Class}-instances, which the given type is an instance of. This list will
* always start with the given {@link Class} and ends with {@code Object.class}. This also includes interfaces.
*
* @param The {@link Class}, which types are requested
* @return A list of all types, the given {@code source} can be handled at.
*/
private static List<Class<?>> getTypes(final Class<?> source) {
// It has to be a List(!), otherwise getCast() could fail, because it is not searching for the most explicity
// type first. So we have to returned an ordered collection, containing source itself as first element.
final List<Class<?>> types = new ArrayList<Class<?>>();
Class<?> temp = source;
while (temp != null) {
types.add(temp);
fillInInterfaces(types, temp);
temp = temp.getSuperclass();
}
return types;
}
/**
* Adds all interfaces, implemented or extend by the given {@code baseType}, to the given {@link Collection}.
*
* @param target The {@link Collection} filled with the implementing interfaces
* @param baseType The {@link Class} which interfaces should be determined.
*/
private static void fillInInterfaces(final Collection<Class<?>> target, final Class<?> baseType) {
for (final Class<?> interfaze : baseType.getInterfaces()) {
if (!target.contains(interfaze)) {// add any interface only once
target.add(interfaze);
}
fillInInterfaces(target, interfaze);
}
}
private static Class<?> getType(final Object value) {
return (value == null) ? Object.class : value.getClass();
}
// --- Perform Cast ---
@SuppressWarnings("unchecked")
private static Object castEnum(final String value, final Class<?> type) {
try {
final Class<? extends Enum> enumType = type.asSubclass(Enum.class);
final Object e = Enum.valueOf(enumType, value);
return e;
}
catch (final Exception exception) {
return null;
}
}
@SuppressWarnings("unchecked")
private static <T> Object castObject(final Object value, final Class<?> from, final Class<T> to) {
final Object casted;
final Cast cast = getCast(from, to);
if (cast == INVALID_CAST) {
throw new IllegalArgumentException("Cast not supported. '" + value + "' (type=" + from.getCanonicalName() + ") to " + to.getCanonicalName());
}
try {
casted = cast.cast(value);
}
catch (final Throwable e) {
throw new IllegalArgumentException("Casting '" + value + "' (type=" + from.getCanonicalName() + ") to " + to.getCanonicalName() + " failed.", e);
}
return casted;
}
// --- Get Cast ---
private static <I, O> Cast<Object, Object> getCast(final Class<I> from, final Class<O> to) {
final Class<?> source = wrapPrimitive(from);
final Class<?> target = wrapPrimitive(to);
if (source == target) {
return SELF_CAST; // Stop overhead for same types
}
else if (target.isAssignableFrom(source)) {
return SELF_CAST; // Stop overhead if the types are compatible by nature.
}
else { // Okay, lets search a matching cast
for (final Class<?> type : getTypes(source)) {// search for a casting with source or any supertype as input
final HashMap<Class<?>, Cast<?, ?>> casts = castings.get(type);
if (casts == null) {
continue;
}
final Cast<Object, Object> cast = (Cast<Object, Object>) casts.get(target);
if (cast != null) {
return cast; // matching cast found
}
}
return INVALID_CAST; // no matching cast exists
}
}
// --- Boolean Util ---
/**
* Returns {@code true} if a {@link String} could be considered as {@code boolean}. This is assumed if the given
* {@link String} matches {@link #PATTERN_BOOLEAN}.
* <p>
* <b>Note:</b> The result of this method does not make any assumption to the result of {@link #asBoolean(String)},
* because {@link #asBoolean(String)} always returns a boolean value for any {@link String}.
*
* @param value The {@link String} to check, if it could be considered as {@code boolean}
* @return {@code true} if the {@link String} could be considered very likely as boolean. {@code false} if not.
*/
static final boolean couldBeBoolean(final String value) {
return PATTERN_BOOLEAN.matcher(value).matches();
}
/**
* A {@link String} is {@code false} if it:
* <ul>
* <li>is {@code null}
* <li>is empty
* <li>matches {@link #PATTERN_FALSE}. (ignoring case)
* </ul>
* Will return {@code true} in all other cases.
*
* @param value The {@link String} to check
* @return {@code false} if the {@link String} matches any of the conditions above. {@code true} otherwise.
*/
static final boolean asBoolean(final String value) {
return (value != null) && !value.isEmpty() && !PATTERN_FALSE.matcher(value).matches();
}
// --- Primitive Type Handling ---
/**
* Returns the corresponding {@code null}-value of the given {@code type}, if it would not be set explicitly. This
* means this method returns:
* <ul>
* <li> {@code null} for all non-primitve types.
* <li> {@code false} for {@code boolean}
* <li> {@code 0} for {@code char}, {@code int}, {@code short} , {@code byte} and {@code long}
* <li> {@code 0.0} for {@code float} and {@code double}
* </ul>
*
* @param type The type to consider.
* @return The default value of the given {@code type}.
*/
@SuppressWarnings("unchecked")
private static <T> T castNullToPrimitive(final Class<T> type) {
T result;
if (type == null) {
result = null;
}
else if (isPrimitive(type)) {
final Class<?> wrapped = wrapPrimitive(type);
//@formatter:off
if (wrapped == Boolean.class) { result = (T) NULL_BOOLEAN; }
else if (wrapped == Character.class){ result = (T) NULL_CHARACTER; }
else if (wrapped == Byte.class) { result = (T) NULL_BYTE; }
else if (wrapped == Short.class) { result = (T) NULL_SHORT; }
else if (wrapped == Integer.class) { result = (T) NULL_INTEGER; }
else if (wrapped == Long.class) { result = (T) NULL_LONG; }
else if (wrapped == Float.class) { result = (T) NULL_FLOAT; }
else if (wrapped == Double.class) { result = (T) NULL_DOUBLE; }
else if (wrapped == Void.class) { result = null;}
else {throw new IllegalArgumentException("Unkown primitive type: "+ type); }
//@formatter:on
}
else {
result = null;
}
return result;
}
/**
* Returns the wrapping class for the given primitive {@code type}. Will return {@code null} if {@code type} is
* {@code null}. Will return the given {@code type}, if {@code type} is not primitive. Will also return {@code null}
* for {@link Void#TYPE}.
*
* @param type The primitive type
* @return The wrapping class for the given type.
*/
private static final Class<?> wrapPrimitive(final Class<?> type) {
Class<?> result;
if (type == null) {
result = null;
}
else if (type.isPrimitive()) {
//@formatter:off
if (type == boolean.class) { result = Boolean.class; }
else if (type == char.class) { result = Character.class; }
else if (type == byte.class) { result = Byte.class; }
else if (type == short.class) { result = Short.class; }
else if (type == int.class) { result = Integer.class; }
else if (type == long.class) { result = Long.class; }
else if (type == float.class) { result = Float.class; }
else if (type == double.class) { result = Double.class; }
else if (type == void.class) { result = Void.class; }
else { throw new IllegalArgumentException("Unkown primitive type: "+ type);}
//@formatter:on
}
else {
result = type;
}
return result;
}
/**
* Returns {@code true} if the given {@code type} is primitive or a wrapper class for a primitive type. Returns
* {@code false} otherwise.
*
* @param type The {@code type} to check, if it is primitive or a primitive wrapper.
* @return {@code true} if the given {@code type} is primitive or a wrapper for a primitive type. {@code false}
* otherwise.
*/
private static final boolean isPrimitive(final Class<?> type) {
return type.isPrimitive() //
|| (type == Character.class) //
|| (type == Boolean.class)//
|| (type == Integer.class) //
|| (type == Double.class) //
|| (type == Float.class) //
|| (type == Short.class) //
|| (type == Long.class) //
|| (type == Byte.class) //
|| (type == Void.class) //
;
}
// -----------------------------
// Private Class
public static SimpleDateFormat getDateFormat() {
return DATE_FORMAT;
}
public static Object[] getEmptyArray() {
return EMPTY_ARRAY;
}
private static abstract class Cast<IN, OUT> {
// --- Private Attributes ---
private final Class<IN> inputType;
private final Class<OUT> outputType;
// --- Constructor ---
/**
* Creates a new Cast-instance, to cast values from the given source ( {@code from}) to the given target (
* {@code to}) type.
*
* @param from The source type. This type should be <b>as abstract as possible</b> to allow many types to use
* this instance
* @param to The target type. This type should be <b>as concrete as possible</b> to handle all types in the
* type-hierarchie of the concrete type. In the end we always need a concrete implementation to
* realize an abstract one. So keeping types abstract here would lower the available options.
*/
public Cast(final Class<IN> from, final Class<OUT> to) {
this.inputType = from;
this.outputType = to;
}
// --- Public Methods ---
public final Class<IN> getInputType() {
return this.inputType;
}
public final Class<OUT> getOutputType() {
return this.outputType;
}
public abstract OUT cast(IN value) throws Exception;
@Override
public String toString() {
return "Autocast[in=" + getInputType().getCanonicalName() + " out=" + getOutputType().getCanonicalName() + "]";
}
/**
* Returns {@code false} if this instance really casts an instance to another. Returns {@code true} if this cast
* instance doesn't really do anything on invoking {@link #cast(Object)}.
*/
@SuppressWarnings("static-method")
public boolean isNoop() {
return false;
}
// --- Protected Methods ---
}
private static final class CastStringToNumber extends Cast<String, Number> {
// --- Private Attributes ---
/** See {@link Double#valueOf(String)} for regex explanation */
private static final Pattern DOUBLE_REGEX_PATTERN;
static {
final String digits = "(\\p{Digit}+)";
final String hexDigits = "(\\p{XDigit}+)";
final String exp = "[eE][+-]?" + digits;
final String regex = ("[\\x00-\\x20]*" //
+ "[+-]?(" //
+ "NaN|" //
+ "Infinity|" //
+ "(((" + digits + "(\\.)?(" + digits + "?)(" + exp + ")?)|" //
+ "(\\.(" + digits + ")(" + exp + ")?)|" //
+ "((" //
+ "(0[xX]" + hexDigits + "(\\.)?)|" //
+ "(0[xX]" + hexDigits + "?(\\.)" + hexDigits + ")" //
+ ")[pP][+-]?" + digits + "))" + "[fFdD]?))" + "[\\x00-\\x20]*");
DOUBLE_REGEX_PATTERN = Pattern.compile(regex);
}
private static final BigInteger BYTE_MIN = BigInteger.valueOf(Byte.MIN_VALUE);
private static final BigInteger BYTE_MAX = BigInteger.valueOf(Byte.MAX_VALUE);
private static final BigInteger LONG_MIN = BigInteger.valueOf(Long.MIN_VALUE);
private static final BigInteger LONG_MAX = BigInteger.valueOf(Long.MAX_VALUE);
private static final BigInteger SHORT_MIN = BigInteger.valueOf(Short.MIN_VALUE);
private static final BigInteger SHORT_MAX = BigInteger.valueOf(Short.MAX_VALUE);
private static final BigInteger INTEGER_MIN = BigInteger.valueOf(Integer.MIN_VALUE);
private static final BigInteger INTEGER_MAX = BigInteger.valueOf(Integer.MAX_VALUE);
private static final BigDecimal FLOAT_MAX = BigDecimal.valueOf(Float.MAX_VALUE);
private static final BigDecimal FLOAT_MIN = BigDecimal.valueOf(-Float.MAX_VALUE);
private static final BigDecimal DOUBLE_MAX = BigDecimal.valueOf(Double.MAX_VALUE);
private static final BigDecimal DOUBLE_MIN = BigDecimal.valueOf(-Double.MAX_VALUE);
// --- Constructor ---
public CastStringToNumber() {
super(String.class, Number.class);
}
// --- Public Methods ---
@Override
public Number cast(final String value) throws RuntimeException {
return value == null ? null : parseNumber(value);
}
// --- Private Methods ---
/**
* Returns the best matching {@link Number}-instance available for the given {@code string}. Throws a
* {@link NumberFormatException} if the given {@code String} could not be parsed to any {@link Number} instance.
* <p>
* The returned {@link Number}-instance depends on the value, represented by the given {@link String}:
* <p>
* If the {@code string} does not represent a decimal value, it will be converted to:
* <ul>
* <li>{@link Byte}, if in range of -2<sup>7</sup> <= value <= 2<sup>7</sup>-1
* <li>{@link Short}, if in range of -2<sup>15</sup> <= value <= 2<sup>15</sup>-1
* <li>{@link Integer}, if in range of -2<sup>31</sup> <= value <= 2<sup>31</sup>-1
* <li>{@link Long}, if in range of -2<sup>63</sup> <= value <= 2<sup>63</sup>-1
* <li>{@link BigInteger} for all other values, witch does not fit one of the ranges above
* </ul>
* If the {@code string} represents a decimal value, it will be converted to:
* <ul>
* <li>{@link Float}, if in range of (2-2<sup>-23</sup>)·2<sup>127</sup> <= value <=
* (2-2<sup>-23</sup>)·2<sup>127</sup>
* <li>{@link Double}, if in range of (2-2<sup>-52</sup>)·2<sup>1023</sup> <= value <=
* (2-2<sup>-52</sup>)·2<sup>1023</sup>
* <li>{@link BigDecimal} for all other values, witch does not fit one of the ranges above
* </ul>
*
* @param string The {@link String} to parse to a number
* @return The resulting {@link Number}-instance.
* @throws NumberFormatException if the given {@link String} could not be parsed to any {@link Number}-instance.
*/
private static Number parseNumber(final String string) {
if (isIntegerSequence(string)) {// INTEGER
final BigInteger bigInt = new BigInteger(string);
//@formatter:off
if (isInRange(bigInt, BYTE_MIN, BYTE_MAX)) { return Byte.valueOf(bigInt.byteValue()); }
else if (isInRange(bigInt, SHORT_MIN, SHORT_MAX)) { return Short.valueOf(bigInt.shortValue()); }
else if (isInRange(bigInt, INTEGER_MIN, INTEGER_MAX)) { return Integer.valueOf(bigInt.intValue()); }
else if (isInRange(bigInt, LONG_MIN, LONG_MAX)) { return Long.valueOf(bigInt.longValue()); }
else { return bigInt;}
//@formatter:on
}
else if (isDoubleSequence(string)) {// DOUBLE
final BigDecimal bigDec = new BigDecimal(string);
//@formatter:off
if (isInRange(bigDec, FLOAT_MIN, FLOAT_MAX)) { return Float.valueOf(bigDec.floatValue()); }
else if (isInRange(bigDec, DOUBLE_MIN, DOUBLE_MAX)) { return Double.valueOf(bigDec.doubleValue()); }
else { return bigDec;}
//@formatter:on
}
else {
throw new NumberFormatException("Could not parse '" + string + "' to a number");
}
}
/**
* Returns {@code true} if the given {@link CharSequence} has the format of a valid decimal integer. This does
* not make any statement, if the given {@code sequence} is in range of {@code byte}, {@code short}, {@code int}
* or {@code long}.
*
* @param sequence The {@link CharSequence} to check
* @return Returns {@code true} if the given {@link String} has the format of a valid decimal integer.
* {@code false} if not.
*/
private static boolean isIntegerSequence(final CharSequence sequence) {
final int count = sequence.length();
if (count < 1) {
return false;
}
final boolean negative = (sequence.charAt(0) == '-');
final boolean positive = (sequence.charAt(0) == '+');
final boolean sign = negative || positive;
if (sign && (count == 1)) {
return false;
}
int charValue;
for (int index = sign ? 1 : 0; index < count; index++) {
charValue = sequence.charAt(index);
if ((charValue < '0') || (charValue > '9')) {
return false;// not 0-9 -> cancel
}
}
return true;
}
private static boolean isInRange(final BigInteger value, final BigInteger min, final BigInteger max) {
return (value.compareTo(min) >= 0) && (value.compareTo(max) <= 0);
}
/**
* Returns {@code true} if the given {@link CharSequence} has the format of a valid decimal double. This does
* not make any statement, if the given {@code string} is in range of {@code float} or {@code double}.
*
* @param string The {@link String} to check
* @return Returns {@code true} if the given {@link String} has the format of a valid double. {@code false} if
* not.
*/
private static boolean isDoubleSequence(final String string) {
return DOUBLE_REGEX_PATTERN.matcher(string).matches();
}
private static boolean isInRange(final BigDecimal value, final BigDecimal min, final BigDecimal max) {
return (value.compareTo(min) >= 0) && (value.compareTo(max) <= 0);
}
}
}