declarative style for specifying tracked annotations

GitOrigin-RevId: 94bc427ed465cecc0cf2885e2a2cad749338848b
This commit is contained in:
Eugene Zhuravlev
2025-04-02 12:04:20 +02:00
committed by intellij-monorepo-bot
parent 4b4d12c3fb
commit 3bb6012069
5 changed files with 270 additions and 160 deletions

View File

@@ -36,6 +36,7 @@ out/production/AnnotationsTracker/D3Sub.class
out/production/AnnotationsTracker/D3Sub2.class
out/production/AnnotationsTracker/D4Sub.class
out/production/AnnotationsTracker/D4Sub2.class
out/production/AnnotationsTracker/D5Sub.class
out/production/AnnotationsTracker/D5Sub2.class
End of files
Compiling files:
@@ -56,5 +57,6 @@ src/D3Sub.java
src/D3Sub2.java
src/D4Sub.java
src/D4Sub2.java
src/D5Sub.java
src/D5Sub2.java
End of files

View File

@@ -0,0 +1,46 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import java.util.Set;
public class AnnotationGroup {
public enum AffectionKind {
added, removed, changed
}
public enum AnnTarget {
type, field, method, method_parameter
}
public enum AffectionScope {
/**
* If present in the returned result set, the usages of the annotated program element (class, field, method) will be affected.
* it means that files where this program element is references, will be marked for recompilation
*/
usages,
/**
* If present in the returned result set, the subclasses of the annotated class will be affected.
* If returned for an annotated field/method, the subclasses of the class containing this field/method will be affected.
*/
subclasses
}
public final String name;
public final Set<AffectionKind> affectionKind;
public final Set<AffectionScope> affectionScope;
public final Set<AnnTarget> targets;
public final Set<TypeRepr.ClassType> types;
private AnnotationGroup(String name, Set<AffectionKind> affectionKind, Set<AffectionScope> affectionScope, Set<AnnTarget> targets, Set<TypeRepr.ClassType> types) {
this.name = name;
this.affectionKind = affectionKind;
this.affectionScope = affectionScope;
this.targets = targets;
this.types = types;
}
public static AnnotationGroup of(String name, Set<AnnTarget> targets, Set<AffectionKind> affectionKind, Set<AffectionScope> affectionScope, Set<TypeRepr.ClassType> annotationTypes) {
return new AnnotationGroup(name, affectionKind, affectionScope, targets, annotationTypes);
}
}

View File

