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 049110c379..0eb6344e5b 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 @@ -180,8 +180,8 @@ public static ThrowsClause createThr return ThrowsClause.from(codeUnit, types); } - public static InstanceofCheck createInstanceofCheck(JavaCodeUnit codeUnit, JavaClass target, int lineNumber) { - return InstanceofCheck.from(codeUnit, target, lineNumber); + public static InstanceofCheck createInstanceofCheck(JavaCodeUnit codeUnit, JavaClass type, int lineNumber, boolean declaredInLambda) { + return InstanceofCheck.from(codeUnit, type, lineNumber, declaredInLambda); } public static JavaTypeVariable createTypeVariable(String name, OWNER owner, JavaClass erasure) { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java index 24669dfc0a..68bfb5e047 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java @@ -65,5 +65,7 @@ public interface ImportContext { Set createReferencedClassObjectsFor(JavaCodeUnit codeUnit); + Set createInstanceofChecksFor(JavaCodeUnit codeUnit); + JavaClass resolveClass(String fullyQualifiedClassName); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/InstanceofCheck.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/InstanceofCheck.java index d87f792662..7630e0dc7c 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/InstanceofCheck.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/InstanceofCheck.java @@ -28,27 +28,29 @@ public final class InstanceofCheck implements HasType, HasOwner, HasSourceCodeLocation { private final JavaCodeUnit owner; - private final JavaClass target; + private final JavaClass type; private final int lineNumber; + private final boolean declaredInLambda; private final SourceCodeLocation sourceCodeLocation; - private InstanceofCheck(JavaCodeUnit owner, JavaClass target, int lineNumber) { + private InstanceofCheck(JavaCodeUnit owner, JavaClass type, int lineNumber, boolean declaredInLambda) { this.owner = checkNotNull(owner); - this.target = checkNotNull(target); + this.type = checkNotNull(type); this.lineNumber = lineNumber; + this.declaredInLambda = declaredInLambda; sourceCodeLocation = SourceCodeLocation.of(owner.getOwner(), lineNumber); } @Override @PublicAPI(usage = ACCESS) public JavaClass getRawType() { - return target; + return type; } @Override @PublicAPI(usage = ACCESS) public JavaType getType() { - return target; + return type; } @Override @@ -62,6 +64,11 @@ public int getLineNumber() { return lineNumber; } + @PublicAPI(usage = ACCESS) + public boolean isDeclaredInLambda() { + return declaredInLambda; + } + @Override public SourceCodeLocation getSourceCodeLocation() { return sourceCodeLocation; @@ -71,12 +78,13 @@ public SourceCodeLocation getSourceCodeLocation() { public String toString() { return toStringHelper(this) .add("owner", owner) - .add("target", target) + .add("type", type) .add("lineNumber", lineNumber) + .add("declaredInLambda", declaredInLambda) .toString(); } - static InstanceofCheck from(JavaCodeUnit owner, JavaClass target, int lineNumber) { - return new InstanceofCheck(owner, target, lineNumber); + static InstanceofCheck from(JavaCodeUnit owner, JavaClass type, int lineNumber, boolean declaredInLambda) { + return new InstanceofCheck(owner, type, lineNumber, declaredInLambda); } } 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 57863e28d6..0ba56ce57d 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 @@ -63,7 +63,6 @@ public abstract class JavaCodeUnit private final Parameters parameters; private final String fullName; private final List> typeParameters; - private final Set instanceofChecks; private Set fieldAccesses = Collections.emptySet(); private Set methodCalls = Collections.emptySet(); @@ -72,6 +71,7 @@ public abstract class JavaCodeUnit private Set constructorReferences = Collections.emptySet(); private Set tryCatchBlocks = Collections.emptySet(); private Set referencedClassObjects; + private Set instanceofChecks; JavaCodeUnit(JavaCodeUnitBuilder builder) { super(builder); @@ -79,7 +79,6 @@ public abstract class JavaCodeUnit returnType = new ReturnType(this, builder); parameters = new Parameters(this, builder); fullName = formatMethod(getOwner().getName(), getName(), namesOf(getRawParameterTypes())); - instanceofChecks = ImmutableSet.copyOf(builder.getInstanceofChecks(this)); } /** @@ -281,6 +280,7 @@ void completeFrom(ImportContext context) { .map(builder -> builder.build(this, context)) .collect(toImmutableSet()); referencedClassObjects = context.createReferencedClassObjectsFor(this); + instanceofChecks = context.createInstanceofChecksFor(this); } @ResolvesTypesViaReflection diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java index 58ccd18bc4..101e235227 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java @@ -85,6 +85,7 @@ class ClassFileImportRecord { private final Set rawMethodReferenceRecords = new HashSet<>(); private final Set rawConstructorReferenceRecords = new HashSet<>(); private final Set rawReferencedClassObjects = new HashSet<>(); + private final Set rawInstanceofChecks = new HashSet<>(); private final SyntheticAccessRecorder syntheticLambdaAccessRecorder = createSyntheticLambdaAccessRecorder(); private final SyntheticAccessRecorder syntheticPrivateAccessRecorder = createSyntheticPrivateAccessRecorder(); @@ -251,6 +252,10 @@ void registerReferencedClassObject(RawReferencedClassObject referencedClassObjec rawReferencedClassObjects.add(referencedClassObject); } + void registerInstanceofCheck(RawInstanceofCheck instanceofCheck) { + rawInstanceofChecks.add(instanceofCheck); + } + void forEachRawFieldAccessRecord(Consumer doWithRecord) { fixSyntheticOrigins( rawFieldAccessRecords, COPY_RAW_FIELD_ACCESS_RECORD, @@ -293,6 +298,13 @@ void forEachRawReferencedClassObject(Consumer doWithRe ).forEach(doWithReferencedClassObject); } + void forEachRawInstanceofCheck(Consumer doWithInstanceofCheck) { + fixSyntheticOrigins( + rawInstanceofChecks, COPY_RAW_INSTANCEOF_CHECK, + syntheticLambdaAccessRecorder + ).forEach(doWithInstanceofCheck); + } + private > Stream fixSyntheticOrigins( Set rawAccessRecordsIncludingSyntheticAccesses, Function> createAccessWithNewOrigin, @@ -324,6 +336,9 @@ Map getClasses() { private static final Function COPY_RAW_REFERENCED_CLASS_OBJECT = referencedClassObject -> copyInto(new RawReferencedClassObject.Builder(), referencedClassObject); + private static final Function COPY_RAW_INSTANCEOF_CHECK = + instanceofCheck -> copyInto(new RawInstanceofCheck.Builder(), instanceofCheck); + private static > BUILDER copyInto(BUILDER builder, RawCodeUnitDependency referencedClassObject) { builder .withOrigin(referencedClassObject.getOrigin()) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java index 2b285f16ca..57b461b041 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileProcessor.java @@ -287,6 +287,16 @@ public void handleReferencedClassObject(JavaClassDescriptor type, int lineNumber .build()); } + @Override + public void handleInstanceofCheck(JavaClassDescriptor instanceOfCheckType, int lineNumber) { + importRecord.registerInstanceofCheck(new RawInstanceofCheck.Builder() + .withOrigin(codeUnit) + .withTarget(instanceOfCheckType) + .withLineNumber(lineNumber) + .withDeclaredInLambda(false) + .build()); + } + @Override public void handleTryCatchBlock(Label start, Label end, Label handler, JavaClassDescriptor throwableType) { LOG.trace("Found try/catch block between {} and {} for throwable {}", start, end, throwableType); diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java index 33940b7177..476ff9bdf2 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java @@ -34,6 +34,7 @@ import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget; import com.tngtech.archunit.core.domain.AccessTarget.MethodReferenceTarget; import com.tngtech.archunit.core.domain.ImportContext; +import com.tngtech.archunit.core.domain.InstanceofCheck; import com.tngtech.archunit.core.domain.JavaAccess; import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; @@ -72,6 +73,7 @@ import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.completeGenericSuperclass; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.completeMembers; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.completeTypeParameters; +import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createInstanceofCheck; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createJavaClasses; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createReferencedClassObject; import static com.tngtech.archunit.core.importer.DomainBuilders.BuilderWithBuildParameter.BuildFinisher.build; @@ -91,6 +93,7 @@ class ClassGraphCreator implements ImportContext { private final SetMultimap> processedMethodReferenceRecords = HashMultimap.create(); private final SetMultimap> processedConstructorReferenceRecords = HashMultimap.create(); private final SetMultimap processedReferencedClassObjects = HashMultimap.create(); + private final SetMultimap processedInstanceofChecks = HashMultimap.create(); ClassGraphCreator(ClassFileImportRecord importRecord, DependencyResolutionProcess dependencyResolutionProcess, ClassResolver classResolver) { this.importRecord = importRecord; @@ -129,6 +132,7 @@ private void completeCodeUnitDependencies() { importRecord.forEachRawConstructorReferenceRecord(record -> tryProcess(record, AccessRecord.Factory.forConstructorReferenceRecord(), processedConstructorReferenceRecords)); importRecord.forEachRawReferencedClassObject(this::processReferencedClassObject); + importRecord.forEachRawInstanceofCheck(this::processInstanceofCheck); } private , B extends RawAccessRecord> void tryProcess( @@ -151,6 +155,17 @@ private void processReferencedClassObject(RawReferencedClassObject rawReferenced processedReferencedClassObjects.put(origin, referencedClassObject); } + private void processInstanceofCheck(RawInstanceofCheck rawInstanceofCheck) { + JavaCodeUnit origin = rawInstanceofCheck.getOrigin().resolveFrom(classes); + InstanceofCheck instanceofCheck = createInstanceofCheck( + origin, + classes.getOrResolve(rawInstanceofCheck.getTarget().getFullyQualifiedClassName()), + rawInstanceofCheck.getLineNumber(), + rawInstanceofCheck.isDeclaredInLambda() + ); + processedInstanceofChecks.put(origin, instanceofCheck); + } + @Override public Set createFieldAccessesFor(JavaCodeUnit codeUnit, Set tryCatchBlockBuilders) { ImmutableSet.Builder result = ImmutableSet.builder(); @@ -354,6 +369,11 @@ public Set createReferencedClassObjectsFor(JavaCodeUnit c return ImmutableSet.copyOf(processedReferencedClassObjects.get(codeUnit)); } + @Override + public Set createInstanceofChecksFor(JavaCodeUnit codeUnit) { + return ImmutableSet.copyOf(processedInstanceofChecks.get(codeUnit)); + } + @Override public JavaClass resolveClass(String fullyQualifiedClassName) { return classes.getOrResolve(fullyQualifiedClassName); 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 4b1e99f4ea..1eaa2518c8 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 @@ -44,7 +44,6 @@ import com.tngtech.archunit.core.domain.DomainObjectCreationContext; import com.tngtech.archunit.core.domain.Formatters; import com.tngtech.archunit.core.domain.ImportContext; -import com.tngtech.archunit.core.domain.InstanceofCheck; import com.tngtech.archunit.core.domain.JavaAccess; import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; @@ -80,7 +79,6 @@ import static com.google.common.collect.Sets.union; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.completeTypeVariable; import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createGenericArrayType; -import static com.tngtech.archunit.core.domain.DomainObjectCreationContext.createInstanceofCheck; 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.createTryCatchBlock; @@ -248,7 +246,6 @@ public abstract static class JavaCodeUnitBuilder parameterAnnotationsByIndex; private JavaCodeUnitTypeParametersBuilder typeParametersBuilder; private List throwsDeclarations; - private final List instanceOfChecks = new ArrayList<>(); private JavaCodeUnitBuilder() { } @@ -280,11 +277,6 @@ SELF withThrowsClause(List throwsDeclarations) { return self(); } - SELF addInstanceOfCheck(RawInstanceofCheck rawInstanceOfChecks) { - this.instanceOfChecks.add(rawInstanceOfChecks); - return self(); - } - String getReturnTypeName() { return rawReturnType.getFullyQualifiedClassName(); } @@ -331,14 +323,6 @@ public ThrowsClause getThrowsClause( return createThrowsClause(codeUnit, asJavaClasses(this.throwsDeclarations)); } - public Set getInstanceofChecks(JavaCodeUnit codeUnit) { - ImmutableSet.Builder result = ImmutableSet.builder(); - for (RawInstanceofCheck instanceOfCheck : this.instanceOfChecks) { - result.add(createInstanceofCheck(codeUnit, get(instanceOfCheck.getTarget().getFullyQualifiedClassName()), instanceOfCheck.getLineNumber())); - } - return result.build(); - } - private List asJavaClasses(List descriptors) { ImmutableList.Builder result = ImmutableList.builder(); for (JavaClassDescriptor javaClassDescriptor : descriptors) { 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 6624e6b123..ca6973d972 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 @@ -71,7 +71,6 @@ import static com.tngtech.archunit.core.importer.JavaClassDescriptorImporter.isAsmMethodHandle; import static com.tngtech.archunit.core.importer.JavaClassDescriptorImporter.isLambdaMetafactory; import static com.tngtech.archunit.core.importer.JavaClassDescriptorImporter.isLambdaMethod; -import static com.tngtech.archunit.core.importer.RawInstanceofCheck.from; import static java.util.Arrays.stream; import static java.util.stream.Collectors.toList; @@ -401,7 +400,7 @@ public void visitMethodInsn(int opcode, String owner, String name, String desc, public void visitTypeInsn(int opcode, String type) { if (opcode == Opcodes.INSTANCEOF) { JavaClassDescriptor instanceOfCheckType = JavaClassDescriptorImporter.createFromAsmObjectTypeName(type); - codeUnitBuilder.addInstanceOfCheck(from(instanceOfCheckType, actualLineNumber)); + accessHandler.handleInstanceofCheck(instanceOfCheckType, actualLineNumber); declarationHandler.onDeclaredInstanceofCheck(instanceOfCheckType.getFullyQualifiedClassName()); } } @@ -537,6 +536,8 @@ interface AccessHandler { void handleReferencedClassObject(JavaClassDescriptor type, int lineNumber); + void handleInstanceofCheck(JavaClassDescriptor instanceOfCheckType, int lineNumber); + void handleTryCatchBlock(Label start, Label end, Label handler, JavaClassDescriptor throwableType); void handleTryFinallyBlock(Label start, Label end, Label handler); @@ -577,6 +578,10 @@ public void handleLambdaInstruction(String owner, String name, String desc) { public void handleReferencedClassObject(JavaClassDescriptor type, int lineNumber) { } + @Override + public void handleInstanceofCheck(JavaClassDescriptor instanceOfCheckType, int lineNumber) { + } + @Override public void handleTryCatchBlock(Label start, Label end, Label handler, JavaClassDescriptor throwableType) { } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/RawInstanceofCheck.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/RawInstanceofCheck.java index 021c9b33ee..df8b7277b0 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/RawInstanceofCheck.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/RawInstanceofCheck.java @@ -20,32 +20,82 @@ import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkNotNull; -class RawInstanceofCheck { +class RawInstanceofCheck implements RawCodeUnitDependency { + private final RawAccessRecord.CodeUnit origin; private final JavaClassDescriptor target; private final int lineNumber; + private final boolean declaredInLambda; - private RawInstanceofCheck(JavaClassDescriptor target, int lineNumber) { + private RawInstanceofCheck(RawAccessRecord.CodeUnit origin, JavaClassDescriptor target, int lineNumber, boolean declaredInLambda) { + this.origin = checkNotNull(origin); this.target = checkNotNull(target); this.lineNumber = lineNumber; + this.declaredInLambda = declaredInLambda; } - static RawInstanceofCheck from(JavaClassDescriptor target, int lineNumber) { - return new RawInstanceofCheck(target, lineNumber); + @Override + public RawAccessRecord.CodeUnit getOrigin() { + return origin; } - JavaClassDescriptor getTarget() { + @Override + public JavaClassDescriptor getTarget() { return target; } - int getLineNumber() { + @Override + public int getLineNumber() { return lineNumber; } + @Override + public boolean isDeclaredInLambda() { + return declaredInLambda; + } + @Override public String toString() { return toStringHelper(this) + .add("origin", origin) .add("target", target) .add("lineNumber", lineNumber) + .add("declaredInLambda", declaredInLambda) .toString(); } + + static class Builder implements RawCodeUnitDependencyBuilder { + private RawAccessRecord.CodeUnit origin; + private JavaClassDescriptor target; + private int lineNumber; + private boolean declaredInLambda; + + @Override + public RawInstanceofCheck.Builder withOrigin(RawAccessRecord.CodeUnit origin) { + this.origin = origin; + return this; + } + + @Override + public RawInstanceofCheck.Builder withTarget(JavaClassDescriptor target) { + this.target = target; + return this; + } + + @Override + public RawInstanceofCheck.Builder withLineNumber(int lineNumber) { + this.lineNumber = lineNumber; + return this; + } + + @Override + public RawInstanceofCheck.Builder withDeclaredInLambda(boolean declaredInLambda) { + this.declaredInLambda = declaredInLambda; + return this; + } + + @Override + public RawInstanceofCheck build() { + return new RawInstanceofCheck(origin, target, lineNumber, declaredInLambda); + } + } } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterLambdaDependenciesTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterLambdaDependenciesTest.java index f3ee7651f9..5327166165 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterLambdaDependenciesTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterLambdaDependenciesTest.java @@ -9,6 +9,7 @@ import java.util.function.Supplier; import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget; +import com.tngtech.archunit.core.domain.InstanceofCheck; import com.tngtech.archunit.core.domain.JavaAccess; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; @@ -19,6 +20,7 @@ import com.tngtech.archunit.core.domain.JavaMethodCall; import com.tngtech.archunit.core.domain.JavaMethodReference; import com.tngtech.archunit.core.domain.ReferencedClassObject; +import com.tngtech.archunit.core.importer.testexamples.instanceofcheck.CheckingInstanceofFromLambda; import com.tngtech.archunit.core.importer.testexamples.referencedclassobjects.ReferencingClassObjectsFromLambda; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; @@ -36,8 +38,10 @@ import static com.tngtech.archunit.testutil.Assertions.assertThatAccess; import static com.tngtech.archunit.testutil.Assertions.assertThatAccesses; import static com.tngtech.archunit.testutil.Assertions.assertThatCall; +import static com.tngtech.archunit.testutil.Assertions.assertThatInstanceofChecks; import static com.tngtech.archunit.testutil.Assertions.assertThatReferencedClassObjects; import static com.tngtech.archunit.testutil.assertion.AccessesAssertion.access; +import static com.tngtech.archunit.testutil.assertion.InstanceofChecksAssertion.instanceofCheck; 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.$$; @@ -527,6 +531,17 @@ public void imports_referenced_class_object_in_lambda() { ); } + @Test + public void imports_instanceof_checks_in_lambda() { + JavaClasses classes = new ClassFileImporter().importClasses(CheckingInstanceofFromLambda.class); + Set instanceofChecks = classes.get(CheckingInstanceofFromLambda.class).getInstanceofChecks(); + + assertThatInstanceofChecks(instanceofChecks).containInstanceofChecks( + instanceofCheck(FilterInputStream.class, 11).declaredInLambda(), + instanceofCheck(File.class, 15).declaredInLambda() + ); + } + private Condition syntheticLambdaMethods() { return new Condition<>(method -> isLambdaMethodName(method.getName()), "synthetic lambda methods"); } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterMembersTest.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterMembersTest.java index 13f1a90515..9661deb159 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterMembersTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ClassFileImporterMembersTest.java @@ -36,13 +36,14 @@ import com.tngtech.archunit.core.importer.testexamples.instanceofcheck.ChecksInstanceofInConstructor; import com.tngtech.archunit.core.importer.testexamples.instanceofcheck.ChecksInstanceofInMethod; import com.tngtech.archunit.core.importer.testexamples.instanceofcheck.ChecksInstanceofInStaticInitializer; +import com.tngtech.archunit.core.importer.testexamples.instanceofcheck.ChecksMultipleInstanceofs; import com.tngtech.archunit.core.importer.testexamples.instanceofcheck.InstanceofChecked; import com.tngtech.archunit.core.importer.testexamples.methodimport.ClassWithMultipleMethods; import com.tngtech.archunit.core.importer.testexamples.methodimport.ClassWithObjectVoidAndIntIntSerializableMethod; import com.tngtech.archunit.core.importer.testexamples.methodimport.ClassWithStringStringMethod; import com.tngtech.archunit.core.importer.testexamples.methodimport.ClassWithThrowingMethod; import com.tngtech.archunit.core.importer.testexamples.referencedclassobjects.ReferencingClassObjects; -import com.tngtech.archunit.testutil.assertion.ReferencedClassObjectsAssertion; +import com.tngtech.archunit.testutil.assertion.ReferencedClassObjectsAssertion.ExpectedReferencedClassObject; import org.assertj.core.util.Objects; import org.junit.Test; @@ -61,11 +62,13 @@ import static com.tngtech.archunit.core.importer.ClassFileImporterTestUtils.getFields; import static com.tngtech.archunit.core.importer.ClassFileImporterTestUtils.getMethods; import static com.tngtech.archunit.testutil.Assertions.assertThat; +import static com.tngtech.archunit.testutil.Assertions.assertThatInstanceofChecks; import static com.tngtech.archunit.testutil.Assertions.assertThatReferencedClassObjects; import static com.tngtech.archunit.testutil.Assertions.assertThatThrowsClause; import static com.tngtech.archunit.testutil.Assertions.assertThatType; import static com.tngtech.archunit.testutil.Assertions.assertThatTypes; import static com.tngtech.archunit.testutil.ReflectionTestUtils.field; +import static com.tngtech.archunit.testutil.assertion.InstanceofChecksAssertion.instanceofCheck; import static com.tngtech.archunit.testutil.assertion.ReferencedClassObjectsAssertion.referencedClassObject; import static java.util.stream.Collectors.toSet; @@ -267,11 +270,11 @@ public void imports_overridden_methods_correctly() { public void imports_referenced_class_objects() { JavaClass javaClass = new ClassFileImporter().importClass(ReferencingClassObjects.class); - Set expectedInConstructor = + Set expectedInConstructor = ImmutableSet.of(referencedClassObject(File.class, 19), referencedClassObject(Path.class, 19)); - Set expectedInMethod = + Set expectedInMethod = ImmutableSet.of(referencedClassObject(FileSystem.class, 22), referencedClassObject(Charset.class, 22)); - Set expectedInStaticInitializer = + Set expectedInStaticInitializer = ImmutableSet.of(referencedClassObject(FilterInputStream.class, 16), referencedClassObject(Buffer.class, 16)); assertThatReferencedClassObjects(javaClass.getConstructor().getReferencedClassObjects()) @@ -288,6 +291,22 @@ public void imports_referenced_class_objects() { .containReferencedClassObjects(concat(expectedInConstructor, expectedInMethod, expectedInStaticInitializer)); } + @Test + public void imports_instanceof_checks() { + assertThatInstanceofChecks(new ClassFileImporter().importClass(ChecksInstanceofInConstructor.class).getConstructor(Object.class).getInstanceofChecks()) + .containInstanceofChecks(instanceofCheck(InstanceofChecked.class, 6)); + assertThatInstanceofChecks(new ClassFileImporter().importClass(ChecksInstanceofInMethod.class).getMethod("method", Object.class).getInstanceofChecks()) + .containInstanceofChecks(instanceofCheck(InstanceofChecked.class, 6)); + assertThatInstanceofChecks(new ClassFileImporter().importClass(ChecksInstanceofInStaticInitializer.class).getStaticInitializer().get().getInstanceofChecks()) + .containInstanceofChecks(instanceofCheck(InstanceofChecked.class, 6)); + assertThatInstanceofChecks(new ClassFileImporter().importClass(ChecksMultipleInstanceofs.class).getInstanceofChecks()) + .hasSize(3) + .containInstanceofChecks( + instanceofCheck(InstanceofChecked.class, 6), + instanceofCheck(InstanceofChecked.class, 10), + instanceofCheck(InstanceofChecked.class, 15)); + } + @Test public void classes_know_which_fields_have_their_type() { JavaClasses classes = new ClassFileImporter().importClasses(SomeClass.class, OtherClass.class, SomeEnum.class); @@ -348,6 +367,10 @@ public void classes_know_which_instanceof_checks_check_their_type() { .map(instanceofCheck -> instanceofCheck.getOwner().getOwner()) .collect(toSet()); - assertThatTypes(origins).matchInAnyOrder(ChecksInstanceofInMethod.class, ChecksInstanceofInConstructor.class, ChecksInstanceofInStaticInitializer.class); + assertThatTypes(origins).matchInAnyOrder( + ChecksInstanceofInMethod.class, + ChecksInstanceofInConstructor.class, + ChecksInstanceofInStaticInitializer.class, + ChecksMultipleInstanceofs.class); } } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java index 10ea685730..5a31d7f33a 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java @@ -16,6 +16,7 @@ import com.tngtech.archunit.core.domain.AccessTarget.MethodCallTarget; import com.tngtech.archunit.core.domain.DomainObjectCreationContext; import com.tngtech.archunit.core.domain.ImportContext; +import com.tngtech.archunit.core.domain.InstanceofCheck; import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClassDescriptor; @@ -435,6 +436,11 @@ public Set createReferencedClassObjectsFor(JavaCodeUnit c return Collections.emptySet(); } + @Override + public Set createInstanceofChecksFor(JavaCodeUnit codeUnit) { + return Collections.emptySet(); + } + @Override public JavaClass resolveClass(String fullyQualifiedClassName) { throw new UnsupportedOperationException("Override me where necessary"); diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/CheckingInstanceofFromLambda.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/CheckingInstanceofFromLambda.java new file mode 100644 index 0000000000..ba07fa6bac --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/CheckingInstanceofFromLambda.java @@ -0,0 +1,17 @@ +package com.tngtech.archunit.core.importer.testexamples.instanceofcheck; + +import java.io.File; +import java.io.FilterInputStream; +import java.util.function.Predicate; +import java.util.function.Supplier; + +@SuppressWarnings("unused") +public class CheckingInstanceofFromLambda { + Predicate reference() { + return (object) -> object instanceof FilterInputStream; + } + + Supplier> nestedReference() { + return () -> (object) -> object instanceof File; + } +} diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksMultipleInstanceofs.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksMultipleInstanceofs.java new file mode 100644 index 0000000000..804c88bce0 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/testexamples/instanceofcheck/ChecksMultipleInstanceofs.java @@ -0,0 +1,17 @@ +package com.tngtech.archunit.core.importer.testexamples.instanceofcheck; + +@SuppressWarnings({"StatementWithEmptyBody", "unused", "ConstantConditions"}) +public class ChecksMultipleInstanceofs { + static { + boolean foo = ((Object) null) instanceof InstanceofChecked; + } + + public ChecksMultipleInstanceofs(Object param) { + if (param instanceof InstanceofChecked) { + } + } + + boolean method(Object param) { + return param instanceof InstanceofChecked; + } +} 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 387bc9170b..d966e64aa5 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java @@ -7,6 +7,7 @@ import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.AccessTarget.FieldAccessTarget; import com.tngtech.archunit.core.domain.Dependency; +import com.tngtech.archunit.core.domain.InstanceofCheck; import com.tngtech.archunit.core.domain.JavaAccess; import com.tngtech.archunit.core.domain.JavaAnnotation; import com.tngtech.archunit.core.domain.JavaClass; @@ -41,6 +42,7 @@ import com.tngtech.archunit.testutil.assertion.DependencyAssertion; import com.tngtech.archunit.testutil.assertion.DescribedPredicateAssertion; import com.tngtech.archunit.testutil.assertion.ExpectedAccessCreation; +import com.tngtech.archunit.testutil.assertion.InstanceofChecksAssertion; import com.tngtech.archunit.testutil.assertion.JavaAnnotationAssertion; import com.tngtech.archunit.testutil.assertion.JavaAnnotationsAssertion; import com.tngtech.archunit.testutil.assertion.JavaClassAssertion; @@ -158,6 +160,10 @@ public static ReferencedClassObjectsAssertion assertThatReferencedClassObjects(S return new ReferencedClassObjectsAssertion(referencedClassObjects); } + public static InstanceofChecksAssertion assertThatInstanceofChecks(Set instanceofChecks) { + return new InstanceofChecksAssertion(instanceofChecks); + } + public static JavaEnumConstantAssertion assertThat(JavaEnumConstant enumConstant) { return new JavaEnumConstantAssertion(enumConstant); } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/InstanceofChecksAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/InstanceofChecksAssertion.java new file mode 100644 index 0000000000..a925a47ef4 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/InstanceofChecksAssertion.java @@ -0,0 +1,86 @@ +package com.tngtech.archunit.testutil.assertion; + +import java.util.Set; +import java.util.function.Predicate; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.tngtech.archunit.core.domain.InstanceofCheck; +import org.assertj.core.api.AbstractIterableAssert; +import org.assertj.core.api.AbstractObjectAssert; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static java.util.stream.Collectors.toSet; +import static java.util.stream.StreamSupport.stream; +import static org.assertj.core.api.Assertions.assertThat; + +public class InstanceofChecksAssertion extends AbstractIterableAssert, InstanceofCheck, InstanceofChecksAssertion.InstanceofCheckAssertion> { + public InstanceofChecksAssertion(Set instanceofChecks) { + super(instanceofChecks, InstanceofChecksAssertion.class); + } + + @Override + protected InstanceofCheckAssertion toAssert(InstanceofCheck value, String description) { + return new InstanceofCheckAssertion(value).as(description); + } + + @Override + protected InstanceofChecksAssertion newAbstractIterableAssert(Iterable iterable) { + return new InstanceofChecksAssertion(ImmutableSet.copyOf(iterable)); + } + + public void containInstanceofChecks(ExpectedInstanceofCheck... expectedInstanceofChecks) { + containInstanceofChecks(ImmutableList.copyOf(expectedInstanceofChecks)); + } + + public void containInstanceofChecks(Iterable expectedInstanceofChecks) { + Set unmatchedClassObjects = stream(expectedInstanceofChecks.spliterator(), false) + .filter(expected -> actual.stream().noneMatch(expected)) + .collect(toSet()); + assertThat(unmatchedClassObjects).as("Instanceof checks not contained in " + actual).isEmpty(); + } + + static class InstanceofCheckAssertion extends AbstractObjectAssert { + InstanceofCheckAssertion(InstanceofCheck instanceofCheck) { + super(instanceofCheck, InstanceofCheckAssertion.class); + } + } + + public static ExpectedInstanceofCheck instanceofCheck(Class type, int lineNumber) { + return new ExpectedInstanceofCheck(type, lineNumber); + } + + public static class ExpectedInstanceofCheck implements Predicate { + private final Class type; + private final int lineNumber; + private final boolean declaredInLambda; + + private ExpectedInstanceofCheck(Class type, int lineNumber) { + this(type, lineNumber, false); + } + + private ExpectedInstanceofCheck(Class type, int lineNumber, boolean declaredInLambda) { + this.type = type; + this.lineNumber = lineNumber; + this.declaredInLambda = declaredInLambda; + } + + public ExpectedInstanceofCheck declaredInLambda() { + return new ExpectedInstanceofCheck(type, lineNumber, true); + } + + @Override + public boolean test(InstanceofCheck input) { + return input.getRawType().isEquivalentTo(type) && input.getLineNumber() == lineNumber && input.isDeclaredInLambda() == declaredInLambda; + } + + @Override + public String toString() { + return toStringHelper(this) + .add("type", type) + .add("lineNumber", lineNumber) + .add("declaredInLambda", declaredInLambda) + .toString(); + } + } +}