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