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 }