OUTCASTGEEK

tips, tricks, tuts, and more

bean traversal afternoon hack


If you work in the enterprise, I am sure you have encountered structures like this:

NestedPojo.java:

package com.outcastgeek.traversal.pojos;

public class NestedPojo {

    private String nestedPojoField1;
    private String nestedPojoField2;
    private PojoLevelOne pojoLevelOne;

    public String getNestedPojoField1() {
        return nestedPojoField1;
    }

    public void setNestedPojoField1(String nestedPojoField1) {
        this.nestedPojoField1 = nestedPojoField1;
    }

    public String getNestedPojoField2() {
        return nestedPojoField2;
    }

    public void setNestedPojoField2(String nestedPojoField2) {
        this.nestedPojoField2 = nestedPojoField2;
    }

    public PojoLevelOne getPojoLevelOne() {
        return pojoLevelOne;
    }

    public void setPojoLevelOne(PojoLevelOne pojoLevelOne) {
        this.pojoLevelOne = pojoLevelOne;
    }
}
PojoLevelOne.java:
package com.outcastgeek.traversal.pojos;

public class PojoLevelOne {

    private String pojoLevelOne1;
    private String pojoLevelOne2;
    private PojoLevelTwo pojoLevelTwo;

    public String getPojoLevelOne1() {
        return pojoLevelOne1;
    }

    public void setPojoLevelOne1(String pojoLevelOne1) {
        this.pojoLevelOne1 = pojoLevelOne1;
    }

    public String getPojoLevelOne2() {
        return pojoLevelOne2;
    }

    public void setPojoLevelOne2(String pojoLevelOne2) {
        this.pojoLevelOne2 = pojoLevelOne2;
    }

    public PojoLevelTwo getPojoLevelTwo() {
        return pojoLevelTwo;
    }

    public void setPojoLevelTwo(PojoLevelTwo pojoLevelTwo) {
        this.pojoLevelTwo = pojoLevelTwo;
    }
}
PojoLevelTwo.java:
package com.outcastgeek.traversal.pojos;

public class PojoLevelTwo {

    private String pojoLevelTwo1;
    private String pojoLevelTwo2;
    private PojoLevelThree pojoLevelThree;

    public String getPojoLevelTwo1() {
        return pojoLevelTwo1;
    }

    public void setPojoLevelTwo1(String pojoLevelTwo1) {
        this.pojoLevelTwo1 = pojoLevelTwo1;
    }

    public String getPojoLevelTwo2() {
        return pojoLevelTwo2;
    }

    public void setPojoLevelTwo2(String pojoLevelTwo2) {
        this.pojoLevelTwo2 = pojoLevelTwo2;
    }

    public PojoLevelThree getPojoLevelThree() {
        return pojoLevelThree;
    }

    public void setPojoLevelThree(PojoLevelThree pojoLevelThree) {
        this.pojoLevelThree = pojoLevelThree;
    }
}
PojoLevelThree.java:
package com.outcastgeek.traversal.pojos;

public class PojoLevelThree {

    private String pojoLevelThree1;
    private String pojoLevelThree2;
    private PojoLevelFour pojoLevelFour;

    public String getPojoLevelThree1() {
        return pojoLevelThree1;
    }

    public void setPojoLevelThree1(String pojoLevelThree1) {
        this.pojoLevelThree1 = pojoLevelThree1;
    }

    public String getPojoLevelThree2() {
        return pojoLevelThree2;
    }

    public void setPojoLevelThree2(String pojoLevelThree2) {
        this.pojoLevelThree2 = pojoLevelThree2;
    }

    public PojoLevelFour getPojoLevelFour() {
        return pojoLevelFour;
    }

    public void setPojoLevelFour(PojoLevelFour pojoLevelFour) {
        this.pojoLevelFour = pojoLevelFour;
    }
}
PojoLevelFour.java:
package com.outcastgeek.traversal.pojos;

public class PojoLevelFour {

    private String pojoLevelFour1;
    private String pojoLevelFour2;

    public String getPojoLevelFour1() {
        return pojoLevelFour1;
    }

    public void setPojoLevelFour1(String pojoLevelFour1) {
        this.pojoLevelFour1 = pojoLevelFour1;
    }

    public String getPojoLevelFour2() {
        return pojoLevelFour2;
    }

    public void setPojoLevelFour2(String pojoLevelFour2) {
        this.pojoLevelFour2 = pojoLevelFour2;
    }
}

The problem is that it leads you to write code like the following, which I find extremely annoying:

TheProblem.java:

package com.outcastgeek.traversal.problem;

