Skip to content

Commit

Permalink
add method codeUnits().should().onlyBeCalled() to syntax #810
Browse files Browse the repository at this point in the history
... which allows restricting callers of code units via

* `...onlyBeCalled().byClassesThat(..)`
* `...onlyBeCalled().byMethodsThat(..)`
* `...onlyBeCalled().byConstructorsThat(..)`
* `...onlyBeCalled().byCodeUnitsThat(..)`
  • Loading branch information
codecholeric authored Mar 12, 2022
2 parents 4912463 + 0bc8cef commit 348819d
Show file tree
Hide file tree
Showing 15 changed files with 651 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ public Set<JavaFieldAccess> getFieldAccesses() {
return fieldAccesses;
}

@PublicAPI(usage = ACCESS)
public abstract Set<? extends JavaCall<?>> getCallsOfSelf();

@PublicAPI(usage = ACCESS)
public Set<JavaMethodCall> getMethodCallsFromSelf() {
return methodCalls;
Expand Down Expand Up @@ -225,6 +228,11 @@ public boolean isConstructor() {
return false;
}

@PublicAPI(usage = ACCESS)
public boolean isMethod() {
return false;
}

@Override
@SuppressWarnings("unchecked") // we know the 'owning' member is this code unit
public Set<? extends JavaAnnotation<? extends JavaCodeUnit>> getAnnotations() {
Expand Down Expand Up @@ -354,6 +362,16 @@ public boolean apply(JavaCodeUnit input) {
}
};
}

@PublicAPI(usage = ACCESS)
public static DescribedPredicate<JavaCodeUnit> method() {
return new DescribedPredicate<JavaCodeUnit>("method") {
@Override
public boolean apply(JavaCodeUnit input) {
return input.isMethod();
}
};
}
}

@PublicAPI(usage = ACCESS)
Expand All @@ -377,6 +395,15 @@ public ThrowsClause<T> apply(T input) {
}
};
}

