diff options
author | Raphael Moll <ralf@android.com> | 2010-11-27 23:18:24 -0800 |
---|---|---|
committer | Raphael Moll <ralf@android.com> | 2010-11-30 12:14:08 -0800 |
commit | b281c42014e66e6b9ecef5b1911e51ded8d172cb (patch) | |
tree | f018e076527e5a1c5c3c66c251007ab19a2a1b4b /eclipse/plugins/com.android.ide.eclipse.adt/src/com | |
parent | bbdf6557c97b99a1781b67afd9721c34269da296 (diff) | |
download | sdk-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')
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 |