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