001 // Copyright 2004, 2005 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.tapestry.form; 016 017 import java.util.ArrayList; 018 import java.util.Collections; 019 import java.util.HashMap; 020 import java.util.HashSet; 021 import java.util.List; 022 import java.util.Map; 023 import java.util.Set; 024 025 import org.apache.tapestry.Tapestry; 026 027 /** 028 * A utility class often used with the {@link org.apache.tapestry.form.ListEdit} component. A 029 * ListEditMap is loaded with data objects before the ListEdit renders, and again before the 030 * ListEdit rewinds. This streamlines the synchronization of the form against data on the server. It 031 * is most useful when the set of objects is of a manageable size (say, no more than a few hundred 032 * objects). 033 * <p> 034 * The map stores a list of keys, and relates each key to a value. It also tracks a deleted flag for 035 * each key. 036 * <p> 037 * Usage: <br> 038 * The page or component should implement {@link org.apache.tapestry.event.PageBeginRenderListener} 039 * and implement 040 * {@link org.apache.tapestry.event.PageBeginRenderListener#pageBeginRender(org.apache.tapestry.event.PageEvent)} 041 * to initialize the map. 042 * <p> 043 * The external data (from which keys and values are obtained) is queried, and each key/value pair 044 * is {@link #add(Object, Object) added} to the map, in the order that items should be presented. 045 * <p> 046 * The {@link org.apache.tapestry.form.ListEdit}'s source parameter should be bound to the map's 047 * {@link #getKeys() keys} property. The value parameter should be bound to the map's 048 * {@link #setKey(Object) key} property. 049 * <p> 050 * The {@link org.apache.tapestry.form.ListEdit}'s listener parameter should be bound to a listener 051 * method to synchronize a property of the component from the map. <code> 052 * public void synchronize() 053 * { 054 * ListEditMap map = ...; 055 * <i>Type</i> object = (<i>Type</i>)map.getValue(); 056 * 057 * if (object == null) 058 * ... 059 * 060 * set<i>Property</i>(object); 061 * } 062 * </code> 063 * <p> 064 * You may also connect a {@link org.apache.tapestry.form.Checkbox}'s selected parameter to the 065 * map's {@link #isDeleted() deleted} property. 066 * <p> 067 * You may track inclusion in other sets by subclassing ListEditMap and implementing new boolean 068 * properties. The accessor method should be a call to {@link #checkSet(Set)} and the mutator method 069 * should be a call to {@link #updateSet(Set, boolean)}. 070 * 071 * @author Howard Lewis Ship 072 * @since 3.0 073 */ 074 075 public class ListEditMap 076 { 077 private Map _map = new HashMap(); 078 079 private List _keys = new ArrayList(); 080 081 private Set _deletedKeys; 082 083 private Object _currentKey; 084 085 /** 086 * Records the key and value into this map. The keys may be obtained, in the order in which they 087 * are added, using {@link #getKeys()}. This also sets the current key (so that you may invoke 088 * {@link #setDeleted(boolean)}, for example). 089 */ 090 091 public void add(Object key, Object value) 092 { 093 _currentKey = key; 094 095 _keys.add(_currentKey); 096 _map.put(_currentKey, value); 097 } 098 099 /** 100 * Returns a List of keys, in the order that keys were added to the map (using 101 * {@link #add(Object, Object)}. The caller must not modify the List. 102 */ 103 104 public List getKeys() 105 { 106 return _keys; 107 } 108 109 /** 110 * Sets the key for the map. This defines the key used with the other methods: 111 * {@link #getValue()}, {@link #isDeleted()}, {@link #setDeleted(boolean)}. 112 */ 113 114 public void setKey(Object key) 115 { 116 _currentKey = key; 117 } 118 119 /** 120 * Returns the current key within the map. 121 */ 122 123 public Object getKey() 124 { 125 return _currentKey; 126 } 127 128 /** 129 * Returns the value for the key (set using {@link #setKey(Object)}). May return null if no 130 * such key has been added (this can occur if a data object is deleted between the time a form 131 * is rendered and the time a form is submitted). 132 */ 133 134 public Object getValue() 135 { 136 return _map.get(_currentKey); 137 } 138 139 /** 140 * Returns true if the {@link #setKey(Object) current key} is in the set of deleted keys. 141 */ 142 143 public boolean isDeleted() 144 { 145 return checkSet(_deletedKeys); 146 } 147 148 /** 149 * Returns true if the set contains the {@link #getKey() current key}. Returns false is the set 150 * is null, or doesn't contain the current key. 151 */ 152 153 protected boolean checkSet(Set set) 154 { 155 if (set == null) 156 return false; 157 158 return set.contains(_currentKey); 159 } 160 161 /** 162 * Adds or removes the {@link #setKey(Object) current key} from the set of deleted keys. 163 */ 164 165 public void setDeleted(boolean value) 166 { 167 _deletedKeys = updateSet(_deletedKeys, value); 168 } 169 170 /** 171 * Updates the set, adding or removing the {@link #getKey() current key} from it. Returns the 172 * set passed in. If the value is true and the set is null, an new instance of {@link HashSet} 173 * is created and returned. 174 */ 175 176 protected Set updateSet(Set set, boolean value) 177 { 178 if (value) 179 { 180 if (set == null) 181 set = new HashSet(); 182 183 set.add(_currentKey); 184 } 185 else 186 { 187 if (set != null) 188 set.remove(_currentKey); 189 } 190 191 return set; 192 } 193 194 /** 195 * Returns the deleted keys in an unspecified order. Returns a List, which may be empty if no 196 * keys have been deleted. 197 */ 198 199 public List getDeletedKeys() 200 { 201 return convertSetToList(_deletedKeys); 202 } 203 204 /** 205 * Removes keys and values that are in the set of deleted keys, then clears the set of deleted 206 * keys. After invoking this method, {@link #getValues()} and {@link #getAllValues()} will 207 * return equivalent lists and {@link #getKeys()} will no longer show any of the deleted keys. 208 * Note that this method <em>does not</em> change the current key. Subclasses that track 209 * additional key sets may want to override this method to remove deleted keys from those key 210 * sets. 211 */ 212 213 public void purgeDeletedKeys() 214 { 215 if (_deletedKeys == null) 216 return; 217 218 _map.keySet().removeAll(_deletedKeys); 219 _keys.removeAll(_deletedKeys); 220 221 _deletedKeys = null; 222 } 223 224 /** 225 * Invoked to convert a set into a List. 226 * 227 * @param set 228 * a set (which may be empty or null) 229 * @return a list (possibly empty) of the items in the set 230 */ 231 232 protected List convertSetToList(Set set) 233 { 234 if (Tapestry.isEmpty(set)) 235 return Collections.EMPTY_LIST; 236 237 return new ArrayList(set); 238 } 239 240 /** 241 * Returns all the values stored in the map, in the order in which values were added to the map 242 * using {@link #add(Object, Object)}. 243 */ 244 245 public List getAllValues() 246 { 247 int count = _keys.size(); 248 List result = new ArrayList(count); 249 250 for (int i = 0; i < count; i++) 251 { 252 Object key = _keys.get(i); 253 Object value = _map.get(key); 254 255 result.add(value); 256 } 257 258 return result; 259 } 260 261 /** 262 * Returns all the values stored in the map, excluding those whose id has been marked deleted, 263 * in the order in which values were added to the map using {@link #add(Object, Object)}. 264 */ 265 266 public List getValues() 267 { 268 int deletedCount = Tapestry.size(_deletedKeys); 269 270 if (deletedCount == 0) 271 return getAllValues(); 272 273 int count = _keys.size(); 274 275 List result = new ArrayList(count - deletedCount); 276 277 for (int i = 0; i < count; i++) 278 { 279 Object key = _keys.get(i); 280 281 if (_deletedKeys.contains(key)) 282 continue; 283 284 Object value = _map.get(key); 285 result.add(value); 286 } 287 288 return result; 289 } 290 291 }