    001    // Copyright 2011 The Apache Software Foundation
    002    //
    003    // Licensed under the Apache License, Version 2.0 (the "License");
    004    // you may not use this file except in compliance with the License.
    005    // You may obtain a copy of the License at
    006    //
    007    // http://www.apache.org/licenses/LICENSE-2.0
    008    //
    009    // Unless required by applicable law or agreed to in writing, software
    010    // distributed under the License is distributed on an "AS IS" BASIS,
    011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012    // See the License for the specific language governing permissions and
    013    // limitations under the License.
    014
    015    package org.apache.tapestry5.tree;
    016
    017    import org.apache.tapestry5.ValueEncoder;
    018    import org.apache.tapestry5.func.F;
    019    import org.apache.tapestry5.func.Mapper;
    020    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    021
    022    import java.util.Collections;
    023    import java.util.LinkedList;
    024    import java.util.List;
    025    import java.util.Map;
    026
    027    /**
    028     * A default implementation of TreeModel that starts with a {@link ValueEncoder} (for the element to string conversion),
    029     * a {@link TreeModelAdapter}, and a list of root nodes.
    030     * <p/>
    031     * This implementation is <em>not</em> thread safe.
    032     *
    033     * @param <T>
    034     * @since 5.3
    035     */
    036    @SuppressWarnings({"UnusedDeclaration"})
    037    public class DefaultTreeModel<T> implements TreeModel<T>
    038    {
    039        private final ValueEncoder<T> encoder;
    040
    041        private final TreeModelAdapter<T> adapter;
    042
    043        private final List<TreeNode<T>> roots;
    044
    045        private final Map<String, TreeNode<T>> cache = CollectionFactory.newMap();
    046
    047        private final Mapper<T, TreeNode<T>> toTreeNode = new Mapper<T, TreeNode<T>>()
    048        {
    049            public TreeNode<T> map(T value)
    050            {
    051                return new DefaultTreeNode(value);
    052            }
    053        };
    054
    055        private class DefaultTreeNode implements TreeNode<T>
    056        {
    057            private final T value;
    058
    059            private List<TreeNode<T>> children;
    060
    061            DefaultTreeNode(T value)
    062            {
    063                this.value = value;
    064            }
    065
    066            public String getId()
    067            {
    068                return encoder.toClient(value);
    069            }
    070
    071            public T getValue()
    072            {
    073                return value;
    074            }
    075
    076            public boolean isLeaf()
    077            {
    078                return adapter.isLeaf(value);
    079            }
    080
    081            public boolean getHasChildren()
    082            {
    083                return adapter.hasChildren(value);
    084            }
    085
    086            public List<TreeNode<T>> getChildren()
    087            {
    088                if (children == null)
    089                {
    090                    List<T> childValues = adapter.getChildren(value);
    091
    092                    boolean empty = childValues == null || childValues.isEmpty();
    093
    094                    children = empty
    095                            ? emptyTreeNodeList()
    096                            : F.flow(childValues).map(toTreeNode).toList();
    097                }
    098
    099                return children;
    100            }
    101
    102            public String getLabel()
    103            {
    104                return adapter.getLabel(value);
    105            }
    106
    107            private List<TreeNode<T>> emptyTreeNodeList()
    108            {
    109                return Collections.emptyList();
    110            }
    111
    112        }
    113
    114        /**
    115         * Creates a new model starting from a single root element.
    116         *
    117         * @param encoder used to convert values to strings and vice-versa
    118         * @param adapter adapts elements to the tree
    119         * @param root    defines the root node of the model
    120         */
    121        public DefaultTreeModel(ValueEncoder<T> encoder, TreeModelAdapter<T> adapter, T root)
    122        {
    123            this(encoder, adapter, Collections.singletonList(root));
    124        }
    125
    126        /**
    127         * Standard constructor.
    128         *
    129         * @param encoder used to convert values to strings and vice-versa
    130         * @param adapter adapts elements to the tree
    131         * @param roots   defines the root nodes of the model
    132         */
    133        public DefaultTreeModel(ValueEncoder<T> encoder, TreeModelAdapter<T> adapter, List<T> roots)
    134        {
    135            assert encoder != null;
    136            assert adapter != null;
    137            assert roots != null;
    138            assert !roots.isEmpty();
    139
    140            this.encoder = encoder;
    141            this.adapter = adapter;
    142            this.roots = F.flow(roots).map(toTreeNode).toList();
    143        }
    144
    145        public List<TreeNode<T>> getRootNodes()
    146        {
    147            return roots;
    148        }
    149
    150        public TreeNode<T> getById(String id)
    151        {
    152            assert id != null;
    153
    154            TreeNode<T> result = findById(id);
    155
    156            if (result == null)
    157                throw new IllegalArgumentException(String.format("Could not locate TreeNode '%s'.", id));
    158
    159            return result;
    160        }
    161
    162        private TreeNode<T> findById(String id)
    163        {
    164            TreeNode<T> result = cache.get(id);
    165
    166            if (result != null)
    167                return result;
    168
    169            LinkedList<TreeNode<T>> queue = new LinkedList<TreeNode<T>>(roots);
    170
    171            while (!queue.isEmpty())
    172            {
    173                TreeNode<T> node = queue.removeFirst();
    174
    175                String nodeId = node.getId();
    176
    177                cache.put(nodeId, node);
    178
    179                if (nodeId.equals(id))
    180                    return node;
    181
    182                if (!node.isLeaf() && node.getHasChildren())
    183                {
    184                    for (TreeNode<T> child : node.getChildren())
    185                    {
    186                        queue.addFirst(child);
    187                    }
    188                }
    189            }
    190
    191            return null;
    192        }
    193
    194        public TreeNode<T> find(T element)
    195        {
    196            return findById(encoder.toClient(element));
    197        }
    198
    199    }