Skip to content

Commit

Permalink
Merge pull request #174 from gestalt-config/feat/map-leaf
Browse files Browse the repository at this point in the history
Feat/map leaf
  • Loading branch information
credmond-git authored Mar 23, 2024
2 parents 57c9b99 + 1c7242d commit 3a54db5
Show file tree
Hide file tree
Showing 21 changed files with 916 additions and 1,221 deletions.
18 changes: 9 additions & 9 deletions README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ public class GestaltBuilder {
private Set<String> securityRules = new HashSet<>(
List.of("bearer", "cookie", "credential", "id",
"key", "keystore", "passphrase", "password",
"salt", "secret", "secure", "ssl",
"token", "truststore"));
"private", "salt", "secret", "secure",
"ssl", "token", "truststore"));
private String secretMask = "*****";
private SecretConcealer secretConcealer;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,40 @@
import org.github.gestalt.config.utils.GResultOf;
import org.github.gestalt.config.utils.PathUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Supplier;

import static java.lang.System.Logger.Level.TRACE;

/**
* Decode a list type.
*
* @author <a href="mailto:colin.redmond@outlook.com"> Colin Redmond </a> (c) 2024.
*/
public final class ListDecoder extends CollectionDecoder<List<?>> {
private static final System.Logger logger = System.getLogger(ListDecoder.class.getName());

Map<Class<?>, Supplier<List>> supplierMap = new HashMap<>();

Class<?> sequencedCollection;

public ListDecoder() {
supplierMap.put(List.class, ArrayList::new);
supplierMap.put(AbstractList.class, ArrayList::new);
supplierMap.put(CopyOnWriteArrayList.class, CopyOnWriteArrayList::new);
supplierMap.put(ArrayList.class, ArrayList::new);
supplierMap.put(LinkedList.class, LinkedList::new);
supplierMap.put(Stack.class, Stack::new);
supplierMap.put(Vector.class, Vector::new);
try {
sequencedCollection = Class.forName("java.util.SequencedCollection");
supplierMap.put(sequencedCollection, ArrayList::new);
} catch (ClassNotFoundException e) {
sequencedCollection = null;
logger.log(TRACE, "Unable to find class java.util.SequencedCollection, SequencedCollectionDecoder disabled");
}
}

@Override
public String name() {
Expand All @@ -24,14 +49,23 @@ public String name() {

@Override
public boolean canDecode(String path, Tags tags, ConfigNode node, TypeCapture<?> type) {
return List.class.isAssignableFrom(type.getRawType()) && type.hasParameter();
return List.class.isAssignableFrom(type.getRawType()) && type.hasParameter() ||
(sequencedCollection != null && sequencedCollection.equals(type.getRawType())); // NOPMD
}

@SuppressWarnings({"rawtypes", "unchecked"})
@Override
protected GResultOf<List<?>> arrayDecode(String path, Tags tags, ConfigNode node, TypeCapture<?> klass,
DecoderContext decoderContext) {
List<ValidationError> errors = new ArrayList<>();
List<Object> results = new ArrayList<>(node.size());
Supplier<List> mapSupplier = supplierMap.get(klass.getRawType());

if (mapSupplier == null) {
logger.log(TRACE, "Unable to find supplier for " + klass.getRawType() + ", defaulting to ArrayList");
mapSupplier = supplierMap.get(List.class);
}

List results = mapSupplier.get();

for (int i = 0; i < node.size(); i++) {
var valueOptional = node.getIndex(i);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package org.github.gestalt.config.decoder;

import org.github.gestalt.config.entity.ValidationError;
import org.github.gestalt.config.node.ArrayNode;
import org.github.gestalt.config.node.ConfigNode;
import org.github.gestalt.config.node.LeafNode;
import org.github.gestalt.config.node.MapNode;
import org.github.gestalt.config.node.*;
import org.github.gestalt.config.reflect.TypeCapture;
import org.github.gestalt.config.tag.Tags;
import org.github.gestalt.config.utils.ClassUtils;
Expand All @@ -13,8 +10,11 @@
import org.github.gestalt.config.utils.PathUtil;

import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Stream;

import static java.lang.System.Logger.Level.TRACE;

/**
* Decode a Map. Assumes that the key is a simple class that can be decoded from a single string. ie a Boolean, String, Int.
* The value can be any type we can decode.
Expand All @@ -23,6 +23,27 @@
*/
public final class MapDecoder implements Decoder<Map<?, ?>> {

private static final System.Logger logger = System.getLogger(MapDecoder.class.getName());

Class<?> sequencedMap;

Map<Class<?>, Supplier<Map>> supplierMap = new HashMap<>();

public MapDecoder() {
supplierMap.put(Map.class, HashMap::new);
supplierMap.put(HashMap.class, HashMap::new);
supplierMap.put(TreeMap.class, TreeMap::new);
supplierMap.put(LinkedHashMap.class, LinkedHashMap::new);

try {
sequencedMap = Class.forName("java.util.SequencedMap");
supplierMap.put(sequencedMap, LinkedHashMap::new);
} catch (ClassNotFoundException e) {
sequencedMap = null;
logger.log(TRACE, "Unable to find class java.util.SequencedMap, SequencedMapDecoder disabled");
}
}

@Override
public Priority priority() {
return Priority.MEDIUM;
Expand All @@ -35,14 +56,50 @@ public String name() {

@Override
public boolean canDecode(String path, Tags tags, ConfigNode node, TypeCapture<?> type) {
return Map.class.isAssignableFrom(type.getRawType()) && type.hasParameter();
return Map.class.isAssignableFrom(type.getRawType()) && type.hasParameter() &&
(node.getNodeType() == NodeType.MAP || node.getNodeType() == NodeType.LEAF);
}

@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public GResultOf<Map<?, ?>> decode(String path, Tags tags, ConfigNode node, TypeCapture<?> type, DecoderContext decoderContext) {
GResultOf<Map<?, ?>> results;
if (node instanceof MapNode) {
if (node instanceof LeafNode) {
// if this is a leaf node, try and convert a single string in the format k1=v1,k2=v2 into a map node
// once it has been converted to a map node recursively call this method to decode the new map node
if (node.getValue().isPresent()) {
List<ValidationError> errors = new ArrayList<>();
Map<String, ConfigNode> mapResult = new HashMap<>();

// convert the string in the format k1=v1,k2=v2 to a map
String value = node.getValue().get();
String[] mapKeyValue = value.split("(?<!\\\\),");
for (String entry : mapKeyValue) {
if (entry.isBlank()) {
continue;
}

String[] keyValuePair = entry.split("(?<!\\\\)=", 2);
if (keyValuePair.length != 2) {
errors.add(new ValidationError.MapEntryInvalid(path, entry, node, decoderContext.getSecretConcealer()));
continue;
}

mapResult.put(keyValuePair[0].trim(), new LeafNode(keyValuePair[1].trim()));
}

// if there are no errors try and decode the new map.
// otherwise return the errors.
if (errors.isEmpty()) {
results = decode(path, tags, new MapNode(mapResult), type, decoderContext);
} else {
results = GResultOf.errors(errors);
}
} else {
results = GResultOf.errors(new ValidationError.DecodingLeafMissingValue(path, name()));
}

} else if (node instanceof MapNode) {
MapNode mapNode = (MapNode) node;
List<TypeCapture<?>> genericInterfaces = type.getParameterTypes();

Expand All @@ -52,6 +109,12 @@ public boolean canDecode(String path, Tags tags, ConfigNode node, TypeCapture<?>
TypeCapture<?> keyType = genericInterfaces.get(0);
TypeCapture<?> valueType = genericInterfaces.get(1);

Supplier<Map> mapSupplier = supplierMap.get(type.getRawType());
if (mapSupplier == null) {
logger.log(TRACE, "Unable to find supplier for " + type.getRawType() + ", defaulting to HashMap");
mapSupplier = supplierMap.get(Map.class);
}

List<ValidationError> errors = new ArrayList<>();

var stream = mapNode.getMapNode().entrySet().stream();
Expand Down Expand Up @@ -91,7 +154,7 @@ public boolean canDecode(String path, Tags tags, ConfigNode node, TypeCapture<?>
return null;
})
.filter(Objects::nonNull)
.collect(HashMap::new, (m, v) -> m.put(v.getFirst(), v.getSecond()), HashMap::putAll);
.collect(mapSupplier, (m, v) -> m.put(v.getFirst(), v.getSecond()), Map::putAll);


return GResultOf.resultOf(map, errors);
Expand Down

This file was deleted.

Loading

0 comments on commit 3a54db5

Please sign in to comment.