import com.outcastgeek.traversal.pojos.NestedPojo;
import com.outcastgeek.traversal.pojos.PojoLevelFour;
import com.outcastgeek.traversal.pojos.PojoLevelOne;
import com.outcastgeek.traversal.pojos.PojoLevelThree;
import com.outcastgeek.traversal.pojos.PojoLevelTwo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TheProblem {

    private static Logger logger = LoggerFactory.getLogger(TheProblem.class);

    public void someMethodThatDoesStuffWithValueDownBelow(NestedPojo nestedPojo) {

        PojoLevelOne pojoLevelOne = nestedPojo.getPojoLevelOne();
        if (pojoLevelOne != null) {
            logger.info("Passed level one null check!!!!");
            PojoLevelTwo pojoLevelTwo = pojoLevelOne.getPojoLevelTwo();
            if (pojoLevelTwo != null) {
                logger.info("Passed level two null check!!!!");
                PojoLevelThree pojoLevelThree = pojoLevelTwo.getPojoLevelThree();
                if (pojoLevelThree != null) {
                    logger.info("Passed level three null check!!!!");
                    PojoLevelFour pojoLevelFour = pojoLevelThree.getPojoLevelFour();
                    if (pojoLevelFour != null) {
                        logger.info("Passed level four null check!!!!");
                        logger.info("Guess what, we did all of this work just to print these ({}, {})!!!!", pojoLevelFour.getPojoLevelFour1(), pojoLevelFour.getPojoLevelFour2());
                    }
                }
            }
        }
    }
}

I like to stay DRY, so meet the bean traversal afternoon hack:

TraverseUtils.java:

package com.outcastgeek.traversal;

import com.outcastgeek.exceptions.TraverseException;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;

/**
 * TraverseUtils
 */
public class TraverseUtils {

    private static Logger logger = LoggerFactory.getLogger(TraverseUtils.class);

    /**
     * @param pojo is the POJO to be traversed
     * @param pathSteps is traversal path
     * @return true or false
     * @throws TraverseException
     */
    public static boolean isNullPath(Object pojo, String... pathSteps) throws TraverseException {

        boolean isNullPath = false;

        try {
            Class pojoClass = pojo.getClass();
            Method[] declaredMethods = pojoClass.getDeclaredMethods();
            int pathStepLength = pathSteps.length;
            logger.debug("Traversing {}...", pojo);
            for (int i = 0; i < pathStepLength; i++) {
                String step = pathSteps[i];
                logger.debug("Step: {}", step);
                Object value = null;
                for (Method method:declaredMethods) {
                    String methodName = method.getName();
                    if (StringUtils.containsIgnoreCase(methodName, step)) {
                        value = pojoClass.getDeclaredMethod(methodName).invoke(pojo);
                        break;
                    }
                }
                if (value != null) {
                    if (i == pathStepLength - 1) {
                        break;
                    } else {
                        String[] followingSteps = ArrayUtils.removeElement(pathSteps, step);
                        return isNullPath(value, followingSteps);
                    }
                } else {
                    isNullPath = true;
                    break;
                }
            }
        } catch (Exception e) {
            throw new TraverseException(e);
        }

        return isNullPath;
    }

    /**
     * @param pojo is the POJO to be traversed
     * @param pathSteps is traversal path
     * @return the object a the end of the path
     * @throws TraverseException
     */
    public static Object getPath(Object pojo, String... pathSteps) throws TraverseException {

        Object value = null;

        try {
            Class pojoClass = pojo.getClass();
            Method[] declaredMethods = pojoClass.getDeclaredMethods();
            int pathStepLength = pathSteps.length;
            logger.debug("Traversing {}...", pojo);
            for (int i = 0; i < pathStepLength; i++) {
                String step = pathSteps[i];
                logger.debug("Step: {}", step);
                for (Method method:declaredMethods) {
                    String methodName = method.getName();
                    if (StringUtils.containsIgnoreCase(methodName, step)) {
                        value = pojoClass.getDeclaredMethod(methodName).invoke(pojo);
                        break;
                    }
                }
                if (i == pathStepLength - 1) {
                    break;
                } else {
                    String[] followingSteps = ArrayUtils.removeElement(pathSteps, step);
                    return getPath(value, followingSteps);
                }
            }
        } catch (Exception e) {
            throw new TraverseException(e);
        }

        return value;
    }
}
TraverseException.java:
package com.outcastgeek.exceptions;

/**
 * TraverseException
 */
public class TraverseException extends Exception {

    /**
     * @param t is the Throwable to be wrapped.
     */
    public TraverseException(Throwable t) {
        super(t);
    }
}

Here is a very minimal test which demonstrates how to check for nulls, or retrieve values:

TraverseUtilsTest.java:

package com.outcastgeek.traversal;

