001/** 002 * Copyright 2005-2014 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krad.uif.util; 017 018import static org.junit.Assert.assertEquals; 019import static org.junit.Assert.assertFalse; 020import static org.junit.Assert.assertNotNull; 021import static org.junit.Assert.assertNull; 022import static org.junit.Assert.assertSame; 023import static org.junit.Assert.assertTrue; 024import static org.junit.Assert.fail; 025 026import java.beans.PropertyDescriptor; 027import java.beans.PropertyEditorSupport; 028import java.io.Serializable; 029import java.lang.annotation.Retention; 030import java.lang.annotation.RetentionPolicy; 031import java.lang.reflect.Method; 032import java.math.BigDecimal; 033import java.sql.Timestamp; 034import java.text.SimpleDateFormat; 035import java.util.Arrays; 036import java.util.Date; 037import java.util.List; 038import java.util.Map; 039 040import org.junit.AfterClass; 041import org.junit.BeforeClass; 042import org.junit.Test; 043import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader; 044import org.kuali.rice.core.api.util.type.KualiDecimal; 045import org.kuali.rice.core.api.util.type.KualiPercent; 046import org.kuali.rice.krad.uif.UifParameters; 047import org.kuali.rice.krad.uif.component.BindingInfo; 048import org.kuali.rice.krad.uif.container.CollectionGroup; 049import org.kuali.rice.krad.uif.container.CollectionGroupBase; 050import org.kuali.rice.krad.uif.container.CollectionGroupBuilder; 051import org.kuali.rice.krad.uif.container.Group; 052import org.kuali.rice.krad.uif.container.GroupBase; 053import org.kuali.rice.krad.uif.element.Action; 054import org.kuali.rice.krad.uif.element.Message; 055import org.kuali.rice.krad.uif.element.ViewHeader; 056import org.kuali.rice.krad.uif.layout.StackedLayoutManager; 057import org.kuali.rice.krad.uif.layout.StackedLayoutManagerBase; 058import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle; 059import org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl; 060import org.kuali.rice.krad.uif.view.FormView; 061import org.kuali.rice.krad.uif.view.ViewPresentationControllerBase; 062import org.kuali.rice.krad.util.GlobalVariables; 063import org.kuali.rice.krad.web.bind.RequestAccessible; 064import org.kuali.rice.krad.web.bind.UifConfigurableWebBindingInitializer; 065import org.kuali.rice.krad.web.bind.UifServletRequestDataBinder; 066import org.kuali.rice.krad.web.controller.UifControllerHelper; 067import org.kuali.rice.krad.web.form.UifFormBase; 068import org.slf4j.Logger; 069import org.slf4j.LoggerFactory; 070import org.springframework.mock.web.MockHttpServletRequest; 071import org.springframework.web.context.request.RequestContextHolder; 072import org.springframework.web.context.request.ServletRequestAttributes; 073import org.springframework.web.context.request.ServletWebRequest; 074 075public class ObjectPropertyUtilsTest extends ProcessLoggingUnitTest { 076 077 final Logger LOG = LoggerFactory.getLogger(ObjectPropertyUtilsTest.class); 078 079 @Retention(RetentionPolicy.RUNTIME) 080 public @interface TestAnnotation { 081 String afoo(); 082 } 083 084 @BeforeClass 085 public static void setup() throws Exception { 086 UifUnitTestUtils.establishMockConfig("ObjectPropertyUtilsTest"); 087 } 088 089 @AfterClass 090 public static void teardown() throws Exception { 091 UifUnitTestUtils.tearDownMockConfig(); 092 } 093 094 public static class TestBean implements Serializable { 095 096 private static final long serialVersionUID = 1L; 097 098 public TestBean() {} 099 100 @RequestAccessible 101 private String rwProp; 102 103 private TestBeanTwo complexProp; 104 105 public String getRwProp() { 106 return this.rwProp; 107 } 108 109 public void setRwProp(String r) { 110 this.rwProp = r; 111 } 112 113 private String woProp; 114 115 public void setWoProp(String w) { 116 this.woProp = w; 117 } 118 119 private String roProp; 120 121 @TestAnnotation(afoo = "abar") 122 public String getRoProp() { 123 return this.roProp; 124 } 125 126 private Boolean bitProp; 127 128 public boolean isBitProp() { 129 return bitProp != null && bitProp; 130 } 131 132 public Boolean getBitProp() { 133 return bitProp; 134 } 135 136 public void setBitProp(Boolean bitProp) { 137 this.bitProp = bitProp; 138 } 139 140 private boolean booleanProp; 141 142 public boolean isBooleanProp() { 143 return booleanProp; 144 } 145 146 public void setBooleanProp(boolean booleanProp) { 147 this.booleanProp = booleanProp; 148 } 149 150 private Timestamp timestampProp; 151 152 public Timestamp getTimestampProp() { 153 return timestampProp; 154 } 155 156 public void setTimestampProp(Timestamp timestampProp) { 157 this.timestampProp = timestampProp; 158 } 159 160 private Date dateProp; 161 162 public Date getDateProp() { 163 return dateProp; 164 } 165 166 public void setDateProp(Date dateProp) { 167 this.dateProp = dateProp; 168 } 169 170 private int intProp; 171 172 public int getIntProp() { 173 return intProp; 174 } 175 176 private BigDecimal bigDecimalProp; 177 178 public BigDecimal getBigDecimalProp() { 179 return bigDecimalProp; 180 } 181 182 public void setBigDecimalProp(BigDecimal bigDecimalProp) { 183 this.bigDecimalProp = bigDecimalProp; 184 } 185 186 public void setIntProp(int intProp) { 187 this.intProp = intProp; 188 } 189 190 private Integer integerProp; 191 192 public Integer getIntegerProp() { 193 return integerProp; 194 } 195 196 public void setIntegerProp(Integer integerProp) { 197 this.integerProp = integerProp; 198 } 199 200 private TestBean next; 201 202 public TestBean getNext() { 203 return next; 204 } 205 206 public void setNext(TestBean next) { 207 this.next = next; 208 } 209 210 private List<String> stuffs; 211 212 public List<String> getStuffs() { 213 return stuffs; 214 } 215 216 public void setStuffs(List<String> stuffs) { 217 this.stuffs = stuffs; 218 } 219 220 private Object[] arrayProp; 221 222 public Object[] getArrayProp() { 223 return arrayProp; 224 } 225 226 public void setArrayProp(Object[] arrayProp) { 227 this.arrayProp = arrayProp; 228 } 229 230 private Map<String, Object> mapProp; 231 232 public Map<String, Object> getMapProp() { 233 return this.mapProp; 234 } 235 236 public void setMapProp(Map<String, Object> mapProp) { 237 this.mapProp = mapProp; 238 } 239 240 /** 241 * @return the complexProp 242 */ 243 public TestBeanTwo getComplexProp() { 244 return this.complexProp; 245 } 246 247 /** 248 * @param complexProp the complexProp to set 249 */ 250 public void setComplexProp(TestBeanTwo complexProp) { 251 this.complexProp = complexProp; 252 } 253 } 254 255 public static class TestBeanTwo { 256 257 private String fooProp; 258 259 /** 260 * @return the fooProp 261 */ 262 public String getFooProp() { 263 return this.fooProp; 264 } 265 266 /** 267 * @param fooProp the fooProp to set 268 */ 269 public void setFooProp(String fooProp) { 270 this.fooProp = fooProp; 271 } 272 } 273 274 @Test 275 public void testSetBoolean() { 276 TestBean tb = new TestBean(); 277 ObjectPropertyUtils.setPropertyValue(tb, "booleanProp", "true"); 278 assertTrue(tb.isBooleanProp()); 279 } 280 281 @Test 282 public void testGetPropertyDescriptor() { 283 Map<String, PropertyDescriptor> pds = ObjectPropertyUtils.getPropertyDescriptors(TestBean.class); 284 assertNotNull(pds.get("rwProp")); 285 assertNotNull(pds.get("roProp")); 286 assertNotNull(pds.get("woProp")); 287 assertNull(pds.get("foobar")); 288 } 289 290 @Test 291 public void testGet() { 292 TestBean tb = new TestBean(); 293 tb.setRwProp("foobar"); 294 assertEquals("foobar", ObjectPropertyUtils.getPropertyValue(tb, "rwProp")); 295 296 tb.roProp = "barbaz"; 297 assertEquals("barbaz", ObjectPropertyUtils.getPropertyValue(tb, "roProp")); 298 299 try { 300 ObjectPropertyUtils.getPropertyValue(tb, "woProp"); 301 // KULRICE-10677 - should return null - fail("expected exception"); 302 } catch (RuntimeException e) { 303 // KULRICE-10677 - should return null 304 throw e; 305 } 306 } 307 308 @Test 309 public void testLookup() { 310 TestBean tb = new TestBean(); 311 tb.roProp = "barbaz"; 312 assertEquals("barbaz", ObjectPropertyUtils.getPropertyValue(tb, "roProp")); 313 314 Map<String, Object> tm = new java.util.HashMap<String, Object>(); 315 tb.setMapProp(tm); 316 tm.put("barbaz", "hooray!"); 317 tm.put("bar.baz", "hoorah!"); 318 tm.put("bar.[baz]", "foobah!"); 319 tm.put("b'('r.[\"ain)\"s]", "zombie!"); 320 assertEquals("hooray!", ObjectPropertyUtils.getPropertyValue(tb, "mapProp[barbaz]")); 321 assertEquals("hooray!", ObjectPropertyUtils.getPropertyValue(tb, "mapProp['barbaz']")); 322 assertEquals("hooray!", ObjectPropertyUtils.getPropertyValue(tb, "mapProp[\"barbaz\"]")); 323 assertEquals("hoorah!", ObjectPropertyUtils.getPropertyValue(tb, "mapProp[bar.baz]")); 324 assertEquals("foobah!", ObjectPropertyUtils.getPropertyValue(tb, "mapProp[bar.[baz]]")); 325 assertEquals("zombie!", ObjectPropertyUtils.getPropertyValue(tb, "mapProp['b'('r.[\"ain)\"s]']")); 326 assertEquals("zombie!", ObjectPropertyUtils.getPropertyValue(tb, "mapProp[b'('r.[\"ain)\"s]]")); 327 328 TestBean tb2 = new TestBean(); 329 tb2.setRwProp("foodbar"); 330 tb.setNext(tb2); 331 tm.put("blah", new Object[]{"next", "rwProp"}); 332 tm.put("baz", tb2); 333 assertTrue(ObjectPropertyUtils.isReadableProperty(tb, "mapProp[\"baz\"].rwProp")); 334 assertEquals("barbaz", ObjectPropertyUtils.getPropertyValue(tb, "roProp")); 335 assertEquals("foodbar", ObjectPropertyUtils.getPropertyValue(tb, "next.rwProp")); 336 337 tb.setStuffs(Arrays.asList(new String[]{"foo", "bar", "baz",})); 338 assertEquals("bar", ObjectPropertyUtils.getPropertyValue(tb, "stuffs[1]")); 339 340 TestBean rb = new TestBean(); 341 TestBean nb = new TestBean(); 342 TestBean lb = new TestBean(); 343 rb.setNext(nb); 344 nb.setNext(lb); 345 assertEquals(String.class, ObjectPropertyUtils.getPropertyType(rb, "next.next.rwProp")); 346 rb.setRwProp("baz"); 347 nb.setRwProp("bar"); 348 lb.setRwProp("foo"); 349 assertEquals("foo", ObjectPropertyUtils.getPropertyValue(rb, "next.next.rwProp")); 350 } 351 352 @Test 353 public void testSet() throws Throwable { 354 TestBean tb = new TestBean(); 355 ObjectPropertyUtils.setPropertyValue(tb, "rwProp", "foobar"); 356 assertEquals("foobar", tb.getRwProp()); 357 358 ObjectPropertyUtils.setPropertyValue(tb, "woProp", "barbaz"); 359 assertEquals("barbaz", tb.woProp); 360 361 try { 362 ObjectPropertyUtils.setPropertyValue(tb, "roProp", "bazfoo"); 363 fail("expected exception"); 364 } catch (Exception E) { 365 // OK! 366 } 367 368 long now = System.currentTimeMillis(); 369 ObjectPropertyUtils.setPropertyValue(tb, "dateProp", new java.sql.Date(now)); 370 assertEquals(now, tb.getDateProp().getTime()); 371 372 String dateStr = "01/03/2013"; 373 ObjectPropertyUtils.setPropertyValue(tb, "dateProp", dateStr); 374 Date expectedDate = new SimpleDateFormat("MM/dd/yy").parse(dateStr); 375 assertEquals(expectedDate, tb.getDateProp()); 376 } 377 378 @Test 379 public void testGetAsText() throws Throwable { 380 String dateStr = "01/03/2013"; 381 Date expectedDate = new SimpleDateFormat("MM/dd/yy").parse(dateStr); 382 TestBean tb = new TestBean(); 383 tb.setDateProp(expectedDate); 384 assertEquals("01/03/13", ObjectPropertyUtils.getPropertyValueAsText(tb, "dateProp")); 385 } 386 387 public static class TestForm extends UifFormBase { 388 389 private static final long serialVersionUID = 6597388705374534394L; 390 private TestBean bean; 391 392 /** 393 * @return the bean 394 */ 395 public TestBean getBean() { 396 return this.bean; 397 } 398 399 /** 400 * @param bean the bean to set 401 */ 402 public void setBean(TestBean bean) { 403 this.bean = bean; 404 } 405 406 } 407 408 public static class FooEditor extends PropertyEditorSupport { 409 410 @Override 411 public String getAsText() { 412 return "foobar"; 413 } 414 415 } 416 417 @Test 418 public void testCustomEditor() throws Throwable { 419 TestForm form = new TestForm(); 420 MockHttpServletRequest request = new MockHttpServletRequest(); 421 request.setParameter(UifParameters.VIEW_ID, "TestView"); 422 request.setParameter("bean.next.rwProp", "not foobar"); 423 RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); 424 UifServletRequestDataBinder binder = new UifServletRequestDataBinder(form); 425 new UifConfigurableWebBindingInitializer().initBinder(binder, new ServletWebRequest(request)); 426 binder.bind(request); 427 UifControllerHelper.invokeViewLifecycle(request, form); 428 assertEquals("foobar", ObjectPropertyUtils.getPropertyValueAsText(form, "bean.next.rwProp")); 429 } 430 431 @Test 432 public void testPathSet() { 433 TestBean tb = new TestBean(); 434 ObjectPropertyUtils.setPropertyValue(tb, "rwProp", "bar"); 435 assertEquals("bar", tb.getRwProp()); 436 ObjectPropertyUtils.setPropertyValue(tb, "next", new TestBean()); 437 ObjectPropertyUtils.setPropertyValue(tb, "next.next", new TestBean()); 438 ObjectPropertyUtils.setPropertyValue(tb, "next.next.woProp", "baz"); 439 assertEquals("baz", tb.getNext().getNext().woProp); 440 } 441 442 @Test 443 public void testBulk() { 444 Map<String, String> pd = new java.util.HashMap<String, String>(); 445 pd.put("rwProp", "foobar"); 446 pd.put("intProp", "3"); 447 pd.put("booleanProp", "true"); 448 pd.put("stuffs", "foo,bar,baz"); 449 for (int i = 0; i < 10000; i++) { 450 TestBean tb = new TestBean(); 451 ObjectPropertyUtils.copyPropertiesToObject(pd, tb); 452 assertEquals("foobar", tb.getRwProp()); 453 assertEquals(3, tb.getIntProp()); 454 assertEquals(true, tb.isBooleanProp()); 455 assertEquals(3, tb.getStuffs().size()); 456 assertEquals("foo", tb.getStuffs().get(0)); 457 assertEquals("bar", tb.getStuffs().get(1)); 458 assertEquals("baz", tb.getStuffs().get(2)); 459 } 460 } 461 462 @Test 463 public void testReadWriteCheck() { 464 TestBean tb = new TestBean(); 465 assertTrue(ObjectPropertyUtils.isReadableProperty(tb, "rwProp")); 466 assertTrue(ObjectPropertyUtils.isWritableProperty(tb, "rwProp")); 467 assertTrue(ObjectPropertyUtils.isReadableProperty(tb, "roProp")); 468 assertFalse(ObjectPropertyUtils.isWritableProperty(tb, "roProp")); 469 assertFalse(ObjectPropertyUtils.isReadableProperty(tb, "woProp")); 470 assertTrue(ObjectPropertyUtils.isWritableProperty(tb, "woProp")); 471 } 472 473 @Test 474 public void testKradUifTemplateHeaderMetadata() { 475 FormView formView = new FormView(); 476 ViewHeader viewHeader = new ViewHeader(); 477 formView.setHeader(viewHeader); 478 Message headerMetadataMessage = new Message(); 479 viewHeader.setMetadataMessage(headerMetadataMessage); 480 assertSame(headerMetadataMessage, ObjectPropertyUtils.getPropertyValue(formView, "header.metadataMessage")); 481 } 482 483 /** 484 * Collection list item type, for testing UIF interaction with ObjectPropertyUtils. 485 */ 486 public static class CollectionTestItem { 487 488 /** 489 * A string property, called foobar. 490 */ 491 String foobar; 492 493 /** 494 * @return the foobar 495 */ 496 public String getFoobar() { 497 return this.foobar; 498 } 499 500 /** 501 * @param foobar the foobar to set 502 */ 503 public void setFoobar(String foobar) { 504 this.foobar = foobar; 505 } 506 507 } 508 509 /** 510 * Reference to a collection, for testing UIF interaction with ObjectPropertyUtils. 511 */ 512 public static class CollectionTestListRef { 513 514 /** 515 * The collection. 516 */ 517 List<CollectionTestItem> bar; 518 519 /** 520 * Mapping of new line items. 521 */ 522 Map<String, CollectionTestItem> baz; 523 524 /** 525 * @return the bar 526 */ 527 public List<CollectionTestItem> getBar() { 528 return this.bar; 529 } 530 531 /** 532 * @param bar the bar to set 533 */ 534 public void setBar(List<CollectionTestItem> bar) { 535 this.bar = bar; 536 } 537 538 /** 539 * @return the baz 540 */ 541 public Map<String, CollectionTestItem> getBaz() { 542 return this.baz; 543 } 544 545 /** 546 * @param baz the baz to set 547 */ 548 public void setBaz(Map<String, CollectionTestItem> baz) { 549 this.baz = baz; 550 } 551 552 } 553 554 /** 555 * Mock collection form for UIF interaction with ObjectPropertyUtils. 556 */ 557 public static class CollectionTestForm extends UifFormBase { 558 559 private static final long serialVersionUID = 1798800132492441253L; 560 561 /** 562 * Reference to a data object that has a collection. 563 */ 564 CollectionTestListRef foo; 565 566 /** 567 * @return the foo 568 */ 569 public CollectionTestListRef getFoo() { 570 return this.foo; 571 } 572 573 /** 574 * @param foo the foo to set 575 */ 576 public void setFoo(CollectionTestListRef foo) { 577 this.foo = foo; 578 } 579 580 } 581 582 @Test 583 public void testKradUifCollectionGroupBuilder() throws Throwable { 584 UifUnitTestUtils.establishMockConfig(ObjectPropertyUtilsTest.class.getSimpleName()); 585 UifUnitTestUtils.establishMockUserSession("testuser"); 586 try { 587 // Performance medium generates this property path: 588 // newCollectionLines['newCollectionLines_'mediumCollection1'_.subList'] 589 590 // Below recreates the stack trace that ensued due to poorly escaped quotes, 591 // and proves that the parser works around bad quoting in a manner similar to BeanWrapper 592 593 final CollectionGroupBuilder collectionGroupBuilder = new CollectionGroupBuilder(); 594 final CollectionTestForm form = new CollectionTestForm(); 595 CollectionTestItem item = new CollectionTestItem(); 596 item.setFoobar("barfoo"); 597 ObjectPropertyUtils.setPropertyValue(form, "foo.baz['foo_bar_'badquotes'_.foobar']", item); 598 assertEquals("barfoo", form.foo.baz.get("foo_bar_'badquotes'_.foobar").foobar); 599 600 final FormView view = new FormView(); 601 view.setFormClass(CollectionTestForm.class); 602 view.setViewHelperService(new ViewHelperServiceImpl()); 603 view.setPresentationController(new ViewPresentationControllerBase()); 604 view.setAuthorizer(UifUnitTestUtils.getAllowMostViewAuthorizer()); 605 606 final CollectionGroup collectionGroup = new CollectionGroupBase(); 607 collectionGroup.setCollectionObjectClass(CollectionTestItem.class); 608 collectionGroup.setAddLinePropertyName("addLineFoo"); 609 610 StackedLayoutManager layoutManager = new StackedLayoutManagerBase(); 611 Group lineGroupPrototype = new GroupBase(); 612 layoutManager.setLineGroupPrototype(lineGroupPrototype); 613 collectionGroup.setLayoutManager(layoutManager); 614 615 BindingInfo addLineBindingInfo = new BindingInfo(); 616 addLineBindingInfo.setBindingPath("foo.baz['foo_bar_'badquotes'_.foobar']"); 617 collectionGroup.setAddLineBindingInfo(addLineBindingInfo); 618 619 BindingInfo collectionBindingInfo = new BindingInfo(); 620 collectionBindingInfo.setBindingPath("foo.bar"); 621 collectionGroup.setBindingInfo(collectionBindingInfo); 622 623 ViewLifecycle.encapsulateLifecycle(view, form, null, new Runnable() { 624 @Override 625 public void run() { 626 collectionGroupBuilder.build(view, form, (CollectionGroup) CopyUtils.copy(collectionGroup)); 627 } 628 }); 629 } finally { 630 GlobalVariables.setUserSession(null); 631 GlobalVariables.clear(); 632 GlobalResourceLoader.stop(); 633 } 634 } 635 636 @Test 637 public void testSetStringMapFromInt() { 638 Action action = new Action(); 639 ObjectPropertyUtils.setPropertyValue(action, "actionParameters['lineIndex']", 34); 640 assertEquals("34", action.getActionParameter("lineIndex")); 641 } 642 643 @Test 644 public void testClassNavigation() { 645 assertEquals(String.class, ObjectPropertyUtils.getPropertyType(TestBean.class, "complexProp.fooProp")); 646 647 try { 648 // valid first reference, invalid second reference 649 assertEquals(null, ObjectPropertyUtils.getPropertyType(TestBean.class, "complexProp.foobar")); 650 // NULL is ok - fail("KULRICE-10677 - is this ok?"); 651 } catch (IllegalArgumentException e) { 652 // IAE is not ok - KULRICE-10677 is this ok? 653 throw e; 654 } 655 656 try { 657 // invalid single reference 658 assertEquals(null, ObjectPropertyUtils.getPropertyType(TestBean.class, "foo")); 659 // NULL is ok - fail("KULRICE-10677 - is this ok?"); 660 } catch (IllegalArgumentException e) { 661 // IAE is not ok - KULRICE-10677 is this ok? 662 throw e; 663 } 664 665 try { 666 // invalid first reference 667 assertEquals(null, ObjectPropertyUtils.getPropertyType(TestBean.class, "foo.bar")); 668 // NULL is ok - fail("KULRICE-10677 - is this ok?"); 669 } catch (IllegalArgumentException e) { 670 // IAE is not ok - KULRICE-10677 is this ok? 671 throw e; 672 } 673 } 674 675 @Test 676 public void testPropertySplitPath() { 677 String path = "foo.foo1.foo2"; 678 String[] splitPaths = ObjectPropertyUtils.splitPropertyPath(path); 679 680 assertEquals(3, splitPaths.length); 681 assertEquals("foo", splitPaths[0]); 682 assertEquals("foo1", splitPaths[1]); 683 assertEquals("foo2", splitPaths[2]); 684 685 path = "foo[1]"; 686 splitPaths = ObjectPropertyUtils.splitPropertyPath(path); 687 688 assertEquals(1, splitPaths.length); 689 assertEquals("foo[1]", splitPaths[0]); 690 691 path = "foo.foo1['key.nested'].foo2"; 692 splitPaths = ObjectPropertyUtils.splitPropertyPath(path); 693 694 assertEquals(3, splitPaths.length); 695 assertEquals("foo", splitPaths[0]); 696 assertEquals("foo1['key.nested']", splitPaths[1]); 697 assertEquals("foo2", splitPaths[2]); 698 699 path = "foo.foo1['key.nested'].foo2.foo3['key.nest.nest'].foo4"; 700 splitPaths = ObjectPropertyUtils.splitPropertyPath(path); 701 702 assertEquals(5, splitPaths.length); 703 assertEquals("foo", splitPaths[0]); 704 assertEquals("foo1['key.nested']", splitPaths[1]); 705 assertEquals("foo2", splitPaths[2]); 706 assertEquals("foo3['key.nest.nest']", splitPaths[3]); 707 assertEquals("foo4", splitPaths[4]); 708 } 709 710 // Classes used by testGetterInInterfaceOrSuperHasWiderType to check covariant return types on JDK6 711 712 // Holds a concrete superclass of KualiPercent 713 public interface KualiDecimalHolder { 714 KualiDecimal getDecimal(); 715 } 716 717 // Holds an interface that is implemented by Integer 718 public interface ComparableHolder { 719 Comparable<?> getComparable(); 720 } 721 722 // Holds an abstract class that is extended by Integer 723 public interface NumberHolder { 724 Number getNumber(); 725 } 726 727 public class NumberedImplOne implements NumberHolder { 728 @Override 729 public Integer getNumber() { 730 return 1; 731 } 732 } 733 734 public abstract class AbstractNumberHolder implements NumberHolder { 735 @Override 736 public abstract Number getNumber(); 737 } 738 739 public class ConcreteNumberHolder extends AbstractNumberHolder { 740 @Override 741 public Number getNumber() { 742 return 1; 743 } 744 } 745 746 public class ConcreteNarrowedNumberHolder extends ConcreteNumberHolder { 747 @Override 748 public Integer getNumber() { 749 return 1; 750 } 751 } 752 753 public class ConcreteNarrowedNumberHolderSub extends ConcreteNarrowedNumberHolder { 754 755 } 756 757 public class ComparableHolderImpl implements ComparableHolder { 758 @Override 759 public Integer getComparable() { 760 return 1; 761 } 762 } 763 764 public class KualiPercentHolder implements KualiDecimalHolder { 765 @Override 766 public KualiPercent getDecimal() { 767 return new KualiPercent(1d); 768 } 769 } 770 771 /** 772 * Verifies (at least when run on Linux in JDK6) our fix for the JDK6 Introspector 773 * bug/shortcoming WRT covariant return types that results in a wider getter method being 774 * preferred over a more specific implementation getter method. 775 * 776 * <p> 777 * This makes the type reported by Introspector for read methods depending on the order of 778 * Methods depend on the order that they are returned by reflection on a class, which has been 779 * demonstrated to vary between platforms. 780 * </p> 781 */ 782 @Test 783 public void testGetterInInterfaceOrSuperHasWiderType() { 784 Method readMethod = null; 785 786 readMethod = ObjectPropertyUtils.getReadMethod(ComparableHolderImpl.class, "comparable"); 787 assertEquals(Integer.class, readMethod.getReturnType()); 788 789 readMethod = ObjectPropertyUtils.getReadMethod(NumberedImplOne.class, "number"); 790 assertEquals(Integer.class, readMethod.getReturnType()); 791 792 readMethod = ObjectPropertyUtils.getReadMethod(ConcreteNarrowedNumberHolder.class, "number"); 793 assertEquals(Integer.class, readMethod.getReturnType()); 794 795 readMethod = ObjectPropertyUtils.getReadMethod(ConcreteNarrowedNumberHolderSub.class, "number"); 796 assertEquals(Integer.class, readMethod.getReturnType()); 797 798 // This case is *not* covered by our workaround, and would fail w/ JDK 6 on Linux if enabled. 799 // The interface has a concrete superclass, which will be returned in JDK6 on Linux where the 800 // Method order returned by reflection on a class is different, and the Introspector isn't smart 801 // enough to ask which Method return type is more specific. 802 readMethod = ObjectPropertyUtils.getReadMethod(KualiPercentHolder.class, "decimal"); 803 804 if (readMethod.getReturnType() == KualiDecimal.class) { 805 LOG.info("I bet you're using JDK6 on Linux"); 806 } 807 808 // Other cases to test if we have to refine this functionality: 809 // * similar to the ConcreteNarrowedNumberHolder, 810 // but creating an abstract impl of AbstractKualiDecimalHolder as the intermediate class 811 // * similar to ConcreteNarrowedNumberHolderSub, but ConcreteNarrowedKualiDecimalHolderSub 812 } 813 814}