ADT: Extract String IDs from Layout XML strings.

This commit is contained in:
Raphael
2009-07-10 19:49:31 -04:00
parent fe1b8d8f82
commit 32ad938b85
8 changed files with 689 additions and 191 deletions

View File

@@ -666,6 +666,12 @@
id="com.android.ide.eclipse.adt.refactoring.extract.string"
name="Extract Android String">
</command>
<keyBinding
commandId="com.android.ide.eclipse.adt.refactoring.extract.string"
contextId="org.eclipse.ui.globalScope"
keyConfigurationId="org.eclipse.ui.defaultAcceleratorConfiguration"
keySequence="M3+M2+A S">
</keyBinding>
</extension>
<extension
point="org.eclipse.ltk.core.refactoring.refactoringContributions">

View File

@@ -310,12 +310,12 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
Object[] choices = null;
if (attrInfo.isInValue) {
// Editing an attribute's value... Get the attribute name and then the
// possible choice for the tuple(parent,attribute)
// possible choices for the tuple(parent,attribute)
String value = attrInfo.value;
if (value.startsWith("'") || value.startsWith("\"")) { //$NON-NLS-1$ //$NON-NLS-2$
value = value.substring(1);
// The prefix that was found at the beginning only scan for characters
// valid of tag name. We now know the real prefix for this attribute's
// valid for tag name. We now know the real prefix for this attribute's
// value, which is needed to generate the completion choices below.
attrInfo.correctedPrefix = value;
} else {
@@ -772,7 +772,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
IDescriptorProvider descriptorProvider = data.getDescriptorProvider(mDescriptorId);
if (descriptorProvider != null) {
mRootDescriptor = new ElementDescriptor("",
mRootDescriptor = new ElementDescriptor("", //$NON-NLS-1$
descriptorProvider.getRootElementDescriptors());
}
}

View File

@@ -565,6 +565,11 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
* Callers <em>must</em> call model.releaseFromRead() when done, typically
* in a try..finally clause.
*
* Portability note: this uses getModelManager which is part of wst.sse.core; however
* the interface returned is part of wst.sse.core.internal.provisional so we can
* expect it to change in a distant future if they start cleaning their codebase,
* however unlikely that is.
*
* @return The model for the XML document or null if cannot be obtained from the editor
*/
public final IStructuredModel getModelForRead() {

View File

@@ -32,6 +32,8 @@ import org.eclipse.swt.widgets.Composite;
*/
public final class ReferenceAttributeDescriptor extends TextAttributeDescriptor {
/** The {@link ResourceType} that this reference attribute can accept. It can be null,
* in which case any reference type can be used. */
private ResourceType mResourceType;
/**
@@ -66,6 +68,12 @@ public final class ReferenceAttributeDescriptor extends TextAttributeDescriptor
}
/** Returns the {@link ResourceType} that this reference attribute can accept.
* It can be null, in which case any reference type can be used. */
public ResourceType getResourceType() {
return mResourceType;
}
/**
* @return A new {@link UiResourceAttributeNode} linked to this reference descriptor.
*/

View File

@@ -74,6 +74,7 @@ public class ExtractStringAction implements IWorkbenchWindowActionDelegate {
/** Keep track of the current workbench window. */
private IWorkbenchWindow mWindow;
private ITextSelection mSelection;
private IEditorPart mEditor;
private IFile mFile;
/**
@@ -107,7 +108,8 @@ public class ExtractStringAction implements IWorkbenchWindowActionDelegate {
if (selection instanceof ITextSelection) {
mSelection = (ITextSelection) selection;
if (mSelection.getLength() > 0) {
mFile = getSelectedFile();
mEditor = getActiveEditor();
mFile = getSelectedFile(mEditor);
}
}
@@ -119,7 +121,7 @@ public class ExtractStringAction implements IWorkbenchWindowActionDelegate {
*/
public void run(IAction action) {
if (mSelection != null && mFile != null) {
ExtractStringRefactoring ref = new ExtractStringRefactoring(mFile, mSelection);
ExtractStringRefactoring ref = new ExtractStringRefactoring(mFile, mEditor, mSelection);
RefactoringWizard wizard = new ExtractStringWizard(ref, mFile.getProject());
RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard);
try {
@@ -130,6 +132,21 @@ public class ExtractStringAction implements IWorkbenchWindowActionDelegate {
}
}
/**
* Returns the active editor (hopefully matching our selection) or null.
*/
private IEditorPart getActiveEditor() {
IWorkbenchWindow wwin = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
if (wwin != null) {
IWorkbenchPage page = wwin.getActivePage();
if (page != null) {
return page.getActiveEditor();
}
}
return null;
}
/**
* Returns the active {@link IFile} (hopefully matching our selection) or null.
* The file is only returned if it's a file from a project with an Android nature.
@@ -138,12 +155,7 @@ public class ExtractStringAction implements IWorkbenchWindowActionDelegate {
* for the refactoring. This check is performed when the refactoring is invoked since
* it can then produce meaningful error messages as needed.
*/
private IFile getSelectedFile() {
IWorkbenchWindow wwin = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
if (wwin != null) {
IWorkbenchPage page = wwin.getActivePage();
if (page != null) {
IEditorPart editor = page.getActiveEditor();
private IFile getSelectedFile(IEditorPart editor) {
if (editor != null) {
IEditorInput input = editor.getEditorInput();
@@ -162,8 +174,6 @@ public class ExtractStringAction implements IWorkbenchWindowActionDelegate {
}
}
}
}
}
return null;
}

View File

@@ -17,8 +17,17 @@
package com.android.ide.eclipse.adt.internal.refactorings.extractstring;
import com.android.ide.eclipse.adt.AndroidConstants;
import com.android.ide.eclipse.adt.internal.editors.AndroidEditor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ReferenceAttributeDescriptor;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import com.android.ide.eclipse.adt.internal.project.AndroidManifestParser;
import com.android.ide.eclipse.adt.internal.resources.ResourceType;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType;
import com.android.sdklib.SdkConstants;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
@@ -63,6 +72,16 @@ import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.TextEditGroup;
import org.eclipse.ui.IEditorPart;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
import org.w3c.dom.Node;
import java.io.BufferedReader;
import java.io.IOException;
@@ -70,6 +89,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -85,11 +105,6 @@ import java.util.Map;
* <li> The action finds the {@link ICompilationUnit} being edited as well as the current
* {@link ITextSelection}. The action creates a new instance of this refactoring as
* well as an {@link ExtractStringWizard} and runs the operation.
* <li> TODO: to support refactoring from an XML file, the action should give the {@link IFile}
* and then here we would have to determine whether it's a suitable Android XML file or a
* suitable Java file.
* TODO: enumerate the exact valid contexts in Android XML files, e.g. attributes in layout
* files or text elements (e.g. <string>foo</string>) for values, etc.
* <li> Step 1 of the refactoring is to check the preliminary conditions. Right now we check
* that the java source is not read-only and is in sync. We also try to find a string under
* the selection. If this fails, the refactoring is aborted.
@@ -113,8 +128,6 @@ import java.util.Map;
* <li> Create an AST rewriter to edit the source Java file and replace all occurences by the
* new computed R.string.foo. Also need to rewrite imports to import R as needed.
* If there's already a conflicting R included, we need to insert the FQCN instead.
* <li> TODO: If the source is an XML file, determine if we need to change an attribute or a
* a text element.
* <li> TODO: Have a pref in the wizard: [x] Change other XML Files
* <li> TODO: Have a pref in the wizard: [x] Change other Java Files
* </ul>
@@ -142,9 +155,14 @@ public class ExtractStringRefactoring extends Refactoring {
/** The {@link Mode} of operation of the refactoring. */
private final Mode mMode;
/** Non-null when editing an Android Resource XML file: identifies the attribute name
* of the value being edited. When null, the source is an Android Java file. */
private String mXmlAttributeName;
/** The file model being manipulated.
* Value is null when not on {@link Mode#EDIT_SOURCE} mode. */
private final IFile mFile;
/** The editor. Non-null when invoked from {@link ExtractStringAction}. Null otherwise. */
private final IEditorPart mEditor;
/** 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}.
@@ -180,9 +198,9 @@ public class ExtractStringRefactoring extends Refactoring {
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$
public ExtractStringRefactoring(Map<String, String> arguments)
throws NullPointerException {
public ExtractStringRefactoring(Map<String, String> arguments) throws NullPointerException {
mMode = Mode.valueOf(arguments.get(KEY_MODE));
IPath path = Path.fromPortableString(arguments.get(KEY_PROJECT));
@@ -195,11 +213,15 @@ public class ExtractStringRefactoring extends Refactoring {
mSelectionStart = Integer.parseInt(arguments.get(KEY_SEL_START));
mSelectionEnd = Integer.parseInt(arguments.get(KEY_SEL_END));
mTokenString = arguments.get(KEY_TOK_ESC);
mXmlAttributeName = arguments.get(KEY_XML_ATTR_NAME);
} else {
mFile = null;
mSelectionStart = mSelectionEnd = -1;
mTokenString = null;
mXmlAttributeName = null;
}
mEditor = null;
}
private Map<String, String> createArgumentMap() {
@@ -211,6 +233,7 @@ public class ExtractStringRefactoring extends Refactoring {
args.put(KEY_SEL_START, Integer.toString(mSelectionStart));
args.put(KEY_SEL_END, Integer.toString(mSelectionEnd));
args.put(KEY_TOK_ESC, mTokenString);
args.put(KEY_XML_ATTR_NAME, mXmlAttributeName);
}
return args;
}
@@ -222,11 +245,13 @@ public class ExtractStringRefactoring extends Refactoring {
* or an existing one.
*
* @param file The source file to process. Cannot be null. File must exist in workspace.
* @param editor
* @param selection The selection in the source file. Cannot be null or empty.
*/
public ExtractStringRefactoring(IFile file, ITextSelection selection) {
public ExtractStringRefactoring(IFile file, IEditorPart editor, ITextSelection selection) {
mMode = Mode.EDIT_SOURCE;
mFile = file;
mEditor = editor;
mProject = file.getProject();
mSelectionStart = selection.getOffset();
mSelectionEnd = mSelectionStart + Math.max(0, selection.getLength() - 1);
@@ -243,6 +268,7 @@ public class ExtractStringRefactoring extends Refactoring {
public ExtractStringRefactoring(IProject project, boolean enforceNew) {
mMode = enforceNew ? Mode.SELECT_NEW_ID : Mode.SELECT_ID;
mFile = null;
mEditor = null;
mProject = project;
mSelectionStart = mSelectionEnd = -1;
}
@@ -304,10 +330,10 @@ public class ExtractStringRefactoring extends Refactoring {
RefactoringStatus status = new RefactoringStatus();
try {
monitor.beginTask("Checking preconditions...", 5);
monitor.beginTask("Checking preconditions...", 6);
if (mMode != Mode.EDIT_SOURCE) {
monitor.worked(5);
monitor.worked(6);
return status;
}
@@ -334,11 +360,36 @@ public class ExtractStringRefactoring extends Refactoring {
// That was not a Java file. Ignore.
}
if (mUnit == null) {
// Check this an XML file and get the selection and its context.
// TODO
status.addFatalError("Selection must be inside a Java source file.");
if (mUnit != null) {
monitor.worked(1);
return status;
}
// Check this a Layout XML file and get the selection and its context.
if (mFile != null && AndroidConstants.EXT_XML.equals(mFile.getFileExtension())) {
// Currently we only support Android resource XML files, so they must have a path
// similar to
// project/res/<type>[-<configuration>]/*.xml
// There is no support for sub folders, so the segment count must be 4.
// We don't need to check the type folder name because a/ we only accept
// an AndroidEditor source and b/ aapt generates a compilation error for
// unknown folders.
IPath path = mFile.getFullPath();
// check if we are inside the project/res/* folder.
if (path.segmentCount() == 4) {
if (path.segment(1).equalsIgnoreCase(SdkConstants.FD_RESOURCES)) {
if (!findSelectionInXmlFile(mFile, status, monitor)) {
return status;
}
}
}
}
if (!status.isOK()) {
status.addFatalError("Selection must be inside a Java source or an Android Layout XML file.");
}
} finally {
monitor.done();
}
@@ -353,6 +404,7 @@ public class ExtractStringRefactoring extends Refactoring {
* to the status.
*
* On success, advance the monitor by 3.
* Returns status.isOK().
*/
private boolean findSelectionInJavaUnit(ICompilationUnit unit,
RefactoringStatus status, IProgressMonitor monitor) {
@@ -373,7 +425,7 @@ public class ExtractStringRefactoring extends Refactoring {
token = scanner.getNextToken()) {
if (scanner.getCurrentTokenStartPosition() <= mSelectionStart &&
scanner.getCurrentTokenEndPosition() >= mSelectionEnd) {
// found the token, but only keep of the right type
// found the token, but only keep if the right type
if (token == ITerminalSymbols.TokenNameStringLiteral) {
mTokenString = new String(scanner.getCurrentTokenSource());
}
@@ -412,6 +464,205 @@ public class ExtractStringRefactoring extends Refactoring {
monitor.worked(1);
return status.isOK();
}
/**
* Try to find the selected XML element. This implementation replies on the refactoring
* originating from an Android Layout Editor. We rely on some internal properties of the
* Structured XML editor to retrieve file content to avoid parsing it again. We also rely
* on our specific Android XML model to get element & attribute descriptor properties.
*
* If selection matches a string literal, capture it, otherwise add a fatal error
* to the status.
*
* On success, advance the monitor by 1.
* Returns status.isOK().
*/
private boolean findSelectionInXmlFile(IFile file,
RefactoringStatus status,
IProgressMonitor monitor) {
try {
if (!(mEditor instanceof AndroidEditor)) {
status.addFatalError("Only the Android XML Editor is currently supported.");
return status.isOK();
}
AndroidEditor editor = (AndroidEditor) mEditor;
IStructuredModel smodel = null;
Node node = null;
String attrName = null;
try {
// See the portability note in AndroidEditor#getModelForRead() javadoc.
smodel = editor.getModelForRead();
if (smodel != null) {
// The structured model gives the us the actual XML Node element where the
// offset is. By using this Node, we can find the exact UiElementNode of our
// model and thus we'll be able to get the properties of the attribute -- to
// check if it accepts a string reference. This does not however tell us if
// the selection is actually in an attribute value, nor which attribute is
// being edited.
for(int offset = mSelectionStart; offset >= 0 && node == null; --offset) {
node = (Node) smodel.getIndexedRegion(offset);
}
if (node == null) {
status.addFatalError("The selection does not match any element in the XML document.");
return status.isOK();
}
if (node.getNodeType() != Node.ELEMENT_NODE) {
status.addFatalError("The selection is not inside an actual XML element.");
return status.isOK();
}
IStructuredDocument sdoc = smodel.getStructuredDocument();
if (sdoc != null) {
// Portability note: all the structured document implementation is
// under wst.sse.core.internal.provisional so we can expect it to change in
// a distant future if they start cleaning their codebase, however unlikely
// that is.
int selStart = mSelectionStart;
IStructuredDocumentRegion region =
sdoc.getRegionAtCharacterOffset(selStart);
if (region != null &&
DOMRegionContext.XML_TAG_NAME.equals(region.getType())) {
// The region gives us the textual representation of the XML element
// where the selection starts, split using sub-regions. We now just
// need to iterate through the sub-regions to find which one
// contains the actual selection. We're interested in an attribute
// value however when we find one we want to memorize the attribute
// name that was defined just before.
int startInRegion = selStart - region.getStartOffset();
int nb = region.getNumberOfRegions();
ITextRegionList list = region.getRegions();
for (int i = 0; i < nb; i++) {
ITextRegion subRegion = list.get(i);
String type = subRegion.getType();
if (DOMRegionContext.XML_TAG_ATTRIBUTE_NAME.equals(type)) {
attrName = region.getText(subRegion);
}
if (subRegion.getStart() <= startInRegion &&
startInRegion <= subRegion.getEnd() &&
DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE.equals(type)) {
// We found the value. Only accept it if not empty
// and if we found an attribute name before.
String text = region.getText(subRegion);
// The attribute value will contain the XML quotes. Remove them.
int len = text.length();
if (len >= 2 &&
text.charAt(0) == '"' &&
text.charAt(len - 1) == '"') {
text = text.substring(1, len - 1);
} else if (len >= 2 &&
text.charAt(0) == '\'' &&
text.charAt(len - 1) == '\'') {
text = text.substring(1, len - 1);
}
if (text.length() > 0 && attrName != null) {
mTokenString = text;
}
break;
}
}
if (mTokenString == null) {
status.addFatalError(
"The selection is not inside an actual XML attribute value.");
}
}
}
if (mTokenString != null && node != null && attrName != null) {
UiElementNode rootUiNode = editor.getUiRootNode();
UiElementNode currentUiNode =
rootUiNode == null ? null : rootUiNode.findXmlNode(node);
ReferenceAttributeDescriptor attrDesc = null;
if (currentUiNode != null) {
// remove any namespace prefix from the attribute name
String name = attrName;
int pos = name.indexOf(':');
if (pos > 0 && pos < name.length() - 1) {
name = name.substring(pos + 1);
}
for (UiAttributeNode attrNode : currentUiNode.getUiAttributes()) {
if (attrNode.getDescriptor().getXmlLocalName().equals(name)) {
AttributeDescriptor desc = attrNode.getDescriptor();
if (desc instanceof ReferenceAttributeDescriptor) {
attrDesc = (ReferenceAttributeDescriptor) desc;
}
break;
}
}
}
// The attribute descriptor is a resource reference. It must either accept
// of any resource type or specifically accept string types.
if (attrDesc != null &&
(attrDesc.getResourceType() == null ||
attrDesc.getResourceType() == ResourceType.STRING)) {
// We have one more check to do: is the current string value already
// an Android XML string reference? If so, we can't edit it.
if (mTokenString.startsWith("@")) { //$NON-NLS-1$
int pos1 = 0;
if (mTokenString.length() > 1 && mTokenString.charAt(1) == '+') {
pos1++;
}
int pos2 = mTokenString.indexOf('/');
if (pos2 > pos1) {
String kind = mTokenString.substring(pos1 + 1, pos2);
mTokenString = null;
status.addFatalError(String.format(
"The attribute %1$s already contains a %2$s reference.",
attrName,
kind));
}
}
if (mTokenString != null) {
// We're done with all our checks. mTokenString contains the
// current attribute value. We don't memorize the region nor the
// attribute, however we memorize the textual attribute name so
// that we can offer replacement for all its occurrences.
mXmlAttributeName = attrName;
}
} else {
mTokenString = null;
status.addFatalError(String.format(
"The attribute %1$s does not accept a string reference.",
attrName));
}
} else {
// We shouldn't get here: we're missing one of the token string, the node
// or the attribute name. All of them have been checked earlier so don't
// set any specific error.
mTokenString = null;
}
}
} finally {
if (smodel != null) {
smodel.releaseFromRead();
}
}
} finally {
monitor.worked(1);
}
return status.isOK();
}
/**
* Tests from org.eclipse.jdt.internal.corext.refactoringChecks#validateEdit()
@@ -514,9 +765,18 @@ public class ExtractStringRefactoring extends Refactoring {
}
if (mMode == Mode.EDIT_SOURCE) {
List<Change> changes = null;
if (mXmlAttributeName != null) {
// Prepare the change to the Android resource XML file
changes = computeXmlSourceChanges(mFile,
mXmlStringId, mTokenString, mXmlAttributeName,
status, monitor);
} else {
// Prepare the change to the Java compilation unit
List<Change> changes = computeJavaChanges(mUnit, mXmlStringId, mTokenString,
changes = computeJavaChanges(mUnit, mXmlStringId, mTokenString,
status, SubMonitor.convert(monitor, 1));
}
if (changes != null) {
mChanges.addAll(changes);
}
@@ -634,7 +894,7 @@ public class ExtractStringRefactoring extends Refactoring {
* We need to deal with the case where the element is written as <resources/>, in
* which case the caller will want to replace /> by ">...</...>". To do that we return
* two values: the first offset of the closing tag (e.g. / or >) and the length, which
* can only be 1 or 2. If it's 2, the caller have to deal with /> instead of just >.
* can only be 1 or 2. If it's 2, the caller has to deal with /> instead of just >.
*
* @param contents An existing buffer to parse.
* @param tag The tag to look for.
@@ -730,6 +990,205 @@ public class ExtractStringRefactoring extends Refactoring {
return false;
}
/**
* Computes the changes to be made to the source Android XML file(s) and
* returns a list of {@link Change}.
*/
private List<Change> computeXmlSourceChanges(IFile sourceFile,
String xmlStringId,
String tokenString,
String xmlAttrName,
RefactoringStatus status,
IProgressMonitor monitor) {
if (!sourceFile.exists()) {
status.addFatalError(String.format("XML file '%1$s' does not exist.",
sourceFile.getFullPath().toOSString()));
return null;
}
// In the initial condition check we validated that this file is part of
// an Android resource folder, with a folder path that looks like
// /project/res/<type>-<configuration>/<filename.xml>
// Here we are going to offer XML source change for the same filename accross all
// configurations of the same res type. E.g. if we're processing a res/layout/main.xml
// file then we want to offer changes for res/layout-fr/main.xml. We compute such a
// list here.
HashSet<IFile> files = new HashSet<IFile>();
files.add(sourceFile);
if (AndroidConstants.EXT_XML.equals(sourceFile.getFileExtension())) {
IPath path = sourceFile.getFullPath();
if (path.segmentCount() == 4 && path.segment(1).equals(SdkConstants.FD_RESOURCES)) {
IProject project = sourceFile.getProject();
String filename = path.segment(3);
String initialTypeName = path.segment(2);
ResourceFolderType type = ResourceFolderType.getFolderType(initialTypeName);
IContainer res = sourceFile.getParent().getParent();
if (type != null && res != null && res.getType() == IResource.FOLDER) {
try {
for (IResource r : res.members()) {
if (r != null && r.getType() == IResource.FOLDER) {
String name = r.getName();
// Skip the initial folder name, it's already in the list.
if (!name.equals(initialTypeName)) {
// Only accept the same folder type (e.g. layout-*)
ResourceFolderType t =
ResourceFolderType.getFolderType(initialTypeName);
if (t == type) {
// recompute the path
IPath p = res.getFullPath().append(name).append(filename);
IResource f = project.findMember(p);
if (f != null && f instanceof IFile) {
files.add((IFile) f);
}
}
}
}
}
} catch (CoreException e) {
// Ignore.
}
}
}
}
SubMonitor subMonitor = SubMonitor.convert(monitor, Math.min(1, files.size()));
ArrayList<Change> changes = new ArrayList<Change>();
try {
// Portability note: getModelManager is part of wst.sse.core however the
// interface returned is part of wst.sse.core.internal.provisional so we can
// expect it to change in a distant future if they start cleaning their codebase,
// however unlikely that is.
IModelManager modelManager = StructuredModelManager.getModelManager();
for (IFile file : files) {
IStructuredDocument sdoc = modelManager.createStructuredDocumentFor(file);
if (sdoc == null) {
status.addFatalError("XML structured document not found"); //$NON-NLS-1$
return null;
}
TextFileChange xmlChange = new TextFileChange(getName(), file);
xmlChange.setTextType("xml"); //$NON-NLS-1$
MultiTextEdit multiEdit = new MultiTextEdit();
ArrayList<TextEditGroup> editGroups = new ArrayList<TextEditGroup>();
String quotedReplacement = quotedAttrValue("@string/" + xmlStringId);
// Prepare the change set
try {
for (IStructuredDocumentRegion region : sdoc.getStructuredDocumentRegions()) {
// Only look at XML "top regions"
if (!DOMRegionContext.XML_TAG_NAME.equals(region.getType())) {
continue;
}
int nb = region.getNumberOfRegions();
ITextRegionList list = region.getRegions();
String lastAttrName = null;
for (int i = 0; i < nb; i++) {
ITextRegion subRegion = list.get(i);
String type = subRegion.getType();
if (DOMRegionContext.XML_TAG_ATTRIBUTE_NAME.equals(type)) {
// Memorize the last attribute name seen
lastAttrName = region.getText(subRegion);
} else if (DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE.equals(type)) {
// Check this is the attribute and the original string
String text = region.getText(subRegion);
int len = text.length();
if (len >= 2 &&
text.charAt(0) == '"' &&
text.charAt(len - 1) == '"') {
text = text.substring(1, len - 1);
} else if (len >= 2 &&
text.charAt(0) == '\'' &&
text.charAt(len - 1) == '\'') {
text = text.substring(1, len - 1);
}
if (xmlAttrName.equals(lastAttrName) && tokenString.equals(text)) {
// Found an occurrence. Create a change for it.
TextEdit edit = new ReplaceEdit(
region.getStartOffset() + subRegion.getStart(),
subRegion.getTextLength(),
quotedReplacement);
TextEditGroup editGroup = new TextEditGroup(
"Replace attribute string by ID",
edit);
multiEdit.addChild(edit);
editGroups.add(editGroup);
}
}
}
}
} catch (Throwable t) {
// Since we use some internal APIs, use a broad catch-all to report any
// unexpected issue rather than crash the whole refactoring.
status.addFatalError(
String.format("XML refactoring error: %1$s", t.getMessage()));
} finally {
if (multiEdit.hasChildren()) {
xmlChange.setEdit(multiEdit);
for (TextEditGroup group : editGroups) {
xmlChange.addTextEditChangeGroup(
new TextEditChangeGroup(xmlChange, group));
}
changes.add(xmlChange);
}
subMonitor.worked(1);
}
} // for files
} catch (IOException e) {
status.addFatalError(String.format("XML model IO error: %1$s.", e.getMessage()));
} catch (CoreException e) {
status.addFatalError(String.format("XML model core error: %1$s.", e.getMessage()));
} finally {
if (changes.size() > 0) {
return changes;
}
}
return null;
}
/**
* Returns a quoted attribute value suitable to be placed after an attributeName=
* statement in an XML stream.
*
* According to http://www.w3.org/TR/2008/REC-xml-20081126/#NT-AttValue
* the attribute value can be either quoted using ' or " and the corresponding
* entities &apos; or &quot; must be used inside.
*/
private String quotedAttrValue(String attrValue) {
if (attrValue.indexOf('"') == -1) {
// no double-quotes inside, use double-quotes around.
return '"' + attrValue + '"';
}
if (attrValue.indexOf('\'') == -1) {
// no single-quotes inside, use single-quotes around.
return '\'' + attrValue + '\'';
}
// If we get here, there's a mix. Opt for double-quote around and replace
// inner double-quotes.
attrValue = attrValue.replace("\"", "&quot;"); //$NON-NLS-1$ //$NON-NLS-2$
return '"' + attrValue + '"';
}
/**
* Computes the changes to be made to Java file(s) and returns a list of {@link Change}.
*/
@@ -766,7 +1225,8 @@ public class ExtractStringRefactoring extends Refactoring {
if (error != null) {
status.addFatalError(
String.format("Failed to parse file %1$s: %2$s.",
manifestFile.getFullPath(), error));
manifestFile == null ? "" : manifestFile.getFullPath(), //$NON-NLS-1$
error));
return null;
}
@@ -856,12 +1316,12 @@ public class ExtractStringRefactoring extends Refactoring {
// TextFileChange and accumulate in changes. Right now only one source is
// modified.
subMonitor.worked(1);
if (changes.size() > 0) {
return changes;
}
subMonitor.worked(1);
} catch (CoreException e) {
// ImportRewrite.rewriteImports failed.
status.addFatalError(e.getMessage());

View File

@@ -366,7 +366,7 @@ public final class FolderConfiguration implements Comparable<FolderConfiguration
}
}
return result.toString();
return result == null ? null : result.toString();
}
public int compareTo(FolderConfiguration folderConfig) {

View File

@@ -1,5 +1,14 @@
#!/bin/bash
HOST=`uname`
if [ "${HOST:0:6}" == "CYGWIN" ]; then
if [ "x$1" == "x" ] || [ `basename "$1"` != "layoutlib.jar" ]; then
echo "Usage: $0 sdk/platforms/xxx/data/layoutlib.jar"
echo "Argument 1 should be the path to the layoutlib.jar that should be updated by create_bridge_symlinks.sh."
exit 1
fi
fi
echo "### $0 executing"
function die() {