unmodifiableTypeParameters;
+
+ protected Parameterizable(final ElementKind kind) {
+ super(kind); // no need to validate; sealed class; subclasses already validate
+ this.typeParameters = new ArrayList<>(5);
+ this.unmodifiableTypeParameters = Collections.unmodifiableList(this.typeParameters);
+ }
+
+ @Override // Parameterizable
+ public List extends TypeParameterElement> getTypeParameters() {
+ return this.unmodifiableTypeParameters;
+ }
+
+ public final void addTypeParameter(final P tp) {
+ this.typeParameters.add(validateAndEncloseTypeParameter(tp));
+ }
+
+ public final
void addTypeParameters(final Iterable extends P> tps) {
+ for (final P tp : tps) {
+ this.addTypeParameter(tp);
+ }
+ }
+
+ private final
P validateAndEncloseTypeParameter(final P tp) {
+ switch (tp.getKind()) {
+ case TYPE_PARAMETER:
+ final TypeMirror t = tp.asType();
+ switch (t.getKind()) {
+ case TYPEVAR:
+ tp.setEnclosingElement(this); // idempotent
+ assert tp.getGenericElement() == this;
+ assert tp.getEnclosingElement() == this;
+ assert tp.asType() == t;
+ assert t instanceof TypeVariable tv ? tv.asElement() == tp : true;
+ return tp;
+ default:
+ throw new IllegalArgumentException("typeParameter: " + tp + "; ((TypeVariable)tp.asType()).getKind(): " + t.getKind());
+ }
+ default:
+ throw new IllegalArgumentException("typeParameter: " + tp);
+ }
+ }
+
+ @Override // Element
+ public final boolean isUnnamed() {
+ return false;
+ }
+
+
+ /*
+ * Static methods.
+ */
+
+
+ public static final List extends TypeMirror> typeArguments(final TypeMirror t) {
+ switch (t.getKind()) {
+ case DECLARED:
+ return ((DeclaredType)t).getTypeArguments();
+ case EXECUTABLE:
+ return ((ExecutableType)t).getTypeVariables();
+ default:
+ return List.of();
+ }
+ }
+
+}
diff --git a/lang/src/main/java/org/microbean/lang/element/RecordComponentElement.java b/lang/src/main/java/org/microbean/lang/element/RecordComponentElement.java
new file mode 100644
index 00000000..60a6bd37
--- /dev/null
+++ b/lang/src/main/java/org/microbean/lang/element/RecordComponentElement.java
@@ -0,0 +1,129 @@
+/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
+ *
+ * Copyright © 2023 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.lang.element;
+
+import java.util.Set;
+
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ElementVisitor;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+
+public final class RecordComponentElement extends Element implements javax.lang.model.element.RecordComponentElement {
+
+ private ExecutableElement accessor;
+
+ public RecordComponentElement() {
+ super(ElementKind.RECORD_COMPONENT);
+ // Record components are always public and nothing else.
+ this.addModifier(Modifier.PUBLIC);
+ }
+
+ @Override // AbstractElement
+ public final R accept(final ElementVisitor v, final P p) {
+ return v.visitRecordComponent(this, p);
+ }
+
+ @Override // Element
+ public final boolean isUnnamed() {
+ return false;
+ }
+
+ @Override // RecordComponentElement
+ public final ExecutableElement getAccessor() {
+ return this.accessor;
+ }
+
+ public final void setAccessor(final ExecutableElement e) {
+ final Object old = this.getAccessor();
+ if (old == null) {
+ if (e != null) {
+ this.accessor = this.validateAccessor(e);
+ }
+ } else if (old != e) {
+ throw new IllegalStateException("e: " + e + "; old: " + old);
+ }
+ }
+
+ @Override
+ protected final E validateEnclosedElement(final E e) {
+ throw new IllegalArgumentException("record components cannot enclose Elements");
+ }
+
+ @Override
+ protected final ElementKind validateKind(final ElementKind kind) {
+ switch (kind) {
+ case RECORD_COMPONENT:
+ return kind;
+ default:
+ throw new IllegalArgumentException("kind: " + kind);
+ }
+ }
+
+ @Override // Element
+ protected final Modifier validateModifier(final Modifier modifier) {
+ switch (modifier) {
+ case PUBLIC:
+ return modifier;
+ default:
+ throw new IllegalArgumentException("modifier: " + modifier);
+ }
+ }
+
+ @Override
+ protected final TypeMirror validateType(final TypeMirror type) {
+ switch (type.getKind()) {
+ case DECLARED:
+ return type;
+ default:
+ throw new IllegalArgumentException("type: " + type);
+ }
+ }
+
+ private final ExecutableElement validateAccessor(final ExecutableElement e) {
+ switch (e.getKind()) {
+ case METHOD:
+ final Set modifiers = e.getModifiers();
+ if (modifiers.contains(Modifier.ABSTRACT)) {
+ throw new IllegalArgumentException("abstract accessor: " + e);
+ } else if (e.isDefault() || modifiers.contains(Modifier.DEFAULT)) {
+ throw new IllegalArgumentException("default accessor: " + e);
+ } else if (modifiers.contains(Modifier.NATIVE)) {
+ throw new IllegalArgumentException("native accessor: " + e);
+ } else if (!modifiers.contains(Modifier.PUBLIC)) {
+ throw new IllegalArgumentException("non-public accessor: " + e);
+ } else if (modifiers.contains(Modifier.STATIC)) {
+ throw new IllegalArgumentException("static accessor: " + e);
+ } else if (e.asType().getKind() != TypeKind.EXECUTABLE ||
+ !e.getParameters().isEmpty() ||
+ !e.getThrownTypes().isEmpty() ||
+ !e.getTypeParameters().isEmpty() ||
+ !e.getReturnType().equals(this.asType()) ||
+ !e.getSimpleName().equals(this.getSimpleName())) {
+ throw new IllegalArgumentException("invalid accessor: " + e);
+ }
+ // TODO: here or elsewhere: e must have Modifier.PUBLIC; e must not be static, etc. etc.
+ return e;
+ default:
+ throw new IllegalArgumentException("e: " + e);
+ }
+ }
+
+}
diff --git a/lang/src/main/java/org/microbean/lang/element/TypeElement.java b/lang/src/main/java/org/microbean/lang/element/TypeElement.java
new file mode 100644
index 00000000..80cd0489
--- /dev/null
+++ b/lang/src/main/java/org/microbean/lang/element/TypeElement.java
@@ -0,0 +1,220 @@
+/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
+ *
+ * Copyright © 2022–2023 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.lang.element;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ElementVisitor;
+import javax.lang.model.element.NestingKind;
+import javax.lang.model.element.QualifiedNameable;
+import javax.lang.model.element.RecordComponentElement;
+
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+
+public final class TypeElement extends Parameterizable implements javax.lang.model.element.TypeElement {
+
+ private final NestingKind nestingKind;
+
+ private TypeMirror superclass;
+
+ private final List interfaces;
+
+ private final List unmodifiableInterfaces;
+
+ private final List permittedSubclasses;
+
+ private final List unmodifiablePermittedSubclasses;
+
+ private final List recordComponents;
+
+ private final List unmodifiableRecordComponents;
+
+ public TypeElement(final ElementKind kind) {
+ this(kind, NestingKind.TOP_LEVEL);
+ }
+
+ public TypeElement(final ElementKind kind,
+ final NestingKind nestingKind) {
+ super(kind);
+ this.nestingKind = nestingKind == null ? NestingKind.TOP_LEVEL : nestingKind;
+ this.interfaces = new ArrayList<>(5);
+ this.unmodifiableInterfaces = Collections.unmodifiableList(this.interfaces);
+ this.permittedSubclasses = new ArrayList<>(5);
+ this.unmodifiablePermittedSubclasses = Collections.unmodifiableList(this.permittedSubclasses);
+ if (kind == ElementKind.RECORD) {
+ this.recordComponents = new ArrayList<>(7);
+ this.unmodifiableRecordComponents = Collections.unmodifiableList(this.recordComponents);
+ } else {
+ this.recordComponents = List.of();
+ this.unmodifiableRecordComponents = List.of();
+ }
+ }
+
+ @Override // TypeElement
+ public final R accept(final ElementVisitor v, final P p) {
+ return v.visitType(this, p);
+ }
+
+ @Override // TypeElement
+ public final List extends TypeMirror> getInterfaces() {
+ return this.unmodifiableInterfaces;
+ }
+
+ @SuppressWarnings("fallthrough")
+ public final void addInterface(final TypeMirror i) {
+ switch (i.getKind()) {
+ case DECLARED:
+ switch (((DeclaredType)i).asElement().getKind()) {
+ case INTERFACE:
+ this.interfaces.add(i);
+ return;
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+ throw new IllegalArgumentException("i: " + i);
+ }
+
+ @Override // TypeElement
+ public final NestingKind getNestingKind() {
+ return this.nestingKind;
+ }
+
+ @Override // TypeElement
+ public final javax.lang.model.element.Name getQualifiedName() {
+ final QualifiedNameable qn;
+ switch (this.getNestingKind()) {
+ case ANONYMOUS:
+ case LOCAL:
+ qn = null;
+ break;
+ case MEMBER:
+ case TOP_LEVEL:
+ qn = (QualifiedNameable)this.getEnclosingElement();
+ break;
+ default:
+ throw new AssertionError();
+ }
+ return qn == null ? this.getSimpleName() : Name.of(qn.getQualifiedName() + "." + this.getSimpleName());
+ }
+
+ @Override // TypeElement
+ public final TypeMirror getSuperclass() {
+ return this.superclass;
+ }
+
+ public final void setSuperclass(final TypeMirror superclass) {
+ final Object old = this.getSuperclass();
+ if (old == null) {
+ if (superclass != null) {
+ this.superclass = validateSuperclass(superclass);
+ }
+ } else if (old != superclass) {
+ throw new IllegalStateException();
+ }
+ }
+
+ @Override // TypeElement
+ public final List extends TypeMirror> getPermittedSubclasses() {
+ return this.unmodifiablePermittedSubclasses;
+ }
+
+ public final void addPermittedSubclass(final TypeMirror t) {
+ this.permittedSubclasses.add(this.validatePermittedSubclass(t));
+ }
+
+ public final void addPermittedSubclasses(final Iterable extends TypeMirror> ts) {
+ for (final TypeMirror t : ts) {
+ this.addPermittedSubclass(t);
+ }
+ }
+
+ protected TypeMirror validatePermittedSubclass(final TypeMirror t) {
+ return Objects.requireNonNull(t, "t");
+ }
+
+ @Override // TypeElement
+ public final List extends RecordComponentElement> getRecordComponents() {
+ return this.unmodifiableRecordComponents;
+ }
+
+ @Override
+ protected final E validateEnclosedElement(E e) {
+ e = super.validateEnclosedElement(e);
+ switch (e.getKind()) {
+ case RECORD_COMPONENT:
+ switch (e.asType().getKind()) {
+ case DECLARED:
+ return e;
+ default:
+ throw new IllegalArgumentException("e: " + e);
+ }
+ default:
+ // throw new IllegalArgumentException("e: " + e);
+ return e;
+ }
+ }
+
+ @Override // Element
+ protected ElementKind validateKind(final ElementKind kind) {
+ switch (kind) {
+ case ANNOTATION_TYPE:
+ case CLASS:
+ case ENUM:
+ case INTERFACE:
+ case RECORD:
+ return kind;
+ default:
+ throw new IllegalArgumentException("kind: " + kind);
+ }
+ }
+
+ @Override // Element
+ protected TypeMirror validateType(final TypeMirror type) {
+ switch (type.getKind()) {
+ case DECLARED:
+ case ERROR:
+ return type;
+ default:
+ throw new IllegalArgumentException("type: " + type);
+ }
+ }
+
+ @Override
+ public final String toString() {
+ final CharSequence n = this.getQualifiedName();
+ if (n == null) {
+ return "";
+ } else if (this.isUnnamed() || n.length() <= 0) {
+ return "";
+ } else {
+ return n.toString();
+ }
+ }
+
+ private static final T validateSuperclass(final T superclass) {
+ return superclass;
+ }
+
+}
diff --git a/lang/src/main/java/org/microbean/lang/element/TypeParameterElement.java b/lang/src/main/java/org/microbean/lang/element/TypeParameterElement.java
new file mode 100644
index 00000000..91f6d737
--- /dev/null
+++ b/lang/src/main/java/org/microbean/lang/element/TypeParameterElement.java
@@ -0,0 +1,146 @@
+/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
+ *
+ * Copyright © 2023 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.lang.element;
+
+import java.util.List;
+
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ElementVisitor;
+
+import javax.lang.model.type.IntersectionType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+
+import org.microbean.lang.type.DefineableType;
+
+public final class TypeParameterElement extends Element implements javax.lang.model.element.TypeParameterElement {
+
+ private javax.lang.model.element.Element genericElement;
+
+ public TypeParameterElement() {
+ super(ElementKind.TYPE_PARAMETER);
+ }
+
+ public & TypeVariable>
+ TypeParameterElement(final javax.lang.model.element.Name simpleName, final T typeVariable) {
+ this();
+ this.setSimpleName(simpleName);
+ typeVariable.setDefiningElement(this);
+ }
+
+ @Override // Element
+ public final R accept(final ElementVisitor v, final P p) {
+ return v.visitTypeParameter(this, p);
+ }
+
+ @Override // Element
+ protected final TypeMirror validateType(final TypeMirror type) {
+ if (type.getKind() == TypeKind.TYPEVAR && type instanceof TypeVariable) {
+ return type;
+ }
+ throw new IllegalArgumentException("type: " + type);
+ }
+
+ @Override // Element
+ public final boolean isUnnamed() {
+ return false;
+ }
+
+ @Override // TypeParameterElement
+ public final List extends TypeMirror> getBounds() {
+ return boundsFrom((TypeVariable)this.asType());
+ }
+
+ @Override // Element
+ public final javax.lang.model.element.Element getEnclosingElement() {
+ return this.getGenericElement();
+ }
+
+ @Override // Element
+ public final void setEnclosingElement(final javax.lang.model.element.Element genericElement) {
+ this.setGenericElement(genericElement);
+ }
+
+ @Override // TypeParameterElement
+ public final javax.lang.model.element.Element getGenericElement() {
+ return this.genericElement;
+ }
+
+ public final void setGenericElement(final javax.lang.model.element.Element genericElement) {
+ final Object old = this.getGenericElement();
+ if (old == null) {
+ if (genericElement != null) {
+ this.genericElement = validateGenericElement(genericElement);
+ }
+ } else if (old != genericElement) {
+ throw new IllegalStateException("old: " + old + "; genericElement: " + genericElement);
+ }
+ }
+
+ @Override
+ public final String toString() {
+ return this.getSimpleName() + " " + this.asType();
+ }
+
+ private final javax.lang.model.element.Element validateGenericElement(final javax.lang.model.element.Element element) {
+ if (element == null) {
+ return null;
+ } else if (element == this) {
+ throw new IllegalArgumentException("element: " + element);
+ }
+ switch (element.getKind()) {
+ case CLASS:
+ case CONSTRUCTOR:
+ case INTERFACE:
+ case METHOD:
+ return element;
+ default:
+ throw new IllegalArgumentException("Not a valid generic element: " + element);
+ }
+ }
+
+
+ /*
+ * Static methods.
+ */
+
+
+ private static final TypeVariable validateTypeVariable(final TypeVariable tv) {
+ switch (tv.getKind()) {
+ case TYPEVAR:
+ return tv;
+ default:
+ throw new IllegalArgumentException("tv: " + tv);
+ }
+ }
+
+ private static final List extends TypeMirror> boundsFrom(final TypeVariable typeVariable) {
+ final TypeMirror upperBound = typeVariable.getUpperBound();
+ switch (upperBound.getKind()) {
+ case INTERSECTION:
+ return ((IntersectionType)upperBound).getBounds();
+ case ARRAY:
+ case DECLARED:
+ case TYPEVAR:
+ return List.of(upperBound);
+ default:
+ throw new IllegalArgumentException("typeVariable: " + typeVariable);
+ }
+ }
+
+}
diff --git a/lang/src/main/java/org/microbean/lang/element/VariableElement.java b/lang/src/main/java/org/microbean/lang/element/VariableElement.java
new file mode 100644
index 00000000..e7994cc9
--- /dev/null
+++ b/lang/src/main/java/org/microbean/lang/element/VariableElement.java
@@ -0,0 +1,116 @@
+/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
+ *
+ * Copyright © 2023 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.lang.element;
+
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ElementVisitor;
+
+import javax.lang.model.type.TypeMirror;
+
+public final class VariableElement extends Element implements javax.lang.model.element.VariableElement {
+
+ public Object constantValue;
+
+ public VariableElement(final ElementKind kind) {
+ super(kind);
+ }
+
+ public VariableElement(final ElementKind kind, final Object constantValue) {
+ super(kind);
+ if (constantValue != null) {
+ this.setConstantValue(constantValue);
+ }
+ }
+
+ @Override // Element
+ public final R accept(final ElementVisitor v, final P p) {
+ return v.visitVariable(this, p);
+ }
+
+ @Override // Element
+ public final boolean isUnnamed() {
+ return false;
+ }
+
+ @Override // VariableElement
+ public final Object getConstantValue() {
+ return this.constantValue;
+ }
+
+ public final void setConstantValue(final Object constantValue) {
+ final Object old = this.getConstantValue();
+ if (old == null) {
+ if (constantValue != null) {
+ this.constantValue = validateConstantValue(constantValue);
+ }
+ } else if (old != constantValue) {
+ throw new IllegalStateException();
+ }
+ }
+
+ @Override
+ public final String toString() {
+ return this.getSimpleName().toString();
+ }
+
+ @Override
+ protected final ElementKind validateKind(final ElementKind kind) {
+ switch (kind) {
+ case BINDING_VARIABLE:
+ case ENUM:
+ case ENUM_CONSTANT:
+ case EXCEPTION_PARAMETER:
+ case FIELD:
+ case LOCAL_VARIABLE:
+ case PARAMETER:
+ case RESOURCE_VARIABLE:
+ return kind;
+ default:
+ throw new IllegalArgumentException("kind: " + kind);
+ }
+ }
+
+ @Override
+ protected final TypeMirror validateType(final TypeMirror type) {
+ switch (type.getKind()) {
+ case ARRAY:
+ case DECLARED:
+ case INTERSECTION:
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case SHORT:
+ case TYPEVAR:
+ case WILDCARD:
+ return type;
+ default:
+ throw new IllegalArgumentException("type: " + type);
+ }
+ }
+
+ private final Object validateConstantValue(final Object constantValue) {
+ if (constantValue == this) {
+ throw new IllegalArgumentException("constantValue: " + constantValue);
+ }
+ return constantValue;
+ }
+
+}
diff --git a/lang/src/main/java/org/microbean/lang/element/package-info.java b/lang/src/main/java/org/microbean/lang/element/package-info.java
new file mode 100644
index 00000000..08060cd4
--- /dev/null
+++ b/lang/src/main/java/org/microbean/lang/element/package-info.java
@@ -0,0 +1,20 @@
+/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
+ *
+ * Copyright © 2023 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.
+ */
+
+/**
+ * Provides classes and interfaces related to the Java language model.
+ *
+ * @author Laird Nelson
+ */
+package org.microbean.lang.element;
diff --git a/lang/src/main/java/org/microbean/lang/package-info.java b/lang/src/main/java/org/microbean/lang/package-info.java
new file mode 100644
index 00000000..c3cb6b81
--- /dev/null
+++ b/lang/src/main/java/org/microbean/lang/package-info.java
@@ -0,0 +1,24 @@
+/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
+ *
+ * Copyright © 2022–2023 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.
+ */
+
+/**
+ * Provides classes and interfaces related to the Java language model.
+ *
+ * @author Laird Nelson
+ */
+package org.microbean.lang;
diff --git a/lang/src/main/java/org/microbean/lang/type/ArrayType.java b/lang/src/main/java/org/microbean/lang/type/ArrayType.java
new file mode 100644
index 00000000..5c0eda91
--- /dev/null
+++ b/lang/src/main/java/org/microbean/lang/type/ArrayType.java
@@ -0,0 +1,90 @@
+/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
+ *
+ * Copyright © 2022–2023 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.lang.type;
+
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeVisitor;
+
+public final class ArrayType extends TypeMirror implements javax.lang.model.type.ArrayType {
+
+ private javax.lang.model.type.TypeMirror componentType;
+
+ public ArrayType() {
+ super(TypeKind.ARRAY);
+ }
+
+ public ArrayType(final javax.lang.model.type.TypeMirror componentType) {
+ this();
+ this.setComponentType(componentType);
+ }
+
+
+ /*
+ * Instance methods.
+ */
+
+
+ @Override // TypeMirror
+ public final R accept(final TypeVisitor v, final P p) {
+ return v.visitArray(this, p);
+ }
+
+ @Override // ArrayType
+ public final javax.lang.model.type.TypeMirror getComponentType() {
+ return this.componentType;
+ }
+
+ public final void setComponentType(final javax.lang.model.type.TypeMirror componentType) {
+ final javax.lang.model.type.TypeMirror old = this.getComponentType();
+ if (old == null) {
+ if (componentType != null) {
+ this.componentType = this.validateComponentType(componentType);
+ }
+ } else if (old != componentType) {
+ throw new IllegalStateException();
+ }
+ }
+
+ public final String toString() {
+ return this.componentType + "[]";
+ }
+
+ private final javax.lang.model.type.TypeMirror validateComponentType(final javax.lang.model.type.TypeMirror componentType) {
+ if (componentType == this) {
+ throw new IllegalArgumentException("componentType: " + componentType);
+ }
+ switch (componentType.getKind()) {
+ case ARRAY:
+ case DECLARED:
+ // case INTERSECTION:
+ case BOOLEAN:
+ case BYTE:
+ case CHAR:
+ case DOUBLE:
+ case FLOAT:
+ case INT:
+ case LONG:
+ case SHORT:
+ case TYPEVAR:
+ case WILDCARD:
+ return componentType;
+ default:
+ throw new IllegalArgumentException("componentType: " + componentType);
+ }
+ }
+
+}
diff --git a/lang/src/main/java/org/microbean/lang/type/Capture.java b/lang/src/main/java/org/microbean/lang/type/Capture.java
new file mode 100644
index 00000000..90c1264a
--- /dev/null
+++ b/lang/src/main/java/org/microbean/lang/type/Capture.java
@@ -0,0 +1,134 @@
+/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
+ *
+ * Copyright © 2022–2023 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.lang.type;
+
+import javax.lang.model.element.ElementKind;
+
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.TypeVisitor;
+import javax.lang.model.type.WildcardType;
+
+import org.microbean.lang.element.Name;
+import org.microbean.lang.element.TypeParameterElement;
+
+public final class Capture extends DefineableType implements TypeVariable {
+
+ private TypeMirror upperBound;
+
+ private TypeMirror lowerBound;
+
+ private final WildcardType wildcardType;
+
+ public Capture(final WildcardType w) {
+ this(Name.of(""), w);
+ }
+
+ public Capture(final javax.lang.model.element.Name name, final WildcardType w) {
+ super(TypeKind.TYPEVAR);
+ this.wildcardType = validateWildcardType(w);
+ this.setDefiningElement(new TypeParameterElement(name, this));
+ }
+
+ @Override // DefineableType
+ protected final javax.lang.model.element.TypeParameterElement validateDefiningElement(final javax.lang.model.element.TypeParameterElement e) {
+ if (e.getKind() != ElementKind.TYPE_PARAMETER) {
+ throw new IllegalArgumentException("e: " + e);
+ }
+ final Object t = e.asType();
+ if (t != null && this != t) {
+ throw new IllegalArgumentException("e: " + e + "; this (" + this + ") != e.asType() (" + t + ")");
+ }
+ return e;
+ }
+
+ @Override // TypeVariable
+ public final R accept(final TypeVisitor v, final P p) {
+ return v.visitTypeVariable(this, p);
+ }
+
+ @Override
+ public final TypeMirror getLowerBound() {
+ return this.lowerBound;
+ }
+
+ public final void setLowerBound(final TypeMirror t) {
+ final Object old = this.getLowerBound();
+ if (old == null) {
+ if (t != null) {
+ this.lowerBound = validateLowerBound(t);
+ }
+ } else if (old != t) {
+ throw new IllegalStateException();
+ }
+ }
+
+ @Override // TypeVariable
+ public final TypeMirror getUpperBound() {
+ return this.upperBound;
+ }
+
+ public final void setUpperBound(final TypeMirror t) {
+ final Object old = this.getUpperBound();
+ if (old == null) {
+ if (t != null) {
+ this.upperBound = validateUpperBound(t);
+ }
+ } else if (old != t) {
+ throw new IllegalStateException();
+ }
+ }
+
+ public final WildcardType getWildcardType() {
+ return this.wildcardType;
+ }
+
+
+ /*
+ * Static methods.
+ */
+
+
+ private static final TypeMirror validateUpperBound(final TypeMirror upperBound) {
+ switch (upperBound.getKind()) {
+ case DECLARED:
+ case INTERSECTION:
+ case TYPEVAR:
+ return upperBound;
+ default:
+ throw new IllegalArgumentException("upperBound: " + upperBound);
+ }
+ }
+
+ private static final TypeMirror validateLowerBound(final TypeMirror lowerBound) {
+ return lowerBound;
+ }
+
+ private static final WildcardType validateWildcardType(final WildcardType w) {
+ if (w != null) {
+ switch (w.getKind()) {
+ case WILDCARD:
+ break;
+ default:
+ throw new IllegalArgumentException("w: " + w);
+ }
+ }
+ return w;
+ }
+
+}
diff --git a/lang/src/main/java/org/microbean/lang/type/DeclaredType.java b/lang/src/main/java/org/microbean/lang/type/DeclaredType.java
new file mode 100644
index 00000000..c215ba8c
--- /dev/null
+++ b/lang/src/main/java/org/microbean/lang/type/DeclaredType.java
@@ -0,0 +1,206 @@
+/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
+ *
+ * Copyright © 2022–2023 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.lang.type;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.lang.model.element.TypeElement;
+
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVisitor;
+
+public sealed class DeclaredType extends DefineableType implements javax.lang.model.type.DeclaredType
+ permits ErrorType {
+
+
+ /*
+ * Instance fields.
+ */
+
+
+ private TypeMirror enclosingType;
+
+ // ArrayType, DeclaredType, ErrorType, TypeVariable, WildcardType
+ private final List typeArguments;
+
+ private final List unmodifiableTypeArguments;
+
+ // See
+ // https://github.com/openjdk/jdk/blob/jdk-20+11/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java#L1197-L1200
+ private Boolean erased;
+
+
+ /*
+ * Constructors.
+ */
+
+
+ public DeclaredType() {
+ super(TypeKind.DECLARED);
+ this.typeArguments = new ArrayList<>(5);
+ this.unmodifiableTypeArguments = Collections.unmodifiableList(this.typeArguments);
+ }
+
+ public DeclaredType(final boolean erased) {
+ this();
+ this.setErased(erased);
+ }
+
+ DeclaredType(final TypeKind kind, final boolean erased) {
+ super(kind);
+ this.typeArguments = new ArrayList<>(5);
+ this.unmodifiableTypeArguments = Collections.unmodifiableList(this.typeArguments);
+ this.setErased(erased);
+ }
+
+
+ /*
+ * Instance methods.
+ */
+
+
+ @Override // TypeMirror
+ public R accept(final TypeVisitor v, final P p) {
+ return v.visitDeclared(this, p);
+ }
+
+ @Override
+ protected TypeKind validateKind(final TypeKind kind) {
+ return switch (kind) {
+ case DECLARED -> kind;
+ default -> throw new IllegalArgumentException("kind: " + kind);
+ };
+ }
+
+ @Override // DefineableType
+ protected TypeElement validateDefiningElement(final TypeElement e) {
+ switch (e.getKind()) {
+ case ANNOTATION_TYPE:
+ case CLASS:
+ case ENUM:
+ case INTERFACE:
+ case RECORD:
+ final javax.lang.model.type.DeclaredType elementType = (javax.lang.model.type.DeclaredType)e.asType();
+ if (elementType == null) {
+ throw new IllegalArgumentException("e: " + e + "; e.asType() == null");
+ } else if (this != elementType) {
+ // We are a parameterized type, i.e. a type usage, i.e. the-type-denoted-by-Set
+ // vs. the-type-denoted-by-Set
+ final int size = this.getTypeArguments().size();
+ if (size > 0 && size != elementType.getTypeArguments().size()) {
+ // We aren't a raw type (size > 0) and our type arguments aren't of the required size, so someone passed a bad
+ // defining element.
+ throw new IllegalArgumentException("e: " + e);
+ }
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("e: " + e);
+ }
+ return e;
+ }
+
+ public final boolean isErased() {
+ final Boolean erased = this.erased;
+ return erased == null ? false : erased.booleanValue();
+ }
+
+ public final void setErased(final boolean b) {
+ final Boolean old = this.erased;
+ if (old == null) {
+ this.erased = Boolean.valueOf(b);
+ } else if (!old.equals(Boolean.valueOf(b))) {
+ throw new IllegalStateException();
+ }
+ }
+
+ @Override // DeclaredType
+ public final TypeMirror getEnclosingType() {
+ return this.enclosingType;
+ }
+
+ public final void setEnclosingType(final TypeMirror enclosingType) {
+ final Object old = this.getEnclosingType();
+ if (old == null) {
+ if (enclosingType != null) {
+ this.enclosingType = this.validateEnclosingType(enclosingType);
+ }
+ } else if (old != enclosingType) {
+ throw new IllegalStateException();
+ }
+ }
+
+ private final TypeMirror validateEnclosingType(final TypeMirror t) {
+ if (t == null) {
+ return NoType.NONE;
+ } else if (t == this) {
+ throw new IllegalArgumentException("t: " + t);
+ }
+ switch (t.getKind()) {
+ case DECLARED:
+ case NONE:
+ return t;
+ default:
+ throw new IllegalArgumentException("t: " + t);
+ }
+ }
+
+ @Override // DeclaredType
+ public final List extends TypeMirror> getTypeArguments() {
+ return this.unmodifiableTypeArguments;
+ }
+
+ public final void addTypeArgument(final TypeMirror t) {
+ this.typeArguments.add(this.validateTypeArgument(t));
+ }
+
+ public final void addTypeArguments(final Iterable extends TypeMirror> ts) {
+ for (final TypeMirror t : ts) {
+ this.addTypeArgument(t);
+ }
+ }
+
+ private final TypeMirror validateTypeArgument(final TypeMirror t) {
+ if (t == this) {
+ throw new IllegalArgumentException("t: " + t);
+ } else if (this.isErased()) {
+ throw new IllegalStateException("this.isErased()");
+ }
+
+ switch (t.getKind()) {
+ case ARRAY:
+ case DECLARED:
+ // case INTERSECTION: // JLS says reference types and wildcards only
+ case TYPEVAR:
+ case WILDCARD:
+ break;
+ default:
+ throw new IllegalArgumentException("t: " + t);
+ }
+ return t;
+ }
+
+ @Override
+ public String toString() {
+ final Object element = this.asElement();
+ return element == null ? "(undefined type; arguments: " + this.getTypeArguments() + ")" : element.toString();
+ }
+
+}
diff --git a/lang/src/main/java/org/microbean/lang/type/DefineableType.java b/lang/src/main/java/org/microbean/lang/type/DefineableType.java
new file mode 100644
index 00000000..97031ff0
--- /dev/null
+++ b/lang/src/main/java/org/microbean/lang/type/DefineableType.java
@@ -0,0 +1,69 @@
+/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
+ *
+ * Copyright © 2022–2023 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.lang.type;
+
+import javax.lang.model.element.Element;
+
+import javax.lang.model.type.TypeKind;
+
+public abstract sealed class DefineableType extends TypeMirror permits Capture, DeclaredType, TypeVariable {
+
+ private E definingElement;
+
+ protected DefineableType(final TypeKind kind) {
+ super(kind);
+ }
+
+ public final E asElement() {
+ return this.definingElement;
+ }
+
+ public final void setDefiningElement(final E definingElement) {
+ final E old = this.asElement();
+ if (old == null) {
+ if (definingElement != null) {
+ this.definingElement = this.validateDefiningElement(definingElement);
+ }
+ } else if (old != definingElement) {
+ throw new IllegalStateException();
+ }
+ }
+
+ public final boolean isDefined() {
+ return this.asElement() != null;
+ }
+
+ public final javax.lang.model.type.TypeMirror getElementType() {
+ final Element e = this.asElement();
+ return e == null ? null : e.asType();
+ }
+
+ protected abstract E validateDefiningElement(final E e);
+
+ @Override
+ protected TypeKind validateKind(final TypeKind kind) {
+ switch (kind) {
+ case DECLARED:
+ case ERROR:
+ case TYPEVAR:
+ return kind;
+ default:
+ throw new IllegalArgumentException("kind: " + kind);
+ }
+ }
+
+}
diff --git a/lang/src/main/java/org/microbean/lang/type/DelegatingTypeMirror.java b/lang/src/main/java/org/microbean/lang/type/DelegatingTypeMirror.java
new file mode 100644
index 00000000..a1cfcca0
--- /dev/null
+++ b/lang/src/main/java/org/microbean/lang/type/DelegatingTypeMirror.java
@@ -0,0 +1,351 @@
+/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
+ *
+ * Copyright © 2023 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.lang.type;
+
+import java.lang.annotation.Annotation;
+
+import java.lang.constant.ClassDesc;
+import java.lang.constant.Constable;
+import java.lang.constant.ConstantDesc;
+import java.lang.constant.DynamicConstantDesc;
+import java.lang.constant.MethodHandleDesc;
+import java.lang.constant.MethodTypeDesc;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ErrorType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.IntersectionType;
+import javax.lang.model.type.NullType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.TypeVisitor;
+import javax.lang.model.type.UnionType;
+import javax.lang.model.type.WildcardType;
+
+import org.microbean.lang.CompletionLock;
+import org.microbean.lang.Lang;
+import org.microbean.lang.TypeAndElementSource;
+import org.microbean.lang.Equality;
+
+import org.microbean.lang.element.DelegatingElement;
+
+import org.microbean.lang.type.NoType;
+import org.microbean.lang.type.Types;
+
+import static java.lang.constant.ConstantDescs.BSM_INVOKE;
+import static java.lang.constant.DirectMethodHandleDesc.Kind.STATIC;
+
+public final class DelegatingTypeMirror
+ implements ArrayType,
+ Constable,
+ ErrorType,
+ ExecutableType,
+ IntersectionType,
+ javax.lang.model.type.NoType,
+ NullType,
+ PrimitiveType,
+ TypeVariable,
+ UnionType,
+ WildcardType {
+
+ private static final ClassDesc CD_DelegatingTypeMirror = ClassDesc.of("org.microbean.lang.type.DelegatingTypeMirror");
+
+ private static final ClassDesc CD_TypeAndElementSource = ClassDesc.of("org.microbean.lang.TypeAndElementSource");
+
+ private static final ClassDesc CD_Equality = ClassDesc.of("org.microbean.lang.Equality");
+
+ private static final ClassDesc CD_TypeMirror = ClassDesc.of("javax.lang.model.type.TypeMirror");
+
+ private final TypeAndElementSource elementSource;
+
+ private final TypeMirror delegate;
+
+ private final Equality ehc;
+
+ private DelegatingTypeMirror(final TypeMirror delegate, final TypeAndElementSource elementSource, final Equality ehc) {
+ super();
+ this.delegate = unwrap(Objects.requireNonNull(delegate, "delegate"));
+ this.elementSource = elementSource == null ? Lang.typeAndElementSource() : elementSource;
+ this.ehc = ehc == null ? new Equality(true) : ehc;
+ }
+
+ @Override // TypeMirror
+ public final R accept(final TypeVisitor