import com.outcastgeek.exceptions.TraverseException;
import com.outcastgeek.traversal.pojos.*;
import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class TraverseUtilsTest {

    public static final String POJO_LEVEL_ONE = "getpojoLevelOne";
    public static final String POJO_LEVEL_TWO = "getpojoLevelTwo";
    public static final String POJO_LEVEL_THREE = "getpojoLevelThree";
    public static final String POJO_LEVEL_FOUR = "getpojoLevelFour";
    public static final String VALUE_ONE = "valueOne";
    public static final String VALUE_TWO = "valueTwo";
    private NestedPojo nestedPojo;

    @Before
    public void setUp() {
        nestedPojo = new NestedPojo();
    }

    @Test
    public void checkLevelFourPojoIsNull() throws TraverseException {
        // setup
        PojoLevelThree pojoLevelThree = new PojoLevelThree();
        PojoLevelTwo pojoLevelTwo = new PojoLevelTwo();
        pojoLevelTwo.setPojoLevelThree(pojoLevelThree);
        PojoLevelOne pojoLevelOne = new PojoLevelOne();
        pojoLevelOne.setPojoLevelTwo(pojoLevelTwo);
        nestedPojo.setPojoLevelOne(pojoLevelOne);

        // run
        boolean response = TraverseUtils.isNullPath(nestedPojo, POJO_LEVEL_ONE, POJO_LEVEL_TWO, POJO_LEVEL_THREE, POJO_LEVEL_FOUR);

        // verify
        assertTrue(response);
    }

    @Test
    public void checkLevelFourPojoIsNotNull() throws TraverseException {
        // setup
        PojoLevelFour pojoLevelFour = new PojoLevelFour();
        PojoLevelThree pojoLevelThree = new PojoLevelThree();
        pojoLevelThree.setPojoLevelFour(pojoLevelFour);
        PojoLevelTwo pojoLevelTwo = new PojoLevelTwo();
        pojoLevelTwo.setPojoLevelThree(pojoLevelThree);
        PojoLevelOne pojoLevelOne = new PojoLevelOne();
        pojoLevelOne.setPojoLevelTwo(pojoLevelTwo);
        nestedPojo.setPojoLevelOne(pojoLevelOne);

        // run
        boolean response = TraverseUtils.isNullPath(nestedPojo, POJO_LEVEL_ONE, POJO_LEVEL_TWO, POJO_LEVEL_THREE, POJO_LEVEL_FOUR);

        // verify
        assertFalse(response);
    }

    @Test
    public void getLevelFourPojo() throws TraverseException {
        // setup
        PojoLevelFour pojoLevelFour = new PojoLevelFour();
        pojoLevelFour.setPojoLevelFour1(VALUE_ONE);
        pojoLevelFour.setPojoLevelFour2(VALUE_TWO);
        PojoLevelThree pojoLevelThree = new PojoLevelThree();
        pojoLevelThree.setPojoLevelFour(pojoLevelFour);
        PojoLevelTwo pojoLevelTwo = new PojoLevelTwo();
        pojoLevelTwo.setPojoLevelThree(pojoLevelThree);
        PojoLevelOne pojoLevelOne = new PojoLevelOne();
        pojoLevelOne.setPojoLevelTwo(pojoLevelTwo);
        nestedPojo.setPojoLevelOne(pojoLevelOne);

        // run
        PojoLevelFour response = (PojoLevelFour) TraverseUtils.getPath(nestedPojo, POJO_LEVEL_ONE, POJO_LEVEL_TWO, POJO_LEVEL_THREE, POJO_LEVEL_FOUR);

        // verify
        assertTrue(VALUE_ONE.equals(response.getPojoLevelFour1()));
        assertTrue(VALUE_TWO.equals(response.getPojoLevelFour2()));
    }

    @Test
    public void getLevelTwoPojo() throws TraverseException {
        // setup
        PojoLevelTwo pojoLevelTwo = new PojoLevelTwo();
        pojoLevelTwo.setPojoLevelTwo1(VALUE_ONE);
        pojoLevelTwo.setPojoLevelTwo2(VALUE_TWO);
        PojoLevelOne pojoLevelOne = new PojoLevelOne();
        pojoLevelOne.setPojoLevelTwo(pojoLevelTwo);
        nestedPojo.setPojoLevelOne(pojoLevelOne);

        // run
        PojoLevelTwo response = (PojoLevelTwo) TraverseUtils.getPath(nestedPojo, POJO_LEVEL_ONE, POJO_LEVEL_TWO);

        // verify
        assertTrue(VALUE_ONE.equals(response.getPojoLevelTwo1()));
        assertTrue(VALUE_TWO.equals(response.getPojoLevelTwo2()));
    }
}

You will need these on your classpath:

<dependencies>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.1</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.0.13</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
</dependencies>

This hack traverses POJOs using String steps which are partial or exact name for the name methods to be used to traverse. You can grab the full source here github.com/outcastgeek/beantraversal. Any questions, feedback, or comments?


❯❯ Back to Blog ❮❮ ❮ Previous: Progress Meter with d3js Reactjs and Angularjs Next: manage your static assets with python ❯
blog comments powered by Disqus