aboutsummaryrefslogtreecommitdiffstats
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com
diff options
context:
space:
mode:
authorRaphael Moll <ralf@android.com>2010-11-27 23:18:24 -0800
committerRaphael Moll <ralf@android.com>2010-11-30 12:14:08 -0800
commitb281c42014e66e6b9ecef5b1911e51ded8d172cb (patch)
treef018e076527e5a1c5c3c66c251007ab19a2a1b4b /eclipse/plugins/com.android.ide.eclipse.adt/src/com
parentbbdf6557c97b99a1781b67afd9721c34269da296 (diff)
downloadsdk-b281c42014e66e6b9ecef5b1911e51ded8d172cb.zip
sdk-b281c42014e66e6b9ecef5b1911e51ded8d172cb.tar.gz
sdk-b281c42014e66e6b9ecef5b1911e51ded8d172cb.tar.bz2
ADT string refactoring: replace in all files.
When doing an extract string either from Java or XML: - can scan/replace in all other Java files. - can scan/replace in all other XML files. - in Java, also replace in assignements. - in XML, also replace existing string name if already defined. Change-Id: Ifeef5fd444c2c18b9c071955b8e8567d6515ea95
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com')
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/DisabledTextEditGroup.java40
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringInputPage.java112
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java352
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ReplaceStringsVisitor.java42
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceFolderType.java7
5 files changed, 452 insertions, 101 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/DisabledTextEditGroup.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/DisabledTextEditGroup.java
new file mode 100755
index 0000000..15f6c47
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/DisabledTextEditGroup.java
@@ -0,0 +1,40 @@
+/*
+ * 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/ExtractStringInputPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringInputPage.java
index cbcd581..8dab07e 100644
--- 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
@@ -36,8 +36,10 @@ 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;
@@ -70,6 +72,10 @@ class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage
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 */
@@ -86,6 +92,20 @@ class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage
private XmlStringFileHelper mXmlHelper = new XmlStringFileHelper();
+ private final OnConfigSelectorUpdated mOnConfigSelectorUpdated = new OnConfigSelectorUpdated();
+
+ private ModifyListener mValidateOnModify = new ModifyListener() {
+ 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$
@@ -97,17 +117,21 @@ class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage
* <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
*/
public void createControl(Composite parent) {
Composite content = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout();
- layout.numColumns = 1;
content.setLayout(layout);
createStringGroup(content);
createResFileGroup(content);
+ createOptionGroup(content);
- validatePage();
+ initUi();
setControl(content);
}
@@ -123,10 +147,9 @@ class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage
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");
- } else {
- group.setText("New String");
}
GridLayout layout = new GridLayout();
@@ -152,19 +175,14 @@ class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage
}
});
-
- // TODO provide an option to replace all occurences of this string instead of
- // just the one.
-
// 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.");
- } else {
- label.setText("ID &R.string.");
}
mStringIdCombo = new Combo(group, SWT.SINGLE | SWT.LEFT | SWT.BORDER | SWT.DROP_DOWN);
@@ -174,17 +192,8 @@ class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage
ref.setNewStringId(mStringIdCombo.getText().trim());
- mStringIdCombo.addModifyListener(new ModifyListener() {
- public void modifyText(ModifyEvent e) {
- validatePage();
- }
- });
- mStringIdCombo.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- validatePage();
- }
- });
+ mStringIdCombo.addModifyListener(mValidateOnModify);
+ mStringIdCombo.addSelectionListener(mValidateOnSelection);
}
/**
@@ -196,7 +205,9 @@ class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage
private void createResFileGroup(Composite content) {
Group group = new Group(content, SWT.NONE);
- group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ GridData gd = new GridData(GridData.FILL_HORIZONTAL);
+ gd.grabExcessVerticalSpace = true;
+ group.setLayoutData(gd);
group.setText("XML resource to edit");
GridLayout layout = new GridLayout();
@@ -210,13 +221,12 @@ class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage
label.setText("&Configuration:");
mConfigSelector = new ConfigurationSelector(group, SelectorMode.DEFAULT);
- GridData gd = new GridData(GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL);
+ 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);
- OnConfigSelectorUpdated onConfigSelectorUpdated = new OnConfigSelectorUpdated();
- mConfigSelector.setOnChangeListener(onConfigSelectorUpdated);
+ mConfigSelector.setOnChangeListener(mOnConfigSelectorUpdated);
// line: selection of the output file
@@ -226,15 +236,50 @@ class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage
mResFileCombo = new Combo(group, SWT.DROP_DOWN);
mResFileCombo.select(0);
mResFileCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
- mResFileCombo.addModifyListener(onConfigSelectorUpdated);
+ mResFileCombo.addModifyListener(mOnConfigSelectorUpdated);
+ }
- // set output file name to the last one used
+ /**
+ * 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);
- onConfigSelectorUpdated.run();
+ mOnConfigSelectorUpdated.run();
+ validatePage();
}
/**
@@ -278,6 +323,9 @@ class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage
ExtractStringRefactoring ref = getOurRefactoring();
+ ref.setReplaceAllJava(mReplaceAllJava.getSelection());
+ ref.setReplaceAllXml(mReplaceAllXml.isEnabled() && mReplaceAllXml.getSelection());
+
// Analyze fatal errors.
String text = mStringIdCombo.getText().trim();
@@ -372,7 +420,7 @@ class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage
}
}
- public class OnConfigSelectorUpdated implements Runnable, ModifyListener {
+ 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(
@@ -422,9 +470,10 @@ class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage
mConfigSelector.getConfiguration(mTempConfig);
StringBuffer sb = new StringBuffer(RES_FOLDER_ABS);
sb.append(mTempConfig.getFolderName(ResourceFolderType.VALUES));
- sb.append('/');
+ sb.append(AndroidConstants.WS_SEP);
String newPath = sb.toString();
+
if (newPath.equals(currPath) && newPath.equals(mLastFolderUsedInCombo)) {
// Path has not changed. No need to reload.
return;
@@ -546,4 +595,7 @@ class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage
}
}
+ // 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/ExtractStringRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java
index aa2b49d..b621a69 100644
--- 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
@@ -31,6 +31,7 @@ import com.android.sdklib.xml.ManifestData;
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;
@@ -43,6 +44,9 @@ 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;
@@ -82,10 +86,14 @@ 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
@@ -176,8 +184,13 @@ public class ExtractStringRefactoring extends Refactoring {
/** 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. */
+ * 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)}. */
@@ -185,15 +198,29 @@ public class ExtractStringRefactoring extends Refactoring {
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_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));
@@ -219,6 +246,8 @@ public class ExtractStringRefactoring extends Refactoring {
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) {
@@ -253,10 +282,13 @@ public class ExtractStringRefactoring extends Refactoring {
/**
* 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.
+ * @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;
@@ -267,6 +299,36 @@ public class ExtractStringRefactoring extends Refactoring {
}
/**
+ * 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
@@ -785,7 +847,7 @@ public class ExtractStringRefactoring extends Refactoring {
RefactoringStatus status = new RefactoringStatus();
try {
- monitor.beginTask("Checking post-conditions...", 3);
+ monitor.beginTask("Checking post-conditions...", 5);
if (mXmlStringId == null || mXmlStringId.length() <= 0) {
// this is not supposed to happen
@@ -820,10 +882,10 @@ public class ExtractStringRefactoring extends Refactoring {
mChanges = new ArrayList<Change>();
- // Prepare the change for the XML file.
-
- if (mXmlHelper.valueOfStringId(mProject, mTargetXmlFileWsPath, mXmlStringId) == null) {
- // We actually change it only if the ID doesn't exist yet
+ // 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 = createXmlChange((IFile) targetXml, mXmlStringId, mXmlStringValue,
status, SubMonitor.convert(monitor, 1));
if (change != null) {
@@ -853,6 +915,46 @@ public class ExtractStringRefactoring extends Refactoring {
}
}
+ if (mReplaceAllJava) {
+ 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;
+ }
+ 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,
+ status, SubMonitor.convert(submon, 1));
+ if (changes != null) {
+ mChanges.addAll(changes);
+ }
+ }
+ }
+ }
+
monitor.worked(1);
} finally {
monitor.done();
@@ -861,6 +963,108 @@ public class ExtractStringRefactoring extends Refactoring {
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>() {
+ 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;
+ {
+ // We want to process the manifest
+ IResource man = mProject.findMember("AndroidManifest.xml"); // TODO find a constant
+ if (man.exists() && man instanceof IFile) {
+ 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(AndroidConstants.WS_RESOURCES);
+ if (f.exists()) {
+ try {
+ mFolders.addAll(
+ Arrays.asList(f.members(IContainer.EXCLUDE_DERIVED)));
+ } catch (CoreException e) {
+ // pass
+ }
+ }
+
+ // 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();
+ }
+ }
+
+ 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 (AndroidConstants.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);
+ }
+ }
+ }
+ }
+
+ public IFile next() {
+ IFile file = mFiles.poll();
+ hasNext();
+ return file;
+ }
+
+ 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.
@@ -881,7 +1085,7 @@ public class ExtractStringRefactoring extends Refactoring {
TextFileChange xmlChange = new TextFileChange(getName(), targetXml);
xmlChange.setTextType(AndroidConstants.EXT_XML);
- String error = "";
+ String error = ""; //$NON-NLS-1$
TextEdit edit = null;
TextEditGroup editGroup = null;
@@ -901,8 +1105,8 @@ public class ExtractStringRefactoring extends Refactoring {
if (edit == null) {
status.addFatalError(String.format("Failed to modify file %1$s%2$s",
- mTargetXmlFileWsPath,
- error == null ? "" : ": " + error)); //$NON-NLS-1$
+ targetXml == null ? "" : targetXml.getFullPath(), //$NON-NLS-1$
+ error == null ? "" : ": " + error)); //$NON-NLS-1$
return null;
}
@@ -1288,7 +1492,8 @@ public class ExtractStringRefactoring extends Refactoring {
// Remove " or ' quoting present in the attribute value
text = unquoteAttrValue(text);
- if (xmlAttrName.equals(lastAttrName) && tokenString.equals(text)) {
+ if (tokenString.equals(text) &&
+ (xmlAttrName == null || xmlAttrName.equals(lastAttrName))) {
// Found an occurrence. Create a change for it.
TextEdit edit = new ReplaceEdit(
@@ -1359,6 +1564,67 @@ public class ExtractStringRefactoring extends Refactoring {
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>() {
+ 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
+ }
+ }
+
+ 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;
+ }
+
+ public ICompilationUnit next() {
+ ICompilationUnit unit = mUnits.poll();
+ hasNext();
+ return unit;
+ }
+
+ 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}.
*/
@@ -1395,23 +1661,6 @@ public class ExtractStringRefactoring extends Refactoring {
return null;
}
- // TODO in a future version we might want to collect various Java files that
- // need to be updated in the same project and process them all together.
- // To do that we need to use an ASTRequestor and parser.createASTs, kind of
- // like this:
- //
- // ASTRequestor requestor = new ASTRequestor() {
- // @Override
- // public void acceptAST(ICompilationUnit sourceUnit, CompilationUnit astNode) {
- // super.acceptAST(sourceUnit, astNode);
- // // TODO process astNode
- // }
- // };
- // ...
- // parser.createASTs(compilationUnits, bindingKeys, requestor, monitor)
- //
- // and then add multiple TextFileChange to the changes arraylist.
-
// Right now the changes array will contain one TextFileChange at most.
ArrayList<Change> changes = new ArrayList<Change>();
@@ -1471,7 +1720,11 @@ public class ExtractStringRefactoring extends Refactoring {
// 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) {
- change.addTextEditChangeGroup(new TextEditChangeGroup(change, editGroup));
+ TextEditChangeGroup group = new TextEditChangeGroup(change, editGroup);
+ if (editGroup instanceof EnabledTextEditGroup) {
+ group.setEnabled(((EnabledTextEditGroup) editGroup).isEnabled());
+ }
+ change.addTextEditChangeGroup(group);
}
changes.add(change);
@@ -1494,6 +1747,8 @@ public class ExtractStringRefactoring extends Refactoring {
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.
@@ -1548,27 +1803,4 @@ public class ExtractStringRefactoring extends Refactoring {
IResource resource = mProject.getFile(xmlFileWsPath);
return resource;
}
-
- /**
- * 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;
- }
-
}
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
index dd0f9f4..1a0521b 100755
--- 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
@@ -19,6 +19,7 @@ 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;
@@ -88,15 +89,16 @@ class ReplaceStringsVisitor extends ASTVisitor {
// or if we should generate a Context.getString() call.
boolean useGetResource = false;
useGetResource = examineVariableDeclaration(node) ||
- examineMethodInvocation(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
@@ -106,8 +108,10 @@ class ReplaceStringsVisitor extends ASTVisitor {
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.
+ // 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;
}
}
@@ -120,7 +124,7 @@ class ReplaceStringsVisitor extends ASTVisitor {
title = "Replace string by Context.getString(R.string...)";
}
- TextEditGroup editGroup = new TextEditGroup(title);
+ TextEditGroup editGroup = new EnabledTextEditGroup(title, !disabledChange);
mEditGroups.add(editGroup);
mRewriter.replace(node, newNode, editGroup);
}
@@ -128,8 +132,8 @@ class ReplaceStringsVisitor extends ASTVisitor {
}
/**
- * Examines if the StringLiteral is part of of an assignment to a string,
- * e.g. String foo = id.
+ * 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
@@ -161,6 +165,24 @@ class ReplaceStringsVisitor extends ASTVisitor {
}
/**
+ * 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
@@ -412,9 +434,11 @@ class ReplaceStringsVisitor extends ASTVisitor {
*/
@SuppressWarnings("unchecked")
private <T extends ASTNode> T findParentClass(ASTNode node, Class<T> clazz) {
- for (node = node.getParent(); node != null; node = node.getParent()) {
- if (node.getClass().equals(clazz)) {
- return (T) node;
+ if (node != null) {
+ for (node = node.getParent(); node != null; node = node.getParent()) {
+ if (node.getClass().equals(clazz)) {
+ return (T) node;
+ }
}
}
return null;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceFolderType.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceFolderType.java
index 0a56ff5..735a23c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceFolderType.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceFolderType.java
@@ -38,10 +38,13 @@ public enum ResourceFolderType {
mName = name;
}
+ /**
+ * Returns the folder name for this resource folder type.
+ */
public String getName() {
return mName;
}
-
+
/**
* Returns the enum by name.
* @param name The enum string value.
@@ -55,7 +58,7 @@ public enum ResourceFolderType {
}
return null;
}
-
+
/**
* Returns the {@link ResourceFolderType} from the folder name
* @param folderName The name of the folder. This must be a valid folder name in the format