Index: ui/org/eclipse/jdt/internal/ui/javaeditor/JavaEditor.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/javaeditor/JavaEditor.java,v retrieving revision 1.365 diff -u -r1.365 JavaEditor.java --- ui/org/eclipse/jdt/internal/ui/javaeditor/JavaEditor.java 14 Jun 2005 16:20:33 -0000 1.365 +++ ui/org/eclipse/jdt/internal/ui/javaeditor/JavaEditor.java 6 Oct 2005 14:53:04 -0000 @@ -208,6 +208,7 @@ import org.eclipse.jdt.internal.ui.javaeditor.selectionactions.StructureSelectNextAction; import org.eclipse.jdt.internal.ui.javaeditor.selectionactions.StructureSelectPreviousAction; import org.eclipse.jdt.internal.ui.javaeditor.selectionactions.StructureSelectionAction; +import org.eclipse.jdt.internal.ui.search.BreakContinueTargetFinder; import org.eclipse.jdt.internal.ui.search.ExceptionOccurrencesFinder; import org.eclipse.jdt.internal.ui.search.ImplementOccurrencesFinder; import org.eclipse.jdt.internal.ui.search.MethodExitsFinder; @@ -1555,6 +1556,14 @@ * @since 3.0 */ private boolean fMarkMethodExitPoints; + + /** + * Tells whether to mark targets of break and continue statements in this editor. + * Only valid if {@link #fMarkOccurrenceAnnotations} is true. + * @since 3.2 + */ + private boolean fMarkBreakContinueTargets; + /** * Tells whether to mark implementors in this editor. * Only valid if {@link #fMarkOccurrenceAnnotations} is true. @@ -1673,6 +1682,7 @@ fMarkExceptions= store.getBoolean(PreferenceConstants.EDITOR_MARK_EXCEPTION_OCCURRENCES); fMarkImplementors= store.getBoolean(PreferenceConstants.EDITOR_MARK_IMPLEMENTORS); fMarkMethodExitPoints= store.getBoolean(PreferenceConstants.EDITOR_MARK_METHOD_EXIT_POINTS); + fMarkBreakContinueTargets= store.getBoolean(PreferenceConstants.EDITOR_MARK_BREAK_CONTINUE_TARGETS); } /* @@ -2471,6 +2481,10 @@ fMarkMethodExitPoints= newBooleanValue; return; } + if (PreferenceConstants.EDITOR_MARK_BREAK_CONTINUE_TARGETS.equals(property)) { + fMarkBreakContinueTargets= newBooleanValue; + return; + } if (PreferenceConstants.EDITOR_MARK_IMPLEMENTORS.equals(property)) { fMarkImplementors= newBooleanValue; return; @@ -2929,6 +2943,16 @@ } } + if ((matches == null || matches.isEmpty()) && (fMarkBreakContinueTargets || fMarkTypeOccurrences)) { + BreakContinueTargetFinder finder= new BreakContinueTargetFinder(); + String message= finder.initialize(astRoot, selection.getOffset(), selection.getLength()); + if (message == null) { + matches= finder.perform(); + if (!fMarkBreakContinueTargets && !matches.isEmpty()) + matches.clear(); + } + } + if ((matches == null || matches.isEmpty()) && (fMarkImplementors || fMarkTypeOccurrences)) { ImplementOccurrencesFinder finder= new ImplementOccurrencesFinder(); String message= finder.initialize(astRoot, selection.getOffset(), selection.getLength()); Index: ui/org/eclipse/jdt/internal/ui/preferences/MarkOccurrencesConfigurationBlock.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/MarkOccurrencesConfigurationBlock.java,v retrieving revision 1.14 diff -u -r1.14 MarkOccurrencesConfigurationBlock.java --- ui/org/eclipse/jdt/internal/ui/preferences/MarkOccurrencesConfigurationBlock.java 17 Jun 2005 15:51:52 -0000 1.14 +++ ui/org/eclipse/jdt/internal/ui/preferences/MarkOccurrencesConfigurationBlock.java 6 Oct 2005 14:53:04 -0000 @@ -85,6 +85,7 @@ overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, PreferenceConstants.EDITOR_MARK_EXCEPTION_OCCURRENCES)); overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, PreferenceConstants.EDITOR_MARK_METHOD_EXIT_POINTS)); overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, PreferenceConstants.EDITOR_MARK_IMPLEMENTORS)); + overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, PreferenceConstants.EDITOR_MARK_BREAK_CONTINUE_TARGETS)); overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, PreferenceConstants.EDITOR_STICKY_OCCURRENCES)); OverlayPreferenceStore.OverlayKey[] keys= new OverlayPreferenceStore.OverlayKey[overlayKeys.size()]; @@ -142,6 +143,10 @@ slave= addCheckBox(composite, label, PreferenceConstants.EDITOR_MARK_IMPLEMENTORS, 0); //$NON-NLS-1$ createDependency(master, PreferenceConstants.EDITOR_MARK_IMPLEMENTORS, slave); + label= PreferencesMessages.MarkOccurrencesConfigurationBlock_markBreakContinueTargets; + slave= addCheckBox(composite, label, PreferenceConstants.EDITOR_MARK_BREAK_CONTINUE_TARGETS, 0); //$NON-NLS-1$ + createDependency(master, PreferenceConstants.EDITOR_MARK_BREAK_CONTINUE_TARGETS, slave); + addFiller(composite); label= PreferencesMessages.MarkOccurrencesConfigurationBlock_stickyOccurrences; Index: ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.java,v retrieving revision 1.22 diff -u -r1.22 PreferencesMessages.java --- ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.java 9 Jun 2005 12:35:27 -0000 1.22 +++ ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.java 6 Oct 2005 14:53:04 -0000 @@ -216,6 +216,7 @@ public static String MarkOccurrencesConfigurationBlock_markExceptionOccurrences; public static String MarkOccurrencesConfigurationBlock_markMethodExitPoints; public static String MarkOccurrencesConfigurationBlock_markImplementors; + public static String MarkOccurrencesConfigurationBlock_markBreakContinueTargets; public static String MarkOccurrencesConfigurationBlock_stickyOccurrences; public static String JavaElementInfoPage_binary; public static String JavaElementInfoPage_classpath_entry_kind; Index: ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.properties =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.properties,v retrieving revision 1.320 diff -u -r1.320 PreferencesMessages.properties --- ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.properties 20 Jun 2005 19:11:01 -0000 1.320 +++ ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.properties 6 Oct 2005 14:53:04 -0000 @@ -234,6 +234,7 @@ MarkOccurrencesConfigurationBlock_markExceptionOccurrences= E&xpressions throwing a declared exception MarkOccurrencesConfigurationBlock_markMethodExitPoints= Method &exits MarkOccurrencesConfigurationBlock_markImplementors= Methods implementing an &interface +MarkOccurrencesConfigurationBlock_markBreakContinueTargets= Targets of &break and continue statements MarkOccurrencesConfigurationBlock_stickyOccurrences= &Keep marks when the selection changes JavaElementInfoPage_binary=binary Index: ui/org/eclipse/jdt/internal/ui/search/SearchMessages.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/search/SearchMessages.java,v retrieving revision 1.10 diff -u -r1.10 SearchMessages.java --- ui/org/eclipse/jdt/internal/ui/search/SearchMessages.java 19 Apr 2005 10:45:48 -0000 1.10 +++ ui/org/eclipse/jdt/internal/ui/search/SearchMessages.java 6 Oct 2005 14:53:05 -0000 @@ -193,6 +193,8 @@ public static String MatchFilter_InexactFilter_actionLabel; public static String MatchFilter_InexactFilter_description; public static String MethodExitsFinder_no_return_type_selected; + public static String BreakContinueFinder_cannot_hightlight; + public static String BreakContinueFinder_no_break_or_continue_selected; public static String TextSearchLabelProvider_matchCountFormat; public static String FiltersDialog_title; public static String FiltersDialog_filters_label; Index: ui/org/eclipse/jdt/internal/ui/search/SearchMessages.properties =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/search/SearchMessages.properties,v retrieving revision 1.103 diff -u -r1.103 SearchMessages.properties --- ui/org/eclipse/jdt/internal/ui/search/SearchMessages.properties 13 Apr 2005 17:35:12 -0000 1.103 +++ ui/org/eclipse/jdt/internal/ui/search/SearchMessages.properties 6 Oct 2005 14:53:05 -0000 @@ -248,6 +248,9 @@ MethodExitsFinder_no_return_type_selected=No return type selected +BreakContinueFinder_cannot_hightlight=Occurrences in this element cannot be highlighted +BreakContinueFinder_no_break_or_continue_selected=No break or continue selected + TextSearchLabelProvider_matchCountFormat={0} ({1} matches) FiltersDialog_title=Java Search Filters Index: ui/org/eclipse/jdt/ui/PreferenceConstants.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/PreferenceConstants.java,v retrieving revision 1.168 diff -u -r1.168 PreferenceConstants.java --- ui/org/eclipse/jdt/ui/PreferenceConstants.java 27 May 2005 08:04:13 -0000 1.168 +++ ui/org/eclipse/jdt/ui/PreferenceConstants.java 6 Oct 2005 14:53:05 -0000 @@ -2222,6 +2222,17 @@ public static final String EDITOR_MARK_METHOD_EXIT_POINTS= "markMethodExitPoints"; //$NON-NLS-1$ /** + * A named preference that controls whether targets for of break and continue statements are marked. + * Only valid if {@link #EDITOR_MARK_OCCURRENCES} is true. + *

+ * Value is of type Boolean. + *

+ * + * @since 3.2 + */ + public static final String EDITOR_MARK_BREAK_CONTINUE_TARGETS= "markBreakContinueTargets"; //$NON-NLS-1$ + + /** * A named preference that controls whether method exit points are marked. * Only valid if {@link #EDITOR_MARK_OCCURRENCES} is true. *

