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