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