001    // Copyright 2007, 2008, 2009, 2010, 2011 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.tapestry5.internal.services;
016    
017    import org.antlr.runtime.ANTLRInputStream;
018    import org.antlr.runtime.CommonTokenStream;
019    import org.antlr.runtime.tree.Tree;
020    import org.apache.tapestry5.PropertyConduit;
021    import org.apache.tapestry5.internal.InternalPropertyConduit;
022    import org.apache.tapestry5.internal.antlr.PropertyExpressionLexer;
023    import org.apache.tapestry5.internal.antlr.PropertyExpressionParser;
024    import org.apache.tapestry5.internal.util.IntegerRange;
025    import org.apache.tapestry5.internal.util.MultiKey;
026    import org.apache.tapestry5.ioc.AnnotationProvider;
027    import org.apache.tapestry5.ioc.annotations.PostInjection;
028    import org.apache.tapestry5.ioc.internal.NullAnnotationProvider;
029    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
030    import org.apache.tapestry5.ioc.internal.util.GenericsUtils;
031    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
032    import org.apache.tapestry5.ioc.services.*;
033    import org.apache.tapestry5.ioc.util.AvailableValues;
034    import org.apache.tapestry5.ioc.util.UnknownValueException;
035    import org.apache.tapestry5.plastic.*;
036    import org.apache.tapestry5.services.*;
037    
038    import java.io.ByteArrayInputStream;
039    import java.io.IOException;
040    import java.io.InputStream;
041    import java.lang.annotation.Annotation;
042    import java.lang.reflect.*;
043    import java.util.ArrayList;
044    import java.util.HashMap;
045    import java.util.List;
046    import java.util.Map;
047    
048    import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.*;
049    
050    public class PropertyConduitSourceImpl implements PropertyConduitSource, InvalidationListener
051    {
052        static class ConduitMethods
053        {
054            private static final MethodDescription GET = getMethodDescription(PropertyConduit.class, "get", Object.class);
055    
056            private static final MethodDescription SET = getMethodDescription(PropertyConduit.class, "set", Object.class,
057                    Object.class);
058    
059            private static final MethodDescription GET_PROPERTY_TYPE = getMethodDescription(PropertyConduit.class,
060                    "getPropertyType");
061    
062            private static final MethodDescription GET_PROPERTY_NAME = getMethodDescription(InternalPropertyConduit.class,
063                    "getPropertyName");
064    
065            private static final MethodDescription GET_ANNOTATION = getMethodDescription(AnnotationProvider.class,
066                    "getAnnotation", Class.class);
067    
068        }
069    
070        static class DelegateMethods
071        {
072            static final Method INVERT = getMethod(PropertyConduitDelegate.class, "invert", Object.class);
073    
074            static final Method RANGE = getMethod(PropertyConduitDelegate.class, "range", int.class, int.class);
075    
076            static final Method COERCE = getMethod(PropertyConduitDelegate.class, "coerce", Object.class, Class.class);
077        }
078    
079        static class ArrayListMethods
080        {
081            static final Method ADD = getMethod(ArrayList.class, "add", Object.class);
082        }
083    
084        static class HashMapMethods
085        {
086            static final Method PUT = getMethod(HashMap.class, "put", Object.class, Object.class);
087        }
088    
089        private static InstructionBuilderCallback RETURN_NULL = new InstructionBuilderCallback()
090        {
091            public void doBuild(InstructionBuilder builder)
092            {
093                builder.loadNull().returnResult();
094            }
095        };
096    
097        private static final String[] SINGLE_OBJECT_ARGUMENT = new String[]
098                {Object.class.getName()};
099    
100        @SuppressWarnings("unchecked")
101        private static Method getMethod(Class containingClass, String name, Class... parameterTypes)
102        {
103            try
104            {
105                return containingClass.getMethod(name, parameterTypes);
106            } catch (NoSuchMethodException ex)
107            {
108                throw new IllegalArgumentException(ex);
109            }
110        }
111    
112        private static MethodDescription getMethodDescription(Class containingClass, String name, Class... parameterTypes)
113        {
114            return new MethodDescription(getMethod(containingClass, name, parameterTypes));
115        }
116    
117        private final AnnotationProvider nullAnnotationProvider = new NullAnnotationProvider();
118    
119        /**
120         * How are null values in intermdiate terms to be handled?
121         */
122        private enum NullHandling
123        {
124            /**
125             * Add code to check for null and throw exception if null.
126             */
127            FORBID,
128    
129            /**
130             * Add code to check for null and short-circuit (i.e., the "?."
131             * safe-dereference operator)
132             */
133            ALLOW
134        }
135    
136        /**
137         * One term in an expression. Expressions start with some root type and each term advances
138         * to a new type.
139         */
140        private class Term
141        {
142            /**
143             * The generic type of the term.
144             */
145            final Type type;
146    
147            final Class genericType;
148    
149            /**
150             * Describes the term, for use in error messages.
151             */
152            final String description;
153    
154            final AnnotationProvider annotationProvider;
155    
156            /**
157             * Callback that will implement the term.
158             */
159            final InstructionBuilderCallback callback;
160    
161            Term(Type type, Class genericType, String description, AnnotationProvider annotationProvider,
162                 InstructionBuilderCallback callback)
163            {
164                this.type = type;
165                this.genericType = genericType;
166                this.description = description;
167                this.annotationProvider = annotationProvider;
168                this.callback = callback;
169            }
170    
171            Term(Type type, String description, AnnotationProvider annotationProvider, InstructionBuilderCallback callback)
172            {
173                this(type, GenericsUtils.asClass(type), description, annotationProvider, callback);
174            }
175    
176            Term(Type type, String description, InstructionBuilderCallback callback)
177            {
178                this(type, description, null, callback);
179            }
180    
181            /**
182             * Returns a clone of this Term with a new callback.
183             */
184            Term withCallback(InstructionBuilderCallback newCallback)
185            {
186                return new Term(type, genericType, description, annotationProvider, newCallback);
187            }
188        }
189    
190        private final PropertyAccess access;
191    
192        private final PlasticProxyFactory proxyFactory;
193    
194        private final TypeCoercer typeCoercer;
195    
196        private final StringInterner interner;
197    
198        /**
199         * Keyed on combination of root class and expression.
200         */
201        private final Map<MultiKey, PropertyConduit> cache = CollectionFactory.newConcurrentMap();
202    
203        private final Invariant invariantAnnotation = new Invariant()
204        {
205            public Class<? extends Annotation> annotationType()
206            {
207                return Invariant.class;
208            }
209        };
210    
211        private final AnnotationProvider invariantAnnotationProvider = new AnnotationProvider()
212        {
213            public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
214            {
215                if (annotationClass == Invariant.class)
216                    return annotationClass.cast(invariantAnnotation);
217    
218                return null;
219            }
220        };
221    
222        private final PropertyConduit literalTrue;
223    
224        private final PropertyConduit literalFalse;
225    
226        private final PropertyConduit literalNull;
227    
228        private final PropertyConduitDelegate sharedDelegate;
229    
230        /**
231         * Encapsulates the process of building a PropertyConduit instance from an
232         * expression, as an {@link PlasticClassTransformer}.
233         */
234        class PropertyConduitBuilder implements PlasticClassTransformer
235        {
236            private final Class rootType;
237    
238            private final String expression;
239    
240            private final Tree tree;
241    
242            private Class conduitPropertyType;
243    
244            private String conduitPropertyName;
245    
246            private AnnotationProvider annotationProvider = nullAnnotationProvider;
247    
248            private PlasticField delegateField;
249    
250            private PlasticClass plasticClass;
251    
252            private PlasticMethod getRootMethod, navMethod;
253    
254            PropertyConduitBuilder(Class rootType, String expression, Tree tree)
255            {
256                this.rootType = rootType;
257                this.expression = expression;
258                this.tree = tree;
259            }
260    
261            public void transform(PlasticClass plasticClass)
262            {
263                this.plasticClass = plasticClass;
264    
265                // Create the various methods; also determine the conduit's property type, property name and identify
266                // the annotation provider.
267    
268                implementNavMethodAndAccessors();
269    
270                implementOtherMethods();
271    
272                plasticClass.addToString(String.format("PropertyConduit[%s %s]", rootType.getName(), expression));
273            }
274    
275            private void implementOtherMethods()
276            {
277                PlasticField annotationProviderField = plasticClass.introduceField(AnnotationProvider.class,
278                        "annotationProvider").inject(annotationProvider);
279    
280                plasticClass.introduceMethod(ConduitMethods.GET_ANNOTATION).delegateTo(annotationProviderField);
281    
282                plasticClass.introduceMethod(ConduitMethods.GET_PROPERTY_NAME, new InstructionBuilderCallback()
283                {
284                    public void doBuild(InstructionBuilder builder)
285                    {
286                        builder.loadConstant(conduitPropertyName).returnResult();
287                    }
288                });
289    
290                final PlasticField propertyTypeField = plasticClass.introduceField(Class.class, "propertyType").inject(
291                        conduitPropertyType);
292    
293                plasticClass.introduceMethod(ConduitMethods.GET_PROPERTY_TYPE, new InstructionBuilderCallback()
294                {
295                    public void doBuild(InstructionBuilder builder)
296                    {
297                        builder.loadThis().getField(propertyTypeField).returnResult();
298                    }
299                });
300    
301            }
302    
303            /**
304             * Creates a method that does a conversion from Object to the expected root type, with
305             * a null check.
306             */
307            private void implementGetRoot()
308            {
309                getRootMethod = plasticClass.introducePrivateMethod(PlasticUtils.toTypeName(rootType), "getRoot",
310                        SINGLE_OBJECT_ARGUMENT, null);
311    
312                getRootMethod.changeImplementation(new InstructionBuilderCallback()
313                {
314                    public void doBuild(InstructionBuilder builder)
315                    {
316                        builder.loadArgument(0).dupe().when(Condition.NULL, new InstructionBuilderCallback()
317                        {
318                            public void doBuild(InstructionBuilder builder)
319                            {
320                                builder.throwException(NullPointerException.class,
321                                        String.format("Root object of property expression '%s' is null.", expression));
322                            }
323                        });
324    
325                        builder.checkcast(rootType).returnResult();
326                    }
327                });
328            }
329    
330            private boolean isLeaf(Tree node)
331            {
332                int type = node.getType();
333    
334                return type != DEREF && type != SAFEDEREF;
335            }
336    
337            private void implementNavMethodAndAccessors()
338            {
339                implementGetRoot();
340    
341                // First, create the navigate method.
342    
343                final List<InstructionBuilderCallback> callbacks = CollectionFactory.newList();
344    
345                Type activeType = rootType;
346    
347                Tree node = tree;
348    
349                while (!isLeaf(node))
350                {
351                    Term term = analyzeDerefNode(activeType, node);
352    
353                    callbacks.add(term.callback);
354    
355                    activeType = term.type;
356    
357                    // Second term is the continuation, possibly another chained
358                    // DEREF, etc.
359                    node = node.getChild(1);
360                }
361    
362                Class activeClass = GenericsUtils.asClass(activeType);
363    
364                if (callbacks.isEmpty())
365                {
366                    navMethod = getRootMethod;
367                } else
368                {
369                    navMethod = plasticClass.introducePrivateMethod(PlasticUtils.toTypeName(activeClass), "navigate",
370                            SINGLE_OBJECT_ARGUMENT, null);
371    
372                    navMethod.changeImplementation(new InstructionBuilderCallback()
373                    {
374                        public void doBuild(InstructionBuilder builder)
375                        {
376                            builder.loadThis().loadArgument(0).invokeVirtual(getRootMethod);
377    
378                            for (InstructionBuilderCallback callback : callbacks)
379                            {
380                                callback.doBuild(builder);
381                            }
382    
383                            builder.returnResult();
384                        }
385                    });
386                }
387    
388                implementAccessors(activeType, node);
389            }
390    
391            private void implementAccessors(Type activeType, Tree node)
392            {
393                switch (node.getType())
394                {
395                    case IDENTIFIER:
396    
397                        implementPropertyAccessors(activeType, node);
398    
399                        return;
400    
401                    case INVOKE:
402    
403                        // So, at this point, we have the navigation method written
404                        // and it covers all but the terminal
405                        // de-reference. node is an IDENTIFIER or INVOKE. We're
406                        // ready to use the navigation
407                        // method to implement get() and set().
408    
409                        implementMethodAccessors(activeType, node);
410    
411                        return;
412    
413                    case RANGEOP:
414    
415                        // As currently implemented, RANGEOP can only appear as the
416                        // top level, which
417                        // means we didn't need the navigate method after all.
418    
419                        implementRangeOpGetter(node);
420                        implementNoOpSetter();
421    
422                        conduitPropertyType = IntegerRange.class;
423    
424                        return;
425    
426                    case LIST:
427    
428                        implementListGetter(node);
429                        implementNoOpSetter();
430    
431                        conduitPropertyType = List.class;
432    
433                        return;
434    
435                    case MAP:
436                        implementMapGetter(node);
437                        implementNoOpSetter();
438    
439                        conduitPropertyType = Map.class;
440    
441                        return;
442    
443    
444                    case NOT:
445                        implementNotOpGetter(node);
446                        implementNoOpSetter();
447    
448                        conduitPropertyType = boolean.class;
449    
450                        return;
451    
452                    default:
453                        throw unexpectedNodeType(node, IDENTIFIER, INVOKE, RANGEOP, LIST, NOT);
454                }
455            }
456    
457            public void implementMethodAccessors(final Type activeType, final Tree invokeNode)
458            {
459                final Term term = buildInvokeTerm(activeType, invokeNode);
460    
461                implementNoOpSetter();
462    
463                conduitPropertyName = term.description;
464                conduitPropertyType = term.genericType;
465                annotationProvider = term.annotationProvider;
466    
467                plasticClass.introduceMethod(ConduitMethods.GET, new InstructionBuilderCallback()
468                {
469                    public void doBuild(InstructionBuilder builder)
470                    {
471                        invokeNavigateMethod(builder);
472    
473                        term.callback.doBuild(builder);
474    
475                        boxIfPrimitive(builder, conduitPropertyType);
476    
477                        builder.returnResult();
478                    }
479                });
480    
481                implementNoOpSetter();
482            }
483    
484            public void implementPropertyAccessors(Type activeType, Tree identifierNode)
485            {
486                String propertyName = identifierNode.getText();
487    
488                PropertyAdapter adapter = findPropertyAdapter(activeType, propertyName);
489    
490                conduitPropertyType = adapter.getType();
491                conduitPropertyName = propertyName;
492                annotationProvider = adapter;
493    
494                implementGetter(activeType, adapter);
495                implementSetter(activeType, adapter);
496            }
497    
498            private void implementSetter(Type activeType, PropertyAdapter adapter)
499            {
500                if (adapter.getWriteMethod() != null)
501                {
502                    implementSetter(adapter.getWriteMethod());
503                    return;
504                }
505    
506                if (adapter.getField() != null && adapter.isUpdate())
507                {
508                    implementSetter(adapter.getField());
509                    return;
510                }
511    
512                implementNoOpMethod(ConduitMethods.SET, "Expression '%s' for class %s is read-only.", expression,
513                        rootType.getName());
514            }
515    
516            private boolean isStatic(Member member)
517            {
518                return Modifier.isStatic(member.getModifiers());
519            }
520    
521            private void implementSetter(final Field field)
522            {
523                if (isStatic(field))
524                {
525                    plasticClass.introduceMethod(ConduitMethods.SET, new InstructionBuilderCallback()
526                    {
527                        public void doBuild(InstructionBuilder builder)
528                        {
529                            builder.loadArgument(1).castOrUnbox(PlasticUtils.toTypeName(field.getType()));
530    
531                            builder.putStaticField(field.getDeclaringClass().getName(), field.getName(), field.getType());
532    
533                            builder.returnResult();
534                        }
535                    });
536    
537                    return;
538                }
539    
540                plasticClass.introduceMethod(ConduitMethods.SET, new InstructionBuilderCallback()
541                {
542                    public void doBuild(InstructionBuilder builder)
543                    {
544                        invokeNavigateMethod(builder);
545    
546                        builder.loadArgument(1).castOrUnbox(PlasticUtils.toTypeName(field.getType()));
547    
548                        builder.putField(field.getDeclaringClass().getName(), field.getName(), field.getType());
549    
550                        builder.returnResult();
551                    }
552                });
553            }
554    
555            private void implementSetter(final Method writeMethod)
556            {
557                plasticClass.introduceMethod(ConduitMethods.SET, new InstructionBuilderCallback()
558                {
559                    public void doBuild(InstructionBuilder builder)
560                    {
561                        invokeNavigateMethod(builder);
562    
563                        Class propertyType = writeMethod.getParameterTypes()[0];
564                        String propertyTypeName = PlasticUtils.toTypeName(propertyType);
565    
566                        builder.loadArgument(1).castOrUnbox(propertyTypeName);
567    
568                        builder.invoke(writeMethod);
569    
570                        builder.returnResult();
571                    }
572                });
573            }
574    
575            private void implementGetter(Type activeType, PropertyAdapter adapter)
576            {
577                if (adapter.getReadMethod() != null)
578                {
579                    implementGetter(adapter.getReadMethod());
580                    return;
581                }
582    
583                if (adapter.getField() != null)
584                {
585                    implementGetter(adapter.getField());
586                    return;
587                }
588    
589                implementNoOpMethod(ConduitMethods.GET, "Expression '%s' for class %s is write-only.", expression,
590                        rootType.getName());
591            }
592    
593            private void implementGetter(final Field field)
594            {
595                if (isStatic(field))
596                {
597                    plasticClass.introduceMethod(ConduitMethods.GET, new InstructionBuilderCallback()
598                    {
599                        public void doBuild(InstructionBuilder builder)
600                        {
601                            builder.getStaticField(field.getDeclaringClass().getName(), field.getName(), field.getType());
602    
603                            // Cast not necessary here since the return type of get() is Object
604    
605                            boxIfPrimitive(builder, field.getType());
606    
607                            builder.returnResult();
608                        }
609                    });
610    
611                    return;
612                }
613    
614                plasticClass.introduceMethod(ConduitMethods.GET, new InstructionBuilderCallback()
615                {
616                    public void doBuild(InstructionBuilder builder)
617                    {
618                        invokeNavigateMethod(builder);
619    
620                        builder.getField(field.getDeclaringClass().getName(), field.getName(), field.getType());
621    
622                        // Cast not necessary here since the return type of get() is Object
623    
624                        boxIfPrimitive(builder, field.getType());
625    
626                        builder.returnResult();
627                    }
628                });
629            }
630    
631            private void implementGetter(final Method readMethod)
632            {
633                plasticClass.introduceMethod(ConduitMethods.GET, new InstructionBuilderCallback()
634                {
635                    public void doBuild(InstructionBuilder builder)
636                    {
637                        invokeNavigateMethod(builder);
638    
639                        invokeMethod(builder, readMethod, null, 0);
640    
641                        boxIfPrimitive(builder, conduitPropertyType);
642    
643                        builder.returnResult();
644                    }
645                });
646            }
647    
648            private void implementRangeOpGetter(final Tree rangeNode)
649            {
650                plasticClass.introduceMethod(ConduitMethods.GET, new InstructionBuilderCallback()
651                {
652                    public void doBuild(InstructionBuilder builder)
653                    {
654                        // Put the delegate on top of the stack
655    
656                        builder.loadThis().getField(getDelegateField());
657    
658                        invokeMethod(builder, DelegateMethods.RANGE, rangeNode, 0);
659    
660                        builder.returnResult();
661                    }
662                });
663            }
664    
665            /**
666             * @param node subexpression to invert
667             */
668            private void implementNotOpGetter(final Tree node)
669            {
670                // Implement get() as navigate, then do a method invocation based on node
671                // then, then pass (wrapped) result to delegate.invert()
672    
673                plasticClass.introduceMethod(ConduitMethods.GET, new InstructionBuilderCallback()
674                {
675                    public void doBuild(InstructionBuilder builder)
676                    {
677                        Type expressionType = implementNotExpression(builder, node);
678    
679                        // Yes, we know this will always be the case, for now.
680    
681                        boxIfPrimitive(builder, expressionType);
682    
683                        builder.returnResult();
684                    }
685                });
686            }
687    
688            /**
689             * The first part of any implementation of get() or set(): invoke the navigation method
690             * and if the result is null, return immediately.
691             */
692            private void invokeNavigateMethod(InstructionBuilder builder)
693            {
694                builder.loadThis().loadArgument(0).invokeVirtual(navMethod);
695    
696                builder.dupe().when(Condition.NULL, RETURN_NULL);
697            }
698    
699            /**
700             * Uses the builder to add instructions for a subexpression.
701             *
702             * @param builder    used to add instructions
703             * @param activeType type of value on top of the stack when this code will execute, or null if no value on stack
704             * @param node       defines the expression
705             * @return the expression type
706             */
707            private Type implementSubexpression(InstructionBuilder builder, Type activeType, Tree node)
708            {
709                Term term;
710    
711                while (true)
712                {
713                    switch (node.getType())
714                    {
715                        case IDENTIFIER:
716                        case INVOKE:
717    
718                            if (activeType == null)
719                            {
720                                invokeGetRootMethod(builder);
721    
722                                activeType = rootType;
723                            }
724    
725                            term = buildTerm(activeType, node);
726    
727                            term.callback.doBuild(builder);
728    
729                            return term.type;
730    
731                        case INTEGER:
732    
733                            builder.loadConstant(new Long(node.getText()));
734    
735                            return long.class;
736    
737                        case DECIMAL:
738    
739                            builder.loadConstant(new Double(node.getText()));
740    
741                            return double.class;
742    
743                        case STRING:
744    
745                            builder.loadConstant(node.getText());
746    
747                            return String.class;
748    
749                        case DEREF:
750                        case SAFEDEREF:
751    
752                            if (activeType == null)
753                            {
754                                invokeGetRootMethod(builder);
755    
756                                activeType = rootType;
757                            }
758    
759                            term = analyzeDerefNode(activeType, node);
760    
761                            term.callback.doBuild(builder);
762    
763                            activeType = GenericsUtils.asClass(term.type);
764    
765                            node = node.getChild(1);
766    
767                            break;
768    
769                        case TRUE:
770                        case FALSE:
771    
772                            builder.loadConstant(node.getType() == TRUE ? 1 : 0);
773    
774                            return boolean.class;
775    
776                        case LIST:
777    
778                            return implementListConstructor(builder, node);
779    
780                        case MAP:
781                            return implementMapConstructor(builder, node);
782    
783                        case NOT:
784    
785                            return implementNotExpression(builder, node);
786    
787                        case THIS:
788    
789                            invokeGetRootMethod(builder);
790    
791                            return rootType;
792    
793                        case NULL:
794    
795                            builder.loadNull();
796    
797                            return Void.class;
798    
799                        default:
800                            throw unexpectedNodeType(node, TRUE, FALSE, INTEGER, DECIMAL, STRING, DEREF, SAFEDEREF,
801                                    IDENTIFIER, INVOKE, LIST, NOT, THIS, NULL);
802                    }
803                }
804            }
805    
806            public void invokeGetRootMethod(InstructionBuilder builder)
807            {
808                builder.loadThis().loadArgument(0).invokeVirtual(getRootMethod);
809            }
810    
811            private void implementListGetter(final Tree listNode)
812            {
813                plasticClass.introduceMethod(ConduitMethods.GET, new InstructionBuilderCallback()
814                {
815                    public void doBuild(InstructionBuilder builder)
816                    {
817                        implementListConstructor(builder, listNode);
818    
819                        builder.returnResult();
820                    }
821                });
822            }
823    
824            private Type implementListConstructor(InstructionBuilder builder, Tree listNode)
825            {
826                // First, create an empty instance of ArrayList
827    
828                int count = listNode.getChildCount();
829    
830                builder.newInstance(ArrayList.class);
831                builder.dupe().loadConstant(count).invokeConstructor(ArrayList.class, int.class);
832    
833                for (int i = 0; i < count; i++)
834                {
835                    builder.dupe(); // the ArrayList
836    
837                    Type expressionType = implementSubexpression(builder, null, listNode.getChild(i));
838    
839                    boxIfPrimitive(builder, GenericsUtils.asClass(expressionType));
840    
841                    // Add the value to the array, then pop off the returned boolean
842                    builder.invoke(ArrayListMethods.ADD).pop();
843                }
844    
845                return ArrayList.class;
846            }
847    
848            private void implementMapGetter(final Tree mapNode)
849            {
850                plasticClass.introduceMethod(ConduitMethods.GET, new InstructionBuilderCallback()
851                {
852                    public void doBuild(InstructionBuilder builder)
853                    {
854                        implementMapConstructor(builder, mapNode);
855    
856                        builder.returnResult();
857                    }
858                });
859            }
860    
861            private Type implementMapConstructor(InstructionBuilder builder, Tree mapNode)
862            {
863                int count = mapNode.getChildCount();
864                builder.newInstance(HashMap.class);
865                builder.dupe().loadConstant(count).invokeConstructor(HashMap.class, int.class);
866    
867                for (int i = 0; i < count; i += 2)
868                {
869                    builder.dupe();
870    
871                    //build the key:
872                    Type keyType = implementSubexpression(builder, null, mapNode.getChild(i));
873                    boxIfPrimitive(builder, GenericsUtils.asClass(keyType));
874    
875                    //and the value:
876                    Type valueType = implementSubexpression(builder, null, mapNode.getChild(i + 1));
877                    boxIfPrimitive(builder, GenericsUtils.asClass(valueType));
878    
879                    //put the value into the array, then pop off the returned object.
880                    builder.invoke(HashMapMethods.PUT).pop();
881    
882                }
883    
884                return HashMap.class;
885            }
886    
887    
888            private void implementNoOpSetter()
889            {
890                implementNoOpMethod(ConduitMethods.SET, "Expression '%s' for class %s is read-only.", expression,
891                        rootType.getName());
892            }
893    
894            public void implementNoOpMethod(MethodDescription method, String format, Object... arguments)
895            {
896                final String message = String.format(format, arguments);
897    
898                plasticClass.introduceMethod(method).changeImplementation(new InstructionBuilderCallback()
899                {
900                    public void doBuild(InstructionBuilder builder)
901                    {
902                        builder.throwException(RuntimeException.class, message);
903                    }
904                });
905            }
906    
907            /**
908             * Invokes a method that may take parameters. The children of the invokeNode are subexpressions
909             * to be evaluated, and potentially coerced, so that they may be passed to the method.
910             *
911             * @param builder     constructs code
912             * @param method      method to invoke
913             * @param node        INVOKE or RANGEOP node
914             * @param childOffset offset within the node to the first child expression (1 in an INVOKE node because the
915             *                    first child is the method name, 0 in a RANGEOP node)
916             */
917            private void invokeMethod(InstructionBuilder builder, Method method, Tree node, int childOffset)
918            {
919                // We start with the target object for the method on top of the stack.
920                // Next, we have to push each method parameter, which may include boxing/deboxing
921                // and coercion. Once the code is in good shape, there's a lot of room to optimize
922                // the bytecode (a bit too much boxing/deboxing occurs, as well as some unnecessary
923                // trips through TypeCoercer). We might also want to have a local variable to store
924                // the root object (result of getRoot()).
925    
926                Class[] parameterTypes = method.getParameterTypes();
927    
928                for (int i = 0; i < parameterTypes.length; i++)
929                {
930                    Type expressionType = implementSubexpression(builder, null, node.getChild(i + childOffset));
931    
932                    // The value left on the stack is not primitive, and expressionType represents
933                    // its real type.
934    
935                    Class parameterType = parameterTypes[i];
936    
937                    if (!parameterType.isAssignableFrom(GenericsUtils.asClass(expressionType)))
938                    {
939                        boxIfPrimitive(builder, expressionType);
940    
941                        builder.loadThis().getField(getDelegateField());
942                        builder.swap().loadTypeConstant(PlasticUtils.toWrapperType(parameterType));
943                        builder.invoke(DelegateMethods.COERCE);
944    
945                        if (parameterType.isPrimitive())
946                        {
947                            builder.castOrUnbox(parameterType.getName());
948                        } else
949                        {
950                            builder.checkcast(parameterType);
951                        }
952                    }
953    
954                    // And that should leave an object of the correct type on the stack,
955                    // ready for the method invocation.
956                }
957    
958                // Now the target object and all parameters are in place.
959    
960                builder.invoke(method.getDeclaringClass(), method.getReturnType(), method.getName(),
961                        method.getParameterTypes());
962            }
963    
964            /**
965             * Analyzes a DEREF or SAFEDEREF node, proving back a term that identifies its type and provides a callback to
966             * peform the dereference.
967             *
968             * @return a term indicating the type of the expression to this point, and a {@link InstructionBuilderCallback}
969             *         to advance the evaluation of the expression form the previous value to the current
970             */
971            private Term analyzeDerefNode(Type activeType, Tree node)
972            {
973                // The first child is the term.
974    
975                Tree term = node.getChild(0);
976    
977                boolean allowNull = node.getType() == SAFEDEREF;
978    
979                return buildTerm(activeType, term, allowNull ? NullHandling.ALLOW : NullHandling.FORBID);
980            }
981    
982            private Term buildTerm(Type activeType, Tree term, final NullHandling nullHandling)
983            {
984                assertNodeType(term, IDENTIFIER, INVOKE);
985    
986                final Term simpleTerm = buildTerm(activeType, term);
987    
988                if (simpleTerm.genericType.isPrimitive())
989                    return simpleTerm;
990    
991                return simpleTerm.withCallback(new InstructionBuilderCallback()
992                {
993                    public void doBuild(InstructionBuilder builder)
994                    {
995                        simpleTerm.callback.doBuild(builder);
996    
997                        builder.dupe().when(Condition.NULL, new InstructionBuilderCallback()
998                        {
999                            public void doBuild(InstructionBuilder builder)
1000                            {
1001                                switch (nullHandling)
1002                                {
1003                                    // It is necessary to load a null onto the stack (even if there's already one
1004                                    // there) because of the verifier. It sees the return when the stack contains an
1005                                    // intermediate value (along the navigation chain) and thinks the method is
1006                                    // returning a value of the wrong type.
1007    
1008                                    case ALLOW:
1009                                        builder.loadNull().returnResult();
1010    
1011                                    case FORBID:
1012    
1013                                        builder.loadConstant(simpleTerm.description);
1014                                        builder.loadConstant(expression);
1015                                        builder.loadArgument(0);
1016    
1017                                        builder.invokeStatic(PropertyConduitSourceImpl.class, NullPointerException.class,
1018                                                "nullTerm", String.class, String.class, Object.class);
1019                                        builder.throwException();
1020    
1021                                        break;
1022    
1023                                }
1024                            }
1025                        });
1026                    }
1027                });
1028            }
1029    
1030            private void assertNodeType(Tree node, int... expected)
1031            {
1032                int type = node.getType();
1033    
1034                for (int e : expected)
1035                {
1036                    if (type == e)
1037                        return;
1038                }
1039    
1040                throw unexpectedNodeType(node, expected);
1041            }
1042    
1043            private RuntimeException unexpectedNodeType(Tree node, int... expected)
1044            {
1045                List<String> tokenNames = CollectionFactory.newList();
1046    
1047                for (int i = 0; i < expected.length; i++)
1048                    tokenNames.add(PropertyExpressionParser.tokenNames[expected[i]]);
1049    
1050                String message = String.format("Node %s was type %s, but was expected to be (one of) %s.",
1051                        node.toStringTree(), PropertyExpressionParser.tokenNames[node.getType()],
1052                        InternalUtils.joinSorted(tokenNames));
1053    
1054                return new RuntimeException(message);
1055            }
1056    
1057            private Term buildTerm(Type activeType, Tree termNode)
1058            {
1059                switch (termNode.getType())
1060                {
1061                    case INVOKE:
1062    
1063                        return buildInvokeTerm(activeType, termNode);
1064    
1065                    case IDENTIFIER:
1066    
1067                        return buildPropertyAccessTerm(activeType, termNode);
1068    
1069                    default:
1070                        throw unexpectedNodeType(termNode, INVOKE, IDENTIFIER);
1071                }
1072            }
1073    
1074            private Term buildPropertyAccessTerm(Type activeType, Tree termNode)
1075            {
1076                String propertyName = termNode.getText();
1077    
1078                PropertyAdapter adapter = findPropertyAdapter(activeType, propertyName);
1079    
1080                // Prefer the accessor over the field
1081    
1082                if (adapter.getReadMethod() != null)
1083                {
1084                    return buildGetterMethodAccessTerm(activeType, propertyName,
1085                            adapter.getReadMethod());
1086                }
1087    
1088                if (adapter.getField() != null)
1089                {
1090                    return buildPublicFieldAccessTerm(activeType, propertyName,
1091                            adapter.getField());
1092                }
1093    
1094                throw new RuntimeException(String.format(
1095                        "Property '%s' of class %s is not readable (it has no read accessor method).", adapter.getName(),
1096                        adapter.getBeanType().getName()));
1097            }
1098    
1099            public PropertyAdapter findPropertyAdapter(Type activeType, String propertyName)
1100            {
1101                Class activeClass = GenericsUtils.asClass(activeType);
1102    
1103                ClassPropertyAdapter classAdapter = access.getAdapter(activeClass);
1104                PropertyAdapter adapter = classAdapter.getPropertyAdapter(propertyName);
1105    
1106                if (adapter == null)
1107                {
1108                    final List<String> names = classAdapter.getPropertyNames();
1109                    final String className = activeClass.getName();
1110                    throw new UnknownValueException(String.format(
1111                            "Class %s does not contain a property (or public field) named '%s'.", className, propertyName),
1112                            new AvailableValues("Properties (and public fields)", names));
1113                }
1114                return adapter;
1115            }
1116    
1117            private Term buildGetterMethodAccessTerm(final Type activeType, String propertyName, final Method readMethod)
1118            {
1119                Type returnType = GenericsUtils.extractActualType(activeType, readMethod);
1120    
1121                return new Term(returnType, propertyName, new InstructionBuilderCallback()
1122                {
1123                    public void doBuild(InstructionBuilder builder)
1124                    {
1125                        invokeMethod(builder, readMethod, null, 0);
1126    
1127                        Type genericType = GenericsUtils.extractActualType(activeType, readMethod);
1128    
1129                        castToGenericType(builder, readMethod.getReturnType(), genericType);
1130                    }
1131                });
1132            }
1133    
1134            private Term buildPublicFieldAccessTerm(Type activeType, String propertyName, final Field field)
1135            {
1136                final Type fieldType = GenericsUtils.extractActualType(activeType, field);
1137    
1138                return new Term(fieldType, propertyName, new InstructionBuilderCallback()
1139                {
1140                    public void doBuild(InstructionBuilder builder)
1141                    {
1142                        Class rawFieldType = field.getType();
1143    
1144                        String rawTypeName = PlasticUtils.toTypeName(rawFieldType);
1145                        String containingClassName = field.getDeclaringClass().getName();
1146                        String fieldName = field.getName();
1147    
1148                        if (isStatic(field))
1149                        {
1150                            // We've gone to the trouble of loading the root object, or navigated to some other object,
1151                            // but we don't need or want the instance, since it's a static field we're accessing.
1152                            // Ideally, we would optimize this, and only generate and invoke the getRoot() and nav() methods as needed, but
1153                            // access to public fields is relatively rare, and the cost is just the unused bytecode.
1154    
1155                            builder.pop();
1156    
1157                            builder.getStaticField(containingClassName, fieldName, rawTypeName);
1158    
1159                        } else
1160                        {
1161                            builder.getField(containingClassName, fieldName, rawTypeName);
1162                        }
1163    
1164                        castToGenericType(builder, rawFieldType, fieldType);
1165                    }
1166    
1167                });
1168            }
1169    
1170            /**
1171             * Casts the results of a field read or method invocation based on generic information.
1172             *
1173             * @param builder     used to add instructions
1174             * @param rawType     the simple type (often Object) of the field (or method return type)
1175             * @param genericType the generic Type, from which parameterizations can be determined
1176             */
1177            private void castToGenericType(InstructionBuilder builder, Class rawType, final Type genericType)
1178            {
1179                if (!genericType.equals(rawType))
1180                {
1181                    Class castType = GenericsUtils.asClass(genericType);
1182                    builder.checkcast(castType);
1183                }
1184            }
1185    
1186            private Term buildInvokeTerm(final Type activeType, final Tree invokeNode)
1187            {
1188                String methodName = invokeNode.getChild(0).getText();
1189    
1190                int parameterCount = invokeNode.getChildCount() - 1;
1191    
1192                Class activeClass = GenericsUtils.asClass(activeType);
1193    
1194                final Method method = findMethod(activeClass, methodName, parameterCount);
1195    
1196                if (method.getReturnType().equals(void.class))
1197                    throw new RuntimeException(String.format("Method %s.%s() returns void.", activeClass.getName(),
1198                            methodName));
1199    
1200                Type returnType = GenericsUtils.extractActualType(activeType, method);
1201    
1202                return new Term(returnType, toUniqueId(method), new AnnotationProvider()
1203                {
1204                    public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
1205                    {
1206                        return method.getAnnotation(annotationClass);
1207                    }
1208                }, new InstructionBuilderCallback()
1209                {
1210                    public void doBuild(InstructionBuilder builder)
1211                    {
1212                        invokeMethod(builder, method, invokeNode, 1);
1213    
1214                        Type genericType = GenericsUtils.extractActualType(activeType, method);
1215    
1216                        castToGenericType(builder, method.getReturnType(), genericType);
1217                    }
1218                }
1219                );
1220            }
1221    
1222            private Method findMethod(Class activeType, String methodName, int parameterCount)
1223            {
1224                Class searchType = activeType;
1225    
1226                while (true)
1227                {
1228    
1229                    for (Method method : searchType.getMethods())
1230                    {
1231                        if (method.getParameterTypes().length == parameterCount
1232                                && method.getName().equalsIgnoreCase(methodName))
1233                            return method;
1234                    }
1235    
1236                    // TAP5-330
1237                    if (searchType != Object.class)
1238                    {
1239                        searchType = Object.class;
1240                    } else
1241                    {
1242                        throw new RuntimeException(String.format("Class %s does not contain a public method named '%s()'.",
1243                                activeType.getName(), methodName));
1244                    }
1245                }
1246            }
1247    
1248            public void boxIfPrimitive(InstructionBuilder builder, Type termType)
1249            {
1250                boxIfPrimitive(builder, GenericsUtils.asClass(termType));
1251            }
1252    
1253            public void boxIfPrimitive(InstructionBuilder builder, Class termType)
1254            {
1255                if (termType.isPrimitive())
1256                    builder.boxPrimitive(termType.getName());
1257            }
1258    
1259            public Class implementNotExpression(InstructionBuilder builder, final Tree notNode)
1260            {
1261                Type expressionType = implementSubexpression(builder, null, notNode.getChild(0));
1262    
1263                boxIfPrimitive(builder, expressionType);
1264    
1265                // Now invoke the delegate invert() method
1266    
1267                builder.loadThis().getField(getDelegateField());
1268    
1269                builder.swap().invoke(DelegateMethods.INVERT);
1270    
1271                return boolean.class;
1272            }
1273    
1274            /**
1275             * Defer creation of the delegate field unless actually needed.
1276             */
1277            private PlasticField getDelegateField()
1278            {
1279                if (delegateField == null)
1280                    delegateField = plasticClass.introduceField(PropertyConduitDelegate.class, "delegate").inject(
1281                            sharedDelegate);
1282    
1283                return delegateField;
1284            }
1285        }
1286    
1287        public PropertyConduitSourceImpl(PropertyAccess access, @ComponentLayer
1288        PlasticProxyFactory proxyFactory, TypeCoercer typeCoercer, StringInterner interner)
1289        {
1290            this.access = access;
1291            this.proxyFactory = proxyFactory;
1292            this.typeCoercer = typeCoercer;
1293            this.interner = interner;
1294    
1295            literalTrue = createLiteralConduit(Boolean.class, true);
1296            literalFalse = createLiteralConduit(Boolean.class, false);
1297            literalNull = createLiteralConduit(Void.class, null);
1298    
1299            sharedDelegate = new PropertyConduitDelegate(typeCoercer);
1300        }
1301    
1302        @PostInjection
1303        public void listenForInvalidations(@ComponentClasses InvalidationEventHub hub)
1304        {
1305            hub.addInvalidationListener(this);
1306        }
1307    
1308    
1309        public PropertyConduit create(Class rootClass, String expression)
1310        {
1311            assert rootClass != null;
1312            assert InternalUtils.isNonBlank(expression);
1313    
1314            MultiKey key = new MultiKey(rootClass, expression);
1315    
1316            PropertyConduit result = cache.get(key);
1317    
1318            if (result == null)
1319            {
1320                result = build(rootClass, expression);
1321                cache.put(key, result);
1322            }
1323    
1324            return result;
1325        }
1326    
1327        /**
1328         * Clears its caches when the component class loader is invalidated; this is
1329         * because it will be common to generate
1330         * conduits rooted in a component class (which will no longer be valid and
1331         * must be released to the garbage
1332         * collector).
1333         */
1334        public void objectWasInvalidated()
1335        {
1336            cache.clear();
1337        }
1338    
1339        /**
1340         * Builds a subclass of {@link PropertyConduitDelegate} that implements the
1341         * get() and set() methods and overrides the
1342         * constructor. In a worst-case race condition, we may build two (or more)
1343         * conduits for the same
1344         * rootClass/expression, and it will get sorted out when the conduit is
1345         * stored into the cache.
1346         *
1347         * @param rootClass  class of root object for expression evaluation
1348         * @param expression expression to be evaluated
1349         * @return the conduit
1350         */
1351        private PropertyConduit build(final Class rootClass, String expression)
1352        {
1353            Tree tree = parse(expression);
1354    
1355            try
1356            {
1357                switch (tree.getType())
1358                {
1359                    case TRUE:
1360    
1361                        return literalTrue;
1362    
1363                    case FALSE:
1364    
1365                        return literalFalse;
1366    
1367                    case NULL:
1368    
1369                        return literalNull;
1370    
1371                    case INTEGER:
1372    
1373                        // Leading '+' may screw this up.
1374                        // TODO: Singleton instance for "0", maybe "1"?
1375    
1376                        return createLiteralConduit(Long.class, new Long(tree.getText()));
1377    
1378                    case DECIMAL:
1379    
1380                        // Leading '+' may screw this up.
1381                        // TODO: Singleton instance for "0.0"?
1382    
1383                        return createLiteralConduit(Double.class, new Double(tree.getText()));
1384    
1385                    case STRING:
1386    
1387                        return createLiteralConduit(String.class, tree.getText());
1388    
1389                    case RANGEOP:
1390    
1391                        Tree fromNode = tree.getChild(0);
1392                        Tree toNode = tree.getChild(1);
1393    
1394                        // If the range is defined as integers (not properties, etc.)
1395                        // then it is possible to calculate the value here, once, and not
1396                        // build a new class.
1397    
1398                        if (fromNode.getType() != INTEGER || toNode.getType() != INTEGER)
1399                            break;
1400    
1401                        int from = Integer.parseInt(fromNode.getText());
1402                        int to = Integer.parseInt(toNode.getText());
1403    
1404                        IntegerRange ir = new IntegerRange(from, to);
1405    
1406                        return createLiteralConduit(IntegerRange.class, ir);
1407    
1408                    case THIS:
1409    
1410                        return createLiteralThisPropertyConduit(rootClass);
1411    
1412                    default:
1413                        break;
1414                }
1415    
1416                return proxyFactory.createProxy(InternalPropertyConduit.class,
1417                        new PropertyConduitBuilder(rootClass, expression, tree)).newInstance();
1418            } catch (Exception ex)
1419            {
1420                throw new PropertyExpressionException(String.format("Exception generating conduit for expression '%s': %s",
1421                        expression, InternalUtils.toMessage(ex)), expression, ex);
1422            }
1423        }
1424    
1425        private PropertyConduit createLiteralThisPropertyConduit(final Class rootClass)
1426        {
1427            return new PropertyConduit()
1428            {
1429                public Object get(Object instance)
1430                {
1431                    return instance;
1432                }
1433    
1434                public void set(Object instance, Object value)
1435                {
1436                    throw new RuntimeException(ServicesMessages.literalConduitNotUpdateable());
1437                }
1438    
1439                public Class getPropertyType()
1440                {
1441                    return rootClass;
1442                }
1443    
1444                public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
1445                {
1446                    return invariantAnnotationProvider.getAnnotation(annotationClass);
1447                }
1448            };
1449        }
1450    
1451        private <T> PropertyConduit createLiteralConduit(Class<T> type, T value)
1452        {
1453            return new LiteralPropertyConduit(typeCoercer, type, invariantAnnotationProvider, interner.format(
1454                    "LiteralPropertyConduit[%s]", value), value);
1455        }
1456    
1457        private Tree parse(String expression)
1458        {
1459            InputStream is = new ByteArrayInputStream(expression.getBytes());
1460    
1461            ANTLRInputStream ais;
1462    
1463            try
1464            {
1465                ais = new ANTLRInputStream(is);
1466            } catch (IOException ex)
1467            {
1468                throw new RuntimeException(ex);
1469            }
1470    
1471            PropertyExpressionLexer lexer = new PropertyExpressionLexer(ais);
1472    
1473            CommonTokenStream tokens = new CommonTokenStream(lexer);
1474    
1475            PropertyExpressionParser parser = new PropertyExpressionParser(tokens);
1476    
1477            try
1478            {
1479                return (Tree) parser.start().getTree();
1480            } catch (Exception ex)
1481            {
1482                throw new RuntimeException(String.format("Error parsing property expression '%s': %s.", expression,
1483                        ex.getMessage()), ex);
1484            }
1485        }
1486    
1487        /**
1488         * May be invoked from fabricated PropertyConduit instances.
1489         */
1490        public static NullPointerException nullTerm(String term, String expression, Object root)
1491        {
1492            String message = String.format("Property '%s' (within property expression '%s', of %s) is null.", term,
1493                    expression, root);
1494    
1495            return new NullPointerException(message);
1496        }
1497    
1498        private static String toUniqueId(Method method)
1499        {
1500            StringBuilder builder = new StringBuilder(method.getName()).append("(");
1501            String sep = "";
1502    
1503            for (Class parameterType : method.getParameterTypes())
1504            {
1505                builder.append(sep);
1506                builder.append(PlasticUtils.toTypeName(parameterType));
1507    
1508                sep = ",";
1509            }
1510    
1511            return builder.append(")").toString();
1512        }
1513    }