blob: b0d9462a1f112c16699f3d7a8d1d5f5b121d8285 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 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 Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.ui.search;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.ToolFactory;
import org.eclipse.jdt.core.compiler.IScanner;
import org.eclipse.jdt.core.compiler.InvalidInputException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BreakStatement;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ContinueStatement;
import org.eclipse.jdt.core.dom.DoStatement;
import org.eclipse.jdt.core.dom.EnhancedForStatement;
import org.eclipse.jdt.core.dom.ForStatement;
import org.eclipse.jdt.core.dom.Initializer;
import org.eclipse.jdt.core.dom.LabeledStatement;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SwitchStatement;
import org.eclipse.jdt.core.dom.WhileStatement;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.NodeFinder;
import org.eclipse.jdt.internal.ui.JavaPlugin;
/**
* 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>
*
* @since 3.2
*/
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= {MethodDeclaration.class, Initializer.class};
private static final Class[] BREAKTARGETS= {ForStatement.class, EnhancedForStatement.class, WhileStatement.class, DoStatement.class, SwitchStatement.class};
private static final Class[] CONTINUETARGETS= {ForStatement.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(CompilationUnit 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(CompilationUnit 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(CompilationUnit root) {
try {
IJavaElement rootElem= root.getJavaElement();
if ((rootElem instanceof ISourceReference))
return ((ISourceReference)rootElem).getSource();
else
return null;
} catch (JavaModelException 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 */
JavaPlugin.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 MethodDeclaration) && !(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(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;
}
}