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