Merge change 2472 into donut

* changes:
  ADT #1877529: Fixes a couple potential NPEs in content assists.
This commit is contained in:
Android (Google) Code Review
2009-05-26 15:26:00 -07:00

View File

@@ -16,6 +16,7 @@
package com.android.ide.eclipse.adt.internal.editors; package com.android.ide.eclipse.adt.internal.editors;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
@@ -30,6 +31,7 @@ import com.android.ide.eclipse.adt.internal.editors.uimodel.UiFlagAttributeNode;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.sdklib.SdkConstants; import com.android.sdklib.SdkConstants;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.ITextViewer;
@@ -74,7 +76,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
/** Regexp to detect an element tag name */ /** Regexp to detect an element tag name */
private static Pattern sFirstElementWord = Pattern.compile("^[a-zA-Z0-9_:]+"); //$NON-NLS-1$ private static Pattern sFirstElementWord = Pattern.compile("^[a-zA-Z0-9_:]+"); //$NON-NLS-1$
/** Regexp to detect whitespace */ /** Regexp to detect whitespace */
private static Pattern sWhitespace = Pattern.compile("\\s+"); //$NON-NLS-1$ private static Pattern sWhitespace = Pattern.compile("\\s+"); //$NON-NLS-1$
@@ -86,11 +88,11 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
private ElementDescriptor mRootDescriptor; private ElementDescriptor mRootDescriptor;
private final int mDescriptorId; private final int mDescriptorId;
private AndroidEditor mEditor; private AndroidEditor mEditor;
/** /**
* Constructor for AndroidContentAssist * Constructor for AndroidContentAssist
* @param descriptorId An id for {@link AndroidTargetData#getDescriptorProvider(int)}. * @param descriptorId An id for {@link AndroidTargetData#getDescriptorProvider(int)}.
* The Id can be one of {@link AndroidTargetData#DESCRIPTOR_MANIFEST}, * The Id can be one of {@link AndroidTargetData#DESCRIPTOR_MANIFEST},
* {@link AndroidTargetData#DESCRIPTOR_LAYOUT}, * {@link AndroidTargetData#DESCRIPTOR_LAYOUT},
@@ -110,17 +112,22 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
* @param viewer the viewer whose document is used to compute the proposals * @param viewer the viewer whose document is used to compute the proposals
* @param offset an offset within the document for which completions should be computed * @param offset an offset within the document for which completions should be computed
* @return an array of completion proposals or <code>null</code> if no proposals are possible * @return an array of completion proposals or <code>null</code> if no proposals are possible
* *
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeCompletionProposals(org.eclipse.jface.text.ITextViewer, int) * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeCompletionProposals(org.eclipse.jface.text.ITextViewer, int)
*/ */
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
if (mEditor == null) { if (mEditor == null) {
mEditor = getAndroidEditor(viewer); mEditor = getAndroidEditor(viewer);
if (mEditor == null) {
// This should not happen. Duck and forget.
AdtPlugin.log(IStatus.ERROR, "Editor not found during completion");
return null;
}
} }
UiElementNode rootUiNode = mEditor.getUiRootNode(); UiElementNode rootUiNode = mEditor.getUiRootNode();
Object[] choices = null; /* An array of ElementDescriptor, or AttributeDescriptor Object[] choices = null; /* An array of ElementDescriptor, or AttributeDescriptor
or String or null */ or String or null */
String parent = ""; //$NON-NLS-1$ String parent = ""; //$NON-NLS-1$
@@ -136,7 +143,12 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
// check to see if we can find a UiElementNode matching this XML node // check to see if we can find a UiElementNode matching this XML node
UiElementNode currentUiNode = UiElementNode currentUiNode =
rootUiNode == null ? null : rootUiNode.findXmlNode(currentNode); rootUiNode == null ? null : rootUiNode.findXmlNode(currentNode);
if (currentNode == null) {
// Should not happen (an XML doc always has at least a doc node). Just give up.
return null;
}
if (currentNode.getNodeType() == Node.ELEMENT_NODE) { if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
parent = currentNode.getNodeName(); parent = currentNode.getNodeName();
@@ -154,7 +166,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
// We're editing attributes in an element node (either the attributes' names // We're editing attributes in an element node (either the attributes' names
// or their values). // or their values).
choices = getChoicesForAttribute(parent, currentNode, currentUiNode, info); choices = getChoicesForAttribute(parent, currentNode, currentUiNode, info);
if (info.correctedPrefix != null) { if (info.correctedPrefix != null) {
wordPrefix = info.correctedPrefix; wordPrefix = info.correctedPrefix;
} }
@@ -180,7 +192,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
needTag = '<'; needTag = '<';
} }
} }
// get the selection length // get the selection length
int selectionLength = 0; int selectionLength = 0;
ISelection selection = viewer.getSelectionProvider().getSelection(); ISelection selection = viewer.getSelectionProvider().getSelection();
@@ -196,7 +208,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
/** /**
* Returns the namespace prefix matching the Android Resource URI. * Returns the namespace prefix matching the Android Resource URI.
* If no such declaration is found, returns the default "android" prefix. * If no such declaration is found, returns the default "android" prefix.
* *
* @param node The current node. Must not be null. * @param node The current node. Must not be null.
* @param nsUri The namespace URI of which the prefix is to be found, * @param nsUri The namespace URI of which the prefix is to be found,
* e.g. {@link SdkConstants#NS_RESOURCES} * e.g. {@link SdkConstants#NS_RESOURCES}
@@ -210,9 +222,9 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
if (XmlnsAttributeDescriptor.XMLNS_URI.equals(nsUri)) { if (XmlnsAttributeDescriptor.XMLNS_URI.equals(nsUri)) {
return "xmlns"; //$NON-NLS-1$ return "xmlns"; //$NON-NLS-1$
} }
HashSet<String> visited = new HashSet<String>(); HashSet<String> visited = new HashSet<String>();
String prefix = null; String prefix = null;
for (; prefix == null && for (; prefix == null &&
node != null && node != null &&
@@ -230,7 +242,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
} }
} }
} }
// Use a sensible default prefix if we can't find one. // Use a sensible default prefix if we can't find one.
// We need to make sure the prefix is not one that was declared in the scope // We need to make sure the prefix is not one that was declared in the scope
// visited above. // visited above.
@@ -251,7 +263,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
* <p/> * <p/>
* Example: <manifest><applic*cursor* => returns the list of all elements that * Example: <manifest><applic*cursor* => returns the list of all elements that
* can be found under <manifest>, of which <application> is one of the choices. * can be found under <manifest>, of which <application> is one of the choices.
* *
* @return an ElementDescriptor[] or null if no valid element was found. * @return an ElementDescriptor[] or null if no valid element was found.
*/ */
private Object[] getChoicesForElement(String parent, Node current_node) { private Object[] getChoicesForElement(String parent, Node current_node) {
@@ -287,8 +299,8 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
* lenient as suitable for attribute values. * lenient as suitable for attribute values.
* - AttribInfo.needTag will be non-zero if we find that the attribute completion proposal * - AttribInfo.needTag will be non-zero if we find that the attribute completion proposal
* must be double-quoted. * must be double-quoted.
* @param currentUiNode * @param currentUiNode
* *
* @return an AttributeDescriptor[] if the user is editing an attribute name. * @return an AttributeDescriptor[] if the user is editing an attribute name.
* a String[] if the user is editing an attribute value with some known values, * a String[] if the user is editing an attribute value with some known values,
* or null if nothing is known about the context. * or null if nothing is known about the context.
@@ -309,7 +321,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
} else { } else {
attrInfo.needTag = '"'; attrInfo.needTag = '"';
} }
if (currentUiNode != null) { if (currentUiNode != null) {
// look for an UI attribute matching the current attribute name // look for an UI attribute matching the current attribute name
String attrName = attrInfo.name; String attrName = attrInfo.name;
@@ -329,7 +341,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
if (currAttrNode != null) { if (currAttrNode != null) {
choices = currAttrNode.getPossibleValues(value); choices = currAttrNode.getPossibleValues(value);
if (currAttrNode instanceof UiFlagAttributeNode) { if (currAttrNode instanceof UiFlagAttributeNode) {
// A "flag" can consist of several values separated by "or" (|). // A "flag" can consist of several values separated by "or" (|).
// If the correct prefix contains such a pipe character, we change // If the correct prefix contains such a pipe character, we change
@@ -345,7 +357,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
if (choices == null) { if (choices == null) {
// fallback on the older descriptor-only based lookup. // fallback on the older descriptor-only based lookup.
// in order to properly handle the special case of the name attribute in // in order to properly handle the special case of the name attribute in
// the action tag, we need the grandparent of the action node, to know // the action tag, we need the grandparent of the action node, to know
// what type of actions we need. // what type of actions we need.
@@ -358,7 +370,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
greatGrandParentName = greatGrandParent.getLocalName(); greatGrandParentName = greatGrandParent.getLocalName();
} }
} }
AndroidTargetData data = mEditor.getTargetData(); AndroidTargetData data = mEditor.getTargetData();
if (data != null) { if (data != null) {
choices = data.getAttributeValues(parent, attrInfo.name, greatGrandParentName); choices = data.getAttributeValues(parent, attrInfo.name, greatGrandParentName);
@@ -382,7 +394,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
* This means the user is editing outside of any XML element or attribute. * This means the user is editing outside of any XML element or attribute.
* Simply return the list of XML elements that can be present there, based on the * Simply return the list of XML elements that can be present there, based on the
* parent of the current node. * parent of the current node.
* *
* @return An ElementDescriptor[] or null. * @return An ElementDescriptor[] or null.
*/ */
private Object[] getChoicesForTextNode(Node currentNode) { private Object[] getChoicesForTextNode(Node currentNode) {
@@ -413,7 +425,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
* - AttributeDescriptor: a possible attribute descriptor which XML name should be completed. * - AttributeDescriptor: a possible attribute descriptor which XML name should be completed.
* - String: string values to display as-is to the user. Typically those are possible * - String: string values to display as-is to the user. Typically those are possible
* values for a given attribute. * values for a given attribute.
* *
* @return The ICompletionProposal[] to display to the user. * @return The ICompletionProposal[] to display to the user.
*/ */
private ICompletionProposal[] computeProposals(int offset, Node currentNode, private ICompletionProposal[] computeProposals(int offset, Node currentNode,
@@ -421,7 +433,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
boolean is_attribute, int selectionLength) { boolean is_attribute, int selectionLength) {
ArrayList<CompletionProposal> proposals = new ArrayList<CompletionProposal>(); ArrayList<CompletionProposal> proposals = new ArrayList<CompletionProposal>();
HashMap<String, String> nsUriMap = new HashMap<String, String>(); HashMap<String, String> nsUriMap = new HashMap<String, String>();
for (Object choice : choices) { for (Object choice : choices) {
String keyword = null; String keyword = null;
String nsPrefix = null; String nsPrefix = null;
@@ -441,7 +453,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
if (choice instanceof TextAttributeDescriptor) { if (choice instanceof TextAttributeDescriptor) {
tooltip = ((TextAttributeDescriptor) choice).getTooltip(); tooltip = ((TextAttributeDescriptor) choice).getTooltip();
} }
// Get the namespace URI for the attribute. Note that some attributes // Get the namespace URI for the attribute. Note that some attributes
// do not have a namespace and thus return null here. // do not have a namespace and thus return null here.
String nsUri = ((AttributeDescriptor)choice).getNamespaceUri(); String nsUri = ((AttributeDescriptor)choice).getNamespaceUri();
@@ -455,13 +467,13 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
if (nsPrefix != null) { if (nsPrefix != null) {
nsPrefix += ":"; //$NON-NLS-1$ nsPrefix += ":"; //$NON-NLS-1$
} }
} else if (choice instanceof String) { } else if (choice instanceof String) {
keyword = (String) choice; keyword = (String) choice;
} else { } else {
continue; // discard unknown choice continue; // discard unknown choice
} }
String nsKeyword = nsPrefix == null ? keyword : (nsPrefix + keyword); String nsKeyword = nsPrefix == null ? keyword : (nsPrefix + keyword);
if (keyword.startsWith(wordPrefix) || if (keyword.startsWith(wordPrefix) ||
@@ -499,7 +511,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
proposals.add(proposal); proposals.add(proposal);
} }
} }
return proposals.toArray(new ICompletionProposal[proposals.size()]); return proposals.toArray(new ICompletionProposal[proposals.size()]);
} }
@@ -510,7 +522,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
* <p/> * <p/>
* Elements can have children if the descriptor has children element descriptors * Elements can have children if the descriptor has children element descriptors
* or if one of the attributes is a TextValueDescriptor. * or if one of the attributes is a TextValueDescriptor.
* *
* @param descriptor An ElementDescriptor or an AttributeDescriptor * @param descriptor An ElementDescriptor or an AttributeDescriptor
* @return True if the descriptor is an ElementDescriptor that can have children or a text value * @return True if the descriptor is an ElementDescriptor that can have children or a text value
*/ */
@@ -548,7 +560,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
/** /**
* Returns the characters which when entered by the user should * Returns the characters which when entered by the user should
* automatically trigger the presentation of possible completions. * automatically trigger the presentation of possible completions.
* *
* In our case, we auto-activate on opening tags and attributes namespace. * In our case, we auto-activate on opening tags and attributes namespace.
* *
* @return the auto activation characters for completion proposal or <code>null</code> * @return the auto activation characters for completion proposal or <code>null</code>
@@ -569,7 +581,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
public String getErrorMessage() { public String getErrorMessage() {
return null; return null;
} }
/** /**
* Heuristically extracts the prefix used for determining template relevance * Heuristically extracts the prefix used for determining template relevance
* from the viewer's document. The default implementation returns the String from * from the viewer's document. The default implementation returns the String from
@@ -578,7 +590,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
* *
* The part were we access the docment was extracted from * The part were we access the docment was extracted from
* org.eclipse.jface.text.templatesTemplateCompletionProcessor and adapted to our needs. * org.eclipse.jface.text.templatesTemplateCompletionProcessor and adapted to our needs.
* *
* @param viewer the viewer * @param viewer the viewer
* @param offset offset into document * @param offset offset into document
* @return the prefix to consider * @return the prefix to consider
@@ -611,7 +623,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
return ""; //$NON-NLS-1$ return ""; //$NON-NLS-1$
} }
} }
/** /**
* Extracts the character at the given offset. * Extracts the character at the given offset.
* Returns 0 if the offset is invalid. * Returns 0 if the offset is invalid.
@@ -676,7 +688,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
// text will contain the full string of the current element, // text will contain the full string of the current element,
// i.e. whatever is after the "<" to the current cursor // i.e. whatever is after the "<" to the current cursor
String text = document.get(offset, n - offset); String text = document.get(offset, n - offset);
// Normalize whitespace to single spaces // Normalize whitespace to single spaces
text = sWhitespace.matcher(text).replaceAll(" "); //$NON-NLS-1$ text = sWhitespace.matcher(text).replaceAll(" "); //$NON-NLS-1$
@@ -684,13 +696,13 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
// any whitespace. If there's nothing left, no attribute has been defined yet. // any whitespace. If there's nothing left, no attribute has been defined yet.
// Be sure to keep any whitespace after the initial word if any, as it matters. // Be sure to keep any whitespace after the initial word if any, as it matters.
text = sFirstElementWord.matcher(text).replaceFirst(""); //$NON-NLS-1$ text = sFirstElementWord.matcher(text).replaceFirst(""); //$NON-NLS-1$
// There MUST be space after the element name. If not, the cursor is still // There MUST be space after the element name. If not, the cursor is still
// defining the element name. // defining the element name.
if (!text.startsWith(" ")) { //$NON-NLS-1$ if (!text.startsWith(" ")) { //$NON-NLS-1$
return null; return null;
} }
// Remove full attributes: // Remove full attributes:
// Syntax: // Syntax:
// name = "..." quoted string with all but < and " // name = "..." quoted string with all but < and "
@@ -709,7 +721,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
// merged with the previous one. // merged with the previous one.
// - string with an = sign, optionally followed by a quote (' or "): the user is // - string with an = sign, optionally followed by a quote (' or "): the user is
// writing the value of the attribute. // writing the value of the attribute.
int pos_equal = text.indexOf('='); int pos_equal = text.indexOf('=');
if (pos_equal == -1) { if (pos_equal == -1) {
info.isInValue = false; info.isInValue = false;
info.name = text.trim(); info.name = text.trim();
@@ -749,7 +761,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
return node; return node;
} }
/** /**
* Computes (if needed) and returns the root descriptor. * Computes (if needed) and returns the root descriptor.
*/ */
@@ -758,17 +770,17 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
AndroidTargetData data = mEditor.getTargetData(); AndroidTargetData data = mEditor.getTargetData();
if (data != null) { if (data != null) {
IDescriptorProvider descriptorProvider = data.getDescriptorProvider(mDescriptorId); IDescriptorProvider descriptorProvider = data.getDescriptorProvider(mDescriptorId);
if (descriptorProvider != null) { if (descriptorProvider != null) {
mRootDescriptor = new ElementDescriptor("", mRootDescriptor = new ElementDescriptor("",
descriptorProvider.getRootElementDescriptors()); descriptorProvider.getRootElementDescriptors());
} }
} }
} }
return mRootDescriptor; return mRootDescriptor;
} }
/** /**
* Returns the active {@link AndroidEditor} matching this source viewer. * Returns the active {@link AndroidEditor} matching this source viewer.
*/ */
@@ -789,7 +801,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
return null; return null;
} }
} }