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