| /******************************************************************************* |
| * 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; |
| } |
| } |