blob: d3da232d7defcb9865ab2f65b98a484372396a0c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.flink.architecture.common;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaMethod;
import com.tngtech.archunit.core.domain.JavaParameterizedType;
import com.tngtech.archunit.core.domain.JavaType;
import com.tngtech.archunit.core.domain.properties.HasName;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.apache.flink.architecture.common.SourcePredicates.isJavaClass;
/** Common conditions for architecture tests. */
public class Conditions {
/** Generic condition to check fulfillment of a predicate. */
public static <T extends HasName> ArchCondition<T> fulfill(DescribedPredicate<T> predicate) {
return new ArchCondition<T>(predicate.getDescription()) {
@Override
public void check(T item, ConditionEvents events) {
if (!predicate.apply(item)) {
final String message =
String.format(
"%s does not satisfy: %s",
item.getName(), predicate.getDescription());
events.add(SimpleConditionEvent.violated(item, message));
}
}
};
}
/**
* Tests leaf types of a method against the given predicate.
*
* <p>Given some {@link JavaType}, "leaf" types are recursively determined as described below.
* Leaf types are taken from argument, return, and (declared) exception types.
*
* <ul>
* <li>If the type is an array type, check its base component type.
* <li>If the type is a generic type, check the type itself and all of its type arguments.
* <li>Otherwise, check just the type itself.
* </ul>
*/
public static ArchCondition<JavaMethod> haveLeafTypes(
DescribedPredicate<JavaClass> typePredicate) {
return haveLeafReturnTypes(typePredicate)
.and(haveLeafArgumentTypes(typePredicate))
.and(haveLeafExceptionTypes(typePredicate));
}
/**
* Tests leaf return types of a method against the given predicate.
*
* <p>See {@link #haveLeafTypes(DescribedPredicate)} for details.
*/
public static ArchCondition<JavaMethod> haveLeafReturnTypes(
DescribedPredicate<JavaClass> typePredicate) {
return new ArchCondition<JavaMethod>(
"have leaf return types" + typePredicate.getDescription()) {
@Override
public void check(JavaMethod method, ConditionEvents events) {
for (JavaClass leafType : getLeafTypes(method.getReturnType())) {
if (!isJavaClass(leafType)) {
continue;
}
if (!typePredicate.apply(leafType)) {
final String message =
String.format(
"%s: Returned leaf type %s does not satisfy: %s",
method.getFullName(),
leafType.getName(),
typePredicate.getDescription());
events.add(SimpleConditionEvent.violated(method, message));
}
}
}
};
}
/**
* Tests leaf argument types of a method against the given predicate.
*
* <p>See {@link #haveLeafTypes(DescribedPredicate)} for details.
*/
public static ArchCondition<JavaMethod> haveLeafArgumentTypes(
DescribedPredicate<JavaClass> typePredicate) {
return new ArchCondition<JavaMethod>(
"have leaf argument types" + typePredicate.getDescription()) {
@Override
public void check(JavaMethod method, ConditionEvents events) {
final List<JavaClass> leafArgumentTypes =
method.getParameterTypes().stream()
.flatMap(argumentType -> getLeafTypes(argumentType).stream())
.collect(Collectors.toList());
for (JavaClass leafType : leafArgumentTypes) {
if (!isJavaClass(leafType)) {
continue;
}
if (!typePredicate.apply(leafType)) {
final String message =
String.format(
"%s: Argument leaf type %s does not satisfy: %s",
method.getFullName(),
leafType.getName(),
typePredicate.getDescription());
events.add(SimpleConditionEvent.violated(method, message));
}
}
}
};
}
/**
* Tests leaf exception types of a method against the given predicate.
*
* <p>See {@link #haveLeafTypes(DescribedPredicate)} for details.
*/
public static ArchCondition<JavaMethod> haveLeafExceptionTypes(
DescribedPredicate<JavaClass> typePredicate) {
return new ArchCondition<JavaMethod>(
"have leaf exception types" + typePredicate.getDescription()) {
@Override
public void check(JavaMethod method, ConditionEvents events) {
final List<JavaClass> leafArgumentTypes =
method.getExceptionTypes().stream()
.flatMap(argumentType -> getLeafTypes(argumentType).stream())
.collect(Collectors.toList());
for (JavaClass leafType : leafArgumentTypes) {
if (!isJavaClass(leafType)) {
continue;
}
if (!typePredicate.apply(leafType)) {
final String message =
String.format(
"%s: Exception leaf type %s does not satisfy: %s",
method.getFullName(),
leafType.getName(),
typePredicate.getDescription());
events.add(SimpleConditionEvent.violated(method, message));
}
}
}
};
}
private static List<JavaClass> getLeafTypes(JavaType type) {
final JavaClass baseComponentType = type.toErasure().getBaseComponentType();
if (type instanceof JavaParameterizedType) {
final JavaParameterizedType parameterizedType = (JavaParameterizedType) type;
return Stream.concat(
Stream.of(baseComponentType),
parameterizedType.getActualTypeArguments().stream())
.flatMap(types -> getLeafTypes(types).stream())
.filter(SourcePredicates::isJavaClass)
.collect(Collectors.toList());
}
return Collections.singletonList(baseComponentType);
}
private Conditions() {}
}