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