diff --git a/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/generator/ContainerPropertyGeneratorContext.java b/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/generator/ContainerPropertyGeneratorContext.java index 8a392c856..9f919caf2 100644 --- a/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/generator/ContainerPropertyGeneratorContext.java +++ b/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/generator/ContainerPropertyGeneratorContext.java @@ -31,12 +31,13 @@ public final class ContainerPropertyGeneratorContext { private final Property property; @Nullable private final ArbitraryContainerInfo containerInfo; + @Nullable private final ArbitraryContainerInfoGenerator containerInfoGenerator; public ContainerPropertyGeneratorContext( Property property, @Nullable ArbitraryContainerInfo containerInfo, - ArbitraryContainerInfoGenerator containerInfoGenerator + @Nullable ArbitraryContainerInfoGenerator containerInfoGenerator ) { this.property = property; this.containerInfo = containerInfo; diff --git a/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/tree/DefaultTraverseNode.java b/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/tree/DefaultTraverseNode.java index 8b7835ed8..bfe024d06 100644 --- a/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/tree/DefaultTraverseNode.java +++ b/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/tree/DefaultTraverseNode.java @@ -215,7 +215,7 @@ public LazyArbitrary getLazyPropertyPath() { @Override public boolean expand() { - if (this.expandedTypeDefinition == resolvedTypeDefinition) { + if (this.expandedTypeDefinition != null) { return false; } @@ -264,10 +264,31 @@ public void forceExpand() { } ).collect(Collectors.toList()); - this.setMergedChildren(newChildren); + this.children = newChildren; this.expandedTypeDefinition = resolvedTypeDefinition; } + @Override + public void forceExpand(TypeDefinition typeDefinition) { + List children; + if (this.getTreeProperty().isContainer()) { + children = this.expandContainerNode( + typeDefinition, + traverseContext.withParentProperties() + ).collect(Collectors.toList()); + } else { + children = this.generateChildrenNodes( + typeDefinition.getResolvedProperty(), + typeDefinition.getPropertyGenerator() + .generateChildProperties(typeDefinition.getResolvedProperty()), + this.nullInject, + traverseContext.withParentProperties() + ); + } + this.children = children; + this.expandedTypeDefinition = typeDefinition; + } + @Override public TraverseNodeMetadata getMetadata() { return this; diff --git a/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/tree/TraverseNode.java b/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/tree/TraverseNode.java index 3541029a6..bec917f9b 100644 --- a/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/tree/TraverseNode.java +++ b/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/tree/TraverseNode.java @@ -62,6 +62,21 @@ public interface TraverseNode { */ void forceExpand(); + /** + * expands the {@link TraverseNode} forcibly. In result, it always generates the child {@link TraverseNode}s. + * It generates the child {@link TraverseNode}s by given {@link TypeDefinition}. + * {@code Force} means that it expands as if it were a root node, even if it is not. + * Unlike {@link #expand()}, it expands without metadata generated on expanding of the parent {@link TraverseNode}. + *

+ * The child nodes are always generated by the given {@link TypeDefinition} unlike {@link #forceExpand()}. + *

+ * The leaf {@link TraverseNode} may generate child {@link TraverseNode}s. + * For example, the {@link TraverseNode} with a self-reference. + * + * @param typeDefinition the {@link TypeDefinition} to expand forcibly + */ + void forceExpand(TypeDefinition typeDefinition); + /** * retrieves the metadata to traverse the tree. Some of its properties can be mutated during traversal. * diff --git a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkeyBuilder.java b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkeyBuilder.java index c2709e3c4..54a47bde1 100644 --- a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkeyBuilder.java +++ b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkeyBuilder.java @@ -517,7 +517,8 @@ public FixtureMonkey build() { MonkeyManipulatorFactory monkeyManipulatorFactory = new MonkeyManipulatorFactory( new AtomicInteger(), monkeyExpressionFactory, - fixtureMonkeyOptions.getDecomposedContainerValueFactory() + fixtureMonkeyOptions.getDecomposedContainerValueFactory(), + fixtureMonkeyOptions.getContainerPropertyGenerators() ); Randoms.setSeed(seed); diff --git a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/customizer/MonkeyManipulatorFactory.java b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/customizer/MonkeyManipulatorFactory.java index dd22932ab..5542288ec 100644 --- a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/customizer/MonkeyManipulatorFactory.java +++ b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/customizer/MonkeyManipulatorFactory.java @@ -39,6 +39,7 @@ import com.navercorp.fixturemonkey.api.arbitrary.CombinableArbitrary; import com.navercorp.fixturemonkey.api.container.DecomposedContainerValueFactory; import com.navercorp.fixturemonkey.api.generator.ArbitraryContainerInfo; +import com.navercorp.fixturemonkey.api.generator.ContainerPropertyGenerator; import com.navercorp.fixturemonkey.api.lazy.LazyArbitrary; import com.navercorp.fixturemonkey.api.matcher.MatcherOperator; import com.navercorp.fixturemonkey.api.property.Property; @@ -60,15 +61,18 @@ public final class MonkeyManipulatorFactory { private final AtomicInteger sequence; private final MonkeyExpressionFactory monkeyExpressionFactory; private final DecomposedContainerValueFactory decomposedContainerValueFactory; + private final List> containerPropertyGenerators; public MonkeyManipulatorFactory( AtomicInteger sequence, MonkeyExpressionFactory monkeyExpressionFactory, - DecomposedContainerValueFactory decomposedContainerValueFactory + DecomposedContainerValueFactory decomposedContainerValueFactory, + List> containerPropertyGenerators ) { this.sequence = sequence; this.monkeyExpressionFactory = monkeyExpressionFactory; this.decomposedContainerValueFactory = decomposedContainerValueFactory; + this.containerPropertyGenerators = containerPropertyGenerators; } public ArbitraryManipulator newArbitraryManipulator( @@ -239,7 +243,8 @@ public MonkeyManipulatorFactory copy() { return new MonkeyManipulatorFactory( new AtomicInteger(sequence.get()), monkeyExpressionFactory, - decomposedContainerValueFactory + decomposedContainerValueFactory, + containerPropertyGenerators ); } @@ -259,24 +264,28 @@ private NodeManipulator convertToNodeManipulator(int sequence, @Nullable Object return new NodeSetLazyManipulator<>( sequence, decomposedContainerValueFactory, + containerPropertyGenerators, LazyArbitrary.lazy(() -> ((Arbitrary)value).sample()) ); } else if (value instanceof DefaultArbitraryBuilder) { return new NodeSetLazyManipulator<>( sequence, decomposedContainerValueFactory, + containerPropertyGenerators, LazyArbitrary.lazy(() -> ((DefaultArbitraryBuilder)value).sample()) ); } else if (value instanceof Supplier) { return new NodeSetLazyManipulator<>( sequence, decomposedContainerValueFactory, + containerPropertyGenerators, LazyArbitrary.lazy((Supplier)value) ); } else if (value instanceof LazyArbitrary) { return new NodeSetLazyManipulator<>( sequence, decomposedContainerValueFactory, + containerPropertyGenerators, (LazyArbitrary)value ); } else if (value instanceof Unique) { @@ -287,6 +296,7 @@ private NodeManipulator convertToNodeManipulator(int sequence, @Nullable Object return new NodeSetDecomposedValueManipulator<>( sequence, decomposedContainerValueFactory, + containerPropertyGenerators, value ); } diff --git a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/customizer/NodeSetDecomposedValueManipulator.java b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/customizer/NodeSetDecomposedValueManipulator.java index 4758972fb..ec8db4550 100644 --- a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/customizer/NodeSetDecomposedValueManipulator.java +++ b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/customizer/NodeSetDecomposedValueManipulator.java @@ -23,7 +23,6 @@ import static com.navercorp.fixturemonkey.api.type.Types.isAssignable; import static com.navercorp.fixturemonkey.api.type.Types.nullSafe; -import java.util.Collections; import java.util.List; import java.util.function.Function; @@ -36,6 +35,12 @@ import com.navercorp.fixturemonkey.api.container.DecomposableJavaContainer; import com.navercorp.fixturemonkey.api.container.DecomposedContainerValueFactory; import com.navercorp.fixturemonkey.api.generator.ArbitraryContainerInfo; +import com.navercorp.fixturemonkey.api.generator.ContainerProperty; +import com.navercorp.fixturemonkey.api.generator.ContainerPropertyGenerator; +import com.navercorp.fixturemonkey.api.generator.ContainerPropertyGeneratorContext; +import com.navercorp.fixturemonkey.api.matcher.MatcherOperator; +import com.navercorp.fixturemonkey.api.property.DefaultTypeDefinition; +import com.navercorp.fixturemonkey.api.property.LazyPropertyGenerator; import com.navercorp.fixturemonkey.api.property.MapEntryElementProperty; import com.navercorp.fixturemonkey.api.property.Property; import com.navercorp.fixturemonkey.api.property.TypeDefinition; @@ -43,22 +48,24 @@ import com.navercorp.fixturemonkey.api.type.Types; import com.navercorp.fixturemonkey.tree.GenerateFixtureContext; import com.navercorp.fixturemonkey.tree.ObjectNode; -import com.navercorp.fixturemonkey.tree.StartNodePredicate; @API(since = "0.4.0", status = Status.MAINTAINED) public final class NodeSetDecomposedValueManipulator implements NodeManipulator { private final int sequence; private final DecomposedContainerValueFactory decomposedContainerValueFactory; + private final List> containerPropertyGenerators; @Nullable private final T value; public NodeSetDecomposedValueManipulator( int sequence, DecomposedContainerValueFactory decomposedContainerValueFactory, + List> containerPropertyGenerators, @Nullable T value ) { this.sequence = sequence; this.decomposedContainerValueFactory = decomposedContainerValueFactory; + this.containerPropertyGenerators = containerPropertyGenerators; this.value = value; } @@ -118,17 +125,34 @@ private void setValue(ObjectNode objectNode, @Nullable Object value) { if (forced) { ArbitraryContainerInfo containerInfo = new ArbitraryContainerInfo(decomposedContainerSize, decomposedContainerSize); - objectNode.addTreeNodeManipulator( - new ContainerInfoManipulator( - Collections.singletonList(StartNodePredicate.INSTANCE), + + ContainerPropertyGenerator containerPropertyGenerator = containerPropertyGenerators.stream() + .filter(it -> it.match(objectNode.getOriginalProperty())) + .map(MatcherOperator::getOperator) + .findFirst() + .orElseThrow( + () -> new IllegalStateException( + "ContainerPropertyGenerator not found given property" + objectNode.getOriginalProperty() + ) + ); + + ContainerProperty containerProperty = containerPropertyGenerator.generate( + new ContainerPropertyGeneratorContext( + objectNode.getOriginalProperty(), containerInfo, - sequence + null ) ); - objectNode.forceExpand(); - } - objectNode.expand(); + objectNode.forceExpand( + new DefaultTypeDefinition( + objectNode.getOriginalProperty(), + new LazyPropertyGenerator(p -> containerProperty.getElementProperties()) + ) + ); + } else { + objectNode.expand(); + } List children = nullSafe(objectNode.getChildren()).asList(); if (objectNode.getArbitraryProperty() @@ -147,7 +171,7 @@ private void setValue(ObjectNode objectNode, @Nullable Object value) { return; } - objectNode.expand(); + objectNode.forceExpand(); List children = nullSafe(objectNode.getChildren()).asList(); if (children.isEmpty() || Types.getActualType(objectNode.getResolvedProperty().getType()).isInterface()) { CombinableArbitrary combinableArbitrary = CombinableArbitrary.from(value); @@ -171,7 +195,6 @@ private void setValue(ObjectNode objectNode, @Nullable Object value) { objectNode.setResolvedTypeDefinition(typeDefinition); } - objectNode.forceExpand(); for (ObjectNode child : nullSafe(objectNode.getChildren()).asList()) { if (!typeDefinition.getResolvedProperty().equals(child.getResolvedParentProperty())) { continue; diff --git a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/customizer/NodeSetLazyManipulator.java b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/customizer/NodeSetLazyManipulator.java index 63b24ed97..b5f716c67 100644 --- a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/customizer/NodeSetLazyManipulator.java +++ b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/customizer/NodeSetLazyManipulator.java @@ -18,6 +18,8 @@ package com.navercorp.fixturemonkey.customizer; +import java.util.List; + import org.apiguardian.api.API; import org.apiguardian.api.API.Status; @@ -25,7 +27,9 @@ import com.navercorp.fixturemonkey.api.arbitrary.CombinableArbitrary; import com.navercorp.fixturemonkey.api.container.DecomposedContainerValueFactory; +import com.navercorp.fixturemonkey.api.generator.ContainerPropertyGenerator; import com.navercorp.fixturemonkey.api.lazy.LazyArbitrary; +import com.navercorp.fixturemonkey.api.matcher.MatcherOperator; import com.navercorp.fixturemonkey.customizer.Values.Just; import com.navercorp.fixturemonkey.tree.GenerateFixtureContext; import com.navercorp.fixturemonkey.tree.ObjectNode; @@ -34,15 +38,18 @@ public final class NodeSetLazyManipulator implements NodeManipulator { private final int sequence; private final DecomposedContainerValueFactory decomposedContainerValueFactory; + private final List> containerPropertyGenerators; private final LazyArbitrary lazyArbitrary; public NodeSetLazyManipulator( int sequence, DecomposedContainerValueFactory decomposedContainerValueFactory, + List> containerPropertyGenerators, LazyArbitrary lazyArbitrary ) { this.sequence = sequence; this.decomposedContainerValueFactory = decomposedContainerValueFactory; + this.containerPropertyGenerators = containerPropertyGenerators; this.lazyArbitrary = lazyArbitrary; } @@ -70,7 +77,12 @@ public void manipulate(ObjectNode objectNode) { } NodeSetDecomposedValueManipulator nodeSetDecomposedValueManipulator = - new NodeSetDecomposedValueManipulator<>(sequence, decomposedContainerValueFactory, value); + new NodeSetDecomposedValueManipulator<>( + sequence, + decomposedContainerValueFactory, + containerPropertyGenerators, + value + ); nodeSetDecomposedValueManipulator.manipulate(objectNode); lazyArbitrary.clear(); } diff --git a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/tree/GenerateFixtureContext.java b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/tree/GenerateFixtureContext.java index d7283ff85..b2d8ba728 100644 --- a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/tree/GenerateFixtureContext.java +++ b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/tree/GenerateFixtureContext.java @@ -232,7 +232,10 @@ public ArbitraryGeneratorContext generateContext( Property resolvedParentProperty = objectNode.getMetadata().getResolvedTypeDefinition().getResolvedProperty(); objectNode.expand(); List children = nullSafe(objectNode.getChildren()).asList().stream() - .filter(it -> resolvedParentProperty.equals(it.getMetadata().getResolvedParentProperty())) + .filter(it -> Types.isAssignable( + Types.getActualType(resolvedParentProperty.getType()), + Types.getActualType(it.getMetadata().getResolvedParentProperty().getType())) + ) .collect(Collectors.toList()); for (ObjectNode childNode : children) { diff --git a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/tree/ObjectNode.java b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/tree/ObjectNode.java index ac458f6ed..7214886ae 100644 --- a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/tree/ObjectNode.java +++ b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/tree/ObjectNode.java @@ -89,6 +89,16 @@ public void forceExpand() { ); } + @Override + public void forceExpand(TypeDefinition typeDefinition) { + this.traverseNode.forceExpand(typeDefinition); + this.setChildren( + nullSafe(this.traverseNode.getChildren()).asList().stream() + .map(it -> new ObjectNode(it, generateFixtureContext.newChildNodeContext())) + .collect(Collectors.toList()) + ); + } + @Override public TraverseNodeMetadata getMetadata() { return traverseNode.getMetadata(); diff --git a/fixture-monkey/src/test/java/com/navercorp/fixturemonkey/test/FixtureMonkeyOptionsTest.java b/fixture-monkey/src/test/java/com/navercorp/fixturemonkey/test/FixtureMonkeyOptionsTest.java index 727df7564..4a2d97919 100644 --- a/fixture-monkey/src/test/java/com/navercorp/fixturemonkey/test/FixtureMonkeyOptionsTest.java +++ b/fixture-monkey/src/test/java/com/navercorp/fixturemonkey/test/FixtureMonkeyOptionsTest.java @@ -1609,4 +1609,30 @@ void allArbitraryGeneratorSkipReturnsNull() { then(actual).isNull(); } + + @Property + void setConcrete() { + // given + List> implementations = new ArrayList<>(); + implementations.add(ConcreteStringValue.class); + implementations.add(ConcreteIntValue.class); + + FixtureMonkey sut = FixtureMonkey.builder() + .plugin( + new InterfacePlugin() + .abstractClassExtends(AbstractValue.class, implementations) + ) + .build(); + + ConcreteStringValue expected = new ConcreteStringValue(); + expected.setValue("stringValue"); + expected.setStringValue("test"); + + // when + AbstractValue actual = sut.giveMeBuilder(AbstractValue.class) + .set(expected) + .sample(); + + then(actual).isEqualTo(expected); + } }