/* * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.tools.visualvm.core.explorer; import com.sun.tools.visualvm.core.datasource.DataSource; import com.sun.tools.visualvm.core.datasource.DataSourceRepository; import com.sun.tools.visualvm.core.datasupport.DataChangeEvent; import com.sun.tools.visualvm.core.datasupport.DataChangeListener; import com.sun.tools.visualvm.core.datasource.descriptor.DataSourceDescriptor; import com.sun.tools.visualvm.core.datasource.descriptor.DataSourceDescriptorFactory; import com.sun.tools.visualvm.core.datasupport.Utils; import java.awt.Image; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.ImageIcon; import javax.swing.SwingUtilities; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; import org.openide.util.NbBundle; import org.openide.util.RequestProcessor; /** * * @author Jiri Sedlacek */ class ExplorerModelBuilder implements DataChangeListener<DataSource> { private static final RequestProcessor queue = new RequestProcessor("Explorer Builder Processor"); // NOI18N private static ExplorerModelBuilder instance; private final ExplorerNode explorerRoot; private final DefaultTreeModel explorerModel; private final Map<DataSource, ExplorerNode> nodes = new HashMap(); private final Map<DataSource, PropertyChangeListener> visibilityListeners = new HashMap(); private final Map<DataSourceDescriptor, PropertyChangeListener> descriptorListeners = new HashMap(); private static final ExplorerNodesComparator RELATIVE_COMPARATOR = new ExplorerNodesComparator(new RelativePositionComparator()); public static synchronized ExplorerModelBuilder getInstance() { if (instance == null) instance = new ExplorerModelBuilder(); return instance; } DefaultTreeModel getModel() { return explorerModel; } ExplorerNode getNodeFor(DataSource dataSource) { return nodes.get(dataSource); } public void dataChanged(final DataChangeEvent event) { queue.post(new Runnable() { public void run() { Set<DataSource> removed = event.getRemoved(); Set<DataSource> added = event.getAdded(); if (!removed.isEmpty()) processRemovedDataSources(removed); if (!added.isEmpty()) processAddedDataSources(added); } }); } private void processAddedDataSources(Set<DataSource> added) { for (DataSource dataSource : added) installVisibilityListener(dataSource); processIndependentAddedDataSources(Utils.getIndependentDataSources(added)); } private void processIndependentAddedDataSources(Set<DataSource> added) { Set<DataSource> addedDisplayable = new HashSet(); for (DataSource dataSource : added) { if (isDisplayed(dataSource) && dataSource != DataSource.ROOT) return; if (isDisplayable(dataSource)) addedDisplayable.add(dataSource); } if (!addedDisplayable.isEmpty()) processAddedDisplayableDataSources(addedDisplayable); } private void processRemovedDataSources(Set<DataSource> removed) { for (DataSource dataSource : removed) uninstallVisibilityListener(dataSource); processIndependentRemovedDataSources(Utils.getIndependentDataSources(removed)); } private void processIndependentRemovedDataSources(Set<DataSource> removed) { Set<DataSource> removedDisplayed = new HashSet(); for (DataSource dataSource : removed) if (isDisplayed(dataSource)) removedDisplayed.add(dataSource); if (!removedDisplayed.isEmpty()) processRemovedDisplayedDataSources(removedDisplayed); } private void processAddedDisplayableDataSources(Set<DataSource> addedDisplayable) { final List<ExplorerNode> addedNodes = new ArrayList(); final ProgressHandle[] pHandle = new ProgressHandle[1]; SwingUtilities.invokeLater(new Runnable() { public void run() { pHandle[0] = ProgressHandleFactory.createHandle(NbBundle.getMessage(ExplorerModelBuilder.class, "LBL_Computing_description")); pHandle[0].setInitialDelay(5000); pHandle[0].start(); } }); try { for (DataSource dataSource : addedDisplayable) { if (dataSource != DataSource.ROOT) { final ExplorerNode node = new ExplorerNode(dataSource); addedNodes.add(node); DataSourceDescriptor descriptor = DataSourceDescriptorFactory.getDescriptor(dataSource); PropertyChangeListener descriptorListener = new PropertyChangeListener() { public void propertyChange(final PropertyChangeEvent evt) { queue.post(new Runnable() { public void run() { updateNode(node, evt); } }); } }; descriptor.addPropertyChangeListener(descriptorListener); descriptorListeners.put(descriptor, descriptorListener); updateNode(node, descriptor); } } } finally { SwingUtilities.invokeLater(new Runnable() { public void run() { pHandle[0].finish(); } }); } Collections.sort(addedNodes, RELATIVE_COMPARATOR); try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { addNodes(addedNodes); } }); } catch (Exception e) {} Set<DataSource> addedChildren = new HashSet(); for (DataSource dataSource : addedDisplayable) addedChildren.addAll(dataSource.getRepository().getDataSources()); if (!addedChildren.isEmpty()) processIndependentAddedDataSources(addedChildren); } private void processRemovedDisplayedDataSources(Set<DataSource> removedDisplayed) { Set<DataSource> removedChildren = new HashSet(); for (DataSource dataSource : removedDisplayed) removedChildren.addAll(dataSource.getRepository().getDataSources()); if (!removedChildren.isEmpty()) processIndependentRemovedDataSources(removedChildren); final Set<ExplorerNode> removedNodes = new HashSet(); for (DataSource dataSource : removedDisplayed) { DataSourceDescriptor descriptor = DataSourceDescriptorFactory.getDescriptor(dataSource); PropertyChangeListener descriptorListener = descriptorListeners.get(descriptor); descriptor.removePropertyChangeListener(descriptorListener); descriptorListeners.remove(descriptor); ExplorerNode node = nodes.get(dataSource); removedNodes.add(node); } try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { removeNodes(removedNodes); } }); } catch (Exception e) {} } private void updateNode(ExplorerNode node, DataSourceDescriptor descriptor) { node.setName(descriptor.getName()); node.setIcon(descriptor.getIcon() == null ? null : new ImageIcon(descriptor.getIcon())); node.setPreferredPosition(descriptor.getPreferredPosition()); node.setComparator(descriptor.getChildrenComparator()); node.setAutoExpansionPolicy(descriptor.getAutoExpansionPolicy()); } private void updateNode(final ExplorerNode node, final PropertyChangeEvent evt) { String property = evt.getPropertyName(); Object newValue = evt.getNewValue(); // Node name needs to be updated if (DataSourceDescriptor.PROPERTY_NAME.equals(property)) { String name = (String)newValue; Runnable updater = node.setName(name) ? new Runnable() { public void run() { updateContainer(node.getParent()); } } : new Runnable() { public void run() { explorerModel.nodeChanged(node); } }; try { SwingUtilities.invokeAndWait(updater); } catch (Exception e) {} // Node icon needs to be updated } else if (DataSourceDescriptor.PROPERTY_ICON.equals(property)) { Image icon = (Image)newValue; node.setIcon(icon == null ? null : new ImageIcon(icon)); try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { explorerModel.nodeChanged(node); } }); } catch (Exception e) {} // Node position within its parent needs to be updated } else if (DataSourceDescriptor.PROPERTY_PREFERRED_POSITION.equals(property)) { Integer preferredPosition = (Integer)newValue; node.setPreferredPosition(preferredPosition); final ExplorerNode parent = (ExplorerNode)node.getParent(); if (parent != null) { try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { int nodeIndex = parent.getIndex(node); parent.remove(node); explorerModel.nodesWereRemoved(parent, new int[] { nodeIndex }, new Object[] { node }); parent.addNode(node); explorerModel.nodesWereInserted(parent, new int[] { parent.getIndex(node) }); } }); } catch (Exception e) {} } } else if (DataSourceDescriptor.PROPERTY_CHILDREN_COMPARATOR.equals(property)) { final Comparator<DataSource> comparator = (Comparator<DataSource>)newValue; SwingUtilities.invokeLater(new Runnable() { public void run() { if (node.setComparator(comparator)) updateContainer(node); } }); } else if (DataSourceDescriptor.PROPERTY_EXPANSION_POLICY.equals(property)) { node.setAutoExpansionPolicy((Integer)evt.getNewValue()); } } private void updateContainer(TreeNode node) { // Save selection Set<DataSource> selectedDataSources = ExplorerSupport.sharedInstance(). getSelectedDataSources(); explorerModel.nodeStructureChanged(node); // Try to restore selection ExplorerSupport.sharedInstance().selectDataSources(selectedDataSources); } private void addNodes(List<ExplorerNode> added) { Map<ExplorerNode, List<Integer>> indexes = new HashMap(); // Save selection Set<DataSource> selectedDataSources = ExplorerSupport.sharedInstance().getSelectedDataSources(); // Add nodes and create parent entries for (ExplorerNode node : added) { DataSource dataSource = node.getUserObject(); ExplorerNode nodeParent = getNodeFor(dataSource.getOwner()); nodes.put(dataSource, node); nodeParent.addNode(node); indexes.put(nodeParent, new ArrayList()); } // Compute children indexes for (ExplorerNode node : added) { ExplorerNode nodeParent = (ExplorerNode)node.getParent(); indexes.get(nodeParent).add(nodeParent.getIndex(node)); } // Notify tree model // Synchronize relative positions for (Map.Entry<ExplorerNode, List<Integer>> entry : indexes.entrySet()) { List<Integer> indexesList = entry.getValue(); Collections.sort(indexesList); int[] indexesArr = new int[indexesList.size()]; for (int i = 0; i < indexesArr.length; i++) indexesArr[i] = indexesList.get(i); final ExplorerNode parent = entry.getKey(); explorerModel.nodesWereInserted(parent, indexesArr); } // Try to restore selection ExplorerSupport.sharedInstance().selectDataSources(selectedDataSources); } private void removeNodes(Set<ExplorerNode> removed) { Map<ExplorerNode, List<IndexNodePair>> pairs = new HashMap(); // Save selection Set<DataSource> selectedDataSources = ExplorerSupport.sharedInstance().getSelectedDataSources(); // Cache indexes and childs for (ExplorerNode node : removed) { ExplorerNode nodeParent = (ExplorerNode)node.getParent(); List<IndexNodePair> list = pairs.get(nodeParent); if (list == null) { list = new ArrayList(); pairs.put(nodeParent, list); } list.add(new IndexNodePair(nodeParent.getIndex(node), node)); } // Remove nodes for (ExplorerNode node : removed) { node.removeFromParent(); nodes.remove(node.getUserObject()); } // Notify tree model // Synchronize relative positions for (Map.Entry<ExplorerNode, List<IndexNodePair>> entry : pairs.entrySet()) { List<IndexNodePair> indexesList = entry.getValue(); Collections.sort(indexesList); int[] indexesArr = new int[indexesList.size()]; Object[] childsArr = new Object[indexesList.size()]; for (int i = 0; i < indexesArr.length; i++) { IndexNodePair pair = indexesList.get(i); indexesArr[i] = pair.index; childsArr[i] = pair.node; } final ExplorerNode parent = entry.getKey(); explorerModel.nodesWereRemoved(parent, indexesArr, childsArr); } // Try to restore selection ExplorerSupport.sharedInstance().selectDataSources(selectedDataSources); } private void installVisibilityListener(final DataSource dataSource) { PropertyChangeListener visibilityListener = new PropertyChangeListener() { public void propertyChange(final PropertyChangeEvent evt) { queue.post(new Runnable() { public void run() { if ((Boolean)evt.getNewValue()) { if (isDisplayed(dataSource.getOwner()) && !isDisplayed(dataSource)) processAddedDisplayableDataSources(Collections.singleton(dataSource)); } else { if (isDisplayed(dataSource)) processRemovedDisplayedDataSources(Collections.singleton(dataSource)); } } }); } }; dataSource.addPropertyChangeListener(DataSource.PROPERTY_VISIBLE, visibilityListener); visibilityListeners.put(dataSource, visibilityListener); } private void uninstallVisibilityListener(DataSource dataSource) { PropertyChangeListener visibilityListener = visibilityListeners.get(dataSource); dataSource.removePropertyChangeListener(DataSource.PROPERTY_VISIBLE, visibilityListener); visibilityListeners.remove(dataSource); } private boolean isDisplayed(DataSource dataSource) { return nodes.get(dataSource) != null; } private boolean isDisplayable(DataSource dataSource) { if (dataSource == DataSource.ROOT) return true; return dataSource.isVisible() && isDisplayed(dataSource.getOwner()); } private ExplorerModelBuilder() { explorerRoot = new ExplorerNode(DataSource.ROOT); explorerRoot.setAutoExpansionPolicy(DataSourceDescriptor.EXPAND_ON_EACH_FIRST_CHILD); explorerModel = new DefaultTreeModel(explorerRoot); nodes.put(DataSource.ROOT, explorerRoot); DataSourceRepository.sharedInstance().addDataChangeListener(this, DataSource.class); } private static class RelativePositionComparator extends DataSourcesComparator { protected int getRelativePosition(DataSource d, int positionType) { try { // throws NumberFormatException return Integer.parseInt(d.getStorage().getCustomProperty( ExplorerNode.PROPERTY_RELATIVE_POSITION)); } catch (Exception e) { return positionType; } } } private static class IndexNodePair implements Comparable { public int index; public ExplorerNode node; public IndexNodePair(int index, ExplorerNode node) { this.index = index; this.node = node; } public int compareTo(Object o) { IndexNodePair pair = (IndexNodePair)o; if (index == pair.index) return 0; if (index > pair.index) return 1; else return -1; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final IndexNodePair other = (IndexNodePair) obj; if (this.index != other.index) { return false; } return true; } @Override public int hashCode() { int hash = 7; hash = 37 * hash + this.index; return hash; } } }