-
Notifications
You must be signed in to change notification settings - Fork 82
/
IntroduceFactoryRefactoring.java
1385 lines (1208 loc) · 60.3 KB
/
IntroduceFactoryRefactoring.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*******************************************************************************
* Copyright (c) 2000, 2020 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Samrat Dhillon <samrat.dhillon@gmail.com> - [introduce factory] Introduce Factory on an abstract class adds a statement to create an instance of that class - https://bugs.eclipse.org/bugs/show_bug.cgi?id=395016
* Stephan Herrmann - Contribution for Bug 463360 - [override method][null] generating method override should not create redundant null annotations
*******************************************************************************/
package org.eclipse.jdt.internal.corext.refactoring.code;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.TextEditGroup;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ConstructorInvocation;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.MethodRef;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.RecordDeclaration;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.jdt.core.dom.SuperConstructorInvocation;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeParameter;
import org.eclipse.jdt.core.dom.VariableDeclaration;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.core.refactoring.CompilationUnitChange;
import org.eclipse.jdt.core.refactoring.IJavaRefactorings;
import org.eclipse.jdt.core.refactoring.descriptors.IntroduceFactoryDescriptor;
import org.eclipse.jdt.core.refactoring.descriptors.JavaRefactoringDescriptor;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.internal.core.manipulation.BindingLabelProviderCore;
import org.eclipse.jdt.internal.core.manipulation.JavaElementLabelsCore;
import org.eclipse.jdt.internal.core.manipulation.StubUtility;
import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels;
import org.eclipse.jdt.internal.core.refactoring.descriptors.RefactoringSignatureDescriptorFactory;
import org.eclipse.jdt.internal.corext.codemanipulation.CodeGenerationSettings;
import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility2Core;
import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.dom.BodyDeclarationRewrite;
import org.eclipse.jdt.internal.corext.dom.ModifierRewrite;
import org.eclipse.jdt.internal.corext.refactoring.Checks;
import org.eclipse.jdt.internal.corext.refactoring.JDTRefactoringDescriptorComment;
import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringArguments;
import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringDescriptorUtil;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringScopeFactory;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringSearchEngine2;
import org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup;
import org.eclipse.jdt.internal.corext.refactoring.changes.DynamicValidationRefactoringChange;
import org.eclipse.jdt.internal.corext.refactoring.changes.DynamicValidationStateChange;
import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.ASTCreator;
import org.eclipse.jdt.internal.corext.refactoring.util.ResourceUtil;
import org.eclipse.jdt.internal.corext.util.JdtFlags;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.corext.util.MethodsSourcePositionComparator;
import org.eclipse.jdt.internal.corext.util.SearchUtils;
import org.eclipse.jdt.internal.ui.JavaUIStatus;
import org.eclipse.jdt.internal.ui.preferences.JavaPreferencesSettings;
import org.eclipse.jdt.internal.ui.util.Progress;
/**
* Refactoring class that permits the substitution of a factory method
* for direct calls to a given constructor.
* @author rfuhrer
*/
public class IntroduceFactoryRefactoring extends Refactoring {
private static final String ATTRIBUTE_PROTECT= "protect"; //$NON-NLS-1$
/**
* The handle for the compilation unit holding the selection that was
* passed into this refactoring.
*/
private ICompilationUnit fCUHandle;
/**
* The AST for the compilation unit holding the selection that was
* passed into this refactoring.
*/
private CompilationUnit fCU;
/**
* Handle for compilation unit in which the factory method/class/interface will be
* generated.
*/
private ICompilationUnit fFactoryUnitHandle;
/**
* The start of the original textual selection in effect when this refactoring
* was initiated. If the refactoring was initiated from a structured selection
* (e.g. from the outline view), then this refers to the textual selection that
* corresponds to the structured selection item.
*/
private int fSelectionStart;
/**
* The length of the original textual selection in effect when this refactoring
* was initiated. If the refactoring was initiated from a structured selection
* (e.g. from the outline view), then this refers to the textual selection that
* corresponds to the structured selection item.
*/
private int fSelectionLength;
/**
* The AST node corresponding to the user's textual selection.
*/
private ASTNode fSelectedNode;
/**
* The method binding for the selected constructor.
*/
private IMethodBinding fCtorBinding;
/**
* <code>TypeDeclaration</code> for class containing the constructor to be
* encapsulated.
*/
private AbstractTypeDeclaration fCtorOwningClass;
/**
* The name to be given to the generated factory method.
*/
private String fNewMethodName= null;
/**
* An array of <code>SearchResultGroup</code>'s of all call sites
* that refer to the constructor signature in question.
*/
private SearchResultGroup[] fAllCallsTo;
/**
* The class that will own the factory method/class/interface.
*/
private AbstractTypeDeclaration fFactoryOwningClass;
/**
* The newly-generated factory method.
*/
private MethodDeclaration fFactoryMethod= null;
/**
* An array containing the names of the constructor's formal arguments,
* if available, otherwise "arg1" ... "argN".
*/
private String[] fFormalArgNames= null;
/**
* An array of <code>ITypeBinding</code>'s that describes the types of
* the constructor arguments, in order.
*/
private ITypeBinding[] fArgTypes;
/**
* True iff the given constructor has a varargs signature.
*/
private boolean fCtorIsVarArgs;
/**
* If true, change the visibility of the constructor to protected to better
* encapsulate it.
*/
private boolean fProtectConstructor= true;
/**
* An <code>ImportRewrite</code> that manages imports needed to satisfy
* newly-introduced type references in the <code>ICompilationUnit</code>
* currently being rewritten during <code>createChange()</code>.
*/
private ImportRewrite fImportRewriter;
/**
* True iff there are call sites for the constructor to be encapsulated
* located in binary classes.
*/
private boolean fCallSitesInBinaryUnits;
/**
* <code>CompilationUnit</code> in which the factory is to be created.
*/
private CompilationUnit fFactoryCU;
/**
* The fully qualified name of the factory class. This is only used
* if invoked from a refactoring script.
*/
private String fFactoryClassName;
private int fConstructorVisibility= Modifier.PRIVATE;
/**
* Creates a new <code>IntroduceFactoryRefactoring</code> with the given selection
* on the given compilation unit.
* @param cu the <code>ICompilationUnit</code> in which the user selection was made, or <code>null</code> if invoked from scripting
* @param selectionStart the start of the textual selection in <code>cu</code>
* @param selectionLength the length of the textual selection in <code>cu</code>
*/
public IntroduceFactoryRefactoring(ICompilationUnit cu, int selectionStart, int selectionLength) {
Assert.isTrue(selectionStart >= 0);
Assert.isTrue(selectionLength >= 0);
fSelectionStart= selectionStart;
fSelectionLength= selectionLength;
fCUHandle= cu;
if (cu != null)
initialize();
}
public IntroduceFactoryRefactoring(JavaRefactoringArguments arguments, RefactoringStatus status) {
this(null, 0, 0);
RefactoringStatus initializeStatus= initialize(arguments);
status.merge(initializeStatus);
}
private void initialize() {
fCU= ASTCreator.createAST(fCUHandle, null);
}
/**
* Finds and returns the <code>ASTNode</code> for the given source text
* selection, if it is an entire constructor call or the class name portion
* of a constructor call or constructor declaration, or null otherwise.
* @param unit The compilation unit in which the selection was made
* @param offset The textual offset of the start of the selection
* @param length The length of the selection in characters
* @return ClassInstanceCreation or MethodDeclaration
*/
private ASTNode getTargetNode(ICompilationUnit unit, int offset, int length) {
ASTNode node= ASTNodes.getNormalizedNode(NodeFinder.perform(fCU, offset, length));
if (node.getNodeType() == ASTNode.CLASS_INSTANCE_CREATION)
return node;
if (node.getNodeType() == ASTNode.METHOD_DECLARATION && ((MethodDeclaration)node).isConstructor())
return node;
// we have some sub node. Make sure its the right child of the parent
StructuralPropertyDescriptor location= node.getLocationInParent();
ASTNode parent= node.getParent();
if (location == ClassInstanceCreation.TYPE_PROPERTY
|| (location == MethodDeclaration.NAME_PROPERTY && ((MethodDeclaration)parent).isConstructor())) {
return parent;
}
return null;
}
/**
* Determines what kind of AST node was selected, and returns an error status
* if the kind of node is inappropriate for this refactoring.
* @return a RefactoringStatus indicating whether the selection is valid
*/
private RefactoringStatus checkSelection(IProgressMonitor pm) throws JavaModelException {
try {
pm.beginTask(RefactoringCoreMessages.IntroduceFactory_examiningSelection, 2);
fSelectedNode= getTargetNode(fCUHandle, fSelectionStart, fSelectionLength);
if (fSelectedNode == null)
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_notAConstructorInvocation);
// getTargetNode() must return either a ClassInstanceCreation or a
// constructor MethodDeclaration; nothing else.
if (fSelectedNode instanceof ClassInstanceCreation) {
ClassInstanceCreation classInstanceCreation= (ClassInstanceCreation)fSelectedNode;
fCtorBinding= classInstanceCreation.resolveConstructorBinding();
} else if (fSelectedNode instanceof MethodDeclaration) {
MethodDeclaration methodDeclaration= (MethodDeclaration)fSelectedNode;
fCtorBinding= methodDeclaration.resolveBinding();
}
if (fCtorBinding == null)
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_unableToResolveConstructorBinding);
// If this constructor is of a generic type, get the generic version,
// not some instantiation thereof.
fCtorBinding= fCtorBinding.getMethodDeclaration();
pm.worked(1);
// We don't handle constructors of nested types at the moment
if (fCtorBinding.getDeclaringClass().isNested())
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_unsupportedNestedTypes);
ITypeBinding ctorType= fCtorBinding.getDeclaringClass();
IType ctorOwningType= (IType) ctorType.getJavaElement();
if (ctorOwningType.isBinary())
// Can't modify binary CU; don't know what CU to put factory method
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_constructorInBinaryClass);
if (ctorOwningType.isEnum())
// Doesn't make sense to encapsulate enum constructors
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_constructorInEnum);
// Put the generated factory method inside the type that owns the constructor
fFactoryUnitHandle= ctorOwningType.getCompilationUnit();
fFactoryCU= getASTFor(fFactoryUnitHandle);
Name ctorOwnerName= (Name) NodeFinder.perform(fFactoryCU, ctorOwningType.getNameRange());
fCtorOwningClass= ASTNodes.getParent(ctorOwnerName, AbstractTypeDeclaration.class);
fFactoryOwningClass= fCtorOwningClass;
pm.worked(1);
if (fNewMethodName == null)
return setNewMethodName("create" + fCtorBinding.getName());//$NON-NLS-1$
else
return new RefactoringStatus();
} finally {
pm.done();
}
}
/*
* @see org.eclipse.jdt.internal.corext.refactoring.base.Refactoring#checkActivation(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException {
try {
pm.beginTask(RefactoringCoreMessages.IntroduceFactory_checkingActivation, 1);
if (!fCUHandle.isStructureKnown())
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceFactory_syntaxError);
return checkSelection(Progress.subMonitor(pm, 1));
} finally {
pm.done();
}
}
/**
* @return the set of compilation units that will be affected by this
* particular invocation of this refactoring. This in general includes
* the class containing the constructor in question, as well as all
* call sites to the constructor.
*/
private ICompilationUnit[] collectAffectedUnits(SearchResultGroup[] searchHits) {
Collection<ICompilationUnit> result= new ArrayList<>();
boolean hitInFactoryClass= false;
for (SearchResultGroup rg : searchHits) {
ICompilationUnit icu= rg.getCompilationUnit();
result.add(icu);
if (icu.equals(fFactoryUnitHandle))
hitInFactoryClass= true;
}
if (!hitInFactoryClass)
result.add(fFactoryUnitHandle);
return result.toArray(new ICompilationUnit[result.size()]);
}
/**
* @return a <code>SearchPattern</code> that finds all calls to the constructor
* identified by the argument <code>methodBinding</code>.
*/
private SearchPattern createSearchPattern(IMethod ctor, IMethodBinding methodBinding) {
Assert.isNotNull(methodBinding,
RefactoringCoreMessages.IntroduceFactory_noBindingForSelectedConstructor);
if (ctor != null)
return SearchPattern.createPattern(ctor, IJavaSearchConstants.REFERENCES, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE);
else { // perhaps a synthetic method? (but apparently not always... hmmm...)
// Can't find an IMethod for this method, so build a string pattern instead
StringBuilder buf= new StringBuilder();
buf.append(methodBinding.getDeclaringClass().getQualifiedName())
.append("(");//$NON-NLS-1$
for(int i=0; i < fArgTypes.length; i++) {
if (i != 0)
buf.append(","); //$NON-NLS-1$
buf.append(fArgTypes[i].getQualifiedName());
}
buf.append(")"); //$NON-NLS-1$
return SearchPattern.createPattern(buf.toString(), IJavaSearchConstants.CONSTRUCTOR,
IJavaSearchConstants.REFERENCES, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE);
}
}
private IJavaSearchScope createSearchScope(IMethod ctor, IMethodBinding binding) throws JavaModelException {
if (ctor != null) {
return RefactoringScopeFactory.create(ctor);
} else {
ITypeBinding type= Bindings.getTopLevelType(binding.getDeclaringClass());
return RefactoringScopeFactory.create(type.getJavaElement());
}
}
/**
* @return an array of <code>SearchResultGroup</code>'s like the argument,
* but omitting those groups that have no corresponding compilation unit
* (i.e. are binary and therefore can't be modified).
*/
private SearchResultGroup[] excludeBinaryUnits(SearchResultGroup[] groups) {
Collection<SearchResultGroup> result= new ArrayList<>();
for (SearchResultGroup rg : groups) {
ICompilationUnit unit= rg.getCompilationUnit();
if (unit != null) // ignore hits within a binary unit
result.add(rg);
else
fCallSitesInBinaryUnits= true;
}
return result.toArray(new SearchResultGroup[result.size()]);
}
/**
* Search for all calls to the given <code>IMethodBinding</code> in the project
* that contains the compilation unit <code>fCUHandle</code>.
* @return an array of <code>SearchResultGroup</code>'s that identify the search matches
*/
private SearchResultGroup[] searchForCallsTo(IMethodBinding methodBinding, IProgressMonitor pm, RefactoringStatus status) throws JavaModelException {
IMethod method= (IMethod) methodBinding.getJavaElement();
SearchPattern searchPattern= createSearchPattern(method, methodBinding);
if (searchPattern == null) {
return new SearchResultGroup[0];
}
final RefactoringSearchEngine2 engine= new RefactoringSearchEngine2(searchPattern);
engine.setFiltering(true, true);
engine.setScope(createSearchScope(method, methodBinding));
engine.setStatus(status);
engine.searchPattern(Progress.subMonitor(pm, 1));
return (SearchResultGroup[]) engine.getResults();
}
/**
* Returns an array of <code>SearchResultGroup</code>'s containing all method
* calls in the Java project that invoke the constructor identified by the given
* <code>IMethodBinding</code>
* @param ctorBinding an <code>IMethodBinding</code> identifying a particular
* constructor signature to search for
* @param pm an <code>IProgressMonitor</code> to use during this potentially
* lengthy operation
* @return an array of <code>SearchResultGroup</code>'s identifying all
* calls to the given constructor signature
*/
private SearchResultGroup[] findAllCallsTo(IMethodBinding ctorBinding, IProgressMonitor pm, RefactoringStatus status) throws JavaModelException {
SearchResultGroup[] groups= excludeBinaryUnits(searchForCallsTo(ctorBinding, pm, status));
return groups;
}
private IType findNonPrimaryType(String fullyQualifiedName, IProgressMonitor pm, RefactoringStatus status) throws JavaModelException {
SearchPattern p= SearchPattern.createPattern(fullyQualifiedName, IJavaSearchConstants.TYPE, IJavaSearchConstants.DECLARATIONS, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE);
if (p == null) {
return null;
}
final RefactoringSearchEngine2 engine= new RefactoringSearchEngine2(p);
engine.setFiltering(true, true);
engine.setScope(RefactoringScopeFactory.create(fCtorBinding.getDeclaringClass().getJavaElement().getJavaProject()));
engine.setStatus(status);
engine.searchPattern(Progress.subMonitor(pm, 1));
SearchResultGroup[] groups= (SearchResultGroup[]) engine.getResults();
for (SearchResultGroup rg : groups) {
for (SearchMatch match : rg.getSearchResults()) {
if (match.getAccuracy() == SearchMatch.A_ACCURATE) {
return (IType) match.getElement();
}
}
}
return null;
}
/*
* @see org.eclipse.jdt.internal.corext.refactoring.base.Refactoring#checkInput(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException {
try {
pm.beginTask(RefactoringCoreMessages.IntroduceFactory_checking_preconditions, 1);
RefactoringStatus result= new RefactoringStatus();
if (fFactoryClassName != null)
result.merge(setFactoryClass(fFactoryClassName));
if (result.hasFatalError())
return result;
fArgTypes= fCtorBinding.getParameterTypes();
fCtorIsVarArgs= fCtorBinding.isVarargs();
fAllCallsTo= findAllCallsTo(fCtorBinding, pm, result);
fFormalArgNames= findCtorArgNames();
ICompilationUnit[] affectedFiles= collectAffectedUnits(fAllCallsTo);
result.merge(Checks.validateModifiesFiles(ResourceUtil.getFiles(affectedFiles), getValidationContext(), pm));
if (fCallSitesInBinaryUnits)
result.merge(RefactoringStatus.createWarningStatus(RefactoringCoreMessages.IntroduceFactory_callSitesInBinaryClass));
if(Modifier.isAbstract(fCtorBinding.getDeclaringClass().getModifiers())){
result.merge(RefactoringStatus.createWarningStatus(RefactoringCoreMessages.IntroduceFactory_abstractClass));
}
return result;
} finally {
pm.done();
}
}
/**
* @return an array containing the argument names for the constructor
* identified by <code>fCtorBinding</code>, if available, or default
* names if unavailable (e.g. if the constructor resides in a binary unit).
*/
private String[] findCtorArgNames() {
int numArgs= fCtorBinding.getParameterTypes().length;
String[] names= new String[numArgs];
CompilationUnit ctorUnit= ASTNodes.getParent(fCtorOwningClass, CompilationUnit.class);
MethodDeclaration ctorDecl= (MethodDeclaration) ctorUnit.findDeclaringNode(fCtorBinding.getKey());
if (ctorDecl != null) {
List<SingleVariableDeclaration> formalArgs= ctorDecl.parameters();
int i= 0;
if (formalArgs.size() == 0 && numArgs > 0 && fCtorBinding.isCompactConstructor()) {
ASTNode parent= ctorDecl.getParent();
if (parent instanceof RecordDeclaration) {
RecordDeclaration recDecl= (RecordDeclaration) parent;
formalArgs= recDecl.recordComponents();
}
}
for(Iterator<SingleVariableDeclaration> iter= formalArgs.iterator(); iter.hasNext(); i++) {
SingleVariableDeclaration svd= iter.next();
names[i]= svd.getName().getIdentifier();
}
return names;
}
// Have no way of getting the formal argument names; just fake it.
for(int i=0; i < numArgs; i++)
names[i]= "arg" + (i+1); //$NON-NLS-1$
return names;
}
/**
* Creates and returns a new MethodDeclaration that represents the factory method to be used in
* place of direct calls to the constructor in question.
*
* @param ast An AST used as a factory for various AST nodes
* @param ctorBinding binding for the constructor being wrapped
* @param unitRewriter the ASTRewrite to be used
* @return the new method declaration
* @throws CoreException if an exception occurs while accessing its corresponding resource
*/
private MethodDeclaration createFactoryMethod(AST ast, IMethodBinding ctorBinding, ASTRewrite unitRewriter) throws CoreException{
MethodDeclaration newMethod= ast.newMethodDeclaration();
SimpleName newMethodName= ast.newSimpleName(fNewMethodName);
ClassInstanceCreation newCtorCall= ast.newClassInstanceCreation();
ReturnStatement ret= ast.newReturnStatement();
Block body= ast.newBlock();
List<Statement> stmts= body.statements();
String retTypeName= ctorBinding.getName();
createFactoryMethodSignature(ast, newMethod);
newMethod.setName(newMethodName);
newMethod.setBody(body);
ITypeBinding declaringClass= fCtorBinding.getDeclaringClass();
ITypeBinding[] ctorOwnerTypeParameters= declaringClass.getTypeParameters();
setMethodReturnType(newMethod, retTypeName, ctorOwnerTypeParameters, ast);
newMethod.modifiers().addAll(ASTNodeFactory.newModifiers(ast, Modifier.STATIC | Modifier.PUBLIC));
setCtorTypeArguments(newCtorCall, retTypeName, ctorOwnerTypeParameters, ast);
createFactoryMethodConstructorArgs(ast, newCtorCall);
if (Modifier.isAbstract(declaringClass.getModifiers())) {
AnonymousClassDeclaration decl= ast.newAnonymousClassDeclaration();
CodeGenerationSettings settings= JavaPreferencesSettings.getCodeGenerationSettings(fCUHandle.getJavaProject());
ImportRewriteContext context= new ContextSensitiveImportRewriteContext(fFactoryCU, decl.getStartPosition(), fImportRewriter);
for (IMethodBinding unImplementedMethod : getUnimplementedMethods(declaringClass)) {
MethodDeclaration newMethodDecl= StubUtility2Core.createImplementationStub(fCUHandle, unitRewriter, fImportRewriter, context,
unImplementedMethod, unImplementedMethod.getDeclaringClass(), settings, false, new NodeFinder(fFactoryCU, decl.getStartPosition(), 0).getCoveringNode());
decl.bodyDeclarations().add(newMethodDecl);
}
newCtorCall.setAnonymousClassDeclaration(decl);
}
ret.setExpression(newCtorCall);
stmts.add(ret);
return newMethod;
}
private IMethodBinding[] getUnimplementedMethods(ITypeBinding binding) {
IMethodBinding[] unimplementedMethods= StubUtility2Core.getUnimplementedMethods(binding, null);
Arrays.sort(unimplementedMethods, new MethodsSourcePositionComparator(binding));
return unimplementedMethods;
}
/**
* Sets the type being instantiated in the given constructor call, including
* specifying any necessary type arguments.
* @param newCtorCall the constructor call to modify
* @param ctorTypeName the simple name of the type being instantiated
* @param ctorOwnerTypeParameters the formal type parameters of the type being
* instantiated
* @param ast utility object used to create AST nodes
*/
private void setCtorTypeArguments(ClassInstanceCreation newCtorCall, String ctorTypeName, ITypeBinding[] ctorOwnerTypeParameters, AST ast) {
if (ctorOwnerTypeParameters.length == 0) // easy, just a simple type
newCtorCall.setType(ASTNodeFactory.newType(ast, ctorTypeName));
else {
Type baseType= ast.newSimpleType(ast.newSimpleName(ctorTypeName));
ParameterizedType newInstantiatedType= ast.newParameterizedType(baseType);
List<Type> newInstTypeArgs= newInstantiatedType.typeArguments();
for (ITypeBinding ctorOwnerTypeParameter : ctorOwnerTypeParameters) {
Type typeArg= ASTNodeFactory.newType(ast, ctorOwnerTypeParameter.getName());
newInstTypeArgs.add(typeArg);
}
newCtorCall.setType(newInstantiatedType);
}
}
/**
* Sets the return type of the factory method, including any necessary type
* arguments. E.g., for constructor <code>Foo()</code> in <code>Foo<T></code>,
* the factory method defines a method type parameter <code><T></code> and
* returns a <code>Foo<T></code>.
* @param newMethod the method whose return type is to be set
* @param retTypeName the simple name of the return type (without type parameters)
* @param ctorOwnerTypeParameters the formal type parameters of the type that the
* factory method instantiates (whose constructor is being encapsulated)
* @param ast utility object used to create AST nodes
*/
private void setMethodReturnType(MethodDeclaration newMethod, String retTypeName, ITypeBinding[] ctorOwnerTypeParameters, AST ast) {
if (ctorOwnerTypeParameters.length == 0)
newMethod.setReturnType2(ast.newSimpleType(ast.newSimpleName(retTypeName)));
else {
Type baseType= ast.newSimpleType(ast.newSimpleName(retTypeName));
ParameterizedType newRetType= ast.newParameterizedType(baseType);
List<Type> newRetTypeArgs= newRetType.typeArguments();
for (ITypeBinding ctorOwnerTypeParameter : ctorOwnerTypeParameters) {
Type retTypeArg= ASTNodeFactory.newType(ast, ctorOwnerTypeParameter.getName());
newRetTypeArgs.add(retTypeArg);
}
newMethod.setReturnType2(newRetType);
}
}
/**
* Creates and adds the necessary argument declarations to the given factory method.<br>
* An argument is needed for each original constructor argument for which the
* evaluation of the actual arguments across all calls was not able to be
* pushed inside the factory method (e.g. arguments with side-effects, references
* to fields if the factory method is to be static or reside in a factory class,
* or arguments that varied across the set of constructor calls).<br>
* <code>fArgTypes</code> identifies such arguments by a <code>null</code> value.
* @param ast utility object used to create AST nodes
* @param newMethod the <code>MethodDeclaration</code> for the factory method
*/
private void createFactoryMethodSignature(AST ast, MethodDeclaration newMethod) {
List<SingleVariableDeclaration> argDecls= newMethod.parameters();
for(int i=0; i < fArgTypes.length; i++) {
SingleVariableDeclaration argDecl= ast.newSingleVariableDeclaration();
Type argType;
if (i == (fArgTypes.length - 1) && fCtorIsVarArgs) {
// The trailing varargs arg has an extra array dimension, compared to
// what we need to pass to setType()...
argType= typeNodeForTypeBinding(fArgTypes[i].getElementType(),
fArgTypes[i].getDimensions()-1, ast);
argDecl.setVarargs(true);
} else
argType= typeNodeForTypeBinding(fArgTypes[i], 0, ast);
argDecl.setName(ast.newSimpleName(fFormalArgNames[i]));
argDecl.setType(argType);
argDecls.add(argDecl);
}
List<Type> exceptions= newMethod.thrownExceptionTypes();
for (ITypeBinding ctorExcept : fCtorBinding.getExceptionTypes()) {
exceptions.add(fImportRewriter.addImport(ctorExcept, ast));
}
copyTypeParameters(ast, newMethod);
}
/**
* Copies the constructor's parent type's type parameters, if any, as
* method type parameters of the new static factory method. (Recall
* that static methods can't refer to type arguments of the enclosing
* class, since they have no instance to serve as a context.)<br>
* Makes sure to copy the bounds from the owning type, to ensure that the
* return type of the factory method satisfies the bounds of the type
* being instantiated.<br>
* E.g., for ctor Foo() in the type Foo<T extends Number>, be sure that
* the factory method is declared as<br>
* <code>static <T extends Number> Foo<T> createFoo()</code><br>
* and not simply<br>
* <code>static <T> Foo<T> createFoo()</code><br>
* or the compiler will bark.
* @param ast utility object needed to create ASTNode's for the new method
* @param newMethod the method onto which to copy the type parameters
*/
private void copyTypeParameters(AST ast, MethodDeclaration newMethod) {
List<TypeParameter> factoryMethodTypeParms= newMethod.typeParameters();
for (ITypeBinding ctorOwnerTypeParm : fCtorBinding.getDeclaringClass().getTypeParameters()) {
TypeParameter newParm= ast.newTypeParameter();
List<Type> newParmBounds= newParm.typeBounds();
newParm.setName(ast.newSimpleName(ctorOwnerTypeParm.getName()));
for (ITypeBinding parmTypeBound : ctorOwnerTypeParm.getTypeBounds()) {
if (parmTypeBound.isClass() && parmTypeBound.getSuperclass() == null) {
continue;
}
Type newBound= fImportRewriter.addImport(parmTypeBound, ast);
newParmBounds.add(newBound);
}
factoryMethodTypeParms.add(newParm);
}
}
/**
* @param extraDims number of extra array dimensions to add to the resulting type
* @return a Type that describes the given ITypeBinding. If the binding
* refers to an object type, use the import rewriter to determine whether
* the reference requires a new import, or instead needs to be qualified.<br>
* Like ASTNodeFactory.newType(), but for the handling of imports.
*/
private Type typeNodeForTypeBinding(ITypeBinding argType, int extraDims, AST ast) {
if (extraDims > 0) {
return ast.newArrayType(typeNodeForTypeBinding(argType, 0, ast), extraDims);
} else if (argType.isArray()) {
Type elementType= typeNodeForTypeBinding(argType.getElementType(), extraDims, ast);
return ast.newArrayType(elementType, argType.getDimensions());
} else {
return fImportRewriter.addImport(argType, ast);
}
}
/**
* Create the list of actual arguments to the constructor call that is
* encapsulated inside the factory method, and associate the arguments
* with the given constructor call object.
* @param ast utility object used to create AST nodes
* @param newCtorCall the newly-generated constructor call to be wrapped inside
* the factory method
*/
private void createFactoryMethodConstructorArgs(AST ast, ClassInstanceCreation newCtorCall) {
List<Expression> argList= newCtorCall.arguments();
for(int i=0; i < fArgTypes.length; i++) {
ASTNode ctorArg= ast.newSimpleName(fFormalArgNames[i]);
argList.add((Expression) ctorArg);
}
}
/**
* Updates the constructor call.
*
* @param ctorCall the ClassInstanceCreation to be marked as replaced
* @param unitRewriter the AST rewriter
* @param gd the edit group to use
*/
private void rewriteFactoryMethodCall(ClassInstanceCreation ctorCall, ASTRewrite unitRewriter, TextEditGroup gd) {
AST ast= unitRewriter.getAST();
MethodInvocation factoryMethodCall= ast.newMethodInvocation();
ASTNode ctorCallParent= ctorCall.getParent();
StructuralPropertyDescriptor ctorCallLocation= ctorCall.getLocationInParent();
if (ctorCallLocation instanceof ChildListPropertyDescriptor) {
ListRewrite ctorCallParentListRewrite= unitRewriter.getListRewrite(ctorCallParent, (ChildListPropertyDescriptor)ctorCallLocation);
int index= ctorCallParentListRewrite.getOriginalList().indexOf(ctorCall);
ctorCall= (ClassInstanceCreation)ctorCallParentListRewrite.getRewrittenList().get(index);
} else {
ctorCall= (ClassInstanceCreation)unitRewriter.get(ctorCallParent, ctorCallLocation);
}
ListRewrite actualFactoryArgs= unitRewriter.getListRewrite(factoryMethodCall, MethodInvocation.ARGUMENTS_PROPERTY);
ListRewrite actualCtorArgs= unitRewriter.getListRewrite(ctorCall, ClassInstanceCreation.ARGUMENTS_PROPERTY);
// Need to use a qualified name for the factory method if we're not
// in the context of the class holding the factory.
AbstractTypeDeclaration callOwner= ASTNodes.getParent(ctorCall, AbstractTypeDeclaration.class);
ITypeBinding callOwnerBinding= callOwner.resolveBinding();
if (callOwnerBinding == null || !Bindings.equals(callOwner.resolveBinding(), fFactoryOwningClass.resolveBinding())) {
String qualifier= fImportRewriter.addImport(fFactoryOwningClass.resolveBinding());
factoryMethodCall.setExpression(ASTNodeFactory.newName(ast, qualifier));
}
factoryMethodCall.setName(ast.newSimpleName(fNewMethodName));
List<Expression> actualCtorArgsList= actualCtorArgs.getRewrittenList();
for (Expression actualCtorArg : actualCtorArgsList) {
ASTNode movedArg;
if (ASTNodes.isExistingNode(actualCtorArg)) {
movedArg= unitRewriter.createMoveTarget(actualCtorArg);
} else {
unitRewriter.remove(actualCtorArg, gd);
movedArg= actualCtorArg;
}
actualFactoryArgs.insertLast(movedArg, gd);
}
unitRewriter.replace(ctorCall, factoryMethodCall, gd);
}
/**
* @return true iff the given <code>ICompilationUnit</code> is the unit
* containing the original constructor
*/
private boolean isConstructorUnit(ICompilationUnit unit) {
return unit.equals(ASTCreator.getCu(fCtorOwningClass));
}
/**
* @return true iff we should actually change the original constructor's
* visibility to <code>protected</code>. This takes into account the user-
* requested mode and whether the constructor's compilation unit is in
* source form.
*/
private boolean shouldProtectConstructor() {
return fProtectConstructor && fCtorOwningClass != null;
}
/**
* Creates and adds the necessary change to make the constructor method protected.
* @return false iff the constructor didn't exist (i.e. was implicit)
*/
private boolean protectConstructor(CompilationUnit unitAST, ASTRewrite unitRewriter, TextEditGroup declGD) {
MethodDeclaration constructor= (MethodDeclaration) unitAST.findDeclaringNode(fCtorBinding.getKey());
// No need to rewrite the modifiers if the visibility is what we already want it to be.
if (constructor == null || (JdtFlags.getVisibilityCode(constructor)) == fConstructorVisibility)
return false;
ModifierRewrite.create(unitRewriter, constructor).setVisibility(fConstructorVisibility, declGD);
return true;
}
/**
* Add all changes necessary on the <code>ICompilationUnit</code> in the given
* <code>SearchResultGroup</code> to implement the refactoring transformation
* to the given <code>CompilationUnitChange</code>.
* @param rg the <code>SearchResultGroup</code> for which changes should be created
* @param unitChange the CompilationUnitChange object for the compilation unit in question
* @return <code>true</code> iff a change has been added
*/
private boolean addAllChangesFor(SearchResultGroup rg, ICompilationUnit unitHandle, CompilationUnitChange unitChange) throws CoreException {
// ICompilationUnit unitHandle= rg.getCompilationUnit();
Assert.isTrue(rg == null || rg.getCompilationUnit() == unitHandle);
CompilationUnit unit= getASTFor(unitHandle);
ASTRewrite unitRewriter= ASTRewrite.create(unit.getAST());
MultiTextEdit root= new MultiTextEdit();
boolean someChange= false;
unitChange.setEdit(root);
fImportRewriter= StubUtility.createImportRewrite(unit, true);
// First create the factory method
if (unitHandle.equals(fFactoryUnitHandle)) {
TextEditGroup factoryGD= new TextEditGroup(RefactoringCoreMessages.IntroduceFactory_addFactoryMethod);
createFactoryChange(unitRewriter, unit, factoryGD);
unitChange.addTextEditGroup(factoryGD);
someChange= true;
}
// Now rewrite all the constructor calls to use the factory method
if (rg != null)
if (replaceConstructorCalls(rg, unit, unitRewriter, unitChange))
someChange= true;
// Finally, make the constructor private, if requested.
if (shouldProtectConstructor() && isConstructorUnit(unitHandle)) {
TextEditGroup declGD= new TextEditGroup(RefactoringCoreMessages.IntroduceFactory_protectConstructor);
if (protectConstructor(unit, unitRewriter, declGD)) {
unitChange.addTextEditGroup(declGD);
someChange= true;
}
}
if (someChange) {
root.addChild(unitRewriter.rewriteAST());
root.addChild(fImportRewriter.rewriteImports(null));
}
return someChange;
}
/**
* @return an AST for the given compilation unit handle.<br>
* If this is the unit containing the selection or the unit in which the factory
* is to reside, checks the appropriate field (<code>fCU</code> or <code>fFactoryCU</code>,
* respectively) and initializes the field with a new AST only if not already done.
*/
private CompilationUnit getASTFor(ICompilationUnit unitHandle) {
if (unitHandle.equals(fCUHandle)) { // is this the unit containing the selection?
if (fCU == null) {
fCU= ASTCreator.createAST(unitHandle, null);
if (fCUHandle.equals(fFactoryUnitHandle)) // if selection unit and factory unit are the same...
fFactoryCU= fCU; // ...make sure the factory unit gets initialized
}
return fCU;
} else if (unitHandle.equals(fFactoryUnitHandle)) { // is this the "factory unit"?
if (fFactoryCU == null)
fFactoryCU= ASTCreator.createAST(unitHandle, null);
return fFactoryCU;
} else
return ASTCreator.createAST(unitHandle, null);
}
/**
* Use the given <code>ASTRewrite</code> to replace direct calls to the constructor with calls
* to the newly-created factory method.
*
* @param rg the <code>SearchResultGroup</code> indicating all of the constructor references
* @param unit the <code>CompilationUnit</code> to be rewritten
* @param unitRewriter the rewriter
* @param unitChange the compilation unit change
* @return true iff at least one constructor call site was rewritten.
*/
private boolean replaceConstructorCalls(SearchResultGroup rg, CompilationUnit unit, ASTRewrite unitRewriter, CompilationUnitChange unitChange) throws CoreException {
Assert.isTrue(ASTCreator.getCu(unit).equals(rg.getCompilationUnit()));
SearchMatch[] hits= rg.getSearchResults();
/*
* Sort by descending offset, such that nested constructor calls are processed first. This
* is necessary, since they can only be moved into the factory method invocation after they
* have been rewritten.
*/
Arrays.sort(hits, (m1, m2) -> m2.getOffset() - m1.getOffset());
boolean someCallPatched= false;