Skip to content

Commit 47cec8f

Browse files
committed
Add object-farm-api module and adapter layer for tree-based object generation
1 parent 203132c commit 47cec8f

File tree

298 files changed

+67847
-894
lines changed

Some content is hidden

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

298 files changed

+67847
-894
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

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
@@ -41,6 +41,7 @@
4141

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

4647
private static final Logger LOGGER = LoggerFactory.getLogger(BuilderArbitraryIntrospector.class);
@@ -76,20 +77,17 @@ public ArbitraryIntrospectorResult introspect(ArbitraryGeneratorContext context)
7677
Method buildMethod;
7778
try {
7879
builderType = this.getBuilderType(type);
79-
buildMethod = BUILD_METHOD_CACHE.computeIfAbsent(
80-
builderType,
81-
t -> {
82-
String buildMethodName = typedBuildMethodName.getOrDefault(t, defaultBuildMethodName);
83-
Method method = Reflections.findMethod(builderType, buildMethodName);
84-
if (method == null) {
85-
throw new IllegalStateException(
86-
"Can not retrieve a build method. type: " + type + " buildMethodName: " + buildMethodName
87-
);
88-
}
89-
method.setAccessible(true);
90-
return method;
80+
buildMethod = BUILD_METHOD_CACHE.computeIfAbsent(builderType, t -> {
81+
String buildMethodName = typedBuildMethodName.getOrDefault(t, defaultBuildMethodName);
82+
Method method = Reflections.findMethod(builderType, buildMethodName);
83+
if (method == null) {
84+
throw new IllegalStateException(
85+
"Can not retrieve a build method. type: " + type + " buildMethodName: " + buildMethodName
86+
);
9187
}
92-
);
88+
method.setAccessible(true);
89+
return method;
90+
});
9391
} catch (Exception ex) {
9492
ArbitraryGeneratorLoggingContext loggingContext = context.getLoggingContext();
9593
if (loggingContext.isEnableLoggingFail()) {
@@ -99,38 +97,34 @@ public ArbitraryIntrospectorResult introspect(ArbitraryGeneratorContext context)
9997
}
10098
Method builderMethod = BUILDER_CACHE.get(type);
10199

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

@@ -166,8 +160,8 @@ private Class<?> getBuilderType(Class<?> objectType) {
166160

167161
if (builderMethod == null) {
168162
throw new IllegalArgumentException(
169-
"Class has no builder class. "
170-
+ "type: " + objectType.getName() + " builderMethodName: " + builderMethodName
163+
"Class has no builder class. " + "type: " + objectType.getName()
164+
+ " builderMethodName: " + builderMethodName
171165
);
172166
}
173167

@@ -182,7 +176,20 @@ private String getFieldName(Property property) {
182176
}
183177

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

188195
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
@@ -43,7 +43,7 @@ public final class PriorityConstructorArbitraryIntrospector implements Arbitrary
4343
public static final PriorityConstructorArbitraryIntrospector INSTANCE =
4444
new PriorityConstructorArbitraryIntrospector();
4545

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

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

114-
return CONSTRUCTOR_INTROSPECTORS_BY_PROPERTY.computeIfAbsent(
114+
return constructorIntrospectorsByProperty.computeIfAbsent(
115115
property,
116116
p -> {
117117
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)