/* * 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 *

* Rename participants are registered via the extension point * org.eclipse.ltk.core.refactoring.moveParticipants. * Extensions to this extension point must therefore extend org.eclipse.ltk.core.refactoring.participants.MoveParticipant. *

*/ @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 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 edits = new ArrayList(); 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 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 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); } } } }