Is it possible automatically instantiation of a nested Property with Commons Bean Utils?

11.9k views Asked by At

I'm using PropertyUtils.setProperty(object, name, value) method of Apache Commons Bean Utils:

Giving these classes:

public class A {
    B b;
}

public class B {
    C c;
}

public class C {
}

And this:

A a = new A();
C c = new C();
PropertyUtils.setProperty(a, "b.c", c); //exception

If I try that I get: org.apache.commons.beanutils.NestedNullException: Null property value for 'b.c' on bean class 'class A'

Is it possible to tell PropertyUtils that if a nested property has a null value try to instantiate it (default constructor) before trying to go deeper?

Any other approach?

Thank you

7

There are 7 answers

1
Alfredo Osorio On BEST ANSWER

I solved it by doing this:

private void instantiateNestedProperties(Object obj, String fieldName) {
    try {
        String[] fieldNames = fieldName.split("\\.");
        if (fieldNames.length > 1) {
            StringBuffer nestedProperty = new StringBuffer();
            for (int i = 0; i < fieldNames.length - 1; i++) {
                String fn = fieldNames[i];
                if (i != 0) {
                    nestedProperty.append(".");
                }
                nestedProperty.append(fn);

                Object value = PropertyUtils.getProperty(obj, nestedProperty.toString());

                if (value == null) {
                    PropertyDescriptor propertyDescriptor = PropertyUtils.getPropertyDescriptor(obj, nestedProperty.toString());
                    Class<?> propertyType = propertyDescriptor.getPropertyType();
                    Object newInstance = propertyType.newInstance();
                    PropertyUtils.setProperty(obj, nestedProperty.toString(), newInstance);
                }
            }
        }
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    } catch (InvocationTargetException e) {
        throw new RuntimeException(e);
    } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
    } catch (InstantiationException e) {
        throw new RuntimeException(e);
    }
}
0
Noctiluque On

I know the question is about apache commons PropertyUtils.setProperty but there is very similar functionality available in Spring Expression Language "SpEL" which does exactly what you want. Better still it deals with lists and arrays too. The doc link above is for spring 4.x but the code below works for me in spring 3.2.9.

    StockOrder stockOrder = new StockOrder(); // Your root class here

    SpelParserConfiguration config = new SpelParserConfiguration(true,true);   // auto create objects if null
    ExpressionParser parser = new SpelExpressionParser(config);
    StandardEvaluationContext modelContext = new StandardEvaluationContext(stockOrder);

    parser.parseExpression("techId").setValue(modelContext, "XXXYYY1");
    parser.parseExpression("orderLines[0].partNumber").setValue(modelContext, "65498");
    parser.parseExpression("orderLines[0].inventories[0].serialNumber").setValue(modelContext, "54686513216");

    System.out.println(ReflectionToStringBuilder.toString(stockOrder));
0
Amol Katdare On

After doing some research, the short answer to "Is it possible..." question is No.

0
pfuy On

A little correction:

String fn = fieldNames[i];
if (i != 0) {
        nestedProperty.append(".");
}
nestedProperty.append(fn);
Object value = PropertyUtils.getProperty(obj, nestedProperty.toString());
0
s_bighead On

IMHO, the best solution is to get rid of the commons-beanutils and use Spring Framework org.springframework.beans.PropertyAccessorFactory

BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(targetObject);
wrapper.setAutoGrowNestedPaths(true);

I won't delve into details of how it works, but if you want to check it out, go take a look at the link above, this API is quite intuitive, but you'll need to have Spring Framework Core configured on your classpath, so I wouldn't recommend that you add spring just for the sake of this feature.

However,

If you only have commons-beanutils as your ally, this following code snippet may help you to grow your nested paths, as you set the values, therefore, you won't need to concern about the null objects along the path properties.

In this example I used with JPA Tuple Query to construct a custom object with some specific property paths with its corresponding values to be set.

import java.util.ArrayList;
import java.util.List;

import javax.persistence.Tuple;
import javax.persistence.TupleElement;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.beanutils.expression.DefaultResolver;
public class TupleToObject<T> {


    public List<T> transformResult(List<Tuple> result, Class<T> targetClass) {
        try {
            List<T> objects = new ArrayList<>();
            for (Tuple tuple : result) {
                T target = targetClass.newInstance();

                List<TupleElement<?>> elements = tuple.getElements();
                for (TupleElement<?> tupleElement : elements) {
                    String alias = tupleElement.getAlias();
                    Object value = tuple.get(alias);

                    if (value != null) {
                        instantiateObject(target, alias);
                        PropertyUtils.setNestedProperty(target, alias, value);
                    }

                }
                objects.add(target);
            }
            return objects;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    private void instantiateObject(T target, String propertyPath) throws Exception {
        DefaultResolver resolver = new DefaultResolver();
        Object currentTarget = target;
        while (resolver.hasNested(propertyPath)) {
            final String property = resolver.next(propertyPath);
            Object value = PropertyUtils.getSimpleProperty(currentTarget, property);
            if (value == null) {
                Class<?> propertyType = PropertyUtils.getPropertyType(currentTarget, property);
                value = propertyType.newInstance();
                PropertyUtils.setSimpleProperty(currentTarget, property, value);
            }
            currentTarget = value;

            propertyPath = resolver.remove(propertyPath);
        }
    }
}

This code is using commons-beanutils-1.9.3.jar

Hope it helps!

0
Seng Zhe On

I have used only reflection w/o Apache library to achieve this. The assumption is that all object to be traversed are all POJOs, and default construction is publicly accessible. This way, there is no need to construct the reference path for each loop.

public Object getOrCreateEmbeddedObject(Object inputObj,String[] fieldNames) throws Exception {

    Object cursor = inputObj;

    //Loop until second last index
    for (int i = 0; i < fieldNames.length - 1; i++){
        Field ff = getClassFieldFrom(cursor,fieldNames[i]);
        Object child = ff.get(cursor);
        if(null == child) {
            Class<?> cls=ff.getType();
            child = cls.newInstance();
            ff.set(cursor, child);
        }
        cursor = child;
    }

    return cursor;
}

private Field getClassFieldFrom(Object object, String fieldStr)
            throws NoSuchFieldException {
        java.lang.reflect.Field ff = object.getClass().getDeclaredField(fieldStr);
        ff.setAccessible(true);
        return ff;
}

If you have any suggestion to my solution , please let me know.

0
David Lavender On

I went for the very basic approach of just instantiating each of the objects by default:

public class A {
    B b = new B();
}

public class B {
    C c = new C();
}

public class C {
}

Not ideal, but it worked for my situation and didn't involve complicated fixes.