aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceParticipant.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceParticipant.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceParticipant.java752
1 files changed, 752 insertions, 0 deletions
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
new file mode 100644
index 000000000..438e82223
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceParticipant.java
@@ -0,0 +1,752 @@
+/*
+ * 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);
+ }
+ }
+ }
+}