/bundles/org.eclipse.wst.jsdt.ui/src/org/eclipse/wst/jsdt/internal/ui/search/BreakContinueTargetFinder.java
Java | 281 lines | 213 code | 29 blank | 39 comment | 48 complexity | 363f59d256b224490a01f89cfd2922b1 MD5 | raw file
- /*******************************************************************************
- * Copyright (c) 2000, 2008 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 Corporation - initial API and implementation
- *******************************************************************************/
- package org.eclipse.wst.jsdt.internal.ui.search;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collections;
- import java.util.List;
- import org.eclipse.wst.jsdt.core.IJavaScriptElement;
- import org.eclipse.wst.jsdt.core.ISourceReference;
- import org.eclipse.wst.jsdt.core.JavaScriptModelException;
- import org.eclipse.wst.jsdt.core.ToolFactory;
- import org.eclipse.wst.jsdt.core.compiler.IScanner;
- import org.eclipse.wst.jsdt.core.compiler.InvalidInputException;
- import org.eclipse.wst.jsdt.core.dom.AST;
- import org.eclipse.wst.jsdt.core.dom.ASTNode;
- import org.eclipse.wst.jsdt.core.dom.ASTVisitor;
- import org.eclipse.wst.jsdt.core.dom.Block;
- import org.eclipse.wst.jsdt.core.dom.BreakStatement;
- import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
- import org.eclipse.wst.jsdt.core.dom.ContinueStatement;
- import org.eclipse.wst.jsdt.core.dom.DoStatement;
- import org.eclipse.wst.jsdt.core.dom.EnhancedForStatement;
- import org.eclipse.wst.jsdt.core.dom.ForInStatement;
- import org.eclipse.wst.jsdt.core.dom.ForStatement;
- import org.eclipse.wst.jsdt.core.dom.Initializer;
- import org.eclipse.wst.jsdt.core.dom.LabeledStatement;
- import org.eclipse.wst.jsdt.core.dom.FunctionDeclaration;
- import org.eclipse.wst.jsdt.core.dom.SimpleName;
- import org.eclipse.wst.jsdt.core.dom.SwitchStatement;
- import org.eclipse.wst.jsdt.core.dom.WhileStatement;
- import org.eclipse.wst.jsdt.internal.corext.dom.ASTNodes;
- import org.eclipse.wst.jsdt.internal.corext.dom.NodeFinder;
- import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
- /**
- * Class used to find the target for a break or continue statement according
- * to the language specification.
- * <p>
- * The target statement is a while, do, switch, for or a labeled statement.
- * Break is described in section 14.15 of the JLS3 and continue in section 14.16.</p>
- *
- *
- */
- public class BreakContinueTargetFinder extends ASTVisitor {
- private ASTNode fSelected;
- private boolean fIsBreak;
- private SimpleName fLabel;
- private String fContents;//contents are used for scanning to select the right extent of the keyword
- private static final Class[] STOPPERS= {FunctionDeclaration.class, Initializer.class};
- private static final Class[] BREAKTARGETS= {ForStatement.class, ForInStatement.class, EnhancedForStatement.class, WhileStatement.class, DoStatement.class, SwitchStatement.class};
- private static final Class[] CONTINUETARGETS= {ForStatement.class, ForInStatement.class, EnhancedForStatement.class, WhileStatement.class, DoStatement.class};
- private static final int BRACE_LENGTH= 1;
- /*
- * Initializes the finder. Returns error message or <code>null</code> if everything is OK.
- */
- public String initialize(JavaScriptUnit root, int offset, int length) {
- return initialize(root, NodeFinder.perform(root, offset, length));
- }
-
- /*
- * Initializes the finder. Returns error message or <code>null</code> if everything is OK.
- */
- public String initialize(JavaScriptUnit root, ASTNode node) {
- ASTNode controlNode= getBreakOrContinueNode(node);
- if (controlNode != null){
- fContents= getContents(root);
- if (fContents == null)
- return SearchMessages.BreakContinueTargetFinder_cannot_highlight;
- fSelected= controlNode;
- fIsBreak= fSelected instanceof BreakStatement;
- fLabel= getLabel();
- return null;
- } else {
- return SearchMessages.BreakContinueTargetFinder_no_break_or_continue_selected;
- }
- }
- /* Returns contents or <code>null</code> if there's trouble. */
- private String getContents(JavaScriptUnit root) {
- try {
- IJavaScriptElement rootElem= root.getJavaElement();
- if ((rootElem instanceof ISourceReference))
- return ((ISourceReference)rootElem).getSource();
- else
- return null;
- } catch (JavaScriptModelException e) {
- //We must handle it here because JavaEditor does not expect an exception
-
- /* showing a dialog here would be too heavy but we cannot just
- * swallow the exception */
- JavaScriptPlugin.log(e);
- return null;
- }
- }
- //extract the control node: handle labels
- private ASTNode getBreakOrContinueNode(ASTNode selectedNode) {
- if (selectedNode instanceof BreakStatement)
- return selectedNode;
- if (selectedNode instanceof ContinueStatement)
- return selectedNode;
- if (selectedNode instanceof SimpleName && selectedNode.getParent() instanceof BreakStatement)
- return selectedNode.getParent();
- if (selectedNode instanceof SimpleName && selectedNode.getParent() instanceof ContinueStatement)
- return selectedNode.getParent();
- return null;
- }
- public List perform() {
- return getNodesToHighlight();
- }
- private SimpleName getLabel() {
- if (fIsBreak){
- BreakStatement bs= (BreakStatement) fSelected;
- return bs.getLabel();
- } else {
- ContinueStatement cs= (ContinueStatement) fSelected;
- return cs.getLabel();
- }
- }
-
- private List getNodesToHighlight() {
- ASTNode targetNode= findTargetNode(fSelected);
- if (!isEnclosingStatement(targetNode))
- return Collections.EMPTY_LIST;
-
- List list= new ArrayList();
- ASTNode node= makeFakeNodeForFirstToken(targetNode);
- if (node != null)
- list.add(node);
-
- if (fIsBreak) {
- node= makeFakeNodeForClosingBrace(targetNode);
- if (node != null)
- list.add(node);
- }
-
- return list;
-
- }
- private boolean isEnclosingStatement(ASTNode targetNode) {
- return (targetNode != null) && !(targetNode instanceof FunctionDeclaration) && !(targetNode instanceof Initializer);
- }
- private ASTNode findTargetNode(ASTNode node) {
- do {
- node= node.getParent();
- } while (keepWalkingUp(node));
- return node;
- }
- private ASTNode makeFakeNodeForFirstToken(ASTNode node) {
- try {
- int length= getLengthOfFirstTokenOf(node);
- if (length < 1)
- return node;//fallback
- return makeFakeNode(node.getStartPosition(), length, node.getAST());
- } catch (InvalidInputException e) {
- return node;//fallback
- }
- }
- private SimpleName makeFakeNode(int start, int length, AST ast) {
- String fakeName= makeStringOfLength(length);
- SimpleName name= ast.newSimpleName(fakeName);
- name.setSourceRange(start, length);
- return name;
- }
- private ASTNode makeFakeNodeForClosingBrace(ASTNode targetNode) {
- ASTNode maybeBlock= getOptionalBlock(targetNode);
- if (maybeBlock == null)
- return null;
-
- /* Ideally, we'd scan backwards to find the '}' token, but it may be an overkill
- * so I'll just assume the closing brace token has a fixed length. */
- return makeFakeNode(ASTNodes.getExclusiveEnd(maybeBlock)-BRACE_LENGTH, BRACE_LENGTH, targetNode.getAST());
- }
- /*
- * Block cannot be return type here because SwitchStatement has no block
- * and yet it does have a closing brace.
- */
- private ASTNode getOptionalBlock(ASTNode targetNode) {
- final ASTNode[] maybeBlock= new ASTNode[1];
- targetNode.accept(new ASTVisitor(){
- public boolean visit(ForStatement node) {
- if (node.getBody() instanceof Block)
- maybeBlock[0]= node.getBody();
- return false;
- }
- public boolean visit(ForInStatement node) {
- if (node.getBody() instanceof Block)
- maybeBlock[0]= node.getBody();
- return false;
- }
- public boolean visit(EnhancedForStatement node) {
- if (node.getBody() instanceof Block)
- maybeBlock[0]= node.getBody();
- return false;
- }
- public boolean visit(WhileStatement node) {
- if (node.getBody() instanceof Block)
- maybeBlock[0]= node.getBody();
- return false;
- }
- public boolean visit(DoStatement node) {
- if (node.getBody() instanceof Block)
- maybeBlock[0]= node.getBody();
- return false;
- }
- public boolean visit(SwitchStatement node) {
- maybeBlock[0]= node;
- return false;
- }
- });
- return maybeBlock[0];
- }
- private static String makeStringOfLength(int length) {
- char[] chars= new char[length];
- Arrays.fill(chars, 'x');
- return new String(chars);
- }
- //must scan because of unicode
- private int getLengthOfFirstTokenOf(ASTNode node) throws InvalidInputException {
- IScanner scanner= ToolFactory.createScanner(true, true, false, true);
- scanner.setSource(getSource(node).toCharArray());
- scanner.getNextToken();
- return scanner.getRawTokenSource().length;
- }
- private String getSource(ASTNode node) {
- return fContents.substring(node.getStartPosition(), ASTNodes.getInclusiveEnd(node));
- }
- private boolean keepWalkingUp(ASTNode node) {
- if (node == null)
- return false;
- if (isAnyInstanceOf(STOPPERS, node))
- return false;
- if (fLabel != null && LabeledStatement.class.isInstance(node)){
- LabeledStatement ls= (LabeledStatement)node;
- return ! areEqualLabels(ls.getLabel(), fLabel);
- }
- if (fLabel == null && fIsBreak && isAnyInstanceOf(BREAKTARGETS, node))
- return false;
- if (fLabel == null && !fIsBreak && isAnyInstanceOf(CONTINUETARGETS, node))
- return false;
- return true;
- }
- //TODO see bug 33739 - resolveBinding always returns null
- //so we just compare names
- private static boolean areEqualLabels(SimpleName labelToMatch, SimpleName labelSelected) {
- return labelSelected.getIdentifier().equals(labelToMatch.getIdentifier());
- }
- private static boolean isAnyInstanceOf(Class[] continueTargets, ASTNode node) {
- for (int i= 0; i < continueTargets.length; i++) {
- if (continueTargets[i].isInstance(node))
- return true;
- }
- return false;
- }
- }