de.skuzzle.test.snapshots.normalize.ObjectTraversal Maven / Gradle / Ivy
Show all versions of snapshot-tests-normalize Show documentation
package de.skuzzle.test.snapshots.normalize;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Spliterator;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* Allows to recursively iterate all members of an actual object. Each member is wrapped
* into a {@link ObjectMember} instance from which it can be read/modified. How members
* are discovered is defined by the {@link ObjectMembers} strategy interface.
*
* Regardless of the used strategy, certain types are always handled specially when
* discovered while iterating an object's members:
*
* - We never recurse into types of the packages
java.*
or
* javax.*
* - As such, we never recurse into primitive wrappers
* - When discovering a {@link Collection} type, an {@link Iterable} type or an array
* type, we do not recurse into the actual type but iterate the contained elements
* instead
*
*
* The actual order of traversal is undefined but deterministic.
*
* Traversal gracefully handles cycles within the object graph. Every actual encountered
* object instance is only visited once, regardless of how often it is referenced within
* the object graph.
*
* @author Simon Taddiken
* @see ObjectMember
* @see ObjectMembers
*/
public final class ObjectTraversal {
public static void applyActions(Object root, ObjectMembers strategy, ObjectMemberAction... actions) {
applyActions(root, strategy, Arrays.asList(actions));
}
public static void applyActions(Object root, ObjectMembers strategy, Collection actions) {
Stream members = members(root, strategy);
for (final ObjectMemberAction action : actions) {
members = action.applyTo(members);
}
members.forEach(action -> {
});
}
/**
* Creates a lazily populated Stream of {@link ObjectMember object members} that are
* recursively reachable from the given root object.
*
* @param root Root object from which members shall be discovered - may be null.
* @param strategy Strategy for discovering members. Can be obtained from static
* factories in {@link ObjectMembers} itself.
* @return Stream of members.
*/
public static Stream members(Object root, ObjectMembers strategy) {
Objects.requireNonNull(strategy, "strategy must not be null");
final var context = new VisitorContext();
return membersOfRecursive(root, null, context, strategy)
.filter(member -> !context.isTerminal(member.parent())
|| member instanceof TerminalTypeInCollection)
.sorted(Comparator.comparing(ObjectMember::name));
}
private static Stream membersOfRecursive(Object root, Object collectionParent, VisitorContext context,
ObjectMembers strategy) {
return Stream.of(root)
.flatMap(start -> membersOf(start, collectionParent, context, strategy))
.flatMap(member -> Stream.concat(
Stream.of(member),
membersOfRecursive(member.value(), collectionParent, context, strategy)));
}
private static Stream membersOf(Object root, Object collectionParent, VisitorContext context,
ObjectMembers strategy) {
if (root == null || !context.alreadyVisisted(root)) {
return Stream.empty();
} else if (root instanceof Collection>) {
final Collection> c = (Collection>) root;
return c.stream().flatMap(element -> membersOfRecursive(element, c, context, strategy));
} else if (root.getClass().isArray()) {
final Object[] c = (Object[]) root;
return Arrays.stream(c).flatMap(element -> membersOfRecursive(element, c, context, strategy));
} else if (root instanceof Iterable>) {
final Iterable> it = (Iterable>) root;
final Spliterator> spliterator = it.spliterator();
return StreamSupport.stream(spliterator, false)
.flatMap(element -> membersOfRecursive(element, spliterator, context, strategy));
} else if (root instanceof Map, ?>) {
final Map, ?> map = (Map, ?>) root;
return Stream.concat(
membersOfRecursive(map.keySet(), collectionParent, context, strategy),
membersOfRecursive(map.values(), collectionParent, context, strategy));
} else if (context.isTerminal(root)) {
if (collectionParent != null) {
return Stream.of(new TerminalTypeInCollection(root, collectionParent));
}
return Stream.empty();
}
return strategy.directMembersOf(root, collectionParent, context);
}
private static final class TerminalTypeInCollection implements ObjectMember {
private final Object value;
private final Object collectionParent;
public TerminalTypeInCollection(Object value, Object collectionParent) {
this.value = Objects.requireNonNull(value);
this.collectionParent = Objects.requireNonNull(collectionParent);
}
@Override
public Object parent() {
return collectionParent;
}
@Override
public Optional