diff --git a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkey.java b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkey.java index f97d7df76..aa2b41228 100644 --- a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkey.java +++ b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkey.java @@ -22,9 +22,11 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.Function; +import java.util.TreeMap; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.apiguardian.api.API; @@ -42,6 +44,7 @@ import com.navercorp.fixturemonkey.api.type.TypeReference; import com.navercorp.fixturemonkey.customizer.ArbitraryManipulator; import com.navercorp.fixturemonkey.customizer.MonkeyManipulatorFactory; +import com.navercorp.fixturemonkey.customizer.PriorityMatcherOperator; import com.navercorp.fixturemonkey.experimental.ExperimentalArbitraryBuilder; import com.navercorp.fixturemonkey.resolver.ArbitraryBuilderContext; import com.navercorp.fixturemonkey.resolver.ArbitraryResolver; @@ -57,24 +60,26 @@ public final class FixtureMonkey { private final MonkeyContext monkeyContext; private final List>> registeredArbitraryBuilders = new ArrayList<>(); private final MonkeyManipulatorFactory monkeyManipulatorFactory; + private final Map>>> + priorityGroupedMatchers = new HashMap<>(); public FixtureMonkey( FixtureMonkeyOptions fixtureMonkeyOptions, ArbitraryTraverser traverser, ManipulatorOptimizer manipulatorOptimizer, MonkeyContext monkeyContext, - List>>> registeredArbitraryBuilders, + List registeredArbitraryBuilders, MonkeyManipulatorFactory monkeyManipulatorFactory, - Map>>> mapsByRegisteredName + Map mapsByRegisteredName ) { this.fixtureMonkeyOptions = fixtureMonkeyOptions; this.traverser = traverser; this.manipulatorOptimizer = manipulatorOptimizer; this.monkeyContext = monkeyContext; this.monkeyManipulatorFactory = monkeyManipulatorFactory; - initializeRegisteredArbitraryBuilders(registeredArbitraryBuilders); - initializeNamedArbitraryBuilderMap(mapsByRegisteredName); - shuffleRegisteredArbitraryBuilders(); + groupMatchersByPriorityFromList(registeredArbitraryBuilders); + groupMatchersByPriorityFromNamedMap(mapsByRegisteredName); + shuffleAndRegisterByPriority(priorityGroupedMatchers); } public static FixtureMonkeyBuilder builder() { @@ -191,33 +196,51 @@ public Arbitrary giveMeArbitrary(TypeReference typeReference) { return this.giveMeBuilder(typeReference).build(); } - private void initializeRegisteredArbitraryBuilders( - List>>> registeredArbitraryBuilders + private void groupMatchersByPriorityFromList( + List registeredArbitraryBuilders ) { - List>> generatedRegisteredArbitraryBuilder = - registeredArbitraryBuilders.stream() - .map(it -> new MatcherOperator<>(it.getMatcher(), it.getOperator().apply(this))) - .collect(toList()); - - for (int i = generatedRegisteredArbitraryBuilder.size() - 1; i >= 0; i--) { - this.registeredArbitraryBuilders.add(generatedRegisteredArbitraryBuilder.get(i)); - } + priorityGroupedMatchers.putAll( + registeredArbitraryBuilders + .stream() + .collect(Collectors.groupingBy( + PriorityMatcherOperator::getPriority, + Collectors.mapping( + it -> new MatcherOperator<>( + it.getMatcherOperator(), it.getMatcherOperator().getOperator().apply(this) + ), + Collectors.toList() + ) + )) + ); } - private void initializeNamedArbitraryBuilderMap( - Map>>> mapsByRegisteredName + private void groupMatchersByPriorityFromNamedMap( + Map mapsByRegisteredName ) { - mapsByRegisteredName.forEach((registeredName, matcherOperator) -> { - registeredArbitraryBuilders.add( - new MatcherOperator<>( - new NamedMatcher(matcherOperator.getMatcher(), registeredName), - matcherOperator.getOperator().apply(this) - ) - ); - }); + priorityGroupedMatchers.putAll( + mapsByRegisteredName.entrySet().stream() + .collect(Collectors.groupingBy( + entry -> entry.getValue().getPriority(), + Collectors.mapping( + entry -> new MatcherOperator<>( + new NamedMatcher(entry.getValue().getMatcherOperator().getMatcher(), entry.getKey()), + entry.getValue().getMatcherOperator().getOperator().apply(this) + ), + Collectors.toList() + ) + )) + ); } - private void shuffleRegisteredArbitraryBuilders() { - Collections.shuffle(registeredArbitraryBuilders, Randoms.current()); + private void shuffleAndRegisterByPriority( + Map>>> groupedByPriority + ) { + TreeMap>>> sortedGroupedByPriority = + new TreeMap<>(groupedByPriority); + + sortedGroupedByPriority.forEach((priority, matcherOperators) -> { + Collections.shuffle(matcherOperators, Randoms.current()); + this.registeredArbitraryBuilders.addAll(matcherOperators); + }); } } 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 8b3db271f..4d96aabce 100644 --- a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkeyBuilder.java +++ b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkeyBuilder.java @@ -32,6 +32,7 @@ import org.apiguardian.api.API; import org.apiguardian.api.API.Status; +import com.navercorp.fixturemonkey.annotation.Order; import com.navercorp.fixturemonkey.api.constraint.JavaConstraintGenerator; import com.navercorp.fixturemonkey.api.container.DecomposedContainerValueFactory; import com.navercorp.fixturemonkey.api.context.MonkeyContext; @@ -62,6 +63,7 @@ import com.navercorp.fixturemonkey.buildergroup.ArbitraryBuilderCandidate; import com.navercorp.fixturemonkey.buildergroup.ArbitraryBuilderGroup; import com.navercorp.fixturemonkey.customizer.MonkeyManipulatorFactory; +import com.navercorp.fixturemonkey.customizer.PriorityMatcherOperator; import com.navercorp.fixturemonkey.expression.ArbitraryExpressionFactory; import com.navercorp.fixturemonkey.expression.MonkeyExpressionFactory; import com.navercorp.fixturemonkey.resolver.ManipulatorOptimizer; @@ -73,13 +75,11 @@ @API(since = "0.4.0", status = Status.MAINTAINED) public final class FixtureMonkeyBuilder { private final FixtureMonkeyOptionsBuilder fixtureMonkeyOptionsBuilder = FixtureMonkeyOptions.builder(); - private final List>>> - registeredArbitraryBuilders = new ArrayList<>(); + private final List registeredArbitraryBuilders = new ArrayList<>(); private ManipulatorOptimizer manipulatorOptimizer = new NoneManipulatorOptimizer(); private MonkeyExpressionFactory monkeyExpressionFactory = new ArbitraryExpressionFactory(); private final MonkeyContextBuilder monkeyContextBuilder = MonkeyContext.builder(); - private final Map>>> - registeredArbitraryListByRegisteredName = new HashMap<>(); + private final Map registeredArbitraryListByRegisteredName = new HashMap<>(); private long seed = System.nanoTime(); // The default plugins are listed below. @@ -317,6 +317,14 @@ public FixtureMonkeyBuilder register( return this.register(MatcherOperator.assignableTypeMatchOperator(type, registeredArbitraryBuilder)); } + public FixtureMonkeyBuilder register( + Class type, + Function> registeredArbitraryBuilder, + int priority + ) { + return this.register(MatcherOperator.assignableTypeMatchOperator(type, registeredArbitraryBuilder), priority); + } + public FixtureMonkeyBuilder registerExactType( Class type, Function> registeredArbitraryBuilder @@ -334,7 +342,15 @@ public FixtureMonkeyBuilder registerAssignableType( public FixtureMonkeyBuilder register( MatcherOperator>> registeredArbitraryBuilder ) { - this.registeredArbitraryBuilders.add(registeredArbitraryBuilder); + this.registeredArbitraryBuilders.add(new PriorityMatcherOperator(registeredArbitraryBuilder)); + return this; + } + + public FixtureMonkeyBuilder register( + MatcherOperator>> registeredArbitraryBuilder, + int priority + ) { + this.registeredArbitraryBuilders.add(new PriorityMatcherOperator(registeredArbitraryBuilder, priority)); return this; } @@ -361,6 +377,11 @@ public FixtureMonkeyBuilder registerGroup(Class... arbitraryBuilderGroups) { throw new RuntimeException(ex); } }; + + if (arbitraryBuilderGroup.isAnnotationPresent(Order.class)) { + Order order = arbitraryBuilderGroup.getAnnotation(Order.class); + this.register(actualType, registerArbitraryBuilder, order.value()); + } this.register(actualType, registerArbitraryBuilder); } catch (Exception ex) { // ignored @@ -385,6 +406,23 @@ public FixtureMonkeyBuilder registerGroup(ArbitraryBuilderGroup... arbitraryBuil return this; } + public FixtureMonkeyBuilder registerGroup( + ArbitraryBuilderGroup arbitraryBuilderGroup, + int priority + ) { + List> candidates = arbitraryBuilderGroup.generateCandidateList() + .getCandidates(); + + for (ArbitraryBuilderCandidate candidate : candidates) { + this.register( + candidate.getClassType(), + candidate.getArbitraryBuilderRegisterer(), + priority + ); + } + return this; + } + public FixtureMonkeyBuilder registeredName( String registeredName, Class type, @@ -396,7 +434,25 @@ public FixtureMonkeyBuilder registeredName( MatcherOperator>> matcherOperator = MatcherOperator.assignableTypeMatchOperator(type, arbitraryBuilder); - this.registeredArbitraryListByRegisteredName.put(registeredName, matcherOperator); + this.registeredArbitraryListByRegisteredName.put(registeredName, new PriorityMatcherOperator(matcherOperator)); + return this; + } + + public FixtureMonkeyBuilder registeredName( + String registeredName, + Class type, + Function> arbitraryBuilder, + int priority + ) { + if (registeredArbitraryListByRegisteredName.containsKey(registeredName)) { + throw new IllegalArgumentException("Duplicated ArbitraryBuilder name: " + registeredName); + } + MatcherOperator>> matcherOperator = + MatcherOperator.assignableTypeMatchOperator(type, arbitraryBuilder); + + this.registeredArbitraryListByRegisteredName.put( + registeredName, new PriorityMatcherOperator(matcherOperator, priority) + ); return this; } diff --git a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/annotation/Order.java b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/annotation/Order.java new file mode 100644 index 000000000..a8d260658 --- /dev/null +++ b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/annotation/Order.java @@ -0,0 +1,30 @@ +/* + * Fixture Monkey + * + * Copyright (c) 2021-present NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.fixturemonkey.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface Order { + int value() default 0; +} diff --git a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/customizer/PriorityMatcherOperator.java b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/customizer/PriorityMatcherOperator.java new file mode 100644 index 000000000..096322399 --- /dev/null +++ b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/customizer/PriorityMatcherOperator.java @@ -0,0 +1,60 @@ +/* + * Fixture Monkey + * + * Copyright (c) 2021-present NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.fixturemonkey.customizer; + +import java.util.function.Function; + +import com.navercorp.fixturemonkey.ArbitraryBuilder; +import com.navercorp.fixturemonkey.FixtureMonkey; +import com.navercorp.fixturemonkey.api.matcher.MatcherOperator; + +public final class PriorityMatcherOperator { + private final int priority; + private final MatcherOperator>> matcherOperator; + + public PriorityMatcherOperator( + MatcherOperator>> matcherOperator, + int priority + ) { + checkPriority(priority); + this.matcherOperator = matcherOperator; + this.priority = priority; + } + + public PriorityMatcherOperator( + final MatcherOperator>> matcherOperator + ) { + this.priority = Integer.MAX_VALUE; + this.matcherOperator = matcherOperator; + } + + public int getPriority() { + return priority; + } + + public MatcherOperator>> getMatcherOperator() { + return matcherOperator; + } + + private void checkPriority(int priority) { + if (priority < 0) { + throw new IllegalArgumentException("Priority must be greater than or equal to 0"); + } + } +} diff --git a/fixture-monkey/src/test/java/com/navercorp/fixturemonkey/test/FixtureMonkeyOptionsAdditionalTestSpecs.java b/fixture-monkey/src/test/java/com/navercorp/fixturemonkey/test/FixtureMonkeyOptionsAdditionalTestSpecs.java index d87896af7..4d5d9c35b 100644 --- a/fixture-monkey/src/test/java/com/navercorp/fixturemonkey/test/FixtureMonkeyOptionsAdditionalTestSpecs.java +++ b/fixture-monkey/src/test/java/com/navercorp/fixturemonkey/test/FixtureMonkeyOptionsAdditionalTestSpecs.java @@ -36,6 +36,7 @@ import com.navercorp.fixturemonkey.ArbitraryBuilder; import com.navercorp.fixturemonkey.FixtureMonkey; +import com.navercorp.fixturemonkey.annotation.Order; import com.navercorp.fixturemonkey.api.arbitrary.CombinableArbitrary; import com.navercorp.fixturemonkey.api.generator.ArbitraryContainerInfo; import com.navercorp.fixturemonkey.api.generator.ArbitraryGenerator; @@ -101,6 +102,29 @@ public ArbitraryBuilder concreteIntValue(FixtureMonkey fixture } } + @Order(value = 1) + public static class RegisterGroupWithPriority { + public static final ConcreteIntValue FIXED_INT_VALUE = new ConcreteIntValue(); + + public ArbitraryBuilder string(FixtureMonkey fixtureMonkey) { + return fixtureMonkey.giveMeBuilder(String.class) + .set(Arbitraries.strings().numeric().ofMinLength(4).ofMaxLength(6)); + } + + public ArbitraryBuilder> stringList(FixtureMonkey fixtureMonkey) { + return fixtureMonkey.giveMeBuilder(new TypeReference>() { + }) + .setInner( + new InnerSpec() + .minSize(5) + ); + } + + public ArbitraryBuilder concreteIntValue(FixtureMonkey fixtureMonkey) { + return fixtureMonkey.giveMeBuilder(FIXED_INT_VALUE); + } + } + public static class ChildBuilderGroup implements ArbitraryBuilderGroup { public static final ConcreteIntValue FIXED_INT_VALUE = new ConcreteIntValue(); @@ -138,6 +162,43 @@ public ArbitraryBuilderCandidateList generateCandidateList() { } } + public static class SiblingBuilderGroup implements ArbitraryBuilderGroup { + public static final ConcreteIntValue FIXED_INT_VALUE = new ConcreteIntValue(); + + @Override + public ArbitraryBuilderCandidateList generateCandidateList() { + return ArbitraryBuilderCandidateList.create() + .add( + ArbitraryBuilderCandidateFactory + .of(String.class) + .builder( + arbitraryBuilder -> arbitraryBuilder.set( + Arbitraries.strings() + .numeric() + .ofMinLength(4) + .ofMaxLength(6) + ) + ) + ) + .add( + ArbitraryBuilderCandidateFactory + .of(new TypeReference>() { + }) + .builder( + builder -> builder.setInner( + new InnerSpec() + .minSize(4) + ) + ) + ) + .add( + ArbitraryBuilderCandidateFactory + .of(ConcreteIntValue.class) + .value(FIXED_INT_VALUE) + ); + } + } + interface PairInterface { S getFirst(); 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 04259b5b4..75fd87911 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 @@ -99,8 +99,10 @@ import com.navercorp.fixturemonkey.test.FixtureMonkeyOptionsAdditionalTestSpecs.PairInterface; import com.navercorp.fixturemonkey.test.FixtureMonkeyOptionsAdditionalTestSpecs.PairIntrospector; import com.navercorp.fixturemonkey.test.FixtureMonkeyOptionsAdditionalTestSpecs.RegisterGroup; +import com.navercorp.fixturemonkey.test.FixtureMonkeyOptionsAdditionalTestSpecs.RegisterGroupWithPriority; import com.navercorp.fixturemonkey.test.FixtureMonkeyOptionsAdditionalTestSpecs.SelfRecursiveAbstractValue; import com.navercorp.fixturemonkey.test.FixtureMonkeyOptionsAdditionalTestSpecs.SelfRecursiveImplementationValue; +import com.navercorp.fixturemonkey.test.FixtureMonkeyOptionsAdditionalTestSpecs.SiblingBuilderGroup; import com.navercorp.fixturemonkey.test.FixtureMonkeyOptionsAdditionalTestSpecs.SimpleObjectChild; import com.navercorp.fixturemonkey.test.FixtureMonkeyOptionsAdditionalTestSpecs.UniqueArbitraryGenerator; import com.navercorp.fixturemonkey.test.FixtureMonkeyTestSpecs.ComplexObject; @@ -780,7 +782,6 @@ void registerGroup() { then(actual).hasSizeBetween(1, 3); then(actual2).hasSizeLessThan(5); then(actual3).isEqualTo(RegisterGroup.FIXED_INT_VALUE); - } @Property @@ -872,6 +873,86 @@ void registerSetFirst() { then(actual).isEqualTo(expected); } + @Property + void registerWithPriority() { + FixtureMonkey sut = FixtureMonkey.builder() + .register(String.class, monkey -> monkey.giveMeBuilder("test"), 1) + .register(String.class, monkey -> monkey.giveMeBuilder("test2"), 2) + .build(); + + String actual = sut.giveMeBuilder(String.class) + .sample(); + + then(actual).isEqualTo("test"); + } + + @Property + void registeredNameWithPriority() { + FixtureMonkey sut = FixtureMonkey.builder() + .registeredName( + "test", + String.class, + monkey -> monkey.giveMeBuilder("test"), + 1 + ) + .registeredName( + "test2", + String.class, + monkey -> monkey.giveMeBuilder("test2"), + 2 + ) + .build(); + + String actual = sut.giveMeBuilder(String.class) + .sample(); + + then(actual).isEqualTo("test"); + } + + @Property + void registerGroupWithPriorityAnnotation() { + FixtureMonkey sut = FixtureMonkey.builder() + .registerGroup(RegisterGroup.class, RegisterGroupWithPriority.class) + .build(); + + String actual = sut.giveMeOne(SimpleObject.class) + .getStr(); + List actual2 = sut.giveMeOne(new TypeReference>() { + }); + ConcreteIntValue actual3 = sut.giveMeOne(ConcreteIntValue.class); + + then(actual).hasSizeBetween(4, 6); + then(actual2).hasSizeGreaterThan(4); + then(actual3).isEqualTo(RegisterGroup.FIXED_INT_VALUE); + } + + @Property + void registerGroupWithPriority() { + FixtureMonkey sut = FixtureMonkey.builder() + .registerGroup(new SiblingBuilderGroup(), 1) + .registerGroup(new ChildBuilderGroup(), 2) + .build(); + + String actual = sut.giveMeOne(SimpleObject.class) + .getStr(); + List actual2 = sut.giveMeOne(new TypeReference>() { + }); + ConcreteIntValue actual3 = sut.giveMeOne(ConcreteIntValue.class); + + then(actual).hasSizeBetween(4, 6); + then(actual2).hasSizeGreaterThan(3); + then(actual3).isEqualTo(ChildBuilderGroup.FIXED_INT_VALUE); + } + + @Property + void registerWithPriorityLessThenZero() { + thenThrownBy(() -> FixtureMonkey.builder() + .register(String.class, monkey -> monkey.giveMeBuilder("test"), -1) + .build() + ).isExactlyInstanceOf(IllegalArgumentException.class) + .hasMessage("Priority must be greater than or equal to 0"); + } + @Property void nullableElement() { FixtureMonkey sut = FixtureMonkey.builder()