/******************************************************************************* * Copyright (c) 2005 IBM Corporation 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: * IBM Rational Software - Initial API and implementation *******************************************************************************/ package org.eclipse.cdt.ui.tests.DOMAST; import java.lang.reflect.Array; import org.eclipse.cdt.core.dom.ast.IASTNode; import org.eclipse.cdt.core.dom.ast.IASTNodeLocation; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorStatement; import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.parser.util.ArrayUtil; import org.eclipse.cdt.internal.core.dom.parser.ASTNode; /** * @author dsteffle */ public class DOMASTNodeParent extends DOMASTNodeLeaf { private static final int NO_PREPROCESSOR_STATMENT = -1; private static final DOMASTNodeLeaf[] EMPTY_CHILDREN_ARRAY = new DOMASTNodeLeaf[0]; private static final int DEFAULT_NODE_CHAIN_SIZE = 4; private static final int DEFAULT_CHILDREN_SIZE = 4; int index=0; private DOMASTNodeLeaf[] children; boolean cleanupedElements = false; private int indexFirstPreproStmnt=NO_PREPROCESSOR_STATMENT; public int getStartSearch() { return index; } public DOMASTNodeParent() { super(null); children = EMPTY_CHILDREN_ARRAY; } public DOMASTNodeParent(IASTNode node) { super(node); children = new DOMASTNodeLeaf[DEFAULT_CHILDREN_SIZE]; } public void addChild(DOMASTNodeLeaf child) { if (child.getNode() instanceof IASTPreprocessorStatement && indexFirstPreproStmnt == NO_PREPROCESSOR_STATMENT) { indexFirstPreproStmnt=index; } if (index==children.length) { children = (DOMASTNodeLeaf[])ArrayUtil.append(DOMASTNodeLeaf.class, children, child); index++; } else { children[index++] = child; } child.setParent(this); } public void removeChild(DOMASTNodeLeaf child) { for(int i=0; i<children.length; i++) { if (children[i] == child) { children[i] = null; break; } } child.setParent(null); } public DOMASTNodeLeaf[] getChildren(boolean cleanupElements) { if (cleanupElements) { return getChildren(); } return children; } public DOMASTNodeLeaf [] getChildren() { // remove null children from the array (if not already done so) if (!cleanupedElements) { cleanChildren(); } return children; } /** * Inserts obj into the array at position pos and if this is not possible (due to a bad offset) * then the obj is just appended to the end of the array. * * @param c * @param array * @param obj * @param pos * @return */ public Object[] insert(Class c, Object[] array, Object obj, int pos) { if (pos < 0 || pos >= array.length) { return ArrayUtil.append(c, array, obj); } Object[] temp = (Object[]) Array.newInstance( c, array.length + 1 ); if (pos > 0) { System.arraycopy( array, 0, temp, 0, pos ); temp[pos] = obj; System.arraycopy( array, pos, temp, pos + 1, array.length - pos ); } else { temp[0] = obj; System.arraycopy( array, 0, temp, 1, array.length ); } return temp; } public void cleanChildren() { // remove null elements children = (DOMASTNodeLeaf[])ArrayUtil.removeNulls(DOMASTNodeLeaf.class, children); // sort the elements //if (indexFirstPreproStmnt >= 0) { // TODO Devin what if it's ALL preprocessor statements ? int firstOffset=0; int firstLength=0; int checkOffset=0; int checkLength=0; boolean moved=false; for (int j=0, i=0; j < children.length && children[j] != null; j++) { if( !(children[j].getNode() instanceof IASTPreprocessorStatement) ) continue; while(true) { if (i==j) break; // don't need to check itself or anything after it checkOffset = ((ASTNode)children[j].getNode()).getOffset(); checkLength = ((ASTNode)children[j].getNode()).getLength(); firstOffset = ((ASTNode)children[i].getNode()).getOffset(); firstLength = ((ASTNode)children[i].getNode()).getLength(); // if the checking element comes before the first element then move the checking element before the first element if (checkOffset < firstOffset && checkOffset + checkLength < firstOffset + firstLength) { DOMASTNodeLeaf temp = children[j]; System.arraycopy( children, i, children, i + 1, j - i ); children[i] = temp; break; } // if the checking element is within the bounds of the first element then it must be a child of that element if (checkOffset > firstOffset && checkOffset + checkLength < firstOffset + firstLength) { DOMASTNodeLeaf temp = children[j]; if( j + 1 < children.length ) System.arraycopy( children, j + 1, children, j, children.length - j - 1 ); children[ children.length - 1 ] = null; ((DOMASTNodeParent)children[i]).addChild(temp); j--; break; } i++; } } // } children = (DOMASTNodeLeaf[])ArrayUtil.removeNulls(DOMASTNodeLeaf.class, children); // need to also clean up the children's children, to make sure all nulls are removed (prevent expansion sign when there isn't one) for(int i=0; i<children.length; i++) { if (children[i] instanceof DOMASTNodeParent) { DOMASTNodeLeaf[] kids = ((DOMASTNodeParent)children[i]).children; // remove null elements kids = (DOMASTNodeLeaf[])ArrayUtil.removeNulls(DOMASTNodeLeaf.class, kids); ((DOMASTNodeParent)children[i]).setChildren(kids); } } cleanupedElements = true; } public boolean hasChildren() { return children.length>0; } /** * Returns the DOMASTNodeParent whose IASTNode is the parent of the IASTNode. * * This function is an optimization function used to only search merged preprocessor nodes. * * @param node * @return */ public DOMASTNodeParent findTreeParentForMergedNode(IASTNode node) { if (node == null || node.getParent() == null) return null; IASTNode parentToFind = node.getParent(); // first check this node before checking children if (this.getNode() == parentToFind) { return this; } // build the chain of nodes... and use it to search the tree for the DOMASTNodeParent that owns the node's parent IASTNode[] nodeChain = new IASTNode[DEFAULT_NODE_CHAIN_SIZE]; IASTNode topNode = node.getParent(); ArrayUtil.append(IASTNode.class, nodeChain, node); nodeChain = (IASTNode[])ArrayUtil.append(IASTNode.class, nodeChain, topNode); while(topNode.getParent() != null && !(topNode.getParent() instanceof IASTTranslationUnit)) { topNode = topNode.getParent(); nodeChain = (IASTNode[])ArrayUtil.append(IASTNode.class, nodeChain, topNode); } // loop through the chain of nodes and use it to only search the necessary children required to find the node DOMASTNodeLeaf[] childrenToSearch = children; int j=childrenToSearch.length-1; outerLoop: for(int i=nodeChain.length-1; i>=0; i--) { if (nodeChain[i] != null) { parentToFind = nodeChain[i]; for(; j>=0; j--) { if (childrenToSearch[j] instanceof DOMASTNodeParent) { if ( childrenToSearch[j].getNode() == node.getParent() ) { return (DOMASTNodeParent)childrenToSearch[j]; } if (childrenToSearch[j].getNode() == parentToFind) { int pos = j; j = ((DOMASTNodeParent)childrenToSearch[pos]).getStartSearch(); childrenToSearch = ((DOMASTNodeParent)childrenToSearch[pos]).getChildren(false); continue outerLoop; } } } } } return null; // nothing found } /** * Returns the DOMASTNodeParent whose IASTNode is the parent of the IASTNode. * * @param node * @return */ public DOMASTNodeParent findTreeParentForNode(IASTNode node) { if (node == null || node.getParent() == null) return null; IASTNode parentToFind = node.getParent(); // first check this node before checking children if (this.getNode() == parentToFind) { return this; } // build the chain of nodes... and use it to search the tree for the DOMASTNodeParent that owns the node's parent IASTNode[] nodeChain = new IASTNode[DEFAULT_NODE_CHAIN_SIZE]; IASTNode topNode = node.getParent(); ArrayUtil.append(IASTNode.class, nodeChain, node); nodeChain = (IASTNode[])ArrayUtil.append(IASTNode.class, nodeChain, topNode); while(topNode.getParent() != null && !(topNode.getParent() instanceof IASTTranslationUnit)) { topNode = topNode.getParent(); nodeChain = (IASTNode[])ArrayUtil.append(IASTNode.class, nodeChain, topNode); } // loop through the chain of nodes and use it to only search the necessary children required to find the node DOMASTNodeLeaf[] childrenToSearch = children; int j=getStartSearch(); outerLoop: for(int i=nodeChain.length-1; i>=0; i--) { if (nodeChain[i] != null) { parentToFind = nodeChain[i]; for(; j>=0; j--) { // use the DOMASTNodeParent's index to start searching at the end of it's children (performance optimization) if (j<childrenToSearch.length && childrenToSearch[j] instanceof DOMASTNodeParent) { if ( childrenToSearch[j].getNode() == node.getParent() ) { return (DOMASTNodeParent)childrenToSearch[j]; } if (childrenToSearch[j].getNode() == parentToFind) { int pos = j; j = ((DOMASTNodeParent)childrenToSearch[pos]).getStartSearch(); childrenToSearch = ((DOMASTNodeParent)childrenToSearch[pos]).getChildren(false); continue outerLoop; } } } } } // try finding the best parent possible IASTNode parent = node.getParent(); DOMASTNodeParent tree = null; while (parent != null && tree == null) { tree = findTreeParentForNode(parent); if (tree != null) return tree; parent = parent.getParent(); } return null; // nothing found } public int relativeNodePosition( IASTNode n ){ ASTNode astNode = (ASTNode) n; if( !cleanupedElements ){ cleanChildren(); } if( children.length > 0 ){ ASTNode first = (ASTNode) children[0].getNode(); if( first.getOffset() > astNode.getOffset() ) return -1; ASTNode last = (ASTNode) children[ children.length - 1 ].getNode(); if( (last.getOffset() + last.getLength()) < (astNode.getOffset() + astNode.getLength()) ) return 1; return 0; } return super.relativeNodePosition( n ); } /** * Returns the DOMASTNodeParent that corresponds to the IASTNode. This is the DOMASTNodeParent * that represents the IASTNode in the DOM AST View. * * @param node * @return */ public DOMASTNodeParent findTreeObject(IASTNode node, boolean useOffset) { if (node == null) return null; // first check this node before checking children if (equalNodes(node, this.getNode(), useOffset)) { return this; } if( children.length == 0 ) return null; if( !cleanupedElements ){ cleanChildren(); } int a = 0, z = children.length - 1; int idx = (z - a) / 2 ; while( true ){ int compare = children[ idx ].relativeNodePosition( node ); if( compare == 0 ){ if( children[idx] instanceof DOMASTNodeParent ){ return ((DOMASTNodeParent)children[idx]).findTreeObject( node, useOffset ); } return null; //?? } else if( compare == -1 ) z = idx; else a = idx; int diff = z - a; if( diff == 0 ) return null; else if( diff == 1 ) idx = ( idx == z ) ? a : z; else idx = a + ( z - a ) / 2; if( z == a ) return null; if( z - a == 1 && children[ a ].relativeNodePosition( node ) == 1 && children[ z ].relativeNodePosition( node ) == -1 ) return null; } } private boolean equalNodes(IASTNode node1, IASTNode node2, boolean useOffset) { if (useOffset) { if (node1 instanceof ASTNode && node2 instanceof ASTNode) { if (((ASTNode)node1).getOffset() == ((ASTNode)node2).getOffset() && ((ASTNode)node1).getLength() == ((ASTNode)node2).getLength()) { if (node1.getClass().equals(node2.getClass())) return true; else return false; } } else { IASTNodeLocation[] locs1 = node1.getNodeLocations(); IASTNodeLocation[] locs2 = node2.getNodeLocations(); for(int i=0; i<locs1.length && i<locs2.length; i++) { if (locs1[i].getNodeOffset() != locs2[i].getNodeOffset() || locs1[i].getNodeLength() != locs2[i].getNodeLength()) return false; } if (node1.getClass().equals(node2.getClass())) return true; else return false; } } else { if ( node1 == node2 ) return true; } return false; } public void setChildren(DOMASTNodeLeaf[] children) { this.children = children; } }