diff --git a/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/property/ConcreteTypeCandidateConcretePropertyResolver.java b/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/property/ConcreteTypeCandidateConcretePropertyResolver.java index ee625ed056..79a85238f3 100644 --- a/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/property/ConcreteTypeCandidateConcretePropertyResolver.java +++ b/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/property/ConcreteTypeCandidateConcretePropertyResolver.java @@ -20,7 +20,6 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedType; -import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Arrays; import java.util.List; @@ -32,6 +31,7 @@ import org.apiguardian.api.API; import org.apiguardian.api.API.Status; +import com.navercorp.fixturemonkey.api.type.GenericType; import com.navercorp.fixturemonkey.api.type.Types; @API(since = "1.0.16", status = Status.EXPERIMENTAL) @@ -53,23 +53,7 @@ public List resolve(Property property) { return concreteTypes.stream() .map(it -> { - Type concreteGenericType = new ParameterizedType() { - - @Override - public Type[] getActualTypeArguments() { - return typeArguments; - } - - @Override - public Type getRawType() { - return it; - } - - @Override - public Type getOwnerType() { - return null; - } - }; + Type concreteGenericType = new GenericType(it, typeArguments, null); AnnotatedType genericAnnotatedType = new AnnotatedType() { @Override @@ -125,24 +109,6 @@ public Optional getAnnotation(Class annotationClass public Object getValue(Object instance) { return property.getValue(instance); } - - @Override - public int hashCode() { - return getType().hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() == obj.getClass()) { - return false; - } - - Property that = (Property)obj; - return getType().equals(that.getType()); - } }; }) .collect(Collectors.toList()); diff --git a/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/type/GenericType.java b/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/type/GenericType.java new file mode 100644 index 0000000000..2003943a37 --- /dev/null +++ b/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/type/GenericType.java @@ -0,0 +1,95 @@ +/* + * 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.api.type; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Objects; + +import javax.annotation.Nullable; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +/** + * Represents a parameterized type with a container type, type arguments, and an optional owner type. + *

+ * It is mainly used for the type with type arguments. + * For example, {@code List}, {@code Map}, {@code CustomClass} + *

+ * This class is marked as experimental and may change in future releases. + */ +@API(since = "1.0.22", status = Status.EXPERIMENTAL) +public final class GenericType implements ParameterizedType { + private final Type containerType; + private final Type[] typeArguments; + @Nullable + private final Type ownerType; + + /** + * Constructs a new {@code GenericType} instance with the specified container type, resolved type arguments, + * and an optional owner type. + * + * @param containerType the container type + * @param resolvedTypeArguments the resolved type arguments + * @param ownerType the owner type, or {@code null} if there is no owner type + */ + public GenericType(Type containerType, Type[] resolvedTypeArguments, @Nullable Type ownerType) { + this.containerType = containerType; + this.ownerType = ownerType; + this.typeArguments = resolvedTypeArguments; + } + + @Override + public Type[] getActualTypeArguments() { + return typeArguments; + } + + @Override + public Type getRawType() { + return containerType; + } + + @Override + @Nullable + public Type getOwnerType() { + return this.ownerType; + } + + @Override + public int hashCode() { + return Objects.hash(containerType, ownerType, Arrays.hashCode(typeArguments)); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ParameterizedType)) { + return false; + } + + ParameterizedType that = (ParameterizedType)obj; + return this.containerType.equals(that.getRawType()) + && Arrays.equals(this.typeArguments, that.getActualTypeArguments()) + && Objects.equals(this.ownerType, that.getOwnerType()); + } +} diff --git a/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/type/Types.java b/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/type/Types.java index 5a1ba7547e..1f711eb332 100644 --- a/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/type/Types.java +++ b/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/type/Types.java @@ -287,22 +287,7 @@ private static AnnotatedType resolvesParentTypeGenerics( } ParameterizedType type = (ParameterizedType)fieldParameterizedType.getType(); - Type resolveType = new ParameterizedType() { - @Override - public Type[] getActualTypeArguments() { - return resolvedTypes; - } - - @Override - public Type getRawType() { - return type.getRawType(); - } - - @Override - public Type getOwnerType() { - return type.getOwnerType(); - } - }; + Type resolveType = new GenericType(type.getRawType(), resolvedTypes, type.getOwnerType()); return AnnotatedTypes.newAnnotatedParameterizedType( resolvedGenericsTypes, @@ -326,22 +311,11 @@ private static AnnotatedArrayType resolveGenericsArrayType( types[i] = ownerGenericsTypes[i].getType(); } - ParameterizedType genericComponentTypeWithGeneric = new ParameterizedType() { - @Override - public Type[] getActualTypeArguments() { - return types; - } - - @Override - public Type getRawType() { - return genericComponentType.getRawType(); - } - - @Override - public Type getOwnerType() { - return genericComponentType.getOwnerType(); - } - }; + ParameterizedType genericComponentTypeWithGeneric = new GenericType( + genericComponentType.getRawType(), + types, + genericComponentType.getOwnerType() + ); Type resolveType = (GenericArrayType)() -> genericComponentTypeWithGeneric; @@ -432,6 +406,9 @@ public static Class wrapperToPrimitive(final Class cls) { return wrapperPrimitiveMap.get(cls); } + /** + * It is same as {@code toClass.isAssignableFrom(cls)}. + */ public static boolean isAssignable(Class cls, Class toClass) { return isAssignable(cls, toClass, true); } diff --git a/fixture-monkey-tests/kotlin-tests/src/test/kotlin/com/navercorp/fixturemonkey/tests/kotlin/KotlinTest.kt b/fixture-monkey-tests/kotlin-tests/src/test/kotlin/com/navercorp/fixturemonkey/tests/kotlin/KotlinTest.kt index 21e03f34bd..2196e7f842 100644 --- a/fixture-monkey-tests/kotlin-tests/src/test/kotlin/com/navercorp/fixturemonkey/tests/kotlin/KotlinTest.kt +++ b/fixture-monkey-tests/kotlin-tests/src/test/kotlin/com/navercorp/fixturemonkey/tests/kotlin/KotlinTest.kt @@ -30,6 +30,7 @@ import com.navercorp.fixturemonkey.api.introspector.ConstructorPropertiesArbitra import com.navercorp.fixturemonkey.api.introspector.FactoryMethodArbitraryIntrospector import com.navercorp.fixturemonkey.api.introspector.FailoverIntrospector import com.navercorp.fixturemonkey.api.introspector.FieldReflectionArbitraryIntrospector +import com.navercorp.fixturemonkey.api.matcher.AssignableTypeMatcher import com.navercorp.fixturemonkey.api.matcher.MatcherOperator import com.navercorp.fixturemonkey.api.plugin.InterfacePlugin import com.navercorp.fixturemonkey.api.property.ConcreteTypeCandidateConcretePropertyResolver @@ -781,12 +782,12 @@ class KotlinTest { } @RepeatedTest(TEST_COUNT) - fun pushExactTypePropertyCandidateResolver() { + fun interfaceImplementsExtendsInterface() { val sut = FixtureMonkey.builder() .plugin(KotlinPlugin()) - .pushExactTypePropertyCandidateResolver( - Collection::class.java, - ConcreteTypeCandidateConcretePropertyResolver(listOf(Set::class.java)) + .plugin( + InterfacePlugin() + .interfaceImplements(Collection::class.java, listOf(Set::class.java)) ) .build() @@ -869,6 +870,25 @@ class KotlinTest { then(actual.lambda).isNotNull } + @Test + fun interfaceImplementsAssignableTypeGeneratesConcreteTypeNotThrows() { + val sut = FixtureMonkey.builder() + .plugin(KotlinPlugin()) + .plugin( + InterfacePlugin() + .interfaceImplements( + AssignableTypeMatcher(Collection::class.java), + listOf(LinkedList::class.java) + ) + + ) + .build() + + val actual: ArrayList = sut.giveMeOne() + + then(actual).isInstanceOf(ArrayList::class.java) + } + companion object { private val SUT: FixtureMonkey = FixtureMonkey.builder() .plugin(KotlinPlugin()) 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 3e249b119a..17da398f4c 100644 --- a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkeyBuilder.java +++ b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkeyBuilder.java @@ -515,6 +515,11 @@ public FixtureMonkeyBuilder seed(long seed) { return this; } + /** + * Use {@link InterfacePlugin#interfaceImplements(Class, List)} + * or {@link InterfacePlugin#abstractClassExtends(Class, List)} instead. + */ + @Deprecated public FixtureMonkeyBuilder pushExactTypePropertyCandidateResolver( Class type, CandidateConcretePropertyResolver candidateConcretePropertyResolver diff --git a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/tree/ArbitraryTraverser.java b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/tree/ArbitraryTraverser.java index 4126456450..66a71c9057 100644 --- a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/tree/ArbitraryTraverser.java +++ b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/tree/ArbitraryTraverser.java @@ -18,6 +18,7 @@ package com.navercorp.fixturemonkey.tree; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -323,6 +324,21 @@ private List resolveCandidateProperties(Property property) { List resolvedCandidateProperties = new ArrayList<>(); List candidateProperties = candidateConcretePropertyResolver.resolve(p); for (Property candidateProperty : candidateProperties) { + // compares by type until a specific property implementation is created for the generic type. + Type candidateType = candidateProperty.getType(); + + boolean assignableType = Types.isAssignable( + Types.getActualType(candidateType), + Types.getActualType(p.getType()) + ); + // if (p.getType().equals(candidateType) || !assignableType) { + if (p.getType().equals(candidateType) ) { + // prevents infinite recursion + resolvedCandidateProperties.addAll( + DefaultCandidateConcretePropertyResolver.INSTANCE.resolve(p) + ); + continue; + } resolvedCandidateProperties.addAll(resolveCandidateProperties(candidateProperty)); } return resolvedCandidateProperties;