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