AI 144488: am: CL 144486 ADT: Call the Extract String Refactoring to create a new XML String
ID when selecting a string reference. Original author: raphael Merged from: //branches/cupcake/... Automated import of CL 144488
This commit is contained in:
committed by
The Android Open Source Project
parent
bb10b2afb6
commit
f23d59c068
@@ -17,34 +17,72 @@
|
||||
package com.android.ide.eclipse.adt.refactorings.extractstring;
|
||||
|
||||
|
||||
import com.android.ide.eclipse.adt.wizards.newstring.NewStringBaseImpl;
|
||||
import com.android.ide.eclipse.adt.wizards.newstring.NewStringBaseImpl.INewStringPageCallback;
|
||||
import com.android.ide.eclipse.adt.wizards.newstring.NewStringBaseImpl.ValidationStatus;
|
||||
import com.android.ide.eclipse.adt.ui.ConfigurationSelector;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration;
|
||||
import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
|
||||
import org.eclipse.core.resources.IFolder;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.jface.wizard.IWizardPage;
|
||||
import org.eclipse.jface.wizard.WizardPage;
|
||||
import org.eclipse.ltk.ui.refactoring.UserInputWizardPage;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.ModifyEvent;
|
||||
import org.eclipse.swt.events.ModifyListener;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Combo;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Group;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @see ExtractStringRefactoring
|
||||
*/
|
||||
class ExtractStringInputPage extends UserInputWizardPage
|
||||
implements IWizardPage, INewStringPageCallback {
|
||||
class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage {
|
||||
|
||||
private NewStringBaseImpl mImpl;
|
||||
/** Last res file path used, shared across the session instances but specific to the
|
||||
* current project. The default for unknown projects is {@link #DEFAULT_RES_FILE_PATH}. */
|
||||
private static HashMap<String, String> sLastResFilePath = new HashMap<String, String>();
|
||||
|
||||
/** The project where the user selection happened. */
|
||||
private final IProject mProject;
|
||||
|
||||
/** Test field where the user enters the new ID to be generated or replaced with. */
|
||||
private Text mStringIdField;
|
||||
/** The configuration selector, to select the resource path of the XML file. */
|
||||
private ConfigurationSelector mConfigSelector;
|
||||
/** The combo to display the existing XML files or enter a new one. */
|
||||
private Combo mResFileCombo;
|
||||
|
||||
/** Regex pattern to read a valid res XML file path. It checks that the are 2 folders and
|
||||
* a leaf file name ending with .xml */
|
||||
private static final Pattern RES_XML_FILE_REGEX = Pattern.compile(
|
||||
"/res/[a-z][a-zA-Z0-9_-]+/[^.]+\\.xml"); //$NON-NLS-1$
|
||||
/** Absolute destination folder root, e.g. "/res/" */
|
||||
private static final String RES_FOLDER_ABS =
|
||||
AndroidConstants.WS_RESOURCES + AndroidConstants.WS_SEP;
|
||||
/** Relative destination folder root, e.g. "res/" */
|
||||
private static final String RES_FOLDER_REL =
|
||||
SdkConstants.FD_RESOURCES + AndroidConstants.WS_SEP;
|
||||
|
||||
private static final String DEFAULT_RES_FILE_PATH = "/res/values/strings.xml"; //$NON-NLS-1$
|
||||
|
||||
private XmlStringFileHelper mXmlHelper = new XmlStringFileHelper();
|
||||
|
||||
public ExtractStringInputPage(IProject project) {
|
||||
super("ExtractStringInputPage"); //$NON-NLS-1$
|
||||
mImpl = new NewStringBaseImpl(project, this);
|
||||
mProject = project;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,7 +97,10 @@ class ExtractStringInputPage extends UserInputWizardPage
|
||||
layout.numColumns = 1;
|
||||
content.setLayout(layout);
|
||||
|
||||
mImpl.createControl(content);
|
||||
createStringGroup(content);
|
||||
createResFileGroup(content);
|
||||
|
||||
validatePage();
|
||||
setControl(content);
|
||||
}
|
||||
|
||||
@@ -68,15 +109,18 @@ class ExtractStringInputPage extends UserInputWizardPage
|
||||
* and by which options.
|
||||
*
|
||||
* @param content A composite with a 1-column grid layout
|
||||
* @return The {@link Text} field for the new String ID name.
|
||||
*/
|
||||
public Text createStringGroup(Composite content) {
|
||||
public void createStringGroup(Composite content) {
|
||||
|
||||
final ExtractStringRefactoring ref = getOurRefactoring();
|
||||
|
||||
Group group = new Group(content, SWT.NONE);
|
||||
group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
group.setText("String Replacement");
|
||||
if (ref.getMode() == ExtractStringRefactoring.Mode.EDIT_SOURCE) {
|
||||
group.setText("String Replacement");
|
||||
} else {
|
||||
group.setText("New String");
|
||||
}
|
||||
|
||||
GridLayout layout = new GridLayout();
|
||||
layout.numColumns = 2;
|
||||
@@ -85,7 +129,7 @@ class ExtractStringInputPage extends UserInputWizardPage
|
||||
// line: Textfield for string value (based on selection, if any)
|
||||
|
||||
Label label = new Label(group, SWT.NONE);
|
||||
label.setText("String:");
|
||||
label.setText("String");
|
||||
|
||||
String selectedString = ref.getTokenString();
|
||||
|
||||
@@ -97,7 +141,7 @@ class ExtractStringInputPage extends UserInputWizardPage
|
||||
|
||||
stringValueField.addModifyListener(new ModifyListener() {
|
||||
public void modifyText(ModifyEvent e) {
|
||||
if (mImpl.validatePage()) {
|
||||
if (validatePage()) {
|
||||
ref.setNewStringValue(stringValueField.getText());
|
||||
}
|
||||
}
|
||||
@@ -110,23 +154,76 @@ class ExtractStringInputPage extends UserInputWizardPage
|
||||
// line : Textfield for new ID
|
||||
|
||||
label = new Label(group, SWT.NONE);
|
||||
label.setText("Replace by 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.");
|
||||
}
|
||||
|
||||
final Text stringIdField = new Text(group, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
|
||||
stringIdField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
stringIdField.setText(guessId(selectedString));
|
||||
mStringIdField = new Text(group, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
|
||||
mStringIdField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
mStringIdField.setText(guessId(selectedString));
|
||||
|
||||
ref.setNewStringId(stringIdField.getText().trim());
|
||||
ref.setNewStringId(mStringIdField.getText().trim());
|
||||
|
||||
stringIdField.addModifyListener(new ModifyListener() {
|
||||
mStringIdField.addModifyListener(new ModifyListener() {
|
||||
public void modifyText(ModifyEvent e) {
|
||||
if (mImpl.validatePage()) {
|
||||
ref.setNewStringId(stringIdField.getText().trim());
|
||||
if (validatePage()) {
|
||||
ref.setNewStringId(mStringIdField.getText().trim());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return stringIdField;
|
||||
/**
|
||||
* Creates the lower group with the fields to choose the resource confirmation and
|
||||
* the target XML file.
|
||||
*
|
||||
* @param content A composite with a 1-column grid layout
|
||||
*/
|
||||
private void createResFileGroup(Composite content) {
|
||||
|
||||
Group group = new Group(content, SWT.NONE);
|
||||
group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
group.setText("XML resource to edit");
|
||||
|
||||
GridLayout layout = new GridLayout();
|
||||
layout.numColumns = 2;
|
||||
group.setLayout(layout);
|
||||
|
||||
// line: selection of the res config
|
||||
|
||||
Label label;
|
||||
label = new Label(group, SWT.NONE);
|
||||
label.setText("Configuration:");
|
||||
|
||||
mConfigSelector = new ConfigurationSelector(group);
|
||||
GridData gd = new GridData(2, GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL);
|
||||
gd.widthHint = ConfigurationSelector.WIDTH_HINT;
|
||||
gd.heightHint = ConfigurationSelector.HEIGHT_HINT;
|
||||
mConfigSelector.setLayoutData(gd);
|
||||
OnConfigSelectorUpdated onConfigSelectorUpdated = new OnConfigSelectorUpdated();
|
||||
mConfigSelector.setOnChangeListener(onConfigSelectorUpdated);
|
||||
|
||||
// line: selection of the output file
|
||||
|
||||
label = new Label(group, SWT.NONE);
|
||||
label.setText("Resource file:");
|
||||
|
||||
mResFileCombo = new Combo(group, SWT.DROP_DOWN);
|
||||
mResFileCombo.select(0);
|
||||
mResFileCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
mResFileCombo.addModifyListener(onConfigSelectorUpdated);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,8 +254,251 @@ class ExtractStringInputPage extends UserInputWizardPage
|
||||
return (ExtractStringRefactoring) getRefactoring();
|
||||
}
|
||||
|
||||
public void postValidatePage(ValidationStatus status) {
|
||||
ExtractStringRefactoring ref = getOurRefactoring();
|
||||
ref.setTargetFile(mImpl.getResFileProjPath());
|
||||
/**
|
||||
* Validates fields of the wizard input page. Displays errors as appropriate and
|
||||
* enable the "Next" button (or not) by calling {@link #setPageComplete(boolean)}.
|
||||
*
|
||||
* @return True if the page has been positively validated. It may still have warnings.
|
||||
*/
|
||||
private boolean validatePage() {
|
||||
boolean success = true;
|
||||
|
||||
// Analyze fatal errors.
|
||||
|
||||
String text = mStringIdField.getText().trim();
|
||||
if (text == null || text.length() < 1) {
|
||||
setErrorMessage("Please provide a resource ID.");
|
||||
success = false;
|
||||
} else {
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
boolean ok = i == 0 ?
|
||||
Character.isJavaIdentifierStart(c) :
|
||||
Character.isJavaIdentifierPart(c);
|
||||
if (!ok) {
|
||||
setErrorMessage(String.format(
|
||||
"The resource ID must be a valid Java identifier. The character %1$c at position %2$d is not acceptable.",
|
||||
c, i+1));
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String resFile = mResFileCombo.getText();
|
||||
if (success) {
|
||||
if (resFile == null || resFile.length() == 0) {
|
||||
setErrorMessage("A resource file name is required.");
|
||||
success = false;
|
||||
} else if (!RES_XML_FILE_REGEX.matcher(resFile).matches()) {
|
||||
setErrorMessage("The XML file name is not valid.");
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Analyze info & warnings.
|
||||
|
||||
if (success) {
|
||||
setErrorMessage(null);
|
||||
|
||||
ExtractStringRefactoring ref = getOurRefactoring();
|
||||
|
||||
ref.setTargetFile(resFile);
|
||||
sLastResFilePath.put(mProject.getFullPath().toPortableString(), resFile);
|
||||
|
||||
if (mXmlHelper.isResIdDuplicate(mProject, resFile, text)) {
|
||||
String msg = String.format("There's already a string item called '%1$s' in %2$s.",
|
||||
text, resFile);
|
||||
if (ref.getMode() == ExtractStringRefactoring.Mode.SELECT_NEW_ID) {
|
||||
setErrorMessage(msg);
|
||||
success = false;
|
||||
} else {
|
||||
setMessage(msg, WizardPage.WARNING);
|
||||
}
|
||||
} else if (mProject.findMember(resFile) == null) {
|
||||
setMessage(
|
||||
String.format("File %2$s does not exist and will be created.",
|
||||
text, resFile),
|
||||
WizardPage.INFORMATION);
|
||||
} else {
|
||||
setMessage(null);
|
||||
}
|
||||
}
|
||||
|
||||
setPageComplete(success);
|
||||
return success;
|
||||
}
|
||||
|
||||
public class OnConfigSelectorUpdated implements Runnable, ModifyListener {
|
||||
|
||||
/** Regex pattern to parse a valid res path: it reads (/res/folder-name/)+(filename). */
|
||||
private final Pattern mPathRegex = Pattern.compile(
|
||||
"(/res/[a-z][a-zA-Z0-9_-]+/)(.+)"); //$NON-NLS-1$
|
||||
|
||||
/** Temporary config object used to retrieve the Config Selector value. */
|
||||
private FolderConfiguration mTempConfig = new FolderConfiguration();
|
||||
|
||||
private HashMap<String, TreeSet<String>> mFolderCache =
|
||||
new HashMap<String, TreeSet<String>>();
|
||||
private String mLastFolderUsedInCombo = null;
|
||||
private boolean mInternalConfigChange;
|
||||
private boolean mInternalFileComboChange;
|
||||
|
||||
/**
|
||||
* Callback invoked when the {@link ConfigurationSelector} has been changed.
|
||||
* <p/>
|
||||
* The callback does the following:
|
||||
* <ul>
|
||||
* <li> Examine the current file name to retrieve the XML filename, if any.
|
||||
* <li> Recompute the path based on the configuration selector (e.g. /res/values-fr/).
|
||||
* <li> Examine the path to retrieve all the files in it. Keep those in a local cache.
|
||||
* <li> If the XML filename from step 1 is not in the file list, it's a custom file name.
|
||||
* Insert it and sort it.
|
||||
* <li> Re-populate the file combo with all the choices.
|
||||
* <li> Select the original XML file.
|
||||
*/
|
||||
public void run() {
|
||||
if (mInternalConfigChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get current leafname, if any
|
||||
String leafName = ""; //$NON-NLS-1$
|
||||
String currPath = mResFileCombo.getText();
|
||||
Matcher m = mPathRegex.matcher(currPath);
|
||||
if (m.matches()) {
|
||||
// Note: groups 1 and 2 cannot be null.
|
||||
leafName = m.group(2);
|
||||
currPath = m.group(1);
|
||||
} else {
|
||||
// There was a path but it was invalid. Ignore it.
|
||||
currPath = ""; //$NON-NLS-1$
|
||||
}
|
||||
|
||||
// recreate the res path from the current configuration
|
||||
mConfigSelector.getConfiguration(mTempConfig);
|
||||
StringBuffer sb = new StringBuffer(RES_FOLDER_ABS);
|
||||
sb.append(mTempConfig.getFolderName(ResourceFolderType.VALUES));
|
||||
sb.append('/');
|
||||
|
||||
String newPath = sb.toString();
|
||||
if (newPath.equals(currPath) && newPath.equals(mLastFolderUsedInCombo)) {
|
||||
// Path has not changed. No need to reload.
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all the files at the new path
|
||||
|
||||
TreeSet<String> filePaths = mFolderCache.get(newPath);
|
||||
|
||||
if (filePaths == null) {
|
||||
filePaths = new TreeSet<String>();
|
||||
|
||||
IFolder folder = mProject.getFolder(newPath);
|
||||
if (folder != null && folder.exists()) {
|
||||
try {
|
||||
for (IResource res : folder.members()) {
|
||||
String name = res.getName();
|
||||
if (res.getType() == IResource.FILE && name.endsWith(".xml")) {
|
||||
filePaths.add(newPath + name);
|
||||
}
|
||||
}
|
||||
} catch (CoreException e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
|
||||
mFolderCache.put(newPath, filePaths);
|
||||
}
|
||||
|
||||
currPath = newPath + leafName;
|
||||
if (leafName.length() > 0 && !filePaths.contains(currPath)) {
|
||||
filePaths.add(currPath);
|
||||
}
|
||||
|
||||
// Fill the combo
|
||||
try {
|
||||
mInternalFileComboChange = true;
|
||||
|
||||
mResFileCombo.removeAll();
|
||||
|
||||
for (String filePath : filePaths) {
|
||||
mResFileCombo.add(filePath);
|
||||
}
|
||||
|
||||
int index = -1;
|
||||
if (leafName.length() > 0) {
|
||||
index = mResFileCombo.indexOf(currPath);
|
||||
if (index >= 0) {
|
||||
mResFileCombo.select(index);
|
||||
}
|
||||
}
|
||||
|
||||
if (index == -1) {
|
||||
mResFileCombo.setText(currPath);
|
||||
}
|
||||
|
||||
mLastFolderUsedInCombo = newPath;
|
||||
|
||||
} finally {
|
||||
mInternalFileComboChange = false;
|
||||
}
|
||||
|
||||
// finally validate the whole page
|
||||
validatePage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback invoked when {@link ExtractStringInputPage#mResFileCombo} has been
|
||||
* modified.
|
||||
*/
|
||||
public void modifyText(ModifyEvent e) {
|
||||
if (mInternalFileComboChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
String wsFolderPath = mResFileCombo.getText();
|
||||
|
||||
// This is a custom path, we need to sanitize it.
|
||||
// First it should start with "/res/". Then we need to make sure there are no
|
||||
// relative paths, things like "../" or "./" or even "//".
|
||||
wsFolderPath = wsFolderPath.replaceAll("/+\\.\\./+|/+\\./+|//+|\\\\+|^/+", "/"); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
wsFolderPath = wsFolderPath.replaceAll("^\\.\\./+|^\\./+", ""); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
wsFolderPath = wsFolderPath.replaceAll("/+\\.\\.$|/+\\.$|/+$", ""); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
// We get "res/foo" from selections relative to the project when we want a "/res/foo" path.
|
||||
if (wsFolderPath.startsWith(RES_FOLDER_REL)) {
|
||||
wsFolderPath = RES_FOLDER_ABS + wsFolderPath.substring(RES_FOLDER_REL.length());
|
||||
|
||||
mInternalFileComboChange = true;
|
||||
mResFileCombo.setText(wsFolderPath);
|
||||
mInternalFileComboChange = false;
|
||||
}
|
||||
|
||||
if (wsFolderPath.startsWith(RES_FOLDER_ABS)) {
|
||||
wsFolderPath = wsFolderPath.substring(RES_FOLDER_ABS.length());
|
||||
|
||||
int pos = wsFolderPath.indexOf(AndroidConstants.WS_SEP_CHAR);
|
||||
if (pos >= 0) {
|
||||
wsFolderPath = wsFolderPath.substring(0, pos);
|
||||
}
|
||||
|
||||
String[] folderSegments = wsFolderPath.split(FolderConfiguration.QUALIFIER_SEP);
|
||||
|
||||
if (folderSegments.length > 0) {
|
||||
String folderName = folderSegments[0];
|
||||
|
||||
if (folderName != null && !folderName.equals(wsFolderPath)) {
|
||||
// update config selector
|
||||
mInternalConfigChange = true;
|
||||
mConfigSelector.setConfiguration(folderSegments);
|
||||
mInternalConfigChange = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validatePage();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.ide.eclipse.adt.refactorings.extractstring;
|
||||
|
||||
import com.android.ide.eclipse.adt.wizards.newstring.NewStringHelper;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.common.project.AndroidManifestParser;
|
||||
|
||||
@@ -98,8 +97,8 @@ import java.util.Map;
|
||||
* <li> On success, the wizard is shown, which let the user input the new ID to use.
|
||||
* <li> The wizard sets the user input values into this refactoring instance, e.g. the new string
|
||||
* ID, the XML file to update, etc. The wizard does use the utility method
|
||||
* {@link NewStringHelper#isResIdDuplicate(IProject, String, String)} to check whether the new
|
||||
* ID is already defined in the target XML file.
|
||||
* {@link XmlStringFileHelper#isResIdDuplicate(IProject, String, String)} to check whether
|
||||
* the new ID is already defined in the target XML file.
|
||||
* <li> Once Preview or Finish is selected in the wizard, the
|
||||
* {@link #checkFinalConditions(IProgressMonitor)} is called to double-check the user input
|
||||
* and compute the actual changes.
|
||||
@@ -122,10 +121,23 @@ import java.util.Map;
|
||||
*/
|
||||
public class ExtractStringRefactoring extends Refactoring {
|
||||
|
||||
private enum Mode {
|
||||
public enum Mode {
|
||||
/**
|
||||
* the Extract String refactoring is called on an <em>existing</em> source file.
|
||||
* Its purpose is then to get the selected string of the source and propose to
|
||||
* change it by an XML id. The XML id may be a new one or an existing one.
|
||||
*/
|
||||
EDIT_SOURCE,
|
||||
MAKE_ID,
|
||||
MAKE_NEW_ID
|
||||
/**
|
||||
* The Extract String refactoring is called without any source file.
|
||||
* Its purpose is then to create a new XML string ID or select/modify an existing one.
|
||||
*/
|
||||
SELECT_ID,
|
||||
/**
|
||||
* The Extract String refactoring is called without any source file.
|
||||
* Its purpose is then to create a new XML string ID. The ID must not already exist.
|
||||
*/
|
||||
SELECT_NEW_ID
|
||||
}
|
||||
|
||||
/** The {@link Mode} of operation of the refactoring. */
|
||||
@@ -133,6 +145,8 @@ public class ExtractStringRefactoring extends Refactoring {
|
||||
/** The file model being manipulated.
|
||||
* Value is null when not on {@link Mode#EDIT_SOURCE} mode. */
|
||||
private final IFile mFile;
|
||||
/** The project that contains {@link #mFile} and that contains the target XML file to modify. */
|
||||
private final IProject mProject;
|
||||
/** The start of the selection in {@link #mFile}.
|
||||
* Value is -1 when not on {@link Mode#EDIT_SOURCE} mode. */
|
||||
private final int mSelectionStart;
|
||||
@@ -158,18 +172,29 @@ public class ExtractStringRefactoring extends Refactoring {
|
||||
* used by {@link #createChange(IProgressMonitor)}. */
|
||||
private ArrayList<Change> mChanges;
|
||||
|
||||
private NewStringHelper mHelper = new NewStringHelper();
|
||||
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$
|
||||
|
||||
public ExtractStringRefactoring(Map<String, String> arguments)
|
||||
throws NullPointerException {
|
||||
mMode = Mode.valueOf(arguments.get("mode")); //$NON-NLS-1$
|
||||
mMode = Mode.valueOf(arguments.get(KEY_MODE));
|
||||
|
||||
IPath path = Path.fromPortableString(arguments.get(KEY_PROJECT));
|
||||
mProject = (IProject) ResourcesPlugin.getWorkspace().getRoot().findMember(path);
|
||||
|
||||
if (mMode == Mode.EDIT_SOURCE) {
|
||||
IPath path = Path.fromPortableString(arguments.get("file")); //$NON-NLS-1$
|
||||
path = Path.fromPortableString(arguments.get(KEY_FILE));
|
||||
mFile = (IFile) ResourcesPlugin.getWorkspace().getRoot().findMember(path);
|
||||
mSelectionStart = Integer.parseInt(arguments.get("sel-start")); //$NON-NLS-1$
|
||||
mSelectionEnd = Integer.parseInt(arguments.get("sel-end")); //$NON-NLS-1$
|
||||
mTokenString = arguments.get("tok-esc"); //$NON-NLS-1$
|
||||
|
||||
mSelectionStart = Integer.parseInt(arguments.get(KEY_SEL_START));
|
||||
mSelectionEnd = Integer.parseInt(arguments.get(KEY_SEL_END));
|
||||
mTokenString = arguments.get(KEY_TOK_ESC);
|
||||
} else {
|
||||
mFile = null;
|
||||
mSelectionStart = mSelectionEnd = -1;
|
||||
@@ -179,12 +204,13 @@ public class ExtractStringRefactoring extends Refactoring {
|
||||
|
||||
private Map<String, String> createArgumentMap() {
|
||||
HashMap<String, String> args = new HashMap<String, String>();
|
||||
args.put("mode", mMode.name()); //$NON-NLS-1$
|
||||
args.put(KEY_MODE, mMode.name());
|
||||
args.put(KEY_PROJECT, mProject.getFullPath().toPortableString());
|
||||
if (mMode == Mode.EDIT_SOURCE) {
|
||||
args.put("file", mFile.getFullPath().toPortableString()); //$NON-NLS-1$
|
||||
args.put("sel-start", Integer.toString(mSelectionStart)); //$NON-NLS-1$
|
||||
args.put("sel-end", Integer.toString(mSelectionEnd)); //$NON-NLS-1$
|
||||
args.put("tok-esc", mTokenString); //$NON-NLS-1$
|
||||
args.put(KEY_FILE, mFile.getFullPath().toPortableString());
|
||||
args.put(KEY_SEL_START, Integer.toString(mSelectionStart));
|
||||
args.put(KEY_SEL_END, Integer.toString(mSelectionEnd));
|
||||
args.put(KEY_TOK_ESC, mTokenString);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
@@ -201,6 +227,7 @@ public class ExtractStringRefactoring extends Refactoring {
|
||||
public ExtractStringRefactoring(IFile file, ITextSelection selection) {
|
||||
mMode = Mode.EDIT_SOURCE;
|
||||
mFile = file;
|
||||
mProject = file.getProject();
|
||||
mSelectionStart = selection.getOffset();
|
||||
mSelectionEnd = mSelectionStart + Math.max(0, selection.getLength() - 1);
|
||||
}
|
||||
@@ -209,25 +236,35 @@ 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.
|
||||
*
|
||||
* @param project The project where the target XML file to modify is located. Cannot be null.
|
||||
* @param enforceNew If true the XML ID must be a new one. If false, an existing ID can be
|
||||
* used.
|
||||
*/
|
||||
public ExtractStringRefactoring(boolean enforceNew) {
|
||||
mMode = enforceNew ? Mode.MAKE_NEW_ID : Mode.MAKE_ID;
|
||||
public ExtractStringRefactoring(IProject project, boolean enforceNew) {
|
||||
mMode = enforceNew ? Mode.SELECT_NEW_ID : Mode.SELECT_ID;
|
||||
mFile = null;
|
||||
mProject = project;
|
||||
mSelectionStart = mSelectionEnd = -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @see org.eclipse.ltk.core.refactoring.Refactoring#getName()
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
if (mMode == Mode.SELECT_ID) {
|
||||
return "Create or USe Android String";
|
||||
} else if (mMode == Mode.SELECT_NEW_ID) {
|
||||
return "Create New Android String";
|
||||
}
|
||||
|
||||
return "Extract Android String";
|
||||
}
|
||||
|
||||
public Mode getMode() {
|
||||
return mMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the actual string selected, after UTF characters have been escaped,
|
||||
* good for display.
|
||||
@@ -236,6 +273,10 @@ public class ExtractStringRefactoring extends Refactoring {
|
||||
return mTokenString;
|
||||
}
|
||||
|
||||
public String getXmlStringId() {
|
||||
return mXmlStringId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 1 of 3 of the refactoring:
|
||||
* Checks that the current selection meets the initial condition before the ExtractString
|
||||
@@ -459,7 +500,7 @@ public class ExtractStringRefactoring extends Refactoring {
|
||||
|
||||
// Prepare the change for the XML file.
|
||||
|
||||
if (!mHelper.isResIdDuplicate(mFile.getProject(), mTargetXmlFileWsPath, mXmlStringId)) {
|
||||
if (!mXmlHelper.isResIdDuplicate(mProject, mTargetXmlFileWsPath, mXmlStringId)) {
|
||||
// We actually change it only if the ID doesn't exist yet
|
||||
Change change = createXmlChange((IFile) targetXml, mXmlStringId, mXmlStringValue,
|
||||
status, SubMonitor.convert(monitor, 1));
|
||||
@@ -525,7 +566,7 @@ public class ExtractStringRefactoring extends Refactoring {
|
||||
content.append("<resources>\n"); //$NON-NLS-1$
|
||||
|
||||
edit = new InsertEdit(0, content.toString());
|
||||
editGroup = new TextEditGroup("Create ID in new XML file", edit);
|
||||
editGroup = new TextEditGroup("Create <string> in new XML file", edit);
|
||||
} else {
|
||||
// The file exist. Attempt to parse it as a valid XML document.
|
||||
try {
|
||||
@@ -556,7 +597,7 @@ public class ExtractStringRefactoring extends Refactoring {
|
||||
}
|
||||
|
||||
edit = new ReplaceEdit(offset, len, content.toString());
|
||||
editGroup = new TextEditGroup("Insert ID in XML file", edit);
|
||||
editGroup = new TextEditGroup("Insert <string> in XML file", edit);
|
||||
}
|
||||
} catch (CoreException e) {
|
||||
// Failed to read file. Ignore. Will return null below.
|
||||
@@ -702,8 +743,7 @@ public class ExtractStringRefactoring extends Refactoring {
|
||||
// the FQCN of the R class.
|
||||
String packageName = null;
|
||||
String error = null;
|
||||
IProject proj = unit.getJavaProject().getProject();
|
||||
IResource manifestFile = proj.findMember(AndroidConstants.FN_ANDROID_MANIFEST);
|
||||
IResource manifestFile = mProject.findMember(AndroidConstants.FN_ANDROID_MANIFEST);
|
||||
if (manifestFile == null || manifestFile.getType() != IResource.FILE) {
|
||||
error = "File not found";
|
||||
} else {
|
||||
@@ -895,7 +935,7 @@ public class ExtractStringRefactoring extends Refactoring {
|
||||
mXmlStringId);
|
||||
|
||||
ExtractStringDescriptor desc = new ExtractStringDescriptor(
|
||||
mUnit.getJavaProject().getElementName(), //project
|
||||
mProject.getName(), //project
|
||||
comment, //description
|
||||
comment, //comment
|
||||
createArgumentMap());
|
||||
@@ -919,8 +959,7 @@ public class ExtractStringRefactoring extends Refactoring {
|
||||
* compilation unit. The resource may not exist.
|
||||
*/
|
||||
private IResource getTargetXmlResource(String xmlFileWsPath) {
|
||||
IProject proj = mFile.getProject();
|
||||
IResource resource = proj.getFile(xmlFileWsPath);
|
||||
IResource resource = mProject.getFile(xmlFileWsPath);
|
||||
return resource;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.wizards.newstring;
|
||||
package com.android.ide.eclipse.adt.refactorings.extractstring;
|
||||
|
||||
import com.android.ide.eclipse.common.project.AndroidXPathFactory;
|
||||
|
||||
@@ -35,7 +35,7 @@ import javax.xml.xpath.XPathExpressionException;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class NewStringHelper {
|
||||
class XmlStringFileHelper {
|
||||
|
||||
/** A temporary cache of R.string IDs defined by a given xml file. The key is the
|
||||
* project path of the file, the data is a set of known string Ids for that file. */
|
||||
@@ -43,7 +43,7 @@ public class NewStringHelper {
|
||||
/** An instance of XPath, created lazily on demand. */
|
||||
private XPath mXPath;
|
||||
|
||||
public NewStringHelper() {
|
||||
public XmlStringFileHelper() {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,6 +70,7 @@ public class ReferenceChooserDialog extends SelectionStatusDialog {
|
||||
private FilteredTree mFilteredTree;
|
||||
private Button mNewResButton;
|
||||
private final IProject mProject;
|
||||
private TreeViewer mTreeViewer;
|
||||
|
||||
/**
|
||||
* @param project
|
||||
@@ -159,8 +160,8 @@ public class ReferenceChooserDialog extends SelectionStatusDialog {
|
||||
mFilteredTree.setLayoutData(data);
|
||||
mFilteredTree.setFont(parent.getFont());
|
||||
|
||||
TreeViewer treeViewer = mFilteredTree.getViewer();
|
||||
Tree tree = treeViewer.getTree();
|
||||
mTreeViewer = mFilteredTree.getViewer();
|
||||
Tree tree = mTreeViewer.getTree();
|
||||
|
||||
tree.addSelectionListener(new SelectionListener() {
|
||||
public void widgetDefaultSelected(SelectionEvent e) {
|
||||
@@ -172,9 +173,9 @@ public class ReferenceChooserDialog extends SelectionStatusDialog {
|
||||
}
|
||||
});
|
||||
|
||||
treeViewer.setLabelProvider(new ResourceLabelProvider());
|
||||
treeViewer.setContentProvider(new ResourceContentProvider(false /* fullLevels */));
|
||||
treeViewer.setInput(mResources);
|
||||
mTreeViewer.setLabelProvider(new ResourceLabelProvider());
|
||||
mTreeViewer.setContentProvider(new ResourceContentProvider(false /* fullLevels */));
|
||||
mTreeViewer.setInput(mResources);
|
||||
}
|
||||
|
||||
protected void handleSelection() {
|
||||
@@ -242,8 +243,10 @@ public class ReferenceChooserDialog extends SelectionStatusDialog {
|
||||
// We only support adding new strings right now
|
||||
mNewResButton.setEnabled(type == ResourceType.STRING);
|
||||
|
||||
String title = String.format("New %1$s", type == null ? "Resource" : type.getDisplayName());
|
||||
String title = String.format("New %1$s...",
|
||||
type == null ? "Resource" : type.getDisplayName());
|
||||
mNewResButton.setText(title);
|
||||
mNewResButton.pack();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -259,14 +262,19 @@ public class ReferenceChooserDialog extends SelectionStatusDialog {
|
||||
// We currently only support strings
|
||||
if (type == ResourceType.STRING) {
|
||||
|
||||
ExtractStringRefactoring ref = new ExtractStringRefactoring(true /*enforceNew*/);
|
||||
ExtractStringRefactoring ref = new ExtractStringRefactoring(
|
||||
mProject, true /*enforceNew*/);
|
||||
RefactoringWizard wizard = new ExtractStringWizard(ref, mProject);
|
||||
RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard);
|
||||
try {
|
||||
IWorkbench w = PlatformUI.getWorkbench();
|
||||
op.run(w.getDisplay().getActiveShell(), wizard.getDefaultPageTitle());
|
||||
if (op.run(w.getDisplay().getActiveShell(), wizard.getDefaultPageTitle()) ==
|
||||
IDialogConstants.OK_ID) {
|
||||
mTreeViewer.refresh();
|
||||
|
||||
// TODO Select string
|
||||
// select it if possible
|
||||
setupInitialSelection(type, ref.getXmlStringId());
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
// Interrupted. Pass.
|
||||
}
|
||||
@@ -335,7 +343,9 @@ public class ReferenceChooserDialog extends SelectionStatusDialog {
|
||||
if (resourceName.equals(resourceItem.getName())) {
|
||||
// name of the resource match, we select it,
|
||||
TreePath treePath = new TreePath(new Object[] { resourceType, resourceItem });
|
||||
mFilteredTree.getViewer().setSelection(new TreeSelection(treePath));
|
||||
mFilteredTree.getViewer().setSelection(
|
||||
new TreeSelection(treePath),
|
||||
true /*reveal*/);
|
||||
|
||||
// and we're done.
|
||||
return;
|
||||
@@ -345,7 +355,9 @@ public class ReferenceChooserDialog extends SelectionStatusDialog {
|
||||
// if we get here, the resource type is valid, but the resource is missing.
|
||||
// we select and expand the resource type element.
|
||||
TreePath treePath = new TreePath(new Object[] { resourceType });
|
||||
mFilteredTree.getViewer().setSelection(new TreeSelection(treePath));
|
||||
mFilteredTree.getViewer().setSelection(
|
||||
new TreeSelection(treePath),
|
||||
true /*reveal*/);
|
||||
mFilteredTree.getViewer().setExpandedState(resourceType, true /* expanded */);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,439 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.wizards.newstring;
|
||||
|
||||
|
||||
import com.android.ide.eclipse.adt.ui.ConfigurationSelector;
|
||||
import com.android.ide.eclipse.common.AndroidConstants;
|
||||
import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration;
|
||||
import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
|
||||
import org.eclipse.core.resources.IFolder;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.jface.wizard.WizardPage;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.ModifyEvent;
|
||||
import org.eclipse.swt.events.ModifyListener;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Combo;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Group;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class NewStringBaseImpl {
|
||||
|
||||
public interface INewStringPageCallback {
|
||||
/**
|
||||
* Creates the top group with the field to replace which string and by what
|
||||
* and by which options.
|
||||
*
|
||||
* @param content A composite with a 1-column grid layout
|
||||
* @return The {@link Text} field for the new String ID name.
|
||||
*/
|
||||
public Text createStringGroup(Composite content);
|
||||
|
||||
/** Implements {@link WizardPage#setErrorMessage(String)} */
|
||||
public void setErrorMessage(String newMessage);
|
||||
/** Implements {@link WizardPage#setMessage(String, int)} */
|
||||
public void setMessage(String msg, int type);
|
||||
/** Implements {@link WizardPage#setPageComplete(boolean)} */
|
||||
public void setPageComplete(boolean success);
|
||||
|
||||
public void postValidatePage(ValidationStatus status);
|
||||
}
|
||||
|
||||
public class ValidationStatus {
|
||||
private String mError = null;
|
||||
private String mMessage = null;
|
||||
public int mMessageType = WizardPage.NONE;
|
||||
|
||||
public boolean success() {
|
||||
return getError() != null;
|
||||
}
|
||||
|
||||
public void setError(String error) {
|
||||
mError = error;
|
||||
mMessageType = WizardPage.ERROR;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return mError;
|
||||
}
|
||||
|
||||
public void setMessage(String msg, int type) {
|
||||
mMessage = msg;
|
||||
mMessageType = type;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return mMessage;
|
||||
}
|
||||
|
||||
public int getMessageType() {
|
||||
return mMessageType;
|
||||
}
|
||||
}
|
||||
|
||||
/** Last res file path used, shared across the session instances but specific to the
|
||||
* current project. The default for unknown projects is {@link #DEFAULT_RES_FILE_PATH}. */
|
||||
private static HashMap<String, String> sLastResFilePath = new HashMap<String, String>();
|
||||
|
||||
/** The project where the user selection happened. */
|
||||
private final IProject mProject;
|
||||
/** Text field where the user enters the new ID. */
|
||||
private Text mStringIdField;
|
||||
/** The configuration selector, to select the resource path of the XML file. */
|
||||
private ConfigurationSelector mConfigSelector;
|
||||
/** The combo to display the existing XML files or enter a new one. */
|
||||
private Combo mResFileCombo;
|
||||
|
||||
private NewStringHelper mHelper = new NewStringHelper();
|
||||
|
||||
/** Regex pattern to read a valid res XML file path. It checks that the are 2 folders and
|
||||
* a leaf file name ending with .xml */
|
||||
private static final Pattern RES_XML_FILE_REGEX = Pattern.compile(
|
||||
"/res/[a-z][a-zA-Z0-9_-]+/[^.]+\\.xml"); //$NON-NLS-1$
|
||||
/** Absolute destination folder root, e.g. "/res/" */
|
||||
private static final String RES_FOLDER_ABS =
|
||||
AndroidConstants.WS_RESOURCES + AndroidConstants.WS_SEP;
|
||||
/** Relative destination folder root, e.g. "res/" */
|
||||
private static final String RES_FOLDER_REL =
|
||||
SdkConstants.FD_RESOURCES + AndroidConstants.WS_SEP;
|
||||
|
||||
private static final String DEFAULT_RES_FILE_PATH = "/res/values/strings.xml"; //$NON-NLS-1$
|
||||
|
||||
private final INewStringPageCallback mWizardPage;
|
||||
|
||||
public NewStringBaseImpl(IProject project, INewStringPageCallback wizardPage) {
|
||||
mProject = project;
|
||||
mWizardPage = wizardPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the UI for the new string wizard.
|
||||
*/
|
||||
public void createControl(Composite parent) {
|
||||
mStringIdField = mWizardPage.createStringGroup(parent);
|
||||
createResFileGroup(parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the lower group with the fields to choose the resource confirmation and
|
||||
* the target XML file.
|
||||
*
|
||||
* @param content A composite with a 1-column grid layout
|
||||
*/
|
||||
private void createResFileGroup(Composite content) {
|
||||
|
||||
Group group = new Group(content, SWT.NONE);
|
||||
group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
group.setText("XML resource to edit");
|
||||
|
||||
GridLayout layout = new GridLayout();
|
||||
layout.numColumns = 2;
|
||||
group.setLayout(layout);
|
||||
|
||||
// line: selection of the res config
|
||||
|
||||
Label label;
|
||||
label = new Label(group, SWT.NONE);
|
||||
label.setText("Configuration:");
|
||||
|
||||
mConfigSelector = new ConfigurationSelector(group);
|
||||
GridData gd = new GridData(2, GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL);
|
||||
gd.widthHint = ConfigurationSelector.WIDTH_HINT;
|
||||
gd.heightHint = ConfigurationSelector.HEIGHT_HINT;
|
||||
mConfigSelector.setLayoutData(gd);
|
||||
OnConfigSelectorUpdated onConfigSelectorUpdated = new OnConfigSelectorUpdated();
|
||||
mConfigSelector.setOnChangeListener(onConfigSelectorUpdated);
|
||||
|
||||
// line: selection of the output file
|
||||
|
||||
label = new Label(group, SWT.NONE);
|
||||
label.setText("Resource file:");
|
||||
|
||||
mResFileCombo = new Combo(group, SWT.DROP_DOWN);
|
||||
mResFileCombo.select(0);
|
||||
mResFileCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
mResFileCombo.addModifyListener(onConfigSelectorUpdated);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates fields of the wizard input page. Displays errors as appropriate and
|
||||
* enable the "Next" button (or not) by calling
|
||||
* {@link INewStringPageCallback#setPageComplete(boolean)}.
|
||||
*
|
||||
* @return True if the page has been positively validated. It may still have warnings.
|
||||
*/
|
||||
public boolean validatePage() {
|
||||
ValidationStatus status = new ValidationStatus();
|
||||
|
||||
validateStringFields(status);
|
||||
if (status.success()) {
|
||||
validatePathFields(status);
|
||||
}
|
||||
|
||||
mWizardPage.postValidatePage(status);
|
||||
|
||||
mWizardPage.setErrorMessage(status.getError());
|
||||
mWizardPage.setMessage(status.getMessage(), status.getMessageType());
|
||||
mWizardPage.setPageComplete(status.success());
|
||||
return status.success();
|
||||
}
|
||||
|
||||
public void validateStringFields(ValidationStatus status) {
|
||||
|
||||
String text = mStringIdField.getText().trim();
|
||||
if (text == null || text.length() < 1) {
|
||||
status.setError("Please provide a resource ID to replace with.");
|
||||
} else {
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
boolean ok = i == 0 ?
|
||||
Character.isJavaIdentifierStart(c) :
|
||||
Character.isJavaIdentifierPart(c);
|
||||
if (!ok) {
|
||||
status.setError(String.format(
|
||||
"The resource ID must be a valid Java identifier. The character %1$c at position %2$d is not acceptable.",
|
||||
c, i+1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ValidationStatus validatePathFields(ValidationStatus status) {
|
||||
String resFile = getResFileProjPath();
|
||||
if (resFile == null || resFile.length() == 0) {
|
||||
status.setError("A resource file name is required.");
|
||||
} else if (!RES_XML_FILE_REGEX.matcher(resFile).matches()) {
|
||||
status.setError("The XML file name is not valid.");
|
||||
}
|
||||
|
||||
if (status.success()) {
|
||||
sLastResFilePath.put(mProject.getFullPath().toPortableString(), resFile);
|
||||
|
||||
String text = mStringIdField.getText().trim();
|
||||
|
||||
if (mHelper.isResIdDuplicate(mProject, resFile, text)) {
|
||||
status.setMessage(
|
||||
String.format("There's already a string item called '%1$s' in %2$s.",
|
||||
text, resFile), WizardPage.WARNING);
|
||||
} else if (mProject.findMember(resFile) == null) {
|
||||
status.setMessage(
|
||||
String.format("File %2$s does not exist and will be created.",
|
||||
text, resFile), WizardPage.INFORMATION);
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
public String getResFileProjPath() {
|
||||
return mResFileCombo.getText().trim();
|
||||
}
|
||||
|
||||
public class OnConfigSelectorUpdated implements Runnable, ModifyListener {
|
||||
|
||||
/** Regex pattern to parse a valid res path: it reads (/res/folder-name/)+(filename). */
|
||||
private final Pattern mPathRegex = Pattern.compile(
|
||||
"(/res/[a-z][a-zA-Z0-9_-]+/)(.+)"); //$NON-NLS-1$
|
||||
|
||||
/** Temporary config object used to retrieve the Config Selector value. */
|
||||
private FolderConfiguration mTempConfig = new FolderConfiguration();
|
||||
|
||||
private HashMap<String, TreeSet<String>> mFolderCache =
|
||||
new HashMap<String, TreeSet<String>>();
|
||||
private String mLastFolderUsedInCombo = null;
|
||||
private boolean mInternalConfigChange;
|
||||
private boolean mInternalFileComboChange;
|
||||
|
||||
/**
|
||||
* Callback invoked when the {@link ConfigurationSelector} has been changed.
|
||||
* <p/>
|
||||
* The callback does the following:
|
||||
* <ul>
|
||||
* <li> Examine the current file name to retrieve the XML filename, if any.
|
||||
* <li> Recompute the path based on the configuration selector (e.g. /res/values-fr/).
|
||||
* <li> Examine the path to retrieve all the files in it. Keep those in a local cache.
|
||||
* <li> If the XML filename from step 1 is not in the file list, it's a custom file name.
|
||||
* Insert it and sort it.
|
||||
* <li> Re-populate the file combo with all the choices.
|
||||
* <li> Select the original XML file.
|
||||
*/
|
||||
public void run() {
|
||||
if (mInternalConfigChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get current leafname, if any
|
||||
String leafName = ""; //$NON-NLS-1$
|
||||
String currPath = mResFileCombo.getText();
|
||||
Matcher m = mPathRegex.matcher(currPath);
|
||||
if (m.matches()) {
|
||||
// Note: groups 1 and 2 cannot be null.
|
||||
leafName = m.group(2);
|
||||
currPath = m.group(1);
|
||||
} else {
|
||||
// There was a path but it was invalid. Ignore it.
|
||||
currPath = ""; //$NON-NLS-1$
|
||||
}
|
||||
|
||||
// recreate the res path from the current configuration
|
||||
mConfigSelector.getConfiguration(mTempConfig);
|
||||
StringBuffer sb = new StringBuffer(RES_FOLDER_ABS);
|
||||
sb.append(mTempConfig.getFolderName(ResourceFolderType.VALUES));
|
||||
sb.append('/');
|
||||
|
||||
String newPath = sb.toString();
|
||||
if (newPath.equals(currPath) && newPath.equals(mLastFolderUsedInCombo)) {
|
||||
// Path has not changed. No need to reload.
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all the files at the new path
|
||||
|
||||
TreeSet<String> filePaths = mFolderCache.get(newPath);
|
||||
|
||||
if (filePaths == null) {
|
||||
filePaths = new TreeSet<String>();
|
||||
|
||||
IFolder folder = mProject.getFolder(newPath);
|
||||
if (folder != null && folder.exists()) {
|
||||
try {
|
||||
for (IResource res : folder.members()) {
|
||||
String name = res.getName();
|
||||
if (res.getType() == IResource.FILE && name.endsWith(".xml")) { //$NON-NLS-1$
|
||||
filePaths.add(newPath + name);
|
||||
}
|
||||
}
|
||||
} catch (CoreException e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
|
||||
mFolderCache.put(newPath, filePaths);
|
||||
}
|
||||
|
||||
currPath = newPath + leafName;
|
||||
if (leafName.length() > 0 && !filePaths.contains(currPath)) {
|
||||
filePaths.add(currPath);
|
||||
}
|
||||
|
||||
// Fill the combo
|
||||
try {
|
||||
mInternalFileComboChange = true;
|
||||
|
||||
mResFileCombo.removeAll();
|
||||
|
||||
for (String filePath : filePaths) {
|
||||
mResFileCombo.add(filePath);
|
||||
}
|
||||
|
||||
int index = -1;
|
||||
if (leafName.length() > 0) {
|
||||
index = mResFileCombo.indexOf(currPath);
|
||||
if (index >= 0) {
|
||||
mResFileCombo.select(index);
|
||||
}
|
||||
}
|
||||
|
||||
if (index == -1) {
|
||||
mResFileCombo.setText(currPath);
|
||||
}
|
||||
|
||||
mLastFolderUsedInCombo = newPath;
|
||||
|
||||
} finally {
|
||||
mInternalFileComboChange = false;
|
||||
}
|
||||
|
||||
// finally validate the whole page
|
||||
validatePage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback invoked when {@link NewStringBaseImpl#mResFileCombo} has been
|
||||
* modified.
|
||||
*/
|
||||
public void modifyText(ModifyEvent e) {
|
||||
if (mInternalFileComboChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
String wsFolderPath = mResFileCombo.getText();
|
||||
|
||||
// This is a custom path, we need to sanitize it.
|
||||
// First it should start with "/res/". Then we need to make sure there are no
|
||||
// relative paths, things like "../" or "./" or even "//".
|
||||
wsFolderPath = wsFolderPath.replaceAll("/+\\.\\./+|/+\\./+|//+|\\\\+|^/+", "/"); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
wsFolderPath = wsFolderPath.replaceAll("^\\.\\./+|^\\./+", ""); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
wsFolderPath = wsFolderPath.replaceAll("/+\\.\\.$|/+\\.$|/+$", ""); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
|
||||
// We get "res/foo" from selections relative to the project when we want a "/res/foo" path.
|
||||
if (wsFolderPath.startsWith(RES_FOLDER_REL)) {
|
||||
wsFolderPath = RES_FOLDER_ABS + wsFolderPath.substring(RES_FOLDER_REL.length());
|
||||
|
||||
mInternalFileComboChange = true;
|
||||
mResFileCombo.setText(wsFolderPath);
|
||||
mInternalFileComboChange = false;
|
||||
}
|
||||
|
||||
if (wsFolderPath.startsWith(RES_FOLDER_ABS)) {
|
||||
wsFolderPath = wsFolderPath.substring(RES_FOLDER_ABS.length());
|
||||
|
||||
int pos = wsFolderPath.indexOf(AndroidConstants.WS_SEP_CHAR);
|
||||
if (pos >= 0) {
|
||||
wsFolderPath = wsFolderPath.substring(0, pos);
|
||||
}
|
||||
|
||||
String[] folderSegments = wsFolderPath.split(FolderConfiguration.QUALIFIER_SEP);
|
||||
|
||||
if (folderSegments.length > 0) {
|
||||
String folderName = folderSegments[0];
|
||||
|
||||
if (folderName != null && !folderName.equals(wsFolderPath)) {
|
||||
// update config selector
|
||||
mInternalConfigChange = true;
|
||||
mConfigSelector.setConfiguration(folderSegments);
|
||||
mInternalConfigChange = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validatePage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.wizards.newstring;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.jface.wizard.Wizard;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class NewStringWizard extends Wizard {
|
||||
|
||||
protected static final String MAIN_PAGE_NAME = "newXmlStringPage"; //$NON-NLS-1$
|
||||
|
||||
private NewStringWizardPage mMainPage;
|
||||
|
||||
public NewStringWizard(IProject project) {
|
||||
super();
|
||||
|
||||
mMainPage = createMainPage(project);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the wizard page.
|
||||
* <p/>
|
||||
* Please do NOT override this method.
|
||||
* <p/>
|
||||
* This is protected so that it can be overridden by unit tests.
|
||||
* However the contract of this class is private and NO ATTEMPT will be made
|
||||
* to maintain compatibility between different versions of the plugin.
|
||||
* @param project
|
||||
*/
|
||||
protected NewStringWizardPage createMainPage(IProject project) {
|
||||
return new NewStringWizardPage(project, MAIN_PAGE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPages() {
|
||||
addPage(mMainPage);
|
||||
super.addPages();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.eclipse.jface.wizard.Wizard#performFinish()
|
||||
*/
|
||||
@Override
|
||||
public boolean performFinish() {
|
||||
// pass
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/org/documents/epl-v10.php
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ide.eclipse.adt.wizards.newstring;
|
||||
|
||||
import com.android.ide.eclipse.adt.wizards.newstring.NewStringBaseImpl.INewStringPageCallback;
|
||||
import com.android.ide.eclipse.adt.wizards.newstring.NewStringBaseImpl.ValidationStatus;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.jface.wizard.WizardPage;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.ModifyEvent;
|
||||
import org.eclipse.swt.events.ModifyListener;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Group;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class NewStringWizardPage extends WizardPage implements INewStringPageCallback {
|
||||
|
||||
private NewStringBaseImpl mImpl;
|
||||
|
||||
/** Field displaying the user-selected string to be replaced. */
|
||||
private Label mStringValueField;
|
||||
|
||||
private String mNewStringId;
|
||||
|
||||
public NewStringWizardPage(IProject project, String pageName) {
|
||||
super(pageName);
|
||||
mImpl = new NewStringBaseImpl(project, this);
|
||||
}
|
||||
|
||||
public String getNewStringValue() {
|
||||
return mStringValueField.getText();
|
||||
}
|
||||
|
||||
public String getNewStringId() {
|
||||
return mNewStringId;
|
||||
}
|
||||
|
||||
public String getResFilePathProjPath() {
|
||||
return mImpl.getResFileProjPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the UI for the new string wizard.
|
||||
*/
|
||||
public void createControl(Composite parent) {
|
||||
Composite content = new Composite(parent, SWT.NONE);
|
||||
GridLayout layout = new GridLayout();
|
||||
layout.numColumns = 1;
|
||||
content.setLayout(layout);
|
||||
|
||||
mImpl.createControl(content);
|
||||
setControl(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the top group with the field to replace which string and by what
|
||||
* and by which options.
|
||||
*
|
||||
* @param content A composite with a 1-column grid layout
|
||||
* @return The {@link Text} field for the new String ID name.
|
||||
*/
|
||||
public Text createStringGroup(Composite content) {
|
||||
|
||||
Group group = new Group(content, SWT.NONE);
|
||||
group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
group.setText("New String");
|
||||
|
||||
GridLayout layout = new GridLayout();
|
||||
layout.numColumns = 2;
|
||||
group.setLayout(layout);
|
||||
|
||||
Label label = new Label(group, SWT.NONE);
|
||||
label.setText("String:");
|
||||
|
||||
mStringValueField = new Label(group, SWT.NONE);
|
||||
mStringValueField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
mStringValueField.setText(""); //$NON-NLS-1$
|
||||
|
||||
// TODO provide an option to refactor all known occurences of this string.
|
||||
|
||||
// line : Textfield for new ID
|
||||
|
||||
label = new Label(group, SWT.NONE);
|
||||
label.setText("Replace by R.string.");
|
||||
|
||||
final Text stringIdField = new Text(group, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
|
||||
stringIdField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
|
||||
stringIdField.setText("");
|
||||
|
||||
mNewStringId = stringIdField.getText().trim();
|
||||
|
||||
stringIdField.addModifyListener(new ModifyListener() {
|
||||
public void modifyText(ModifyEvent e) {
|
||||
if (mImpl.validatePage()) {
|
||||
mNewStringId = stringIdField.getText().trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return stringIdField;
|
||||
}
|
||||
|
||||
public void postValidatePage(ValidationStatus status) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user