Clover coverage report - Code Coverage for tapestry release 4.0-beta-12
Coverage timestamp: Sun Oct 30 2005 16:22:01 EST
file stats: LOC: 314   Methods: 16
NCLOC: 128   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
DefaultPrimaryKeyConverter.java 86.4% 98.1% 100% 95.7%
coverage coverage
 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    public class DefaultPrimaryKeyConverter implements IPrimaryKeyConverter
 54    {
 55    private final Map _map = new HashMap();
 56   
 57    private final List _keys = new ArrayList();
 58   
 59    // The values added to the Map, in the order they were added.
 60    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  1 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    }
 81   
 82  17 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    }
 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  8 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  7 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).l
 128    */
 129  8 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  3 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  3 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  5 public final void setDeleted(boolean deleted)
 162    {
 163  5 _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  5 protected final Set updateValueSetForLastValue(Set set, boolean inSet)
 179    {
 180  5 if (inSet)
 181    {
 182  4 if (set == null)
 183  4 set = new HashSet();
 184   
 185  4 set.add(_lastValue);
 186   
 187  4 return set;
 188    }
 189   
 190  1 if (set != null)
 191  1 set.remove(_lastValue);
 192   
 193  1 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  9 public final Object getLastValue()
 205    {
 206  9 return _lastValue;
 207    }
 208   
 209    /**
 210    * Returns an unmodifiable set of all values marked as deleted.
 211    */
 212   
 213  6 public final Set getDeletedValues()
 214    {
 215  6 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  6 protected final Set createUnmodifiableSet(Set valueSet)
 227    {
 228  6 return valueSet == null ? Collections.emptySet() : 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  1 public final void removeDeletedValues()
 237    {
 238  1 _lastValue = null;
 239   
 240  1 if (isDeletedValuesEmpty())
 241  0 return;
 242   
 243  1 int count = _keys.size();
 244   
 245  1 for (int i = count - 1; i >= 0; i--)
 246    {
 247  2 if (_deletedValues.contains(_values.get(i)))
 248    {
 249  1 _values.remove(i);
 250  1 Object key = _keys.remove(i);
 251   
 252  1 _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  4 public final Object getPrimaryKey(Object value)
 268    {
 269  4 int index = _values.indexOf(value);
 270   
 271  4 if (index < 0)
 272  1 throw new ApplicationRuntimeException(UtilMessages.valueNotFound(value), value, null,
 273    null);
 274   
 275  3 _lastValue = value;
 276   
 277  3 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  7 public final Object getValue(Object primaryKey)
 288    {
 289  7 Object result = _map.get(primaryKey);
 290   
 291  7 if (result == null)
 292  3 result = provideMissingValue(primaryKey);
 293   
 294  6 _lastValue = result;
 295   
 296  6 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  1 protected Object provideMissingValue(Object key)
 310    {
 311  1 return null;
 312    }
 313   
 314    }