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