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