forked from microbean/microbean-construct
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathUniversalConstruct.java
More file actions
407 lines (358 loc) · 16 KB
/
UniversalConstruct.java
File metadata and controls
407 lines (358 loc) · 16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
*
* Copyright © 2024–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;
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.util.List;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Supplier;
import javax.lang.model.AnnotatedConstruct;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.QualifiedNameable;
import javax.lang.model.type.TypeMirror;
import org.microbean.construct.constant.Constables;
import org.microbean.construct.element.UniversalAnnotation;
import org.microbean.construct.element.UniversalElement;
import org.microbean.construct.type.UniversalType;
import static java.lang.constant.ConstantDescs.BSM_INVOKE;
import static java.lang.constant.ConstantDescs.CD_List;
import static java.lang.constant.MethodHandleDesc.ofConstructor;
import static java.util.Objects.requireNonNull;
/**
* An abstract implementation of {@link AnnotatedConstruct} from which only {@link UniversalElement} and {@link
* UniversalType} descend.
*
* @param <T> a type of {@link AnnotatedConstruct}, which may be only either {@link Element} or {@link TypeMirror}
*
* @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
*
* @see AnnotatedConstruct
*
* @see UniversalElement
*
* @see UniversalType
*/
public abstract sealed class UniversalConstruct<T extends AnnotatedConstruct>
implements AnnotatedConstruct, Constable
permits UniversalElement, UniversalType {
/*
* Static fields.
*/
private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
/*
* Instance fields.
*/
private final PrimordialDomain domain;
// Eventually this should become a lazy constant/stable value
// volatile not needed
private Supplier<? extends T> delegateSupplier;
// Eventually this should become a lazy constant/stable value
private volatile String s;
// Eventually this should become a lazy constant/stable value
private volatile CopyOnWriteArrayList<AnnotationMirror> annotations;
/*
* Constructors.
*/
/**
* Creates a new {@link AnnotatedConstruct}.
*
* @param delegate a delegate to which operations are delegated; must not be {@code null}
*
* @param domain a {@link PrimordialDomain} representing the construct domain from which the supplied {@code
* delegate} is presumed to have originated; must not be {@code null}
*
* @exception NullPointerException if either argument is {@code null}
*
* @see #UniversalConstruct(List, AnnotatedConstruct, PrimordialDomain)
*/
protected UniversalConstruct(final T delegate, final PrimordialDomain domain) {
this(null, delegate, domain);
}
/**
* Creates a new {@link AnnotatedConstruct}.
*
* @param annotations a {@link List} of {@link AnnotationMirror} instances representing annotations, often
* synthetic, that this {@link UniversalConstruct} should bear; may be {@code null} in which case only the annotations
* from the supplied {@code delegate} will be used
* @param delegate a delegate to which operations are delegated; must not be {@code null}
*
* @param domain a {@link PrimordialDomain} representing the construct domain from which the supplied {@code
* delegate} is presumed to have originated; must not be {@code null}
*
* @exception NullPointerException if any argument is {@code null}
*/
@SuppressWarnings("try")
protected UniversalConstruct(final List<? extends AnnotationMirror> annotations,
final T delegate,
final PrimordialDomain domain) {
super();
this.domain = requireNonNull(domain, "domain");
if (annotations != null) {
this.annotations = new CopyOnWriteArrayList<>(annotations);
}
final T unwrappedDelegate = unwrap(requireNonNull(delegate, "delegate"));
if (unwrappedDelegate == delegate) {
this.delegateSupplier = () -> {
try (var lock = domain.lock()) {
// No unwrapping happened so do symbol completion early; most common case.
if (unwrappedDelegate instanceof Element) {
((Element)unwrappedDelegate).getModifiers();
} else {
((TypeMirror)unwrappedDelegate).getKind();
}
this.delegateSupplier = () -> unwrappedDelegate;
}
return unwrappedDelegate;
};
} else {
assert delegate instanceof UniversalConstruct;
// Symbol completion already happened because unwrapping actually happened
this.delegateSupplier = () -> unwrappedDelegate;
}
}
/*
* Instance methods.
*/
/**
* Returns the delegate to which operations are delegated.
*
* <p>The delegate is guaranteed not to be an instance of {@link UniversalConstruct}.</p>
*
* @return a non-{@code null} delegate
*
* @see Element
*
* @see TypeMirror
*/
public final T delegate() {
final T delegate = this.delegateSupplier.get();
assert !(delegate instanceof UniversalConstruct);
return delegate;
}
@Override // Constable
public final Optional<? extends ConstantDesc> describeConstable() {
final PrimordialDomain primordialDomain = this.domain();
if (domain instanceof Domain d && d instanceof Constable dc) {
final T delegate = this.delegate();
final List<AnnotationMirror> annotations = this.annotations; // volatile read; may be null and that's OK
return Constables.describe(delegate, d)
.flatMap(delegateDesc -> Constables.describe(annotations)
.map(annosDesc -> DynamicConstantDesc.of(BSM_INVOKE,
ofConstructor(ClassDesc.of(this.getClass().getName()),
CD_List,
ClassDesc.of(delegate instanceof TypeMirror ? TypeMirror.class.getName() : Element.class.getName()),
ClassDesc.of(PrimordialDomain.class.getName())),
annosDesc,
delegateDesc,
dc.describeConstable().orElseThrow())));
}
return Optional.empty();
}
/**
* Returns the {@link PrimordialDomain} supplied at construction time.
*
* @return the non-{@code null} {@link PrimordialDomain} supplied at construction time
*/
public final PrimordialDomain domain() {
return this.domain;
}
@Override // Object
public final boolean equals(final Object other) {
// Interesting; equality does not cause symbol completion. See:
//
// https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java#L553-L559
// (the only type that overrides this is ArrayType; see
// https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java#L1402-L1406)
//
// https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java
// (Symbol (Element) doesn't override it at all.)
return this == other || switch (other) {
case null -> false;
case UniversalConstruct<?> uc when this.getClass() == uc.getClass() -> this.delegate().equals(uc.delegate());
default -> false;
};
}
/**
* Returns a non-{@code null}, determinate, <strong>mutable</strong>, thread-safe {@link List} of {@link
* AnnotationMirror} instances representing the annotations to be considered <dfn>directly present</dfn> on this
* {@link UniversalConstruct} implementation.
*
* @return a non-{@code null}, determinate, <strong>mutable</strong> thread-safe {@link List} of {@link
* AnnotationMirror}s
*
* @see AnnotatedConstruct#getAnnotationMirrors()
*/
@Override // AnnotatedConstruct
@SuppressWarnings("try")
public final List<AnnotationMirror> getAnnotationMirrors() {
CopyOnWriteArrayList<AnnotationMirror> annotations = this.annotations; // volatile read
if (annotations == null) {
try (var lock = this.domain().lock()) {
this.annotations = annotations = // volatile write
new CopyOnWriteArrayList<>(UniversalAnnotation.of(this.delegate().getAnnotationMirrors(), this.domain()));
}
}
return annotations;
}
/**
* Makes a <strong>best effort</strong> to return an {@link Annotation} of the appropriate type <dfn>present</dfn> on
* this {@link UniversalConstruct} implementation.
*
* <p>See the specification for the {@link AnnotatedConstruct#getAnnotation(Class)} method for important details.</p>
*
* <p>{@link UniversalConstruct} implementations deliberately permit modification of their {@linkplain
* #getAnnotationMirrors() annotations}. Consequently, this override first checks to see if there is at least one
* {@link AnnotationMirror} whose {@linkplain AnnotationMirror#getAnnotationType() annotation type} is declared by a
* {@link javax.lang.model.element.TypeElement} whose {@linkplain
* javax.lang.model.element.TypeElement#getQualifiedName() qualified name} is {@linkplain
* javax.lang.model.element.Name#contentEquals(CharSequence) equal to} the {@linkplain Class#getCanonicalName()
* canonical name} of the supplied {@link Class}. If there is, then the {@link AnnotatedConstruct#getAnnotation(Class)
* getAnnotation(Class)} method is invoked on the {@linkplain #delegate() delegate} and its result is
* returned. Otherwise, {@code null} is returned.</p>
*
* <p>There are circumstances where the {@link Annotation} returned by this method may not accurately reflect a
* synthetic annotation added to this {@link AnnotatedConstruct} implementation's {@linkplain #getAnnotationMirrors()
* annotations}.</p>
*
* <p>In general, the use of this method is discouraged.</p>
*
* @param annotationType a {@link Class} that is an annotation interface; must not be {@code null}
*
* @return an appropriate {@link Annotation}, or {@code null}
*
* @exception NullPointerException if {@code annotationType} is {@code null}
*
* @see AnnotatedConstruct#getAnnotation(Class)
*
* @deprecated The use of this method is discouraged.
*/
@Deprecated
@Override // AnnotatedConstruct
@SuppressWarnings("try")
public final <A extends Annotation> A getAnnotation(final Class<A> annotationType) {
if (!annotationType.isAnnotation()) {
return null;
}
final String canonicalName = annotationType.getCanonicalName();
for (final AnnotationMirror a : this.getAnnotationMirrors()) {
if (((QualifiedNameable)a.getAnnotationType().asElement()).getQualifiedName().contentEquals(canonicalName)) {
// TODO: is this lock actually needed, given how delegateSupplier works?
try (var lock = this.domain().lock()) {
return this.delegate().getAnnotation(annotationType);
}
}
}
return null;
}
/**
* Makes a <strong>best effort</strong> to return an array of {@link Annotation}s of the appropriate type
* <dfn>associated</dfn> with this {@link UniversalConstruct} implementation.
*
* <p>See the specification for the {@link AnnotatedConstruct#getAnnotationsByType(Class)} method for important
* details.</p>
*
* <p>{@link UniversalConstruct} implementations deliberately permit modification of their {@linkplain
* #getAnnotationMirrors() annotations}. Consequently, this override first checks to see if there is at least one
* {@link AnnotationMirror} whose {@linkplain AnnotationMirror#getAnnotationType() annotation type} is declared by a
* {@link javax.lang.model.element.TypeElement} whose {@linkplain
* javax.lang.model.element.TypeElement#getQualifiedName() qualified name} is {@linkplain
* javax.lang.model.element.Name#contentEquals(CharSequence) equal to} the {@linkplain Class#getCanonicalName()
* canonical name} of the supplied {@link Class}. If there is, then the {@link
* AnnotatedConstruct#getAnnotationsByType(Class) getAnnotationsByType(Class)} method is invoked on the {@linkplain
* #delegate() delegate} and its result is returned. Otherwise, an empty array is returned.</p>
*
* <p>There are circumstances where the {@link Annotation} array returned by this method may not accurately reflect
* synthetic annotations added to this {@link AnnotatedConstruct} implementation's {@linkplain #getAnnotationMirrors()
* annotations}.</p>
*
* <p>In general, the use of this method is discouraged.</p>
*
* @param annotationType a {@link Class} that is an annotation interface; must not be {@code null}
*
* @return an appropriate {@link Annotation}, or {@code null}
*
* @exception NullPointerException if {@code annotationType} is {@code null}
*
* @see AnnotatedConstruct#getAnnotation(Class)
*
* @deprecated The use of this method is discouraged.
*/
@Deprecated
@Override // AnnotatedConstruct
@SuppressWarnings({"try", "unchecked"})
public final <A extends Annotation> A[] getAnnotationsByType(final Class<A> annotationType) {
if (!annotationType.isAnnotation()) {
return (A[])EMPTY_ANNOTATION_ARRAY;
}
final String canonicalName = annotationType.getCanonicalName();
for (final AnnotationMirror a : this.getAnnotationMirrors()) {
if (((QualifiedNameable)a.getAnnotationType().asElement()).getQualifiedName().contentEquals(canonicalName)) {
// TODO: is this lock actually needed, given how delegateSupplier works?
try (var lock = this.domain().lock()) {
return this.delegate().getAnnotationsByType(annotationType);
}
}
}
return (A[])EMPTY_ANNOTATION_ARRAY;
}
@Override // Object
public final int hashCode() {
// Interesting; hashCode doesn't cause symbol completion. See:
//
// https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java#L565-L568
// (AnnoConstruct doesn't define it so super.hashCode() is Object.hashCode())
//
// https://github.com/openjdk/jdk/blob/jdk-26%2B25/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java
// (Symbol (Element) doesn't override it at all.)
return this.delegate().hashCode();
}
@Override // Object
@SuppressWarnings("try")
public final String toString() {
String s = this.s; // volatile read
if (s == null) {
try (var lock = this.domain().lock()) {
s = this.s = this.delegate().toString(); // volatile write, read
}
}
return s;
}
/*
* Static methods.
*/
/**
* <dfn>Unwraps</dfn> the supplied {@link AnnotatedConstruct} implementation such that the returned value is not an
* instance of {@link UniversalConstruct}.
*
* @param <T> an {@link AnnotatedConstruct} subtype (possibly {@link UniversalElement} or {@link UniversalType})
*
* @param t an {@link AnnotatedConstruct}; may be {@code null}
*
* @return an object of the appropriate type that is guaranteed not to be an instance of {@link UniversalConstruct}
*
* @see #delegate()
*/
@SuppressWarnings("unchecked")
public static final <T extends AnnotatedConstruct> T unwrap(T t) {
while (t instanceof UniversalConstruct<?> uc) {
t = (T)uc.delegate();
}
return t;
}
}