View Javadoc

1   /*
2    * Copyright (c) 2001-2004,
3    * RedVerst Group, ISP RAS http://www.ispras.ru
4    * All rights reserved.
5    *
6    * Redistribution and use in source and binary forms, with or without
7    * modification, are permitted provided that the following conditions are met:
8    *
9    * 1. Redistributions of source code must retain the above copyright notice, this
10   *    list of conditions and the following disclaimer.
11   *
12   * 2. Redistributions in binary form must reproduce the above copyright notice,
13   *    this list of conditions and the following disclaimer in the documentation
14   *    and/or other materials provided with the distribution.
15   *
16   * 3. The names "ATP", "TreeDL", "RedVerst", "ISP RAS"
17   *    may not be used to endorse or promote products derived from this software
18   *    without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
24   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
27   * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30   */
31  
32  package com.unitesk.atp.dynattrs;
33  
34  import java.lang.reflect.Array;
35  import java.lang.reflect.Method;
36  import java.util.HashMap;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Set;
40  
41  /***
42   * This is a collection of static methods to work with
43   * different implementations of attributes.
44   *
45   * @author <A href="mailto:demakov@ispras.ru">Alexey Demakov</A>
46   * @author <A href="mailto:monakhov@ispras.ru">Alexander Monakhov</A>
47   * @version $Id: Accessor.java,v 1.8 2006/02/09 06:30:07 all-x Exp $
48   */
49  public class Accessor
50  {
51      //--------------------------------------------------------------------------
52      // access to properties using JavaBeans naming conventions
53  
54      /***
55       * Converts the first char of a string to upper case.
56       *
57       * @param s         The string to convert.
58       * @return          The parameter string with the first character converted
59       *                  to uppercase if possible.
60       * @throws NullPointerException
61       *                  when <code>s == null</code>.
62       */
63      public static String capitalize( String s )
64      {
65          if( s.length() == 0 )
66          {
67              return s;
68          }
69  
70          char chars[] = s.toCharArray();
71          chars[0] = Character.toUpperCase( chars[0] );
72          return new String( chars );
73      }
74  
75      /***
76       * Converts the first char of a string to lower case.
77       *
78       * @param s         The string to convert.
79       * @return          The parameter string with the first character converted
80       *                  to lowercase if possible.
81       * @throws NullPointerException
82       *                  when <code>s == null</code>.
83       */
84      public static String decapitalize( String s )
85      {
86          if( s.length() == 0 )
87          {
88              return s;
89          }
90  
91          char chars[] = s.toCharArray();
92          chars[0] = Character.toLowerCase( chars[0] );
93          return new String( chars );
94      }
95  
96      /***
97       * Checks existence of bean property of the specified object.
98       *
99       * @param bean      The specified object.
100      * @param name      The specified bean property name.
101      * @return          <code>true</code> if there are bean property read method.
102      * @throws NullPointerException
103      *                  If <code>bean</code> is <code>null</code>.
104      * @throws AttributeException
105      *                 Some possible reasons:
106      *                 <UL>
107      *                 <LI><CODE>name == null</CODE></LI>
108      *                 </UL>
109      *                 (analyze exception parameters).
110      * @see #getBeanPropertyReadMethod(Object,String)
111      */
112     public static boolean hasBeanProperty( Object bean, String name )
113     {
114         return getBeanPropertyReadMethod( bean, name ) != null;
115     }
116 
117         /***
118          * Checks writability of bean property of the specified object.
119          *
120          * @param bean   The specified object.
121          * @param name   The specified bean property name.
122          * @return <code>true</code> if there are bean property write method.
123      * @throws NullPointerException
124      *                  If <code>bean</code> is <code>null</code>.
125      * @throws AttributeException
126      *                 Some possible reasons:
127      *                 <UL>
128      *                 <LI><CODE>name == null</CODE></LI>
129      *                 </UL>
130      *                 (analyze exception parameters).
131          * @see #getBeanPropertyWriteMethod(Object,String)
132          */
133     public static boolean isBeanPropertyWritable( Object bean, String name )
134     {
135         return getBeanPropertyWriteMethod( bean, name ) != null;
136     }
137 
138     /***
139      * Returns a value of the specified bean property.
140      *
141      * @param bean      The specified object.
142      * @param name      The specified bean property name.
143      * @return          The value of bean property of specified object.
144      * @throws NullPointerException
145      *                  If <code>bean</code> is <code>null</code>.
146      * @throws AttributeException
147      *                 Some possible reasons:
148      *                 <UL>
149      *                 <LI><CODE>name == null</CODE></LI>
150      *                 <LI>there is no attribute with the specified name</LI>
151      *                 <LI>there was an underlying exception</LI>
152      *                 </UL>
153      *                 (analyze exception parameters).
154      * @see #hasBeanProperty(Object,String)
155      */
156     public static Object getBeanProperty( Object bean, String name )
157     {
158         Method method = getBeanPropertyReadMethod( bean, name );
159 
160         if( method == null )
161         {
162             throw new AttributeException( bean, name );
163         }
164 
165         try
166         {
167             return method.invoke( bean, get_par );
168         }
169         catch( Exception e )
170         {
171             throw new AttributeException( bean, name, null, e );
172         }
173     }
174 
175     /***
176      * Checks if a value of the bean property can be indexed.
177      * This method uses {@link #getBeanProperty(Object, String) getBeanProperty(bean,name)}
178      * to get property value.
179      *
180      * @param bean   The specified object.
181      * @param name   The specified bean property name.
182      * @return <code>true</code> if value of bean property {@link #isIndexed(Object) is indexed}.
183      * @throws NullPointerException
184      *                  If <code>bean</code> is <code>null</code>.
185      * @throws AttributeException
186      *                 Some possible reasons:
187      *                 <UL>
188      *                 <LI><CODE>name == null</CODE></LI>
189      *                 <LI>there is no attribute with the specified name</LI>
190      *                 <LI>attribute value is null</LI>
191      *                 <LI>there was an underlying exception</LI>
192      *                 </UL>
193      *                 (analyze exception parameters).
194      * @throws AttributeException
195      *                  If bean property with the specified name doesn't exist (cause == null),
196      *                  or there was an underlying exception (see cause).
197      */
198     public static boolean isBeanPropertyIndexed( Object bean, String name )
199     {
200         Object value = getBeanProperty( bean, name );
201 
202         try
203         {
204             return isIndexed( value );
205         }
206         catch( Exception e )
207         {
208             throw new AttributeException( bean, name, value, e );
209         }
210     }
211 
212     /***
213      * Sets the new value of the specified bean property.
214      *
215      * @param bean   The specified object.
216      * @param name   The specified bean property name.
217      * @param value   The new value of bean property.
218      * @throws NullPointerException
219      *                  If <code>bean</code> is <code>null</code>.
220      * @throws AttributeException
221      *                 Some possible reasons:
222      *                 <UL>
223      *                 <LI><CODE>name == null</CODE></LI>
224      *                 <LI>there is no attribute with the specified name</LI>
225      *                 <LI>there was an underlying exception</LI>
226      *                 </UL>
227      *                 (analyze exception parameters).
228      */
229     public static void setBeanProperty( Object bean, String name, Object value )
230     {
231         Method method = getBeanPropertyWriteMethod( bean, name );
232 
233         if( method == null )
234         {
235             throw new AttributeException( bean, name, value );
236         }
237 
238         set_par[0] = value;
239         try
240         {
241             method.invoke( bean, set_par );
242         }
243         catch( Exception e )
244         {
245             throw new AttributeException( bean, name, value, e );
246         }
247     }
248 
249     /***
250      * Returns the set of names of bean properties of the specified object.
251      * @param bean      The specified object.
252      * @return          The set that contains names of all object's bean properties.
253      * @throws NullPointerException
254      *                  If bean is null.
255      */
256     public static Set/*String*/ getBeanPropertyNames( Object bean )
257     {
258         return getBeanClassPropertyNames( bean.getClass() );
259     }
260 
261     /***
262      * Returns the set of names of bean properties for objects of the specified class.
263      * @param cls       The specified class.
264      * @return          The set that contains names of bean properties for objects of
265      *                  the specified class.
266      * @throws NullPointerException
267      *                  If cls is null.
268      */
269     public static Set/*String*/ getBeanClassPropertyNames( Class cls )
270     {
271         return getBeanClassPropertyDescriptors( cls ).keySet();
272     }
273 
274     //--------------------------------------------------------------------------
275     // indexed values
276 
277     /***
278      * Checks if value can be indexed.
279      * A value is indexed if it is an array or instance of {@link List}.
280      * <CODE>null</CODE> is not indexed value.
281      *
282      * @param value     The specified value.
283      * @return          <code>true</code> if the specified value is array
284      *                  or instance of {@link List}.
285      */
286     public static boolean isIndexed( Object value )
287     {
288         return value != null && (value.getClass().isArray() || value instanceof List);
289     }
290 
291     /***
292      * Returns the size of the indexed value.
293      *
294      * @param value     The specified indexed value.
295      * @return          Size of the specified indexed value.
296      * @throws NullPointerException
297      *                  If the specified value is <code>null</code>.
298      * @throws ClassCastException
299      *                  If the specified value {@link #isIndexed(Object) is not indexed}.
300      */
301     public static int sizeIndexed( Object value )
302     {
303         if( value.getClass().isArray() )
304         {
305             return Array.getLength( value );
306         }
307         return ((List)value).size();
308     }
309 
310     /***
311      * Returns element of the specified index value.
312      *
313      * @param value     The specified indexed value.
314      * @param index     The specified index.
315      * @return          An element of array or list with the specified index.
316      * @throws NullPointerException
317      *                  If the specified value is <code>null</code>.
318      * @throws ClassCastException
319      *                  If the specified value {@link #isIndexed(Object) is not indexed}.
320      * @throws IndexOutOfBoundsException
321      *                  If the specified index is out of bounds of indexed value.
322      */
323     public static Object getIndexed( Object value, int index )
324     {
325         if( value.getClass().isArray() )
326         {
327             return Array.get( value, index );
328         }
329         return ((List)value).get( index );
330     }
331 
332     /***
333      * Sets the new element's value of indexed value.
334      *
335      * @param value   The specified indexed value.
336      * @param index   The specified index.
337      * @param elem    The new value of element.
338      * @throws NullPointerException
339      *                  If <code>value</code> is <code>null</code>.
340      * @throws ClassCastException
341      *                  If the specified value {@link #isIndexed(Object) is not indexed}.
342      * @throws IndexOutOfBoundsException
343      *                  If the specified index is out of bounds of indexed value.
344      */
345     public static void setIndexed( Object value, int index, Object elem )
346     {
347         if( value.getClass().isArray() )
348         {
349             Array.set( value, index, elem );
350         } else {
351             ((List)value).set( index, elem );
352         }
353     }
354 
355     //--------------------------------------------------------------------------
356     // attributes for any object. if object is instance of Attributed,
357     // interface Attributed is used else interface JavaBeans is used
358 
359     /***
360      * Checks existence of an attribute with the specified name of the specified object.
361      *
362      * @param obj   The specified object.
363      * @param name  The specified attribute name.
364      * @return If <code>obj</code> implements {@link Attributed} interface use
365      *         {@link Attributed#hasAttribute(String) its method} to get attribute value.
366      *         Otherwise {@link #hasBeanProperty(Object,String) check existence of bean property}.
367      * @throws NullPointerException
368      *                  If <code>obj</code> is <code>null</code>.
369      * @throws AttributeException
370      *                 Some possible reasons:
371      *                 <UL>
372      *                 <LI><CODE>name == null</CODE></LI>
373      *                 <LI>there was an underlying exception</LI>
374      *                 </UL>
375      *                 (analyze exception parameters).
376      * @since 3.6.3
377      */
378     public static boolean hasAttribute( Object obj, String name )
379     {
380         if( obj instanceof Attributed )
381         {
382             return ((Attributed)obj).hasAttribute( name );
383         } else {
384             return hasBeanProperty( obj, name );
385         }
386     }
387 
388     /***
389      * Returns value of attribute of the specified object.
390      *
391      * @param obj   The specified object.
392      * @param name  The specified attribute name.
393      * @return If <code>obj</code> implements {@link Attributed} interface use
394      *         {@link Attributed#getAttribute(String) its method} to get attribute value.
395      *         Otherwise return {@link #getBeanProperty(Object,String) bean property value}.
396      * @throws NullPointerException
397      *                  If <code>obj</code> is <code>null</code>.
398      * @throws AttributeException
399      *                 Some possible reasons:
400      *                 <UL>
401      *                 <LI><CODE>name == null</CODE></LI>
402      *                 <LI>there is no attribute with the specified name</LI>
403      *                 <LI>there was an underlying exception</LI>
404      *                 </UL>
405      *                 (analyze exception parameters).
406      */
407     public static Object getAttribute( Object obj, String name )
408     {
409         if( obj instanceof Attributed )
410         {
411             return ((Attributed)obj).getAttribute( name );
412         } else {
413             return getBeanProperty( obj, name );
414         }
415     }
416 
417     /***
418      * Returns size of indexed attribute of the specified object.
419      *
420      * @param obj       The specified object.
421      * @param name      The specified attribute name.
422      * @return          If <code>obj</code> implements {@link Attributed} interface use
423      *                  {@link Attributed#sizeAttribute(String) its method} to get attribute size.
424      *                  Otherwise {@link #getAttribute(Object,String) get attribute value}
425      *                  and return {@link #sizeIndexed(Object) its size}.
426      * @throws NullPointerException
427      *                  If <code>obj</code> is <code>null</code>.
428      * @throws AttributeException
429      *                  Some possible reasons:
430      *                  <UL>
431      *                  <LI><CODE>name == null</CODE></LI>
432      *                  <LI>there is no attribute with the specified name</LI>
433      *                  <LI>attribute value is not indexed</LI>
434      *                  <LI>there was an underlying exception</LI>
435      *                  </UL>
436      *                  (analyze exception parameters).
437      */
438     public static int sizeAttribute( Object obj, String name )
439     {
440         if( obj instanceof Attributed )
441         {
442             return ((Attributed)obj).sizeAttribute( name );
443         } else {
444             Object value = getAttribute( obj, name );
445             try
446             {
447                 return sizeIndexed( value );
448             }
449             catch( Exception e )
450             {
451                 throw new AttributeException( obj, name, value, e );
452             }
453         }
454     }
455 
456     /***
457      * Returns element of indexed attribute by index.
458      *
459      * @param obj       The specified object.
460      * @param name      The specified attribute name.
461      * @param index     The specified index.
462      * @return          If <code>obj</code> implements {@link Attributed} interface use
463      *                  {@link Attributed#getAttribute(String,int) its method} to get
464      *                  attribute element.
465      *                  Otherwise {@link #getAttribute(Object,String) get attribute value}
466      *                  and return {@link #getIndexed(Object,int) its element by index}.
467      * @throws NullPointerException
468      *                  If <code>obj</code> is <code>null</code>.
469      * @throws AttributeException
470      *                  Some possible reasons:
471      *                  <UL>
472      *                  <LI><CODE>name == null</CODE></LI>
473      *                  <LI>there is no attribute with the specified name</LI>
474      *                  <LI>attribute value is not indexed</LI>
475      *                  <LI>there was an underlying exception</LI>
476      *                  </UL>
477      *                  (analyze exception parameters).
478      */
479     public static Object getAttribute( Object obj, String name, int index )
480     {
481         if( obj instanceof Attributed )
482         {
483             return ((Attributed)obj).getAttribute( name, index );
484         } else {
485             Object value = getAttribute( obj, name );
486             try
487             {
488                 return getIndexed( value, index );
489             }
490             catch( Exception e )
491             {
492                 throw new AttributeException( obj, name, value, e );
493             }
494         }
495     }
496 
497     /***
498      * Sets the new value of attribute.
499      * If <code>obj</code> implements {@link Attributed} interface use
500      * {@link Attributed#setAttribute(String,Object) its method} to set
501      * attribute.
502      * Otherwise tries {@link #setBeanProperty(Object, String, Object) to set bean property}
503      *
504      * @param obj       The specified object.
505      * @param name      The specified attribute name.
506      * @param value      The new value of attribute.
507      * @throws AttributeException
508      *                 Some possible reasons:
509      *                 <UL>
510      *                 <LI><CODE>name == null</CODE></LI>
511      *                 <LI>there is no attribute with the specified name</LI>
512      *                 <LI>attribute is not writable</LI>
513      *                 <LI>attribute element type is not compatible with <CODE>elem</CODE></LI>
514      *                 <LI>there was an underlying exception</LI>
515      *                 </UL>
516      *                 (analyze exception parameters).
517      * @since 3.5.3 
518      */
519     public static void setAttribute( Object obj
520                                    , String name
521                                    , Object value
522                                    )
523     {
524         if( obj instanceof Attributed )
525         {
526             ((Attributed)obj).setAttribute( name, value);
527         } else {
528             setBeanProperty( obj, name, value );
529         }
530     }
531 
532     /***
533      * Sets the new value of element of indexed attribute by index.
534      * If <code>obj</code> implements {@link Attributed} interface use
535      * {@link Attributed#setAttribute(String,int,Object) its method} to set
536      * attribute element.
537      * Otherwise {@link #getAttribute(Object,String) get attribute value}
538      * and {@link #setIndexed(Object,int,Object) set its element by index}.
539      *
540      * @param obj       The specified object.
541      * @param name      The specified attribute name.
542      * @param index     The specified index.
543      * @param elem      The new value of element.
544      * @throws AttributeException
545      *                 Some possible reasons:
546      *                 <UL>
547      *                 <LI><CODE>name == null</CODE></LI>
548      *                 <LI>there is no attribute with the specified name</LI>
549      *                 <LI>attribute is not writable</LI>
550      *                 <LI>attribute is not indexed</LI>
551      *                 <LI>attribute index is out of bounds</LI>
552      *                 <LI>attribute element type is not compatible with <CODE>elem</CODE></LI>
553      *                 <LI>there was an underlying exception</LI>
554      *                 </UL>
555      *                 (analyze exception parameters).
556      */
557     public static void setAttribute( Object obj
558                                    , String name
559                                    , int index
560                                    , Object elem
561                                    )
562     {
563         if( obj instanceof Attributed )
564         {
565             ((Attributed)obj).setAttribute( name, index, elem);
566         } else {
567             Object value = getAttribute( obj, name );
568             try
569             {
570                 setIndexed( value, index, elem );
571             }
572             catch( Exception e )
573             {
574                 throw new AttributeException( obj, name, value, e );
575             }
576         }
577     }
578 
579         /***
580          * Returns the set of attribute names the specified object.
581          * @param obj   The specified object.
582          * @return If <code>obj</code> implements {@link Attributed} interface use
583          *         {@link Attributed#getAttributeNames() its method} to get attribute names.
584          *         Otherwise {@link #getBeanPropertyNames(Object) get bean property names}.
585          */
586     public static Set/*String*/ getAttributeNames( Object obj )
587     {
588         if( obj instanceof Attributed )
589         {
590             return ((Attributed)obj).getAttributeNames();
591         } else {
592             return getBeanPropertyNames( obj );
593         }
594     }
595 
596     /***
597      * Checks whether the given name is a valid identifier.
598      * This method uses {@link Character#isUnicodeIdentifierStart(char)}
599      * and {@link Character#isUnicodeIdentifierPart(char)} methods.
600      *
601      * @param name   the name to check
602      * @return       <code>true</code> if the given name is a valid identifier,
603      *               <code>false</code> otherwise.
604      */
605     public static boolean isID( String name )
606     {
607         int l = name.length();
608 
609         if( l == 0 )
610         {
611             return false;
612         }
613 
614         char[] chars = name.toCharArray();
615 
616         if( !Character.isUnicodeIdentifierStart( chars[0] ) )
617         {
618             return false;
619         }
620 
621         for( int i = 1; i < chars.length; i++ )
622         {
623             if( !Character.isUnicodeIdentifierPart( chars[i] ) )
624             {
625                 return false;
626             }
627         }
628         return true;
629     }
630 
631     /***
632      * Возвращает значение, на которое указывает заданная цепочка атрибутов для данного объекта.
633      * Цепочка атрибутов имеет следующий вид:
634      * <P><BLOCKQUOTE><code>
635      * path      ::= attribute ( "." attribute )* ;<BR>
636      * attribute ::= &lt;attr_name:ID&gt; ( "[" index "]" )? ;<BR>
637      * index     ::= &lt;index_var:ID&gt; | number ;<BR>
638      * number    ::= ( "-" )? ( &lt;DIGIT&gt; )+ ;<BR>
639      * </BLOCKQUOTE></code>
640      * <P>Каждый элемент цепочки является либо именем атрибута, либо именем списочного атрибута
641      * с указанным в скобках индексом элемента. Индекс может быть задан именем переменной
642      * <code>index_var</code>, значение которой берется из <code>variable_map</code> как значение
643      * соответствующего атрибута, или целочисленным литералом <code>number</code>.
644      * Отрицательному индексу -i соответствует элемент списка с индексом L-i, где L - длина списка.
645      * <P>Значение, на которое указывает цепочка атрибутов, определяется рекурсивно:
646      * <UL>
647      * <LI>Для цепочки длины нуль это значение самого объекта.</LI>
648      * <LI>Для цепочки длины k рекурсивно находим объект, на который указывает цепочка длины k-1.
649      * Результатом является значение k-го элемента цепочки, применённого к этого объекту.</LI>
650      * </UL>
651      *
652      * @param obj           The specified object.
653      * @param path          The chain of attribute names.
654      * @param variableMap   Defined variables.
655      * @return              The value pointed by path of attributes.
656      * @see #getAttribute(Object,String)
657      * @see #getAttribute(Object, String, int)
658      */
659     public static Object getAttribute( Object obj
660                                      , String path
661                                      , Attributed variableMap
662                                      )
663     {
664         int from = 0;
665         int to = 0;
666 
667         Object nextobj;
668         String attrname;
669         int realindex = -1;
670 
671         while( true )
672         {
673             to = path.indexOf( '.', from );
674             if( to == -1 )
675             {
676                 to = path.length();
677             }
678 
679             int bracket = path.indexOf ( '[', from );
680             if( bracket >= 0 && bracket < to )
681             {
682 
683                 attrname = path.substring( from, bracket );
684                 if( path.charAt( to - 1 ) != ']' )
685                 {
686                     throw new AttributePathException( AttributePathException.NO_CLOSING_BRACKET
687                                                     , obj
688                                                     , path
689                                                     , bracket
690                                                     , null
691                                                     );
692                 }
693 
694                 String index_string = path.substring( bracket + 1, to - 1 );
695 
696                 if( isID( index_string ) )  // if NAME
697                 {
698                     Object mapped_index;
699                     try
700                     {
701                         mapped_index = variableMap.getAttribute( index_string );
702                     }
703                     catch( AttributeException e )
704                     {
705                         throw new AttributePathException
706                                   ( AttributePathException.ATTRIBUTED_EXCEPTION
707                                   , obj
708                                   , path
709                                   , bracket + 1
710                                   , e
711                                   );
712                     }
713                     try
714                     {
715                         realindex = ((Integer)mapped_index).intValue();
716                     }
717                     catch( ClassCastException e )
718                     {
719                         throw new AttributePathException( AttributePathException.INDEX_VALUE
720                                                         , obj
721                                                         , path
722                                                         , bracket + 1
723                                                         , e
724                                                         );
725                     }
726                 } else {
727                     // if DIGITAL INDEX
728                     try
729                     {
730                         realindex = Integer.parseInt( index_string );
731                     }
732                     catch ( NumberFormatException e )
733                     {
734                         throw new AttributePathException( AttributePathException.INDEX_VALUE
735                                                         , obj
736                                                         , path
737                                                         , bracket + 1
738                                                         , e
739                                                         );
740                     }
741                     if( realindex < 0 )
742                     {
743                         try
744                         {
745                             realindex = sizeAttribute( obj, attrname ) + realindex;
746                         }
747                         catch( AttributeException e )
748                         {
749                             throw new AttributePathException
750                                       ( AttributePathException.ATTRIBUTED_EXCEPTION
751                                       , obj
752                                       , path
753                                       , from
754                                       , e
755                                       );
756                         }
757                     }
758                 }
759                 try
760                 {
761                     nextobj = getAttribute( obj, attrname, realindex );
762                 }
763                 catch( AttributeException e )
764                 {
765                     throw new AttributePathException( AttributePathException.ATTRIBUTED_EXCEPTION
766                                                     , obj
767                                                     , path
768                                                     , from
769                                                     , e
770                                                     );
771                 }
772             } else {
773                 attrname = path.substring( from, to );
774                 try
775                 {
776                     nextobj = getAttribute( obj, attrname );
777                 }
778                 catch( AttributeException e )
779                 {
780                     throw new AttributePathException( AttributePathException.ATTRIBUTED_EXCEPTION
781                                                     , obj
782                                                     , path
783                                                     , from
784                                                     , e
785                                                     );
786                 }
787             }
788 
789             if( to != path.length() )
790             {
791                 from = to + 1;
792                 realindex = -1;
793                 obj = nextobj;
794             } else {
795                 return nextobj;
796             }
797         }
798     }
799 
800     //--------------------------------------------------------------------------
801     // auxilary methods
802 
803     private static class PropertyDescriptor
804     {
805         private Method read_method;
806         private Method write_method;
807 
808         public PropertyDescriptor( Method read_method, Method write_method )
809         {
810             this.read_method = read_method;
811             this.write_method = write_method;
812         }
813 
814         public Method getReadMethod()
815         {
816             return read_method;
817         }
818 
819         public Method getWriteMethod()
820         {
821             return write_method;
822         }
823     }
824 
825     private static Map/*Class,Map(String,PropertyDescriptor)*/
826     getBeanClassPropertyDescriptors( Class cls )
827     {
828         if( class_to_properties_descriptor.containsKey( cls ) )
829         {
830             return (Map/*Class,Map(String,PropertyDescriptor)*/)
831                    class_to_properties_descriptor.get( cls );
832         }
833 
834         Method[] methods = cls.getMethods();
835         Map/*String,PropertyDescriptor*/ res
836             = new HashMap/*String,PropertyDescriptor*/();
837 
838         for( int i = 0; i < methods.length; i++ )
839         {
840             if(    methods[i].getParameterTypes().length == 0
841                 && !methods[i].getReturnType().equals( void.class )
842               )
843             {
844                 String method_name = methods[i].getName();
845 
846 //                if(    methods[i].getReturnType().equals( boolean.class )
847 //                    && method_name.startsWith( "is" )
848 //                    && method_name.length() > 2
849 //                  )
850 //                {
851 //                    String capitalized_property_name
852 //                        = method_name.substring( 2 );
853 //                    Method read_method = methods[i];
854 //
855 //                    res.put( decapitalize( capitalized_property_name )
856 //                           , new PropertyDescriptor
857 //                                 ( read_method
858 //                                 , findWriteMethod( cls
859 //                                                  , read_method
860 //                                                  , capitalized_property_name
861 //                                                  )
862 //                                 )
863 //                           );
864 //                } else
865                 if(    method_name.startsWith( "get" )
866                     && method_name.length() > "get".length()
867                   )
868                 {
869                     String capitalized_property_name
870                         = method_name.substring( "get".length() );
871                     Method read_method = methods[i];
872                     read_method.setAccessible( true );
873 
874                     res.put( decapitalize( capitalized_property_name )
875                            , new PropertyDescriptor
876                                  ( read_method
877                                  , findWriteMethod( cls
878                                                   , read_method
879                                                   , capitalized_property_name
880                                                   )
881                                  )
882                            );
883                 }
884             }
885         }
886 
887         class_to_properties_descriptor.put( cls, res );
888 
889         return res;
890     }
891 
892     private static Method findWriteMethod( Class cls
893                                          , Method read_method
894                                          , String capitalizedPropertyName
895                                          )
896     {
897         try
898         {
899             set_type[0] = read_method.getReturnType();
900             Method write_method = cls.getMethod( "set" + capitalizedPropertyName
901                                                , set_type
902                                                );
903             if( write_method != null ) write_method.setAccessible( true );
904             return write_method; 
905         }
906         catch( Exception e )
907         {
908             return null;
909         }
910     }
911 
912     /***
913      * Returns read method for the specified property of the specified bean.
914      * @param bean      Object to get read method from.
915      * @param name      Property name.
916      * @return          Read method or <code>null</code> if object has
917      *                  no property with such name.
918      * @throws NullPointerException
919      *                  If <code>bean</code> is <code>null</code>.
920      * @throws AttributeException
921      *                 Some possible reasons:
922      *                 <UL>
923      *                 <LI><CODE>name == null</CODE></LI>
924      *                 </UL>
925      *                 (analyze exception parameters).
926      */
927     protected static Method getBeanPropertyReadMethod( Object bean
928                                                      , String name
929                                                      )
930     {
931         if( name == null )
932         {
933             throw new AttributeException( bean, name );
934         }
935         PropertyDescriptor pd
936             = (PropertyDescriptor)
937               getBeanClassPropertyDescriptors( bean.getClass() )
938               .get( name );
939 
940         return pd != null ? pd.getReadMethod() : null;
941     }
942 
943     /***
944      * Returns write method for the specified property of the specified bean.
945      * @param bean      Object to get write method from.
946      * @param name      Property name.
947      * @return          Write method or <code>null</code> if object has
948      *                  no property with such name.
949      * @throws NullPointerException
950      *                  If <code>bean</code> is <code>null</code>.
951      * @throws AttributeException
952      *                  Some possible reasons:
953      *                  <UL>
954      *                  <LI><CODE>name == null</CODE></LI>
955      *                  </UL>
956      *                  (analyze exception parameters).
957      */
958     protected static Method getBeanPropertyWriteMethod( Object bean
959                                                       , String name
960                                                       )
961     {
962         if( name == null )
963         {
964             throw new AttributeException( bean, name );
965         }
966         PropertyDescriptor pd
967             = (PropertyDescriptor)
968               getBeanClassPropertyDescriptors( bean.getClass() )
969               .get( name );
970 
971         return pd != null ? pd.getWriteMethod() : null;
972     }
973 
974     private static Class[] set_type = new Class[1];
975 
976     private static Object[] get_par = new Object[0];
977     private static Object[] set_par = new Object[1];
978 
979     private static Map/*Class,Map(String,PropertyDescriptor)*/
980         class_to_properties_descriptor
981             = new HashMap/*Class,Map(String,PropertyDescriptor)*/();
982 }