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