diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidTypeMoveParticipant.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidTypeMoveParticipant.java | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidTypeMoveParticipant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidTypeMoveParticipant.java new file mode 100644 index 000000000..2146184c8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidTypeMoveParticipant.java @@ -0,0 +1,362 @@ +/* + * 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.refactorings.core; + +import static com.android.SdkConstants.ANDROID_URI; +import static com.android.SdkConstants.ATTR_CLASS; +import static com.android.SdkConstants.ATTR_CONTEXT; +import static com.android.SdkConstants.ATTR_NAME; +import static com.android.SdkConstants.DOT_XML; +import static com.android.SdkConstants.EXT_XML; +import static com.android.SdkConstants.TOOLS_URI; +import static com.android.SdkConstants.VIEW_FRAGMENT; +import static com.android.SdkConstants.VIEW_TAG; + +import com.android.SdkConstants; +import com.android.annotations.NonNull; +import com.android.ide.common.xml.ManifestData; +import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.resources.ResourceFolderType; +import com.android.utils.SdkUtils; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.CompositeChange; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.TextFileChange; +import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; +import org.eclipse.ltk.core.refactoring.participants.MoveParticipant; +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.xml.core.internal.provisional.document.IDOMModel; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * A participant to participate in refactorings that move a type in an Android project. + * The class updates android manifest and the layout file + * The user can suppress refactoring by disabling the "Update references" checkbox + * <p> + * Rename participants are registered via the extension point <code> + * org.eclipse.ltk.core.refactoring.moveParticipants</code>. + * Extensions to this extension point must therefore extend <code>org.eclipse.ltk.core.refactoring.participants.MoveParticipant</code>. + * </p> + */ +@SuppressWarnings("restriction") +public class AndroidTypeMoveParticipant extends MoveParticipant { + + private IProject mProject; + protected IFile mManifestFile; + protected String mOldFqcn; + protected String mNewFqcn; + protected String mAppPackage; + + @Override + public String getName() { + return "Android Type Move"; + } + + @Override + public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context) + throws OperationCanceledException { + return new RefactoringStatus(); + } + + @Override + protected boolean initialize(Object element) { + if (element instanceof IType) { + IType type = (IType) element; + IJavaProject javaProject = (IJavaProject) type.getAncestor(IJavaElement.JAVA_PROJECT); + mProject = javaProject.getProject(); + IResource manifestResource = mProject.findMember(AdtConstants.WS_SEP + + SdkConstants.FN_ANDROID_MANIFEST_XML); + + if (manifestResource == null || !manifestResource.exists() + || !(manifestResource instanceof IFile)) { + RefactoringUtil.logInfo("Invalid or missing the " + + SdkConstants.FN_ANDROID_MANIFEST_XML + " in the " + mProject.getName() + + " project."); + return false; + } + mManifestFile = (IFile) manifestResource; + ManifestData manifestData; + manifestData = AndroidManifestHelper.parseForData(mManifestFile); + if (manifestData == null) { + return false; + } + mAppPackage = manifestData.getPackage(); + mOldFqcn = type.getFullyQualifiedName(); + Object destination = getArguments().getDestination(); + if (destination instanceof IPackageFragment) { + IPackageFragment packageFragment = (IPackageFragment) destination; + mNewFqcn = packageFragment.getElementName() + "." + type.getElementName(); + } else if (destination instanceof IResource) { + try { + IPackageFragment[] fragments = javaProject.getPackageFragments(); + for (IPackageFragment fragment : fragments) { + IResource resource = fragment.getResource(); + if (resource.equals(destination)) { + mNewFqcn = fragment.getElementName() + '.' + type.getElementName(); + break; + } + } + } catch (JavaModelException e) { + // pass + } + } + return mOldFqcn != null && mNewFqcn != null; + } + + return false; + } + + @Override + public Change createChange(IProgressMonitor pm) throws CoreException, + OperationCanceledException { + if (pm.isCanceled()) { + return null; + } + if (!getArguments().getUpdateReferences()) { + return null; + } + CompositeChange result = new CompositeChange(getName()); + result.markAsSynthetic(); + + addManifestFileChanges(result); + + // Update layout files; we don't just need to react to custom view + // changes, we need to update fragment references and even tool:context activity + // references + addLayoutFileChanges(mProject, result); + + // Also update in dependent projects + ProjectState projectState = Sdk.getProjectState(mProject); + if (projectState != null) { + Collection<ProjectState> parentProjects = projectState.getFullParentProjects(); + for (ProjectState parentProject : parentProjects) { + IProject project = parentProject.getProject(); + addLayoutFileChanges(project, result); + } + } + + return (result.getChildren().length == 0) ? null : result; + } + + private void addManifestFileChanges(CompositeChange result) { + addXmlFileChanges(mManifestFile, result, true); + } + + private void addLayoutFileChanges(IProject project, CompositeChange result) { + try { + // Update references in XML resource files + IFolder resFolder = project.getFolder(SdkConstants.FD_RESOURCES); + + IResource[] folders = resFolder.members(); + for (IResource folder : folders) { + String folderName = folder.getName(); + ResourceFolderType folderType = ResourceFolderType.getFolderType(folderName); + if (folderType != ResourceFolderType.LAYOUT) { + continue; + } + if (!(folder instanceof IFolder)) { + continue; + } + IResource[] files = ((IFolder) folder).members(); + for (int i = 0; i < files.length; i++) { + IResource member = files[i]; + if ((member instanceof IFile) && member.exists()) { + IFile file = (IFile) member; + String fileName = member.getName(); + + if (SdkUtils.endsWith(fileName, DOT_XML)) { + addXmlFileChanges(file, result, false); + } + } + } + } + } catch (CoreException e) { + RefactoringUtil.log(e); + } + } + + private boolean addXmlFileChanges(IFile file, CompositeChange changes, boolean isManifest) { + IModelManager modelManager = StructuredModelManager.getModelManager(); + IStructuredModel model = null; + try { + model = modelManager.getExistingModelForRead(file); + if (model == null) { + model = modelManager.getModelForRead(file); + } + if (model != null) { + IStructuredDocument document = model.getStructuredDocument(); + if (model instanceof IDOMModel) { + IDOMModel domModel = (IDOMModel) model; + Element root = domModel.getDocument().getDocumentElement(); + if (root != null) { + List<TextEdit> edits = new ArrayList<TextEdit>(); + if (isManifest) { + addManifestReplacements(edits, root, document); + } else { + addLayoutReplacements(edits, root, document); + } + if (!edits.isEmpty()) { + MultiTextEdit rootEdit = new MultiTextEdit(); + rootEdit.addChildren(edits.toArray(new TextEdit[edits.size()])); + TextFileChange change = new TextFileChange(file.getName(), file); + change.setTextType(EXT_XML); + change.setEdit(rootEdit); + changes.add(change); + } + } + } else { + return false; + } + } + + return true; + } catch (IOException e) { + AdtPlugin.log(e, null); + } catch (CoreException e) { + AdtPlugin.log(e, null); + } finally { + if (model != null) { + model.releaseFromRead(); + } + } + + return false; + } + + private void addLayoutReplacements( + @NonNull List<TextEdit> edits, + @NonNull Element element, + @NonNull IStructuredDocument document) { + String tag = element.getTagName(); + if (tag.equals(mOldFqcn)) { + int start = RefactoringUtil.getTagNameRangeStart(element, document); + if (start != -1) { + int end = start + mOldFqcn.length(); + edits.add(new ReplaceEdit(start, end - start, mNewFqcn)); + } + } else if (tag.equals(VIEW_TAG)) { + Attr classNode = element.getAttributeNode(ATTR_CLASS); + if (classNode != null && classNode.getValue().equals(mOldFqcn)) { + int start = RefactoringUtil.getAttributeValueRangeStart(classNode, document); + if (start != -1) { + int end = start + mOldFqcn.length(); + edits.add(new ReplaceEdit(start, end - start, mNewFqcn)); + } + } + } else if (tag.equals(VIEW_FRAGMENT)) { + Attr classNode = element.getAttributeNode(ATTR_CLASS); + if (classNode == null) { + classNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME); + } + if (classNode != null && classNode.getValue().equals(mOldFqcn)) { + int start = RefactoringUtil.getAttributeValueRangeStart(classNode, document); + if (start != -1) { + int end = start + mOldFqcn.length(); + edits.add(new ReplaceEdit(start, end - start, mNewFqcn)); + } + } + } else if (element.hasAttributeNS(TOOLS_URI, ATTR_CONTEXT)) { + Attr classNode = element.getAttributeNodeNS(TOOLS_URI, ATTR_CONTEXT); + if (classNode != null && classNode.getValue().equals(mOldFqcn)) { + int start = RefactoringUtil.getAttributeValueRangeStart(classNode, document); + if (start != -1) { + int end = start + mOldFqcn.length(); + edits.add(new ReplaceEdit(start, end - start, mNewFqcn)); + } + } + } + + NodeList children = element.getChildNodes(); + for (int i = 0, n = children.getLength(); i < n; i++) { + Node child = children.item(i); + if (child.getNodeType() == Node.ELEMENT_NODE) { + addLayoutReplacements(edits, (Element) child, document); + } + } + } + + private void addManifestReplacements( + @NonNull List<TextEdit> edits, + @NonNull Element element, + @NonNull IStructuredDocument document) { + NamedNodeMap attributes = element.getAttributes(); + for (int i = 0, n = attributes.getLength(); i < n; i++) { + Attr attr = (Attr) attributes.item(i); + if (!RefactoringUtil.isManifestClassAttribute(attr)) { + continue; + } + + String value = attr.getValue(); + if (value.equals(mOldFqcn)) { + int start = RefactoringUtil.getAttributeValueRangeStart(attr, document); + if (start != -1) { + int end = start + mOldFqcn.length(); + edits.add(new ReplaceEdit(start, end - start, mNewFqcn)); + } + } else if (value.startsWith(".")) { //$NON-NLS-1$ + String fqcn = mAppPackage + value; + if (fqcn.equals(mOldFqcn)) { + int start = RefactoringUtil.getAttributeValueRangeStart(attr, document); + if (start != -1) { + int end = start + value.length(); + edits.add(new ReplaceEdit(start, end - start, mNewFqcn)); + } + } + } + } + + NodeList children = element.getChildNodes(); + for (int i = 0, n = children.getLength(); i < n; i++) { + Node child = children.item(i); + if (child.getNodeType() == Node.ELEMENT_NODE) { + addManifestReplacements(edits, (Element) child, document); + } + } + } +} |