I'd like to enforce a policy in my project, that if something is made public
then it must be used from another package. I'd like to enforce this for pretty much everything - classes, interfaces, methods...
methods()
.that()
.arePublic()
.should()
.onlyBeCalled()
.byClassesThat()
.resideOutsideOfPackage(/*...*/);
However, resideOutsideOfPackage
requires a specific String packageName
whereas, I'd like to use the package name of the currently traversed method's class'.
In this case you need to implement your own ArchCondition
, I have prepared a skeleton that you can adapt to your needs.
ArchRule enforceLeastRequiredModifier = CompositeArchRule.of(
classes().should(haveLeastRequiredModifier())
).and(
members().should(haveLeastRequiredModifier())
);
private static <T extends HasModifiers & HasDescription & HasSourceCodeLocation> ArchCondition<T> haveLeastRequiredModifier() {
return new ArchCondition<T>("have least required modifier") {
@Override
public void check(T item, ConditionEvents events) {
boolean isPublic = item.getModifiers().contains(PUBLIC);
boolean isProtected = item.getModifiers().contains(PROTECTED);
boolean isPrivate = item.getModifiers().contains(PRIVATE);
boolean isPackagePrivate = !isPublic && !isProtected && !isPrivate;
if (isPublic) {
boolean isUsedInOtherPackage;
if (item instanceof JavaClass) {
JavaClass javaClass = (JavaClass) item;
isUsedInOtherPackage = javaClass.getAccessesToSelf().stream().anyMatch(access -> !access.getOriginOwner().getPackageName().equals(javaClass.getPackageName()));
} else {
JavaMember javaMember = (JavaMember) item;
JavaClass memberOwner = javaMember.getOwner();
isUsedInOtherPackage = javaMember.getAccessesToSelf().stream().anyMatch(access -> !access.getOriginOwner().getPackageName().equals(memberOwner.getPackageName()));
}
if (isUsedInOtherPackage) {
events.add(satisfied(item, createMessage(item, "is used in other package")));
} else {
events.add(violated(item, createMessage(item, "could be package-private")));
}
}
// TODO if needed, do the same for isProtected and isPackagePrivate
}
};
}
Or alternatively split the big condition in smaller ones by writing rules like
classes().that().arePublic().should(beAccessedFromOtherPackage())