    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
    137        this.encoder = encoder;
    138        this.adapter = adapter;
    139        this.roots = F.flow(roots).map(toTreeNode).toList();
    140    }
    141
    142    public List<TreeNode<T>> getRootNodes()
    143    {
    144        return roots;
    145    }
    146
    147    public TreeNode<T> getById(String id)
    148    {
    149        assert id != null;
    150
    151        TreeNode<T> result = findById(id);
    152
    153        if (result == null)
    154            throw new IllegalArgumentException(String.format("Could not locate TreeNode '%s'.", id));
    155
    156        return result;
    157    }
    158
    159    private TreeNode<T> findById(String id)
    160    {
    161        TreeNode<T> result = cache.get(id);
    162
    163        if (result != null)
    164            return result;
    165
    166        LinkedList<TreeNode<T>> queue = new LinkedList<TreeNode<T>>(roots);
    167
    168        while (!queue.isEmpty())
    169        {
    170            TreeNode<T> node = queue.removeFirst();
    171
    172            String nodeId = node.getId();
    173
    174            cache.put(nodeId, node);
    175
    176            if (nodeId.equals(id))
    177                return node;
    178
    179            if (!node.isLeaf() && node.getHasChildren())
    180            {
    181                for (TreeNode<T> child : node.getChildren())
    182                {
    183                    queue.addFirst(child);
    184                }
    185            }
    186        }
    187
    188        return null;
    189    }
    190
    191    public TreeNode<T> find(T element)
    192    {
    193        return findById(encoder.toClient(element));
    194    }
    195
    196}