    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
    015package org.apache.tapestry5.tree;
    016
    017import org.apache.tapestry5.ValueEncoder;
    018import org.apache.tapestry5.func.F;
    019import org.apache.tapestry5.func.Mapper;
    020import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    021
    022import java.util.Collections;
    023import java.util.LinkedList;
    024import java.util.List;
    025import 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"})
    037public 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}