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}