View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper;
17  
18  import japa.parser.ast.CompilationUnit;
19  import japa.parser.ast.ImportDeclaration;
20  import japa.parser.ast.Node;
21  import japa.parser.ast.body.BodyDeclaration;
22  import japa.parser.ast.body.ClassOrInterfaceDeclaration;
23  import japa.parser.ast.body.FieldDeclaration;
24  import japa.parser.ast.body.TypeDeclaration;
25  import japa.parser.ast.body.VariableDeclarator;
26  import japa.parser.ast.expr.AnnotationExpr;
27  import org.apache.commons.lang.ClassUtils;
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  
31  import java.util.ArrayList;
32  import java.util.Collection;
33  import java.util.List;
34  
35  /**
36   * This is a sort of visitor "helper" class that looks to see if a particular annotation is on a class and if not
37   * places it on the class.
38   */
39  public class AnnotationHelper extends VoidVisitorHelperBase<String> {
40  
41      private static final Log LOG = LogFactory.getLog(AnnotationHelper.class);
42  
43      private final Collection<AnnotationResolver> resolvers;
44      private final boolean removeExisting;
45  
46      public AnnotationHelper(Collection<AnnotationResolver> resolvers, boolean removeExisting) {
47          this.resolvers = resolvers;
48          this.removeExisting = removeExisting;
49      }
50  
51      @Override
52      public void visitPre(final ClassOrInterfaceDeclaration n, final String mappedClass) {
53          addAnnotation(n, mappedClass, Level.CLASS);
54      }
55  
56      @Override
57      public void visitPre(final FieldDeclaration n, final String mappedClass) {
58          addAnnotation(n, mappedClass, Level.FIELD);
59      }
60  
61      /** walks up the tree until reaching the CompilationUnit. */
62      private CompilationUnit getCompilationUnit(Node n) {
63          Node unit = n;
64          while (!(unit instanceof CompilationUnit) && unit != null) {
65              unit = unit.getParentNode();
66          }
67          return (CompilationUnit) unit;
68      }
69  
70      private void addAnnotation(final BodyDeclaration n, final String mappedClass, Level level) {
71          for (AnnotationResolver resolver : resolvers) {
72              if (resolver.getLevel() == level) {
73                  LOG.debug("Evaluating resolver " + ClassUtils.getShortClassName(resolver.getClass()) + " for " + getTypeOrFieldNameForMsg(n) + ".");
74  
75                  final String fullyQualifiedName = resolver.getFullyQualifiedName();
76  
77                  //1 figure out if annotation is already imported either via star import or single import.
78                  final CompilationUnit unit = getCompilationUnit(n);
79                  final List<ImportDeclaration> imports = unit.getImports() != null ? unit.getImports() : new ArrayList<ImportDeclaration>();
80                  final boolean foundAnnImport = imported(imports, fullyQualifiedName);
81  
82                  //2 check if annotation already exists...
83                  final AnnotationExpr existingAnnotation = findAnnotation(n, fullyQualifiedName, foundAnnImport);
84  
85                  //3 if removeExisting is set and the annotation exists, then remove the annotation prior to calling the resolver
86                  //Note: cannot remove the import without much more complex logic because the annotation may exist on other nodes in the CompilationUnit
87                  //Could traverse the entire CompilationUnit searching for the annotation if we wanted to determine whether we can safely remove an import
88                  if (removeExisting && existingAnnotation != null) {
89                      LOG.info("removing existing " + existingAnnotation + " from " + getTypeOrFieldNameForMsg(n) + ".");
90                      final List<AnnotationExpr> annotations = n.getAnnotations() != null ? n.getAnnotations() : new ArrayList<AnnotationExpr>();
91                      annotations.remove(existingAnnotation);
92                      n.setAnnotations(annotations);
93                  }
94  
95                  //4 add annotation if it doesn't already exist or if replaceExisting is set
96                  // and the annotation resolves (meaning the resolver determines if should be added by returning a non-null value)
97                  if (existingAnnotation == null || (existingAnnotation != null && removeExisting)) {
98                      NodeData nodes = resolver.resolve(n, mappedClass);
99                      if (nodes != null && nodes.annotation != null) {
100                         LOG.info("adding " + nodes.annotation + " to " + getTypeOrFieldNameForMsg(n) + ".");
101                         final List<AnnotationExpr> annotations = n.getAnnotations() != null ? n.getAnnotations() : new ArrayList<AnnotationExpr>();
102                         annotations.add(nodes.annotation);
103                         n.setAnnotations(annotations);
104 
105                         //5 add import for annotation
106                         if (!foundAnnImport) {
107                             LOG.info("adding import " + fullyQualifiedName + " to " + getTypeNameForMsg(n) + ".");
108                             imports.add(nodes.annotationImport);
109                         }
110 
111                         //6 add additional imports if they are needed
112                         if (nodes.additionalImports != null) {
113                             for (ImportDeclaration aImport : nodes.additionalImports) {
114                                 if (aImport.isStatic() || aImport.isAsterisk()) {
115                                     throw new IllegalStateException("The additional imports should not be static or star imports");
116                                 }
117                                 final boolean imported = imported(imports, aImport.getName().toString());
118                                 if (!imported) {
119                                     LOG.info("adding import " + aImport.getName().toString() + " to " + getTypeNameForMsg(n) + ".");
120                                     imports.add(aImport);
121                                 }
122                             }
123                         }
124 
125                         unit.setImports(imports);
126 
127                         if (nodes.nestedDeclaration != null) {
128                             final TypeDeclaration parent = unit.getTypes().get(0);
129 
130                             final List<BodyDeclaration> members = parent.getMembers() != null ? parent.getMembers() : new ArrayList<BodyDeclaration>();
131                             final TypeDeclaration existingNestedDeclaration = findTypeDeclaration(members, nodes.nestedDeclaration.getName());
132 
133                             //7 if removeExisting is set and the nested declaration exists, then remove the nested declaration
134                             if (removeExisting) {
135                                 if (existingNestedDeclaration != null) {
136                                     LOG.info("removing existing nested declaration " + existingNestedDeclaration.getName() + " from " + getTypeOrFieldNameForMsg(n) + ".");
137                                     members.remove(existingNestedDeclaration);
138                                 }
139                             }
140 
141                             //8 add nested class
142                             if (existingNestedDeclaration == null || (existingNestedDeclaration != null && removeExisting)) {
143                                 nodes.nestedDeclaration.setParentNode(parent);
144                                 LOG.info("adding nested declaration " + nodes.nestedDeclaration.getName() + " to " + getTypeOrFieldNameForMsg(n) + ".");
145                                 members.add(nodes.nestedDeclaration);
146                             }
147                             parent.setMembers(members);
148                         }
149                     }
150                 }
151             }
152         }
153     }
154 
155     private AnnotationExpr findAnnotation(final BodyDeclaration n, String fullyQualifiedName, boolean foundAnnImport) {
156         final String simpleName = ClassUtils.getShortClassName(fullyQualifiedName);
157         final List<AnnotationExpr> annotations = n.getAnnotations() != null ? n.getAnnotations() : new ArrayList<AnnotationExpr>();
158 
159         for (AnnotationExpr ae : annotations) {
160             final String name = ae.getName().toString();
161             if ((simpleName.equals(name) && foundAnnImport)) {
162                 LOG.info("found " + ae + " on " + getTypeOrFieldNameForMsg(n) + ".");
163                 return ae;
164             }
165 
166             if (fullyQualifiedName.equals(name)) {
167                 LOG.info("found " + ae + " on " + getTypeOrFieldNameForMsg(n) + ".");
168                 return ae;
169             }
170         }
171         return null;
172     }
173 
174     private TypeDeclaration findTypeDeclaration(List<BodyDeclaration> members, String name) {
175         if (members != null) {
176             for (BodyDeclaration bd : members) {
177                 if (bd instanceof TypeDeclaration) {
178                     if (((TypeDeclaration) bd).getName().equals(name)) {
179                         return (TypeDeclaration) bd;
180                     }
181                 }
182             }
183         }
184         return null;
185     }
186 
187     private boolean imported(List<ImportDeclaration> imports, String fullyQualifiedName) {
188         final String packageName = ClassUtils.getPackageName(fullyQualifiedName);
189 
190         for (final ImportDeclaration i : imports) {
191             if (!i.isStatic()) {
192                 final String importName = i.getName().toString();
193                 if (i.isAsterisk()) {
194                     if (packageName.equals(importName)) {
195                         if ( LOG.isDebugEnabled() ) {
196                             LOG.debug("found import " + packageName + ".* on " + getTypeNameForMsg(i) + ".");
197                         }
198                         return true;
199                     }
200                 } else {
201                     if (fullyQualifiedName.equals(importName)) {
202                         if ( LOG.isDebugEnabled() ) {
203                             LOG.debug("found import " + fullyQualifiedName + " on " + getTypeNameForMsg(i) + ".");
204                         }
205                         return true;
206                     }
207                 }
208             }
209         }
210         return false;
211     }
212 
213     private String getTypeOrFieldNameForMsg(final BodyDeclaration n) {
214         if (n instanceof TypeDeclaration) {
215             return ((TypeDeclaration) n).getName();
216         } else if (n instanceof FieldDeclaration) {
217             final FieldDeclaration fd = (FieldDeclaration) n;
218             //this wont work for nested classes but we should be in nexted classes at this point
219             final CompilationUnit unit = getCompilationUnit(n);
220             final TypeDeclaration parent = unit.getTypes().get(0);
221             Collection<String> variableNames = new ArrayList<String>();
222             if (fd.getVariables() != null) {
223                 for (VariableDeclarator vd : fd.getVariables()) {
224                     variableNames.add(vd.getId().getName());
225                 }
226             }
227             return variableNames.size() == 1 ?
228                     parent.getName() + "." + variableNames.iterator().next() :
229                     parent.getName() + "." + variableNames.toString();
230 
231         }
232         return null;
233     }
234 
235     private String getTypeNameForMsg(final Node n) {
236         final CompilationUnit unit = getCompilationUnit(n);
237         final TypeDeclaration parent = unit.getTypes().get(0);
238         return parent.getName();
239     }
240 }