blob: 2146184c8be8c4ff3d8c51d59c86007b2802d352 [file] [log] [blame]
/*
* 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);
}
}
}
}