@PublicAPI(usage = ACCESS)
public static final ChainableFunction<JavaCodeUnit, Set<? extends JavaCall<?>>> GET_CALLS_OF_SELF =
new ChainableFunction<JavaCodeUnit, Set<? extends JavaCall<?>>>() {
@Override
public Set<? extends JavaCall<?>> apply(JavaCodeUnit input) {
return input.getCallsOfSelf();
}
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ public Optional<Object> getDefaultValue() {
return annotationDefaultValue;
}

@Override
@PublicAPI(usage = ACCESS)
public boolean isMethod() {
return true;
}

@PublicAPI(usage = ACCESS)
public Set<JavaMethodCall> getCallsOfSelf() {
return getReverseDependencies().getCallsTo(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,9 @@ public String getDescription() {
public ThrowsClause<JavaStaticInitializer> getThrowsClause() {
return ThrowsClause.empty(this);
}

@Override
public Set<? extends JavaCall<?>> getCallsOfSelf() {
return emptySet();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import com.tngtech.archunit.core.domain.JavaAccess;
import com.tngtech.archunit.core.domain.JavaClass;

class AllAccessesCondition extends AllAttributesMatchCondition<JavaAccess<?>> {
class AllAccessesCondition extends AllAttributesMatchCondition<JavaAccess<?>, JavaClass> {
private final Function<JavaClass, ? extends Collection<JavaAccess<?>>> getRelevantAccesses;

AllAccessesCondition(String prefix, DescribedPredicate<JavaAccess<?>> predicate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,25 @@

import java.util.Collection;

import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ConditionEvents;

import static com.tngtech.archunit.lang.conditions.ArchConditions.containOnlyElementsThat;

abstract class AllAttributesMatchCondition<T> extends ArchCondition<JavaClass> {
private final ArchCondition<T> condition;
abstract class AllAttributesMatchCondition<ATTRIBUTE, OWNER> extends ArchCondition<OWNER> {
private final ArchCondition<ATTRIBUTE> condition;

AllAttributesMatchCondition(String description, ArchCondition<T> condition) {
AllAttributesMatchCondition(String description, ArchCondition<ATTRIBUTE> condition) {
super(description);
this.condition = condition;
}

@Override
public final void check(JavaClass item, ConditionEvents events) {
public final void check(OWNER item, ConditionEvents events) {
containOnlyElementsThat(condition).check(relevantAttributes(item), events);
}

abstract Collection<T> relevantAttributes(JavaClass item);
abstract Collection<? extends ATTRIBUTE> relevantAttributes(OWNER item);

@Override
public String toString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import static com.tngtech.archunit.PublicAPI.Usage.ACCESS;

@PublicAPI(usage = ACCESS)
public final class AllDependenciesCondition extends AllAttributesMatchCondition<Dependency> {
public final class AllDependenciesCondition extends AllAttributesMatchCondition<Dependency, JavaClass> {
private final DescribedPredicate<? super Dependency> conditionPredicate;
private final Function<JavaClass, ? extends Collection<Dependency>> javaClassToRelevantDependencies;
private final DescribedPredicate<Dependency> ignorePredicate;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import com.tngtech.archunit.core.domain.properties.HasAnnotations;
import com.tngtech.archunit.core.domain.properties.HasModifiers;
import com.tngtech.archunit.core.domain.properties.HasName;
import com.tngtech.archunit.core.domain.properties.HasOwner;
import com.tngtech.archunit.core.domain.properties.HasOwner.Functions.Get;
import com.tngtech.archunit.core.domain.properties.HasOwner.Predicates.With;
import com.tngtech.archunit.core.domain.properties.HasSourceCodeLocation;
Expand Down Expand Up @@ -91,6 +92,9 @@
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleNameEndingWith;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleNameStartingWith;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.type;
import static com.tngtech.archunit.core.domain.JavaCodeUnit.Functions.Get.GET_CALLS_OF_SELF;
import static com.tngtech.archunit.core.domain.JavaCodeUnit.Predicates.constructor;
import static com.tngtech.archunit.core.domain.JavaCodeUnit.Predicates.method;
import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME;
import static com.tngtech.archunit.core.domain.JavaMember.Predicates.declaredIn;
import static com.tngtech.archunit.core.domain.JavaModifier.FINAL;
Expand Down Expand Up @@ -1205,6 +1209,35 @@ public static ArchCondition<JavaCodeUnit> declareThrowableOfType(DescribedPredic
return new DoesConditionByPredicate<>(declareThrowableOfType);
}

@PublicAPI(usage = ACCESS)
public static ArchCondition<JavaCodeUnit> onlyBeCalledByClassesThat(DescribedPredicate<? super JavaClass> predicate) {
ChainableFunction<JavaAccess<?>, JavaCodeUnit> origin = JavaAccess.Functions.Get.origin();
ChainableFunction<HasOwner<JavaClass>, JavaClass> owner = Get.owner();
return new CodeUnitOnlyCallsCondition<>("only be called by classes that " + predicate.getDescription(),
origin.then(owner).is(predicate), GET_CALLS_OF_SELF);
}

@PublicAPI(usage = ACCESS)
public static ArchCondition<JavaCodeUnit> onlyBeCalledByCodeUnitsThat(DescribedPredicate<? super JavaMember> predicate) {
ChainableFunction<JavaAccess<?>, ? extends JavaCodeUnit> origin = JavaAccess.Functions.Get.origin();
return new CodeUnitOnlyCallsCondition<>("only be called by code units that " + predicate.getDescription(),
origin.is(predicate), GET_CALLS_OF_SELF);
}

@PublicAPI(usage = ACCESS)
public static ArchCondition<JavaCodeUnit> onlyBeCalledByMethodsThat(DescribedPredicate<? super JavaMember> predicate) {
ChainableFunction<JavaAccess<?>, ? extends JavaCodeUnit> origin = JavaAccess.Functions.Get.origin();
return new CodeUnitOnlyCallsCondition<>("only be called by methods that " + predicate.getDescription(),
origin.is(method().and(predicate)), GET_CALLS_OF_SELF);
}

@PublicAPI(usage = ACCESS)
public static ArchCondition<JavaCodeUnit> onlyBeCalledByConstructorsThat(DescribedPredicate<? super JavaMember> predicate) {
ChainableFunction<JavaAccess<?>, ? extends JavaCodeUnit> origin = JavaAccess.Functions.Get.origin();
return new CodeUnitOnlyCallsCondition<>("only be called by constructors that " + predicate.getDescription(),
origin.is(constructor().and(predicate)), GET_CALLS_OF_SELF);
}

private static <T extends HasDescription & HasSourceCodeLocation> String createMessage(T object, String message) {
return object.getDescription() + " " + message + " in " + object.getSourceCodeLocation();
}
Expand All @@ -1223,7 +1256,7 @@ private static <T extends HasDescription & HasSourceCodeLocation> String createM
new IsConditionByPredicate<>("a local class", JavaClass.Predicates.LOCAL_CLASSES);

private static class HaveOnlyModifiersCondition<T extends HasModifiers & HasDescription & HasSourceCodeLocation>
extends AllAttributesMatchCondition<T> {
extends AllAttributesMatchCondition<T, JavaClass> {

private final Function<JavaClass, ? extends Collection<T>> getHasModifiers;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import com.tngtech.archunit.core.domain.JavaAccess;
import com.tngtech.archunit.core.domain.JavaClass;

class ClassOnlyAccessesCondition<T extends JavaAccess<?>> extends AllAttributesMatchCondition<T> {
class ClassOnlyAccessesCondition<T extends JavaAccess<?>> extends AllAttributesMatchCondition<T, JavaClass> {
private final Function<JavaClass, ? extends Collection<T>> getRelevantAccesses;

ClassOnlyAccessesCondition(DescribedPredicate<? super T> predicate, Function<JavaClass, ? extends Collection<T>> getRelevantAccesses) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2014-2022 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.lang.conditions;

import java.util.Collection;

import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.base.Function;
import com.tngtech.archunit.core.domain.JavaAccess;
import com.tngtech.archunit.core.domain.JavaCodeUnit;

class CodeUnitOnlyCallsCondition<T extends JavaAccess<?>> extends AllAttributesMatchCondition<T, JavaCodeUnit> {
private final Function<? super JavaCodeUnit, ? extends Collection<? extends T>> getRelevantAccesses;

CodeUnitOnlyCallsCondition(String description, DescribedPredicate<T> predicate,
Function<? super JavaCodeUnit, ? extends Collection<? extends T>> getRelevantAccesses) {
super(description, new JavaAccessCondition<>(predicate));
this.getRelevantAccesses = getRelevantAccesses;
}

@Override
Collection<? extends T> relevantAttributes(JavaCodeUnit item) {
return getRelevantAccesses.apply(item);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.tngtech.archunit.lang.conditions.ArchConditions;
import com.tngtech.archunit.lang.syntax.elements.CodeUnitsShould;
import com.tngtech.archunit.lang.syntax.elements.CodeUnitsShouldConjunction;
import com.tngtech.archunit.lang.syntax.elements.OnlyBeCalledSpecification;

import static com.tngtech.archunit.lang.conditions.ArchConditions.not;

Expand Down Expand Up @@ -148,6 +149,11 @@ public SELF notDeclareThrowableOfType(DescribedPredicate<? super JavaClass> pred
return addCondition(not(ArchConditions.declareThrowableOfType(predicate)));
}

@Override
public OnlyBeCalledSpecification<SELF> onlyBeCalled() {
return new OnlyBeCalledSpecificationInternal<>(self());
}

static class CodeUnitsShouldInternal extends AbstractCodeUnitsShouldInternal<JavaCodeUnit, CodeUnitsShouldInternal> {

CodeUnitsShouldInternal(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,11 @@ public SELF orShould() {
return copyWithNewCondition(conditionAggregator.thatORsWith(ObjectsShouldInternal.<MEMBER>prependDescription("should")));
}

@SuppressWarnings("unchecked")
SELF self() {
return (SELF) this;
}

static class MembersShouldInternal extends AbstractMembersShouldInternal<JavaMember, MembersShouldInternal> {

MembersShouldInternal(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2014-2022 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.lang.syntax;

import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.base.Function;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaCodeUnit;
import com.tngtech.archunit.core.domain.JavaMember;
import com.tngtech.archunit.lang.conditions.ArchConditions;
import com.tngtech.archunit.lang.syntax.elements.ClassesThat;
import com.tngtech.archunit.lang.syntax.elements.OnlyBeCalledSpecification;

class OnlyBeCalledSpecificationInternal<SHOULD extends AbstractMembersShouldInternal<? extends JavaCodeUnit, SHOULD>> implements OnlyBeCalledSpecification<SHOULD> {
private final SHOULD codeUnitsShould;

OnlyBeCalledSpecificationInternal(SHOULD codeUnitsShould) {
this.codeUnitsShould = codeUnitsShould;
}

@Override
public SHOULD byClassesThat(DescribedPredicate<? super JavaClass> predicate) {
return codeUnitsShould.addCondition(ArchConditions.onlyBeCalledByClassesThat(predicate));
}

@Override
public ClassesThat<SHOULD> byClassesThat() {
return new ClassesThatInternal<>(new Function<DescribedPredicate<? super JavaClass>, SHOULD>() {
@Override
public SHOULD apply(DescribedPredicate<? super JavaClass> predicate) {
return codeUnitsShould.addCondition(ArchConditions.onlyBeCalledByClassesThat(predicate));
}
});
}

@Override
public SHOULD byCodeUnitsThat(DescribedPredicate<? super JavaMember> predicate) {
return codeUnitsShould.addCondition(ArchConditions.onlyBeCalledByCodeUnitsThat(predicate));
}

@Override
public SHOULD byMethodsThat(DescribedPredicate<? super JavaMember> predicate) {
return codeUnitsShould.addCondition(ArchConditions.onlyBeCalledByMethodsThat(predicate));
}

@Override
public SHOULD byConstructorsThat(DescribedPredicate<? super JavaMember> predicate) {
return codeUnitsShould.addCondition(ArchConditions.onlyBeCalledByConstructorsThat(predicate));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,19 @@ public interface CodeUnitsShould<CONJUNCTION extends CodeUnitsShouldConjunction<
@PublicAPI(usage = ACCESS)
CONJUNCTION notHaveRawParameterTypes(Class<?>... parameterTypes);

/**
* Asserts that only certain objects call the code units selected by this rule.
* <br><br>
* E.g.
* <pre><code>
* {@link ArchRuleDefinition#codeUnits() codeUnits()}.{@link GivenCodeUnits#should() should()}.{@link CodeUnitsShould#onlyBeCalled() onlyBeCalled()}.{@link OnlyBeCalledSpecification#byClassesThat() byClassesThat()}.{@link ClassesThat#belongToAnyOf(Class[]) belongToAnyOf(SomeClass.class)}
* </code></pre>
*
* @return A syntax element that allows restricting how code units can be called
*/
@PublicAPI(usage = ACCESS)
OnlyBeCalledSpecification<CONJUNCTION> onlyBeCalled();

/**
* Asserts that {@link JavaCodeUnit JavaCodeUnits} have the specified fully qualified raw parameter type names.
* <br><br>
Expand Down
Loading

0 comments on commit 348819d

Please sign in to comment.