Skip to content

Commit

Permalink
Version 0.3.1
Browse files Browse the repository at this point in the history
* Added multithreaded option for mvn execution in `Makefile`.
* Improved tests for `GetByPath` and added flags for logging, exceptions and forced access for fields.
  • Loading branch information
tryptichon committed Mar 29, 2022
1 parent 62c4e5a commit cf96095
Show file tree
Hide file tree
Showing 12 changed files with 154 additions and 68 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# v0.3.1

* Added multithreaded option for mvn execution in `Makefile`.

`common`

* Improved tests for `GetByPath` and added flags for logging, exceptions and forced access for fields.

# v0.3.0

`common`
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
PACKAGE_VERSION := $(shell cat ./VERSION)
MVN_OPTIONS ?= -T 1C

compile: set_version
mvn $(MVN_OPTIONS) compile
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.3.0
0.3.1
2 changes: 1 addition & 1 deletion collections/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<artifactId>java-project</artifactId>
<groupId>co.arago</groupId>
<version>0.3.0</version>
<version>0.3.1</version>
</parent>

<groupId>co.arago.util</groupId>
Expand Down
4 changes: 4 additions & 0 deletions common/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# v0.3.1

* Improved tests for `GetByPath` and added flags for logging, exceptions and forced access for fields.

# v0.3.0

* Added optional exceptions to `GetByPath` and added tests.
Expand Down
2 changes: 1 addition & 1 deletion common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>co.arago</groupId>
<artifactId>java-project</artifactId>
<version>0.3.0</version>
<version>0.3.1</version>
</parent>

