001/* 002 * ============================================================================ 003 * Copyright © 2002-2023 by Thomas Thrien. 004 * All Rights Reserved. 005 * ============================================================================ 006 * Licensed to the public under the agreements of the GNU Lesser General Public 007 * License, version 3.0 (the "License"). You may obtain a copy of the License at 008 * 009 * http://www.gnu.org/licenses/lgpl.html 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 014 * License for the specific language governing permissions and limitations 015 * under the License. 016 */ 017 018package org.tquadrat.foundation.ap; 019 020import static java.lang.reflect.Proxy.isProxyClass; 021import static org.apiguardian.api.API.Status.STABLE; 022import static org.tquadrat.foundation.lang.CommonConstants.EMPTY_STRING; 023import static org.tquadrat.foundation.lang.DebugOutput.ifDebug; 024import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 025 026import javax.annotation.processing.Messager; 027import javax.annotation.processing.ProcessingEnvironment; 028import javax.lang.model.element.AnnotationMirror; 029import javax.lang.model.element.AnnotationValue; 030import javax.lang.model.element.Element; 031import javax.lang.model.element.ExecutableElement; 032import javax.lang.model.element.Name; 033import javax.lang.model.element.TypeElement; 034import javax.lang.model.type.TypeMirror; 035import javax.tools.Diagnostic; 036import java.lang.annotation.Annotation; 037import java.util.List; 038import java.util.NoSuchElementException; 039import java.util.Optional; 040import java.util.Set; 041import java.util.StringJoiner; 042 043import org.apiguardian.api.API; 044import org.tquadrat.foundation.annotation.ClassVersion; 045 046/** 047 * The specification for a set of helpers for annotation processing. 048 * 049 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 050 * @version $Id: APHelper.java 1061 2023-09-25 16:32:43Z tquadrat $ 051 * @since 0.1.0 052 * 053 * @UMLGraph.link 054 */ 055@ClassVersion( sourceVersion = "$Id: APHelper.java 1061 2023-09-25 16:32:43Z tquadrat $" ) 056@API( status = STABLE, since = "0.1.0" ) 057public interface APHelper extends Messager, ProcessingEnvironment 058{ 059 /*---------*\ 060 ====** Methods **========================================================== 061 \*---------*/ 062 /** 063 * <p>{@summary Returns the flag that indicates whether some debug 064 * information should be emitted to the generated code.}</p> 065 * <p>The value is controlled by the value of the annotation processor 066 * option 067 * {@value APBase#ADD_DEBUG_OUTPUT}.</p> 068 * 069 * @return {@code true} if the debug information should be added, 070 * {@code false} if not. 071 */ 072 @SuppressWarnings( "BooleanMethodNameMustStartWithQuestion" ) 073 public boolean addDebugOutput(); 074 075 /** 076 * Retrieves the 077 * {@link AnnotationMirror} 078 * for the given annotation 079 * from the given 080 * {@link Element} 081 * instance. Only declared annotations are considered, not the inherited 082 * ones. 083 * 084 * @param element The element. 085 * @param annotationClass The annotation to look for. 086 * @return An instance of 087 * {@link Optional} 088 * that holds the found {@code AnnotationMirror} instance. 089 * 090 * @see Element#getAnnotationMirrors() 091 */ 092 public default Optional<? extends AnnotationMirror> getAnnotationMirror( final Element element, final Class<? extends Annotation> annotationClass ) 093 { 094 final var isProxyClass = isProxyClass( requireNonNullArgument( annotationClass, "annotationClass" ) ); 095 ifDebug( a -> "annotationClass = %s".formatted( ((Class<?>) a [0]).getName() ), annotationClass ); 096 final String annotationClassName; 097 if( isProxyClass ) 098 { 099 final var interfaces = annotationClass.getInterfaces(); 100 ifDebug( interfaces.length > 1, currentInterfaces -> 101 { 102 final var joiner = new StringJoiner( ", ", "annotation interface: ", EMPTY_STRING ); 103 //noinspection SuspiciousArrayCast 104 for( final var currentInterface : ((Class<?> []) currentInterfaces) ) 105 { 106 joiner.add( currentInterface.getName() ); 107 } 108 return joiner.toString(); 109 }, (Object []) interfaces ); 110 if( interfaces.length != 1 ) 111 throw new AnnotationProcessingError( "annotationClass proxy '%s' implements more than one interface".formatted( annotationClass.getName() ) ); 112 annotationClassName = interfaces [0].getName(); 113 } 114 else 115 { 116 annotationClassName = requireNonNullArgument( annotationClass, "annotationClass" ).getName(); 117 } 118 ifDebug( "effective annotationClassName = %s"::formatted, annotationClassName ); 119 final var retValue = requireNonNullArgument( element, "element" ).getAnnotationMirrors().stream() 120 .filter( annotationMirror -> annotationMirror.getAnnotationType().toString().equals( annotationClassName ) ) 121 .findFirst(); 122 ifDebug( retValue.isEmpty(), e -> 123 { 124 final var joiner = new StringJoiner( ", ", "annotationMirrors: ", EMPTY_STRING ); 125 for( final var mirror : ((Element) e [0]).getAnnotationMirrors() ) 126 { 127 joiner.add( mirror.getAnnotationType().toString() ); 128 } 129 return joiner.toString(); 130 }, element ); 131 132 //---* Done *---------------------------------------------------------- 133 return retValue; 134 } // getAnnotationMirror() 135 136 /** 137 * Retrieves the annotation value from the given annotation mirror. 138 * 139 * @param annotationMirror The annotation mirror. 140 * @return An instance of 141 * {@link Optional} 142 * that holds the found annotation value. 143 */ 144 public default Optional<? extends AnnotationValue> getAnnotationValue( final AnnotationMirror annotationMirror ) 145 { 146 final var retValue = getAnnotationValue( annotationMirror, "value" ); 147 148 //---* Done *---------------------------------------------------------- 149 return retValue; 150 } // getAnnotationValue() 151 152 /** 153 * Retrieves the annotation value with the given name from the given 154 * annotation mirror. 155 * 156 * @param annotationMirror The annotation mirror. 157 * @param name The name of the desired value. 158 * @return An instance of 159 * {@link Optional} 160 * that holds the found annotation value. 161 */ 162 public default Optional<? extends AnnotationValue> getAnnotationValue( final AnnotationMirror annotationMirror, final String name ) 163 { 164 final var elementValues = getElementUtils().getElementValuesWithDefaults( annotationMirror ); 165 @SuppressWarnings( "unlikely-arg-type" ) 166 final var retValue = elementValues.keySet().stream() 167 .filter( executableElement -> executableElement.getSimpleName().toString().equals( name ) ) 168 .map( elementValues::get ) 169 .findAny(); 170 171 //---* Done *---------------------------------------------------------- 172 return retValue; 173 } // getAnnotationValue() 174 175 /** 176 * <p>{@summary Retrieves a 177 * {@link TypeMirror} 178 * instance from an annotation.}</p> 179 * <p>In case an annotation defines a 180 * {@link Class Class<?>} 181 * attribute, the value for that attribute is either {@code null} or 182 * something strange, but never an instance of {@code Class<?>}. So 183 * we need some special code to get something useful from the 184 * annotation.</p> 185 * <p>This implementations assumes the default name 186 * "{@code value}" for the attribute.</p> 187 * 188 * @param element The annotated element. 189 * @param annotationClass The type of the annotation. 190 * @return An instance of 191 * {@link Optional} 192 * that holds the {@code TypeMirror} 193 * instance. 194 * @throws NoSuchElementException The given element is not annotated with 195 * an annotation of the given type. 196 */ 197 public default Optional<TypeMirror> getTypeMirrorFromAnnotationValue( final Element element, final Class<? extends Annotation> annotationClass ) 198 { 199 final var annotationMirror = getAnnotationMirror( element, annotationClass ) 200 .orElseThrow( () -> new NoSuchElementException( annotationClass.getName() ) ); 201 202 final var retValue = getAnnotationValue( annotationMirror ) 203 .map( annotationValue -> (TypeMirror) annotationValue.getValue() ); 204 205 //---* Done *---------------------------------------------------------- 206 return retValue; 207 } // getTypeMirrorFromAnnotationValue() 208 209 /** 210 * <p>{@summary Retrieves a 211 * {@link TypeMirror} 212 * instance from an annotation.}</p> 213 * <p>In case an annotation defines a 214 * {@link Class Class<?>} 215 * attribute, the value for that attribute is either {@code null} or 216 * something strange, but never an instance of {@code Class<?>}. So we 217 * need some special code to get something useful from the annotation.</p> 218 * 219 * @param element The annotated element. 220 * @param annotationClass The type of the annotation. 221 * @param attributeName The name of the attribute that holds the class. 222 * @return An instance of 223 * {@link Optional} 224 * that holds the {@code TypeMirror} 225 * instance. 226 * @throws NoSuchElementException The given element is not annotated with 227 * an annotation of the given type. 228 */ 229 public default Optional<TypeMirror> getTypeMirrorFromAnnotationValue( final Element element, final Class<? extends Annotation> annotationClass, final String attributeName ) 230 { 231 final var annotationMirror = getAnnotationMirror( element, annotationClass ) 232 .orElseThrow( () -> new NoSuchElementException( annotationClass.getName() ) ); 233 234 final var retValue = getAnnotationValue( annotationMirror, attributeName ) 235 .map( annotationValue -> (TypeMirror) annotationValue.getValue() ); 236 237 //---* Done *---------------------------------------------------------- 238 return retValue; 239 } // getTypeMirrorFromAnnotationValue() 240 241 /** 242 * Checks whether the given element has the given annotation. 243 * 244 * @note If the retention for the given annotation is not 245 * {@link java.lang.annotation.RetentionPolicy#RUNTIME RUNTIME} 246 * it could have been removed from the element in an earlier compile 247 * run. 248 * 249 * @param <A> The type of the annotation. 250 * @param element The element to inspect. 251 * @param annotationType The type of the annotation to look for. 252 * @return {@code true} if the element is annotated with the given 253 * annotation, {@code false} if not. 254 * 255 * @see java.lang.annotation.RetentionPolicy#RUNTIME 256 */ 257 public default <A extends Annotation> boolean hasAnnotation( final Element element, final Class<A> annotationType ) 258 { 259 final var annotation = requireNonNullArgument( element, "element" ).getAnnotation( requireNonNullArgument( annotationType, "annotationType" ) ); 260 final var retValue = annotation != null; 261 262 //---* Done *---------------------------------------------------------- 263 return retValue; 264 } // hasAnnotation() 265 266 /** 267 * Determines whether the given instance of 268 * {@link TypeMirror} 269 * is for an 270 * {@link Enum} 271 * type. 272 * 273 * @param type The type to check. 274 * @return {@code true} if the given type is an {@code Enum} type, 275 * {@code false} otherwise. 276 */ 277 public boolean isEnumType( final TypeMirror type ); 278 279 /** 280 * {@inheritDoc} 281 */ 282 @Override 283 public default void printMessage( final Diagnostic.Kind kind, final CharSequence msg ) 284 { 285 getMessager().printMessage( kind, msg ); 286 } // printMessage() 287 288 /** 289 * {@inheritDoc} 290 */ 291 @Override 292 public default void printMessage( final Diagnostic.Kind kind, final CharSequence msg, final Element element ) 293 { 294 getMessager().printMessage( kind, msg, element ); 295 } // printMessage() 296 297 /** 298 * {@inheritDoc} 299 */ 300 @Override 301 public default void printMessage( final Diagnostic.Kind kind, final CharSequence msg, final Element element, final AnnotationMirror annotation ) 302 { 303 getMessager().printMessage( kind, msg, element, annotation ); 304 } // printMessage() 305 306 /** 307 * {@inheritDoc} 308 */ 309 @Override 310 public default void printMessage( final Diagnostic.Kind kind, final CharSequence msg, final Element element, final AnnotationMirror annotation, final AnnotationValue value ) 311 { 312 getMessager().printMessage( kind, msg, element, annotation, value ); 313 } // printMessage() 314 315 /** 316 * <p>{@summary Retrieves the names of a method's arguments.}</p> 317 * <p>This method will return the names of the arguments as defined only 318 * if the compiler flag {@code -parameters} is set; otherwise, the 319 * arguments are just counted ({@code arg0}, {@code arg1}, {@code arg2}, 320 * …).</p> 321 * 322 * @param method The method. 323 * @return The names of the arguments. 324 */ 325 public List<Name> retrieveArgumentNames( final ExecutableElement method ); 326 327 /** 328 * <p>{@summary Retrieves the generic types for the given type.}</p> 329 * 330 * @param type The 331 * {@link TypeMirror} instance to inspect. 332 * @return The type arguments the given type was defined with; will be the 333 * empty list if that type was not generic. 334 */ 335 public List<? extends TypeMirror> retrieveGenericTypes( final TypeMirror type ); 336 337 /** 338 * Retrieves the interfaces are that implemented or extended by the given 339 * type element. 340 * 341 * @param typeElement The type element to inspect. 342 * @param interfaces The already retrieved interfaces. 343 */ 344 public default void retrieveInterfaces( final TypeElement typeElement, final Set<? super TypeElement> interfaces ) 345 { 346 interfaces.add( typeElement ); 347 348 for( final var typeMirror : typeElement.getInterfaces() ) 349 { 350 final var nextInterface = (TypeElement) getTypeUtils().asElement( typeMirror ); 351 if( !interfaces.contains( nextInterface ) ) 352 { 353 retrieveInterfaces( nextInterface, interfaces ); 354 } 355 } 356 } // retrieveInterfaces() 357} 358// class APHelper 359 360/* 361 * End of File 362 */