diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java index 9ba42f6ec7..939266fcc3 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java @@ -146,6 +146,10 @@ public static Source createSource(URI uri, Optional sourceFileName, bool return new Source(uri, sourceFileName, md5InClassSourcesEnabled); } + public static ReferencedClassObject createReferencedClassObject(JavaCodeUnit codeUnit, JavaClass javaClass, int lineNumber) { + return ReferencedClassObject.from(codeUnit, javaClass, lineNumber); + } + public static ThrowsClause createThrowsClause(CODE_UNIT codeUnit, List types) { return ThrowsClause.from(codeUnit, types); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java index d48fed6bd7..8297ea54a7 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java @@ -648,6 +648,15 @@ public List> getTypeParameters() { return typeParameters; } + @PublicAPI(usage = ACCESS) + public Set getReferencedClassObjects() { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (JavaCodeUnit codeUnit : codeUnits) { + result.addAll(codeUnit.getReferencedClassObjects()); + } + return result.build(); + } + @Override @PublicAPI(usage = ACCESS) public JavaClass toErasure() { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java index f46b457033..3b059f7cb0 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaCodeUnit.java @@ -50,6 +50,7 @@ public abstract class JavaCodeUnit extends JavaMember implements HasParameterTyp private final JavaClass returnType; private final JavaClassList parameters; private final String fullName; + private final Set referencedClassObjects; private final Set instanceofChecks; private Set fieldAccesses = Collections.emptySet(); @@ -61,7 +62,8 @@ public abstract class JavaCodeUnit extends JavaMember implements HasParameterTyp this.returnType = builder.getReturnType(); this.parameters = builder.getParameters(); fullName = formatMethod(getOwner().getName(), getName(), getRawParameterTypes()); - instanceofChecks = builder.getInstanceofChecks(this); + referencedClassObjects = ImmutableSet.copyOf(builder.getReferencedClassObjects(this)); + instanceofChecks = ImmutableSet.copyOf(builder.getInstanceofChecks(this)); } /** @@ -112,6 +114,11 @@ public Set getConstructorCallsFromSelf() { return constructorCalls; } + @PublicAPI(usage = ACCESS) + public Set getReferencedClassObjects() { + return referencedClassObjects; + } + @PublicAPI(usage = ACCESS) public Set getInstanceofChecks() { return instanceofChecks; diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/ReferencedClassObject.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/ReferencedClassObject.java new file mode 100644 index 0000000000..1489f8e62b --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/ReferencedClassObject.java @@ -0,0 +1,102 @@ +/* + * Copyright 2014-2021 TNG Technology Consulting GmbH + * + * 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.tngtech.archunit.core.domain; + +import com.tngtech.archunit.PublicAPI; +import com.tngtech.archunit.base.ChainableFunction; +import com.tngtech.archunit.core.domain.properties.HasOwner; +import com.tngtech.archunit.core.domain.properties.HasSourceCodeLocation; +import com.tngtech.archunit.core.domain.properties.HasType; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; + +@PublicAPI(usage = ACCESS) +public final class ReferencedClassObject implements HasType, HasOwner, HasSourceCodeLocation { + private final JavaCodeUnit owner; + private final JavaClass value; + private final int lineNumber; + private final SourceCodeLocation sourceCodeLocation; + + private ReferencedClassObject(JavaCodeUnit owner, JavaClass value, int lineNumber) { + this.owner = checkNotNull(owner); + this.value = checkNotNull(value); + this.lineNumber = lineNumber; + sourceCodeLocation = SourceCodeLocation.of(owner.getOwner(), lineNumber); + } + + @Override + @PublicAPI(usage = ACCESS) + public JavaType getType() { + return getRawType(); + } + + @Override + @PublicAPI(usage = ACCESS) + public JavaClass getRawType() { + return value; + } + + @Override + @PublicAPI(usage = ACCESS) + public JavaCodeUnit getOwner() { + return owner; + } + + @PublicAPI(usage = ACCESS) + public JavaClass getValue() { + return value; + } + + @PublicAPI(usage = ACCESS) + public int getLineNumber() { + return lineNumber; + } + + @Override + @PublicAPI(usage = ACCESS) + public SourceCodeLocation getSourceCodeLocation() { + return sourceCodeLocation; + } + + @Override + public String toString() { + return toStringHelper(this) + .add("owner", owner) + .add("value", value) + .add("sourceCodeLocation", sourceCodeLocation) + .toString(); + } + + static ReferencedClassObject from(JavaCodeUnit owner, JavaClass javaClass, int lineNumber) { + return new ReferencedClassObject(owner, javaClass, lineNumber); + } + + @PublicAPI(usage = ACCESS) + public static final class Functions { + private Functions() { + } + + @PublicAPI(usage = ACCESS) + public static final ChainableFunction GET_VALUE = new ChainableFunction() { + @Override + public JavaClass apply(ReferencedClassObject input) { + return input.getValue(); + } + }; + } +} diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java index a50f751d12..edddcc2a7c 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java @@ -59,6 +59,7 @@ import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.JavaTypeVariable; import com.tngtech.archunit.core.domain.JavaWildcardType; +import com.tngtech.archunit.core.domain.ReferencedClassObject; import com.tngtech.archunit.core.domain.Source; import com.tngtech.archunit.core.domain.ThrowsClause; @@ -67,6 +68,7 @@ import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.completeTypeVariable; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createInstanceofCheck; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createJavaClassList; +import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createReferencedClassObject; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createSource; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createThrowsClause; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createTypeVariable; @@ -217,6 +219,7 @@ public abstract static class JavaCodeUnitBuilder parameters; private List throwsDeclarations; + private final Set rawReferencedClassObjects = new HashSet<>(); private final List instanceOfChecks = new ArrayList<>(); private JavaCodeUnitBuilder() { @@ -237,6 +240,11 @@ SELF withThrowsClause(List throwsDeclarations) { return self(); } + SELF addReferencedClassObject(RawReferencedClassObject rawReferencedClassObject) { + rawReferencedClassObjects.add(rawReferencedClassObject); + return self(); + } + SELF addInstanceOfCheck(RawInstanceofCheck rawInstanceOfChecks) { this.instanceOfChecks.add(rawInstanceOfChecks); return self(); @@ -262,6 +270,14 @@ public ThrowsClause getThrowsClause( return createThrowsClause(codeUnit, asJavaClasses(this.throwsDeclarations)); } + public Set getReferencedClassObjects(JavaCodeUnit codeUnit) { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (RawReferencedClassObject rawReferencedClassObject : this.rawReferencedClassObjects) { + result.add(createReferencedClassObject(codeUnit, get(rawReferencedClassObject.getClassName()), rawReferencedClassObject.getLineNumber())); + } + return result.build(); + } + public Set getInstanceofChecks(JavaCodeUnit codeUnit) { ImmutableSet.Builder result = ImmutableSet.builder(); for (RawInstanceofCheck instanceOfCheck : this.instanceOfChecks) { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassDescriptorImporter.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassDescriptorImporter.java index 5d317da71f..eb5bebb9d6 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassDescriptorImporter.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassDescriptorImporter.java @@ -27,10 +27,14 @@ class JavaClassDescriptorImporter { * i.e. java/lang/Object (note that this is not a descriptor like Ljava/lang/Object;) */ static JavaClassDescriptor createFromAsmObjectTypeName(String objectTypeName) { - return importAsmType(Type.getObjectType(objectTypeName)); + return JavaClassDescriptor.From.name(Type.getObjectType(objectTypeName).getClassName()); } - static JavaClassDescriptor importAsmType(Type type) { + static JavaClassDescriptor importAsmType(Object type) { + return importAsmType((Type) type); + } + + private static JavaClassDescriptor importAsmType(Type type) { return JavaClassDescriptor.From.name(type.getClassName()); } @@ -39,7 +43,7 @@ static boolean isAsmType(Object value) { } static Object importAsmTypeIfPossible(Object value) { - return isAsmType(value) ? importAsmType((Type) value) : value; + return isAsmType(value) ? importAsmType(value) : value; } static JavaClassDescriptor importAsmTypeFromDescriptor(String typeDescriptor) { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java index 4c3ffc3b7d..749d6b7328 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaClassProcessor.java @@ -318,6 +318,14 @@ public void visitLineNumber(int line, Label start) { accessHandler.setLineNumber(actualLineNumber); } + @Override + public void visitLdcInsn(Object value) { + if (JavaClassDescriptorImporter.isAsmType(value)) { + codeUnitBuilder.addReferencedClassObject( + RawReferencedClassObject.from(JavaClassDescriptorImporter.importAsmType(value), actualLineNumber)); + } + } + @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { accessHandler.handleFieldInstruction(opcode, owner, name, desc); diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/RawReferencedClassObject.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/RawReferencedClassObject.java new file mode 100644 index 0000000000..9d5b6a3b02 --- /dev/null +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/RawReferencedClassObject.java @@ -0,0 +1,51 @@ +/* + * Copyright 2014-2021 TNG Technology Consulting GmbH + * + * 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.tngtech.archunit.core.importer; + +import com.tngtech.archunit.core.domain.JavaClassDescriptor; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Preconditions.checkNotNull; + +class RawReferencedClassObject { + private final JavaClassDescriptor type; + private final int lineNumber; + + private RawReferencedClassObject(JavaClassDescriptor type, int lineNumber) { + this.type = checkNotNull(type); + this.lineNumber = lineNumber; + } + + static RawReferencedClassObject from(JavaClassDescriptor target, int lineNumber) { + return new RawReferencedClassObject(target, lineNumber); + } + + String getClassName() { + return type.getFullyQualifiedClassName(); + } + + int getLineNumber() { + return lineNumber; + } + + @Override + public String toString() { + return toStringHelper(this) + .add("type", type) + .add("lineNumber", lineNumber) + .toString(); + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/ReferencedClassObjectTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/ReferencedClassObjectTest.java new file mode 100644 index 0000000000..0e8cad2339 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/ReferencedClassObjectTest.java @@ -0,0 +1,30 @@ +package com.tngtech.archunit.core.domain; + +import java.io.File; + +import com.tngtech.archunit.core.importer.ClassFileImporter; +import org.junit.Test; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.tngtech.archunit.core.domain.ReferencedClassObject.Functions.GET_VALUE; +import static org.assertj.core.api.Assertions.assertThat; + +public class ReferencedClassObjectTest { + + @Test + public void function_getValue() { + class SomeClass { + @SuppressWarnings("unused") + Class call() { + return File.class; + } + } + + JavaClasses classes = new ClassFileImporter().importClasses(SomeClass.class, File.class); + JavaMethod owner = classes.get(SomeClass.class).getMethod("call"); + + ReferencedClassObject referencedClassObject = getOnlyElement(owner.getReferencedClassObjects()); + + assertThat(GET_VALUE.apply(referencedClassObject)).isEqualTo(classes.get(File.class)); + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java index de795b987f..f4f22fb5f6 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterTest.java @@ -1,6 +1,7 @@ package com.tngtech.archunit.core.importer; import java.io.File; +import java.io.FilterInputStream; import java.io.IOException; import java.io.PrintStream; import java.io.Serializable; @@ -12,6 +13,9 @@ import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; +import java.nio.Buffer; +import java.nio.charset.Charset; +import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -147,6 +151,7 @@ import com.tngtech.archunit.core.importer.testexamples.pathone.Class12; import com.tngtech.archunit.core.importer.testexamples.pathtwo.Class21; import com.tngtech.archunit.core.importer.testexamples.pathtwo.Class22; +import com.tngtech.archunit.core.importer.testexamples.referencedclassobjects.ReferencingClassObjects; import com.tngtech.archunit.core.importer.testexamples.simpleimport.AnnotationParameter; import com.tngtech.archunit.core.importer.testexamples.simpleimport.AnnotationToImport; import com.tngtech.archunit.core.importer.testexamples.simpleimport.ClassToImportOne; @@ -159,6 +164,7 @@ import com.tngtech.archunit.testutil.ArchConfigurationRule; import com.tngtech.archunit.testutil.LogTestRule; import com.tngtech.archunit.testutil.OutsideOfClassPathRule; +import com.tngtech.archunit.testutil.assertion.ReferencedClassObjectsAssertion.ExpectedReferencedClassObject; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; @@ -174,6 +180,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Predicates.containsPattern; import static com.google.common.base.Predicates.not; +import static com.google.common.collect.Iterables.concat; import static com.google.common.collect.Iterables.getFirst; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.Sets.newHashSet; @@ -200,12 +207,14 @@ import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.archunit.testutil.Assertions.assertThatAccess; import static com.tngtech.archunit.testutil.Assertions.assertThatCall; +import static com.tngtech.archunit.testutil.Assertions.assertThatReferencedClassObjects; import static com.tngtech.archunit.testutil.Assertions.assertThatType; import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static com.tngtech.archunit.testutil.ReflectionTestUtils.constructor; import static com.tngtech.archunit.testutil.ReflectionTestUtils.field; import static com.tngtech.archunit.testutil.ReflectionTestUtils.method; import static com.tngtech.archunit.testutil.TestUtils.namesOf; +import static com.tngtech.archunit.testutil.assertion.ReferencedClassObjectsAssertion.referencedClassObject; import static com.tngtech.java.junit.dataprovider.DataProviders.$; import static com.tngtech.java.junit.dataprovider.DataProviders.$$; import static com.tngtech.java.junit.dataprovider.DataProviders.testForEach; @@ -836,6 +845,31 @@ public void imports_overridden_methods_correctly() throws Exception { assertThat(subclass.getCodeUnitWithParameterTypes("getSomeField").getModifiers()).containsOnly(PUBLIC); } + @Test + public void imports_referenced_class_objects() { + JavaClass javaClass = new ClassFileImporter().importClass(ReferencingClassObjects.class); + + Set expectedInConstructor = + ImmutableSet.of(referencedClassObject(File.class, 19), referencedClassObject(Path.class, 19)); + Set expectedInMethod = + ImmutableSet.of(referencedClassObject(FileSystem.class, 22), referencedClassObject(Charset.class, 22)); + Set expectedInStaticInitializer = + ImmutableSet.of(referencedClassObject(FilterInputStream.class, 16), referencedClassObject(Buffer.class, 16)); + + assertThatReferencedClassObjects(javaClass.getConstructor().getReferencedClassObjects()) + .hasSize(2) + .containReferencedClassObjects(expectedInConstructor); + assertThatReferencedClassObjects(javaClass.getMethod("referencedClassObjectsInMethod").getReferencedClassObjects()) + .hasSize(2) + .containReferencedClassObjects(expectedInMethod); + assertThatReferencedClassObjects(javaClass.getStaticInitializer().get().getReferencedClassObjects()) + .hasSize(2) + .containReferencedClassObjects(expectedInStaticInitializer); + assertThatReferencedClassObjects(javaClass.getReferencedClassObjects()) + .hasSize(6) + .containReferencedClassObjects(concat(expectedInConstructor, expectedInMethod, expectedInStaticInitializer)); + } + @Test public void imports_own_get_field_access() throws Exception { JavaClass classWithOwnFieldAccess = classesIn("testexamples/fieldaccessimport").get(OwnFieldAccess.class); diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/referencedclassobjects/ReferencingClassObjects.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/referencedclassobjects/ReferencingClassObjects.java new file mode 100644 index 0000000000..fd643bfad3 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/referencedclassobjects/ReferencingClassObjects.java @@ -0,0 +1,24 @@ +package com.tngtech.archunit.core.importer.testexamples.referencedclassobjects; + +import java.io.File; +import java.io.FilterInputStream; +import java.nio.Buffer; +import java.nio.charset.Charset; +import java.nio.file.FileSystem; +import java.nio.file.Path; +import java.util.List; + +import com.google.common.collect.ImmutableList; + +@SuppressWarnings({"FieldMayBeFinal", "unused"}) +public class ReferencingClassObjects { + static { + List> referencedClassObjectsInStaticInitializer = ImmutableList.of(FilterInputStream.class, Buffer.class); + } + + List> referencedClassObjectsInConstructor = ImmutableList.>of(File.class, Path.class); + + List> referencedClassObjectsInMethod() { + return ImmutableList.of(FileSystem.class, Charset.class); + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java b/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java index adee9f10c9..162bc4d55c 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java @@ -33,6 +33,7 @@ import com.tngtech.archunit.core.domain.JavaPackage; import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.JavaTypeVariable; +import com.tngtech.archunit.core.domain.ReferencedClassObject; import com.tngtech.archunit.core.domain.ThrowsClause; import com.tngtech.archunit.core.domain.ThrowsDeclaration; import com.tngtech.archunit.lang.ArchCondition; @@ -58,6 +59,7 @@ import com.tngtech.archunit.testutil.assertion.JavaTypeAssertion; import com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion; import com.tngtech.archunit.testutil.assertion.JavaTypesAssertion; +import com.tngtech.archunit.testutil.assertion.ReferencedClassObjectsAssertion; import org.assertj.core.api.AbstractIterableAssert; import org.assertj.core.api.AbstractListAssert; import org.assertj.core.api.AbstractObjectAssert; @@ -161,6 +163,10 @@ public static JavaFieldAssertion assertThat(JavaField field) { return new JavaFieldAssertion(field); } + public static ReferencedClassObjectsAssertion assertThatReferencedClassObjects(Set referencedClassObjects) { + return new ReferencedClassObjectsAssertion(referencedClassObjects); + } + public static JavaEnumConstantAssertion assertThat(JavaEnumConstant enumConstant) { return new JavaEnumConstantAssertion(enumConstant); } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ReferencedClassObjectsAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ReferencedClassObjectsAssertion.java new file mode 100644 index 0000000000..ae7392b9c6 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/ReferencedClassObjectsAssertion.java @@ -0,0 +1,69 @@ +package com.tngtech.archunit.testutil.assertion; + +import java.util.Set; + +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; +import com.tngtech.archunit.core.domain.ReferencedClassObject; +import org.assertj.core.api.AbstractIterableAssert; +import org.assertj.core.api.AbstractObjectAssert; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static org.assertj.core.api.Assertions.assertThat; + +public class ReferencedClassObjectsAssertion extends AbstractIterableAssert, ReferencedClassObject, ReferencedClassObjectsAssertion.ReferencedClassObjectAssertion> { + public ReferencedClassObjectsAssertion(Set referencedClassObjects) { + super(referencedClassObjects, ReferencedClassObjectsAssertion.class); + } + + @Override + protected ReferencedClassObjectAssertion toAssert(ReferencedClassObject value, String description) { + return new ReferencedClassObjectAssertion(value).as(description); + } + + public void containReferencedClassObjects(Iterable expectedReferencedClassObjects) { + final FluentIterable actualReferencedClassObjects = FluentIterable.from(actual); + Set unmatchedClassObjects = FluentIterable.from(expectedReferencedClassObjects) + .filter(new Predicate() { + @Override + public boolean apply(ExpectedReferencedClassObject expectedReferencedClassObject) { + return !actualReferencedClassObjects.anyMatch(expectedReferencedClassObject); + } + }).toSet(); + assertThat(unmatchedClassObjects).as("Referenced class objects not contained in " + actual).isEmpty(); + } + + static class ReferencedClassObjectAssertion extends AbstractObjectAssert { + public ReferencedClassObjectAssertion(ReferencedClassObject referencedClassObject) { + super(referencedClassObject, ReferencedClassObjectAssertion.class); + } + } + + public static ExpectedReferencedClassObject referencedClassObject(Class type, int lineNumber) { + return new ExpectedReferencedClassObject(type, lineNumber); + } + + public static class ExpectedReferencedClassObject implements Predicate { + private final Class type; + private final int lineNumber; + + private ExpectedReferencedClassObject(Class type, int lineNumber) { + this.type = type; + this.lineNumber = lineNumber; + } + + @Override + @SuppressWarnings("ConstantConditions") + public boolean apply(ReferencedClassObject input) { + return input.getValue().isEquivalentTo(type) && input.getLineNumber() == lineNumber; + } + + @Override + public String toString() { + return toStringHelper(this) + .add("type", type) + .add("lineNumber", lineNumber) + .toString(); + } + } +}