All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tngtech.archunit.core.domain.JavaCodeUnit Maven / Gradle / Ivy

Go to download

A Java architecture test library, to specify and assert architecture rules in plain Java - Module 'archunit'

There is a newer version: 1.3.0
Show newest version
/*
 * Copyright 2014-2023 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.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.tngtech.archunit.PublicAPI;
import com.tngtech.archunit.base.ChainableFunction;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.base.ForwardingList;
import com.tngtech.archunit.base.MayResolveTypesViaReflection;
import com.tngtech.archunit.base.ResolvesTypesViaReflection;
import com.tngtech.archunit.core.domain.properties.HasName;
import com.tngtech.archunit.core.domain.properties.HasOwner;
import com.tngtech.archunit.core.domain.properties.HasParameterTypes;
import com.tngtech.archunit.core.domain.properties.HasReturnType;
import com.tngtech.archunit.core.domain.properties.HasThrowsClause;
import com.tngtech.archunit.core.domain.properties.HasTypeParameters;
import com.tngtech.archunit.core.importer.DomainBuilders.JavaCodeUnitBuilder;
import com.tngtech.archunit.core.importer.DomainBuilders.TryCatchBlockBuilder;

import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Sets.union;
import static com.tngtech.archunit.PublicAPI.Usage.ACCESS;
import static com.tngtech.archunit.core.domain.Formatters.formatMethod;
import static com.tngtech.archunit.core.domain.properties.HasName.Utils.namesOf;
import static java.util.stream.Collectors.toSet;

/**
 * Represents a unit of code containing accesses to other units of code. A unit of code can be
 * 
    *
  • a method
  • *
  • a constructor
  • *
  • a static initializer
  • *
* in particular every place, where Java code with behavior, like calling other methods or accessing fields, can * be defined. */ @PublicAPI(usage = ACCESS) public abstract class JavaCodeUnit extends JavaMember implements HasParameterTypes, HasReturnType, HasTypeParameters, HasThrowsClause { private final ReturnType returnType; private final Parameters parameters; private final String fullName; private final List> typeParameters; private Set fieldAccesses = Collections.emptySet(); private Set methodCalls = Collections.emptySet(); private Set constructorCalls = Collections.emptySet(); private Set methodReferences = Collections.emptySet(); private Set constructorReferences = Collections.emptySet(); private Set tryCatchBlocks = Collections.emptySet(); private Set referencedClassObjects; private Set instanceofChecks; JavaCodeUnit(JavaCodeUnitBuilder builder) { super(builder); typeParameters = builder.getTypeParameters(this); returnType = new ReturnType(this, builder); parameters = new Parameters(this, builder); fullName = formatMethod(getOwner().getName(), getName(), namesOf(getRawParameterTypes())); } /** * @return The full name of this {@link JavaCodeUnit}, i.e. a string containing {@code ${declaringClass}.${name}(${parameterTypes})} */ @Override @PublicAPI(usage = ACCESS) public String getFullName() { return fullName; } /** * @return the raw parameter types of this {@link JavaCodeUnit}. On the contrary to {@link #getParameterTypes()} * these will always be {@link JavaClass} and thus not containing any parameterization/generic * information. Note that the raw parameter types can contain synthetic parameters added by the compiler. * E.g. for inner class constructors that receive the outer class as synthetic parameter or enum constructors * that receive enum name and ordinal as synthetic parameters. There is no guarantee about the number * of synthetic parameters, nor if they are appended or prepended, as e.g. local classes will append * all local variables from the outer scope that are referenced as additional synthetic constructor * parameters. * * @see #getParameterTypes() * @see #getParameters() */ @Override @PublicAPI(usage = ACCESS) public List getRawParameterTypes() { return parameters.getRawParameterTypes(); } /** * @return the (possibly generic) parameter types of this {@link JavaCodeUnit}. This could for example be a * {@link JavaTypeVariable} or a {@link JavaParameterizedType}, but also simply a {@link JavaClass}.
* Note that if the method has a generic signature (e.g. declaring a parameterized type) then * on the contrary to {@link #getRawParameterTypes()} these types will be parsed from the * signature encoded in the bytecode, i.e. they will not contain synthetic parameters added by the * compiler. However, if there is no generic signature, then this information can also not be parsed * from the signature. In this case the parameter types will be equal to the raw parameter types * and by that can also contain synthetic parameters. * * @see #getRawParameterTypes() * @see #getParameters() */ @Override @PublicAPI(usage = ACCESS) public List getParameterTypes() { return parameters.getParameterTypes(); } /** * @return the {@link JavaParameter parameters} of this {@link JavaCodeUnit}. On the contrary to the Reflection API this will only contain * the parameters from the signature and not synthetic parameters, if the signature is generic. In these cases * {@link #getParameters()}{@code .size()} will always be equal to {@link #getParameterTypes()}{@code .size()}, * but not necessarily to {@link #getRawParameterTypes()}{@code .size()} in case the compiler adds synthetic parameters.
* Note that for non-generic method signatures {@link #getParameters()} actually contains the raw parameter types and thus * can also contain synthetic parameters. Unfortunately there is no way at the moment to distinguish synthetic * parameters from non-synthetic parameters in these cases. * * @see #getRawParameterTypes() * @see #getParameterTypes() */ @PublicAPI(usage = ACCESS) public List getParameters() { return parameters; } @Override @PublicAPI(usage = ACCESS) public abstract ThrowsClause getThrowsClause(); /** * @return The types thrown by this method, similar to {@link Method#getExceptionTypes()} */ @PublicAPI(usage = ACCESS) public List getExceptionTypes() { return getThrowsClause().getTypes(); } @Override @PublicAPI(usage = ACCESS) public JavaType getReturnType() { return returnType.get(); } @Override @PublicAPI(usage = ACCESS) public JavaClass getRawReturnType() { return returnType.getRaw(); } /** * @return All raw types involved in this code unit's signature, * which is the union of all raw types involved in the {@link #getReturnType() return type}, * the {@link #getParameterTypes() parameter types} and the {@link #getTypeParameters() type parameters} of this code unit. * For a definition of "all raw types involved" consult {@link JavaType#getAllInvolvedRawTypes()}. */ @Override @PublicAPI(usage = ACCESS) public Set getAllInvolvedRawTypes() { return Stream.of( Stream.of(this.returnType.get()), this.parameters.getParameterTypes().stream(), this.typeParameters.stream() ).flatMap(s -> s).map(JavaType::getAllInvolvedRawTypes).flatMap(Set::stream).collect(toSet()); } @PublicAPI(usage = ACCESS) public Set getFieldAccesses() { return fieldAccesses; } @PublicAPI(usage = ACCESS) public abstract Set> getCallsOfSelf(); @PublicAPI(usage = ACCESS) public Set getMethodCallsFromSelf() { return methodCalls; } @PublicAPI(usage = ACCESS) public Set getConstructorCallsFromSelf() { return constructorCalls; } @PublicAPI(usage = ACCESS) public Set getMethodReferencesFromSelf() { return methodReferences; } @PublicAPI(usage = ACCESS) public Set getConstructorReferencesFromSelf() { return constructorReferences; } @PublicAPI(usage = ACCESS) public Set getReferencedClassObjects() { return referencedClassObjects; } @PublicAPI(usage = ACCESS) public Set getInstanceofChecks() { return instanceofChecks; } @PublicAPI(usage = ACCESS) public Set getTryCatchBlocks() { return tryCatchBlocks; } @PublicAPI(usage = ACCESS) public Set> getCallsFromSelf() { return union(getMethodCallsFromSelf(), getConstructorCallsFromSelf()); } @PublicAPI(usage = ACCESS) public Set> getCodeUnitReferencesFromSelf() { return union(getMethodReferencesFromSelf(), getConstructorReferencesFromSelf()); } @PublicAPI(usage = ACCESS) public Set> getAccessesFromSelf() { return ImmutableSet.>builder() .addAll(getCallsFromSelf()) .addAll(getFieldAccesses()) .addAll(getCodeUnitReferencesFromSelf()) .build(); } @PublicAPI(usage = ACCESS) 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> getAnnotations() { return (Set>) super.getAnnotations(); } @Override @SuppressWarnings("unchecked") // we know the 'owning' member is this code unit public JavaAnnotation getAnnotationOfType(String typeName) { return (JavaAnnotation) super.getAnnotationOfType(typeName); } @Override @SuppressWarnings("unchecked") // we know the 'owning' member is this code unit public Optional> tryGetAnnotationOfType(String typeName) { return (Optional>) super.tryGetAnnotationOfType(typeName); } @PublicAPI(usage = ACCESS) public List> getTypeParameters() { return typeParameters; } @PublicAPI(usage = ACCESS) public List>> getParameterAnnotations() { return parameters.getAnnotations(); } void completeFrom(ImportContext context) { Set tryCatchBlockBuilders = context.createTryCatchBlockBuilders(this); fieldAccesses = context.createFieldAccessesFor(this, tryCatchBlockBuilders); methodCalls = context.createMethodCallsFor(this, tryCatchBlockBuilders); constructorCalls = context.createConstructorCallsFor(this, tryCatchBlockBuilders); methodReferences = context.createMethodReferencesFor(this, tryCatchBlockBuilders); constructorReferences = context.createConstructorReferencesFor(this, tryCatchBlockBuilders); tryCatchBlocks = tryCatchBlockBuilders.stream() .map(builder -> builder.build(this)) .collect(toImmutableSet()); referencedClassObjects = context.createReferencedClassObjectsFor(this); instanceofChecks = context.createInstanceofChecksFor(this); } @ResolvesTypesViaReflection @MayResolveTypesViaReflection(reason = "Just part of a bigger resolution process") static Class[] reflect(List parameters) { return parameters.stream().map(JavaClass::reflect).toArray(Class[]::new); } private static class Parameters extends ForwardingList { private final List rawParameterTypes; private final List parameterTypes; private final List>> parameterAnnotations; private final List parameters; Parameters(JavaCodeUnit owner, JavaCodeUnitBuilder builder) { rawParameterTypes = builder.getRawParameterTypes(); parameterTypes = getParameterTypes(builder.getGenericParameterTypes(owner)); parameters = createParameters(owner, builder, parameterTypes); parameterAnnotations = annotationsOf(parameters); } private List>> annotationsOf(List parameters) { ImmutableList.Builder>> result = ImmutableList.builder(); for (JavaParameter parameter : parameters) { result.add(parameter.getAnnotations()); } return result.build(); } private static List createParameters(JavaCodeUnit owner, JavaCodeUnitBuilder builder, List parameterTypes) { ImmutableList.Builder result = ImmutableList.builder(); for (int i = 0; i < parameterTypes.size(); i++) { result.add(new JavaParameter(owner, builder.getParameterAnnotationsBuilder(i), i, parameterTypes.get(i))); } return result.build(); } @SuppressWarnings({"unchecked", "rawtypes"}) // the cast is safe because the list is immutable, thus used in a covariant way private List getParameterTypes(List genericParameterTypes) { return genericParameterTypes.isEmpty() ? (List) rawParameterTypes : genericParameterTypes; } List getRawParameterTypes() { return rawParameterTypes; } List getParameterTypes() { return parameterTypes; } List>> getAnnotations() { return parameterAnnotations; } @Override protected List delegate() { return parameters; } } private static class ReturnType { private final JavaClass rawReturnType; private final JavaType returnType; ReturnType(JavaCodeUnit owner, JavaCodeUnitBuilder builder) { rawReturnType = builder.getRawReturnType(); returnType = builder.getGenericReturnType(owner); } JavaClass getRaw() { return rawReturnType; } JavaType get() { return returnType; } } /** * Predefined {@link DescribedPredicate predicates} targeting {@link JavaCodeUnit}. * Note that due to inheritance further predicates for {@link JavaCodeUnit} can be found in the following locations: *
    *
  • {@link JavaMember.Predicates}
  • *
  • {@link HasParameterTypes.Predicates}
  • *
  • {@link HasReturnType.Predicates}
  • *
  • {@link HasThrowsClause.Predicates}
  • *
*/ @PublicAPI(usage = ACCESS) public static final class Predicates { private Predicates() { } @PublicAPI(usage = ACCESS) public static DescribedPredicate constructor() { return new DescribedPredicate("constructor") { @Override public boolean test(JavaCodeUnit input) { return input.isConstructor(); } }; } @PublicAPI(usage = ACCESS) public static DescribedPredicate method() { return new DescribedPredicate("method") { @Override public boolean test(JavaCodeUnit input) { return input.isMethod(); } }; } } /** * Predefined {@link ChainableFunction functions} to transform {@link JavaCodeUnit}. * Note that due to inheritance further functions for {@link JavaCodeUnit} can be found in the following locations: *
    *
  • {@link HasName.Functions}
  • *
  • {@link HasName.AndFullName.Functions}
  • *
  • {@link HasReturnType.Functions}
  • *
  • {@link HasOwner.Functions}
  • *
*/ @PublicAPI(usage = ACCESS) public static final class Functions { private Functions() { } @PublicAPI(usage = ACCESS) public static final class Get { private Get() { } @PublicAPI(usage = ACCESS) public static ChainableFunction> throwsClause() { return new ChainableFunction>() { // getThrowsClause() will always return a ThrowsClause typed to the owner, i.e. T // We want to avoid that annoying recursive SELF type parameter and instead override covariantly... @SuppressWarnings("unchecked") @Override public ThrowsClause apply(T input) { return (ThrowsClause) input.getThrowsClause(); } }; } @PublicAPI(usage = ACCESS) public static final ChainableFunction>> GET_CALLS_OF_SELF = new ChainableFunction>>() { @Override public Set> apply(JavaCodeUnit input) { return input.getCallsOfSelf(); } }; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy