Clover coverage report - Code Coverage for tapestry release 4.0-beta-5
Coverage timestamp: Fri Aug 26 2005 21:16:17 EDT
file stats: LOC: 500   Methods: 16
NCLOC: 274   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
ForBean.java 75% 87.7% 81.2% 83%
coverage coverage
 1    // Copyright 2004, 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.components;
 16   
 17    import java.util.ArrayList;
 18    import java.util.Collections;
 19    import java.util.HashMap;
 20    import java.util.Iterator;
 21    import java.util.List;
 22    import java.util.Map;
 23   
 24    import org.apache.tapestry.IBinding;
 25    import org.apache.tapestry.IForm;
 26    import org.apache.tapestry.IMarkupWriter;
 27    import org.apache.tapestry.IRequestCycle;
 28    import org.apache.tapestry.Tapestry;
 29    import org.apache.tapestry.TapestryUtils;
 30    import org.apache.tapestry.coerce.ValueConverter;
 31    import org.apache.tapestry.form.AbstractFormComponent;
 32    import org.apache.tapestry.services.DataSqueezer;
 33    import org.apache.tapestry.services.ExpressionEvaluator;
 34   
 35    /**
 36    * @author mb
 37    */
 38    public abstract class ForBean extends AbstractFormComponent {
 39    private static final char DESC_VALUE = 'V';
 40    private static final char DESC_PRIMARY_KEY = 'P';
 41   
 42    // parameters
 43    public abstract Object getSource();
 44    public abstract Object getFullSource();
 45    public abstract String getElement();
 46    public abstract boolean getVolatile();
 47    public abstract Object getDefaultValue();
 48    public abstract String getPrimaryKey();
 49    public abstract IPrimaryKeyConverter getConverter();
 50    public abstract String getKeyExpression();
 51   
 52    // properties
 53    public abstract Map getPrimaryKeyMap();
 54    public abstract void setPrimaryKeyMap(Map primaryKeys);
 55   
 56    public abstract List getSourcePrimaryKeys();
 57    public abstract void setSourcePrimaryKeys(List sourcePrimaryKeys);
 58   
 59    public abstract List getSavedSourceData();
 60    public abstract void setSavedSourceData(List sourceData);
 61   
 62    public abstract Iterator getFullSourceIterator();
 63    public abstract void setFullSourceIterator(Iterator fullSourceIterator);
 64   
 65    // injects
 66    public abstract DataSqueezer getDataSqueezer();
 67    public abstract ValueConverter getValueConverter();
 68    public abstract ExpressionEvaluator getExpressionEvaluator();
 69   
 70   
 71    private Object _value;
 72    private int _index;
 73    private boolean _rendering;
 74   
 75    /**
 76    * Gets the source binding and iterates through
 77    * its values. For each, it updates the value binding and render's its wrapped elements.
 78    *
 79    **/
 80   
 81  15 protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
 82    {
 83    // Clear the cache between rewind and render.
 84    // This allows the value of 'source' to be changed by the form listeners.
 85  15 setSavedSourceData(null);
 86   
 87    // form may be null if component is not located in a form
 88  15 IForm form = (IForm) cycle.getAttribute(TapestryUtils.FORM_ATTRIBUTE);
 89   
 90    // If the cycle is rewinding, but not this particular form,
 91    // then do nothing (don't even render the body).
 92  15 boolean cycleRewinding = cycle.isRewinding();
 93  15 if (cycleRewinding && form != null && !form.isRewinding())
 94  0 return;
 95   
 96    // Get the data to be iterated upon. Store in form if needed.
 97  15 Iterator dataSource = getData(cycle, form);
 98   
 99    // Do not iterate if dataSource is null.
 100    // The dataSource was either not convertable to Iterator, or was empty.
 101  15 if (dataSource == null)
 102  0 return;
 103   
 104  15 String element = getElement();
 105   
 106    // Perform the iterations
 107  15 try
 108    {
 109  15 _index = 0;
 110  15 _rendering = true;
 111   
 112  15 while (dataSource.hasNext())
 113    {
 114    // Get current value
 115  34 _value = dataSource.next();
 116   
 117    // Update output component parameters
 118  34 updateOutputParameters();
 119   
 120    // Render component
 121  34 if (element != null)
 122    {
 123  12 writer.begin(element);
 124  12 renderInformalParameters(writer, cycle);
 125    }
 126   
 127  34 renderBody(writer, cycle);
 128   
 129  34 if (element != null)
 130  12 writer.end();
 131   
 132  34 _index++;
 133    }
 134    }
 135    finally
 136    {
 137  15 _rendering = false;
 138  15 _value = null;
 139    }
 140    }
 141   
 142   
 143    /**
 144    * Returns a list with the values to be iterated upon.
 145    *
 146    * The list is obtained in different ways:
 147    * - If the component is not located in a form or 'volatile' is set to true,
 148    * then the simply the values passed to 'source' are returned (same as Foreach)
 149    * - If the component is in a form, and the form is rewinding, the values stored
 150    * in the form are returned -- rewind is then always the same as render.
 151    * - If the component is in a form, and the form is being rendered, the values
 152    * are stored in the form as Hidden fields.
 153    *
 154    * @param cycle The current request cycle
 155    * @param form The form within which the component is located (if any)
 156    * @return An iterator with the values to be cycled upon
 157    **/
 158  15 private Iterator getData(IRequestCycle cycle, IForm form) {
 159  15 if (form == null || getVolatile())
 160  6 return getSourceData().iterator();
 161   
 162  9 String name = form.getElementId(this);
 163  9 if (cycle.isRewinding())
 164  3 return getStoredData(cycle, name);
 165  6 return storeSourceData(form, name);
 166    }
 167   
 168    /**
 169    * Returns a {@link java.util.List} containing the values provided
 170    * by the identified source binding.
 171    *
 172    * @return a list with the values to iterate upon.
 173    * null if conversion cannot be performed.
 174    **/
 175  20 protected List getSourceData()
 176    {
 177  20 List sourceData = getSavedSourceData();
 178  20 if (sourceData == null) {
 179  14 Object source = getSource();
 180  14 sourceData = (List) getValueConverter().coerceValue(source, List.class);
 181  14 setSavedSourceData(sourceData);
 182    }
 183  20 return sourceData;
 184    }
 185   
 186    /**
 187    * Returns a list of the values stored as Hidden fields in the form.
 188    * A conversion is performed if the primary key of the value is stored.
 189    *
 190    * @param cycle The current request cycle
 191    * @param name The name of the HTTP parameter whether the values
 192    * @return an iterator with the values stored in the provided Hidden fields
 193    **/
 194  3 protected Iterator getStoredData(IRequestCycle cycle, String name)
 195    {
 196  3 String[] submittedPrimaryKeys = cycle.getParameters(name);
 197  3 String pkDesc = submittedPrimaryKeys[0];
 198   
 199    // unsqueeze data
 200  3 List data = new ArrayList(submittedPrimaryKeys.length-1);
 201   
 202  3 List pks = null;
 203  3 IBinding primaryKeysBinding = getBinding("primaryKeys");
 204  3 if (primaryKeysBinding != null)
 205  0 pks = new ArrayList(submittedPrimaryKeys.length-1);
 206  3 for (int i = 1; i < submittedPrimaryKeys.length; i++) {
 207  6 String stringRep = submittedPrimaryKeys[i];
 208  6 Object value = getDataSqueezer().unsqueeze(stringRep);
 209  6 data.add(value);
 210  6 if (primaryKeysBinding != null && i <= pkDesc.length() && pkDesc.charAt(i-1) == DESC_PRIMARY_KEY)
 211  0 pks.add(value);
 212    }
 213   
 214    // update the binding with the list of primary keys
 215  3 if (primaryKeysBinding != null)
 216  0 primaryKeysBinding.setObject(pks);
 217   
 218    // convert from primary keys to data
 219  3 for (int i = 0; i < data.size(); i++) {
 220  6 if (i <= pkDesc.length() && pkDesc.charAt(i) == DESC_PRIMARY_KEY) {
 221  4 Object pk = data.get(i);
 222  4 Object value = getValueFromPrimaryKey(pk);
 223  4 data.set(i, value);
 224    }
 225    }
 226   
 227  3 return data.iterator();
 228    }
 229   
 230    /**
 231    * Stores the provided data in the form and then returns the data as an iterator.
 232    * If the primary key of the value can be determined,
 233    * then that primary key is saved instead.
 234    *
 235    * @param form The form where the data will be stored
 236    * @param name The name under which the data will be stored
 237    * @return an iterator with the bound values stored in the form
 238    **/
 239  6 protected Iterator storeSourceData(IForm form, String name)
 240    {
 241  6 List sourceData = getSourceData();
 242  6 if (sourceData == null)
 243  0 return null;
 244   
 245  6 List sourcePrimaryKeys = evaluateSourcePrimaryKeys();
 246  6 if (sourcePrimaryKeys == null)
 247  0 return null;
 248   
 249    // store primary keys
 250  6 form.addHiddenValue(name, sourcePrimaryKeys.get(0).toString());
 251  6 for (int i = 1; i < sourcePrimaryKeys.size(); i++) {
 252  12 Object pk = sourcePrimaryKeys.get(i);
 253  12 String stringRep = getDataSqueezer().squeeze(pk);
 254  12 form.addHiddenValue(name, stringRep);
 255    }
 256   
 257  6 return sourceData.iterator();
 258    }
 259   
 260    /**
 261    * Converts the values in the 'source' parameter to primary keys
 262    * and returns a list containing the primary keys or the values themselves
 263    * if a primary key cannot be extracted. The first element of the array is a
 264    * string that shows whether a particular element is a primary key or a value.
 265    * The method also stores the evaluated primary keys in a map that can be used
 266    * to determine the value that a particular primary key represents.
 267    *
 268    * @return an array consisting of the primary keys of the source values or
 269    * the values themselves if a primary key cannot be found. The first element
 270    * of the array is a string describing whether a particular element is
 271    * a primary key or a value.
 272    */
 273  6 private List evaluateSourcePrimaryKeys()
 274    {
 275    // check if the result is already cached to avoid evaluating again
 276  6 List sourcePrimaryKeys = getSourcePrimaryKeys();
 277  6 if (sourcePrimaryKeys != null)
 278  0 return sourcePrimaryKeys;
 279   
 280  6 List sourceData = getSourceData();
 281  6 if (sourceData == null)
 282  0 return null;
 283   
 284    // extract primary keys from data
 285  6 StringBuffer pkDesc = new StringBuffer(sourceData.size());
 286  6 sourcePrimaryKeys = new ArrayList(sourceData.size()+1);
 287  6 sourcePrimaryKeys.add(pkDesc);
 288  6 for (Iterator it = sourceData.iterator(); it.hasNext();) {
 289  12 Object value = it.next();
 290   
 291  12 Object pk = getPrimaryKeyFromValue(value);
 292  12 if (pk == null) {
 293  4 pkDesc.append(DESC_VALUE);
 294  4 pk = value;
 295    }
 296    else {
 297  8 pkDesc.append(DESC_PRIMARY_KEY);
 298    }
 299  12 sourcePrimaryKeys.add(pk);
 300    }
 301   
 302  6 setSourcePrimaryKeys(sourcePrimaryKeys);
 303   
 304  6 return sourcePrimaryKeys;
 305    }
 306   
 307    /**
 308    * Converts the values in the 'source' parameter to primary keys if possible.
 309    * Stores the evaluated primary keys in a map to determine the value
 310    * that a particular primary key represents.
 311    *
 312    * @return the map from primary keys to their corresponding objects
 313    */
 314  4 private Map fillSourcePrimaryKeysMap()
 315    {
 316    // check if the result is already cached to avoid evaluating again
 317  4 Map primaryKeyMap = getPrimaryKeyMap();
 318  4 if (primaryKeyMap != null)
 319  2 return primaryKeyMap;
 320   
 321  2 List sourceData = getSourceData();
 322  2 if (sourceData == null)
 323  0 return null;
 324   
 325    // extract primary keys from data
 326  2 primaryKeyMap = new HashMap();
 327  2 for (Iterator it = sourceData.iterator(); it.hasNext();) {
 328  4 Object value = it.next();
 329  4 Object pk = getPrimaryKeyFromValue(value);
 330  4 if (pk != null)
 331  4 primaryKeyMap.put(pk, value);
 332    }
 333   
 334  2 setPrimaryKeyMap(primaryKeyMap);
 335   
 336  2 return primaryKeyMap;
 337    }
 338   
 339    /**
 340    * Returns the primary key of the given value.
 341    * Uses the 'keyExpression' or the 'converter' (if either is provided).
 342    *
 343    * @param value The value from which the primary key should be extracted
 344    * @return The primary key of the value, or null if such cannot be extracted.
 345    */
 346  54 private Object getPrimaryKeyFromValue(Object value) {
 347  54 if (value == null)
 348  0 return null;
 349   
 350  54 Object primaryKey = null;
 351   
 352  54 String keyExpression = getKeyExpression();
 353  54 if (keyExpression != null)
 354  50 primaryKey = getExpressionEvaluator().read(value, keyExpression);
 355   
 356  54 if (primaryKey == null) {
 357  4 IPrimaryKeyConverter converter = getConverter();
 358  4 if (converter != null)
 359  0 primaryKey = converter.getPrimaryKey(value);
 360    }
 361   
 362  54 return primaryKey;
 363    }
 364   
 365    /**
 366    * Returns a value that corresponds to the provided primary key.
 367    * Uses the 'keyExpression' or the 'converter' (if either is provided).
 368    * If 'keyExpression' is defined, it extracts the primary keys of all values
 369    * in 'source' until a match is found. If there is no match, it does the same
 370    * with 'fullSource'. If that does not help either, 'converter' is used.
 371    * Finally, the 'defaultValue' is returned as a last resort.
 372    *
 373    * @param primaryKey The primary key that identifies the value
 374    * @return A value with an identical primary key, or null if such is not found.
 375    */
 376  4 private Object getValueFromPrimaryKey(Object primaryKey) {
 377  4 Object value = null;
 378   
 379  4 Map primaryKeyMap = fillSourcePrimaryKeysMap();
 380  4 if (primaryKeyMap != null)
 381  4 value = primaryKeyMap.get(primaryKey);
 382   
 383  4 if (value == null) {
 384    // if fullSource is defined, try to get the object in that way
 385  2 Object fullSource = getFullSource();
 386  2 if (fullSource != null)
 387  1 value = findPrimaryKeyMatchInFullSource(primaryKey, fullSource);
 388    }
 389   
 390  4 if (value == null) {
 391  1 IPrimaryKeyConverter converter = getConverter();
 392  1 if (converter != null)
 393  0 value = converter.getValue(primaryKey);
 394    }
 395   
 396  4 if (value == null)
 397  1 value = getDefaultValue();
 398   
 399  4 return value;
 400    }
 401   
 402    /**
 403    * Iterates over the fullSource parameter until a value with a matching primary key is found.
 404    * The primary keys generated and the fullSource iterator are stored in properties
 405    * to avoid repeated evaluation.
 406    *
 407    * @param primaryKey the primary key to be matched
 408    * @param fullSource the provided list of objects
 409    * @return an object with a matching primary key, or null if such is not found
 410    */
 411  1 private Object findPrimaryKeyMatchInFullSource(Object primaryKey, Object fullSource)
 412    {
 413  1 Map primaryKeyMap = getPrimaryKeyMap();
 414  1 if (primaryKeyMap == null)
 415  0 primaryKeyMap = new HashMap();
 416   
 417  1 Iterator it = getFullSourceIterator();
 418  1 if (it == null) {
 419  1 it = (Iterator) getValueConverter().coerceValue(fullSource, Iterator.class);
 420  1 if (it == null)
 421  0 it = Collections.EMPTY_LIST.iterator();
 422    }
 423   
 424  1 try {
 425  38 while (it.hasNext()) {
 426  38 Object sourceValue = it.next();
 427  38 if (sourceValue == null)
 428  0 continue;
 429   
 430  38 Object sourcePrimaryKey = getPrimaryKeyFromValue(sourceValue);
 431  38 if (sourcePrimaryKey != null)
 432  38 primaryKeyMap.put(sourcePrimaryKey, sourceValue);
 433   
 434  38 if (primaryKey.equals(sourcePrimaryKey)) {
 435  1 return sourceValue;
 436    }
 437    }
 438   
 439  0 return null;
 440    }
 441    finally {
 442  1 setFullSourceIterator(it);
 443  1 setPrimaryKeyMap(primaryKeyMap);
 444    }
 445    }
 446   
 447    /**
 448    * Updates the index and value output parameters if bound.
 449    */
 450  34 private void updateOutputParameters()
 451    {
 452  34 IBinding indexBinding = getBinding("index");
 453  34 if (indexBinding != null)
 454  18 indexBinding.setObject(new Integer(_index));
 455   
 456  34 IBinding valueBinding = getBinding("value");
 457  34 if (valueBinding != null)
 458  18 valueBinding.setObject(_value);
 459    }
 460   
 461    /**
 462    * Returns the most recent value extracted from the source parameter.
 463    *
 464    * @throws org.apache.tapestry.ApplicationRuntimeException if the For is not currently rendering.
 465    *
 466    **/
 467   
 468  28 public final Object getValue()
 469    {
 470  28 if (!_rendering)
 471  0 throw Tapestry.createRenderOnlyPropertyException(this, "value");
 472   
 473  28 return _value;
 474    }
 475   
 476    /**
 477    * The index number, within the {@link #getSource() source}, of the
 478    * the current value.
 479    *
 480    * @throws org.apache.tapestry.ApplicationRuntimeException if the For is not currently rendering.
 481    *
 482    **/
 483   
 484  28 public int getIndex()
 485    {
 486  28 if (!_rendering)
 487  0 throw Tapestry.createRenderOnlyPropertyException(this, "index");
 488   
 489  28 return _index;
 490    }
 491   
 492  0 public boolean isDisabled()
 493    {
 494  0 return false;
 495    }
 496   
 497    // Do nothing in those methods, but make the JVM happy
 498  0 protected void renderFormComponent(IMarkupWriter writer, IRequestCycle cycle) { }
 499  0 protected void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle) { }
 500    }