com.tngtech.archunit.junit.internal.ElementResolver Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of archunit-junit5-engine Show documentation
Show all versions of archunit-junit5-engine Show documentation
A Java architecture test library, to specify and assert architecture rules in plain Java - Module 'archunit-junit5-engine'
The newest version!
/*
* Copyright 2014-2024 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.junit.internal;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Deque;
import java.util.LinkedList;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import com.tngtech.archunit.base.ClassLoaders;
import com.tngtech.archunit.base.MayResolveTypesViaReflection;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.UniqueId;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.tngtech.archunit.junit.internal.ArchUnitTestDescriptor.CLASS_SEGMENT_TYPE;
import static com.tngtech.archunit.junit.internal.ArchUnitTestDescriptor.FIELD_SEGMENT_TYPE;
import static com.tngtech.archunit.junit.internal.ArchUnitTestDescriptor.METHOD_SEGMENT_TYPE;
class ElementResolver {
private final ArchUnitEngineDescriptor engineDescriptor;
private final UniqueId processedId;
private final Deque segmentsToResolve;
private ElementResolver(ArchUnitEngineDescriptor engineDescriptor, UniqueId processedId, Deque segmentsToResolve) {
this.engineDescriptor = checkNotNull(engineDescriptor);
this.processedId = checkNotNull(processedId);
this.segmentsToResolve = checkNotNull(segmentsToResolve);
}
PossiblyResolvedClass resolveClass() {
UniqueId.Segment nextSegment = checkNotNull(segmentsToResolve.peekFirst());
if (!CLASS_SEGMENT_TYPE.equals(nextSegment.getType())) {
return new ClassNotRequested();
}
return tryResolveClass(classOf(nextSegment), processedId.append(nextSegment));
}
PossiblyResolvedClass resolveClass(Class> clazz) {
return tryResolveClass(clazz, processedId.append(CLASS_SEGMENT_TYPE, clazz.getName()));
}
PossiblyResolvedMember resolveMethod(Method method) {
return resolveMember(method, METHOD_SEGMENT_TYPE);
}
PossiblyResolvedMember resolveField(Field field) {
return resolveMember(field, FIELD_SEGMENT_TYPE);
}
private PossiblyResolvedMember resolveMember(Member member, String segmentType) {
UniqueId requestedId = processedId.append(segmentType, member.getName());
return engineDescriptor.findByUniqueId(requestedId).isPresent()
? new SuccessfullyResolvedMember()
: new UnresolvedMember(member, segmentType);
}
void resolve(String segmentType, String segmentValue, Consumer doIfResolved) {
if (segmentsToResolve.isEmpty()) {
handleNewSegment(segmentType, segmentValue, doIfResolved);
} else {
handleRequestedSegment(segmentType, segmentValue, doIfResolved);
}
}
UniqueId getUniqueId() {
return processedId;
}
private PossiblyResolvedClass tryResolveClass(Class> clazz, UniqueId classId) {
ElementResolver childResolver = new ElementResolver(engineDescriptor, classId, tail(segmentsToResolve));
return engineDescriptor.findByUniqueId(classId)
.map(testDescriptor ->
new RequestedAndSuccessfullyResolvedClass(testDescriptor, childResolver))
.orElseGet(() -> new RequestedButUnresolvedClass(clazz, childResolver));
}
private Deque tail(Deque segmentsToResolve) {
LinkedList result = new LinkedList<>(segmentsToResolve);
result.pollFirst();
return result;
}
@MayResolveTypesViaReflection(reason = "Within the ArchUnitTestEngine we may resolve types via reflection, since they are needed anyway")
private Class> classOf(UniqueId.Segment segment) {
try {
return ClassLoaders.loadClass(segment.getValue());
} catch (ClassNotFoundException e) {
throw new ArchTestInitializationException(e, "Failed to load class from %s segment %s",
UniqueId.class.getSimpleName(), segment);
}
}
private void handleRequestedSegment(String segmentType, String segmentValue, Consumer doIfResolved) {
UniqueId.Segment nextSegment = checkNotNull(segmentsToResolve.peekFirst());
if (matches(segmentType, segmentValue).test(nextSegment)) {
doIfResolved.accept(new ElementResolver(engineDescriptor, processedId.append(nextSegment), tail(segmentsToResolve)));
}
}
private Predicate matches(String segmentType, String segmentValue) {
return nextSegment -> nextSegment.getType().equals(segmentType) && nextSegment.getValue().equals(segmentValue);
}
private void handleNewSegment(String segmentType, String segmentValue, Consumer doIfResolved) {
doIfResolved.accept(new ElementResolver(engineDescriptor, processedId.append(segmentType, segmentValue), new LinkedList<>()));
}
static ElementResolver create(ArchUnitEngineDescriptor engineDescriptor, UniqueId rootId, UniqueId targetId) {
return new ElementResolver(engineDescriptor, rootId, getRemainingSegments(rootId, targetId));
}
static ElementResolver create(ArchUnitEngineDescriptor engineDescriptor, UniqueId rootId, Class> testClass) {
UniqueId targetId = rootId.append(CLASS_SEGMENT_TYPE, testClass.getName());
return create(engineDescriptor, rootId, targetId);
}
static ElementResolver create(ArchUnitEngineDescriptor engineDescriptor, UniqueId rootId, Class> testClass, Method testMethod) {
UniqueId targetId = rootId
.append(CLASS_SEGMENT_TYPE, testClass.getName())
.append(METHOD_SEGMENT_TYPE, testMethod.getName());
return create(engineDescriptor, rootId, targetId);
}
static ElementResolver create(ArchUnitEngineDescriptor engineDescriptor, UniqueId rootId, Class> testClass, Field testField) {
UniqueId targetId = rootId
.append(CLASS_SEGMENT_TYPE, testClass.getName())
.append(FIELD_SEGMENT_TYPE, testField.getName());
return create(engineDescriptor, rootId, targetId);
}
private static Deque getRemainingSegments(UniqueId rootId, UniqueId targetId) {
Deque remainingSegments = new LinkedList<>(targetId.getSegments());
rootId.getSegments().forEach(segment -> {
checkState(segment.equals(remainingSegments.peekFirst()),
"targetId %s should start with rootId %s", targetId, rootId);
remainingSegments.pollFirst();
});
return remainingSegments;
}
abstract static class PossiblyResolvedClass {
void ifRequestedButUnresolved(BiConsumer, ElementResolver> doIfResolved) {
}
PossiblyResolvedClass ifRequestedAndResolved(BiConsumer doIfResolved) {
return this;
}
}
private static class RequestedAndSuccessfullyResolvedClass extends PossiblyResolvedClass {
private final CreatesChildren classDescriptor;
private final ElementResolver childResolver;
RequestedAndSuccessfullyResolvedClass(TestDescriptor classDescriptor, ElementResolver childResolver) {
checkArgument(classDescriptor instanceof CreatesChildren,
"descriptor with uniqueId %s is expected to implement %s",
classDescriptor.getUniqueId(), CreatesChildren.class.getSimpleName());
this.classDescriptor = (CreatesChildren) classDescriptor;
this.childResolver = childResolver;
}
@Override
RequestedAndSuccessfullyResolvedClass ifRequestedAndResolved(BiConsumer doIfResolved) {
doIfResolved.accept(classDescriptor, childResolver);
return this;
}
}
private class RequestedButUnresolvedClass extends PossiblyResolvedClass {
private final Class> clazz;
private final ElementResolver childResolver;
RequestedButUnresolvedClass(Class> clazz, ElementResolver childResolver) {
this.clazz = clazz;
this.childResolver = childResolver;
}
@Override
void ifRequestedButUnresolved(BiConsumer, ElementResolver> doWithChildResolver) {
doWithChildResolver.accept(clazz, childResolver);
}
}
private static class ClassNotRequested extends PossiblyResolvedClass {
}
abstract static class PossiblyResolvedMember {
abstract void ifUnresolved(Consumer childResolver);
}
private static class SuccessfullyResolvedMember extends PossiblyResolvedMember {
@Override
void ifUnresolved(Consumer childResolver) {
}
}
private class UnresolvedMember extends PossiblyResolvedMember {
private final Member member;
private final String segmentType;
UnresolvedMember(Member member, String segmentType) {
this.member = member;
this.segmentType = segmentType;
}
@Override
void ifUnresolved(Consumer doWithChildResolver) {
resolve(segmentType, member.getName(), doWithChildResolver);
}
}
}