| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 |
| * |
| * 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 org.netbeans.modules.java.editor.base.semantic; |
| |
| import com.sun.source.tree.CatchTree; |
| import com.sun.source.tree.ClassTree; |
| import com.sun.source.tree.CompilationUnitTree; |
| import com.sun.source.tree.IfTree; |
| import com.sun.source.tree.LambdaExpressionTree; |
| import com.sun.source.tree.MethodTree; |
| import com.sun.source.tree.MethodInvocationTree; |
| import com.sun.source.tree.NewClassTree; |
| import com.sun.source.tree.ReturnTree; |
| import com.sun.source.tree.ThrowTree; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.tree.TryTree; |
| import com.sun.source.util.TreePath; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Stack; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ElementKind; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.type.TypeMirror; |
| import javax.lang.model.util.Types; |
| import javax.swing.text.Document; |
| import org.netbeans.api.java.source.CompilationInfo; |
| import org.netbeans.api.java.source.support.CancellableTreePathScanner; |
| |
| /** |
| * |
| * @author Jan Lahoda |
| */ |
| public class MethodExitDetector extends CancellableTreePathScanner<Boolean, Stack<Tree>> { |
| |
| public MethodExitDetector() {} |
| |
| private CompilationInfo info; |
| private Document doc; |
| private List<int[]> highlights; |
| private boolean doExitPoints; |
| private Collection<TypeMirror> exceptions; |
| private Stack<Map<TypeMirror, List<Tree>>> exceptions2HighlightsStack; |
| |
| public List<int[]> process(CompilationInfo info, Document document, TreePath methoddeclorblock, Collection<Tree> excs) { |
| this.info = info; |
| this.doc = document; |
| this.highlights = new ArrayList<int[]>(); |
| this.exceptions2HighlightsStack = new Stack<Map<TypeMirror, List<Tree>>>(); |
| this.exceptions2HighlightsStack.push(null); |
| |
| try { |
| CompilationUnitTree cu = info.getCompilationUnit(); |
| |
| //"return" exit point only if not searching for exceptions: |
| doExitPoints = excs == null; |
| |
| Boolean wasReturn = scan(methoddeclorblock, null); |
| |
| if (isCanceled()) |
| return null; |
| |
| if (doExitPoints && wasReturn != Boolean.TRUE) { |
| int lastBracket = Utilities.findLastBracket(methoddeclorblock.getLeaf(), cu, info.getTrees().getSourcePositions(), document); |
| |
| if (lastBracket != (-1)) { |
| //highlight the "fall over" exitpoint: |
| highlights.add(new int[] {lastBracket, lastBracket + 1}); |
| } |
| } |
| |
| List<TypeMirror> exceptions = null; |
| |
| if (excs != null) { |
| exceptions = new ArrayList<TypeMirror>(); |
| |
| for (Tree t : excs) { |
| if (isCanceled()) |
| return null; |
| |
| TypeMirror m = info.getTrees().getTypeMirror(TreePath.getPath(cu, t)); |
| |
| if (m != null) { |
| exceptions.add(m); |
| } |
| } |
| } |
| |
| Types t = info.getTypes(); |
| |
| assert exceptions2HighlightsStack.size() == 1 : exceptions2HighlightsStack.size(); |
| |
| Map<TypeMirror, List<Tree>> exceptions2Highlights = exceptions2HighlightsStack.peek(); |
| |
| //exceptions2Highlights may be null if the method is empty (or not finished, like "public void") |
| //see ExitPointsEmptyMethod and ExitPointsStartedMethod tests: |
| if (exceptions2Highlights != null) { |
| for (TypeMirror type1 : exceptions2Highlights.keySet()) { |
| if (isCanceled()) |
| return null; |
| |
| boolean add = true; |
| |
| if (exceptions != null) { |
| add = false; |
| |
| for (TypeMirror type2 : exceptions) { |
| add |= t.isAssignable(type1, type2); |
| } |
| } |
| |
| if (add) { |
| for (Tree tree : exceptions2Highlights.get(type1)) { |
| addHighlightFor(tree); |
| } |
| } |
| } |
| } |
| |
| return highlights; |
| } finally { |
| //clean-up: |
| this.info = null; |
| this.doc = null; |
| this.highlights = null; |
| this.exceptions2HighlightsStack = null; |
| } |
| } |
| |
| private void addHighlightFor(Tree t) { |
| int start = (int) info.getTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), t); |
| int end = (int) info.getTrees().getSourcePositions().getEndPosition(info.getCompilationUnit(), t); |
| |
| highlights.add(new int[] {start, end}); |
| } |
| |
| private void addToExceptionsMap(TypeMirror key, Tree value) { |
| if (key == null || value == null) |
| return ; |
| |
| Map<TypeMirror, List<Tree>> map = exceptions2HighlightsStack.peek(); |
| |
| if (map == null) { |
| map = new HashMap<TypeMirror, List<Tree>>(); |
| exceptions2HighlightsStack.pop(); |
| exceptions2HighlightsStack.push(map); |
| } |
| |
| List<Tree> l = map.get(key); |
| |
| if (l == null) { |
| map.put(key, l = new ArrayList<Tree>()); |
| } |
| |
| l.add(value); |
| } |
| |
| private void doPopup() { |
| Map<TypeMirror, List<Tree>> top = exceptions2HighlightsStack.pop(); |
| |
| if (top == null) |
| return ; |
| |
| Map<TypeMirror, List<Tree>> result = exceptions2HighlightsStack.pop(); |
| |
| if (result == null) { |
| exceptions2HighlightsStack.push(top); |
| return ; |
| } |
| |
| for (TypeMirror key : top.keySet()) { |
| List<Tree> topKey = top.get(key); |
| List<Tree> resultKey = result.get(key); |
| |
| if (topKey == null) |
| continue; |
| |
| if (resultKey == null) { |
| result.put(key, topKey); |
| continue; |
| } |
| |
| resultKey.addAll(topKey); |
| } |
| |
| exceptions2HighlightsStack.push(result); |
| } |
| |
| @Override |
| public Boolean visitTry(TryTree tree, Stack<Tree> d) { |
| exceptions2HighlightsStack.push(null); |
| |
| Boolean returnInTryBlock = scan(tree.getBlock(), d); |
| |
| boolean returnInCatchBlock = true; |
| |
| for (Tree t : tree.getCatches()) { |
| Boolean b = scan(t, d); |
| |
| returnInCatchBlock &= b == Boolean.TRUE; |
| } |
| |
| Boolean returnInFinallyBlock = scan(tree.getFinallyBlock(), d); |
| |
| doPopup(); |
| |
| if (returnInTryBlock == Boolean.TRUE && returnInCatchBlock) |
| return Boolean.TRUE; |
| |
| return returnInFinallyBlock; |
| } |
| |
| @Override |
| public Boolean visitReturn(ReturnTree tree, Stack<Tree> d) { |
| if (exceptions == null) { |
| if (doExitPoints) { |
| addHighlightFor(tree); |
| } |
| } |
| |
| super.visitReturn(tree, d); |
| return Boolean.TRUE; |
| } |
| |
| @Override |
| public Boolean visitCatch(CatchTree tree, Stack<Tree> d) { |
| TypeMirror type1 = info.getTrees().getTypeMirror(new TreePath(new TreePath(getCurrentPath(), tree.getParameter()), tree.getParameter().getType())); |
| Types t = info.getTypes(); |
| |
| if (type1 != null) { |
| Set<TypeMirror> toRemove = new HashSet<TypeMirror>(); |
| Map<TypeMirror, List<Tree>> exceptions2Highlights = exceptions2HighlightsStack.peek(); |
| |
| if (exceptions2Highlights != null) { |
| for (TypeMirror type2 : exceptions2Highlights.keySet()) { |
| if (t.isAssignable(type2, type1)) { |
| toRemove.add(type2); |
| } |
| } |
| |
| for (TypeMirror type : toRemove) { |
| exceptions2Highlights.remove(type); |
| } |
| } |
| |
| } |
| |
| scan(tree.getParameter(), d); |
| return scan(tree.getBlock(), d); |
| } |
| |
| @Override |
| public Boolean visitMethodInvocation(MethodInvocationTree tree, Stack<Tree> d) { |
| Element el = info.getTrees().getElement(new TreePath(getCurrentPath(), tree.getMethodSelect())); |
| |
| if (el == null) { |
| System.err.println("Warning: decl == null"); |
| System.err.println("tree=" + tree); |
| } |
| |
| if (el != null && el.getKind() == ElementKind.METHOD) { |
| for (TypeMirror m : ((ExecutableElement) el).getThrownTypes()) { |
| addToExceptionsMap(m, tree); |
| } |
| } |
| |
| super.visitMethodInvocation(tree, d); |
| return null; |
| } |
| |
| @Override |
| public Boolean visitThrow(ThrowTree tree, Stack<Tree> d) { |
| addToExceptionsMap(info.getTrees().getTypeMirror(new TreePath(getCurrentPath(), tree.getExpression())), tree); |
| |
| super.visitThrow(tree, d); |
| |
| return Boolean.TRUE; |
| } |
| |
| @Override |
| public Boolean visitNewClass(NewClassTree tree, Stack<Tree> d) { |
| Element el = info.getTrees().getElement(getCurrentPath()); |
| |
| if (el != null && el.getKind() == ElementKind.CONSTRUCTOR) { |
| for (TypeMirror m : ((ExecutableElement) el).getThrownTypes()) { |
| addToExceptionsMap(m, tree); |
| } |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public Boolean visitMethod(MethodTree node, Stack<Tree> p) { |
| scan(node.getModifiers(), p); |
| scan(node.getReturnType(), p); |
| scan(node.getTypeParameters(), p); |
| scan(node.getParameters(), p); |
| scan(node.getThrows(), p); |
| return scan(node.getBody(), p); |
| } |
| |
| @Override |
| public Boolean visitIf(IfTree node, Stack<Tree> p) { |
| scan(node.getCondition(), p); |
| Boolean thenResult = scan(node.getThenStatement(), p); |
| Boolean elseResult = scan(node.getElseStatement(), p); |
| |
| if (thenResult == Boolean.TRUE && elseResult == Boolean.TRUE) |
| return Boolean.TRUE; |
| |
| return null; |
| } |
| |
| @Override |
| public Boolean visitClass(ClassTree node, Stack<Tree> p) { |
| return null; |
| } |
| |
| @Override |
| public Boolean visitLambdaExpression(LambdaExpressionTree node, Stack<Tree> p) { |
| return null; |
| } |
| } |