001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2024 microBean™. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 006 * the License. You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 011 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 012 * specific language governing permissions and limitations under the License. 013 */ 014package org.microbean.construct.element; 015 016import java.util.ArrayList; 017import java.util.Collection; 018import java.util.Collections; 019import java.util.List; 020import java.util.Objects; 021 022import javax.lang.model.element.AnnotationMirror; 023import javax.lang.model.element.AnnotationValue; 024import javax.lang.model.element.AnnotationValueVisitor; 025import javax.lang.model.element.ExecutableElement; 026import javax.lang.model.element.VariableElement; 027 028import javax.lang.model.type.TypeMirror; 029 030import org.microbean.construct.Domain; 031 032import org.microbean.construct.type.UniversalType; 033 034/** 035 * An {@link AnnotationValue} implementation. 036 * 037 * @param delegate an {@link AnnotationValue} to which operations will be delegated; must not be {@code null} 038 * 039 * @param domain a {@link Domain}; must not be {@code null} 040 * 041 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a> 042 * 043 * @see AnnotationValue 044 */ 045public final record AnnotationValueRecord(AnnotationValue delegate, Domain domain) implements AnnotationValue { 046 047 048 /* 049 * Constructors. 050 */ 051 052 053 /** 054 * Creates a new {@link AnnotationValueRecord}. 055 * 056 * @param delegate an {@link AnnotationValue} to which operations will be delegated; must not be {@code null} 057 * 058 * @param domain a {@link Domain}; must not be {@code null} 059 * 060 * @exception NullPointerException if either argument is {@code null} 061 */ 062 public AnnotationValueRecord { 063 Objects.requireNonNull(delegate, "delegate"); 064 Objects.requireNonNull(domain, "domain"); 065 } 066 067 068 /* 069 * Instance methods. 070 */ 071 072 073 @Override // AnnotationValue 074 @SuppressWarnings({ "try", "unchecked" }) 075 public final <R, P> R accept(final AnnotationValueVisitor<R, P> v, final P p) { 076 try (var lock = this.domain().lock()) { 077 return switch (this.getValue()) { 078 case null -> v.visitUnknown(this, p); // ...or AssertionError? 079 case AnnotationMirror a -> v.visitAnnotation(a, p); 080 case List<?> l -> v.visitArray((List<? extends AnnotationValue>)l, p); 081 case TypeMirror t -> v.visitType(t, p); 082 case VariableElement e -> v.visitEnumConstant(e, p); 083 case Boolean b -> v.visitBoolean(b, p); 084 case Byte b -> v.visitByte(b, p); 085 case Character c -> v.visitChar(c, p); 086 case Double d -> v.visitDouble(d, p); 087 case Float f -> v.visitFloat(f, p); 088 case Integer i -> v.visitInt(i, p); 089 case Long l -> v.visitLong(l, p); 090 case Short s -> v.visitShort(s, p); 091 case String s -> v.visitString(s, p); 092 default -> v.visitUnknown(this, p); 093 }; 094 } 095 } 096 097 @Override // Object 098 @SuppressWarnings("try") 099 public final boolean equals(final Object other) { 100 return this == other || switch (other) { 101 case null -> false; 102 case AnnotationValue av -> { 103 try (var lock = this.domain().lock()) { 104 // The mere act of getting a value (even of type String) can trigger symbol completion: 105 // https://github.com/openjdk/jdk/blob/jdk-25%2B3/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Constants.java#L49 106 yield this.delegate().equals(av instanceof AnnotationValueRecord avr ? avr.delegate() : av); 107 } 108 } 109 default -> false; 110 }; 111 } 112 113 @Override // AnnotationValue 114 @SuppressWarnings({ "try", "unchecked" }) 115 public final Object getValue() { 116 final Domain domain = this.domain(); 117 final Object value; 118 try (var lock = domain.lock()) { 119 // The mere act of getting a value (even of type String) can trigger symbol completion: 120 // https://github.com/openjdk/jdk/blob/jdk-25%2B3/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Constants.java#L49 121 value = this.delegate().getValue(); 122 } 123 return switch (value) { 124 case null -> throw new AssertionError(); 125 case AnnotationMirror a -> AnnotationRecord.of(a, domain); 126 case List<?> l -> of((List<? extends AnnotationValue>)l, domain); 127 case TypeMirror t -> UniversalType.of(t, domain); 128 case VariableElement e -> UniversalElement.of(e, domain); 129 default -> value; 130 }; 131 } 132 133 @Override // Object 134 public final int hashCode() { 135 return this.getValue().hashCode(); 136 } 137 138 @Override // AnnotationValue 139 @SuppressWarnings("try") 140 public final String toString() { 141 try (var lock = this.domain().lock()) { 142 // Can cause symbol completion and/or Name#toString() calls 143 return this.delegate().toString(); 144 } 145 } 146 147 148 /* 149 * Static methods. 150 */ 151 152 153 /** 154 * Returns a non-{@code null} {@link AnnotationValueRecord} that is either the supplied {@link AnnotationValue} (if it 155 * itself is {@code null} or is an {@link AnnotationValueRecord}) or one that wraps it. 156 * 157 * @param av an {@link AnnotationValue}; may be {@code null} 158 * 159 * @param domain a {@link Domain}; must not be {@code null} 160 * 161 * @return an {@link AnnotationValueRecord}, or {@code null} (if {@code av} is {@code null}) 162 * 163 * @exception NullPointerException if {@code domain} is {@code null} 164 * 165 * @see #AnnotationValueRecord(AnnotationValue, Domain) 166 */ 167 public static final AnnotationValueRecord of(final AnnotationValue av, final Domain domain) { 168 return switch (av) { 169 case null -> null; 170 case AnnotationValueRecord avr -> avr; 171 default -> new AnnotationValueRecord(av, domain); 172 }; 173 } 174 175 /** 176 * Returns a non-{@code null}, immutable {@link List} of {@link AnnotationValueRecord}s whose elements wrap the 177 * supplied {@link List}'s elements. 178 * 179 * @param avs a {@link Collection} of {@link AnnotationValue}s; must not be {@code null} 180 * 181 * @param domain a {@link Domain}; must not be {@code null} 182 * 183 * @return a non-{@code null}, immutable {@link List} of {@link AnnotationValueRecord}s 184 * 185 * @exception NullPointerException if either argument is {@code null} 186 */ 187 public static final List<? extends AnnotationValueRecord> of(final Collection<? extends AnnotationValue> avs, 188 final Domain domain) { 189 if (avs.isEmpty()) { 190 return List.of(); 191 } 192 final List<AnnotationValueRecord> newAvs = new ArrayList<>(avs.size()); 193 for (final AnnotationValue av : avs) { 194 newAvs.add(AnnotationValueRecord.of(av, domain)); 195 } 196 return Collections.unmodifiableList(newAvs); 197 } 198 199 200}