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 015package org.apache.tapestry.util; 016 017import java.util.ArrayList; 018import java.util.Collections; 019import java.util.HashMap; 020import java.util.HashSet; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024 025import org.apache.hivemind.ApplicationRuntimeException; 026import org.apache.hivemind.util.Defense; 027import 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 */ 053public 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}