swingx-master/swingx-demos/swingx-demos-swingxset/src/main/java/org/jdesktop/swingx/binding/JTreeAdapterProvider.java
/*
 * $Id$
 *
 * Copyright 2009 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.jdesktop.swingx.binding;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreeSelectionModel;

import org.jdesktop.beansbinding.ext.BeanAdapterProvider;
import org.jdesktop.swingbinding.adapters.BeanAdapterBase;

/**
 * @author Karl George Schaefer
 * @author Shannon Hickey (for ideas from adapters package)
 */
public final class JTreeAdapterProvider implements BeanAdapterProvider {

    private static final String SELECTED_ELEMENT_P = "selectedElement";
    private static final String SELECTED_ELEMENTS_P = "selectedElements";
    private static final String SELECTED_ELEMENT_UN_P = SELECTED_ELEMENT_P + "_UNWRAP_NODE";
    private static final String SELECTED_ELEMENTS_UN_P = SELECTED_ELEMENTS_P + "_UNWRAP_NODE";

    public final class Adapter extends BeanAdapterBase {
        private JTree tree;

        private Handler handler;

        private Object cachedElementOrElements;

        private Adapter(JTree tree, String property) {
            super(property);
            this.tree = tree;
        }

        private boolean isPlural() {
            return property == SELECTED_ELEMENTS_P;
        }

        public Object getSelectedElement() {
            return JTreeAdapterProvider.getSelectedElement(tree);
        }

        public Object getSelectedElement_UNWRAP_NODE() {
            Object o = JTreeAdapterProvider.getSelectedElement(tree);
            
            return unwrappedObject(o);
        }
        
        public List<Object> getSelectedElements() {
            return JTreeAdapterProvider.getSelectedElements(tree);
        }

        public List<Object> getSelectedElements_UNWRAP_NODE() {
            List<Object> objects = JTreeAdapterProvider.getSelectedElements(tree);
            List<Object> unwrapped = new ArrayList<Object>(objects.size());
            
            for (Object o : objects) {
                unwrapped.add(unwrappedObject(o));
            }
            
            return unwrapped;
        }
        
        //why oh why is there no getUserObject method in the interface?!
        private Object unwrappedObject(Object o) {
            //possible to have null element
            if (o == null) {
                return o;
            }
            
            try {
                Method m = o.getClass().getMethod("getUserObject");
                
                return m.invoke(o);
            } catch (SecurityException ignore) {
                //just return o
            } catch (NoSuchMethodException ignore) {
                //just return o
            } catch (IllegalAccessException ignore) {
                //just return o
            } catch (InvocationTargetException ignore) {
                //just return o
            }
            
            return o;
        }
        
        protected void listeningStarted() {
            handler = new Handler();
            cachedElementOrElements = isPlural() ? getSelectedElements() : JTreeAdapterProvider
                    .getSelectedElement(tree);
            tree.addPropertyChangeListener("selectionModel", handler);
            tree.getSelectionModel().addTreeSelectionListener(handler);
        }

        protected void listeningStopped() {
            tree.getSelectionModel().removeTreeSelectionListener(handler);
            tree.removePropertyChangeListener("selectionModel", handler);
            cachedElementOrElements = null;
            handler = null;
        }

        private class Handler implements TreeSelectionListener, PropertyChangeListener {
            private void treeSelectionChanged() {
                Object oldElementOrElements = cachedElementOrElements;
                cachedElementOrElements = getSelectedElements();
                firePropertyChange(oldElementOrElements, cachedElementOrElements);
            }

            public void valueChanged(TreeSelectionEvent e) {
                treeSelectionChanged();
            }

            public void propertyChange(PropertyChangeEvent pce) {
                ((TreeSelectionModel) pce.getOldValue()).removeTreeSelectionListener(handler);
                ((TreeSelectionModel) pce.getNewValue()).addTreeSelectionListener(handler);
                treeSelectionChanged();
            }
        }
    }

    private static List<Object> getSelectedElements(JTree tree) {
        assert tree != null;

        TreeSelectionModel selectionModel = tree.getSelectionModel();
        int min = selectionModel.getMinSelectionRow();
        int max = selectionModel.getMaxSelectionRow();

        if (min < 0 || max < 0) {
            return new ArrayList<Object>(0);
        }

        ArrayList<Object> elements = new ArrayList<Object>(max - min + 1);

        for (int i = min; i <= max; i++) {
            if (selectionModel.isRowSelected(i)) {
                elements.add(getElement(tree, i));
            }
        }

        return elements;
    }

    private static Object getSelectedElement(JTree tree) {
        assert tree != null;

        int index = tree.getSelectionModel().getLeadSelectionRow();
        index = tree.getSelectionModel().isRowSelected(index) ? index : tree.getSelectionModel()
                .getMinSelectionRow();

        if (index == -1) {
            return null;
        }

        return getElement(tree, index);
    }

    private static Object getElement(JTree tree, int index) {
        return tree.getPathForRow(index).getLastPathComponent();
    }

    public boolean providesAdapter(Class<?> type, String property) {
        if (!JTree.class.isAssignableFrom(type)) {
            return false;
        }

        property = property.intern();

        return property == SELECTED_ELEMENT_P
                || property == SELECTED_ELEMENT_UN_P
                || property == SELECTED_ELEMENTS_P
                || property == SELECTED_ELEMENTS_UN_P;
    }

    public Object createAdapter(Object source, String property) {
        if (!providesAdapter(source.getClass(), property)) {
            throw new IllegalArgumentException();
        }

        return new Adapter((JTree) source, property);
    }

    public Class<?> getAdapterClass(Class<?> type) {
        return JTree.class.isAssignableFrom(type) ? JTreeAdapterProvider.Adapter.class : null;
    }

}
