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