0001: /*******************************************************************************
0002: * Copyright (c) 2006, 2007 IBM Corporation and others.
0003: * All rights reserved. This program and the accompanying materials
0004: * are made available under the terms of the Eclipse Public License v1.0
0005: * which accompanies this distribution, and is available at
0006: * http://www.eclipse.org/legal/epl-v10.html
0007: *
0008: * Contributors:
0009: * IBM Corporation - initial API and implementation
0010: * Remy Chi Jian Suen <remy.suen@gmail.com> - bug 201566
0011: *******************************************************************************/package org.eclipse.pde.internal.ui.editor.contentassist;
0012:
0013: import java.util.ArrayList;
0014: import java.util.Iterator;
0015: import java.util.TreeSet;
0016: import java.util.regex.Pattern;
0017:
0018: import org.eclipse.core.resources.IResource;
0019: import org.eclipse.jdt.core.search.IJavaSearchConstants;
0020: import org.eclipse.jface.text.BadLocationException;
0021: import org.eclipse.jface.text.IDocument;
0022: import org.eclipse.jface.text.ITextSelection;
0023: import org.eclipse.jface.text.ITextViewer;
0024: import org.eclipse.jface.text.contentassist.ContentAssistEvent;
0025: import org.eclipse.jface.text.contentassist.ICompletionListener;
0026: import org.eclipse.jface.text.contentassist.ICompletionProposal;
0027: import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
0028: import org.eclipse.jface.viewers.ISelection;
0029: import org.eclipse.pde.core.IBaseModel;
0030: import org.eclipse.pde.core.IIdentifiable;
0031: import org.eclipse.pde.core.plugin.IPluginBase;
0032: import org.eclipse.pde.core.plugin.IPluginExtension;
0033: import org.eclipse.pde.core.plugin.IPluginExtensionPoint;
0034: import org.eclipse.pde.core.plugin.IPluginModelBase;
0035: import org.eclipse.pde.core.plugin.IPluginObject;
0036: import org.eclipse.pde.core.plugin.PluginRegistry;
0037: import org.eclipse.pde.internal.core.ischema.IMetaAttribute;
0038: import org.eclipse.pde.internal.core.ischema.ISchemaAttribute;
0039: import org.eclipse.pde.internal.core.ischema.ISchemaComplexType;
0040: import org.eclipse.pde.internal.core.ischema.ISchemaCompositor;
0041: import org.eclipse.pde.internal.core.ischema.ISchemaElement;
0042: import org.eclipse.pde.internal.core.ischema.ISchemaObject;
0043: import org.eclipse.pde.internal.core.ischema.ISchemaRestriction;
0044: import org.eclipse.pde.internal.core.ischema.ISchemaSimpleType;
0045: import org.eclipse.pde.internal.core.text.AbstractEditingModel;
0046: import org.eclipse.pde.internal.core.text.IDocumentAttributeNode;
0047: import org.eclipse.pde.internal.core.text.IDocumentElementNode;
0048: import org.eclipse.pde.internal.core.text.IDocumentRange;
0049: import org.eclipse.pde.internal.core.text.IDocumentTextNode;
0050: import org.eclipse.pde.internal.core.text.IReconcilingParticipant;
0051: import org.eclipse.pde.internal.core.text.plugin.PluginModelBase;
0052: import org.eclipse.pde.internal.core.util.IdUtil;
0053: import org.eclipse.pde.internal.ui.PDEPluginImages;
0054: import org.eclipse.pde.internal.ui.PDEUIMessages;
0055: import org.eclipse.pde.internal.ui.editor.PDEFormEditor;
0056: import org.eclipse.pde.internal.ui.editor.PDESourcePage;
0057: import org.eclipse.pde.internal.ui.editor.text.XMLUtil;
0058: import org.eclipse.swt.graphics.Image;
0059: import org.eclipse.ui.forms.editor.FormEditor;
0060:
0061: public class XMLContentAssistProcessor extends
0062: TypePackageCompletionProcessor implements
0063: IContentAssistProcessor, ICompletionListener {
0064:
0065: protected boolean fAssistSessionStarted;
0066:
0067: // Specific assist types
0068:
0069: protected static final int F_INFER_BY_OBJECT = -1;
0070:
0071: protected static final int F_EXTENSION_POINT = 0;
0072:
0073: protected static final int F_EXTENSION = 1;
0074:
0075: protected static final int F_ELEMENT = 2;
0076:
0077: protected static final int F_ATTRIBUTE = 3;
0078:
0079: protected static final int F_CLOSE_TAG = 4;
0080:
0081: protected static final int F_ATTRIBUTE_VALUE = 5;
0082:
0083: protected static final int F_EXTENSION_ATTRIBUTE_POINT_VALUE = 6;
0084:
0085: protected static final int F_EXTENSION_POINT_AND_VALUE = 7;
0086:
0087: protected static final int F_TOTAL_TYPES = 8;
0088:
0089: // proposal generation type
0090: private static final int F_NO_ASSIST = 0, F_ADD_ATTRIB = 1,
0091: F_ADD_CHILD = 2, F_OPEN_TAG = 3;
0092:
0093: private static final ArrayList F_V_BOOLS = new ArrayList();
0094: static {
0095: F_V_BOOLS.add(new VirtualSchemaObject(
0096: "true", null, F_ATTRIBUTE_VALUE)); //$NON-NLS-1$
0097: F_V_BOOLS.add(new VirtualSchemaObject(
0098: "false", null, F_ATTRIBUTE_VALUE)); //$NON-NLS-1$
0099: }
0100:
0101: private static final String F_STR_EXT_PT = "extension-point"; //$NON-NLS-1$
0102: private static final String F_STR_EXT = "extension"; //$NON-NLS-1$
0103:
0104: private PDESourcePage fSourcePage;
0105: private final Image[] fImages = new Image[F_TOTAL_TYPES];
0106: // TODO add a listener to add/remove extension points as they are added/removed from working models
0107: private IDocumentRange fRange;
0108: private int fDocLen = -1;
0109:
0110: /** All external plug-in extension points */
0111: private ArrayList fExternalExtPoints;
0112:
0113: /** All internal plug-in extension points */
0114: private ArrayList fInternalExtPoints;
0115:
0116: /** All external and internal plug-in extension points */
0117: private ArrayList fAllExtPoints;
0118:
0119: public XMLContentAssistProcessor(PDESourcePage sourcePage) {
0120: fSourcePage = sourcePage;
0121: }
0122:
0123: public ICompletionProposal[] computeCompletionProposals(
0124: ITextViewer viewer, int offset) {
0125: IDocument doc = viewer.getDocument();
0126: int docLen = doc.getLength();
0127: if (docLen == fDocLen)
0128: return null; // left/right cursor has been pressed - cancel content assist
0129:
0130: fDocLen = docLen;
0131: IBaseModel model = getModel();
0132: if (model instanceof AbstractEditingModel
0133: && fSourcePage.isDirty()
0134: && ((AbstractEditingModel) model).isStale()
0135: && fRange == null) {
0136: ((AbstractEditingModel) model).reconciled(doc);
0137: } else if (fAssistSessionStarted) {
0138: // Always reconcile when content assist is first invoked
0139: // Fix Bug # 149478
0140: ((AbstractEditingModel) model).reconciled(doc);
0141: fAssistSessionStarted = false;
0142: }
0143:
0144: if (fRange == null) {
0145: assignRange(offset);
0146: } else {
0147: // TODO - we may be looking at the wrong fRange
0148: // when this happens --> reset it and reconcile
0149: // how can we tell if we are looking at the wrong one... ?
0150: boolean resetAndReconcile = false;
0151: if (!(fRange instanceof IDocumentAttributeNode))
0152: // too easy to reconcile.. this is temporary
0153: resetAndReconcile = true;
0154:
0155: if (resetAndReconcile) {
0156: fRange = null;
0157: if (model instanceof IReconcilingParticipant)
0158: ((IReconcilingParticipant) model).reconciled(doc);
0159: }
0160: }
0161: // Get content assist text if any
0162: XMLContentAssistText caText = XMLContentAssistText.parse(
0163: offset, doc);
0164:
0165: if (caText != null) {
0166: return computeCATextProposal(doc, offset, caText);
0167: } else if (fRange instanceof IDocumentAttributeNode) {
0168: return computeCompletionProposal(
0169: (IDocumentAttributeNode) fRange, offset, doc);
0170: } else if (fRange instanceof IDocumentElementNode) {
0171: return computeCompletionProposal(
0172: (IDocumentElementNode) fRange, offset, doc);
0173: } else if (fRange instanceof IDocumentTextNode) {
0174: return null;
0175: } else if (model instanceof PluginModelBase) {
0176: // broken model - infer from text content
0177: return computeBrokenModelProposal(((PluginModelBase) model)
0178: .getLastErrorNode(), offset, doc);
0179: }
0180: return null;
0181: }
0182:
0183: /**
0184: * @param proposals
0185: * @param id
0186: * @param print
0187: * @return
0188: */
0189: protected ICompletionProposal[] debugPrintProposals(
0190: ICompletionProposal[] proposals, String id, boolean print) {
0191: if (proposals == null) {
0192: System.out.println("[0] " + id); //$NON-NLS-1$
0193: return proposals;
0194: }
0195: System.out.println("[" + proposals.length + "] " + id); //$NON-NLS-1$ //$NON-NLS-2$
0196: if (print == false) {
0197: return proposals;
0198: }
0199: for (int i = 0; i < proposals.length; i++) {
0200: System.out.println(proposals[i].getDisplayString());
0201: }
0202: return proposals;
0203: }
0204:
0205: private void assignRange(int offset) {
0206: fRange = fSourcePage.getRangeElement(offset, true);
0207: if (fRange == null)
0208: return;
0209: // if we are rigth AT (cursor before) the range, we want to contribute
0210: // to its parent
0211: if (fRange instanceof IDocumentAttributeNode) {
0212: if (((IDocumentAttributeNode) fRange).getNameOffset() == offset)
0213: fRange = ((IDocumentAttributeNode) fRange)
0214: .getEnclosingElement();
0215: } else if (fRange instanceof IDocumentElementNode) {
0216: if (((IDocumentElementNode) fRange).getOffset() == offset)
0217: fRange = ((IDocumentElementNode) fRange)
0218: .getParentNode();
0219: } else if (fRange instanceof IDocumentTextNode) {
0220: if (((IDocumentTextNode) fRange).getOffset() == offset)
0221: fRange = ((IDocumentTextNode) fRange)
0222: .getEnclosingElement();
0223: }
0224: }
0225:
0226: private ICompletionProposal[] computeCATextProposal(IDocument doc,
0227: int offset, XMLContentAssistText caText) {
0228: fRange = fSourcePage.getRangeElement(offset, true);
0229: if ((fRange != null) && (fRange instanceof IDocumentTextNode)) {
0230: // We have a text node.
0231: // Get its parent element
0232: fRange = ((IDocumentTextNode) fRange).getEnclosingElement();
0233: }
0234: if ((fRange != null)
0235: && (fRange instanceof IDocumentElementNode)) {
0236: return computeAddChildProposal(
0237: (IDocumentElementNode) fRange, caText
0238: .getStartOffset(), doc, caText.getText());
0239: }
0240: return null;
0241: }
0242:
0243: private ICompletionProposal[] computeCompletionProposal(
0244: IDocumentAttributeNode attr, int offset, IDocument doc) {
0245: if (offset < attr.getValueOffset())
0246: return null;
0247: int[] offests = new int[] { offset, offset, offset };
0248: String[] guess = guessContentRequest(offests, doc, false);
0249: if (guess == null)
0250: return null;
0251: // String element = guess[0];
0252: // String attribute = guess[1];
0253: String attrValue = guess[2];
0254:
0255: IPluginObject obj = XMLUtil.getTopLevelParent(attr);
0256: if (obj instanceof IPluginExtension) {
0257: if (attr.getAttributeName()
0258: .equals(IPluginExtension.P_POINT)
0259: && offset >= attr.getValueOffset()) {
0260: return computeExtPointAttrProposals(attr, offset,
0261: attrValue);
0262: }
0263: ISchemaAttribute sAttr = XMLUtil.getSchemaAttribute(attr,
0264: ((IPluginExtension) obj).getPoint());
0265: if (sAttr == null)
0266: return null;
0267:
0268: if (sAttr.getKind() == IMetaAttribute.JAVA) {
0269: IResource resource = obj.getModel()
0270: .getUnderlyingResource();
0271: if (resource == null)
0272: return null;
0273: // Revisit: NEW CODE HERE
0274: ArrayList list = new ArrayList();
0275: ICompletionProposal[] proposals = null;
0276:
0277: generateTypePackageProposals(attrValue, resource
0278: .getProject(), list, offset
0279: - attrValue.length(),
0280: IJavaSearchConstants.CLASS_AND_INTERFACE);
0281:
0282: if ((list != null) && (list.size() != 0)) {
0283: // Convert the results array list into an array of completion
0284: // proposals
0285: proposals = (ICompletionProposal[]) list
0286: .toArray(new ICompletionProposal[list
0287: .size()]);
0288: sortCompletions(proposals);
0289: return proposals;
0290: }
0291: return null;
0292: } else if (sAttr.getKind() == IMetaAttribute.RESOURCE) {
0293: // provide proposals with all resources in current plugin?
0294:
0295: } else { // we have an IMetaAttribute.STRING kind
0296: if (sAttr.getType() == null)
0297: return null;
0298: ISchemaRestriction sRestr = (sAttr.getType())
0299: .getRestriction();
0300: ArrayList objs = new ArrayList();
0301: if (sRestr == null) {
0302: ISchemaSimpleType type = sAttr.getType();
0303: if (type != null
0304: && type.getName().equals("boolean")) //$NON-NLS-1$
0305: objs = F_V_BOOLS;
0306: } else {
0307: Object[] restrictions = sRestr.getChildren();
0308: for (int i = 0; i < restrictions.length; i++)
0309: if (restrictions[i] instanceof ISchemaObject)
0310: objs.add(new VirtualSchemaObject(
0311: ((ISchemaObject) restrictions[i])
0312: .getName(), null,
0313: F_ATTRIBUTE_VALUE));
0314: }
0315: return computeAttributeProposal(attr, offset,
0316: attrValue, objs);
0317: }
0318: } else if (obj instanceof IPluginExtensionPoint) {
0319: if (attr.getAttributeValue().equals(
0320: IPluginExtensionPoint.P_SCHEMA)) {
0321: // provide proposals with all schama files in current plugin?
0322:
0323: }
0324: }
0325: return null;
0326: }
0327:
0328: /**
0329: * @param attribute
0330: * @param offset
0331: * @param currentAttributeValue
0332: * @return
0333: */
0334: private ICompletionProposal[] computeExtPointAttrProposals(
0335: IDocumentAttributeNode attribute, int offset,
0336: String currentAttributeValue) {
0337: // Get all the applicable extension points
0338: ArrayList allExtensionPoints = getAllExtensionPoints(F_EXTENSION_ATTRIBUTE_POINT_VALUE);
0339: // Ensure we found extension points
0340: if ((allExtensionPoints == null)
0341: || (allExtensionPoints.size() == 0)) {
0342: return null;
0343: }
0344: // If there is no current attribute value, then the applicable extension
0345: // points do not need to be filtered. Return the list as is
0346: if ((currentAttributeValue == null)
0347: || (currentAttributeValue.length() == 0)) {
0348: return convertListToProposal(allExtensionPoints, attribute,
0349: offset);
0350: }
0351: // Create the filtered proposal list
0352: ArrayList filteredProposalList = new ArrayList();
0353: // Filter the applicable extension points by the current attribute
0354: // value
0355: filterExtPointAttrProposals(filteredProposalList,
0356: allExtensionPoints, currentAttributeValue);
0357: // Convert into a digestable list of proposals
0358: return convertListToProposal(filteredProposalList, attribute,
0359: offset);
0360: }
0361:
0362: /**
0363: * @param node
0364: * @param offset
0365: * @param filter
0366: * @return
0367: */
0368: private ICompletionProposal[] computeRootNodeProposals(
0369: IDocumentElementNode node, int offset, String filter) {
0370: // Create the filtered proposal list
0371: ArrayList filteredProposalList = new ArrayList();
0372: // Add extension to the list
0373: addToList(
0374: filteredProposalList,
0375: filter,
0376: new VirtualSchemaObject(
0377: F_STR_EXT,
0378: PDEUIMessages.XMLContentAssistProcessor_extensions,
0379: F_EXTENSION));
0380: // Add extension point to the list
0381: addToList(
0382: filteredProposalList,
0383: filter,
0384: new VirtualSchemaObject(
0385: F_STR_EXT_PT,
0386: PDEUIMessages.XMLContentAssistProcessor_extensionPoints,
0387: F_EXTENSION_POINT));
0388: // Get all avaliable extensions with point attribute values
0389: ArrayList allExtensionPoints = getAllExtensionPoints(F_EXTENSION_POINT_AND_VALUE);
0390: // Ensure we found extension points
0391: if ((allExtensionPoints == null)
0392: || (allExtensionPoints.size() == 0)) {
0393: // Return the current proposal list without extension points
0394: return convertListToProposal(filteredProposalList, node,
0395: offset);
0396: }
0397: // If there is no current value, then the applicable extension
0398: // points do not need to be filtered. Merge the list as is with the
0399: // existing proposals
0400: if ((filter == null) || (filter.length() == 0)) {
0401: filteredProposalList.addAll(allExtensionPoints);
0402: return convertListToProposal(filteredProposalList, node,
0403: offset);
0404: }
0405: // Filter the applicable extension points by the current value
0406: filterExtPointAttrProposals(filteredProposalList,
0407: allExtensionPoints, filter);
0408: // Convert into a digestable list of proposals
0409: return convertListToProposal(filteredProposalList, node, offset);
0410: }
0411:
0412: /**
0413: * @param filteredProposalList - Must not be NULL
0414: * @param allExtensionPoints - Must not be NULL
0415: * @param filter - Must not be NULL
0416: */
0417: private void filterExtPointAttrProposals(
0418: ArrayList filteredProposalList,
0419: ArrayList allExtensionPoints, String filter) {
0420: // Create a regex pattern out of the current attribute value
0421: // Ignore case
0422: String patternString = "(?i)" + filter; //$NON-NLS-1$
0423: // Compile the pattern
0424: Pattern pattern = Pattern.compile(patternString);
0425: // Iterate over the applicable extension points and add extension points
0426: // matching the pattern to the filtered proposal list
0427: Iterator iterator = allExtensionPoints.iterator();
0428: while (iterator.hasNext()) {
0429: // Get the schema object
0430: ISchemaObject schemaObject = (ISchemaObject) iterator
0431: .next();
0432: // Ensure the schema object is defined
0433: if (schemaObject == null) {
0434: continue;
0435: }
0436: // Get the name of the schema object
0437: String name = schemaObject.getName();
0438: // If the current attribute value matches some part of the name
0439: // add it to the list
0440: if (pattern.matcher(name).find()) {
0441: filteredProposalList.add(schemaObject);
0442: }
0443: }
0444: }
0445:
0446: private ICompletionProposal[] computeAttributeProposal(
0447: IDocumentAttributeNode attr, int offset, String currValue,
0448: ArrayList validValues) {
0449: if (validValues == null)
0450: return null;
0451: ArrayList list = new ArrayList();
0452: for (int i = 0; i < validValues.size(); i++)
0453: addToList(list, currValue, (ISchemaObject) validValues
0454: .get(i));
0455:
0456: return convertListToProposal(list, attr, offset);
0457: }
0458:
0459: private ICompletionProposal[] computeCompletionProposal(
0460: IDocumentElementNode node, int offset, IDocument doc) {
0461: int prop_type = determineAssistType(node, doc, offset);
0462: switch (prop_type) {
0463: case F_ADD_ATTRIB:
0464: return computeAddAttributeProposal(F_INFER_BY_OBJECT, node,
0465: offset, doc, null, node.getXMLTagName());
0466: case F_OPEN_TAG:
0467: return computeOpenTagProposal(node, offset, doc);
0468: case F_ADD_CHILD:
0469: return computeAddChildProposal(node, offset, doc, null);
0470: }
0471: return null;
0472: }
0473:
0474: private int determineAssistType(IDocumentElementNode node,
0475: IDocument doc, int offset) {
0476: int len = node.getLength();
0477: int off = node.getOffset();
0478: if (len == -1 || off == -1)
0479: return F_NO_ASSIST;
0480:
0481: offset = offset - off; // look locally
0482: if (offset > node.getXMLTagName().length() + 1) {
0483: try {
0484: String eleValue = doc.get(off, len);
0485: int ind = eleValue.indexOf('>');
0486: if (ind > 0 && eleValue.charAt(ind - 1) == '/')
0487: ind -= 1;
0488: if (offset <= ind) {
0489: if (canInsertAttrib(eleValue, offset))
0490: return F_ADD_ATTRIB;
0491: return F_NO_ASSIST;
0492: }
0493: ind = eleValue.lastIndexOf('<');
0494: if (ind == 0 && offset == len - 1)
0495: return F_OPEN_TAG; // childless node - check if it can be cracked open
0496: if (ind + 1 < len && eleValue.charAt(ind + 1) == '/'
0497: && offset <= ind)
0498: return F_ADD_CHILD;
0499: } catch (BadLocationException e) {
0500: }
0501: }
0502: return F_NO_ASSIST;
0503: }
0504:
0505: private boolean canInsertAttrib(String eleValue, int offset) {
0506: // character before offset must be whitespace
0507: // character on offset must be whitespace, '/' or '>'
0508: char c = eleValue.charAt(offset);
0509: return offset - 1 >= 0
0510: && Character.isWhitespace(eleValue.charAt(offset - 1))
0511: && (Character.isWhitespace(c) || c == '/' || c == '>');
0512: }
0513:
0514: private ICompletionProposal[] computeAddChildProposal(
0515: IDocumentElementNode node, int offset, IDocument doc,
0516: String filter) {
0517: ArrayList propList = new ArrayList();
0518: if (node instanceof IPluginBase) {
0519: return computeRootNodeProposals(node, offset, filter);
0520: } else if (node instanceof IPluginExtensionPoint) {
0521: return null;
0522: } else {
0523: IPluginObject obj = XMLUtil.getTopLevelParent(node);
0524: if (obj instanceof IPluginExtension) {
0525: ISchemaElement sElement = XMLUtil.getSchemaElement(
0526: node, ((IPluginExtension) obj).getPoint());
0527: if ((sElement != null)
0528: && (sElement.getType() instanceof ISchemaComplexType)) {
0529: // We have a schema complex type. Either the element has attributes
0530: // or the element has children.
0531: // Generate the list of element proposals
0532: TreeSet elementSet = XMLElementProposalComputer
0533: .computeElementProposal(sElement, node);
0534: // Filter the list of element proposals
0535: Iterator iterator = elementSet.iterator();
0536: while (iterator.hasNext()) {
0537: addToList(propList, filter,
0538: (ISchemaObject) iterator.next());
0539: }
0540: } else {
0541: return null;
0542: }
0543: }
0544: }
0545: return convertListToProposal(propList, node, offset);
0546: }
0547:
0548: private ICompletionProposal[] computeOpenTagProposal(
0549: IDocumentElementNode node, int offset, IDocument doc) {
0550: IPluginObject obj = XMLUtil.getTopLevelParent(node);
0551: if (obj instanceof IPluginExtension) {
0552: ISchemaElement sElem = XMLUtil.getSchemaElement(node,
0553: ((IPluginExtension) obj).getPoint());
0554: if (sElem == null)
0555: return null;
0556: ISchemaCompositor comp = ((ISchemaComplexType) sElem
0557: .getType()).getCompositor();
0558: if (comp != null)
0559: return new ICompletionProposal[] { new XMLCompletionProposal(
0560: node, null, offset, this ) };
0561: }
0562: return null;
0563: }
0564:
0565: private ICompletionProposal[] computeAddAttributeProposal(int type,
0566: IDocumentElementNode node, int offset, IDocument doc,
0567: String filter, String tag) {
0568: String nodeName = tag;
0569: if (nodeName == null && node != null)
0570: nodeName = node.getXMLTagName();
0571: if (type == F_EXTENSION || node instanceof IPluginExtension) {
0572: ISchemaElement sElem = XMLUtil.getSchemaElement(node,
0573: node != null ? ((IPluginExtension) node).getPoint()
0574: : null);
0575: ISchemaObject[] sAttrs = sElem != null ? sElem
0576: .getAttributes()
0577: : new ISchemaObject[] {
0578: new VirtualSchemaObject(
0579: IIdentifiable.P_ID,
0580: PDEUIMessages.XMLContentAssistProcessor_extId,
0581: F_ATTRIBUTE),
0582: new VirtualSchemaObject(
0583: IPluginObject.P_NAME,
0584: PDEUIMessages.XMLContentAssistProcessor_extName,
0585: F_ATTRIBUTE),
0586: new VirtualSchemaObject(
0587: IPluginExtension.P_POINT,
0588: PDEUIMessages.XMLContentAssistProcessor_extPoint,
0589: F_ATTRIBUTE) };
0590: return computeAttributeProposals(sAttrs, node, offset,
0591: filter, nodeName);
0592: } else if (type == F_EXTENSION_POINT
0593: || node instanceof IPluginExtensionPoint) {
0594: ISchemaObject[] sAttrs = new ISchemaObject[] {
0595: new VirtualSchemaObject(
0596: IIdentifiable.P_ID,
0597: PDEUIMessages.XMLContentAssistProcessor_extPointId,
0598: F_ATTRIBUTE),
0599: new VirtualSchemaObject(
0600: IPluginObject.P_NAME,
0601: PDEUIMessages.XMLContentAssistProcessor_extPointName,
0602: F_ATTRIBUTE),
0603: new VirtualSchemaObject(
0604: IPluginExtensionPoint.P_SCHEMA,
0605: PDEUIMessages.XMLContentAssistProcessor_schemaLocation,
0606: F_ATTRIBUTE) };
0607: return computeAttributeProposals(sAttrs, node, offset,
0608: filter, nodeName);
0609: } else {
0610: IPluginObject obj = XMLUtil.getTopLevelParent(node);
0611: if (obj instanceof IPluginExtension) {
0612: ISchemaElement sElem = XMLUtil.getSchemaElement(node,
0613: node != null ? ((IPluginExtension) obj)
0614: .getPoint() : null);
0615: ISchemaObject[] sAttrs = sElem != null ? sElem
0616: .getAttributes() : null;
0617: return computeAttributeProposals(sAttrs, node, offset,
0618: filter, nodeName);
0619: }
0620: }
0621: return null;
0622: }
0623:
0624: private void addToList(ArrayList list, String filter,
0625: ISchemaObject object) {
0626: if (object == null)
0627: return;
0628: if (filter == null || filter.length() == 0)
0629: list.add(object);
0630: else {
0631: String name = object.getName();
0632: if (filter.regionMatches(true, 0, name, 0, filter.length()))
0633: list.add(object);
0634: }
0635: }
0636:
0637: private ICompletionProposal[] computeBrokenModelProposal(
0638: IDocumentElementNode parent, int offset, IDocument doc) {
0639: if (parent == null)
0640: return null;
0641:
0642: int[] offArr = new int[] { offset, offset, offset };
0643: String[] guess = guessContentRequest(offArr, doc, true);
0644: if (guess == null)
0645: return null;
0646:
0647: int elRepOffset = offArr[0];
0648: int atRepOffset = offArr[1];
0649: int atValRepOffest = offArr[2];
0650: String element = guess[0];
0651: String attr = guess[1];
0652: String attVal = guess[2];
0653:
0654: IPluginObject obj = XMLUtil.getTopLevelParent(parent);
0655: if (obj instanceof IPluginExtension) {
0656: String point = ((IPluginExtension) obj).getPoint();
0657: if (attr == null)
0658: // search for element proposals
0659: return computeAddChildProposal(parent, elRepOffset,
0660: doc, element);
0661:
0662: ISchemaElement sEle = XMLUtil.getSchemaElement(parent,
0663: point);
0664: if (sEle == null)
0665: return null;
0666: sEle = sEle.getSchema().findElement(element);
0667: if (sEle == null)
0668: return null;
0669:
0670: if (attr.indexOf('=') != -1)
0671: // search for attribute content proposals
0672: return computeBrokenModelAttributeContentProposal(
0673: parent, atValRepOffest, element, attr, attVal);
0674:
0675: // search for attribute proposals
0676: return computeAttributeProposals(sEle.getAttributes(),
0677: null, atRepOffset, attr, element);
0678: } else if (parent instanceof IPluginBase) {
0679: if (attr == null)
0680: return computeAddChildProposal(parent, elRepOffset,
0681: doc, element);
0682: if (element.equalsIgnoreCase(F_STR_EXT))
0683: return computeAddAttributeProposal(F_EXTENSION, null,
0684: atRepOffset, doc, attr, F_STR_EXT);
0685: if (element.equalsIgnoreCase(F_STR_EXT_PT))
0686: return computeAddAttributeProposal(F_EXTENSION_POINT,
0687: null, atRepOffset, doc, attr, F_STR_EXT_PT);
0688: }
0689: return null;
0690: }
0691:
0692: private ICompletionProposal[] computeBrokenModelAttributeContentProposal(
0693: IDocumentElementNode parent, int offset, String element,
0694: String attr, String filter) {
0695: // TODO use computeCompletionProposal(IDocumentAttributeNode attr, int offset) if possible
0696: // or refactor above to be used here
0697: // CURRENTLY: attribute completion only works in non-broken models
0698: return null;
0699: }
0700:
0701: private String[] guessContentRequest(int[] offset, IDocument doc,
0702: boolean brokenModel) {
0703: StringBuffer nodeBuffer = new StringBuffer();
0704: StringBuffer attrBuffer = new StringBuffer();
0705: StringBuffer attrValBuffer = new StringBuffer();
0706: String node = null;
0707: String attr = null;
0708: String attVal = null;
0709: int quoteCount = 0;
0710: try {
0711: while (--offset[0] >= 0) {
0712: char c = doc.getChar(offset[0]);
0713: if (c == '"') {
0714: quoteCount += 1;
0715: nodeBuffer.setLength(0);
0716: attrBuffer.setLength(0);
0717: if (attVal != null) // ran into 2nd quotation mark, we are out of range
0718: continue;
0719: offset[2] = offset[0];
0720: attVal = attrValBuffer.toString();
0721: } else if (Character.isWhitespace(c)) {
0722: nodeBuffer.setLength(0);
0723: if (attr == null) {
0724: offset[1] = offset[0];
0725: int attBuffLen = attrBuffer.length();
0726: if (attBuffLen > 0
0727: && attrBuffer.charAt(attBuffLen - 1) == '=')
0728: attrBuffer.setLength(attBuffLen - 1);
0729: attr = attrBuffer.toString();
0730: }
0731: } else if (c == '<') {
0732: node = nodeBuffer.toString();
0733: break;
0734: } else if (c == '>') {
0735: // only enable content assist if user is inside an open tag
0736: return null;
0737: } else {
0738: attrValBuffer.insert(0, c);
0739: attrBuffer.insert(0, c);
0740: nodeBuffer.insert(0, c);
0741: }
0742: }
0743: } catch (BadLocationException e) {
0744: }
0745: if (node == null)
0746: return null;
0747:
0748: if (quoteCount % 2 == 0)
0749: attVal = null;
0750: else if (brokenModel)
0751: return null; // open quotes - don't provide assist
0752:
0753: return new String[] { node, attr, attVal };
0754: }
0755:
0756: protected IBaseModel getModel() {
0757: return fSourcePage.getInputContext().getModel();
0758: }
0759:
0760: protected ITextSelection getCurrentSelection() {
0761: ISelection sel = fSourcePage.getSelectionProvider()
0762: .getSelection();
0763: if (sel instanceof ITextSelection)
0764: return (ITextSelection) sel;
0765: return null;
0766: }
0767:
0768: protected void flushDocument() {
0769: fSourcePage.getInputContext().flushEditorInput();
0770: }
0771:
0772: private ICompletionProposal[] computeAttributeProposals(
0773: ISchemaObject[] sAttrs, IDocumentElementNode node,
0774: int offset, String filter, String parentName) {
0775: if (sAttrs == null || sAttrs.length == 0)
0776: return null;
0777: IDocumentAttributeNode[] attrs = node != null ? node
0778: .getNodeAttributes() : new IDocumentAttributeNode[0];
0779:
0780: ArrayList list = new ArrayList();
0781: for (int i = 0; i < sAttrs.length; i++) {
0782: int k; // if we break early we wont add
0783: for (k = 0; k < attrs.length; k++)
0784: if (attrs[k].getAttributeName().equals(
0785: sAttrs[i].getName()))
0786: break;
0787: if (k == attrs.length)
0788: addToList(list, filter, sAttrs[i]);
0789: }
0790: if (filter != null && filter.length() == 0)
0791: list.add(0, new VirtualSchemaObject(parentName, null,
0792: F_CLOSE_TAG));
0793: return convertListToProposal(list, node, offset);
0794: }
0795:
0796: private ICompletionProposal[] convertListToProposal(ArrayList list,
0797: IDocumentRange range, int offset) {
0798: ICompletionProposal[] proposals = new ICompletionProposal[list
0799: .size()];
0800: if (proposals.length == 0)
0801: return null;
0802: for (int i = 0; i < proposals.length; i++)
0803: proposals[i] = new XMLCompletionProposal(range,
0804: (ISchemaObject) list.get(i), offset, this );
0805: return proposals;
0806: }
0807:
0808: public void assistSessionEnded(ContentAssistEvent event) {
0809: fRange = null;
0810:
0811: // Reset cached internal and external extension point proposals
0812: fAllExtPoints = null;
0813: // Reset cached internal point proposals
0814: // Note: Not resetting cached external point proposals
0815: // Assumption is that the users workspace can change; but, not the
0816: // platform including all the external plugins
0817: fInternalExtPoints = null;
0818:
0819: fDocLen = -1;
0820: }
0821:
0822: public void assistSessionStarted(ContentAssistEvent event) {
0823: fAssistSessionStarted = true;
0824: }
0825:
0826: public void selectionChanged(ICompletionProposal proposal,
0827: boolean smartToggle) {
0828: }
0829:
0830: /**
0831: * @param vSchemaType
0832: * @return
0833: */
0834: private ArrayList getAllExtensionPoints(int vSchemaType) {
0835: // Return the previous extension points if defined
0836: if (fAllExtPoints != null) {
0837: return fAllExtPoints;
0838: }
0839: // Get the plugin model base
0840: IPluginModelBase model = getPluginModelBase();
0841: // Note: All plug-in extension points are cached except the
0842: // extension points defined by the plugin.xml we are currently
0843: // editing. This means if a plug-in in the workspace defines a new
0844: // extension point and the plugin.xml editor is still open, the
0845: // new extension point will not show up as a proposal because it is
0846: // using a cached list of extension points.
0847: // External extensions points are all extension points not defined by
0848: // the plugin currently being edited - opposed to non-workpspace
0849: // plugins. Internal extension points are the extension points defined
0850: // by the plugin currently being edited. May want to modify this
0851: // behaviour in the future.
0852: // Add all external extension point proposals to the list
0853: fAllExtPoints = (ArrayList) getExternalExtensionPoints(model,
0854: vSchemaType).clone();
0855: // Add all internal extension point proposals to the list
0856: fAllExtPoints.addAll(getInternalExtensionPoints(model,
0857: vSchemaType));
0858:
0859: return fAllExtPoints;
0860: }
0861:
0862: /**
0863: * @param model
0864: * @return
0865: */
0866: private ArrayList getExternalExtensionPoints(
0867: IPluginModelBase model, int vSchemaType) {
0868: // Return the previous external extension points if defined
0869: if (fExternalExtPoints != null) {
0870: updateExternalExtPointTypes(vSchemaType);
0871: return fExternalExtPoints;
0872: }
0873: // Query for all external extension points
0874: fExternalExtPoints = new ArrayList();
0875: // Get all plug-ins in the workspace
0876: IPluginModelBase[] plugins = PluginRegistry.getActiveModels();
0877: // Process each plugin
0878: for (int i = 0; i < plugins.length; i++) {
0879: // Make sure this plugin is not the one we are currently
0880: // editing which defines internal extension points.
0881: // We don't want to cache internal extension points because the
0882: // workspace can change.
0883: if (plugins[i].getPluginBase().getId().equals(
0884: model.getPluginBase().getId())) {
0885: // Skip this plugin
0886: continue;
0887: }
0888: // Get all extension points defined by this plugin
0889: IPluginExtensionPoint[] points = plugins[i].getPluginBase()
0890: .getExtensionPoints();
0891: // Process each extension point
0892: for (int j = 0; j < points.length; j++) {
0893: VirtualSchemaObject vObject = new VirtualSchemaObject(
0894: IdUtil.getFullId(points[j], model), points[j],
0895: vSchemaType);
0896: // Add the proposal to the list
0897: fExternalExtPoints.add(vObject);
0898: }
0899: }
0900: return fExternalExtPoints;
0901: }
0902:
0903: /**
0904: * Handles edge case.
0905: * External extension points are cached.
0906: * As a result, these types persist over each other depending on what
0907: * context content assist is invoked first:
0908: * F_EXTENSION_ATTRIBUTE_POINT_VALUE
0909: * F_EXTENSION_POINT_AND_VALUE
0910: * @param newVType
0911: */
0912: private void updateExternalExtPointTypes(int newVType) {
0913: // Ensure we have proposals
0914: if (fExternalExtPoints.size() == 0) {
0915: return;
0916: }
0917: // Get the first proposal
0918: VirtualSchemaObject vObject = (VirtualSchemaObject) fExternalExtPoints
0919: .get(0);
0920: // If the first proposals type is the same as the new type, then we
0921: // do not have to do the update. That is because all the proposals
0922: // have the same type.
0923: if (vObject.getVType() == newVType) {
0924: return;
0925: }
0926: // Update all the proposals with the new type
0927: Iterator iterator = fExternalExtPoints.iterator();
0928: while (iterator.hasNext()) {
0929: ((VirtualSchemaObject) iterator.next()).setVType(newVType);
0930: }
0931: }
0932:
0933: /**
0934: * @param model
0935: * @return
0936: */
0937: private ArrayList getInternalExtensionPoints(
0938: IPluginModelBase model, int vSchemaType) {
0939: // Return the previous internal extension points if defined
0940: if (fInternalExtPoints != null) {
0941: // Realistically, this line should never be hit
0942: return fInternalExtPoints;
0943: }
0944: fInternalExtPoints = new ArrayList();
0945: // Get all extension points defined by this plugin
0946: IPluginExtensionPoint[] points = model.getPluginBase()
0947: .getExtensionPoints();
0948: // Process each extension point
0949: for (int j = 0; j < points.length; j++) {
0950: VirtualSchemaObject vObject = new VirtualSchemaObject(
0951: IdUtil.getFullId(points[j], model), points[j],
0952: vSchemaType);
0953: // Add the proposal to the list
0954: fInternalExtPoints.add(vObject);
0955: }
0956: return fInternalExtPoints;
0957: }
0958:
0959: /**
0960: * Returns a BundlePluginModel which has a getId() method that works.
0961: * getModel() method returns a PluginModel whose getId() method does not
0962: * work.
0963: * @return
0964: */
0965: private IPluginModelBase getPluginModelBase() {
0966: FormEditor formEditor = fSourcePage.getEditor();
0967: if ((formEditor instanceof PDEFormEditor) == false) {
0968: return null;
0969: }
0970: IBaseModel bModel = ((PDEFormEditor) formEditor)
0971: .getAggregateModel();
0972: if ((bModel instanceof IPluginModelBase) == false) {
0973: return null;
0974: }
0975: return (IPluginModelBase) bModel;
0976: }
0977:
0978: public Image getImage(int type) {
0979: if (fImages[type] == null) {
0980: switch (type) {
0981: case F_EXTENSION_POINT:
0982: case F_EXTENSION_ATTRIBUTE_POINT_VALUE:
0983: return fImages[type] = PDEPluginImages.DESC_EXT_POINT_OBJ
0984: .createImage();
0985: case F_EXTENSION_POINT_AND_VALUE:
0986: case F_EXTENSION:
0987: return fImages[type] = PDEPluginImages.DESC_EXTENSION_OBJ
0988: .createImage();
0989: case F_ELEMENT:
0990: case F_CLOSE_TAG:
0991: return fImages[type] = PDEPluginImages.DESC_XML_ELEMENT_OBJ
0992: .createImage();
0993: case F_ATTRIBUTE:
0994: case F_ATTRIBUTE_VALUE:
0995: return fImages[type] = PDEPluginImages.DESC_ATT_URI_OBJ
0996: .createImage();
0997: }
0998: }
0999: return fImages[type];
1000: }
1001:
1002: public void dispose() {
1003: for (int i = 0; i < fImages.length; i++)
1004: if (fImages[i] != null && !fImages[i].isDisposed())
1005: fImages[i].dispose();
1006: }
1007:
1008: /**
1009: * @return
1010: */
1011: public PDESourcePage getSourcePage() {
1012: return fSourcePage;
1013: }
1014:
1015: }
|