Skip to content

Commit

Permalink
improve performance of the annotation overlay
Browse files Browse the repository at this point in the history
There's several improvements in this commit:

1. Transformed annotations are always cached, even if no transformation
   applied. This makes it much faster in the most common case, while
   adding no significant memory overhead, because vast majority of objects
   already existed.

2. The transformation context implementation creates a copy of annotations
   lazily, on the first need. Again, this makes it much faster in the most
   common case (which is no annotation transformations applied).

3. Several copies were eliminated because they were just unnecessary overhead.
   For example, `AnnotationTarget.annotations()` or `declaredAnnotations()`
   already return unmodifiable collections, so there's no need to rewrap
   them again.

4. The [potentially transformed] annotations are no longer stored as a `Set`
   but as a `Collection`. This saves several copies and doesn't affect public
   API, which has always been defined in terms of `Collection`s.

5. Lambdas in `AnnotationOverlayImpl` were replaced by annonymous classes.
   The [modest] usage of streams was eliminated completely.
  • Loading branch information
Ladicek committed Feb 11, 2025
1 parent f5d3381 commit e4abfd1
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 47 deletions.
144 changes: 105 additions & 39 deletions core/src/main/java/org/jboss/jandex/AnnotationOverlayImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,18 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;

import org.jboss.jandex.AnnotationTransformation.TransformationContext;

