/******************************************************************************* * Copyright (c) 2005, 2012 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_STATEMENT = -1; private static final DOMASTNodeLeaf[] EMPTY_CHILDREN_ARRAY = {}; private static final int DEFAULT_NODE_CHAIN_SIZE = 4; private static final int DEFAULT_CHILDREN_SIZE = 4; int index; private DOMASTNodeLeaf[] children; boolean cleanupedElements; private int indexFirstPreproStmnt= NO_PREPROCESSOR_STATEMENT; 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_STATEMENT) { indexFirstPreproStmnt=index; } if (index==children.length) { children = 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 <T> T[] insert(Class<T> c, T[] array, T obj, int pos) { if (pos < 0 || pos >= array.length) { return ArrayUtil.append(c, array, obj); } T[] temp = (T[]) 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 = 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 = 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 = 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 = ArrayUtil.append(IASTNode.class, nodeChain, topNode); while(topNode.getParent() != null && !(topNode.getParent() instanceof IASTTranslationUnit)) { topNode = topNode.getParent(); nodeChain = 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 = ArrayUtil.append(IASTNode.class, nodeChain, topNode); while(topNode.getParent() != null && !(topNode.getParent() instanceof IASTTranslationUnit)) { topNode = topNode.getParent(); nodeChain = 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 } @Override 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; } }