blob: 4649e948af90911ccdd5c5b7150566248fabbed6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2008 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.wst.xml.ui.internal.correction;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.xml.core.internal.contentmodel.CMAttributeDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNamedNodeMap;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNode;
import org.eclipse.wst.xml.core.internal.contentmodel.basic.CMNamedNodeMapImpl;
import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.ModelQuery;
import org.eclipse.wst.xml.core.internal.modelquery.ModelQueryUtil;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
public class XMLQuickAssistProcessor implements IQuickAssistProcessor {
public boolean canAssist(IQuickAssistInvocationContext invocationContext) {
return true;
}
public boolean canFix(Annotation annotation) {
return false;
}
public ICompletionProposal[] computeQuickAssistProposals(IQuickAssistInvocationContext invocationContext) {
List proposals = new ArrayList();
getLocalRenameQuickAssistProposal(proposals, invocationContext.getSourceViewer(), invocationContext.getOffset());
getSurroundWithNewElementQuickAssistProposal(proposals, invocationContext.getSourceViewer(), invocationContext.getOffset());
getInsertRequiredAttrs(proposals, invocationContext.getSourceViewer(), invocationContext.getOffset());
return (ICompletionProposal[]) proposals.toArray(new ICompletionProposal[proposals.size()]);
}
public String getErrorMessage() {
return null;
}
private void getInsertRequiredAttrs(List proposals, ISourceViewer viewer, int offset) {
IDOMNode node = (IDOMNode) getNodeAt(viewer, offset);
if ((node != null) && (node.getNodeType() == Node.ELEMENT_NODE)) {
IStructuredDocumentRegion startStructuredDocumentRegion = node.getStartStructuredDocumentRegion();
if ((startStructuredDocumentRegion != null) && startStructuredDocumentRegion.containsOffset(offset)) {
IDOMNode cursorNode = (IDOMNode) getNodeAt(viewer, offset);
List requiredAttrs = getRequiredAttrs(cursorNode);
if (requiredAttrs.size() > 0) {
NamedNodeMap currentAttrs = node.getAttributes();
List insertAttrs = new ArrayList();
if (currentAttrs.getLength() == 0) {
insertAttrs.addAll(requiredAttrs);
}
else {
for (int i = 0; i < requiredAttrs.size(); i++) {
String requiredAttrName = ((CMAttributeDeclaration) requiredAttrs.get(i)).getAttrName();
boolean found = false;
for (int j = 0; j < currentAttrs.getLength(); j++) {
String currentAttrName = currentAttrs.item(j).getNodeName();
if (requiredAttrName.compareToIgnoreCase(currentAttrName) == 0) {
found = true;
break;
}
}
if (!found) {
insertAttrs.add(requiredAttrs.get(i));
}
}
}
if (insertAttrs.size() > 0) {
proposals.add(new InsertRequiredAttrsQuickAssistProposal(insertAttrs));
}
}
}
}
}
private void getLocalRenameQuickAssistProposal(List proposals, ISourceViewer viewer, int offset) {
IDOMNode node = (IDOMNode) getNodeAt(viewer, offset);
IStructuredDocumentRegion startStructuredDocumentRegion = node == null ? null : node.getStartStructuredDocumentRegion();
IStructuredDocumentRegion endStructuredDocumentRegion = node == null ? null : node.getEndStructuredDocumentRegion();
ITextRegion region = null;
int regionTextEndOffset = 0;
if ((startStructuredDocumentRegion != null) && startStructuredDocumentRegion.containsOffset(offset)) {
region = startStructuredDocumentRegion.getRegionAtCharacterOffset(offset);
regionTextEndOffset = startStructuredDocumentRegion.getTextEndOffset(region);
}
else if ((endStructuredDocumentRegion != null) && endStructuredDocumentRegion.containsOffset(offset)) {
region = endStructuredDocumentRegion.getRegionAtCharacterOffset(offset);
regionTextEndOffset = endStructuredDocumentRegion.getTextEndOffset(region);
}
if ((region != null) && ((region.getType() == DOMRegionContext.XML_TAG_NAME) || (region.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME)) && (offset <= regionTextEndOffset)) {
proposals.add(new RenameInFileQuickAssistProposal());
}
}
private ModelQuery getModelQuery(Node node) {
if (node.getNodeType() == Node.DOCUMENT_NODE) {
return ModelQueryUtil.getModelQuery((Document) node);
}
else {
return ModelQueryUtil.getModelQuery(node.getOwnerDocument());
}
}
private List getRequiredAttrs(Node node) {
List result = new ArrayList();
ModelQuery modelQuery = getModelQuery(node);
if (modelQuery != null) {
CMElementDeclaration elementDecl = modelQuery.getCMElementDeclaration((Element) node);
if (elementDecl != null) {
CMNamedNodeMap attrMap = elementDecl.getAttributes();
CMNamedNodeMapImpl allAttributes = new CMNamedNodeMapImpl(attrMap);
List nodes = ModelQueryUtil.getModelQuery(node.getOwnerDocument()).getAvailableContent((Element) node, elementDecl, ModelQuery.INCLUDE_ATTRIBUTES);
for (int k = 0; k < nodes.size(); k++) {
CMNode cmnode = (CMNode) nodes.get(k);
if (cmnode.getNodeType() == CMNode.ATTRIBUTE_DECLARATION) {
allAttributes.put(cmnode);
}
}
attrMap = allAttributes;
Iterator it = attrMap.iterator();
CMAttributeDeclaration attr = null;
while (it.hasNext()) {
attr = (CMAttributeDeclaration) it.next();
if (attr.getUsage() == CMAttributeDeclaration.REQUIRED) {
result.add(attr);
}
}
}
}
return result;
}
private void getSurroundWithNewElementQuickAssistProposal(List proposals, ISourceViewer viewer, int offset) {
IDOMNode node = (IDOMNode) getNodeAt(viewer, offset);
if (node != null) {
proposals.add(new SurroundWithNewElementQuickAssistProposal());
}
}
/**
* Returns the closest IndexedRegion for the offset and viewer allowing
* for differences between viewer offsets and model positions. note: this
* method returns an IndexedRegion for read only
*
* @param viewer
* the viewer whose document is used to compute the proposals
* @param documentOffset
* an offset within the document for which completions should
* be computed
* @return an IndexedRegion
*/
private IndexedRegion getNodeAt(ITextViewer viewer, int documentOffset) {
// copied from ContentAssistUtils.getNodeAt()
if (viewer == null)
return null;
IndexedRegion node = null;
IModelManager mm = StructuredModelManager.getModelManager();
IStructuredModel model = null;
if (mm != null)
model = mm.getExistingModelForRead(viewer.getDocument());
try {
if (model != null) {
int lastOffset = documentOffset;
node = model.getIndexedRegion(documentOffset);
while (node == null && lastOffset >= 0) {
lastOffset--;
node = model.getIndexedRegion(lastOffset);
}
}
}
finally {
if (model != null)
model.releaseFromRead();
}
return node;
}
}