Skip to content

Commit

Permalink
Add disableFromNode to NodeMapper
Browse files Browse the repository at this point in the history
  • Loading branch information
Ethan Trepka authored and srchase committed Jul 21, 2020
1 parent 0e65fc2 commit c8ccb5a
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,14 @@
final class DefaultNodeDeserializers {

// Deserialize an exact type if it matches (i.e., the setter expects a Node value).
private static final ObjectCreatorFactory EXACT_CREATOR_FACTORY = (nodeType, targetType) -> {
private static final ObjectCreatorFactory EXACT_CREATOR_FACTORY = (nodeType, targetType, nodeMapper) -> {
return Node.class.isAssignableFrom(targetType) && targetType.isAssignableFrom(nodeType.getNodeClass())
? (node, target, param, pointer, mapper) -> node
: null;
};

// Creates booleans from BooleanNodes.
private static final ObjectCreatorFactory BOOLEAN_CREATOR_FACTORY = (nodeType, targetType) -> {
private static final ObjectCreatorFactory BOOLEAN_CREATOR_FACTORY = (nodeType, targetType, nodeMapper) -> {
if (nodeType == NodeType.BOOLEAN) {
if (targetType == Boolean.class || targetType == boolean.class || targetType == Object.class) {
return (node, target, param, pointer, mapper) -> node.expectBooleanNode().getValue();
Expand All @@ -76,15 +76,15 @@ final class DefaultNodeDeserializers {
};

// Null nodes always return null values.
private static final ObjectCreatorFactory NULL_CREATOR = (nodeType, targetType) -> {
private static final ObjectCreatorFactory NULL_CREATOR = (nodeType, targetType, nodeMapper) -> {
if (nodeType == NodeType.NULL) {
return (node, target, param, pointer, mapper) -> null;
}
return null;
};

// String nodes can create java.land.String or a Smithy ShapeId.
private static final ObjectCreatorFactory STRING_CREATOR = (nodeType, into) -> {
private static final ObjectCreatorFactory STRING_CREATOR = (nodeType, into, nodeMapper) -> {
if (nodeType == NodeType.STRING) {
if (into == String.class || into == Object.class) {
return (node, target, param, pointer, mapper) -> node.expectStringNode().getValue();
Expand Down Expand Up @@ -116,7 +116,7 @@ final class DefaultNodeDeserializers {
}

// Creates numbers from NumberNodes.
private static final ObjectCreatorFactory NUMBER_CREATOR = (nodeType, targetType) -> {
private static final ObjectCreatorFactory NUMBER_CREATOR = (nodeType, targetType, nodeMapper) -> {
if (nodeType == NodeType.NUMBER) {
if (NUMBER_MAPPERS.containsKey(targetType)) {
return (node, target, param, pointer, mapper) -> {
Expand All @@ -135,7 +135,7 @@ private interface ReflectiveSupplier<T> {
// Deserialize an ArrayNode into a Collection.
private static final ObjectCreatorFactory COLLECTION_CREATOR = new ObjectCreatorFactory() {
@Override
public NodeMapper.ObjectCreator getCreator(NodeType nodeType, Class<?> target) {
public NodeMapper.ObjectCreator getCreator(NodeType nodeType, Class<?> target, NodeMapper nodeMapper) {
if (nodeType != NodeType.ARRAY) {
return null;
}
Expand Down Expand Up @@ -209,7 +209,7 @@ private static String getCauseMessage(Throwable e) {
// Deserialize an ObjectNode into a Map.
private static final ObjectCreatorFactory MAP_CREATOR = new ObjectCreatorFactory() {
@Override
public NodeMapper.ObjectCreator getCreator(NodeType nodeType, Class<?> target) {
public NodeMapper.ObjectCreator getCreator(NodeType nodeType, Class<?> target, NodeMapper nodeMapper) {
if (nodeType != NodeType.OBJECT) {
return null;
}
Expand Down Expand Up @@ -269,21 +269,24 @@ private ReflectiveSupplier<Map<String, Object>> createSupplierFromReflection(Cla
};

// Creates an object from any type of Node using the #fromNode factory method.
private static final ObjectCreatorFactory FROM_NODE_CREATOR = (nodeType, target) -> {
for (Method method : target.getMethods()) {
if ((method.getName().equals("fromNode"))
&& target.isAssignableFrom(method.getReturnType())
&& method.getParameters().length == 1
&& Node.class.isAssignableFrom(method.getParameters()[0].getType())
&& Modifier.isStatic(method.getModifiers())) {
return (node, targetType, paramType, pointer, mapper) -> {
try {
return method.invoke(null, node);
} catch (ReflectiveOperationException e) {
String message = "Unable to deserialize Node using fromNode method: " + getCauseMessage(e);
throw NodeDeserializationException.fromReflectiveContext(targetType, pointer, node, e, message);
}
};
private static final ObjectCreatorFactory FROM_NODE_CREATOR = (nodeType, target, nodeMapper) -> {
if (!nodeMapper.getDisableFromNode().contains(target)) {
for (Method method : target.getMethods()) {
if ((method.getName().equals("fromNode"))
&& target.isAssignableFrom(method.getReturnType())
&& method.getParameters().length == 1
&& Node.class.isAssignableFrom(method.getParameters()[0].getType())
&& Modifier.isStatic(method.getModifiers())) {
return (node, targetType, paramType, pointer, mapper) -> {
try {
return method.invoke(null, node);
} catch (ReflectiveOperationException e) {
String message = "Unable to deserialize Node using fromNode method: " + getCauseMessage(e);
throw NodeDeserializationException
.fromReflectiveContext(targetType, pointer, node, e, message);
}
};
}
}
}
return null;
Expand Down Expand Up @@ -439,7 +442,7 @@ private static Class<?> resolveClassFromType(Type type) {

// Creates an object from any type of Node using the #builder factory method.
@SuppressWarnings("unchecked")
private static final ObjectCreatorFactory FROM_BUILDER_CREATOR = (nodeType, target) -> {
private static final ObjectCreatorFactory FROM_BUILDER_CREATOR = (nodeType, target, nodeMapper) -> {
if (nodeType != NodeType.OBJECT) {
return null;
}
Expand Down Expand Up @@ -475,7 +478,7 @@ private static void applySourceLocation(Object object, FromSourceLocation source
}

// Attempts to create a Bean style POJO using a zero-value constructor.
private static final ObjectCreatorFactory BEAN_CREATOR = (nodeType, target) -> {
private static final ObjectCreatorFactory BEAN_CREATOR = (nodeType, target, nodeMapper) -> {
if (nodeType != NodeType.OBJECT) {
return null;
}
Expand Down Expand Up @@ -511,7 +514,7 @@ private static void applySourceLocation(Object object, FromSourceLocation source
// calling toString on the variant matches the given string.
// Mimic's Jackson's behavior when using READ_ENUMS_USING_TO_STRING
// See https://github.com/FasterXML/jackson-databind/wiki/Deserialization-Features
private static final ObjectCreatorFactory ENUM_CREATOR = (nodeType, target) -> {
private static final ObjectCreatorFactory ENUM_CREATOR = (nodeType, target, nodeMapper) -> {
if (nodeType != NodeType.STRING || !Enum.class.isAssignableFrom(target)) {
return null;
}
Expand Down Expand Up @@ -550,7 +553,7 @@ private interface FromStringClassFactory {
File.class, File::new
);

private static final ObjectCreatorFactory FROM_STRING = (nodeType, target) -> {
private static final ObjectCreatorFactory FROM_STRING = (nodeType, target, nodeMapper) -> {
if (nodeType != NodeType.STRING || !FROM_STRING_CLASSES.containsKey(target)) {
return null;
}
Expand Down Expand Up @@ -589,9 +592,9 @@ private interface FromStringClassFactory {
BEAN_CREATOR
);

static final ObjectCreatorFactory DEFAULT_CHAIN = (nodeType, target) -> {
static final ObjectCreatorFactory DEFAULT_CHAIN = (nodeType, target, nodeMapper) -> {
for (ObjectCreatorFactory factory : DEFAULT_FACTORIES) {
NodeMapper.ObjectCreator result = factory.getCreator(nodeType, target);
NodeMapper.ObjectCreator result = factory.getCreator(nodeType, target, nodeMapper);
if (result != null) {
return result;
}
Expand All @@ -602,9 +605,9 @@ private interface FromStringClassFactory {
// Creates an ObjectCreatorFactory that caches the result of finding ObjectCreators.
private static ObjectCreatorFactory cachedCreator(ObjectCreatorFactory delegate) {
ConcurrentMap<Pair<NodeType, Class>, NodeMapper.ObjectCreator> cache = new ConcurrentHashMap<>();
return (nodeType, target) -> {
return (nodeType, target, nodeMapper) -> {
return cache.computeIfAbsent(Pair.of(nodeType, target), pair -> {
return delegate.getCreator(pair.left, pair.right);
return delegate.getCreator(pair.left, pair.right, nodeMapper);
});
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,15 +164,17 @@ interface ObjectCreatorFactory {
*
* @param nodeType Node type being converted.
* @param target The class to create from the Node.
* @param nodeMapper The NodeMapper being used to call the ObjectCreator.
* @return Returns the {@code ObjectCreator} or {@code null} if the factory cannot handle the given arguments.
* @throws NodeDeserializationException when unable to create a factory.
*/
ObjectCreator getCreator(NodeType nodeType, Class<?> target);
ObjectCreator getCreator(NodeType nodeType, Class<?> target, NodeMapper nodeMapper);
}

private static final Logger LOGGER = Logger.getLogger(NodeMapper.class.getName());
private WhenMissing whenMissing = WhenMissing.WARN;
private final Set<Class> disableToNode = new HashSet<>();
private final Set<Class> disableFromNode = new HashSet<>();
private boolean serializeNullValues = false;
private boolean omitEmptyValues;

Expand Down Expand Up @@ -246,6 +248,41 @@ public Set<Class> getDisableToNode() {
return disableToNode;
}

/**
* Disables the use of {@code fromNode} method for a specific class
* when deserializing the class.
*
* <p>This method disables a specific concrete class and does not
* disable subclasses or implementations of an interface.
*
* <p>This is useful when using the NodeMapper inside of a {@code fromNode}
* implementation.
*
* @param type Class to disable the {@code fromNode} method deserialization for.
*/
public void disableFromNodeForClass(Class type) {
disableFromNode.add(type);
}

/**
* Enables the use of the {@code FromNode} method for a specific class
* when deserializing the class.
*
* @param type Class to enable the {@code fromNode} method deserialization for.
*/
public void enableFromNodeForClass(Class type) {
disableFromNode.remove(type);
}

/**
* Gets the set of classes where {@code fromNode} is disabled.
*
* @return Returns the disabled classes.
*/
public Set<Class> getDisableFromNode() {
return disableFromNode;
}

/**
* Gets whether or not empty arrays and empty objects are omitted from
* serialized POJOs.
Expand Down Expand Up @@ -496,7 +533,7 @@ <T> T deserializeNext(
Objects.requireNonNull(mapper, "Deserialization mapper cannot be null");

try {
ObjectCreator creator = creatorFactory.getCreator(value.getType(), into);
ObjectCreator creator = creatorFactory.getCreator(value.getType(), into, this);
if (creator == null) {
throw createError(into, pointer, value, null, null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,31 @@ public String getFoo() {
}
}

@Test
public void canDisableFromNodeInsideOfClass() {
ObjectNode objectNode = ObjectNode.objectNodeBuilder().withMember("foo", "baz").build();
NodeMapper mapper = new NodeMapper();
mapper.disableFromNodeForClass(DisabledFromNode.class);
DisabledFromNode result = mapper.deserialize(objectNode, DisabledFromNode.class);
assertThat(result.getFoo(), equalTo("baz"));
}

public static final class DisabledFromNode {
private String foo;

public static DisabledFromNode fromNode() {
throw new RuntimeException("Was not meant to run!");
}

public String getFoo() {
return foo;
}

public void setFoo(String foo) {
this.foo = foo;
}
}

@Test
public void deserializesWithFromNodeFactoryAndUnknownPropertiesWithWarning() {
Node baz = Node.parse("{\"foo\": \"hi\", \"baz\": 10, \"inner\": {\"inner\": {\"noSetter!\": \"inn!\"}}}");
Expand Down

0 comments on commit c8ccb5a

Please sign in to comment.