diff --git a/archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java b/archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java index 9a672db5f9..6c844ddbfe 100644 --- a/archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java +++ b/archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java @@ -972,6 +972,9 @@ Stream OnionArchitectureTest() { .by(field(ShoppingService.class, "productRepository").ofType(ProductRepository.class)) .by(field(ShoppingService.class, "shoppingCartRepository").ofType(ShoppingCartRepository.class)) + .by(method(AdministrationCLI.class, "handle") + .referencingClassObject(ProductRepository.class) + .inLine(16)) .by(callFromMethod(AdministrationCLI.class, "handle", String[].class, AdministrationPort.class) .toMethod(ProductRepository.class, "getTotalCount") .inLine(17).asDependency()) diff --git a/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedDependency.java b/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedDependency.java index 4a2a19b5dd..ecf6fa2f66 100644 --- a/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedDependency.java +++ b/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedDependency.java @@ -190,7 +190,11 @@ public ExpectedDependency withAnnotationType(Class annotationType) { } public AddsLineNumber checkingInstanceOf(Class target) { - return new AddsLineNumber(owner, getOriginName(), target); + return new AddsLineNumber(owner, getOriginName(), "checks instanceof", target); + } + + public AddsLineNumber referencingClassObject(Class target) { + return new AddsLineNumber(owner, getOriginName(), "references class object", target); } private String getOriginName() { @@ -201,15 +205,17 @@ public static class AddsLineNumber { private final Class owner; private final String origin; private final Class target; + private final String dependencyType; - private AddsLineNumber(Class owner, String origin, Class target) { + private AddsLineNumber(Class owner, String origin, String dependencyType, Class target) { this.owner = owner; this.origin = origin; this.target = target; + this.dependencyType = dependencyType; } public ExpectedDependency inLine(int lineNumber) { - String dependencyPattern = getDependencyPattern(origin, "checks instanceof", target.getName(), lineNumber); + String dependencyPattern = getDependencyPattern(origin, dependencyType, target.getName(), lineNumber); return new ExpectedDependency(owner, target, dependencyPattern); } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java index 120c2c8c55..af2850301b 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java @@ -28,6 +28,7 @@ import com.tngtech.archunit.base.HasDescription; import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.core.domain.properties.HasName; +import com.tngtech.archunit.core.domain.properties.HasOwner; import com.tngtech.archunit.core.domain.properties.HasSourceCodeLocation; import static com.google.common.base.Preconditions.checkArgument; @@ -100,44 +101,52 @@ static Dependency fromInheritance(JavaClass origin, JavaClass targetSupertype) { } static Set tryCreateFromField(JavaField field) { - return tryCreateDependencyFromJavaMember(field, "has type", field.getRawType()); + return tryCreateDependency(field, "has type", field.getRawType()); } static Set tryCreateFromReturnType(JavaMethod method) { - return tryCreateDependencyFromJavaMember(method, "has return type", method.getRawReturnType()); + return tryCreateDependency(method, "has return type", method.getRawReturnType()); } static Set tryCreateFromParameter(JavaCodeUnit codeUnit, JavaClass parameter) { - return tryCreateDependencyFromJavaMember(codeUnit, "has parameter of type", parameter); + return tryCreateDependency(codeUnit, "has parameter of type", parameter); } static Set tryCreateFromThrowsDeclaration(ThrowsDeclaration declaration) { - return tryCreateDependencyFromJavaMember(declaration.getLocation(), "throws type", declaration.getRawType()); + return tryCreateDependency(declaration.getLocation(), "throws type", declaration.getRawType()); } static Set tryCreateFromInstanceofCheck(InstanceofCheck instanceofCheck) { - return tryCreateDependencyFromJavaMemberWithLocation(instanceofCheck.getOwner(), "checks instanceof", instanceofCheck.getRawType(), instanceofCheck.getLineNumber()); + return tryCreateDependency( + instanceofCheck.getOwner(), "checks instanceof", + instanceofCheck.getRawType(), instanceofCheck.getSourceCodeLocation()); + } + + static Set tryCreateFromReferencedClassObject(ReferencedClassObject referencedClassObject) { + return tryCreateDependency( + referencedClassObject.getOwner(), "references class object", + referencedClassObject.getRawType(), referencedClassObject.getSourceCodeLocation()); } static Set tryCreateFromAnnotation(JavaAnnotation target) { Origin origin = findSuitableOrigin(target, target.getAnnotatedElement()); - return tryCreateDependency(origin.originClass, origin.originDescription, "is annotated with", target.getRawType()); + return tryCreateDependency(origin, "is annotated with", target.getRawType()); } static Set tryCreateFromAnnotationMember(JavaAnnotation annotation, JavaClass memberType) { Origin origin = findSuitableOrigin(annotation, annotation.getAnnotatedElement()); - return tryCreateDependency(origin.originClass, origin.originDescription, "has annotation member of type", memberType); + return tryCreateDependency(origin, "has annotation member of type", memberType); } static Set tryCreateFromTypeParameter(JavaTypeVariable typeParameter, JavaClass typeParameterDependency) { String dependencyType = "has type parameter '" + typeParameter.getName() + "' depending on"; Origin origin = findSuitableOrigin(typeParameter, typeParameter.getOwner()); - return tryCreateDependency(origin.originClass, origin.originDescription, dependencyType, typeParameterDependency); + return tryCreateDependency(origin, dependencyType, typeParameterDependency); } static Set tryCreateFromGenericSuperclassTypeArguments(JavaClass originClass, JavaType superclass, JavaClass typeArgumentDependency) { String dependencyType = "has generic superclass " + bracketFormat(superclass.getName()) + " with type argument depending on"; - return tryCreateDependency(originClass, originClass.getDescription(), dependencyType, typeArgumentDependency); + return tryCreateDependency(originClass, originClass.getDescription(), dependencyType, typeArgumentDependency, originClass.getSourceCodeLocation()); } private static Origin findSuitableOrigin(Object dependencyCause, Object originCandidate) { @@ -152,22 +161,20 @@ private static Origin findSuitableOrigin(Object dependencyCause, Object originCa throw new IllegalStateException("Could not find suitable dependency origin for " + dependencyCause); } - private static Set tryCreateDependencyFromJavaMember(JavaMember origin, String dependencyType, JavaClass target) { - return tryCreateDependency(origin.getOwner(), origin.getDescription(), dependencyType, target); - } - - private static Set tryCreateDependencyFromJavaMemberWithLocation(JavaMember origin, String dependencyType, JavaClass target, int lineNumber) { - return tryCreateDependency(origin.getOwner(), origin.getDescription(), dependencyType, target, SourceCodeLocation.of(origin.getOwner(), lineNumber)); + private static & HasDescription> Set tryCreateDependency( + T origin, String dependencyType, JavaClass targetClass) { + return tryCreateDependency(origin, dependencyType, targetClass, origin.getOwner().getSourceCodeLocation()); } - private static Set tryCreateDependency( - JavaClass originClass, String originDescription, String dependencyType, JavaClass targetClass) { + private static & HasDescription> Set tryCreateDependency( + T origin, String dependencyType, JavaClass targetClass, SourceCodeLocation sourceCodeLocation) { - return tryCreateDependency(originClass, originDescription, dependencyType, targetClass, originClass.getSourceCodeLocation()); + return tryCreateDependency(origin.getOwner(), origin.getDescription(), dependencyType, targetClass, sourceCodeLocation); } private static Set tryCreateDependency( JavaClass originClass, String originDescription, String dependencyType, JavaClass targetClass, SourceCodeLocation sourceCodeLocation) { + ImmutableSet.Builder dependencies = ImmutableSet.builder() .addAll(createComponentTypeDependencies(originClass, originDescription, targetClass, sourceCodeLocation)); String targetDescription = bracketFormat(targetClass.getName()); @@ -177,7 +184,9 @@ private static Set tryCreateDependency( return dependencies.build(); } - private static Set createComponentTypeDependencies(JavaClass originClass, String originDescription, JavaClass targetClass, SourceCodeLocation sourceCodeLocation) { + private static Set createComponentTypeDependencies( + JavaClass originClass, String originDescription, JavaClass targetClass, SourceCodeLocation sourceCodeLocation) { + ImmutableSet.Builder result = ImmutableSet.builder(); Optional componentType = targetClass.tryGetComponentType(); while (componentType.isPresent()) { @@ -271,7 +280,7 @@ public static JavaClasses toTargetClasses(Iterable dependencies) { return JavaClasses.of(classes); } - private static class Origin { + private static class Origin implements HasOwner, HasDescription { private final JavaClass originClass; private final String originDescription; @@ -279,6 +288,16 @@ private Origin(JavaClass originClass, String originDescription) { this.originClass = originClass; this.originDescription = originDescription; } + + @Override + public JavaClass getOwner() { + return originClass; + } + + @Override + public String getDescription() { + return originDescription; + } } public static final class Predicates { 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/InstanceofCheck.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/InstanceofCheck.java index 83a8f06694..d02400f648 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 @@ -17,21 +17,25 @@ import com.tngtech.archunit.PublicAPI; 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; -public final class InstanceofCheck implements HasType, HasOwner { +public final class InstanceofCheck implements HasType, HasOwner, HasSourceCodeLocation { private final JavaCodeUnit owner; private final JavaClass target; private final int lineNumber; + private final SourceCodeLocation sourceCodeLocation; private InstanceofCheck(JavaCodeUnit owner, JavaClass target, int lineNumber) { this.owner = checkNotNull(owner); this.target = checkNotNull(target); this.lineNumber = lineNumber; + sourceCodeLocation = SourceCodeLocation.of(owner.getOwner(), lineNumber); } @Override @@ -57,6 +61,20 @@ public int getLineNumber() { return lineNumber; } + @Override + public SourceCodeLocation getSourceCodeLocation() { + return sourceCodeLocation; + } + + @Override + public String toString() { + return toStringHelper(this) + .add("owner", owner) + .add("target", target) + .add("lineNumber", lineNumber) + .toString(); + } + static InstanceofCheck from(JavaCodeUnit owner, JavaClass target, int lineNumber) { return new InstanceofCheck(owner, target, lineNumber); } 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/JavaClassDependencies.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDependencies.java index c08dfd60e4..3d8d957884 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDependencies.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDependencies.java @@ -52,6 +52,7 @@ public Set get() { result.addAll(constructorParameterDependenciesFromSelf()); result.addAll(annotationDependenciesFromSelf()); result.addAll(instanceofCheckDependenciesFromSelf()); + result.addAll(referencedClassObjectDependenciesFromSelf()); result.addAll(typeParameterDependenciesFromSelf()); return result.build(); } @@ -158,6 +159,14 @@ private Set instanceofCheckDependenciesFromSelf() { return result.build(); } + private Set referencedClassObjectDependenciesFromSelf() { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (ReferencedClassObject referencedClassObject : javaClass.getReferencedClassObjects()) { + result.addAll(Dependency.tryCreateFromReferencedClassObject(referencedClassObject)); + } + return result.build(); + } + private Set typeParameterDependenciesFromSelf() { ImmutableSet.Builder result = ImmutableSet.builder(); for (JavaTypeVariable typeVariable : javaClass.getTypeParameters()) { 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/AccessRecord.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/AccessRecord.java index 8a0dc24e99..79c2c679fe 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/AccessRecord.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/AccessRecord.java @@ -233,7 +233,7 @@ public JavaCodeUnit getCaller() { @Override public FieldAccessTarget getTarget() { Supplier> fieldSupplier = new FieldTargetSupplier(targetOwner.getAllFields(), record.target); - JavaClass fieldType = classes.getOrResolve(JavaClassDescriptorImporter.importAsmType(record.target.desc).getFullyQualifiedClassName()); + JavaClass fieldType = classes.getOrResolve(JavaClassDescriptorImporter.importAsmTypeFromDescriptor(record.target.desc).getFullyQualifiedClassName()); return new FieldAccessTargetBuilder() .withOwner(targetOwner) .withName(record.target.name) 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 fbaa371d83..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,10 +43,10 @@ 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 importAsmType(String typeDescriptor) { + static JavaClassDescriptor importAsmTypeFromDescriptor(String typeDescriptor) { return importAsmType(Type.getType(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 a578d7b568..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 @@ -206,7 +206,7 @@ public FieldVisitor visitField(int access, String name, String desc, String sign DomainBuilders.JavaFieldBuilder fieldBuilder = new DomainBuilders.JavaFieldBuilder() .withName(name) - .withType(JavaClassDescriptorImporter.importAsmType(desc)) + .withType(JavaClassDescriptorImporter.importAsmTypeFromDescriptor(desc)) .withModifiers(JavaModifier.getModifiersForField(access)) .withDescriptor(desc); declarationHandler.onDeclaredField(fieldBuilder); @@ -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); @@ -500,7 +508,7 @@ public void visitEnd() { } private static DomainBuilders.JavaAnnotationBuilder annotationBuilderFor(String desc) { - return new DomainBuilders.JavaAnnotationBuilder().withType(JavaClassDescriptorImporter.importAsmType(desc)); + return new DomainBuilders.JavaAnnotationBuilder().withType(JavaClassDescriptorImporter.importAsmTypeFromDescriptor(desc)); } private static class AnnotationProcessor extends AnnotationVisitor { @@ -736,7 +744,7 @@ private static ValueBuilder javaEnumBuilder(final String desc, final String valu public Optional build(T owner, ImportContext importContext) { return Optional.of( new DomainBuilders.JavaEnumConstantBuilder() - .withDeclaringClass(importContext.resolveClass(JavaClassDescriptorImporter.importAsmType(desc).getFullyQualifiedClassName())) + .withDeclaringClass(importContext.resolveClass(JavaClassDescriptorImporter.importAsmTypeFromDescriptor(desc).getFullyQualifiedClassName())) .withName(value) .build()); } 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 56bb777a51..43afbc19b6 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 @@ -17,6 +17,7 @@ import com.tngtech.archunit.core.domain.JavaClassDescriptor; +import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkNotNull; class RawInstanceofCheck { @@ -28,15 +29,23 @@ private RawInstanceofCheck(JavaClassDescriptor target, int lineNumber) { this.lineNumber = lineNumber; } - public static RawInstanceofCheck from(JavaClassDescriptor target, int lineNumber) { + static RawInstanceofCheck from(JavaClassDescriptor target, int lineNumber) { return new RawInstanceofCheck(target, lineNumber); } - public JavaClassDescriptor getTarget() { + JavaClassDescriptor getTarget() { return target; } - public int getLineNumber() { + int getLineNumber() { return lineNumber; } + + @Override + public String toString() { + return toStringHelper(this) + .add("target", target) + .add("lineNumber", lineNumber) + .toString(); + } } 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/DependencyTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/DependencyTest.java index 3d7aed0736..da3786e878 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/DependencyTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/DependencyTest.java @@ -6,13 +6,16 @@ import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.nio.file.FileSystem; import java.util.Set; import com.google.common.base.MoreObjects; +import com.google.common.collect.FluentIterable; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.testobjects.ClassWithArrayDependencies; import com.tngtech.archunit.core.domain.testobjects.ClassWithDependencyOnInstanceofCheck; import com.tngtech.archunit.core.domain.testobjects.ClassWithDependencyOnInstanceofCheck.InstanceOfCheckTarget; +import com.tngtech.archunit.core.domain.testobjects.DependenciesOnClassObjects; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.testutil.Assertions; import com.tngtech.archunit.testutil.assertion.DependenciesAssertion; @@ -24,6 +27,7 @@ import org.junit.runner.RunWith; import static com.google.common.collect.Iterables.getOnlyElement; +import static com.tngtech.archunit.base.Guava.toGuava; import static com.tngtech.archunit.core.domain.Dependency.Functions.GET_ORIGIN_CLASS; import static com.tngtech.archunit.core.domain.Dependency.Functions.GET_TARGET_CLASS; import static com.tngtech.archunit.core.domain.Dependency.Predicates.dependency; @@ -32,6 +36,7 @@ import static com.tngtech.archunit.core.domain.TestUtils.importClassWithContext; import static com.tngtech.archunit.core.domain.TestUtils.importClassesWithContext; import static com.tngtech.archunit.core.domain.TestUtils.simulateCall; +import static com.tngtech.archunit.core.domain.properties.HasType.Predicates.rawType; import static com.tngtech.archunit.testutil.Assertions.assertThat; import static com.tngtech.archunit.testutil.Assertions.assertThatDependencies; import static com.tngtech.archunit.testutil.Assertions.assertThatType; @@ -289,6 +294,24 @@ class ClassWithTypeParameters extends Base { ClassWithTypeParameters.class.getName(), Base.class.getName(), String.class.getName(), getClass().getSimpleName())); } + @Test + public void Dependency_from_referenced_class_object() { + JavaMethod origin = new ClassFileImporter() + .importClass(DependenciesOnClassObjects.class) + .getMethod("referencedClassObjectsInMethod"); + ReferencedClassObject referencedClassObject = getOnlyElement(FluentIterable.from(origin.getReferencedClassObjects()) + .filter(toGuava(rawType(FileSystem.class))) + .toSet()); + + Dependency dependency = getOnlyElement(Dependency.tryCreateFromReferencedClassObject(referencedClassObject)); + + assertThatType(dependency.getOriginClass()).matches(DependenciesOnClassObjects.class); + assertThatType(dependency.getTargetClass()).matches(FileSystem.class); + assertThat(dependency.getDescription()).as("description") + .contains(String.format("Method <%s> references class object <%s> in (%s.java:%d)", + origin.getFullName(), FileSystem.class.getName(), DependenciesOnClassObjects.class.getSimpleName(), 22)); + } + @Test public void origin_predicates_match() { assertThatDependency(Origin.class, Target.class) diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java index ca7c62f975..b764a45f62 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java @@ -2,8 +2,13 @@ import java.io.BufferedInputStream; import java.io.File; +import java.io.FilterInputStream; import java.io.Serializable; import java.lang.annotation.Retention; +import java.nio.Buffer; +import java.nio.charset.Charset; +import java.nio.file.FileSystem; +import java.nio.file.Path; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collection; @@ -25,6 +30,7 @@ import com.tngtech.archunit.core.domain.testobjects.ArrayComponentTypeDependencies; import com.tngtech.archunit.core.domain.testobjects.B; import com.tngtech.archunit.core.domain.testobjects.ComponentTypeDependency; +import com.tngtech.archunit.core.domain.testobjects.DependenciesOnClassObjects; import com.tngtech.archunit.core.domain.testobjects.InterfaceForA; import com.tngtech.archunit.core.domain.testobjects.SuperA; import com.tngtech.archunit.core.importer.ClassFileImporter; @@ -639,6 +645,31 @@ class ClassWithTypeParameters< ); } + @Test + public void direct_dependencies_from_self_by_referenced_class_objects() { + JavaClass javaClass = new ClassFileImporter().importClass(DependenciesOnClassObjects.class); + + assertThatDependencies(javaClass.getDirectDependenciesFromSelf()) + .contain(from(DependenciesOnClassObjects.class).to(FilterInputStream.class).inLocation(DependenciesOnClassObjects.class, 16) + .withDescriptionContaining("references class object") + + .from(DependenciesOnClassObjects.class).to(Buffer.class).inLocation(DependenciesOnClassObjects.class, 16) + .withDescriptionContaining("references class object") + + .from(DependenciesOnClassObjects.class).to(File.class).inLocation(DependenciesOnClassObjects.class, 19) + .withDescriptionContaining("references class object") + + .from(DependenciesOnClassObjects.class).to(Path.class).inLocation(DependenciesOnClassObjects.class, 19) + .withDescriptionContaining("references class object") + + .from(DependenciesOnClassObjects.class).to(FileSystem.class).inLocation(DependenciesOnClassObjects.class, 22) + .withDescriptionContaining("references class object") + + .from(DependenciesOnClassObjects.class).to(Charset.class).inLocation(DependenciesOnClassObjects.class, 22) + .withDescriptionContaining("references class object") + ); + } + @Test public void direct_dependencies_from_self_finds_correct_set_of_target_types() { JavaClass javaClass = importPackagesOf(getClass()).get(ClassWithAnnotationDependencies.class); @@ -872,6 +903,21 @@ class SecondDependingOnOtherThroughTypeParameter< ); } + @Test + public void direct_dependencies_to_self_by_referenced_class_objects() { + JavaClass javaClass = new ClassFileImporter() + .importClasses(DependenciesOnClassObjects.class, DependenciesOnClassObjects.MoreDependencies.class, Charset.class) + .get(Charset.class); + + assertThatDependencies(javaClass.getDirectDependenciesToSelf()) + .contain(from(DependenciesOnClassObjects.class).to(Charset.class).inLocation(DependenciesOnClassObjects.class, 22) + .withDescriptionContaining("references class object") + + .from(DependenciesOnClassObjects.MoreDependencies.class).to(Charset.class).inLocation(DependenciesOnClassObjects.class, 27) + .withDescriptionContaining("references class object") + ); + } + @Test public void direct_dependencies_to_self_finds_correct_set_of_origin_types() { JavaClasses classes = importPackagesOf(getClass()); @@ -879,17 +925,17 @@ public void direct_dependencies_to_self_finds_correct_set_of_origin_types() { Set origins = getOriginsOfDependenciesTo(classes.get(WithType.class)); assertThatTypes(origins).matchInAnyOrder( - ClassWithAnnotationDependencies.class, ClassWithSelfReferences.class, WithNestedAnnotations.class, OnMethodParam.class); + getClass(), ClassWithAnnotationDependencies.class, ClassWithSelfReferences.class, WithNestedAnnotations.class, OnMethodParam.class); origins = getOriginsOfDependenciesTo(classes.get(B.class)); assertThatTypes(origins).matchInAnyOrder( - ClassWithAnnotationDependencies.class, OnMethodParam.class, AAccessingB.class, AhavingMembersOfTypeB.class); + getClass(), ClassWithAnnotationDependencies.class, OnMethodParam.class, AAccessingB.class, AhavingMembersOfTypeB.class); origins = getOriginsOfDependenciesTo(classes.get(SomeEnumAsNestedAnnotationParameter.class)); assertThatTypes(origins).matchInAnyOrder( - ClassWithAnnotationDependencies.class, WithEnum.class); + getClass(), ClassWithAnnotationDependencies.class, WithEnum.class); } private Set getOriginsOfDependenciesTo(JavaClass withType) { 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/domain/testobjects/DependenciesOnClassObjects.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/DependenciesOnClassObjects.java new file mode 100644 index 0000000000..06a3bb0531 --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/testobjects/DependenciesOnClassObjects.java @@ -0,0 +1,30 @@ +package com.tngtech.archunit.core.domain.testobjects; + +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 DependenciesOnClassObjects { + 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); + } + + public static class MoreDependencies { + Class moreReferencedClassObjectsInMethod() { + return Charset.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/lang/syntax/elements/ShouldOnlyByClassesThatTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ShouldOnlyByClassesThatTest.java index 40d9c6d476..61283ee958 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ShouldOnlyByClassesThatTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ShouldOnlyByClassesThatTest.java @@ -934,7 +934,7 @@ public void areNotInnerClasses_predicate(ClassesThat c public void areAnonymousClasses_predicate(ClassesThat classesShouldOnlyBeBy) { Set classes = filterClassesAppearingInFailureReport( classesShouldOnlyBeBy.areAnonymousClasses()) - .on(ClassAccessingAnonymousClass.class, anonymousClassBeingAccessed.getClass(), + .on(ClassAccessingAnonymousClass_Reference, anonymousClassBeingAccessed.getClass(), ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); assertThatTypes(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); @@ -945,10 +945,10 @@ public void areAnonymousClasses_predicate(ClassesThat public void areNotAnonymousClasses_predicate(ClassesThat classesShouldOnlyBeBy) { Set classes = filterClassesAppearingInFailureReport( classesShouldOnlyBeBy.areNotAnonymousClasses()) - .on(ClassAccessingAnonymousClass.class, anonymousClassBeingAccessed.getClass(), + .on(ClassAccessingAnonymousClass_Reference, anonymousClassBeingAccessed.getClass(), ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatTypes(classes).matchInAnyOrder(ClassAccessingAnonymousClass.class, anonymousClassBeingAccessed.getClass()); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingAnonymousClass_Reference, anonymousClassBeingAccessed.getClass()); } @Test @@ -956,7 +956,7 @@ public void areNotAnonymousClasses_predicate(ClassesThat classesShouldOnlyBeBy) { Set classes = filterClassesAppearingInFailureReport( classesShouldOnlyBeBy.areLocalClasses()) - .on(ClassBeingAccessedByLocalClass.class, ClassBeingAccessedByLocalClass.getLocalClass(), + .on(ClassAccessingLocalClass_Reference, ClassBeingAccessedByLocalClass.getLocalClass(), ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); assertThatTypes(classes).matchInAnyOrder(ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); @@ -967,10 +967,10 @@ public void areLocalClasses_predicate(ClassesThat clas public void areNotLocalClasses_predicate(ClassesThat classesShouldOnlyBeBy) { Set classes = filterClassesAppearingInFailureReport( classesShouldOnlyBeBy.areNotLocalClasses()) - .on(ClassBeingAccessedByLocalClass.class, ClassBeingAccessedByLocalClass.getLocalClass(), + .on(ClassAccessingLocalClass_Reference, ClassBeingAccessedByLocalClass.getLocalClass(), ClassAccessingOtherClass.class, ClassBeingAccessedByOtherClass.class); - assertThatTypes(classes).matchInAnyOrder(ClassBeingAccessedByLocalClass.class, ClassBeingAccessedByLocalClass.getLocalClass()); + assertThatTypes(classes).matchInAnyOrder(ClassAccessingLocalClass_Reference, ClassBeingAccessedByLocalClass.getLocalClass()); } @Test @@ -1050,6 +1050,14 @@ private static DescribedPredicate classWithNameOf(Class type) { return GET_NAME.is(equalTo(type.getName())); } + private static Class classForName(String className) { + try { + return Class.forName(className); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + private static class ClassAccessedByFoo { void method() { } @@ -1306,6 +1314,8 @@ void access() { private class InnerMemberClassBeingAccessed { } + // This must be loaded via Reflection, otherwise the test will be tainted by the dependency on the class object + private static final Class ClassAccessingAnonymousClass_Reference = classForName("com.tngtech.archunit.lang.syntax.elements.ShouldOnlyByClassesThatTest$ClassAccessingAnonymousClass"); private static class ClassAccessingAnonymousClass { @SuppressWarnings("unused") void access() { @@ -1320,15 +1330,20 @@ public void run() { } }; + // This must be loaded via Reflection, otherwise the test will be tainted by the dependency on the class object + private static final Class ClassAccessingLocalClass_Reference = classForName("com.tngtech.archunit.lang.syntax.elements.ShouldOnlyByClassesThatTest$ClassBeingAccessedByLocalClass"); + private static class ClassBeingAccessedByLocalClass { static Class getLocalClass() { + @SuppressWarnings({"unused", "InstantiationOfUtilityClass"}) class LocalClass { - @SuppressWarnings("unused") void access() { new ClassBeingAccessedByLocalClass(); } } - return LocalClass.class; + + // This must be loaded via Reflection, otherwise the test will be tainted by the dependency on the class object + return classForName("com.tngtech.archunit.lang.syntax.elements.ShouldOnlyByClassesThatTest$ClassBeingAccessedByLocalClass$1LocalClass"); } } } 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(); + } + } +}