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 }