aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidPackageRenameParticipant.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidPackageRenameParticipant.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidPackageRenameParticipant.java547
1 files changed, 547 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidPackageRenameParticipant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidPackageRenameParticipant.java
new file mode 100644
index 000000000..b821777a5
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidPackageRenameParticipant.java
@@ -0,0 +1,547 @@
+/*
+ * 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.ATTR_PACKAGE;
+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.editors.layout.gle2.DomUtilities;
+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.IPath;
+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.JavaModelException;
+import org.eclipse.jdt.internal.corext.refactoring.changes.RenamePackageChange;
+import org.eclipse.jdt.internal.corext.refactoring.rename.RenameCompilationUnitProcessor;
+import org.eclipse.jdt.internal.corext.refactoring.rename.RenameTypeProcessor;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.Region;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.CompositeChange;
+import org.eclipse.ltk.core.refactoring.FileStatusContext;
+import org.eclipse.ltk.core.refactoring.NullChange;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.ltk.core.refactoring.RefactoringStatusContext;
+import org.eclipse.ltk.core.refactoring.TextFileChange;
+import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
+import org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor;
+import org.eclipse.ltk.core.refactoring.participants.RenameParticipant;
+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.IndexedRegion;
+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.Document;
+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 rename a package 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.renameParticipants</code>.
+ * Extensions to this extension point must therefore extend
+ * <code>org.eclipse.ltk.core.refactoring.participants.RenameParticipant</code>.
+ * </p>
+ */
+@SuppressWarnings("restriction")
+public class AndroidPackageRenameParticipant extends RenameParticipant {
+
+ private IProject mProject;
+ private IFile mManifestFile;
+ private IPackageFragment mPackageFragment;
+ private String mOldPackage;
+ private String mNewPackage;
+ private String mAppPackage;
+ private boolean mRefactoringAppPackage;
+
+ @Override
+ public String getName() {
+ return "Android Package Rename";
+ }
+
+ @Override
+ public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context)
+ throws OperationCanceledException {
+ if (mAppPackage.equals(mOldPackage) && !mRefactoringAppPackage) {
+ IRegion region = null;
+ Document document = DomUtilities.getDocument(mManifestFile);
+ if (document != null && document.getDocumentElement() != null) {
+ Attr attribute = document.getDocumentElement().getAttributeNode(ATTR_PACKAGE);
+ if (attribute instanceof IndexedRegion) {
+ IndexedRegion ir = (IndexedRegion) attribute;
+ int start = ir.getStartOffset();
+ region = new Region(start, ir.getEndOffset() - start);
+ }
+ }
+ if (region == null) {
+ region = new Region(0, 0);
+ }
+ // There's no line wrapping in the error dialog, so split up the message into
+ // individually digestible pieces of information
+ RefactoringStatusContext ctx = new FileStatusContext(mManifestFile, region);
+ RefactoringStatus status = RefactoringStatus.createInfoStatus(
+ "You are refactoring the same package as your application's " +
+ "package (specified in the manifest).\n", ctx);
+ status.addInfo(
+ "Note that this refactoring does NOT also update your " +
+ "application package.", ctx);
+ status.addInfo("The application package defines your application's identity.", ctx);
+ status.addInfo(
+ "If you change it, then it is considered to be a different application.", ctx);
+ status.addInfo("(Users of the previous version cannot update to the new version.)",
+ ctx);
+ status.addInfo(
+ "The application package, and the package containing the code, can differ.",
+ ctx);
+ status.addInfo(
+ "To really change application package, " +
+ "choose \"Android Tools\" > \"Rename Application Package.\" " +
+ "from the project context menu.", ctx);
+ return status;
+ }
+
+ return new RefactoringStatus();
+ }
+
+ @Override
+ protected boolean initialize(final Object element) {
+ mRefactoringAppPackage = false;
+ try {
+ // Only propose this refactoring if the "Update References" checkbox is set.
+ if (!getArguments().getUpdateReferences()) {
+ return false;
+ }
+
+ if (element instanceof IPackageFragment) {
+ mPackageFragment = (IPackageFragment) element;
+ if (!mPackageFragment.containsJavaResources()) {
+ return false;
+ }
+ IJavaProject javaProject = (IJavaProject) mPackageFragment
+ .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;
+ String packageName = mPackageFragment.getElementName();
+ ManifestData manifestData;
+ manifestData = AndroidManifestHelper.parseForData(mManifestFile);
+ if (manifestData == null) {
+ return false;
+ }
+ mAppPackage = manifestData.getPackage();
+ mOldPackage = packageName;
+ mNewPackage = getArguments().getNewName();
+ if (mOldPackage == null || mNewPackage == null) {
+ return false;
+ }
+
+ if (RefactoringUtil.isRefactorAppPackage()
+ && mAppPackage != null
+ && mAppPackage.equals(packageName)) {
+ mRefactoringAppPackage = true;
+ }
+
+ return true;
+ }
+ } catch (JavaModelException ignore) {
+ }
+ return false;
+ }
+
+
+ @Override
+ public Change createChange(IProgressMonitor pm) throws CoreException,
+ OperationCanceledException {
+ if (pm.isCanceled()) {
+ return null;
+ }
+ if (!getArguments().getUpdateReferences()) {
+ return null;
+ }
+
+ RefactoringProcessor p = getProcessor();
+ if (p instanceof RenameCompilationUnitProcessor) {
+ RenameTypeProcessor rtp =
+ ((RenameCompilationUnitProcessor) p).getRenameTypeProcessor();
+ if (rtp != null) {
+ String pattern = rtp.getFilePatterns();
+ boolean updQualf = rtp.getUpdateQualifiedNames();
+ if (updQualf && pattern != null && pattern.contains("xml")) { //$NON-NLS-1$
+ // Do not propose this refactoring if the
+ // "Update fully qualified names in non-Java files" option is
+ // checked and the file patterns mention XML. [c.f. SDK bug 21589]
+ return null;
+ }
+ }
+ }
+
+ IPath pkgPath = mPackageFragment.getPath();
+ IPath genPath = mProject.getFullPath().append(SdkConstants.FD_GEN_SOURCES);
+ if (genPath.isPrefixOf(pkgPath)) {
+ RefactoringUtil.logInfo(getName() + ": Cannot rename generated package.");
+ 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);
+ }
+ }
+
+ if (mRefactoringAppPackage) {
+ Change genChange = getGenPackageChange(pm);
+ if (genChange != null) {
+ result.add(genChange);
+ }
+
+ return new NullChange("Update Imports") {
+ @Override
+ public Change perform(IProgressMonitor monitor) throws CoreException {
+ FixImportsJob job = new FixImportsJob("Fix Rename Package",
+ mManifestFile, mNewPackage);
+ job.schedule(500);
+
+ // Not undoable: just return null instead of an undo-change.
+ return null;
+ }
+ };
+ }
+
+ return (result.getChildren().length == 0) ? null : result;
+ }
+
+ /**
+ * Returns Android gen package text change
+ *
+ * @param pm the progress monitor
+ *
+ * @return Android gen package text change
+ * @throws CoreException if an error happens
+ * @throws OperationCanceledException if the operation is canceled
+ */
+ public Change getGenPackageChange(IProgressMonitor pm) throws CoreException,
+ OperationCanceledException {
+ if (mRefactoringAppPackage) {
+ IPackageFragment genJavaPackageFragment = getGenPackageFragment();
+ if (genJavaPackageFragment != null && genJavaPackageFragment.exists()) {
+ return new RenamePackageChange(genJavaPackageFragment, mNewPackage, true);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return the gen package fragment
+ */
+ private IPackageFragment getGenPackageFragment() throws JavaModelException {
+ IJavaProject javaProject = (IJavaProject) mPackageFragment
+ .getAncestor(IJavaElement.JAVA_PROJECT);
+ if (javaProject != null && javaProject.isOpen()) {
+ IProject project = javaProject.getProject();
+ IFolder genFolder = project.getFolder(SdkConstants.FD_GEN_SOURCES);
+ if (genFolder.exists()) {
+ String javaPackagePath = mAppPackage.replace('.', '/');
+ IPath genJavaPackagePath = genFolder.getFullPath().append(javaPackagePath);
+ IPackageFragment genPackageFragment = javaProject
+ .findPackageFragment(genJavaPackagePath);
+ return genPackageFragment;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the new class name
+ *
+ * @param fqcn the fully qualified class name in the renamed package
+ * @return the new class name
+ */
+ private String getNewClassName(String fqcn) {
+ assert isInRenamedPackage(fqcn) : fqcn;
+ int lastDot = fqcn.lastIndexOf('.');
+ if (lastDot < 0) {
+ return mNewPackage;
+ }
+ String name = fqcn.substring(lastDot, fqcn.length());
+ String newClassName = mNewPackage + name;
+ return newClassName;
+ }
+
+ 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 boolean isInRenamedPackage(String fqcn) {
+ return fqcn.startsWith(mOldPackage)
+ && fqcn.length() > mOldPackage.length()
+ && fqcn.indexOf('.', mOldPackage.length() + 1) == -1;
+ }
+
+ private void addLayoutReplacements(
+ @NonNull List<TextEdit> edits,
+ @NonNull Element element,
+ @NonNull IStructuredDocument document) {
+ String tag = element.getTagName();
+ if (isInRenamedPackage(tag)) {
+ int start = RefactoringUtil.getTagNameRangeStart(element, document);
+ if (start != -1) {
+ int end = start + tag.length();
+ edits.add(new ReplaceEdit(start, end - start, getNewClassName(tag)));
+ }
+ } else {
+ Attr classNode = null;
+ if (tag.equals(VIEW_TAG)) {
+ classNode = element.getAttributeNode(ATTR_CLASS);
+ } else if (tag.equals(VIEW_FRAGMENT)) {
+ classNode = element.getAttributeNode(ATTR_CLASS);
+ if (classNode == null) {
+ classNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
+ }
+ } else if (element.hasAttributeNS(TOOLS_URI, ATTR_CONTEXT)) {
+ classNode = element.getAttributeNodeNS(TOOLS_URI, ATTR_CONTEXT);
+ if (classNode != null && classNode.getValue().startsWith(".")) { //$NON-NLS-1$
+ classNode = null;
+ }
+ }
+ if (classNode != null) {
+ String fqcn = classNode.getValue();
+ if (isInRenamedPackage(fqcn)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(classNode, document);
+ if (start != -1) {
+ int end = start + fqcn.length();
+ edits.add(new ReplaceEdit(start, end - start, getNewClassName(fqcn)));
+ }
+ }
+ }
+ }
+
+ 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) {
+ if (mRefactoringAppPackage &&
+ element == element.getOwnerDocument().getDocumentElement()) {
+ // Update the app package declaration
+ Attr pkg = element.getAttributeNode(ATTR_PACKAGE);
+ if (pkg != null && pkg.getValue().equals(mOldPackage)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(pkg, document);
+ if (start != -1) {
+ int end = start + mOldPackage.length();
+ edits.add(new ReplaceEdit(start, end - start, mNewPackage));
+ }
+ }
+ }
+
+ 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 (isInRenamedPackage(value)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(attr, document);
+ if (start != -1) {
+ int end = start + value.length();
+ edits.add(new ReplaceEdit(start, end - start, getNewClassName(value)));
+ }
+ } else if (value.startsWith(".")) {
+ // If we're renaming the app package
+ String fqcn = mAppPackage + value;
+ if (isInRenamedPackage(fqcn)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(attr, document);
+ if (start != -1) {
+ int end = start + value.length();
+ String newClassName = getNewClassName(fqcn);
+ if (mRefactoringAppPackage) {
+ newClassName = newClassName.substring(mNewPackage.length());
+ } else if (newClassName.startsWith(mOldPackage)
+ && newClassName.charAt(mOldPackage.length()) == '.') {
+ newClassName = newClassName.substring(mOldPackage.length());
+ }
+
+ if (!newClassName.equals(value)) {
+ edits.add(new ReplaceEdit(start, end - start, newClassName));
+ }
+ }
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+ }
+}