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    }