| /* |
| * 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); |
| } |
| } |
| } |
| } |