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}