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