elements0 = new ArrayList<>(elements.size());
- for (final SyntheticAnnotationElement e : elements) {
- elements0.add(new InternalAnnotationElement(e.annotationMirrors(), e.type(), e.name(), e.defaultValue()));
+ for (final SyntheticAnnotationElement sae : elements) {
+ elements0.add(new InternalAnnotationElement(sae.annotationMirrors(), sae.type(), sae.name(), sae.defaultValue()));
}
this.elements = unmodifiableList(elements0);
}
@@ -572,7 +614,7 @@ public SyntheticAnnotationElement(final List extends AnnotationMirror> annotat
case ArrayType t when t.getKind() == ARRAY -> validateScalarType(t.getComponentType());
default -> validateScalarType(type);
};
- if (name.equals("getClass") || name.equals("hashCode") || name.equals("toString")) {
+ if (name.contentEquals("getClass") || name.contentEquals("hashCode") || name.contentEquals("toString")) {
// java.lang.Object-declared methods that might otherwise meet annotation element requirements
throw new IllegalArgumentException("name: " + name);
}
@@ -728,13 +770,15 @@ private final class InternalAnnotationElement implements ExecutableElement {
private InternalAnnotationElement(final List extends AnnotationMirror> annotationMirrors,
final TypeMirror type,
- final SyntheticName name,
- final SyntheticAnnotationValue defaultValue) {
+ final Name name,
+ final AnnotationValue defaultValue) {
super();
this.annotationMirrors = new CopyOnWriteArrayList<>(annotationMirrors);
- this.t = new Type(type);
- this.name = requireNonNull(name, "name");
- this.defaultValue = defaultValue;
+ // This Type is NOT an inner class and cannot receive annotations so this is OK.
+ this.t = type instanceof Type t ? t : new Type(type);
+ this.name = name instanceof SyntheticName sn ? sn : new SyntheticName(name);
+ this.defaultValue =
+ defaultValue instanceof SyntheticAnnotationValue sav ? sav : new SyntheticAnnotationValue(defaultValue.getValue());
}
@@ -861,6 +905,7 @@ private Type(final TypeMirror type) { // the "return type"
this.type = switch (type) {
case null -> throw new NullPointerException("type");
case ArrayType t when t.getKind() == ARRAY -> validateScalarType(t.getComponentType());
+ case Type t -> t.type;
default -> validateScalarType(type);
};
}
diff --git a/src/main/java/org/microbean/construct/element/SyntheticLocalVariableElement.java b/src/main/java/org/microbean/construct/element/SyntheticLocalVariableElement.java
new file mode 100644
index 0000000..786c210
--- /dev/null
+++ b/src/main/java/org/microbean/construct/element/SyntheticLocalVariableElement.java
@@ -0,0 +1,196 @@
+/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
+ *
+ * Copyright © 2026 microBean™.
+ *
+ * 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 org.microbean.construct.element;
+
+import java.lang.annotation.Annotation;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ElementVisitor;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.VariableElement;
+
+import javax.lang.model.type.TypeMirror;
+
+import static java.util.Objects.requireNonNull;
+
+import static javax.lang.model.element.ElementKind.LOCAL_VARIABLE;
+
+/**
+ * An experimental {@link VariableElement} implementation that is a synthetic representation of a local
+ * variable.
+ *
+ * {@link SyntheticLocalVariableElement} instances may be useful for capturing declaration annotations that really
+ * pertain to type usage. Such scenarios are often found in dependency injection systems.
+ *
+ * @author Laird Nelson
+ */
+public final class SyntheticLocalVariableElement implements VariableElement {
+
+ private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
+
+ private final List annotationMirrors;
+
+ private final Name name;
+
+ private final TypeMirror type;
+
+ /**
+ * Creates a new {@link SyntheticLocalVariableElement}.
+ *
+ * @param type a non-{@code null} {@link TypeMirror} that a hypothetical local variable may bear; as of this writing
+ * no validation is performed on any argument supplied for this parameter
+ *
+ * @exception NullPointerException if {@code type} is {@code null}
+ *
+ * @see #SyntheticLocalVariableElement(Collection, TypeMirror, String)
+ */
+ public SyntheticLocalVariableElement(final TypeMirror type) {
+ this(List.of(), type, null);
+ }
+
+ /**
+ * Creates a new {@link SyntheticLocalVariableElement}.
+ *
+ * @param type a non-{@code null} {@link TypeMirror} that a hypothetical local variable may bear; as of this writing
+ * no validation is performed on any argument supplied for this parameter
+ *
+ * @param name the name of this {@link SyntheticLocalVariableElement}; may be {@code null}
+ *
+ * @exception NullPointerException if {@code type} is {@code null}
+ *
+ * @see #SyntheticLocalVariableElement(Collection, TypeMirror, String)
+ */
+ public SyntheticLocalVariableElement(final TypeMirror type, final String name) {
+ this(List.of(), type, name);
+ }
+
+ /**
+ * Creates a new {@link SyntheticLocalVariableElement}.
+ *
+ * @param as a non-{@code null} {@link Collection} of {@link AnnotationMirror}s
+ *
+ * @param type a non-{@code null} {@link TypeMirror} that a hypothetical local variable may bear; as of this writing
+ * no validation is performed on any argument supplied for this parameter
+ *
+ * @exception NullPointerException if {@code as} or {@code type} is {@code null}
+ *
+ * @see #SyntheticLocalVariableElement(Collection, TypeMirror, String)
+ */
+ public SyntheticLocalVariableElement(final Collection extends AnnotationMirror> as, final TypeMirror type) {
+ this(as, type, null);
+ }
+
+ /**
+ * Creates a new {@link SyntheticLocalVariableElement}.
+ *
+ * @param as a non-{@code null} {@link Collection} of {@link AnnotationMirror}s
+ *
+ * @param type a non-{@code null} {@link TypeMirror} that a hypothetical local variable may bear; as of this writing
+ * no validation is performed on any argument supplied for this parameter
+ *
+ * @param name the name of this {@link SyntheticLocalVariableElement}; may be {@code null}
+ *
+ * @exception NullPointerException if {@code as} or {@code type} is {@code null}
+ */
+ public SyntheticLocalVariableElement(final Collection extends AnnotationMirror> as,
+ final TypeMirror type,
+ final String name) {
+ super();
+ this.annotationMirrors = new CopyOnWriteArrayList<>(as);
+ this.name = new SyntheticName(name == null ? "" : name);
+ this.type = requireNonNull(type, "type");
+ }
+
+ @Override // VariableElement (Element)
+ public final R accept(final ElementVisitor v, final P p) {
+ return v.visitVariable(this, p);
+ }
+
+ @Override // VariableElement (Element)
+ public final TypeMirror asType() {
+ return this.type;
+ }
+
+ @Override // VariableElement (Object)
+ public final boolean equals(final Object other) {
+ return this == other || switch (other) {
+ case null -> false;
+ case SyntheticLocalVariableElement her when this.getClass() == her.getClass() -> this.type.equals(her.type) && this.name.contentEquals(her.name);
+ default -> false;
+ };
+ }
+
+ @Override // VariableElement (AnnotatedConstruct)
+ public final List getAnnotationMirrors() {
+ return this.annotationMirrors;
+ }
+
+ @Deprecated
+ @Override // VariableElement (AnnotatedConstruct)
+ public final A getAnnotation(final Class annotationType) {
+ return null; // deliberate
+ }
+
+ @Deprecated
+ @Override // VariableElement (AnnotatedConstruct)
+ @SuppressWarnings("unchecked")
+ public final A[] getAnnotationsByType(final Class annotationType) {
+ return (A[])EMPTY_ANNOTATION_ARRAY; // deliberate
+ }
+
+ @Override // VariableElement
+ public final Object getConstantValue() {
+ return null;
+ }
+
+ @Override // VariableElement (Element)
+ public final List extends Element> getEnclosedElements() {
+ return List.of();
+ }
+
+ @Override // VariableElement (Element)
+ public final Element getEnclosingElement() {
+ return null; // deliberate
+ }
+
+ @Override // VariableElement (Element)
+ public final ElementKind getKind() {
+ return LOCAL_VARIABLE;
+ }
+
+ @Override // VariableElement (Element)
+ public final Set getModifiers() {
+ return Set.of();
+ }
+
+ @Override // VariableElement (Element)
+ public final Name getSimpleName() {
+ return this.name;
+ }
+
+ @Override // Object
+ public final int hashCode() {
+ return this.name.hashCode() ^ this.type.hashCode();
+ }
+
+}
diff --git a/src/main/java/org/microbean/construct/element/SyntheticName.java b/src/main/java/org/microbean/construct/element/SyntheticName.java
index 98dde45..beefc96 100644
--- a/src/main/java/org/microbean/construct/element/SyntheticName.java
+++ b/src/main/java/org/microbean/construct/element/SyntheticName.java
@@ -61,6 +61,18 @@ public final class SyntheticName implements Constable, Name {
*/
+ /**
+ * Creates a new {@link SyntheticName}.
+ *
+ * @param value the actual name; must not be {@code null}
+ *
+ * @exception NullPointerException if {@code value} is {@code null}
+ */
+ public SyntheticName(final Name value) {
+ super();
+ this.value = value instanceof SyntheticName sn ? sn.value : value.toString();
+ }
+
/**
* Creates a new {@link SyntheticName}.
*
diff --git a/src/test/java/org/microbean/construct/element/TestSyntheticAnnotationMirror.java b/src/test/java/org/microbean/construct/element/TestSyntheticAnnotationMirror.java
new file mode 100644
index 0000000..8a0b104
--- /dev/null
+++ b/src/test/java/org/microbean/construct/element/TestSyntheticAnnotationMirror.java
@@ -0,0 +1,75 @@
+/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
+ *
+ * Copyright © 2026 microBean™.
+ *
+ * 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 org.microbean.construct.element;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+
+import org.junit.jupiter.api.Test;
+
+import org.microbean.construct.DefaultDomain;
+import org.microbean.construct.Domain;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@Deprecated(since = "never") // used by a test in this suite; this class is not actually deprecated
+final class TestSyntheticAnnotationMirror {
+
+ private TestSyntheticAnnotationMirror() {
+ super();
+ }
+
+ @Test
+ final void test() {
+ final AnnotationMirror deprecated =
+ new DefaultDomain().typeElement(this.getClass().getCanonicalName()).getAnnotationMirrors().get(0);
+ final TypeElement deprecatedTE = (TypeElement)deprecated.getAnnotationType().asElement();
+
+ final List extends Element> elements = deprecatedTE.getEnclosedElements();
+ assertEquals(2, elements.size()); // since, forRemoval
+
+ ExecutableElement ee = (ExecutableElement)elements.get(0);
+ assertTrue(ee.getSimpleName().contentEquals("since")); // declaration order is retained per contract
+ ee = (ExecutableElement)elements.get(1);
+ assertTrue(ee.getSimpleName().contentEquals("forRemoval"));
+
+ final AnnotationMirror syntheticDeprecated = new SyntheticAnnotationMirror(deprecated);
+
+ final Map extends ExecutableElement, ? extends AnnotationValue> originalValues = deprecated.getElementValues();
+ final Map extends ExecutableElement, ? extends AnnotationValue> syntheticValues = syntheticDeprecated.getElementValues();
+ assertEquals(originalValues.size(), syntheticValues.size());
+ assertNotEquals(originalValues, syntheticValues);
+
+ final SyntheticAnnotationTypeElement syntheticDeprecatedTE =
+ (SyntheticAnnotationTypeElement)syntheticDeprecated.getAnnotationType().asElement();
+ assertTrue(deprecatedTE.getQualifiedName().contentEquals(syntheticDeprecatedTE.getQualifiedName()));
+
+ final List extends AnnotationMirror> metaAnnotations = syntheticDeprecatedTE.getAnnotationMirrors();
+ assertEquals(3, metaAnnotations.size()); // @Documented, @Retention, @Target
+ assertEquals(deprecatedTE.getAnnotationMirrors(), metaAnnotations); // note that synthetic copies weren't made
+
+ syntheticDeprecatedTE.getAnnotationMirrors().clear(); // annotations are mutable on synthetics
+ assertEquals(0, metaAnnotations.size()); // annotation collections that are returned are thread-safe and mutable
+ assertEquals(3, deprecatedTE.getAnnotationMirrors().size()); // this was unaffected
+ }
+
+}