001/** 002 * Copyright 2005-2015 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper; 017 018import japa.parser.ast.CompilationUnit; 019import japa.parser.ast.ImportDeclaration; 020import japa.parser.ast.Node; 021import japa.parser.ast.body.BodyDeclaration; 022import japa.parser.ast.body.ClassOrInterfaceDeclaration; 023import japa.parser.ast.body.FieldDeclaration; 024import japa.parser.ast.body.TypeDeclaration; 025import japa.parser.ast.body.VariableDeclarator; 026import japa.parser.ast.expr.AnnotationExpr; 027import org.apache.commons.lang.ClassUtils; 028import org.apache.logging.log4j.Logger; 029import org.apache.logging.log4j.LogManager; 030 031import java.util.ArrayList; 032import java.util.Collection; 033import java.util.List; 034 035/** 036 * This is a sort of visitor "helper" class that looks to see if a particular annotation is on a class and if not 037 * places it on the class. 038 */ 039public class AnnotationHelper extends VoidVisitorHelperBase<String> { 040 041 private static final Logger LOG = LogManager.getLogger(AnnotationHelper.class); 042 043 private final Collection<AnnotationResolver> resolvers; 044 private final boolean removeExisting; 045 046 public AnnotationHelper(Collection<AnnotationResolver> resolvers, boolean removeExisting) { 047 this.resolvers = resolvers; 048 this.removeExisting = removeExisting; 049 } 050 051 @Override 052 public void visitPre(final ClassOrInterfaceDeclaration n, final String mappedClass) { 053 addAnnotation(n, mappedClass, Level.CLASS); 054 } 055 056 @Override 057 public void visitPre(final FieldDeclaration n, final String mappedClass) { 058 addAnnotation(n, mappedClass, Level.FIELD); 059 } 060 061 /** walks up the tree until reaching the CompilationUnit. */ 062 private CompilationUnit getCompilationUnit(Node n) { 063 Node unit = n; 064 while (!(unit instanceof CompilationUnit) && unit != null) { 065 unit = unit.getParentNode(); 066 } 067 return (CompilationUnit) unit; 068 } 069 070 private void addAnnotation(final BodyDeclaration n, final String mappedClass, Level level) { 071 for (AnnotationResolver resolver : resolvers) { 072 if (resolver.getLevel() == level) { 073 LOG.debug("Evaluating resolver " + ClassUtils.getShortClassName(resolver.getClass()) + " for " + getTypeOrFieldNameForMsg(n) + "."); 074 075 final String fullyQualifiedName = resolver.getFullyQualifiedName(); 076 077 //1 figure out if annotation is already imported either via star import or single import. 078 final CompilationUnit unit = getCompilationUnit(n); 079 final List<ImportDeclaration> imports = unit.getImports() != null ? unit.getImports() : new ArrayList<ImportDeclaration>(); 080 final boolean foundAnnImport = imported(imports, fullyQualifiedName); 081 082 //2 check if annotation already exists... 083 final AnnotationExpr existingAnnotation = findAnnotation(n, fullyQualifiedName, foundAnnImport); 084 085 //3 if removeExisting is set and the annotation exists, then remove the annotation prior to calling the resolver 086 //Note: cannot remove the import without much more complex logic because the annotation may exist on other nodes in the CompilationUnit 087 //Could traverse the entire CompilationUnit searching for the annotation if we wanted to determine whether we can safely remove an import 088 if (removeExisting && existingAnnotation != null) { 089 LOG.info("removing existing " + existingAnnotation + " from " + getTypeOrFieldNameForMsg(n) + "."); 090 final List<AnnotationExpr> annotations = n.getAnnotations() != null ? n.getAnnotations() : new ArrayList<AnnotationExpr>(); 091 annotations.remove(existingAnnotation); 092 n.setAnnotations(annotations); 093 } 094 095 //4 add annotation if it doesn't already exist or if replaceExisting is set 096 // and the annotation resolves (meaning the resolver determines if should be added by returning a non-null value) 097 if (existingAnnotation == null || (existingAnnotation != null && removeExisting)) { 098 NodeData nodes = resolver.resolve(n, mappedClass); 099 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}