/******************************************************************************* * Copyright (c) 2000, 2011 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.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; 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.NodeFinder; 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.TokenScanner; import org.eclipse.jdt.internal.corext.util.Messages; import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels; /** * 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 implements IOccurrencesFinder { public static final String ID= "BreakContinueTargetFinder"; //$NON-NLS-1$ private ASTNode fSelected; private boolean fIsBreak; private SimpleName fLabel; private String fDescription; private CompilationUnit fASTRoot; 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) { fASTRoot= root; try { if (root.getTypeRoot() == null || root.getTypeRoot().getBuffer() == null) return SearchMessages.BreakContinueTargetFinder_cannot_highlight; } catch (JavaModelException e) { return SearchMessages.BreakContinueTargetFinder_cannot_highlight; } fSelected= controlNode; fIsBreak= fSelected instanceof BreakStatement; fLabel= getLabel(); fDescription= Messages.format(SearchMessages.BreakContinueTargetFinder_occurrence_description, BasicElementLabels.getJavaElementName(ASTNodes.asString(fSelected))); return null; } else { return SearchMessages.BreakContinueTargetFinder_no_break_or_continue_selected; } } //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; } private SimpleName getLabel() { if (fIsBreak){ BreakStatement bs= (BreakStatement) fSelected; return bs.getLabel(); } else { ContinueStatement cs= (ContinueStatement) fSelected; return cs.getLabel(); } } /** * Returns the locations of all occurrences or <code>null</code> if no matches are found * * @return the locations of all occurrences or <code>null</code> if no matches are found */ public OccurrenceLocation[] getOccurrences() { ASTNode targetNode= findTargetNode(fSelected); if (!isEnclosingStatement(targetNode)) return null; List<OccurrenceLocation> list= new ArrayList<OccurrenceLocation>(); OccurrenceLocation location= getLocationForFirstToken(targetNode); if (location != null) { list.add(location); } if (fIsBreak) { location= getLocationForClosingBrace(targetNode); if (location != null) { list.add(location); } } if (!list.isEmpty()) { return list.toArray(new OccurrenceLocation[list.size()]); } return null; } 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 OccurrenceLocation getLocationForFirstToken(ASTNode node) { try { int nextEndOffset= new TokenScanner(fASTRoot.getTypeRoot()).getNextEndOffset(node.getStartPosition(), true); return new OccurrenceLocation(node.getStartPosition(), nextEndOffset - node.getStartPosition(), 0, fDescription); } catch (CoreException e) { // ignore } return new OccurrenceLocation(node.getStartPosition(), node.getLength(), 0, fDescription); } private OccurrenceLocation getLocationForClosingBrace(ASTNode targetNode) { /* 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. */ int offset= ASTNodes.getExclusiveEnd(targetNode) - BRACE_LENGTH; return new OccurrenceLocation(offset, BRACE_LENGTH, 0, fDescription); } 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) { if (isAnyInstanceOf(fIsBreak ? BREAKTARGETS : CONTINUETARGETS, node)) return node.getParent() instanceof LabeledStatement; // for behavior consistency of break targets: see bug 339176 if (node instanceof LabeledStatement) return false; } return true; } 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; } public CompilationUnit getASTRoot() { return fASTRoot; } public String getElementName() { return ASTNodes.asString(fSelected); } public String getID() { return ID; } public String getJobLabel() { return SearchMessages.BreakContinueTargetFinder_job_label; } public int getSearchKind() { return IOccurrencesFinder.K_BREAK_TARGET_OCCURRENCE; } public String getUnformattedPluralLabel() { if (fIsBreak) { return SearchMessages.BreakContinueTargetFinder_break_label_plural; } else { return SearchMessages.BreakContinueTargetFinder_continue_label_plural; } } public String getUnformattedSingularLabel() { if (fIsBreak) { return SearchMessages.BreakContinueTargetFinder_break_label_singular; } else { return SearchMessages.BreakContinueTargetFinder_continue_label_singular; } } }