@@ -3,11 +3,13 @@ package org.jetbrains.jps.dependency.java;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.dependency.*;
import org.jetbrains.jps.dependency.diff.Difference;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
@@ -21,64 +23,165 @@ import static org.jetbrains.jps.javac.Iterators.*;
public abstract class JvmDifferentiateStrategyImpl implements JvmDifferentiateStrategy{
private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.dependency.java.JvmDifferentiateStrategyImpl");
protected enum AnnotationAffectionKind {
added, removed, changed
}
protected final <T extends AnnotationInstance, D extends AnnotationInstance.Diff<T>> boolean isAffectedByAnnotations(
Proto element, Difference.Specifier<T, D> annotationsDiff, Set<AnnotationAffectionKind> affectionKinds, Predicate<? super TypeRepr.ClassType> annotationSelector
Proto element, Difference.Specifier<T, D> annotationsDiff, Set<AnnotationGroup.AffectionKind> affectionKinds, Predicate<? super TypeRepr.ClassType> annotationSelector
) {
return !element.isPrivate() && find(getAffectedAnnotations(annotationsDiff, affectionKinds), annotationSelector::test) != null;
}
protected final <T extends AnnotationInstance, D extends AnnotationInstance.Diff<T>> Iterable<TypeRepr.ClassType> getAffectedAnnotations(
Difference.Specifier<T, D> annotationsDiff, Set<AnnotationAffectionKind> affectionKinds
Difference.Specifier<T, D> annotationsDiff, Set<AnnotationGroup.AffectionKind> affectionKinds
) {
Iterable<? extends T> added = affectionKinds.contains(AnnotationAffectionKind.added)? annotationsDiff.added() : List.of();
Iterable<? extends T> removed = affectionKinds.contains(AnnotationAffectionKind.removed)? annotationsDiff.removed() : List.of();
Iterable<? extends T> changed = affectionKinds.contains(AnnotationAffectionKind.changed)? map(annotationsDiff.changed(), Difference.Change::getPast) : List.of();
Iterable<? extends T> added = affectionKinds.contains(AnnotationGroup.AffectionKind.added)? annotationsDiff.added() : List.of();
Iterable<? extends T> removed = affectionKinds.contains(AnnotationGroup.AffectionKind.removed)? annotationsDiff.removed() : List.of();
Iterable<? extends T> changed = affectionKinds.contains(AnnotationGroup.AffectionKind.changed)? map(annotationsDiff.changed(), Difference.Change::getPast) : List.of();
return map(flat(List.of(added, removed, changed)), AnnotationInstance::getAnnotationClass);
}
protected enum AnnotationAffectionScope {
/**
* If present in the returned result set, the usages of the annotated program element (class, field, method) will be affected.
* it means that files where this program element is references, will be marked for recompilation
*/
usages,
/**
* If present in the returned result set, the subclasses of the annotated class will be affected.
* If returned for an annotated field/method, the subclasses of the class containing this field/method will be affected.
*/
subclasses
protected Iterable<AnnotationGroup> getTrackedAnnotations() {
return Collections.emptyList();
}
protected void affectClassAnnotationUsages(DifferentiateContext context, Set<AnnotationAffectionScope> toRecompile, Difference.Change<JvmClass, JvmClass.Diff> change, Utils future, Utils present) {
@Override
public boolean isAnnotationTracked(TypeRepr.@NotNull ClassType annotationType) {
return find(getTrackedAnnotations(), gr -> gr.types.contains(annotationType)) != null;
}
private Set<AnnotationGroup.AffectionScope> getMaxPossibleScope() {
Set<AnnotationGroup.AffectionScope> result = EnumSet.noneOf(AnnotationGroup.AffectionScope.class);
for (AnnotationGroup group : getTrackedAnnotations()) {
result.addAll(group.affectionScope);
}
return result;
}
@Override
public boolean processClassAnnotations(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> change, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationDiff, Utils future, Utils present) {
Set<AnnotationGroup.AffectionScope> maxScope = getMaxPossibleScope();
if (maxScope.isEmpty()) {
return true;
}
JvmClass changedClass = change.getPast();
boolean affectUsages = toRecompile.contains(AnnotationAffectionScope.usages);
Set<AnnotationGroup.AffectionScope> affectionScope = EnumSet.noneOf(AnnotationGroup.AffectionScope.class);
for (AnnotationGroup group : filter(getTrackedAnnotations(), gr -> gr.targets.contains(AnnotationGroup.AnnTarget.type))) {
if (isAffectedByAnnotations(changedClass, annotationDiff, group.affectionKind, group.types::contains)) {
debug(group.name, " changed for ", changedClass.getName(), " --- affecting class usages");
affectionScope.addAll(group.affectionScope);
if (affectionScope.equals(maxScope)) {
break;
}
}
}
if (!affectionScope.isEmpty()) {
affectClassAnnotationUsages(context, affectionScope, change, future, present);
}
return true;
}
@Override
public boolean processFieldAnnotations(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> clsChange, Difference.Change<JvmField, JvmField.Diff> fieldChange, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationDiff, Utils future, Utils present) {
Set<AnnotationGroup.AffectionScope> maxScope = getMaxPossibleScope();
if (maxScope.isEmpty()) {
return true;
}
JvmField changedField = fieldChange.getPast();
Set<AnnotationGroup.AffectionScope> affectionScope = EnumSet.noneOf(AnnotationGroup.AffectionScope.class);
for (AnnotationGroup group : filter(getTrackedAnnotations(), gr -> gr.targets.contains(AnnotationGroup.AnnTarget.field))) {
if (isAffectedByAnnotations(changedField, annotationDiff, group.affectionKind, group.types::contains) ) {
debug(group.name, " changed for field ", changedField, " --- affecting field usages");
affectionScope.addAll(group.affectionScope);
if (affectionScope.equals(maxScope)) {
break;
}
}
}
if (!affectionScope.isEmpty()) {
affectFieldAnnotationUsages(context, affectionScope, clsChange, changedField, future, present);
}
return true;
}
@Override
public boolean processMethodAnnotations(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> clsChange, Difference.Change<JvmMethod, JvmMethod.Diff> methodChange, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationsDiff, Difference.Specifier<ParamAnnotation, ParamAnnotation.Diff> paramAnnotationsDiff, Utils future, Utils present) {
Set<AnnotationGroup.AffectionScope> maxScope = getMaxPossibleScope();
JvmMethod changedMethod = methodChange.getPast();
if (!changedMethod.isFinal()) {
maxScope.add(AnnotationGroup.AffectionScope.subclasses);
}
Set<AnnotationGroup.AffectionScope> affectionScope = EnumSet.noneOf(AnnotationGroup.AffectionScope.class);
for (AnnotationGroup group : filter(getTrackedAnnotations(), gr -> gr.targets.contains(AnnotationGroup.AnnTarget.method))) {
if (isAffectedByAnnotations(changedMethod, annotationsDiff, group.affectionKind, group.types::contains)) {
affectionScope.addAll(group.affectionScope);
if (!changedMethod.isFinal()) {
// ensure the affection scope is expanded for subclasses
affectionScope.add(AnnotationGroup.AffectionScope.subclasses);
debug(group.name, " changed for non-final method ", changedMethod, " --- affecting method usages and subclasses");
}
else {
debug(group.name, " changed for method ", changedMethod, " --- affecting method usages");
}
if (affectionScope.equals(maxScope)) {
break;
}
}
}
if (!affectionScope.equals(maxScope)) {
for (AnnotationGroup group : filter(getTrackedAnnotations(), gr -> gr.targets.contains(AnnotationGroup.AnnTarget.method_parameter))) {
if (isAffectedByAnnotations(changedMethod, paramAnnotationsDiff, group.affectionKind, group.types::contains)) {
affectionScope.addAll(group.affectionScope);
if (!changedMethod.isFinal()) {
// ensure the affection scope is expanded for subclasses
affectionScope.add(AnnotationGroup.AffectionScope.subclasses);
debug(group.name, " changed for non-final method parameters ", changedMethod, " --- affecting method usages and subclasses");
}
else {
debug(group.name, " changed for method parameters ", changedMethod, " --- affecting method usages");
}
if (affectionScope.equals(maxScope)) {
break;
}
}
}
}
if (!affectionScope.isEmpty()) {
affectMethodAnnotationUsages(context, affectionScope, clsChange, changedMethod, future, present);
}
return true;
}
protected void affectClassAnnotationUsages(DifferentiateContext context, Set<AnnotationGroup.AffectionScope> toRecompile, Difference.Change<JvmClass, JvmClass.Diff> change, Utils future, Utils present) {
JvmClass changedClass = change.getPast();
boolean affectUsages = toRecompile.contains(AnnotationGroup.AffectionScope.usages);
if (affectUsages) {
context.affectUsage(new ClassUsage(changedClass.getReferenceID()));
}
if (toRecompile.contains(AnnotationAffectionScope.subclasses)) {
if (toRecompile.contains(AnnotationGroup.AffectionScope.subclasses)) {
affectSubclasses(context, future, changedClass.getReferenceID(), affectUsages);
}
}
protected void affectFieldAnnotationUsages(DifferentiateContext context, Set<AnnotationAffectionScope> toRecompile, Difference.Change<JvmClass, JvmClass.Diff> clsChange, JvmField changedField, Utils future, Utils present) {
protected void affectFieldAnnotationUsages(DifferentiateContext context, Set<AnnotationGroup.AffectionScope> toRecompile, Difference.Change<JvmClass, JvmClass.Diff> clsChange, JvmField changedField, Utils future, Utils present) {
JvmClass changedClass = clsChange.getPast();
if (toRecompile.contains(AnnotationAffectionScope.usages)) {
if (toRecompile.contains(AnnotationGroup.AffectionScope.usages)) {
affectMemberUsages(context, changedClass.getReferenceID(), changedField, future.collectSubclassesWithoutField(changedClass.getReferenceID(), changedField));
}
if (toRecompile.contains(AnnotationAffectionScope.subclasses)) {
if (toRecompile.contains(AnnotationGroup.AffectionScope.subclasses)) {
affectSubclasses(context, future, changedClass.getReferenceID(), false);
}
}
protected void affectMethodAnnotationUsages(DifferentiateContext context, Set<AnnotationAffectionScope> toRecompile, Difference.Change<JvmClass, JvmClass.Diff> clsChange, JvmMethod changedMethod, Utils future, Utils present) {
protected void affectMethodAnnotationUsages(DifferentiateContext context, Set<AnnotationGroup.AffectionScope> toRecompile, Difference.Change<JvmClass, JvmClass.Diff> clsChange, JvmMethod changedMethod, Utils future, Utils present) {
JvmClass changedClass = clsChange.getPast();
if (toRecompile.contains(AnnotationAffectionScope.usages)) {
if (toRecompile.contains(AnnotationGroup.AffectionScope.usages)) {
affectMemberUsages(context, changedClass.getReferenceID(), changedMethod, future.collectSubclassesWithoutMethod(changedClass.getReferenceID(), changedMethod));
if (changedMethod.isAbstract() || toRecompile.contains(AnnotationAffectionScope.subclasses)) {
if (changedMethod.isAbstract() || toRecompile.contains(AnnotationGroup.AffectionScope.subclasses)) {
for (Pair<JvmClass, JvmMethod> pair : recurse(Pair.create(changedClass, changedMethod), p -> p.second.isOverridable()? future.getOverridingMethods(p.first, p.second, p.second::isSameByJavaRules) : Collections.emptyList(), false)) {
JvmNodeReferenceID clsId = pair.first.getReferenceID();
JvmMethod meth = pair.getSecond();
@@ -86,7 +189,7 @@ public abstract class JvmDifferentiateStrategyImpl implements JvmDifferentiateSt
}
}
}
if (toRecompile.contains(AnnotationAffectionScope.subclasses)) {
if (toRecompile.contains(AnnotationGroup.AffectionScope.subclasses)) {
affectSubclasses(context, future, changedClass.getReferenceID(), false);
}
}

View File

@@ -23,81 +23,59 @@ import static org.jetbrains.jps.javac.Iterators.*;
*/
public final class KotlinJvmDifferentiateStrategy extends JvmDifferentiateStrategyImpl {
private static final TypeRepr.ClassType JVM_OVERLOADS_ANNOTATION = new TypeRepr.ClassType("kotlin/jvm/JvmOverloads");
private static final Set<TypeRepr.ClassType> ourDeprecationAnnotations = Set.of(
new TypeRepr.ClassType("kotlin/Deprecated"),
new TypeRepr.ClassType("kotlin/DeprecatedSinceKotlin")
);
private static final Set<String> ourNullabilityAnnotations = Set.of(
"org/jetbrains/annotations/Nullable",
"androidx/annotation/Nullable",
"android/support/annotation/Nullable",
"android/annotation/Nullable",
"com/android/annotations/Nullable",
"org/eclipse/jdt/annotation/Nullable",
"org/checkerframework/checker/nullness/qual/Nullable",
"javax/annotation/Nullable",
"javax/annotation/CheckForNull",
"edu/umd/cs/findbugs/annotations/CheckForNull",
"edu/umd/cs/findbugs/annotations/Nullable",
"edu/umd/cs/findbugs/annotations/PossiblyNull",
"io/reactivex/annotations/Nullable",
"io/reactivex/rxjava3/annotations/Nullable",
"javax/annotation/Nonnull",
"org/jetbrains/annotations/NotNull",
"edu/umd/cs/findbugs/annotations/NonNull",
"androidx/annotation/NonNull",
"android/support/annotation/NonNull",
"android/annotation/NonNull",
"com/android/annotations/NonNull",
"org/eclipse/jdt/annotation/NonNull",
"org/checkerframework/checker/nullness/qual/NonNull",
"lombok/NonNull",
"io/reactivex/annotations/NonNull",
"io/reactivex/rxjava3/annotations/NonNull"
private static final List<AnnotationGroup> ourTrackedAnnotations = List.of(
AnnotationGroup.of(
"Nullability annotations",
EnumSet.of(AnnotationGroup.AnnTarget.field, AnnotationGroup.AnnTarget.method, AnnotationGroup.AnnTarget.method_parameter),
EnumSet.of(AnnotationGroup.AffectionKind.added, AnnotationGroup.AffectionKind.removed),
EnumSet.of(AnnotationGroup.AffectionScope.usages),
Set.of(
new TypeRepr.ClassType("org/jetbrains/annotations/Nullable"),
new TypeRepr.ClassType("androidx/annotation/Nullable"),
new TypeRepr.ClassType("android/support/annotation/Nullable"),
new TypeRepr.ClassType("android/annotation/Nullable"),
new TypeRepr.ClassType("com/android/annotations/Nullable"),
new TypeRepr.ClassType("org/eclipse/jdt/annotation/Nullable"),
new TypeRepr.ClassType("org/checkerframework/checker/nullness/qual/Nullable"),
new TypeRepr.ClassType("javax/annotation/Nullable"),
new TypeRepr.ClassType("javax/annotation/CheckForNull"),
new TypeRepr.ClassType("edu/umd/cs/findbugs/annotations/CheckForNull"),
new TypeRepr.ClassType("edu/umd/cs/findbugs/annotations/Nullable"),
new TypeRepr.ClassType("edu/umd/cs/findbugs/annotations/PossiblyNull"),
new TypeRepr.ClassType("io/reactivex/annotations/Nullable"),
new TypeRepr.ClassType("io/reactivex/rxjava3/annotations/Nullable"),
new TypeRepr.ClassType("javax/annotation/Nonnull"),
new TypeRepr.ClassType("org/jetbrains/annotations/NotNull"),
new TypeRepr.ClassType("edu/umd/cs/findbugs/annotations/NonNull"),
new TypeRepr.ClassType("androidx/annotation/NonNull"),
new TypeRepr.ClassType("android/support/annotation/NonNull"),
new TypeRepr.ClassType("android/annotation/NonNull"),
new TypeRepr.ClassType("com/android/annotations/NonNull"),
new TypeRepr.ClassType("org/eclipse/jdt/annotation/NonNull"),
new TypeRepr.ClassType("org/checkerframework/checker/nullness/qual/NonNull"),
new TypeRepr.ClassType("lombok/NonNull"),
new TypeRepr.ClassType("io/reactivex/annotations/NonNull"),
new TypeRepr.ClassType("io/reactivex/rxjava3/annotations/NonNull")
)
),
AnnotationGroup.of(
"Deprecation annotations",
EnumSet.of(AnnotationGroup.AnnTarget.type, AnnotationGroup.AnnTarget.field, AnnotationGroup.AnnTarget.method),
EnumSet.of(AnnotationGroup.AffectionKind.added, AnnotationGroup.AffectionKind.changed),
EnumSet.of(AnnotationGroup.AffectionScope.usages),
Set.of(
new TypeRepr.ClassType("kotlin/Deprecated"),
new TypeRepr.ClassType("kotlin/DeprecatedSinceKotlin")
)
)
);
@Override
public boolean isAnnotationTracked(TypeRepr.@NotNull ClassType annotationType) {
return ourNullabilityAnnotations.contains(annotationType.getJvmName()) || ourDeprecationAnnotations.contains(annotationType);
}
@Override
public boolean processClassAnnotations(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> change, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationDiff, Utils future, Utils present) {
JvmClass changedClass = change.getPast();
if (isAffectedByAnnotations(changedClass, annotationDiff, EnumSet.of(AnnotationAffectionKind.added, AnnotationAffectionKind.changed), ourDeprecationAnnotations::contains)) {
debug("Deprecation annotations changed for ", changedClass.getName(), " --- affecting class usages");
affectClassAnnotationUsages(context, EnumSet.of(AnnotationAffectionScope.usages), change, future, present);
}
return super.processClassAnnotations(context, change, annotationDiff, future, present);
}
@Override
public boolean processFieldAnnotations(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> clsChange, Difference.Change<JvmField, JvmField.Diff> fieldChange, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationDiff, Utils future, Utils present) {
JvmField changedField = fieldChange.getPast();
if (
isAffectedByAnnotations(changedField, annotationDiff, EnumSet.of(AnnotationAffectionKind.added, AnnotationAffectionKind.removed), t -> ourNullabilityAnnotations.contains(t.getJvmName())) ||
isAffectedByAnnotations(changedField, annotationDiff, EnumSet.of(AnnotationAffectionKind.added, AnnotationAffectionKind.changed), ourDeprecationAnnotations::contains)
) {
debug("Nullability or Deprecation annotations changed for field ", changedField, " --- affecting field usages");
affectFieldAnnotationUsages(context, EnumSet.of(AnnotationAffectionScope.usages), clsChange, changedField, future, present);
}
return super.processFieldAnnotations(context, clsChange, fieldChange, annotationDiff, future, present);
}
@Override
public boolean processMethodAnnotations(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> clsChange, Difference.Change<JvmMethod, JvmMethod.Diff> methodChange, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationsDiff, Difference.Specifier<ParamAnnotation, ParamAnnotation.Diff> paramAnnotationsDiff, Utils future, Utils present) {
JvmMethod changedMethod = methodChange.getPast();
if (
isAffectedByAnnotations(changedMethod, annotationsDiff, EnumSet.of(AnnotationAffectionKind.added, AnnotationAffectionKind.removed), t -> ourNullabilityAnnotations.contains(t.getJvmName())) ||
isAffectedByAnnotations(changedMethod, paramAnnotationsDiff, EnumSet.of(AnnotationAffectionKind.added, AnnotationAffectionKind.removed), t -> ourNullabilityAnnotations.contains(t.getJvmName())) ||
isAffectedByAnnotations(changedMethod, annotationsDiff, EnumSet.of(AnnotationAffectionKind.added, AnnotationAffectionKind.changed), ourDeprecationAnnotations::contains)
) {
debug("Nullability annotations/parameter annotations or Deprecation annotations changed for method ", changedMethod, " --- affecting method usages");
EnumSet<AnnotationAffectionScope> affection = changedMethod.isFinal()? EnumSet.of(AnnotationAffectionScope.usages) : EnumSet.of(AnnotationAffectionScope.usages, AnnotationAffectionScope.subclasses);
affectMethodAnnotationUsages(context, affection, clsChange, changedMethod, future, present);
}
return super.processMethodAnnotations(context, clsChange, methodChange, annotationsDiff, paramAnnotationsDiff, future, present);
protected Iterable<AnnotationGroup> getTrackedAnnotations() {
return ourTrackedAnnotations;
}
@Override
@@ -555,18 +533,18 @@ public final class KotlinJvmDifferentiateStrategy extends JvmDifferentiateStrate
}
@Override
protected void affectMethodAnnotationUsages(DifferentiateContext context, Set<AnnotationAffectionScope> toRecompile, Difference.Change<JvmClass, JvmClass.Diff> clsChange, JvmMethod changedMethod, Utils future, Utils present) {
protected void affectMethodAnnotationUsages(DifferentiateContext context, Set<AnnotationGroup.AffectionScope> toRecompile, Difference.Change<JvmClass, JvmClass.Diff> clsChange, JvmMethod changedMethod, Utils future, Utils present) {
super.affectMethodAnnotationUsages(context, toRecompile, clsChange, changedMethod, future, present);
if (toRecompile.contains(AnnotationAffectionScope.usages)) {
if (toRecompile.contains(AnnotationGroup.AffectionScope.usages)) {
JvmClass changedClass = clsChange.getPast();
affectMemberLookupUsages(context, changedClass, KJvmUtils.getMethodKotlinName(changedClass, changedMethod), present);
}
}
@Override
protected void affectClassAnnotationUsages(DifferentiateContext context, Set<AnnotationAffectionScope> toRecompile, Difference.Change<JvmClass, JvmClass.Diff> change, Utils future, Utils present) {
protected void affectClassAnnotationUsages(DifferentiateContext context, Set<AnnotationGroup.AffectionScope> toRecompile, Difference.Change<JvmClass, JvmClass.Diff> change, Utils future, Utils present) {
super.affectClassAnnotationUsages(context, toRecompile, change, future, present);
if (toRecompile.contains(AnnotationAffectionScope.usages)) {
if (toRecompile.contains(AnnotationGroup.AffectionScope.usages)) {
affectClassLookupUsages(context, change.getPast());
}
}

View File

@@ -1,15 +1,11 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.builders.java.dependencyView.MockAnnotation;
import org.jetbrains.jps.builders.java.dependencyView.MockHierarchyAnnotation;
import org.jetbrains.jps.dependency.DifferentiateContext;
import org.jetbrains.jps.dependency.diff.Difference;
import org.jetbrains.jps.javac.Iterators;
import java.util.EnumSet;
import java.util.Objects;
import java.util.List;
import java.util.Set;
/**
@@ -19,57 +15,42 @@ public class TestJvmDifferentiateStrategy extends JvmDifferentiateStrategyImpl {
private static final String ANOTATION_NAME = MockAnnotation.class.getName().replace('.', '/');
private static final String HIERARCHY_ANOTATION_NAME = MockHierarchyAnnotation.class.getName().replace('.', '/');
private static final Set<String> KOTLIN_TESTS_ANOTATION_NAMES = Set.of("foo/Ann", "Ann");
private static final List<AnnotationGroup> ourTrackedAnnotations = List.of(
AnnotationGroup.of(
"Test annotation",
EnumSet.allOf(AnnotationGroup.AnnTarget.class),
EnumSet.allOf(AnnotationGroup.AffectionKind.class),
EnumSet.of(AnnotationGroup.AffectionScope.usages),
Set.of(
new TypeRepr.ClassType(ANOTATION_NAME)
)
),
AnnotationGroup.of(
"Test hierarchy annotation",
EnumSet.allOf(AnnotationGroup.AnnTarget.class),
EnumSet.allOf(AnnotationGroup.AffectionKind.class),
EnumSet.of(AnnotationGroup.AffectionScope.subclasses),
Set.of(
new TypeRepr.ClassType(HIERARCHY_ANOTATION_NAME)
)
),
AnnotationGroup.of(
"Kotlin test annotations",
EnumSet.of(AnnotationGroup.AnnTarget.type, AnnotationGroup.AnnTarget.field, AnnotationGroup.AnnTarget.method),
EnumSet.allOf(AnnotationGroup.AffectionKind.class),
EnumSet.of(AnnotationGroup.AffectionScope.usages, AnnotationGroup.AffectionScope.subclasses),
Set.of(
new TypeRepr.ClassType("foo/Ann"),
new TypeRepr.ClassType("Ann")
)
)
);
@Override
public boolean isAnnotationTracked(@NotNull TypeRepr.ClassType annotationType) {
String typeName = annotationType.getJvmName();
return KOTLIN_TESTS_ANOTATION_NAMES.contains(typeName) || Objects.equals(ANOTATION_NAME, typeName) || Objects.equals(HIERARCHY_ANOTATION_NAME, typeName);
}
@Override
public boolean processClassAnnotations(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> change, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationDiff, Utils future, Utils present) {
Set<AnnotationAffectionScope> affectionScope = getAffectionScope(getAffectedAnnotations(annotationDiff, EnumSet.of(AnnotationAffectionKind.added, AnnotationAffectionKind.removed, AnnotationAffectionKind.changed)));
if (!affectionScope.isEmpty()) {
affectClassAnnotationUsages(context, affectionScope, change, future, present);
}
return super.processClassAnnotations(context, change, annotationDiff, future, present);
}
@Override
public boolean processFieldAnnotations(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> clsChange, Difference.Change<JvmField, JvmField.Diff> fieldChange, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationDiff, Utils future, Utils present) {
Set<AnnotationAffectionScope> affectionScope = getAffectionScope(getAffectedAnnotations(annotationDiff, EnumSet.of(AnnotationAffectionKind.added, AnnotationAffectionKind.removed, AnnotationAffectionKind.changed)));
if (!affectionScope.isEmpty()) {
affectFieldAnnotationUsages(context, affectionScope, clsChange, fieldChange.getPast(), future, present);
}
return super.processFieldAnnotations(context, clsChange, fieldChange, annotationDiff, future, present);
}
@Override
public boolean processMethodAnnotations(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> clsChange, Difference.Change<JvmMethod, JvmMethod.Diff> methodChange, Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationsDiff, Difference.Specifier<ParamAnnotation, ParamAnnotation.Diff> paramAnnotationsDiff, Utils future, Utils present) {
EnumSet<AnnotationAffectionKind> affectionKinds = EnumSet.of(AnnotationAffectionKind.added, AnnotationAffectionKind.removed, AnnotationAffectionKind.changed);
Set<AnnotationAffectionScope> affectionScope = getAffectionScope(
Iterators.flat(getAffectedAnnotations(annotationsDiff, affectionKinds), getAffectedAnnotations(paramAnnotationsDiff, affectionKinds))
);
if (!affectionScope.isEmpty()) {
affectMethodAnnotationUsages(context, affectionScope, clsChange, methodChange.getPast(), future, present);
}
return super.processMethodAnnotations(context, clsChange, methodChange, annotationsDiff, paramAnnotationsDiff, future, present);
}
private static Set<AnnotationAffectionScope> getAffectionScope(Iterable<TypeRepr.ClassType> trackedAnnotations) {
Set<AnnotationAffectionScope> result = EnumSet.noneOf(AnnotationAffectionScope.class);
for (TypeRepr.ClassType annotation : trackedAnnotations) {
if (ANOTATION_NAME.equals(annotation.getJvmName())) {
result.add(AnnotationAffectionScope.usages);
}
else if (HIERARCHY_ANOTATION_NAME.equals(annotation.getJvmName())) {
result.add(AnnotationAffectionScope.subclasses);
}
else if (KOTLIN_TESTS_ANOTATION_NAMES.contains(annotation.getJvmName())) {
result.addAll(EnumSet.of(AnnotationAffectionScope.usages, AnnotationAffectionScope.subclasses));
}
}
return result;
protected Iterable<AnnotationGroup> getTrackedAnnotations() {
return ourTrackedAnnotations;
}
}