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