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