CastUtil - Der Universelle Caster

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);
	}
}

}

Tatsächlich wüßte ich schon ein paar Fälle, wo sowas praktisch sein könnte (speziell, grob: Wenn man CSV-Dateien liest, und die Strings (der “Zellen” der Tabelle) eben Zahlen sind…)
Das “Killer-Beispiel” hast du ja schon gepostet: Aus was für abstrusen Strings man nicht alles ein Datum parsen kann :sick: Und ob “03/04/05” nun für 2005-04-03 oder 1905-04-03 oder 2005-03-04 steht, weiß man nicht (fiese (praxisferne?) Frage: Was passiert, wenn man zwei verschiedene DateFormats in seinem Programm behandeln können will?).
Der Code sieht beim Drüberscrollen aus, als hättest du schon viele mögliche Stolpersteine berücksichtigt, aber für eine weitere Einschätzung habe ich es noch nicht genau genug angeschaut.

beeindruckend langes Programm, paar kleine Anmerkungen:

  • der Name leider, Cast ist für mich ein recht eindeutiger Fachbegriff, reine Typumwandlung ohne das Objekt zu berühren, also es liegt danach dasselbe vor,
    davon kann hier ja kaum die Rede sein, ‚Transform‘ oder irgendwas in der Richtung wäre genauere Bezeichnung, aber das ganze Programm willst du sicher nicht mehr umbenennen :wink:

  • Generics-Einsatz scheint mir hier oft übertrieben, und du hast doch schon @SuppressWarnings("rawtypes") drüberstehen,
    ich habe eben 46x <?> oder <?, ?> entfernen lassen und macht keinen Unterschied

als Paradebeispiel herausgesucht habe ich noch die Methode
private static <I, O> Cast<Object, Object> getCast(final Class<I> from, final Class<O> to) ,
I und O und <Object, Object> für den Rückgabewert haben keine Aussage,
Aufrufer brauchen das auch nicht (zumindest nachdem <?, ?> bereits entfernt)

die Zeile
final Cast<Object, Object> cast = (Cast<Object, Object>) casts.get(target);
darin meckert mit Eclipse schon im Original als unnötig an, in einem echten Cast (!) mit Klammern sind eckige Klammern nahezu immer unnütz,
ansonsten muss man auch nicht casten, nur
final Cast cast = casts.get(target);

  • Javadoc…, braucht man wirklich Farben, Formatierung, Links zu Klassen (auch String…), Konstanten usw. im Popupfenster zu Javadoc?,
    im Quellcode selber ist das dann kaum mehr zu lesen, ein ziemlich hoher Preis,

hängt etwas davon ab wie oft man direkt den Quellcode der Klasse anschaut, bei Tools in der Regel seltener,
hier ins Forum zur Ansicht gepostet ist es natürlich wiederum besonders ungünstig

    /**
     * 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}
     * [...]
     */

aber in Java-API und gewiss auch anderswo ebenso verbreitet…


