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