All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tectonica.util.FieldAccessor Maven / Gradle / Ivy

package com.tectonica.util;

import java.lang.reflect.Field;

/**
 * Simple reflection-based wrapper for a specific field in a specific instance. The field is identified by a dot-notation (e.g.
 * "address.city.name") and can be manipulated conveniently via the various methods.
 * 
 * @author Zach Melamed
 */
public class FieldAccessor
{
	private FieldAccessor()
	{}

	private Object instance;
	private String fieldPath;
	private Field field;

	public Object getInstance()
	{
		return instance;
	}

	public String getPath()
	{
		return fieldPath;
	}

	public Class getType()
	{
		if (field == null)
			return null;
		return field.getType();
	}

	public static FieldAccessor of(Object instance, String fieldPath, boolean ensurePath)
	{
//		System.out.println("Looking for '" + fieldPath + "' in " + object.getClass().getName());
		FieldAccessor fa = new FieldAccessor();
		fa.fieldPath = fieldPath;
		fa.setFieldAndObject(instance, fieldPath, ensurePath);
		return fa;
	}

	private void setFieldAndObject(Object current, String fieldPath, boolean ensurePath)
	{
		int i = fieldPath.indexOf('.');
		if (i < 0)
		{
			// terminal field in the path, we initialize the internal values
			field = getField(current, fieldPath);
			field.setAccessible(true);
			instance = current;
			return;
		}

		// further drill-down is needed
		String fieldName = fieldPath.substring(0, i);
		String remainingPath = fieldPath.substring(i + 1);
		Object memberInstance = getMemberInstance(current, fieldName);
		if (memberInstance == null)
		{
			if (!ensurePath)
				return; // leaves the internal values uninitialized, setters won't work
			memberInstance = createMemberInstance(current, fieldName);
		}
		setFieldAndObject(memberInstance, remainingPath, ensurePath);
	}

	private Field getField(Object instance, String fieldName)
	{
		try
		{
			Class clz = instance.getClass();
			while (!Object.class.equals(clz))
			{
				Field field = clz.getDeclaredField(fieldName);
				if (field != null)
					return field;
				clz = clz.getSuperclass();
			}
			return null;
		}
		catch (Exception e)
		{
			throw new RuntimeException("couldn't extract '" + fieldName + "' from " + instance, e);
		}
	}

	private Object getMemberInstance(Object instance, String fieldName)
	{
		Field field = getField(instance, fieldName);
		try
		{
			field.setAccessible(true);
			return field.get(instance);
		}
		catch (Exception e)
		{
			throw new RuntimeException("couldn't extract value of '" + fieldName + "' from " + instance, e);
		}
	}

	private Object createMemberInstance(Object instance, String fieldName)
	{
		try
		{
			Field memberField = getField(instance, fieldName);
			memberField.setAccessible(true);
			Object memberInstance = memberField.getType().newInstance();
			memberField.set(instance, memberInstance);
			return memberInstance;
		}
		catch (Exception e)
		{
			throw new RuntimeException("couldn't create member '" + fieldName + "' in " + instance, e);
		}
	}

	public Object getValue()
	{
		if (field == null)
			return null;

		try
		{
			return field.get(instance);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	public void setValue(Object value)
	{
		if (field == null)
			throw new RuntimeException("Object containing " + fieldPath + " doesn't exist, use ensurePath=true");

		try
		{
			field.set(instance, value);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	/**
	 * Similar to {@link #setValue(Object)}, but doesn't require that the passed object is of the same class as the existing field. It will
	 * perform conversions where feasible. Specifically:
	 * 
    *
  • in enum fields, the method will accept any object whose toString() returns a value equal to the Enum's toString() *
  • with primitives fields, the method will accept any object whose toString() returns a parseable value for the assigned primitive *
*/ public void setValueFromAny(Object value) throws IllegalAccessException { if (value == null) { if (field == null) return; // nullifying a non-existing field isn't considered an error setValue(null); return; } if (field == null) throw new RuntimeException("Object containing " + fieldPath + " doesn't exist, use ensurePath=true"); try { Class assignFrom = value.getClass(); Class assignTo = getType(); if (assignTo.isAssignableFrom(assignFrom)) setValue(value); else { String valueAsStr = value.toString(); if (!valueAsStr.isEmpty()) { if (assignTo.isEnum()) applyEnum(assignTo, valueAsStr); else { Object primitive = strToPrimitive(valueAsStr, assignTo); if (primitive != null) setValue(primitive); else throw new RuntimeException("Couldn't set field " + fieldPath + ": can't assign " + assignFrom.getName() + " to " + assignTo.getName()); } } } } catch (Exception e) { throw new RuntimeException("couldn't assign to '" + fieldPath + "' value of " + value, e); } } private void applyEnum(Class enumType, String enumValue) throws IllegalAccessException { for (Object e : enumType.getEnumConstants()) { if (e.toString().equals(enumValue)) { setValue(e); return; } } throw new RuntimeException("Value '" + enumValue + "' couldn't be assigned to enum " + fieldPath + " (of type " + enumType.getName() + ")"); } private Object strToPrimitive(String value, Class clz) { if (Integer.class == clz || int.class == clz) return Integer.parseInt(value); if (Double.class == clz || double.class == clz) return Double.parseDouble(value); if (Long.class == clz || long.class == clz) return Long.parseLong(value); if (Float.class == clz || float.class == clz) return Float.parseFloat(value); if (Boolean.class == clz || boolean.class == clz) return Boolean.parseBoolean(value); if (Byte.class == clz || byte.class == clz) return Byte.parseByte(value); if (Short.class == clz || short.class == clz) return Short.parseShort(value); return null; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy