diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings')
24 files changed, 0 insertions, 8264 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 deleted file mode 100644 index b821777a5..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidPackageRenameParticipant.java +++ /dev/null @@ -1,547 +0,0 @@ -/* - * 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); - } - } - } -} 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 deleted file mode 100644 index 2146184c8..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidTypeMoveParticipant.java +++ /dev/null @@ -1,362 +0,0 @@ -/* - * 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); - } - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidTypeRenameParticipant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidTypeRenameParticipant.java deleted file mode 100644 index 7843ab3b4..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidTypeRenameParticipant.java +++ /dev/null @@ -1,529 +0,0 @@ -/* - * 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_MANIFEST_XML; -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.CLASS_VIEW; -import static com.android.SdkConstants.DOT_XML; -import static com.android.SdkConstants.EXT_XML; -import static com.android.SdkConstants.R_CLASS; -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.editors.manifest.ManifestInfo; -import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; -import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; -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.resources.ResourceType; -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.NullProgressMonitor; -import org.eclipse.core.runtime.OperationCanceledException; -import org.eclipse.jdt.core.IField; -import org.eclipse.jdt.core.IJavaElement; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.IType; -import org.eclipse.jdt.core.ITypeHierarchy; -import org.eclipse.jdt.internal.corext.refactoring.rename.RenameCompilationUnitProcessor; -import org.eclipse.jdt.internal.corext.refactoring.rename.RenameTypeProcessor; -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.RefactoringProcessor; -import org.eclipse.ltk.core.refactoring.participants.RenameParticipant; -import org.eclipse.ltk.core.refactoring.participants.RenameRefactoring; -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 rename 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.renameParticipants</code>. - * Extensions to this extension point must therefore extend - * <code>org.eclipse.ltk.core.refactoring.participants.RenameParticipant</code>. - */ -@SuppressWarnings("restriction") -public class AndroidTypeRenameParticipant extends RenameParticipant { - private IProject mProject; - private IFile mManifestFile; - private String mOldFqcn; - private String mNewFqcn; - private String mOldSimpleName; - private String mNewSimpleName; - private String mOldDottedName; - private String mNewDottedName; - private boolean mIsCustomView; - - /** - * Set while we are creating an embedded Java refactoring. This could cause a recursive - * invocation of the XML renaming refactoring to react to the field, so this is flag - * during the call to the Java processor, and is used to ignore requests for adding in - * field reactions during that time. - */ - private static boolean sIgnore; - - @Override - public String getName() { - return "Android Type Rename"; - } - - @Override - public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context) - throws OperationCanceledException { - return new RefactoringStatus(); - } - - @Override - protected boolean initialize(Object element) { - if (sIgnore) { - return false; - } - - 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( - String.format("Invalid or missing file %1$s in project %2$s", - SdkConstants.FN_ANDROID_MANIFEST_XML, - mProject.getName())); - return false; - } - - try { - IType classView = javaProject.findType(CLASS_VIEW); - if (classView != null) { - ITypeHierarchy hierarchy = type.newSupertypeHierarchy(new NullProgressMonitor()); - if (hierarchy.contains(classView)) { - mIsCustomView = true; - } - } - } catch (CoreException e) { - AdtPlugin.log(e, null); - } - - mManifestFile = (IFile) manifestResource; - ManifestData manifestData; - manifestData = AndroidManifestHelper.parseForData(mManifestFile); - if (manifestData == null) { - return false; - } - mOldSimpleName = type.getElementName(); - mOldDottedName = '.' + mOldSimpleName; - mOldFqcn = type.getFullyQualifiedName(); - String packageName = type.getPackageFragment().getElementName(); - mNewSimpleName = getArguments().getNewName(); - mNewDottedName = '.' + mNewSimpleName; - if (packageName != null) { - mNewFqcn = packageName + mNewDottedName; - } else { - mNewFqcn = mNewSimpleName; - } - if (mOldFqcn == null || mNewFqcn == null) { - return false; - } - if (!RefactoringUtil.isRefactorAppPackage() && mNewFqcn.indexOf('.') == -1) { - mNewFqcn = packageName + mNewDottedName; - } - return true; - } - return false; - } - - @Override - public Change createChange(IProgressMonitor pm) throws CoreException, - OperationCanceledException { - if (pm.isCanceled()) { - return null; - } - - // Only propose this refactoring if the "Update References" checkbox is set. - 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; - } - } - } - - CompositeChange result = new CompositeChange(getName()); - - // Only show the children in the refactoring preview dialog - result.markAsSynthetic(); - - addManifestFileChanges(mManifestFile, result); - addLayoutFileChanges(mProject, result); - addJavaChanges(mProject, result, pm); - - // Also update in dependent projects - // TODO: Also do the Java elements, if they are in Jar files, since the library - // projects do this (and the JDT refactoring does not include them) - ProjectState projectState = Sdk.getProjectState(mProject); - if (projectState != null) { - Collection<ProjectState> parentProjects = projectState.getFullParentProjects(); - for (ProjectState parentProject : parentProjects) { - IProject project = parentProject.getProject(); - IResource manifestResource = project.findMember(AdtConstants.WS_SEP - + SdkConstants.FN_ANDROID_MANIFEST_XML); - if (manifestResource != null && manifestResource.exists() - && manifestResource instanceof IFile) { - addManifestFileChanges((IFile) manifestResource, result); - } - addLayoutFileChanges(project, result); - addJavaChanges(project, result, pm); - } - } - - // Look for the field change on the R.java class; it's a derived file - // and will generate file modified manually warnings. Disable it. - RenameResourceParticipant.disableRClassChanges(result); - - return (result.getChildren().length == 0) ? null : result; - } - - private void addJavaChanges(IProject project, CompositeChange result, IProgressMonitor monitor) { - if (!mIsCustomView) { - return; - } - - // Also rename styleables, if any - try { - // Find R class - IJavaProject javaProject = BaseProjectHelper.getJavaProject(project); - ManifestInfo info = ManifestInfo.get(project); - info.getPackage(); - String rFqcn = info.getPackage() + '.' + R_CLASS; - IType styleable = javaProject.findType(rFqcn + '.' + ResourceType.STYLEABLE.getName()); - if (styleable != null) { - IField[] fields = styleable.getFields(); - CompositeChange fieldChanges = null; - for (IField field : fields) { - String name = field.getElementName(); - if (name.equals(mOldSimpleName) || name.startsWith(mOldSimpleName) - && name.length() > mOldSimpleName.length() - && name.charAt(mOldSimpleName.length()) == '_') { - // Rename styleable fields - String newName = name.equals(mOldSimpleName) ? mNewSimpleName : - mNewSimpleName + name.substring(mOldSimpleName.length()); - RenameRefactoring refactoring = - RenameResourceParticipant.createFieldRefactoring(field, - newName, true); - - try { - sIgnore = true; - RefactoringStatus status = refactoring.checkAllConditions(monitor); - if (status != null && !status.hasError()) { - Change fieldChange = refactoring.createChange(monitor); - if (fieldChange != null) { - if (fieldChanges == null) { - fieldChanges = new CompositeChange( - "Update custom view styleable fields"); - // Disable these changes. They sometimes end up - // editing the wrong offsets. It looks like Eclipse - // doesn't ensure that after applying each change it - // also adjusts the other field offsets. I poked around - // and couldn't find a way to do this properly, but - // at least by listing the diffs here it shows what should - // be done. - fieldChanges.setEnabled(false); - } - // Disable change: see comment above. - fieldChange.setEnabled(false); - fieldChanges.add(fieldChange); - } - } - } catch (CoreException e) { - AdtPlugin.log(e, null); - } finally { - sIgnore = false; - } - } - } - if (fieldChanges != null) { - result.add(fieldChanges); - } - } - } catch (CoreException e) { - AdtPlugin.log(e, null); - } - } - - private void addManifestFileChanges(IFile manifestFile, CompositeChange result) { - addXmlFileChanges(manifestFile, result, null); - } - - 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 && - folderType != ResourceFolderType.VALUES) { - 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, folderType); - } - } - } - } - } catch (CoreException e) { - RefactoringUtil.log(e); - } - } - - private boolean addXmlFileChanges(IFile file, CompositeChange changes, - ResourceFolderType folderType) { - 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 (folderType == null) { - assert file.getName().equals(ANDROID_MANIFEST_XML); - addManifestReplacements(edits, root, document); - } else if (folderType == ResourceFolderType.VALUES) { - addValueReplacements(edits, root, document); - } else { - assert folderType == ResourceFolderType.LAYOUT; - 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)) { - // TODO: Handle inner classes ($ vs .) ? - 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)); - } - } else if (classNode != null && classNode.getValue().equals(mOldDottedName)) { - int start = RefactoringUtil.getAttributeValueRangeStart(classNode, document); - if (start != -1) { - int end = start + mOldDottedName.length(); - edits.add(new ReplaceEdit(start, end - start, mNewDottedName)); - } - } - } - - 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 addValueReplacements( - @NonNull List<TextEdit> edits, - @NonNull Element root, - @NonNull IStructuredDocument document) { - // Look for styleable renames for custom views - String declareStyleable = ResourceType.DECLARE_STYLEABLE.getName(); - List<Element> topLevel = DomUtilities.getChildren(root); - for (Element element : topLevel) { - String tag = element.getTagName(); - if (declareStyleable.equals(tag)) { - Attr nameNode = element.getAttributeNode(ATTR_NAME); - if (nameNode != null && mOldSimpleName.equals(nameNode.getValue())) { - int start = RefactoringUtil.getAttributeValueRangeStart(nameNode, document); - if (start != -1) { - int end = start + mOldSimpleName.length(); - edits.add(new ReplaceEdit(start, end - start, mNewSimpleName)); - } - } - } - } - } - - 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.equals(mOldDottedName)) { - int start = RefactoringUtil.getAttributeValueRangeStart(attr, document); - if (start != -1) { - int end = start + mOldDottedName.length(); - edits.add(new ReplaceEdit(start, end - start, mNewDottedName)); - } - } - } - - 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); - } - } - } -}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/FixImportsJob.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/FixImportsJob.java deleted file mode 100644 index 552e6a845..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/FixImportsJob.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * 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 org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IMarker; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IncrementalProjectBuilder; -import org.eclipse.core.resources.WorkspaceJob; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; -import org.eclipse.jdt.core.ICompilationUnit; -import org.eclipse.jdt.core.IJavaElement; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.IPackageFragment; -import org.eclipse.jdt.core.ISourceRange; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.dom.CompilationUnit; -import org.eclipse.jdt.core.search.TypeNameMatch; -import org.eclipse.jdt.internal.corext.codemanipulation.CodeGenerationSettings; -import org.eclipse.jdt.internal.corext.codemanipulation.OrganizeImportsOperation; -import org.eclipse.jdt.internal.corext.codemanipulation.OrganizeImportsOperation.IChooseImportQuery; -import org.eclipse.jdt.internal.ui.actions.WorkbenchRunnableAdapter; -import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility; -import org.eclipse.jdt.internal.ui.preferences.JavaPreferencesSettings; -import org.eclipse.jdt.ui.SharedASTProvider; -import org.eclipse.swt.widgets.Display; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.progress.IProgressService; - -/** - * The helper class which fixes the import errors after refactoring - * - */ -@SuppressWarnings("restriction") -public class FixImportsJob extends WorkspaceJob { - - private IFile mAndroidManifest; - - private String mJavaPackage; - - /** - * Creates a new <code>FixImportsJob</code> - * - * @param name the job name - * @param androidManifest the android manifest file - * @param javaPackage the android java package - */ - public FixImportsJob(String name, IFile androidManifest, String javaPackage) { - super(name); - this.mAndroidManifest = androidManifest; - this.mJavaPackage = javaPackage; - } - - @Override - public IStatus runInWorkspace(final IProgressMonitor monitor) throws CoreException { - if (mJavaPackage == null || mAndroidManifest == null || !mAndroidManifest.exists()) { - return Status.CANCEL_STATUS; - } - IProject project = mAndroidManifest.getProject(); - IJavaProject javaProject = JavaCore.create(project); - if (javaProject == null || !javaProject.isOpen()) { - return Status.CANCEL_STATUS; - } - - project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor); - - IMarker[] markers = project.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE); - for (int i = 0; i < markers.length; i++) { - IMarker marker = markers[i]; - IResource resource = marker.getResource(); - try { - IJavaElement element = JavaCore.create(resource); - if (element != null && (element instanceof ICompilationUnit)) { - final ICompilationUnit cu = (ICompilationUnit) element; - IPackageFragment packageFragment = (IPackageFragment) cu - .getAncestor(IJavaElement.PACKAGE_FRAGMENT); - if (packageFragment != null && packageFragment.exists()) { - String packageName = packageFragment.getElementName(); - if (packageName != null && packageName.startsWith(mJavaPackage)) { - CompilationUnit astRoot = SharedASTProvider.getAST(cu, - SharedASTProvider.WAIT_ACTIVE_ONLY, null); - CodeGenerationSettings settings = JavaPreferencesSettings - .getCodeGenerationSettings(cu.getJavaProject()); - final boolean hasAmbiguity[] = new boolean[] { - false - }; - IChooseImportQuery query = new IChooseImportQuery() { - @Override - public TypeNameMatch[] chooseImports(TypeNameMatch[][] openChoices, - ISourceRange[] ranges) { - hasAmbiguity[0] = true; - return new TypeNameMatch[0]; - } - }; - final OrganizeImportsOperation op = new OrganizeImportsOperation(cu, - astRoot, settings.importIgnoreLowercase, !cu.isWorkingCopy(), - true, query); - Display.getDefault().asyncExec(new Runnable() { - - @Override - public void run() { - try { - IProgressService progressService = PlatformUI - .getWorkbench().getProgressService(); - progressService.run( - true, - true, - new WorkbenchRunnableAdapter(op, op - .getScheduleRule())); - IEditorPart openEditor = EditorUtility.isOpenInEditor(cu); - if (openEditor != null) { - openEditor.doSave(monitor); - } - } catch (Throwable e) { - RefactoringUtil.log(e); - } - } - }); - - } - } - } - } catch (Throwable e) { - RefactoringUtil.log(e); - } - } - return Status.OK_STATUS; - } -}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RefactoringUtil.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RefactoringUtil.java deleted file mode 100644 index 04ebcfa26..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RefactoringUtil.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * 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_NAME; -import static com.android.xml.AndroidManifest.ATTRIBUTE_BACKUP_AGENT; -import static com.android.xml.AndroidManifest.ATTRIBUTE_MANAGE_SPACE_ACTIVITY; -import static com.android.xml.AndroidManifest.ATTRIBUTE_PARENT_ACTIVITY_NAME; -import static com.android.xml.AndroidManifest.ATTRIBUTE_TARGET_ACTIVITY; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.xml.AndroidManifest; - -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.IDocument; -import org.eclipse.wst.sse.core.StructuredModelManager; -import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; -import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; -import org.w3c.dom.Attr; -import org.w3c.dom.Element; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; - -/** - * The utility class for android refactoring - * - */ -@SuppressWarnings("restriction") -public class RefactoringUtil { - - private static boolean sRefactorAppPackage = false; - - /** - * Releases SSE read model; saves SSE model if exists edit model - * Called in dispose method of refactoring change classes - * - * @param model the SSE model - * @param document the document - */ - public static void fixModel(IStructuredModel model, IDocument document) { - if (model != null) { - model.releaseFromRead(); - } - model = null; - if (document == null) { - return; - } - try { - model = StructuredModelManager.getModelManager().getExistingModelForEdit(document); - if (model != null) { - model.save(); - } - } catch (UnsupportedEncodingException e1) { - // ignore - } catch (IOException e1) { - // ignore - } catch (CoreException e1) { - // ignore - } finally { - if (model != null) { - model.releaseFromEdit(); - } - } - } - - /** - * Logs the info message - * - * @param message the message - */ - public static void logInfo(String message) { - AdtPlugin.log(IStatus.INFO, AdtPlugin.PLUGIN_ID, message); - } - - /** - * Logs the the exception - * - * @param e the exception - */ - public static void log(Throwable e) { - AdtPlugin.log(e, e.getMessage()); - } - - /** - * @return true if Rename/Move package needs to change the application package - * default is false - * - */ - public static boolean isRefactorAppPackage() { - return sRefactorAppPackage; - } - - /** - * @param refactorAppPackage true if Rename/Move package needs to change the application package - */ - public static void setRefactorAppPackage(boolean refactorAppPackage) { - RefactoringUtil.sRefactorAppPackage = refactorAppPackage; - } - - /** - * Returns the range of the attribute value in the given document - * - * @param attr the attribute to look up - * @param document the document containing the attribute - * @return the range of the value text, not including quotes, in the document - */ - public static int getAttributeValueRangeStart( - @NonNull Attr attr, - @NonNull IDocument document) { - IndexedRegion region = (IndexedRegion) attr; - int potentialStart = attr.getName().length() + 2; // + 2: add =" - String text; - try { - text = document.get(region.getStartOffset(), - region.getEndOffset() - region.getStartOffset()); - } catch (BadLocationException e) { - return -1; - } - String value = attr.getValue(); - int index = text.indexOf(value, potentialStart); - if (index != -1) { - return region.getStartOffset() + index; - } else { - return -1; - } - } - - /** - * Returns the start of the tag name of the given element - * - * @param element the element to look up - * @param document the document containing the attribute - * @return the index of the start tag in the document - */ - public static int getTagNameRangeStart( - @NonNull Element element, - @NonNull IDocument document) { - IndexedRegion region = (IndexedRegion) element; - int potentialStart = 1; // add '<' - String text; - try { - text = document.get(region.getStartOffset(), - region.getEndOffset() - region.getStartOffset()); - } catch (BadLocationException e) { - return -1; - } - int index = text.indexOf(element.getTagName(), potentialStart); - if (index != -1) { - return region.getStartOffset() + index; - } else { - return -1; - } - } - - /** - * Returns whether the given manifest attribute should be considered to describe - * a class name. These will be eligible for refactoring when classes are renamed - * or moved. - * - * @param attribute the manifest attribute - * @return true if this attribute can describe a class - */ - public static boolean isManifestClassAttribute(@NonNull Attr attribute) { - return isManifestClassAttribute( - attribute.getOwnerElement().getTagName(), - attribute.getNamespaceURI(), - attribute.getLocalName()); - } - - /** - * Returns whether the given manifest attribute should be considered to describe - * a class name. These will be eligible for refactoring when classes are renamed - * or moved. - * - * @param tag the tag, if known - * @param uri the attribute namespace, if any - * @param name the attribute local name, if any - * @return true if this attribute can describe a class - */ - public static boolean isManifestClassAttribute( - @Nullable String tag, - @Nullable String uri, - @Nullable String name) { - if (name == null) { - return false; - } - - if ((name.equals(ATTR_NAME) - && (AndroidManifest.NODE_ACTIVITY.equals(tag) - || AndroidManifest.NODE_APPLICATION.equals(tag) - || AndroidManifest.NODE_INSTRUMENTATION.equals(tag) - || AndroidManifest.NODE_PROVIDER.equals(tag) - || AndroidManifest.NODE_SERVICE.equals(tag) - || AndroidManifest.NODE_RECEIVER.equals(tag))) - || name.equals(ATTRIBUTE_TARGET_ACTIVITY) - || name.equals(ATTRIBUTE_MANAGE_SPACE_ACTIVITY) - || name.equals(ATTRIBUTE_BACKUP_AGENT) - || name.equals(ATTRIBUTE_PARENT_ACTIVITY_NAME)) { - return ANDROID_URI.equals(uri); - } - - return false; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourcePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourcePage.java deleted file mode 100644 index 6779fd322..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourcePage.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2012 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.PREFIX_RESOURCE_REF; -import static com.android.SdkConstants.R_CLASS; - -import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator; -import com.android.resources.ResourceType; - -import org.eclipse.jdt.internal.ui.refactoring.TextInputWizardPage; -import org.eclipse.jface.dialogs.Dialog; -import org.eclipse.ltk.core.refactoring.RefactoringStatus; -import org.eclipse.ltk.core.refactoring.participants.RenameRefactoring; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -import java.util.Set; - -@SuppressWarnings("restriction") // JDT refactoring UI -class RenameResourcePage extends TextInputWizardPage implements SelectionListener { - private Label mXmlLabel; - private Label mJavaLabel; - private Button mUpdateReferences; - private boolean mCanClear; - private ResourceType mType; - private ResourceNameValidator mValidator; - - /** - * Create the wizard. - * @param type the type of the resource to be renamed - * @param initial initial renamed value - * @param canClear whether the dialog should allow clearing the field - */ - public RenameResourcePage(ResourceType type, String initial, boolean canClear) { - super(type.getName(), true, initial); - mType = type; - mCanClear = canClear; - - mValidator = ResourceNameValidator.create(false /*allowXmlExtension*/, - (Set<String>) null, mType); - } - - @SuppressWarnings("unused") // SWT constructors aren't really unused, they have side effects - @Override - public void createControl(Composite parent) { - Composite container = new Composite(parent, SWT.NULL); - setControl(container); - initializeDialogUnits(container); - container.setLayout(new GridLayout(2, false)); - Label nameLabel = new Label(container, SWT.NONE); - nameLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); - nameLabel.setText("New Name:"); - Text text = super.createTextInputField(container); - text.selectAll(); - text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); - Label xmlLabel = new Label(container, SWT.NONE); - xmlLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); - xmlLabel.setText("XML:"); - mXmlLabel = new Label(container, SWT.NONE); - mXmlLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1)); - Label javaLabel = new Label(container, SWT.NONE); - javaLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); - javaLabel.setText("Java:"); - mJavaLabel = new Label(container, SWT.NONE); - mJavaLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1)); - new Label(container, SWT.NONE); - new Label(container, SWT.NONE); - mUpdateReferences = new Button(container, SWT.CHECK); - mUpdateReferences.setSelection(true); - mUpdateReferences.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1)); - mUpdateReferences.setText("Update References"); - mUpdateReferences.addSelectionListener(this); - - Dialog.applyDialogFont(container); - } - - @Override - public void setVisible(boolean visible) { - if (visible) { - RenameResourceProcessor processor = getProcessor(); - String newName = processor.getNewName(); - if (newName != null && newName.length() > 0 - && !newName.equals(getInitialValue())) { - Text textField = getTextField(); - textField.setText(newName); - textField.setSelection(0, newName.length()); - } - } - - super.setVisible(visible); - } - - @Override - protected RefactoringStatus validateTextField(String newName) { - if (newName.isEmpty() && isEmptyInputValid()) { - getProcessor().setNewName(""); - return RefactoringStatus.createWarningStatus( - "The resource definition will be deleted"); - } - - String error = mValidator.isValid(newName); - if (error != null) { - return RefactoringStatus.createErrorStatus(error); - } - - RenameResourceProcessor processor = getProcessor(); - processor.setNewName(newName); - return processor.checkNewName(newName); - } - - private RenameResourceProcessor getProcessor() { - RenameRefactoring refactoring = (RenameRefactoring) getRefactoring(); - return (RenameResourceProcessor) refactoring.getProcessor(); - } - - @Override - protected boolean isEmptyInputValid() { - return mCanClear; - } - - @Override - protected boolean isInitialInputValid() { - RenameResourceProcessor processor = getProcessor(); - return processor.getNewName() != null - && !processor.getNewName().equals(processor.getCurrentName()); - } - - @Override - protected void textModified(String text) { - super.textModified(text); - if (mXmlLabel != null && mJavaLabel != null) { - String xml = PREFIX_RESOURCE_REF + mType.getName() + '/' + text; - String java = R_CLASS + '.' + mType.getName() + '.' + text; - if (text.isEmpty()) { - xml = java = ""; - } - mXmlLabel.setText(xml); - mJavaLabel.setText(java); - } - } - - // ---- Implements SelectionListener ---- - - @Override - public void widgetSelected(SelectionEvent e) { - if (e.getSource() == mUpdateReferences) { - RenameResourceProcessor processor = getProcessor(); - boolean update = mUpdateReferences.getSelection(); - processor.setUpdateReferences(update); - } - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceParticipant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceParticipant.java deleted file mode 100644 index 438e82223..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceParticipant.java +++ /dev/null @@ -1,752 +0,0 @@ -/* - * Copyright (C) 2012 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_PREFIX; -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_ID; -import static com.android.SdkConstants.ATTR_NAME; -import static com.android.SdkConstants.ATTR_TYPE; -import static com.android.SdkConstants.DOT_XML; -import static com.android.SdkConstants.EXT_XML; -import static com.android.SdkConstants.FD_RES; -import static com.android.SdkConstants.FN_RESOURCE_CLASS; -import static com.android.SdkConstants.NEW_ID_PREFIX; -import static com.android.SdkConstants.PREFIX_RESOURCE_REF; -import static com.android.SdkConstants.PREFIX_THEME_REF; -import static com.android.SdkConstants.R_CLASS; -import static com.android.SdkConstants.TAG_ITEM; -import static com.android.SdkConstants.TOOLS_URI; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AdtUtils; -import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; -import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; -import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator; -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.resources.ResourceType; -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.IField; -import org.eclipse.jdt.core.IJavaElement; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.IType; -import org.eclipse.jdt.internal.corext.refactoring.rename.RenameFieldProcessor; -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.TextChange; -import org.eclipse.ltk.core.refactoring.TextFileChange; -import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; -import org.eclipse.ltk.core.refactoring.participants.RenameParticipant; -import org.eclipse.ltk.core.refactoring.participants.RenameRefactoring; -import org.eclipse.ltk.core.refactoring.resource.RenameResourceChange; -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.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.List; - -/** - * A rename participant handling renames of resources (such as R.id.foo and R.layout.bar). - * This reacts to refactorings of fields in the R inner classes (such as R.id), and updates - * the XML files as appropriate; renaming .xml files, updating XML attributes, resource - * references in style declarations, and so on. - */ -@SuppressWarnings("restriction") // WTP API -public class RenameResourceParticipant extends RenameParticipant { - /** The project we're refactoring in */ - private @NonNull IProject mProject; - - /** The type of the resource we're refactoring, such as {@link ResourceType#ID} */ - private @NonNull ResourceType mType; - /** - * The type of the resource folder we're refactoring in, such as - * {@link ResourceFolderType#VALUES}. When refactoring non value files, we need to - * rename the files as well. - */ - private @NonNull ResourceFolderType mFolderType; - - /** The previous name of the resource */ - private @NonNull String mOldName; - - /** The new name of the resource */ - private @NonNull String mNewName; - - /** Whether references to the resource should be updated */ - private boolean mUpdateReferences; - - /** A match pattern to look for in XML, such as {@code @attr/foo} */ - private @NonNull String mXmlMatch1; - - /** A match pattern to look for in XML, such as {@code ?attr/foo} */ - private @Nullable String mXmlMatch2; - - /** A match pattern to look for in XML, such as {@code ?foo} */ - private @Nullable String mXmlMatch3; - - /** The value to replace a reference to {@link #mXmlMatch1} with, such as {@code @attr/bar} */ - private @NonNull String mXmlNewValue1; - - /** The value to replace a reference to {@link #mXmlMatch2} with, such as {@code ?attr/bar} */ - private @Nullable String mXmlNewValue2; - - /** The value to replace a reference to {@link #mXmlMatch3} with, such as {@code ?bar} */ - private @Nullable String mXmlNewValue3; - - /** - * If non null, this refactoring was initiated as a file rename of an XML file (and if - * null, we are just reacting to a Java field rename) - */ - private IFile mRenamedFile; - - /** - * If renaming a field, we need to create an embedded field refactoring to update the - * Java sources referring to the corresponding R class field. This is stored as an - * instance such that we can have it participate in both the condition check methods - * as well as the {@link #createChange(IProgressMonitor)} refactoring operation. - */ - private RenameRefactoring mFieldRefactoring; - - /** - * Set while we are creating an embedded Java refactoring. This could cause a recursive - * invocation of the XML renaming refactoring to react to the field, so this is flag - * during the call to the Java processor, and is used to ignore requests for adding in - * field reactions during that time. - */ - private static boolean sIgnore; - - /** - * Creates a new {@linkplain RenameResourceParticipant} - */ - public RenameResourceParticipant() { - } - - @Override - public String getName() { - return "Android Rename Field Participant"; - } - - @Override - protected boolean initialize(Object element) { - if (sIgnore) { - return false; - } - - if (element instanceof IField) { - IField field = (IField) element; - IType declaringType = field.getDeclaringType(); - if (declaringType != null) { - if (R_CLASS.equals(declaringType.getParent().getElementName())) { - String typeName = declaringType.getElementName(); - mType = ResourceType.getEnum(typeName); - if (mType != null) { - mUpdateReferences = getArguments().getUpdateReferences(); - mFolderType = AdtUtils.getFolderTypeFor(mType); - IJavaProject javaProject = (IJavaProject) field.getAncestor( - IJavaElement.JAVA_PROJECT); - mProject = javaProject.getProject(); - mOldName = field.getElementName(); - mNewName = getArguments().getNewName(); - mFieldRefactoring = null; - mRenamedFile = null; - createXmlSearchPatterns(); - return true; - } - } - } - - return false; - } else if (element instanceof IFile) { - IFile file = (IFile) element; - mProject = file.getProject(); - if (BaseProjectHelper.isAndroidProject(mProject)) { - IPath path = file.getFullPath(); - int segments = path.segmentCount(); - if (segments == 4 && path.segment(1).equals(FD_RES)) { - String parentName = file.getParent().getName(); - mFolderType = ResourceFolderType.getFolderType(parentName); - if (mFolderType != null && mFolderType != ResourceFolderType.VALUES) { - mType = AdtUtils.getResourceTypeFor(mFolderType); - if (mType != null) { - mUpdateReferences = getArguments().getUpdateReferences(); - mProject = file.getProject(); - mOldName = AdtUtils.stripAllExtensions(file.getName()); - mNewName = AdtUtils.stripAllExtensions(getArguments().getNewName()); - mRenamedFile = file; - createXmlSearchPatterns(); - - mFieldRefactoring = null; - IField field = getResourceField(mProject, mType, mOldName); - if (field != null) { - mFieldRefactoring = createFieldRefactoring(field); - } else { - // no corresponding field; aapt has not run yet. Perhaps user has - // turned off auto build. - mFieldRefactoring = null; - } - - return true; - } - } - } - } - } else if (element instanceof String) { - String uri = (String) element; - if (uri.startsWith(PREFIX_RESOURCE_REF) && !uri.startsWith(ANDROID_PREFIX)) { - RenameResourceProcessor processor = (RenameResourceProcessor) getProcessor(); - mProject = processor.getProject(); - mType = processor.getType(); - mFolderType = AdtUtils.getFolderTypeFor(mType); - mOldName = processor.getCurrentName(); - mNewName = processor.getNewName(); - assert uri.endsWith(mOldName) && uri.contains(mType.getName()) : uri; - mUpdateReferences = getArguments().getUpdateReferences(); - if (mNewName.isEmpty()) { - mUpdateReferences = false; - } - mRenamedFile = null; - createXmlSearchPatterns(); - mFieldRefactoring = null; - if (!mNewName.isEmpty()) { - IField field = getResourceField(mProject, mType, mOldName); - if (field != null) { - mFieldRefactoring = createFieldRefactoring(field); - } - } - - return true; - } - } - - return false; - } - - /** Create nested Java refactoring which updates the R field references, if applicable */ - private RenameRefactoring createFieldRefactoring(IField field) { - return createFieldRefactoring(field, mNewName, mUpdateReferences); - } - - /** - * Create nested Java refactoring which updates the R field references, if - * applicable - * - * @param field the field to be refactored - * @param newName the new name - * @param updateReferences whether references should be updated - * @return a new rename refactoring - */ - public static RenameRefactoring createFieldRefactoring( - @NonNull IField field, - @NonNull String newName, - boolean updateReferences) { - RenameFieldProcessor processor = new RenameFieldProcessor(field); - processor.setRenameGetter(false); - processor.setRenameSetter(false); - RenameRefactoring refactoring = new RenameRefactoring(processor); - processor.setUpdateReferences(updateReferences); - processor.setUpdateTextualMatches(false); - processor.setNewElementName(newName); - try { - if (refactoring.isApplicable()) { - return refactoring; - } - } catch (CoreException e) { - AdtPlugin.log(e, null); - } - - return null; - } - - private void createXmlSearchPatterns() { - // Set up search strings for the attribute iterator. This will - // identify string matches for mXmlMatch1, 2 and 3, and when matched, - // will add a replacement edit for mXmlNewValue1, 2, or 3. - mXmlMatch2 = null; - mXmlNewValue2 = null; - mXmlMatch3 = null; - mXmlNewValue3 = null; - - String typeName = mType.getName(); - if (mUpdateReferences) { - mXmlMatch1 = PREFIX_RESOURCE_REF + typeName + '/' + mOldName; - mXmlNewValue1 = PREFIX_RESOURCE_REF + typeName + '/' + mNewName; - if (mType == ResourceType.ID) { - mXmlMatch2 = NEW_ID_PREFIX + mOldName; - mXmlNewValue2 = NEW_ID_PREFIX + mNewName; - } else if (mType == ResourceType.ATTR) { - // When renaming @attr/foo, also edit ?attr/foo - mXmlMatch2 = PREFIX_THEME_REF + typeName + '/' + mOldName; - mXmlNewValue2 = PREFIX_THEME_REF + typeName + '/' + mNewName; - // as well as ?foo - mXmlMatch3 = PREFIX_THEME_REF + mOldName; - mXmlNewValue3 = PREFIX_THEME_REF + mNewName; - } - } else if (mType == ResourceType.ID) { - mXmlMatch1 = NEW_ID_PREFIX + mOldName; - mXmlNewValue1 = NEW_ID_PREFIX + mNewName; - } - } - - @Override - public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context) - throws OperationCanceledException { - if (mRenamedFile != null && getArguments().getNewName().indexOf('.') == -1 - && mRenamedFile.getName().indexOf('.') != -1) { - return RefactoringStatus.createErrorStatus( - String.format("You must include the file extension (%1$s?)", - mRenamedFile.getName().substring(mRenamedFile.getName().indexOf('.')))); - } - - // Ensure that the new name is valid - if (mNewName != null && !mNewName.isEmpty()) { - ResourceNameValidator validator = ResourceNameValidator.create(false, mProject, mType); - String error = validator.isValid(mNewName); - if (error != null) { - return RefactoringStatus.createErrorStatus(error); - } - } - - if (mFieldRefactoring != null) { - try { - sIgnore = true; - return mFieldRefactoring.checkAllConditions(pm); - } catch (CoreException e) { - AdtPlugin.log(e, null); - } finally { - sIgnore = false; - } - } - - return new RefactoringStatus(); - } - - @Override - public Change createChange(IProgressMonitor monitor) throws CoreException, - OperationCanceledException { - if (monitor.isCanceled()) { - return null; - } - - CompositeChange result = new CompositeChange("Update resource references"); - - // Only show the children in the refactoring preview dialog - result.markAsSynthetic(); - - addResourceFileChanges(result, mProject, monitor); - - // If renaming resources in a library project, also offer to rename references - // in including projects - if (mUpdateReferences) { - ProjectState projectState = Sdk.getProjectState(mProject); - if (projectState != null && projectState.isLibrary()) { - List<ProjectState> parentProjects = projectState.getParentProjects(); - for (ProjectState state : parentProjects) { - IProject project = state.getProject(); - CompositeChange nested = new CompositeChange( - String.format("Update references in %1$s", project.getName())); - addResourceFileChanges(nested, project, monitor); - if (nested.getChildren().length > 0) { - result.add(nested); - } - } - } - } - - if (mFieldRefactoring != null) { - // We have to add in Java field refactoring - try { - sIgnore = true; - addJavaChanges(result, monitor); - } finally { - sIgnore = false; - } - } else { - // Disable field refactoring added by the default Java field rename handler - disableExistingResourceFileChange(); - } - - return (result.getChildren().length == 0) ? null : result; - } - - /** - * Adds all changes to resource files (typically XML but also renaming drawable files - * - * @param project the Android project - * @param className the layout classes - */ - private void addResourceFileChanges( - CompositeChange change, - IProject project, - IProgressMonitor monitor) - throws OperationCanceledException { - if (monitor.isCanceled()) { - return; - } - - try { - // Update resource references in the manifest - IFile manifest = project.getFile(SdkConstants.ANDROID_MANIFEST_XML); - if (manifest != null) { - addResourceXmlChanges(manifest, change, null); - } - - // Update references in XML resource files - IFolder resFolder = project.getFolder(SdkConstants.FD_RESOURCES); - - IResource[] folders = resFolder.members(); - for (IResource folder : folders) { - if (!(folder instanceof IFolder)) { - continue; - } - String folderName = folder.getName(); - ResourceFolderType folderType = ResourceFolderType.getFolderType(folderName); - 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)) { - addResourceXmlChanges(file, change, folderType); - } - - if ((mRenamedFile == null || !mRenamedFile.equals(file)) - && fileName.startsWith(mOldName) - && fileName.length() > mOldName.length() - && fileName.charAt(mOldName.length()) == '.' - && mFolderType != ResourceFolderType.VALUES - && mFolderType == folderType) { - // Rename this file - String newFile = mNewName + fileName.substring(mOldName.length()); - IPath path = file.getFullPath(); - change.add(new RenameResourceChange(path, newFile)); - } - } - } - } - } catch (CoreException e) { - RefactoringUtil.log(e); - } - } - - private void addJavaChanges(CompositeChange result, IProgressMonitor monitor) - throws CoreException, OperationCanceledException { - if (monitor.isCanceled()) { - return; - } - - RefactoringStatus status = mFieldRefactoring.checkAllConditions(monitor); - if (status != null && !status.hasError()) { - Change fieldChanges = mFieldRefactoring.createChange(monitor); - if (fieldChanges != null) { - result.add(fieldChanges); - - // Look for the field change on the R.java class; it's a derived file - // and will generate file modified manually warnings. Disable it. - disableRClassChanges(fieldChanges); - } - } - } - - private boolean addResourceXmlChanges( - IFile file, - CompositeChange changes, - ResourceFolderType folderType) { - 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>(); - addReplacements(edits, root, document, folderType); - 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 addReplacements( - @NonNull List<TextEdit> edits, - @NonNull Element element, - @NonNull IStructuredDocument document, - @Nullable ResourceFolderType folderType) { - String tag = element.getTagName(); - if (folderType == ResourceFolderType.VALUES) { - // Look for - // <item name="main_layout" type="layout">...</item> - // <item name="myid" type="id"/> - // <string name="mystring">...</string> - // etc - if (tag.equals(mType.getName()) - || (tag.equals(TAG_ITEM) - && (mType == ResourceType.ID - || mType.getName().equals(element.getAttribute(ATTR_TYPE))))) { - Attr nameNode = element.getAttributeNode(ATTR_NAME); - if (nameNode != null && nameNode.getValue().equals(mOldName)) { - int start = RefactoringUtil.getAttributeValueRangeStart(nameNode, document); - if (start != -1) { - int end = start + mOldName.length(); - edits.add(new ReplaceEdit(start, end - start, mNewName)); - } - } - } - } - - NamedNodeMap attributes = element.getAttributes(); - for (int i = 0, n = attributes.getLength(); i < n; i++) { - Attr attr = (Attr) attributes.item(i); - String value = attr.getValue(); - - // If not updating references, only update XML matches that define the id - if (!mUpdateReferences && (!ATTR_ID.equals(attr.getLocalName()) || - !ANDROID_URI.equals(attr.getNamespaceURI()))) { - - if (TOOLS_URI.equals(attr.getNamespaceURI()) && value.equals(mXmlMatch1)) { - int start = RefactoringUtil.getAttributeValueRangeStart(attr, document); - if (start != -1) { - int end = start + mXmlMatch1.length(); - edits.add(new ReplaceEdit(start, end - start, mXmlNewValue1)); - } - } - - continue; - } - - // Replace XML attribute reference, such as - // android:id="@+id/oldName" => android:id="+id/newName" - - String match = null; - String matchedValue = null; - - if (value.equals(mXmlMatch1)) { - match = mXmlMatch1; - matchedValue = mXmlNewValue1; - } else if (value.equals(mXmlMatch2)) { - match = mXmlMatch2; - matchedValue = mXmlNewValue2; - } else if (value.equals(mXmlMatch3)) { - match = mXmlMatch3; - matchedValue = mXmlNewValue3; - } else { - continue; - } - - if (match != null) { - if (mNewName.isEmpty() && ATTR_ID.equals(attr.getLocalName()) && - ANDROID_URI.equals(attr.getNamespaceURI())) { - // Delete attribute - IndexedRegion region = (IndexedRegion) attr; - int start = region.getStartOffset(); - int end = region.getEndOffset(); - edits.add(new ReplaceEdit(start, end - start, "")); - } else { - int start = RefactoringUtil.getAttributeValueRangeStart(attr, document); - if (start != -1) { - int end = start + match.length(); - edits.add(new ReplaceEdit(start, end - start, matchedValue)); - } - } - } - } - - 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) { - addReplacements(edits, (Element) child, document, folderType); - } else if (child.getNodeType() == Node.TEXT_NODE && mUpdateReferences) { - // Replace XML text, such as @color/custom_theme_color in - // <item name="android:windowBackground">@color/custom_theme_color</item> - // - String text = child.getNodeValue(); - int index = getFirstNonBlankIndex(text); - if (index != -1) { - String match = null; - String matchedValue = null; - if (mXmlMatch1 != null - && text.startsWith(mXmlMatch1) && text.trim().equals(mXmlMatch1)) { - match = mXmlMatch1; - matchedValue = mXmlNewValue1; - } else if (mXmlMatch2 != null - && text.startsWith(mXmlMatch2) && text.trim().equals(mXmlMatch2)) { - match = mXmlMatch2; - matchedValue = mXmlNewValue2; - } else if (mXmlMatch3 != null - && text.startsWith(mXmlMatch3) && text.trim().equals(mXmlMatch3)) { - match = mXmlMatch3; - matchedValue = mXmlNewValue3; - } - if (match != null) { - IndexedRegion region = (IndexedRegion) child; - int start = region.getStartOffset() + index; - int end = start + match.length(); - edits.add(new ReplaceEdit(start, end - start, matchedValue)); - } - } - } - } - } - - /** - * Returns the index of the first non-space character in the string, or -1 - * if the string is empty or has only whitespace - * - * @param s the string to check - * @return the index of the first non whitespace character - */ - private int getFirstNonBlankIndex(String s) { - for (int i = 0, n = s.length(); i < n; i++) { - if (!Character.isWhitespace(s.charAt(i))) { - return i; - } - } - - return -1; - } - - /** - * Initiates a renaming of a resource item - * - * @param project the project containing the resource references - * @param type the type of resource - * @param name the name of the resource - * @return false if initiating the rename failed - */ - @Nullable - private static IField getResourceField( - @NonNull IProject project, - @NonNull ResourceType type, - @NonNull String name) { - try { - IJavaProject javaProject = BaseProjectHelper.getJavaProject(project); - if (javaProject == null) { - return null; - } - - String pkg = ManifestInfo.get(project).getPackage(); - // TODO: Rename in all libraries too? - IType t = javaProject.findType(pkg + '.' + R_CLASS + '.' + type.getName()); - if (t == null) { - return null; - } - - return t.getField(name); - } catch (CoreException e) { - AdtPlugin.log(e, null); - } - - return null; - } - - /** - * Searches for existing changes in the refactoring which modifies the R - * field to rename it. it's derived so performing this change will generate - * a "generated code was modified manually" warning - */ - private void disableExistingResourceFileChange() { - IFolder genFolder = mProject.getFolder(SdkConstants.FD_GEN_SOURCES); - if (genFolder != null && genFolder.exists()) { - ManifestInfo manifestInfo = ManifestInfo.get(mProject); - String pkg = manifestInfo.getPackage(); - if (pkg != null) { - IFile rFile = genFolder.getFile(pkg.replace('.', '/') + '/' + FN_RESOURCE_CLASS); - TextChange change = getTextChange(rFile); - if (change != null) { - change.setEnabled(false); - } - } - } - } - - /** - * Searches for existing changes in the refactoring which modifies the R - * field to rename it. it's derived so performing this change will generate - * a "generated code was modified manually" warning - * - * @param change the change to disable R file changes in - */ - public static void disableRClassChanges(Change change) { - if (change.getName().equals(FN_RESOURCE_CLASS)) { - change.setEnabled(false); - } - // Look for the field change on the R.java class; it's a derived file - // and will generate file modified manually warnings. Disable it. - if (change instanceof CompositeChange) { - for (Change outer : ((CompositeChange) change).getChildren()) { - disableRClassChanges(outer); - } - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceProcessor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceProcessor.java deleted file mode 100644 index 5ea99411c..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceProcessor.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 2012 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.PREFIX_RESOURCE_REF; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.eclipse.adt.AdtConstants; -import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator; -import com.android.resources.ResourceType; - -import org.eclipse.core.resources.IProject; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.ltk.core.refactoring.Change; -import org.eclipse.ltk.core.refactoring.RefactoringStatus; -import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; -import org.eclipse.ltk.core.refactoring.participants.ParticipantManager; -import org.eclipse.ltk.core.refactoring.participants.RefactoringParticipant; -import org.eclipse.ltk.core.refactoring.participants.RenameArguments; -import org.eclipse.ltk.core.refactoring.participants.RenameProcessor; -import org.eclipse.ltk.core.refactoring.participants.SharableParticipants; - -/** - * A rename processor for Android resources. - */ -public class RenameResourceProcessor extends RenameProcessor { - private IProject mProject; - private ResourceType mType; - private String mCurrentName; - private String mNewName; - private boolean mUpdateReferences = true; - private ResourceNameValidator mValidator; - private RenameArguments mRenameArguments; - - /** - * Creates a new rename resource processor. - * - * @param project the project containing the renamed resource - * @param type the type of the resource - * @param currentName the current name of the resource - * @param newName the new name of the resource, or null if not known - */ - public RenameResourceProcessor( - @NonNull IProject project, - @NonNull ResourceType type, - @NonNull String currentName, - @Nullable String newName) { - mProject = project; - mType = type; - mCurrentName = currentName; - mNewName = newName != null ? newName : currentName; - mUpdateReferences= true; - mValidator = ResourceNameValidator.create(false, mProject, mType); - } - - /** - * Returns the project containing the renamed resource - * - * @return the project containing the renamed resource - */ - @NonNull - public IProject getProject() { - return mProject; - } - - /** - * Returns the new resource name - * - * @return the new resource name - */ - @NonNull - public String getNewName() { - return mNewName; - } - - /** - * Returns the current name of the resource - * - * @return the current name of the resource - */ - public String getCurrentName() { - return mCurrentName; - } - - /** - * Returns the type of the resource - * - * @return the type of the resource - */ - @NonNull - public ResourceType getType() { - return mType; - } - - /** - * Sets the new name - * - * @param newName the new name - */ - public void setNewName(@NonNull String newName) { - mNewName = newName; - } - - /** - * Returns {@code true} if the refactoring processor also updates references - * - * @return {@code true} if the refactoring processor also updates references - */ - public boolean isUpdateReferences() { - return mUpdateReferences; - } - - /** - * Specifies if the refactoring processor also updates references. The - * default behavior is to update references. - * - * @param updateReferences {@code true} if the refactoring processor should - * also updates references - */ - public void setUpdateReferences(boolean updateReferences) { - mUpdateReferences = updateReferences; - } - - /** - * Checks the given new potential name and returns a {@link RefactoringStatus} indicating - * whether the potential new name is valid - * - * @param name the name to check - * @return a {@link RefactoringStatus} with the validation result - */ - public RefactoringStatus checkNewName(String name) { - String error = mValidator.isValid(name); - if (error != null) { - return RefactoringStatus.createFatalErrorStatus(error); - } - - return new RefactoringStatus(); - } - - @Override - public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException { - return new RefactoringStatus(); - } - - @Override - public RefactoringStatus checkFinalConditions(IProgressMonitor pm, - CheckConditionsContext context) throws CoreException { - pm.beginTask("", 1); - try { - mRenameArguments = new RenameArguments(getNewName(), isUpdateReferences()); - return new RefactoringStatus(); - } finally { - pm.done(); - } - } - - @Override - public Change createChange(IProgressMonitor pm) throws CoreException { - pm.beginTask("", 1); - try { - // Added by {@link RenameResourceParticipant} - return null; - } finally { - pm.done(); - } - } - - @Override - public Object[] getElements() { - return new Object[0]; - } - - @Override - public String getIdentifier() { - return "com.android.ide.renameResourceProcessor"; //$NON-NLS-1$ - } - - @Override - public String getProcessorName() { - return "Rename Android Resource"; - } - - @Override - public boolean isApplicable() { - return true; - } - - @Override - public RefactoringParticipant[] loadParticipants(RefactoringStatus status, - SharableParticipants shared) throws CoreException { - String[] affectedNatures = new String[] { AdtConstants.NATURE_DEFAULT }; - String url = PREFIX_RESOURCE_REF + mType.getName() + '/' + mCurrentName; - return ParticipantManager.loadRenameParticipants(status, this, url, mRenameArguments, - null, affectedNatures, shared); - } -}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceWizard.java deleted file mode 100644 index 6ffe25d22..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceWizard.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.resources.ResourceType; - -import org.eclipse.core.resources.IProject; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.jdt.internal.ui.IJavaHelpContextIds; -import org.eclipse.jdt.internal.ui.JavaPluginImages; -import org.eclipse.jdt.internal.ui.refactoring.reorg.RenameRefactoringWizard; -import org.eclipse.jdt.ui.refactoring.RefactoringSaveHelper; -import org.eclipse.jface.dialogs.IDialogConstants; -import org.eclipse.ltk.core.refactoring.RefactoringStatus; -import org.eclipse.ltk.core.refactoring.participants.RenameRefactoring; -import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; -import org.eclipse.swt.widgets.Shell; - -/** - * Rename refactoring wizard for Android resources such as {@code @id/foo} - */ -@SuppressWarnings("restriction") // JDT refactoring UI -public class RenameResourceWizard extends RenameRefactoringWizard { - private ResourceType mType; - private boolean mCanClear; - - /** - * Constructs a new {@linkplain RenameResourceWizard} - * - * @param refactoring the refactoring - * @param type the type of resource being renamed - * @param canClear whether the user can clear the value - */ - public RenameResourceWizard( - @NonNull RenameRefactoring refactoring, - @NonNull ResourceType type, - boolean canClear) { - super(refactoring, - "Rename Resource", - "Enter the new name for this resource", - JavaPluginImages.DESC_WIZBAN_REFACTOR_FIELD, - IJavaHelpContextIds.RENAME_FIELD_WIZARD_PAGE); - mType = type; - mCanClear = canClear; - } - - @Override - protected void addUserInputPages() { - RenameRefactoring refactoring = (RenameRefactoring) getRefactoring(); - RenameResourceProcessor processor = (RenameResourceProcessor) refactoring.getProcessor(); - String name = processor.getNewName(); - addPage(new RenameResourcePage(mType, name, mCanClear)); - } - - /** - * Initiates a renaming of a resource item - * - * @param shell the shell to parent the dialog to - * @param project the project containing the resource references - * @param type the type of resource - * @param currentName the name of the resource - * @param newName the new name, or null if not known - * @param canClear whether the name is allowed to be cleared - * @return false if initiating the rename failed - */ - public static RenameResult renameResource( - @NonNull Shell shell, - @NonNull IProject project, - @NonNull ResourceType type, - @NonNull String currentName, - @Nullable String newName, - boolean canClear) { - try { - RenameResourceProcessor processor = new RenameResourceProcessor(project, type, - currentName, newName); - RenameRefactoring refactoring = new RenameRefactoring(processor); - if (!refactoring.isApplicable()) { - return RenameResult.unavailable(); - } - - if (!show(refactoring, processor, shell, type, canClear)) { - return RenameResult.canceled(); - } - return RenameResult.name(processor.getNewName()); - } catch (CoreException e) { - AdtPlugin.log(e, null); - } - - return RenameResult.unavailable(); - } - - /** - * Show a refactoring dialog for the given resource refactoring operation - * - * @param refactoring the rename refactoring - * @param processor the field processor - * @param parent the parent shell - * @param type the resource type - * @param canClear whether the user is allowed to clear/reset the name to - * nothing - * @return true if the refactoring was performed, and false if it was - * canceled - * @throws CoreException if an unexpected error occurs - */ - private static boolean show( - @NonNull RenameRefactoring refactoring, - @NonNull RenameResourceProcessor processor, - @NonNull Shell parent, - @NonNull ResourceType type, - boolean canClear) throws CoreException { - RefactoringSaveHelper saveHelper = new RefactoringSaveHelper( - RefactoringSaveHelper.SAVE_REFACTORING); - if (!saveHelper.saveEditors(parent)) { - return false; - } - - try { - RenameResourceWizard wizard = new RenameResourceWizard(refactoring, type, canClear); - RefactoringWizardOpenOperation operation = new RefactoringWizardOpenOperation(wizard); - String dialogTitle = wizard.getDefaultPageTitle(); - int result = operation.run(parent, dialogTitle == null ? "" : dialogTitle); - RefactoringStatus status = operation.getInitialConditionCheckingStatus(); - if (status.hasFatalError()) { - return false; - } - if (result == RefactoringWizardOpenOperation.INITIAL_CONDITION_CHECKING_FAILED - || result == IDialogConstants.CANCEL_ID) { - saveHelper.triggerIncrementalBuild(); - return false; - } - - // Save modified resources; need to trigger R file regeneration - saveHelper.saveEditors(parent); - - return true; - } catch (InterruptedException e) { - return false; // Canceled - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceXmlTextAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceXmlTextAction.java deleted file mode 100644 index 8ecb08836..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceXmlTextAction.java +++ /dev/null @@ -1,410 +0,0 @@ -/* - * Copyright (C) 2012 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_MANIFEST_XML; -import static com.android.SdkConstants.ANDROID_PREFIX; -import static com.android.SdkConstants.ANDROID_THEME_PREFIX; -import static com.android.SdkConstants.ATTR_NAME; -import static com.android.SdkConstants.ATTR_TYPE; -import static com.android.SdkConstants.TAG_ITEM; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.resources.ResourceUrl; -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.editors.manifest.ManifestInfo; -import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; -import com.android.ide.eclipse.adt.internal.sdk.ProjectState; -import com.android.ide.eclipse.adt.internal.sdk.Sdk; -import com.android.resources.ResourceType; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.IType; -import org.eclipse.jdt.internal.corext.refactoring.rename.RenameTypeProcessor; -import org.eclipse.jdt.internal.ui.refactoring.reorg.RenameTypeWizard; -import org.eclipse.jface.action.Action; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.ITextSelection; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.ISelectionProvider; -import org.eclipse.ltk.core.refactoring.participants.RenameRefactoring; -import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IFileEditorInput; -import org.eclipse.ui.IWorkbenchWindow; -import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.texteditor.IDocumentProvider; -import org.eclipse.ui.texteditor.ITextEditor; -import org.eclipse.ui.texteditor.ITextEditorExtension; -import org.eclipse.ui.texteditor.ITextEditorExtension2; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -import java.util.List; - -/** - * Text action for XML files to invoke renaming - * <p> - * TODO: Handle other types of renaming: invoking class renaming when editing - * class names in layout files and manifest files, renaming attribute names when - * editing a styleable attribute, etc. - */ -@SuppressWarnings("restriction") // Java rename refactoring -public final class RenameResourceXmlTextAction extends Action { - private final ITextEditor mEditor; - - /** - * Creates a new {@linkplain RenameResourceXmlTextAction} - * - * @param editor the associated editor - */ - public RenameResourceXmlTextAction(@NonNull ITextEditor editor) { - super("Rename"); - mEditor = editor; - } - - @Override - public void run() { - if (!validateEditorInputState()) { - return; - } - IFile file = getFile(); - if (file == null) { - return; - } - IProject project = file.getProject(); - if (project == null) { - return; - } - IDocument document = getDocument(); - if (document == null) { - return; - } - ITextSelection selection = getSelection(); - if (selection == null) { - return; - } - - ResourceUrl resource = findResource(document, selection.getOffset()); - - if (resource == null) { - resource = findItemDefinition(document, selection.getOffset()); - } - - if (resource != null) { - ResourceType type = resource.type; - String name = resource.name; - Shell shell = mEditor.getSite().getShell(); - boolean canClear = false; - - RenameResourceWizard.renameResource(shell, project, type, name, null, canClear); - return; - } - - String className = findClassName(document, file, selection.getOffset()); - if (className != null) { - assert className.equals(className.trim()); - IType type = findType(className, project); - if (type != null) { - RenameTypeProcessor processor = new RenameTypeProcessor(type); - //processor.setNewElementName(className); - processor.setUpdateQualifiedNames(true); - processor.setUpdateSimilarDeclarations(false); - //processor.setMatchStrategy(?); - //processor.setFilePatterns(patterns); - processor.setUpdateReferences(true); - - RenameRefactoring refactoring = new RenameRefactoring(processor); - RenameTypeWizard wizard = new RenameTypeWizard(refactoring); - RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard); - try { - IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); - op.run(window.getShell(), wizard.getDefaultPageTitle()); - } catch (InterruptedException e) { - } - } - - return; - } - - // Fallback: tell user the cursor isn't in the right place - MessageDialog.openInformation(mEditor.getSite().getShell(), - "Rename", - "Operation unavailable on the current selection.\n" - + "Select an Android resource name or class."); - } - - private boolean validateEditorInputState() { - if (mEditor instanceof ITextEditorExtension2) - return ((ITextEditorExtension2) mEditor).validateEditorInputState(); - else if (mEditor instanceof ITextEditorExtension) - return !((ITextEditorExtension) mEditor).isEditorInputReadOnly(); - else if (mEditor != null) - return mEditor.isEditable(); - else - return false; - } - - /** - * Searches for a resource URL around the caret, such as {@code @string/foo} - * - * @param document the document to search in - * @param offset the offset to search at - * @return a resource pair, or null if not found - */ - @Nullable - public static ResourceUrl findResource(@NonNull IDocument document, int offset) { - try { - int max = document.getLength(); - if (offset >= max) { - offset = max - 1; - } else if (offset < 0) { - offset = 0; - } else if (offset > 0) { - // If the caret is right after a resource name (meaning getChar(offset) points - // to the following character), back up - char c = document.getChar(offset); - if (!isValidResourceNameChar(c)) { - offset--; - } - } - - int start = offset; - boolean valid = true; - for (; start >= 0; start--) { - char c = document.getChar(start); - if (c == '@' || c == '?') { - break; - } else if (!isValidResourceNameChar(c)) { - valid = false; - break; - } - } - if (valid) { - // Search forwards for the end - int end = start + 1; - for (; end < max; end++) { - char c = document.getChar(end); - if (!isValidResourceNameChar(c)) { - break; - } - } - if (end > start + 1) { - String url = document.get(start, end - start); - - // Don't allow renaming framework resources -- @android:string/ok etc - if (url.startsWith(ANDROID_PREFIX) || url.startsWith(ANDROID_THEME_PREFIX)) { - return null; - } - - return ResourceUrl.parse(url); - } - } - } catch (BadLocationException e) { - AdtPlugin.log(e, null); - } - - return null; - } - - private static boolean isValidResourceNameChar(char c) { - return c == '@' || c == '?' || c == '/' || c == '+' || Character.isJavaIdentifierPart(c); - } - - /** - * Searches for an item definition around the caret, such as - * {@code <string name="foo">My String</string>} - */ - private ResourceUrl findItemDefinition(IDocument document, int offset) { - Node node = DomUtilities.getNode(document, offset); - if (node == null) { - return null; - } - if (node.getNodeType() == Node.TEXT_NODE) { - node = node.getParentNode(); - } - if (node == null || node.getNodeType() != Node.ELEMENT_NODE) { - return null; - } - - Element element = (Element) node; - String name = element.getAttribute(ATTR_NAME); - if (name == null || name.isEmpty()) { - return null; - } - String typeString = element.getTagName(); - if (TAG_ITEM.equals(typeString)) { - typeString = element.getAttribute(ATTR_TYPE); - if (typeString == null || typeString.isEmpty()) { - return null; - } - } - ResourceType type = ResourceType.getEnum(typeString); - if (type != null) { - return ResourceUrl.create(type, name, false, false); - } - - return null; - } - - /** - * Searches for a fully qualified class name around the caret, such as {@code foo.bar.MyClass} - * - * @param document the document to search in - * @param file the file, if known - * @param offset the offset to search at - * @return a resource pair, or null if not found - */ - @Nullable - public static String findClassName( - @NonNull IDocument document, - @Nullable IFile file, - int offset) { - try { - int max = document.getLength(); - if (offset >= max) { - offset = max - 1; - } else if (offset < 0) { - offset = 0; - } else if (offset > 0) { - // If the caret is right after a resource name (meaning getChar(offset) points - // to the following character), back up - char c = document.getChar(offset); - if (Character.isJavaIdentifierPart(c)) { - offset--; - } - } - - int start = offset; - for (; start >= 0; start--) { - char c = document.getChar(start); - if (c == '"' || c == '<' || c == '/') { - start++; - break; - } else if (c != '.' && !Character.isJavaIdentifierPart(c)) { - return null; - } - } - // Search forwards for the end - int end = start + 1; - for (; end < max; end++) { - char c = document.getChar(end); - if (c != '.' && !Character.isJavaIdentifierPart(c)) { - if (c != '"' && c != '>' && !Character.isWhitespace(c)) { - return null; - } - break; - } - } - if (end > start + 1) { - String fqcn = document.get(start, end - start); - int dot = fqcn.indexOf('.'); - if (dot == -1) { // Only support fully qualified names - return null; - } - if (dot == 0) { // Special case for manifests: prepend package - if (file != null && file.getName().equals(ANDROID_MANIFEST_XML)) { - ManifestInfo info = ManifestInfo.get(file.getProject()); - return info.getPackage() + fqcn; - } - return null; - } - - return fqcn; - } - } catch (BadLocationException e) { - AdtPlugin.log(e, null); - } - - return null; - } - - @Nullable - private IType findType(@NonNull String className, @NonNull IProject project) { - IType type = null; - try { - IJavaProject javaProject = BaseProjectHelper.getJavaProject(project); - type = javaProject.findType(className); - if (type == null || !type.exists()) { - return null; - } - if (!type.isBinary()) { - return type; - } - // See if this class is coming through a library project jar file and - // if so locate the real class - ProjectState projectState = Sdk.getProjectState(project); - if (projectState != null) { - List<IProject> libraries = projectState.getFullLibraryProjects(); - for (IProject library : libraries) { - javaProject = BaseProjectHelper.getJavaProject(library); - type = javaProject.findType(className); - if (type != null && type.exists() && !type.isBinary()) { - return type; - } - } - } - } catch (CoreException e) { - AdtPlugin.log(e, null); - } - - return null; - } - - private ITextSelection getSelection() { - ISelectionProvider selectionProvider = mEditor.getSelectionProvider(); - if (selectionProvider == null) { - return null; - } - ISelection selection = selectionProvider.getSelection(); - if (!(selection instanceof ITextSelection)) { - return null; - } - return (ITextSelection) selection; - } - - private IDocument getDocument() { - IDocumentProvider documentProvider = mEditor.getDocumentProvider(); - if (documentProvider == null) { - return null; - } - IDocument document = documentProvider.getDocument(mEditor.getEditorInput()); - if (document == null) { - return null; - } - return document; - } - - @Nullable - private IFile getFile() { - IEditorInput input = mEditor.getEditorInput(); - if (input instanceof IFileEditorInput) { - IFileEditorInput fileInput = (IFileEditorInput) input; - return fileInput.getFile(); - } - - return null; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResult.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResult.java deleted file mode 100644 index ade346fa9..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResult.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2012 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 com.android.annotations.NonNull; -import com.android.annotations.Nullable; - -/** - * A result from a renaming operation - */ -public class RenameResult { - private boolean mCanceled; - private boolean mUnavailable; - private @Nullable String mName; - private boolean mClear; - - /** - * Constructs a new rename result - */ - private RenameResult() { - } - - /** - * Creates a new blank {@linkplain RenameResult} - * @return a new result - */ - @NonNull - public static RenameResult create() { - return new RenameResult(); - } - - /** - * Creates a new {@linkplain RenameResult} for a user canceled renaming operation - * @return a canceled operation - */ - @NonNull - public static RenameResult canceled() { - return new RenameResult().setCanceled(true); - } - - /** - * Creates a {@linkplain RenameResult} for a renaming operation that was - * not available (for example because the field attempted to be renamed - * does not yet exist (or does not exist any more) - * - * @return a new result - */ - @NonNull - public static RenameResult unavailable() { - return new RenameResult().setUnavailable(true); - } - - /** - * Creates a new {@linkplain RenameResult} for a successful renaming - * operation to the given name - * - * @param name the new name - * @return a new result - */ - @NonNull - public static RenameResult name(@Nullable String name) { - return new RenameResult().setName(name); - } - - /** - * Marks this result as canceled - * - * @param canceled whether the result was canceled - * @return this, for constructor chaining - */ - @NonNull - public RenameResult setCanceled(boolean canceled) { - mCanceled = canceled; - return this; - } - - /** - * Marks this result as unavailable - * - * @param unavailable whether this result was unavailable - * @return this, for constructor chaining - */ - @NonNull - public RenameResult setUnavailable(boolean unavailable) { - mUnavailable = unavailable; - return this; - } - - /** - * Sets the new name of the renaming operation - * - * @param name the new name - * @return this, for constructor chaining - */ - @NonNull - public RenameResult setName(@Nullable String name) { - mName = name; - return this; - } - - /** - * Marks this result as clearing the name (reverting it back to the default) - * - * @param clear whether the name was cleared - * @return this, for constructor chaining - */ - @NonNull - public RenameResult setCleared(boolean clear) { - mClear = clear; - return this; - } - - /** - * Returns whether this result represents a canceled renaming operation - * - * @return true if the operation was canceled - */ - public boolean isCanceled() { - return mCanceled; - } - - /** - * Returns whether this result represents an unavailable renaming operation - * - * @return true if the operation was not available - */ - public boolean isUnavailable() { - return mUnavailable; - } - - /** - * Returns whether this result represents a renaming back to the default (possibly - * clear) name. In this case, {@link #getName()} will return {@code null}. - * - * @return true if the name should be reset - */ - public boolean isCleared() { - return mClear; - } - - /** - * Returns the new name. - * - * @return the new name - */ - @Nullable - public String getName() { - return mName; - } -}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/EnabledTextEditGroup.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/EnabledTextEditGroup.java deleted file mode 100644 index 15f6c4719..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/EnabledTextEditGroup.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.extractstring; - -import org.eclipse.text.edits.TextEditGroup; - -/** - * A {@link TextEditGroup} that we want to enable or disable by default. - * This is used to propose a change that may not compile, so we'll create - * a change that is disabled. - * <p/> - * Disabling the change is done by the refactoring class when processing - * the text edit groups generated by the Java AST visitor. - */ -class EnabledTextEditGroup extends TextEditGroup { - private final boolean mEnabled; - - public EnabledTextEditGroup(String name, boolean enabled) { - super(name); - mEnabled = enabled; - } - - public boolean isEnabled() { - return mEnabled; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringAction.java deleted file mode 100644 index 14556fd9f..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringAction.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2009 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.extractstring; - -import com.android.ide.eclipse.adt.AdtConstants; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.jdt.core.ICompilationUnit; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.text.ITextSelection; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.ltk.ui.refactoring.RefactoringWizard; -import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.IWorkbenchWindow; -import org.eclipse.ui.IWorkbenchWindowActionDelegate; -import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.part.FileEditorInput; - -/* - * Quick Reference Link: - * http://www.eclipse.org/articles/article.php?file=Article-Unleashing-the-Power-of-Refactoring/index.html - * and - * http://www.ibm.com/developerworks/opensource/library/os-ecjdt/ - */ - -/** - * Action executed when the "Extract String" menu item is invoked. - * <p/> - * The intent of the action is to start a refactoring that extracts a source string and - * replaces it by an Android string resource ID. - * <p/> - * Workflow: - * <ul> - * <li> The action is currently located in the Refactoring menu in the main menu. - * <li> TODO: extend the popup refactoring menu in a Java or Android XML file. - * <li> The action is only enabled if the selection is 1 character or more. That is at least part - * of the string must be selected, it's not enough to just move the insertion point. This is - * a limitation due to {@link #selectionChanged(IAction, ISelection)} not being called when - * the insertion point is merely moved. TODO: address this limitation. - * <ul> The action gets the current {@link ISelection}. It also knows the current - * {@link IWorkbenchWindow}. However for the refactoring we are also interested in having the - * actual resource file. By looking at the Active Window > Active Page > Active Editor we - * can get the {@link IEditorInput} and find the {@link ICompilationUnit} (aka Java file) - * that is being edited. - * <ul> TODO: change this to find the {@link IFile} being manipulated. The {@link ICompilationUnit} - * can be inferred using {@link JavaCore#createCompilationUnitFrom(IFile)}. This will allow - * us to be able to work with a selection from an Android XML file later. - * <li> The action creates a new {@link ExtractStringRefactoring} and make it run on in a new - * {@link ExtractStringWizard}. - * <ul> - */ -public class ExtractStringAction implements IWorkbenchWindowActionDelegate { - - /** Keep track of the current workbench window. */ - private IWorkbenchWindow mWindow; - private ITextSelection mSelection; - private IEditorPart mEditor; - private IFile mFile; - - /** - * Keep track of the current workbench window. - */ - @Override - public void init(IWorkbenchWindow window) { - mWindow = window; - } - - @Override - public void dispose() { - // Nothing to do - } - - /** - * Examine the selection to determine if the action should be enabled or not. - * <p/> - * Keep a link to the relevant selection structure (i.e. a part of the Java AST). - */ - @Override - public void selectionChanged(IAction action, ISelection selection) { - - // Note, two kinds of selections are returned here: - // ITextSelection on a Java source window - // IStructuredSelection in the outline or navigator - // This simply deals with the refactoring based on a non-empty selection. - // At that point, just enable the action and later decide if it's valid when it actually - // runs since we don't have access to the AST yet. - - mSelection = null; - mFile = null; - - if (selection instanceof ITextSelection) { - mSelection = (ITextSelection) selection; - if (mSelection.getLength() > 0) { - mEditor = getActiveEditor(); - mFile = getSelectedFile(mEditor); - } - } - - action.setEnabled(mSelection != null && mFile != null); - } - - /** - * Create a new instance of our refactoring and a wizard to configure it. - */ - @Override - public void run(IAction action) { - if (mSelection != null && mFile != null) { - ExtractStringRefactoring ref = new ExtractStringRefactoring(mFile, mEditor, mSelection); - RefactoringWizard wizard = new ExtractStringWizard(ref, mFile.getProject()); - RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard); - try { - op.run(mWindow.getShell(), wizard.getDefaultPageTitle()); - } catch (InterruptedException e) { - // Interrupted. Pass. - } - } - } - - /** - * Returns the active editor (hopefully matching our selection) or null. - */ - private IEditorPart getActiveEditor() { - IWorkbenchWindow wwin = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); - if (wwin != null) { - IWorkbenchPage page = wwin.getActivePage(); - if (page != null) { - return page.getActiveEditor(); - } - } - - return null; - } - - /** - * Returns the active {@link IFile} (hopefully matching our selection) or null. - * The file is only returned if it's a file from a project with an Android nature. - * <p/> - * At that point we do not try to analyze if the selection nor the file is suitable - * for the refactoring. This check is performed when the refactoring is invoked since - * it can then produce meaningful error messages as needed. - */ - private IFile getSelectedFile(IEditorPart editor) { - if (editor != null) { - IEditorInput input = editor.getEditorInput(); - - if (input instanceof FileEditorInput) { - FileEditorInput fi = (FileEditorInput) input; - IFile file = fi.getFile(); - if (file.exists()) { - IProject proj = file.getProject(); - try { - if (proj != null && proj.hasNature(AdtConstants.NATURE_DEFAULT)) { - return file; - } - } catch (CoreException e) { - // ignore - } - } - } - } - - return null; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringContribution.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringContribution.java deleted file mode 100644 index 61bd06e81..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringContribution.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2009 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.extractstring; - -import org.eclipse.ltk.core.refactoring.RefactoringContribution; -import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; - -import java.util.Map; - -/** - * @see ExtractStringDescriptor - */ -public class ExtractStringContribution extends RefactoringContribution { - - /* (non-Javadoc) - * @see org.eclipse.ltk.core.refactoring.RefactoringContribution#createDescriptor(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.util.Map, int) - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - @Override - public RefactoringDescriptor createDescriptor( - String id, - String project, - String description, - String comment, - Map arguments, - int flags) - throws IllegalArgumentException { - return new ExtractStringDescriptor(project, description, comment, arguments); - } - - @SuppressWarnings("rawtypes") - @Override - public Map retrieveArgumentMap(RefactoringDescriptor descriptor) { - if (descriptor instanceof ExtractStringDescriptor) { - return ((ExtractStringDescriptor) descriptor).getArguments(); - } - return super.retrieveArgumentMap(descriptor); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringDescriptor.java deleted file mode 100644 index 190736aad..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringDescriptor.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2009 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.extractstring; - -import org.eclipse.core.runtime.CoreException; -import org.eclipse.ltk.core.refactoring.Refactoring; -import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; -import org.eclipse.ltk.core.refactoring.RefactoringStatus; - -import java.util.Map; - -/** - * A descriptor that allows an {@link ExtractStringRefactoring} to be created from - * a previous instance of itself. - */ -public class ExtractStringDescriptor extends RefactoringDescriptor { - - public static final String ID = - "com.android.ide.eclipse.adt.refactoring.extract.string"; //$NON-NLS-1$ - - private final Map<String, String> mArguments; - - public ExtractStringDescriptor(String project, String description, String comment, - Map<String, String> arguments) { - super(ID, project, description, comment, - RefactoringDescriptor.STRUCTURAL_CHANGE | RefactoringDescriptor.MULTI_CHANGE //flags - ); - mArguments = arguments; - } - - public Map<String, String> getArguments() { - return mArguments; - } - - /** - * Creates a new refactoring instance for this refactoring descriptor based on - * an argument map. The argument map is created by the refactoring itself in - * {@link ExtractStringRefactoring#createChange(org.eclipse.core.runtime.IProgressMonitor)} - * <p/> - * This is apparently used to replay a refactoring. - * - * {@inheritDoc} - * - * @throws CoreException - */ - @Override - public Refactoring createRefactoring(RefactoringStatus status) throws CoreException { - try { - ExtractStringRefactoring ref = new ExtractStringRefactoring(mArguments); - return ref; - } catch (NullPointerException e) { - status.addFatalError("Failed to recreate ExtractStringRefactoring from descriptor"); - return null; - } - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringInputPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringInputPage.java deleted file mode 100644 index 5ac5f5c4e..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringInputPage.java +++ /dev/null @@ -1,606 +0,0 @@ -/* - * Copyright (C) 2009 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.extractstring; - - -import com.android.SdkConstants; -import com.android.ide.common.resources.configuration.FolderConfiguration; -import com.android.ide.eclipse.adt.AdtConstants; -import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector; -import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.SelectorMode; -import com.android.resources.ResourceFolderType; - -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.jface.wizard.WizardPage; -import org.eclipse.ltk.ui.refactoring.UserInputWizardPage; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Combo; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Group; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.TreeSet; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * @see ExtractStringRefactoring - */ -class ExtractStringInputPage extends UserInputWizardPage { - - /** Last res file path used, shared across the session instances but specific to the - * current project. The default for unknown projects is {@link #DEFAULT_RES_FILE_PATH}. */ - private static HashMap<String, String> sLastResFilePath = new HashMap<String, String>(); - - /** The project where the user selection happened. */ - private final IProject mProject; - - /** Text field where the user enters the new ID to be generated or replaced with. */ - private Combo mStringIdCombo; - /** Text field where the user enters the new string value. */ - private Text mStringValueField; - /** The configuration selector, to select the resource path of the XML file. */ - private ConfigurationSelector mConfigSelector; - /** The combo to display the existing XML files or enter a new one. */ - private Combo mResFileCombo; - /** Checkbox asking whether to replace in all Java files. */ - private Button mReplaceAllJava; - /** Checkbox asking whether to replace in all XML files with same name but other res config */ - private Button mReplaceAllXml; - - /** Regex pattern to read a valid res XML file path. It checks that the are 2 folders and - * a leaf file name ending with .xml */ - private static final Pattern RES_XML_FILE_REGEX = Pattern.compile( - "/res/[a-z][a-zA-Z0-9_-]+/[^.]+\\.xml"); //$NON-NLS-1$ - /** Absolute destination folder root, e.g. "/res/" */ - private static final String RES_FOLDER_ABS = - AdtConstants.WS_RESOURCES + AdtConstants.WS_SEP; - /** Relative destination folder root, e.g. "res/" */ - private static final String RES_FOLDER_REL = - SdkConstants.FD_RESOURCES + AdtConstants.WS_SEP; - - private static final String DEFAULT_RES_FILE_PATH = "/res/values/strings.xml"; //$NON-NLS-1$ - - private XmlStringFileHelper mXmlHelper = new XmlStringFileHelper(); - - private final OnConfigSelectorUpdated mOnConfigSelectorUpdated = new OnConfigSelectorUpdated(); - - private ModifyListener mValidateOnModify = new ModifyListener() { - @Override - public void modifyText(ModifyEvent e) { - validatePage(); - } - }; - - private SelectionListener mValidateOnSelection = new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - validatePage(); - } - }; - - public ExtractStringInputPage(IProject project) { - super("ExtractStringInputPage"); //$NON-NLS-1$ - mProject = project; - } - - /** - * Create the UI for the refactoring wizard. - * <p/> - * Note that at that point the initial conditions have been checked in - * {@link ExtractStringRefactoring}. - * <p/> - * - * Note: the special tag below defines this as the entry point for the WindowsDesigner Editor. - * @wbp.parser.entryPoint - */ - @Override - public void createControl(Composite parent) { - Composite content = new Composite(parent, SWT.NONE); - GridLayout layout = new GridLayout(); - content.setLayout(layout); - - createStringGroup(content); - createResFileGroup(content); - createOptionGroup(content); - - initUi(); - setControl(content); - } - - /** - * Creates the top group with the field to replace which string and by what - * and by which options. - * - * @param content A composite with a 1-column grid layout - */ - public void createStringGroup(Composite content) { - - final ExtractStringRefactoring ref = getOurRefactoring(); - - Group group = new Group(content, SWT.NONE); - group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - group.setText("New String"); - if (ref.getMode() == ExtractStringRefactoring.Mode.EDIT_SOURCE) { - group.setText("String Replacement"); - } - - GridLayout layout = new GridLayout(); - layout.numColumns = 2; - group.setLayout(layout); - - // line: Textfield for string value (based on selection, if any) - - Label label = new Label(group, SWT.NONE); - label.setText("&String"); - - String selectedString = ref.getTokenString(); - - mStringValueField = new Text(group, SWT.SINGLE | SWT.LEFT | SWT.BORDER); - mStringValueField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - mStringValueField.setText(selectedString != null ? selectedString : ""); //$NON-NLS-1$ - - ref.setNewStringValue(mStringValueField.getText()); - - mStringValueField.addModifyListener(new ModifyListener() { - @Override - public void modifyText(ModifyEvent e) { - validatePage(); - } - }); - - // line : Textfield for new ID - - label = new Label(group, SWT.NONE); - label.setText("ID &R.string."); - if (ref.getMode() == ExtractStringRefactoring.Mode.EDIT_SOURCE) { - label.setText("&Replace by R.string."); - } else if (ref.getMode() == ExtractStringRefactoring.Mode.SELECT_NEW_ID) { - label.setText("New &R.string."); - } - - mStringIdCombo = new Combo(group, SWT.SINGLE | SWT.LEFT | SWT.BORDER | SWT.DROP_DOWN); - mStringIdCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - mStringIdCombo.setText(guessId(selectedString)); - mStringIdCombo.forceFocus(); - - ref.setNewStringId(mStringIdCombo.getText().trim()); - - mStringIdCombo.addModifyListener(mValidateOnModify); - mStringIdCombo.addSelectionListener(mValidateOnSelection); - } - - /** - * Creates the lower group with the fields to choose the resource confirmation and - * the target XML file. - * - * @param content A composite with a 1-column grid layout - */ - private void createResFileGroup(Composite content) { - - Group group = new Group(content, SWT.NONE); - GridData gd = new GridData(GridData.FILL_HORIZONTAL); - gd.grabExcessVerticalSpace = true; - group.setLayoutData(gd); - group.setText("XML resource to edit"); - - GridLayout layout = new GridLayout(); - layout.numColumns = 2; - group.setLayout(layout); - - // line: selection of the res config - - Label label; - label = new Label(group, SWT.NONE); - label.setText("&Configuration:"); - - mConfigSelector = new ConfigurationSelector(group, SelectorMode.DEFAULT); - gd = new GridData(GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL); - gd.horizontalSpan = 2; - gd.widthHint = ConfigurationSelector.WIDTH_HINT; - gd.heightHint = ConfigurationSelector.HEIGHT_HINT; - mConfigSelector.setLayoutData(gd); - mConfigSelector.setOnChangeListener(mOnConfigSelectorUpdated); - - // line: selection of the output file - - label = new Label(group, SWT.NONE); - label.setText("Resource &file:"); - - mResFileCombo = new Combo(group, SWT.DROP_DOWN); - mResFileCombo.select(0); - mResFileCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - mResFileCombo.addModifyListener(mOnConfigSelectorUpdated); - } - - /** - * Creates the bottom option groups with a few checkboxes. - * - * @param content A composite with a 1-column grid layout - */ - private void createOptionGroup(Composite content) { - Group options = new Group(content, SWT.NONE); - options.setText("Options"); - GridData gd_Options = new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1); - gd_Options.widthHint = 77; - options.setLayoutData(gd_Options); - options.setLayout(new GridLayout(1, false)); - - mReplaceAllJava = new Button(options, SWT.CHECK); - mReplaceAllJava.setToolTipText("When checked, the exact same string literal will be replaced in all Java files."); - mReplaceAllJava.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); - mReplaceAllJava.setText("Replace in all &Java files"); - mReplaceAllJava.addSelectionListener(mValidateOnSelection); - - mReplaceAllXml = new Button(options, SWT.CHECK); - mReplaceAllXml.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); - mReplaceAllXml.setToolTipText("When checked, string literals will be replaced in other XML resource files having the same name but located in different resource configuration folders."); - mReplaceAllXml.setText("Replace in all &XML files for different configuration"); - mReplaceAllXml.addSelectionListener(mValidateOnSelection); - } - - // -- Start of internal part ---------- - // Hide everything down-below from WindowsDesigner Editor - //$hide>>$ - - /** - * Init UI just after it has been created the first time. - */ - private void initUi() { - // set output file name to the last one used - String projPath = mProject.getFullPath().toPortableString(); - String filePath = sLastResFilePath.get(projPath); - - mResFileCombo.setText(filePath != null ? filePath : DEFAULT_RES_FILE_PATH); - mOnConfigSelectorUpdated.run(); - validatePage(); - } - - /** - * Utility method to guess a suitable new XML ID based on the selected string. - */ - public static String guessId(String text) { - if (text == null) { - return ""; //$NON-NLS-1$ - } - - // make lower case - text = text.toLowerCase(Locale.US); - - // everything not alphanumeric becomes an underscore - text = text.replaceAll("[^a-zA-Z0-9]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$ - - // the id must be a proper Java identifier, so it can't start with a number - if (text.length() > 0 && !Character.isJavaIdentifierStart(text.charAt(0))) { - text = "_" + text; //$NON-NLS-1$ - } - return text; - } - - /** - * Returns the {@link ExtractStringRefactoring} instance used by this wizard page. - */ - private ExtractStringRefactoring getOurRefactoring() { - return (ExtractStringRefactoring) getRefactoring(); - } - - /** - * Validates fields of the wizard input page. Displays errors as appropriate and - * enable the "Next" button (or not) by calling {@link #setPageComplete(boolean)}. - * - * If validation succeeds, this updates the text id & value in the refactoring object. - * - * @return True if the page has been positively validated. It may still have warnings. - */ - private boolean validatePage() { - boolean success = true; - - ExtractStringRefactoring ref = getOurRefactoring(); - - ref.setReplaceAllJava(mReplaceAllJava.getSelection()); - ref.setReplaceAllXml(mReplaceAllXml.isEnabled() && mReplaceAllXml.getSelection()); - - // Analyze fatal errors. - - String text = mStringIdCombo.getText().trim(); - if (text == null || text.length() < 1) { - setErrorMessage("Please provide a resource ID."); - success = false; - } else { - for (int i = 0; i < text.length(); i++) { - char c = text.charAt(i); - boolean ok = i == 0 ? - Character.isJavaIdentifierStart(c) : - Character.isJavaIdentifierPart(c); - if (!ok) { - setErrorMessage(String.format( - "The resource ID must be a valid Java identifier. The character %1$c at position %2$d is not acceptable.", - c, i+1)); - success = false; - break; - } - } - - // update the field in the refactoring object in case of success - if (success) { - ref.setNewStringId(text); - } - } - - String resFile = mResFileCombo.getText(); - if (success) { - if (resFile == null || resFile.length() == 0) { - setErrorMessage("A resource file name is required."); - success = false; - } else if (!RES_XML_FILE_REGEX.matcher(resFile).matches()) { - setErrorMessage("The XML file name is not valid."); - success = false; - } - } - - // Analyze info & warnings. - - if (success) { - setErrorMessage(null); - - ref.setTargetFile(resFile); - sLastResFilePath.put(mProject.getFullPath().toPortableString(), resFile); - - String idValue = mXmlHelper.valueOfStringId(mProject, resFile, text); - if (idValue != null) { - String msg = String.format("%1$s already contains a string ID '%2$s' with value '%3$s'.", - resFile, - text, - idValue); - if (ref.getMode() == ExtractStringRefactoring.Mode.SELECT_NEW_ID) { - setErrorMessage(msg); - success = false; - } else { - setMessage(msg, WizardPage.WARNING); - } - } else if (mProject.findMember(resFile) == null) { - setMessage( - String.format("File %2$s does not exist and will be created.", - text, resFile), - WizardPage.INFORMATION); - } else { - setMessage(null); - } - } - - if (success) { - // Also update the text value in case of success. - ref.setNewStringValue(mStringValueField.getText()); - } - - setPageComplete(success); - return success; - } - - private void updateStringValueCombo() { - String resFile = mResFileCombo.getText(); - Map<String, String> ids = mXmlHelper.getResIdsForFile(mProject, resFile); - - // get the current text from the combo, to make sure we don't change it - String currText = mStringIdCombo.getText(); - - // erase the choices and fill with the given ids - mStringIdCombo.removeAll(); - mStringIdCombo.setItems(ids.keySet().toArray(new String[ids.size()])); - - // set the current text to preserve it in case it changed - if (!currText.equals(mStringIdCombo.getText())) { - mStringIdCombo.setText(currText); - } - } - - private class OnConfigSelectorUpdated implements Runnable, ModifyListener { - - /** Regex pattern to parse a valid res path: it reads (/res/folder-name/)+(filename). */ - private final Pattern mPathRegex = Pattern.compile( - "(/res/[a-z][a-zA-Z0-9_-]+/)(.+)"); //$NON-NLS-1$ - - /** Temporary config object used to retrieve the Config Selector value. */ - private FolderConfiguration mTempConfig = new FolderConfiguration(); - - private HashMap<String, TreeSet<String>> mFolderCache = - new HashMap<String, TreeSet<String>>(); - private String mLastFolderUsedInCombo = null; - private boolean mInternalConfigChange; - private boolean mInternalFileComboChange; - - /** - * Callback invoked when the {@link ConfigurationSelector} has been changed. - * <p/> - * The callback does the following: - * <ul> - * <li> Examine the current file name to retrieve the XML filename, if any. - * <li> Recompute the path based on the configuration selector (e.g. /res/values-fr/). - * <li> Examine the path to retrieve all the files in it. Keep those in a local cache. - * <li> If the XML filename from step 1 is not in the file list, it's a custom file name. - * Insert it and sort it. - * <li> Re-populate the file combo with all the choices. - * <li> Select the original XML file. - */ - @Override - public void run() { - if (mInternalConfigChange) { - return; - } - - // get current leafname, if any - String leafName = ""; //$NON-NLS-1$ - String currPath = mResFileCombo.getText(); - Matcher m = mPathRegex.matcher(currPath); - if (m.matches()) { - // Note: groups 1 and 2 cannot be null. - leafName = m.group(2); - currPath = m.group(1); - } else { - // There was a path but it was invalid. Ignore it. - currPath = ""; //$NON-NLS-1$ - } - - // recreate the res path from the current configuration - mConfigSelector.getConfiguration(mTempConfig); - StringBuffer sb = new StringBuffer(RES_FOLDER_ABS); - sb.append(mTempConfig.getFolderName(ResourceFolderType.VALUES)); - sb.append(AdtConstants.WS_SEP); - - String newPath = sb.toString(); - - if (newPath.equals(currPath) && newPath.equals(mLastFolderUsedInCombo)) { - // Path has not changed. No need to reload. - return; - } - - // Get all the files at the new path - - TreeSet<String> filePaths = mFolderCache.get(newPath); - - if (filePaths == null) { - filePaths = new TreeSet<String>(); - - IFolder folder = mProject.getFolder(newPath); - if (folder != null && folder.exists()) { - try { - for (IResource res : folder.members()) { - String name = res.getName(); - if (res.getType() == IResource.FILE && name.endsWith(".xml")) { - filePaths.add(newPath + name); - } - } - } catch (CoreException e) { - // Ignore. - } - } - - mFolderCache.put(newPath, filePaths); - } - - currPath = newPath + leafName; - if (leafName.length() > 0 && !filePaths.contains(currPath)) { - filePaths.add(currPath); - } - - // Fill the combo - try { - mInternalFileComboChange = true; - - mResFileCombo.removeAll(); - - for (String filePath : filePaths) { - mResFileCombo.add(filePath); - } - - int index = -1; - if (leafName.length() > 0) { - index = mResFileCombo.indexOf(currPath); - if (index >= 0) { - mResFileCombo.select(index); - } - } - - if (index == -1) { - mResFileCombo.setText(currPath); - } - - mLastFolderUsedInCombo = newPath; - - } finally { - mInternalFileComboChange = false; - } - - // finally validate the whole page - updateStringValueCombo(); - validatePage(); - } - - /** - * Callback invoked when {@link ExtractStringInputPage#mResFileCombo} has been - * modified. - */ - @Override - public void modifyText(ModifyEvent e) { - if (mInternalFileComboChange) { - return; - } - - String wsFolderPath = mResFileCombo.getText(); - - // This is a custom path, we need to sanitize it. - // First it should start with "/res/". Then we need to make sure there are no - // relative paths, things like "../" or "./" or even "//". - wsFolderPath = wsFolderPath.replaceAll("/+\\.\\./+|/+\\./+|//+|\\\\+|^/+", "/"); //$NON-NLS-1$ //$NON-NLS-2$ - wsFolderPath = wsFolderPath.replaceAll("^\\.\\./+|^\\./+", ""); //$NON-NLS-1$ //$NON-NLS-2$ - wsFolderPath = wsFolderPath.replaceAll("/+\\.\\.$|/+\\.$|/+$", ""); //$NON-NLS-1$ //$NON-NLS-2$ - - // We get "res/foo" from selections relative to the project when we want a "/res/foo" path. - if (wsFolderPath.startsWith(RES_FOLDER_REL)) { - wsFolderPath = RES_FOLDER_ABS + wsFolderPath.substring(RES_FOLDER_REL.length()); - - mInternalFileComboChange = true; - mResFileCombo.setText(wsFolderPath); - mInternalFileComboChange = false; - } - - if (wsFolderPath.startsWith(RES_FOLDER_ABS)) { - wsFolderPath = wsFolderPath.substring(RES_FOLDER_ABS.length()); - - int pos = wsFolderPath.indexOf(AdtConstants.WS_SEP_CHAR); - if (pos >= 0) { - wsFolderPath = wsFolderPath.substring(0, pos); - } - - String[] folderSegments = wsFolderPath.split(SdkConstants.RES_QUALIFIER_SEP); - - if (folderSegments.length > 0) { - String folderName = folderSegments[0]; - - if (folderName != null && !folderName.equals(wsFolderPath)) { - // update config selector - mInternalConfigChange = true; - mConfigSelector.setConfiguration(folderSegments); - mInternalConfigChange = false; - } - } - } - - updateStringValueCombo(); - validatePage(); - } - } - - // End of hiding from SWT Designer - //$hide<<$ - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringProposal.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringProposal.java deleted file mode 100644 index 5400be4e4..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringProposal.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2011 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.extractstring; - -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AdtUtils; - -import org.eclipse.core.resources.IFile; -import org.eclipse.jdt.core.IBuffer; -import org.eclipse.jdt.core.JavaModelException; -import org.eclipse.jdt.core.dom.ASTNode; -import org.eclipse.jdt.ui.text.java.IInvocationContext; -import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.ITextSelection; -import org.eclipse.jface.text.TextSelection; -import org.eclipse.jface.text.contentassist.IContextInformation; -import org.eclipse.ltk.ui.refactoring.RefactoringWizard; -import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.Point; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.IWorkbenchWindow; -import org.eclipse.ui.PlatformUI; - -/** - * Proposal for extracting strings in Java files - */ -public class ExtractStringProposal implements IJavaCompletionProposal { - private IInvocationContext mContext; - - public ExtractStringProposal(IInvocationContext context) { - mContext = context; - } - - @Override - public void apply(IDocument document) { - IEditorPart editor = AdtUtils.getActiveEditor(); - IFile file = AdtUtils.getActiveFile(); - if (editor == null || file == null) { - return; - } - - ASTNode coveringNode = mContext.getCoveringNode(); - int start = coveringNode.getStartPosition(); - int length = coveringNode.getLength(); - ITextSelection selection = new TextSelection(start, length); - - ExtractStringRefactoring refactoring = new ExtractStringRefactoring(file, editor, - selection); - - RefactoringWizard wizard = new ExtractStringWizard(refactoring, file.getProject()); - RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard); - try { - IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); - op.run(window.getShell(), wizard.getDefaultPageTitle()); - } catch (InterruptedException e) { - } - } - - @Override - public Point getSelection(IDocument document) { - return null; - } - - @Override - public String getAdditionalProposalInfo() { - try { - ASTNode coveringNode = mContext.getCoveringNode(); - int start = coveringNode.getStartPosition(); - int length = coveringNode.getLength(); - IBuffer buffer = mContext.getCompilationUnit().getBuffer(); - StringBuilder sb = new StringBuilder(); - String string = buffer.getText(start, length); - string = ExtractStringRefactoring.unquoteAttrValue(string); - String token = ExtractStringInputPage.guessId(string); - - // Look up the beginning and the end of the line (outside of the extracted string) - // such that we can show a preview of the diff, e.g. if you have - // foo.setTitle("Hello"); we want to show foo.setTitle(R.string.hello); - // so we need to extract "foo.setTitle(" and ");". - - // Look backwards to the beginning of the line (and strip whitespace) - int i = start - 1; - while (i > 0) { - char c = buffer.getChar(i); - if (c == '\r' || (c == '\n')) { - break; - } - i--; - } - String linePrefix = buffer.getText(i + 1, start - (i + 1)).trim(); - - // Look forwards to the end of the line (and strip whitespace) - i = start + length; - while (i < buffer.getLength()) { - char c = buffer.getChar(i); - if (c == '\r' || (c == '\n')) { - break; - } - i++; - } - String lineSuffix = buffer.getText(start + length, i - (start + length)); - - // Should we show the replacement as just R.string.foo or - // context.getString(R.string.foo) ? - boolean useContext = false; - ASTNode parent = coveringNode.getParent(); - if (parent != null) { - int type = parent.getNodeType(); - if (type == ASTNode.ASSIGNMENT - || type == ASTNode.VARIABLE_DECLARATION_STATEMENT - || type == ASTNode.VARIABLE_DECLARATION_FRAGMENT - || type == ASTNode.VARIABLE_DECLARATION_EXPRESSION) { - useContext = true; - } - } - - // Display .java change: - sb.append("...<br>"); //$NON-NLS-1$ - sb.append(linePrefix); - sb.append("<b>"); //$NON-NLS-1$ - if (useContext) { - sb.append("context.getString("); //$NON-NLS-1$ - } - sb.append("R.string."); //$NON-NLS-1$ - sb.append(token); - if (useContext) { - sb.append(")"); //$NON-NLS-1$ - } - sb.append("</b>"); //$NON-NLS-1$ - sb.append(lineSuffix); - sb.append("<br>...<br>"); //$NON-NLS-1$ - - // Display strings.xml change: - sb.append("<br>"); //$NON-NLS-1$ - sb.append("<resources><br>"); //$NON-NLS-1$ - sb.append(" <b><string name=\""); //$NON-NLS-1$ - sb.append(token); - sb.append("\">"); //$NON-NLS-1$ - sb.append(string); - sb.append("</string></b><br>"); //$NON-NLS-1$ - sb.append("</resources>"); //$NON-NLS-1$ - - return sb.toString(); - } catch (JavaModelException e) { - AdtPlugin.log(e, null); - } - - return "Initiates the Extract String refactoring operation"; - } - - @Override - public String getDisplayString() { - return "Extract String"; - } - - @Override - public Image getImage() { - return AdtPlugin.getAndroidLogo(); - } - - @Override - public IContextInformation getContextInformation() { - return null; - } - - @Override - public int getRelevance() { - return 80; - } -}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java deleted file mode 100644 index db0b0967d..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java +++ /dev/null @@ -1,1933 +0,0 @@ -/* - * Copyright (C) 2009 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.extractstring; - -import static com.android.SdkConstants.QUOT_ENTITY; -import static com.android.SdkConstants.STRING_PREFIX; - -import com.android.SdkConstants; -import com.android.ide.common.res2.ValueXmlHelper; -import com.android.ide.common.xml.ManifestData; -import com.android.ide.eclipse.adt.AdtConstants; -import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; -import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.adt.internal.editors.descriptors.ReferenceAttributeDescriptor; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; -import com.android.resources.ResourceFolderType; -import com.android.resources.ResourceType; - -import org.eclipse.core.resources.IContainer; -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.resources.ResourceAttributes; -import org.eclipse.core.resources.ResourcesPlugin; -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.Path; -import org.eclipse.core.runtime.SubMonitor; -import org.eclipse.jdt.core.IBuffer; -import org.eclipse.jdt.core.ICompilationUnit; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.IPackageFragment; -import org.eclipse.jdt.core.IPackageFragmentRoot; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.JavaModelException; -import org.eclipse.jdt.core.ToolFactory; -import org.eclipse.jdt.core.compiler.IScanner; -import org.eclipse.jdt.core.compiler.ITerminalSymbols; -import org.eclipse.jdt.core.compiler.InvalidInputException; -import org.eclipse.jdt.core.dom.AST; -import org.eclipse.jdt.core.dom.ASTNode; -import org.eclipse.jdt.core.dom.ASTParser; -import org.eclipse.jdt.core.dom.CompilationUnit; -import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; -import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; -import org.eclipse.jface.text.ITextSelection; -import org.eclipse.ltk.core.refactoring.Change; -import org.eclipse.ltk.core.refactoring.ChangeDescriptor; -import org.eclipse.ltk.core.refactoring.CompositeChange; -import org.eclipse.ltk.core.refactoring.Refactoring; -import org.eclipse.ltk.core.refactoring.RefactoringChangeDescriptor; -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.InsertEdit; -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.ui.IEditorPart; -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.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 org.w3c.dom.Node; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Queue; - -/** - * This refactoring extracts a string from a file and replaces it by an Android resource ID - * such as R.string.foo. - * <p/> - * There are a number of scenarios, which are not all supported yet. The workflow works as - * such: - * <ul> - * <li> User selects a string in a Java and invokes the {@link ExtractStringAction}. - * <li> The action finds the {@link ICompilationUnit} being edited as well as the current - * {@link ITextSelection}. The action creates a new instance of this refactoring as - * well as an {@link ExtractStringWizard} and runs the operation. - * <li> Step 1 of the refactoring is to check the preliminary conditions. Right now we check - * that the java source is not read-only and is in sync. We also try to find a string under - * the selection. If this fails, the refactoring is aborted. - * <li> On success, the wizard is shown, which lets the user input the new ID to use. - * <li> The wizard sets the user input values into this refactoring instance, e.g. the new string - * ID, the XML file to update, etc. The wizard does use the utility method - * {@link XmlStringFileHelper#valueOfStringId(IProject, String, String)} to check whether - * the new ID is already defined in the target XML file. - * <li> Once Preview or Finish is selected in the wizard, the - * {@link #checkFinalConditions(IProgressMonitor)} is called to double-check the user input - * and compute the actual changes. - * <li> When all changes are computed, {@link #createChange(IProgressMonitor)} is invoked. - * </ul> - * - * The list of changes are: - * <ul> - * <li> If the target XML does not exist, create it with the new string ID. - * <li> If the target XML exists, find the <resources> node and add the new string ID right after. - * If the node is <resources/>, it needs to be opened. - * <li> Create an AST rewriter to edit the source Java file and replace all occurrences by the - * new computed R.string.foo. Also need to rewrite imports to import R as needed. - * If there's already a conflicting R included, we need to insert the FQCN instead. - * <li> TODO: Have a pref in the wizard: [x] Change other XML Files - * <li> TODO: Have a pref in the wizard: [x] Change other Java Files - * </ul> - */ -@SuppressWarnings("restriction") -public class ExtractStringRefactoring extends Refactoring { - - public enum Mode { - /** - * the Extract String refactoring is called on an <em>existing</em> source file. - * Its purpose is then to get the selected string of the source and propose to - * change it by an XML id. The XML id may be a new one or an existing one. - */ - EDIT_SOURCE, - /** - * The Extract String refactoring is called without any source file. - * Its purpose is then to create a new XML string ID or select/modify an existing one. - */ - SELECT_ID, - /** - * The Extract String refactoring is called without any source file. - * Its purpose is then to create a new XML string ID. The ID must not already exist. - */ - SELECT_NEW_ID - } - - /** The {@link Mode} of operation of the refactoring. */ - private final Mode mMode; - /** Non-null when editing an Android Resource XML file: identifies the attribute name - * of the value being edited. When null, the source is an Android Java file. */ - private String mXmlAttributeName; - /** The file model being manipulated. - * Value is null when not on {@link Mode#EDIT_SOURCE} mode. */ - private final IFile mFile; - /** The editor. Non-null when invoked from {@link ExtractStringAction}. Null otherwise. */ - private final IEditorPart mEditor; - /** The project that contains {@link #mFile} and that contains the target XML file to modify. */ - private final IProject mProject; - /** The start of the selection in {@link #mFile}. - * Value is -1 when not on {@link Mode#EDIT_SOURCE} mode. */ - private final int mSelectionStart; - /** The end of the selection in {@link #mFile}. - * Value is -1 when not on {@link Mode#EDIT_SOURCE} mode. */ - private final int mSelectionEnd; - - /** The compilation unit, only defined if {@link #mFile} points to a usable Java source file. */ - private ICompilationUnit mUnit; - /** The actual string selected, after UTF characters have been escaped, good for display. - * Value is null when not on {@link Mode#EDIT_SOURCE} mode. */ - private String mTokenString; - - /** The XML string ID selected by the user in the wizard. */ - private String mXmlStringId; - /** The XML string value. Might be different than the initial selected string. */ - private String mXmlStringValue; - /** The path of the XML file that will define {@link #mXmlStringId}, selected by the user - * in the wizard. This is relative to the project, e.g. "/res/values/string.xml" */ - private String mTargetXmlFileWsPath; - /** True if we should find & replace in all Java files. */ - private boolean mReplaceAllJava; - /** True if we should find & replace in all XML files of the same name in other res configs - * (other than the main {@link #mTargetXmlFileWsPath}.) */ - private boolean mReplaceAllXml; - - /** The list of changes computed by {@link #checkFinalConditions(IProgressMonitor)} and - * used by {@link #createChange(IProgressMonitor)}. */ - private ArrayList<Change> mChanges; - - private XmlStringFileHelper mXmlHelper = new XmlStringFileHelper(); - - private static final String KEY_MODE = "mode"; //$NON-NLS-1$ - private static final String KEY_FILE = "file"; //$NON-NLS-1$ - private static final String KEY_PROJECT = "proj"; //$NON-NLS-1$ - private static final String KEY_SEL_START = "sel-start"; //$NON-NLS-1$ - private static final String KEY_SEL_END = "sel-end"; //$NON-NLS-1$ - private static final String KEY_TOK_ESC = "tok-esc"; //$NON-NLS-1$ - private static final String KEY_XML_ATTR_NAME = "xml-attr-name"; //$NON-NLS-1$ - private static final String KEY_RPLC_ALL_JAVA = "rplc-all-java"; //$NON-NLS-1$ - private static final String KEY_RPLC_ALL_XML = "rplc-all-xml"; //$NON-NLS-1$ - - /** - * This constructor is solely used by {@link ExtractStringDescriptor}, - * to replay a previous refactoring. - * <p/> - * To create a refactoring from code, please use one of the two other constructors. - * - * @param arguments A map previously created using {@link #createArgumentMap()}. - * @throws NullPointerException - */ - public ExtractStringRefactoring(Map<String, String> arguments) throws NullPointerException { - - mReplaceAllJava = Boolean.parseBoolean(arguments.get(KEY_RPLC_ALL_JAVA)); - mReplaceAllXml = Boolean.parseBoolean(arguments.get(KEY_RPLC_ALL_XML)); - mMode = Mode.valueOf(arguments.get(KEY_MODE)); - - IPath path = Path.fromPortableString(arguments.get(KEY_PROJECT)); - mProject = (IProject) ResourcesPlugin.getWorkspace().getRoot().findMember(path); - - if (mMode == Mode.EDIT_SOURCE) { - path = Path.fromPortableString(arguments.get(KEY_FILE)); - mFile = (IFile) ResourcesPlugin.getWorkspace().getRoot().findMember(path); - - mSelectionStart = Integer.parseInt(arguments.get(KEY_SEL_START)); - mSelectionEnd = Integer.parseInt(arguments.get(KEY_SEL_END)); - mTokenString = arguments.get(KEY_TOK_ESC); - mXmlAttributeName = arguments.get(KEY_XML_ATTR_NAME); - } else { - mFile = null; - mSelectionStart = mSelectionEnd = -1; - mTokenString = null; - mXmlAttributeName = null; - } - - mEditor = null; - } - - private Map<String, String> createArgumentMap() { - HashMap<String, String> args = new HashMap<String, String>(); - args.put(KEY_RPLC_ALL_JAVA, Boolean.toString(mReplaceAllJava)); - args.put(KEY_RPLC_ALL_XML, Boolean.toString(mReplaceAllXml)); - args.put(KEY_MODE, mMode.name()); - args.put(KEY_PROJECT, mProject.getFullPath().toPortableString()); - if (mMode == Mode.EDIT_SOURCE) { - args.put(KEY_FILE, mFile.getFullPath().toPortableString()); - args.put(KEY_SEL_START, Integer.toString(mSelectionStart)); - args.put(KEY_SEL_END, Integer.toString(mSelectionEnd)); - args.put(KEY_TOK_ESC, mTokenString); - args.put(KEY_XML_ATTR_NAME, mXmlAttributeName); - } - return args; - } - - /** - * Constructor to use when the Extract String refactoring is called on an - * *existing* source file. Its purpose is then to get the selected string of - * the source and propose to change it by an XML id. The XML id may be a new one - * or an existing one. - * - * @param file The source file to process. Cannot be null. File must exist in workspace. - * @param editor The editor. - * @param selection The selection in the source file. Cannot be null or empty. - */ - public ExtractStringRefactoring(IFile file, IEditorPart editor, ITextSelection selection) { - mMode = Mode.EDIT_SOURCE; - mFile = file; - mEditor = editor; - mProject = file.getProject(); - mSelectionStart = selection.getOffset(); - mSelectionEnd = mSelectionStart + Math.max(0, selection.getLength() - 1); - } - - /** - * Constructor to use when the Extract String refactoring is called without - * any source file. Its purpose is then to create a new XML string ID. - * <p/> - * For example this is currently invoked by the ResourceChooser when - * the user wants to create a new string rather than select an existing one. - * - * @param project The project where the target XML file to modify is located. Cannot be null. - * @param enforceNew If true the XML ID must be a new one. - * If false, an existing ID can be used. - */ - public ExtractStringRefactoring(IProject project, boolean enforceNew) { - mMode = enforceNew ? Mode.SELECT_NEW_ID : Mode.SELECT_ID; - mFile = null; - mEditor = null; - mProject = project; - mSelectionStart = mSelectionEnd = -1; - } - - /** - * Sets the replacement string ID. Used by the wizard to set the user input. - */ - public void setNewStringId(String newStringId) { - mXmlStringId = newStringId; - } - - /** - * Sets the replacement string ID. Used by the wizard to set the user input. - */ - public void setNewStringValue(String newStringValue) { - mXmlStringValue = newStringValue; - } - - /** - * Sets the target file. This is a project path, e.g. "/res/values/strings.xml". - * Used by the wizard to set the user input. - */ - public void setTargetFile(String targetXmlFileWsPath) { - mTargetXmlFileWsPath = targetXmlFileWsPath; - } - - public void setReplaceAllJava(boolean replaceAllJava) { - mReplaceAllJava = replaceAllJava; - } - - public void setReplaceAllXml(boolean replaceAllXml) { - mReplaceAllXml = replaceAllXml; - } - - /** - * @see org.eclipse.ltk.core.refactoring.Refactoring#getName() - */ - @Override - public String getName() { - if (mMode == Mode.SELECT_ID) { - return "Create or Use Android String"; - } else if (mMode == Mode.SELECT_NEW_ID) { - return "Create New Android String"; - } - - return "Extract Android String"; - } - - public Mode getMode() { - return mMode; - } - - /** - * Gets the actual string selected, after UTF characters have been escaped, - * good for display. Value can be null. - */ - public String getTokenString() { - return mTokenString; - } - - /** Returns the XML string ID selected by the user in the wizard. */ - public String getXmlStringId() { - return mXmlStringId; - } - - /** - * Step 1 of 3 of the refactoring: - * Checks that the current selection meets the initial condition before the ExtractString - * wizard is shown. The check is supposed to be lightweight and quick. Note that at that - * point the wizard has not been created yet. - * <p/> - * Here we scan the source buffer to find the token matching the selection. - * The check is successful is a Java string literal is selected, the source is in sync - * and is not read-only. - * <p/> - * This is also used to extract the string to be modified, so that we can display it in - * the refactoring wizard. - * - * @see org.eclipse.ltk.core.refactoring.Refactoring#checkInitialConditions(org.eclipse.core.runtime.IProgressMonitor) - * - * @throws CoreException - */ - @Override - public RefactoringStatus checkInitialConditions(IProgressMonitor monitor) - throws CoreException, OperationCanceledException { - - mUnit = null; - mTokenString = null; - - RefactoringStatus status = new RefactoringStatus(); - - try { - monitor.beginTask("Checking preconditions...", 6); - - if (mMode != Mode.EDIT_SOURCE) { - monitor.worked(6); - return status; - } - - if (!checkSourceFile(mFile, status, monitor)) { - return status; - } - - // Try to get a compilation unit from this file. If it fails, mUnit is null. - try { - mUnit = JavaCore.createCompilationUnitFrom(mFile); - - // Make sure the unit is not read-only, e.g. it's not a class file or inside a Jar - if (mUnit.isReadOnly()) { - status.addFatalError("The file is read-only, please make it writeable first."); - return status; - } - - // This is a Java file. Check if it contains the selection we want. - if (!findSelectionInJavaUnit(mUnit, status, monitor)) { - return status; - } - - } catch (Exception e) { - // That was not a Java file. Ignore. - } - - if (mUnit != null) { - monitor.worked(1); - return status; - } - - // Check this a Layout XML file and get the selection and its context. - if (mFile != null && SdkConstants.EXT_XML.equals(mFile.getFileExtension())) { - - // Currently we only support Android resource XML files, so they must have a path - // similar to - // project/res/<type>[-<configuration>]/*.xml - // project/AndroidManifest.xml - // There is no support for sub folders, so the segment count must be 4 or 2. - // 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 = mFile.getFullPath(); - if ((path.segmentCount() == 4 && - path.segment(1).equalsIgnoreCase(SdkConstants.FD_RESOURCES)) || - (path.segmentCount() == 2 && - path.segment(1).equalsIgnoreCase(SdkConstants.FN_ANDROID_MANIFEST_XML))) { - if (!findSelectionInXmlFile(mFile, status, monitor)) { - return status; - } - } - } - - if (!status.isOK()) { - status.addFatalError( - "Selection must be inside a Java source or an Android Layout XML file."); - } - - } finally { - monitor.done(); - } - - return status; - } - - /** - * Try to find the selected Java element in the compilation unit. - * - * If selection matches a string literal, capture it, otherwise add a fatal error - * to the status. - * - * On success, advance the monitor by 3. - * Returns status.isOK(). - */ - private boolean findSelectionInJavaUnit(ICompilationUnit unit, - RefactoringStatus status, IProgressMonitor monitor) { - try { - IBuffer buffer = unit.getBuffer(); - - IScanner scanner = ToolFactory.createScanner( - false, //tokenizeComments - false, //tokenizeWhiteSpace - false, //assertMode - false //recordLineSeparator - ); - scanner.setSource(buffer.getCharacters()); - monitor.worked(1); - - for(int token = scanner.getNextToken(); - token != ITerminalSymbols.TokenNameEOF; - token = scanner.getNextToken()) { - if (scanner.getCurrentTokenStartPosition() <= mSelectionStart && - scanner.getCurrentTokenEndPosition() >= mSelectionEnd) { - // found the token, but only keep if the right type - if (token == ITerminalSymbols.TokenNameStringLiteral) { - mTokenString = new String(scanner.getCurrentTokenSource()); - } - break; - } else if (scanner.getCurrentTokenStartPosition() > mSelectionEnd) { - // scanner is past the selection, abort. - break; - } - } - } catch (JavaModelException e1) { - // Error in unit.getBuffer. Ignore. - } catch (InvalidInputException e2) { - // Error in scanner.getNextToken. Ignore. - } finally { - monitor.worked(1); - } - - if (mTokenString != null) { - // As a literal string, the token should have surrounding quotes. Remove them. - // Note: unquoteAttrValue technically removes either " or ' paired quotes, whereas - // the Java token should only have " quotes. Since we know the type to be a string - // literal, there should be no confusion here. - mTokenString = unquoteAttrValue(mTokenString); - - // We need a non-empty string literal - if (mTokenString.length() == 0) { - mTokenString = null; - } - } - - if (mTokenString == null) { - status.addFatalError("Please select a Java string literal."); - } - - monitor.worked(1); - return status.isOK(); - } - - /** - * Try to find the selected XML element. This implementation replies on the refactoring - * originating from an Android Layout Editor. We rely on some internal properties of the - * Structured XML editor to retrieve file content to avoid parsing it again. We also rely - * on our specific Android XML model to get element & attribute descriptor properties. - * - * If selection matches a string literal, capture it, otherwise add a fatal error - * to the status. - * - * On success, advance the monitor by 1. - * Returns status.isOK(). - */ - private boolean findSelectionInXmlFile(IFile file, - RefactoringStatus status, - IProgressMonitor monitor) { - - try { - if (!(mEditor instanceof AndroidXmlEditor)) { - status.addFatalError("Only the Android XML Editor is currently supported."); - return status.isOK(); - } - - AndroidXmlEditor editor = (AndroidXmlEditor) mEditor; - IStructuredModel smodel = null; - Node node = null; - String currAttrName = null; - - try { - // See the portability note in AndroidXmlEditor#getModelForRead() javadoc. - smodel = editor.getModelForRead(); - if (smodel != null) { - // The structured model gives the us the actual XML Node element where the - // offset is. By using this Node, we can find the exact UiElementNode of our - // model and thus we'll be able to get the properties of the attribute -- to - // check if it accepts a string reference. This does not however tell us if - // the selection is actually in an attribute value, nor which attribute is - // being edited. - for(int offset = mSelectionStart; offset >= 0 && node == null; --offset) { - node = (Node) smodel.getIndexedRegion(offset); - } - - if (node == null) { - status.addFatalError( - "The selection does not match any element in the XML document."); - return status.isOK(); - } - - if (node.getNodeType() != Node.ELEMENT_NODE) { - status.addFatalError("The selection is not inside an actual XML element."); - return status.isOK(); - } - - IStructuredDocument sdoc = smodel.getStructuredDocument(); - if (sdoc != null) { - // Portability note: all the structured document implementation is - // under wst.sse.core.internal.provisional so we can expect it to change in - // a distant future if they start cleaning their codebase, however unlikely - // that is. - - int selStart = mSelectionStart; - IStructuredDocumentRegion region = - sdoc.getRegionAtCharacterOffset(selStart); - if (region != null && - DOMRegionContext.XML_TAG_NAME.equals(region.getType())) { - // Find if any sub-region representing an attribute contains the - // selection. If it does, returns the name of the attribute in - // currAttrName and returns the value in the field mTokenString. - currAttrName = findSelectionInRegion(region, selStart); - - if (mTokenString == null) { - status.addFatalError( - "The selection is not inside an actual XML attribute value."); - } - } - } - - if (mTokenString != null && node != null && currAttrName != null) { - - // Validate that the attribute accepts a string reference. - // This sets mTokenString to null by side-effect when it fails and - // adds a fatal error to the status as needed. - validateSelectedAttribute(editor, node, currAttrName, status); - - } else { - // We shouldn't get here: we're missing one of the token string, the node - // or the attribute name. All of them have been checked earlier so don't - // set any specific error. - mTokenString = null; - } - } - } catch (Throwable t) { - // Since we use some internal APIs, use a broad catch-all to report any - // unexpected issue rather than crash the whole refactoring. - status.addFatalError( - String.format("XML parsing error: %1$s", t.getMessage())); - } finally { - if (smodel != null) { - smodel.releaseFromRead(); - } - } - - } finally { - monitor.worked(1); - } - - return status.isOK(); - } - - /** - * The region gives us the textual representation of the XML element - * where the selection starts, split using sub-regions. We now just - * need to iterate through the sub-regions to find which one - * contains the actual selection. We're interested in an attribute - * value however when we find one we want to memorize the attribute - * name that was defined just before. - * - * @return When the cursor is on a valid attribute name or value, returns the string of - * attribute name. As a side-effect, returns the value of the attribute in {@link #mTokenString} - */ - private String findSelectionInRegion(IStructuredDocumentRegion region, int selStart) { - - String currAttrName = null; - - int startInRegion = selStart - region.getStartOffset(); - - int nb = region.getNumberOfRegions(); - ITextRegionList list = region.getRegions(); - String currAttrValue = 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)) { - currAttrName = region.getText(subRegion); - - // I like to select the attribute definition and invoke - // the extract string wizard. So if the selection is on - // the attribute name part, find the value that is just - // after and use it as if it were the selection. - - if (subRegion.getStart() <= startInRegion && - startInRegion < subRegion.getTextEnd()) { - // A well-formed attribute is composed of a name, - // an equal sign and the value. There can't be any space - // in between, which makes the parsing a lot easier. - if (i <= nb - 3 && - DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS.equals( - list.get(i + 1).getType())) { - subRegion = list.get(i + 2); - type = subRegion.getType(); - if (DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE.equals( - type)) { - currAttrValue = region.getText(subRegion); - } - } - } - - } else if (subRegion.getStart() <= startInRegion && - startInRegion < subRegion.getTextEnd() && - DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE.equals(type)) { - currAttrValue = region.getText(subRegion); - } - - if (currAttrValue != null) { - // We found the value. Only accept it if not empty - // and if we found an attribute name before. - String text = currAttrValue; - - // The attribute value contains XML quotes. Remove them. - text = unquoteAttrValue(text); - if (text.length() > 0 && currAttrName != null) { - // Setting mTokenString to non-null marks the fact we - // accept this attribute. - mTokenString = text; - } - - break; - } - } - - return currAttrName; - } - - /** - * Attribute values found as text for {@link DOMRegionContext#XML_TAG_ATTRIBUTE_VALUE} - * contain XML quotes. This removes the quotes (either single or double quotes). - * - * @param attrValue The attribute value, as extracted by - * {@link IStructuredDocumentRegion#getText(ITextRegion)}. - * Must not be null. - * @return The attribute value, without quotes. Whitespace is not trimmed, if any. - * String may be empty, but not null. - */ - static String unquoteAttrValue(String attrValue) { - int len = attrValue.length(); - int len1 = len - 1; - if (len >= 2 && - attrValue.charAt(0) == '"' && - attrValue.charAt(len1) == '"') { - attrValue = attrValue.substring(1, len1); - } else if (len >= 2 && - attrValue.charAt(0) == '\'' && - attrValue.charAt(len1) == '\'') { - attrValue = attrValue.substring(1, len1); - } - - return attrValue; - } - - /** - * Validates that the attribute accepts a string reference. - * This sets mTokenString to null by side-effect when it fails and - * adds a fatal error to the status as needed. - */ - private void validateSelectedAttribute(AndroidXmlEditor editor, Node node, - String attrName, RefactoringStatus status) { - UiElementNode rootUiNode = editor.getUiRootNode(); - UiElementNode currentUiNode = - rootUiNode == null ? null : rootUiNode.findXmlNode(node); - ReferenceAttributeDescriptor attrDesc = null; - - if (currentUiNode != null) { - // remove any namespace prefix from the attribute name - String name = attrName; - int pos = name.indexOf(':'); - if (pos > 0 && pos < name.length() - 1) { - name = name.substring(pos + 1); - } - - for (UiAttributeNode attrNode : currentUiNode.getAllUiAttributes()) { - if (attrNode.getDescriptor().getXmlLocalName().equals(name)) { - AttributeDescriptor desc = attrNode.getDescriptor(); - if (desc instanceof ReferenceAttributeDescriptor) { - attrDesc = (ReferenceAttributeDescriptor) desc; - } - break; - } - } - } - - // The attribute descriptor is a resource reference. It must either accept - // of any resource type or specifically accept string types. - if (attrDesc != null && - (attrDesc.getResourceType() == null || - attrDesc.getResourceType() == ResourceType.STRING)) { - // We have one more check to do: is the current string value already - // an Android XML string reference? If so, we can't edit it. - if (mTokenString != null && mTokenString.startsWith("@")) { //$NON-NLS-1$ - int pos1 = 0; - if (mTokenString.length() > 1 && mTokenString.charAt(1) == '+') { - pos1++; - } - int pos2 = mTokenString.indexOf('/'); - if (pos2 > pos1) { - String kind = mTokenString.substring(pos1 + 1, pos2); - if (ResourceType.STRING.getName().equals(kind)) { - mTokenString = null; - status.addFatalError(String.format( - "The attribute %1$s already contains a %2$s reference.", - attrName, - kind)); - } - } - } - - if (mTokenString != null) { - // We're done with all our checks. mTokenString contains the - // current attribute value. We don't memorize the region nor the - // attribute, however we memorize the textual attribute name so - // that we can offer replacement for all its occurrences. - mXmlAttributeName = attrName; - } - - } else { - mTokenString = null; - status.addFatalError(String.format( - "The attribute %1$s does not accept a string reference.", - attrName)); - } - } - - /** - * Tests from org.eclipse.jdt.internal.corext.refactoringChecks#validateEdit() - * Might not be useful. - * - * On success, advance the monitor by 2. - * - * @return False if caller should abort, true if caller should continue. - */ - private boolean checkSourceFile(IFile file, - RefactoringStatus status, - IProgressMonitor monitor) { - // check whether the source file is in sync - if (!file.isSynchronized(IResource.DEPTH_ZERO)) { - status.addFatalError("The file is not synchronized. Please save it first."); - return false; - } - monitor.worked(1); - - // make sure we can write to it. - ResourceAttributes resAttr = file.getResourceAttributes(); - if (resAttr == null || resAttr.isReadOnly()) { - status.addFatalError("The file is read-only, please make it writeable first."); - return false; - } - monitor.worked(1); - - return true; - } - - /** - * Step 2 of 3 of the refactoring: - * Check the conditions once the user filled values in the refactoring wizard, - * then prepare the changes to be applied. - * <p/> - * In this case, most of the sanity checks are done by the wizard so essentially this - * should only be called if the wizard positively validated the user input. - * - * Here we do check that the target resource XML file either does not exists or - * is not read-only. - * - * @see org.eclipse.ltk.core.refactoring.Refactoring#checkFinalConditions(IProgressMonitor) - * - * @throws CoreException - */ - @Override - public RefactoringStatus checkFinalConditions(IProgressMonitor monitor) - throws CoreException, OperationCanceledException { - RefactoringStatus status = new RefactoringStatus(); - - try { - monitor.beginTask("Checking post-conditions...", 5); - - if (mXmlStringId == null || mXmlStringId.length() <= 0) { - // this is not supposed to happen - status.addFatalError("Missing replacement string ID"); - } else if (mTargetXmlFileWsPath == null || mTargetXmlFileWsPath.length() <= 0) { - // this is not supposed to happen - status.addFatalError("Missing target xml file path"); - } - monitor.worked(1); - - // Either that resource must not exist or it must be a writable file. - IResource targetXml = getTargetXmlResource(mTargetXmlFileWsPath); - if (targetXml != null) { - if (targetXml.getType() != IResource.FILE) { - status.addFatalError( - String.format("XML file '%1$s' is not a file.", mTargetXmlFileWsPath)); - } else { - ResourceAttributes attr = targetXml.getResourceAttributes(); - if (attr != null && attr.isReadOnly()) { - status.addFatalError( - String.format("XML file '%1$s' is read-only.", - mTargetXmlFileWsPath)); - } - } - } - monitor.worked(1); - - if (status.hasError()) { - return status; - } - - mChanges = new ArrayList<Change>(); - - - // Prepare the change to create/edit the String ID in the res/values XML file. - if (!mXmlStringValue.equals( - mXmlHelper.valueOfStringId(mProject, mTargetXmlFileWsPath, mXmlStringId))) { - // We actually change it only if the ID doesn't exist yet or has a different value - Change change = createXmlChanges((IFile) targetXml, mXmlStringId, mXmlStringValue, - status, SubMonitor.convert(monitor, 1)); - if (change != null) { - mChanges.add(change); - } - } - - if (status.hasError()) { - return status; - } - - if (mMode == Mode.EDIT_SOURCE) { - List<Change> changes = null; - if (mXmlAttributeName != null) { - // Prepare the change to the Android resource XML file - changes = computeXmlSourceChanges(mFile, - mXmlStringId, - mTokenString, - mXmlAttributeName, - true, // allConfigurations - status, - monitor); - - } else if (mUnit != null) { - // Prepare the change to the Java compilation unit - changes = computeJavaChanges(mUnit, mXmlStringId, mTokenString, - status, SubMonitor.convert(monitor, 1)); - } - if (changes != null) { - mChanges.addAll(changes); - } - } - - if (mReplaceAllJava) { - String currentIdentifier = mUnit != null ? mUnit.getHandleIdentifier() : ""; //$NON-NLS-1$ - - SubMonitor submon = SubMonitor.convert(monitor, 1); - for (ICompilationUnit unit : findAllJavaUnits()) { - // Only process Java compilation units that exist, are not derived - // and are not read-only. - if (unit == null || !unit.exists()) { - continue; - } - IResource resource = unit.getResource(); - if (resource == null || resource.isDerived()) { - continue; - } - - // Ensure that we don't process the current compilation unit (processed - // as mUnit above) twice - if (currentIdentifier.equals(unit.getHandleIdentifier())) { - continue; - } - - ResourceAttributes attrs = resource.getResourceAttributes(); - if (attrs != null && attrs.isReadOnly()) { - continue; - } - - List<Change> changes = computeJavaChanges( - unit, mXmlStringId, mTokenString, - status, SubMonitor.convert(submon, 1)); - if (changes != null) { - mChanges.addAll(changes); - } - } - } - - if (mReplaceAllXml) { - SubMonitor submon = SubMonitor.convert(monitor, 1); - for (IFile xmlFile : findAllResXmlFiles()) { - if (xmlFile != null) { - List<Change> changes = computeXmlSourceChanges(xmlFile, - mXmlStringId, - mTokenString, - mXmlAttributeName, - false, // allConfigurations - status, - SubMonitor.convert(submon, 1)); - if (changes != null) { - mChanges.addAll(changes); - } - } - } - } - - monitor.worked(1); - } finally { - monitor.done(); - } - - return status; - } - - // --- XML changes --- - - /** - * Returns a foreach-compatible iterator over all XML files in the project's - * /res folder, excluding the target XML file (the one where we'll write/edit - * the string id). - */ - private Iterable<IFile> findAllResXmlFiles() { - return new Iterable<IFile>() { - @Override - public Iterator<IFile> iterator() { - return new Iterator<IFile>() { - final Queue<IFile> mFiles = new LinkedList<IFile>(); - final Queue<IResource> mFolders = new LinkedList<IResource>(); - IPath mFilterPath1 = null; - IPath mFilterPath2 = null; - { - // Filter out the XML file where we'll be writing the XML string id. - IResource filterRes = mProject.findMember(mTargetXmlFileWsPath); - if (filterRes != null) { - mFilterPath1 = filterRes.getFullPath(); - } - // Filter out the XML source file, if any (e.g. typically a layout) - if (mFile != null) { - mFilterPath2 = mFile.getFullPath(); - } - - // We want to process the manifest - IResource man = mProject.findMember("AndroidManifest.xml"); // TODO find a constant - if (man.exists() && man instanceof IFile && !man.equals(mFile)) { - mFiles.add((IFile) man); - } - - // Add all /res folders (technically we don't need to process /res/values - // XML files that contain resources/string elements, but it's easier to - // not filter them out.) - IFolder f = mProject.getFolder(AdtConstants.WS_RESOURCES); - if (f.exists()) { - try { - mFolders.addAll( - Arrays.asList(f.members(IContainer.EXCLUDE_DERIVED))); - } catch (CoreException e) { - // pass - } - } - } - - @Override - public boolean hasNext() { - if (!mFiles.isEmpty()) { - return true; - } - - while (!mFolders.isEmpty()) { - IResource res = mFolders.poll(); - if (res.exists() && res instanceof IFolder) { - IFolder f = (IFolder) res; - try { - getFileList(f); - if (!mFiles.isEmpty()) { - return true; - } - } catch (CoreException e) { - // pass - } - } - } - return false; - } - - private void getFileList(IFolder folder) throws CoreException { - for (IResource res : folder.members(IContainer.EXCLUDE_DERIVED)) { - // Only accept file resources which are not derived and actually exist - if (res.exists() && !res.isDerived() && res instanceof IFile) { - IFile file = (IFile) res; - // Must have an XML extension - if (SdkConstants.EXT_XML.equals(file.getFileExtension())) { - IPath p = file.getFullPath(); - // And not be either paths we want to filter out - if ((mFilterPath1 != null && mFilterPath1.equals(p)) || - (mFilterPath2 != null && mFilterPath2.equals(p))) { - continue; - } - mFiles.add(file); - } - } - } - } - - @Override - public IFile next() { - IFile file = mFiles.poll(); - hasNext(); - return file; - } - - @Override - public void remove() { - throw new UnsupportedOperationException( - "This iterator does not support removal"); //$NON-NLS-1$ - } - }; - } - }; - } - - /** - * Internal helper that actually prepares the {@link Change} that adds the given - * ID to the given XML File. - * <p/> - * This does not actually modify the file. - * - * @param targetXml The file resource to modify. - * @param xmlStringId The new ID to insert. - * @param tokenString The old string, which will be the value in the XML string. - * @return A new {@link TextEdit} that describes how to change the file. - */ - private Change createXmlChanges(IFile targetXml, - String xmlStringId, - String tokenString, - RefactoringStatus status, - SubMonitor monitor) { - - TextFileChange xmlChange = new TextFileChange(getName(), targetXml); - xmlChange.setTextType(SdkConstants.EXT_XML); - - String error = ""; //$NON-NLS-1$ - TextEdit edit = null; - TextEditGroup editGroup = null; - - try { - if (!targetXml.exists()) { - // Kludge: use targetXml==null as a signal this is a new file being created - targetXml = null; - } - - edit = createXmlReplaceEdit(targetXml, xmlStringId, tokenString, status, - SubMonitor.convert(monitor, 1)); - } catch (IOException e) { - error = e.toString(); - } catch (CoreException e) { - // Failed to read file. Ignore. Will handle error below. - error = e.toString(); - } - - if (edit == null) { - status.addFatalError(String.format("Failed to modify file %1$s%2$s", - targetXml == null ? "" : targetXml.getFullPath(), //$NON-NLS-1$ - error == null ? "" : ": " + error)); //$NON-NLS-1$ - return null; - } - - editGroup = new TextEditGroup(targetXml == null ? "Create <string> in new XML file" - : "Insert <string> in XML file", - edit); - - xmlChange.setEdit(edit); - // The TextEditChangeGroup let the user toggle this change on and off later. - xmlChange.addTextEditChangeGroup(new TextEditChangeGroup(xmlChange, editGroup)); - - monitor.worked(1); - return xmlChange; - } - - /** - * Scan the XML file to find the best place where to insert the new string element. - * <p/> - * This handles a variety of cases, including replacing existing ids in place, - * adding the top resources element if missing and the XML PI if not present. - * It tries to preserve indentation when adding new elements at the end of an existing XML. - * - * @param file The XML file to modify, that must be present in the workspace. - * Pass null to create a change for a new file that doesn't exist yet. - * @param xmlStringId The new ID to insert. - * @param tokenString The old string, which will be the value in the XML string. - * @param status The in-out refactoring status. Used to log a more detailed error if the - * XML has a top element that is not a resources element. - * @param monitor A monitor to track progress. - * @return A new {@link TextEdit} for either a replace or an insert operation, or null in case - * of error. - * @throws CoreException - if the file's contents or description can not be read. - * @throws IOException - if the file's contents can not be read or its detected encoding does - * not support its contents. - */ - private TextEdit createXmlReplaceEdit(IFile file, - String xmlStringId, - String tokenString, - RefactoringStatus status, - SubMonitor monitor) - throws IOException, CoreException { - - IModelManager modelMan = StructuredModelManager.getModelManager(); - - final String NODE_RESOURCES = SdkConstants.TAG_RESOURCES; - final String NODE_STRING = SdkConstants.TAG_STRING; - final String ATTR_NAME = SdkConstants.ATTR_NAME; - - - // Scan the source to find the best insertion point. - - // 1- The most common case we need to handle is the one of inserting at the end - // of a valid XML document, respecting the whitespace last used. - // - // Ideally we have this structure: - // <xml ...> - // <resource> - // ...ws1...<string>blah</string>...ws2... - // </resource> - // - // where ws1 and ws2 are the whitespace respectively before and after the last element - // just before the closing </resource>. - // In this case we want to generate the new string just before ws2...</resource> with - // the same whitespace as ws1. - // - // 2- Another expected case is there's already an existing string which "name" attribute - // equals to xmlStringId and we just want to replace its value. - // - // Other cases we need to handle: - // 3- There is no element at all -> create a full new <resource>+<string> content. - // 4- There is <resource/>, that is the tag is not opened. This can be handled as the - // previous case, generating full content but also replacing <resource/>. - // 5- There is a top element that is not <resource>. That's a fatal error and we abort. - - IStructuredModel smodel = null; - - // Single and double quotes must be escaped in the <string>value</string> declaration - tokenString = ValueXmlHelper.escapeResourceString(tokenString); - - try { - IStructuredDocument sdoc = null; - boolean checkTopElement = true; - boolean replaceStringContent = false; - boolean hasPiXml = false; - int newResStart = 0; - int newResLength = 0; - String lineSep = "\n"; //$NON-NLS-1$ - - if (file != null) { - smodel = modelMan.getExistingModelForRead(file); - if (smodel != null) { - sdoc = smodel.getStructuredDocument(); - } else if (smodel == null) { - // The model is not currently open. - if (file.exists()) { - sdoc = modelMan.createStructuredDocumentFor(file); - } else { - sdoc = modelMan.createNewStructuredDocumentFor(file); - } - } - } - - if (sdoc == null && file != null) { - // Get a document matching the actual saved file - sdoc = modelMan.createStructuredDocumentFor(file); - } - - if (sdoc != null) { - String wsBefore = ""; //$NON-NLS-1$ - String lastWs = null; - - lineSep = sdoc.getLineDelimiter(); - if (lineSep == null || lineSep.length() == 0) { - // That wasn't too useful, let's go back to a reasonable default - lineSep = "\n"; //$NON-NLS-1$ - } - - for (IStructuredDocumentRegion regions : sdoc.getStructuredDocumentRegions()) { - String type = regions.getType(); - - if (DOMRegionContext.XML_CONTENT.equals(type)) { - - if (replaceStringContent) { - // Generate a replacement for a <string> value matching the string ID. - return new ReplaceEdit( - regions.getStartOffset(), regions.getLength(), tokenString); - } - - // Otherwise capture what should be whitespace content - lastWs = regions.getFullText(); - continue; - - } else if (DOMRegionContext.XML_PI_OPEN.equals(type) && !hasPiXml) { - - int nb = regions.getNumberOfRegions(); - ITextRegionList list = regions.getRegions(); - for (int i = 0; i < nb; i++) { - ITextRegion region = list.get(i); - type = region.getType(); - if (DOMRegionContext.XML_TAG_NAME.equals(type)) { - String name = regions.getText(region); - if ("xml".equals(name)) { //$NON-NLS-1$ - hasPiXml = true; - break; - } - } - } - continue; - - } else if (!DOMRegionContext.XML_TAG_NAME.equals(type)) { - // ignore things which are not a tag nor text content (such as comments) - continue; - } - - int nb = regions.getNumberOfRegions(); - ITextRegionList list = regions.getRegions(); - - String name = null; - String attrName = null; - String attrValue = null; - boolean isEmptyTag = false; - boolean isCloseTag = false; - - for (int i = 0; i < nb; i++) { - ITextRegion region = list.get(i); - type = region.getType(); - - if (DOMRegionContext.XML_END_TAG_OPEN.equals(type)) { - isCloseTag = true; - } else if (DOMRegionContext.XML_EMPTY_TAG_CLOSE.equals(type)) { - isEmptyTag = true; - } else if (DOMRegionContext.XML_TAG_NAME.equals(type)) { - name = regions.getText(region); - } else if (DOMRegionContext.XML_TAG_ATTRIBUTE_NAME.equals(type) && - NODE_STRING.equals(name)) { - // Record the attribute names into a <string> element. - attrName = regions.getText(region); - } else if (DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE.equals(type) && - ATTR_NAME.equals(attrName)) { - // Record the value of a <string name=...> attribute - attrValue = regions.getText(region); - - if (attrValue != null && - unquoteAttrValue(attrValue).equals(xmlStringId)) { - // We found a <string name=> matching the string ID to replace. - // We'll generate a replacement when we process the string value - // (that is the next XML_CONTENT region.) - replaceStringContent = true; - } - } - } - - if (checkTopElement) { - // Check the top element has a resource name - checkTopElement = false; - if (!NODE_RESOURCES.equals(name)) { - status.addFatalError( - String.format("XML file lacks a <resource> tag: %1$s", - mTargetXmlFileWsPath)); - return null; - - } - - if (isEmptyTag) { - // The top element is an empty "<resource/>" tag. We need to do - // a full new resource+string replacement. - newResStart = regions.getStartOffset(); - newResLength = regions.getLength(); - } - } - - if (NODE_RESOURCES.equals(name)) { - if (isCloseTag) { - // We found the </resource> tag and we want - // to insert just before this one. - - StringBuilder content = new StringBuilder(); - content.append(wsBefore) - .append("<string name=\"") //$NON-NLS-1$ - .append(xmlStringId) - .append("\">") //$NON-NLS-1$ - .append(tokenString) - .append("</string>"); //$NON-NLS-1$ - - // Backup to insert before the whitespace preceding </resource> - IStructuredDocumentRegion insertBeforeReg = regions; - while (true) { - IStructuredDocumentRegion previous = insertBeforeReg.getPrevious(); - if (previous != null && - DOMRegionContext.XML_CONTENT.equals(previous.getType()) && - previous.getText().trim().length() == 0) { - insertBeforeReg = previous; - } else { - break; - } - } - if (insertBeforeReg == regions) { - // If we have not found any whitespace before </resources>, - // at least add a line separator. - content.append(lineSep); - } - - return new InsertEdit(insertBeforeReg.getStartOffset(), - content.toString()); - } - } else { - // For any other tag than <resource>, capture whitespace before and after. - if (!isCloseTag) { - wsBefore = lastWs; - } - } - } - } - - // We reach here either because there's no XML content at all or because - // there's an empty <resource/>. - // Provide a full new resource+string replacement. - StringBuilder content = new StringBuilder(); - if (!hasPiXml) { - content.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); //$NON-NLS-1$ - content.append(lineSep); - } else if (newResLength == 0 && sdoc != null) { - // If inserting at the end, check if the last region is some whitespace. - // If there's no newline, insert one ourselves. - IStructuredDocumentRegion lastReg = sdoc.getLastStructuredDocumentRegion(); - if (lastReg != null && lastReg.getText().indexOf('\n') == -1) { - content.append('\n'); - } - } - - // FIXME how to access formatting preferences to generate the proper indentation? - content.append("<resources>").append(lineSep); //$NON-NLS-1$ - content.append(" <string name=\"") //$NON-NLS-1$ - .append(xmlStringId) - .append("\">") //$NON-NLS-1$ - .append(tokenString) - .append("</string>") //$NON-NLS-1$ - .append(lineSep); - content.append("</resources>").append(lineSep); //$NON-NLS-1$ - - if (newResLength > 0) { - // Replace existing piece - return new ReplaceEdit(newResStart, newResLength, content.toString()); - } else { - // Insert at the end. - int offset = sdoc == null ? 0 : sdoc.getLength(); - return new InsertEdit(offset, content.toString()); - } - } catch (IOException e) { - // This is expected to happen and is properly reported to the UI. - throw e; - } catch (CoreException e) { - // This is expected to happen and is properly reported to the UI. - throw e; - } catch (Throwable t) { - // Since we use some internal APIs, use a broad catch-all to report any - // unexpected issue rather than crash the whole refactoring. - status.addFatalError( - String.format("XML replace error: %1$s", t.getMessage())); - } finally { - if (smodel != null) { - smodel.releaseFromRead(); - } - } - - return null; - } - - /** - * Computes the changes to be made to the source Android XML file and - * returns a list of {@link Change}. - * <p/> - * This function scans an XML file, looking for an attribute value equals to - * <code>tokenString</code>. If non null, <code>xmlAttrName</code> limit the search - * to only attributes that have that name. - * If found, a change is made to replace each occurrence of <code>tokenString</code> - * by a new "@string/..." using the new <code>xmlStringId</code>. - * - * @param sourceFile The file to process. - * A status error will be generated if it does not exists. - * Must not be null. - * @param tokenString The string to find. Must not be null or empty. - * @param xmlAttrName Optional attribute name to limit the search. Can be null. - * @param allConfigurations True if this function should can all XML files with the same - * name and the same resource type folder but with different configurations. - * @param status Status used to report fatal errors. - * @param monitor Used to log progress. - */ - private List<Change> computeXmlSourceChanges(IFile sourceFile, - String xmlStringId, - String tokenString, - String xmlAttrName, - boolean allConfigurations, - RefactoringStatus status, - IProgressMonitor monitor) { - - if (!sourceFile.exists()) { - status.addFatalError(String.format("XML file '%1$s' does not exist.", - sourceFile.getFullPath().toOSString())); - return null; - } - - // We shouldn't be trying to replace a null or empty string. - assert tokenString != null && tokenString.length() > 0; - if (tokenString == null || tokenString.length() == 0) { - return null; - } - - // Note: initially this method was only processing files using a pattern - // /project/res/<type>-<configuration>/<filename.xml> - // However the last version made that more generic to be able to process any XML - // files. We should probably revisit and simplify this later. - HashSet<IFile> files = new HashSet<IFile>(); - files.add(sourceFile); - - if (allConfigurations && SdkConstants.EXT_XML.equals(sourceFile.getFileExtension())) { - IPath path = sourceFile.getFullPath(); - if (path.segmentCount() == 4 && path.segment(1).equals(SdkConstants.FD_RESOURCES)) { - IProject project = sourceFile.getProject(); - String filename = path.segment(3); - String initialTypeName = path.segment(2); - ResourceFolderType type = ResourceFolderType.getFolderType(initialTypeName); - - IContainer res = sourceFile.getParent().getParent(); - if (type != null && res != null && res.getType() == IResource.FOLDER) { - try { - for (IResource r : res.members()) { - if (r != null && r.getType() == IResource.FOLDER) { - String name = r.getName(); - // Skip the initial folder name, it's already in the list. - if (!name.equals(initialTypeName)) { - // Only accept the same folder type (e.g. layout-*) - ResourceFolderType t = - ResourceFolderType.getFolderType(name); - if (type.equals(t)) { - // recompute the path - IPath p = res.getProjectRelativePath().append(name). - append(filename); - IResource f = project.findMember(p); - if (f != null && f instanceof IFile) { - files.add((IFile) f); - } - } - } - } - } - } catch (CoreException e) { - // Ignore. - } - } - } - } - - SubMonitor subMonitor = SubMonitor.convert(monitor, Math.min(1, files.size())); - - ArrayList<Change> changes = new ArrayList<Change>(); - - // Portability note: getModelManager is part of wst.sse.core however the - // interface returned is part of wst.sse.core.internal.provisional so we can - // expect it to change in a distant future if they start cleaning their codebase, - // however unlikely that is. - IModelManager modelManager = StructuredModelManager.getModelManager(); - - for (IFile file : files) { - - IStructuredModel smodel = null; - MultiTextEdit multiEdit = null; - TextFileChange xmlChange = null; - ArrayList<TextEditGroup> editGroups = null; - - try { - IStructuredDocument sdoc = null; - - smodel = modelManager.getExistingModelForRead(file); - if (smodel != null) { - sdoc = smodel.getStructuredDocument(); - } else if (smodel == null) { - // The model is not currently open. - if (file.exists()) { - sdoc = modelManager.createStructuredDocumentFor(file); - } else { - sdoc = modelManager.createNewStructuredDocumentFor(file); - } - } - - if (sdoc == null) { - status.addFatalError("XML structured document not found"); //$NON-NLS-1$ - continue; - } - - multiEdit = new MultiTextEdit(); - editGroups = new ArrayList<TextEditGroup>(); - xmlChange = new TextFileChange(getName(), file); - xmlChange.setTextType("xml"); //$NON-NLS-1$ - - String quotedReplacement = quotedAttrValue(STRING_PREFIX + xmlStringId); - - // Prepare the change set - for (IStructuredDocumentRegion regions : sdoc.getStructuredDocumentRegions()) { - // Only look at XML "top regions" - if (!DOMRegionContext.XML_TAG_NAME.equals(regions.getType())) { - continue; - } - - int nb = regions.getNumberOfRegions(); - ITextRegionList list = regions.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 = regions.getText(subRegion); - - } else if (DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE.equals(type)) { - // Check this is the attribute and the original string - String text = regions.getText(subRegion); - - // Remove " or ' quoting present in the attribute value - text = unquoteAttrValue(text); - - if (tokenString.equals(text) && - (xmlAttrName == null || xmlAttrName.equals(lastAttrName))) { - - // Found an occurrence. Create a change for it. - TextEdit edit = new ReplaceEdit( - regions.getStartOffset() + subRegion.getStart(), - subRegion.getTextLength(), - quotedReplacement); - TextEditGroup editGroup = new TextEditGroup( - "Replace attribute string by ID", - edit); - - multiEdit.addChild(edit); - editGroups.add(editGroup); - } - } - } - } - } catch (Throwable t) { - // Since we use some internal APIs, use a broad catch-all to report any - // unexpected issue rather than crash the whole refactoring. - status.addFatalError( - String.format("XML refactoring error: %1$s", t.getMessage())); - } finally { - if (smodel != null) { - smodel.releaseFromRead(); - } - - if (multiEdit != null && - xmlChange != null && - editGroups != null && - multiEdit.hasChildren()) { - xmlChange.setEdit(multiEdit); - for (TextEditGroup group : editGroups) { - xmlChange.addTextEditChangeGroup( - new TextEditChangeGroup(xmlChange, group)); - } - changes.add(xmlChange); - } - subMonitor.worked(1); - } - } // for files - - if (changes.size() > 0) { - return changes; - } - return null; - } - - /** - * Returns a quoted attribute value suitable to be placed after an attributeName= - * statement in an XML stream. - * - * According to http://www.w3.org/TR/2008/REC-xml-20081126/#NT-AttValue - * the attribute value can be either quoted using ' or " and the corresponding - * entities ' or " must be used inside. - */ - private String quotedAttrValue(String attrValue) { - if (attrValue.indexOf('"') == -1) { - // no double-quotes inside, use double-quotes around. - return '"' + attrValue + '"'; - } - if (attrValue.indexOf('\'') == -1) { - // no single-quotes inside, use single-quotes around. - return '\'' + attrValue + '\''; - } - // If we get here, there's a mix. Opt for double-quote around and replace - // inner double-quotes. - attrValue = attrValue.replace("\"", QUOT_ENTITY); //$NON-NLS-1$ - return '"' + attrValue + '"'; - } - - // --- Java changes --- - - /** - * Returns a foreach compatible iterator over all ICompilationUnit in the project. - */ - private Iterable<ICompilationUnit> findAllJavaUnits() { - final IJavaProject javaProject = JavaCore.create(mProject); - - return new Iterable<ICompilationUnit>() { - @Override - public Iterator<ICompilationUnit> iterator() { - return new Iterator<ICompilationUnit>() { - final Queue<ICompilationUnit> mUnits = new LinkedList<ICompilationUnit>(); - final Queue<IPackageFragment> mFragments = new LinkedList<IPackageFragment>(); - { - try { - IPackageFragment[] tmpFrags = javaProject.getPackageFragments(); - if (tmpFrags != null && tmpFrags.length > 0) { - mFragments.addAll(Arrays.asList(tmpFrags)); - } - } catch (JavaModelException e) { - // pass - } - } - - @Override - public boolean hasNext() { - if (!mUnits.isEmpty()) { - return true; - } - - while (!mFragments.isEmpty()) { - try { - IPackageFragment fragment = mFragments.poll(); - if (fragment.getKind() == IPackageFragmentRoot.K_SOURCE) { - ICompilationUnit[] tmpUnits = fragment.getCompilationUnits(); - if (tmpUnits != null && tmpUnits.length > 0) { - mUnits.addAll(Arrays.asList(tmpUnits)); - return true; - } - } - } catch (JavaModelException e) { - // pass - } - } - return false; - } - - @Override - public ICompilationUnit next() { - ICompilationUnit unit = mUnits.poll(); - hasNext(); - return unit; - } - - @Override - public void remove() { - throw new UnsupportedOperationException( - "This iterator does not support removal"); //$NON-NLS-1$ - } - }; - } - }; - } - - /** - * Computes the changes to be made to Java file(s) and returns a list of {@link Change}. - * <p/> - * This function scans a Java compilation unit using {@link ReplaceStringsVisitor}, looking - * for a string literal equals to <code>tokenString</code>. - * If found, a change is made to replace each occurrence of <code>tokenString</code> by - * a piece of Java code that somehow accesses R.string.<code>xmlStringId</code>. - * - * @param unit The compilated unit to process. Must not be null. - * @param tokenString The string to find. Must not be null or empty. - * @param status Status used to report fatal errors. - * @param monitor Used to log progress. - */ - private List<Change> computeJavaChanges(ICompilationUnit unit, - String xmlStringId, - String tokenString, - RefactoringStatus status, - SubMonitor monitor) { - - // We shouldn't be trying to replace a null or empty string. - assert tokenString != null && tokenString.length() > 0; - if (tokenString == null || tokenString.length() == 0) { - return null; - } - - // Get the Android package name from the Android Manifest. We need it to create - // the FQCN of the R class. - String packageName = null; - String error = null; - IResource manifestFile = mProject.findMember(SdkConstants.FN_ANDROID_MANIFEST_XML); - if (manifestFile == null || manifestFile.getType() != IResource.FILE) { - error = "File not found"; - } else { - ManifestData manifestData = AndroidManifestHelper.parseForData((IFile) manifestFile); - if (manifestData == null) { - error = "Invalid content"; - } else { - packageName = manifestData.getPackage(); - if (packageName == null) { - error = "Missing package definition"; - } - } - } - - if (error != null) { - status.addFatalError( - String.format("Failed to parse file %1$s: %2$s.", - manifestFile == null ? "" : manifestFile.getFullPath(), //$NON-NLS-1$ - error)); - return null; - } - - // Right now the changes array will contain one TextFileChange at most. - ArrayList<Change> changes = new ArrayList<Change>(); - - // This is the unit that will be modified. - TextFileChange change = new TextFileChange(getName(), (IFile) unit.getResource()); - change.setTextType("java"); //$NON-NLS-1$ - - // Create an AST for this compilation unit - ASTParser parser = ASTParser.newParser(AST.JLS3); - parser.setProject(unit.getJavaProject()); - parser.setSource(unit); - parser.setResolveBindings(true); - ASTNode node = parser.createAST(monitor.newChild(1)); - - // The ASTNode must be a CompilationUnit, by design - if (!(node instanceof CompilationUnit)) { - status.addFatalError(String.format("Internal error: ASTNode class %s", //$NON-NLS-1$ - node.getClass())); - return null; - } - - // ImportRewrite will allow us to add the new type to the imports and will resolve - // what the Java source must reference, e.g. the FQCN or just the simple name. - ImportRewrite importRewrite = ImportRewrite.create((CompilationUnit) node, true); - String Rqualifier = packageName + ".R"; //$NON-NLS-1$ - Rqualifier = importRewrite.addImport(Rqualifier); - - // Rewrite the AST itself via an ASTVisitor - AST ast = node.getAST(); - ASTRewrite astRewrite = ASTRewrite.create(ast); - ArrayList<TextEditGroup> astEditGroups = new ArrayList<TextEditGroup>(); - ReplaceStringsVisitor visitor = new ReplaceStringsVisitor( - ast, astRewrite, astEditGroups, - tokenString, Rqualifier, xmlStringId); - node.accept(visitor); - - // Finally prepare the change set - try { - MultiTextEdit edit = new MultiTextEdit(); - - // Create the edit to change the imports, only if anything changed - TextEdit subEdit = importRewrite.rewriteImports(monitor.newChild(1)); - if (subEdit.hasChildren()) { - edit.addChild(subEdit); - } - - // Create the edit to change the Java source, only if anything changed - subEdit = astRewrite.rewriteAST(); - if (subEdit.hasChildren()) { - edit.addChild(subEdit); - } - - // Only create a change set if any edit was collected - if (edit.hasChildren()) { - change.setEdit(edit); - - // Create TextEditChangeGroups which let the user turn changes on or off - // individually. This must be done after the change.setEdit() call above. - for (TextEditGroup editGroup : astEditGroups) { - TextEditChangeGroup group = new TextEditChangeGroup(change, editGroup); - if (editGroup instanceof EnabledTextEditGroup) { - group.setEnabled(((EnabledTextEditGroup) editGroup).isEnabled()); - } - change.addTextEditChangeGroup(group); - } - - changes.add(change); - } - - monitor.worked(1); - - if (changes.size() > 0) { - return changes; - } - - } catch (CoreException e) { - // ImportRewrite.rewriteImports failed. - status.addFatalError(e.getMessage()); - } - return null; - } - - // ---- - - /** - * Step 3 of 3 of the refactoring: returns the {@link Change} that will be able to do the - * work and creates a descriptor that can be used to replay that refactoring later. - * - * @see org.eclipse.ltk.core.refactoring.Refactoring#createChange(org.eclipse.core.runtime.IProgressMonitor) - * - * @throws CoreException - */ - @Override - public Change createChange(IProgressMonitor monitor) - throws CoreException, OperationCanceledException { - - try { - monitor.beginTask("Applying changes...", 1); - - CompositeChange change = new CompositeChange( - getName(), - mChanges.toArray(new Change[mChanges.size()])) { - @Override - public ChangeDescriptor getDescriptor() { - - String comment = String.format( - "Extracts string '%1$s' into R.string.%2$s", - mTokenString, - mXmlStringId); - - ExtractStringDescriptor desc = new ExtractStringDescriptor( - mProject.getName(), //project - comment, //description - comment, //comment - createArgumentMap()); - - return new RefactoringChangeDescriptor(desc); - } - }; - - monitor.worked(1); - - return change; - - } finally { - monitor.done(); - } - - } - - /** - * Given a file project path, returns its resource in the same project than the - * compilation unit. The resource may not exist. - */ - private IResource getTargetXmlResource(String xmlFileWsPath) { - IResource resource = mProject.getFile(xmlFileWsPath); - return resource; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringWizard.java deleted file mode 100644 index 556dff0df..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringWizard.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2009 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.extractstring; - -import org.eclipse.core.resources.IProject; -import org.eclipse.ltk.ui.refactoring.RefactoringWizard; - -/** - * A wizard for ExtractString based on a simple dialog with one page. - * - * @see ExtractStringInputPage - * @see ExtractStringRefactoring - */ -public class ExtractStringWizard extends RefactoringWizard { - - private final IProject mProject; - - /** - * Create a wizard for ExtractString based on a simple dialog with one page. - * - * @param ref The instance of {@link ExtractStringRefactoring} to associate to the wizard. - * @param project The project where the wizard was invoked from (e.g. where the user selection - * happened, so that we can retrieve project resources.) - */ - public ExtractStringWizard(ExtractStringRefactoring ref, IProject project) { - super(ref, DIALOG_BASED_USER_INTERFACE | PREVIEW_EXPAND_FIRST_NODE); - mProject = project; - setDefaultPageTitle(ref.getName()); - } - - @Override - protected void addUserInputPages() { - addPage(new ExtractStringInputPage(mProject)); - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ReplaceStringsVisitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ReplaceStringsVisitor.java deleted file mode 100644 index e058ce1ba..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ReplaceStringsVisitor.java +++ /dev/null @@ -1,480 +0,0 @@ -/* - * Copyright (C) 2009 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.extractstring; - -import org.eclipse.jdt.core.dom.AST; -import org.eclipse.jdt.core.dom.ASTNode; -import org.eclipse.jdt.core.dom.ASTVisitor; -import org.eclipse.jdt.core.dom.Assignment; -import org.eclipse.jdt.core.dom.ClassInstanceCreation; -import org.eclipse.jdt.core.dom.Expression; -import org.eclipse.jdt.core.dom.IMethodBinding; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.IVariableBinding; -import org.eclipse.jdt.core.dom.MethodDeclaration; -import org.eclipse.jdt.core.dom.MethodInvocation; -import org.eclipse.jdt.core.dom.Modifier; -import org.eclipse.jdt.core.dom.Name; -import org.eclipse.jdt.core.dom.SimpleName; -import org.eclipse.jdt.core.dom.SimpleType; -import org.eclipse.jdt.core.dom.SingleVariableDeclaration; -import org.eclipse.jdt.core.dom.StringLiteral; -import org.eclipse.jdt.core.dom.Type; -import org.eclipse.jdt.core.dom.TypeDeclaration; -import org.eclipse.jdt.core.dom.VariableDeclarationExpression; -import org.eclipse.jdt.core.dom.VariableDeclarationFragment; -import org.eclipse.jdt.core.dom.VariableDeclarationStatement; -import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; -import org.eclipse.text.edits.TextEditGroup; - -import java.util.ArrayList; -import java.util.List; -import java.util.TreeMap; - -/** - * Visitor used by {@link ExtractStringRefactoring} to extract a string from an existing - * Java source and replace it by an Android XML string reference. - * - * @see ExtractStringRefactoring#computeJavaChanges - */ -class ReplaceStringsVisitor extends ASTVisitor { - - private static final String CLASS_ANDROID_CONTEXT = "android.content.Context"; //$NON-NLS-1$ - private static final String CLASS_JAVA_CHAR_SEQUENCE = "java.lang.CharSequence"; //$NON-NLS-1$ - private static final String CLASS_JAVA_STRING = "java.lang.String"; //$NON-NLS-1$ - - - private final AST mAst; - private final ASTRewrite mRewriter; - private final String mOldString; - private final String mRQualifier; - private final String mXmlId; - private final ArrayList<TextEditGroup> mEditGroups; - - public ReplaceStringsVisitor(AST ast, - ASTRewrite astRewrite, - ArrayList<TextEditGroup> editGroups, - String oldString, - String rQualifier, - String xmlId) { - mAst = ast; - mRewriter = astRewrite; - mEditGroups = editGroups; - mOldString = oldString; - mRQualifier = rQualifier; - mXmlId = xmlId; - } - - @SuppressWarnings("unchecked") - @Override - public boolean visit(StringLiteral node) { - if (node.getLiteralValue().equals(mOldString)) { - - // We want to analyze the calling context to understand whether we can - // just replace the string literal by the named int constant (R.id.foo) - // or if we should generate a Context.getString() call. - boolean useGetResource = false; - useGetResource = examineVariableDeclaration(node) || - examineMethodInvocation(node) || - examineAssignment(node); - - Name qualifierName = mAst.newName(mRQualifier + ".string"); //$NON-NLS-1$ - SimpleName idName = mAst.newSimpleName(mXmlId); - ASTNode newNode = mAst.newQualifiedName(qualifierName, idName); - boolean disabledChange = false; - String title = "Replace string by ID"; - - if (useGetResource) { - Expression context = methodHasContextArgument(node); - if (context == null && !isClassDerivedFromContext(node)) { - // if we don't have a class that derives from Context and - // we don't have a Context method argument, then try a bit harder: - // can we find a method or a field that will give us a context? - context = findContextFieldOrMethod(node); - - if (context == null) { - // If not, let's write Context.getString(), which is technically - // invalid but makes it a good clue on how to fix it. Since these - // will not compile, we create a disabled change by default. - context = mAst.newSimpleName("Context"); //$NON-NLS-1$ - disabledChange = true; - } - } - - MethodInvocation mi2 = mAst.newMethodInvocation(); - mi2.setName(mAst.newSimpleName("getString")); //$NON-NLS-1$ - mi2.setExpression(context); - mi2.arguments().add(newNode); - - newNode = mi2; - title = "Replace string by Context.getString(R.string...)"; - } - - TextEditGroup editGroup = new EnabledTextEditGroup(title, !disabledChange); - mEditGroups.add(editGroup); - mRewriter.replace(node, newNode, editGroup); - } - return super.visit(node); - } - - /** - * Examines if the StringLiteral is part of an assignment corresponding to the - * a string variable declaration, e.g. String foo = id. - * - * The parent fragment is of syntax "var = expr" or "var[] = expr". - * We want the type of the variable, which is either held by a - * VariableDeclarationStatement ("type [fragment]") or by a - * VariableDeclarationExpression. In either case, the type can be an array - * but for us all that matters is to know whether the type is an int or - * a string. - */ - private boolean examineVariableDeclaration(StringLiteral node) { - VariableDeclarationFragment fragment = findParentClass(node, - VariableDeclarationFragment.class); - - if (fragment != null) { - ASTNode parent = fragment.getParent(); - - Type type = null; - if (parent instanceof VariableDeclarationStatement) { - type = ((VariableDeclarationStatement) parent).getType(); - } else if (parent instanceof VariableDeclarationExpression) { - type = ((VariableDeclarationExpression) parent).getType(); - } - - if (type instanceof SimpleType) { - return isJavaString(type.resolveBinding()); - } - } - - return false; - } - - /** - * Examines if the StringLiteral is part of a assignment to a variable that - * is a string. We need to lookup the variable to find its type, either in the - * enclosing method or class type. - */ - private boolean examineAssignment(StringLiteral node) { - - Assignment assignment = findParentClass(node, Assignment.class); - if (assignment != null) { - Expression left = assignment.getLeftHandSide(); - - ITypeBinding typeBinding = left.resolveTypeBinding(); - return isJavaString(typeBinding); - } - - return false; - } - - /** - * If the expression is part of a method invocation (aka a function call) or a - * class instance creation (aka a "new SomeClass" constructor call), we try to - * find the type of the argument being used. If it is a String (most likely), we - * want to return true (to generate a getString() call). However if there might - * be a similar method that takes an int, in which case we don't want to do that. - * - * This covers the case of Activity.setTitle(int resId) vs setTitle(String str). - */ - @SuppressWarnings("rawtypes") - private boolean examineMethodInvocation(StringLiteral node) { - - ASTNode parent = null; - List arguments = null; - IMethodBinding methodBinding = null; - - MethodInvocation invoke = findParentClass(node, MethodInvocation.class); - if (invoke != null) { - parent = invoke; - arguments = invoke.arguments(); - methodBinding = invoke.resolveMethodBinding(); - } else { - ClassInstanceCreation newclass = findParentClass(node, ClassInstanceCreation.class); - if (newclass != null) { - parent = newclass; - arguments = newclass.arguments(); - methodBinding = newclass.resolveConstructorBinding(); - } - } - - if (parent != null && arguments != null && methodBinding != null) { - // We want to know which argument this is. - // Walk up the hierarchy again to find the immediate child of the parent, - // which should turn out to be one of the invocation arguments. - ASTNode child = null; - for (ASTNode n = node; n != parent; ) { - ASTNode p = n.getParent(); - if (p == parent) { - child = n; - break; - } - n = p; - } - if (child == null) { - // This can't happen: a parent of 'node' must be the child of 'parent'. - return false; - } - - // Find the index - int index = 0; - for (Object arg : arguments) { - if (arg == child) { - break; - } - index++; - } - - if (index == arguments.size()) { - // This can't happen: one of the arguments of 'invoke' must be 'child'. - return false; - } - - // Eventually we want to determine if the parameter is a string type, - // in which case a Context.getString() call must be generated. - boolean useStringType = false; - - // Find the type of that argument - ITypeBinding[] types = methodBinding.getParameterTypes(); - if (index < types.length) { - ITypeBinding type = types[index]; - useStringType = isJavaString(type); - } - - // Now that we know that this method takes a String parameter, can we find - // a variant that would accept an int for the same parameter position? - if (useStringType) { - String name = methodBinding.getName(); - ITypeBinding clazz = methodBinding.getDeclaringClass(); - nextMethod: for (IMethodBinding mb2 : clazz.getDeclaredMethods()) { - if (methodBinding == mb2 || !mb2.getName().equals(name)) { - continue; - } - // We found a method with the same name. We want the same parameters - // except that the one at 'index' must be an int type. - ITypeBinding[] types2 = mb2.getParameterTypes(); - int len2 = types2.length; - if (types.length == len2) { - for (int i = 0; i < len2; i++) { - if (i == index) { - ITypeBinding type2 = types2[i]; - if (!("int".equals(type2.getQualifiedName()))) { //$NON-NLS-1$ - // The argument at 'index' is not an int. - continue nextMethod; - } - } else if (!types[i].equals(types2[i])) { - // One of the other arguments do not match our original method - continue nextMethod; - } - } - // If we got here, we found a perfect match: a method with the same - // arguments except the one at 'index' is an int. In this case we - // don't need to convert our R.id into a string. - useStringType = false; - break; - } - } - } - - return useStringType; - } - return false; - } - - /** - * Examines if the StringLiteral is part of a method declaration (a.k.a. a function - * definition) which takes a Context argument. - * If such, it returns the name of the variable as a {@link SimpleName}. - * Otherwise it returns null. - */ - private SimpleName methodHasContextArgument(StringLiteral node) { - MethodDeclaration decl = findParentClass(node, MethodDeclaration.class); - if (decl != null) { - for (Object obj : decl.parameters()) { - if (obj instanceof SingleVariableDeclaration) { - SingleVariableDeclaration var = (SingleVariableDeclaration) obj; - if (isAndroidContext(var.getType())) { - return mAst.newSimpleName(var.getName().getIdentifier()); - } - } - } - } - return null; - } - - /** - * Walks up the node hierarchy to find the class (aka type) where this statement - * is used and returns true if this class derives from android.content.Context. - */ - private boolean isClassDerivedFromContext(StringLiteral node) { - TypeDeclaration clazz = findParentClass(node, TypeDeclaration.class); - if (clazz != null) { - // This is the class that the user is currently writing, so it can't be - // a Context by itself, it has to be derived from it. - return isAndroidContext(clazz.getSuperclassType()); - } - return false; - } - - private Expression findContextFieldOrMethod(StringLiteral node) { - TypeDeclaration clazz = findParentClass(node, TypeDeclaration.class); - return clazz == null ? null : findContextFieldOrMethod(clazz.resolveBinding()); - } - - private Expression findContextFieldOrMethod(ITypeBinding clazzType) { - TreeMap<Integer, Expression> results = new TreeMap<Integer, Expression>(); - findContextCandidates(results, clazzType, 0 /*superType*/); - if (results.size() > 0) { - Integer bestRating = results.keySet().iterator().next(); - return results.get(bestRating); - } - return null; - } - - /** - * Find all method or fields that are candidates for providing a Context. - * There can be various choices amongst this class or its super classes. - * Sort them by rating in the results map. - * - * The best ever choice is to find a method with no argument that returns a Context. - * The second suitable choice is to find a Context field. - * The least desirable choice is to find a method with arguments. It's not really - * desirable since we can't generate these arguments automatically. - * - * Methods and fields from supertypes are ignored if they are private. - * - * The rating is reversed: the lowest rating integer is used for the best candidate. - * Because the superType argument is actually a recursion index, this makes the most - * immediate classes more desirable. - * - * @param results The map that accumulates the rating=>expression results. The lower - * rating number is the best candidate. - * @param clazzType The class examined. - * @param superType The recursion index. - * 0 for the immediate class, 1 for its super class, etc. - */ - private void findContextCandidates(TreeMap<Integer, Expression> results, - ITypeBinding clazzType, - int superType) { - for (IMethodBinding mb : clazzType.getDeclaredMethods()) { - // If we're looking at supertypes, we can't use private methods. - if (superType != 0 && Modifier.isPrivate(mb.getModifiers())) { - continue; - } - - if (isAndroidContext(mb.getReturnType())) { - // We found a method that returns something derived from Context. - - int argsLen = mb.getParameterTypes().length; - if (argsLen == 0) { - // We'll favor any method that takes no argument, - // That would be the best candidate ever, so we can stop here. - MethodInvocation mi = mAst.newMethodInvocation(); - mi.setName(mAst.newSimpleName(mb.getName())); - results.put(Integer.MIN_VALUE, mi); - return; - } else { - // A method with arguments isn't as interesting since we wouldn't - // know how to populate such arguments. We'll use it if there are - // no other alternatives. We'll favor the one with the less arguments. - Integer rating = Integer.valueOf(10000 + 1000 * superType + argsLen); - if (!results.containsKey(rating)) { - MethodInvocation mi = mAst.newMethodInvocation(); - mi.setName(mAst.newSimpleName(mb.getName())); - results.put(rating, mi); - } - } - } - } - - // A direct Context field would be more interesting than a method with - // arguments. Try to find one. - for (IVariableBinding var : clazzType.getDeclaredFields()) { - // If we're looking at supertypes, we can't use private field. - if (superType != 0 && Modifier.isPrivate(var.getModifiers())) { - continue; - } - - if (isAndroidContext(var.getType())) { - // We found such a field. Let's use it. - Integer rating = Integer.valueOf(superType); - results.put(rating, mAst.newSimpleName(var.getName())); - break; - } - } - - // Examine the super class to see if we can locate a better match - clazzType = clazzType.getSuperclass(); - if (clazzType != null) { - findContextCandidates(results, clazzType, superType + 1); - } - } - - /** - * Walks up the node hierarchy and returns the first ASTNode of the requested class. - * Only look at parents. - * - * Implementation note: this is a generic method so that it returns the node already - * casted to the requested type. - */ - @SuppressWarnings("unchecked") - private <T extends ASTNode> T findParentClass(ASTNode node, Class<T> clazz) { - if (node != null) { - for (node = node.getParent(); node != null; node = node.getParent()) { - if (node.getClass().equals(clazz)) { - return (T) node; - } - } - } - return null; - } - - /** - * Returns true if the given type is or derives from android.content.Context. - */ - private boolean isAndroidContext(Type type) { - if (type != null) { - return isAndroidContext(type.resolveBinding()); - } - return false; - } - - /** - * Returns true if the given type is or derives from android.content.Context. - */ - private boolean isAndroidContext(ITypeBinding type) { - for (; type != null; type = type.getSuperclass()) { - if (CLASS_ANDROID_CONTEXT.equals(type.getQualifiedName())) { - return true; - } - } - return false; - } - - /** - * Returns true if this type binding represents a String or CharSequence type. - */ - private boolean isJavaString(ITypeBinding type) { - for (; type != null; type = type.getSuperclass()) { - if (CLASS_JAVA_STRING.equals(type.getQualifiedName()) || - CLASS_JAVA_CHAR_SEQUENCE.equals(type.getQualifiedName())) { - return true; - } - } - return false; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/XmlStringFileHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/XmlStringFileHelper.java deleted file mode 100644 index 01e814ef2..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/XmlStringFileHelper.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2009 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.extractstring; - -import com.android.SdkConstants; -import com.android.ide.eclipse.adt.AdtPlugin; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -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.xml.core.internal.provisional.document.IDOMDocument; -import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; - -import java.util.HashMap; -import java.util.Map; -import java.util.TreeMap; - -/** - * An helper utility to get IDs out of an Android XML resource file. - */ -@SuppressWarnings("restriction") -class XmlStringFileHelper { - - /** A temporary cache of R.string IDs defined by a given xml file. The key is the - * project path of the file, the data is a set of known string Ids for that file. - * - * Map type: map [String filename] => map [String id => String value]. - */ - private HashMap<String, Map<String, String>> mResIdCache = - new HashMap<String, Map<String, String>>(); - - public XmlStringFileHelper() { - } - - /** - * Utility method used by the wizard to retrieve the actual value definition of a given - * string ID. - * - * @param project The project contain the XML file. - * @param xmlFileWsPath The project path of the XML file, e.g. "/res/values/strings.xml". - * The given file may or may not exist. - * @param stringId The string ID to find. - * @return The value string if the ID is defined, null otherwise. - */ - public String valueOfStringId(IProject project, String xmlFileWsPath, String stringId) { - Map<String, String> cache = getResIdsForFile(project, xmlFileWsPath); - return cache.get(stringId); - } - - /** - * Utility method that retrieves all the *string* IDs defined in the given Android resource - * file. The instance maintains an internal cache so a given file is retrieved only once. - * Callers should consider the set to be read-only. - * - * @param project The project contain the XML file. - * @param xmlFileWsPath The project path of the XML file, e.g. "/res/values/strings.xml". - * The given file may or may not exist. - * @return The map of string IDs => values defined in the given file. Cached. Never null. - */ - public Map<String, String> getResIdsForFile(IProject project, String xmlFileWsPath) { - Map<String, String> cache = mResIdCache.get(xmlFileWsPath); - if (cache == null) { - cache = internalGetResIdsForFile(project, xmlFileWsPath); - mResIdCache.put(xmlFileWsPath, cache); - } - return cache; - } - - /** - * Extract all the defined string IDs from a given file using XPath. - * @param project The project contain the XML file. - * @param xmlFileWsPath The project path of the file to parse. It may not exist. - * @return The map of all string IDs => values defined in the file. - * The returned set is always non null. It is empty if the file does not exist. - */ - private Map<String, String> internalGetResIdsForFile(IProject project, String xmlFileWsPath) { - - TreeMap<String, String> ids = new TreeMap<String, String>(); - - // Access the project that contains the resource that contains the compilation unit - IResource resource = project.getFile(xmlFileWsPath); - - if (resource != null && resource.exists() && resource.getType() == IResource.FILE) { - IStructuredModel smodel = null; - - try { - IFile file = (IFile) resource; - IModelManager modelMan = StructuredModelManager.getModelManager(); - smodel = modelMan.getExistingModelForRead(file); - if (smodel == null) { - smodel = modelMan.getModelForRead(file); - } - - if (smodel instanceof IDOMModel) { - IDOMDocument doc = ((IDOMModel) smodel).getDocument(); - - // We want all the IDs in an XML structure like this: - // <resources> - // <string name="ID">something</string> - // </resources> - - Node root = findChild(doc, null, SdkConstants.TAG_RESOURCES); - if (root != null) { - for (Node strNode = findChild(root, null, - SdkConstants.TAG_STRING); - strNode != null; - strNode = findChild(null, strNode, - SdkConstants.TAG_STRING)) { - NamedNodeMap attrs = strNode.getAttributes(); - Node nameAttr = attrs.getNamedItem(SdkConstants.ATTR_NAME); - if (nameAttr != null) { - String id = nameAttr.getNodeValue(); - - // Find the TEXT node right after the element. - // Whitespace matters so we don't try to normalize it. - String text = ""; //$NON-NLS-1$ - for (Node txtNode = strNode.getFirstChild(); - txtNode != null && txtNode.getNodeType() == Node.TEXT_NODE; - txtNode = txtNode.getNextSibling()) { - text += txtNode.getNodeValue(); - } - - ids.put(id, text); - } - } - } - } - - } catch (Throwable e) { - AdtPlugin.log(e, "GetResIds failed in %1$s", xmlFileWsPath); //$NON-NLS-1$ - } finally { - if (smodel != null) { - smodel.releaseFromRead(); - } - } - } - - return ids; - } - - /** - * Utility method that finds the next node of the requested element name. - * - * @param parent The parent node. If not null, will to start searching its children. - * Set to null when iterating through children. - * @param lastChild The last child returned. Use null when visiting a parent the first time. - * @param elementName The element name of the node to find. - * @return The next children or sibling nide with the requested element name or null. - */ - private Node findChild(Node parent, Node lastChild, String elementName) { - if (lastChild == null && parent != null) { - lastChild = parent.getFirstChild(); - } else if (lastChild != null) { - lastChild = lastChild.getNextSibling(); - } - - for ( ; lastChild != null ; lastChild = lastChild.getNextSibling()) { - if (lastChild.getNodeType() == Node.ELEMENT_NODE && - lastChild.getNamespaceURI() == null && // resources don't have any NS URI - elementName.equals(lastChild.getLocalName())) { - return lastChild; - } - } - - return null; - } - -} 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 deleted file mode 100644 index 406cebca4..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/ApplicationPackageNameRefactoring.java +++ /dev/null @@ -1,586 +0,0 @@ -/* - * 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; - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/ApplicationPackageNameRefactoringWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/ApplicationPackageNameRefactoringWizard.java deleted file mode 100644 index 3651855a7..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/ApplicationPackageNameRefactoringWizard.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 org.eclipse.ltk.core.refactoring.Refactoring; -import org.eclipse.ltk.ui.refactoring.RefactoringWizard; - -/** - * @see RenamePackageAction - */ -class ApplicationPackageNameRefactoringWizard extends RefactoringWizard { - - public ApplicationPackageNameRefactoringWizard(Refactoring refactoring) { - super(refactoring, 0); - } - - @Override - protected void addUserInputPages() { - } -} - diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/RenamePackageAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/RenamePackageAction.java deleted file mode 100644 index bb475aab1..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/RenamePackageAction.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * 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 com.android.ide.common.xml.ManifestData; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; - -import org.eclipse.core.resources.IProject; -import org.eclipse.core.runtime.IAdaptable; -import org.eclipse.core.runtime.Status; -import org.eclipse.jdt.core.dom.AST; -import org.eclipse.jdt.core.dom.Name; -import org.eclipse.jdt.ui.refactoring.RefactoringSaveHelper; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.dialogs.IInputValidator; -import org.eclipse.jface.dialogs.InputDialog; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.window.Window; -import org.eclipse.ltk.core.refactoring.Refactoring; -import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; -import org.eclipse.ui.IObjectActionDelegate; -import org.eclipse.ui.IWorkbenchPart; -import org.eclipse.ui.IWorkbenchWindow; -import org.eclipse.ui.IWorkbenchWindowActionDelegate; - -import java.util.Iterator; - -/** - * Refactoring steps: - * <ol> - * <li>Update the "package" attribute of the <manifest> tag with the new - * name.</li> - * <li>Replace all values for the "android:name" attribute in the - * <application> and "component class" (<activity>, <service>, - * <receiver>, and <provider>) tags with the non-shorthand version - * of the class name</li> - * <li>Replace package resource imports (*.R) in .java files</li> - * <li>Update package name in the namespace declarations (e.g. "xmlns:app") - * used for custom styleable attributes in layout resource files</li> - * </ol> - * Caveat: Sometimes it is necessary to perform a project-wide - * "Organize Imports" afterwards. (CTRL+SHIFT+O when a project has active - * selection) - */ -public class RenamePackageAction implements IObjectActionDelegate { - - private ISelection mSelection; - @SuppressWarnings("unused") private IWorkbenchPart mTargetPart; // TODO cleanup - - /** - * @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart) - */ - @Override - public void setActivePart(IAction action, IWorkbenchPart targetPart) { - mTargetPart = targetPart; - } - - @Override - public void selectionChanged(IAction action, ISelection selection) { - mSelection = selection; - } - - /** - * @see IWorkbenchWindowActionDelegate#init - */ - public void init(IWorkbenchWindow window) { - // pass - } - - @Override - public void run(IAction action) { - - // Prompt for refactoring on the selected project - if (mSelection instanceof IStructuredSelection) { - for (Iterator<?> it = ((IStructuredSelection) mSelection).iterator(); it.hasNext();) { - Object element = it.next(); - IProject project = null; - if (element instanceof IProject) { - project = (IProject) element; - } else if (element instanceof IAdaptable) { - project = (IProject) ((IAdaptable) element).getAdapter(IProject.class); - } - if (project != null) { - // It is advisable that the user saves before proceeding, - // revealing any compilation errors. The following lines - // enforce a save as a convenience. - RefactoringSaveHelper save_helper = new RefactoringSaveHelper( - RefactoringSaveHelper.SAVE_ALL_ALWAYS_ASK); - if (save_helper.saveEditors(AdtPlugin.getShell())) { - promptNewName(project); - } - } - } - } - } - - /* - * Validate the new package name and start the refactoring wizard - */ - private void promptNewName(final IProject project) { - - ManifestData manifestData = AndroidManifestHelper.parseForData(project); - if (manifestData == null) { - return; - } - - final String oldPackageNameString = manifestData.getPackage(); - - final AST astValidator = AST.newAST(AST.JLS3); - Name oldPackageName = astValidator.newName(oldPackageNameString); - - IInputValidator validator = new IInputValidator() { - - @Override - public String isValid(String newText) { - try { - astValidator.newName(newText); - } catch (IllegalArgumentException e) { - return "Illegal package name."; - } - - if (newText.equals(oldPackageNameString)) - return "No change."; - else - return null; - } - }; - - InputDialog dialog = new InputDialog(AdtPlugin.getShell(), - "Rename Application Package", "Enter new package name:", oldPackageNameString, - validator); - - if (dialog.open() == Window.OK) { - Name newPackageName = astValidator.newName(dialog.getValue()); - initiateAndroidPackageRefactoring(project, oldPackageName, newPackageName); - } - } - - - private void initiateAndroidPackageRefactoring( - final IProject project, - Name oldPackageName, - Name newPackageName) { - - Refactoring package_name_refactoring = - new ApplicationPackageNameRefactoring(project, oldPackageName, newPackageName); - - ApplicationPackageNameRefactoringWizard wizard = - new ApplicationPackageNameRefactoringWizard(package_name_refactoring); - RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard); - try { - op.run(AdtPlugin.getShell(), package_name_refactoring.getName()); - } catch (InterruptedException e) { - Status s = new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, e.getMessage(), e); - AdtPlugin.getDefault().getLog().log(s); - } - } -} |