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 }