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