Class SFProcessor

  • All Implemented Interfaces:
    javax.annotation.processing.Processor

    public class SFProcessor
    extends javax.annotation.processing.AbstractProcessor
    Annotation Processor for the StaticFunctional annotation.

    Processes the @StaticFunctional Annotation so that javac will properly handle it.

    IMPORTANT: In order for the annotation to be processed properly by the javac (compiler) tool, this class must be specified either on the command-line at compile time using the '-processor' CLI-Switch, or in the Java HTML Jar Library 'META-INF/' directory inside the file named:

    META-INF/services/javax.annotation.processing


    • Field Summary

      • Fields inherited from class javax.annotation.processing.AbstractProcessor

        processingEnv
    • Constructor Summary

      Constructors 
      Constructor
      SFProcessor()
    • Method Summary

       
      Methods: interface javax.annotation.processing.Processor
      Modifier and Type Method
      Set<String> getSupportedAnnotationTypes()
      javax.lang.model.SourceVersion getSupportedSourceVersion()
      void init​(javax.annotation.processing.ProcessingEnvironment env)
      boolean process​(Set<? extends javax.lang.model.element.TypeElement> set, javax.annotation.processing.RoundEnvironment roundEnv)
      • Methods inherited from class javax.annotation.processing.AbstractProcessor

        getCompletions, getSupportedOptions, isInitialized
      • Methods inherited from class java.lang.Object

        clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    • Method Detail

      • init

        🡅  🡇    
        public void init​(javax.annotation.processing.ProcessingEnvironment env)
        Method that is requuired by the 'AbstractProcessor' interface that allows this processor to initialize & receive the necessary environment variables.
        Specified by:
        init in interface javax.annotation.processing.Processor
        Overrides:
        init in class javax.annotation.processing.AbstractProcessor
        Parameters:
        env - This provides the instance-reference to the messaging-output tool.
        Code:
        Exact Method Body:
         this.messager = env.getMessager();
        
      • getSupportedAnnotationTypes

        🡅  🡇    
        public java.util.Set<java.lang.String> getSupportedAnnotationTypes()
        Method required by AbstractProcessor
        Specified by:
        getSupportedAnnotationTypes in interface javax.annotation.processing.Processor
        Overrides:
        getSupportedAnnotationTypes in class javax.annotation.processing.AbstractProcessor
        Returns:
        The set of annotations supported by this processor. The Set that is returned contains just the name of the StaticFunctional class-name.
        Code:
        Exact Method Body:
         TreeSet<String> ret = new TreeSet<>();
         ret.add(StaticFunctional.class.getCanonicalName());
         return ret;
        
      • getSupportedSourceVersion

        🡅  🡇    
        public javax.lang.model.SourceVersion getSupportedSourceVersion()
        Method required by AbstractProcessor
        Specified by:
        getSupportedSourceVersion in interface javax.annotation.processing.Processor
        Overrides:
        getSupportedSourceVersion in class javax.annotation.processing.AbstractProcessor
        Returns:
        the Java version supported
        Code:
        Exact Method Body:
         return SourceVersion.latestSupported();
        
      • process

        🡅    
        public boolean process​
                    (java.util.Set<? extends javax.lang.model.element.TypeElement> set,
                     javax.annotation.processing.RoundEnvironment roundEnv)
        
        This implements the processing for the Annotation @StaticFunctional. It does a lot of validity checks on the parameter input.
        Specified by:
        process in interface javax.annotation.processing.Processor
        Specified by:
        process in class javax.annotation.processing.AbstractProcessor
        Code:
        Exact Method Body:
         boolean errors = false;
        
         // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
         // The main / outer for loop iterates through all ".java" files having @StaticFunctional
         // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
         //
         // Though it is of course possible to annotate things other than "types" (a.k.a. an 
         // '.java' file, or inner-class), @StaticFunctional is declared with the
         // "Annotation for Annotations" @Target, setting its value to "TYPE"
        
         for (Element ciet : roundEnv.getElementsAnnotatedWith(StaticFunctional.class))
         {
             ElementKind k = ciet.getKind();
        
             if ((k != ElementKind.CLASS) && (k != ElementKind.INTERFACE))
             {
                 messager.printMessage(
                     Diagnostic.Kind.ERROR, LOCATION(ciet) +
                     "\tOnly classes and interfaces can be annotated with @" +
                     StaticFunctional.class.getSimpleName() + '\n' +
                     ciet.toString() + " is neither of these, it is of kind: " + k.toString()
                 );
        
                 errors = true;
                 continue;
             }
        
             Vector<String> excused = new Vector<>();
             Vector<Excuse> excuses = new Vector<>();
        
             Counter errorCount = new Counter();
        
        
             // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
             // First for-loop iterates all @StaticFunctional Annotations placed on **ONE** Type
             // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
             //
             // It is possible for mutiple annotations to be placed on the same CIET/Type/Class
             // The "Annotation for Annotations" (@Repeatable) does that.  In any case, the method
             // "GetAnnotationMirrors" returns all of the "Mirrors" (an object that hold precixely
             // the information that the user typed in his '.java' file for @StaticFunctional) that
             // were placed or "decorated" onto the class/ciet/type (".java" file).
        
             for (AnnotationMirror am : ciet.getAnnotationMirrors())
        
                 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
                 // This class only handles the @StaticFunctional Annotation
                 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
                 //
                 // This test is likely superfluous, because this method would not be invoked
                 // for any other Annotaiton.  Leave it here anyway (This first 'if' statement).
                 //
                 // NOTE: Below, the only error-checking done is to make sure that the user has only
                 //       provided Annotation-Elements named "Excued" or "Excuses".  Any others are
                 //       flagged as an error.
                 //
                 // The values passed to these two (arrays) are saved in the two Vector's that were
                 // created above (Vector<String> excuses & Vector<Excuse> excused).  Not much else
                 // is done in this beginning for-loop.
        
                 if (am.getAnnotationType().toString().equals
                     ("Torello.HTML.Tools.JavaDoc.StaticFunctional"))
        
                     am.getElementValues().forEach((ExecutableElement ee, AnnotationValue av) ->
                     {
                         // AND HERE-IN LIES THE TRUE-MADNESS.
                         // "av.getValue()" returns an instance of: com.sun.tools.javac.util.List
                         // And I have never heard of such a thing...
        
                         if (ee.toString().equals("Excused()"))
                         {
                             // Java Assertion.  This Really **SHOULD** Hold, but just in case....
                             if (! Iterable.class.isAssignableFrom(av.getValue().getClass()))
                                 throw new InternalError(
                                     "Excused() Annotation-Element-Value Type is not Iterable, " +
                                     '[' + av.getValue().getClass().getName() + ']'
                                 );
                             else
                                 for (Object fieldName : (Iterable) av.getValue())
                                     // The 'toString()' actually does include the quotation-marks
                                     excused.add
                                         (StringParse.ifQuotesStripQuotes(fieldName.toString()));
        
        
                             // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
                             // Back in the day, a Reg-Ex was used...
                             // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
                             //
                             // Matcher m = EXCUSED.matcher(av.toString());
                             // while (m.find())
                             //      excused.add(StringParse.ifQuotesStripQuotes(m.group(1)));
                         }
        
                         else if (ee.toString().equals("Excuses()"))
                         {
                             // Java Assertion.  This Really **SHOULD** Hold, but just in case....
                             if (! Iterable.class.isAssignableFrom(av.getValue().getClass()))
                                 throw new InternalError(
                                     "Excuses() Annotation-Element-Value Type is not Iterable, " +
                                     '[' + av.getValue().getClass().getName() + ']'
                                 );
        
                             else
                                 // The nature of the excuse is not used during compile-time, and
                                 // therefore, retrieving the enum-constant is completely irrelevant
                                 // The 'excuses' Vector is completely ignored except for the call
                                 // to 'excuses.size()'  If this ever changes, this for-loop would
                                 // be useful.  Since this is extremely-low overhead... it doesn't
                                 // matter.
                                 //
                                 // NOTE: The '?' "type" of the Iterable<?> is:
                                 //       Iterable<com.sun.tools.javac.code.Attribute$Enum>
                                 //
                                 // This seems a little preposterous on the part of Java/Sun/Oracle
                                 // Which is why this for loop isn't being replaced by "av.size()"
                                 // or something like that.
        
                                 for (Object excuseName : (Iterable) av.getValue())
                                 {
                                     // FINAL NOTE OF THE TRUE MADNESS:  When compiling with Java 11
                                     // the "excuseName.toString" returns the fully-qualified enum
                                     // constant name (Torello.HTML.Tools.JavaDoc.Excuse.LOGGING),
                                     // while in Java 17, the String returned is just "LOGGING"
                                     // (To use the "LOGGING" excuse as an example)
        
                                     String  tempStr = excuseName.toString();
                                     int     dotPos  = tempStr.lastIndexOf('.');
        
                                     if (dotPos != -1) tempStr = tempStr.substring(dotPos + 1);
        
                                     // System.out.println("excuseName: " + tempStr);
                                     excuses.add(Excuse.valueOf(tempStr));
                                 }
        
        
                             // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
                             // Back in the day, a Reg-Ex was used...
                             // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
                             //
                             // System.out.println("Excuses: " + av.toString());
                             // Matcher m = EXCUSES.matcher(av.toString());
                             // while (m.find()) excuses.add(Excuse.valueOf(m.group(1)));
                         }
        
        
                         // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
                         // Unreachable Code.  javac will not compile extranneous Annotation-Elems
                         // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
                         //
                         // Since this stuff sort of sucks, leave it here for "for your information"
                         // If you annotate something like:
                         //
                         // @StaticFunctional(Excused="myField1", Excuses=Excuse.CONFIGURATION,
                         //      MadeUpElem=true)
                         //
                         // JavaC will not send it here, it will print an error message that the 
                         // symbol-method "MadeUpElem" is not found.
        
                         else
                         {
                             messager.printMessage(
                                 Diagnostic.Kind.ERROR, LOCATION(ciet) +
                                 "\tThere was an annotation parameter whose name wasn't " +
                                     "recognized: " + ee.toString() + "\n" +
                                 "\tThe only parameter's that may be passed to @StaticFunctional " +
                                     "are 'Excuses' and 'Excused'\n"
                             );
        
                             errorCount.addOne();
                         }
                     });
        
             if (errorCount.size() > 0) errors = true;
        
        
             // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
             // Do the "Parallel Array" Check, first.
             // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        
             if (excuses.size() != excused.size()) messager.printMessage(
                 Diagnostic.Kind.ERROR, LOCATION(ciet) +
                 "\tYou have passed " + excused.size() + " excused field name" +
                 ((excused.size() == 1) ? "" : "s") + ", " +
                 "and " + excuses.size() + " explanation" +
                 ((excuses.size() == 1) ? "" : "s") + " for " +
                 ((excused.size() == 1) ? "that field" : "those fields") + ".\n" +
                 "\tThe two optional array-parameters to the @StaticFunctional annotation must " +
                 "be parallel-arrays, and when used, their lengths must be equal."
             );
        
        
             // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
             // Check, first, whether or not "Excused Field Names" are all valid Java Identifiers
             // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
             //
             // This whole iterator-thing is needed to prevent multiple error messages about the
             // same mistaken field-name.  If a name is not a valid Java-Identifier, then an error
             // message is printed here.  There should be no reason to print a second error message
             // later mentioning that that field name could not be found...
             //
             // Therefore, when not a valid Java-Identifier, the name of the Field should just be
             // removed, immediately, right here.  (The Iterator.remove() method removes it from
             // the underlying Vector<String>).
        
             Iterator<String> excusedIter = excused.iterator();
        
             while (excusedIter.hasNext())
        
                 if (! checkJavaIdentifier(ciet, excusedIter.next()))
                 {
                     errors = true;
                     excusedIter.remove();
                 }
        
        
             // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
             // NOW: Iterate all the "Entities" (which Java calls "Elements") inside this CIET/Type
             // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
             //
             // At this point, there may have already been errors.  The field names could be invalid
             // Java Identifiers and the arrays might not have been parallel.  However, there isn't
             // a reason to stop to printing more error messages.  Getting as many of them out as
             // possible is faster for the programmer.
             //
             // IMPORTANT: Any "additional" information passed to @StaticFunctional through the
             //            "Annotation-Elements" (Excused & Excuses), **CAN ONLY BE** for **FIELDS**
             //
             // This loop checks constructors, methods, and fields, and there for that reason, this
             // should continue rather than quitting now - only to have a developer find finding
             // more errors the next time he tries to compile.
        
             int constructorCount = 0;
        
             for (Element e : ciet.getEnclosedElements())
             {
                 Set<Modifier> modifiers = e.getModifiers();
        
                 switch (e.getKind())
                 {
                     // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
                     // Inspects a Method, and makes sure it has been declared static
                     // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        
                     case METHOD :
        
                         if (! modifiers.contains(Modifier.STATIC))
                             errors |= modifierError(ElementKind.METHOD, Modifier.STATIC, ciet, e);
        
                         break;
        
        
                     // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
                     // Inspects a field, and ensure that it is both static & final -- or excused.
                     // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        
                     case FIELD :
        
                         boolean isExcused = excused.contains(e.getSimpleName().toString());
        
                         // NOTE: non-static fields **CANNOT** be excused, only non-final fields
                         if (! modifiers.contains(Modifier.STATIC))
                             errors |= modifierError(ElementKind.FIELD, Modifier.STATIC, ciet, e);
        
                         if (! modifiers.contains(Modifier.FINAL))
                         {
                             if (! isExcused)
                                 errors |= fieldIsNotFinalButIsAlsoNotExcused(ciet, e);
                         }
        
                         else if (isExcused) messager.printMessage(
                             Diagnostic.Kind.WARNING, LOCATION(ciet) +
                             "\tField [" + e.getSimpleName().toString() + "] is declared " +
                             "excused; but this field is declared final, and therefore does not " +
                             "need to be excused."
                         );
        
                         if (isExcused) excused.remove(e.getSimpleName().toString());
        
                         break;
        
        
                     // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
                     // Checks a constructor, and make sure it has zero-arguments, and is private
                     // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        
                     case CONSTRUCTOR :
        
                         constructorCount++;
        
                         // NOTE: The static inner-class / nested-type "ZeeroArgConstructor" has a
                         //       big explanation for why it is needed, rather than doing the check
                         //       right here.
                         //
                         // The check requires counting the number of parameters to the constructor,
                         // but that information is completely-unavailable without the visitor.
        
                         if (e.accept(new ZeroArgContructor(messager, ciet), null))
                             errors = true;
        
                         // This 'if-branch' can only execute if this particular constructor has
                         // zero-args, but has not been declared private.  This is important for the
                         // "sinister" reason that the Java Auto-Generated Zero-Arg Constructor is
                         // never actually something that the programmer typed into his '.java' file
                         //
                         // The Error Messages reminds them of this tidbit of information...
                         //
                         // NOTE:  The order here is important to prevent multiple errors about the
                         //        same constructor.
        
                         else if (! modifiers.contains(Modifier.PRIVATE))
                             errors |= modifierError
                                 (ElementKind.CONSTRUCTOR, Modifier.PRIVATE, ciet, e);
        
                         break;
        
                     default: break;
        
                 }   // switch-statement: of the "Elements" inside this CIET/Type
                     // which in JavaDoc-Upgrader-Speak are unfortunately called "Entities" instead
        
             } // For-Loop: Iterating the "Elements" of this CIET/Type
        
        
             // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
             // This make sure there aren't any 'extranneous' excused field-names
             // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
             //
             // NOTE: In the loop above, whenever an "Element" (Java-Doc-Upgrader-Speak "Entities")
             //       is found by the switch-statement, above, it is removed from the Vector<String>
             //       that lists all of the "Excused" Fields (which aren't required to be 'final')
        
             if (excused.size() > 0) messager.printMessage(
                 Diagnostic.Kind.ERROR, LOCATION(ciet) +
                 "\tThere were fields listed as excused and non-final, but those fields were " +
                 "not actually members of the " + ciet.getKind().toString().toLowerCase() + "." +
                 "\n\tUn-Resolved Excused Feild-Names: " +
                 StrCSV.toCSV(excused, s -> "[" + s + "]", false, null)
             );
        
             errors |= (excused.size() > 0);
        
        
             // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
             // Make sure there is only 1 constructor. 
             // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
             //
             // This test is a little superfluous, otherwise one of the constructors would not have
             // had zero-arguments, and therefore would have been flagged (and mentioned) in the 
             // previous loop that iterates all of the CIET/Type "Elements" ("Entities").  It 
             // is a little friendly reminder.  No more no less.
        
             if (constructorCount > 1)
             {
                 messager.printMessage(
                     Diagnostic.Kind.ERROR, LOCATION(ciet) +
                     "\tThere are " + constructorCount + " constructors.  " +
                     "There must be precisely one private, zero-argument, constructor."
                 );
        
                 errors = true;
             }
        
         } // for-loop iterating all of the CIET/Types annotated with @StaticFunction
        
         return errors;