blob: 235f0ac78c734ceaf2ee06c83e0106154ca48e69 [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.internal.ui.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.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
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.internal.ui.dtd.IAttribute;
import org.eclipse.ant.internal.ui.dtd.IDfm;
import org.eclipse.ant.internal.ui.dtd.IElement;
import org.eclipse.ant.internal.ui.dtd.ISchema;
import org.eclipse.ant.internal.ui.dtd.ParseError;
import org.eclipse.ant.internal.ui.dtd.Parser;
import org.eclipse.ant.internal.ui.editor.utils.ProjectHelper;
import org.eclipse.ant.internal.ui.model.AntUIImages;
import org.eclipse.ant.internal.ui.model.AntUIPlugin;
import org.eclipse.ant.internal.ui.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.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.
*/
public class AntEditorCompletionProcessor implements IContentAssistProcessor {
private Comparator proposalComparator= new Comparator() {
public int compare(Object o1, Object o2) {
String string1 = ((ICompletionProposal)o1).getDisplayString();
String string2 = ((ICompletionProposal)o2).getDisplayString();
return string1.compareToIgnoreCase(string2);
}
};
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 stream = getClass().getResourceAsStream(ANT_1_5_DTD_FILENAME);
InputStreamReader reader = new InputStreamReader(stream, "UTF-8"); //$NON-NLS-1$
Parser parser = new Parser();
ISchema schema= parser.parseDTD(reader, "project"); //$NON-NLS-1$
reader.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 documentString, String prefix) {
String taskString = null;
ICompletionProposal[] proposals= null;
/*
* Completions will be determined depending on the proposal mode.
*/
switch (determineProposalMode(documentString, cursorPosition, prefix)) {
case PROPOSAL_MODE_ATTRIBUTE_PROPOSAL:
taskString = getTaskStringFromDocumentStringToPrefix(documentString.substring(0, cursorPosition-prefix.length()));
proposals= getAttributeProposals(taskString, prefix);
break;
case PROPOSAL_MODE_TASK_PROPOSAL:
proposals= getTaskProposals(documentString, findParentElement(documentString, lineNumber, columnNumber), prefix);
break;
case PROPOSAL_MODE_TASK_PROPOSAL_CLOSING:
proposals= getClosingTaskProposals(findNotClosedParentElement(documentString, lineNumber, columnNumber), prefix);
break;
case PROPOSAL_MODE_ATTRIBUTE_VALUE_PROPOSAL:
taskString = getTaskStringFromDocumentStringToPrefix(documentString.substring(0, cursorPosition-prefix.length()));
String attributeString = getAttributeStringFromDocumentStringToPrefix(documentString.substring(0, cursorPosition-prefix.length()));
proposals=getAttributeValueProposals(taskString, attributeString, prefix);
break;
case PROPOSAL_MODE_PROPERTY_PROPOSAL:
proposals= getPropertyProposals(documentString, prefix, cursorPosition);
break;
case PROPOSAL_MODE_NONE :
default :
proposals= new ICompletionProposal[0];
}
Arrays.sort(proposals, proposalComparator);
return proposals;
}
/**
* 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 proposals = new ArrayList();
IElement element = dtd.getElement(aTaskName);
if (element != null) {
Iterator keys = element.getAttributes().keySet().iterator();
while (keys.hasNext()) {
String attrName = (String) keys.next();
if (attrName.toLowerCase().startsWith(aPrefix)) {
IAttribute dtdAttributes = (IAttribute) element.getAttributes().get(attrName);
String replacementString = attrName+"=\"\""; //$NON-NLS-1$
String displayString = attrName;
String[] items = dtdAttributes.getEnum();
if (items != null) {
if(items.length > 1) {
displayString += " - ("; //$NON-NLS-1$
}
for (int i = 0; i < items.length; i++) {
displayString += items[i];
if(i+1 < items.length) {
displayString += " | "; //$NON-NLS-1$
} else {
displayString += ")"; //$NON-NLS-1$
}
}
}
String proposalInfo = null;
String required = descriptionProvider.getRequiredAttributeForTaskAttribute(aTaskName, attrName);
if(required != null && required.length() > 0) {
proposalInfo = AntEditorMessages.getString("AntEditorCompletionProcessor.Required___4") + required; //$NON-NLS-1$
proposalInfo += "<BR><BR>"; //$NON-NLS-1$
}
String description = descriptionProvider.getDescriptionForTaskAttribute(aTaskName, attrName);
if(description != null) {
proposalInfo = (proposalInfo == null ? "" : proposalInfo); //$NON-NLS-1$
proposalInfo += description;
}
ICompletionProposal proposal = new AntCompletionProposal(replacementString, cursorPosition - aPrefix.length(), aPrefix.length(), attrName.length()+2, null, displayString, proposalInfo, AntCompletionProposal.TASK_PROPOSAL);
proposals.add(proposal);
}
}
}
return (ICompletionProposal[])proposals.toArray(new ICompletionProposal[proposals.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[] items = attribute.getEnum();
if (items != null) {
String item;
for (int i = 0; i < items.length; i++) {
item= items[i];
if(item.toLowerCase().startsWith(aPrefix)) {
ICompletionProposal proposal = new AntCompletionProposal(item, cursorPosition - aPrefix.length(), aPrefix.length(), item.length(), null, item, null, AntCompletionProposal.TASK_PROPOSAL);
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) {
List proposals = new ArrayList();
Map displayStringToProposals= new HashMap();
Map properties = findPropertiesFromDocument(aDocumentText);
String propertyName;
Image image = AntUIImages.getImage(IAntUIConstants.IMG_PROPERTY);
// 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;
}
for(Iterator i=properties.keySet().iterator(); i.hasNext(); ) {
propertyName= (String)i.next();
if(propertyName.toLowerCase().startsWith(aPrefix)) {
String additionalPropertyInfo = (String)properties.get(propertyName);
String replacementString = new StringBuffer("${").append(propertyName).append('}').toString(); //$NON-NLS-1$
if (displayStringToProposals.get(propertyName) == null) {
ICompletionProposal proposal =
new AntCompletionProposal(
replacementString, replacementOffset, replacementLength,
replacementString.length(), image, propertyName,
additionalPropertyInfo, AntCompletionProposal.PROPERTY_PROPOSAL);
proposals.add(proposal);
displayStringToProposals.put(propertyName, 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 aWholeDocumentString the entire document up to the cursor position
*
* @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 AntCompletionProposal(replacementString, replacementOffset,
replacementLength, elementName.length() + 2 + additionalProposalOffset,
proposalImage, elementName, proposalInfo, AntCompletionProposal.TASK_PROPOSAL);
}
/**
* 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 prefix) {
ICompletionProposal[] proposals= null;
if(unclosedTaskElement != null) {
if(unclosedTaskElement.getTagName().toLowerCase().startsWith(prefix)) {
String replaceString = unclosedTaskElement.getTagName();
proposals= new ICompletionProposal[1];
proposals[0]= new AntCompletionProposal(replaceString + '>', cursorPosition - prefix.length(), prefix.length(), replaceString.length()+1, null, replaceString, null, AntCompletionProposal.TASK_PROPOSAL);
}
}
if (proposals == null) {
proposals= new ICompletionProposal[0];
}
return proposals;
}
/**
* 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 nodeList = anElement.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node childNode = nodeList.item(i);
if(childNode.getNodeType() == Node.ELEMENT_NODE) {
if(childNode.getNodeName().equals(aChildElementName)) {
return (Element)childNode;
}
}
}
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;
char token= 'a';
if (startOfWordToken > 0) {
token= aDocumentText.charAt(startOfWordToken - 1);
}
while (startOfWordToken > 0
&& (Character.isJavaIdentifierPart(token)
|| '.' == token
|| '-' == token)
&& !('$' == token)) {
startOfWordToken--;
token= aDocumentText.charAt(startOfWordToken - 1);
}
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 { // Possibly a Task proposal
int spaceIndex = stringToPrefix.lastIndexOf(' ');
int lessThanIndex = stringToPrefix.lastIndexOf('<');
int greaterThanIndex = stringToPrefix.lastIndexOf('>');
// Task proposal
if(lessThanIndex > spaceIndex && greaterThanIndex < lessThanIndex) {
int slashIndex = stringToPrefix.lastIndexOf('/');
if(slashIndex == lessThanIndex +1) {
return PROPOSAL_MODE_TASK_PROPOSAL_CLOSING; // ... </
}
return PROPOSAL_MODE_TASK_PROPOSAL;
}
if(lessThanIndex < greaterThanIndex) {
if (isPropertyProposalMode(stringToPrefix)) {
return PROPOSAL_MODE_PROPERTY_PROPOSAL;
}
return PROPOSAL_MODE_TASK_PROPOSAL;
}
}
// Property proposal
if (isPropertyProposalMode(stringToPrefix)) {
return PROPOSAL_MODE_PROPERTY_PROPOSAL;
}
return PROPOSAL_MODE_NONE;
}
private boolean isPropertyProposalMode(String stringToPrefix) {
if(stringToPrefix.length() >= 2) {
String lastTwoChars = stringToPrefix.substring(stringToPrefix.length()-2, stringToPrefix.length());
if(lastTwoChars.equals("${") || //$NON-NLS-1$
stringToPrefix.charAt(stringToPrefix.length()-1) == '$') {
return true;
}
}
return false;
}
/**
* 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 index = taskString.indexOf(' ');
if(index > 0) {
taskString = taskString.substring(0, index);
}
index = taskString.indexOf('\n');
if(index > 0) {
taskString = taskString.substring(0, index);
}
index = taskString.indexOf('\r');
if(index > 0) {
taskString = taskString.substring(0, index);
}
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 docStringToPrefix) {
int index = docStringToPrefix.lastIndexOf('=');
String subString = docStringToPrefix.substring(0, index);
subString = subString.trim();
index = subString.lastIndexOf(' ');
if(index > 0) {
subString = subString.substring(index+1, subString.length());
}
index = subString.lastIndexOf('\n');
if(index > 0) {
subString = subString.substring(index+1, subString.length());
}
index = subString.lastIndexOf('\r');
if(index > 0) {
subString = subString.substring(index+1, subString.length());
}
return subString;
}
/**
* 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 project = new Project();
project.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 file = getEditedFile();
String filePath= ""; //$NON-NLS-1$
if (file != null) {
filePath= file.getAbsolutePath();
}
project.setUserProperty("ant.file", filePath); //$NON-NLS-1$
try {
ProjectHelper.configureProject(project, file, aWholeDocumentString); // File will be parsed here
}
catch(BuildException e) {
// ignore a build exception on purpose, since we also parse invalid
// build files.
}
Map properties = project.getProperties();
// Determine the parent
Element element = findEnclosingTargetElement(aWholeDocumentString, lineNumber, columnNumber);
String targetName = null;
if(element == null
|| (targetName = element.getAttribute("name")) == null //$NON-NLS-1$
|| targetName.length() == 0) {
return properties;
}
List sortedTargets = null;
try {
sortedTargets= project.topoSort(targetName, project.getTargets());
} catch (BuildException be) {
return project.getProperties();
}
int curidx = 0;
Target curtarget;
do {
curtarget = (Target) sortedTargets.get(curidx++);
Task[] tasks = curtarget.getTasks();
for (int i = 0; i < tasks.length; i++) {
Task task = tasks[i];
// sequential
if(task instanceof Sequential) {
// (T)
}
// parallel
if(task instanceof Parallel) {
// (T)
}
// waitfor (@since Ant 1.5)
// if(tempTask instanceof WaitFor) {
// // (T)
// }
if(task instanceof Property
|| task instanceof PathConvert
|| task instanceof Available
|| task instanceof UpToDate
|| task instanceof Condition) {
task.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(targetName));
// Need to reget it since tempTable hasn't been updated with Ant 1.5
return project.getProperties();
}
protected File getEditedFile() {
IWorkbenchPage page= AntUIPlugin.getActivePage();
if (page == null) {
return null;
}
IEditorPart editor= page.getActiveEditor();
if (editor == null) {
return null;
}
FileEditorInput input = (FileEditorInput) editor.getEditorInput();
String projectPath = input.getFile().getProject().getLocation().toFile().getAbsolutePath();
String projectRelativeFilePath = input.getFile().getFullPath().removeFirstSegments(1).makeRelative().toString();
return new File(projectPath + File.separator + projectRelativeFilePath);
}
/**
* 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;
}
}