/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Eclipse 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/org/documents/epl-v10.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ide.eclipse.adt.internal.refactoring.changes; import com.android.ide.common.layout.LayoutConstants; import com.android.ide.eclipse.adt.internal.refactoring.core.RefactoringUtil; import org.eclipse.core.filebuffers.ITextFileBufferManager; import org.eclipse.core.filebuffers.LocationKind; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.jface.text.IDocument; import org.eclipse.ltk.core.refactoring.DocumentChange; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.text.edits.TextEdit; import org.eclipse.wst.sse.core.StructuredModelManager; import org.eclipse.wst.sse.core.internal.provisional.IModelManager; import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMAttr; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement; import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; import org.w3c.dom.Attr; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * A text change that operates on android layout using WTP SSE model. * It is base class for Rename Package and Rename Type changes */ @SuppressWarnings("restriction") public class AndroidLayoutChange extends DocumentChange { private IDocument mDocument; private ITextFileBufferManager mManager; private IFile mFile; private IStructuredModel mModel; private Set<AndroidLayoutChangeDescription> mChanges; /** * Creates a new <code>AndroidLayoutChange</code> * * @param file the layout file * @param document the document * @param manager the buffer manager * @param changes the list of changes */ public AndroidLayoutChange(IFile file, IDocument document, ITextFileBufferManager manager, Set<AndroidLayoutChangeDescription> changes) { super("", document); //$NON-NLS-1$ mFile = file; mDocument = document; mManager = manager; mChanges = changes; try { this.mModel = getModel(document); } catch (Exception ignore) { } if (mModel != null) { addEdits(); } } @Override public String getName() { return mFile.getName(); } @Override public RefactoringStatus isValid(IProgressMonitor pm) throws CoreException, OperationCanceledException { RefactoringStatus status = super.isValid(pm); if (mModel == null) { status.addFatalError("Invalid the " + getName() + " file."); } return status; } @Override public void setTextType(String type) { super.setTextType(mFile.getFileExtension()); } @Override public void dispose() { super.dispose(); RefactoringUtil.fixModel(mModel, mDocument); if (mManager != null) { try { mManager.disconnect(mFile.getFullPath(), LocationKind.NORMALIZE, new NullProgressMonitor()); } catch (CoreException e) { RefactoringUtil.log(e); } } } // ---- /** * Adds text edits for this change */ private void addEdits() { MultiTextEdit multiEdit = new MultiTextEdit(); for (AndroidLayoutChangeDescription change : mChanges) { if (!change.isStandalone()) { TextEdit edit = createTextEdit(LayoutConstants.VIEW, LayoutConstants.ATTR_CLASS, change.getClassName(), change.getNewName()); if (edit != null) { multiEdit.addChild(edit); } } else { List<TextEdit> edits = createElementTextEdit(change.getClassName(), change.getNewName()); for (TextEdit edit : edits) { multiEdit.addChild(edit); } } } setEdit(multiEdit); } /** * Returns the text changes which change class (custom layout viewer) in layout file * * @param className the class name * @param newName the new class name * * @return list of text changes */ private List<TextEdit> createElementTextEdit(String className, String newName) { IDOMDocument xmlDoc = getDOMDocument(); List<TextEdit> edits = new ArrayList<TextEdit>(); NodeList nodes = xmlDoc.getElementsByTagName(className); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); if (node instanceof IDOMElement) { IDOMElement domNode = (IDOMElement) node; IStructuredDocumentRegion firstRegion = domNode.getFirstStructuredDocumentRegion(); if (firstRegion != null) { int offset = firstRegion.getStartOffset(); edits.add(new ReplaceEdit(offset + 1, className.length(), newName)); } IStructuredDocumentRegion endRegion = domNode.getEndStructuredDocumentRegion(); if (endRegion != null) { int offset = endRegion.getStartOffset(); edits.add(new ReplaceEdit(offset + 2, className.length(), newName)); } } } return edits; } /** * Returns the SSE DOM document * * @return the attribute value */ private IDOMDocument getDOMDocument() { IDOMModel xmlModel = (IDOMModel) mModel; IDOMDocument xmlDoc = xmlModel.getDocument(); return xmlDoc; } /** * Returns the text change that set new value of attribute * * @param attribute the attribute * @param newValue the new value * * @return the text change */ private TextEdit createTextEdit(Attr attribute, String newValue) { if (attribute == null) return null; if (attribute instanceof IDOMAttr) { IDOMAttr domAttr = (IDOMAttr) attribute; String region = domAttr.getValueRegionText(); int offset = domAttr.getValueRegionStartOffset(); if (region != null && region.length() >= 2) { return new ReplaceEdit(offset + 1, region.length() - 2, newValue); } } return null; } /** * Returns the text change that change the value of attribute from oldValue to newValue * * @param elementName the element name * @param argumentName the attribute name * @param oldName the old value * @param newName the new value * * @return the text change */ private TextEdit createTextEdit(String elementName, String argumentName, String oldName, String newName) { IDOMDocument xmlDoc = getDOMDocument(); String name = null; Attr attr = findAttribute(xmlDoc, elementName, argumentName, oldName); if (attr != null) { name = attr.getValue(); } if (name != null && newName != null) { TextEdit edit = createTextEdit(attr, newName); return edit; } return null; } /** * Finds the attribute with values oldName * * @param xmlDoc the document * @param element the element * @param attributeName the attribute * @param oldValue the value * * @return the attribute */ private Attr findAttribute(IDOMDocument xmlDoc, String element, String attributeName, String oldValue) { NodeList nodes = xmlDoc.getElementsByTagName(element); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); NamedNodeMap attributes = node.getAttributes(); if (attributes != null) { Attr attribute = RefactoringUtil.findAndroidAttributes(attributes, attributeName); if (attribute != null) { String value = attribute.getValue(); if (value != null && value.equals(oldValue)) { return attribute; } } } } return null; } /** * Returns the SSE model for a document * * @param document the document * @return the model */ private IStructuredModel getModel(IDocument document) { IModelManager manager = StructuredModelManager.getModelManager(); IStructuredModel model = manager.getExistingModelForRead(document); if (model == null && document instanceof IStructuredDocument) { model = manager.getModelForRead((IStructuredDocument) document); } return model; } }