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.resolver; 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.MethodDeclaration; 025import japa.parser.ast.body.ModifierSet; 026import japa.parser.ast.body.Parameter; 027import japa.parser.ast.body.TypeDeclaration; 028import japa.parser.ast.body.VariableDeclarator; 029import japa.parser.ast.body.VariableDeclaratorId; 030import japa.parser.ast.expr.AnnotationExpr; 031import japa.parser.ast.expr.AssignExpr; 032import japa.parser.ast.expr.BinaryExpr; 033import japa.parser.ast.expr.BooleanLiteralExpr; 034import japa.parser.ast.expr.CastExpr; 035import japa.parser.ast.expr.Expression; 036import japa.parser.ast.expr.FieldAccessExpr; 037import japa.parser.ast.expr.IntegerLiteralExpr; 038import japa.parser.ast.expr.MarkerAnnotationExpr; 039import japa.parser.ast.expr.MethodCallExpr; 040import japa.parser.ast.expr.NameExpr; 041import japa.parser.ast.expr.NullLiteralExpr; 042import japa.parser.ast.expr.ObjectCreationExpr; 043import japa.parser.ast.expr.QualifiedNameExpr; 044import japa.parser.ast.expr.SingleMemberAnnotationExpr; 045import japa.parser.ast.expr.StringLiteralExpr; 046import japa.parser.ast.expr.ThisExpr; 047import japa.parser.ast.expr.VariableDeclarationExpr; 048import japa.parser.ast.stmt.BlockStmt; 049import japa.parser.ast.stmt.ExpressionStmt; 050import japa.parser.ast.stmt.IfStmt; 051import japa.parser.ast.stmt.ReturnStmt; 052import japa.parser.ast.stmt.Statement; 053import japa.parser.ast.type.ClassOrInterfaceType; 054import japa.parser.ast.type.PrimitiveType; 055import japa.parser.ast.type.Type; 056import japa.parser.ast.type.VoidType; 057import org.apache.ojb.broker.metadata.ClassDescriptor; 058import org.apache.ojb.broker.metadata.DescriptorRepository; 059import org.apache.ojb.broker.metadata.FieldDescriptor; 060import org.kuali.rice.devtools.jpa.eclipselink.conv.ojb.OjbUtil; 061import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.ParserUtil; 062import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.AnnotationResolver; 063import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.Level; 064import org.kuali.rice.devtools.jpa.eclipselink.conv.parser.helper.NodeData; 065 066import java.util.ArrayList; 067import java.util.Collection; 068import java.util.Collections; 069import java.util.List; 070 071public class IdClassResolver implements AnnotationResolver { 072 073 public static final String PACKAGE = "javax.persistence"; 074 public static final String SIMPLE_NAME = "IdClass"; 075 076 private final Collection<DescriptorRepository> descriptorRepositories; 077 078 public IdClassResolver(Collection<DescriptorRepository> descriptorRepositories) { 079 this.descriptorRepositories = descriptorRepositories; 080 } 081 082 @Override 083 public String getFullyQualifiedName() { 084 return PACKAGE + "." + SIMPLE_NAME; 085 } 086 087 @Override 088 public Level getLevel() { 089 return Level.CLASS; 090 } 091 092 @Override 093 public NodeData resolve(Node node, String mappedClass) { 094 if (!(node instanceof ClassOrInterfaceDeclaration)) { 095 throw new IllegalArgumentException("this annotation belongs only on ClassOrInterfaceDeclaration"); 096 } 097 098 final TypeDeclaration dclr = (TypeDeclaration) node; 099 if (!(dclr.getParentNode() instanceof CompilationUnit)) { 100 //handling nested classes 101 return null; 102 } 103 final String name = dclr.getName(); 104 105 final Collection<FieldDescriptor> primaryKeyDescriptors = getPrimaryKeyDescriptors(mappedClass); 106 107 if (primaryKeyDescriptors != null && primaryKeyDescriptors.size() > 1 && nodeContainsPkFields(dclr, 108 primaryKeyDescriptors)) { 109 final NodeAndImports<ClassOrInterfaceDeclaration> primaryKeyClass = createPrimaryKeyClass(name, primaryKeyDescriptors); 110 final String pkClassName = primaryKeyClass.node.getName(); 111 return new NodeData(new SingleMemberAnnotationExpr(new NameExpr(SIMPLE_NAME), new NameExpr(name + "." + pkClassName + ".class")), 112 new ImportDeclaration(new QualifiedNameExpr(new NameExpr(PACKAGE), SIMPLE_NAME), false, false), primaryKeyClass.imprts, primaryKeyClass.node); 113 114 } 115 return null; 116 } 117 118 private boolean nodeContainsPkFields(TypeDeclaration dclr, Collection<FieldDescriptor> pks) { 119 for (FieldDescriptor pk : pks) { 120 boolean contains = false; 121 for (FieldDeclaration field : ParserUtil.getFieldMembers(dclr.getMembers())) { 122 if (field.getVariables().get(0).getId().getName().equals(pk.getAttributeName())) { 123 contains = true; 124 break; 125 } 126 } 127 128 if (!contains) { 129 return false; 130 } 131 } 132 133 return true; 134 } 135 136 private Collection<FieldDescriptor> getPrimaryKeyDescriptors(String clazz) { 137 final Collection<FieldDescriptor> pks = new ArrayList<FieldDescriptor>(); 138 139 final ClassDescriptor cd = OjbUtil.findClassDescriptor(clazz, descriptorRepositories); 140 if (cd != null) { 141 //This causes a stackoverflow and appears to not work correctly 142 //return cd.getPkFields().length > 1; 143 int i = 0; 144 FieldDescriptor[] fds = cd.getFieldDescriptions(); 145 if (fds != null) { 146 for (FieldDescriptor fd : fds) { 147 if (fd.isPrimaryKey()) { 148 pks.add(fd); 149 } 150 } 151 } 152 } 153 return pks; 154 } 155 156 private NodeAndImports<ClassOrInterfaceDeclaration> createPrimaryKeyClass(String parentName, Collection<FieldDescriptor> primaryKeyDescriptors) { 157 final String newName = parentName + "Id"; 158 final Collection<ImportDeclaration> requiredImports = new ArrayList<ImportDeclaration>(); 159 final ClassOrInterfaceDeclaration dclr = new ClassOrInterfaceDeclaration(ModifierSet.PUBLIC | ModifierSet.STATIC | ModifierSet.FINAL, false, newName); 160 dclr.setInterface(false); 161 final List<ClassOrInterfaceType> implmnts = new ArrayList<ClassOrInterfaceType>(); 162 implmnts.add(new ClassOrInterfaceType("Serializable")); 163 final ClassOrInterfaceType comparableImplmnts = new ClassOrInterfaceType("Comparable"); 164 comparableImplmnts.setTypeArgs(Collections.<Type>singletonList(new ClassOrInterfaceType(newName))); 165 implmnts.add(comparableImplmnts); 166 167 dclr.setImplements(implmnts); 168 requiredImports.add(new ImportDeclaration(new QualifiedNameExpr(new NameExpr("java.io"), "Serializable"), false, false)); 169 final List<BodyDeclaration> members = new ArrayList<BodyDeclaration>(); 170 171 for (FieldDescriptor fd : primaryKeyDescriptors) { 172 final String simpleTypeName = ResolverUtil.getType(fd.getClassDescriptor().getClassNameOfObject(), 173 fd.getAttributeName()).getSimpleName(); 174 final String attrName = fd.getAttributeName(); 175 176 members.add(new FieldDeclaration(ModifierSet.PRIVATE, new ClassOrInterfaceType(simpleTypeName), new VariableDeclarator(new VariableDeclaratorId(attrName)))); 177 } 178 179 for (FieldDescriptor fd : primaryKeyDescriptors) { 180 final String simpleTypeName = ResolverUtil.getType(fd.getClassDescriptor().getClassNameOfObject(), 181 fd.getAttributeName()).getSimpleName(); 182 final String attrName = fd.getAttributeName(); 183 final MethodDeclaration getter = new MethodDeclaration(ModifierSet.PUBLIC, new ClassOrInterfaceType(simpleTypeName), "get" + attrName.substring(0, 1).toUpperCase() + attrName.substring(1)); 184 getter.setBody(new BlockStmt(Collections.<Statement>singletonList(new ReturnStmt(new FieldAccessExpr(new ThisExpr(), attrName))))); 185 members.add(getter); 186 187 final MethodDeclaration setter = new MethodDeclaration(ModifierSet.PUBLIC, new VoidType(), "set" + attrName.substring(0, 1).toUpperCase() + attrName.substring(1), 188 Collections.singletonList(new Parameter(new ClassOrInterfaceType(simpleTypeName), new VariableDeclaratorId(attrName)))); 189 190 setter.setBody(new BlockStmt(Collections.<Statement>singletonList(new ExpressionStmt( 191 new AssignExpr(new FieldAccessExpr(new ThisExpr(), attrName), new NameExpr(attrName), AssignExpr.Operator.assign))))); 192 members.add(setter); 193 } 194 195 final NodeAndImports<MethodDeclaration> toString = createPrimaryKeyToString(primaryKeyDescriptors); 196 final NodeAndImports<MethodDeclaration> equals = createPrimaryKeyEquals(primaryKeyDescriptors, newName); 197 final NodeAndImports<MethodDeclaration> hashCode = createPrimaryKeyHashCode(primaryKeyDescriptors); 198 final NodeAndImports<MethodDeclaration> compareTo = createPrimaryKeyCompareTo(primaryKeyDescriptors, newName); 199 200 members.add(toString.node); 201 members.add(equals.node); 202 members.add(hashCode.node); 203 members.add(compareTo.node); 204 205 if (toString.imprts != null) { 206 requiredImports.addAll(toString.imprts); 207 } 208 209 if (equals.imprts != null) { 210 requiredImports.addAll(equals.imprts); 211 } 212 213 if (hashCode.imprts != null) { 214 requiredImports.addAll(hashCode.imprts); 215 } 216 217 if (compareTo.imprts != null) { 218 requiredImports.addAll(compareTo.imprts); 219 } 220 221 dclr.setMembers(members); 222 223 return new NodeAndImports<ClassOrInterfaceDeclaration>(dclr, requiredImports); 224 } 225 226 private NodeAndImports<MethodDeclaration> createPrimaryKeyToString(Collection<FieldDescriptor> primaryKeyDescriptors) { 227 final MethodDeclaration toString = new MethodDeclaration(ModifierSet.PUBLIC, new ClassOrInterfaceType("String"), "toString"); 228 toString.setAnnotations(Collections.<AnnotationExpr>singletonList(new MarkerAnnotationExpr(new NameExpr("Override")))); 229 Expression toStringBuilderExpr = new ObjectCreationExpr(null, new ClassOrInterfaceType("ToStringBuilder"), Collections.<Expression>singletonList(new ThisExpr())); 230 for (FieldDescriptor f : primaryKeyDescriptors) { 231 final List<Expression> args = new ArrayList<Expression>(); 232 args.add(new StringLiteralExpr(f.getAttributeName())); 233 args.add(new FieldAccessExpr(new ThisExpr(), f.getAttributeName())); 234 toStringBuilderExpr = new MethodCallExpr(toStringBuilderExpr, "append", args); 235 } 236 toStringBuilderExpr = new MethodCallExpr(toStringBuilderExpr, "toString"); 237 final BlockStmt toStringBody = new BlockStmt(Collections.<Statement>singletonList(new ReturnStmt(toStringBuilderExpr))); 238 toString.setBody(toStringBody); 239 240 return new NodeAndImports<MethodDeclaration>(toString, 241 Collections.singleton(new ImportDeclaration(new QualifiedNameExpr(new NameExpr("org.apache.commons.lang.builder"), "ToStringBuilder"), false, false))); 242 } 243 244 private NodeAndImports<MethodDeclaration> createPrimaryKeyEquals(Collection<FieldDescriptor> primaryKeyDescriptors, String enclosingClassName) { 245 final MethodDeclaration equals = new MethodDeclaration(ModifierSet.PUBLIC, new PrimitiveType(PrimitiveType.Primitive.Boolean), "equals", 246 Collections.singletonList(new Parameter(new ClassOrInterfaceType("Object"), new VariableDeclaratorId("other")))); 247 equals.setAnnotations(Collections.<AnnotationExpr>singletonList(new MarkerAnnotationExpr(new NameExpr("Override")))); 248 249 final Statement ifEqualNullStmt = new IfStmt(new BinaryExpr(new NameExpr("other"), new NullLiteralExpr(), BinaryExpr.Operator.equals), new ReturnStmt(new BooleanLiteralExpr(false)), null); 250 final Statement ifEqualThisStmt = new IfStmt(new BinaryExpr(new NameExpr("other"), new ThisExpr(), BinaryExpr.Operator.equals), new ReturnStmt(new BooleanLiteralExpr(true)), null); 251 final Statement ifEqualClassStmt = new IfStmt(new BinaryExpr(new MethodCallExpr(new NameExpr("other"), "getClass"), new MethodCallExpr(new ThisExpr(), "getClass"), BinaryExpr.Operator.notEquals), new ReturnStmt(new BooleanLiteralExpr(false)), null); 252 final Statement rhsStmt = new ExpressionStmt(new VariableDeclarationExpr(ModifierSet.FINAL, 253 new ClassOrInterfaceType(enclosingClassName), Collections.singletonList(new VariableDeclarator( 254 new VariableDeclaratorId("rhs"), 255 new CastExpr(new ClassOrInterfaceType(enclosingClassName), new NameExpr("other")))))); 256 257 Expression equalsBuilderExpr = new ObjectCreationExpr(null, new ClassOrInterfaceType("EqualsBuilder"), Collections.<Expression>emptyList()); 258 259 for (FieldDescriptor f : primaryKeyDescriptors) { 260 final List<Expression> args = new ArrayList<Expression>(); 261 args.add(new FieldAccessExpr(new ThisExpr(), f.getAttributeName())); 262 args.add(new FieldAccessExpr(new NameExpr("rhs"), f.getAttributeName())); 263 equalsBuilderExpr = new MethodCallExpr(equalsBuilderExpr, "append", args); 264 } 265 266 equalsBuilderExpr = new MethodCallExpr(equalsBuilderExpr, "isEquals"); 267 final List<Statement> statements = new ArrayList<Statement>(); 268 statements.add(ifEqualNullStmt); 269 statements.add(ifEqualThisStmt); 270 statements.add(ifEqualClassStmt); 271 statements.add(rhsStmt); 272 statements.add(new ReturnStmt(equalsBuilderExpr)); 273 final BlockStmt equalsBody = new BlockStmt(statements); 274 equals.setBody(equalsBody); 275 276 return new NodeAndImports<MethodDeclaration>(equals, 277 Collections.singleton(new ImportDeclaration(new QualifiedNameExpr(new NameExpr("org.apache.commons.lang.builder"), "EqualsBuilder"), false, false))); 278 } 279 280 private NodeAndImports<MethodDeclaration> createPrimaryKeyHashCode(Collection<FieldDescriptor> primaryKeyDescriptors) { 281 final MethodDeclaration hashCode = new MethodDeclaration(ModifierSet.PUBLIC, new PrimitiveType(PrimitiveType.Primitive.Int), "hashCode"); 282 hashCode.setAnnotations(Collections.<AnnotationExpr>singletonList(new MarkerAnnotationExpr(new NameExpr("Override")))); 283 final List<Expression> ctorArgs = new ArrayList<Expression>(); 284 ctorArgs.add(new IntegerLiteralExpr("17")); 285 ctorArgs.add(new IntegerLiteralExpr("37")); 286 Expression hashCodeExpr = new ObjectCreationExpr(null, new ClassOrInterfaceType("HashCodeBuilder"), ctorArgs); 287 288 for (FieldDescriptor f : primaryKeyDescriptors) { 289 final List<Expression> args = new ArrayList<Expression>(); 290 args.add(new FieldAccessExpr(new ThisExpr(), f.getAttributeName())); 291 hashCodeExpr = new MethodCallExpr(hashCodeExpr, "append", args); 292 } 293 294 hashCodeExpr = new MethodCallExpr(hashCodeExpr, "toHashCode"); 295 final BlockStmt equalsBody = new BlockStmt(Collections.<Statement>singletonList(new ReturnStmt(hashCodeExpr))); 296 hashCode.setBody(equalsBody); 297 298 return new NodeAndImports<MethodDeclaration>(hashCode, 299 Collections.singleton(new ImportDeclaration(new QualifiedNameExpr(new NameExpr("org.apache.commons.lang.builder"), "HashCodeBuilder"), false, false))); 300 } 301 302 private NodeAndImports<MethodDeclaration> createPrimaryKeyCompareTo(Collection<FieldDescriptor> primaryKeyDescriptors, String enclosingClassName) { 303 final MethodDeclaration compareTo = new MethodDeclaration(ModifierSet.PUBLIC, new PrimitiveType(PrimitiveType.Primitive.Int), "compareTo", 304 Collections.singletonList(new Parameter(new ClassOrInterfaceType(enclosingClassName), new VariableDeclaratorId("other")))); 305 compareTo.setAnnotations(Collections.<AnnotationExpr>singletonList(new MarkerAnnotationExpr(new NameExpr("Override")))); 306 307 Expression compareToBuilderExpr = new ObjectCreationExpr(null, new ClassOrInterfaceType("CompareToBuilder"), Collections.<Expression>emptyList()); 308 309 for (FieldDescriptor f : primaryKeyDescriptors) { 310 final List<Expression> args = new ArrayList<Expression>(); 311 args.add(new FieldAccessExpr(new ThisExpr(), f.getAttributeName())); 312 args.add(new FieldAccessExpr(new NameExpr("other"), f.getAttributeName())); 313 compareToBuilderExpr = new MethodCallExpr(compareToBuilderExpr, "append", args); 314 } 315 316 compareToBuilderExpr = new MethodCallExpr(compareToBuilderExpr, "toComparison"); 317 final List<Statement> statements = new ArrayList<Statement>(); 318 statements.add(new ReturnStmt(compareToBuilderExpr)); 319 final BlockStmt equalsBody = new BlockStmt(statements); 320 compareTo.setBody(equalsBody); 321 322 return new NodeAndImports<MethodDeclaration>(compareTo, 323 Collections.singleton(new ImportDeclaration(new QualifiedNameExpr(new NameExpr("org.apache.commons.lang.builder"), "CompareToBuilder"), false, false))); 324 } 325 326 private class NodeAndImports<T extends Node> { 327 T node; 328 Collection<ImportDeclaration> imprts; 329 330 NodeAndImports(T node, Collection<ImportDeclaration> imprts) { 331 this.node = node; 332 this.imprts = imprts; 333 } 334 } 335}