/** * This file Copyright (c) 2005-2008 Aptana, Inc. This program is * dual-licensed under both the Aptana Public License and the GNU General * Public license. You may elect to use one or the other of these licenses. * * This program is distributed in the hope that it will be useful, but * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or * NONINFRINGEMENT. Redistribution, except as permitted by whichever of * the GPL or APL you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or modify this * program under the terms of the GNU General Public License, * Version 3, as published by the Free Software Foundation. You should * have received a copy of the GNU General Public License, Version 3 along * with this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Aptana provides a special exception to allow redistribution of this file * with certain other free and open source software ("FOSS") code and certain additional terms * pursuant to Section 7 of the GPL. You may view the exception and these * terms on the web at http://www.aptana.com/legal/gpl/. * * 2. For the Aptana Public License (APL), this program and the * accompanying materials are made available under the terms of the APL * v1.0 which accompanies this distribution, and is available at * http://www.aptana.com/legal/apl/. * * You may view the GPL, Aptana's exception and additional terms, and the * APL in the file titled license.html at the root of the corresponding * plugin containing this source file. * * Any modifications to this file must keep this entire header intact. */ package com.aptana.ide.parsing.nodes; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import com.aptana.ide.io.SourceWriter; import com.aptana.ide.lexer.Lexeme; /** * @author Kevin Lindsey */ public class ParseNodeBase implements IParseNode { private static final String SEPARATOR = "/"; //$NON-NLS-1$ private String _name; private String _type; private int _typeIndex; private IParseNode _parent; private IParseNode[] _children; private int _size; private List<IParseNodeAttribute> _attributes; private String _language; private Lexeme _startingLexeme; private Lexeme _endingLexeme; /** * Create a generic parse node * * @param typeIndex * @param language */ public ParseNodeBase(int typeIndex, String language) { this(Integer.toString(typeIndex), typeIndex, language, null, null); } /** * Creates a new generic parse node * * @param typeIndex * @param language * @param startingLexeme */ public ParseNodeBase(int typeIndex, String language, Lexeme startingLexeme) { this(Integer.toString(typeIndex), typeIndex, language, startingLexeme, startingLexeme); } /** * Creates a new generic parse node * * @param typeIndex * @param language * @param startingLexeme */ public ParseNodeBase(int typeIndex, String language, Lexeme startingLexeme, Lexeme endingLexeme) { this(Integer.toString(typeIndex), typeIndex, language, startingLexeme, endingLexeme); } /** * ParseNodeBase * * @param type * @param typeIndex * @param language * @param startingLexeme */ public ParseNodeBase(String type, int typeIndex, String language, Lexeme startingLexeme) { this(type, typeIndex, language, startingLexeme, startingLexeme); } /** * ParseNodeBase * * @param type * @param typeIndex * @param language * @param startingLexeme */ public ParseNodeBase(String type, int typeIndex, String language, Lexeme startingLexeme, Lexeme endingLexeme) { this._type = type; this._typeIndex = typeIndex; this._language = language; this._startingLexeme = startingLexeme; this._endingLexeme = endingLexeme; this._children = new IParseNode[0]; } /** * add * * @param lexeme */ private void add(IParseNode node) { // make sure our private buffer is large enough int currentLength = this._children.length; int size = this._size + 1; // see if the index we want is within our buffer if (size > currentLength) { // it's not, add about 50% to our current buffer size int newLength = (currentLength * 3) / 2 + 1; // create a new empty list IParseNode[] newList = new IParseNode[newLength]; // move the current contents to our new list System.arraycopy(this._children, 0, newList, 0, this._size); // set out current list to the new list this._children = newList; } // place the lexeme into the hole this._children[this._size] = node; // update the current size this._size++; } /** * @see com.aptana.ide.parsing.nodes.IParseNode#appendChild(com.aptana.ide.parsing.nodes.IParseNode) */ public void appendChild(IParseNode child) { if (child == null) { throw new NullPointerException(Messages.ParseNodeBase_Undefined_Child); } if (child instanceof ParseFragment) { ParseFragment fragment = (ParseFragment) child; for (int i = 0; i < fragment.getChildCount(); i++) { this.appendChildHelper(fragment.getChild(i)); } IParseNodeAttribute[] attributes = fragment.getAttributes(); for (int i = 0; i < attributes.length; i++) { IParseNodeAttribute attr = attributes[i]; this.setAttribute(attr.getName(), attr.getValue()); } } else { this.appendChildHelper(child); } } /** * appendChildHelper * * @param child */ private void appendChildHelper(IParseNode child) { // append child this.add(child); // propagate starting and ending offsets this.includeLexemesInRange(child.getStartingLexeme(), child.getEndingLexeme()); // assign parent to child if (child instanceof ParseNodeBase) { ((ParseNodeBase) child)._parent = this; } } /** * @see com.aptana.ide.parsing.nodes.IParseNode#containsOffset(int) */ public boolean containsOffset(int offset) { boolean result = false; if (this._startingLexeme != null && this._endingLexeme != null) { int startingOffset = this._startingLexeme.offset; int endingOffset = this._endingLexeme.getEndingOffset(); result = (startingOffset <= offset && offset < endingOffset); } return result; } /** * createAttribute * * @param name * @param value * @return IParseNodeAttribute */ protected IParseNodeAttribute createAttribute(String name, String value) { return new ParseNodeAttribute(this, name, value); } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getAttribute(java.lang.String) */ public String getAttribute(String attributeName) { String result = ""; //$NON-NLS-1$ if (this._attributes != null) { for (int i = 0; i < this._attributes.size(); i++) { IParseNodeAttribute attribute = this._attributes.get(i); if (attribute.getName().equals(attributeName)) { result = attribute.getValue(); break; } } } return result; } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getAttributeNode(java.lang.String) */ public IParseNodeAttribute getAttributeNode(String attributeName) { IParseNodeAttribute result = null; if (this._attributes != null) { for (int i = 0; i < this._attributes.size(); i++) { IParseNodeAttribute attribute = this._attributes.get(i); if (attribute.getName().equals(attributeName)) { result = attribute; break; } } } return result; } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getAttributes() */ public IParseNodeAttribute[] getAttributes() { if (this._attributes != null) { return this._attributes.toArray(new IParseNodeAttribute[this._attributes.size()]); } else { return new IParseNodeAttribute[0]; } } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getChild(int) */ public IParseNode getChild(int index) { IParseNode result = null; if (0 <= index && index < this._size) { result = this._children[index]; } return result; } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getChildCount() */ public int getChildCount() { return this._size; } /** * getChildIndex * * @return int */ public int getChildIndex() { IParseNode parent = this.getParent(); int result = 0; if (parent != null) { for (int i = 0; i < parent.getChildCount(); i++) { if (parent.getChild(i) == this) { result = i; break; } } } return result; } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getChildren() */ public IParseNode[] getChildren() { IParseNode[] result = new IParseNode[this._size]; if (this._size > 0) { System.arraycopy(this._children, 0, result, 0, this._size); } return result; } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getEndingLexeme() */ public Lexeme getEndingLexeme() { return this._endingLexeme; } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getEndingOffset() */ public int getEndingOffset() { int result = -1; if (this._endingLexeme != null) { result = this._endingLexeme.getEndingOffset(); } return result; } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getLanguage() */ public String getLanguage() { return this._language; } /** * @see com.aptana.ide.lexer.IRange#getLength() */ public int getLength() { return this.getEndingOffset() - this.getStartingOffset(); } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getName() */ public String getName() { if (this._name == null) { // [KEL] temp for debugging if (this._startingLexeme != null) { this._name = this._startingLexeme.getText(); } else { String name = this.getClass().getName(); name = name.substring(name.lastIndexOf('.') + 1); this._name = name; } } return this._name; } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getNodeAtOffset(int) */ public IParseNode getNodeAtOffset(int offset) { IParseNode result = null; if (this.containsOffset(offset)) { result = this; for (int i = 0; i < this._size; i++) { IParseNode child = this._children[i]; if (child.containsOffset(offset)) { result = child.getNodeAtOffset(offset); break; } } } return result; } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getParent() */ public IParseNode getParent() { return this._parent; } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getPath() */ public String getPath() { List<String> parts = new ArrayList<String>(); // walk the ancestor chain to the root node IParseNode current = this; while (current != null) { parts.add(current.getName()); parts.add(SEPARATOR); current = current.getParent(); } // reverse the list Collections.reverse(parts); // convert list into a string StringBuffer buffer = new StringBuffer(); for (String item : parts) { buffer.append(item); } return buffer.toString(); } /** * Gets the root node of this node. * * @return Returns the root node of this node. */ public IParseNode getRootNode() { IParseNode root = this; IParseNode p = this._parent; while (p != null) { root = p; p = p.getParent(); } return root; } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getSource() */ public String getSource() { SourceWriter writer = new SourceWriter(); this.getSource(writer); return writer.toString(); } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getSource(com.aptana.ide.io.SourceWriter) */ public void getSource(SourceWriter writer) { // do nothing } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getStartingLexeme() */ public Lexeme getStartingLexeme() { return this._startingLexeme; } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getStartingOffset() */ public int getStartingOffset() { int result = -1; if (this._startingLexeme != null) { result = this._startingLexeme.offset; } return result; } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getText() */ public String getText() { return ""; //$NON-NLS-1$ } /** * getType * * @return String */ public String getType() { return this._type; } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getTypeIndex() */ public int getTypeIndex() { return this._typeIndex; } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getUniquePath() */ public String getUniquePath() { List<String> parts = new ArrayList<String>(); // walk the ancestor chain to the root node IParseNode current = this; while (current != null) { int index = current.getChildIndex() + 1; parts.add(current.getName() + "[" + index + "]"); //$NON-NLS-1$ //$NON-NLS-2$ parts.add(SEPARATOR); current = current.getParent(); } // reverse the list Collections.reverse(parts); // convert list into a string StringBuffer buffer = new StringBuffer(); for (String item : parts) { buffer.append(item); } return buffer.toString(); } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getXML() */ public String getXML() { SourceWriter writer = new SourceWriter(); this.getXML(writer); return writer.toString(); } /** * @see com.aptana.ide.parsing.nodes.IParseNode#getXML(com.aptana.ide.io.SourceWriter) */ public void getXML(SourceWriter writer) { // begin element writer.printWithIndent("<").print(this.getName()); //$NON-NLS-1$ // output attributes IParseNodeAttribute[] attrs = this.getAttributes(); for (int i = 0; i < attrs.length; i++) { writer.print(" "); //$NON-NLS-1$ attrs[i].getSource(writer); } // handle possible child elements if (this.hasChildren()) { writer.println(">"); //$NON-NLS-1$ writer.increaseIndent(); for (int i = 0; i < this.getChildCount(); i++) { this.getChild(i).getXML(writer); } writer.decreaseIndent(); writer.printWithIndent("</").print(this.getName()).println(">"); //$NON-NLS-1$ //$NON-NLS-2$ } else { writer.println("/>"); //$NON-NLS-1$ } } /** * @see com.aptana.ide.parsing.nodes.IParseNode#hasAttribute(java.lang.String) */ public boolean hasAttribute(String attributeName) { boolean result = false; if (this._attributes != null) { for (int i = 0; i < this._attributes.size(); i++) { IParseNodeAttribute attribute = this._attributes.get(i); if (attribute.getName().equals(attributeName)) { result = true; break; } } } return result; } /** * @see com.aptana.ide.parsing.nodes.IParseNode#hasAttributes() */ public boolean hasAttributes() { return (this._attributes != null && this._attributes.size() > 0); } /** * @see com.aptana.ide.parsing.nodes.IParseNode#hasChildren() */ public boolean hasChildren() { return this._size > 0; } /** * @see com.aptana.ide.parsing.nodes.IParseNode#includeLexemeInRange(com.aptana.ide.lexer.Lexeme) */ public boolean includeLexemeInRange(Lexeme lexeme) { return this.includeLexemesInRange(lexeme, lexeme); } /** * @see com.aptana.ide.parsing.nodes.IParseNode#includeLexemesInRange(com.aptana.ide.lexer.Lexeme, Lexeme) */ public boolean includeLexemesInRange(Lexeme startingLexeme, Lexeme endingLexeme) { boolean result = false; // NOTE: We have to be careful with the test that determines if the starting lexeme comes before // the ending lexeme. It's possible that startingLexeme and endingLexeme could reference the same // lexeme. In that case startingLexeme.getEndingOffset() <= endingLexeme.offset would fail since // a lexeme's ending offset cannot come before it's starting offset. if (startingLexeme != null && endingLexeme != null && startingLexeme.offset <= endingLexeme.offset) { if (this._startingLexeme == null || (startingLexeme.offset != -1 && startingLexeme.getEndingOffset() <= this._startingLexeme.offset)) { this._startingLexeme = startingLexeme; result = true; } if (this._endingLexeme == null || (endingLexeme.offset != -1 && this._endingLexeme.getEndingOffset() <= endingLexeme.offset)) { this._endingLexeme = endingLexeme; result = true; } if (result) { // propagate change to ancestors, as necessary IParseNode parent = this._parent; // NOTE: [KEL] would be faster to do this iteratively instead of recursively if (parent != null) { parent.includeLexemesInRange(startingLexeme, endingLexeme); } } } return result; } /** * @see com.aptana.ide.lexer.IRange#isEmpty() */ public boolean isEmpty() { return (this.getLength() == 0); } /** * @see com.aptana.ide.parsing.nodes.IParseNode#setAttribute(java.lang.String, java.lang.String) */ public void setAttribute(String name, String value) { if (name != null && name.length() > 0 && value != null) { boolean found = false; if (this._attributes != null) { for (int i = 0; i < this._attributes.size(); i++) { IParseNodeAttribute attribute = this._attributes.get(i); if (attribute.getName().equals(name)) { attribute.setValue(value); found = true; break; } } } else { // make sure we have an attribute list this._attributes = new ArrayList<IParseNodeAttribute>(); } if (found == false) { // create a new attribute IParseNodeAttribute attribute = this.createAttribute(name, value); // add the new attribute to our list this._attributes.add(attribute); } } } /** * @see com.aptana.ide.parsing.nodes.IParseNode#setEndingLexeme(com.aptana.ide.lexer.Lexeme) * @deprecated */ public void setEndingLexeme(Lexeme endLexeme) { this.includeLexemeInRange(endLexeme); } /** * @see com.aptana.ide.parsing.nodes.IParseNode#setName(java.lang.String) */ public void setName(String name) { this._name = name; } /** * Allows to append child node before given node * @param node * @param child */ public void appendChildBefore(IParseNode node, IParseNode child) { if (this._children!=null) { ArrayList<IParseNode>list=new ArrayList<IParseNode>(Arrays.asList(this._children)); int indexOf = list.indexOf(node); if (indexOf==-1) { appendChild(child); return; } else { ParseNodeBase base=(ParseNodeBase) child; base._parent=this; list.add(indexOf, child); } this._children=list.toArray(new IParseNode[list.size()]); this._size=this._size+1; } else{ appendChild(child); } } /** * allows to set children of node from a array * @param array */ public void setChildren(IParseNode[] array) { this._children=array; this._size=array.length; for (IParseNode n: array){ ParseNodeBase b=(ParseNodeBase) n; b._parent=this; } } }