Skip to content

Commit cf96095

Browse files
committed
Version 0.3.1
* Added multithreaded option for mvn execution in `Makefile`. * Improved tests for `GetByPath` and added flags for logging, exceptions and forced access for fields.
1 parent 62c4e5a commit cf96095

12 files changed

Lines changed: 154 additions & 68 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# v0.3.1
2+
3+
* Added multithreaded option for mvn execution in `Makefile`.
4+
5+
`common`
6+
7+
* Improved tests for `GetByPath` and added flags for logging, exceptions and forced access for fields.
8+
19
# v0.3.0
210

311
`common`

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
PACKAGE_VERSION := $(shell cat ./VERSION)
2+
MVN_OPTIONS ?= -T 1C
23

34
compile: set_version
45
mvn $(MVN_OPTIONS) compile

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.3.0
1+
0.3.1

collections/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<artifactId>java-project</artifactId>
99
<groupId>co.arago</groupId>
10-
<version>0.3.0</version>
10+
<version>0.3.1</version>
1111
</parent>
1212

1313
<groupId>co.arago.util</groupId>

common/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# v0.3.1
2+
3+
* Improved tests for `GetByPath` and added flags for logging, exceptions and forced access for fields.
4+
15
# v0.3.0
26

37
* Added optional exceptions to `GetByPath` and added tests.

common/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<groupId>co.arago</groupId>
99
<artifactId>java-project</artifactId>
10-
<version>0.3.0</version>
10+
<version>0.3.1</version>
1111
</parent>
1212

1313
<groupId>co.arago.util</groupId>

common/src/main/java/co/arago/util/GetByPath.java

