001    // Copyright 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.util;
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.hivemind.ApplicationRuntimeException;
026    import org.apache.hivemind.util.Defense;
027    import org.apache.tapestry.components.IPrimaryKeyConverter;
028    
029    /**
030     * Companion to the {@link org.apache.tapestry.components.ForBean For component}, this class is an
031     * implementation of {@link org.apache.tapestry.components.IPrimaryKeyConverter} that performs some
032     * additional handling, such as tracking value sets..
033     * <p>
034     * Value sets are sets of value objects maintained by the converter; the converter will provide a
035     * synthetic read/write boolean property that indicates if the {@link #getLastValue() last value} is
036     * or is not in the set.
037     * <p>
038     * A single built-in value set, {@link #isDeleted()} has a special purpose; it controls whether or
039     * not values are returned from {@link #getValues()}. Subclasses may add additional synthetic
040     * boolean properties and additional sets.
041     * <p>
042     * Why not just store a boolean property in the object itself? Well, deleted is a good example of a
043     * property that is meaningful in the context of an operation, but isn't stored ... once an object
044     * is deleted (from secondary storage, such as a database) there's no place to store such a flag.
045     * The DefaultPrimaryKey converter is used in this context to store transient, operation data ...
046     * such as which values are to be deleted.
047     * <p>
048     * This class can be thought of as a successor to {@link org.apache.tapestry.form.ListEditMap}.
049     * 
050     * @author Howard M. Lewis Ship
051     * @since 4.0
052     */
053    public class DefaultPrimaryKeyConverter implements IPrimaryKeyConverter
054    {
055        private final Map _map = new HashMap();
056    
057        private final List _keys = new ArrayList();
058    
059        // The values added to the Map, in the order they were added.
060        private final List _values = new ArrayList();
061    
062        // The last value accessed by getPrimaryKey() or getValue().
063        // Other methods may operate upon this value.
064    
065        private Object _lastValue;
066    
067        private Set _deletedValues;
068    
069        /**
070         * Clears all properties of the converter, returning it to a pristine state. Subclasses should
071         * invoke this implementation in addition to clearing any of their own state.
072         */
073        public void clear()
074        {
075            _map.clear();
076            _keys.clear();
077            _values.clear();
078            _lastValue = null;
079            _deletedValues = null;
080        }
081    
082        public final void add(Object key, Object value)
083        {
084            Defense.notNull(key, "key");
085            Defense.notNull(value, "value");
086    
087            if (_map.containsKey(key))
088                throw new ApplicationRuntimeException(UtilMessages.keyAlreadyExists(key));
089    
090            _map.put(key, value);
091    
092            _keys.add(key);
093            _values.add(value);
094    
095            _lastValue = value;
096        }
097    
098        /**
099         * 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            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            if (isDeletedValuesEmpty())
117                return getAllValues();
118    
119            List result = new ArrayList(_values);
120    
121            result.removeAll(_deletedValues);
122    
123            return result;
124        }
125    
126        /**
127         * Returns true if the deleted values set is empty (or null).
128         */
129        private boolean isDeletedValuesEmpty()
130        {
131            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            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            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            _deletedValues = updateValueSetForLastValue(_deletedValues, deleted);
164        }
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            Set updatedSet = set;
181            if (inSet)
182            {
183                if (updatedSet == null)
184                    updatedSet = new HashSet();
185    
186                updatedSet.add(_lastValue);
187    
188                return updatedSet;
189            }
190    
191            if (updatedSet != null)
192                updatedSet.remove(_lastValue);
193    
194            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            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            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            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            _lastValue = null;
240    
241            if (isDeletedValuesEmpty())
242                return;
243    
244            int count = _keys.size();
245    
246            for (int i = count - 1; i >= 0; i--)
247            {
248                if (_deletedValues.contains(_values.get(i)))
249                {
250                    _values.remove(i);
251                    Object key = _keys.remove(i);
252    
253                    _map.remove(key);
254                }
255            }
256        }
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            int index = _values.indexOf(value);
271    
272            if (index < 0)
273                throw new ApplicationRuntimeException(UtilMessages.valueNotFound(value), value, null,
274                        null);
275    
276            _lastValue = value;
277    
278            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            Object result = _map.get(primaryKey);
291    
292            if (result == null)
293                result = provideMissingValue(primaryKey);
294    
295            _lastValue = result;
296    
297            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            return null;
313        }
314    
315    }