1 package no.uio.ifi.refaktor.changers;
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;
9 import java.util.Random;
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;
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;
46 * This class composes the refactorings known as
47 * Extract Method and Move Method.
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.
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.
60 @SuppressWarnings("restriction")
61 public class ExtractAndMoveMethodChanger extends RefaktorChanger {
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.");
71 private final String message;
73 private Reason(String message) {
74 this.message = message;
77 public String getMessage() {
82 private IProject project;
83 private SmartTextSelection smartTextSelection;
84 private ExtractAndMoveMethodPrefixesExtractor extractor;
85 private Reason reason;
86 private String newMethodName;
88 public ExtractAndMoveMethodChanger(IProject project, SmartTextSelection smartTextSelection) {
89 this(project, smartTextSelection, generateName());
92 public ExtractAndMoveMethodChanger(IProject project, SmartTextSelection smartTextSelection, String newMethodName) {
93 this.project = project;
94 this.smartTextSelection = smartTextSelection;
95 this.setNewMethodName(newMethodName);
97 initializeExtractor();
98 } catch (ExecutionException e) {
99 new RuntimeException(e);
103 public static String generateName() {
104 return "generated_" + Math.abs(new Random().nextLong());
107 public boolean preconditionsAreMet() throws ExecutionException {
108 if (initializationError())
110 if (!extractor.selectionIsValid()) {
111 setReason(Reason.SELECTION_INVALID);
114 extractor.extractProperty();
115 if (!extractor.hasUsefulResults()) {
116 setReason(Reason.NO_TARGET_FOUND);
122 private boolean initializationError() {
123 return reason != null;
126 public void initializeExtractor() throws ExecutionException {
128 assert project.hasNature(JavaCore.NATURE_ID);
129 } catch (CoreException e) {
130 if (!project.exists())
131 setReason(Reason.PROJECT_NOT_EXISTING);
133 setReason(Reason.PROJECT_NOT_OPEN);
136 IJavaProject javaProject = JavaCore.create(project);
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);
146 TypeDeclaration typeDecl = (TypeDeclaration) types.get(0);
147 SimpleName typeName = typeDecl.getName();
149 String packageName = cu.getPackage().getName().getFullyQualifiedName();
150 String typeIdentifier = typeName.getIdentifier();
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);
161 extractor = new ExtractAndMoveMethodPrefixesExtractor(smartTextSelection, icu);
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();
170 ICompilationUnit cu = RefaktorHandleUtils.findType(project, packageName, simpleTypeName).getCompilationUnit();
171 ExtractMethodRefactoring extractMethodRefactoring = makeExtractMethodRefactoring(prefix, cu);
172 checkConditionsAndPerformChange(monitor, undoManager, undoName, extractMethodRefactoring);
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);
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);
186 private Prefix getMostFrequentPrefix(PrefixSet prefixSet) {
187 LinkedList<Prefix> values = prefixSet.toList();
188 sortAscendingByCountAndLength(values);
189 return values.getLast();
192 private void sortAscendingByCountAndLength(LinkedList<Prefix> values) {
193 Collections.sort(values, new Comparator<Prefix>() {
195 public int compare(Prefix p1, Prefix p2) {
196 if (p1.getCount() > p2.getCount()) {
198 } else if (p1.getCount() < p2.getCount()) {
200 } else if (p1.length() > p2.length()) {
202 } else if (p1.length() < p2.length()) {
211 private ExtractMethodRefactoring makeExtractMethodRefactoring(Prefix prefix, ICompilationUnit cu) throws CoreException {
212 ExtractMethodRefactoring refactoring = new ExtractMethodRefactoring(cu, extractor.getSelection().getOffset(), extractor.getSelection().getLength());
214 refactoring.setMethodName(getNewMethodName());
215 refactoring.setValidationContext(null);
216 refactoring.setReplaceDuplicates(true);
217 refactoring.setVisibility(Modifier.PUBLIC);
218 refactoring.setLinkedProposalModel(null);
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());
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;
259 target = prefix.getVariableBindingOfFirstExpression();
260 refactoringProcessor.setTarget(target);
261 RefaktorDebug.println("MoveMethod:");
262 RefaktorDebug.println("Trying to move method " + method.getElementName() + " to "
265 return new MoveRefactoring(refactoringProcessor);
268 private void setReason(Reason reason) {
269 this.reason = reason;
272 public Reason getReason() {
276 public String getNewMethodName() {
277 return newMethodName;
280 public void setNewMethodName(String newMethodName) {
281 this.newMethodName = newMethodName;