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    }