Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
DefaultPrimaryKeyConverter |
|
| 1.9375;1.938 |
1 | // Copyright 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.util; |
|
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.hivemind.ApplicationRuntimeException; |
|
26 | import org.apache.hivemind.util.Defense; |
|
27 | import org.apache.tapestry.components.IPrimaryKeyConverter; |
|
28 | ||
29 | /** |
|
30 | * Companion to the {@link org.apache.tapestry.components.ForBean For component}, this class is an |
|
31 | * implementation of {@link org.apache.tapestry.components.IPrimaryKeyConverter} that performs some |
|
32 | * additional handling, such as tracking value sets.. |
|
33 | * <p> |
|
34 | * Value sets are sets of value objects maintained by the converter; the converter will provide a |
|
35 | * synthetic read/write boolean property that indicates if the {@link #getLastValue() last value} is |
|
36 | * or is not in the set. |
|
37 | * <p> |
|
38 | * A single built-in value set, {@link #isDeleted()} has a special purpose; it controls whether or |
|
39 | * not values are returned from {@link #getValues()}. Subclasses may add additional synthetic |
|
40 | * boolean properties and additional sets. |
|
41 | * <p> |
|
42 | * Why not just store a boolean property in the object itself? Well, deleted is a good example of a |
|
43 | * property that is meaningful in the context of an operation, but isn't stored ... once an object |
|
44 | * is deleted (from secondary storage, such as a database) there's no place to store such a flag. |
|
45 | * The DefaultPrimaryKey converter is used in this context to store transient, operation data ... |
|
46 | * such as which values are to be deleted. |
|
47 | * <p> |
|
48 | * This class can be thought of as a successor to {@link org.apache.tapestry.form.ListEditMap}. |
|
49 | * |
|
50 | * @author Howard M. Lewis Ship |
|
51 | * @since 4.0 |
|
52 | */ |
|
53 | 13 | public class DefaultPrimaryKeyConverter implements IPrimaryKeyConverter |
54 | { |
|
55 | 13 | private final Map _map = new HashMap(); |
56 | ||
57 | 13 | private final List _keys = new ArrayList(); |
58 | ||
59 | // The values added to the Map, in the order they were added. |
|
60 | 13 | private final List _values = new ArrayList(); |
61 | ||
62 | // The last value accessed by getPrimaryKey() or getValue(). |
|
63 | // Other methods may operate upon this value. |
|
64 | ||
65 | private Object _lastValue; |
|
66 | ||
67 | private Set _deletedValues; |
|
68 | ||
69 | /** |
|
70 | * Clears all properties of the converter, returning it to a pristine state. Subclasses should |
|
71 | * invoke this implementation in addition to clearing any of their own state. |
|
72 | */ |
|
73 | public void clear() |
|
74 | { |
|
75 | 1 | _map.clear(); |
76 | 1 | _keys.clear(); |
77 | 1 | _values.clear(); |
78 | 1 | _lastValue = null; |
79 | 1 | _deletedValues = null; |
80 | 1 | } |
81 | ||
82 | public final void add(Object key, Object value) |
|
83 | { |
|
84 | 17 | Defense.notNull(key, "key"); |
85 | 17 | Defense.notNull(value, "value"); |
86 | ||
87 | 17 | if (_map.containsKey(key)) |
88 | 1 | throw new ApplicationRuntimeException(UtilMessages.keyAlreadyExists(key)); |
89 | ||
90 | 16 | _map.put(key, value); |
91 | ||
92 | 16 | _keys.add(key); |
93 | 16 | _values.add(value); |
94 | ||
95 | 16 | _lastValue = value; |
96 | 16 | } |
97 | ||
98 | /** |
|
99 | * Returns a unmodifiable list of values stored into the converter, in the order in which they |
|
100 | * were stored. |
|
101 | * |
|
102 | * @return an unmodifiable List |
|
103 | * @see #add(Object, Object) |
|
104 | */ |
|
105 | public final List getAllValues() |
|
106 | { |
|
107 | 8 | return Collections.unmodifiableList(_values); |
108 | } |
|
109 | ||
110 | /** |
|
111 | * Returns a list of all values stored into the converter, with deleted values removed. |
|
112 | */ |
|
113 | ||
114 | public final List getValues() |
|
115 | { |
|
116 | 7 | if (isDeletedValuesEmpty()) |
117 | 4 | return getAllValues(); |
118 | ||
119 | 3 | List result = new ArrayList(_values); |
120 | ||
121 | 3 | result.removeAll(_deletedValues); |
122 | ||
123 | 3 | return result; |
124 | } |
|
125 | ||
126 | /** |
|
127 | * Returns true if the deleted values set is empty (or null). |
|
128 | */ |
|
129 | private boolean isDeletedValuesEmpty() |
|
130 | { |
|
131 | 8 | return _deletedValues == null || _deletedValues.isEmpty(); |
132 | } |
|
133 | ||
134 | /** |
|
135 | * Checks to see if the {@link #getLastValue() last value} is, or is not, in the set of deleted |
|
136 | * values. |
|
137 | */ |
|
138 | public final boolean isDeleted() |
|
139 | { |
|
140 | 3 | return checkValueSetForLastValue(_deletedValues); |
141 | } |
|
142 | ||
143 | /** |
|
144 | * Checks the set to see if it contains the {@link #getLastValue() last value}. |
|
145 | * |
|
146 | * @param valueSet |
|
147 | * the set to check, which may be null |
|
148 | * @return true if the last value is in the set (if non-null) |
|
149 | */ |
|
150 | protected final boolean checkValueSetForLastValue(Set valueSet) |
|
151 | { |
|
152 | 3 | return valueSet != null && valueSet.contains(_lastValue); |
153 | } |
|
154 | ||
155 | /** |
|
156 | * Adds or removes the {@link #getLastValue() last value} from the |
|
157 | * {@link #getDeletedValues() deleted values set}. |
|
158 | * |
|
159 | * @param deleted |
|
160 | */ |
|
161 | public final void setDeleted(boolean deleted) |
|
162 | { |
|
163 | 5 | _deletedValues = updateValueSetForLastValue(_deletedValues, deleted); |
164 | 5 | } |
165 | ||
166 | /** |
|
167 | * Updates a value set to add or remove the {@link #getLastValue() last value} to the set. The |
|
168 | * logic here will create and return a new Set instance if necessary (that is, if inSet is true |
|
169 | * and set is null). The point is to defer the creation of the set until its actually needed. |
|
170 | * |
|
171 | * @param set |
|
172 | * the set to update, which may be null |
|
173 | * @param inSet |
|
174 | * if true, the last value will be added to the set (creating the set as necessary); |
|
175 | * if false, the last value will be removed |
|
176 | * @return the set passed in, or a new Set instance |
|
177 | */ |
|
178 | protected final Set updateValueSetForLastValue(Set set, boolean inSet) |
|
179 | { |
|
180 | 5 | Set updatedSet = set; |
181 | 5 | if (inSet) |
182 | { |
|
183 | 4 | if (updatedSet == null) |
184 | 4 | updatedSet = new HashSet(); |
185 | ||
186 | 4 | updatedSet.add(_lastValue); |
187 | ||
188 | 4 | return updatedSet; |
189 | } |
|
190 | ||
191 | 1 | if (updatedSet != null) |
192 | 1 | updatedSet.remove(_lastValue); |
193 | ||
194 | 1 | return updatedSet; |
195 | } |
|
196 | ||
197 | /** |
|
198 | * Returns the last active value; this is the value passed to {@link #getPrimaryKey(Object)} or |
|
199 | * the value for the key passed to {@link #getValue(Object)}. |
|
200 | * <p> |
|
201 | * Maintaining <em>value sets</em> involves adding or removing the active value from a set. |
|
202 | * |
|
203 | * @return the last active object |
|
204 | */ |
|
205 | public final Object getLastValue() |
|
206 | { |
|
207 | 9 | return _lastValue; |
208 | } |
|
209 | ||
210 | /** |
|
211 | * Returns an unmodifiable set of all values marked as deleted. |
|
212 | */ |
|
213 | ||
214 | public final Set getDeletedValues() |
|
215 | { |
|
216 | 6 | return createUnmodifiableSet(_deletedValues); |
217 | } |
|
218 | ||
219 | /** |
|
220 | * Converts a value set into a returnable value; null is converted to the empty set, and |
|
221 | * non-null is wrapped as unmodifiable. |
|
222 | * |
|
223 | * @param valueSet |
|
224 | * the set to convert and return |
|
225 | * @return a non-null, non-modifiable Set |
|
226 | */ |
|
227 | protected final Set createUnmodifiableSet(Set valueSet) |
|
228 | { |
|
229 | 6 | return valueSet == null ? Collections.EMPTY_SET : Collections.unmodifiableSet(valueSet); |
230 | } |
|
231 | ||
232 | /** |
|
233 | * Iterates over the keys and values, removing any values (and corresponding keys) that that are |
|
234 | * in the deleted set. After invoking this, {@link #getAllValues()} will be the same as |
|
235 | * {@link #getValues()}. |
|
236 | */ |
|
237 | public final void removeDeletedValues() |
|
238 | { |
|
239 | 1 | _lastValue = null; |
240 | ||
241 | 1 | if (isDeletedValuesEmpty()) |
242 | 0 | return; |
243 | ||
244 | 1 | int count = _keys.size(); |
245 | ||
246 | 3 | for (int i = count - 1; i >= 0; i--) |
247 | { |
|
248 | 2 | if (_deletedValues.contains(_values.get(i))) |
249 | { |
|
250 | 1 | _values.remove(i); |
251 | 1 | Object key = _keys.remove(i); |
252 | ||
253 | 1 | _map.remove(key); |
254 | } |
|
255 | } |
|
256 | 1 | } |
257 | ||
258 | /** |
|
259 | * Gets the primary key of an object previously stored in this converter. |
|
260 | * |
|
261 | * @param value |
|
262 | * an object previously stored in the converter |
|
263 | * @return the corresponding key used to store the object |
|
264 | * @throws ApplicationRuntimeException |
|
265 | * if the value was not previously stored |
|
266 | * @see #add(Object, Object) |
|
267 | */ |
|
268 | public final Object getPrimaryKey(Object value) |
|
269 | { |
|
270 | 4 | int index = _values.indexOf(value); |
271 | ||
272 | 4 | if (index < 0) |
273 | 1 | throw new ApplicationRuntimeException(UtilMessages.valueNotFound(value), value, null, |
274 | null); |
|
275 | ||
276 | 3 | _lastValue = value; |
277 | ||
278 | 3 | return _keys.get(index); |
279 | } |
|
280 | ||
281 | /** |
|
282 | * Given a primary key, locates the corresponding object. May invoke |
|
283 | * {@link #provideMissingValue(Object)} if no such key has been stored into the converter. |
|
284 | * |
|
285 | * @return the object if the key is found, or null otherwise. |
|
286 | * @see #add(Object, Object) |
|
287 | */ |
|
288 | public final Object getValue(Object primaryKey) |
|
289 | { |
|
290 | 7 | Object result = _map.get(primaryKey); |
291 | ||
292 | 7 | if (result == null) |
293 | 3 | result = provideMissingValue(primaryKey); |
294 | ||
295 | 6 | _lastValue = result; |
296 | ||
297 | 6 | return result; |
298 | } |
|
299 | ||
300 | /** |
|
301 | * Invoked by {@link #getValue(Object)} when the key is not found in the converter's map. |
|
302 | * Subclasses may override this method to either obtain the corresponding object from secondary |
|
303 | * storage, to throw an exception, or to provide a new object instance. This implementation |
|
304 | * returns null. |
|
305 | * |
|
306 | * @param key |
|
307 | * the key for which an object was requested |
|
308 | * @return the object for the key, or null if no object may can be provided |
|
309 | */ |
|
310 | protected Object provideMissingValue(Object key) |
|
311 | { |
|
312 | 1 | return null; |
313 | } |
|
314 | ||
315 | } |