Lines changed: 94 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,44 @@ public class GetByPath {
2121
protected final String path;
2222
protected final List<String> splitPath;
2323
protected final boolean throwExceptions;
24+
protected final boolean forceAccess;
25+
protected final boolean logErrors;
26+
27+
public static final class Flags {
28+
private boolean throwExceptions = false;
29+
private boolean forceAccess = false;
30+
private boolean logErrors = false;
31+
32+
public Flags setThrowExceptions(boolean throwExceptions) {
33+
this.throwExceptions = throwExceptions;
34+
return this;
35+
}
36+
37+
public Flags setForceAccess(boolean forceAccess) {
38+
this.forceAccess = forceAccess;
39+
return this;
40+
}
41+
42+
public Flags setLogErrors(boolean logErrors) {
43+
this.logErrors = logErrors;
44+
return this;
45+
}
46+
}
2447

2548
/**
2649
* Protected constructor.
2750
* <p>
28-
* Use {@link #newWith(String)} or {@link #newWith(String, EscapingStringTokenizer, boolean)}.
51+
* Use {@link #newWith(String)} or {@link #newWith(String, EscapingStringTokenizer)}.
2952
*
3053
* @param path The path to use. Example "/data/field".
3154
* @param stringTokenizer The Tokenizer to use. Arbitrary escape and delimiters can be set via this.
32-
* @param throwExceptions Whether to throw exceptions when paths do not match the scanned object.
55+
* @param flags Configuration flags.
3356
*/
34-
protected GetByPath(
35-
String path,
36-
EscapingStringTokenizer stringTokenizer,
37-
boolean throwExceptions) {
57+
protected GetByPath(String path, EscapingStringTokenizer stringTokenizer, Flags flags) {
3858
this.path = path;
39-
this.throwExceptions = throwExceptions;
59+
this.throwExceptions = flags.throwExceptions;
60+
this.forceAccess = flags.forceAccess;
61+
this.logErrors = flags.logErrors;
4062

4163
if (path != null && path.charAt(0) == stringTokenizer.delimiter) {
4264
splitPath = stringTokenizer.build(path);
@@ -50,44 +72,59 @@ protected GetByPath(
5072
*
5173
* @param path The path to use. Example "/data/field".
5274
* @param stringTokenizer The Tokenizer to use. Arbitrary escape and delimiters can be set via this.
53-
* @param throwExceptions Whether to throw exceptions when paths do not match the scanned object.
75+
* @param flags Configuration flags.
5476
* @return New instance of {@link GetByPath}
5577
*/
5678
public static GetByPath newWith(
5779
String path,
5880
EscapingStringTokenizer stringTokenizer,
59-
boolean throwExceptions) {
60-
return new GetByPath(path, stringTokenizer, throwExceptions);
81+
Flags flags) {
82+
return new GetByPath(path, stringTokenizer, flags);
83+
}
84+
85+
/**
86+
* Static constructor
87+
*
88+
* @param path The path to use. Example "/data/field".
89+
* @param stringTokenizer The Tokenizer to use. Arbitrary escape and delimiters can be set via this.
90+
* @return New instance of {@link GetByPath}
91+
*/
92+
public static GetByPath newWith(
93+
String path,
94+
EscapingStringTokenizer stringTokenizer) {
95+
return newWith(
96+
path,
97+
stringTokenizer,
98+
new Flags());
6199
}
62100

63101
/**
64102
* Static constructor. Uses default tokenizer with delimiter '/' and escape char '\'.
65103
*
66-
* @param path The path to use. Example "/data/field".
104+
* @param path The path to use. Example "/data/field".
105+
* @param flags Configuration flags.
67106
* @return New instance of {@link GetByPath}
68107
*/
69-
public static GetByPath newWithExceptions(
70-
String path) {
71-
return newWith(path, EscapingStringTokenizer.newInstance()
72-
.setIncludeEmpty(false)
73-
.setDelimiter('/')
74-
.setEscape('\\'),
75-
true);
108+
public static GetByPath newWith(String path, Flags flags) {
109+
return newWith(
110+
path,
111+
EscapingStringTokenizer.newInstance()
112+
.setIncludeEmpty(false)
113+
.setDelimiter('/')
114+
.setEscape('\\'),
115+
flags);
76116
}
77117

78118
/**
79-
* Static constructor. Uses default tokenizer with delimiter '/' and escape char '\' and
80-
* {@link #throwExceptions} = false.
119+
* Static constructor. Uses default tokenizer with delimiter '/' and escape char '\'.
81120
*
82121
* @param path The path to use. Example "/data/field".
83122
* @return New instance of {@link GetByPath}
84123
*/
85124
public static GetByPath newWith(String path) {
86-
return newWith(path, EscapingStringTokenizer.newInstance()
87-
.setIncludeEmpty(false)
88-
.setDelimiter('/')
89-
.setEscape('\\'),
90-
false);
125+
return newWith(
126+
path,
127+
new Flags());
91128
}
92129

93130
/**
@@ -117,14 +154,14 @@ public Object getByNameArray(List<String> nameArray, Object scannedData) {
117154

118155
String currentName = nameArray.remove(0);
119156

157+
String errorMessage = null;
120158
if (scannedData instanceof Map) {
121159
Map<?, ?> scannedMap = ((Map<?, ?>) scannedData);
122160
Object value = scannedMap.get(currentName);
123161
if (value != null || scannedMap.containsKey(currentName)) {
124162
return getByNameArray(nameArray, value);
125163
}
126164
} else if (scannedData instanceof Collection) {
127-
Collection<?> scannedCollection = ((Collection<?>) scannedData);
128165
Object[] scannedArray = ((Collection<?>) scannedData).toArray();
129166

130167
if (scannedArray.length > 0) {
@@ -133,29 +170,47 @@ public Object getByNameArray(List<String> nameArray, Object scannedData) {
133170
} else {
134171
int pos = Integer.parseInt(currentName);
135172
if (pos > 0 && pos < scannedArray.length) {
136-
return getByNameArray(nameArray, pos);
173+
return getByNameArray(nameArray, scannedArray[pos]);
174+
} else {
175+
errorMessage = String.format("Index '%s' of path '%s' is out of bounds for '%s' of length %d.",
176+
currentName,
177+
path, scannedData.getClass().getName(), scannedArray.length);
137178
}
138179
}
180+
} else {
181+
errorMessage = String.format("Cannot access '%s' of path '%s' for '%s' because it is empty.", currentName,
182+
path, scannedData.getClass().getName());
139183
}
140-
} else if (scannedData != null) {
184+
} else if (!(scannedData instanceof String) &&
185+
!(scannedData instanceof Number) &&
186+
!(scannedData instanceof Boolean)) {
141187
Field field = Reflections.findFieldByName(scannedData.getClass(), currentName);
142188
if (field != null) {
143189
try {
144-
// field.setAccessible(true);
190+
if (forceAccess)
191+
field.setAccessible(true);
145192
return getByNameArray(nameArray, field.get(scannedData));
146193
} catch (IllegalAccessError | IllegalAccessException e) {
147-
log.error("Field '{}' of '{}' is not accessible. {}", currentName, scannedData.getClass().getName(),
148-
e.getMessage());
194+
errorMessage = String.format("Field '%s' of '%s' is not accessible. %s", currentName,
195+
scannedData.getClass().getName(), e.getMessage());
149196
}
150197
}
198+
} else {
199+
errorMessage = String.format("Cannot access '%s' of path '%s': Path too deep, no more data to scan.",
200+
currentName, path);
151201
}
152202

153-
if (this.throwExceptions) {
154-
throw new IllegalArgumentException(
155-
String.format("Cannot find '%s' of '%s' in '%s'.",
156-
currentName,
157-
path,
158-
scannedData != null ? scannedData.getClass().getName() : "null"));
203+
if (throwExceptions) {
204+
if (StringUtils.isBlank(errorMessage))
205+
errorMessage = String.format("Cannot find '%s' of path '%s' in '%s'.",
206+
currentName,
207+
path,
208+
scannedData.getClass().getName());
209+
if (logErrors)
210+
log.error(errorMessage);
211+
throw new IllegalArgumentException(errorMessage);
212+
} else if (StringUtils.isNotBlank(errorMessage) && logErrors) {
213+
log.warn("Returning null because of: " + errorMessage);
159214
}
160215

161216
return null;
@@ -168,7 +223,7 @@ public Object getByNameArray(List<String> nameArray, Object scannedData) {
168223
* i.e '\\/' for a literal '/' as part of a name inside the path.
169224
* <p>
170225
* Delimiter and escape characters can be changed by supplying your own {@link EscapingStringTokenizer} in the
171-
* Constructor {@link GetByPath#GetByPath(String, EscapingStringTokenizer, boolean)} of this class.
226+
* Constructor {@link GetByPath#GetByPath(String, EscapingStringTokenizer, Flags)} of this class.
172227
*
173228
* @param data The data that is searched with the {@link #path}.
174229
* @return The value pointed at by path or null when nothing can be found.
@@ -179,13 +234,6 @@ public Object get(Object data) {
179234
if (StringUtils.equals(path, "/"))
180235
return data;
181236

182-
try {
183-
return getByNameArray(splitPath, data);
184-
} catch (Exception e) {
185-
if (this.throwExceptions)
186-
throw e;
187-
log.error("Error while applying path '{}' to '{}': {}", path, data.getClass().getName(), e);
188-
return null;
189-
}
237+
return getByNameArray(splitPath, data);
190238
}
191239
}

common/src/test/java/co/arago/util/GetByPathTest.java

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.junit.jupiter.api.Test;
66

77
import java.util.HashMap;
8+
import java.util.List;
89
import java.util.Map;
910

1011
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -18,55 +19,79 @@ private static class ClassData {
1819
}
1920

2021
private final Map<String, Object> data = new HashMap<>();
22+
private final GetByPath.Flags logFlags = new GetByPath.Flags().setLogErrors(true);
23+
private final GetByPath.Flags exFlags = new GetByPath.Flags().setThrowExceptions(true).setLogErrors(true);
2124

2225
@BeforeEach
2326
void setUp() {
2427
data.putAll(Map.of("key1", "value1",
25-
"key2", Map.of("key21", "value21", "key22", "value22", "class", new ClassData())));
28+
"key2", Map.of("key21", "value21", "key22", "value22", "class", new ClassData()),
29+
"list", List.of("A", "B", "C", "D"),
30+
"list2", List.of()));
2631
data.put("key3", null);
2732
}
2833

2934
@Test
3035
void getWithoutExceptionGood() {
31-
Object result = GetByPath.newWith("/key2/key22").get(data);
36+
Object result = GetByPath.newWith("/key2/key22", logFlags).get(data);
3237
assertEquals(result, "value22");
3338

34-
result = GetByPath.newWith("/key2/class/classKey").get(data);
39+
result = GetByPath.newWith("/key2/class/classKey", logFlags).get(data);
3540
assertEquals(result, "ClassKeyValue");
41+
42+
result = GetByPath.newWith("/list/:last", logFlags).get(data);
43+
assertEquals(result, "D");
44+
45+
result = GetByPath.newWith("/list/1", logFlags).get(data);
46+
assertEquals(result, "B");
3647
}
3748

3849
@Test
3950
void getWithoutExceptionFail() {
40-
Object result = GetByPath.newWith("/key2/key22/some").get(data);
51+
Object result = GetByPath.newWith("/key2/key22/some", logFlags).get(data);
52+
assertNull(result);
53+
54+
result = GetByPath.newWith("/key3", logFlags).get(data);
55+
assertNull(result);
56+
57+
result = GetByPath.newWith("/key4", logFlags).get(data);
4158
assertNull(result);
4259

43-
result = GetByPath.newWith("/key3").get(data);
60+
result = GetByPath.newWith("/key2/class/hiddenClassKey", logFlags).get(data);
4461
assertNull(result);
4562

46-
result = GetByPath.newWith("/key4").get(data);
63+
result = GetByPath.newWith("/key2/class/wrongClassKey", logFlags).get(data);
4764
assertNull(result);
4865

49-
result = GetByPath.newWith("/key2/class/hiddenClassKey").get(data);
66+
result = GetByPath.newWith("/list/4", logFlags).get(data);
5067
assertNull(result);
5168

52-
result = GetByPath.newWith("/key2/class/wrongClassKey").get(data);
69+
result = GetByPath.newWith("/list2/:last", logFlags).get(data);
5370
assertNull(result);
5471
}
5572

5673
@Test
5774
void getWithException() {
5875
Assertions.assertThrows(IllegalArgumentException.class,
59-
() -> GetByPath.newWithExceptions("/key2/key22/some").get(data));
76+
() -> GetByPath.newWith("/key2/key22/some", exFlags).get(data));
6077

61-
Assertions.assertThrows(IllegalArgumentException.class, () -> GetByPath.newWithExceptions("/key4").get(data));
78+
Assertions.assertThrows(IllegalArgumentException.class,
79+
() -> GetByPath.newWith("/key4", exFlags).get(data));
80+
81+
Assertions.assertThrows(IllegalArgumentException.class,
82+
() -> GetByPath.newWith("/key2/class/hiddenClassKey", exFlags).get(data));
6283

6384
Assertions.assertThrows(IllegalArgumentException.class,
64-
() -> GetByPath.newWithExceptions("/key2/class/hiddenClassKey").get(data));
85+
() -> GetByPath.newWith("/key2/class/wrongClassKey", exFlags).get(data));
6586

6687
Assertions.assertThrows(IllegalArgumentException.class,
67-
() -> GetByPath.newWithExceptions("/key2/class/wrongClassKey").get(data));
88+
() -> GetByPath.newWith("/list/4", exFlags).get(data));
6889

69-
Object result = GetByPath.newWithExceptions("/key3").get(data);
90+
Assertions.assertThrows(IllegalArgumentException.class,
91+
() -> GetByPath.newWith("/list2/:last", exFlags).get(data));
92+
93+
Object result = GetByPath.newWith("/key3", exFlags).get(data);
7094
assertNull(result);
95+
7196
}
7297
}

json-schema/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<groupId>co.arago</groupId>
99
<artifactId>java-project</artifactId>
10-
<version>0.3.0</version>
10+
<version>0.3.1</version>
1111
</parent>
1212

1313
<groupId>co.arago.util</groupId>
@@ -21,7 +21,7 @@
2121
<dependency>
2222
<groupId>co.arago.util</groupId>
2323
<artifactId>json</artifactId>
24-
<version>0.3.0</version>
24+
<version>0.3.1</version>
2525
</dependency>
2626
<dependency>
2727
<groupId>com.kjetland</groupId>

0 commit comments

Comments
 (0)