/******************************************************************************* * Copyright (c) 2007, 2015 Spring IDE Developers * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Spring IDE Developers - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.beans.ui.refactoring.util; import java.io.IOException; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.ILocalVariable; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IType; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.TextChange; import org.eclipse.ltk.core.refactoring.TextFileChange; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.text.edits.TextEdit; import org.eclipse.text.edits.TextEditGroup; import org.eclipse.wst.sse.core.StructuredModelManager; import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; import org.eclipse.wst.xml.core.internal.document.AttrImpl; import org.eclipse.wst.xml.core.internal.document.DOMModelImpl; import org.eclipse.wst.xml.core.internal.document.TextImpl; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument; import org.springframework.ide.eclipse.beans.ui.editor.util.BeansEditorUtils; import org.springframework.ide.eclipse.beans.ui.refactoring.ltk.RenameIdType; import org.springframework.ide.eclipse.core.java.JdtUtils; import org.springframework.util.StringUtils; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * @author Christian Dupuis * @author Torsten Juergeleit * @author Martin Lippert * @author Terry Denney * @since 2.0 */ @SuppressWarnings("restriction") public class BeansRefactoringChangeUtils { public static Change createConstructorArgumentRenameChange(IFile file, IJavaElement affectedElement, String newName, IProgressMonitor pm) throws CoreException { IStructuredModel model = null; try { model = StructuredModelManager.getModelManager().getModelForRead(file); if (model == null) { return null; } IDOMDocument document = ((DOMModelImpl) model).getDocument(); MultiTextEdit multiEdit = new MultiTextEdit(); NodeList nodes = document.getElementsByTagName("bean"); for (int i = 0; i < nodes.getLength(); i++) { Set<TextEdit> edits = createConstructorTextEdits(nodes.item(i), affectedElement, newName, file); if (edits != null) { multiEdit.addChildren(edits.toArray(new TextEdit[edits.size()])); } } if (multiEdit.hasChildren()) { TextFileChange change = new TextFileChange("", file); change.setEdit(multiEdit); for (TextEdit e : multiEdit.getChildren()) { change.addTextEditGroup(new TextEditGroup("Rename constructor argument", e)); } return change; } } catch (IOException e) { } finally { if (model != null) { model.releaseFromRead(); } } return null; } private static Set<TextEdit> createConstructorTextEdits(Node node, IJavaElement element, String newName, IFile file) { if (node == null) { return null; } Set<TextEdit> result = new HashSet<TextEdit>(); if (element instanceof ILocalVariable) { // c-namespace references to the argument name that belong to the changed constructor argument if (node.hasAttributes()) { String argumentName = element.getElementName(); String attributeNameStart = "c:"; String optionalAttributeNameEnd = "-ref"; NamedNodeMap attributes = node.getAttributes(); int attributeCount = attributes.getLength(); for (int i = 0; i < attributeCount; i++) { AttrImpl attribute = (AttrImpl) attributes.item(i); String attributeName = attribute.getNodeName(); if (attributeName != null && attributeName.startsWith(attributeNameStart)) { if (attributeName.equals(attributeNameStart + argumentName) || attributeName.equals(attributeNameStart + argumentName + optionalAttributeNameEnd)) { List<IType> types = BeansEditorUtils.getClassNamesOfBean(file, node); if (element.getParent() != null && element.getParent() instanceof IMethod && types.contains(((IMethod) element.getParent()).getDeclaringType())) { int offset = attribute.getNameRegionStartOffset() + attributeNameStart.length(); result.add(new ReplaceEdit(offset, argumentName.length(), newName)); } } } } } } return result; } public static Change createMethodRenameChange(IFile file, IJavaElement[] affectedElements, String[] newNames, IProgressMonitor pm) throws CoreException { IStructuredModel model = null; try { model = StructuredModelManager.getModelManager().getModelForRead(file); if (model == null) { return null; } IDOMDocument document = ((DOMModelImpl) model).getDocument(); MultiTextEdit multiEdit = new MultiTextEdit(); NodeList nodes = document.getElementsByTagName("bean"); for (int j = 0; j < affectedElements.length; j++) { for (int i = 0; i < nodes.getLength(); i++) { Set<TextEdit> edits = createMethodTextEdits(nodes.item(i), affectedElements[j], newNames[j], file); if (edits != null) { multiEdit.addChildren(edits.toArray(new TextEdit[edits.size()])); } } } if (multiEdit.hasChildren()) { TextFileChange change = new TextFileChange("", file); change.setEdit(multiEdit); for (TextEdit e : multiEdit.getChildren()) { change.addTextEditGroup(new TextEditGroup("Rename Bean property name", e)); } return change; } } catch (IOException e) { } finally { if (model != null) { model.releaseFromRead(); } } return null; } private static Set<TextEdit> createMethodTextEdits(Node node, IJavaElement element, String newName, IFile file) { if (node == null) { return null; } Set<TextEdit> result = new HashSet<TextEdit>(); if (element instanceof IMethod) { if (element.getElementName().startsWith("set")) { String methodName = StringUtils.uncapitalize(element.getElementName().substring(3)); // change those child notes that are property childs and refer to the changed method name NodeList nodes = node.getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { Node child = nodes.item(i); if ("property".equals(child.getLocalName()) && BeansEditorUtils.hasAttribute(child, "name")) { String propertyName = BeansEditorUtils.getAttribute(child, "name"); if (methodName.equals(propertyName)) { List<IType> types = BeansEditorUtils.getClassNamesOfBean(file, node); if (types.contains(((IMethod) element).getDeclaringType())) { AttrImpl attr = (AttrImpl) child.getAttributes().getNamedItem("name"); int offset = attr.getValueRegionStartOffset() + 1; if (offset >= 0) { result.add(new ReplaceEdit(offset, propertyName.length(), newName)); } } } } } // p-namespace references to the properties that belong to the changed method if (node.hasAttributes()) { String attributeNameStart = "p:"; String optionalAttributeNameEnd = "-ref"; NamedNodeMap attributes = node.getAttributes(); int attributeCount = attributes.getLength(); for (int i = 0; i < attributeCount; i++) { AttrImpl attribute = (AttrImpl) attributes.item(i); String attributeName = attribute.getNodeName(); if (attributeName != null && attributeName.startsWith(attributeNameStart)) { if (attributeName.equals(attributeNameStart + methodName) || attributeName.equals(attributeNameStart + methodName + optionalAttributeNameEnd)) { List<IType> types = BeansEditorUtils.getClassNamesOfBean(file, node); if (types.contains(((IMethod) element).getDeclaringType())) { int offset = attribute.getNameRegionStartOffset() + attributeNameStart.length(); result.add(new ReplaceEdit(offset, methodName.length(), newName)); } } } } } } else { TextEdit edit = null; edit = createMethodTextEditForAttribute(node, element, newName, file, "init-method"); if (edit != null) { result.add(edit); } edit = createMethodTextEditForAttribute(node, element, newName, file, "destroy-method"); if (edit != null) { result.add(edit); } edit = createMethodTextEditForAttribute(node, element, newName, file, "factory-method"); if (edit != null) { result.add(edit); } } } return result; } private static TextEdit createMethodTextEditForAttribute(Node node, IJavaElement element, String newName, IFile file, String attrName) { String methodName = element.getElementName(); if (BeansEditorUtils.hasAttribute(node, attrName)) { String attrMethodName = BeansEditorUtils.getAttribute(node, attrName); if (methodName.equals(attrMethodName)) { List<IType> types = BeansEditorUtils.getClassNamesOfBean(file, node); if (types.contains(((IMethod) element).getDeclaringType())) { AttrImpl attr = (AttrImpl) node.getAttributes().getNamedItem(attrName); int offset = attr.getValueRegionStartOffset() + 1; if (offset >= 0) { return new ReplaceEdit(offset, attrMethodName.length(), newName); } } } } return null; } public static Change createRenameBeanIdChange(IFile file, RenameIdType descriptor, String oldBeanId, String newBeanId, boolean updateReferences, IProgressMonitor monitor) throws CoreException { IStructuredModel model = null; try { model = StructuredModelManager.getModelManager().getModelForRead(file); if (model == null) { return null; } IDOMDocument document = ((DOMModelImpl) model).getDocument(); MultiTextEdit multiEdit = new MultiTextEdit(); NodeList nodes = document.getElementsByTagNameNS(descriptor.getElementNamespaceURI(), descriptor.getElementName()); for (int i = 0; i < nodes.getLength(); i++) { TextEdit edit = createRenameBeanIdTextEdit(nodes.item(i), oldBeanId, newBeanId); if (edit != null) { multiEdit.addChild(edit); } } if (model != null) { model.releaseFromRead(); model = null; } TextFileChange refChanges = null; if (updateReferences) { refChanges = createRenameBeanRefsChange(file, descriptor, oldBeanId, newBeanId, monitor); } if (multiEdit.hasChildren()) { TextFileChange change = new TextFileChange("", file); change.setEdit(multiEdit); for (TextEdit e : multiEdit.getChildren()) { change.addTextEditGroup(new TextEditGroup("Rename " + descriptor.getType() + " id", e)); } if (refChanges != null) { MultiTextEdit edit = (MultiTextEdit) refChanges.getEdit(); if (edit.hasChildren()) { for (TextEdit e : edit.getChildren()) { edit.removeChild(e); multiEdit.addChild(e); change.addTextEditGroup(new TextEditGroup("Rename " + descriptor.getType() + " reference", e)); } } } return change; } } catch (IOException e) { } finally { if (model != null) { model.releaseFromRead(); } } return null; } private static TextEdit createRenameBeanIdTextEdit(Node node, String oldBeanId, String newBeanId) { if (node == null) { return null; } String id = BeansEditorUtils.getAttribute(node, "id"); if (oldBeanId.equals(id)) { AttrImpl attr = (AttrImpl) node.getAttributes().getNamedItem("id"); int offset = attr.getValueRegionStartOffset() + 1; if (offset >= 0) { return new ReplaceEdit(offset, oldBeanId.length(), newBeanId); } } return null; } public static TextFileChange createRenameBeanRefsChange(IFile file, String oldBeanId, String newBeanId, IProgressMonitor monitor) throws CoreException { RenameIdType descriptor = RenameIdType.BEAN; return createRenameBeanRefsChange(file, descriptor, oldBeanId, newBeanId, monitor); } public static TextFileChange createRenameBeanRefsChange(IFile file, RenameIdType descriptor, String oldBeanId, String newBeanId, IProgressMonitor monitor) throws CoreException { IStructuredModel model = null; try { model = StructuredModelManager.getModelManager().getModelForRead(file); if (model == null) { return null; } IDOMDocument document = ((DOMModelImpl) model).getDocument(); MultiTextEdit multiEdit = new MultiTextEdit(); NodeList nodes = document.getDocumentElement().getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { createRenameBeanRefsTextEdit(nodes.item(i), descriptor, oldBeanId, newBeanId, multiEdit); } if (multiEdit.hasChildren()) { TextFileChange change = new TextFileChange("", file); change.setEdit(multiEdit); for (TextEdit e : multiEdit.getChildren()) { change.addTextEditGroup(new TextEditGroup("Rename " + descriptor.getType() + " reference", e)); } return change; } } catch (IOException e) { } finally { if (model != null) { model.releaseFromRead(); } } return null; } private static void createRenameBeanRefsTextEdit(Node node, RenameIdType descriptor, String oldBeanId, String newBeanId, MultiTextEdit multiEdit) { if (node == null) { return; } String[] attributeNames = descriptor.getReferenceAttributeNames(); for (String attributeName : attributeNames) { createRenameBeanRefTextEditForAttribute(attributeName, node, oldBeanId, newBeanId, multiEdit); } String[] attributeNameStarts = descriptor.getReferenceAttributeStarts(); String[] attributeNameEnds = descriptor.getReferenceAttributeEnds(); if (attributeNameStarts != null && attributeNameEnds != null && attributeNameStarts.length == attributeNameEnds.length) { for(int i = 0; i < attributeNameStarts.length; i++) { String attributeStart = attributeNameStarts[i]; String attributeEnd = attributeNameEnds[i]; createRenameBeanRefTextEditForAttribute(attributeStart, attributeEnd, node, oldBeanId, newBeanId, multiEdit); } } // createRenameBeanRefTextEditForAttribute("depends-on", node, oldBeanId, newBeanId, multiEdit); // createRenameBeanRefTextEditForAttribute("bean", node, oldBeanId, newBeanId, multiEdit); // createRenameBeanRefTextEditForAttribute("local", node, oldBeanId, newBeanId, multiEdit); // createRenameBeanRefTextEditForAttribute("parent", node, oldBeanId, newBeanId, multiEdit); // createRenameBeanRefTextEditForAttribute("ref", node, oldBeanId, newBeanId, multiEdit); // createRenameBeanRefTextEditForAttribute("key-ref", node, oldBeanId, newBeanId, multiEdit); // createRenameBeanRefTextEditForAttribute("value-ref", node, oldBeanId, newBeanId, multiEdit); // createRenameBeanRefTextEditForAttribute("p:", "-ref", node, oldBeanId, newBeanId, multiEdit); NodeList nodes = node.getChildNodes(); if (nodes != null && nodes.getLength() > 0) { for (int i = 0; i < nodes.getLength(); i++) { createRenameBeanRefsTextEdit(nodes.item(i), descriptor, oldBeanId, newBeanId, multiEdit); } } } private static void createRenameBeanRefTextEditForAttribute(String attributeNameStart, String attributeNameEnd, Node node, String oldBeanId, String newBeanId, MultiTextEdit multiEdit) { if (!node.hasAttributes()) return; NamedNodeMap attributes = node.getAttributes(); int attributeCount = attributes.getLength(); for (int i = 0; i < attributeCount; i++) { Node attribute = attributes.item(i); String attributeName = attribute.getNodeName(); if (attributeName != null && attributeName.startsWith(attributeNameStart) && attributeName.endsWith(attributeNameEnd)) { String beanRef = attribute.getNodeValue(); if (beanRef != null && beanRef.equals(oldBeanId)) { int offset = ((AttrImpl) attribute).getValueRegionStartOffset() + 1; if (offset >= 0) { multiEdit.addChild(new ReplaceEdit(offset, beanRef.length(), newBeanId)); } } } } } private static void createRenameBeanRefTextEditForAttribute(String attributeName, Node node, String oldBeanId, String newBeanId, MultiTextEdit multiEdit) { if (BeansEditorUtils.hasAttribute(node, attributeName)) { String beanRef = BeansEditorUtils.getAttribute(node, attributeName); if (beanRef != null && beanRef.equals(oldBeanId)) { AttrImpl attr = (AttrImpl) node.getAttributes().getNamedItem(attributeName); int offset = attr.getValueRegionStartOffset() + 1; if (offset >= 0) { multiEdit.addChild(new ReplaceEdit(offset, beanRef.length(), newBeanId)); } } } } public static void createRenameChange(TextChange textChange, TextEdit textEdit, IFile file, IJavaElement[] affectedElements, String[] newNames, IProgressMonitor monitor) throws CoreException { IJavaProject jp = JdtUtils.getJavaProject(file.getProject()); IStructuredModel model = null; try { model = StructuredModelManager.getModelManager().getModelForRead(file); if (model == null) { return; } IDOMDocument document = ((DOMModelImpl) model).getDocument(); NodeList nodes = document.getChildNodes(); for (int j = 0; j < affectedElements.length; j++) { IJavaElement je = affectedElements[j]; // check that the element we are about to change is on the file's classpath if (jp == null || (jp != null && jp.isOnClasspath(je))) { for (int i = 0; i < nodes.getLength(); i++) { Node n = nodes.item(i); recursiveCreateTextEdit(newNames[j], textChange, textEdit, je, n); } } } } catch (IOException e) { } finally { if (model != null) { model.releaseFromRead(); } } } private static void recursiveCreateTextEdit(String newName, TextChange textChange, TextEdit textEdit, IJavaElement je, Node n) { createTextEdit(n, je, newName, textChange, textEdit); NodeList children = n.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { recursiveCreateTextEdit(newName, textChange, textEdit, je, children.item(i)); } } private static void createTextEdit(Node node, IJavaElement element, String newName, TextChange textChange, TextEdit textEdit) { if (node == null) { return; } String oldName = (element instanceof IType) ? ((IType) element).getFullyQualifiedName('$') : element .getElementName(); // creating replace edits for attributes NamedNodeMap attributes = node.getAttributes(); if (attributes != null) { for (int i = 0; i < attributes.getLength(); i++) { String attribute = attributes.item(i).getNodeValue(); if (oldName.equals(attribute) || isGoodMatch(attribute, oldName, element instanceof IPackageFragment)) { AttrImpl attr = (AttrImpl) attributes.getNamedItem(attributes.item(i).getNodeName()); int offset = attr.getValueRegionStartOffset() + 1; if (offset >= 0) { ReplaceEdit edit = new ReplaceEdit(offset, oldName.length(), newName); textEdit.addChild(edit); textChange.addTextEditGroup(new TextEditGroup("Update class reference", edit)); } } } } // creating replace edits for value strings if (node instanceof TextImpl) { String value = node.getNodeValue(); if (value != null) { if (oldName.equals(value) || isGoodMatch(value, oldName, element instanceof IPackageFragment)) { int offset = ((TextImpl)node).getStartOffset(); if (offset >= 0) { ReplaceEdit edit = new ReplaceEdit(offset, oldName.length(), newName); textEdit.addChild(edit); textChange.addTextEditGroup(new TextEditGroup("Update class reference", edit)); } } } } } private static boolean isGoodMatch(String value, String oldName, boolean isPackage) { if (value == null || value.length() <= oldName.length()) { return false; } boolean goodLengthMatch = isPackage ? value.lastIndexOf('.') <= oldName.length() : value.charAt(oldName .length()) == '$'; return value.startsWith(oldName) && goodLengthMatch; } }