alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2004 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.modules.search;

import java.awt.Image;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.CharConversionException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import java.util.TreeSet;
import javax.swing.UIManager;
import org.openide.actions.DeleteAction;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataObject;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;
import org.openide.util.RequestProcessor;
import org.openide.util.Utilities;
import org.openide.util.actions.SystemAction;
import org.openide.xml.XMLUtil;
import org.openidex.search.SearchGroup;
import org.openidex.search.SearchType;

/**
 * Children for the result root node.
 *
 * @author  Petr Kuzel
 * @author  Marian Petras
 */
final class ResultTreeChildren extends Children.Keys
                               implements Runnable, Observer {

    /**
     * Keys as found objects for the search.
     * Must us esyncronized access to avoid connurent modifications.
     */
    private Set keys;

    /** Comparator used for sorting children nodes. */
    private final Comparator comparator;
    
    /** */
    private final ResultModel resultModel;

    /** Whether this node has sorted children. */
    private boolean sorted = false;
    
    /** */
    private boolean hasDetails = false;

    /*
     * once number of keys gets greater than BATCH_LEVEL,
     * start batching expensive setKeys call
     */
    private final int BATCH_LEVEL = 61;
    private final int BATCH_INTERVAL_MS = 759;
    private volatile RequestProcessor.Task batchSetKeys;
    private volatile boolean active = false;
    private int size = 0;

    /** Constructor. */
    public ResultTreeChildren(final ResultModel resultModel) {
        this.resultModel = resultModel;
        this.resultModel.addObserver(this);
        
        keys = resultModel.getSearchGroup().getResultObjects();

        this.comparator = new ResultItemsComparator();
    }

    /** Overrides superclass method. */
    protected void addNotify() {
        setKeys(Collections.EMPTY_SET);
        active = true;

        RequestProcessor.getDefault().post(new Runnable() {
             public void run() {
                 synchronized (keys) {
                    setKeys(keys);
                 }
             }
         });
    }

    /** Overrrides superclass method. */
    protected void removeNotify() {
        active = false;
        setKeys(Collections.EMPTY_SET);
    }

    /**
     * Clear children in fast batch manner, does not touch model.
     * Model should be cleaned by client. This approach eliminates costly
     * event driven cleanup.
     */
    void clear() {
        Enumeration en = nodes();
        while (en.hasMoreElements()) {
            FoundNode node = (FoundNode) en.nextElement();
            node.originalDataObject.removePropertyChangeListener(node);
        }
        dispose();
    }

    /** Explicit garbage collect request. */
    void dispose() {
        synchronized (keys) {
            keys = Collections.EMPTY_SET;
        }
        removeNotify();
        resultModel.deleteObserver(this);
    }

    /** Creates nodes. */
    protected Node[] createNodes(Object key) {
        return new Node[] { createFoundNode(key)};
    }

    /** Creates result node with carefully cafted children */
    private FoundNode createFoundNode(Object foundObject) {
        final SearchGroup searchGroup = resultModel.getSearchGroup();
        Node node = searchGroup.getNodeForFoundObject(foundObject);
        SearchType[] types = searchGroup.getSearchTypes();

        // TODO need faster hasDetails check, without creating (and discarding) actual detail nodes
        boolean hasDetails = false;
        for (int i = 0; i < types.length; i++) {
            SearchType searchType = types[i];
            Node[] details = searchType.getDetails(node);
            if ((details != null) && details.length > 0) {
                hasDetails = true;
                this.hasDetails = true;
                break;
            }
        }

        if (hasDetails) {
            return new FoundNode(node, new DetailChildren(node), foundObject);
        } else {
            return new FoundNode(node, Children.LEAF, foundObject);
        }
    }
    
    /**
     */
    boolean isEmpty() {
        return size == 0;
    }
    
    /**
     * Does any of the nodes have at least one detail node.
     *
     * @return  true if so, otherwise
     */
    boolean hasDetails() {
        return hasDetails;
    }

    /**
     */
    public void update(Observable o, Object foundObject) {
        synchronized (keys) {
            keys.add(foundObject);
            size++;
        }

        if (size < BATCH_LEVEL) {
            synchronized (keys) {
                setKeys(keys); //??? -> sort (sorted);
            }
        } else {
            batchSetKeys();  //much faster
        }
    }

    // do not update keys too often it's rather heavyweight operation
    // batch all request that come in BATCH_INTERVAL_MS into one real update
    private void batchSetKeys() {
        if (batchSetKeys == null) {
            batchSetKeys = RequestProcessor.getDefault()
                           .post(this, BATCH_INTERVAL_MS);
        }
    }


    public void removeFoundObject(Object foundObject) {
        boolean removed = false;
        synchronized (keys) {
            removed = keys.remove(foundObject);
        }
        if (removed) {
            sort(sorted);
        }
    }

    /** Sorts/unsorts the children nodes. */
    public void sort(boolean sort) {
        Set newKeys;

        if (sort) {
            newKeys = new TreeSet(comparator);
        } else {
            newKeys = new HashSet();
        }
        synchronized (keys) {
            newKeys.addAll(keys);
        }

        setKeys(newKeys);

        sorted = sort;
    }

    /** Getter for sorted property. */
    public boolean isSorted() {
        return sorted;
    }

    // called from random request processor thread
    public void run() {
        batchSetKeys = null;
        synchronized (keys) {
            if (active) {
                setKeys(keys);
            }
        }
    }
    
    /** Node to show in result window. */
    final class FoundNode extends FilterNode implements PropertyChangeListener {
        
        /** Original data object if there is any. */
        private DataObject originalDataObject;

        /** Original found object. */
        private Object foundObject;


        /**
         */
        FoundNode() {
            super(Node.EMPTY);
        }
        
        /** Use {@link ResultModel#createFoundNode} instead. */
        FoundNode(Node node, org.openide.nodes.Children kids, Object foundObject) {

            // cut off children, we do not need to show them and they eat memory
            super(node, kids);
            
            this.foundObject = foundObject;
            
            this.originalDataObject = (DataObject)
                                      getOriginal().getCookie(DataObject.class);

            if (originalDataObject == null) {
                return; 
            }

            this.originalDataObject.addPropertyChangeListener(this);

            FileObject fileFolder = originalDataObject.getPrimaryFile().getParent();
            if (fileFolder != null) {
                disableDelegation(DELEGATE_SET_SHORT_DESCRIPTION |
                                   DELEGATE_GET_DISPLAY_NAME | DELEGATE_GET_SHORT_DESCRIPTION);
                setShortDescription("");  // NOI18N
            }

        }


        public String getDisplayName() {
            FileObject fileFolder = originalDataObject.getPrimaryFile().getParent();
            if (fileFolder != null) {
                String hint = fileFolder.getPath();
                String orig = getOriginal().getDisplayName();
                return orig + " " + hint;
            } else {
                return getOriginal().getDisplayName();
            }
        }

        public String getHtmlDisplayName() {
            FileObject fileFolder = originalDataObject.getPrimaryFile().getParent();
            if (fileFolder != null) {
                String hint = FileUtil.getFileDisplayName(fileFolder);
                String orig = getOriginal().getDisplayName();
                try {
                    String color;
                    if (UIManager.getDefaults().getColor("Tree.selectionBackground").equals( // NOI18N
                        UIManager.getDefaults().getColor("controlShadow"))) { // NOI18N
                        color = "Tree.selectionBorderColor"; // NOI18N
                    } else {
                        color = "controlShadow"; // NOI18N
                    }

                    return "" + orig + " " + XMLUtil.toElementContent(hint);  // NOI18N
                } catch (CharConversionException e) {
                    return null;
                }
            } else {
                return getOriginal().getHtmlDisplayName();
            }
        }

        /** Gets system actions for this node. Overrides superclass method.
         * Adds RemoveFromSearchAction. */
        public SystemAction[] getActions() {
            List originalActions = new ArrayList(Arrays.asList(super.getActions()));

            int deleteIndex = originalActions.indexOf(SystemAction.get(DeleteAction.class));

            SystemAction removeFromSearch = SystemAction.get(RemoveFromSearchAction.class);

            if (deleteIndex != -1) {
                originalActions.add(deleteIndex, removeFromSearch);
            } else {
                originalActions.add(null);
                originalActions.add(removeFromSearch);
            }

            return (SystemAction[])originalActions.toArray(new SystemAction[originalActions.size()]);
        }

        /** Action performer. Removes node from search result window (and model). Note: it doesn't delete the original. */
        public void removeFromSearch() { 
            if (originalDataObject != null) {
                originalDataObject.removePropertyChangeListener(this);
            }
            ResultTreeChildren.this.removeFoundObject(foundObject);
        }

        /** Destroys node and it's file. Overrides superclass method. */
        public void destroy() throws IOException {
            super.destroy();

            if (originalDataObject != null) {
                originalDataObject.removePropertyChangeListener(this);
            }
            ResultTreeChildren.this.removeFoundObject(foundObject);
        }



        /** Implements PropertyChangeListener litening or originalDataObject. */
        public void propertyChange(PropertyChangeEvent evt) {
            if (DataObject.PROP_VALID.equals(evt.getPropertyName())) {
                // data object might be deleted
                if (!originalDataObject.isValid()) {    //link becomes invalid
                    if (originalDataObject != null) {
                        originalDataObject.removePropertyChangeListener(this);
                    }
                    ResultTreeChildren.this.removeFoundObject(foundObject);
                }
            }
        }

        /** Optimalized JavaNode.getIcon that starts parser! */
        public Image getIcon(int type) {
            if (originalDataObject.getPrimaryFile().hasExt("java")) { // NOI18N
                // take icon from loader, it should be similar to dataobject's one
                try {
                    BeanInfo info = Utilities.getBeanInfo(originalDataObject.getLoader().getClass());
                    return info.getIcon(type);
                } catch (IntrospectionException e) {
                    return AbstractNode.EMPTY.getIcon(type);
                }
            } else {
                return super.getIcon(type);
            }
        }

        /** Optimalized JavaNode.getIcon that starts parser! */
        public Image getOpenedIcon(int type) {
            if (originalDataObject.getPrimaryFile().hasExt("java") ) { // NOI18N
                // take icon from loader, it should be similar to dataobject's one
                try {
                    BeanInfo info = Utilities.getBeanInfo(originalDataObject.getLoader().getClass());
                    return info.getIcon(type);
                } catch (IntrospectionException e) {
                    return AbstractNode.EMPTY.getIcon(type);
                }
            } else {
                return super.getOpenedIcon(type);
            }
        }
    } // End of FoundNode class.
   
    /** Details for found top level (file) nodes. */
    final class DetailChildren extends Children.Array {

        private final Node parent;

        DetailChildren(Node parent) {
            this.parent = parent;
        }

        // TODO why I must subclass Children.Array? I tried to subclass Children directly
        // and returned createNodes result from getNodes() but it did not work.
        // Why I complain, *Children.Array* is very memory expensive structure
        // other implementation (Keys, ...) are even worse :-(

        protected void addNotify() {
            add(createNodes(parent));
        }

        protected void removeNotify() {
            remove(getNodes());
        }

        protected Node[] createNodes(Object key) {
            Node node = (Node) key;           // the parent
            SearchType[] types = resultModel.getSearchGroup().getSearchTypes();
            ArrayList nodes = new ArrayList(5);
            for (int i = 0; i < types.length; i++) {
                SearchType searchType = types[i];
                Node[] details = searchType.getDetails(node);
                if ((details != null) && details.length>0) {
                    for (int j = 0; j < details.length; j++) {
                        Node detail = details[j];
                        nodes.add(detail);
                    }
                }
            }

            return (Node[]) nodes.toArray(new Node[nodes.size()]);
        }

    }

    /**
     */
    final class ResultItemsComparator implements Comparator {
        
        /** */
        private final SearchGroup searchGroup;
        
        /**
         */
        ResultItemsComparator() {
            searchGroup = ResultTreeChildren.this.resultModel.getSearchGroup();
        }

        /* overridden */
        public int compare(Object o1, Object o2) {
            if (o1 == o2) {
                return 0;
            }
            if (o1 == null) {
                return 1;
            }
            if (o2 == null) {
                return -1;
            }
            Node node1 = searchGroup.getNodeForFoundObject(o1);
            Node node2 = searchGroup.getNodeForFoundObject(o2);

            if (node1 == node2) {
                return 0;
            }
            if (node1 == null) {
                return 1;
            }
            if (node2 == null) {
                return -1;
            }
            int result = node1.getDisplayName()
                         .compareTo(node2.getDisplayName());

            /*
             * Must not return that two different nodes are equal,
             * even their names are same.
             */
            return result == 0 ? -1 : result;
        }
            
    }

}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.