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}