2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.modules.j2ee.jpa.verification.fixes;
44
import com.sun.source.tree.AnnotationTree;
45
import com.sun.source.tree.ClassTree;
46
import com.sun.source.tree.ExpressionTree;
47
import com.sun.source.tree.MethodTree;
48
import com.sun.source.tree.ModifiersTree;
49
import com.sun.source.tree.VariableTree;
50
import java.awt.Dialog;
51
import java.io.IOException;
52
import java.util.Collection;
53
import java.util.Collections;
54
import java.util.List;
55
import java.util.TreeSet;
56
import java.util.logging.Level;
57
import javax.lang.model.element.Element;
58
import javax.lang.model.element.ElementKind;
59
import javax.lang.model.element.ExecutableElement;
60
import javax.lang.model.element.Modifier;
61
import javax.lang.model.element.TypeElement;
62
import javax.lang.model.element.VariableElement;
63
import javax.lang.model.type.DeclaredType;
64
import javax.lang.model.type.TypeKind;
65
import javax.lang.model.type.TypeMirror;
66
import javax.lang.model.util.ElementFilter;
67
import org.netbeans.api.java.source.CancellableTask;
68
import org.netbeans.api.java.source.ClasspathInfo;
69
import org.netbeans.api.java.source.CompilationController;
70
import org.netbeans.api.java.source.ElementHandle;
71
import org.netbeans.api.java.source.JavaSource;
72
import org.netbeans.api.java.source.WorkingCopy;
73
import org.netbeans.modules.j2ee.jpa.model.AccessType;
74
import org.netbeans.modules.j2ee.jpa.model.JPAAnnotations;
75
import org.netbeans.modules.j2ee.jpa.model.JPAHelper;
76
import org.netbeans.modules.j2ee.jpa.model.ModelUtils;
77
import org.netbeans.modules.j2ee.jpa.verification.JPAProblemFinder;
78
import org.netbeans.modules.j2ee.jpa.verification.common.Utilities;
79
import org.netbeans.modules.j2ee.metadata.model.api.MetadataModel;
80
import org.netbeans.modules.j2ee.metadata.model.api.MetadataModelAction;
81
import org.netbeans.modules.j2ee.metadata.model.api.MetadataModelException;
82
import org.netbeans.modules.j2ee.persistence.api.metadata.orm.Entity;
83
import org.netbeans.modules.j2ee.persistence.api.metadata.orm.EntityMappingsMetadata;
84
import org.netbeans.modules.j2ee.persistence.api.metadata.orm.ManyToMany;
85
import org.netbeans.modules.j2ee.persistence.api.metadata.orm.OneToMany;
86
import org.netbeans.modules.j2ee.persistence.api.metadata.orm.OneToOne;
87
import org.netbeans.spi.editor.hints.ChangeInfo;
88
import org.netbeans.spi.editor.hints.Fix;
89
import org.openide.DialogDescriptor;
90
import org.openide.DialogDisplayer;
91
import org.openide.filesystems.FileObject;
92
import org.openide.util.NbBundle;
96
* @author Tomasz.Slota@Sun.COM
98
public abstract class AbstractCreateRelationshipHint implements Fix {
99
private FileObject fileObject;
100
private FileObject targetFileObject;
101
private ElementHandle<TypeElement> classHandle;
102
private AccessType accessType;
103
private String targetEntityClassName;
104
private String localAttrName;
106
private String annotationClass;
107
private String complimentaryAnnotationClassName;
108
private String relationName;
110
private Collection<String> fieldsExistingAtTargetClass;
111
private Collection<String> compatibleFieldsExistingAtTargetClass;
113
public AbstractCreateRelationshipHint(FileObject fileObject,
114
ElementHandle<TypeElement> classHandle,
115
AccessType accessType,
116
String localAttrName,
117
String targetEntityClassName,
118
String annotationClass,
119
String complimentaryAnnotationClassName) {
121
this.classHandle = classHandle;
122
this.fileObject = fileObject;
123
this.accessType = accessType;
124
this.targetEntityClassName = targetEntityClassName;
125
this.annotationClass = annotationClass;
126
this.complimentaryAnnotationClassName = complimentaryAnnotationClassName;
127
this.localAttrName = localAttrName;
129
int dotPos = annotationClass.lastIndexOf('.');
130
relationName = dotPos > -1 ? annotationClass.substring(dotPos+1) : annotationClass;
133
public ChangeInfo implement(){
134
examineTargetClass();
135
boolean owningSide = isOwningSideByDefault();
136
String mappedBy = getExistingFieldInRelation();
138
if (mappedBy == null){
139
// couldn't get the corresponding field automatically, display dialog
141
CreateRelationshipPanel pnlPickOrCreateField = new CreateRelationshipPanel();
143
pnlPickOrCreateField.setEntityClassNames(
144
Utilities.getShortClassName(classHandle.getQualifiedName()),
145
Utilities.getShortClassName(targetEntityClassName));
147
pnlPickOrCreateField.setAvailableSelection(getAvailableRelationTypeSelection());
148
pnlPickOrCreateField.setAvailableFields(compatibleFieldsExistingAtTargetClass);
149
pnlPickOrCreateField.setDefaultFieldName(genDefaultFieldName());
150
pnlPickOrCreateField.setExistingFieldNames(fieldsExistingAtTargetClass);
152
DialogDescriptor ddesc = new DialogDescriptor(pnlPickOrCreateField,
153
NbBundle.getMessage(AbstractCreateRelationshipHint.class, "LBL_CreateRelationDlgTitle",
154
relationName, targetEntityClassName));
156
pnlPickOrCreateField.setDlgDescriptor(ddesc);
157
Dialog dlg = DialogDisplayer.getDefault().createDialog(ddesc);
158
dlg.setLocationRelativeTo(null);
159
dlg.setVisible(true);
161
if (ddesc.getValue() == DialogDescriptor.OK_OPTION){
162
String fieldName = null;
164
if (pnlPickOrCreateField.wasCreateNewFieldSelected()){
166
fieldName = pnlPickOrCreateField.getNewIdName();
169
fieldName = pnlPickOrCreateField.getSelectedField().toString();
172
owningSide = pnlPickOrCreateField.owningSide();
173
mappedBy = fieldName;
181
if (mappedBy != null){
182
modifyFiles(owningSide, mappedBy);
188
private void examineTargetClass(){
189
fieldsExistingAtTargetClass = new TreeSet<String>();
190
compatibleFieldsExistingAtTargetClass = new TreeSet<String>();
192
CancellableTask<CompilationController> task = new CancellableTask<CompilationController>(){
193
public void cancel() {}
195
public void run(CompilationController ccontrol) throws Exception {
196
ccontrol.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
198
TypeElement targetClass = ccontrol.getElements().getTypeElement(targetEntityClassName);
199
assert targetClass != null;
200
targetFileObject = org.netbeans.api.java.source.SourceUtils.getFile(targetClass,
201
ccontrol.getClasspathInfo());
203
for (VariableElement field : ElementFilter.fieldsIn(targetClass.getEnclosedElements())){
204
fieldsExistingAtTargetClass.add(field.getSimpleName().toString());
206
TypeMirror type = field.asType();
207
Element typeElement = null;
209
if (isMultiValuedAtTargetEntity()){
210
if (type.getKind() == TypeKind.DECLARED){
211
List<? extends TypeMirror> typeArgs = ((DeclaredType)type).getTypeArguments();
213
if (typeArgs.size() == 1){
214
typeElement = ccontrol.getTypes().asElement(typeArgs.get(0));
218
typeElement = ccontrol.getTypes().asElement(type);
221
if (typeElement != null && typeElement.getKind() == ElementKind.CLASS &&
222
((TypeElement)typeElement).getQualifiedName().contentEquals(classHandle.getQualifiedName())){
224
compatibleFieldsExistingAtTargetClass.add(field.getSimpleName().toString());
230
JavaSource javaSource = JavaSource.forFileObject(fileObject);
233
javaSource.runUserActionTask(task, true);
234
} catch (IOException e){
235
JPAProblemFinder.LOG.log(Level.SEVERE, e.getMessage(), e);
239
private String getExistingFieldInRelation(){
240
String mappedBy = null;
242
MetadataModel<EntityMappingsMetadata> emModel = ModelUtils.getModel(fileObject);
243
mappedBy = emModel.runReadAction(new MetadataModelAction<EntityMappingsMetadata, String>() {
245
public String run(EntityMappingsMetadata metadata) {
246
Entity remoteEntity = ModelUtils.getEntity(metadata, targetEntityClassName);
247
assert remoteEntity != null;
249
if (complimentaryAnnotationClassName.equals(JPAAnnotations.ONE_TO_ONE)){
250
return getMappedByFromOneToOne(remoteEntity);
253
if (complimentaryAnnotationClassName.equals(JPAAnnotations.MANY_TO_MANY)){
254
return getMappedByFromManyToMany(remoteEntity);
257
if (complimentaryAnnotationClassName.equals(JPAAnnotations.ONE_TO_MANY)){
258
return getMappedByFromOneToMany(remoteEntity);
264
} catch (MetadataModelException ex) {
265
JPAProblemFinder.LOG.log(Level.SEVERE, ex.getMessage(), ex);
266
} catch (IOException ex) {
267
JPAProblemFinder.LOG.log(Level.SEVERE, ex.getMessage(), ex);
273
private String getMappedByFromOneToOne(Entity remoteEntity){
274
for (OneToOne one2one : remoteEntity.getAttributes().getOneToOne()){
275
if (classHandle.getQualifiedName().equals(one2one.getTargetEntity())
276
&& localAttrName.equals(one2one.getMappedBy())){
277
return one2one.getName();
284
private String getMappedByFromManyToMany(Entity remoteEntity){
285
for (ManyToMany many2many : remoteEntity.getAttributes().getManyToMany()){
286
if (classHandle.getQualifiedName().equals(many2many.getTargetEntity())
287
&& localAttrName.equals(many2many.getMappedBy())){
288
return many2many.getName();
295
private String getMappedByFromOneToMany(Entity remoteEntity){
296
for (OneToMany oneToMany : remoteEntity.getAttributes().getOneToMany()){
297
if (classHandle.getQualifiedName().equals(oneToMany.getTargetEntity())
298
&& localAttrName.equals(oneToMany .getMappedBy())){
299
return oneToMany .getName();
306
private void modifyFiles(final boolean owningSide, final String mappedBy){
307
CancellableTask<WorkingCopy> task = new CancellableTask<WorkingCopy>(){
308
public void cancel() {}
310
public void run(WorkingCopy workingCopy) throws Exception {
311
workingCopy.toPhase(JavaSource.Phase.RESOLVED);
313
if (fileObject.equals(workingCopy.getFileObject())
314
&& targetFileObject.equals(workingCopy.getFileObject())){
315
modifyLocalClass(workingCopy, mappedBy, owningSide);
316
modifyTargetClass(workingCopy, mappedBy, !owningSide);
317
} else if (fileObject.equals(workingCopy.getFileObject())){
318
modifyLocalClass(workingCopy, mappedBy, owningSide);
320
modifyTargetClass(workingCopy, mappedBy, !owningSide);
325
ClasspathInfo cpi = ClasspathInfo.create(fileObject);
326
JavaSource javaSource = JavaSource.create(cpi, fileObject, targetFileObject);
329
javaSource.runModificationTask(task).commit();
330
} catch (IOException e){
331
JPAProblemFinder.LOG.log(Level.SEVERE, e.getMessage(), e);
335
private void modifyLocalClass(WorkingCopy workingCopy, String mappedBy, boolean owningSide) throws IOException{
336
TypeElement localClass = classHandle.resolve(workingCopy);
338
GenerationUtils genUtils = GenerationUtils.newInstance(workingCopy, localClass);
340
List<ExpressionTree> annArgs = null;
343
annArgs = Collections.singletonList(
344
genUtils.createAnnotationArgument("mappedBy", mappedBy)); //NOI18N
346
annArgs = Collections.<ExpressionTree>emptyList();
349
AnnotationTree ann = genUtils.createAnnotation(annotationClass, annArgs);
351
if (accessType == AccessType.FIELD){
352
VariableElement field = ModelUtils.getField(localClass, localAttrName);
353
VariableTree fieldTree = (VariableTree) workingCopy.getTrees().getTree(field);
354
VariableTree modifiedTree = genUtils.addAnnotation(fieldTree, ann);
355
workingCopy.rewrite(fieldTree, modifiedTree);
356
} else { // accessType == AccessType.PROPERTY
357
ExecutableElement accesor = ModelUtils.getAccesor(localClass, localAttrName);
358
MethodTree fieldTree = (MethodTree) workingCopy.getTrees().getTree(accesor);
359
MethodTree modifiedTree = genUtils.addAnnotation(fieldTree, ann);
360
workingCopy.rewrite(fieldTree, modifiedTree);
364
private void modifyTargetClass(WorkingCopy workingCopy, String mappedBy, boolean owningSide) throws IOException{
365
TypeElement targetClass = workingCopy.getElements().getTypeElement(targetEntityClassName);
366
assert targetClass != null;
368
ClassTree targetClassTree = workingCopy.getTrees().getTree(targetClass);
369
GenerationUtils genUtils = GenerationUtils.newInstance(workingCopy, targetClass);
371
String remoteFieldType = classHandle.getQualifiedName();
373
if (isMultiValuedAtTargetEntity()){
374
remoteFieldType = String.format("java.util.List<%s>", remoteFieldType); //NOI18N
377
VariableTree targetField = null;
378
VariableElement targetFieldElem = ModelUtils.getField(targetClass, mappedBy);
379
MethodTree targetFieldAccesor = resolveAccessor(targetFieldElem, targetClass, workingCopy);
381
ClassTree modifiedClass = targetClassTree;
382
if (targetFieldElem != null){
383
targetField = (VariableTree) workingCopy.getTrees().getTree(targetFieldElem);
385
ModifiersTree fieldModifiers = workingCopy.getTreeMaker().Modifiers(Collections.singleton(Modifier.PRIVATE));
386
targetField = genUtils.createField(fieldModifiers, mappedBy, remoteFieldType);
387
modifiedClass = genUtils.addClassFields(targetClassTree, Collections.singletonList(targetField));
390
AccessType targetEntityAccessType = findTargetEntityAccessType(targetClass);
391
if (AccessType.PROPERTY == targetEntityAccessType){
393
MethodTree targetFieldMutator = resolveMutator(targetFieldElem, targetClass, workingCopy);
394
ModifiersTree accessorMutatorModifiers = workingCopy.getTreeMaker().Modifiers(Collections.singleton(Modifier.PUBLIC));
396
if (targetFieldAccesor == null){
397
targetFieldAccesor = genUtils.createPropertyGetterMethod(accessorMutatorModifiers, mappedBy, remoteFieldType);
398
modifiedClass = workingCopy.getTreeMaker().addClassMember(modifiedClass, targetFieldAccesor);
401
if (targetFieldMutator == null) {
402
MethodTree mutator = genUtils.createPropertySetterMethod(accessorMutatorModifiers, mappedBy, remoteFieldType);
403
modifiedClass = workingCopy.getTreeMaker().addClassMember(modifiedClass, mutator);
406
workingCopy.rewrite(targetClassTree, modifiedClass);
409
List<ExpressionTree> targetAnnArgs = null;
412
targetAnnArgs = Collections.singletonList(
413
genUtils.createAnnotationArgument("mappedBy", localAttrName)); //NOI18N
415
targetAnnArgs = Collections.<ExpressionTree>emptyList();
418
AnnotationTree targetAnn = genUtils.createAnnotation(complimentaryAnnotationClassName, targetAnnArgs);
420
if (targetEntityAccessType == AccessType.FIELD){
421
VariableTree modifiedTree = genUtils.addAnnotation(targetField, targetAnn);
422
workingCopy.rewrite(targetField, modifiedTree);
423
} else { // accessType == AccessType.PROPERTY
424
MethodTree modifiedTree = genUtils.addAnnotation(targetFieldAccesor, targetAnn);
425
workingCopy.rewrite(targetFieldAccesor, modifiedTree);
429
private MethodTree resolveAccessor(VariableElement fieldElem, TypeElement clazz, WorkingCopy workingCopy){
430
return resolvePropertyMethod(fieldElem, clazz, workingCopy, true);
433
private MethodTree resolveMutator(VariableElement fieldElem, TypeElement clazz, WorkingCopy workingCopy){
434
return resolvePropertyMethod(fieldElem, clazz, workingCopy, false);
437
private MethodTree resolvePropertyMethod(VariableElement fieldElem, TypeElement clazz, WorkingCopy workingCopy, boolean accessor){
438
if (fieldElem == null){
441
VariableTree field = (VariableTree) workingCopy.getTrees().getTree(fieldElem);
443
ExecutableElement propertyMethod = accessor
444
? ModelUtils.getAccesor(clazz, field.getName().toString())
445
: ModelUtils.getMutator(workingCopy, clazz, fieldElem);
447
return propertyMethod == null ? null : workingCopy.getTrees().getTree(propertyMethod);
450
private AccessType findTargetEntityAccessType(final TypeElement targetEntityClass){
451
AccessType accessType = AccessType.INDETERMINED;
453
MetadataModel<EntityMappingsMetadata> emModel = ModelUtils.getModel(fileObject);
454
accessType = emModel.runReadAction(new MetadataModelAction<EntityMappingsMetadata, AccessType>() {
456
public AccessType run(EntityMappingsMetadata metadata) {
457
Entity remoteEntity = ModelUtils.getEntity(metadata, targetEntityClassName);
458
assert remoteEntity != null;
460
return JPAHelper.findAccessType(targetEntityClass, remoteEntity);
463
} catch (MetadataModelException ex) {
464
JPAProblemFinder.LOG.log(Level.SEVERE, ex.getMessage(), ex);
465
} catch (IOException ex) {
466
JPAProblemFinder.LOG.log(Level.SEVERE, ex.getMessage(), ex);
472
protected String genDefaultFieldName() {
473
String defaultFieldNameBase = Utilities.getShortClassName(classHandle.getQualifiedName());
475
char initial = Character.toLowerCase(defaultFieldNameBase.charAt(0));
476
defaultFieldNameBase = initial + defaultFieldNameBase.substring(1);
478
if (isMultiValuedAtTargetEntity()){
479
defaultFieldNameBase += "s"; //NOI18N
482
String defaultFieldName = null;
486
defaultFieldName = defaultFieldNameBase + (suffix == 0 ? "" : suffix); //NOI18N
489
while (fieldsExistingAtTargetClass.contains(defaultFieldName));
491
return defaultFieldName;
494
public String getText(){
495
return NbBundle.getMessage(AbstractCreateRelationshipHint.class,
496
"LBL_CreateRelationHint", relationName);
499
protected boolean isOwningSideByDefault() {
500
return getAvailableRelationTypeSelection() != CreateRelationshipPanel.AvailableSelection.INVERSE_ONLY;
503
protected CreateRelationshipPanel.AvailableSelection getAvailableRelationTypeSelection() {
504
return CreateRelationshipPanel.AvailableSelection.BOTH;
507
protected boolean isMultiValuedAtTargetEntity(){
508
return JPAAnnotations.MANY_TO_MANY.equals(complimentaryAnnotationClassName)
509
|| JPAAnnotations.ONE_TO_MANY.equals(complimentaryAnnotationClassName);
512
protected boolean isMultiValuedAtLocalEntity(){
513
return JPAAnnotations.MANY_TO_MANY.equals(annotationClass)
514
|| JPAAnnotations.MANY_TO_ONE.equals(annotationClass);
b'\\ No newline at end of file'