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.String.format;
021import static java.lang.System.out;
022import static java.util.stream.Collectors.toSet;
023import static javax.tools.Diagnostic.Kind.ERROR;
024import static javax.tools.Diagnostic.Kind.NOTE;
025import static javax.tools.Diagnostic.Kind.WARNING;
026import static org.apiguardian.api.API.Status.STABLE;
027import static org.tquadrat.foundation.lang.DebugOutput.ifDebug;
028import static org.tquadrat.foundation.lang.Objects.isNull;
029import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
030import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument;
031
032import javax.annotation.processing.Completion;
033import javax.annotation.processing.Filer;
034import javax.annotation.processing.Messager;
035import javax.annotation.processing.ProcessingEnvironment;
036import javax.annotation.processing.Processor;
037import javax.annotation.processing.RoundEnvironment;
038import javax.annotation.processing.SupportedOptions;
039import javax.annotation.processing.SupportedSourceVersion;
040import javax.lang.model.SourceVersion;
041import javax.lang.model.element.AnnotationMirror;
042import javax.lang.model.element.AnnotationValue;
043import javax.lang.model.element.Element;
044import javax.lang.model.element.ElementKind;
045import javax.lang.model.element.ExecutableElement;
046import javax.lang.model.element.Name;
047import javax.lang.model.element.TypeElement;
048import javax.lang.model.element.VariableElement;
049import javax.lang.model.type.DeclaredType;
050import javax.lang.model.type.TypeMirror;
051import javax.lang.model.util.Elements;
052import javax.lang.model.util.SimpleTypeVisitor14;
053import javax.lang.model.util.Types;
054import javax.tools.Diagnostic;
055import java.lang.annotation.Annotation;
056import java.util.ArrayList;
057import java.util.Collection;
058import java.util.List;
059import java.util.Locale;
060import java.util.Map;
061import java.util.Optional;
062import java.util.Set;
063
064import org.apiguardian.api.API;
065import org.tquadrat.foundation.annotation.ClassVersion;
066
067/**
068 *  The abstract base class for annotation processors for the Foundation
069 *  Library.
070 *
071 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
072 *  @version $Id: APBase.java 1061 2023-09-25 16:32:43Z tquadrat $
073 *  @since 0.1.0
074 *
075 *  @UMLGraph.link
076 */
077@SuppressWarnings( "UseOfSystemOutOrSystemErr" )
078@ClassVersion( sourceVersion = "$Id: APBase.java 1061 2023-09-25 16:32:43Z tquadrat $" )
079@API( status = STABLE, since = "0.1.0" )
080public abstract class APBase implements Processor, APHelper
081{
082        /*-----------*\
083    ====** Constants **========================================================
084        \*-----------*/
085    /**
086     *  The name for sub-package that holds the generated configuration bean
087     *  classes: {@value}.
088     */
089    public static final String PACKAGE_NAME = "generated";
090
091    /**
092     *  The name of the annotation processor option that enables the emission
093     *  of some debug information into the generated code: {@value}.
094     */
095    public static final String ADD_DEBUG_OUTPUT = "org.tquadrat.foundation.ap.addDebugOutput";
096
097    /**
098     *  The name of the annotation processor option for the currently active
099     *  Maven goal: {@value}.
100     */
101    public static final String MAVEN_GOAL = "org.tquadrat.foundation.ap.maven.goal";
102
103    /**
104     *  Message: {@value}.
105     */
106    public static final String MSG_AnnotationOnlyForFields = "%s: Only fields may be annotated with '%s'";
107
108    /**
109     *  Message: {@value}.
110     */
111    public static final String MSG_FieldsOnly = "Only allowed for fields";
112
113    /**
114     *  The message that indicates the illegal use of an annotation: {@value}.
115     */
116    public static final String MSG_IllegalAnnotationUse = "%s: Illegal use of annotation '%s'";
117
118    /**
119     *  The message that indicates that more than one element is annotated with
120     *  the given annotation: {@value}.
121     */
122    public static final String MSG_MultipleElements = "%1$s: Multiple elements are annotated with '%2$s'";
123
124    /**
125     *  The message that indicates that more than one field is annotated with
126     *  the given annotation: {@value}.
127     */
128    public static final String MSG_MultipleFields = "%1$s: Multiple fields are annotated with '%2$s'";
129
130    /**
131     *  The message that indicates that a String constant is required:
132     *  {@value}.
133     */
134    public static final String MSG_StringConstantRequired = "'%s' needs to be a String constant";
135
136        /*------------*\
137    ====** Attributes **=======================================================
138        \*------------*/
139    /**
140     *  The flag that indicates whether some debug information should be
141     *  emitted to the generated code.
142     */
143    private boolean m_AddDebugOutput;
144
145    /**
146     *  The implementation of some utility methods for operating on elements.
147     */
148    private Elements m_ElementUtils;
149
150    /**
151     *  The processing environment.
152     */
153    private ProcessingEnvironment m_Environment;
154
155    /**
156     *  The filer that is used to create new source, class, or auxiliary files.
157     */
158    private Filer m_Filer;
159
160    /**
161     *  A flag that indicates whether Maven is active. This is guessed by the
162     *  existence of the option <code>{@value #MAVEN_GOAL}</code>.
163     */
164    private boolean m_IsMavenActive = false;
165
166    /**
167     *  The
168     *  {@link Messager}
169     *  that is used to report errors, warnings, and other notices.
170     */
171    private Messager m_Messager;
172
173    /**
174     *  The options for this annotation processor.
175     */
176    private Map<String,String> m_Options;
177
178    /**
179     *  The implementation of some utility methods for operating on types.
180     */
181    private Types m_TypeUtils;
182
183        /*--------------*\
184    ====** Constructors **=====================================================
185        \*--------------*/
186    /**
187     *  Creates a new {@code APBase} instance.
188     */
189    protected APBase() { /* Just exists */ }
190
191        /*---------*\
192    ====** Methods **==========================================================
193        \*---------*/
194    /**
195     *  {@inheritDoc}
196     */
197    @Override
198    public final boolean addDebugOutput() { return m_AddDebugOutput; }
199
200    /**
201     *  Adds the name of the annotation processor to the given message.
202     *
203     *  @param  msg The raw message.
204     *  @return The enhanced message.
205     */
206    private final String composeMessage( final CharSequence msg )
207    {
208        final var retValue = format( "%s: %s", getClass().getSimpleName(), msg );
209
210        //---* Done *----------------------------------------------------------
211        return retValue;
212    }   //  composeMessage()
213
214    /**
215     *  {@inheritDoc}
216     */
217    @Override
218    public final Iterable<? extends Completion> getCompletions( final Element element, final AnnotationMirror annotation, final ExecutableElement member, final String userText )
219    {
220        final List<? extends Completion> retValue = List.of();
221
222        //---* Done *----------------------------------------------------------
223        return retValue;
224    }   //  getCompletions()
225
226    /**
227     *  {@inheritDoc}
228     */
229    @Override
230    public final Elements getElementUtils() { return m_ElementUtils; }
231
232    /**
233     *  {@inheritDoc}
234     */
235    @Override
236    public final Filer getFiler() { return m_Filer; }
237
238    /**
239     *  {@inheritDoc}
240     */
241    @Override
242    public final Locale getLocale() { return m_Environment.getLocale(); }
243
244    /**
245     *  {@inheritDoc}
246     */
247    @Override
248    public final Messager getMessager() { return this; }
249
250    /**
251     *  Retrieves the option with the given name from the annotation processors
252     *  startup options.
253     *
254     *  @param  option  The name of the option.
255     *  @return An instance of
256     *      {@link Optional}
257     *      that holds the option value.
258     */
259    protected final Optional<String> getOption( final String option )
260    {
261        final var retValue = Optional.ofNullable( m_Options.get( requireNotEmptyArgument( option, "option" ) ) );
262
263        //---* Done *----------------------------------------------------------
264        return retValue;
265    }   //  getOption()
266
267    /**
268     *  {@inheritDoc}
269     */
270    @Override
271    public final Map<String,String> getOptions() { return Map.copyOf( m_Options ); }
272
273    /**
274     *  {@inheritDoc}
275     */
276    @Override
277    public final SourceVersion getSourceVersion() { return m_Environment.getSourceVersion(); }
278
279    /**
280     *  Returns the classes for the supported annotations.
281     *
282     *  @return The supported annotations.
283     */
284    protected abstract Collection<Class<? extends Annotation>> getSupportedAnnotationClasses();
285
286    /**
287     *  {@inheritDoc}
288     */
289    @Override
290    public final Set<String> getSupportedAnnotationTypes()
291    {
292        final var retValue = getSupportedAnnotationClasses()
293            .stream()
294            .map( Class::getName )
295            .collect( toSet() );
296
297        //---* Done *----------------------------------------------------------
298        return retValue;
299    }   //  getSupportedAnnotationTypes()
300
301    /**
302     *  {@inheritDoc}
303     */
304    @Override
305    public final Set<String> getSupportedOptions()
306    {
307        final var annotation = getClass().getAnnotation( SupportedOptions.class );
308        final Set<String> retValue = isNull( annotation ) ? Set.of() : Set.of( annotation.value() );
309
310        //---* Done *----------------------------------------------------------
311        return retValue;
312    }   //  getSupportedOptions()
313
314    /**
315     *  {@inheritDoc}
316     */
317    @Override
318    public final SourceVersion getSupportedSourceVersion()
319    {
320        final var annotation = getClass().getAnnotation( SupportedSourceVersion.class );
321        final SourceVersion retValue;
322        if( isNull( annotation ) )
323        {
324            retValue = SourceVersion.RELEASE_15;
325            printMessage( WARNING, "No SupportedSourceVersion annotation found on '%s', returning '%s'.".formatted( getClass().getName(), retValue.toString() ) );
326        }
327        else
328        {
329            retValue = annotation.value();
330        }
331
332        //---* Done *----------------------------------------------------------
333        return retValue;
334    }   //  getSupportedSourceVersion()
335
336    /**
337     *  {@inheritDoc}
338     */
339    @Override
340    public final Types getTypeUtils() { return m_TypeUtils; }
341
342    /**
343     *  {@inheritDoc}
344     */
345    @Override
346    public final boolean isEnumType( final TypeMirror type )
347    {
348        final var enumType = m_TypeUtils.erasure( m_ElementUtils.getTypeElement( Enum.class.getName() ).asType() );
349        final var focusType = m_TypeUtils.erasure( requireNonNullArgument( type, "type" ) );
350        final var retValue = m_TypeUtils.isSubtype( focusType, enumType );
351        ifDebug( "type: %1$s%n\tenumType: %2$s%n\tfocusType: %3$s%n\tisEnumType: %4$b"::formatted, type, enumType, focusType, retValue );
352
353        //---* Done *----------------------------------------------------------
354        return retValue;
355    }   //  isEnumType()
356
357    /**
358     *  {@inheritDoc}
359     */
360    @Override
361    public final void init( final ProcessingEnvironment processingEnv )
362    {
363        //---* Keep the processing environment *-------------------------------
364        m_Environment = processingEnv;
365        m_Messager = m_Environment.getMessager();
366        m_Filer = m_Environment.getFiler();
367        m_ElementUtils = m_Environment.getElementUtils();
368        m_TypeUtils = m_Environment.getTypeUtils();
369        m_Options = m_Environment.getOptions();
370
371        m_IsMavenActive = m_Options.containsKey( MAVEN_GOAL );
372        m_AddDebugOutput = m_Options.containsKey( ADD_DEBUG_OUTPUT );
373
374        printMessage( NOTE, "Annotation Processor invoked!" );
375        printMessage( NOTE, "Class: %s".formatted( getClass().getName() ) );
376        for( final var option : m_Options.entrySet() )
377        {
378            printMessage( NOTE, "Option: %s=%s".formatted( option.getKey(), option.getValue() ) );
379        }
380    }   //  init()
381
382    /**
383     *  {@inheritDoc}
384     */
385    @Override
386    public final void printMessage( final Diagnostic.Kind kind, final CharSequence msg )
387    {
388        final var message = composeMessage( msg );
389        m_Messager.printMessage( kind, message );
390        if( m_IsMavenActive )
391        {
392            out.printf( "[%s] %s%n", kind.name(), message );
393        }
394    }   //  printMessage()
395
396    /**
397     *  {@inheritDoc}
398     */
399    @Override
400    public final void printMessage( final Diagnostic.Kind kind, final CharSequence msg, final Element element )
401    {
402        final var message = composeMessage( msg );
403        m_Messager.printMessage( kind, message, element );
404        if( m_IsMavenActive )
405        {
406            out.printf( "[%s] %s (Element: %s)%n", kind.name(), message, element.getSimpleName() );
407        }
408    }   //  printMessage()
409
410    /**
411     *  {@inheritDoc}
412     */
413    @Override
414    public final void printMessage( final Diagnostic.Kind kind, final CharSequence msg, final Element element, final AnnotationMirror annotation )
415    {
416        final var message = composeMessage( msg );
417        m_Messager.printMessage( kind, message, element, annotation );
418        if( m_IsMavenActive )
419        {
420            out.printf( "[%s] %s (Element: %s/Annotation: %s)%n", kind.name(), message, element.getSimpleName(), annotation.getAnnotationType().asElement().getSimpleName() );
421        }
422    }   //  printMessage()
423
424    /**
425     *  {@inheritDoc}
426     */
427    @Override
428    public final void printMessage( final Diagnostic.Kind kind, final CharSequence msg, final Element element, final AnnotationMirror annotation, final AnnotationValue value )
429    {
430        final var message = composeMessage( msg );
431        m_Messager.printMessage( kind, message, element, annotation, value );
432        if( m_IsMavenActive )
433        {
434            out.printf( "[%s] %s (Element: %s/Annotation: %s = %s)%n", kind.name(), message, element.getSimpleName(), annotation.getAnnotationType().asElement().getSimpleName(), value.toString() );
435        }
436    }   //  printMessage()
437
438    /**
439     *  {@inheritDoc}
440     */
441    @Override
442    public abstract boolean process( final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnvironment );
443
444    /**
445     *  <p>{@summary Retrieves the field that is annotated with the given
446     *  annotation.} The expectation is that there is just one
447     *
448     *  @param  roundEnvironment    The environment for information about the
449     *      current and prior round as set to
450     *      {@link #process(Set, RoundEnvironment)}.
451     *  @param  annotationClass The class of the annotation.
452     *  @return An instance of
453     *      {@link Optional}
454     *      that holds the field.
455     *  @throws IllegalAnnotationError  The annotation is invalid.
456     *  @throws CodeGenerationError Multiple fields are annotated with the
457     *      given annotation.
458     */
459    protected final Optional<VariableElement> retrieveAnnotatedField( final RoundEnvironment roundEnvironment, final Class<? extends Annotation> annotationClass ) throws IllegalAnnotationError
460    {
461        requireNonNullArgument( annotationClass, "annotationClass" );
462        ifDebug( currentClass -> "annotationClass: %s".formatted( ((Class<?>) currentClass [0]).getName() ), annotationClass );
463        Optional<VariableElement> retValue = Optional.empty();
464        String errorMessage = null;
465
466        ifDebug( currentClass -> "annotationClass '%s' is in annotations".formatted( ((Class<?>) currentClass [0]).getName() ), annotationClass );
467        VariableElement lastElement = null;
468        var isInError = false;
469        ScanLoop: for( final var element : requireNonNullArgument( roundEnvironment, "roundEnvironment" ).getElementsAnnotatedWith( annotationClass ) )
470        {
471            if( element instanceof final VariableElement variableElement )
472            {
473                if( variableElement.getKind() == ElementKind.FIELD )
474                {
475                    if( isNull( lastElement ) )
476                    {
477                        lastElement = variableElement;
478                        final var value = variableElement.getConstantValue();
479                        if( value instanceof String )
480                        {
481                            retValue = Optional.of( variableElement );
482                        }
483                        else
484                        {
485                            isInError = true;
486                            errorMessage = format( MSG_StringConstantRequired, element.getSimpleName().toString() );
487                            printMessage( ERROR, errorMessage, element );
488                            break ScanLoop;
489                        }
490                    }
491                    else
492                    {
493                        if( !isInError )
494                        {
495                            isInError = true;
496                            errorMessage = format( MSG_MultipleFields, variableElement.getSimpleName().toString(), annotationClass.getSimpleName() );
497                            printMessage( ERROR, errorMessage, lastElement );
498                        }
499
500                        //---* Print the other elements, too *-----------------
501                        printMessage( ERROR, format( MSG_MultipleFields, variableElement.getSimpleName().toString(), annotationClass.getSimpleName() ), element );
502                    }
503                }
504                else
505                {
506                    printMessage( ERROR, format( MSG_AnnotationOnlyForFields, variableElement.getSimpleName().toString(), annotationClass.getSimpleName() ), element );
507                    throw new IllegalAnnotationError( MSG_FieldsOnly, annotationClass );
508                }
509            }
510            else
511            {
512                errorMessage = format( MSG_IllegalAnnotationUse, element.getSimpleName().toString(), annotationClass.getSimpleName() );
513                printMessage( ERROR, errorMessage, element );
514                throw new IllegalAnnotationError( errorMessage );
515            }
516        }   //  ScanLoop:
517        if( isInError ) throw new CodeGenerationError( errorMessage );
518
519        //---* Done *----------------------------------------------------------
520        return retValue;
521    }   //  retrieveAnnotatedField()
522
523    /**
524     *  {@inheritDoc}
525     */
526    @Override
527    public final List<Name> retrieveArgumentNames( final ExecutableElement method )
528    {
529        final var retValue = requireNonNullArgument( method, "method" ).getParameters()
530            .stream()
531            .map( VariableElement::getSimpleName )
532            .toList();
533
534        //---* Done *----------------------------------------------------------
535        return retValue;
536    }   //  retrieveArgumentNames()
537
538    /**
539     *  {@inheritDoc}
540     */
541    @SuppressWarnings( {"AnonymousInnerClassMayBeStatic", "AnonymousInnerClass"} )
542    @Override
543    public List<TypeMirror> retrieveGenericTypes( final TypeMirror type )
544    {
545        final Collection<TypeMirror> buffer = new ArrayList<>();
546
547        //noinspection CollectionAddAllCanBeReplacedWithConstructor
548        buffer.addAll( type.accept( new SimpleTypeVisitor14<Collection<TypeMirror>, Void>( List.of() )
549        {
550            /**
551             *  {@inheritDoc}
552             */
553            @SuppressWarnings( "unchecked" )
554            @Override
555            public final Collection<TypeMirror> visitDeclared( final DeclaredType declaredType, final Void ignored )
556            {
557                return (Collection<TypeMirror>) declaredType.getTypeArguments();
558            }
559        }, null ) );
560
561        //---* Create the return value *---------------------------------------
562        final var retValue = List.copyOf( buffer );
563
564        //---* Done *----------------------------------------------------------
565        return retValue;
566    }   //  retrieveGenericTypes()
567
568    /**
569     *  {@inheritDoc}
570     */
571    @Override
572    public final void retrieveInterfaces( final TypeElement typeElement, final Set<? super TypeElement> interfaces )
573    {
574        interfaces.add( typeElement );
575
576        for( final var typeMirror : typeElement.getInterfaces() )
577        {
578            final var nextInterface = (TypeElement) m_TypeUtils.asElement( typeMirror );
579            if( !interfaces.contains( nextInterface ) )
580            {
581                retrieveInterfaces( nextInterface, interfaces );
582            }
583        }
584    }   //  retrieveInterfaces()
585}
586//  class APBase
587
588/*
589 *  End of File
590 */