If you work in the enterprise, I am sure you have encountered structures like this:
NestedPojo.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<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?