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