| /* |
| * 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.navigation; |
| |
| import com.sun.source.tree.BlockTree; |
| import com.sun.source.tree.CaseTree; |
| import com.sun.source.tree.CatchTree; |
| import com.sun.source.tree.ClassTree; |
| import com.sun.source.tree.DoWhileLoopTree; |
| import com.sun.source.tree.EnhancedForLoopTree; |
| import com.sun.source.tree.ExportsTree; |
| import com.sun.source.tree.ExpressionStatementTree; |
| import com.sun.source.tree.ExpressionTree; |
| import com.sun.source.tree.ForLoopTree; |
| import com.sun.source.tree.IdentifierTree; |
| import com.sun.source.tree.IfTree; |
| import com.sun.source.tree.MemberSelectTree; |
| import com.sun.source.tree.MethodTree; |
| import com.sun.source.tree.ModuleTree; |
| import com.sun.source.tree.NewClassTree; |
| import com.sun.source.tree.OpensTree; |
| import com.sun.source.tree.ParameterizedTypeTree; |
| import com.sun.source.tree.ProvidesTree; |
| import com.sun.source.tree.RequiresTree; |
| import com.sun.source.tree.StatementTree; |
| import com.sun.source.tree.SwitchTree; |
| import com.sun.source.tree.SynchronizedTree; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.tree.Tree.Kind; |
| import com.sun.source.tree.TryTree; |
| import com.sun.source.tree.UsesTree; |
| import com.sun.source.tree.VariableTree; |
| import com.sun.source.tree.WhileLoopTree; |
| import com.sun.source.util.SourcePositions; |
| import com.sun.source.util.TreePath; |
| import org.netbeans.api.java.source.support.ErrorAwareTreeScanner; |
| import com.sun.source.util.Trees; |
| import java.awt.Image; |
| import java.io.CharConversionException; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.atomic.AtomicReference; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ElementKind; |
| import javax.lang.model.element.Modifier; |
| import javax.lang.model.element.ModuleElement; |
| import javax.swing.Icon; |
| import org.netbeans.api.actions.Openable; |
| import org.netbeans.api.annotations.common.CheckForNull; |
| import org.netbeans.api.annotations.common.NonNull; |
| import org.netbeans.api.annotations.common.NullAllowed; |
| import org.netbeans.api.java.lexer.JavaTokenId; |
| import org.netbeans.api.java.source.CompilationController; |
| import org.netbeans.api.java.source.CompilationInfo; |
| import org.netbeans.api.java.source.JavaSource; |
| import org.netbeans.api.java.source.JavaSource.Phase; |
| import org.netbeans.api.java.source.Task; |
| import org.netbeans.api.java.source.TreePathHandle; |
| import org.netbeans.api.java.source.TreeUtilities; |
| import org.netbeans.api.java.source.UiUtils; |
| import org.netbeans.api.java.source.ui.ElementIcons; |
| import org.netbeans.api.lexer.TokenSequence; |
| import org.netbeans.modules.editor.breadcrumbs.spi.BreadcrumbsController; |
| import org.netbeans.modules.editor.breadcrumbs.spi.BreadcrumbsElement; |
| import org.openide.cookies.OpenCookie; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.util.Exceptions; |
| import org.openide.util.ImageUtilities; |
| import org.openide.util.Lookup; |
| import org.openide.util.NbCollections; |
| import org.openide.util.lookup.Lookups; |
| import org.openide.xml.XMLUtil; |
| |
| /** |
| * |
| * @author Dusan Balek |
| */ |
| public class BreadCrumbsNodeImpl implements BreadcrumbsElement { |
| |
| private static final String COLOR = "#707070"; |
| private final Lookup lookup; |
| private final BreadCrumbsNodeImpl parent; |
| private final TreePathHandle tph; |
| private final Callable<? extends Image> iconProvider; |
| private final String htmlDisplayName; |
| // private OpenAction openAction; |
| |
| public BreadCrumbsNodeImpl(final BreadCrumbsNodeImpl parent, final TreePathHandle tph, |
| final Image icon, final String htmlDisplayName, final FileObject fileObject, final int[] pos) { |
| this( |
| parent, |
| tph, |
| new Callable<Image> () { |
| @Override |
| @CheckForNull |
| public Image call() throws Exception { |
| return icon; |
| } |
| }, |
| htmlDisplayName, |
| fileObject, |
| pos); |
| } |
| |
| private BreadCrumbsNodeImpl( |
| @NullAllowed final BreadCrumbsNodeImpl parent, |
| @NonNull final TreePathHandle tph, |
| @NonNull final Callable<? extends Image> iconProvider, |
| @NullAllowed final String htmlDisplayName, |
| @NonNull final FileObject fileObject, |
| @NonNull final int[] pos) { |
| this.lookup = Lookups.fixed(tph, pos, new OpenableImpl(fileObject, pos[0])); |
| this.parent = parent; |
| this.tph = tph; |
| this.iconProvider = iconProvider; |
| this.htmlDisplayName = htmlDisplayName; |
| } |
| |
| @Override |
| public String getHtmlDisplayName() { |
| return htmlDisplayName; |
| } |
| |
| @Override |
| public Image getIcon(int type) { |
| try { |
| return iconProvider.call(); |
| } catch (Exception ex) { |
| Exceptions.printStackTrace(ex); |
| return null; |
| } |
| } |
| |
| @Override |
| public Image getOpenedIcon(int type) { |
| return getIcon(type); |
| } |
| |
| private static final String CONSTRUCTOR_NAME = "<init>"; |
| private static final String ERR_NAME = "<error>"; |
| |
| public static BreadCrumbsNodeImpl createBreadcrumbs(BreadCrumbsNodeImpl parent, final CompilationInfo info, TreePath path, boolean elseSection) { |
| final Trees trees = info.getTrees(); |
| final SourcePositions sp = trees.getSourcePositions(); |
| int[] pos = new int[] {(int) sp.getStartPosition(path.getCompilationUnit(), path.getLeaf()), (int) sp.getEndPosition(path.getCompilationUnit(), path.getLeaf())}; |
| final Tree leaf = path.getLeaf(); |
| switch (leaf.getKind()) { |
| case COMPILATION_UNIT: |
| TreePathHandle tph = TreePathHandle.create(path, info); |
| return new BreadCrumbsNodeImpl(parent, tph, (Image) null, FileUtil.getFileDisplayName(info.getFileObject()), info.getFileObject(), pos); |
| case MODULE: |
| tph = TreePathHandle.create(path, info); |
| return new BreadCrumbsNodeImpl(parent, tph, iconProviderFor(info, path), ((ModuleTree)leaf).getName().toString(), info.getFileObject(), pos); |
| case REQUIRES: |
| tph = TreePathHandle.create(path, info); |
| StringBuilder sb = new StringBuilder("requires "); //NOI18N |
| sb.append("<font color=").append(COLOR).append(">"); // NOI18N |
| sb.append(escape(((RequiresTree) leaf).getModuleName().toString())); |
| sb.append("</font>"); //NOI18N |
| return new BreadCrumbsNodeImpl(parent, tph, iconProviderFor(info, path), sb.toString(), info.getFileObject(), pos); |
| case EXPORTS: |
| tph = TreePathHandle.create(path, info); |
| sb = new StringBuilder("exports "); //NOI18N |
| sb.append("<font color=").append(COLOR).append(">"); // NOI18N |
| sb.append(escape(((ExportsTree) leaf).getPackageName().toString())); |
| sb.append("</font>"); //NOI18N |
| return new BreadCrumbsNodeImpl(parent, tph, iconProviderFor(info, path), sb.toString(), info.getFileObject(), pos); |
| case OPENS: |
| tph = TreePathHandle.create(path, info); |
| sb = new StringBuilder("opens "); //NOI18N |
| sb.append("<font color=").append(COLOR).append(">"); // NOI18N |
| sb.append(escape(((OpensTree) leaf).getPackageName().toString())); |
| sb.append("</font>"); //NOI18N |
| return new BreadCrumbsNodeImpl(parent, tph, iconProviderFor(info, path), sb.toString(), info.getFileObject(), pos); |
| case PROVIDES: |
| tph = TreePathHandle.create(path, info); |
| sb = new StringBuilder("provides "); //NOI18N |
| sb.append("<font color=").append(COLOR).append(">"); // NOI18N |
| sb.append(simpleName(((ProvidesTree) leaf).getServiceName())); |
| sb.append("</font>"); //NOI18N |
| return new BreadCrumbsNodeImpl(parent, tph, iconProviderFor(info, path), sb.toString(), info.getFileObject(), pos); |
| case USES: |
| tph = TreePathHandle.create(path, info); |
| sb = new StringBuilder("uses "); //NOI18N |
| sb.append("<font color=").append(COLOR).append(">"); // NOI18N |
| sb.append(simpleName(((UsesTree) leaf).getServiceName())); |
| sb.append("</font>"); //NOI18N |
| return new BreadCrumbsNodeImpl(parent, tph, iconProviderFor(info, path), sb.toString(), info.getFileObject(), pos); |
| case CLASS: |
| case INTERFACE: |
| case ENUM: |
| case ANNOTATION_TYPE: |
| tph = TreePathHandle.create(path, info); |
| return new BreadCrumbsNodeImpl(parent, tph, iconProviderFor(info, path), className(path), info.getFileObject(), pos); |
| case METHOD: |
| tph = TreePathHandle.create(path, info); |
| MethodTree mt = (MethodTree) leaf; |
| CharSequence name; |
| if (mt.getName().contentEquals(CONSTRUCTOR_NAME)) { |
| name = ((ClassTree) path.getParentPath().getLeaf()).getSimpleName(); |
| } else { |
| name = mt.getName(); |
| } |
| return new BreadCrumbsNodeImpl(parent, tph, iconProviderFor(info, path), name.toString(), info.getFileObject(), pos); |
| case VARIABLE: |
| tph = TreePathHandle.create(path, info); |
| return new BreadCrumbsNodeImpl(parent, tph, iconProviderFor(info, path), ((VariableTree) leaf).getName().toString(), info.getFileObject(), pos); |
| case CASE: |
| tph = TreePathHandle.create(path, info); |
| ExpressionTree expr = ((CaseTree) leaf).getExpression(); |
| sb = new StringBuilder(expr == null ? "default:" : "case "); //NOI18N |
| if (expr != null) { |
| sb.append("<font color=").append(COLOR).append(">"); // NOI18N |
| sb.append(escape(((CaseTree) leaf).getExpression().toString())); |
| sb.append(":"); //NOI18N |
| sb.append("</font>"); //NOI18N |
| } |
| return new BreadCrumbsNodeImpl(parent, tph, DEFAULT_ICON, sb.toString(), info.getFileObject(), pos); |
| case CATCH: |
| tph = TreePathHandle.create(path, info); |
| sb = new StringBuilder("catch "); //NOI18N |
| sb.append("<font color=").append(COLOR).append(">"); // NOI18N |
| sb.append(escape(((CatchTree) leaf).getParameter().toString())); |
| sb.append("</font>"); //NOI18N |
| return new BreadCrumbsNodeImpl(parent, tph, DEFAULT_ICON, sb.toString(), info.getFileObject(), pos); |
| case DO_WHILE_LOOP: |
| tph = TreePathHandle.create(path, info); |
| sb = new StringBuilder("do ... while "); //NOI18N |
| sb.append("<font color=").append(COLOR).append(">"); // NOI18N |
| sb.append(escape(((DoWhileLoopTree) leaf).getCondition().toString())); |
| sb.append("</font>"); //NOI18N |
| return new BreadCrumbsNodeImpl(parent, tph, DEFAULT_ICON, sb.toString(), info.getFileObject(), pos); |
| case ENHANCED_FOR_LOOP: |
| tph = TreePathHandle.create(path, info); |
| sb = new StringBuilder("for "); //NOI18N |
| sb.append("<font color=").append(COLOR).append(">"); // NOI18N |
| sb.append("("); //NOI18N |
| sb.append(escape(((EnhancedForLoopTree) leaf).getVariable().toString())); |
| sb.append(" : "); //NOI18N |
| sb.append(escape(((EnhancedForLoopTree) leaf).getExpression().toString())); |
| sb.append(")"); //NOI18N |
| sb.append("</font>"); //NOI18N |
| return new BreadCrumbsNodeImpl(parent, tph, DEFAULT_ICON, sb.toString(), info.getFileObject(), pos); |
| case FOR_LOOP: |
| tph = TreePathHandle.create(path, info); |
| sb = new StringBuilder("for "); //NOI18N |
| sb.append("<font color=").append(COLOR).append(">"); // NOI18N |
| sb.append("("); //NOI18N |
| List<? extends StatementTree> initializer = ((ForLoopTree) leaf).getInitializer(); |
| if (!initializer.isEmpty() && initializer.get(0).getKind() == Kind.VARIABLE) { |
| sb.append(escape(initializer.get(0).toString())); |
| for (VariableTree currentVar : NbCollections.checkedListByCopy(initializer.subList(1, initializer.size()), VariableTree.class, true)) { |
| sb.append(", "); |
| sb.append(escape(currentVar.getName().toString())); |
| if (currentVar.getInitializer() != null) { |
| sb.append(" = "); |
| sb.append(escape(currentVar.getInitializer().toString())); |
| } |
| } |
| } else { |
| boolean first = true; |
| for (StatementTree init : initializer) { |
| if (!first) sb.append(", "); |
| if (init.getKind() == Kind.EXPRESSION_STATEMENT) { |
| sb.append(((ExpressionStatementTree) init).getExpression().toString()); |
| } else { |
| sb.append(init.toString()); |
| } |
| first = false; |
| } |
| } |
| sb.append("; "); //NOI18N |
| if (((ForLoopTree) leaf).getCondition() != null) { |
| sb.append(escape(((ForLoopTree) leaf).getCondition().toString())); |
| } |
| sb.append("; "); //NOI18N |
| boolean first = true; |
| for (ExpressionStatementTree update : ((ForLoopTree) leaf).getUpdate()) { |
| if (!first) sb.append(", "); |
| sb.append(update.getExpression().toString()); |
| first = false; |
| } |
| sb.append(")"); //NOI18N |
| sb.append("</font>"); //NOI18N |
| return new BreadCrumbsNodeImpl(parent, tph, DEFAULT_ICON, sb.toString(), info.getFileObject(), pos); |
| case IF: |
| tph = TreePathHandle.create(path, info); |
| sb = new StringBuilder(""); //NOI18N |
| Tree last = leaf; |
| while (path != null && path.getLeaf().getKind() == Kind.IF) { |
| StringBuilder temp = new StringBuilder(""); //NOI18N |
| temp.append("if "); //NOI18N |
| temp.append("<font color=").append(COLOR).append(">"); // NOI18N |
| temp.append(escape(((IfTree) path.getLeaf()).getCondition().toString())); |
| temp.append("</font>"); //NOI18N |
| |
| if (((IfTree) path.getLeaf()).getElseStatement() == last || (path.getLeaf() == leaf && elseSection)) { |
| temp.append(" else"); |
| } |
| temp.append(" "); |
| sb.insert(0, temp.toString()); |
| last = path.getLeaf(); |
| path = path.getParentPath(); |
| } |
| sb.delete(sb.length() - 1, sb.length()); |
| IfTree it = (IfTree) leaf; |
| int elseStart = pos[1] + 1; |
| if (it.getElseStatement() != null) { |
| TokenSequence<JavaTokenId> ts = info.getTokenHierarchy().tokenSequence(JavaTokenId.language()); |
| ts.move(elseStart = (int) sp.getStartPosition(path.getCompilationUnit(), it.getElseStatement())); |
| boolean success; |
| while ((success = ts.movePrevious()) && ts.token().id() != JavaTokenId.ELSE) |
| ; |
| elseStart = success ? Math.min(ts.offset(), elseStart) : elseStart; |
| } |
| if (elseSection) { |
| int endPos = (int) sp.getEndPosition(path.getCompilationUnit(), it.getElseStatement()); |
| if (it.getElseStatement().getKind() == Kind.IF) { |
| endPos = (int) sp.getStartPosition(path.getCompilationUnit(), it.getElseStatement()) - 1; |
| } |
| pos = new int[] {elseStart, endPos}; |
| } else { |
| pos[1] = elseStart - 1; |
| } |
| return new BreadCrumbsNodeImpl(parent, tph, DEFAULT_ICON, sb.toString(), info.getFileObject(), pos); |
| case SWITCH: |
| tph = TreePathHandle.create(path, info); |
| sb = new StringBuilder("switch "); //NOI18N |
| sb.append("<font color=").append(COLOR).append(">"); // NOI18N |
| sb.append(escape(((SwitchTree) leaf).getExpression().toString())); |
| sb.append("</font>"); //NOI18N |
| return new BreadCrumbsNodeImpl(parent, tph, DEFAULT_ICON, sb.toString(), info.getFileObject(), pos); |
| case SYNCHRONIZED: |
| tph = TreePathHandle.create(path, info); |
| sb = new StringBuilder("synchronized "); //NOI18N |
| sb.append("<font color=").append(COLOR).append(">"); // NOI18N |
| sb.append(escape(((SynchronizedTree) leaf).getExpression().toString())); |
| sb.append("</font>"); //NOI18N |
| return new BreadCrumbsNodeImpl(parent, tph, DEFAULT_ICON, sb.toString(), info.getFileObject(), pos); |
| case TRY: |
| tph = TreePathHandle.create(path, info); |
| sb = new StringBuilder("try"); //NOI18N |
| return new BreadCrumbsNodeImpl(parent, tph, DEFAULT_ICON, sb.toString(), info.getFileObject(), pos); |
| case WHILE_LOOP: |
| tph = TreePathHandle.create(path, info); |
| sb = new StringBuilder("while "); //NOI18N |
| sb.append("<font color=").append(COLOR).append(">"); // NOI18N |
| sb.append(escape(((WhileLoopTree) leaf).getCondition().toString())); |
| sb.append("</font>"); //NOI18N |
| return new BreadCrumbsNodeImpl(parent, tph, DEFAULT_ICON, sb.toString(), info.getFileObject(), pos); |
| case BLOCK: |
| tph = TreePathHandle.create(path, info); |
| if (TreeUtilities.CLASS_TREE_KINDS.contains(path.getParentPath().getLeaf().getKind())) { |
| sb = new StringBuilder(((BlockTree)leaf).isStatic() ? "<static init>" : "<init>"); //NOI18N |
| return new BreadCrumbsNodeImpl(parent, tph, DEFAULT_ICON, sb.toString(), info.getFileObject(), pos); |
| } else if (path.getParentPath().getLeaf().getKind() == Kind.TRY && ((TryTree) path.getParentPath().getLeaf()).getFinallyBlock() == leaf) { |
| sb = new StringBuilder("finally"); //NOI18N |
| return new BreadCrumbsNodeImpl(parent, tph, DEFAULT_ICON, sb.toString(), info.getFileObject(), pos); |
| } |
| break; |
| } |
| |
| return null; |
| } |
| |
| private static final Pattern UNICODE_SEQUENCE = Pattern.compile("\\\\u([0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z])"); |
| static String escape(String s) { |
| if (s != null) { |
| //unescape unicode sequences first (would be better if Pretty would not print them, but that might be more difficult): |
| Matcher matcher = UNICODE_SEQUENCE.matcher(s); |
| |
| if (matcher.find()) { |
| StringBuilder result = new StringBuilder(); |
| int lastReplaceEnd = 0; |
| do { |
| result.append(s.substring(lastReplaceEnd, matcher.start())); |
| int ch = Integer.parseInt(matcher.group(1), 16); |
| result.append((char) ch); |
| lastReplaceEnd = matcher.end(); |
| } while (matcher.find()); |
| result.append(s.substring(lastReplaceEnd)); |
| s = result.toString(); |
| } |
| try { |
| return XMLUtil.toAttributeValue(s); |
| } catch (CharConversionException ex) { |
| } |
| } |
| return null; |
| } |
| |
| private static final Image DEFAULT_ICON = BreadcrumbsController.NO_ICON; |
| |
| private static Callable<? extends Image> iconProviderFor(CompilationInfo info, TreePath path) { |
| Element el = info.getTrees().getElement(path); |
| final ElementKind kind = el == null ? null : el.getKind(); |
| final Set<Modifier> modifiers = el == null ? null : el.getModifiers(); |
| final ModuleElement.DirectiveKind directiveKind = directiveKind(path.getLeaf().getKind()); |
| return new Callable<Image>() { |
| @Override |
| public Image call() throws Exception { |
| Icon icon = null; |
| if (kind == null) { |
| if (directiveKind == null) |
| return DEFAULT_ICON; |
| icon = ElementIcons.getModuleDirectiveIcon(directiveKind); |
| } else { |
| assert modifiers != null; |
| icon = ElementIcons.getElementIcon(kind, modifiers); |
| } |
| if (icon == null) return DEFAULT_ICON; |
| return ImageUtilities.icon2Image(icon); |
| } |
| }; |
| } |
| |
| private static ModuleElement.DirectiveKind directiveKind(Kind treeKind) { |
| switch (treeKind) { |
| case EXPORTS: |
| return ModuleElement.DirectiveKind.EXPORTS; |
| case PROVIDES: |
| return ModuleElement.DirectiveKind.PROVIDES; |
| case REQUIRES: |
| return ModuleElement.DirectiveKind.REQUIRES; |
| case USES: |
| return ModuleElement.DirectiveKind.USES; |
| } |
| return null; |
| } |
| |
| private static String className(TreePath path) { |
| ClassTree ct = (ClassTree) path.getLeaf(); |
| |
| if (path.getParentPath().getLeaf().getKind() == Kind.NEW_CLASS) { |
| NewClassTree nct = (NewClassTree) path.getParentPath().getLeaf(); |
| |
| if (nct.getClassBody() == ct) { |
| return simpleName(nct.getIdentifier()); |
| } |
| } else if (path.getParentPath().getLeaf() == path.getCompilationUnit()) { |
| ExpressionTree pkg = path.getCompilationUnit().getPackageName(); |
| String pkgName = pkg != null ? pkg.toString() : null; |
| if (pkgName != null && !pkgName.contentEquals(ERR_NAME)) { |
| return pkgName + '.' + ct.getSimpleName().toString(); |
| } |
| } |
| |
| return ct.getSimpleName().toString(); |
| } |
| |
| private static String simpleName(Tree t) { |
| switch (t.getKind()) { |
| case PARAMETERIZED_TYPE: |
| return simpleName(((ParameterizedTypeTree) t).getType()); |
| case IDENTIFIER: |
| return ((IdentifierTree) t).getName().toString(); |
| case MEMBER_SELECT: |
| return ((MemberSelectTree) t).getIdentifier().toString(); |
| default: |
| return "";//XXX |
| } |
| } |
| |
| private final AtomicReference<List<BreadcrumbsElement>> children = new AtomicReference<>(); |
| |
| public List<BreadcrumbsElement> getChildren() { |
| List<BreadcrumbsElement> cached = children.get(); |
| |
| if (cached != null) return cached; |
| |
| final List<BreadcrumbsElement> result = new ArrayList<>(); |
| try { |
| final FileObject file = tph.getFileObject(); |
| if (file == null) { |
| return result; |
| } |
| JavaSource js = JavaSource.forFileObject(file); |
| |
| if (js == null) return result; |
| |
| js.runUserActionTask(new Task<CompilationController>() { |
| @Override public void run(final CompilationController cc) throws Exception { |
| cc.toPhase(Phase.RESOLVED); //XXX: resolved? |
| |
| final TreePath tp = tph.resolve(cc); |
| |
| if (tp == null) { |
| //XXX: log |
| return; |
| } |
| |
| tp.getLeaf().accept(new ErrorAwareTreeScanner<Void, TreePath>() { |
| @Override public Void scan(Tree node, TreePath p) { |
| if (node == null) return null; |
| if (node.getKind() == Kind.IF) { |
| IfTree it = (IfTree) node; |
| BreadCrumbsNodeImpl n = createBreadcrumbs(BreadCrumbsNodeImpl.this, cc, new TreePath(p, node), false); |
| assert n != null; |
| result.add(n); |
| if (it.getElseStatement() != null) { |
| n = createBreadcrumbs(BreadCrumbsNodeImpl.this, cc, new TreePath(p, node), true); |
| assert n != null; |
| result.add(n); |
| |
| if (it.getElseStatement().getKind() == Kind.IF) { |
| scan((IfTree) it.getElseStatement(), new TreePath(p, node)); |
| } |
| } |
| return null; |
| } |
| p = new TreePath(p, node); |
| if (cc.getTreeUtilities().isSynthetic(p)) return null; |
| BreadCrumbsNodeImpl n = createBreadcrumbs(BreadCrumbsNodeImpl.this, cc, p, false); |
| if (n != null) { |
| result.add(n); |
| } else { |
| return super.scan(node, p); |
| } |
| return null; |
| } |
| @Override public Void visitMethod(MethodTree node, TreePath p) { |
| return scan(node.getBody(), p); |
| } |
| }, tp); |
| } |
| }, true); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| |
| return children.compareAndSet(null, result) ? result : children.get(); |
| } |
| |
| public Lookup getLookup() { |
| return lookup; |
| } |
| |
| @Override |
| public BreadcrumbsElement getParent() { |
| return parent; |
| } |
| |
| private static final class OpenableImpl implements Openable, OpenCookie { |
| |
| private final FileObject file; |
| private final int pos; |
| |
| public OpenableImpl(FileObject file, int pos) { |
| this.file = file; |
| this.pos = pos; |
| } |
| |
| @Override |
| public void open() { |
| UiUtils.open(file, pos); |
| } |
| |
| } |
| } |