| /******************************************************************************* |
| * Copyright (c) 2010 xored software, Inc. |
| * |
| * 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: |
| * xored software, Inc. - initial API and Implementation (Alex Panchenko) |
| *******************************************************************************/ |
| package org.eclipse.dltk.javascript.core; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.List; |
| |
| import org.eclipse.dltk.ast.ASTNode; |
| import org.eclipse.dltk.ast.ASTVisitor; |
| import org.eclipse.dltk.javascript.ast.Comment; |
| import org.eclipse.dltk.javascript.ast.FunctionStatement; |
| import org.eclipse.dltk.javascript.ast.ISemicolonStatement; |
| import org.eclipse.dltk.javascript.ast.JSNode; |
| import org.eclipse.dltk.javascript.ast.JSUserNode; |
| import org.eclipse.dltk.javascript.ast.Script; |
| import org.eclipse.dltk.javascript.ast.Statement; |
| |
| public class NodeFinder extends ASTVisitor { |
| private final boolean skipUserExpressions; |
| private final int start; |
| private final int end; |
| |
| public NodeFinder(int position) { |
| this(position, position); |
| } |
| |
| public NodeFinder(int s, int e) { |
| this(false, s, e); |
| } |
| |
| public NodeFinder(boolean skipUserExpressions, int s, int e) { |
| this.start = s; |
| this.end = e; |
| this.skipUserExpressions = skipUserExpressions; |
| } |
| |
| private ASTNode before = null; |
| private ASTNode after = null; |
| |
| private static boolean isBlock(ASTNode node) { |
| return node instanceof Script || node instanceof FunctionStatement |
| || node instanceof Statement; |
| } |
| |
| @Override |
| public boolean visit(ASTNode node) { |
| if (skipUserExpressions && node instanceof JSUserNode) { |
| final JSNode original = ((JSUserNode) node).getOriginal(); |
| if (original != null) { |
| try { |
| original.traverse(this); |
| } catch (RuntimeException e) { |
| throw e; |
| } catch (Exception e) { |
| JavaScriptPlugin.error(e); |
| } |
| return false; |
| } |
| } |
| if (isBlock(node)) { |
| if (node.sourceEnd() < start || node.sourceStart() > end) { |
| return false; |
| } |
| if (node.sourceEnd() == start |
| && node instanceof ISemicolonStatement |
| && ((ISemicolonStatement) node).getSemicolonPosition() >= 0) { |
| return false; |
| } |
| return true; |
| } |
| boolean enter = false; |
| if (before == null || isCloser(node, before, start)) { |
| before = node; |
| enter = true; |
| } |
| if (after == null || isCloser(node, after, end)) { |
| after = node; |
| enter = true; |
| } |
| return enter; |
| } |
| |
| private static boolean isCloser(ASTNode node, ASTNode result, int pos) { |
| return node.sourceStart() >= result.sourceStart() |
| && node.sourceStart() <= pos; |
| } |
| |
| private boolean traverse(Script script) { |
| after = null; |
| before = null; |
| for (Comment comment : script.getComments()) { |
| if (comment.start() < end && comment.end() >= start) { |
| before = comment; |
| return true; |
| } |
| } |
| try { |
| script.traverse(this); |
| return true; |
| } catch (Exception e) { |
| return false; |
| } |
| } |
| |
| public NodeFinder locate(Script script) { |
| if (!traverse(script)) { |
| before = null; |
| after = null; |
| } |
| return this; |
| } |
| |
| public ASTNode getNode() { |
| final List<ASTNode> nodes = new ArrayList<ASTNode>(2); |
| if (isValid(before)) { |
| nodes.add(before); |
| } |
| if (isValid(after)) { |
| nodes.add(after); |
| } |
| if (!nodes.isEmpty()) { |
| if (nodes.size() > 1) { |
| Collections.sort(nodes, new Comparator<ASTNode>() { |
| public int compare(ASTNode o1, ASTNode o2) { |
| int distance1 = distanceTo(o1); |
| int distance2 = distanceTo(o2); |
| return Math.abs(distance1) - Math.abs(distance2); |
| } |
| |
| private int distanceTo(ASTNode o1) { |
| if (o1.sourceStart() >= start |
| && o1.sourceStart() <= end |
| || o1.sourceEnd() >= start |
| && o1.sourceEnd() <= end) { |
| return 0; |
| } else { |
| return ((o1.sourceEnd() + o1.sourceStart()) - (start + end)) / 2; |
| } |
| } |
| }); |
| } |
| return nodes.get(0); |
| } |
| return null; |
| } |
| |
| private boolean isValid(final ASTNode n) { |
| return n != null && n.sourceStart() <= end && n.sourceEnd() >= start; |
| } |
| |
| } |