    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}