| /* |
| * 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.csl.navigation; |
| |
| |
| import java.awt.Image; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.Set; |
| import javax.swing.Action; |
| import javax.swing.Icon; |
| import javax.swing.text.Document; |
| import org.netbeans.editor.BaseDocument; |
| import org.netbeans.modules.csl.api.ElementHandle; |
| import org.netbeans.modules.csl.api.ElementKind; |
| import org.netbeans.modules.csl.api.Modifier; |
| import org.netbeans.modules.csl.api.StructureItem; |
| import org.netbeans.modules.csl.core.GsfHtmlFormatter; |
| import org.netbeans.modules.csl.core.Language; |
| import org.netbeans.modules.csl.core.LanguageRegistry; |
| import org.netbeans.modules.csl.navigation.actions.OpenAction; |
| import org.netbeans.modules.csl.spi.ParserResult; |
| import org.openide.filesystems.FileObject; |
| import org.openide.nodes.AbstractNode; |
| import org.openide.nodes.Children; |
| import org.openide.nodes.Node; |
| import org.openide.util.ImageUtilities; |
| import org.openide.util.NbBundle; |
| import org.openide.util.lookup.Lookups; |
| |
| /** |
| * This file is originally from Retouche, the Java Support |
| * infrastructure in NetBeans. I have modified the file as little |
| * as possible to make merging Retouche fixes back as simple as |
| * possible. |
| * <p> |
| * GSF changes made: Instead of accessing fields on Description object, |
| * replace references to Description with StructureItem interface (descriptions |
| * supplied by language plugins), make method calls on this interface rather |
| * than accessing fields directly. Some data such as the "ui" field was moved |
| * into ElementNode itself rather than sitting on the description object which |
| * is no longer under our control. |
| * <p> |
| * Node representing an element |
| * |
| * |
| * @author Petr Hrebejk |
| */ |
| public class ElementNode extends AbstractNode { |
| |
| |
| private static Node WAIT_NODE; |
| |
| private OpenAction openAction; |
| private StructureItem description; |
| private ClassMemberPanelUI ui; |
| private final FileObject fileObject; // For the root description |
| |
| /** Creates a new instance of TreeNode */ |
| public ElementNode( StructureItem description, ClassMemberPanelUI ui, FileObject fileObject) { |
| super(description.isLeaf() ? Children.LEAF: new ElementChildren(description, ui, fileObject), Lookups.fixed(fileObject)); |
| this.description = description; |
| setDisplayName( description.getName() ); |
| this.ui = ui; |
| this.fileObject = fileObject; |
| } |
| |
| @Override |
| public Image getIcon(int type) { |
| if (description.getCustomIcon() != null) { |
| return ImageUtilities.icon2Image(description.getCustomIcon()); |
| } |
| Icon icon = Icons.getElementIcon(description.getKind(), description.getModifiers()); |
| if (icon != null) { |
| return ImageUtilities.icon2Image(icon); |
| } else { |
| return super.getIcon(type); |
| } |
| } |
| |
| @Override |
| public Image getOpenedIcon(int type) { |
| return getIcon(type); |
| } |
| |
| @Override |
| public java.lang.String getDisplayName() { |
| if (description.getName() == null) { |
| return fileObject.getNameExt(); |
| } else { |
| return description.getName(); |
| } |
| } |
| |
| @Override |
| public String getHtmlDisplayName() { |
| return description.getHtml(new NavigatorFormatter()); |
| } |
| |
| @Override |
| public Action[] getActions( boolean context ) { |
| |
| if ( context || description.getName() == null ) { |
| return ui.getActions(); |
| } |
| else { |
| Action panelActions[] = ui.getActions(); |
| |
| Action actions[] = new Action[ 2 + panelActions.length ]; |
| actions[0] = getOpenAction(); |
| actions[1] = null; |
| for( int i = 0; i < panelActions.length; i++ ){ |
| actions[2 + i] = panelActions[i]; |
| } |
| return actions; |
| } |
| } |
| |
| @Override |
| public Action getPreferredAction() { |
| return getOpenAction(); |
| } |
| |
| |
| private synchronized Action getOpenAction() { |
| if ( openAction == null ) { |
| FileObject fo = ui.getFileObject(); |
| try { |
| openAction = new OpenAction(description.getElementHandle(), fo, |
| description.getPosition()); |
| } catch (UnsupportedOperationException uo) { |
| return null; // root node does not have element handle |
| } |
| } |
| return openAction; |
| } |
| |
| static synchronized Node getWaitNode() { |
| if ( WAIT_NODE == null ) { |
| WAIT_NODE = new WaitNode(); |
| } |
| return WAIT_NODE; |
| } |
| |
| /** |
| * Refreshes the Node recursively. Only initiates the refresh; the refresh |
| * itself may happen asynchronously. |
| */ |
| public void refreshRecursively() { |
| List<Node> toExpand = new ArrayList<Node>(); |
| refreshRecursively(Collections.singleton(this), toExpand); |
| ui.performExpansion(toExpand, Collections.<Node>emptyList()); |
| } |
| |
| private void refreshRecursively(Collection<ElementNode> toDo, final Collection<Node> toExpand) { |
| for (ElementNode elnod : toDo) { |
| final Children ch = elnod.getChildren(); |
| if ( ch instanceof ElementChildren ) { |
| ((ElementChildren)ch).resetKeys((List<StructureItem>)elnod.description.getNestedItems(), elnod.ui.getFilters()); |
| |
| Collection<ElementNode> children = (Collection<ElementNode>)(List)Arrays.asList((Node[])ch.getNodes()); |
| toExpand.addAll(children); |
| refreshRecursively(children, toExpand); |
| } |
| } |
| } |
| |
| public ElementNode getMimeRootNodeForOffset(ParserResult info, int offset) { |
| if (getDescription().getPosition() > offset) { |
| return null; |
| } |
| |
| // Look up the current mime type |
| Document document = info.getSnapshot().getSource().getDocument(false); |
| if (document == null) { |
| return null; |
| } |
| BaseDocument doc = (BaseDocument)document; |
| return getMimeRootNodeForOffset(doc, offset); |
| } |
| |
| ElementNode getMimeRootNodeForOffset(BaseDocument doc, int offset) { |
| List<Language> languages = LanguageRegistry.getInstance().getEmbeddedLanguages(doc, offset); |
| |
| // Look specifically within the |
| if (languages.size() > 0) { |
| Children ch = getChildren(); |
| if ( ch instanceof ElementChildren ) { |
| Node[] children = ch.getNodes(); |
| for (Language language : languages) { |
| // Inefficient linear search because the children may not be |
| // ordered according to the source |
| for (int i = 0; i < children.length; i++) { |
| ElementNode c = (ElementNode) children[i]; |
| if (c.getDescription() instanceof ElementScanningTask.MimetypeRootNode) { |
| ElementScanningTask.MimetypeRootNode mr = (ElementScanningTask.MimetypeRootNode)c.getDescription(); |
| if (mr.language == language) { |
| return c.getNodeForOffset(offset); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // No match in embedded languages - do normal offset search |
| return getNodeForOffset(offset); |
| } |
| |
| public ElementNode getNodeForOffset(int offset) { |
| if (getDescription().getPosition() > offset) { |
| return null; |
| } |
| |
| // Inefficient linear search because the children may not be |
| // ordered according to the source |
| Children ch = getChildren(); |
| if ( ch instanceof ElementChildren ) { |
| Node[] children = ch.getNodes(); |
| for (int i = 0; i < children.length; i++) { |
| ElementNode c = (ElementNode) children[i]; |
| // The promise of the API is broken at several places in the |
| // codebase and thus this needs to be guarded. The assert is in |
| // place to find the violating places. |
| assert c.getDescription().getElementHandle() != null; |
| @SuppressWarnings("null") |
| FileObject childFileObject = c.getDescription().getElementHandle() != null |
| ? c.getDescription().getElementHandle().getFileObject() |
| : null; |
| if (! Objects.equals(this.getFileObject(), childFileObject)) { |
| // e.g. inherited items may be in another file |
| // in such a case, incorrect item is highlighted on the navigator window if the FileObjects are not checked |
| continue; |
| } |
| long start = c.getDescription().getPosition(); |
| if (start <= offset) { |
| long end = c.getDescription().getEndPosition(); |
| if (end >= offset) { |
| return c.getNodeForOffset(offset); |
| } |
| } |
| } |
| } |
| |
| return this; |
| } |
| |
| public void updateRecursively( StructureItem newDescription ) { |
| List<Node> nodesToExpand = new LinkedList<Node>(); |
| List<Node> nodesToExpandRec = new LinkedList<Node>(); |
| updateRecursively(newDescription, nodesToExpand, nodesToExpandRec); |
| ui.performExpansion(nodesToExpand, nodesToExpandRec); |
| } |
| |
| private void updateRecursively( StructureItem newDescription, List<Node> nodesToExpand, List<Node> nodesToExpandRec ) { |
| Children ch = getChildren(); |
| |
| //If a node that was a LEAF now has children the child type has to be changed from Children.LEAF |
| //to ElementChildren to be able to hold the new child data |
| if(!(ch instanceof ElementChildren) && newDescription.getNestedItems() != null && |
| newDescription.getNestedItems().size()>0) { |
| ch=new ElementChildren(ui, fileObject); |
| setChildren(ch); |
| } |
| |
| if ( ch instanceof ElementChildren ) { |
| HashSet<StructureItem> oldSubs = new HashSet<StructureItem>( description.getNestedItems() ); |
| |
| |
| // Create a hashtable which maps StructureItem to node. |
| // We will then identify the nodes by the description. The trick is |
| // that the new and old description are equal and have the same hashcode |
| Node[] nodes = ch.getNodes( true ); |
| HashMap<StructureItem,ElementNode> oldD2node = new HashMap<StructureItem,ElementNode>(); |
| for (Node node : nodes) { |
| oldD2node.put(((ElementNode)node).description, (ElementNode)node); |
| } |
| |
| // Now refresh keys |
| ((ElementChildren)ch).resetKeys((List<StructureItem>)newDescription.getNestedItems(), ui.getFilters()); |
| |
| |
| // Reread nodes |
| nodes = ch.getNodes( true ); |
| |
| boolean alreadyExpanded = false; |
| |
| for( StructureItem newSub : newDescription.getNestedItems() ) { |
| ElementNode node = oldD2node.get(newSub); |
| if ( node != null ) { // filtered out |
| if ( !oldSubs.contains(newSub)) { |
| nodesToExpand.add(node); |
| } |
| node.updateRecursively( newSub, nodesToExpand, nodesToExpandRec ); // update the node recursively |
| } else { // a new node |
| if (! alreadyExpanded) { |
| alreadyExpanded = true; |
| if (ui.isExpandedByDefault(this)) { |
| nodesToExpand.add(this); |
| } |
| } |
| for (Node newNode : nodes) { |
| if (newNode instanceof ElementNode && ((ElementNode) newNode).getDescription() == newSub) { |
| nodesToExpandRec.add(newNode); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| StructureItem oldDescription = description; // Remember old description |
| description = newDescription; // set new descrioption to the new node |
| String oldHtml = oldDescription.getHtml(new NavigatorFormatter()); |
| String descHtml = description.getHtml(new NavigatorFormatter()); |
| if ( oldHtml != null && !oldHtml.equals(descHtml)) { |
| // Different headers => we need to fire displayname change |
| fireDisplayNameChange(oldHtml, descHtml); |
| } |
| if( oldDescription.getModifiers() != null && !oldDescription.getModifiers().equals(newDescription.getModifiers())) { |
| fireIconChange(); |
| fireOpenedIconChange(); |
| } |
| } |
| |
| public StructureItem getDescription() { |
| return description; |
| } |
| |
| public FileObject getFileObject() { |
| return fileObject; |
| } |
| |
| private static final class ElementChildren extends Children.Keys<StructureItem> { |
| private ClassMemberPanelUI ui; |
| private FileObject fileObject; |
| private StructureItem parent; |
| |
| @Override |
| protected void addNotify() { |
| super.addNotify(); |
| if (parent != null) { |
| resetKeys((List<StructureItem>)parent.getNestedItems(), ui.getFilters()); |
| } |
| } |
| |
| public ElementChildren(ClassMemberPanelUI ui, FileObject fileObject) { |
| this.ui = ui; |
| this.fileObject = fileObject; |
| } |
| |
| public ElementChildren(StructureItem parent, ClassMemberPanelUI ui, FileObject fileObject) { |
| this.parent = parent; |
| this.ui = ui; |
| this.fileObject = fileObject; |
| } |
| |
| protected Node[] createNodes(StructureItem key) { |
| return new Node[] {new ElementNode(key, ui, fileObject)}; |
| } |
| |
| void resetKeys( List<StructureItem> descriptions, ClassMemberFilters filters ) { |
| setKeys( filters.filter(descriptions) ); |
| } |
| } |
| |
| /** Stores all interesting data about given element. |
| */ |
| static class Description { |
| |
| public static final Comparator<StructureItem> ALPHA_COMPARATOR = |
| new DescriptionComparator(true); |
| public static final Comparator<StructureItem> POSITION_COMPARATOR = |
| new DescriptionComparator(false); |
| |
| ClassMemberPanelUI ui; |
| |
| //FileObject fileObject; // For the root description |
| |
| String name; |
| ElementHandle elementHandle; |
| ElementKind kind; |
| Set<Modifier> modifiers; |
| List<Description> subs; |
| String htmlHeader; |
| long pos; |
| |
| Description( ClassMemberPanelUI ui ) { |
| this.ui = ui; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| |
| if ( o == null ) { |
| //System.out.println("- f nul"); |
| return false; |
| } |
| |
| if ( !(o instanceof Description)) { |
| // System.out.println("- not a desc"); |
| return false; |
| } |
| |
| Description d = (Description)o; |
| |
| if ( kind != d.kind ) { |
| // System.out.println("- kind"); |
| return false; |
| } |
| |
| // Findbugs warns about this field being uninitialized on the following line! |
| if ( !name.equals(d.name) ) { |
| // System.out.println("- name"); |
| return false; |
| } |
| |
| // if ( !this.elementHandle.signatureEquals(d.elementHandle) ) { |
| // return false; |
| // } |
| |
| /* |
| if ( !modifiers.equals(d.modifiers)) { |
| // E.println("- modifiers"); |
| return false; |
| } |
| */ |
| |
| // System.out.println("Equals called"); |
| return true; |
| } |
| |
| |
| @Override |
| public int hashCode() { |
| int hash = 7; |
| |
| hash = 29 * hash + (this.name != null ? this.name.hashCode() : 0); |
| hash = 29 * hash + (this.kind != null ? this.kind.hashCode() : 0); |
| // hash = 29 * hash + (this.modifiers != null ? this.modifiers.hashCode() : 0); |
| return hash; |
| } |
| |
| private static class DescriptionComparator implements Comparator<StructureItem> { |
| |
| boolean alpha; |
| |
| DescriptionComparator( boolean alpha ) { |
| this.alpha = alpha; |
| } |
| |
| public int compare(StructureItem d1, StructureItem d2) { |
| if ( alpha ) { |
| if ( k2i(d1.getKind()) != k2i(d2.getKind()) ) { |
| return k2i(d1.getKind()) - k2i(d2.getKind()); |
| } |
| |
| return d1.getSortText().compareTo(d2.getSortText()); |
| } |
| else { |
| return d1.getPosition() == d2.getPosition() ? 0 : d1.getPosition() < d2.getPosition() ? -1 : 1; |
| } |
| } |
| |
| int k2i( ElementKind kind ) { |
| switch( kind ) { |
| case CONSTRUCTOR: |
| return 1; |
| case METHOD: |
| case DB: |
| return 2; |
| case FIELD: |
| return 3; |
| case CLASS: |
| case INTERFACE: |
| // case ENUM: |
| // case ANNOTATION_TYPE: |
| // return 4; |
| |
| // TODO - what about other types? |
| default: |
| return 100; |
| } |
| } |
| } |
| |
| } |
| |
| private static class WaitNode extends AbstractNode { |
| |
| private Image waitIcon = ImageUtilities.loadImage("org/netbeans/modules/csl/navigation/resources/wait.gif"); // NOI18N |
| private String displayName; |
| |
| WaitNode( ) { |
| super( Children.LEAF ); |
| displayName = NbBundle.getMessage(ElementNode.class, "LBL_WaitNode"); |
| } |
| |
| @Override |
| public Image getIcon(int type) { |
| return waitIcon; |
| } |
| |
| @Override |
| public Image getOpenedIcon(int type) { |
| return getIcon(type); |
| } |
| |
| @java.lang.Override |
| public java.lang.String getDisplayName() { |
| return displayName; |
| } |
| } |
| |
| private static class NavigatorFormatter extends GsfHtmlFormatter { |
| @Override |
| public void name(ElementKind kind, boolean start) { |
| // No special formatting for names |
| } |
| } |
| } |