Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ out/

### Etc ###
.DS_Store
history/
guide/
test-case/
architecture/
plans/
.serena/
.zed/
.java-version
object-farm-api/docs/
article/

### Doc ###
node_modules
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package com.navercorp.fixturemonkey.gradle.plugin

import org.checkerframework.gradle.plugin.CheckerFrameworkExtension
import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.getByType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ public CombinableArbitrary<?> generate(ArbitraryGeneratorContext context) {
if (result != ArbitraryIntrospectorResult.NOT_INTROSPECTED && result.getValue() != null) {
double nullInject = context.getNullInject();
return new TraceableCombinableArbitrary<>(
result.getValue()
.injectNull(nullInject),
result.getValue().injectNull(nullInject),
context.getPropertyPath()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

@API(since = "0.4.0", status = API.Status.MAINTAINED)
public final class BuilderArbitraryIntrospector implements ArbitraryIntrospector {

public static final BuilderArbitraryIntrospector INSTANCE = new BuilderArbitraryIntrospector();

private static final Logger LOGGER = LoggerFactory.getLogger(BuilderArbitraryIntrospector.class);
Expand Down Expand Up @@ -78,20 +79,17 @@ public ArbitraryIntrospectorResult introspect(ArbitraryGeneratorContext context)
Method buildMethod;
try {
builderType = this.getBuilderType(type);
buildMethod = BUILD_METHOD_CACHE.computeIfAbsent(
builderType,
t -> {
String buildMethodName = typedBuildMethodName.getOrDefault(t, defaultBuildMethodName);
Method method = Reflections.findMethod(builderType, buildMethodName);
if (method == null) {
throw new IllegalStateException(
"Can not retrieve a build method. type: " + type + " buildMethodName: " + buildMethodName
);
}
method.setAccessible(true);
return method;
buildMethod = BUILD_METHOD_CACHE.computeIfAbsent(builderType, t -> {
String buildMethodName = typedBuildMethodName.getOrDefault(t, defaultBuildMethodName);
Method method = Reflections.findMethod(builderType, buildMethodName);
if (method == null) {
throw new IllegalStateException(
"Can not retrieve a build method. type: " + type + " buildMethodName: " + buildMethodName
);
}
);
method.setAccessible(true);
return method;
});
} catch (Exception ex) {
ArbitraryGeneratorLoggingContext loggingContext = context.getLoggingContext();
if (loggingContext.isEnableLoggingFail()) {
Expand All @@ -101,38 +99,34 @@ public ArbitraryIntrospectorResult introspect(ArbitraryGeneratorContext context)
}
Method builderMethod = BUILDER_CACHE.get(type);

LazyArbitrary<Object> generateArbitrary = LazyArbitrary.lazy(
() -> {
Object builder = Reflections.invokeMethod(builderMethod, null);

for (ArbitraryProperty arbitraryProperty : childrenProperties) {
String methodName = getFieldName(arbitraryProperty.getObjectProperty().getProperty());
Class<?> actualType = getActualType(arbitraryProperty.getObjectProperty().getProperty());
String buildFieldMethodName = builderType.getName() + "#" + methodName;

String resolvePropertyName =
arbitraryProperty.getObjectProperty().getResolvedPropertyName();
CombinableArbitrary<?> combinableArbitrary =
arbitrariesByResolvedName.get(resolvePropertyName);

Method method = BUILD_FIELD_METHOD_CACHE.computeIfAbsent(buildFieldMethodName, f -> {
Method buildFieldMethod = Reflections.findMethod(builderType, methodName, actualType);
if (buildFieldMethod != null) {
buildFieldMethod.setAccessible(true);
}
return buildFieldMethod;
});
if (method != null) {
Object child = combinableArbitrary.combined();
if (child != null) {
Reflections.invokeMethod(method, builder, child);
}
LazyArbitrary<Object> generateArbitrary = LazyArbitrary.lazy(() -> {
Object builder = Reflections.invokeMethod(builderMethod, null);

for (ArbitraryProperty arbitraryProperty : childrenProperties) {
String methodName = getFieldName(arbitraryProperty.getObjectProperty().getProperty());
Class<?> actualType = getActualType(arbitraryProperty.getObjectProperty().getProperty());
String buildFieldMethodName = builderType.getName() + "#" + methodName;

String resolvePropertyName = arbitraryProperty.getObjectProperty().getResolvedPropertyName();
CombinableArbitrary<?> combinableArbitrary = arbitrariesByResolvedName.get(resolvePropertyName);

Method method = BUILD_FIELD_METHOD_CACHE.computeIfAbsent(buildFieldMethodName, f -> {
Method buildFieldMethod = Reflections.findMethod(builderType, methodName, actualType);
if (buildFieldMethod != null) {
buildFieldMethod.setAccessible(true);
}
return buildFieldMethod;
});
if (method != null) {
Object child = combinableArbitrary.combined();
if (child != null) {
Reflections.invokeMethod(method, builder, child);
}
}

return Reflections.invokeMethod(buildMethod, builder);
}
);

return Reflections.invokeMethod(buildMethod, builder);
});
return new ArbitraryIntrospectorResult(CombinableArbitrary.from(generateArbitrary));
}

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

if (builderMethod == null) {
throw new IllegalArgumentException(
"Class has no builder class. "
+ "type: " + objectType.getName() + " builderMethodName: " + builderMethodName
"Class has no builder class. " + "type: " + objectType.getName()
+ " builderMethodName: " + builderMethodName
);
}

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

private Class<?> getActualType(Property property) {
return Types.getActualType(getActualProperty(property).getType());
Property actualProperty = getActualProperty(property);

// The adapter may resolve a FieldProperty's type to a concrete type (e.g. ArrayList),
// but builder methods accept the field's declared type (e.g. List) as their parameter.
// If the concrete type is a subtype of the declared type, use the declared type instead.
if (actualProperty instanceof FieldProperty) {
Class<?> fieldDeclaredType = ((FieldProperty)actualProperty).getField().getType();
Class<?> propertyType = Types.getActualType(actualProperty.getType());
if (fieldDeclaredType != propertyType && fieldDeclaredType.isAssignableFrom(propertyType)) {
return fieldDeclaredType;
}
}

return Types.getActualType(actualProperty.getType());
}

private Property getActualProperty(Property property) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public final class PriorityConstructorArbitraryIntrospector implements Arbitrary
public static final PriorityConstructorArbitraryIntrospector INSTANCE =
new PriorityConstructorArbitraryIntrospector();

private static final Map<Property, ConstructorArbitraryIntrospector> CONSTRUCTOR_INTROSPECTORS_BY_PROPERTY =
private final Map<Property, ConstructorArbitraryIntrospector> constructorIntrospectorsByProperty =
new ConcurrentLruCache<>(256);

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

return CONSTRUCTOR_INTROSPECTORS_BY_PROPERTY.computeIfAbsent(
return constructorIntrospectorsByProperty.computeIfAbsent(
property,
p -> {
Constructor<?> constructor = TypeCache.getDeclaredConstructors(actualType).stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ public final class FixtureMonkeyOptions {
)
);
public static final int DEFAULT_MAX_UNIQUE_GENERATION_COUNT = 1_000;
public static final int DEFAULT_MAX_RECURSION_DEPTH = 5;
public static final List<MatcherOperator<NullInjectGenerator>> DEFAULT_NULL_INJECT_GENERATORS =
Collections.singletonList(
new MatcherOperator<>(
Expand Down Expand Up @@ -167,7 +168,20 @@ public final class FixtureMonkeyOptions {
private final InstantiatorProcessor instantiatorProcessor;
private final MatcherOperatorRetriever<CandidateConcretePropertyResolver> candidateConcretePropertyResolvers;
private final boolean enableLoggingFail;
private final int maxRecursionDepth;
private final List<TreeMatcherOperator<BuilderContextInitializer>> builderContextInitializers;
/**
* Declared as Object because the actual type (NodeTreeAdapter) is in the fixture-monkey module,
* which cannot be referenced from fixture-monkey-api due to module dependency constraints.
*/
@Nullable
private final Object nodeTreeAdapter;
/**
* Declared as Object because the actual type (AdapterTracer) is in the fixture-monkey module,
* which cannot be referenced from fixture-monkey-api due to module dependency constraints.
*/
@Nullable
private final Object adapterTracer;

public FixtureMonkeyOptions(
MatcherOperatorRetriever<PropertyGenerator> propertyGenerators,
Expand All @@ -190,7 +204,10 @@ public FixtureMonkeyOptions(
InstantiatorProcessor instantiatorProcessor,
MatcherOperatorRetriever<CandidateConcretePropertyResolver> candidateConcretePropertyResolvers,
boolean enableLoggingFail,
List<TreeMatcherOperator<BuilderContextInitializer>> builderContextCustomizer
int maxRecursionDepth,
List<TreeMatcherOperator<BuilderContextInitializer>> builderContextCustomizer,
@Nullable Object nodeTreeAdapter,
@Nullable Object adapterTracer
) {
this.propertyGenerators = propertyGenerators;
this.defaultPropertyGenerator = defaultPropertyGenerator;
Expand All @@ -212,7 +229,10 @@ public FixtureMonkeyOptions(
this.instantiatorProcessor = instantiatorProcessor;
this.candidateConcretePropertyResolvers = candidateConcretePropertyResolvers;
this.enableLoggingFail = enableLoggingFail;
this.maxRecursionDepth = maxRecursionDepth;
this.builderContextInitializers = builderContextCustomizer;
this.nodeTreeAdapter = nodeTreeAdapter;
this.adapterTracer = adapterTracer;
}

public static FixtureMonkeyOptionsBuilder builder() {
Expand Down Expand Up @@ -344,10 +364,37 @@ public boolean isEnableLoggingFail() {
return enableLoggingFail;
}


public int getMaxRecursionDepth() {
return maxRecursionDepth;
}

public List<TreeMatcherOperator<BuilderContextInitializer>> getBuilderContextInitializers() {
return builderContextInitializers;
}

/**
* Returns the node tree adapter for tree-based object generation.
*
* @return the adapter instance, or null if not configured
*/
@Nullable
@API(since = "1.1.0", status = Status.EXPERIMENTAL)
public Object getNodeTreeAdapter() {
return nodeTreeAdapter;
}

/**
* Returns the adapter tracer for debugging tree resolution.
*
* @return the tracer instance, or null if not configured
*/
@Nullable
@API(since = "1.1.0", status = Status.EXPERIMENTAL)
public Object getAdapterTracer() {
return adapterTracer;
}

public List<MatcherOperator<CandidateConcretePropertyResolver>> getCandidateConcretePropertyResolvers() {
return candidateConcretePropertyResolvers.getList();
}
Expand All @@ -361,6 +408,7 @@ public CandidateConcretePropertyResolver getCandidateConcretePropertyResolver(Pr
List<CandidateConcretePropertyResolver> candidateConcretePropertyResolverList =
this.candidateConcretePropertyResolvers.getListByProperty(property)
.stream()
.filter(it -> it.match(property))
.map(MatcherOperator::getOperator)
.collect(Collectors.toList());

Expand Down Expand Up @@ -388,6 +436,7 @@ public FixtureMonkeyOptionsBuilder toBuilder() {
.javaConstraintGenerator(javaConstraintGenerator)
.instantiatorProcessor(instantiatorProcessor)
.candidateConcretePropertyResolvers(new ArrayList<>(candidateConcretePropertyResolvers.getList()))
.maxRecursionDepth(maxRecursionDepth)
.builderContextInitializers(builderContextInitializers);
}

Expand Down
Loading
Loading