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