@@ -3475,6 +3486,7 @@ store.setDefault(PreferenceConstants.EDITOR_MARK_LOCAL_VARIABLE_OCCURRENCES, true); store.setDefault(PreferenceConstants.EDITOR_MARK_EXCEPTION_OCCURRENCES, true); store.setDefault(PreferenceConstants.EDITOR_MARK_METHOD_EXIT_POINTS, true); + store.setDefault(PreferenceConstants.EDITOR_MARK_BREAK_CONTINUE_TARGETS, true); store.setDefault(PreferenceConstants.EDITOR_MARK_IMPLEMENTORS, true); // spell checking Index: ui/org/eclipse/jdt/internal/ui/search/BreakContinueTargetFinder.java =================================================================== RCS file: ui/org/eclipse/jdt/internal/ui/search/BreakContinueTargetFinder.java diff -N ui/org/eclipse/jdt/internal/ui/search/BreakContinueTargetFinder.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ ui/org/eclipse/jdt/internal/ui/search/BreakContinueTargetFinder.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,272 @@ +/******************************************************************************* + * 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.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 spec. + * 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. + */ +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 null if everything is OK. + */ + public String initialize(CompilationUnit root, int offset, int length) { + ASTNode controlNode= getBreakOrContinueNode(NodeFinder.perform(root, offset, length)); + if (controlNode != null){ + fContents= getContents(root); + if (fContents == null) + return SearchMessages.BreakContinueFinder_cannot_hightlight; + + fSelected= controlNode; + fIsBreak= fSelected instanceof BreakStatement; + fLabel= getLabel(); + return null; + } else { + return SearchMessages.BreakContinueFinder_no_break_or_continue_selected; + } + } + + /* Returns contents or null 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 null; + + if (! fIsBreak) + return createListIgnoreNull(makeFakeNodeForFirstToken(targetNode)); + + return createListIgnoreNulls(makeFakeNodeForFirstToken(targetNode), + makeFakeNodeForClosingBrace(targetNode)); + } + + private List createListIgnoreNulls(Object o1, Object o2) { + if (o1 == null) + return createListIgnoreNull(o2); + if (o2 == null) + return createListIgnoreNull(o1); + return Arrays.asList(new Object[]{o1, o2}); + } + + private List createListIgnoreNull(Object o) { + if (o == null) + return null; + return Collections.singletonList(o); + } + + 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; + } +}