/* * Copyright 2017 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 java.net.URI; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.SetMultimap; import com.tngtech.archunit.Internal; import com.tngtech.archunit.base.Function; import com.tngtech.archunit.core.importer.DomainBuilders; import com.tngtech.archunit.core.importer.DomainBuilders.ConstructorCallTargetBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.FieldAccessTargetBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaAnnotationBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaClassBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaConstructorBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaConstructorCallBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaEnumConstantBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaFieldAccessBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaFieldBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaMethodCallBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.JavaStaticInitializerBuilder; import com.tngtech.archunit.core.importer.DomainBuilders.MethodCallTargetBuilder; /** * Together with {@link DomainBuilders}, this class is the link to create domain objects from the import * context. To make the API clear, we try to keep only those methods public, which are really meant to be used. * Constructors of domain objects however, are not to be used under any circumstances, only ArchUnit may construct * domain objects. To keep <code>..domain..</code> and <code>..importer..</code> in reasonably grouped packages, we unfortunately * need to have some public link, which is created by supplying {@link DomainBuilders}, which can only be * instantiated within package <code>..importer..</code> to {@link DomainObjectCreationContext}, which is the only place * to create domain objects.<br><br> * To make up for the public visibility, the JLS forces upon us, * {@link DomainObjectCreationContext} is declared {@link Internal @Internal}, to emphasize that it is not meant * for ArchUnit users, to be accessed in any way. */ @Internal public class DomainObjectCreationContext { public static JavaClasses createJavaClasses(Map<String, JavaClass> classes, ImportContext importContext) { return JavaClasses.of(classes, importContext); } public static JavaClass createJavaClass(JavaClassBuilder builder) { return new JavaClass(builder); } public static void completeClassHierarchy(JavaClass javaClass, ImportContext importContext) { javaClass.completeClassHierarchyFrom(importContext); } public static void completeMembers(JavaClass javaClass, ImportContext importContext) { javaClass.completeMembers(importContext); } public static JavaAnnotation createJavaAnnotation(JavaAnnotationBuilder builder) { return new JavaAnnotation(builder); } public static JavaClassList createJavaClassList(List<JavaClass> elements) { return new JavaClassList(elements); } public static JavaField createJavaField(JavaFieldBuilder builder) { return new JavaField(builder); } public static JavaFieldAccess createJavaFieldAccess(JavaFieldAccessBuilder builder) { return new JavaFieldAccess(builder); } public static AccessTarget.FieldAccessTarget createFieldAccessTarget(FieldAccessTargetBuilder builder) { return new AccessTarget.FieldAccessTarget(builder); } public static JavaConstructor createJavaConstructor(JavaConstructorBuilder builder) { return new JavaConstructor(builder); } public static JavaConstructorCall createJavaConstructorCall(JavaConstructorCallBuilder builder) { return new JavaConstructorCall(builder); } public static AccessTarget.ConstructorCallTarget createConstructorCallTarget(ConstructorCallTargetBuilder builder) { return new AccessTarget.ConstructorCallTarget(builder); } public static JavaMethod createJavaMethod(JavaMethodBuilder builder) { return new JavaMethod(builder); } public static JavaMethodCall createJavaMethodCall(JavaMethodCallBuilder builder) { return new JavaMethodCall(builder); } public static AccessTarget.MethodCallTarget createMethodCallTarget(MethodCallTargetBuilder builder) { return new AccessTarget.MethodCallTarget(builder); } public static JavaStaticInitializer createJavaStaticInitializer(JavaStaticInitializerBuilder builder) { return new JavaStaticInitializer(builder); } public static JavaEnumConstant createJavaEnumConstant(JavaEnumConstantBuilder builder) { return new JavaEnumConstant(builder); } public static Source createSource(URI uri) { return new Source(uri); } static class AccessContext { final SetMultimap<JavaClass, JavaFieldAccess> fieldAccessesByTarget = HashMultimap.create(); final SetMultimap<JavaClass, JavaMethodCall> methodCallsByTarget = HashMultimap.create(); final SetMultimap<String, JavaConstructorCall> constructorCallsByTarget = HashMultimap.create(); private AccessContext() { } void mergeWith(AccessContext other) { fieldAccessesByTarget.putAll(other.fieldAccessesByTarget); methodCallsByTarget.putAll(other.methodCallsByTarget); constructorCallsByTarget.putAll(other.constructorCallsByTarget); } static class Part extends AccessContext { Part() { } Part(JavaCodeUnit codeUnit) { for (JavaFieldAccess access : codeUnit.getFieldAccesses()) { fieldAccessesByTarget.put(access.getTarget().getOwner(), access); } for (JavaMethodCall call : codeUnit.getMethodCallsFromSelf()) { methodCallsByTarget.put(call.getTarget().getOwner(), call); } for (JavaConstructorCall call : codeUnit.getConstructorCallsFromSelf()) { constructorCallsByTarget.put(call.getTarget().getFullName(), call); } } } static class TopProcess extends AccessContext { private final Collection<JavaClass> classes; TopProcess(Collection<JavaClass> classes) { this.classes = classes; } void finish() { for (JavaClass clazz : classes) { for (JavaField field : clazz.getFields()) { field.registerAccessesToField(getFieldAccessesTo(field)); } for (JavaMethod method : clazz.getMethods()) { method.registerCallsToMethod(getMethodCallsOf(method)); } for (final JavaConstructor constructor : clazz.getConstructors()) { constructor.registerCallsToConstructor(constructorCallsByTarget.get(constructor.getFullName())); } } } private Supplier<Set<JavaFieldAccess>> getFieldAccessesTo(final JavaField field) { return newAccessSupplier(field.getOwner(), fieldAccessTargetResolvesTo(field)); } private Function<JavaClass, Set<JavaFieldAccess>> fieldAccessTargetResolvesTo(final JavaField field) { return new Function<JavaClass, Set<JavaFieldAccess>>() { @Override public Set<JavaFieldAccess> apply(JavaClass input) { Set<JavaFieldAccess> result = new HashSet<>(); for (JavaFieldAccess access : fieldAccessesByTarget.get(input)) { if (access.getTarget().resolveField().asSet().contains(field)) { result.add(access); } } return result; } }; } private Supplier<Set<JavaMethodCall>> getMethodCallsOf(final JavaMethod method) { return newAccessSupplier(method.getOwner(), methodCallTargetResolvesTo(method)); } private Function<JavaClass, Set<JavaMethodCall>> methodCallTargetResolvesTo(final JavaMethod method) { return new Function<JavaClass, Set<JavaMethodCall>>() { @Override public Set<JavaMethodCall> apply(JavaClass input) { Set<JavaMethodCall> result = new HashSet<>(); for (JavaMethodCall call : methodCallsByTarget.get(input)) { if (call.getTarget().resolve().contains(method)) { result.add(call); } } return result; } }; } private <T> Supplier<Set<T>> newAccessSupplier(final JavaClass owner, final Function<JavaClass, Set<T>> doWithEachClass) { return Suppliers.memoize(new Supplier<Set<T>>() { @Override public Set<T> get() { ImmutableSet.Builder<T> result = ImmutableSet.builder(); for (final JavaClass javaClass : getPossibleTargetClassesForAccess()) { result.addAll(doWithEachClass.apply(javaClass)); } return result.build(); } private Set<JavaClass> getPossibleTargetClassesForAccess() { return ImmutableSet.<JavaClass>builder() .add(owner) .addAll(owner.getAllSubClasses()) .build(); } }); } } } }