/***** BEGIN LICENSE BLOCK ***** * Version: CPL 1.0/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Common Public * License Version 1.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.eclipse.org/legal/cpl-v10.html * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * Copyright (C) 2006 Lukas Felber <lfelber@hsr.ch> * Copyright (C) 2006 Mirko Stocker <me@misto.ch> * Copyright (C) 2006 Thomas Corbat <tcorbat@hsr.ch> * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the CPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the CPL, the GPL or the LGPL. ***** END LICENSE BLOCK *****/ package org.rubypeople.rdt.refactoring.core.movemethod; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; import java.util.Observable; import java.util.Observer; import org.jruby.ast.FCallNode; import org.jruby.ast.Node; import org.jruby.ast.SelfNode; import org.rubypeople.rdt.refactoring.core.NodeFactory; import org.rubypeople.rdt.refactoring.core.NodeProvider; import org.rubypeople.rdt.refactoring.core.SelectionNodeProvider; import org.rubypeople.rdt.refactoring.editprovider.DeleteEditProvider; import org.rubypeople.rdt.refactoring.editprovider.EditProvider; import org.rubypeople.rdt.refactoring.editprovider.FileEditProvider; import org.rubypeople.rdt.refactoring.editprovider.FileMultiEditProvider; import org.rubypeople.rdt.refactoring.editprovider.IMultiFileEditProvider; import org.rubypeople.rdt.refactoring.editprovider.InsertEditProvider; import org.rubypeople.rdt.refactoring.editprovider.MultiFileEditProvider; import org.rubypeople.rdt.refactoring.nodewrapper.ArgsNodeWrapper; import org.rubypeople.rdt.refactoring.nodewrapper.AttrAccessorNodeWrapper; import org.rubypeople.rdt.refactoring.nodewrapper.FieldNodeWrapper; import org.rubypeople.rdt.refactoring.nodewrapper.MethodCallNodeWrapper; import org.rubypeople.rdt.refactoring.nodewrapper.MethodNodeWrapper; import org.rubypeople.rdt.refactoring.nodewrapper.PartialClassNodeWrapper; import org.rubypeople.rdt.refactoring.nodewrapper.VisibilityNodeWrapper; import org.rubypeople.rdt.refactoring.nodewrapper.VisibilityNodeWrapper.METHOD_VISIBILITY; import org.rubypeople.rdt.refactoring.util.NameHelper; import org.rubypeople.rdt.refactoring.util.NodeUtil; public class MethodMover implements IMultiFileEditProvider, Observer { private MoveMethodConfig config; private Collection<String> visibilitiesToDelete; public MethodMover(MoveMethodConfig config) { this.config = config; visibilitiesToDelete = new ArrayList<String>(); config.addObserver(this); initConfig(); } private void initConfig() { boolean methodHasCallsToSourceClass = methodContainsReferencesToSourceClass(); config.setNewMethodNeedsReferenceToSourceClass(methodHasCallsToSourceClass); if (methodHasCallsToSourceClass) { initMovedMethodArgs(); } config.setSourceClassHasCallsToMovingMethod(sourceClassContainsCallsToMovingMethod()); METHOD_VISIBILITY aktVisibility = config.getSourceClassNode().getMethodVisibility(config.getMethodNode()); config.setMethodVisibility(aktVisibility); setMovedMethodVisibility(); config.setLeaveDelegateMethodInSource(config.needsSecondPage()); } private void setMovedMethodVisibility() { if (config.doesSourceClassHasCallsToMovingMethod() || config.leaveDelegateMethodInSource()) { config.setMovedMethodVisibility(METHOD_VISIBILITY.PUBLIC); } else { config.setMovedMethodVisibility(config.getMethodVisibility()); } } private void initMovedMethodArgs() { String className = config.getSourceClassNode().getName(); String newArgName = className.substring(0, 1).toLowerCase(Locale.ENGLISH) + className.substring(1); while (NameHelper.namesContainName(config.getMethodNode().getLocalNames(), newArgName)) { newArgName = NameHelper.createName(newArgName); } config.setFieldInDestinationClassOfTypeSourceClass(newArgName); ArgsNodeWrapper argsNode = config.getMethodNode().getArgsNode(); config.setMovedMethodArgs(argsNode.cloneWithNewArgName(newArgName)); } private boolean methodContainsReferencesToSourceClass() { for (MethodCallNodeWrapper aktCall : config.getMethodNode().getMethodCallNodes()) { if (isCallToSourceClass(aktCall)) { return true; } } for (FieldNodeWrapper aktFieldNode : NodeProvider.getFieldNodes(config.getMethodNode().getWrappedNode())) { if (aktFieldNode.isInstVar()) { return true; } } return false; } private boolean isCallToSourceClass(MethodCallNodeWrapper callNode) { if (callNode.isCallToClassMethod()) { return false; } boolean isReceiverSelf = callNode.isCallNode() && NodeUtil.nodeAssignableFrom(callNode.getReceiverNode(), SelfNode.class); boolean isNotCallNode = !callNode.isCallNode(); boolean hasExistingMethodName = config.getSourceClassNode().containsMethod(callNode.getName()); return (isReceiverSelf || isNotCallNode) && hasExistingMethodName; } public Collection<FileMultiEditProvider> getFileEditProviders() { MultiFileEditProvider multiFileEditProvider = new MultiFileEditProvider(); multiFileEditProvider.addEditProvider(getInsertMethodInTargetClassProvider()); if (config.leaveDelegateMethodInSource()) { multiFileEditProvider.addEditProvider(getDelegateMethodEditProvider()); } else { multiFileEditProvider.addEditProvider(getDeleteSelectedMethodEditProvider()); addDeleteVisibilityNodesOfMovingMethod(multiFileEditProvider); addUpdateReferencesInSourceClassEditProviders(multiFileEditProvider); } if (config.doesNewMethodNeedsReferenceToSourceClass()) { addMethodVisibilityModifierEditProviders(multiFileEditProvider); addGenerateAccessorsEditProviders(multiFileEditProvider); } addDeleteMethodVisibilitiesEditProvider(multiFileEditProvider); return multiFileEditProvider.getFileEditProviders(); } private void addDeleteMethodVisibilitiesEditProvider(MultiFileEditProvider multiFileEditProvider) { for (VisibilityNodeWrapper aktVisibilityNode : config.getSourceClassNode().getMethodVisibilityNodes()) { String fileName = aktVisibilityNode.getPosition().getFile(); RemovePartOfVisibilityNodeProvider editProvider = new RemovePartOfVisibilityNodeProvider(aktVisibilityNode, visibilitiesToDelete); if (editProvider.shouldRemoveAll()) { multiFileEditProvider.addEditProvider(new FileEditProvider(fileName, new DeleteEditProvider( aktVisibilityNode.getWrappedNode()))); } else if (editProvider.hasChange()) { multiFileEditProvider.addEditProvider(new FileEditProvider(fileName, editProvider)); } } } private void addDeleteVisibilityNodesOfMovingMethod(MultiFileEditProvider multiFileEditProvider) { VisibilityNodeWrapper visibilityNode = config.getSourceClassNode().getMethodVisibilityNode( config.getMethodNode()); String fileName = config.getMethodNode().getPosition().getFile(); if (visibilityNode == null) { return; } if (visibilityNode.getMethodNames().size() == 1) { multiFileEditProvider.addEditProvider(new FileEditProvider(fileName, new DeleteEditProvider(visibilityNode .getWrappedNode()))); } else { visibilitiesToDelete.add(config.getMethodNode().getName()); } } private void addGenerateAccessorsEditProviders(MultiFileEditProvider multiFileEditProvider) { Collection<AttrAccessorNodeWrapper> accessorsToCreate = getMissingAccessors(); String fileName = config.getDocumentProvider().getActiveFileName(); for (AttrAccessorNodeWrapper aktAccessorNode : accessorsToCreate) { EditProvider editProvider = new InsertAccessorEditProvider(aktAccessorNode, config.getSourceClassNode()); multiFileEditProvider.addEditProvider(new FileEditProvider(fileName, editProvider)); } } private Collection<AttrAccessorNodeWrapper> getMissingAccessors() { Collection<FieldNodeWrapper> fieldNodes = NodeProvider.getFieldNodes(config.getMethodNode().getWrappedNode()); Collection<AttrAccessorNodeWrapper> existingAccessors = config.getSourceClassNode().getAccessorNodes(); Map<String, AttrAccessorNodeWrapper> accessorsToCreate = new LinkedHashMap<String, AttrAccessorNodeWrapper>(); String destClassField = config.getFieldInSourceClassOfTypeDestinationClass(); for (FieldNodeWrapper aktFieldNode : fieldNodes) { if (!aktFieldNode.getName().equals(destClassField)) { addAccessorForField(existingAccessors, accessorsToCreate, aktFieldNode); } } return accessorsToCreate.values(); } private void addAccessorForField(Collection<AttrAccessorNodeWrapper> existingAccessors, Map<String, AttrAccessorNodeWrapper> accessorsToCreate, FieldNodeWrapper fieldNode) { AttrAccessorNodeWrapper accessorToInsert = getAccessor(fieldNode); if (fieldNode.isInstVar() && !existsAccessor(accessorToInsert, existingAccessors)) { if (accessorsToCreate.containsKey(fieldNode.getName())) { AttrAccessorNodeWrapper accessor = accessorsToCreate.get(fieldNode.getName()); accessor.addAccessorType(getAccessor(fieldNode)); } else { accessorsToCreate.put(fieldNode.getName(), getAccessor(fieldNode)); } } } private boolean existsAccessor(AttrAccessorNodeWrapper accessorToInsert, Collection<AttrAccessorNodeWrapper> existingAccessors) { for (AttrAccessorNodeWrapper accessorNode : existingAccessors) { if (accessorNode.containsAccessor(accessorToInsert)) { return true; } } return false; } private AttrAccessorNodeWrapper getAccessor(final FieldNodeWrapper aktFieldNode) { String accessorName; if (aktFieldNode.isAsgnNode()) { accessorName = AttrAccessorNodeWrapper.ATTR_WRITER; } else { accessorName = AttrAccessorNodeWrapper.ATTR_READER; } FCallNode fCallNode = NodeFactory.createFCallNode(accessorName, new ArrayList<Node>()); return new AttrAccessorNodeWrapper(fCallNode, NodeFactory.createSymboleNode(aktFieldNode.getName())); } private void addUpdateReferencesInSourceClassEditProviders(MultiFileEditProvider multiFileEditProvider) { Collection<MethodCallNodeWrapper> methodCallsToMovingMethod = getMethodCallsToMovingMethodFromSourceClass(); for (MethodCallNodeWrapper aktCall : methodCallsToMovingMethod) { addReplaceMethodCallEditProvider(aktCall, multiFileEditProvider); } } private void addReplaceMethodCallEditProvider(MethodCallNodeWrapper methodCall, MultiFileEditProvider multiFileEditProvider) { String fileName = methodCall.getPosition().getFile(); EditProvider replaceEdit = new ReplaceMethodCallEditProvider(methodCall, config); multiFileEditProvider.addEditProvider(new FileEditProvider(fileName, replaceEdit)); } private void addMethodVisibilityModifierEditProviders(MultiFileEditProvider multiFileEditProvider) { Collection<MethodNodeWrapper> referencedMethod = getSourceClassMethodsReferencedInMovingMethod(); for (MethodNodeWrapper aktMethod : referencedMethod) { if (!config.getSourceClassNode().getMethodVisibility(aktMethod).equals(METHOD_VISIBILITY.PUBLIC)) { addMethodVisibilityModifierEditProvider(aktMethod, multiFileEditProvider); } } } private void addMethodVisibilityModifierEditProvider(MethodNodeWrapper methodNode, MultiFileEditProvider multiFileEditProvider) { VisibilityNodeWrapper visibilityNode = config.getSourceClassNode().getMethodVisibilityNode(methodNode); String fileName = methodNode.getWrappedNode().getPosition().getFile(); if (visibilityNode == null) { EditProvider insertEdit = new InsertVisibilityEditProvider(methodNode, METHOD_VISIBILITY.PUBLIC); multiFileEditProvider.addEditProvider(new FileEditProvider(fileName, insertEdit)); } else { if (visibilityNode.getVisibility().equals(METHOD_VISIBILITY.PUBLIC)) { return; } if (visibilityNode.getMethodNames().size() == 1) { EditProvider editProvider = new ReplaceVisibilityEditProvider(visibilityNode, METHOD_VISIBILITY.PUBLIC); multiFileEditProvider.addEditProvider(new FileEditProvider(fileName, editProvider)); } else { EditProvider insertEdit = new InsertVisibilityEditProvider(methodNode, METHOD_VISIBILITY.PUBLIC); multiFileEditProvider.addEditProvider(new FileEditProvider(fileName, insertEdit)); visibilitiesToDelete.add(methodNode.getName()); } } } private Collection<MethodNodeWrapper> getSourceClassMethodsReferencedInMovingMethod() { Collection<MethodCallNodeWrapper> calledMethods = config.getMethodNode().getMethodCallNodes(); Collection<MethodNodeWrapper> methods = new ArrayList<MethodNodeWrapper>(); for (MethodCallNodeWrapper aktCall : calledMethods) { if (!aktCall.isCallToClassMethod() && !aktCall.isCallNode() && config.getSourceClassNode().containsMethod(aktCall.getName())) { methods.add(config.getSourceClassNode().getMethod(aktCall.getName())); } } return methods; } private FileEditProvider getDeleteSelectedMethodEditProvider() { EditProvider deleteEditProvider = new DeleteEditProvider(config.getMethodNode().getWrappedNode()); return new FileEditProvider(config.getDocumentProvider().getActiveFileName(), deleteEditProvider); } private FileEditProvider getDelegateMethodEditProvider() { EditProvider editProvider = new DelegateMethodEditProvider(config); return new FileEditProvider(config.getDocumentProvider().getActiveFileName(), editProvider); } private FileEditProvider getInsertMethodInTargetClassProvider() { PartialClassNodeWrapper insertClassPart = config.getDestinationClassNode().getPartialClassNodeForFileName( config.getMethodNode().getPosition().getFile()); if (insertClassPart == null) { insertClassPart = config.getDestinationClassNode().getFirstPartialClassNode(); } InsertEditProvider insertEdit = new InsertMethodEditProvider(config, insertClassPart); return new FileEditProvider(insertClassPart.getFile(), insertEdit); } private Collection<MethodCallNodeWrapper> getMethodCallsToMovingMethodFromSourceClass() { Collection<MethodCallNodeWrapper> allMethodCalls = config.getSourceClassNode().getMethodCallNodes(); Collection<MethodCallNodeWrapper> methodCallsToMovingMethod = new ArrayList<MethodCallNodeWrapper>(); for (MethodCallNodeWrapper aktCall : allMethodCalls) { if (isCallToMovingMethod(aktCall)) { methodCallsToMovingMethod.add(aktCall); } } return methodCallsToMovingMethod; } private boolean sourceClassContainsCallsToMovingMethod() { Collection<MethodCallNodeWrapper> allMethodCalls = config.getSourceClassNode().getMethodCallNodes(); for (MethodCallNodeWrapper aktCall : allMethodCalls) { if (isCallToMovingMethod(aktCall) && !aktCall.isCallToClassMethod()) { return true; } } return false; } private boolean isCallToMovingMethod(MethodCallNodeWrapper methodCall) { String selectedMethodName = config.getMethodNode().getName(); boolean sameName = methodCall.getName().equals(selectedMethodName); boolean notInMovingMethod = !SelectionNodeProvider.isNodeContainedInNode(methodCall.getWrappedNode(), config .getMethodNode().getWrappedNode()); boolean isNotCallNode = !methodCall.isCallNode(); boolean isSelfNode = methodCall.isCallNode() && NodeUtil.nodeAssignableFrom(methodCall.getReceiverNode(), SelfNode.class); boolean sameType = config.getMethodNode().isClassMethod() == methodCall.isCallToClassMethod(); return sameName && sameType && notInMovingMethod && (isNotCallNode || isSelfNode || methodCall.isCallToClassMethod()); } public void update(Observable arg0, Object arg1) { setMovedMethodVisibility(); initWarnings(); } private void initWarnings() { config.resetWarnings(); if (config.doesNewMethodNeedsReferenceToSourceClass()) { for (AttrAccessorNodeWrapper aktAccessorNode : getMissingAccessors()) { config.addWarning(Messages.MethodMover_An + aktAccessorNode.getAccessorTypeName() + Messages.MethodMover_ForField + aktAccessorNode.getAttrName() + Messages.MethodMover_WillBeGenerated); } } if (config.doesNewMethodNeedsReferenceToSourceClass()) { for (MethodNodeWrapper aktMethod : getSourceClassMethodsReferencedInMovingMethod()) { if (!config.getSourceClassNode().getMethodVisibility(aktMethod).equals(METHOD_VISIBILITY.PUBLIC)) { config.addWarning(Messages.MethodMover_TheVisibilityOfMethod + aktMethod.getName() + Messages.MethodMover_WillBeChangedToPublic); } } } METHOD_VISIBILITY newVisibility = config.getMethodVisibility(); METHOD_VISIBILITY oldVisibility = config.getMethodVisibility(); if (!newVisibility.equals(oldVisibility)) { String oldVisibilityName = VisibilityNodeWrapper.getVisibilityName(oldVisibility); String newVisibilityName = VisibilityNodeWrapper.getVisibilityName(newVisibility); config.addWarning(Messages.MethodMover_TheVisibilityOfTheMovingMethod + config.getMethodNode().getName() + Messages.MethodMover_IsChangedFrom + oldVisibilityName + Messages.MethodMover_To + newVisibilityName + '.'); } if (!config.getMethodNode().getName().equals(config.getMovedMethodName())) { config.addWarning(Messages.MethodMover_NameWillBeChangedTo + config.getMovedMethodName() + Messages.MethodMover_DuToNameConflicts); } } }