View Javadoc
1   /**
2    * Copyright 2005-2016 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.BeanInfo;
27  import java.beans.IntrospectionException;
28  import java.beans.Introspector;
29  import java.beans.PropertyDescriptor;
30  import java.beans.PropertyEditorSupport;
31  import java.io.Serializable;
32  import java.lang.annotation.Retention;
33  import java.lang.annotation.RetentionPolicy;
34  import java.lang.reflect.Method;
35  import java.math.BigDecimal;
36  import java.sql.Timestamp;
37  import java.text.SimpleDateFormat;
38  import java.util.Arrays;
39  import java.util.Date;
40  import java.util.List;
41  import java.util.Map;
42  
43  import org.junit.AfterClass;
44  import org.junit.BeforeClass;
45  import org.junit.Test;
46  import org.kuali.rice.core.api.CoreConstants;
47  import org.kuali.rice.core.api.config.property.ConfigContext;
48  import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
49  import org.kuali.rice.core.api.util.type.KualiDecimal;
50  import org.kuali.rice.core.api.util.type.KualiPercent;
51  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
52  import org.kuali.rice.krad.uif.UifConstants;
53  import org.kuali.rice.krad.uif.UifParameters;
54  import org.kuali.rice.krad.uif.component.BindingInfo;
55  import org.kuali.rice.krad.uif.container.CollectionGroup;
56  import org.kuali.rice.krad.uif.container.CollectionGroupBase;
57  import org.kuali.rice.krad.uif.container.CollectionGroupBuilder;
58  import org.kuali.rice.krad.uif.container.Group;
59  import org.kuali.rice.krad.uif.container.GroupBase;
60  import org.kuali.rice.krad.uif.element.Action;
61  import org.kuali.rice.krad.uif.element.Message;
62  import org.kuali.rice.krad.uif.element.ViewHeader;
63  import org.kuali.rice.krad.uif.layout.StackedLayoutManager;
64  import org.kuali.rice.krad.uif.layout.StackedLayoutManagerBase;
65  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
66  import org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl;
67  import org.kuali.rice.krad.uif.view.FormView;
68  import org.kuali.rice.krad.uif.view.ViewPresentationControllerBase;
69  import org.kuali.rice.krad.util.GlobalVariables;
70  import org.kuali.rice.krad.util.KRADConstants;
71  import org.kuali.rice.krad.web.bind.RequestAccessible;
72  import org.kuali.rice.krad.web.bind.UifConfigurableWebBindingInitializer;
73  import org.kuali.rice.krad.web.bind.UifServletRequestDataBinder;
74  import org.kuali.rice.krad.web.form.UifFormBase;
75  import org.slf4j.Logger;
76  import org.slf4j.LoggerFactory;
77  import org.springframework.mock.web.MockHttpServletRequest;
78  import org.springframework.web.context.request.RequestContextHolder;
79  import org.springframework.web.context.request.ServletRequestAttributes;
80  import org.springframework.web.context.request.ServletWebRequest;
81  import org.springframework.web.servlet.ModelAndView;
82  
83  public class ObjectPropertyUtilsTest extends ProcessLoggingUnitTest {
84  
85      final Logger LOG = LoggerFactory.getLogger(ObjectPropertyUtilsTest.class);
86  
87      @Retention(RetentionPolicy.RUNTIME)
88      public @interface TestAnnotation {
89          String afoo();
90      }
91  
92      @BeforeClass
93      public static void setup() throws Exception {
94          UifUnitTestUtils.establishMockConfig("ObjectPropertyUtilsTest");
95      }
96  
97      @AfterClass
98      public static void teardown() throws Exception {
99          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 }