Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
ListEditMap |
|
| 2.0;2 |
1 | // Copyright 2004, 2005 The Apache Software Foundation |
|
2 | // |
|
3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
|
4 | // you may not use this file except in compliance with the License. |
|
5 | // You may obtain a copy of the License at |
|
6 | // |
|
7 | // http://www.apache.org/licenses/LICENSE-2.0 |
|
8 | // |
|
9 | // Unless required by applicable law or agreed to in writing, software |
|
10 | // distributed under the License is distributed on an "AS IS" BASIS, |
|
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
12 | // See the License for the specific language governing permissions and |
|
13 | // limitations under the License. |
|
14 | ||
15 | package org.apache.tapestry.form; |
|
16 | ||
17 | import java.util.ArrayList; |
|
18 | import java.util.Collections; |
|
19 | import java.util.HashMap; |
|
20 | import java.util.HashSet; |
|
21 | import java.util.List; |
|
22 | import java.util.Map; |
|
23 | import java.util.Set; |
|
24 | ||
25 | import org.apache.tapestry.Tapestry; |
|
26 | ||
27 | /** |
|
28 | * A utility class often used with the {@link org.apache.tapestry.form.ListEdit} component. A |
|
29 | * ListEditMap is loaded with data objects before the ListEdit renders, and again before the |
|
30 | * ListEdit rewinds. This streamlines the synchronization of the form against data on the server. It |
|
31 | * is most useful when the set of objects is of a manageable size (say, no more than a few hundred |
|
32 | * objects). |
|
33 | * <p> |
|
34 | * The map stores a list of keys, and relates each key to a value. It also tracks a deleted flag for |
|
35 | * each key. |
|
36 | * <p> |
|
37 | * Usage: <br> |
|
38 | * The page or component should implement {@link org.apache.tapestry.event.PageBeginRenderListener} |
|
39 | * and implement |
|
40 | * {@link org.apache.tapestry.event.PageBeginRenderListener#pageBeginRender(org.apache.tapestry.event.PageEvent)} |
|
41 | * to initialize the map. |
|
42 | * <p> |
|
43 | * The external data (from which keys and values are obtained) is queried, and each key/value pair |
|
44 | * is {@link #add(Object, Object) added} to the map, in the order that items should be presented. |
|
45 | * <p> |
|
46 | * The {@link org.apache.tapestry.form.ListEdit}'s source parameter should be bound to the map's |
|
47 | * {@link #getKeys() keys} property. The value parameter should be bound to the map's |
|
48 | * {@link #setKey(Object) key} property. |
|
49 | * <p> |
|
50 | * The {@link org.apache.tapestry.form.ListEdit}'s listener parameter should be bound to a listener |
|
51 | * method to synchronize a property of the component from the map. <code> |
|
52 | * public void synchronize() |
|
53 | * { |
|
54 | * ListEditMap map = ...; |
|
55 | * <i>Type</i> object = (<i>Type</i>)map.getValue(); |
|
56 | * |
|
57 | * if (object == null) |
|
58 | * ... |
|
59 | * |
|
60 | * set<i>Property</i>(object); |
|
61 | * } |
|
62 | * </code> |
|
63 | * <p> |
|
64 | * You may also connect a {@link org.apache.tapestry.form.Checkbox}'s selected parameter to the |
|
65 | * map's {@link #isDeleted() deleted} property. |
|
66 | * <p> |
|
67 | * You may track inclusion in other sets by subclassing ListEditMap and implementing new boolean |
|
68 | * properties. The accessor method should be a call to {@link #checkSet(Set)} and the mutator method |
|
69 | * should be a call to {@link #updateSet(Set, boolean)}. |
|
70 | * |
|
71 | * @author Howard Lewis Ship |
|
72 | * @since 3.0 |
|
73 | */ |
|
74 | ||
75 | 8 | public class ListEditMap |
76 | { |
|
77 | 8 | private Map _map = new HashMap(); |
78 | ||
79 | 8 | private List _keys = new ArrayList(); |
80 | ||
81 | private Set _deletedKeys; |
|
82 | ||
83 | private Object _currentKey; |
|
84 | ||
85 | /** |
|
86 | * Records the key and value into this map. The keys may be obtained, in the order in which they |
|
87 | * are added, using {@link #getKeys()}. This also sets the current key (so that you may invoke |
|
88 | * {@link #setDeleted(boolean)}, for example). |
|
89 | */ |
|
90 | ||
91 | public void add(Object key, Object value) |
|
92 | { |
|
93 | 24 | _currentKey = key; |
94 | ||
95 | 24 | _keys.add(_currentKey); |
96 | 24 | _map.put(_currentKey, value); |
97 | 24 | } |
98 | ||
99 | /** |
|
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 | 3 | 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 | 9 | _currentKey = key; |
117 | 9 | } |
118 | ||
119 | /** |
|
120 | * Returns the current key within the map. |
|
121 | */ |
|
122 | ||
123 | public Object getKey() |
|
124 | { |
|
125 | 2 | 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 | 2 | 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 | 6 | 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 | 6 | if (set == null) |
156 | 3 | return false; |
157 | ||
158 | 3 | 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 | 8 | _deletedKeys = updateSet(_deletedKeys, value); |
168 | 8 | } |
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 | 8 | Set updatedSet = set; |
179 | 8 | if (value) |
180 | { |
|
181 | 6 | if (updatedSet == null) |
182 | 4 | updatedSet = new HashSet(); |
183 | ||
184 | 6 | updatedSet.add(_currentKey); |
185 | } |
|
186 | else |
|
187 | { |
|
188 | 2 | if (updatedSet != null) |
189 | 1 | updatedSet.remove(_currentKey); |
190 | } |
|
191 | ||
192 | 8 | return updatedSet; |
193 | } |
|
194 | ||
195 | /** |
|
196 | * Returns the deleted keys in an unspecified order. Returns a List, which may be empty if no |
|
197 | * keys have been deleted. |
|
198 | */ |
|
199 | ||
200 | public List getDeletedKeys() |
|
201 | { |
|
202 | 4 | return convertSetToList(_deletedKeys); |
203 | } |
|
204 | ||
205 | /** |
|
206 | * Removes keys and values that are in the set of deleted keys, then clears the set of deleted |
|
207 | * keys. After invoking this method, {@link #getValues()} and {@link #getAllValues()} will |
|
208 | * return equivalent lists and {@link #getKeys()} will no longer show any of the deleted keys. |
|
209 | * Note that this method <em>does not</em> change the current key. Subclasses that track |
|
210 | * additional key sets may want to override this method to remove deleted keys from those key |
|
211 | * sets. |
|
212 | */ |
|
213 | ||
214 | public void purgeDeletedKeys() |
|
215 | { |
|
216 | 2 | if (_deletedKeys == null) |
217 | 1 | return; |
218 | ||
219 | 1 | _map.keySet().removeAll(_deletedKeys); |
220 | 1 | _keys.removeAll(_deletedKeys); |
221 | ||
222 | 1 | _deletedKeys = null; |
223 | 1 | } |
224 | ||
225 | /** |
|
226 | * Invoked to convert a set into a List. |
|
227 | * |
|
228 | * @param set |
|
229 | * a set (which may be empty or null) |
|
230 | * @return a list (possibly empty) of the items in the set |
|
231 | */ |
|
232 | ||
233 | protected List convertSetToList(Set set) |
|
234 | { |
|
235 | 4 | if (Tapestry.isEmpty(set)) |
236 | 2 | return Collections.EMPTY_LIST; |
237 | ||
238 | 2 | return new ArrayList(set); |
239 | } |
|
240 | ||
241 | /** |
|
242 | * Returns all the values stored in the map, in the order in which values were added to the map |
|
243 | * using {@link #add(Object, Object)}. |
|
244 | */ |
|
245 | ||
246 | public List getAllValues() |
|
247 | { |
|
248 | 5 | int count = _keys.size(); |
249 | 5 | List result = new ArrayList(count); |
250 | ||
251 | 18 | for (int i = 0; i < count; i++) |
252 | { |
|
253 | 13 | Object key = _keys.get(i); |
254 | 13 | Object value = _map.get(key); |
255 | ||
256 | 13 | result.add(value); |
257 | } |
|
258 | ||
259 | 5 | return result; |
260 | } |
|
261 | ||
262 | /** |
|
263 | * Returns all the values stored in the map, excluding those whose id has been marked deleted, |
|
264 | * in the order in which values were added to the map using {@link #add(Object, Object)}. |
|
265 | */ |
|
266 | ||
267 | public List getValues() |
|
268 | { |
|
269 | 4 | int deletedCount = Tapestry.size(_deletedKeys); |
270 | ||
271 | 4 | if (deletedCount == 0) |
272 | 1 | return getAllValues(); |
273 | ||
274 | 3 | int count = _keys.size(); |
275 | ||
276 | 3 | List result = new ArrayList(count - deletedCount); |
277 | ||
278 | 12 | for (int i = 0; i < count; i++) |
279 | { |
|
280 | 9 | Object key = _keys.get(i); |
281 | ||
282 | 9 | if (_deletedKeys.contains(key)) |
283 | 4 | continue; |
284 | ||
285 | 5 | Object value = _map.get(key); |
286 | 5 | result.add(value); |
287 | } |
|
288 | ||
289 | 3 | return result; |
290 | } |
|
291 | ||
292 | } |