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