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).l
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            if (inSet)
181            {
182                if (set == null)
183                    set = new HashSet();
184    
185                set.add(_lastValue);
186    
187                return set;
188            }
189    
190            if (set != null)
191                set.remove(_lastValue);
192    
193            return set;
194        }
195    
196        /**
197         * Returns the last active value; this is the value passed to {@link #getPrimaryKey(Object)} or
198         * the value for the key passed to {@link #getValue(Object)}.
199         * <p>
200         * Maintaining <em>value sets</em> involves adding or removing the active value from a set.
201         * 
202         * @return the last active object
203         */
204        public final Object getLastValue()
205        {
206            return _lastValue;
207        }
208    
209        /**
210         * Returns an unmodifiable set of all values marked as deleted.
211         */
212    
213        public final Set getDeletedValues()
214        {
215            return createUnmodifiableSet(_deletedValues);
216        }
217    
218        /**
219         * Converts a value set into a returnable value; null is converted to the empty set, and
220         * non-null is wrapped as unmodifiable.
221         * 
222         * @param valueSet
223         *            the set to convert and return
224         * @return a non-null, non-modifiable Set
225         */
226        protected final Set createUnmodifiableSet(Set valueSet)
227        {
228            return valueSet == null ? Collections.EMPTY_SET : Collections.unmodifiableSet(valueSet);
229        }
230    
231        /**
232         * Iterates over the keys and values, removing any values (and corresponding keys) that that are
233         * in the deleted set. After invoking this, {@link #getAllValues()} will be the same as
234         * {@link #getValues()}.
235         */
236        public final void removeDeletedValues()
237        {
238            _lastValue = null;
239    
240            if (isDeletedValuesEmpty())
241                return;
242    
243            int count = _keys.size();
244    
245            for (int i = count - 1; i >= 0; i--)
246            {
247                if (_deletedValues.contains(_values.get(i)))
248                {
249                    _values.remove(i);
250                    Object key = _keys.remove(i);
251    
252                    _map.remove(key);
253                }
254            }
255        }
256    
257        /**
258         * Gets the primary key of an object previously stored in this converter.
259         * 
260         * @param value
261         *            an object previously stored in the converter
262         * @return the corresponding key used to store the object
263         * @throws ApplicationRuntimeException
264         *             if the value was not previously stored
265         * @see #add(Object, Object)
266         */
267        public final Object getPrimaryKey(Object value)
268        {
269            int index = _values.indexOf(value);
270    
271            if (index < 0)
272                throw new ApplicationRuntimeException(UtilMessages.valueNotFound(value), value, null,
273                        null);
274    
275            _lastValue = value;
276    
277            return _keys.get(index);
278        }
279    
280        /**
281         * Given a primary key, locates the corresponding object. May invoke
282         * {@link #provideMissingValue(Object)} if no such key has been stored into the converter.
283         * 
284         * @return the object if the key is found, or null otherwise.
285         * @see #add(Object, Object)
286         */
287        public final Object getValue(Object primaryKey)
288        {
289            Object result = _map.get(primaryKey);
290    
291            if (result == null)
292                result = provideMissingValue(primaryKey);
293    
294            _lastValue = result;
295    
296            return result;
297        }
298    
299        /**
300         * Invoked by {@link #getValue(Object)} when the key is not found in the converter's map.
301         * Subclasses may override this method to either obtain the corresponding object from secondary
302         * storage, to throw an exception, or to provide a new object instance. This implementation
303         * returns null.
304         * 
305         * @param key
306         *            the key for which an object was requested
307         * @return the object for the key, or null if no object may can be provided
308         */
309        protected Object provideMissingValue(Object key)
310        {
311            return null;
312        }
313    
314    }