001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.javascript.editing;
042:
043: import java.util.ArrayList;
044: import java.util.Collections;
045: import java.util.Iterator;
046: import java.util.ListIterator;
047: import org.mozilla.javascript.Node;
048: import org.mozilla.javascript.Token;
049:
050: /**
051: * This represents a path in a JRuby AST.
052: *
053: * @todo Performance: Make a cache here since I tend to do AstPath(caretOffset) from
054: * several related services for a single parser result
055: *
056: * @author Tor Norbye
057: */
058: public class AstPath implements Iterable<Node> {
059: private ArrayList<Node> path = new ArrayList<Node>(30);
060:
061: public AstPath() {
062: }
063:
064: public AstPath(AstPath other) {
065: path.addAll(other.path);
066: }
067:
068: public AstPath(ArrayList<Node> path) {
069: this .path = path;
070: }
071:
072: /**
073: * Initialize a node path to the given caretOffset
074: */
075: public AstPath(Node root, int caretOffset) {
076: findPathTo(root, caretOffset);
077: }
078:
079: /**
080: * Find the path to the given node in the AST
081: */
082: @SuppressWarnings("unchecked")
083: public AstPath(Node node, Node target) {
084: if (!find(node, target)) {
085: path.clear();
086: } else {
087: // Reverse the list such that node is on top
088: // When I get time rewrite the find method to build the list that way in the first place
089: Collections.reverse(path);
090: }
091: }
092:
093: public void descend(Node node) {
094: path.add(node);
095: }
096:
097: public void ascend() {
098: path.remove(path.size() - 1);
099: }
100:
101: /**
102: * Return true iff this path contains a node of the given node type
103: *
104: * @param nodeType The nodeType to check
105: * @return true if the given nodeType is found in the path
106: */
107: public boolean contains(int nodeType) {
108: for (int i = 0, n = path.size(); i < n; i++) {
109: if (path.get(i).getType() == nodeType) {
110: return true;
111: }
112: }
113:
114: return false;
115: }
116:
117: /**
118: * Find the position closest to the given offset in the AST. Place the path from the leaf up to the path in the
119: * passed in path list.
120: */
121: @SuppressWarnings("unchecked")
122: public Node findPathTo(Node node, int offset) {
123: Node result = find(node, offset);
124: path.add(node);
125:
126: // Reverse the list such that node is on top
127: // When I get time rewrite the find method to build the list that way in the first place
128: Collections.reverse(path);
129:
130: return result;
131: }
132:
133: @SuppressWarnings("unchecked")
134: private Node find(Node node, int offset) {
135: int begin = node.getSourceStart();
136: int end = node.getSourceEnd();
137:
138: if ((offset >= begin) && (offset <= end)) {
139: for (Node child = node.getFirstChild(); child != null; child = child
140: .getNext()) {
141: Node found = find(child, offset);
142:
143: if (found != null) {
144: path.add(child);
145:
146: return found;
147: }
148: }
149:
150: return node;
151: } else {
152: for (Node child = node.getFirstChild(); child != null; child = child
153: .getNext()) {
154: Node found = find(child, offset);
155:
156: if (found != null) {
157: path.add(child);
158:
159: return found;
160: }
161: }
162:
163: return null;
164: }
165: }
166:
167: /**
168: * Find the path to the given node in the AST
169: */
170: @SuppressWarnings("unchecked")
171: public boolean find(Node node, Node target) {
172: if (node == target) {
173: return true;
174: }
175:
176: for (Node child = node.getFirstChild(); child != null; child = child
177: .getNext()) {
178: boolean found = find(child, target);
179:
180: if (found) {
181: path.add(child);
182:
183: return found;
184: }
185: }
186:
187: return false;
188: }
189:
190: @Override
191: public String toString() {
192: StringBuilder sb = new StringBuilder();
193: sb.append("Path(");
194: sb.append(path.size());
195: sb.append(")=[");
196:
197: for (Node n : path) {
198: String name = Token.fullName(n.getType());
199: name = name.substring(name.lastIndexOf('.') + 1);
200: sb.append(name);
201: sb.append(":");
202: }
203:
204: sb.append("]");
205:
206: return sb.toString();
207: }
208:
209: public Node leaf() {
210: if (path.size() == 0) {
211: return null;
212: } else {
213: return path.get(path.size() - 1);
214: }
215: }
216:
217: public Node leafParent() {
218: if (path.size() < 2) {
219: return null;
220: } else {
221: return path.get(path.size() - 2);
222: }
223: }
224:
225: public Node leafGrandParent() {
226: if (path.size() < 3) {
227: return null;
228: } else {
229: return path.get(path.size() - 3);
230: }
231: }
232:
233: public Node root() {
234: if (path.size() == 0) {
235: return null;
236: } else {
237: return path.get(0);
238: }
239: }
240:
241: /** Return an iterator that returns the elements from the leaf back up to the root */
242: public Iterator<Node> iterator() {
243: return new LeafToRootIterator(path);
244: }
245:
246: /** REturn an iterator that starts at the root and walks down to the leaf */
247: public ListIterator<Node> rootToLeaf() {
248: return path.listIterator();
249: }
250:
251: /** Return an iterator that walks from the leaf back up to the root */
252: public ListIterator<Node> leafToRoot() {
253: return new LeafToRootIterator(path);
254: }
255:
256: private static class LeafToRootIterator implements
257: ListIterator<Node> {
258: private final ListIterator<Node> it;
259:
260: private LeafToRootIterator(ArrayList<Node> path) {
261: it = path.listIterator(path.size());
262: }
263:
264: public boolean hasNext() {
265: return it.hasPrevious();
266: }
267:
268: public Node next() {
269: return it.previous();
270: }
271:
272: public boolean hasPrevious() {
273: return it.hasNext();
274: }
275:
276: public Node previous() {
277: return it.next();
278: }
279:
280: public int nextIndex() {
281: return it.previousIndex();
282: }
283:
284: public int previousIndex() {
285: return it.nextIndex();
286: }
287:
288: public void remove() {
289: throw new UnsupportedOperationException(
290: "Not supported yet.");
291: }
292:
293: public void set(Node arg0) {
294: throw new UnsupportedOperationException(
295: "Not supported yet.");
296: }
297:
298: public void add(Node arg0) {
299: throw new UnsupportedOperationException(
300: "Not supported yet.");
301: }
302: }
303: }
|