von Kommentar-Wüsten der Form

    /**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);

halte ich auch nicht viel, immer das gleiche, wenn man den Quellcode anschaut, einmal dürfte reichen,
von außen kaum erreichbar bei private, wenn dann sollte man eher drüber nachdenken was die Klasse ist, das meiste dann klar

  • bei SELF_CAST ist der Code hier im Forum merkwürdig formatiert

soviel fiel mir mal eben zum Code auf, ohne groß was zum Inhalt beizutragen :wink:
und nicht wundern, mehr als meckern kann ich generell kaum

Zur Benamung stimme ich SlaterB zu. Conversion, Coercion oder Transformation wäre besser gewesen. Weiters finde ich selbst bei Utility-Methoden die Verwendung von static recht unglücklich, weil nicht gegen Interfaces programmierbar. Außerdem zuviel Code in einer einzigen Klasse. So sehr unübersichtlich und fast nicht testbar. Besonders kritisch bei Code, der so eine mächtige Funktionalität bietet und wahrscheinlich oft aufgerufen wird. Wenn Du Dir anhand von Interfaces kleine Teile definieren würdest (Registry, einzelne Transformation etc.) würde das auch der Übersichtlichkeit zugute kommen. Die Testbarkeit und Übersichtlichkeit wiegen meiner Meinung nach den Vorteil des schnellen Kopierens auf. Abgesehen davon kann man ja auch eigene jars in Maven, Gradle oder was weiß ich für Build-Tools integrieren. Mach ich mit meinen selbstgeschriebenen Libs auch immer so.

Ansonsten Lob für den Ansatz, dieses Problem nicht kleinteilig und isoliert immer gerade da zu lösen, wo es auftritt, sondern zentral an einer genau dafür vorgesehenen Stelle zusammenzufassen und gründlich zu bearbeiten.

Zeile 680

        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) //```

Wäre sowas nicht handlicher
```private static final List<Class> PRIMITIVE_CLASSES = Arrays.asList(new Class[]{Character.class, Boolean.class, Integer.class, });

    private static final boolean isPrimitive(final Class<?> type) {
        return type.isPrimitive() || PRIMITIVE_CLASSES.contains(type);

Ein paar Zeilen früher

            //@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);}```

private static final Map<Class, Class> TYPE_TO_RESULT = HashMap<>(){
put(boolean.class, Boolean.class);};


if(PRIMITIVE_TO_RESULT.containsKey(type)) {
PRIMITIVE_TO_RESULT.get(type);
} else { throw new IllegalArgumentException("Unkown primitive type: "+ type);}```

Und mit den Null-values läuft es ähnlich ab.

Jetzt könnte man statt, der verschiedenen Listen und Maps, sich eine eigene Klasse basteln und diese in einer Map vorhalten und dann darauf zugreifen.

  Class primitive;
  Class wrapped;
  Object nullValue;
}```

Ich kann mir vorstellen, dass man den Code so etwas übersichtlicher gestalten kann und auch etwas kürzer und somit evtl. auch etwas weniger fehleranfällig und wartbarer.

Hallo,

von der Idee her interessant. Ich könnte mir vorstellen, so etwas zu verwenden.

Allerdings sehe ich den Code kaum als wartbar an. Ich würde das Ding anders nennen (wie hier schon gesagt) und die einzelnen Möglichkeiten modularisieren. Warum nicht eine Transformations-Klasse für jeden Typ? Das wäre leicht erweiterbar. Wenn es dann nicht noch statisch ist, dann sieht das ganze doch schon wesentlich besser aus.

Das mit den primitiven Typen ist mir beim Drüberscrollen auch ins Auge gefallen (nicht zuletzt weil ich ein paar Methoden in diesem Umfeld kürzlich selbst gebaut habe) : Es gibt eine Methode Class#isPrimitive. Und für die Map: Bitte nicht diese “double brace initialization”…

private static final Map<Class, Class> PRIMITIVE_TO_BOXED = HashMap<Class, Class>();
static
{
    PRIMITIVE_TO_BOXED.put(boolean.class, Boolean.class);
    ...
};

static boolean isPrimitive(Class<?> c) { return c.isPrimitive(); }
static boolean isBoxed(Class<?> c) { return BOXED_TO_PRIMITIVE.containsKey(c); }
static Class<?> getBoxed(Class<?> c) { return PRIMITIVE_TO_BOXED.get(c); }

// ( + Fehlerbehandlung, natürlich)

aber das ist erstmal nur ein Detail…

Erstmal vielen Dank für eure Kommentare. Genau deswegen stelle ich es ja hier online, weil ich schon ahne, dass ein paar Dinge nicht komplett optimal sind. Das Handling der Primitiven Typen in Objekte zu speichern ist eine gute Idee.

@Marco13 :

  • Ja, den Namen könnte man wohl ändern. „Transform“ gefällt mir gut =).

  • Im Moment gibt es nur ein Datums-Format. In der aktuellen Variante müsste man wohl den String->Date-„Cast“ umschreiben. Alternativ könnte man ihn gleich so modifizieren, dass man ein Array von unterstützten Formaten hat, das durchprobiert wird, wenn das erste nicht passt.

    @SlaterB :

  • Das suppress hab ich erst dazu geschrieben, als die enums gar nicht mehr mitspielen wollten. Eigentlich hatte ich vor eine „sauber Lösung mit Generics“ zu schaffen. Hab dann natürlich den Code nicht mehr entsprechend geändert.

  • Gebe ich zu: Habe die Kommentare nicht fürs Forum angepasst. In der IDE nutze ich eigentlich IMMER das Popup-Fenster, eben weil ich da mehr Möglichkeiten habe, auch durch Formatierung die Funktionalität besser zu erklären. Fürs Forum ist sowas natürlich ungünstig, zugegeben.

    @nillehammer :
    Ich gebe zu, es ist eine Monolith-Lösung. Aber erweiterbar ist sie deswegen nicht minder. Eigentlich wird ja schon mit der abstracten Klasse „Cast“ gearbeitet, nur die Implementierungen sind eben anonym. Aber wahrscheinlich hast du Recht. Die saubere Lösung wären eigene Klassen, die ich dann über eine öffentliche Methode hinzufüge. Im Moment ist das eben noch alles privat/anonym.

    @Sym :
    Was willst du denn da „nicht-statisch“ machen? Natürlich könnte man den Konstruktor public machen und den beiden öffentlichen Methoden das „static“ entziehen. Aber macht es das um soviel besser? Eine Instanz hat ja selbst eigentlich keinen Zustand. Natürlich könnte man jetzt verschieden Instanzen mit verschieden Cast-Operationen versehen. Aber dann müsste man alle Möglichkeiten von Hand hinzufügen!? Vielleicht stehe ich hier gerade auf dem Schlauch, aber außer der Tatsache, dass man dann ein Objekt in der Hand hat, sehe ich gerade wenig Vorteile darin, die Klasse „nicht-statisch“ zu machen, eben weil sie eigentlich keine Zustand hat.

Ich denke ich werde eure Anregungen nutzen und demnächst mal eine zweite Version präsentieren. Mal gucken, wann ich dazu komme. :wink:

Das erste, an was ich beim Lesen des Themas denken musste, ist Spring Type Conversion (Redirecting...). Ok, ich sehe derzeit überall nur noch Spring, weil ich mich in den letzten Wochen intensiv darin einarbeite. Die dort definierten Interfaces lösen genau das Problem, welches du mit der einzelnen Klasse auch löst.
Dort sind auch die von @nillehammer beschriebenen Probleme sauber gelöst - es ist kein Monolith, sondern flexibel erweiterbar, indem man einen Converter registriert.
Vielleicht kannst du dich dort ja inspirieren lassen - das Konzept wirkt auf mich zumindest sehr durchdacht.

Grob verglichen:
Dein CastUtil entspricht einem statischen ConversionService. Das ist wohl auch der entscheidende Zugangspunkt. Wenn du das ganze dann noch nichtstatisch und von außen konfigurierbar gestaltest (es würde ja reichen, wenn du addCast öffentlich machst), dann sieht das echt gut aus.

Wenn du es nichtstatisch machst und von außen konfigurierst, hättest du die Möglichkeit verschiedene CastServices (so nenne ich jetzt eine nichtstatische Version einfach mal) zu konfigurieren. Das hätte den Vorteil, dass du zum Beispiel verschiedene Implementierungen für eine bestimmte Konversion in verschiedenen Services haben kannst. Und sei es nur eine lokalisierte Version von DateTime → String.

Dagegen könntest du einen convenience-initializer anbieten, mit dem du die „gängigen“ Casts registrierst, falls der Anwender das möchte.

Haste Lust mal ne jar oder so zu machen und die mal irgendwo hochladen? Ich könnte das zwar auch, aber ich behaupte jetzt mal einfach, dass du schon die ganzen Verbesserungsvorschläge umgesetzt hast und ich das jetzt auch nicht machen will :wink:

Ich glaube es wäre angenehmer das Projekt bei Code-Hosting Diensten wie Bitbucket, Github oder Google Code unterzubringen.