/*****************************************************************************
* This file is part of Rinzo
*
* Author: Claudio Cancinos
* WWW: https://sourceforge.net/projects/editorxml
* Copyright (C): 2008, Claudio Cancinos
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; If not, see <http://www.gnu.org/licenses/>
****************************************************************************/
package ar.com.tadp.xml.rinzo.core.contentassist.processors;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jface.text.IDocument;
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 ar.com.tadp.xml.rinzo.core.RinzoXMLEditor;
import ar.com.tadp.xml.rinzo.core.contentassist.proposals.XMLCompletionProposalComparator;
import ar.com.tadp.xml.rinzo.core.model.XMLNode;
/**
* Delegates method invocations to all the elements of delegated {@link IXMLContentAssistProcessor}
*
* @author ccancinos
*/
public class CompositeXMLContentAssistProcessor implements IContentAssistProcessor, IXMLContentAssistProcessor {
protected ITextViewer viewer;
private Collection<IXMLContentAssistProcessor> processors = new ArrayList<IXMLContentAssistProcessor>();
private RinzoXMLEditor editor;
public CompositeXMLContentAssistProcessor(RinzoXMLEditor xmlEditor) {
this.editor = xmlEditor;
}
public void addProcessor(IXMLContentAssistProcessor processor) {
this.processors.add(processor);
}
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int documentOffset) {
this.viewer = viewer;
ICompletionProposal result[] = null;
Collection<ICompletionProposal> resultList = new ArrayList<ICompletionProposal>();
XMLNode currentNode = this.editor.getModel().getTree().getActiveNode(documentOffset);
//TODO MAKE THIS METHOD TO CALL this.computeCompletionProposal SO EACH PROCESSOR COULD GIVE PROPOSALS FOR AN EMPTY FILE LIKE FILE TEMPLATES
if (currentNode == null) {
return new ICompletionProposal[0];
}
if(currentNode.offset == documentOffset) {
currentNode = this.editor.getModel().getTree().getPreviousNode(documentOffset);
}
this.computeCompletionProposal(resultList, documentOffset, currentNode);
result = (ICompletionProposal[]) resultList.toArray(new ICompletionProposal[resultList.size()]);
Arrays.sort(result, XMLCompletionProposalComparator.getInstance());
return result;
}
protected void computeCompletionProposal(Collection<ICompletionProposal> resultList, int offset, XMLNode currentNode) {
String prefix = this.extractPrefix(this.viewer, offset);
if (this.shouldProposeInBody(currentNode, prefix, offset)) {
this.addBodyProposals(currentNode, prefix, this.viewer, offset, resultList);
}
if (this.shouldProposeCloseTag(currentNode, prefix, offset)) {
this.addCloseTagProposals(currentNode, prefix, this.viewer, offset, resultList);
}
if (this.shouldProposeAttributeNames(currentNode, prefix, offset)) {
this.addAttributeProposals(currentNode, prefix, this.viewer, offset, resultList);
}
if (this.shouldProposeAttributeValues(currentNode, prefix, offset)) {
this.addAttributeValuesProposals(currentNode, prefix.substring(0, prefix.indexOf("=")).trim(), prefix,
this.viewer, offset, resultList);
}
}
/**
* We watch for angular brackets since those are often part of XML
* templates.
*
* @param viewer the viewer
* @param offset the offset left of which the prefix is detected
* @return the detected prefix
*/
protected String extractPrefix(ITextViewer viewer, int offset) {
IDocument document= viewer.getDocument();
int i= offset;
if (i > document.getLength()) {
return "";
}
try {
while (i > 0) {
i--;
char ch= document.getChar(i);
if (ch != '<' && !(Character.isJavaIdentifierPart(ch) || ch == ':' || ch == '=' || ch == '"' || ch == '\'')) {
i++;
break;
}
}
return document.get(i, offset - i);
} catch (Exception e) {
return "";
}
}
private boolean shouldProposeAttributeValues(XMLNode currentNode, String prefix, int offset) {
return !currentNode.isTextTag() && isInAttributeValue(prefix);
}
private boolean shouldProposeAttributeNames(XMLNode currentNode, String prefix, int offset) {
return !currentNode.isTextTag() && !currentNode.isEndTag() && !isInAttributeValue(prefix)
&& !prefix.startsWith("<") && !prefix.trim().endsWith("=");
}
private boolean shouldProposeCloseTag(XMLNode currentNode, String prefix, int offset) {
return (currentNode.isIncompleteTag() && !StringUtils.isEmpty(currentNode.getTagName()) && (StringUtils.isEmpty(prefix) || prefix.startsWith("<")))
||
(currentNode.isTextTag() && currentNode.getParent().getCorrespondingNode() == null);
}
private boolean shouldProposeInBody(XMLNode currentNode, String prefix, int offset) {
return currentNode.isTextTag() || currentNode.getContent().trim().equals(prefix)
|| (currentNode.getParent() == null && currentNode.isEndTag());
}
public void addBodyProposals(XMLNode currentNode, String prefix, ITextViewer viewer, int offset, Collection<ICompletionProposal> resultList) {
for (IXMLContentAssistProcessor processor : this.processors) {
processor.addBodyProposals(currentNode, prefix, viewer, offset, resultList);
}
}
public void addAttributeProposals(XMLNode currentNode, String prefix, ITextViewer viewer, int offset, Collection<ICompletionProposal> resultList) {
for (IXMLContentAssistProcessor processor : this.processors) {
processor.addAttributeProposals(currentNode, prefix, viewer, offset, resultList);
}
}
public void addAttributeValuesProposals(XMLNode currentNode, String attributeName, String prefix, ITextViewer viewer, int offset, Collection<ICompletionProposal> resultList) {
for (IXMLContentAssistProcessor processor : this.processors) {
processor.addAttributeValuesProposals(currentNode, attributeName, prefix, viewer, offset, resultList);
}
}
public void addCloseTagProposals(XMLNode currentNode, String prefix, ITextViewer viewer, int offset, Collection<ICompletionProposal> resultList) {
for (IXMLContentAssistProcessor processor : this.processors) {
processor.addCloseTagProposals(currentNode, prefix, viewer, offset, resultList);
}
}
/*
* TODO Here I'm asking if I'm in the part of the declaration of the attribute name or the attribute value.
* This should be handled using the parser partitions and defining the class completion proposal just in the attribute value part.
*/
private boolean isInAttributeValue(String attributePrefix) {
int firsIndex = attributePrefix.indexOf("\"");
firsIndex = (firsIndex != -1) ? firsIndex : attributePrefix.indexOf("\'");
return firsIndex != -1;
}
public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
return null;
}
public char[] getCompletionProposalAutoActivationCharacters() {
return null;
}
public char[] getContextInformationAutoActivationCharacters() {
return null;
}
public String getErrorMessage() {
return null;
}
public IContextInformationValidator getContextInformationValidator() {
return null;
}
}