diff --git a/README.md b/README.md index 8d63114..643f610 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The `DotPathQL` is the core component of this project that allows you to extract - πŸ“‹ **Collection Handling**: Process Lists, Arrays, and other Collections - πŸ—ΊοΈ **Map Support**: Handle both simple and complex Map structures - πŸ“ **Record & POJO Support**: Works with Java Records and traditional classes -- πŸ”’ **Private Field Access**: Can access private fields when getters aren't available +- πŸ”’ **Private Field Access**: Can access private fields when getters aren't available (filtering only) - πŸš€ **Performance Optimized**: Efficient reflection-based property access ## Quick Start @@ -38,10 +38,8 @@ The `DotPathQL` is the core component of this project that allows you to extract ### Filter Usage ```java -DotPathQL filterUtil = new DotPathQL(); - // Filter specific properties from an object -Map result = filterUtil.filter(userObject, List.of( +Map result = new DotPathQL().filter(userObject, List.of( "username", "address.street", "address.city" @@ -51,10 +49,8 @@ Map result = filterUtil.filter(userObject, List.of( ### Exclude Usage ```java -DotPathQL filterUtil = new DotPathQL(); - // Exclude specific properties and return everything else -Map result = filterUtil.exclude(userObject, List.of( +Map result = new DotPathQL().exclude(userObject, List.of( "password", "ssn", "address.country" @@ -63,7 +59,7 @@ Map result = filterUtil.exclude(userObject, List.of( ## Supported Data Structures -- Simple Properties +- Simple Properties (primitive and object types) - Nested Objects - Collections and Arrays - Map Structures @@ -96,7 +92,7 @@ The utility uses a multi-layered approach to access object properties: 1. **Record Components** (Most Efficient): For Java Records, uses the generated accessor methods 2. **Getter Methods**: Tries standard getter methods (`getName()`, `getAddress()`) -3. **Direct Field Access**: Falls back to direct field access for private fields +3. **Direct Field Access**: Falls back to direct field access for private fields (except for the exclude API) ### Nested Structure Processing @@ -178,6 +174,25 @@ List reportFields = List.of( ); ``` +## Helper Utilities + +You can also easy access the map result using the `DotPathQL` utility methods: + +```java +// Step 1 +var dotPathQL = new DotPathQL(); +var result = dotPathQL.filter(userObject, List.of( + "address", + "friendList", + "games" +)); + +// Step 2: Accessing the result +var address = dotPathQL.mapFrom(result, "address"); +var friendList = dotPathQL.listFrom(result, "friendList"); +var games = dotPathQL.arrayFrom(result, "games"); +``` + ## Technical Requirements - **Java**: 17 or higher diff --git a/pom.xml b/pom.xml index ae424db..f963d35 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.github.trackerforce dot-path-ql - 1.0.1-SNAPSHOT + 1.1.0-SNAPSHOT dot-path-ql dotPathQL allows object attribute filtering diff --git a/src/main/java/io/github/trackerforce/DotPathQL.java b/src/main/java/io/github/trackerforce/DotPathQL.java index e62d47a..3b769c1 100644 --- a/src/main/java/io/github/trackerforce/DotPathQL.java +++ b/src/main/java/io/github/trackerforce/DotPathQL.java @@ -14,8 +14,8 @@ @SuppressWarnings("unchecked") public class DotPathQL { - private final PathFilter pathFilter; - private final PathExclude pathExclude; + private final PathCommon pathFilter; + private final PathCommon pathExclude; /** * Constructs a DotPathQL instance with an empty list of default filter paths. @@ -36,7 +36,7 @@ public DotPathQL() { * @return a map containing the filtered properties */ public Map filter(T source, List filterPaths) { - return pathFilter.filter(source, filterPaths); + return pathFilter.run(source, filterPaths); } /** @@ -51,7 +51,7 @@ public Map filter(T source, List filterPaths) { * @return a map containing all properties except the excluded ones */ public Map exclude(T source, List excludePaths) { - return pathExclude.exclude(source, excludePaths); + return pathExclude.run(source, excludePaths); } /** @@ -108,7 +108,16 @@ public Object[] arrayFrom(Map source, String property) { * @param paths the list of default filter paths to add */ public void addDefaultFilterPaths(List paths) { - pathFilter.addDefaultFilterPaths(paths); + pathFilter.addDefaultPaths(paths); + } + + /** + * Adds default exclude paths that will be included in every exclusion operation. + * + * @param paths the list of default exclude paths to add + */ + public void addDefaultExcludePaths(List paths) { + pathExclude.addDefaultPaths(paths); } private boolean isInvalid(Map source, String property) { diff --git a/src/main/java/io/github/trackerforce/PathCommon.java b/src/main/java/io/github/trackerforce/PathCommon.java index 01fa4fe..2782118 100644 --- a/src/main/java/io/github/trackerforce/PathCommon.java +++ b/src/main/java/io/github/trackerforce/PathCommon.java @@ -4,7 +4,9 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Map; /** * Common functionality for handling paths in the DotPathQL library. @@ -14,28 +16,52 @@ abstract class PathCommon { /** - * Default filter paths that can be used across different implementations. - * This allows for easy configuration of common paths to filter. + * Default paths that can be used across different implementations. */ - protected final List defaultFilterPaths; + protected final List defaultPaths; /** - * Constructor to initialize the PathCommon with an empty list of default filter paths. + * Executes the path processing logic for the given source object. + * + * @param the type of the source object + * @param source the source object to process + * @param filterPaths the list of paths to filter or exclude + * @return a map containing the processed properties + */ + abstract Map execute(T source, List filterPaths); + + /** + * Runs the path processing logic for the given source object with the specified paths. + * + * @param the type of the source object + * @param source the source object to process + * @param excludePaths the list of paths to exclude + * @return a map containing the processed properties + */ + Map run(T source, List excludePaths) { + if (source == null) { + return Collections.emptyMap(); + } + + return execute(source, expandGroupedPaths(excludePaths)); + } + + /** + * Constructor to initialize the PathCommon with an empty list of default paths. * This allows subclasses to add their own default paths as needed. */ protected PathCommon() { - this.defaultFilterPaths = new ArrayList<>(); + this.defaultPaths = new ArrayList<>(); } /** - * Adds default filter paths to the list of paths that can be used - * when filtering objects. This allows for easy configuration of common - * paths that should always be available for filtering. + * Adds default paths to the list of paths that can be used + * when processing objects. * - * @param paths the list of paths to add as default filter paths + * @param paths the list of paths to add as default paths */ - public void addDefaultFilterPaths(List paths) { - defaultFilterPaths.addAll(paths); + public void addDefaultPaths(List paths) { + defaultPaths.addAll(paths); } /** diff --git a/src/main/java/io/github/trackerforce/PathExclude.java b/src/main/java/io/github/trackerforce/PathExclude.java index f8c3c01..438fe9d 100644 --- a/src/main/java/io/github/trackerforce/PathExclude.java +++ b/src/main/java/io/github/trackerforce/PathExclude.java @@ -10,13 +10,11 @@ private enum SkipValue { INSTANCE } - public Map exclude(T source, List excludePaths) { - if (source == null) { - return Collections.emptyMap(); - } - - ExclusionNode root = buildExclusionTree(expandGroupedPaths(excludePaths)); + public Map execute(T source, List excludePaths) { Map result = new LinkedHashMap<>(); + excludePaths.addAll(0, defaultPaths); + + ExclusionNode root = buildExclusionTree(excludePaths); buildExcluding(result, source, "", root); return result; } diff --git a/src/main/java/io/github/trackerforce/PathFilter.java b/src/main/java/io/github/trackerforce/PathFilter.java index e557d3c..7e18313 100644 --- a/src/main/java/io/github/trackerforce/PathFilter.java +++ b/src/main/java/io/github/trackerforce/PathFilter.java @@ -5,16 +5,11 @@ @SuppressWarnings("unchecked") class PathFilter extends PathCommon { - public Map filter(T source, List filterPaths) { - if (source == null) { - return Collections.emptyMap(); - } - + public Map execute(T source, List filterPaths) { Map result = new LinkedHashMap<>(); - List expandedPaths = expandGroupedPaths(filterPaths); - expandedPaths.addAll(0, defaultFilterPaths); + filterPaths.addAll(0, defaultPaths); - for (String path : expandedPaths) { + for (String path : filterPaths) { addPathToResult(result, source, path); } diff --git a/src/test/java/io/github/trackerforce/ExcludeTypeClassRecordTest.java b/src/test/java/io/github/trackerforce/ExcludeTypeClassRecordTest.java index 0ca5f94..acae66b 100644 --- a/src/test/java/io/github/trackerforce/ExcludeTypeClassRecordTest.java +++ b/src/test/java/io/github/trackerforce/ExcludeTypeClassRecordTest.java @@ -106,6 +106,21 @@ void shouldExcludeGroupedPaths(String implementation, Object userDetail) { assertFalse(work.containsKey("city")); } + @ParameterizedTest(name = "{0}") + @MethodSource("userDetailProvider") + void shouldAddDefaultExclusionPaths(String implementation, Object userDetail) { + // When + dotPathQL.addDefaultExcludePaths(List.of("username")); + var result = dotPathQL.exclude(userDetail, List.of("address.city")); + + // Then + assertFalse(result.containsKey("username")); + var address = dotPathQL.mapFrom(result, "address"); + assertNotNull(address); + assertFalse(address.containsKey("city")); + assertTrue(address.containsKey("street")); + } + @Test void shouldReturnEmptyMapWhenSourceIsNull() { // When diff --git a/src/test/java/io/github/trackerforce/FilterTypeClassRecordTest.java b/src/test/java/io/github/trackerforce/FilterTypeClassRecordTest.java index 34b0568..8333c44 100644 --- a/src/test/java/io/github/trackerforce/FilterTypeClassRecordTest.java +++ b/src/test/java/io/github/trackerforce/FilterTypeClassRecordTest.java @@ -126,13 +126,9 @@ void shouldReturnFilteredObjectWithComplexMap(String implementation, Object user @ParameterizedTest(name = "{0}") @MethodSource("userDetailProvider") void shouldAddDefaultFilterPaths(String implementation, Object userDetail) { - // Given - var defaultPaths = List.of("username"); - var filterPaths = List.of("address.city"); - // When - dotPathQL.addDefaultFilterPaths(defaultPaths); - var result = dotPathQL.filter(userDetail, filterPaths); + dotPathQL.addDefaultFilterPaths(List.of("username")); + var result = dotPathQL.filter(userDetail, List.of("address.city")); // Then assertEquals(2, result.size());