1 /******************************************************************************* 2 * Copyright (c) 2000, 2019 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.jdt.internal.ui.search; 15 16 import java.util.ArrayList; 17 import java.util.List; 18 19 import org.eclipse.core.runtime.CoreException; 20 21 import org.eclipse.jdt.core.JavaModelException; 22 import org.eclipse.jdt.core.dom.ASTNode; 23 import org.eclipse.jdt.core.dom.ASTVisitor; 24 import org.eclipse.jdt.core.dom.BreakStatement; 25 import org.eclipse.jdt.core.dom.CompilationUnit; 26 import org.eclipse.jdt.core.dom.ContinueStatement; 27 import org.eclipse.jdt.core.dom.DoStatement; 28 import org.eclipse.jdt.core.dom.EnhancedForStatement; 29 import org.eclipse.jdt.core.dom.ForStatement; 30 import org.eclipse.jdt.core.dom.Initializer; 31 import org.eclipse.jdt.core.dom.LabeledStatement; 32 import org.eclipse.jdt.core.dom.MethodDeclaration; 33 import org.eclipse.jdt.core.dom.NodeFinder; 34 import org.eclipse.jdt.core.dom.SimpleName; 35 import org.eclipse.jdt.core.dom.SwitchExpression; 36 import org.eclipse.jdt.core.dom.SwitchStatement; 37 import org.eclipse.jdt.core.dom.WhileStatement; 38 39 import org.eclipse.jdt.internal.core.manipulation.search.IOccurrencesFinder; 40 import org.eclipse.jdt.internal.corext.dom.ASTNodes; 41 import org.eclipse.jdt.internal.corext.dom.TokenScanner; 42 import org.eclipse.jdt.internal.corext.util.Messages; 43 44 import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels; 45 46 47 /** 48 * Class used to find the target for a break or continue statement according 49 * to the language specification. 50 * <p> 51 * The target statement is a while, do, switch, for or a labeled statement. 52 * Break is described in section 14.15 of the JLS3 and continue in section 14.16.</p> 53 * 54 * @since 3.2 55 */ 56 public class BreakContinueTargetFinder extends ASTVisitor implements IOccurrencesFinder { 57 58 public static final String ID= "BreakContinueTargetFinder"; //$NON-NLS-1$ 59 60 private ASTNode fSelected; 61 private boolean fIsBreak; 62 private SimpleName fLabel; 63 private String fDescription; 64 private CompilationUnit fASTRoot; 65 66 private static final Class<?>[] STOPPERS= {MethodDeclaration.class, Initializer.class}; 67 private static final Class<?>[] BREAKTARGETS= {ForStatement.class, EnhancedForStatement.class, WhileStatement.class, DoStatement.class, SwitchStatement.class, SwitchExpression.class}; 68 private static final Class<?>[] CONTINUETARGETS= {ForStatement.class, EnhancedForStatement.class, WhileStatement.class, DoStatement.class}; 69 private static final int BRACE_LENGTH= 1; 70 71 /* 72 * Initializes the finder. Returns error message or <code>null</code> if everything is OK. 73 */ 74 @Override initialize(CompilationUnit root, int offset, int length)75 public String initialize(CompilationUnit root, int offset, int length) { 76 return initialize(root, NodeFinder.perform(root, offset, length)); 77 } 78 79 /* 80 * Initializes the finder. Returns error message or <code>null</code> if everything is OK. 81 */ 82 @Override initialize(CompilationUnit root, ASTNode node)83 public String initialize(CompilationUnit root, ASTNode node) { 84 ASTNode controlNode= getBreakOrContinueNode(node); 85 if (controlNode != null) { 86 fASTRoot= root; 87 88 try { 89 if (root.getTypeRoot() == null || root.getTypeRoot().getBuffer() == null) 90 return SearchMessages.BreakContinueTargetFinder_cannot_highlight; 91 } catch (JavaModelException e) { 92 return SearchMessages.BreakContinueTargetFinder_cannot_highlight; 93 } 94 fSelected= controlNode; 95 fIsBreak= fSelected instanceof BreakStatement; 96 fLabel= getLabel(); 97 fDescription= Messages.format(SearchMessages.BreakContinueTargetFinder_occurrence_description, BasicElementLabels.getJavaElementName(ASTNodes.asString(fSelected))); 98 return null; 99 } else { 100 return SearchMessages.BreakContinueTargetFinder_no_break_or_continue_selected; 101 } 102 } 103 104 105 //extract the control node: handle labels getBreakOrContinueNode(ASTNode selectedNode)106 private ASTNode getBreakOrContinueNode(ASTNode selectedNode) { 107 if (selectedNode instanceof BreakStatement) 108 return selectedNode; 109 if (selectedNode instanceof ContinueStatement) 110 return selectedNode; 111 if (selectedNode instanceof SimpleName && selectedNode.getParent() instanceof BreakStatement) 112 return selectedNode.getParent(); 113 if (selectedNode instanceof SimpleName && selectedNode.getParent() instanceof ContinueStatement) 114 return selectedNode.getParent(); 115 return null; 116 } 117 118 getLabel()119 private SimpleName getLabel() { 120 if (fIsBreak){ 121 BreakStatement bs= (BreakStatement) fSelected; 122 return bs.getLabel(); 123 } else { 124 ContinueStatement cs= (ContinueStatement) fSelected; 125 return cs.getLabel(); 126 } 127 } 128 129 /** 130 * Returns the locations of all occurrences or <code>null</code> if no matches are found 131 * 132 * @return the locations of all occurrences or <code>null</code> if no matches are found 133 */ 134 @Override getOccurrences()135 public OccurrenceLocation[] getOccurrences() { 136 ASTNode targetNode= findTargetNode(fSelected); 137 if (!isEnclosingStatement(targetNode)) 138 return null; 139 140 List<OccurrenceLocation> list= new ArrayList<>(); 141 OccurrenceLocation location= getLocationForFirstToken(targetNode); 142 if (location != null) { 143 list.add(location); 144 } 145 if (fIsBreak) { 146 location= getLocationForClosingBrace(targetNode); 147 if (location != null) { 148 list.add(location); 149 } 150 } 151 if (!list.isEmpty()) { 152 return list.toArray(new OccurrenceLocation[list.size()]); 153 } 154 return null; 155 } 156 isEnclosingStatement(ASTNode targetNode)157 private boolean isEnclosingStatement(ASTNode targetNode) { 158 return (targetNode != null) && !(targetNode instanceof MethodDeclaration) && !(targetNode instanceof Initializer); 159 } 160 findTargetNode(ASTNode node)161 private ASTNode findTargetNode(ASTNode node) { 162 do { 163 node= node.getParent(); 164 } while (keepWalkingUp(node)); 165 return node; 166 } 167 getLocationForFirstToken(ASTNode node)168 private OccurrenceLocation getLocationForFirstToken(ASTNode node) { 169 try { 170 int nextEndOffset= new TokenScanner(fASTRoot.getTypeRoot()).getNextEndOffset(node.getStartPosition(), true); 171 return new OccurrenceLocation(node.getStartPosition(), nextEndOffset - node.getStartPosition(), 0, fDescription); 172 } catch (CoreException e) { 173 // ignore 174 } 175 return new OccurrenceLocation(node.getStartPosition(), node.getLength(), 0, fDescription); 176 } 177 getLocationForClosingBrace(ASTNode targetNode)178 private OccurrenceLocation getLocationForClosingBrace(ASTNode targetNode) { 179 /* Ideally, we'd scan backwards to find the '}' token, but it may be an overkill 180 * so I'll just assume the closing brace token has a fixed length. */ 181 int offset= ASTNodes.getExclusiveEnd(targetNode) - BRACE_LENGTH; 182 return new OccurrenceLocation(offset, BRACE_LENGTH, 0, fDescription); 183 } 184 keepWalkingUp(ASTNode node)185 private boolean keepWalkingUp(ASTNode node) { 186 if (node == null) 187 return false; 188 if (isAnyInstanceOf(STOPPERS, node)) 189 return false; 190 if (fLabel != null && LabeledStatement.class.isInstance(node)){ 191 LabeledStatement ls= (LabeledStatement)node; 192 return ! areEqualLabels(ls.getLabel(), fLabel); 193 } 194 if (fLabel == null) { 195 if (isAnyInstanceOf(fIsBreak ? BREAKTARGETS : CONTINUETARGETS, node)) 196 return node.getParent() instanceof LabeledStatement; // for behavior consistency of break targets: see bug 339176 197 if (node instanceof LabeledStatement) 198 return false; 199 } 200 return true; 201 } 202 areEqualLabels(SimpleName labelToMatch, SimpleName labelSelected)203 private static boolean areEqualLabels(SimpleName labelToMatch, SimpleName labelSelected) { 204 return labelSelected.getIdentifier().equals(labelToMatch.getIdentifier()); 205 } 206 isAnyInstanceOf(Class<?>[] continueTargets, ASTNode node)207 private static boolean isAnyInstanceOf(Class<?>[] continueTargets, ASTNode node) { 208 for (Class<?> continueTarget : continueTargets) { 209 if (continueTarget.isInstance(node)) { 210 return true; 211 } 212 } 213 return false; 214 } 215 216 @Override getASTRoot()217 public CompilationUnit getASTRoot() { 218 return fASTRoot; 219 } 220 221 @Override getElementName()222 public String getElementName() { 223 return ASTNodes.asString(fSelected); 224 } 225 226 @Override getID()227 public String getID() { 228 return ID; 229 } 230 231 @Override getJobLabel()232 public String getJobLabel() { 233 return SearchMessages.BreakContinueTargetFinder_job_label; 234 } 235 236 @Override getSearchKind()237 public int getSearchKind() { 238 return IOccurrencesFinder.K_BREAK_TARGET_OCCURRENCE; 239 } 240 241 @Override getUnformattedPluralLabel()242 public String getUnformattedPluralLabel() { 243 if (fIsBreak) { 244 return SearchMessages.BreakContinueTargetFinder_break_label_plural; 245 } else { 246 return SearchMessages.BreakContinueTargetFinder_continue_label_plural; 247 } 248 } 249 250 @Override getUnformattedSingularLabel()251 public String getUnformattedSingularLabel() { 252 if (fIsBreak) { 253 return SearchMessages.BreakContinueTargetFinder_break_label_singular; 254 } else { 255 return SearchMessages.BreakContinueTargetFinder_continue_label_singular; 256 } 257 } 258 } 259