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