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  
19  import javax.annotation.processing.SupportedAnnotationTypes;
20  import javax.annotation.processing.SupportedSourceVersion;
21  import javax.lang.model.SourceVersion;
22  import javax.lang.model.element.Element;
23  import javax.tools.Diagnostic;
24  import java.util.EnumSet;
25  import java.util.Set;
26  
27  import com.sun.source.tree.CompilationUnitTree;
28  import com.sun.source.tree.IdentifierTree;
29  import com.sun.source.tree.MemberSelectTree;
30  import com.sun.source.tree.NewClassTree;
31  import com.sun.source.tree.Tree;
32  import com.sun.source.util.JavacTask;
33  import com.sun.source.util.Plugin;
34  import com.sun.source.util.TaskEvent;
35  import com.sun.source.util.TaskEvent.Kind;
36  import com.sun.source.util.TaskListener;
37  import com.sun.source.util.TreePath;
38  import com.sun.source.util.TreeScanner;
39  import com.sun.source.util.Trees;
40  import org.orekit.annotation.DefaultDataContext;
41  
42  /**
43   * Processes {@link DefaultDataContext} to issue warnings at compile time.
44   *
45   * <p>To use this plugin add {@code -Xplugin:dataContextPlugin} to the javac command line.
46   * Tested with OpenJDK 8 and 11.
47   *
48   * <p>Do not reference this class unless executing within {@code javac} or you have added
49   * {@code tools.jar} to the class path. {@code tools.jar} is part of the JDK, not JRE, and
50   * is typically located at {@code JAVA_HOME/../lib/tools.jar}.
51   *
52   * @author Evan Ward
53   * @since 10.1
54   */
55  @SupportedAnnotationTypes("org.orekit.annotation.DefaultDataContext")
56  @SupportedSourceVersion(SourceVersion.RELEASE_8)
57  public class DefaultDataContextPlugin implements Plugin, TaskListener {
58  
59      /** Warning message. */
60      static final String MESSAGE = "Use of the default data context from a scope not " +
61              "annotated with @DefaultDataContext. This code may be unintentionally " +
62              "using the default data context.";
63      /** Annotation to search for. */
64      private static final Class<DefaultDataContext> ANNOTATION = DefaultDataContext.class;
65  
66      /** Compiler Trees. */
67      private Trees trees;
68  
69      /** Empty constructor.
70       * <p>
71       * This constructor is not strictly necessary, but it prevents spurious
72       * javadoc warnings with JDK 18 and later.
73       * </p>
74       * @since 12.0
75       */
76      public DefaultDataContextPlugin() {
77          // nothing to do
78      }
79  
80      @Override
81      public String getName() {
82          return "dataContextPlugin";
83      }
84  
85      @Override
86      public synchronized void init(final JavacTask javacTask, final String... args) {
87          javacTask.addTaskListener(this);
88          trees = Trees.instance(javacTask);
89      }
90  
91      @Override
92      public void started(final TaskEvent taskEvent) {
93      }
94  
95      @Override
96      public void finished(final TaskEvent taskEvent) {
97          if (taskEvent.getKind() == Kind.ANALYZE) {
98              final CompilationUnitTree root = taskEvent.getCompilationUnit();
99              root.accept(new AnnotationTreeScanner(root), null);
100         }
101     }
102 
103     /** Finds when an annotation is used and checks the scope has the same annotation. */
104     private class AnnotationTreeScanner extends TreeScanner<Void, Void> {
105 
106         /** Compilation root. */
107         private final CompilationUnitTree root;
108 
109         /**
110          * Create a scanner.
111          *
112          * @param root of the compilation.
113          */
114         AnnotationTreeScanner(final CompilationUnitTree root) {
115             this.root = root;
116         }
117 
118         @Override
119         public Void visitIdentifier(final IdentifierTree identifierTree,
120                                     final Void aVoid) {
121             check(identifierTree);
122             return super.visitIdentifier(identifierTree, aVoid);
123         }
124 
125         @Override
126         public Void visitMemberSelect(final MemberSelectTree memberSelectTree,
127                                       final Void aVoid) {
128             check(memberSelectTree);
129             return super.visitMemberSelect(memberSelectTree, aVoid);
130         }
131 
132         @Override
133         public Void visitNewClass(final NewClassTree newClassTree, final Void aVoid) {
134             check(newClassTree);
135             return super.visitNewClass(newClassTree, aVoid);
136         }
137 
138         /**
139          * Check if this bit of code calls into the default data context from outside the
140          * default data context.
141          *
142          * @param tree to check.
143          */
144         private void check(final Tree tree) {
145             final Element element = trees.getElement(trees.getPath(root, tree));
146             check(tree, element);
147         }
148 
149         /**
150          * Check tricky bits of code.
151          *
152          * @param tree    used to check the containing scope and for logging.
153          * @param element to check for {@link #ANNOTATION}.
154          */
155         private void check(final Tree tree, final Element element) {
156             // element and its containing scopes.
157             if (isAnyElementAnnotated(element)) {
158                 // using code annotated with @DefaultDataContext
159                 // check if current scope is also annotated
160                 if (!isAnyElementAnnotated(trees.getPath(root, tree))) {
161                     // calling the default data context from a method without an annotation
162                     final String message = MESSAGE + " Used: " + element.getKind() + " " + element;
163                     trees.printMessage(Diagnostic.Kind.WARNING, message, tree, root);
164                 }
165             }
166         }
167 
168         /**
169          * Determine if any enclosing element has {@link #ANNOTATION}. Walks towards the
170          * tree root checking each node for the annotation.
171          *
172          * @param element to start the search from. May be {@code null}.
173          * @return {@code true} if {@code element} or any of its parents are annotated,
174          * {@code false} otherwise.
175          */
176         private boolean isAnyElementAnnotated(final Element element) {
177             Element e = element;
178             while (e != null) {
179                 if (e.getAnnotation(ANNOTATION) != null) {
180                     return true;
181                 }
182                 e = e.getEnclosingElement();
183             }
184             return false;
185         }
186 
187         /**
188          * Determine if any enclosing tree has {@link #ANNOTATION}. Walks towards the tree
189          * root checking each node for the annotation.
190          *
191          * @param path to start the search from. May be {@code null}.
192          * @return {@code true} if {@code path} or any of its parents are annotated,
193          * {@code false} otherwise.
194          */
195         private boolean isAnyElementAnnotated(final TreePath path) {
196             // Kinds of declarations which can be annotated
197             final Set<Tree.Kind> toCheck = EnumSet.of(
198                     Tree.Kind.METHOD, Tree.Kind.CLASS, Tree.Kind.VARIABLE,
199                     Tree.Kind.INTERFACE, Tree.Kind.ENUM);
200             TreePath next = path;
201             while (next != null) {
202                 if (toCheck.contains(next.getLeaf().getKind())) {
203                     if (trees.getElement(next).getAnnotation(ANNOTATION) != null) {
204                         return true;
205                     }
206                 }
207                 next = next.getParentPath();
208             }
209             return false;
210         }
211 
212     }
213 
214 }