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 }