package no.uio.ifi.refaktor.changers; import java.lang.reflect.Modifier; import java.util.AbstractList; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.Random; import no.uio.ifi.refaktor.extractors.ExtractAndMoveMethodPrefixesExtractor; import no.uio.ifi.refaktor.extractors.Prefix; import no.uio.ifi.refaktor.extractors.PrefixSet; import no.uio.ifi.refaktor.utils.ParseUtils; import no.uio.ifi.refaktor.utils.RefaktorDebug; import no.uio.ifi.refaktor.utils.RefaktorHandleUtils; import no.uio.ifi.refaktor.utils.SmartTextSelection; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.internal.corext.refactoring.code.ExtractMethodRefactoring; import org.eclipse.jdt.internal.corext.refactoring.structure.ASTNodeSearchUtil; import org.eclipse.jdt.internal.corext.refactoring.structure.MoveInstanceMethodProcessor; import org.eclipse.ltk.core.refactoring.CheckConditionsOperation; import org.eclipse.ltk.core.refactoring.CreateChangeOperation; import org.eclipse.ltk.core.refactoring.IUndoManager; import org.eclipse.ltk.core.refactoring.Refactoring; import org.eclipse.ltk.core.refactoring.participants.MoveRefactoring; /** * This class composes the refactorings known as * Extract Method and Move Method. * * Before extracting, it finds the possible targets for the move * with the help of a PropertyExtractor (see {@link ExtractAndMoveMethodPrefixesExtractor}), * that extracts both the candidates, in the form prefixes (see {@link Prefix}), * and the non-candidates, called unfixes. They are collected into sets * of prefixes (see {@link PrefixSet}).The set of prefixes that * are not enclosing any unfixes is put in the set of safe prefixes. * * The changer then tries to analyze which of the safe prefixes * that is the best candidate. The best candidate is used to find the * target of the composed refactoring. */ @SuppressWarnings("restriction") public class ExtractAndMoveMethodChanger extends RefaktorChanger { public enum Reason { SELECTION_INVALID("The selected text is not valid for this refactoring."), NO_TARGET_FOUND("Could not find target for move."), PROJECT_NOT_EXISTING("The project for the editor where the command was issued is not existing!"), PROJECT_NOT_OPEN("The project for the editor where the command was issued is not open!"), NO_TYPES_FOUND("No types declared in file."), RESOURCE_ACCESS_ERROR("An error occurred while accessing resources."); private final String message; private Reason(String message) { this.message = message; } public String getMessage() { return message; } } private IProject project; private SmartTextSelection smartTextSelection; private ExtractAndMoveMethodPrefixesExtractor extractor; private Reason reason; private String newMethodName; public ExtractAndMoveMethodChanger(IProject project, SmartTextSelection smartTextSelection) { this(project, smartTextSelection, generateName()); } public ExtractAndMoveMethodChanger(IProject project, SmartTextSelection smartTextSelection, String newMethodName) { this.project = project; this.smartTextSelection = smartTextSelection; this.setNewMethodName(newMethodName); try { initializeExtractor(); } catch (ExecutionException e) { new RuntimeException(e); } } public static String generateName() { return "generated_" + Math.abs(new Random().nextLong()); } public boolean preconditionsAreMet() throws ExecutionException { if (initializationError()) return false; if (!extractor.selectionIsValid()) { setReason(Reason.SELECTION_INVALID); return false; } extractor.extractProperty(); if (!extractor.hasUsefulResults()) { setReason(Reason.NO_TARGET_FOUND); return false; } return true; } private boolean initializationError() { return reason != null; } public void initializeExtractor() throws ExecutionException { try { assert project.hasNature(JavaCore.NATURE_ID); } catch (CoreException e) { if (!project.exists()) setReason(Reason.PROJECT_NOT_EXISTING); else setReason(Reason.PROJECT_NOT_OPEN); return; } IJavaProject javaProject = JavaCore.create(project); ICompilationUnit icu = null; ASTNode selectedNode = smartTextSelection.getNodeFinder().getCoveredNode(); CompilationUnit cu = (CompilationUnit) selectedNode.getRoot(); List types = cu.types(); if (types.isEmpty()) { setReason(Reason.NO_TYPES_FOUND); return; } TypeDeclaration typeDecl = (TypeDeclaration) types.get(0); SimpleName typeName = typeDecl.getName(); try { String packageName = cu.getPackage().getName().getFullyQualifiedName(); String typeIdentifier = typeName.getIdentifier(); RefaktorDebug.println("Pkg: " + packageName); RefaktorDebug.println("Id: " + typeIdentifier); icu = javaProject.findType(packageName, typeIdentifier).getCompilationUnit(); } catch (JavaModelException e) { setReason(Reason.RESOURCE_ACCESS_ERROR); return; } assert icu != null; extractor = new ExtractAndMoveMethodPrefixesExtractor(smartTextSelection, icu); } public void internalExecuteChange(IProgressMonitor monitor, IUndoManager undoManager, String undoName) throws JavaModelException, CoreException { Prefix prefix = getMostFrequentPrefix(extractor.getSafePrefixes()); String packageName = prefix.getPackageName(); String simpleTypeName = prefix.getSimpleTypeName(); ICompilationUnit cu = RefaktorHandleUtils.findType(project, packageName, simpleTypeName).getCompilationUnit(); ExtractMethodRefactoring extractMethodRefactoring = makeExtractMethodRefactoring(prefix, cu); checkConditionsAndPerformChange(monitor, undoManager, undoName, extractMethodRefactoring); // Here, the method we'd like to move must already exist since we need to look up an IMethod handle: Refactoring moveRefactoring = makeMoveRefactoring(prefix, packageName, simpleTypeName, extractMethodRefactoring); checkConditionsAndPerformChange(monitor, undoManager, undoName, moveRefactoring); } private void checkConditionsAndPerformChange(IProgressMonitor monitor, IUndoManager undoManager, String undoName, Refactoring refactoring) throws CoreException { CheckConditionsOperation checkConditionsOperation = new CheckConditionsOperation(refactoring, CheckConditionsOperation.ALL_CONDITIONS); CreateChangeOperation createChangeOperation = makeCreateChangeOperation(checkConditionsOperation); makeAndRunPerformChangeOperation(monitor, undoManager, undoName, createChangeOperation); } private Prefix getMostFrequentPrefix(PrefixSet prefixSet) { LinkedList values = prefixSet.toList(); sortAscendingByCountAndLength(values); return values.getLast(); } private void sortAscendingByCountAndLength(LinkedList values) { Collections.sort(values, new Comparator() { @Override public int compare(Prefix p1, Prefix p2) { if (p1.getCount() > p2.getCount()) { return 1; } else if (p1.getCount() < p2.getCount()) { return -1; } else if (p1.length() > p2.length()) { return 1; } else if (p1.length() < p2.length()) { return -1; } return 0; } }); } private ExtractMethodRefactoring makeExtractMethodRefactoring(Prefix prefix, ICompilationUnit cu) throws CoreException { ExtractMethodRefactoring refactoring = new ExtractMethodRefactoring(cu, extractor.getSelection().getOffset(), extractor.getSelection().getLength()); refactoring.setMethodName(getNewMethodName()); refactoring.setValidationContext(null); refactoring.setReplaceDuplicates(true); refactoring.setVisibility(Modifier.PUBLIC); refactoring.setLinkedProposalModel(null); return refactoring; } private MoveRefactoring makeMoveRefactoring(Prefix prefix, String packageName, String simpleTypeName, ExtractMethodRefactoring refactoring) throws JavaModelException { IMethod method = RefaktorHandleUtils.findMethodHandle(project, packageName, simpleTypeName, refactoring.getSignature()); Assert.isNotNull(method, "Can't find "+packageName+"."+simpleTypeName+"#"+refactoring.getMethodName()); MoveInstanceMethodProcessor refactoringProcessor = new MoveInstanceMethodProcessor(method, null); if (refactoringProcessor.canEnableDelegateUpdating()) refactoringProcessor.setDelegateUpdating(false); refactoringProcessor.setDeprecateDelegates(true); refactoringProcessor.setUseGetters(true); refactoringProcessor.setUseSetters(true); refactoringProcessor.setInlineDelegator(true); refactoringProcessor.setRemoveDelegator(true); // Sanity check, removes a dependency on 'method' [vs] assert method.getDeclaringType().getElementName().equals(simpleTypeName) : method.getDeclaringType().getElementName() +" / " +simpleTypeName; refactoringProcessor.setTargetName(simpleTypeName.toLowerCase()); System.err.println(refactoringProcessor.getPossibleTargets().length); for (IVariableBinding v: refactoringProcessor.getPossibleTargets()) { System.err.print(v.getName()); } IVariableBinding target = null; if (method.getParameters().length != 0) { MethodDeclaration node = ASTNodeSearchUtil.getMethodDeclarationNode(method, ParseUtils.parse(method.getCompilationUnit())); Object property = node.getStructuralProperty(MethodDeclaration.PARAMETERS_PROPERTY); assert property instanceof AbstractList; AbstractList nodeList = (AbstractList) property; for (Object declaration: nodeList) { assert declaration instanceof SingleVariableDeclaration; IVariableBinding declarationBinding = ((SingleVariableDeclaration)declaration).resolveBinding(); if (declarationBinding.getType().isEqualTo(prefix.getVariableBindingOfFirstExpression().getType())) { target = declarationBinding; } } } // Target is field if (target == null) target = prefix.getVariableBindingOfFirstExpression(); refactoringProcessor.setTarget(target); RefaktorDebug.println("MoveMethod:"); RefaktorDebug.println("Trying to move method " + method.getElementName() + " to " + target.getName()); return new MoveRefactoring(refactoringProcessor); } private void setReason(Reason reason) { this.reason = reason; } public Reason getReason() { return reason; } public String getNewMethodName() { return newMethodName; } public void setNewMethodName(String newMethodName) { this.newMethodName = newMethodName; } }