View Javadoc
1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.util;
17  
18  import static org.junit.Assert.assertEquals;
19  import static org.junit.Assert.assertFalse;
20  import static org.junit.Assert.assertNotNull;
21  import static org.junit.Assert.assertNull;
22  import static org.junit.Assert.assertSame;
23  import static org.junit.Assert.assertTrue;
24  import static org.junit.Assert.fail;
25  
26  import java.beans.PropertyDescriptor;
27  import java.beans.PropertyEditorSupport;
28  import java.io.Serializable;
29  import java.lang.annotation.Retention;
30  import java.lang.annotation.RetentionPolicy;
31  import java.lang.reflect.Method;
32  import java.math.BigDecimal;
33  import java.sql.Timestamp;
34  import java.text.SimpleDateFormat;
35  import java.util.Arrays;
36  import java.util.Date;
37  import java.util.List;
38  import java.util.Map;
39  
40  import org.junit.AfterClass;
41  import org.junit.BeforeClass;
42  import org.junit.Test;
43  import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
44  import org.kuali.rice.core.api.util.type.KualiDecimal;
45  import org.kuali.rice.core.api.util.type.KualiPercent;
46  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
47  import org.kuali.rice.krad.uif.UifConstants;
48  import org.kuali.rice.krad.uif.UifParameters;
49  import org.kuali.rice.krad.uif.component.BindingInfo;
50  import org.kuali.rice.krad.uif.container.CollectionGroup;
51  import org.kuali.rice.krad.uif.container.CollectionGroupBase;
52  import org.kuali.rice.krad.uif.container.CollectionGroupBuilder;
53  import org.kuali.rice.krad.uif.container.Group;
54  import org.kuali.rice.krad.uif.container.GroupBase;
55  import org.kuali.rice.krad.uif.element.Action;
56  import org.kuali.rice.krad.uif.element.Message;
57  import org.kuali.rice.krad.uif.element.ViewHeader;
58  import org.kuali.rice.krad.uif.layout.StackedLayoutManager;
59  import org.kuali.rice.krad.uif.layout.StackedLayoutManagerBase;
60  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
61  import org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl;
62  import org.kuali.rice.krad.uif.view.FormView;
63  import org.kuali.rice.krad.uif.view.ViewPresentationControllerBase;
64  import org.kuali.rice.krad.util.GlobalVariables;
65  import org.kuali.rice.krad.web.bind.RequestAccessible;
66  import org.kuali.rice.krad.web.bind.UifConfigurableWebBindingInitializer;
67  import org.kuali.rice.krad.web.bind.UifServletRequestDataBinder;
68  import org.kuali.rice.krad.web.form.UifFormBase;
69  import org.slf4j.Logger;
70  import org.slf4j.LoggerFactory;
71  import org.springframework.mock.web.MockHttpServletRequest;
72  import org.springframework.web.context.request.RequestContextHolder;
73  import org.springframework.web.context.request.ServletRequestAttributes;
74  import org.springframework.web.context.request.ServletWebRequest;
75  import org.springframework.web.servlet.ModelAndView;
76  
77  public class ObjectPropertyUtilsTest extends ProcessLoggingUnitTest {
78  
79      final Logger LOG = LoggerFactory.getLogger(ObjectPropertyUtilsTest.class);
80  
81      @Retention(RetentionPolicy.RUNTIME)
82      public @interface TestAnnotation {
83          String afoo();
84      }
85  
86      @BeforeClass
87      public static void setup() throws Exception {
88          UifUnitTestUtils.establishMockConfig("ObjectPropertyUtilsTest");
89      }
90  
91      @AfterClass
92      public static void teardown() throws Exception {
93          UifUnitTestUtils.tearDownMockConfig();
94      }
95  
96      public static class TestBean implements Serializable {
97  
98          private static final long serialVersionUID = 1L;
99  
100         public TestBean() {}
101 
102         @RequestAccessible
103         private String rwProp;
104         
105         private TestBeanTwo complexProp;
106 
107         public String getRwProp() {
108             return this.rwProp;
109         }
110 
111         public void setRwProp(String r) {
112             this.rwProp = r;
113         }
114 
115         private String woProp;
116 
117         public void setWoProp(String w) {
118             this.woProp = w;
119         }
120 
121         private String roProp;
122 
123         @TestAnnotation(afoo = "abar")
124         public String getRoProp() {
125             return this.roProp;
126         }
127 
128         private Boolean bitProp;
129 
130         public boolean isBitProp() {
131             return bitProp != null && bitProp;
132         }
133 
134         public Boolean getBitProp() {
135             return bitProp;
136         }
137 
138         public void setBitProp(Boolean bitProp) {
139             this.bitProp = bitProp;
140         }
141 
142         private boolean booleanProp;
143 
144         public boolean isBooleanProp() {
145             return booleanProp;
146         }
147 
148         public void setBooleanProp(boolean booleanProp) {
149             this.booleanProp = booleanProp;
150         }
151 
152         private Timestamp timestampProp;
153 
154         public Timestamp getTimestampProp() {
155             return timestampProp;
156         }
157 
158         public void setTimestampProp(Timestamp timestampProp) {
159             this.timestampProp = timestampProp;
160         }
161 
162         private Date dateProp;
163 
164         public Date getDateProp() {
165             return dateProp;
166         }
167 
168         public void setDateProp(Date dateProp) {
169             this.dateProp = dateProp;
170         }
171 
172         private int intProp;
173 
174         public int getIntProp() {
175             return intProp;
176         }
177 
178         private BigDecimal bigDecimalProp;
179 
180         public BigDecimal getBigDecimalProp() {
181             return bigDecimalProp;
182         }
183 
184         public void setBigDecimalProp(BigDecimal bigDecimalProp) {
185             this.bigDecimalProp = bigDecimalProp;
186         }
187 
188         public void setIntProp(int intProp) {
189             this.intProp = intProp;
190         }
191 
192         private Integer integerProp;
193 
194         public Integer getIntegerProp() {
195             return integerProp;
196         }
197 
198         public void setIntegerProp(Integer integerProp) {
199             this.integerProp = integerProp;
200         }
201 
202         private TestBean next;
203 
204         public TestBean getNext() {
205             return next;
206         }
207 
208         public void setNext(TestBean next) {
209             this.next = next;
210         }
211 
212         private List<String> stuffs;
213 
214         public List<String> getStuffs() {
215             return stuffs;
216         }
217 
218         public void setStuffs(List<String> stuffs) {
219             this.stuffs = stuffs;
220         }
221 
222         private Object[] arrayProp;
223 
224         public Object[] getArrayProp() {
225             return arrayProp;
226         }
227 
228         public void setArrayProp(Object[] arrayProp) {
229             this.arrayProp = arrayProp;
230         }
231 
232         private Map<String, Object> mapProp;
233 
234         public Map<String, Object> getMapProp() {
235             return this.mapProp;
236         }
237 
238         public void setMapProp(Map<String, Object> mapProp) {
239             this.mapProp = mapProp;
240         }
241 
242         /**
243          * @return the complexProp
244          */
245         public TestBeanTwo getComplexProp() {
246             return this.complexProp;
247         }
248 
249         /**
250          * @param complexProp the complexProp to set
251          */
252         public void setComplexProp(TestBeanTwo complexProp) {
253             this.complexProp = complexProp;
254         }
255     }
256 
257     public static class TestBeanTwo {
258 
259         private String fooProp;
260 
261         /**
262          * @return the fooProp
263          */
264         public String getFooProp() {
265             return this.fooProp;
266         }
267 
268         /**
269          * @param fooProp the fooProp to set
270          */
271         public void setFooProp(String fooProp) {
272             this.fooProp = fooProp;
273         }
274     }
275 
276     @Test
277     public void testSetBoolean() {
278         TestBean tb = new TestBean();
279         ObjectPropertyUtils.setPropertyValue(tb, "booleanProp", "true");
280         assertTrue(tb.isBooleanProp());
281     }
282 
283     @Test
284     public void testGetPropertyDescriptor() {
285         Map<String, PropertyDescriptor> pds = ObjectPropertyUtils.getPropertyDescriptors(TestBean.class);
286         assertNotNull(pds.get("rwProp"));
287         assertNotNull(pds.get("roProp"));
288         assertNotNull(pds.get("woProp"));
289         assertNull(pds.get("foobar"));
290     }
291 
292     @Test
293     public void testGet() {
294         TestBean tb = new TestBean();
295         tb.setRwProp("foobar");
296         assertEquals("foobar", ObjectPropertyUtils.getPropertyValue(tb, "rwProp"));
297 
298         tb.roProp = "barbaz";
299         assertEquals("barbaz", ObjectPropertyUtils.getPropertyValue(tb, "roProp"));
300 
301         try {
302             ObjectPropertyUtils.getPropertyValue(tb, "woProp");
303             // KULRICE-10677 - should return null - fail("expected exception");
304         } catch (RuntimeException e) {
305             // KULRICE-10677 - should return null
306             throw e;
307         }
308     }
309 
310     @Test
311     public void testLookup() {
312         TestBean tb = new TestBean();
313         tb.roProp = "barbaz";
314         assertEquals("barbaz", ObjectPropertyUtils.getPropertyValue(tb, "roProp"));
315 
316         Map<String, Object> tm = new java.util.HashMap<String, Object>();
317         tb.setMapProp(tm);
318         tm.put("barbaz", "hooray!");
319         tm.put("bar.baz", "hoorah!");
320         tm.put("bar.[baz]", "foobah!");
321         tm.put("b'('r.[\"ain)\"s]", "zombie!");
322         assertEquals("hooray!", ObjectPropertyUtils.getPropertyValue(tb, "mapProp[barbaz]"));
323         assertEquals("hooray!", ObjectPropertyUtils.getPropertyValue(tb, "mapProp['barbaz']"));
324         assertEquals("hooray!", ObjectPropertyUtils.getPropertyValue(tb, "mapProp[\"barbaz\"]"));
325         assertEquals("hoorah!", ObjectPropertyUtils.getPropertyValue(tb, "mapProp[bar.baz]"));
326         assertEquals("foobah!", ObjectPropertyUtils.getPropertyValue(tb, "mapProp[bar.[baz]]"));
327         assertEquals("zombie!", ObjectPropertyUtils.getPropertyValue(tb, "mapProp['b'('r.[\"ain)\"s]']"));
328         assertEquals("zombie!", ObjectPropertyUtils.getPropertyValue(tb, "mapProp[b'('r.[\"ain)\"s]]"));
329 
330         TestBean tb2 = new TestBean();
331         tb2.setRwProp("foodbar");
332         tb.setNext(tb2);
333         tm.put("blah", new Object[]{"next", "rwProp"});
334         tm.put("baz", tb2);
335         assertTrue(ObjectPropertyUtils.isReadableProperty(tb, "mapProp[\"baz\"].rwProp"));
336         assertEquals("barbaz", ObjectPropertyUtils.getPropertyValue(tb, "roProp"));
337         assertEquals("foodbar", ObjectPropertyUtils.getPropertyValue(tb, "next.rwProp"));
338 
339         tb.setStuffs(Arrays.asList(new String[]{"foo", "bar", "baz",}));
340         assertEquals("bar", ObjectPropertyUtils.getPropertyValue(tb, "stuffs[1]"));
341 
342         TestBean rb = new TestBean();
343         TestBean nb = new TestBean();
344         TestBean lb = new TestBean();
345         rb.setNext(nb);
346         nb.setNext(lb);
347         assertEquals(String.class, ObjectPropertyUtils.getPropertyType(rb, "next.next.rwProp"));
348         rb.setRwProp("baz");
349         nb.setRwProp("bar");
350         lb.setRwProp("foo");
351         assertEquals("foo", ObjectPropertyUtils.getPropertyValue(rb, "next.next.rwProp"));
352     }
353 
354     @Test
355     public void testSet() throws Throwable {
356         TestBean tb = new TestBean();
357         ObjectPropertyUtils.setPropertyValue(tb, "rwProp", "foobar");
358         assertEquals("foobar", tb.getRwProp());
359 
360         ObjectPropertyUtils.setPropertyValue(tb, "woProp", "barbaz");
361         assertEquals("barbaz", tb.woProp);
362 
363         try {
364             ObjectPropertyUtils.setPropertyValue(tb, "roProp", "bazfoo");
365             fail("expected exception");
366         } catch (Exception E) {
367             // OK!
368         }
369 
370         long now = System.currentTimeMillis();
371         ObjectPropertyUtils.setPropertyValue(tb, "dateProp", new java.sql.Date(now));
372         assertEquals(now, tb.getDateProp().getTime());
373 
374         String dateStr = "01/03/2013";
375         ObjectPropertyUtils.setPropertyValue(tb, "dateProp", dateStr);
376         Date expectedDate = new SimpleDateFormat("MM/dd/yy").parse(dateStr);
377         assertEquals(expectedDate, tb.getDateProp());
378     }
379 
380     @Test
381     public void testGetAsText() throws Throwable {
382         String dateStr = "01/03/2013";
383         Date expectedDate = new SimpleDateFormat("MM/dd/yy").parse(dateStr);
384         TestBean tb = new TestBean();
385         tb.setDateProp(expectedDate);
386         assertEquals("01/03/13", ObjectPropertyUtils.getPropertyValueAsText(tb, "dateProp"));
387     }
388 
389     public static class TestForm extends UifFormBase {
390         
391         private static final long serialVersionUID = 6597388705374534394L;
392         private TestBean bean;
393 
394         /**
395          * @return the bean
396          */
397         public TestBean getBean() {
398             return this.bean;
399         }
400 
401         /**
402          * @param bean the bean to set
403          */
404         public void setBean(TestBean bean) {
405             this.bean = bean;
406         }
407         
408     }
409     
410     public static class FooEditor extends PropertyEditorSupport {
411 
412         @Override
413         public String getAsText() {
414             return "foobar";
415         }
416         
417     }
418 
419     // TODO: Move to integration test
420 //    @Test
421 //    public void testCustomEditor() throws Throwable {
422 //        TestForm form = new TestForm();
423 //        MockHttpServletRequest request = new MockHttpServletRequest();
424 //        request.setParameter(UifParameters.VIEW_ID, "TestViewWithPropertyEditor");
425 //        request.setParameter("bean.next.rwProp", "not foobar");
426 //        RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
427 //        UifServletRequestDataBinder binder = new UifServletRequestDataBinder(form);
428 //        new UifConfigurableWebBindingInitializer().initBinder(binder, new ServletWebRequest(request));
429 //        binder.bind(request);
430 //
431 //        ModelAndView modelAndView = new ModelAndView();
432 //        modelAndView.addObject(UifConstants.DEFAULT_MODEL_NAME, form);
433 //
434 //        KRADServiceLocatorWeb.getModelAndViewService().prepareView(request, modelAndView);
435 //        assertEquals("foobar", ObjectPropertyUtils.getPropertyValueAsText(form, "bean.next.rwProp"));
436 //    }
437 
438     @Test
439     public void testPathSet() {
440         TestBean tb = new TestBean();
441         ObjectPropertyUtils.setPropertyValue(tb, "rwProp", "bar");
442         assertEquals("bar", tb.getRwProp());
443         ObjectPropertyUtils.setPropertyValue(tb, "next", new TestBean());
444         ObjectPropertyUtils.setPropertyValue(tb, "next.next", new TestBean());
445         ObjectPropertyUtils.setPropertyValue(tb, "next.next.woProp", "baz");
446         assertEquals("baz", tb.getNext().getNext().woProp);
447     }
448 
449     @Test
450     public void testBulk() {
451         Map<String, String> pd = new java.util.HashMap<String, String>();
452         pd.put("rwProp", "foobar");
453         pd.put("intProp", "3");
454         pd.put("booleanProp", "true");
455         pd.put("stuffs", "foo,bar,baz");
456         for (int i = 0; i < 10000; i++) {
457             TestBean tb = new TestBean();
458             ObjectPropertyUtils.copyPropertiesToObject(pd, tb);
459             assertEquals("foobar", tb.getRwProp());
460             assertEquals(3, tb.getIntProp());
461             assertEquals(true, tb.isBooleanProp());
462             assertEquals(3, tb.getStuffs().size());
463             assertEquals("foo", tb.getStuffs().get(0));
464             assertEquals("bar", tb.getStuffs().get(1));
465             assertEquals("baz", tb.getStuffs().get(2));
466         }
467     }
468 
469     @Test
470     public void testReadWriteCheck() {
471         TestBean tb = new TestBean();
472         assertTrue(ObjectPropertyUtils.isReadableProperty(tb, "rwProp"));
473         assertTrue(ObjectPropertyUtils.isWritableProperty(tb, "rwProp"));
474         assertTrue(ObjectPropertyUtils.isReadableProperty(tb, "roProp"));
475         assertFalse(ObjectPropertyUtils.isWritableProperty(tb, "roProp"));
476         assertFalse(ObjectPropertyUtils.isReadableProperty(tb, "woProp"));
477         assertTrue(ObjectPropertyUtils.isWritableProperty(tb, "woProp"));
478     }
479 
480     @Test
481     public void testKradUifTemplateHeaderMetadata() {
482         FormView formView = new FormView();
483         ViewHeader viewHeader = new ViewHeader();
484         formView.setHeader(viewHeader);
485         Message headerMetadataMessage = new Message();
486         viewHeader.setMetadataMessage(headerMetadataMessage);
487         assertSame(headerMetadataMessage, ObjectPropertyUtils.getPropertyValue(formView, "header.metadataMessage"));
488     }
489 
490     /**
491      * Collection list item type, for testing UIF interaction with ObjectPropertyUtils.
492      */
493     public static class CollectionTestItem {
494 
495         /**
496          * A string property, called foobar.
497          */
498         String foobar;
499 
500         /**
501          * @return the foobar
502          */
503         public String getFoobar() {
504             return this.foobar;
505         }
506 
507         /**
508          * @param foobar the foobar to set
509          */
510         public void setFoobar(String foobar) {
511             this.foobar = foobar;
512         }
513 
514     }
515 
516     /**
517      * Reference to a collection, for testing UIF interaction with ObjectPropertyUtils.
518      */
519     public static class CollectionTestListRef {
520 
521         /**
522          * The collection.
523          */
524         List<CollectionTestItem> bar;
525 
526         /**
527          * Mapping of new line items.
528          */
529         Map<String, CollectionTestItem> baz;
530 
531         /**
532          * @return the bar
533          */
534         public List<CollectionTestItem> getBar() {
535             return this.bar;
536         }
537 
538         /**
539          * @param bar the bar to set
540          */
541         public void setBar(List<CollectionTestItem> bar) {
542             this.bar = bar;
543         }
544 
545         /**
546          * @return the baz
547          */
548         public Map<String, CollectionTestItem> getBaz() {
549             return this.baz;
550         }
551 
552         /**
553          * @param baz the baz to set
554          */
555         public void setBaz(Map<String, CollectionTestItem> baz) {
556             this.baz = baz;
557         }
558 
559     }
560 
561     /**
562      * Mock collection form for UIF interaction with ObjectPropertyUtils.
563      */
564     public static class CollectionTestForm extends UifFormBase {
565 
566         private static final long serialVersionUID = 1798800132492441253L;
567 
568         /**
569          * Reference to a data object that has a collection.
570          */
571         CollectionTestListRef foo;
572 
573         /**
574          * @return the foo
575          */
576         public CollectionTestListRef getFoo() {
577             return this.foo;
578         }
579 
580         /**
581          * @param foo the foo to set
582          */
583         public void setFoo(CollectionTestListRef foo) {
584             this.foo = foo;
585         }
586 
587     }
588 
589     @Test
590     public void testKradUifCollectionGroupBuilder() throws Throwable {
591         UifUnitTestUtils.establishMockConfig(ObjectPropertyUtilsTest.class.getSimpleName());
592         UifUnitTestUtils.establishMockUserSession("testuser");
593         try {
594             // Performance medium generates this property path:
595             // newCollectionLines['newCollectionLines_'mediumCollection1'_.subList']
596 
597             // Below recreates the stack trace that ensued due to poorly escaped quotes,
598             // and proves that the parser works around bad quoting in a manner similar to BeanWrapper 
599 
600             final CollectionGroupBuilder collectionGroupBuilder = new CollectionGroupBuilder();
601             final CollectionTestForm form = new CollectionTestForm();
602             CollectionTestItem item = new CollectionTestItem();
603             item.setFoobar("barfoo");
604             ObjectPropertyUtils.setPropertyValue(form, "foo.baz['foo_bar_'badquotes'_.foobar']", item);
605             assertEquals("barfoo", form.foo.baz.get("foo_bar_'badquotes'_.foobar").foobar);
606 
607             final FormView view = new FormView();
608             view.setFormClass(CollectionTestForm.class);
609             view.setViewHelperService(new ViewHelperServiceImpl());
610             view.setPresentationController(new ViewPresentationControllerBase());
611             view.setAuthorizer(UifUnitTestUtils.getAllowMostViewAuthorizer());
612 
613             final CollectionGroup collectionGroup = new CollectionGroupBase();
614             collectionGroup.setCollectionObjectClass(CollectionTestItem.class);
615             collectionGroup.setAddLinePropertyName("addLineFoo");
616 
617             StackedLayoutManager layoutManager = new StackedLayoutManagerBase();
618             Group lineGroupPrototype = new GroupBase();
619             layoutManager.setLineGroupPrototype(lineGroupPrototype);
620             collectionGroup.setLayoutManager(layoutManager);
621 
622             BindingInfo addLineBindingInfo = new BindingInfo();
623             addLineBindingInfo.setBindingPath("foo.baz['foo_bar_'badquotes'_.foobar']");
624             collectionGroup.setAddLineBindingInfo(addLineBindingInfo);
625 
626             BindingInfo collectionBindingInfo = new BindingInfo();
627             collectionBindingInfo.setBindingPath("foo.bar");
628             collectionGroup.setBindingInfo(collectionBindingInfo);
629 
630             ViewLifecycle.encapsulateLifecycle(view, form, null, new Runnable() {
631                 @Override
632                 public void run() {
633                     collectionGroupBuilder.build(view, form, (CollectionGroup) CopyUtils.copy(collectionGroup));
634                 }
635             });
636         } finally {
637             GlobalVariables.setUserSession(null);
638             GlobalVariables.clear();
639             GlobalResourceLoader.stop();
640         }
641     }
642 
643     @Test
644     public void testSetStringMapFromInt() {
645         Action action = new Action();
646         ObjectPropertyUtils.setPropertyValue(action, "actionParameters['lineIndex']", 34);
647         assertEquals("34", action.getActionParameter("lineIndex"));
648     }
649 
650     @Test
651     public void testClassNavigation() {
652         assertEquals(String.class, ObjectPropertyUtils.getPropertyType(TestBean.class, "complexProp.fooProp"));
653 
654         try {
655             // valid first reference, invalid second reference
656             assertEquals(null, ObjectPropertyUtils.getPropertyType(TestBean.class, "complexProp.foobar"));
657             // NULL is ok - fail("KULRICE-10677 - is this ok?");
658         } catch (IllegalArgumentException e) {
659             // IAE is not ok - KULRICE-10677 is this ok?
660             throw e;
661         }
662 
663         try {
664             // invalid single reference
665             assertEquals(null, ObjectPropertyUtils.getPropertyType(TestBean.class, "foo"));
666             // NULL is ok - fail("KULRICE-10677 - is this ok?");
667         } catch (IllegalArgumentException e) {
668             // IAE is not ok - KULRICE-10677 is this ok?
669             throw e;
670         }
671 
672         try {
673             // invalid first reference
674             assertEquals(null, ObjectPropertyUtils.getPropertyType(TestBean.class, "foo.bar"));
675             // NULL is ok - fail("KULRICE-10677 - is this ok?");
676         } catch (IllegalArgumentException e) {
677             // IAE is not ok - KULRICE-10677 is this ok?
678             throw e;
679         }
680     }
681 
682     @Test
683     public void testPropertySplitPath() {
684         String path = "foo.foo1.foo2";
685         String[] splitPaths = ObjectPropertyUtils.splitPropertyPath(path);
686 
687         assertEquals(3, splitPaths.length);
688         assertEquals("foo", splitPaths[0]);
689         assertEquals("foo1", splitPaths[1]);
690         assertEquals("foo2", splitPaths[2]);
691 
692         path = "foo[1]";
693         splitPaths = ObjectPropertyUtils.splitPropertyPath(path);
694 
695         assertEquals(1, splitPaths.length);
696         assertEquals("foo[1]", splitPaths[0]);
697 
698         path = "foo.foo1['key.nested'].foo2";
699         splitPaths = ObjectPropertyUtils.splitPropertyPath(path);
700 
701         assertEquals(3, splitPaths.length);
702         assertEquals("foo", splitPaths[0]);
703         assertEquals("foo1['key.nested']", splitPaths[1]);
704         assertEquals("foo2", splitPaths[2]);
705 
706         path = "foo.foo1['key.nested'].foo2.foo3['key.nest.nest'].foo4";
707         splitPaths = ObjectPropertyUtils.splitPropertyPath(path);
708 
709         assertEquals(5, splitPaths.length);
710         assertEquals("foo", splitPaths[0]);
711         assertEquals("foo1['key.nested']", splitPaths[1]);
712         assertEquals("foo2", splitPaths[2]);
713         assertEquals("foo3['key.nest.nest']", splitPaths[3]);
714         assertEquals("foo4", splitPaths[4]);
715     }
716     
717     @Test
718     public void testCanonicalPath() {
719         String path = "foo.foo1.foo2";
720         assertEquals(path, ObjectPropertyUtils.getCanonicalPath(path));
721 
722         path = "foo[1]";
723         assertEquals("foo", ObjectPropertyUtils.getCanonicalPath(path));
724 
725         path = "foo.foo1['key.nested'].foo2";
726         assertEquals("foo.foo1.foo2", ObjectPropertyUtils.getCanonicalPath(path));
727 
728         path = "foo.foo1['key.nested'].foo2.foo3['key.nest.nest'].foo4";
729         assertEquals("foo.foo1.foo2.foo3.foo4", ObjectPropertyUtils.getCanonicalPath(path));
730     }
731 
732     // Classes used by testGetterInInterfaceOrSuperHasWiderType to check covariant return types on JDK6
733 
734     // Holds a concrete superclass of KualiPercent
735     public interface KualiDecimalHolder {
736         KualiDecimal getDecimal();
737     }
738 
739     // Holds an interface that is implemented by Integer
740     public interface ComparableHolder {
741         Comparable<?> getComparable();
742     }
743 
744     // Holds an abstract class that is extended by Integer
745     public interface NumberHolder {
746         Number getNumber();
747     }
748 
749     public class NumberedImplOne implements NumberHolder {
750         @Override
751         public Integer getNumber() {
752             return 1;
753         }
754     }
755 
756     public abstract class AbstractNumberHolder implements NumberHolder {
757         @Override
758         public abstract Number getNumber();
759     }
760 
761     public class ConcreteNumberHolder extends AbstractNumberHolder {
762         @Override
763         public Number getNumber() {
764             return 1;
765         }
766     }
767 
768     public class ConcreteNarrowedNumberHolder extends ConcreteNumberHolder {
769         @Override
770         public Integer getNumber() {
771             return 1;
772         }
773     }
774 
775     public class ConcreteNarrowedNumberHolderSub extends ConcreteNarrowedNumberHolder {
776 
777     }
778 
779     public class ComparableHolderImpl implements ComparableHolder {
780         @Override
781         public Integer getComparable() {
782             return 1;
783         }
784     }
785 
786     public class KualiPercentHolder implements KualiDecimalHolder {
787         @Override
788         public KualiPercent getDecimal() {
789             return new KualiPercent(1d);
790         }
791     }
792 
793     /**
794      * Verifies (at least when run on Linux in JDK6) our fix for the JDK6 Introspector
795      * bug/shortcoming WRT covariant return types that results in a wider getter method being
796      * preferred over a more specific implementation getter method.
797      * 
798      * <p>
799      * This makes the type reported by Introspector for read methods depending on the order of
800      * Methods depend on the order that they are returned by reflection on a class, which has been
801      * demonstrated to vary between platforms.
802      * </p>
803      */
804     @Test
805     public void testGetterInInterfaceOrSuperHasWiderType() {
806         Method readMethod = null;
807 
808         readMethod = ObjectPropertyUtils.getReadMethod(ComparableHolderImpl.class, "comparable");
809         assertEquals(Integer.class, readMethod.getReturnType());
810 
811         readMethod = ObjectPropertyUtils.getReadMethod(NumberedImplOne.class, "number");
812         assertEquals(Integer.class, readMethod.getReturnType());
813 
814         readMethod = ObjectPropertyUtils.getReadMethod(ConcreteNarrowedNumberHolder.class, "number");
815         assertEquals(Integer.class, readMethod.getReturnType());
816 
817         readMethod = ObjectPropertyUtils.getReadMethod(ConcreteNarrowedNumberHolderSub.class, "number");
818         assertEquals(Integer.class, readMethod.getReturnType());
819 
820         // This case is *not* covered by our workaround, and would fail w/ JDK 6 on Linux if enabled.
821         // The interface has a concrete superclass, which will be returned in JDK6 on Linux where the
822         // Method order returned by reflection on a class is different, and the Introspector isn't smart
823         // enough to ask which Method return type is more specific.
824         readMethod = ObjectPropertyUtils.getReadMethod(KualiPercentHolder.class, "decimal");
825 
826         if (readMethod.getReturnType() == KualiDecimal.class) {
827             LOG.info("I bet you're using JDK6 on Linux");
828         }
829 
830         // Other cases to test if we have to refine this functionality:
831         // * similar to the ConcreteNarrowedNumberHolder,
832         //   but creating an abstract impl of AbstractKualiDecimalHolder as the intermediate class
833         // * similar to ConcreteNarrowedNumberHolderSub, but ConcreteNarrowedKualiDecimalHolderSub
834     }
835 
836 }