| /* |
| * Copyright (c) 2013, the Dart project authors. |
| * |
| * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except |
| * in compliance with the License. You may obtain a copy of the License at |
| * |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Unless required by applicable law or agreed to in writing, software distributed under the License |
| * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
| * or implied. See the License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.dart.engine.services.internal.correction; |
| |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| import com.google.dart.engine.ast.AstNode; |
| import com.google.dart.engine.ast.CatchClause; |
| import com.google.dart.engine.ast.CompilationUnit; |
| import com.google.dart.engine.ast.DoStatement; |
| import com.google.dart.engine.ast.ForStatement; |
| import com.google.dart.engine.ast.SwitchMember; |
| import com.google.dart.engine.ast.SwitchStatement; |
| import com.google.dart.engine.ast.TryStatement; |
| import com.google.dart.engine.ast.WhileStatement; |
| import com.google.dart.engine.scanner.Token; |
| import com.google.dart.engine.services.internal.util.TokenUtils; |
| import com.google.dart.engine.services.status.RefactoringStatus; |
| import com.google.dart.engine.services.status.RefactoringStatusContext; |
| import com.google.dart.engine.services.util.SelectionAnalyzer; |
| import com.google.dart.engine.utilities.source.SourceRange; |
| |
| import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeEndEnd; |
| import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeStartStart; |
| |
| import java.util.List; |
| |
| /** |
| * Analyzer to check if a selection covers a valid set of statements of AST. |
| */ |
| public class StatementAnalyzer extends SelectionAnalyzer { |
| |
| /** |
| * @return <code>true</code> if "nodes" contains "node". |
| */ |
| private static boolean contains(List<AstNode> nodes, AstNode node) { |
| return nodes.contains(node); |
| } |
| |
| /** |
| * @return <code>true</code> if "nodes" contains one of the "otherNodes". |
| */ |
| private static boolean contains(List<AstNode> nodes, List<? extends AstNode> otherNodes) { |
| for (AstNode otherNode : otherNodes) { |
| if (nodes.contains(otherNode)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| protected final CorrectionUtils utils; |
| private final RefactoringStatus status = new RefactoringStatus(); |
| |
| public StatementAnalyzer(CompilationUnit cunit, SourceRange selection) throws Exception { |
| this(new CorrectionUtils(cunit), selection); |
| } |
| |
| public StatementAnalyzer(CorrectionUtils utils, SourceRange selection) { |
| super(selection); |
| this.utils = utils; |
| } |
| |
| /** |
| * @return the {@link RefactoringStatus} result of checking selection. |
| */ |
| public RefactoringStatus getStatus() { |
| return status; |
| } |
| |
| @Override |
| public Void visitCompilationUnit(CompilationUnit node) { |
| super.visitCompilationUnit(node); |
| if (!hasSelectedNodes()) { |
| return null; |
| } |
| // check that selection does not begin/end in comment |
| { |
| int selectionStart = selection.getOffset(); |
| int selectionEnd = selection.getEnd(); |
| List<SourceRange> commentRanges = utils.getCommentRanges(); |
| for (SourceRange commentRange : commentRanges) { |
| if (commentRange.contains(selectionStart)) { |
| invalidSelection("Selection begins inside a comment."); |
| } |
| if (commentRange.containsExclusive(selectionEnd)) { |
| invalidSelection("Selection ends inside a comment."); |
| } |
| } |
| } |
| // more checks |
| if (!status.hasFatalError()) { |
| checkSelectedNodes(node); |
| } |
| return null; |
| } |
| |
| @Override |
| public Void visitDoStatement(DoStatement node) { |
| super.visitDoStatement(node); |
| List<AstNode> selectedNodes = getSelectedNodes(); |
| if (contains(selectedNodes, node.getBody())) { |
| invalidSelection("Operation not applicable to a 'do' statement's body and expression."); |
| } |
| return null; |
| } |
| |
| @Override |
| public Void visitForStatement(ForStatement node) { |
| super.visitForStatement(node); |
| List<AstNode> selectedNodes = getSelectedNodes(); |
| boolean containsInit = contains(selectedNodes, node.getInitialization()) |
| || contains(selectedNodes, node.getVariables()); |
| boolean containsCondition = contains(selectedNodes, node.getCondition()); |
| boolean containsUpdaters = contains(selectedNodes, node.getUpdaters()); |
| boolean containsBody = contains(selectedNodes, node.getBody()); |
| if (containsInit && containsCondition) { |
| invalidSelection("Operation not applicable to a 'for' statement's initializer and condition."); |
| } else if (containsCondition && containsUpdaters) { |
| invalidSelection("Operation not applicable to a 'for' statement's condition and updaters."); |
| } else if (containsUpdaters && containsBody) { |
| invalidSelection("Operation not applicable to a 'for' statement's updaters and body."); |
| } |
| return null; |
| } |
| |
| @Override |
| public Void visitSwitchStatement(SwitchStatement node) { |
| super.visitSwitchStatement(node); |
| List<AstNode> selectedNodes = getSelectedNodes(); |
| List<SwitchMember> switchMembers = node.getMembers(); |
| for (AstNode selectedNode : selectedNodes) { |
| if (switchMembers.contains(selectedNode)) { |
| invalidSelection("Selection must either cover whole switch statement or parts of a single case block."); |
| break; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public Void visitTryStatement(TryStatement node) { |
| super.visitTryStatement(node); |
| AstNode firstSelectedNode = getFirstSelectedNode(); |
| if (firstSelectedNode != null) { |
| if (firstSelectedNode == node.getBody() || firstSelectedNode == node.getFinallyBlock()) { |
| invalidSelection("Selection must either cover whole try statement or parts of try, catch, or finally block."); |
| } else { |
| List<CatchClause> catchClauses = node.getCatchClauses(); |
| for (CatchClause catchClause : catchClauses) { |
| if (firstSelectedNode == catchClause || firstSelectedNode == catchClause.getBody() |
| || firstSelectedNode == catchClause.getExceptionParameter()) { |
| invalidSelection("Selection must either cover whole try statement or parts of try, catch, or finally block."); |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public Void visitWhileStatement(WhileStatement node) { |
| super.visitWhileStatement(node); |
| List<AstNode> selectedNodes = getSelectedNodes(); |
| if (contains(selectedNodes, node.getCondition()) && contains(selectedNodes, node.getBody())) { |
| invalidSelection("Operation not applicable to a while statement's expression and body."); |
| } |
| return null; |
| } |
| |
| /** |
| * Records fatal error with given message. |
| */ |
| protected final void invalidSelection(String message) { |
| invalidSelection(message, null); |
| } |
| |
| /** |
| * Records fatal error with given message and {@link RefactoringStatusContext}. |
| */ |
| protected final void invalidSelection(String message, RefactoringStatusContext context) { |
| status.addFatalError(message, context); |
| reset(); |
| } |
| |
| /** |
| * Checks final selected {@link AstNode}s after processing {@link CompilationUnit}. |
| */ |
| private void checkSelectedNodes(CompilationUnit unit) { |
| List<AstNode> nodes = getSelectedNodes(); |
| // some tokens before first selected node |
| { |
| AstNode firstNode = nodes.get(0); |
| SourceRange rangeBeforeFirstNode = rangeStartStart(selection, firstNode); |
| if (hasTokens(rangeBeforeFirstNode)) { |
| invalidSelection( |
| "The beginning of the selection contains characters that do not belong to a statement.", |
| new RefactoringStatusContext(unit, rangeBeforeFirstNode)); |
| } |
| } |
| // some tokens after last selected node |
| { |
| AstNode lastNode = Iterables.getLast(nodes); |
| SourceRange rangeAfterLastNode = rangeEndEnd(lastNode, selection); |
| if (hasTokens(rangeAfterLastNode)) { |
| invalidSelection( |
| "The end of the selection contains characters that do not belong to a statement.", |
| new RefactoringStatusContext(unit, rangeAfterLastNode)); |
| } |
| } |
| } |
| |
| /** |
| * @return the {@link Token}s in given {@link SourceRange}. |
| */ |
| private List<Token> getTokens(SourceRange range) { |
| try { |
| String text = utils.getText(range); |
| return TokenUtils.getTokens(text); |
| } catch (Throwable e) { |
| return Lists.newArrayList(); |
| } |
| } |
| |
| /** |
| * @return <code>true</code> if there are {@link Token}s in the given {@link SourceRange}. |
| */ |
| private boolean hasTokens(SourceRange range) { |
| return !getTokens(range).isEmpty(); |
| } |
| } |