blob: df8fd0e0b8baf4fc5193deca1a812678a5b66388 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2002, 2003 GEBIT Gesellschaft fuer EDV-Beratung
* und Informatik-Technologien mbH,
* Berlin, Duesseldorf, Frankfurt (Germany) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* GEBIT Gesellschaft fuer EDV-Beratung und Informatik-Technologien mbH - initial API and implementation
* IBM Corporation - bug fixes
*******************************************************************************/
package org.eclipse.ant.ui.internal.editor;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeSet;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Target;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.Available;
import org.apache.tools.ant.taskdefs.Parallel;
import org.apache.tools.ant.taskdefs.PathConvert;
import org.apache.tools.ant.taskdefs.Property;
import org.apache.tools.ant.taskdefs.Sequential;
import org.apache.tools.ant.taskdefs.UpToDate;
import org.apache.tools.ant.taskdefs.condition.Condition;
import org.apache.xerces.parsers.SAXParser;
import org.eclipse.ant.ui.internal.dtd.IAttribute;
import org.eclipse.ant.ui.internal.dtd.IDfm;
import org.eclipse.ant.ui.internal.dtd.IElement;
import org.eclipse.ant.ui.internal.dtd.ISchema;
import org.eclipse.ant.ui.internal.dtd.ParseError;
import org.eclipse.ant.ui.internal.dtd.Parser;
import org.eclipse.ant.ui.internal.editor.utils.ProjectHelper;
import org.eclipse.ant.ui.internal.model.AntUIImages;
import org.eclipse.ant.ui.internal.model.AntUIPlugin;
import org.eclipse.ant.ui.internal.model.IAntUIConstants;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.CompletionProposal;
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.swt.graphics.Image;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.part.FileEditorInput;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
/**
* The text completion processor for the Ant Editor.
*
* @author Alf Schiefelbein
*/
public class AntEditorCompletionProcessor implements IContentAssistProcessor {
/**
* Helper Set that may only be used to collect
* <code>ICompletionProposal</code> instances.
* <P>
* This implementation tests the proposal objects by their display string
* for equality.
*/
protected class CompletionSet extends TreeSet {
private Map displayStringToProposal = new HashMap();
/**
* Creates an instance that compares using the
* <code>CompletionComparator</code>.
*/
public CompletionSet() {
super(new Comparator() {
public int compare(Object o1, Object o2) {
String tempString1 = ((ICompletionProposal)o1).getDisplayString();
String tempString2 = ((ICompletionProposal)o2).getDisplayString();
return tempString1.compareToIgnoreCase(tempString2);
}
});
}
/**
* Adds the specified <code>ICompletionProposal</code> only if another
* proposal with the same display string is not contained already.
*
* @param o must be instanceof <code>ICompletionProposal</code>
*/
public boolean add(Object o) {
ICompletionProposal tempProposal = (ICompletionProposal)o;
if(!displayStringToProposal.containsKey(tempProposal.getDisplayString())) {
boolean tempResult = super.add(o);
if(tempResult) {
displayStringToProposal.put(tempProposal.getDisplayString(), tempProposal);
}
return tempResult;
}
return false;
}
/**
* Returns true if no another proposal with the same display string is
* contained allready.
*
* @param o must be instanceof <code>ICompletionProposal</code>
*/
public boolean contains(Object o) {
ICompletionProposal tempProposal = (ICompletionProposal)o;
if(!displayStringToProposal.containsKey(tempProposal.getDisplayString())) {
return true;
}
return false;
}
/**
* Removes the specified proposal.
*
* @param o must be instanceof <code>ICompletionProposal</code>
*/
public boolean remove(Object o) {
ICompletionProposal tempProposal = (ICompletionProposal)o;
boolean tempResult = super.remove(o);
if(tempResult) {
Object tempObject = displayStringToProposal.remove(tempProposal.getDisplayString());
if(tempObject == null) {
throw new NoSuchElementException(AntEditorMessages.getString("AntEditorCompletionProcessor.Serious_Error")); //$NON-NLS-1$
}
}
return tempResult;
}
}
private final static int PROPOSAL_MODE_NONE = 0;
private final static int PROPOSAL_MODE_TASK_PROPOSAL = 1;
private final static int PROPOSAL_MODE_ATTRIBUTE_PROPOSAL = 2;
private final static int PROPOSAL_MODE_TASK_PROPOSAL_CLOSING = 3;
private final static int PROPOSAL_MODE_ATTRIBUTE_VALUE_PROPOSAL = 4;
private final static int PROPOSAL_MODE_PROPERTY_PROPOSAL = 5;
/**
* The line where the cursor sits now.
* <P>
* The first line has index '1'.
*/
protected int lineNumber = -1;
/**
* The startingColumn where the cursor sits now.
* <P>
* The first startingColumn has index '1'.
*/
protected int columnNumber = -1;
/**
* The additional offset required from a required attribute to
* place the cursor for the current proposal
*/
private int additionalProposalOffset = -1;
private static final String ANT_1_5_DTD_FILENAME = "/ant1.5b.dtd"; //$NON-NLS-1$
/**
* The dtd.
*/
private static ISchema dtd;
/**
* Cursor position, counted from the beginning of the document.
* <P>
* The first position has index '0'.
*/
protected int cursorPosition = -1;
/**
* The text viewer.
*/
private ITextViewer viewer;
/**
* The set of characters that will trigger the activation of the
* completion proposal computation.
*/
private char[] autoActivationChars= null;
/**
* The provider for all task and attribute descriptions.
*/
private TaskDescriptionProvider descriptionProvider = new TaskDescriptionProvider();
private AntEditorSaxDefaultHandler lastDefaultHandler;
/**
* Constructor for AntEditorCompletionProcessor.
*/
public AntEditorCompletionProcessor() {
super();
if(dtd == null) {
try {
dtd = parseDtd();
} catch (IOException e) {
AntUIPlugin.log(e);
} catch (ParseError e) {
AntUIPlugin.log(e);
}
}
}
/**
* Parses the dtd.
*/
private ISchema parseDtd() throws ParseError, IOException {
InputStream tempStream = getClass().getResourceAsStream(ANT_1_5_DTD_FILENAME);
InputStreamReader tempReader = new InputStreamReader(tempStream, "UTF-8"); //$NON-NLS-1$
Parser parser = new Parser();
ISchema schema= parser.parseDTD(tempReader, "project"); //$NON-NLS-1$
tempReader.close();
return schema;
}
/**
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeCompletionProposals(ITextViewer, int)
*/
public ICompletionProposal[] computeCompletionProposals(ITextViewer refViewer, int documentOffset) {
this.viewer = refViewer;
return determineProposals();
}
/**
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeContextInformation(ITextViewer, int)
*/
public IContextInformation[] computeContextInformation(ITextViewer refViewer, int documentOffset) {
return new IContextInformation[0];
}
/**
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getCompletionProposalAutoActivationCharacters()
*/
public char[] getCompletionProposalAutoActivationCharacters() {
return autoActivationChars;
}
/**
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getContextInformationAutoActivationCharacters()
*/
public char[] getContextInformationAutoActivationCharacters() {
return null;
}
/**
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getContextInformationValidator()
*/
public IContextInformationValidator getContextInformationValidator() {
return null;
}
/**
* @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getErrorMessage()
*/
public String getErrorMessage() {
return AntEditorMessages.getString("AntEditorCompletionProcessor.No_Text_Completions_2"); //$NON-NLS-1$
}
/**
* Returns the new determined proposals.
*/
private ICompletionProposal[] determineProposals() {
cursorPosition = ((ITextSelection)viewer.getSelectionProvider().getSelection()).getOffset();
IDocument doc = viewer.getDocument();
try {
lineNumber = doc.getLineOfOffset(cursorPosition);
columnNumber = cursorPosition - doc.getLineOffset(lineNumber);
} catch (BadLocationException e) {
AntUIPlugin.log(e);
}
String prefix = getCurrentPrefix();
if (prefix == null || cursorPosition == -1) {
AntUIPlugin.getStandardDisplay().beep();
return null;
}
ICompletionProposal[] proposals = getProposalsFromDocument(doc.get(), prefix);
return proposals;
}
/**
* Returns the proposals for the specified document.
*
* @param aDocmuentString the text of the currently edited file as one string.
* @param the prefix
*/
private ICompletionProposal[] getProposalsFromDocument(String aDocumentString, String aPrefix) {
String taskString = null;
/*
* Completions will be determined depending on the proposal mode.
*/
switch (determineProposalMode(aDocumentString, cursorPosition, aPrefix)) {
case PROPOSAL_MODE_NONE :
break;
case PROPOSAL_MODE_ATTRIBUTE_PROPOSAL:
taskString = getTaskStringFromDocumentStringToPrefix(aDocumentString.substring(0, cursorPosition-aPrefix.length()));
return getAttributeProposals(taskString, aPrefix);
case PROPOSAL_MODE_TASK_PROPOSAL:
return getTaskProposals(aDocumentString, findParentElement(aDocumentString, lineNumber, columnNumber), aPrefix);
case PROPOSAL_MODE_TASK_PROPOSAL_CLOSING:
return getClosingTaskProposals(findNotClosedParentElement(aDocumentString, lineNumber, columnNumber), aPrefix);
case PROPOSAL_MODE_ATTRIBUTE_VALUE_PROPOSAL:
taskString = getTaskStringFromDocumentStringToPrefix(aDocumentString.substring(0, cursorPosition-aPrefix.length()));
String tempAttributeString = getAttributeStringFromDocumentStringToPrefix(aDocumentString.substring(0, cursorPosition-aPrefix.length()));
return getAttributeValueProposals(taskString, tempAttributeString, aPrefix);
case PROPOSAL_MODE_PROPERTY_PROPOSAL:
return getPropertyProposals(aDocumentString, aPrefix, cursorPosition);
default :
break;
}
return new ICompletionProposal[0];
}
/**
* Returns all possible attributes for the specified task.
*
* @param aTaskName the name of the task for that the attribute shall be
* completed
* @param aPrefix prefix, that all proposals should start with. The prefix
* may be an empty string.
*/
protected ICompletionProposal[] getAttributeProposals(String aTaskName, String aPrefix) {
List tempProposals = new ArrayList();
IElement tempElement = dtd.getElement(aTaskName);
if (tempElement != null) {
Iterator tempKeys = tempElement.getAttributes().keySet().iterator();
while (tempKeys.hasNext()) {
String tempAttrName = (String) tempKeys.next();
if (tempAttrName.toLowerCase().startsWith(aPrefix)) {
IAttribute tempDTDAttribute = (IAttribute) tempElement.getAttributes().get(tempAttrName);
String tempReplacementString = tempAttrName+"=\"\""; //$NON-NLS-1$
String tempDisplayString = tempAttrName;
String[] tempItems = tempDTDAttribute.getEnum();
if (tempItems != null) {
if(tempItems.length > 1) {
tempDisplayString += " - ("; //$NON-NLS-1$
}
for (int i = 0; i < tempItems.length; i++) {
tempDisplayString += tempItems[i];
if(i+1 < tempItems.length) {
tempDisplayString += " | "; //$NON-NLS-1$
}
else {
tempDisplayString += ")"; //$NON-NLS-1$
}
}
}
String tempProposalInfo = null;
String tempRequired = descriptionProvider.getRequiredAttributeForTaskAttribute(aTaskName, tempAttrName);
if(tempRequired != null && tempRequired.length() > 0) {
tempProposalInfo = AntEditorMessages.getString("AntEditorCompletionProcessor.Required___4") + tempRequired; //$NON-NLS-1$
tempProposalInfo += "<BR><BR>"; //$NON-NLS-1$
}
String tempDescription = descriptionProvider.getDescriptionForTaskAttribute(aTaskName, tempAttrName);
if(tempDescription != null) {
tempProposalInfo = (tempProposalInfo == null ? "" : tempProposalInfo); //$NON-NLS-1$
tempProposalInfo += tempDescription;
}
ICompletionProposal tempProposal = new CompletionProposal(tempReplacementString, cursorPosition - aPrefix.length(), aPrefix.length(), tempAttrName.length()+2, null, tempDisplayString, null, tempProposalInfo);
/*
* This is how we do it, once we have the documentation.
*
* IContextInformation tempContextInfo = new ContextInformation("contextDisplayString", "informationDisplayString");
* ICompletionProposal tempProposal = new CompletionProposal(tempAttrName+"=\"\"", cursorPosition - aPrefix.length(), aPrefix.length(), tempAttrName.length()+2, null, tempAttrName, tempContextInfo, "Pustekuchen ;-) !!!");
*/
tempProposals.add(tempProposal);
}
}
}
return (ICompletionProposal[])tempProposals.toArray(new ICompletionProposal[tempProposals.size()]);
}
/**
* Returns all possible values for the specified attribute of the specified
* task.
*
* @param aTaskName the name of the task that the specified attribute
* belongs to.
*
* @param anAttributeName the name of the attribute for that the value
* shall be completed
*
* @param aPrefix prefix, that all proposals should start with. The prefix
* may be an empty string.
*/
private ICompletionProposal[] getAttributeValueProposals(String aTaskName, String anAttributeName, String aPrefix) {
List proposals = new ArrayList();
IElement taskElement = dtd.getElement(aTaskName);
if (taskElement != null) {
IAttribute attribute = (IAttribute) taskElement.getAttributes().get(anAttributeName);
if (attribute != null) {
String[] tempItems = attribute.getEnum();
if (tempItems != null) {
String item;
for (int i = 0; i < tempItems.length; i++) {
item= tempItems[i];
if(item.toLowerCase().startsWith(aPrefix)) {
ICompletionProposal proposal = new CompletionProposal(item, cursorPosition - aPrefix.length(), aPrefix.length(), item.length(), null, item, null, null);
proposals.add(proposal);
}
}
}
}
}
return (ICompletionProposal[])proposals.toArray(new ICompletionProposal[proposals.size()]);
}
/**
* Returns all possible properties for the specified prefix.
* <P>
* Note that the completion mode must be property mode, otherwise it is not
* safe to call this method.
*/
protected ICompletionProposal[] getPropertyProposals(String aDocumentText, String aPrefix, int aCursorPosition) {
Set proposals = new CompletionSet();
Map properties = findPropertiesFromDocument(aDocumentText);
String propertyName;
Image image = AntUIImages.getImage(IAntUIConstants.IMG_PROPERTY);
for(Iterator i=properties.keySet().iterator(); i.hasNext(); ) {
propertyName= (String)i.next();
if(propertyName.toLowerCase().startsWith(aPrefix)) {
String additionalPropertyInfo = (String)properties.get(propertyName);
// Determine replacement length and offset
// String from beginning to the beginning of the prefix
int replacementLength = aPrefix.length();
int replacementOffset = 0;
String stringToPrefix = aDocumentText.substring(0, aCursorPosition - aPrefix.length());
// Property proposal
String lastTwoCharacters = stringToPrefix.substring(stringToPrefix.length()-2, stringToPrefix.length());
if(lastTwoCharacters.equals("${")) { //$NON-NLS-1$
replacementLength += 2;
replacementOffset = aCursorPosition - aPrefix.length() - 2;
}
else if(lastTwoCharacters.endsWith("$")) { //$NON-NLS-1$
replacementLength += 1;
replacementOffset = aCursorPosition - aPrefix.length() - 1;
} else {
throw new AntEditorException(AntEditorMessages.getString("AntEditorCompletionProcessor.Error")); //$NON-NLS-1$
}
if(aDocumentText.length() > aCursorPosition && aDocumentText.charAt(aCursorPosition) == '}') {
replacementLength += 1;
}
String replacementString = new StringBuffer("${").append(propertyName).append('}').toString(); //$NON-NLS-1$
ICompletionProposal proposal =
new CompletionProposal(
replacementString,
replacementOffset,
replacementLength,
replacementString.length(),
image,
propertyName, null,
additionalPropertyInfo);
proposals.add(proposal);
}
}
return (ICompletionProposal[])proposals.toArray(new ICompletionProposal[proposals.size()]);
}
/**
* Returns all possible attributes for the specified parent task element.
* <P>
* No completions will be returned if <code>aParentTaskElement</code> is
* not known.
*
* @param aParentTaskName name of the parent(surrounding) task or
* <code>null</code> if completion should be done for the root element.
*
* @param aPrefix prefix, that all proposals should start with. The prefix
* may be an empty string.
*/
protected ICompletionProposal[] getTaskProposals(String aWholeDocumentString, Element aParentTaskElement, String aPrefix) {
// The code this replaced assumed that child elements
// are unordered; there was no provision for walking
// through a child sequence. This works for the Ant
// 1.5 DTD but not in general. I kept the assumption. bf
List proposals = new ArrayList();
if (aParentTaskElement == null) {
// DTDs do not designate a root element.
// The previous code must have looked for an element that
// was not in the content model of any other element.
// This test doesn't work with a DTD used to validate
// document partitions, a DTD with multiple candidate
// roots, etc. The right answer is to get
// the root element from the document. If there isn't
// one, we assume "project". bf
String rootElementName = null;
if (lastDefaultHandler != null) {
rootElementName = lastDefaultHandler.rootElementName;
}
if (rootElementName == null) {
rootElementName = "project"; //$NON-NLS-1$
}
IElement rootElement = dtd.getElement(rootElementName);
if(rootElement != null && rootElementName.toLowerCase().startsWith(aPrefix)) {
additionalProposalOffset= 0;
ICompletionProposal proposal = newCompletionProposal(aWholeDocumentString, aPrefix, rootElementName);
proposals.add(proposal);
}
} else {
IElement parent = dtd.getElement(aParentTaskElement.getTagName());
if (parent != null) {
IDfm dfm = parent.getDfm();
String[] accepts = dfm.getAccepts();
String elementName;
ICompletionProposal proposal;
for (int i = 0; i < accepts.length; i++) {
additionalProposalOffset= 0;
elementName = accepts[i];
if(elementName.toLowerCase().startsWith(aPrefix)) {
proposal = newCompletionProposal(aWholeDocumentString, aPrefix, elementName);
proposals.add(proposal);
}
}
}
}
return (ICompletionProposal[])proposals.toArray(new ICompletionProposal[proposals.size()]);
}
private ICompletionProposal newCompletionProposal(String aWholeDocumentString, String aPrefix, String elementName) {
Image proposalImage = AntUIImages.getImage(IAntUIConstants.IMG_TASK_PROPOSAL);
String proposalInfo = descriptionProvider.getDescriptionForTask(elementName);
String replacementString = getTaskProposalReplacementString(elementName);
int replacementOffset = cursorPosition - aPrefix.length();
int replacementLength = aPrefix.length();
if(replacementOffset > 0 && aWholeDocumentString.charAt(replacementOffset-1) == '<') {
replacementOffset--;
replacementLength++;
}
return new CompletionProposal(replacementString, replacementOffset,
replacementLength, elementName.length() + 2 + additionalProposalOffset,
proposalImage, elementName,
null, proposalInfo);
}
/**
* Returns the one possible completion for the specified unclosed task
* element.
*
* @param unclosedTaskElement the task element that hasn't been closed
* last
*
* @param aPrefix prefix, that the one possible proposals should start
* with. The prefix may be an empty string.
*
* @return array which may contain either one or none proposals
*/
private ICompletionProposal[] getClosingTaskProposals(Element unclosedTaskElement, String aPrefix) {
Set tempProposals = new CompletionSet();
if(unclosedTaskElement != null) {
if(unclosedTaskElement.getTagName().toLowerCase().startsWith(aPrefix)) {
String tempReplaceString = unclosedTaskElement.getTagName();
tempProposals.add(new CompletionProposal(tempReplaceString + '>', cursorPosition - aPrefix.length(), aPrefix.length(), tempReplaceString.length()+1, null, tempReplaceString, null, null));
}
}
return (ICompletionProposal[])tempProposals.toArray(new ICompletionProposal[tempProposals.size()]);
}
/**
* Returns the replacement string for the specified task name.
*/
private String getTaskProposalReplacementString(String aTaskName) {
StringBuffer replacement = new StringBuffer("<"); //$NON-NLS-1$
replacement.append(aTaskName);
Node attributeNode= descriptionProvider.getAttributesNode(aTaskName);
if (attributeNode != null) {
appendRequiredAttributes(replacement, attributeNode);
} else if ("project".equals(aTaskName)){ //$NON-NLS-1$
replacement.append(" default=\"\""); //$NON-NLS-1$
additionalProposalOffset= 9;
}
if (isEmpty(aTaskName)) {
replacement.append("/>"); //$NON-NLS-1$
} else {
replacement.append("></"); //$NON-NLS-1$
replacement.append(aTaskName);
replacement.append('>');
}
return replacement.toString();
}
private void appendRequiredAttributes(StringBuffer replacement, Node attributeNode) {
boolean requiredAdded= false;
NodeList attributes= attributeNode.getChildNodes();
String required;
Node attribute;
for (int i = 0; i < attributes.getLength(); i++) {
attribute = attributes.item(i);
required= descriptionProvider.getRequiredOfNode(attribute);
if (required.equalsIgnoreCase("yes")) { //$NON-NLS-1$
String attributeName= descriptionProvider.getTaskAttributeName(attribute);
replacement.append(' ');
replacement.append(attributeName);
replacement.append("=\"\""); //$NON-NLS-1$
if (!requiredAdded){
additionalProposalOffset= attributeName.length() + 2;
requiredAdded= true;
}
}
}
}
/**
* Returns whether the named element is empty, thus may not have any child
* elements.
*/
private boolean isEmpty(String aDTDElementName) {
IElement element = dtd.getElement(aDTDElementName);
return element.isEmpty();
}
/**
* Finds a direct child element with <code>aChildElementName</code> of
* <code>anElement</code>.
* <P>
* The child will not be searched for in the whole hierarchy but only in
* the hierarchy step below.
*
* @return the found child or <code>null</code> if not found.
*/
protected Element findChildElementNamedOf(Element anElement, String aChildElementName) {
NodeList tempNodeList = anElement.getChildNodes();
for (int i = 0; i < tempNodeList.getLength(); i++) {
Node tempChildNode = (Node)tempNodeList.item(i);
if(tempChildNode.getNodeType() == Node.ELEMENT_NODE) {
if(tempChildNode.getNodeName().equals(aChildElementName)) {
return (Element)tempChildNode;
}
}
}
return null;
}
/**
* Determines the current prefix, that should be used for completion.
*/
private String getCurrentPrefix() {
ITextSelection selection = (ITextSelection)viewer.getSelectionProvider().getSelection();
if (selection.getLength() > 0) {
return null;
}
IDocument doc = viewer.getDocument();
return getPrefixFromDocument(doc.get(), selection.getOffset()).toLowerCase();
}
/**
* Returns the prefix in the specified document text with respect to the
* specified offset.
*
* @param aDocumentString the whole content of the edited file as String
* @param anOffset the cursor position
*/
protected String getPrefixFromDocument(String aDocumentText, int anOffset) {
int startOfWordToken = anOffset;
while (startOfWordToken > 0
&& (Character.isJavaIdentifierPart(aDocumentText.charAt(startOfWordToken - 1))
|| '.' == aDocumentText.charAt(startOfWordToken - 1)
|| '-' == aDocumentText.charAt(startOfWordToken - 1))
&& !('$' == aDocumentText.charAt(startOfWordToken - 1))) {
startOfWordToken--;
}
if (startOfWordToken != anOffset) {
return aDocumentText.substring(startOfWordToken, anOffset);
} else {
return ""; //$NON-NLS-1$
}
}
/**
* Returns the current proposal mode.
*/
protected int determineProposalMode(String aWholeDocumentString, int aCursorPosition, String aPrefix) {
// String from beginning of document to the beginning of the prefix
String stringToPrefix = aWholeDocumentString.substring(0, aCursorPosition - aPrefix.length());
// Is trimmable from behind
String trimmedString = stringToPrefix.trim();
char lastChar = 0;
if(trimmedString.length() > 0) {
lastChar = trimmedString.charAt(trimmedString.length()-1);
} else {
return PROPOSAL_MODE_TASK_PROPOSAL;
}
if(stringToPrefix.charAt(stringToPrefix.length()-1) != lastChar && lastChar != '>') {
/*
* Substring must be trimmable from behind in case of attribute
* proposal because a space or a new line must be used as delimiter
* between task name and attribute or attribute and attribute.
* Example: '<property id="bla" name="hups"'
*/
// Attribute proposal
if(lastChar != '>' && lastChar != '<') {
String taskString =
getTaskStringFromDocumentStringToPrefix(
trimmedString);
if(taskString != null && isNamedTaskKnown(taskString)) {
return PROPOSAL_MODE_ATTRIBUTE_PROPOSAL;
}
}
} else if(stringToPrefix.charAt(stringToPrefix.length()-1) == '"') {
// Attribute value proposal
String taskString =
getTaskStringFromDocumentStringToPrefix(
trimmedString);
if(taskString != null && isNamedTaskKnown(taskString)) {
return PROPOSAL_MODE_ATTRIBUTE_VALUE_PROPOSAL;
}
} else { // Task proposal
int tempSpaceIndex = stringToPrefix.lastIndexOf(' ');
int tempLessThanIndex = stringToPrefix.lastIndexOf('<');
int tempGreaterThanIndex = stringToPrefix.lastIndexOf('>');
// Task proposal
if(tempLessThanIndex > tempSpaceIndex && tempGreaterThanIndex < tempLessThanIndex) {
int tempSlashIndex = stringToPrefix.lastIndexOf('/');
if(tempSlashIndex == tempLessThanIndex +1) {
return PROPOSAL_MODE_TASK_PROPOSAL_CLOSING; // ... </
}
return PROPOSAL_MODE_TASK_PROPOSAL;
}
if(tempLessThanIndex < tempGreaterThanIndex && "".equals(aPrefix)) { //$NON-NLS-1$
// no other regular character may be between '>' and cursor position
int tempActIndex = aCursorPosition;
do {
char tempChar = stringToPrefix.charAt(--tempActIndex);
if(tempChar != ' ' && tempChar != '\t' && tempChar != '\n' && tempChar != '\r') {
break; // found a character -> no task proposal mode
}
} while(tempActIndex > tempGreaterThanIndex);
// no character found in between
if(tempActIndex == tempGreaterThanIndex) {
return PROPOSAL_MODE_TASK_PROPOSAL;
}
}
}
// Property proposal
if(stringToPrefix.length() >= 2) {
String tempLastTwoCharacters = stringToPrefix.substring(stringToPrefix.length()-2, stringToPrefix.length());
if(tempLastTwoCharacters.equals("${") || //$NON-NLS-1$
stringToPrefix.charAt(stringToPrefix.length()-1) == '$') {
return PROPOSAL_MODE_PROPERTY_PROPOSAL;
}
}
return PROPOSAL_MODE_NONE;
}
/**
* Returns the last occuring task string in the specified string.
* <P>
* The returned string must not necessarily be a valid Ant task string.
* This can be tested with the method <code>inNamedTaskKnown(String)</code>
* after invoking this method.
*
* @param aDocumentStringToPrefix the String that contains the whole string
* of the currently edited file from the beginning up to the prefix for code
* completion. Example: '<project default="name"><property '.
*
* @return the extracted task string or <code>null</code> if no string could
* be extracted.
*/
private String getTaskStringFromDocumentStringToPrefix(String aDocumentStringToPrefix) {
int lessThanIndex = aDocumentStringToPrefix.lastIndexOf('<');
if(lessThanIndex > -1) {
String taskString = aDocumentStringToPrefix.trim();
taskString = taskString.substring(lessThanIndex+1, taskString.length());
int tempIndex = taskString.indexOf(' ');
if(tempIndex > 0) {
taskString = taskString.substring(0, tempIndex);
}
tempIndex = taskString.indexOf('\n');
if(tempIndex > 0) {
taskString = taskString.substring(0, tempIndex);
}
tempIndex = taskString.indexOf('\r');
if(tempIndex > 0) {
taskString = taskString.substring(0, tempIndex);
}
return taskString;
}
return null;
}
/**
* Returns the last occuring attribute string in the specified string.
* <P>
* Calling this method is only safe if the current proposal mode is really
* <code>PROPOSAL_MODE_ATTRIBUTE_VALUE_PROPOSAL</code>.
*/
private String getAttributeStringFromDocumentStringToPrefix(String aDocumentStringToPrefix) {
int tempIndex = aDocumentStringToPrefix.lastIndexOf('=');
String tempSubString = aDocumentStringToPrefix.substring(0, tempIndex);
tempSubString = tempSubString.trim();
tempIndex = tempSubString.lastIndexOf(' ');
if(tempIndex > 0) {
tempSubString = tempSubString.substring(tempIndex+1, tempSubString.length());
}
tempIndex = tempSubString.lastIndexOf('\n');
if(tempIndex > 0) {
tempSubString = tempSubString.substring(tempIndex+1, tempSubString.length());
}
tempIndex = tempSubString.lastIndexOf('\r');
if(tempIndex > 0) {
tempSubString = tempSubString.substring(tempIndex+1, tempSubString.length());
}
return tempSubString;
}
/**
* Returns whether the specified task name is known according to the DTD.
*/
private boolean isNamedTaskKnown(String aTaskName) {
return dtd.getElement(aTaskName) != null;
}
/**
* Finds the parent task element in respect to the cursor position.
*
* @return the parent task element or <code>null</code> if not found.
*/
protected Element findParentElement(String aWholeDocumentString, int aLineNumber, int aColumnNumber) {
// Return the parent
return parseEditedFileSearchingForParent(aWholeDocumentString, aLineNumber, aColumnNumber).getParentElement(true);
}
/**
* Parses the actually edited file as far as possible.
* <P>
* The returned handler can be asked about what happened while parsing
* the document.
*
* @return the handler that has been used for parsing or <code>null</code>
* if parsing couldn't be done because of some error.
*/
private AntEditorSaxDefaultHandler parseEditedFileSearchingForParent(String aWholeDocumentString, int aLineNumber, int aColumnNumber) {
SAXParser parser = getSAXParser();
if(parser == null){
return null;
}
// Set the handler
AntEditorSaxDefaultHandler handler = null;
File editedFile= getEditedFile();
try {
File parent = null;
if(editedFile != null) {
parent = editedFile.getParentFile();
}
handler = new AntEditorSaxDefaultHandler(parent, aLineNumber, aColumnNumber);
} catch (ParserConfigurationException e) {
AntUIPlugin.log(e);
}
parse(aWholeDocumentString, parser, handler, editedFile);
lastDefaultHandler = handler; // bf
return handler;
}
private void parse(String aWholeDocumentString, SAXParser parser, AntEditorSaxDefaultHandler handler, File editedFile) {
InputSource inputSource = new InputSource(new StringReader(aWholeDocumentString));
if (editedFile != null) {
//needed for resolving relative external entities
inputSource.setSystemId(editedFile.getAbsolutePath());
}
parser.setContentHandler(handler);
parser.setDTDHandler(handler);
parser.setEntityResolver(handler);
parser.setErrorHandler(handler);
try {
parser.parse(inputSource);
} catch(SAXParseException e) {
// Ignore since that happens always if the edited file is not valid. We try to handle that.
} catch (SAXException e) {
AntUIPlugin.log(e);
} catch (IOException e) {
//ignore since can happen when user has incorrect paths / protocols for external entities
}
}
private SAXParser getSAXParser() {
SAXParser parser = null;
try {
parser = new SAXParser();
parser.setFeature("http://xml.org/sax/features/namespaces", false); //$NON-NLS-1$
} catch (SAXException e) {
AntUIPlugin.log(e);
}
return parser;
}
/**
* Parses the actually edited file as far as possible.
* <P>
* We use the parsing facilities of the ant plug-in here.
*
* @return a map with all the found properties
*/
private Map findPropertiesFromDocument(String aWholeDocumentString) {
/*
* What is implemented here:
* - We first use the ant plug-in to create a Project instance.
* - We determine the enclosing parent task element
* - We determine the dependency Vector for the parent task element
* - We work our way through the dependency Vector and execute the
* Property relevant tasks.
*/
// Create an initialized project
Project tempProject = new Project();
tempProject.init();
/*
* Ant's parsing facilities always works on a file, therefore we need
* to determine the actual location of the file. Though the file
* contents will not be parsed. We parse the passed document string
* that is passed.
*/
File tempFile = getEditedFile();
String filePath= ""; //$NON-NLS-1$
if (tempFile != null) {
filePath= tempFile.getAbsolutePath();
}
tempProject.setUserProperty("ant.file", filePath); //$NON-NLS-1$
try {
ProjectHelper.configureProject(tempProject, tempFile, aWholeDocumentString); // File will be parsed here
}
catch(BuildException e) {
// ignore a build exception on purpose, since we also parse invalid
// build files.
}
Map properties = tempProject.getProperties();
// Determine the parent
Element tempElement = findEnclosingTargetElement(aWholeDocumentString, lineNumber, columnNumber);
String tempTargetName = null;
if(tempElement == null
|| (tempTargetName = tempElement.getAttribute("name")) == null //$NON-NLS-1$
|| tempTargetName.length() == 0) {
return properties;
}
List tempSortedTargets = null;
try {
tempSortedTargets= tempProject.topoSort(tempTargetName, tempProject.getTargets());
} catch (BuildException be) {
return tempProject.getProperties();
}
int curidx = 0;
Target curtarget;
do {
curtarget = (Target) tempSortedTargets.get(curidx++);
Task[] tempTasks = curtarget.getTasks();
for (int i = 0; i < tempTasks.length; i++) {
Task tempTask = tempTasks[i];
// sequential
if(tempTask instanceof Sequential) {
// (T)
}
// parallel
if(tempTask instanceof Parallel) {
// (T)
}
// waitfor (@since Ant 1.5)
// if(tempTask instanceof WaitFor) {
// // (T)
// }
if(tempTask instanceof Property
|| tempTask instanceof PathConvert
|| tempTask instanceof Available
|| tempTask instanceof UpToDate
|| tempTask instanceof Condition) {
((Task)tempTask).perform();
}
// Ant 1.5
// if(tempTask instanceof LoadFile) {
//
// }
// Ant 1.5
// if(tempTask instanceof XmlProperty) {
//
// }
// Ant 1.5
// if(tempTask instanceof Basename) {
//
// }
// Ant 1.5
// if(tempTask instanceof Dirname) {
//
// }
// Ant 1.5
// if(tempTask instanceof LoadProperties) {
//
// }
}
} while (!curtarget.getName().equals(tempTargetName));
// Need to reget it since tempTable hasn't been updated with Ant 1.5
return tempProject.getProperties();
}
protected File getEditedFile() {
IWorkbenchPage page= AntUIPlugin.getActivePage();
if (page == null) {
return null;
}
IEditorPart editor= page.getActiveEditor();
if (editor == null) {
return null;
}
FileEditorInput tempInput = (FileEditorInput) editor.getEditorInput();
String tempProjectPath = tempInput.getFile().getProject().getLocation().toFile().getAbsolutePath();
String tempProjectRelativeFilePath = tempInput.getFile().getFullPath().removeFirstSegments(1).makeRelative().toString();
File tempFile = new File(tempProjectPath + File.separator +tempProjectRelativeFilePath);
return tempFile;
}
/**
* Finds the parent task element in respect to the cursor position which
* that has not been closed yet.
*
* @return the not closed parent task element or <code>null</code> if not
* found.
*/
private Element findNotClosedParentElement(String aWholeDocumentString, int aLineNumber, int aColumnNumber) {
AntEditorSaxDefaultHandler handler = parseEditedFileSearchingForParent(aWholeDocumentString, aLineNumber, aColumnNumber);
if(handler != null) {
// A not closed parent element can only be found by guessing.
if(handler.getParentElement(false) == null) {
return handler.getParentElement(true);
}
}
return null;
}
/**
* Finds the enclosing target element in respect to the cursor position.
*
* @return the enclosing target element or <code>null</code> if not found.
*/
private Element findEnclosingTargetElement(String aWholeDocumentString, int aLineNumber, int aColumnNumber) {
// Get a new SAX Parser
SAXParser parser = getSAXParser();
if (parser == null) {
return null;
}
// Set the handler
EnclosingTargetSearchingHandler handler = null;
File editedFile= getEditedFile();
try {
File parent = null;
if(editedFile != null) {
parent = editedFile.getParentFile();
}
handler = new EnclosingTargetSearchingHandler(parent, aLineNumber, aColumnNumber);
} catch (ParserConfigurationException e) {
AntUIPlugin.log(e);
}
parse(aWholeDocumentString, parser, handler, editedFile);
return handler.getParentElement(true);
}
/**
* Sets this processor's set of characters triggering the activation of the
* completion proposal computation.
*
* @param activationSet the activation set
*/
public void setCompletionProposalAutoActivationCharacters(char[] activationSet) {
autoActivationChars= activationSet;
}
}