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