aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/ApplicationPackageNameRefactoring.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/ApplicationPackageNameRefactoring.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/ApplicationPackageNameRefactoring.java586
1 files changed, 586 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/ApplicationPackageNameRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/ApplicationPackageNameRefactoring.java
new file mode 100644
index 000000000..406cebca4
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/ApplicationPackageNameRefactoring.java
@@ -0,0 +1,586 @@
+/*
+ * 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.renamepackage;
+
+import static com.android.SdkConstants.FN_BUILD_CONFIG_BASE;
+import static com.android.SdkConstants.FN_MANIFEST_BASE;
+import static com.android.SdkConstants.FN_RESOURCE_BASE;
+
+import com.android.SdkConstants;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.xml.AndroidManifest;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceVisitor;
+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.core.runtime.Status;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.dom.AST;
+import org.eclipse.jdt.core.dom.ASTParser;
+import org.eclipse.jdt.core.dom.ASTVisitor;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.ImportDeclaration;
+import org.eclipse.jdt.core.dom.Name;
+import org.eclipse.jdt.core.dom.QualifiedName;
+import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
+import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.CompositeChange;
+import org.eclipse.ltk.core.refactoring.Refactoring;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.ltk.core.refactoring.TextEditChangeGroup;
+import org.eclipse.ltk.core.refactoring.TextFileChange;
+import org.eclipse.text.edits.MalformedTreeException;
+import org.eclipse.text.edits.MultiTextEdit;
+import org.eclipse.text.edits.ReplaceEdit;
+import org.eclipse.text.edits.TextEdit;
+import org.eclipse.text.edits.TextEditGroup;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
+import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Wrapper class defining the stages of the refactoring process
+ */
+@SuppressWarnings("restriction")
+class ApplicationPackageNameRefactoring extends Refactoring {
+ private final IProject mProject;
+ private final Name mOldPackageName;
+ private final Name mNewPackageName;
+
+ List<String> MAIN_COMPONENT_TYPES_LIST = Arrays.asList(MAIN_COMPONENT_TYPES);
+
+ ApplicationPackageNameRefactoring(
+ IProject project,
+ Name oldPackageName,
+ Name newPackageName) {
+ mProject = project;
+ mOldPackageName = oldPackageName;
+ mNewPackageName = newPackageName;
+ }
+
+ @Override
+ public RefactoringStatus checkInitialConditions(IProgressMonitor pm)
+ throws CoreException, OperationCanceledException {
+
+ // Accurate refactoring of the "shorthand" names in
+ // AndroidManifest.xml depends on not having compilation errors.
+ if (mProject.findMaxProblemSeverity(
+ IMarker.PROBLEM,
+ true,
+ IResource.DEPTH_INFINITE) == IMarker.SEVERITY_ERROR) {
+ return
+ RefactoringStatus.createFatalErrorStatus("Fix the errors in your project, first.");
+ }
+
+ return new RefactoringStatus();
+ }
+
+ @Override
+ public RefactoringStatus checkFinalConditions(IProgressMonitor pm)
+ throws OperationCanceledException {
+
+ return new RefactoringStatus();
+ }
+
+ @Override
+ public Change createChange(IProgressMonitor pm) throws CoreException,
+ OperationCanceledException {
+
+ // Traverse all files in the project, building up a list of changes
+ JavaFileVisitor fileVisitor = new JavaFileVisitor();
+ mProject.accept(fileVisitor);
+ return fileVisitor.getChange();
+ }
+
+ @Override
+ public String getName() {
+ return "AndroidPackageNameRefactoring"; //$NON-NLS-1$
+ }
+
+ public final static String[] MAIN_COMPONENT_TYPES = {
+ AndroidManifest.NODE_ACTIVITY, AndroidManifest.NODE_SERVICE,
+ AndroidManifest.NODE_RECEIVER, AndroidManifest.NODE_PROVIDER,
+ AndroidManifest.NODE_APPLICATION
+ };
+
+
+ TextEdit updateJavaFileImports(CompilationUnit cu) {
+
+ ImportVisitor importVisitor = new ImportVisitor(cu.getAST());
+ cu.accept(importVisitor);
+ TextEdit rewrittenImports = importVisitor.getTextEdit();
+
+ // If the import of R was potentially implicit, insert an import statement
+ if (rewrittenImports != null && cu.getPackage().getName().getFullyQualifiedName()
+ .equals(mOldPackageName.getFullyQualifiedName())) {
+
+ UsageVisitor usageVisitor = new UsageVisitor();
+ cu.accept(usageVisitor);
+
+ if (usageVisitor.seenAny()) {
+ ImportRewrite irw = ImportRewrite.create(cu, true);
+ if (usageVisitor.hasSeenR()) {
+ irw.addImport(mNewPackageName.getFullyQualifiedName() + '.'
+ + FN_RESOURCE_BASE);
+ }
+ if (usageVisitor.hasSeenBuildConfig()) {
+ irw.addImport(mNewPackageName.getFullyQualifiedName() + '.'
+ + FN_BUILD_CONFIG_BASE);
+ }
+ if (usageVisitor.hasSeenManifest()) {
+ irw.addImport(mNewPackageName.getFullyQualifiedName() + '.'
+ + FN_MANIFEST_BASE);
+ }
+
+ try {
+ rewrittenImports.addChild( irw.rewriteImports(null) );
+ } catch (MalformedTreeException e) {
+ Status s = new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, e.getMessage(), e);
+ AdtPlugin.getDefault().getLog().log(s);
+ } catch (CoreException e) {
+ Status s = new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, e.getMessage(), e);
+ AdtPlugin.getDefault().getLog().log(s);
+ }
+ }
+ }
+
+ return rewrittenImports;
+ }
+
+ // XML utility functions
+ private String stripQuotes(String text) {
+ int len = text.length();
+ if (len >= 2 && text.charAt(0) == '"' && text.charAt(len - 1) == '"') {
+ return text.substring(1, len - 1);
+ } else if (len >= 2 && text.charAt(0) == '\'' && text.charAt(len - 1) == '\'') {
+ return text.substring(1, len - 1);
+ }
+ return text;
+ }
+
+ private String addQuotes(String text) {
+ return '"' + text + '"';
+ }
+
+ /*
+ * Make the appropriate package name changes to a resource file,
+ * e.g. .xml files in res/layout. This entails updating the namespace
+ * declarations for custom styleable attributes. The namespace prefix
+ * is user-defined and may be declared in any element where or parent
+ * element of where the prefix is used.
+ */
+ TextFileChange editXmlResourceFile(IFile file) {
+
+ IModelManager modelManager = StructuredModelManager.getModelManager();
+ IStructuredDocument sdoc = null;
+ try {
+ sdoc = modelManager.createStructuredDocumentFor(file);
+ } catch (IOException e) {
+ Status s = new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, e.getMessage(), e);
+ AdtPlugin.getDefault().getLog().log(s);
+ } catch (CoreException e) {
+ Status s = new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, e.getMessage(), e);
+ AdtPlugin.getDefault().getLog().log(s);
+ }
+
+ if (sdoc == null) {
+ return null;
+ }
+
+ TextFileChange xmlChange = new TextFileChange("XML resource file edit", file);
+ xmlChange.setTextType(SdkConstants.EXT_XML);
+
+ MultiTextEdit multiEdit = new MultiTextEdit();
+ ArrayList<TextEditGroup> editGroups = new ArrayList<TextEditGroup>();
+
+ final String oldAppNamespaceString = String.format(AdtConstants.NS_CUSTOM_RESOURCES,
+ mOldPackageName.getFullyQualifiedName());
+ final String newAppNamespaceString = String.format(AdtConstants.NS_CUSTOM_RESOURCES,
+ mNewPackageName.getFullyQualifiedName());
+
+ // Prepare the change set
+ for (IStructuredDocumentRegion region : sdoc.getStructuredDocumentRegions()) {
+
+ if (!DOMRegionContext.XML_TAG_NAME.equals(region.getType())) {
+ continue;
+ }
+
+ int nb = region.getNumberOfRegions();
+ ITextRegionList list = region.getRegions();
+ String lastAttrName = null;
+
+ for (int i = 0; i < nb; i++) {
+ ITextRegion subRegion = list.get(i);
+ String type = subRegion.getType();
+
+ if (DOMRegionContext.XML_TAG_ATTRIBUTE_NAME.equals(type)) {
+ // Memorize the last attribute name seen
+ lastAttrName = region.getText(subRegion);
+
+ } else if (DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE.equals(type)) {
+ // Check this is the attribute and the original string
+
+ if (lastAttrName != null &&
+ lastAttrName.startsWith(SdkConstants.XMLNS_PREFIX)) {
+
+ String lastAttrValue = region.getText(subRegion);
+ if (oldAppNamespaceString.equals(stripQuotes(lastAttrValue))) {
+
+ // Found an occurrence. Create a change for it.
+ TextEdit edit = new ReplaceEdit(
+ region.getStartOffset() + subRegion.getStart(),
+ subRegion.getTextLength(),
+ addQuotes(newAppNamespaceString));
+ TextEditGroup editGroup = new TextEditGroup(
+ "Replace package name in custom namespace prefix", edit);
+
+ multiEdit.addChild(edit);
+ editGroups.add(editGroup);
+ }
+ }
+ }
+ }
+ }
+
+ if (multiEdit.hasChildren()) {
+ xmlChange.setEdit(multiEdit);
+ for (TextEditGroup group : editGroups) {
+ xmlChange.addTextEditChangeGroup(new TextEditChangeGroup(xmlChange, group));
+ }
+
+ return xmlChange;
+ }
+ return null;
+ }
+
+ /*
+ * Replace all instances of the package name in AndroidManifest.xml.
+ * This includes expanding shorthand paths for each Component (Activity,
+ * Service, etc.) and of course updating the application package name.
+ * The namespace prefix might not be "android", so we resolve it
+ * dynamically.
+ */
+ TextFileChange editAndroidManifest(IFile file) {
+
+ IModelManager modelManager = StructuredModelManager.getModelManager();
+ IStructuredDocument sdoc = null;
+ try {
+ sdoc = modelManager.createStructuredDocumentFor(file);
+ } catch (IOException e) {
+ Status s = new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, e.getMessage(), e);
+ AdtPlugin.getDefault().getLog().log(s);
+ } catch (CoreException e) {
+ Status s = new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, e.getMessage(), e);
+ AdtPlugin.getDefault().getLog().log(s);
+ }
+
+ if (sdoc == null) {
+ return null;
+ }
+
+ TextFileChange xmlChange = new TextFileChange("Make Manifest edits", file);
+ xmlChange.setTextType(SdkConstants.EXT_XML);
+
+ MultiTextEdit multiEdit = new MultiTextEdit();
+ ArrayList<TextEditGroup> editGroups = new ArrayList<TextEditGroup>();
+
+ // The namespace prefix is guaranteed to be resolved before
+ // the first use of this attribute
+ String android_name_attribute = null;
+
+ // Prepare the change set
+ for (IStructuredDocumentRegion region : sdoc.getStructuredDocumentRegions()) {
+
+ // Only look at XML "top regions"
+ if (!DOMRegionContext.XML_TAG_NAME.equals(region.getType())) {
+ continue;
+ }
+
+ int nb = region.getNumberOfRegions();
+ ITextRegionList list = region.getRegions();
+ String lastTagName = null, lastAttrName = null;
+
+ for (int i = 0; i < nb; i++) {
+ ITextRegion subRegion = list.get(i);
+ String type = subRegion.getType();
+
+ if (DOMRegionContext.XML_TAG_NAME.equals(type)) {
+ // Memorize the last tag name seen
+ lastTagName = region.getText(subRegion);
+
+ } else if (DOMRegionContext.XML_TAG_ATTRIBUTE_NAME.equals(type)) {
+ // Memorize the last attribute name seen
+ lastAttrName = region.getText(subRegion);
+
+ } else if (DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE.equals(type)) {
+
+ String lastAttrValue = region.getText(subRegion);
+ if (lastAttrName != null &&
+ lastAttrName.startsWith(SdkConstants.XMLNS_PREFIX)) {
+
+ // Resolves the android namespace prefix for this file
+ if (SdkConstants.ANDROID_URI.equals(stripQuotes(lastAttrValue))) {
+ String android_namespace_prefix = lastAttrName
+ .substring(SdkConstants.XMLNS_PREFIX.length());
+ android_name_attribute = android_namespace_prefix + ':'
+ + AndroidManifest.ATTRIBUTE_NAME;
+ }
+ } else if (AndroidManifest.NODE_MANIFEST.equals(lastTagName)
+ && AndroidManifest.ATTRIBUTE_PACKAGE.equals(lastAttrName)) {
+
+ // Found an occurrence. Create a change for it.
+ TextEdit edit = new ReplaceEdit(region.getStartOffset()
+ + subRegion.getStart(), subRegion.getTextLength(),
+ addQuotes(mNewPackageName.getFullyQualifiedName()));
+
+ multiEdit.addChild(edit);
+ editGroups.add(new TextEditGroup("Change Android package name", edit));
+
+ } else if (MAIN_COMPONENT_TYPES_LIST.contains(lastTagName)
+ && lastAttrName != null
+ && lastAttrName.equals(android_name_attribute)) {
+
+ String package_path = stripQuotes(lastAttrValue);
+ String old_package_name_string = mOldPackageName.getFullyQualifiedName();
+
+ String absolute_path = AndroidManifest.combinePackageAndClassName(
+ old_package_name_string, package_path);
+
+ TextEdit edit = new ReplaceEdit(region.getStartOffset()
+ + subRegion.getStart(), subRegion.getTextLength(),
+ addQuotes(absolute_path));
+
+ multiEdit.addChild(edit);
+
+ editGroups.add(new TextEditGroup("Update component path", edit));
+ }
+ }
+ }
+ }
+
+ if (multiEdit.hasChildren()) {
+ xmlChange.setEdit(multiEdit);
+ for (TextEditGroup group : editGroups) {
+ xmlChange.addTextEditChangeGroup(new TextEditChangeGroup(xmlChange, group));
+ }
+
+ return xmlChange;
+ }
+ return null;
+ }
+
+
+ /*
+ * Iterates through all project files, taking distinct actions based on
+ * whether the file is:
+ * 1) a .java file (replaces or inserts the "import" statements)
+ * 2) a .xml layout file (updates namespace declarations)
+ * 3) the AndroidManifest.xml
+ */
+ class JavaFileVisitor implements IResourceVisitor {
+
+ final List<TextFileChange> mChanges = new ArrayList<TextFileChange>();
+
+ final ASTParser mParser = ASTParser.newParser(AST.JLS3);
+
+ public CompositeChange getChange() {
+
+ Collections.reverse(mChanges);
+ CompositeChange change = new CompositeChange("Refactoring Application package name",
+ mChanges.toArray(new Change[mChanges.size()]));
+ change.markAsSynthetic();
+ return change;
+ }
+
+ @Override
+ public boolean visit(IResource resource) throws CoreException {
+ if (resource instanceof IFile) {
+ IFile file = (IFile) resource;
+ if (SdkConstants.EXT_JAVA.equals(file.getFileExtension())) {
+
+ ICompilationUnit icu = JavaCore.createCompilationUnitFrom(file);
+
+ mParser.setSource(icu);
+ CompilationUnit cu = (CompilationUnit) mParser.createAST(null);
+
+ TextEdit textEdit = updateJavaFileImports(cu);
+ if (textEdit != null && textEdit.hasChildren()) {
+ MultiTextEdit edit = new MultiTextEdit();
+ edit.addChild(textEdit);
+
+ TextFileChange text_file_change = new TextFileChange(file.getName(), file);
+ text_file_change.setTextType(SdkConstants.EXT_JAVA);
+ text_file_change.setEdit(edit);
+ mChanges.add(text_file_change);
+ }
+
+ // XXX Partially taken from ExtractStringRefactoring.java
+ // Check this a Layout XML file and get the selection and
+ // its context.
+ } else if (SdkConstants.EXT_XML.equals(file.getFileExtension())) {
+
+ if (SdkConstants.FN_ANDROID_MANIFEST_XML.equals(file.getName())) {
+ // Ensure that this is the root manifest, not some other copy
+ // (such as the one in bin/)
+ IPath path = file.getFullPath();
+ if (path.segmentCount() == 2) {
+ TextFileChange manifest_change = editAndroidManifest(file);
+ mChanges.add(manifest_change);
+ }
+ } else {
+
+ // Currently we only support Android resource XML files,
+ // so they must have a path similar to
+ // project/res/<type>[-<configuration>]/*.xml
+ // There is no support for sub folders, so the segment count must be 4.
+ // We don't need to check the type folder name because
+ // a/ we only accept an AndroidXmlEditor source and
+ // b/ aapt generates a compilation error for unknown folders.
+ IPath path = file.getFullPath();
+ // check if we are inside the project/res/* folder.
+ if (path.segmentCount() == 4) {
+ if (path.segment(1).equalsIgnoreCase(SdkConstants.FD_RESOURCES)) {
+
+
+ TextFileChange xmlChange = editXmlResourceFile(file);
+ if (xmlChange != null) {
+ mChanges.add(xmlChange);
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+
+ } else if (resource instanceof IFolder) {
+ return !SdkConstants.FD_GEN_SOURCES.equals(resource.getName());
+ }
+
+ return true;
+ }
+ }
+
+ private static class UsageVisitor extends ASTVisitor {
+ private boolean mSeenManifest;
+ private boolean mSeenR;
+ private boolean mSeenBuildConfig;
+
+ @Override
+ public boolean visit(QualifiedName node) {
+ Name qualifier = node.getQualifier();
+ if (qualifier.isSimpleName()) {
+ String name = qualifier.toString();
+ if (name.equals(FN_RESOURCE_BASE)) {
+ mSeenR = true;
+ } else if (name.equals(FN_BUILD_CONFIG_BASE)) {
+ mSeenBuildConfig = true;
+ } else if (name.equals(FN_MANIFEST_BASE)) {
+ mSeenManifest = true;
+ }
+ }
+ return super.visit(node);
+ };
+
+ public boolean seenAny() {
+ return mSeenR || mSeenBuildConfig || mSeenManifest;
+ }
+
+ public boolean hasSeenBuildConfig() {
+ return mSeenBuildConfig;
+ }
+ public boolean hasSeenManifest() {
+ return mSeenManifest;
+ }
+ public boolean hasSeenR() {
+ return mSeenR;
+ }
+ }
+
+ private class ImportVisitor extends ASTVisitor {
+
+ final AST mAst;
+ final ASTRewrite mRewriter;
+
+ ImportVisitor(AST ast) {
+ mAst = ast;
+ mRewriter = ASTRewrite.create(ast);
+ }
+
+ public TextEdit getTextEdit() {
+ try {
+ return this.mRewriter.rewriteAST();
+ } catch (JavaModelException e) {
+ Status s = new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, e.getMessage(), e);
+ AdtPlugin.getDefault().getLog().log(s);
+ } catch (IllegalArgumentException e) {
+ Status s = new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, e.getMessage(), e);
+ AdtPlugin.getDefault().getLog().log(s);
+ }
+ return null;
+ }
+
+ @Override
+ public boolean visit(ImportDeclaration id) {
+
+ Name importName = id.getName();
+ if (importName.isQualifiedName()) {
+ QualifiedName qualifiedImportName = (QualifiedName) importName;
+
+ String identifier = qualifiedImportName.getName().getIdentifier();
+ if (identifier.equals(FN_RESOURCE_BASE)) {
+ mRewriter.replace(qualifiedImportName.getQualifier(), mNewPackageName,
+ null);
+ } else if (identifier.equals(FN_BUILD_CONFIG_BASE)
+ && mOldPackageName.toString().equals(
+ qualifiedImportName.getQualifier().toString())) {
+ mRewriter.replace(qualifiedImportName.getQualifier(), mNewPackageName,
+ null);
+
+ } else if (identifier.equals(FN_MANIFEST_BASE)
+ && mOldPackageName.toString().equals(
+ qualifiedImportName.getQualifier().toString())) {
+ mRewriter.replace(qualifiedImportName.getQualifier(), mNewPackageName,
+ null);
+ }
+ }
+
+ return true;
+ }
+ }
+}