Staging
v0.5.1
swh:1:snp:508923e52305fedd5556b26d8042e0ec70598cef
Raw File
FieldSetter.java
package org.pulloid;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Map;

/**
 * A field setter is used to set values to a specific field in a Java class. These values are mostly string values extracted from XML.
 * However it can be other types of values because of the use of {@link StringTransformer}.
 * 
 * @author Romain Laboisse labrom@gmail.com
 *
 * @param <T> The target object type.
 */
public class FieldSetter<T> {


	/**
	 * The list of types that this setter knows how to transform strings to.
	 */
	@SuppressWarnings("unchecked")
	private static final Collection<Class<?>> SUPPORTED_CONVERT_TYPES = new ArrayList<Class<?>>(Arrays.asList(
			boolean.class,
			Boolean.class,
			byte.class,
			Byte.class,
			short.class,
			Short.class,
			int.class,
			Integer.class,
			float.class,
			Float.class,
			long.class,
			Long.class,
			double.class,
			Double.class,
			BigInteger.class,
			BigDecimal.class,
			Number.class,
			Calendar.class,
			Date.class));
	
	private static final long PIVOT_DATE_MILLISEC = 33000000000l; // Around year 1971 in milli-seconds, around year 3000 in seconds
	
	private Field field;
	private Class<?> fieldType;
	private String fieldName;
	private Method setterMethod;
	private boolean canConvert;

	/**
	 * This constructor is the same as {@code FieldSetter(targetType, fieldName, null)}.
	 * @param targetType
	 * @param fieldName
	 */
	public FieldSetter(Class<T> targetType, String fieldName) {
		this(targetType, fieldName, null);
	}

	/**
	 * Creates a field setter.
	 * @param targetType The target type. If null, this setter will assume the target object is a Map of String, Object.
	 * @param fieldName Must not be null.
	 * @param targetFieldType The actual type of the field to set. It can be null however we make the assumption that if none is specified
	 * then this setter will be used with string values only.
	 */
	public FieldSetter(Class<T> targetType, String fieldName, Class<?> targetFieldType) {
		if(fieldName == null)
			throw new NullPointerException("fieldName must not be null");
		
		// If this is a Map, we'll just call put(String, Object) - we support Map<String, Object> only
		if(targetType == null || Map.class.isAssignableFrom(targetType)) {
			this.fieldName = fieldName;
			setFieldType(targetFieldType != null ? targetFieldType : String.class); // If not type specified, String is assumed
			return;
		}
		
		try {
			// We don't need to know the field type since we're just looking for the member field with the right name
			this.field = targetType.getField(fieldName);
			if(targetFieldType != null && field.getType() != targetFieldType) { // If a type was specified, check it's the right type
				throw new CursorException(String.format("Found a field but with wrong type: %1$s instead of assumed %2$s", field.getType().getName(), targetFieldType.getName()));
			}
			setFieldType(field.getType());
		} catch(NoSuchFieldException nsfe) {
			// No such field, trying setter method
			String setterMethodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);

			if(targetFieldType != null) {
				try {
					this.setterMethod = targetType.getMethod(setterMethodName, targetFieldType);
					setFieldType(targetFieldType);
					return;
				} catch (SecurityException se) {
					throw new CursorException(se);
				} catch (NoSuchMethodException nsme) {
					throw new CursorException(String.format("Didn't find a setter method with the right type: %1$s", targetFieldType.getName()));
				}
			} else {
				for(Method m : targetType.getMethods()) {
					if(m.getName().equals(setterMethodName)) {
						Class<?>[] params = m.getParameterTypes();
						if(params.length == 1) { // Guess we found the right setter method
							this.setterMethod = m;
							setFieldType(params[0]);
							return;
						}
					}
				}
			}
			
			throw new CursorException(String.format("Unable to find a field or a setter method for field %1$s in class %2$s", fieldName, targetType.getName()));
		}
	}

	private void setFieldType(Class<?> type) {
		this.fieldType = type;
		this.canConvert = SUPPORTED_CONVERT_TYPES.contains(this.fieldType);
	}


	/**
	 * Converts a value to this setter's target field type if needed.
	 * @param value The value to set.
	 * @return
	 */
	final Object getFinalValue(Object value) {
		if(fieldType == null || value == null || !canConvert)
			return value;
		
		String strValue = value instanceof String ? (String)value : value.toString();

		// Boolean values
		if(fieldType == Boolean.class || fieldType == boolean.class) {
			if(value.equals(1) || value.equals("1"))
				return true;
			return strValue.equalsIgnoreCase("true");
		}
		
		// Numbers
		if(fieldType == Byte.class)
			return new Byte(strValue);
		if(fieldType == byte.class)
			return Byte.parseByte(strValue);
		if(fieldType == Short.class)
			return new Short(strValue);
		if(fieldType == short.class)
			return Short.parseShort(strValue);
		if(fieldType == Integer.class)
			return new Integer(strValue);
		if(fieldType == int.class)
			return Integer.parseInt(strValue);
		if(fieldType == Long.class)
			return new Long(strValue);
		if(fieldType == long.class)
			return Long.parseLong(strValue);
		if(fieldType == Float.class)
			return new Float(strValue);
		if(fieldType == float.class)
			return Float.parseFloat(strValue);
		if(fieldType == Double.class)
			return new Double(strValue);
		if(fieldType == double.class)
			return Double.parseDouble(strValue);
		if(fieldType == BigInteger.class)
			return new BigInteger(strValue);
		if(fieldType == BigDecimal.class)
			return new BigDecimal(strValue);
		// For numbers, fall back to Double
		if(fieldType == Number.class)
			return new Double(strValue);
		
		if(fieldType == Calendar.class || fieldType == Date.class) {
			long millis = 0; // Epoch
			if(value instanceof Number) {
				millis = ((Number)value).longValue();
			} else {
				try {
					millis = Long.parseLong(strValue);
				} catch(NumberFormatException e) {
					// TODO What to do here?
				}
			}
			if(millis < PIVOT_DATE_MILLISEC)
				millis *= 1000;
			if(fieldType == Calendar.class) {
				Calendar cal = Calendar.getInstance();
				cal.setTimeInMillis(millis);
				return cal;
			}
			return new Date(millis);
		}
		return value;
	}

	/**
	 * Sets a value to the field represented by this setter.
	 * @param target The target object.
	 * @param value The value to set.
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public boolean set(T target, Object value) {
		if(value == null)
			return false;
		
		// In case of a map
		if(fieldName != null) {
			((Map<String, Object>)target).put(fieldName, getFinalValue(value));
			return true;
		}
		
		if(field != null) {
			// TODO Transform value is needed
			try {
				field.set(target, getFinalValue(value));
				return true;
			} catch(IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} else if(setterMethod != null) {
			
			// TODO Transform value is needed
			try {
				setterMethod.invoke(target, getFinalValue(value));
				return true;
			} catch(IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch(InvocationTargetException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return false;
	}
	
	String getFieldName() {
		if(fieldName != null)
			return fieldName;
		
		if(field != null)
			return field.getName();
		
		String field = setterMethod.getName().substring(3);
		field = field.substring(0, 1).toLowerCase() + field.substring(1);
		return field;
	}
}
back to top