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