001 // Copyright 2004, 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.components;
016
017 import java.util.ArrayList;
018 import java.util.Collections;
019 import java.util.HashMap;
020 import java.util.Iterator;
021 import java.util.List;
022 import java.util.Map;
023
024 import org.apache.tapestry.IBinding;
025 import org.apache.tapestry.IForm;
026 import org.apache.tapestry.IMarkupWriter;
027 import org.apache.tapestry.IRequestCycle;
028 import org.apache.tapestry.Tapestry;
029 import org.apache.tapestry.TapestryUtils;
030 import org.apache.tapestry.coerce.ValueConverter;
031 import org.apache.tapestry.form.AbstractFormComponent;
032 import org.apache.tapestry.services.DataSqueezer;
033 import org.apache.tapestry.services.ExpressionEvaluator;
034
035 /**
036 * @author mb
037 * @since 4.0
038 * @see org.apache.tapestry.components.IPrimaryKeyConverter
039 * @see org.apache.tapestry.util.DefaultPrimaryKeyConverter
040 */
041 public abstract class ForBean extends AbstractFormComponent
042 {
043 // constants
044
045 /**
046 * Prefix on the hidden value stored into the field to indicate the the actual value is stored
047 * (this is used when there is no primary key converter). The remainder of the string is a
048 * {@link DataSqueezer squeezed} representation of the value.
049 */
050 private static final char DESC_VALUE = 'V';
051
052 /**
053 * Prefix on the hidden value stored into the field that indicates the primary key of the
054 * iterated value is stored; the remainder of the string is a {@link DataSqueezer squeezed}
055 * representation of the primary key. The {@link IPrimaryKeyConverter converter} is used to
056 * obtain the value from this key.
057 */
058 private static final char DESC_PRIMARY_KEY = 'P';
059
060 private final RepSource COMPLETE_REP_SOURCE = new CompleteRepSource();
061
062 private final RepSource KEY_EXPRESSION_REP_SOURCE = new KeyExpressionRepSource();
063
064 // parameters
065 public abstract String getElement();
066
067 public abstract String getKeyExpression();
068
069 public abstract IPrimaryKeyConverter getConverter();
070
071 public abstract Object getDefaultValue();
072
073 public abstract boolean getMatch();
074
075 public abstract boolean getVolatile();
076
077 // injects
078 public abstract DataSqueezer getDataSqueezer();
079
080 public abstract ValueConverter getValueConverter();
081
082 public abstract ExpressionEvaluator getExpressionEvaluator();
083
084 // intermediate members
085 private Object _value;
086
087 private int _index;
088
089 private boolean _rendering;
090
091 /**
092 * Gets the source binding and iterates through its values. For each, it updates the value
093 * binding and render's its wrapped elements.
094 */
095 protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle)
096 {
097 // form may be null if component is not located in a form
098 IForm form = (IForm) cycle.getAttribute(TapestryUtils.FORM_ATTRIBUTE);
099
100 // If the cycle is rewinding, but not this particular form,
101 // then do nothing (don't even render the body).
102 boolean cycleRewinding = cycle.isRewinding();
103 if (cycleRewinding && form != null && !form.isRewinding())
104 return;
105
106 // Get the data to be iterated upon. Store in form if needed.
107 Iterator dataSource = getData(cycle, form);
108
109 // Do not iterate if dataSource is null.
110 // The dataSource was either not convertable to Iterator, or was empty.
111 if (dataSource == null)
112 return;
113
114 String element = getElement();
115
116 // Perform the iterations
117 try
118 {
119 _index = 0;
120 _rendering = true;
121
122 while (dataSource.hasNext())
123 {
124 // Get current value
125 _value = dataSource.next();
126
127 // Update output component parameters
128 updateOutputParameters();
129
130 // Render component
131 if (element != null)
132 {
133 writer.begin(element);
134 renderInformalParameters(writer, cycle);
135 }
136
137 renderBody(writer, cycle);
138
139 if (element != null)
140 writer.end();
141
142 _index++;
143 }
144 }
145 finally
146 {
147 _rendering = false;
148 _value = null;
149 }
150 }
151
152 /**
153 * Returns the most recent value extracted from the source parameter.
154 *
155 * @throws org.apache.tapestry.ApplicationRuntimeException
156 * if the For is not currently rendering.
157 */
158
159 public final Object getValue()
160 {
161 if (!_rendering)
162 throw Tapestry.createRenderOnlyPropertyException(this, "value");
163
164 return _value;
165 }
166
167 /**
168 * The index number, within the {@link #getSource() source}, of the the current value.
169 *
170 * @throws org.apache.tapestry.ApplicationRuntimeException
171 * if the For is not currently rendering.
172 */
173
174 public int getIndex()
175 {
176 if (!_rendering)
177 throw Tapestry.createRenderOnlyPropertyException(this, "index");
178
179 return _index;
180 }
181
182 public boolean isDisabled()
183 {
184 return false;
185 }
186
187 /**
188 * Updates the index and value output parameters if bound.
189 */
190 protected void updateOutputParameters()
191 {
192 IBinding indexBinding = getBinding("index");
193 if (indexBinding != null)
194 indexBinding.setObject(new Integer(_index));
195
196 IBinding valueBinding = getBinding("value");
197 if (valueBinding != null)
198 valueBinding.setObject(_value);
199 }
200
201 /**
202 * Updates the primaryKeys parameter if bound.
203 */
204 protected void updatePrimaryKeysParameter(String[] stringReps)
205 {
206 IBinding primaryKeysBinding = getBinding("primaryKeys");
207 if (primaryKeysBinding == null)
208 return;
209
210 DataSqueezer squeezer = getDataSqueezer();
211
212 int repsCount = stringReps.length;
213 List primaryKeys = new ArrayList(repsCount);
214 for (int i = 0; i < stringReps.length; i++)
215 {
216 String rep = stringReps[i];
217 if (rep.length() == 0 || rep.charAt(0) != DESC_PRIMARY_KEY)
218 continue;
219 Object primaryKey = squeezer.unsqueeze(rep.substring(1));
220 primaryKeys.add(primaryKey);
221 }
222
223 primaryKeysBinding.setObject(primaryKeys);
224 }
225
226 // Do nothing in those methods, but make the JVM happy
227 protected void renderFormComponent(IMarkupWriter writer, IRequestCycle cycle)
228 {
229 }
230
231 protected void rewindFormComponent(IMarkupWriter writer, IRequestCycle cycle)
232 {
233 }
234
235 /**
236 * Returns a list with the values to be iterated upon. The list is obtained in different ways: -
237 * If the component is not located in a form or 'volatile' is set to true, then the simply the
238 * values passed to 'source' are returned (same as Foreach) - If the component is in a form, and
239 * the form is rewinding, the values stored in the form are returned -- rewind is then always
240 * the same as render. - If the component is in a form, and the form is being rendered, the
241 * values are stored in the form as Hidden fields.
242 *
243 * @param cycle
244 * The current request cycle
245 * @param form
246 * The form within which the component is located (if any)
247 * @return An iterator with the values to be cycled upon
248 */
249 private Iterator getData(IRequestCycle cycle, IForm form)
250 {
251 if (form == null || getVolatile())
252 return evaluateSourceIterator();
253
254 String name = form.getElementId(this);
255 if (cycle.isRewinding())
256 return getStoredData(cycle, name);
257 return storeSourceData(form, name);
258 }
259
260 /**
261 * Returns a list of the values stored as Hidden fields in the form. A conversion is performed
262 * if the primary key of the value is stored.
263 *
264 * @param cycle
265 * The current request cycle
266 * @param name
267 * The name of the HTTP parameter whether the values
268 * @return an iterator with the values stored in the provided Hidden fields
269 */
270 protected Iterator getStoredData(IRequestCycle cycle, String name)
271 {
272 String[] stringReps = cycle.getParameters(name);
273 if (stringReps == null)
274 return null;
275
276 updatePrimaryKeysParameter(stringReps);
277
278 return new ReadSourceDataIterator(stringReps);
279 }
280
281 /**
282 * Pulls data from successive strings (posted by client-side hidden fields); each string
283 * representation may be either a value or a primary key.
284 */
285 private class ReadSourceDataIterator implements Iterator
286 {
287 private final Iterator _sourceIterator = evaluateSourceIterator();
288
289 private final Iterator _fullSourceIterator = evaluateFullSourceIterator();
290
291 private final String[] _stringReps;
292
293 private int _index = 0;
294
295 private final Map _repToValueMap = new HashMap();
296
297 ReadSourceDataIterator(String[] stringReps)
298 {
299 _stringReps = stringReps;
300 }
301
302 public boolean hasNext()
303 {
304 return _index < _stringReps.length;
305 }
306
307 public Object next()
308 {
309 String rep = _stringReps[_index++];
310
311 return getValueFromStringRep(_sourceIterator, _fullSourceIterator, _repToValueMap, rep);
312 }
313
314 public void remove()
315 {
316 throw new UnsupportedOperationException("remove()");
317 }
318
319 }
320
321 /**
322 * Stores the provided data in the form and then returns the data as an iterator. If the primary
323 * key of the value can be determined, then that primary key is saved instead.
324 *
325 * @param form
326 * The form where the data will be stored
327 * @param name
328 * The name under which the data will be stored
329 * @return an iterator with the bound values stored in the form
330 */
331 protected Iterator storeSourceData(IForm form, String name)
332 {
333 return new StoreSourceDataIterator(form, name, evaluateSourceIterator());
334 }
335
336 /**
337 * Iterates over a set of values, using {@link ForBean#getStringRepFromValue(Object)} to obtain
338 * the correct client-side string representation, and working with the form to store each
339 * successive value into the form.
340 */
341 private class StoreSourceDataIterator implements Iterator
342 {
343 private final IForm _form;
344
345 private final String _name;
346
347 private final Iterator _delegate;
348
349 StoreSourceDataIterator(IForm form, String name, Iterator delegate)
350 {
351 _form = form;
352 _name = name;
353 _delegate = delegate;
354 }
355
356 public boolean hasNext()
357 {
358 return _delegate.hasNext();
359 }
360
361 public Object next()
362 {
363 Object value = _delegate.next();
364
365 String rep = getStringRepFromValue(value);
366
367 _form.addHiddenValue(_name, rep);
368
369 return value;
370 }
371
372 public void remove()
373 {
374 throw new UnsupportedOperationException("remove()");
375 }
376 }
377
378 /**
379 * Returns the string representation of the value. The first letter of the string representation
380 * shows whether a value or a primary key is being described.
381 *
382 * @param value
383 * @return
384 */
385 protected String getStringRepFromValue(Object value)
386 {
387 String rep;
388 DataSqueezer squeezer = getDataSqueezer();
389
390 // try to extract the primary key from the value
391 Object pk = getPrimaryKeyFromValue(value);
392 if (pk != null)
393 // Primary key was extracted successfully.
394 rep = DESC_PRIMARY_KEY + squeezer.squeeze(pk);
395 else
396 // primary key could not be extracted. squeeze value.
397 rep = DESC_VALUE + squeezer.squeeze(value);
398
399 return rep;
400 }
401
402 /**
403 * Returns the primary key of the given value. Uses the 'keyExpression' or the 'converter' (if
404 * either is provided).
405 *
406 * @param value
407 * The value from which the primary key should be extracted
408 * @return The primary key of the value, or null if such cannot be extracted.
409 */
410 protected Object getPrimaryKeyFromValue(Object value)
411 {
412 if (value == null)
413 return null;
414
415 Object primaryKey = getKeyExpressionFromValue(value);
416 if (primaryKey == null)
417 primaryKey = getConverterFromValue(value);
418
419 return primaryKey;
420 }
421
422 /**
423 * Uses the 'keyExpression' parameter to determine the primary key of the given value
424 *
425 * @param value
426 * The value from which the primary key should be extracted
427 * @return The primary key of the value as defined by 'keyExpression', or null if such cannot be
428 * extracted.
429 */
430 protected Object getKeyExpressionFromValue(Object value)
431 {
432 String keyExpression = getKeyExpression();
433 if (keyExpression == null)
434 return null;
435
436 Object primaryKey = getExpressionEvaluator().read(value, keyExpression);
437 return primaryKey;
438 }
439
440 /**
441 * Uses the 'converter' parameter to determine the primary key of the given value
442 *
443 * @param value
444 * The value from which the primary key should be extracted
445 * @return The primary key of the value as provided by the converter, or null if such cannot be
446 * extracted.
447 */
448 protected Object getConverterFromValue(Object value)
449 {
450 IPrimaryKeyConverter converter = getConverter();
451 if (converter == null)
452 return null;
453
454 Object primaryKey = converter.getPrimaryKey(value);
455 return primaryKey;
456 }
457
458 /**
459 * Determines the value that corresponds to the given string representation. If the 'match'
460 * parameter is true, attempt to find a value in 'source' or 'fullSource' that generates the
461 * same string representation. Otherwise, create a new value from the string representation.
462 *
463 * @param rep
464 * the string representation for which a value should be returned
465 * @return the value that corresponds to the provided string representation
466 */
467 protected Object getValueFromStringRep(Iterator sourceIterator, Iterator fullSourceIterator,
468 Map repToValueMap, String rep)
469 {
470 Object value = null;
471 DataSqueezer squeezer = getDataSqueezer();
472
473 // Check if the string rep is empty. If so, just return the default value.
474 if (rep == null || rep.length() == 0)
475 return getDefaultValue();
476
477 // If required, find a value with an equivalent string representation and return it
478 boolean match = getMatch();
479 if (match)
480 {
481 value = findValueWithStringRep(
482 sourceIterator,
483 fullSourceIterator,
484 repToValueMap,
485 rep,
486 COMPLETE_REP_SOURCE);
487 if (value != null)
488 return value;
489 }
490
491 // Matching of the string representation was not successful or was disabled.
492 // Use the standard approaches to obtain the value from the rep.
493 char desc = rep.charAt(0);
494 String squeezed = rep.substring(1);
495 switch (desc)
496 {
497 case DESC_VALUE:
498 // If the string rep is just the value itself, unsqueeze it
499 value = squeezer.unsqueeze(squeezed);
500 break;
501
502 case DESC_PRIMARY_KEY:
503 // Perform keyExpression match if not already attempted
504 if (!match && getKeyExpression() != null)
505 value = findValueWithStringRep(
506 sourceIterator,
507 fullSourceIterator,
508 repToValueMap,
509 rep,
510 KEY_EXPRESSION_REP_SOURCE);
511
512 // If 'converter' is defined, try to perform conversion from primary key to value
513 if (value == null)
514 {
515 IPrimaryKeyConverter converter = getConverter();
516 if (converter != null)
517 {
518 Object pk = squeezer.unsqueeze(squeezed);
519 value = converter.getValue(pk);
520 }
521 }
522 break;
523 }
524
525 if (value == null)
526 value = getDefaultValue();
527
528 return value;
529 }
530
531 /**
532 * Attempt to find a value in 'source' or 'fullSource' that generates the provided string
533 * representation. Use the RepSource interface to determine what the string representation of a
534 * particular value is.
535 *
536 * @param rep
537 * the string representation for which a value should be returned
538 * @param repSource
539 * an interface providing the string representation of a given value
540 * @return the value in 'source' or 'fullSource' that corresponds to the provided string
541 * representation
542 */
543 protected Object findValueWithStringRep(Iterator sourceIterator, Iterator fullSourceIterator,
544 Map repToValueMap, String rep, RepSource repSource)
545 {
546 Object value = repToValueMap.get(rep);
547 if (value != null)
548 return value;
549
550 value = findValueWithStringRepInIterator(sourceIterator, repToValueMap, rep, repSource);
551 if (value != null)
552 return value;
553
554 value = findValueWithStringRepInIterator(fullSourceIterator, repToValueMap, rep, repSource);
555 return value;
556 }
557
558 /**
559 * Attempt to find a value in the provided collection that generates the required string
560 * representation. Use the RepSource interface to determine what the string representation of a
561 * particular value is.
562 *
563 * @param rep
564 * the string representation for which a value should be returned
565 * @param repSource
566 * an interface providing the string representation of a given value
567 * @param it
568 * the iterator of the collection in which a value should be searched
569 * @return the value in the provided collection that corresponds to the required string
570 * representation
571 */
572 protected Object findValueWithStringRepInIterator(Iterator it, Map repToValueMap, String rep,
573 RepSource repSource)
574 {
575 while (it.hasNext())
576 {
577 Object sourceValue = it.next();
578 if (sourceValue == null)
579 continue;
580
581 String sourceRep = repSource.getStringRep(sourceValue);
582 repToValueMap.put(sourceRep, sourceValue);
583
584 if (rep.equals(sourceRep))
585 return sourceValue;
586 }
587
588 return null;
589 }
590
591 /**
592 * Returns a new iterator of the values in 'source'.
593 *
594 * @return the 'source' iterator
595 */
596 protected Iterator evaluateSourceIterator()
597 {
598 Iterator it = null;
599 Object source = null;
600
601 IBinding sourceBinding = getBinding("source");
602 if (sourceBinding != null)
603 source = sourceBinding.getObject();
604
605 if (source != null)
606 it = (Iterator) getValueConverter().coerceValue(source, Iterator.class);
607
608 if (it == null)
609 it = Collections.EMPTY_LIST.iterator();
610
611 return it;
612 }
613
614 /**
615 * Returns a new iterator of the values in 'fullSource'.
616 *
617 * @return the 'fullSource' iterator
618 */
619 protected Iterator evaluateFullSourceIterator()
620 {
621 Iterator it = null;
622 Object fullSource = null;
623
624 IBinding fullSourceBinding = getBinding("fullSource");
625 if (fullSourceBinding != null)
626 fullSource = fullSourceBinding.getObject();
627
628 if (fullSource != null)
629 it = (Iterator) getValueConverter().coerceValue(fullSource, Iterator.class);
630
631 if (it == null)
632 it = Collections.EMPTY_LIST.iterator();
633
634 return it;
635 }
636
637 /**
638 * An interface that provides the string representation of a given value
639 */
640 protected interface RepSource
641 {
642 String getStringRep(Object value);
643 }
644
645 /**
646 * An implementation of RepSource that provides the string representation of the given value
647 * using all methods.
648 */
649 protected class CompleteRepSource implements RepSource
650 {
651 public String getStringRep(Object value)
652 {
653 return getStringRepFromValue(value);
654 }
655 }
656
657 /**
658 * An implementation of RepSource that provides the string representation of the given value
659 * using just the 'keyExpression' parameter.
660 */
661 protected class KeyExpressionRepSource implements RepSource
662 {
663 public String getStringRep(Object value)
664 {
665 Object pk = getKeyExpressionFromValue(value);
666 return DESC_PRIMARY_KEY + getDataSqueezer().squeeze(pk);
667 }
668 }
669
670 /**
671 * For component can not take focus.
672 */
673 protected boolean getCanTakeFocus()
674 {
675 return false;
676 }
677
678 public String getClientId()
679 {
680 return null;
681 }
682
683 public String getDisplayName()
684 {
685 return null;
686 }
687
688 }