<groupId>co.arago.util</groupId>
Expand Down
140 changes: 94 additions & 46 deletions common/src/main/java/co/arago/util/GetByPath.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,44 @@ public class GetByPath {
protected final String path;
protected final List<String> splitPath;
protected final boolean throwExceptions;
protected final boolean forceAccess;
protected final boolean logErrors;

public static final class Flags {
private boolean throwExceptions = false;
private boolean forceAccess = false;
private boolean logErrors = false;

public Flags setThrowExceptions(boolean throwExceptions) {
this.throwExceptions = throwExceptions;
return this;
}

public Flags setForceAccess(boolean forceAccess) {
this.forceAccess = forceAccess;
return this;
}

public Flags setLogErrors(boolean logErrors) {
this.logErrors = logErrors;
return this;
}
}

/**
* Protected constructor.
* <p>
* Use {@link #newWith(String)} or {@link #newWith(String, EscapingStringTokenizer, boolean)}.
* Use {@link #newWith(String)} or {@link #newWith(String, EscapingStringTokenizer)}.
*
* @param path The path to use. Example "/data/field".
* @param stringTokenizer The Tokenizer to use. Arbitrary escape and delimiters can be set via this.
* @param throwExceptions Whether to throw exceptions when paths do not match the scanned object.
* @param flags Configuration flags.
*/
protected GetByPath(
String path,
EscapingStringTokenizer stringTokenizer,
boolean throwExceptions) {
protected GetByPath(String path, EscapingStringTokenizer stringTokenizer, Flags flags) {
this.path = path;
this.throwExceptions = throwExceptions;
this.throwExceptions = flags.throwExceptions;
this.forceAccess = flags.forceAccess;
this.logErrors = flags.logErrors;

if (path != null && path.charAt(0) == stringTokenizer.delimiter) {
splitPath = stringTokenizer.build(path);
Expand All @@ -50,44 +72,59 @@ protected GetByPath(
*
* @param path The path to use. Example "/data/field".
* @param stringTokenizer The Tokenizer to use. Arbitrary escape and delimiters can be set via this.
* @param throwExceptions Whether to throw exceptions when paths do not match the scanned object.
* @param flags Configuration flags.
* @return New instance of {@link GetByPath}
*/
public static GetByPath newWith(
String path,
EscapingStringTokenizer stringTokenizer,
boolean throwExceptions) {
return new GetByPath(path, stringTokenizer, throwExceptions);
Flags flags) {
return new GetByPath(path, stringTokenizer, flags);
}

/**
* Static constructor
*
* @param path The path to use. Example "/data/field".
* @param stringTokenizer The Tokenizer to use. Arbitrary escape and delimiters can be set via this.
* @return New instance of {@link GetByPath}
*/
public static GetByPath newWith(
String path,
EscapingStringTokenizer stringTokenizer) {
return newWith(
path,
stringTokenizer,
new Flags());
}

/**
* Static constructor. Uses default tokenizer with delimiter '/' and escape char '\'.
*
* @param path The path to use. Example "/data/field".
* @param path The path to use. Example "/data/field".
* @param flags Configuration flags.
* @return New instance of {@link GetByPath}
*/
public static GetByPath newWithExceptions(
String path) {
return newWith(path, EscapingStringTokenizer.newInstance()
.setIncludeEmpty(false)
.setDelimiter('/')
.setEscape('\\'),
true);
public static GetByPath newWith(String path, Flags flags) {
return newWith(
path,
EscapingStringTokenizer.newInstance()
.setIncludeEmpty(false)
.setDelimiter('/')
.setEscape('\\'),
flags);
}

/**
* Static constructor. Uses default tokenizer with delimiter '/' and escape char '\' and
* {@link #throwExceptions} = false.
* Static constructor. Uses default tokenizer with delimiter '/' and escape char '\'.
*
* @param path The path to use. Example "/data/field".
* @return New instance of {@link GetByPath}
*/
public static GetByPath newWith(String path) {
return newWith(path, EscapingStringTokenizer.newInstance()
.setIncludeEmpty(false)
.setDelimiter('/')
.setEscape('\\'),
false);
return newWith(
path,
new Flags());
}

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

String currentName = nameArray.remove(0);

String errorMessage = null;
if (scannedData instanceof Map) {
Map<?, ?> scannedMap = ((Map<?, ?>) scannedData);
Object value = scannedMap.get(currentName);
if (value != null || scannedMap.containsKey(currentName)) {
return getByNameArray(nameArray, value);
}
} else if (scannedData instanceof Collection) {
Collection<?> scannedCollection = ((Collection<?>) scannedData);
Object[] scannedArray = ((Collection<?>) scannedData).toArray();

if (scannedArray.length > 0) {
Expand All @@ -133,29 +170,47 @@ public Object getByNameArray(List<String> nameArray, Object scannedData) {
} else {
int pos = Integer.parseInt(currentName);
if (pos > 0 && pos < scannedArray.length) {
return getByNameArray(nameArray, pos);
return getByNameArray(nameArray, scannedArray[pos]);
} else {
errorMessage = String.format("Index '%s' of path '%s' is out of bounds for '%s' of length %d.",
currentName,
path, scannedData.getClass().getName(), scannedArray.length);
}
}
} else {
errorMessage = String.format("Cannot access '%s' of path '%s' for '%s' because it is empty.", currentName,
path, scannedData.getClass().getName());
}
} else if (scannedData != null) {
} else if (!(scannedData instanceof String) &&
!(scannedData instanceof Number) &&
!(scannedData instanceof Boolean)) {
Field field = Reflections.findFieldByName(scannedData.getClass(), currentName);
if (field != null) {
try {
// field.setAccessible(true);
if (forceAccess)
field.setAccessible(true);
return getByNameArray(nameArray, field.get(scannedData));
} catch (IllegalAccessError | IllegalAccessException e) {
log.error("Field '{}' of '{}' is not accessible. {}", currentName, scannedData.getClass().getName(),
e.getMessage());
errorMessage = String.format("Field '%s' of '%s' is not accessible. %s", currentName,
scannedData.getClass().getName(), e.getMessage());
}
}
} else {
errorMessage = String.format("Cannot access '%s' of path '%s': Path too deep, no more data to scan.",
currentName, path);
}

if (this.throwExceptions) {
throw new IllegalArgumentException(
String.format("Cannot find '%s' of '%s' in '%s'.",
currentName,
path,
scannedData != null ? scannedData.getClass().getName() : "null"));
if (throwExceptions) {
if (StringUtils.isBlank(errorMessage))
errorMessage = String.format("Cannot find '%s' of path '%s' in '%s'.",
currentName,
path,
scannedData.getClass().getName());
if (logErrors)
log.error(errorMessage);
throw new IllegalArgumentException(errorMessage);
} else if (StringUtils.isNotBlank(errorMessage) && logErrors) {
log.warn("Returning null because of: " + errorMessage);
}

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

try {
return getByNameArray(splitPath, data);
} catch (Exception e) {
if (this.throwExceptions)
throw e;
log.error("Error while applying path '{}' to '{}': {}", path, data.getClass().getName(), e);
return null;
}
return getByNameArray(splitPath, data);
}
}
51 changes: 38 additions & 13 deletions common/src/test/java/co/arago/util/GetByPathTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

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

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

@BeforeEach
void setUp() {
data.putAll(Map.of("key1", "value1",
"key2", Map.of("key21", "value21", "key22", "value22", "class", new ClassData())));
"key2", Map.of("key21", "value21", "key22", "value22", "class", new ClassData()),
"list", List.of("A", "B", "C", "D"),
"list2", List.of()));
data.put("key3", null);
}

@Test
void getWithoutExceptionGood() {
Object result = GetByPath.newWith("/key2/key22").get(data);
Object result = GetByPath.newWith("/key2/key22", logFlags).get(data);
assertEquals(result, "value22");

result = GetByPath.newWith("/key2/class/classKey").get(data);
result = GetByPath.newWith("/key2/class/classKey", logFlags).get(data);
assertEquals(result, "ClassKeyValue");

result = GetByPath.newWith("/list/:last", logFlags).get(data);
assertEquals(result, "D");

result = GetByPath.newWith("/list/1", logFlags).get(data);
assertEquals(result, "B");
}

@Test
void getWithoutExceptionFail() {
Object result = GetByPath.newWith("/key2/key22/some").get(data);
Object result = GetByPath.newWith("/key2/key22/some", logFlags).get(data);
assertNull(result);

result = GetByPath.newWith("/key3", logFlags).get(data);
assertNull(result);

result = GetByPath.newWith("/key4", logFlags).get(data);
assertNull(result);

result = GetByPath.newWith("/key3").get(data);
result = GetByPath.newWith("/key2/class/hiddenClassKey", logFlags).get(data);
assertNull(result);

result = GetByPath.newWith("/key4").get(data);
result = GetByPath.newWith("/key2/class/wrongClassKey", logFlags).get(data);
assertNull(result);

result = GetByPath.newWith("/key2/class/hiddenClassKey").get(data);
result = GetByPath.newWith("/list/4", logFlags).get(data);
assertNull(result);

result = GetByPath.newWith("/key2/class/wrongClassKey").get(data);
result = GetByPath.newWith("/list2/:last", logFlags).get(data);
assertNull(result);
}

@Test
void getWithException() {
Assertions.assertThrows(IllegalArgumentException.class,
() -> GetByPath.newWithExceptions("/key2/key22/some").get(data));
() -> GetByPath.newWith("/key2/key22/some", exFlags).get(data));

Assertions.assertThrows(IllegalArgumentException.class, () -> GetByPath.newWithExceptions("/key4").get(data));
Assertions.assertThrows(IllegalArgumentException.class,
() -> GetByPath.newWith("/key4", exFlags).get(data));

Assertions.assertThrows(IllegalArgumentException.class,
() -> GetByPath.newWith("/key2/class/hiddenClassKey", exFlags).get(data));

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

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

Object result = GetByPath.newWithExceptions("/key3").get(data);
Assertions.assertThrows(IllegalArgumentException.class,
() -> GetByPath.newWith("/list2/:last", exFlags).get(data));

Object result = GetByPath.newWith("/key3", exFlags).get(data);
assertNull(result);

}
}
4 changes: 2 additions & 2 deletions json-schema/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>co.arago</groupId>
<artifactId>java-project</artifactId>
<version>0.3.0</version>
<version>0.3.1</version>
</parent>

<groupId>co.arago.util</groupId>
Expand All @@ -21,7 +21,7 @@
<dependency>
<groupId>co.arago.util</groupId>
<artifactId>json</artifactId>
<version>0.3.0</version>
<version>0.3.1</version>
</dependency>
<dependency>
<groupId>com.kjetland</groupId>
Expand Down
Loading

0 comments on commit cf96095

Please sign in to comment.