| /* |
| * Copyright 2002-2007 Sascha Weinreuter |
| * |
| * Licensed 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.intellij.plugins.xsltDebugger.ui; |
| |
| import com.intellij.ide.DataManager; |
| import com.intellij.openapi.actionSystem.CommonDataKeys; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.pom.Navigatable; |
| import com.intellij.reference.SoftReference; |
| import com.intellij.util.SmartList; |
| import com.intellij.util.containers.StringInterner; |
| import org.intellij.plugins.xsltDebugger.XsltDebuggerSession; |
| import org.intellij.plugins.xsltDebugger.rt.engine.OutputEventQueue; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.tree.DefaultMutableTreeNode; |
| import javax.swing.tree.DefaultTreeModel; |
| import javax.swing.tree.TreeNode; |
| import java.lang.ref.WeakReference; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| /** |
| * Created by IntelliJ IDEA. |
| * User: sweinreuter |
| * Date: 09.06.2007 |
| */ |
| public class GeneratedStructureModel extends DefaultTreeModel { |
| @NonNls |
| private static final String PENDING = "..."; |
| |
| private static WeakReference<StringInterner> ourSharedInterner; |
| |
| private final LinkedList<DefaultMutableTreeNode> myCurrentPath = new LinkedList<DefaultMutableTreeNode>(); |
| private final List<DefaultMutableTreeNode> myLastNodes = new LinkedList<DefaultMutableTreeNode>(); |
| |
| private final StringInterner myInterner = getInterner(); |
| |
| // we keep a shared string interner across all currently running xslt debugger instances. it should go away once |
| // all instances (and their toolwindow contents) are gone. This should minimize the memory usage of the generated |
| // structure tree. |
| private static StringInterner getInterner() { |
| StringInterner interner = SoftReference.dereference(ourSharedInterner); |
| if (interner == null) { |
| interner = new StringInterner(); |
| ourSharedInterner = new WeakReference<StringInterner>(interner); |
| } |
| return interner; |
| } |
| |
| private boolean myFilterWhitespace; |
| private boolean myListenersDisabled; |
| |
| public GeneratedStructureModel() { |
| super(new MyRootNode()); |
| final DefaultMutableTreeNode root = (DefaultMutableTreeNode)getRoot(); |
| myCurrentPath.add(root); |
| root.add(new DefaultMutableTreeNode(PENDING)); |
| } |
| |
| public void update(final List<OutputEventQueue.NodeEvent> eventQueue) { |
| if (!SwingUtilities.isEventDispatchThread()) { |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| public void run() { |
| updateImpl(eventQueue); |
| } |
| }); |
| return; |
| } |
| updateImpl(eventQueue); |
| } |
| |
| @Override |
| public Object getChild(Object parent, int index) { |
| if (!myFilterWhitespace) { |
| return super.getChild(parent, index); |
| } |
| return getFilteredChildren((DefaultMutableTreeNode)parent, false).get(index); |
| } |
| |
| @Override |
| public int getChildCount(Object parent) { |
| if (!myFilterWhitespace) { |
| return super.getChildCount(parent); |
| } |
| return getFilteredChildren((DefaultMutableTreeNode)parent, false).size(); |
| } |
| |
| @Override |
| public boolean isLeaf(Object node) { |
| if (!myFilterWhitespace) { |
| return super.isLeaf(node); |
| } |
| return super.isLeaf(node) || getFilteredChildren((DefaultMutableTreeNode)node, true).size() == 0; |
| } |
| |
| private static List getFilteredChildren(DefaultMutableTreeNode node, boolean checkOnly) { |
| if (node.getChildCount() == 0) { |
| return Collections.emptyList(); |
| } |
| final List<DefaultMutableTreeNode> nodes = checkOnly ? |
| new SmartList<DefaultMutableTreeNode>() : |
| new ArrayList<DefaultMutableTreeNode>(node.getChildCount()); |
| |
| DefaultMutableTreeNode child = (DefaultMutableTreeNode)node.getFirstChild(); |
| while (child != null) { |
| if (child instanceof StructureNode) { |
| final OutputEventQueue.NodeEvent event = (OutputEventQueue.NodeEvent)child.getUserObject(); |
| if (event != null && event.getType() == OutputEventQueue.CHARACTERS) { |
| if (event.getValue().trim().length() == 0) { |
| child = child.getNextSibling(); |
| continue; |
| } |
| } |
| } |
| |
| nodes.add(child); |
| if (checkOnly) return nodes; |
| |
| child = child.getNextSibling(); |
| } |
| return nodes; |
| } |
| |
| private void updateImpl(List<OutputEventQueue.NodeEvent> nodeEvents) { |
| if (nodeEvents.size() > 0) { |
| for (DefaultMutableTreeNode node : myLastNodes) { |
| if (node instanceof StructureNode) { |
| ((StructureNode)node).refresh(); |
| } |
| } |
| myLastNodes.clear(); |
| } |
| |
| for (OutputEventQueue.NodeEvent event : nodeEvents) { |
| event = intern(event); |
| |
| final DefaultMutableTreeNode node = myCurrentPath.getFirst(); |
| switch (event.getType()) { |
| case OutputEventQueue.START_DOCUMENT: |
| break; |
| case OutputEventQueue.START_ELEMENT: |
| final StructureNode child = new StructureNode(event); |
| myLastNodes.add(child); |
| |
| final int index = getChildCount(node) - 1; |
| node.insert(child, node.getChildCount() - 1); |
| child.add(new DefaultMutableTreeNode(PENDING)); |
| |
| myCurrentPath.addFirst(child); |
| |
| nodesWereInserted(node, new int[]{ index }); |
| break; |
| |
| case OutputEventQueue.END_ELEMENT: |
| if (node instanceof MyRootNode) { |
| // unknown xalan bug: start/end is sometimes unbalanced. should be fixed somewhere else... |
| continue; |
| } |
| case OutputEventQueue.END_DOCUMENT: |
| final DefaultMutableTreeNode c = myCurrentPath.removeFirst(); |
| final int childIndex = getChildCount(c) - 1; |
| final int realChildIndex = c.getChildCount() - 1; |
| if (realChildIndex >= 0) { |
| final DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)c.getChildAt(realChildIndex); |
| assert childNode.getUserObject() == PENDING; |
| c.remove(realChildIndex); |
| nodesWereRemoved(c, new int[]{ childIndex }, new Object[]{ childNode }); |
| } |
| break; |
| |
| case OutputEventQueue.TRACE_POINT: |
| case OutputEventQueue.ATTRIBUTE: |
| case OutputEventQueue.CHARACTERS: |
| case OutputEventQueue.COMMENT: |
| case OutputEventQueue.PI: |
| final StructureNode ch = new StructureNode(event); |
| myLastNodes.add(ch); |
| |
| final int i = getChildCount(node) - 1; |
| node.insert(ch, node.getChildCount() - 1); |
| nodesWereInserted(node, new int[]{ i }); |
| } |
| } |
| } |
| |
| private OutputEventQueue.NodeEvent intern(OutputEventQueue.NodeEvent event) { |
| event.myURI = intern(event.myURI); |
| event.myQName = intern(event.myQName); |
| event.myValue = intern(event.myValue); |
| return event; |
| } |
| |
| @Nullable |
| private OutputEventQueue.NodeEvent.QName intern(OutputEventQueue.NodeEvent.QName name) { |
| if (name == null) return null; |
| name.myPrefix = intern(name.myPrefix); |
| name.myLocalName = intern(name.myLocalName); |
| name.myURI = intern(name.myURI); |
| return name; |
| } |
| |
| @Nullable |
| private String intern(String s) { |
| if (s != null) { |
| if (s.length() == 0) return s.intern(); |
| return myInterner.intern(s); |
| } else { |
| return null; |
| } |
| } |
| |
| public boolean isFilterWhitespace() { |
| return myFilterWhitespace; |
| } |
| |
| public void setFilterWhitespace(boolean b) { |
| final boolean old = myFilterWhitespace; |
| myFilterWhitespace = b; |
| if (b != old) { |
| nodeStructureChanged((TreeNode)getRoot()); |
| } |
| } |
| |
| public void finalUpdate(final List<OutputEventQueue.NodeEvent> events) { |
| Runnable runnable = new Runnable() { |
| public void run() { |
| myListenersDisabled = true; |
| try { |
| updateImpl(events); |
| } finally { |
| myListenersDisabled = false; |
| nodeStructureChanged((TreeNode)getRoot()); |
| } |
| } |
| }; |
| ApplicationManager.getApplication().invokeLater(runnable); |
| } |
| |
| @Override |
| protected void fireTreeNodesChanged(Object source, Object[] path, int[] childIndices, Object[] children) { |
| if (myListenersDisabled) return; |
| super.fireTreeNodesChanged(source, path, childIndices, children); |
| } |
| |
| @Override |
| protected void fireTreeNodesInserted(Object source, Object[] path, int[] childIndices, Object[] children) { |
| if (myListenersDisabled) return; |
| super.fireTreeNodesInserted(source, path, childIndices, children); |
| } |
| |
| @Override |
| protected void fireTreeNodesRemoved(Object source, Object[] path, int[] childIndices, Object[] children) { |
| if (myListenersDisabled) return; |
| super.fireTreeNodesRemoved(source, path, childIndices, children); |
| } |
| |
| @Override |
| protected void fireTreeStructureChanged(Object source, Object[] path, int[] childIndices, Object[] children) { |
| if (myListenersDisabled) return; |
| super.fireTreeStructureChanged(source, path, childIndices, children); |
| } |
| |
| private static class MyRootNode extends DefaultMutableTreeNode { |
| public MyRootNode() { |
| super("ROOT"); |
| } |
| } |
| |
| public static class StructureNode extends DefaultMutableTreeNode implements Navigatable { |
| private boolean isNew = true; |
| |
| public StructureNode(OutputEventQueue.NodeEvent event) { |
| super(event); |
| } |
| |
| private void refresh() { |
| isNew = false; |
| } |
| |
| public boolean isNew() { |
| return isNew; |
| } |
| |
| @Override |
| public OutputEventQueue.NodeEvent getUserObject() { |
| return (OutputEventQueue.NodeEvent)super.getUserObject(); |
| } |
| |
| public void navigate(boolean requestFocus) { |
| final OutputEventQueue.NodeEvent event = getUserObject(); |
| final Project project = (Project)DataManager.getInstance().getDataContext().getData(CommonDataKeys.PROJECT.getName()); |
| XsltDebuggerSession.openLocation(project, event.getURI(), event.getLineNumber() - 1); |
| } |
| |
| public boolean canNavigate() { |
| return getUserObject().getLineNumber() > 0; |
| } |
| |
| public boolean canNavigateToSource() { |
| return canNavigate(); |
| } |
| } |
| } |