class AnnotationOverlayImpl implements AnnotationOverlay {
private static final Set<AnnotationInstance> SENTINEL = Collections.unmodifiableSet(new HashSet<>());

final IndexView index;
final boolean compatibleMode;
final boolean runtimeAnnotationsOnly;
final boolean inheritedAnnotations;
final List<AnnotationTransformation> transformations;
final Map<EquivalenceKey, Set<AnnotationInstance>> overlay = new ConcurrentHashMap<>();
final Map<EquivalenceKey, Collection<AnnotationInstance>> overlay = new ConcurrentHashMap<>();

AnnotationOverlayImpl(IndexView index, boolean compatibleMode, boolean runtimeAnnotationsOnly, boolean inheritedAnnotations,
Collection<AnnotationTransformation> annotationTransformations) {
Expand Down Expand Up @@ -216,67 +215,109 @@ public final Collection<AnnotationInstance> annotations(Declaration declaration)
while (clazz != null && !DotName.OBJECT_NAME.equals(clazz.name())) {
for (AnnotationInstance annotation : getAnnotationsFor(clazz)) {
ClassInfo annotationClass = index.getClassByName(annotation.name());
if (annotationClass != null
&& annotationClass.hasDeclaredAnnotation(DotName.INHERITED_NAME)
&& result.stream().noneMatch(it -> it.name().equals(annotation.name()))) {
result.add(annotation);
if (annotationClass != null && annotationClass.hasDeclaredAnnotation(DotName.INHERITED_NAME)) {
boolean noMatchingAnnotationPresent = true;
for (AnnotationInstance it : result) {
if (it.name().equals(annotation.name())) {
noMatchingAnnotationPresent = false;
break;
}
}
if (noMatchingAnnotationPresent) {
result.add(annotation);
}
}
}
clazz = index.getClassByName(clazz.superName());
}
result = Collections.unmodifiableCollection(result);
}

return Collections.unmodifiableCollection(result);
return result;
}

Set<AnnotationInstance> getAnnotationsFor(Declaration declaration) {
Collection<AnnotationInstance> getAnnotationsFor(Declaration declaration) {
EquivalenceKey key = EquivalenceKey.of(declaration);
Set<AnnotationInstance> annotations = overlay.computeIfAbsent(key, ignored -> {
Set<AnnotationInstance> original = getOriginalAnnotations(declaration);
TransformationContextImpl transformationContext = new TransformationContextImpl(declaration, original);
for (AnnotationTransformation transformation : transformations) {
if (transformation.supports(declaration.kind())) {
transformation.apply(transformationContext);
return overlay.computeIfAbsent(key, new Function<EquivalenceKey, Collection<AnnotationInstance>>() {
@Override
public Collection<AnnotationInstance> apply(EquivalenceKey ignored) {
Collection<AnnotationInstance> original = getOriginalAnnotations(declaration);
TransformationContextImpl transformationContext = new TransformationContextImpl(declaration, original);
for (AnnotationTransformation transformation : transformations) {
if (transformation.supports(declaration.kind())) {
transformation.apply(transformationContext);
}
}

if (transformationContext.modified()) {
Collection<AnnotationInstance> result = transformationContext.annotations;
if (result.isEmpty()) {
return Collections.emptyList();
} else if (result.size() == 1) {
return Collections.singleton(result.iterator().next());
} else {
return Collections.unmodifiableCollection(result);
}
}
return original;
}
Set<AnnotationInstance> result = transformationContext.annotations;
return original.equals(result) ? SENTINEL : Collections.unmodifiableSet(result);
});

if (annotations == SENTINEL) {
annotations = getOriginalAnnotations(declaration);
}
return annotations;
}

final Set<AnnotationInstance> getOriginalAnnotations(Declaration declaration) {
Set<AnnotationInstance> result = new HashSet<>();
// always returns an unmodifiable collection
final Collection<AnnotationInstance> getOriginalAnnotations(Declaration declaration) {
if (compatibleMode && declaration.kind() == AnnotationTarget.Kind.METHOD) {
for (AnnotationInstance annotation : declaration.asMethod().annotations()) {
List<AnnotationInstance> annotations = declaration.asMethod().annotations();
if (annotations.isEmpty()) {
return Collections.emptyList();
}

List<AnnotationInstance> result = new ArrayList<>(annotations.size());
for (AnnotationInstance annotation : annotations) {
if (annotation.target() != null
&& (annotation.target().kind() == AnnotationTarget.Kind.METHOD
|| annotation.target().kind() == AnnotationTarget.Kind.METHOD_PARAMETER)
&& (!runtimeAnnotationsOnly || annotation.runtimeVisible())) {
result.add(annotation);
}
}
} else {
for (AnnotationInstance annotation : declaration.declaredAnnotations()) {
if (!runtimeAnnotationsOnly || annotation.runtimeVisible()) {
result.add(annotation);
}
return Collections.unmodifiableList(result);
}

Collection<AnnotationInstance> annotations = declaration.declaredAnnotations();

if (!runtimeAnnotationsOnly) {
return annotations;
}

List<AnnotationInstance> result = new ArrayList<>(annotations.size());
for (AnnotationInstance annotation : annotations) {
if (annotation.runtimeVisible()) {
result.add(annotation);
}
}
return result;
return Collections.unmodifiableList(result);
}

private static final class TransformationContextImpl implements TransformationContext {
private final Declaration declaration;
private final Set<AnnotationInstance> annotations;
private final Collection<AnnotationInstance> originalAnnotations;
private Set<AnnotationInstance> annotations;

TransformationContextImpl(Declaration declaration, Collection<AnnotationInstance> annotations) {
this.declaration = declaration;
this.annotations = new HashSet<>(annotations);
this.originalAnnotations = annotations;
this.annotations = null;
}

boolean modified() {
return annotations != null;
}

private void initializeIfNecessary() {
if (annotations == null) {
annotations = new HashSet<>(originalAnnotations);
}
}

@Override
Expand All @@ -286,6 +327,7 @@ public Declaration declaration() {

@Override
public Collection<AnnotationInstance> annotations() {
initializeIfNecessary();
return annotations;
}

Expand All @@ -298,7 +340,7 @@ public boolean hasAnnotation(Class<? extends Annotation> annotationClass) {
@Override
public boolean hasAnnotation(DotName annotationName) {
Objects.requireNonNull(annotationName);
for (AnnotationInstance annotation : annotations) {
for (AnnotationInstance annotation : modified() ? annotations : originalAnnotations) {
if (annotation.name().equals(annotationName)) {
return true;
}
Expand All @@ -309,7 +351,7 @@ public boolean hasAnnotation(DotName annotationName) {
@Override
public boolean hasAnnotation(Predicate<AnnotationInstance> predicate) {
Objects.requireNonNull(predicate);
for (AnnotationInstance annotation : annotations) {
for (AnnotationInstance annotation : modified() ? annotations : originalAnnotations) {
if (predicate.test(annotation)) {
return true;
}
Expand All @@ -319,20 +361,27 @@ public boolean hasAnnotation(Predicate<AnnotationInstance> predicate) {

@Override
public void add(Class<? extends Annotation> annotationClass) {
initializeIfNecessary();

Objects.requireNonNull(annotationClass);
annotations.add(AnnotationInstance.builder(annotationClass).buildWithTarget(declaration));
}

@Override
public void add(AnnotationInstance annotation) {
initializeIfNecessary();

Objects.requireNonNull(annotation);
if (annotation.target() == null) {
annotation = AnnotationInstance.create(annotation, declaration);
}
annotations.add(Objects.requireNonNull(annotation));
annotations.add(annotation);
}

@Override
public void addAll(AnnotationInstance... annotations) {
initializeIfNecessary();

Objects.requireNonNull(annotations);
for (int i = 0; i < annotations.length; i++) {
if (annotations[i].target() == null) {
Expand All @@ -344,8 +393,17 @@ public void addAll(AnnotationInstance... annotations) {

@Override
public void addAll(Collection<AnnotationInstance> annotations) {
initializeIfNecessary();

Objects.requireNonNull(annotations);
if (annotations.stream().anyMatch(it -> it.target() == null)) {
boolean hasNullTarget = false;
for (AnnotationInstance annotation : annotations) {
if (annotation.target() == null) {
hasNullTarget = true;
break;
}
}
if (hasNullTarget) {
List<AnnotationInstance> fixed = new ArrayList<>();
for (AnnotationInstance annotation : annotations) {
if (annotation.target() == null) {
Expand All @@ -361,12 +419,20 @@ public void addAll(Collection<AnnotationInstance> annotations) {

@Override
public void remove(Predicate<AnnotationInstance> predicate) {
annotations.removeIf(Objects.requireNonNull(predicate));
initializeIfNecessary();

Objects.requireNonNull(predicate);
annotations.removeIf(predicate);
}

@Override
public void removeAll() {
annotations.clear();
// skipping `initializeIfNecessary()` here, because that would do useless work
if (modified()) {
annotations.clear();
} else {
annotations = new HashSet<>();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package org.jboss.jandex;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;

final class MutableAnnotationOverlayImpl extends AnnotationOverlayImpl implements MutableAnnotationOverlay {
Expand All @@ -14,14 +16,14 @@ final class MutableAnnotationOverlayImpl extends AnnotationOverlayImpl implement
}

@Override
Set<AnnotationInstance> getAnnotationsFor(Declaration declaration) {
Collection<AnnotationInstance> getAnnotationsFor(Declaration declaration) {
EquivalenceKey key = EquivalenceKey.of(declaration);
Set<AnnotationInstance> annotations = overlay.get(key);
if (annotations == null) {
annotations = getOriginalAnnotations(declaration);
overlay.put(key, annotations);
}
return annotations;
return overlay.computeIfAbsent(key, new Function<EquivalenceKey, Collection<AnnotationInstance>>() {
@Override
public Collection<AnnotationInstance> apply(EquivalenceKey ignored) {
return new HashSet<>(getOriginalAnnotations(declaration));
}
});
}

@Override
Expand Down

0 comments on commit e4abfd1

Please sign in to comment.