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