]> git.uio.no Git - ifi-stolz-refaktor.git/blob - software/no.uio.ifi.refaktor/src/no/uio/ifi/refaktor/changers/ExtractAndMoveMethodChanger.java
Adding some JavaDoc and cleaning up a bit.
[ifi-stolz-refaktor.git] / software / no.uio.ifi.refaktor / src / no / uio / ifi / refaktor / changers / ExtractAndMoveMethodChanger.java
1 package no.uio.ifi.refaktor.changers;
2
3 import java.lang.reflect.Modifier;
4 import java.util.AbstractList;
5 import java.util.Collections;
6 import java.util.Comparator;
7 import java.util.LinkedList;
8 import java.util.List;
9 import java.util.Random;
10
11 import no.uio.ifi.refaktor.extractors.ExtractAndMoveMethodPrefixesExtractor;
12 import no.uio.ifi.refaktor.extractors.Prefix;
13 import no.uio.ifi.refaktor.extractors.PrefixSet;
14 import no.uio.ifi.refaktor.utils.ParseUtils;
15 import no.uio.ifi.refaktor.utils.RefaktorDebug;
16 import no.uio.ifi.refaktor.utils.RefaktorHandleUtils;
17 import no.uio.ifi.refaktor.utils.SmartTextSelection;
18
19 import org.eclipse.core.commands.ExecutionException;
20 import org.eclipse.core.resources.IProject;
21 import org.eclipse.core.runtime.Assert;
22 import org.eclipse.core.runtime.CoreException;
23 import org.eclipse.core.runtime.IProgressMonitor;
24 import org.eclipse.jdt.core.ICompilationUnit;
25 import org.eclipse.jdt.core.IJavaProject;
26 import org.eclipse.jdt.core.IMethod;
27 import org.eclipse.jdt.core.JavaCore;
28 import org.eclipse.jdt.core.JavaModelException;
29 import org.eclipse.jdt.core.dom.ASTNode;
30 import org.eclipse.jdt.core.dom.CompilationUnit;
31 import org.eclipse.jdt.core.dom.IVariableBinding;
32 import org.eclipse.jdt.core.dom.MethodDeclaration;
33 import org.eclipse.jdt.core.dom.SimpleName;
34 import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
35 import org.eclipse.jdt.core.dom.TypeDeclaration;
36 import org.eclipse.jdt.internal.corext.refactoring.code.ExtractMethodRefactoring;
37 import org.eclipse.jdt.internal.corext.refactoring.structure.ASTNodeSearchUtil;
38 import org.eclipse.jdt.internal.corext.refactoring.structure.MoveInstanceMethodProcessor;
39 import org.eclipse.ltk.core.refactoring.CheckConditionsOperation;
40 import org.eclipse.ltk.core.refactoring.CreateChangeOperation;
41 import org.eclipse.ltk.core.refactoring.IUndoManager;
42 import org.eclipse.ltk.core.refactoring.Refactoring;
43 import org.eclipse.ltk.core.refactoring.participants.MoveRefactoring;
44
45 /**
46  * This class composes the refactorings known as
47  * Extract Method and Move Method.
48  * 
49  * Before extracting, it finds the possible targets for the move 
50  * with the help of a PropertyExtractor (see {@link ExtractAndMoveMethodPrefixesExtractor}), 
51  * that extracts both the candidates, in the form prefixes (see {@link Prefix}), 
52  * and the non-candidates, called unfixes. They are collected into sets
53  * of prefixes (see {@link PrefixSet}).The set of prefixes that 
54  * are not enclosing any unfixes is put in the set of safe prefixes.
55  * 
56  * The changer then tries to analyze which of the safe prefixes 
57  * that is the best candidate. The best candidate is used to find the
58  * target of the composed refactoring.
59  */
60 @SuppressWarnings("restriction")
61 public class ExtractAndMoveMethodChanger extends RefaktorChanger {
62         
63         public enum Reason {
64                 SELECTION_INVALID("The selected text is not valid for this refactoring."),
65                 NO_TARGET_FOUND("Could not find target for move."),
66                 PROJECT_NOT_EXISTING("The project for the editor where the command was issued is not existing!"),
67                 PROJECT_NOT_OPEN("The project for the editor where the command was issued is not open!"),
68                 NO_TYPES_FOUND("No types declared in file."), 
69                 RESOURCE_ACCESS_ERROR("An error occurred while accessing resources.");
70                 
71                 private final String message;
72
73                 private Reason(String message) {
74                         this.message = message;
75                 }
76                 
77                 public String getMessage() {
78                         return message;
79                 }
80         }
81
82         private IProject project;
83         private SmartTextSelection smartTextSelection;
84         private ExtractAndMoveMethodPrefixesExtractor extractor;
85         private Reason reason;
86         private String newMethodName;
87
88         public ExtractAndMoveMethodChanger(IProject project, SmartTextSelection smartTextSelection) {
89                 this(project, smartTextSelection, generateName());
90         }
91         
92         public ExtractAndMoveMethodChanger(IProject project, SmartTextSelection smartTextSelection, String newMethodName) {
93                 this.project = project;
94                 this.smartTextSelection = smartTextSelection;
95                 this.setNewMethodName(newMethodName);
96                 try {
97                         initializeExtractor();
98                 } catch (ExecutionException e) {
99                         new RuntimeException(e);
100                 }
101         }
102         
103         public static String generateName() {
104                 return "generated_" + Math.abs(new Random().nextLong());
105         }
106
107         public boolean preconditionsAreMet() throws ExecutionException {
108                 if (initializationError())
109                         return false;
110                 if (!extractor.selectionIsValid()) {
111                         setReason(Reason.SELECTION_INVALID);
112                         return false;
113                 }
114                 extractor.extractProperty();
115                 if (!extractor.hasUsefulResults()) {
116                         setReason(Reason.NO_TARGET_FOUND);
117                         return false;
118                 }
119                 return true;
120         }
121
122         private boolean initializationError() {
123                 return reason != null;
124         }
125
126         public void initializeExtractor() throws ExecutionException {
127                 try {
128                         assert project.hasNature(JavaCore.NATURE_ID);
129                 } catch (CoreException e) {
130                         if (!project.exists())
131                                 setReason(Reason.PROJECT_NOT_EXISTING);
132                         else
133                                 setReason(Reason.PROJECT_NOT_OPEN);
134                         return;
135                 }
136                 IJavaProject javaProject = JavaCore.create(project);
137
138                 ICompilationUnit icu = null;
139                 ASTNode selectedNode = smartTextSelection.getNodeFinder().getCoveredNode();
140                 CompilationUnit cu = (CompilationUnit) selectedNode.getRoot();
141                 List<?> types = cu.types();
142                 if (types.isEmpty()) {
143                         setReason(Reason.NO_TYPES_FOUND);
144                         return;
145                 }
146                 TypeDeclaration typeDecl = (TypeDeclaration) types.get(0);
147                 SimpleName typeName = typeDecl.getName();
148                 try {
149                         String packageName = cu.getPackage().getName().getFullyQualifiedName();
150                         String typeIdentifier = typeName.getIdentifier();
151
152                         RefaktorDebug.println("Pkg: " + packageName);
153                         RefaktorDebug.println("Id: " + typeIdentifier);
154                         icu = javaProject.findType(packageName, typeIdentifier).getCompilationUnit();
155                 } catch (JavaModelException e) {
156                         setReason(Reason.RESOURCE_ACCESS_ERROR);
157                         return;
158                 }
159
160                 assert icu != null;
161                 extractor = new ExtractAndMoveMethodPrefixesExtractor(smartTextSelection, icu);
162         }
163
164         public void internalExecuteChange(IProgressMonitor monitor, IUndoManager undoManager,
165                         String undoName) throws JavaModelException, CoreException {
166                 Prefix prefix = getMostFrequentPrefix(extractor.getSafePrefixes());
167                 String packageName = prefix.getPackageName();
168                 String simpleTypeName = prefix.getSimpleTypeName();
169
170                 ICompilationUnit cu = RefaktorHandleUtils.findType(project, packageName, simpleTypeName).getCompilationUnit();
171                 ExtractMethodRefactoring extractMethodRefactoring = makeExtractMethodRefactoring(prefix, cu);
172                 checkConditionsAndPerformChange(monitor, undoManager, undoName, extractMethodRefactoring);
173
174                 // Here, the method we'd like to move must already exist since we need to look up an IMethod handle:
175                 Refactoring moveRefactoring = makeMoveRefactoring(prefix, packageName, simpleTypeName, extractMethodRefactoring);
176                 checkConditionsAndPerformChange(monitor, undoManager, undoName, moveRefactoring);
177         }
178
179         private void checkConditionsAndPerformChange(IProgressMonitor monitor, IUndoManager undoManager, 
180                         String undoName, Refactoring refactoring) throws CoreException {
181                 CheckConditionsOperation checkConditionsOperation = new CheckConditionsOperation(refactoring, CheckConditionsOperation.ALL_CONDITIONS);
182                 CreateChangeOperation createChangeOperation = makeCreateChangeOperation(checkConditionsOperation);
183                 makeAndRunPerformChangeOperation(monitor, undoManager, undoName, createChangeOperation);
184         }
185
186         private Prefix getMostFrequentPrefix(PrefixSet prefixSet) {
187                 LinkedList<Prefix> values = prefixSet.toList();
188                 sortAscendingByCountAndLength(values);
189                 return values.getLast();
190         }
191
192         private void sortAscendingByCountAndLength(LinkedList<Prefix> values) {
193                 Collections.sort(values, new Comparator<Prefix>() {
194                         @Override
195                         public int compare(Prefix p1, Prefix p2) {
196                                 if (p1.getCount() > p2.getCount()) {
197                                         return 1;
198                                 } else if (p1.getCount() < p2.getCount()) {
199                                         return -1;
200                                 } else if (p1.length() > p2.length()) {
201                                         return 1;
202                                 } else if (p1.length() < p2.length()) {
203                                         return -1;
204                                 }
205                                 return 0;
206                         }
207                 });
208         }
209
210
211         private ExtractMethodRefactoring makeExtractMethodRefactoring(Prefix prefix, ICompilationUnit cu) throws CoreException {
212                 ExtractMethodRefactoring refactoring = new ExtractMethodRefactoring(cu, extractor.getSelection().getOffset(), extractor.getSelection().getLength());
213
214                 refactoring.setMethodName(getNewMethodName());
215                 refactoring.setValidationContext(null);
216                 refactoring.setReplaceDuplicates(true);
217                 refactoring.setVisibility(Modifier.PUBLIC);
218                 refactoring.setLinkedProposalModel(null);
219                 return refactoring;
220         }
221
222         private MoveRefactoring makeMoveRefactoring(Prefix prefix, String packageName,
223                         String simpleTypeName, ExtractMethodRefactoring refactoring)
224                                         throws JavaModelException {
225                 IMethod method = RefaktorHandleUtils.findMethodHandle(project, packageName, simpleTypeName, refactoring.getSignature());
226                 Assert.isNotNull(method, "Can't find "+packageName+"."+simpleTypeName+"#"+refactoring.getMethodName());
227                 MoveInstanceMethodProcessor refactoringProcessor = new MoveInstanceMethodProcessor(method, null);
228                 if (refactoringProcessor.canEnableDelegateUpdating())
229                         refactoringProcessor.setDelegateUpdating(false);
230                 refactoringProcessor.setDeprecateDelegates(true);
231                 refactoringProcessor.setUseGetters(true);
232                 refactoringProcessor.setUseSetters(true);
233                 refactoringProcessor.setInlineDelegator(true);
234                 refactoringProcessor.setRemoveDelegator(true);
235                 // Sanity check, removes a dependency on 'method' [vs]
236                 assert method.getDeclaringType().getElementName().equals(simpleTypeName) : method.getDeclaringType().getElementName() +" / " +simpleTypeName;
237                 refactoringProcessor.setTargetName(simpleTypeName.toLowerCase());
238                 System.err.println(refactoringProcessor.getPossibleTargets().length);
239                 for (IVariableBinding v: refactoringProcessor.getPossibleTargets()) {
240                         System.err.print(v.getName());
241                 }
242
243                 IVariableBinding target = null;
244                 if (method.getParameters().length != 0) {
245                         MethodDeclaration node = ASTNodeSearchUtil.getMethodDeclarationNode(method, ParseUtils.parse(method.getCompilationUnit()));
246                         Object property = node.getStructuralProperty(MethodDeclaration.PARAMETERS_PROPERTY);
247                         assert property instanceof AbstractList;
248                         AbstractList<?> nodeList = (AbstractList<?>) property;
249                         for (Object declaration: nodeList) {
250                                 assert declaration instanceof SingleVariableDeclaration;
251                                 IVariableBinding declarationBinding = ((SingleVariableDeclaration)declaration).resolveBinding();
252                                 if (declarationBinding.getType().isEqualTo(prefix.getVariableBindingOfFirstExpression().getType())) {
253                                         target = declarationBinding;
254                                 }
255                         }
256                 } 
257                 // Target is field
258                 if (target == null)
259                         target = prefix.getVariableBindingOfFirstExpression();
260                 refactoringProcessor.setTarget(target);
261                 RefaktorDebug.println("MoveMethod:");
262                 RefaktorDebug.println("Trying to move method " + method.getElementName() + " to "
263                                 + target.getName());
264
265                 return new MoveRefactoring(refactoringProcessor);
266         }
267
268         private void setReason(Reason reason) {
269                 this.reason = reason;
270         }
271
272         public Reason getReason() {
273                 return reason;
274         }
275
276         public String getNewMethodName() {
277                 return newMethodName;
278         }
279
280         public void setNewMethodName(String newMethodName) {
281                 this.newMethodName = newMethodName;
282         }
283
284 }