DefaultDataContextPlugin.java

  1. /* Contributed in the public domain.
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.compiler.plugin;

  18. import javax.annotation.processing.SupportedAnnotationTypes;
  19. import javax.annotation.processing.SupportedSourceVersion;
  20. import javax.lang.model.SourceVersion;
  21. import javax.lang.model.element.Element;
  22. import javax.tools.Diagnostic;
  23. import java.util.EnumSet;
  24. import java.util.Set;

  25. import com.sun.source.tree.CompilationUnitTree;
  26. import com.sun.source.tree.IdentifierTree;
  27. import com.sun.source.tree.MemberSelectTree;
  28. import com.sun.source.tree.NewClassTree;
  29. import com.sun.source.tree.Tree;
  30. import com.sun.source.util.JavacTask;
  31. import com.sun.source.util.Plugin;
  32. import com.sun.source.util.TaskEvent;
  33. import com.sun.source.util.TaskEvent.Kind;
  34. import com.sun.source.util.TaskListener;
  35. import com.sun.source.util.TreePath;
  36. import com.sun.source.util.TreeScanner;
  37. import com.sun.source.util.Trees;
  38. import org.orekit.annotation.DefaultDataContext;

  39. /**
  40.  * Processes {@link DefaultDataContext} to issue warnings at compile time.
  41.  *
  42.  * <p>To use this plugin add {@code -Xplugin:dataContextPlugin} to the javac command line.
  43.  * Tested with OpenJDK 8 and 11.
  44.  *
  45.  * <p>Do not reference this class unless executing within {@code javac} or you have added
  46.  * {@code tools.jar} to the class path. {@code tools.jar} is part of the JDK, not JRE, and
  47.  * is typically located at {@code JAVA_HOME/../lib/tools.jar}.
  48.  *
  49.  * @author Evan Ward
  50.  * @since 10.1
  51.  */
  52. @SupportedAnnotationTypes("org.orekit.annotation.DefaultDataContext")
  53. @SupportedSourceVersion(SourceVersion.RELEASE_8)
  54. public class DefaultDataContextPlugin implements Plugin, TaskListener {

  55.     /** Warning message. */
  56.     static final String MESSAGE = "Use of the default data context from a scope not " +
  57.             "annotated with @DefaultDataContext. This code may be unintentionally " +
  58.             "using the default data context.";
  59.     /** Annotation to search for. */
  60.     private static final Class<DefaultDataContext> ANNOTATION = DefaultDataContext.class;

  61.     /** Compiler Trees. */
  62.     private Trees trees;

  63.     /** Empty constructor.
  64.      * <p>
  65.      * This constructor is not strictly necessary, but it prevents spurious
  66.      * javadoc warnings with JDK 18 and later.
  67.      * </p>
  68.      * @since 12.0
  69.      */
  70.     public DefaultDataContextPlugin() {
  71.         // nothing to do
  72.     }

  73.     @Override
  74.     public String getName() {
  75.         return "dataContextPlugin";
  76.     }

  77.     @Override
  78.     public synchronized void init(final JavacTask javacTask, final String... args) {
  79.         javacTask.addTaskListener(this);
  80.         trees = Trees.instance(javacTask);
  81.     }

  82.     @Override
  83.     public void started(final TaskEvent taskEvent) {
  84.     }

  85.     @Override
  86.     public void finished(final TaskEvent taskEvent) {
  87.         if (taskEvent.getKind() == Kind.ANALYZE) {
  88.             final CompilationUnitTree root = taskEvent.getCompilationUnit();
  89.             root.accept(new AnnotationTreeScanner(root), null);
  90.         }
  91.     }

  92.     /** Finds when an annotation is used and checks the scope has the same annotation. */
  93.     private class AnnotationTreeScanner extends TreeScanner<Void, Void> {

  94.         /** Compilation root. */
  95.         private final CompilationUnitTree root;

  96.         /**
  97.          * Create a scanner.
  98.          *
  99.          * @param root of the compilation.
  100.          */
  101.         AnnotationTreeScanner(final CompilationUnitTree root) {
  102.             this.root = root;
  103.         }

  104.         @Override
  105.         public Void visitIdentifier(final IdentifierTree identifierTree,
  106.                                     final Void aVoid) {
  107.             check(identifierTree);
  108.             return super.visitIdentifier(identifierTree, aVoid);
  109.         }

  110.         @Override
  111.         public Void visitMemberSelect(final MemberSelectTree memberSelectTree,
  112.                                       final Void aVoid) {
  113.             check(memberSelectTree);
  114.             return super.visitMemberSelect(memberSelectTree, aVoid);
  115.         }

  116.         @Override
  117.         public Void visitNewClass(final NewClassTree newClassTree, final Void aVoid) {
  118.             check(newClassTree);
  119.             return super.visitNewClass(newClassTree, aVoid);
  120.         }

  121.         /**
  122.          * Check if this bit of code calls into the default data context from outside the
  123.          * default data context.
  124.          *
  125.          * @param tree to check.
  126.          */
  127.         private void check(final Tree tree) {
  128.             final Element element = trees.getElement(trees.getPath(root, tree));
  129.             check(tree, element);
  130.         }

  131.         /**
  132.          * Check tricky bits of code.
  133.          *
  134.          * @param tree    used to check the containing scope and for logging.
  135.          * @param element to check for {@link #ANNOTATION}.
  136.          */
  137.         private void check(final Tree tree, final Element element) {
  138.             // element and its containing scopes.
  139.             if (isAnyElementAnnotated(element)) {
  140.                 // using code annotated with @DefaultDataContext
  141.                 // check if current scope is also annotated
  142.                 if (!isAnyElementAnnotated(trees.getPath(root, tree))) {
  143.                     // calling the default data context from a method without an annotation
  144.                     final String message = MESSAGE + " Used: " + element.getKind() + " " + element;
  145.                     trees.printMessage(Diagnostic.Kind.WARNING, message, tree, root);
  146.                 }
  147.             }
  148.         }

  149.         /**
  150.          * Determine if any enclosing element has {@link #ANNOTATION}. Walks towards the
  151.          * tree root checking each node for the annotation.
  152.          *
  153.          * @param element to start the search from. May be {@code null}.
  154.          * @return {@code true} if {@code element} or any of its parents are annotated,
  155.          * {@code false} otherwise.
  156.          */
  157.         private boolean isAnyElementAnnotated(final Element element) {
  158.             Element e = element;
  159.             while (e != null) {
  160.                 if (e.getAnnotation(ANNOTATION) != null) {
  161.                     return true;
  162.                 }
  163.                 e = e.getEnclosingElement();
  164.             }
  165.             return false;
  166.         }

  167.         /**
  168.          * Determine if any enclosing tree has {@link #ANNOTATION}. Walks towards the tree
  169.          * root checking each node for the annotation.
  170.          *
  171.          * @param path to start the search from. May be {@code null}.
  172.          * @return {@code true} if {@code path} or any of its parents are annotated,
  173.          * {@code false} otherwise.
  174.          */
  175.         private boolean isAnyElementAnnotated(final TreePath path) {
  176.             // Kinds of declarations which can be annotated
  177.             final Set<Tree.Kind> toCheck = EnumSet.of(
  178.                     Tree.Kind.METHOD, Tree.Kind.CLASS, Tree.Kind.VARIABLE,
  179.                     Tree.Kind.INTERFACE, Tree.Kind.ENUM);
  180.             TreePath next = path;
  181.             while (next != null) {
  182.                 if (toCheck.contains(next.getLeaf().getKind())) {
  183.                     if (trees.getElement(next).getAnnotation(ANNOTATION) != null) {
  184.                         return true;
  185.                     }
  186.                 }
  187.                 next = next.getParentPath();
  188.             }
  189.             return false;
  190.         }

  191.     }

  192. }