aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java1331
1 files changed, 0 insertions, 1331 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
deleted file mode 100644
index 5aac51f68..000000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
+++ /dev/null
@@ -1,1331 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.editors;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
-import static com.android.SdkConstants.PREFIX_ANDROID;
-import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
-import static com.android.SdkConstants.PREFIX_THEME_REF;
-import static com.android.SdkConstants.UNIT_DP;
-import static com.android.SdkConstants.UNIT_IN;
-import static com.android.SdkConstants.UNIT_MM;
-import static com.android.SdkConstants.UNIT_PT;
-import static com.android.SdkConstants.UNIT_PX;
-import static com.android.SdkConstants.UNIT_SP;
-import static com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor.ATTRIBUTE_ICON_FILENAME;
-
-import com.android.ide.common.api.IAttributeInfo;
-import com.android.ide.common.api.IAttributeInfo.Format;
-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.ElementDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.SeparatorAttributeDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.TextValueDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
-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.editors.uimodel.UiFlagAttributeNode;
-import com.android.ide.eclipse.adt.internal.editors.uimodel.UiResourceAttributeNode;
-import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
-import com.android.utils.Pair;
-import com.android.utils.XmlUtils;
-
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.jdt.core.IType;
-import org.eclipse.jdt.ui.ISharedImages;
-import org.eclipse.jdt.ui.JavaUI;
-import org.eclipse.jface.text.BadLocationException;
-import org.eclipse.jface.text.IDocument;
-import org.eclipse.jface.text.IRegion;
-import org.eclipse.jface.text.ITextViewer;
-import org.eclipse.jface.text.contentassist.ICompletionProposal;
-import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
-import org.eclipse.jface.text.contentassist.IContextInformation;
-import org.eclipse.jface.text.contentassist.IContextInformationValidator;
-import org.eclipse.jface.text.source.ISourceViewer;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
-import org.w3c.dom.Node;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Pattern;
-
-/**
- * Content Assist Processor for Android XML files
- * <p>
- * Remaining corner cases:
- * <ul>
- * <li>Completion does not work right if there is a space between the = and the opening
- * quote.
- * <li>Replacement completion does not work right if the caret is to the left of the
- * opening quote, where the opening quote is a single quote, and the replacement items use
- * double quotes.
- * </ul>
- */
-@SuppressWarnings("restriction") // XML model
-public abstract class AndroidContentAssist implements IContentAssistProcessor {
-
- /** Regexp to detect a full attribute after an element tag.
- * <pre>Syntax:
- * name = "..." quoted string with all but < and "
- * or:
- * name = '...' quoted string with all but < and '
- * </pre>
- */
- private static Pattern sFirstAttribute = Pattern.compile(
- "^ *[a-zA-Z_:]+ *= *(?:\"[^<\"]*\"|'[^<']*')"); //$NON-NLS-1$
-
- /** Regexp to detect an element tag name */
- private static Pattern sFirstElementWord = Pattern.compile("^[a-zA-Z0-9_:.-]+"); //$NON-NLS-1$
-
- /** Regexp to detect whitespace */
- private static Pattern sWhitespace = Pattern.compile("\\s+"); //$NON-NLS-1$
-
- protected final static String ROOT_ELEMENT = "";
-
- /** Descriptor of the root of the XML hierarchy. This a "fake" ElementDescriptor which
- * is used to list all the possible roots given by actual implementations.
- * DO NOT USE DIRECTLY. Call {@link #getRootDescriptor()} instead. */
- private ElementDescriptor mRootDescriptor;
-
- private final int mDescriptorId;
-
- protected AndroidXmlEditor mEditor;
-
- /**
- * Constructor for AndroidContentAssist
- * @param descriptorId An id for {@link AndroidTargetData#getDescriptorProvider(int)}.
- * The Id can be one of {@link AndroidTargetData#DESCRIPTOR_MANIFEST},
- * {@link AndroidTargetData#DESCRIPTOR_LAYOUT},
- * {@link AndroidTargetData#DESCRIPTOR_MENU},
- * or {@link AndroidTargetData#DESCRIPTOR_OTHER_XML}.
- * All other values will throw an {@link IllegalArgumentException} later at runtime.
- */
- public AndroidContentAssist(int descriptorId) {
- mDescriptorId = descriptorId;
- }
-
- /**
- * Returns a list of completion proposals based on the
- * specified location within the document that corresponds
- * to the current cursor position within the text viewer.
- *
- * @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
- * @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)
- */
- @Override
- public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
- String wordPrefix = extractElementPrefix(viewer, offset);
-
- if (mEditor == null) {
- mEditor = AndroidXmlEditor.fromTextViewer(viewer);
- if (mEditor == null) {
- // This should not happen. Duck and forget.
- AdtPlugin.log(IStatus.ERROR, "Editor not found during completion");
- return null;
- }
- }
-
- // List of proposals, in the order presented to the user.
- List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>(80);
-
- // Look up the caret context - where in an element, or between elements, or
- // within an element's children, is the given caret offset located?
- Pair<Node, Node> context = DomUtilities.getNodeContext(viewer.getDocument(), offset);
- if (context == null) {
- return null;
- }
- Node parentNode = context.getFirst();
- Node currentNode = context.getSecond();
- assert parentNode != null || currentNode != null;
-
- UiElementNode rootUiNode = mEditor.getUiRootNode();
- if (currentNode == null || currentNode.getNodeType() == Node.TEXT_NODE) {
- UiElementNode parentUiNode =
- rootUiNode == null ? null : rootUiNode.findXmlNode(parentNode);
- computeTextValues(proposals, offset, parentNode, currentNode, parentUiNode,
- wordPrefix);
- } else if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
- String parent = currentNode.getNodeName();
- AttribInfo info = parseAttributeInfo(viewer, offset, offset - wordPrefix.length());
- char nextChar = extractChar(viewer, offset);
- if (info != null) {
- // check to see if we can find a UiElementNode matching this XML node
- UiElementNode currentUiNode = rootUiNode == null
- ? null : rootUiNode.findXmlNode(currentNode);
- computeAttributeProposals(proposals, viewer, offset, wordPrefix, currentUiNode,
- parentNode, currentNode, parent, info, nextChar);
- } else {
- computeNonAttributeProposals(viewer, offset, wordPrefix, proposals, parentNode,
- currentNode, parent, nextChar);
- }
- }
-
- return proposals.toArray(new ICompletionProposal[proposals.size()]);
- }
-
- private void computeNonAttributeProposals(ITextViewer viewer, int offset, String wordPrefix,
- List<ICompletionProposal> proposals, Node parentNode, Node currentNode, String parent,
- char nextChar) {
- if (startsWith(parent, wordPrefix)) {
- // We are still editing the element's tag name, not the attributes
- // (the element's tag name may not even be complete)
-
- Object[] choices = getChoicesForElement(parent, currentNode);
- if (choices == null || choices.length == 0) {
- return;
- }
-
- int replaceLength = parent.length() - wordPrefix.length();
- boolean isNew = replaceLength == 0 && nextNonspaceChar(viewer, offset) == '<';
- // Special case: if we are right before the beginning of a new
- // element, wipe out the replace length such that we insert before it,
- // we don't edit the current element.
- if (wordPrefix.length() == 0 && nextChar == '<') {
- replaceLength = 0;
- isNew = true;
- }
-
- // If we found some suggestions, do we need to add an opening "<" bracket
- // for the element? We don't if the cursor is right after "<" or "</".
- // Per XML Spec, there's no whitespace between "<" or "</" and the tag name.
- char needTag = computeElementNeedTag(viewer, offset, wordPrefix);
-
- addMatchingProposals(proposals, choices, offset,
- parentNode != null ? parentNode : null, wordPrefix, needTag,
- false /* isAttribute */, isNew, false /*isComplete*/,
- replaceLength);
- }
- }
-
- private void computeAttributeProposals(List<ICompletionProposal> proposals, ITextViewer viewer,
- int offset, String wordPrefix, UiElementNode currentUiNode, Node parentNode,
- Node currentNode, String parent, AttribInfo info, char nextChar) {
- // We're editing attributes in an element node (either the attributes' names
- // or their values).
-
- if (info.isInValue) {
- if (computeAttributeValues(proposals, offset, parent, info.name, currentNode,
- wordPrefix, info.skipEndTag, info.replaceLength)) {
- return;
- }
- }
-
- // Look up attribute proposals based on descriptors
- Object[] choices = getChoicesForAttribute(parent, currentNode, currentUiNode,
- info, wordPrefix);
- if (choices == null || choices.length == 0) {
- return;
- }
-
- int replaceLength = info.replaceLength;
- if (info.correctedPrefix != null) {
- wordPrefix = info.correctedPrefix;
- }
- char needTag = info.needTag;
- // Look to the right and see if we're followed by whitespace
- boolean isNew = replaceLength == 0
- && (Character.isWhitespace(nextChar) || nextChar == '>' || nextChar == '/');
-
- addMatchingProposals(proposals, choices, offset, parentNode != null ? parentNode : null,
- wordPrefix, needTag, true /* isAttribute */, isNew, info.skipEndTag,
- replaceLength);
- }
-
- private char computeElementNeedTag(ITextViewer viewer, int offset, String wordPrefix) {
- char needTag = 0;
- int offset2 = offset - wordPrefix.length() - 1;
- char c1 = extractChar(viewer, offset2);
- if (!((c1 == '<') || (c1 == '/' && extractChar(viewer, offset2 - 1) == '<'))) {
- needTag = '<';
- }
- return needTag;
- }
-
- protected int computeTextReplaceLength(Node currentNode, int offset) {
- if (currentNode == null) {
- return 0;
- }
-
- assert currentNode != null && currentNode.getNodeType() == Node.TEXT_NODE;
-
- String nodeValue = currentNode.getNodeValue();
- int relativeOffset = offset - ((IndexedRegion) currentNode).getStartOffset();
- int lineEnd = nodeValue.indexOf('\n', relativeOffset);
- if (lineEnd == -1) {
- lineEnd = nodeValue.length();
- }
- return lineEnd - relativeOffset;
- }
-
- /**
- * Gets the choices when the user is editing the name of an XML element.
- * <p/>
- * The user is editing the name of an element (the "parent").
- * Find the grand-parent and if one is found, return its children element list.
- * The name which is being edited should be one of those.
- * <p/>
- * Example: <manifest><applic*cursor* => returns the list of all elements that
- * can be found under <manifest>, of which <application> is one of the choices.
- *
- * @return an ElementDescriptor[] or null if no valid element was found.
- */
- protected Object[] getChoicesForElement(String parent, Node currentNode) {
- ElementDescriptor grandparent = null;
- if (currentNode.getParentNode().getNodeType() == Node.ELEMENT_NODE) {
- grandparent = getDescriptor(currentNode.getParentNode().getNodeName());
- } else if (currentNode.getParentNode().getNodeType() == Node.DOCUMENT_NODE) {
- grandparent = getRootDescriptor();
- }
- if (grandparent != null) {
- for (ElementDescriptor e : grandparent.getChildren()) {
- if (e.getXmlName().startsWith(parent)) {
- return sort(grandparent.getChildren());
- }
- }
- }
-
- return null;
- }
-
- /** Non-destructively sort a list of ElementDescriptors and return the result */
- protected static ElementDescriptor[] sort(ElementDescriptor[] elements) {
- if (elements != null && elements.length > 1) {
- // Sort alphabetically. Must make copy to not destroy original.
- ElementDescriptor[] copy = new ElementDescriptor[elements.length];
- System.arraycopy(elements, 0, copy, 0, elements.length);
-
- Arrays.sort(copy, new Comparator<ElementDescriptor>() {
- @Override
- public int compare(ElementDescriptor e1, ElementDescriptor e2) {
- return e1.getXmlLocalName().compareTo(e2.getXmlLocalName());
- }
- });
-
- return copy;
- }
-
- return elements;
- }
-
- /**
- * Gets the choices when the user is editing an XML attribute.
- * <p/>
- * In input, attrInfo contains details on the analyzed context, namely whether the
- * user is editing an attribute value (isInValue) or an attribute name.
- * <p/>
- * In output, attrInfo also contains two possible new values (this is a hack to circumvent
- * the lack of out-parameters in Java):
- * - AttribInfo.correctedPrefix if the user has been editing an attribute value and it has
- * been detected that what the user typed is different from what extractElementPrefix()
- * predicted. This happens because extractElementPrefix() stops when a character that
- * cannot be an element name appears whereas parseAttributeInfo() uses a grammar more
- * lenient as suitable for attribute values.
- * - AttribInfo.needTag will be non-zero if we find that the attribute completion proposal
- * must be double-quoted.
- * @param currentUiNode
- *
- * @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,
- * or null if nothing is known about the context.
- */
- private Object[] getChoicesForAttribute(
- String parent, Node currentNode, UiElementNode currentUiNode, AttribInfo attrInfo,
- String wordPrefix) {
- Object[] choices = null;
- if (attrInfo.isInValue) {
- // Editing an attribute's value... Get the attribute name and then the
- // possible choices for the tuple(parent,attribute)
- String value = attrInfo.valuePrefix;
- 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 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 {
- attrInfo.needTag = '"';
- }
-
- if (currentUiNode != null) {
- // look for an UI attribute matching the current attribute name
- String attrName = attrInfo.name;
- // remove any namespace prefix from the attribute name
- int pos = attrName.indexOf(':');
- if (pos >= 0) {
- attrName = attrName.substring(pos + 1);
- }
-
- UiAttributeNode currAttrNode = null;
- for (UiAttributeNode attrNode : currentUiNode.getAllUiAttributes()) {
- if (attrNode.getDescriptor().getXmlLocalName().equals(attrName)) {
- currAttrNode = attrNode;
- break;
- }
- }
-
- if (currAttrNode != null) {
- choices = getAttributeValueChoices(currAttrNode, attrInfo, value);
- }
- }
-
- if (choices == null) {
- // fallback on the older descriptor-only based lookup.
-
- // 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
- // what type of actions we need.
- // e.g. activity -> intent-filter -> action[@name]
- String greatGrandParentName = null;
- Node grandParent = currentNode.getParentNode();
- if (grandParent != null) {
- Node greatGrandParent = grandParent.getParentNode();
- if (greatGrandParent != null) {
- greatGrandParentName = greatGrandParent.getLocalName();
- }
- }
-
- AndroidTargetData data = mEditor.getTargetData();
- if (data != null) {
- choices = data.getAttributeValues(parent, attrInfo.name, greatGrandParentName);
- }
- }
- } else {
- // Editing an attribute's name... Get attributes valid for the parent node.
- if (currentUiNode != null) {
- choices = currentUiNode.getAttributeDescriptors();
- } else {
- ElementDescriptor parentDesc = getDescriptor(parent);
- if (parentDesc != null) {
- choices = parentDesc.getAttributes();
- }
- }
- }
- return choices;
- }
-
- protected Object[] getAttributeValueChoices(UiAttributeNode currAttrNode, AttribInfo attrInfo,
- String value) {
- Object[] choices;
- int pos;
- choices = currAttrNode.getPossibleValues(value);
- if (choices != null && currAttrNode instanceof UiResourceAttributeNode) {
- attrInfo.skipEndTag = false;
- }
-
- if (currAttrNode instanceof UiFlagAttributeNode) {
- // A "flag" can consist of several values separated by "or" (|).
- // If the correct prefix contains such a pipe character, we change
- // it so that only the currently edited value is completed.
- pos = value.lastIndexOf('|');
- if (pos >= 0) {
- attrInfo.correctedPrefix = value = value.substring(pos + 1);
- attrInfo.needTag = 0;
- }
-
- attrInfo.skipEndTag = false;
- }
-
- // Should we do suffix completion on dimension units etc?
- choices = completeSuffix(choices, value, currAttrNode);
-
- // Check to see if the user is attempting resource completion
- AttributeDescriptor attributeDescriptor = currAttrNode.getDescriptor();
- IAttributeInfo attributeInfo = attributeDescriptor.getAttributeInfo();
- if (value.startsWith(PREFIX_RESOURCE_REF)
- && !attributeInfo.getFormats().contains(Format.REFERENCE)) {
- // Special case: If the attribute value looks like a reference to a
- // resource, offer to complete it, since in many cases our metadata
- // does not correctly state whether a resource value is allowed. We don't
- // offer these for an empty completion context, but if the user has
- // actually typed "@", in that case list resource matches.
- // For example, for android:minHeight this makes completion on @dimen/
- // possible.
- choices = UiResourceAttributeNode.computeResourceStringMatches(
- mEditor, attributeDescriptor, value);
- attrInfo.skipEndTag = false;
- } else if (value.startsWith(PREFIX_THEME_REF)
- && !attributeInfo.getFormats().contains(Format.REFERENCE)) {
- choices = UiResourceAttributeNode.computeResourceStringMatches(
- mEditor, attributeDescriptor, value);
- attrInfo.skipEndTag = false;
- }
-
- return choices;
- }
-
- /**
- * Compute attribute values. Return true if the complete set of values was
- * added, so addition descriptor information should not be added.
- */
- protected boolean computeAttributeValues(List<ICompletionProposal> proposals, int offset,
- String parentTagName, String attributeName, Node node, String wordPrefix,
- boolean skipEndTag, int replaceLength) {
- return false;
- }
-
- protected void computeTextValues(List<ICompletionProposal> proposals, int offset,
- Node parentNode, Node currentNode, UiElementNode uiParent,
- String wordPrefix) {
-
- if (parentNode != null) {
- // Examine the parent of the text node.
- Object[] choices = getElementChoicesForTextNode(parentNode);
- if (choices != null && choices.length > 0) {
- ISourceViewer viewer = mEditor.getStructuredSourceViewer();
- char needTag = computeElementNeedTag(viewer, offset, wordPrefix);
-
- int replaceLength = 0;
- addMatchingProposals(proposals, choices,
- offset, parentNode, wordPrefix, needTag,
- false /* isAttribute */,
- false /*isNew*/,
- false /*isComplete*/,
- replaceLength);
- }
- }
- }
-
- /**
- * Gets the choices when the user is editing an XML text node.
- * <p/>
- * 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
- * parent of the current node.
- *
- * @return An ElementDescriptor[] or null.
- */
- protected ElementDescriptor[] getElementChoicesForTextNode(Node parentNode) {
- ElementDescriptor[] choices = null;
- String parent;
- if (parentNode.getNodeType() == Node.ELEMENT_NODE) {
- // We're editing a text node which parent is an element node. Limit
- // content assist to elements valid for the parent.
- parent = parentNode.getNodeName();
- ElementDescriptor desc = getDescriptor(parent);
- if (desc == null && parent.indexOf('.') != -1) {
- // The parent is a custom view and we don't have metadata about its
- // allowable children, so just assume any normal layout tag is
- // legal
- desc = mRootDescriptor;
- }
-
- if (desc != null) {
- choices = sort(desc.getChildren());
- }
- } else if (parentNode.getNodeType() == Node.DOCUMENT_NODE) {
- // We're editing a text node at the first level (i.e. root node).
- // Limit content assist to the only valid root elements.
- choices = sort(getRootDescriptor().getChildren());
- }
-
- return choices;
- }
-
- /**
- * Given a list of choices, adds in any that match the current prefix into the
- * proposals list.
- * <p/>
- * Choices is an object array. Items of the array can be:
- * - ElementDescriptor: a possible element 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
- * values for a given attribute.
- * - Pair of Strings: the first value is the keyword to insert, and the second value
- * is the tooltip/help for the value to be displayed in the documentation popup.
- */
- protected void addMatchingProposals(List<ICompletionProposal> proposals, Object[] choices,
- int offset, Node currentNode, String wordPrefix, char needTag,
- boolean isAttribute, boolean isNew, boolean skipEndTag, int replaceLength) {
- if (choices == null) {
- return;
- }
-
- Map<String, String> nsUriMap = new HashMap<String, String>();
- boolean haveLayoutParams = false;
-
- for (Object choice : choices) {
- String keyword = null;
- String nsPrefix = null;
- String nsUri = null;
- Image icon = null;
- String tooltip = null;
- if (choice instanceof ElementDescriptor) {
- keyword = ((ElementDescriptor)choice).getXmlName();
- icon = ((ElementDescriptor)choice).getGenericIcon();
- // Tooltip computed lazily in {@link CompletionProposal}
- } else if (choice instanceof TextValueDescriptor) {
- continue; // Value nodes are not part of the completion choices
- } else if (choice instanceof SeparatorAttributeDescriptor) {
- continue; // not real attribute descriptors
- } else if (choice instanceof AttributeDescriptor) {
- keyword = ((AttributeDescriptor)choice).getXmlLocalName();
- icon = ((AttributeDescriptor)choice).getGenericIcon();
- // Tooltip computed lazily in {@link CompletionProposal}
-
- // Get the namespace URI for the attribute. Note that some attributes
- // do not have a namespace and thus return null here.
- nsUri = ((AttributeDescriptor)choice).getNamespaceUri();
- if (nsUri != null) {
- nsPrefix = nsUriMap.get(nsUri);
- if (nsPrefix == null) {
- nsPrefix = XmlUtils.lookupNamespacePrefix(currentNode, nsUri, false);
- nsUriMap.put(nsUri, nsPrefix);
- }
- }
- if (nsPrefix != null) {
- nsPrefix += ":"; //$NON-NLS-1$
- }
-
- } else if (choice instanceof String) {
- keyword = (String) choice;
- if (isAttribute) {
- icon = IconFactory.getInstance().getIcon(ATTRIBUTE_ICON_FILENAME);
- }
- } else if (choice instanceof Pair<?, ?>) {
- @SuppressWarnings("unchecked")
- Pair<String, String> pair = (Pair<String, String>) choice;
- keyword = pair.getFirst();
- tooltip = pair.getSecond();
- if (isAttribute) {
- icon = IconFactory.getInstance().getIcon(ATTRIBUTE_ICON_FILENAME);
- }
- } else if (choice instanceof IType) {
- IType type = (IType) choice;
- keyword = type.getFullyQualifiedName();
- icon = JavaUI.getSharedImages().getImage(ISharedImages.IMG_OBJS_CUNIT);
- } else {
- continue; // discard unknown choice
- }
-
- String nsKeyword = nsPrefix == null ? keyword : (nsPrefix + keyword);
-
- if (nameStartsWith(nsKeyword, wordPrefix, nsPrefix)) {
- keyword = nsKeyword;
- String endTag = ""; //$NON-NLS-1$
- if (needTag != 0) {
- if (needTag == '"') {
- keyword = needTag + keyword;
- endTag = String.valueOf(needTag);
- } else if (needTag == '<') {
- if (elementCanHaveChildren(choice)) {
- endTag = String.format("></%1$s>", keyword); //$NON-NLS-1$
- } else {
- endTag = "/>"; //$NON-NLS-1$
- }
- keyword = needTag + keyword + ' ';
- } else if (needTag == ' ') {
- keyword = needTag + keyword;
- }
- } else if (!isAttribute && isNew) {
- if (elementCanHaveChildren(choice)) {
- endTag = String.format("></%1$s>", keyword); //$NON-NLS-1$
- } else {
- endTag = "/>"; //$NON-NLS-1$
- }
- keyword = keyword + ' ';
- }
-
- final String suffix;
- int cursorPosition;
- final String displayString;
- if (choice instanceof AttributeDescriptor && isNew) {
- // Special case for attributes: insert ="" stuff and locate caret inside ""
- suffix = "=\"\""; //$NON-NLS-1$
- cursorPosition = keyword.length() + suffix.length() - 1;
- displayString = keyword + endTag; // don't include suffix;
- } else {
- suffix = endTag;
- cursorPosition = keyword.length();
- displayString = null;
- }
-
- if (skipEndTag) {
- assert isAttribute;
- cursorPosition++;
- }
-
- if (nsPrefix != null &&
- keyword.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX, nsPrefix.length())) {
- haveLayoutParams = true;
- }
-
- // For attributes, automatically insert ns:attribute="" and place the cursor
- // inside the quotes.
- // Special case for attributes: insert ="" stuff and locate caret inside ""
- proposals.add(new CompletionProposal(
- this,
- choice,
- keyword + suffix, // String replacementString
- offset - wordPrefix.length(), // int replacementOffset
- wordPrefix.length() + replaceLength,// int replacementLength
- cursorPosition, // cursorPosition
- icon, // Image image
- displayString, // displayString
- null, // IContextInformation contextInformation
- tooltip, // String additionalProposalInfo
- nsPrefix,
- nsUri
- ));
- }
- }
-
- if (wordPrefix.length() > 0 && haveLayoutParams
- && !wordPrefix.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) {
- // Sort layout parameters to the front if we automatically inserted some
- // that you didn't request. For example, you typed "width" and we match both
- // "width" and "layout_width" - should match layout_width.
- String nsPrefix = nsUriMap.get(ANDROID_URI);
- if (nsPrefix == null) {
- nsPrefix = PREFIX_ANDROID;
- } else {
- nsPrefix += ':';
- }
- if (!(wordPrefix.startsWith(nsPrefix)
- && wordPrefix.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX, nsPrefix.length()))) {
- int nextLayoutIndex = 0;
- for (int i = 0, n = proposals.size(); i < n; i++) {
- ICompletionProposal proposal = proposals.get(i);
- String keyword = proposal.getDisplayString();
- if (keyword.startsWith(nsPrefix) &&
- keyword.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX, nsPrefix.length())
- && i != nextLayoutIndex) {
- // Swap to front
- ICompletionProposal temp = proposals.get(nextLayoutIndex);
- proposals.set(nextLayoutIndex, proposal);
- proposals.set(i, temp);
- nextLayoutIndex++;
- }
- }
- }
- }
- }
-
- /**
- * Returns true if the given word starts with the given prefix. The comparison is not
- * case sensitive.
- *
- * @param word the word to test
- * @param prefix the prefix the word should start with
- * @return true if the given word starts with the given prefix
- */
- protected static boolean startsWith(String word, String prefix) {
- int prefixLength = prefix.length();
- int wordLength = word.length();
- if (wordLength < prefixLength) {
- return false;
- }
-
- for (int i = 0; i < prefixLength; i++) {
- if (Character.toLowerCase(prefix.charAt(i))
- != Character.toLowerCase(word.charAt(i))) {
- return false;
- }
- }
-
- return true;
- }
-
- /** @return the editor associated with this content assist */
- AndroidXmlEditor getEditor() {
- return mEditor;
- }
-
- /**
- * This method performs a prefix match for the given word and prefix, with a couple of
- * Android code completion specific twists:
- * <ol>
- * <li> The match is not case sensitive, so {word="fOo",prefix="FoO"} is a match.
- * <li>If the word to be matched has a namespace prefix, the typed prefix doesn't have
- * to match it. So {word="android:foo", prefix="foo"} is a match.
- * <li>If the attribute name part starts with "layout_" it can be omitted. So
- * {word="android:layout_marginTop",prefix="margin"} is a match, as is
- * {word="android:layout_marginTop",prefix="android:margin"}.
- * </ol>
- *
- * @param word the full word to be matched, including namespace if any
- * @param prefix the prefix to check
- * @param nsPrefix the namespace prefix (android: or local definition of android
- * namespace prefix)
- * @return true if the prefix matches for code completion
- */
- protected static boolean nameStartsWith(String word, String prefix, String nsPrefix) {
- if (nsPrefix == null) {
- nsPrefix = ""; //$NON-NLS-1$
- }
-
- int wordStart = nsPrefix.length();
- int prefixStart = 0;
-
- if (startsWith(prefix, nsPrefix)) {
- // Already matches up through the namespace prefix:
- prefixStart = wordStart;
- } else if (startsWith(nsPrefix, prefix)) {
- return true;
- }
-
- int prefixLength = prefix.length();
- int wordLength = word.length();
-
- if (wordLength - wordStart < prefixLength - prefixStart) {
- return false;
- }
-
- boolean matches = true;
- for (int i = prefixStart, j = wordStart; i < prefixLength; i++, j++) {
- char c1 = Character.toLowerCase(prefix.charAt(i));
- char c2 = Character.toLowerCase(word.charAt(j));
- if (c1 != c2) {
- matches = false;
- break;
- }
- }
-
- if (!matches && word.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX, wordStart)
- && !prefix.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX, prefixStart)) {
- wordStart += ATTR_LAYOUT_RESOURCE_PREFIX.length();
-
- if (wordLength - wordStart < prefixLength - prefixStart) {
- return false;
- }
-
- for (int i = prefixStart, j = wordStart; i < prefixLength; i++, j++) {
- char c1 = Character.toLowerCase(prefix.charAt(i));
- char c2 = Character.toLowerCase(word.charAt(j));
- if (c1 != c2) {
- return false;
- }
- }
-
- return true;
- }
-
- return matches;
- }
-
- /**
- * Indicates whether this descriptor describes an element that can potentially
- * have children (either sub-elements or text value). If an element can have children,
- * we want to explicitly write an opening and a separate closing tag.
- * <p/>
- * Elements can have children if the descriptor has children element descriptors
- * or if one of the attributes is a TextValueDescriptor.
- *
- * @param descriptor An ElementDescriptor or an AttributeDescriptor
- * @return True if the descriptor is an ElementDescriptor that can have children or a text
- * value
- */
- private boolean elementCanHaveChildren(Object descriptor) {
- if (descriptor instanceof ElementDescriptor) {
- ElementDescriptor desc = (ElementDescriptor) descriptor;
- if (desc.hasChildren()) {
- return true;
- }
- for (AttributeDescriptor attrDesc : desc.getAttributes()) {
- if (attrDesc instanceof TextValueDescriptor) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Returns the element descriptor matching a given XML node name or null if it can't be
- * found.
- * <p/>
- * This is simplistic; ideally we should consider the parent's chain to make sure we
- * can differentiate between different hierarchy trees. Right now the first match found
- * is returned.
- */
- private ElementDescriptor getDescriptor(String nodeName) {
- return getRootDescriptor().findChildrenDescriptor(nodeName, true /* recursive */);
- }
-
- @Override
- public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
- return null;
- }
-
- /**
- * Returns the characters which when entered by the user should
- * automatically trigger the presentation of possible completions.
- *
- * In our case, we auto-activate on opening tags and attributes namespace.
- *
- * @return the auto activation characters for completion proposal or <code>null</code>
- * if no auto activation is desired
- */
- @Override
- public char[] getCompletionProposalAutoActivationCharacters() {
- return new char[]{ '<', ':', '=' };
- }
-
- @Override
- public char[] getContextInformationAutoActivationCharacters() {
- return null;
- }
-
- @Override
- public IContextInformationValidator getContextInformationValidator() {
- return null;
- }
-
- @Override
- public String getErrorMessage() {
- return null;
- }
-
- /**
- * Heuristically extracts the prefix used for determining template relevance
- * from the viewer's document. The default implementation returns the String from
- * offset backwards that forms a potential XML element name, attribute name or
- * attribute value.
- *
- * The part were we access the document was extracted from
- * org.eclipse.jface.text.templatesTemplateCompletionProcessor and adapted to our needs.
- *
- * @param viewer the viewer
- * @param offset offset into document
- * @return the prefix to consider
- */
- protected String extractElementPrefix(ITextViewer viewer, int offset) {
- int i = offset;
- IDocument document = viewer.getDocument();
- if (i > document.getLength()) return ""; //$NON-NLS-1$
-
- try {
- for (; i > 0; --i) {
- char ch = document.getChar(i - 1);
-
- // We want all characters that can form a valid:
- // - element name, e.g. anything that is a valid Java class/variable literal.
- // - attribute name, including : for the namespace
- // - attribute value.
- // Before we were inclusive and that made the code fragile. So now we're
- // going to be exclusive: take everything till we get one of:
- // - any form of whitespace
- // - any xml separator, e.g. < > ' " and =
- if (Character.isWhitespace(ch) ||
- ch == '<' || ch == '>' || ch == '\'' || ch == '"' || ch == '=') {
- break;
- }
- }
-
- return document.get(i, offset - i);
- } catch (BadLocationException e) {
- return ""; //$NON-NLS-1$
- }
- }
-
- /**
- * Extracts the character at the given offset.
- * Returns 0 if the offset is invalid.
- */
- protected char extractChar(ITextViewer viewer, int offset) {
- IDocument document = viewer.getDocument();
- if (offset > document.getLength()) return 0;
-
- try {
- return document.getChar(offset);
- } catch (BadLocationException e) {
- return 0;
- }
- }
-
- /**
- * Search forward and find the first non-space character and return it. Returns 0 if no
- * such character was found.
- */
- private char nextNonspaceChar(ITextViewer viewer, int offset) {
- IDocument document = viewer.getDocument();
- int length = document.getLength();
- for (; offset < length; offset++) {
- try {
- char c = document.getChar(offset);
- if (!Character.isWhitespace(c)) {
- return c;
- }
- } catch (BadLocationException e) {
- return 0;
- }
- }
-
- return 0;
- }
-
- /**
- * Information about the current edit of an attribute as reported by parseAttributeInfo.
- */
- protected static class AttribInfo {
- public AttribInfo() {
- }
-
- /** True if the cursor is located in an attribute's value, false if in an attribute name */
- public boolean isInValue = false;
- /** The attribute name. Null when not set. */
- public String name = null;
- /** The attribute value top the left of the cursor. Null when not set. The value
- * *may* start with a quote (' or "), in which case we know we don't need to quote
- * the string for the user */
- public String valuePrefix = null;
- /** String typed by the user so far (i.e. right before requesting code completion),
- * which will be corrected if we find a possible completion for an attribute value.
- * See the long comment in getChoicesForAttribute(). */
- public String correctedPrefix = null;
- /** Non-zero if an attribute value need a start/end tag (i.e. quotes or brackets) */
- public char needTag = 0;
- /** Number of characters to replace after the prefix */
- public int replaceLength = 0;
- /** Should the cursor advance through the end tag when inserted? */
- public boolean skipEndTag = false;
- }
-
- /**
- * Try to guess if the cursor is editing an element's name or an attribute following an
- * element. If it's an attribute, try to find if an attribute name is being defined or
- * its value.
- * <br/>
- * This is currently *only* called when we know the cursor is after a complete element
- * tag name, so it should never return null.
- * <br/>
- * Reference for XML syntax: http://www.w3.org/TR/2006/REC-xml-20060816/#sec-starttags
- * <br/>
- * @return An AttribInfo describing which attribute is being edited or null if the cursor is
- * not editing an attribute (in which case it must be an element's name).
- */
- private AttribInfo parseAttributeInfo(ITextViewer viewer, int offset, int prefixStartOffset) {
- AttribInfo info = new AttribInfo();
- int originalOffset = offset;
-
- IDocument document = viewer.getDocument();
- int n = document.getLength();
- if (offset <= n) {
- try {
- // Look to the right to make sure we aren't sitting on the boundary of the
- // beginning of a new element with whitespace before it
- if (offset < n && document.getChar(offset) == '<') {
- return null;
- }
-
- n = offset;
- for (;offset > 0; --offset) {
- char ch = document.getChar(offset - 1);
- if (ch == '>') break;
- if (ch == '<') break;
- }
-
- // text will contain the full string of the current element,
- // i.e. whatever is after the "<" to the current cursor
- String text = document.get(offset, n - offset);
-
- // Normalize whitespace to single spaces
- text = sWhitespace.matcher(text).replaceAll(" "); //$NON-NLS-1$
-
- // Remove the leading element name. By spec, it must be after the < without
- // 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.
- text = sFirstElementWord.matcher(text).replaceFirst(""); //$NON-NLS-1$
-
- // There MUST be space after the element name. If not, the cursor is still
- // defining the element name.
- if (!text.startsWith(" ")) { //$NON-NLS-1$
- return null;
- }
-
- // Remove full attributes:
- // Syntax:
- // name = "..." quoted string with all but < and "
- // or:
- // name = '...' quoted string with all but < and '
- String temp;
- do {
- temp = text;
- text = sFirstAttribute.matcher(temp).replaceFirst(""); //$NON-NLS-1$
- } while(!temp.equals(text));
-
- IRegion lineInfo = document.getLineInformationOfOffset(originalOffset);
- int lineStart = lineInfo.getOffset();
- String line = document.get(lineStart, lineInfo.getLength());
- int cursorColumn = originalOffset - lineStart;
- int prefixLength = originalOffset - prefixStartOffset;
-
- // Now we're left with 3 cases:
- // - nothing: either there is no attribute definition or the cursor located after
- // a completed attribute definition.
- // - a string with no =: the user is writing an attribute name. This case can be
- // merged with the previous one.
- // - string with an = sign, optionally followed by a quote (' or "): the user is
- // writing the value of the attribute.
- int posEqual = text.indexOf('=');
- if (posEqual == -1) {
- info.isInValue = false;
- info.name = text.trim();
-
- // info.name is currently just the prefix of the attribute name.
- // Look at the text buffer to find the complete name (since we need
- // to know its bounds in order to replace it when a different attribute
- // that matches this prefix is chosen)
- int nameStart = cursorColumn;
- for (int nameEnd = nameStart; nameEnd < line.length(); nameEnd++) {
- char c = line.charAt(nameEnd);
- if (!(Character.isLetter(c) || c == ':' || c == '_')) {
- String nameSuffix = line.substring(nameStart, nameEnd);
- info.name = text.trim() + nameSuffix;
- break;
- }
- }
-
- info.replaceLength = info.name.length() - prefixLength;
-
- if (info.name.length() == 0 && originalOffset > 0) {
- // Ensure that attribute names are properly separated
- char prevChar = extractChar(viewer, originalOffset - 1);
- if (prevChar == '"' || prevChar == '\'') {
- // Ensure that the attribute is properly separated from the
- // previous element
- info.needTag = ' ';
- }
- }
- info.skipEndTag = false;
- } else {
- info.isInValue = true;
- info.name = text.substring(0, posEqual).trim();
- info.valuePrefix = text.substring(posEqual + 1);
-
- char quoteChar = '"'; // Does " or ' surround the XML value?
- for (int i = posEqual + 1; i < text.length(); i++) {
- if (!Character.isWhitespace(text.charAt(i))) {
- quoteChar = text.charAt(i);
- break;
- }
- }
-
- // Must compute the complete value
- int valueStart = cursorColumn;
- int valueEnd = valueStart;
- for (; valueEnd < line.length(); valueEnd++) {
- char c = line.charAt(valueEnd);
- if (c == quoteChar) {
- // Make sure this isn't the *opening* quote of the value,
- // which is the case if we invoke code completion with the
- // caret between the = and the opening quote; in that case
- // we consider it value completion, and offer items including
- // the quotes, but we shouldn't bail here thinking we have found
- // the end of the value.
- // Look backwards to make sure we find another " before
- // we find a =
- boolean isFirst = false;
- for (int j = valueEnd - 1; j >= 0; j--) {
- char pc = line.charAt(j);
- if (pc == '=') {
- isFirst = true;
- break;
- } else if (pc == quoteChar) {
- valueStart = j;
- break;
- }
- }
- if (!isFirst) {
- info.skipEndTag = true;
- break;
- }
- }
- }
- int valueEndOffset = valueEnd + lineStart;
- info.replaceLength = valueEndOffset - (prefixStartOffset + prefixLength);
- // Is the caret to the left of the value quote? If so, include it in
- // the replace length.
- int valueStartOffset = valueStart + lineStart;
- if (valueStartOffset == prefixStartOffset && valueEnd > valueStart) {
- info.replaceLength++;
- }
- }
- return info;
- } catch (BadLocationException e) {
- // pass
- }
- }
-
- return null;
- }
-
- /** Returns the root descriptor id to use */
- protected int getRootDescriptorId() {
- return mDescriptorId;
- }
-
- /**
- * Computes (if needed) and returns the root descriptor.
- */
- protected ElementDescriptor getRootDescriptor() {
- if (mRootDescriptor == null) {
- AndroidTargetData data = mEditor.getTargetData();
- if (data != null) {
- IDescriptorProvider descriptorProvider =
- data.getDescriptorProvider(getRootDescriptorId());
-
- if (descriptorProvider != null) {
- mRootDescriptor = new ElementDescriptor("", //$NON-NLS-1$
- descriptorProvider.getRootElementDescriptors());
- }
- }
- }
-
- return mRootDescriptor;
- }
-
- /**
- * Fixed list of dimension units, along with user documentation, for use by
- * {@link #completeSuffix}.
- */
- private static final String[] sDimensionUnits = new String[] {
- UNIT_DP,
- "<b>Density-independent Pixels</b> - an abstract unit that is based on the physical "
- + "density of the screen.",
-
- UNIT_SP,
- "<b>Scale-independent Pixels</b> - this is like the dp unit, but it is also scaled by "
- + "the user's font size preference.",
-
- UNIT_PT,
- "<b>Points</b> - 1/72 of an inch based on the physical size of the screen.",
-
- UNIT_MM,
- "<b>Millimeters</b> - based on the physical size of the screen.",
-
- UNIT_IN,
- "<b>Inches</b> - based on the physical size of the screen.",
-
- UNIT_PX,
- "<b>Pixels</b> - corresponds to actual pixels on the screen. Not recommended.",
- };
-
- /**
- * Fixed list of fractional units, along with user documentation, for use by
- * {@link #completeSuffix}
- */
- private static final String[] sFractionUnits = new String[] {
- "%", //$NON-NLS-1$
- "<b>Fraction</b> - a percentage of the base size",
-
- "%p", //$NON-NLS-1$
- "<b>Fraction</b> - a percentage relative to parent container",
- };
-
- /**
- * Completes suffixes for applicable types (like dimensions and fractions) such that
- * after a dimension number you get completion on unit types like "px".
- */
- private Object[] completeSuffix(Object[] choices, String value, UiAttributeNode currAttrNode) {
- IAttributeInfo attributeInfo = currAttrNode.getDescriptor().getAttributeInfo();
- EnumSet<Format> formats = attributeInfo.getFormats();
- List<Object> suffixes = new ArrayList<Object>();
-
- if (value.length() > 0 && Character.isDigit(value.charAt(0))) {
- boolean hasDimension = formats.contains(Format.DIMENSION);
- boolean hasFraction = formats.contains(Format.FRACTION);
-
- if (hasDimension || hasFraction) {
- // Split up the value into a numeric part (the prefix) and the
- // unit part (the suffix)
- int suffixBegin = 0;
- for (; suffixBegin < value.length(); suffixBegin++) {
- if (!Character.isDigit(value.charAt(suffixBegin))) {
- break;
- }
- }
- String number = value.substring(0, suffixBegin);
- String suffix = value.substring(suffixBegin);
-
- // Add in the matching dimension and/or fraction units, if any
- if (hasDimension) {
- // Each item has two entries in the array of strings: the first odd numbered
- // ones are the unit names and the second even numbered ones are the
- // corresponding descriptions.
- for (int i = 0; i < sDimensionUnits.length; i += 2) {
- String unit = sDimensionUnits[i];
- if (startsWith(unit, suffix)) {
- String description = sDimensionUnits[i + 1];
- suffixes.add(Pair.of(number + unit, description));
- }
- }
-
- // Allow "dip" completion but don't offer it ("dp" is preferred)
- if (startsWith(suffix, "di") || startsWith(suffix, "dip")) { //$NON-NLS-1$ //$NON-NLS-2$
- suffixes.add(Pair.of(number + "dip", "Alternative name for \"dp\"")); //$NON-NLS-1$
- }
- }
- if (hasFraction) {
- for (int i = 0; i < sFractionUnits.length; i += 2) {
- String unit = sFractionUnits[i];
- if (startsWith(unit, suffix)) {
- String description = sFractionUnits[i + 1];
- suffixes.add(Pair.of(number + unit, description));
- }
- }
- }
- }
- }
-
- boolean hasFlag = formats.contains(Format.FLAG);
- if (hasFlag) {
- boolean isDone = false;
- String[] flagValues = attributeInfo.getFlagValues();
- for (String flagValue : flagValues) {
- if (flagValue.equals(value)) {
- isDone = true;
- break;
- }
- }
- if (isDone) {
- // Add in all the new values with a separator of |
- String currentValue = currAttrNode.getCurrentValue();
- for (String flagValue : flagValues) {
- if (currentValue == null || !currentValue.contains(flagValue)) {
- suffixes.add(value + '|' + flagValue);
- }
- }
- }
- }
-
- if (suffixes.size() > 0) {
- // Merge previously added choices (from attribute enums etc) with the new matches
- List<Object> all = new ArrayList<Object>();
- if (choices != null) {
- for (Object s : choices) {
- all.add(s);
- }
- }
- all.addAll(suffixes);
- choices = all.toArray();
- }
-
- return choices;
- }
-}