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