| /******************************************************************************* |
| * Copyright (c) 2002, 2011 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 Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * GEBIT Gesellschaft fuer EDV-Beratung und Informatik-Technologien mbH - initial API and implementation |
| * IBM Corporation - bug fixes |
| * John-Mason P. Shackelford (john-mason.shackelford@pearson.com) - bug 49445 |
| *******************************************************************************/ |
| |
| package org.eclipse.ant.internal.ui.model; |
| |
| import java.io.File; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import com.ibm.icu.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.ant.internal.core.IAntCoreConstants; |
| import org.eclipse.ant.internal.ui.AntImageDescriptor; |
| import org.eclipse.ant.internal.ui.AntUIImages; |
| import org.eclipse.ant.internal.ui.AntUtil; |
| import org.eclipse.ant.internal.ui.IAntUIConstants; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.runtime.IAdaptable; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.jface.resource.ImageDescriptor; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.swt.graphics.Image; |
| |
| /** |
| * General representation of an Ant buildfile element. |
| * |
| */ |
| public class AntElementNode implements IAdaptable, IAntElement { |
| |
| /** |
| * The offset of the corresponding source. |
| * @see #getOffset() |
| */ |
| protected int fOffset= -1; |
| |
| /** |
| * The length of the corresponding source. |
| * @see #getLength() |
| */ |
| protected int fLength= -1; |
| |
| /** |
| * The length of the source to select for this node |
| */ |
| protected int fSelectionLength; |
| |
| /** |
| * The parent node. |
| */ |
| protected AntElementNode fParent; |
| |
| /** |
| * The import node that "imported" this element |
| */ |
| private AntElementNode fImportNode; |
| |
| /** |
| * The child nodes. |
| */ |
| protected List fChildNodes= null; |
| |
| /** |
| * The (tag-)name of the element. |
| */ |
| protected String fName; |
| |
| /** |
| * Whether this element has been generated as part of an element hierarchy |
| * this has problems. This is the severity of the problem. |
| * @see XMLProblem#NO_PROBLEM |
| * @see XMLProblem#SEVERITY_ERROR |
| * @see XMLProblem#SEVERITY_WARNING |
| * @see XMLProblem#SEVERITY_FATAL_ERROR |
| */ |
| private int fProblemSeverity= AntModelProblem.NO_PROBLEM; |
| |
| private String fProblemMessage= null; |
| |
| /** |
| * The absolute file system path of the file this element is |
| * defined within. |
| */ |
| private String fFilePath; |
| |
| /** |
| * Whether this element has been generated from an external entity definition |
| */ |
| private boolean fIsExternal = false; |
| |
| /** |
| * The unique (in the corresponding element tree) path of this element. |
| */ |
| private String fElementPath; |
| |
| /** |
| * The (not necessarily unique) identifier of this element. |
| */ |
| private String fElementIdentifier; |
| |
| /** |
| * The problem associated with this node. May be <code>null</code>. |
| */ |
| private IProblem fProblem; |
| |
| /** |
| * The unique index of this element in it's parents child collection |
| */ |
| private int fIndex= 0; |
| |
| /** |
| * Only used when opening an import element to indicate the location in the imported file |
| */ |
| private int fLine; |
| private int fColumn; |
| |
| /** |
| * Creates an instance with the specified name. |
| */ |
| public AntElementNode(String aName) { |
| fName = aName; |
| } |
| |
| public AntElementNode() { |
| } |
| |
| /** |
| * Returns the name. |
| */ |
| public String getName() { |
| return fName; |
| } |
| |
| /** |
| * Returns the label that is used for display. |
| * <P> |
| * The default implementation returns just the same as the method <code>getName()</code>. |
| * Override this method in your own subclass for special elements in order to provide a |
| * custom label. |
| */ |
| public String getLabel() { |
| return getName(); |
| } |
| |
| /** |
| * Returns the child nodes. |
| */ |
| public List getChildNodes() { |
| return fChildNodes; |
| } |
| |
| /** |
| * Returns the parent <code>AntElementNode</code>. |
| * |
| * @return the parent or <code>null</code> if this element has no parent. |
| */ |
| public AntElementNode getParentNode() { |
| return fParent; |
| } |
| |
| public AntProjectNode getProjectNode() { |
| AntElementNode projectParent= getParentNode(); |
| while (projectParent != null && !(projectParent instanceof AntProjectNode)) { |
| projectParent= projectParent.getParentNode(); |
| } |
| return (AntProjectNode)projectParent; |
| } |
| |
| /** |
| * Adds the specified element as a child. |
| * <P> |
| * The specified element will have this assigned as its parent. |
| */ |
| public void addChildNode(AntElementNode childElement) { |
| childElement.setParent(this); |
| synchronized (this) { |
| if (fChildNodes == null) { |
| fChildNodes= new ArrayList(); |
| } |
| fChildNodes.add(childElement); |
| childElement.setIndex(fChildNodes.size() - 1); |
| } |
| } |
| |
| private void setIndex(int index) { |
| fIndex= index; |
| } |
| |
| protected void setParent(AntElementNode node) { |
| fParent= node; |
| } |
| |
| /** |
| * Sets the absolute file system path of the file this element is defined |
| * within. |
| */ |
| public void setFilePath(String path) { |
| if (path == null) { |
| return; |
| } |
| URL url= null; |
| try { |
| url= new URL(path); |
| } catch (MalformedURLException e) { |
| fFilePath= path; |
| return; |
| } |
| fFilePath = new Path(new File(url.getPath()).getAbsolutePath()).toString(); |
| } |
| |
| /** |
| * Returns the absolute file system path of the file this element is defined |
| * within. Only relevant for nodes that are external |
| * @see #isExternal() |
| */ |
| public String getFilePath() { |
| return fFilePath; |
| } |
| |
| /** |
| * Returns the 0-based index of the first character of the source code for this element, |
| * relative to the source buffer in which this element is contained. |
| * |
| * @return the 0-based index of the first character of the source code for this element, |
| * relative to the source buffer in which this element is contained |
| */ |
| public int getOffset() { |
| return fOffset; |
| } |
| |
| /** |
| * Sets the offset. |
| * |
| * @see #getOffset() |
| */ |
| public void setOffset(int anOffset) { |
| fOffset = anOffset; |
| } |
| |
| /** |
| * Returns the number of characters of the source code for this element, |
| * relative to the source buffer in which this element is contained. |
| * |
| * @return the number of characters of the source code for this element, |
| * relative to the source buffer in which this element is contained |
| */ |
| public int getLength() { |
| return fLength; |
| } |
| |
| /** |
| * Sets the length. |
| * |
| * @see #getLength() |
| */ |
| public void setLength(int aLength) { |
| fLength = aLength; |
| if (fProblem != null && fProblem instanceof AntModelProblem) { |
| ((AntModelProblem)fProblem).setLength(aLength); |
| } |
| } |
| |
| /** |
| * Returns a string representation of this element. |
| */ |
| public String toString() { |
| return "Ant Element Node: " + getLabel() + " Offset: " + getOffset() + " Length: " + getLength(); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ |
| } |
| |
| /** |
| * Returns whether this element has been generated as part of an element |
| * hierarchy that has error(s) associated with it |
| */ |
| public boolean isErrorNode() { |
| return fProblemSeverity == AntModelProblem.SEVERITY_ERROR || fProblemSeverity == AntModelProblem.SEVERITY_FATAL_ERROR; |
| } |
| |
| /** |
| * Returns whether this element has been generated as part of an element |
| * hierarchy that has warning(s) associated with it |
| */ |
| public boolean isWarningNode() { |
| return fProblemSeverity == AntModelProblem.SEVERITY_WARNING; |
| } |
| |
| /** |
| * Sets whether this element has been generated as part of an element |
| * hierarchy that has problems. The severity of the problem is provided. |
| */ |
| public void setProblemSeverity(int severity) { |
| fProblemSeverity= severity; |
| } |
| |
| /** |
| * Returns whether this xml element is defined in an external entity. |
| * |
| * @return boolean |
| */ |
| public boolean isExternal() { |
| return fIsExternal; |
| } |
| |
| /** |
| * Sets whether this xml element is defined in an external entity. |
| */ |
| public void setExternal(boolean isExternal) { |
| fIsExternal = isExternal; |
| } |
| |
| /** |
| * Returns a unique string representation of this element. The format of |
| * the string is not specified. |
| * |
| * @return the string representation |
| */ |
| public String getElementPath() { |
| if (fElementPath == null) { |
| StringBuffer buffer= new StringBuffer(); |
| String buildFileName= getProjectNode().getBuildFileName(); |
| if (buildFileName != null) { |
| buffer.append(buildFileName); |
| } |
| buffer.append(getParentNode() != null ? getParentNode().getElementPath() : IAntCoreConstants.EMPTY_STRING); |
| buffer.append('/'); |
| buffer.append(getElementIdentifier()); |
| buffer.append('['); |
| buffer.append(fIndex); |
| buffer.append(']'); |
| |
| fElementPath= buffer.toString(); |
| } |
| return fElementPath; |
| } |
| |
| private String getElementIdentifier() { |
| if (fElementIdentifier == null) { |
| StringBuffer buffer= escape(new StringBuffer(getName() != null ? getName() : IAntCoreConstants.EMPTY_STRING), '\\', "$/[]\\"); //$NON-NLS-1$ |
| buffer.append('$'); |
| buffer.append(escape(new StringBuffer(getLabel() != null ? getLabel() : IAntCoreConstants.EMPTY_STRING), '\\', "$/[]\\").toString()); //$NON-NLS-1$ |
| |
| fElementIdentifier= buffer.toString(); |
| } |
| return fElementIdentifier; |
| } |
| |
| private StringBuffer escape(StringBuffer sb, char esc, String special) { |
| for (int i= 0; i < sb.length(); i++) { |
| if (special.indexOf(sb.charAt(i)) >= 0) { |
| sb.insert(i++, esc); |
| } |
| } |
| |
| return sb; |
| } |
| |
| /* (non-Javadoc) |
| * @see java.lang.Object#equals(java.lang.Object) |
| */ |
| public boolean equals(Object o2) { |
| Object o1= this; |
| |
| if (o1 == o2) { |
| return true; |
| } |
| if (o2 == null) { |
| return false; |
| } |
| if (!(o1 instanceof AntElementNode || o2 instanceof AntElementNode)) { |
| return o2.equals(o1); |
| } |
| if (!(o1 instanceof AntElementNode && o2 instanceof AntElementNode)) { |
| return false; |
| } |
| |
| AntElementNode e1= (AntElementNode) o1; |
| AntElementNode e2= (AntElementNode) o2; |
| |
| return e1.getElementPath().equals(e2.getElementPath()); |
| } |
| |
| /* (non-Javadoc) |
| * @see java.lang.Object#hashCode() |
| */ |
| public int hashCode() { |
| return getElementPath().hashCode(); |
| } |
| |
| /** |
| * Returns the length of source to select for this node. |
| * @return the length of source to select |
| */ |
| public int getSelectionLength() { |
| return fSelectionLength; |
| } |
| |
| public void setSelectionLength(int selectionLength) { |
| this.fSelectionLength= selectionLength; |
| } |
| |
| /** |
| * Returns the node with the narrowest source range that contains the offset. |
| * It may be this node or one of its children or <code>null</code> if the offset is not in the source range of this node. |
| * @param sourceOffset The source offset |
| * @return the node that includes the offset in its source range or <code>null</code> |
| */ |
| public AntElementNode getNode(int sourceOffset) { |
| synchronized (this) { |
| if (fChildNodes != null) { |
| for (Iterator iter = fChildNodes.iterator(); iter.hasNext(); ) { |
| AntElementNode node = (AntElementNode) iter.next(); |
| AntElementNode containingNode= node.getNode(sourceOffset); |
| if (containingNode != null) { |
| return containingNode; |
| } |
| } |
| } |
| } |
| if (fLength == -1 && fOffset <= sourceOffset && !isExternal()) { //this is still an open element |
| return this; |
| } |
| if (fOffset <= sourceOffset && sourceOffset <= (fOffset + fLength - 2)) { |
| return this; |
| } |
| |
| return null; |
| } |
| |
| public Image getImage() { |
| int flags = 0; |
| |
| if (isErrorNode()) { |
| flags = flags | AntImageDescriptor.HAS_ERRORS; |
| } else if (isWarningNode()) { |
| flags = flags | AntImageDescriptor.HAS_WARNINGS; |
| } |
| if(fImportNode != null || isExternal()){ |
| flags = flags | AntImageDescriptor.IMPORTED; |
| } |
| ImageDescriptor base= getBaseImageDescriptor(); |
| return AntUIImages.getImage(new AntImageDescriptor(base, flags)); |
| } |
| |
| protected ImageDescriptor getBaseImageDescriptor() { |
| return AntUIImages.getImageDescriptor(IAntUIConstants.IMG_TASK_PROPOSAL); |
| } |
| |
| protected IAntModel getAntModel() { |
| AntElementNode parentNode= getParentNode(); |
| while (!(parentNode instanceof AntProjectNode)) { |
| parentNode= parentNode.getParentNode(); |
| } |
| return parentNode.getAntModel(); |
| } |
| |
| /** |
| * Sets the problem associated with this element |
| * @param problem The problem associated with this element. |
| */ |
| public void associatedProblem(IProblem problem) { |
| fProblem= problem; |
| } |
| |
| public IProblem getProblem() { |
| return fProblem; |
| } |
| |
| protected void appendEntityName(StringBuffer displayName) { |
| String path= getFilePath(); |
| |
| if (getImportNode() != null) { |
| displayName.append(MessageFormat.format(AntModelMessages.AntElementNode_9, new String[]{getImportNode().getLabel()})); |
| } else { |
| String entityName= getAntModel().getEntityName(path); |
| displayName.append(MessageFormat.format(AntModelMessages.AntElementNode_9, new String[]{entityName})); |
| } |
| } |
| |
| public AntElementNode getImportNode() { |
| return fImportNode; |
| } |
| |
| public void setImportNode(AntElementNode importNode) { |
| fImportNode = importNode; |
| } |
| |
| public boolean hasChildren() { |
| if (fChildNodes == null) { |
| return false; |
| } |
| return !fChildNodes.isEmpty(); |
| } |
| |
| public void reset() { |
| fChildNodes= null; |
| } |
| |
| public void setExternalInfo(int line, int column) { |
| fLine= line; |
| fColumn= column; |
| } |
| |
| public int[] getExternalInfo() { |
| return new int[] {fLine, fColumn}; |
| } |
| |
| /** |
| * Return the resource that contains the definition of this |
| * Ant node. |
| * @return The resource that contains the definition of this ant node or <code>null</code> |
| * if that resource could not be determined (a buildfile that is external to the workspace). |
| */ |
| public IFile getIFile() { |
| if (isExternal()) { |
| return AntUtil.getFileForLocation(fFilePath, null); |
| } |
| return getBuildFileResource(); |
| } |
| |
| /** |
| * Return the resource that is the main build file for this |
| * Ant node. |
| * @return The resource that is the main buildfile for this ant node or <code>null</code> |
| * if that resource could not be determined (a buildfile that is external to the workspace). |
| */ |
| public IFile getBuildFileResource() { |
| LocationProvider locationProvider= getAntModel().getLocationProvider(); |
| return locationProvider.getFile(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class) |
| */ |
| public Object getAdapter(Class adapter) { |
| return Platform.getAdapterManager().getAdapter(this, adapter); |
| } |
| |
| /** |
| * Returns whether this node is a structural node that should be shown in the buildfile outline. |
| * For example, an AntCommentNode would return <code>false</code> |
| * |
| * @return whether this node is a structural node that should be shown in the buildfile outline |
| */ |
| public boolean isStructuralNode() { |
| return true; |
| } |
| |
| /** |
| * Returns whether to collapse the code folding projection (region) represented by this node. |
| * @return whether the user preference is set to collapse the code folding projection (region) |
| * represented by this node |
| */ |
| public boolean collapseProjection() { |
| return false; |
| } |
| |
| public void dispose() { |
| getAntModel().dispose(); |
| } |
| |
| /** |
| * Returns the name or path of the element referenced at the offset within the declaration of this node or |
| * <code>null</code> if no element is referenced at the offset |
| * @param offset The offset within the declaration of this node |
| * @return <code>null</code> or the name or path of the referenced element |
| */ |
| public String getReferencedElement(int offset) { |
| return null; |
| } |
| |
| public String getProblemMessage() { |
| return fProblemMessage; |
| } |
| |
| public void setProblemMessage(String problemMessage) { |
| fProblemMessage = problemMessage; |
| } |
| |
| /** |
| * Returns whether this node contains a reference to the supplied identifier |
| * |
| * @param identifier |
| * @return whether this node contains a reference to the supplied identifier |
| */ |
| public boolean containsOccurrence(String identifier) { |
| return false; |
| } |
| |
| /** |
| * Returns the identifier to use for matching occurrences in the Ant editor. |
| * |
| * @return the occurrences identifier for this node |
| */ |
| public String getOccurrencesIdentifier() { |
| return getLabel(); |
| } |
| |
| /** |
| * Returns whether the supplied region can be considered as an area in this node containing |
| * a reference. |
| * |
| * @param region the area to consider for finding a reference |
| * @return whether a reference could exist in this node from the supplied region |
| */ |
| public boolean isRegionPotentialReference(IRegion region) { |
| return region.getOffset() >= fOffset; |
| } |
| |
| /** |
| * Returns the complete live list of offsets for the given identifier |
| * @param identifier |
| * @return the list of offsets for the given identifier |
| */ |
| public List computeIdentifierOffsets(String identifier) { |
| return null; |
| } |
| |
| /** |
| * Returns whether the supplied region is from within this node's |
| * declaration identifier area |
| * @param region The region to check |
| * @return whether the region is from within this node and is |
| * the declaration of a reference. |
| */ |
| public boolean isFromDeclaration(IRegion region) { |
| return false; |
| } |
| |
| protected boolean checkReferenceRegion(IRegion region, String textToSearch, String attributeName) { |
| int attributeOffset= textToSearch.indexOf(attributeName); |
| while (attributeOffset > 0 && !Character.isWhitespace(textToSearch.charAt(attributeOffset - 1))) { |
| attributeOffset= textToSearch.indexOf(attributeName, attributeOffset + 1); |
| } |
| if (attributeOffset != -1) { |
| attributeOffset+= attributeName.length(); |
| int attributeOffsetEnd = textToSearch.indexOf('"', attributeOffset); |
| attributeOffsetEnd = textToSearch.indexOf('"', attributeOffsetEnd + 1); |
| return region.getOffset() >= getOffset() + attributeOffset && (region.getOffset() + region.getLength()) <= getOffset() + attributeOffsetEnd; |
| } |
| return false; |
| } |
| } |