Skip to content

Commit 2125445

Browse files
committed
Add object-farm-api module and adapter layer for tree-based object generation
1 parent aa62496 commit 2125445

File tree

308 files changed

+68080
-984
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

308 files changed

+68080
-984
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ out/
2828

2929
### Etc ###
3030
.DS_Store
31+
history/
32+
guide/
33+
test-case/
34+
architecture/
35+
plans/
36+
.serena/
37+
.zed/
38+
.java-version
39+
object-farm-api/docs/
40+
article/
3141

3242
### Doc ###
3343
node_modules

buildSrc/src/main/kotlin/com/navercorp/fixturemonkey/gradle/plugin/CheckerFrameworkConventionsPlugin.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package com.navercorp.fixturemonkey.gradle.plugin
2020

2121
import org.checkerframework.gradle.plugin.CheckerFrameworkExtension
22+
import org.gradle.api.JavaVersion
2223
import org.gradle.api.Plugin
2324
import org.gradle.api.Project
2425
import org.gradle.kotlin.dsl.getByType

fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/generator/IntrospectedArbitraryGenerator.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,7 @@ public CombinableArbitrary<?> generate(ArbitraryGeneratorContext context) {
6464
if (result != ArbitraryIntrospectorResult.NOT_INTROSPECTED && result.getValue() != null) {
6565
double nullInject = context.getNullInject();
6666
return new TraceableCombinableArbitrary<>(
67-
result.getValue()
68-
.injectNull(nullInject),
67+
result.getValue().injectNull(nullInject),
6968
context.getPropertyPath()
7069
);
7170
}

fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/introspector/BuilderArbitraryIntrospector.java

Lines changed: 52 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242

4343
@API(since = "0.4.0", status = API.Status.MAINTAINED)
4444
public final class BuilderArbitraryIntrospector implements ArbitraryIntrospector {
45+
4546
public static final BuilderArbitraryIntrospector INSTANCE = new BuilderArbitraryIntrospector();
4647

4748
private static final Logger LOGGER = LoggerFactory.getLogger(BuilderArbitraryIntrospector.class);
@@ -78,20 +79,17 @@ public ArbitraryIntrospectorResult introspect(ArbitraryGeneratorContext context)
7879
Method buildMethod;
7980
try {
8081
builderType = this.getBuilderType(type);
81-
buildMethod = BUILD_METHOD_CACHE.computeIfAbsent(
82-
builderType,
83-
t -> {
84-
String buildMethodName = typedBuildMethodName.getOrDefault(t, defaultBuildMethodName);
85-
Method method = Reflections.findMethod(builderType, buildMethodName);
86-
if (method == null) {
87-
throw new IllegalStateException(
88-
"Can not retrieve a build method. type: " + type + " buildMethodName: " + buildMethodName
89-
);
90-
}
91-
method.setAccessible(true);
92-
return method;
82+
buildMethod = BUILD_METHOD_CACHE.computeIfAbsent(builderType, t -> {
83+
String buildMethodName = typedBuildMethodName.getOrDefault(t, defaultBuildMethodName);
84+
Method method = Reflections.findMethod(builderType, buildMethodName);
85+
if (method == null) {
86+
throw new IllegalStateException(
87+
"Can not retrieve a build method. type: " + type + " buildMethodName: " + buildMethodName
88+
);
9389
}
94-
);
90+
method.setAccessible(true);
91+
return method;
92+
});
9593
} catch (Exception ex) {
9694
ArbitraryGeneratorLoggingContext loggingContext = context.getLoggingContext();
9795
if (loggingContext.isEnableLoggingFail()) {
@@ -101,38 +99,34 @@ public ArbitraryIntrospectorResult introspect(ArbitraryGeneratorContext context)
10199
}
102100
Method builderMethod = BUILDER_CACHE.get(type);
103101

104-
LazyArbitrary<Object> generateArbitrary = LazyArbitrary.lazy(
105-
() -> {
106-
Object builder = Reflections.invokeMethod(builderMethod, null);
107-
108-
for (ArbitraryProperty arbitraryProperty : childrenProperties) {
109-
String methodName = getFieldName(arbitraryProperty.getObjectProperty().getProperty());
110-
Class<?> actualType = getActualType(arbitraryProperty.getObjectProperty().getProperty());
111-
String buildFieldMethodName = builderType.getName() + "#" + methodName;
112-
113-
String resolvePropertyName =
114-
arbitraryProperty.getObjectProperty().getResolvedPropertyName();
115-
CombinableArbitrary<?> combinableArbitrary =
116-
arbitrariesByResolvedName.get(resolvePropertyName);
117-
118-
Method method = BUILD_FIELD_METHOD_CACHE.computeIfAbsent(buildFieldMethodName, f -> {
119-
Method buildFieldMethod = Reflections.findMethod(builderType, methodName, actualType);
120-
if (buildFieldMethod != null) {
121-
buildFieldMethod.setAccessible(true);
122-
}
123-
return buildFieldMethod;
124-
});
125-
if (method != null) {
126-
Object child = combinableArbitrary.combined();
127-
if (child != null) {
128-
Reflections.invokeMethod(method, builder, child);
129-
}
102+
LazyArbitrary<Object> generateArbitrary = LazyArbitrary.lazy(() -> {
103+
Object builder = Reflections.invokeMethod(builderMethod, null);
104+
105+
for (ArbitraryProperty arbitraryProperty : childrenProperties) {
106+
String methodName = getFieldName(arbitraryProperty.getObjectProperty().getProperty());
107+
Class<?> actualType = getActualType(arbitraryProperty.getObjectProperty().getProperty());
108+
String buildFieldMethodName = builderType.getName() + "#" + methodName;
109+
110+
String resolvePropertyName = arbitraryProperty.getObjectProperty().getResolvedPropertyName();
111+
CombinableArbitrary<?> combinableArbitrary = arbitrariesByResolvedName.get(resolvePropertyName);
112+
113+
Method method = BUILD_FIELD_METHOD_CACHE.computeIfAbsent(buildFieldMethodName, f -> {
114+
Method buildFieldMethod = Reflections.findMethod(builderType, methodName, actualType);
115+
if (buildFieldMethod != null) {
116+
buildFieldMethod.setAccessible(true);
117+
}
118+
return buildFieldMethod;
119+
});
120+
if (method != null) {
121+
Object child = combinableArbitrary.combined();
122+
if (child != null) {
123+
Reflections.invokeMethod(method, builder, child);
130124
}
131125
}
132-
133-
return Reflections.invokeMethod(buildMethod, builder);
134126
}
135-
);
127+
128+
return Reflections.invokeMethod(buildMethod, builder);
129+
});
136130
return new ArbitraryIntrospectorResult(CombinableArbitrary.from(generateArbitrary));
137131
}
138132

@@ -169,8 +163,8 @@ public void setBuildMethodName(Class<?> type, String buildMethodName) {
169163

170164
if (builderMethod == null) {
171165
throw new IllegalArgumentException(
172-
"Class has no builder class. "
173-
+ "type: " + objectType.getName() + " builderMethodName: " + builderMethodName
166+
"Class has no builder class. " + "type: " + objectType.getName()
167+
+ " builderMethodName: " + builderMethodName
174168
);
175169
}
176170

@@ -186,7 +180,20 @@ private String getFieldName(Property property) {
186180
}
187181

188182
private Class<?> getActualType(Property property) {
189-
return Types.getActualType(getActualProperty(property).getType());
183+
Property actualProperty = getActualProperty(property);
184+
185+
// The adapter may resolve a FieldProperty's type to a concrete type (e.g. ArrayList),
186+
// but builder methods accept the field's declared type (e.g. List) as their parameter.
187+
// If the concrete type is a subtype of the declared type, use the declared type instead.
188+
if (actualProperty instanceof FieldProperty) {
189+
Class<?> fieldDeclaredType = ((FieldProperty)actualProperty).getField().getType();
190+
Class<?> propertyType = Types.getActualType(actualProperty.getType());
191+
if (fieldDeclaredType != propertyType && fieldDeclaredType.isAssignableFrom(propertyType)) {
192+
return fieldDeclaredType;
193+
}
194+
}
195+
196+
return Types.getActualType(actualProperty.getType());
190197
}
191198

192199
private Property getActualProperty(Property property) {

fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/introspector/PriorityConstructorArbitraryIntrospector.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public final class PriorityConstructorArbitraryIntrospector implements Arbitrary
4444
public static final PriorityConstructorArbitraryIntrospector INSTANCE =
4545
new PriorityConstructorArbitraryIntrospector();
4646

47-
private static final Map<Property, ConstructorArbitraryIntrospector> CONSTRUCTOR_INTROSPECTORS_BY_PROPERTY =
47+
private final Map<Property, ConstructorArbitraryIntrospector> constructorIntrospectorsByProperty =
4848
new ConcurrentLruCache<>(256);
4949

5050
private final Predicate<Constructor<?>> constructorFilter;
@@ -113,7 +113,7 @@ public PropertyGenerator getRequiredPropertyGenerator(Property property) {
113113
private ConstructorArbitraryIntrospector getConstructorArbitraryIntrospector(Property property) {
114114
Class<?> actualType = Types.getActualType(property.getType());
115115

116-
return CONSTRUCTOR_INTROSPECTORS_BY_PROPERTY.computeIfAbsent(
116+
return constructorIntrospectorsByProperty.computeIfAbsent(
117117
property,
118118
p -> {
119119
Constructor<?> constructor = TypeCache.getDeclaredConstructors(actualType).stream()

fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/option/FixtureMonkeyOptions.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ public final class FixtureMonkeyOptions {
126126
)
127127
);
128128
public static final int DEFAULT_MAX_UNIQUE_GENERATION_COUNT = 1_000;
129+
public static final int DEFAULT_MAX_RECURSION_DEPTH = 5;
129130
public static final List<MatcherOperator<NullInjectGenerator>> DEFAULT_NULL_INJECT_GENERATORS =
130131
Collections.singletonList(
131132
new MatcherOperator<>(
@@ -167,7 +168,20 @@ public final class FixtureMonkeyOptions {
167168
private final InstantiatorProcessor instantiatorProcessor;
168169
private final MatcherOperatorRetriever<CandidateConcretePropertyResolver> candidateConcretePropertyResolvers;
169170
private final boolean enableLoggingFail;
171+
private final int maxRecursionDepth;
170172
private final List<TreeMatcherOperator<BuilderContextInitializer>> builderContextInitializers;
173+
/**
174+
* Declared as Object because the actual type (NodeTreeAdapter) is in the fixture-monkey module,
175+
* which cannot be referenced from fixture-monkey-api due to module dependency constraints.
176+
*/
177+
@Nullable
178+
private final Object nodeTreeAdapter;
179+
/**
180+
* Declared as Object because the actual type (AdapterTracer) is in the fixture-monkey module,
181+
* which cannot be referenced from fixture-monkey-api due to module dependency constraints.
182+
*/
183+
@Nullable
184+
private final Object adapterTracer;
171185

172186
public FixtureMonkeyOptions(
173187
MatcherOperatorRetriever<PropertyGenerator> propertyGenerators,
@@ -190,7 +204,10 @@ public FixtureMonkeyOptions(
190204
InstantiatorProcessor instantiatorProcessor,
191205
MatcherOperatorRetriever<CandidateConcretePropertyResolver> candidateConcretePropertyResolvers,
192206
boolean enableLoggingFail,
193-
List<TreeMatcherOperator<BuilderContextInitializer>> builderContextCustomizer
207+
int maxRecursionDepth,
208+
List<TreeMatcherOperator<BuilderContextInitializer>> builderContextCustomizer,
209+
@Nullable Object nodeTreeAdapter,
210+
@Nullable Object adapterTracer
194211
) {
195212
this.propertyGenerators = propertyGenerators;
196213
this.defaultPropertyGenerator = defaultPropertyGenerator;
@@ -212,7 +229,10 @@ public FixtureMonkeyOptions(
212229
this.instantiatorProcessor = instantiatorProcessor;
213230
this.candidateConcretePropertyResolvers = candidateConcretePropertyResolvers;
214231
this.enableLoggingFail = enableLoggingFail;
232+
this.maxRecursionDepth = maxRecursionDepth;
215233
this.builderContextInitializers = builderContextCustomizer;
234+
this.nodeTreeAdapter = nodeTreeAdapter;
235+
this.adapterTracer = adapterTracer;
216236
}
217237

218238
public static FixtureMonkeyOptionsBuilder builder() {
@@ -344,10 +364,37 @@ public boolean isEnableLoggingFail() {
344364
return enableLoggingFail;
345365
}
346366

367+
368+
public int getMaxRecursionDepth() {
369+
return maxRecursionDepth;
370+
}
371+
347372
public List<TreeMatcherOperator<BuilderContextInitializer>> getBuilderContextInitializers() {
348373
return builderContextInitializers;
349374
}
350375

376+
/**
377+
* Returns the node tree adapter for tree-based object generation.
378+
*
379+
* @return the adapter instance, or null if not configured
380+
*/
381+
@Nullable
382+
@API(since = "1.1.0", status = Status.EXPERIMENTAL)
383+
public Object getNodeTreeAdapter() {
384+
return nodeTreeAdapter;
385+
}
386+
387+
/**
388+
* Returns the adapter tracer for debugging tree resolution.
389+
*
390+
* @return the tracer instance, or null if not configured
391+
*/
392+
@Nullable
393+
@API(since = "1.1.0", status = Status.EXPERIMENTAL)
394+
public Object getAdapterTracer() {
395+
return adapterTracer;
396+
}
397+
351398
public List<MatcherOperator<CandidateConcretePropertyResolver>> getCandidateConcretePropertyResolvers() {
352399
return candidateConcretePropertyResolvers.getList();
353400
}
@@ -361,6 +408,7 @@ public CandidateConcretePropertyResolver getCandidateConcretePropertyResolver(Pr
361408
List<CandidateConcretePropertyResolver> candidateConcretePropertyResolverList =
362409
this.candidateConcretePropertyResolvers.getListByProperty(property)
363410
.stream()
411+
.filter(it -> it.match(property))
364412
.map(MatcherOperator::getOperator)
365413
.collect(Collectors.toList());
366414

@@ -388,6 +436,7 @@ public FixtureMonkeyOptionsBuilder toBuilder() {
388436
.javaConstraintGenerator(javaConstraintGenerator)
389437
.instantiatorProcessor(instantiatorProcessor)
390438
.candidateConcretePropertyResolvers(new ArrayList<>(candidateConcretePropertyResolvers.getList()))
439+
.maxRecursionDepth(maxRecursionDepth)
391440
.builderContextInitializers(builderContextInitializers);
392441
}
393442

0 commit comments

Comments
 (0)