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