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}