Coverage Report - org.apache.tapestry.util.DefaultPrimaryKeyConverter
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultPrimaryKeyConverter
0%
0/63
0%
0/30
2.125
 
 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  0
 public class DefaultPrimaryKeyConverter implements IPrimaryKeyConverter
 54  
 {
 55  0
     private final Map _map = new HashMap();
 56  
 
 57  0
     private final List _keys = new ArrayList();
 58  
 
 59  
     // The values added to the Map, in the order they were added.
 60  0
     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  0
         _map.clear();
 76  0
         _keys.clear();
 77  0
         _values.clear();
 78  0
         _lastValue = null;
 79  0
         _deletedValues = null;
 80  0
     }
 81  
 
 82  
     public final void add(Object key, Object value)
 83  
     {
 84  0
         Defense.notNull(key, "key");
 85  0
         Defense.notNull(value, "value");
 86  
 
 87  0
         if (_map.containsKey(key))
 88  0
             throw new ApplicationRuntimeException(UtilMessages.keyAlreadyExists(key));
 89  
 
 90  0
         _map.put(key, value);
 91  
 
 92  0
         _keys.add(key);
 93  0
         _values.add(value);
 94  
 
 95  0
         _lastValue = value;
 96  0
     }
 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  0
         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  0
         if (isDeletedValuesEmpty())
 117  0
             return getAllValues();
 118  
 
 119  0
         List result = new ArrayList(_values);
 120  
 
 121  0
         result.removeAll(_deletedValues);
 122  
 
 123  0
         return result;
 124  
     }
 125  
 
 126  
     /**
 127  
      * Returns true if the deleted values set is empty (or null).
 128  
      */
 129  
     private boolean isDeletedValuesEmpty()
 130  
     {
 131  0
         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  0
         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  0
         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  0
         _deletedValues = updateValueSetForLastValue(_deletedValues, deleted);
 164  0
     }
 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  0
         Set updatedSet = set;
 181  0
         if (inSet)
 182  
         {
 183  0
             if (updatedSet == null)
 184  0
                 updatedSet = new HashSet();
 185  
 
 186  0
             updatedSet.add(_lastValue);
 187  
 
 188  0
             return updatedSet;
 189  
         }
 190  
 
 191  0
         if (updatedSet != null)
 192  0
             updatedSet.remove(_lastValue);
 193  
 
 194  0
         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  0
         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  0
         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  0
         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  0
         _lastValue = null;
 240  
 
 241  0
         if (isDeletedValuesEmpty())
 242  0
             return;
 243  
 
 244  0
         int count = _keys.size();
 245  
 
 246  0
         for (int i = count - 1; i >= 0; i--)
 247  
         {
 248  0
             if (_deletedValues.contains(_values.get(i)))
 249  
             {
 250  0
                 _values.remove(i);
 251  0
                 Object key = _keys.remove(i);
 252  
 
 253  0
                 _map.remove(key);
 254  
             }
 255  
         }
 256  0
     }
 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  0
         int index = _values.indexOf(value);
 271  
 
 272  0
         if (index < 0)
 273  0
             throw new ApplicationRuntimeException(UtilMessages.valueNotFound(value), value, null,
 274  
                     null);
 275  
 
 276  0
         _lastValue = value;
 277  
 
 278  0
         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  0
         Object result = _map.get(primaryKey);
 291  
 
 292  0
         if (result == null)
 293  0
             result = provideMissingValue(primaryKey);
 294  
 
 295  0
         _lastValue = result;
 296  
 
 297  0
         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  0
         return null;
 313  
     }
 314  
 
 315  
 }