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}