View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.beanutils.expression;
18  
19  /**
20   * Default Property Name Expression {@link Resolver} Implementation.
21   * <p>
22   * This class assists in resolving property names in the following five formats,
23   * with the layout of an identifying String in parentheses:
24   * <ul>
25   * <li><strong>Simple (<code>name</code>)</strong> - The specified
26   *     <code>name</code> identifies an individual property of a particular
27   *     JavaBean.  The name of the actual getter or setter method to be used
28   *     is determined using standard JavaBeans instrospection, so that (unless
29   *     overridden by a <code>BeanInfo</code> class, a property named "xyz"
30   *     will have a getter method named <code>getXyz()</code> or (for boolean
31   *     properties only) <code>isXyz()</code>, and a setter method named
32   *     <code>setXyz()</code>.</li>
33   * <li><strong>Nested (<code>name1.name2.name3</code>)</strong> The first
34   *     name element is used to select a property getter, as for simple
35   *     references above.  The object returned for this property is then
36   *     consulted, using the same approach, for a property getter for a
37   *     property named <code>name2</code>, and so on.  The property value that
38   *     is ultimately retrieved or modified is the one identified by the
39   *     last name element.</li>
40   * <li><strong>Indexed (<code>name[index]</code>)</strong> - The underlying
41   *     property value is assumed to be an array, or this JavaBean is assumed
42   *     to have indexed property getter and setter methods.  The appropriate
43   *     (zero-relative) entry in the array is selected.  <code>List</code>
44   *     objects are now also supported for read/write.  You simply need to define
45   *     a getter that returns the <code>List</code></li>
46   * <li><strong>Mapped (<code>name(key)</code>)</strong> - The JavaBean
47   *     is assumed to have an property getter and setter methods with an
48   *     additional attribute of type <code>java.lang.String</code>.</li>
49   * <li><strong>Combined (<code>name1.name2[index].name3(key)</code>)</strong> -
50   *     Combining mapped, nested, and indexed references is also
51   *     supported.</li>
52   * </ul>
53   *
54   * @version $Id$
55   * @since 1.8.0
56   */
57  public class DefaultResolver implements Resolver {
58  
59      private static final char NESTED        = '.';
60      private static final char MAPPED_START  = '(';
61      private static final char MAPPED_END    = ')';
62      private static final char INDEXED_START = '[';
63      private static final char INDEXED_END   = ']';
64  
65      /**
66       * Default Constructor.
67       */
68      public DefaultResolver() {
69      }
70  
71      /**
72       * Return the index value from the property expression or -1.
73       *
74       * @param expression The property expression
75       * @return The index value or -1 if the property is not indexed
76       * @throws IllegalArgumentException If the indexed property is illegally
77       * formed or has an invalid (non-numeric) value.
78       */
79      public int getIndex(final String expression) {
80          if (expression == null || expression.length() == 0) {
81              return -1;
82          }
83          for (int i = 0; i < expression.length(); i++) {
84              final char c = expression.charAt(i);
85              if (c == NESTED || c == MAPPED_START) {
86                  return -1;
87              } else if (c == INDEXED_START) {
88                  final int end = expression.indexOf(INDEXED_END, i);
89                  if (end < 0) {
90                      throw new IllegalArgumentException("Missing End Delimiter");
91                  }
92                  final String value = expression.substring(i + 1, end);
93                  if (value.length() == 0) {
94                      throw new IllegalArgumentException("No Index Value");
95                  }
96                  int index = 0;
97                  try {
98                      index = Integer.parseInt(value, 10);
99                  } catch (final Exception e) {
100                     throw new IllegalArgumentException("Invalid index value '"
101                             + value + "'");
102                 }
103                 return index;
104             }
105         }
106         return -1;
107     }
108 
109     /**
110      * Return the map key from the property expression or <code>null</code>.
111      *
112      * @param expression The property expression
113      * @return The index value
114      * @throws IllegalArgumentException If the mapped property is illegally formed.
115      */
116     public String getKey(final String expression) {
117         if (expression == null || expression.length() == 0) {
118             return null;
119         }
120         for (int i = 0; i < expression.length(); i++) {
121             final char c = expression.charAt(i);
122             if (c == NESTED || c == INDEXED_START) {
123                 return null;
124             } else if (c == MAPPED_START) {
125                 final int end = expression.indexOf(MAPPED_END, i);
126                 if (end < 0) {
127                     throw new IllegalArgumentException("Missing End Delimiter");
128                 }
129                 return expression.substring(i + 1, end);
130             }
131         }
132         return null;
133     }
134 
135     /**
136      * Return the property name from the property expression.
137      *
138      * @param expression The property expression
139      * @return The property name
140      */
141     public String getProperty(final String expression) {
142         if (expression == null || expression.length() == 0) {
143             return expression;
144         }
145         for (int i = 0; i < expression.length(); i++) {
146             final char c = expression.charAt(i);
147             if (c == NESTED) {
148                 return expression.substring(0, i);
149             } else if (c == MAPPED_START || c == INDEXED_START) {
150                 return expression.substring(0, i);
151             }
152         }
153         return expression;
154     }
155 
156     /**
157      * Indicates whether or not the expression
158      * contains nested property expressions or not.
159      *
160      * @param expression The property expression
161      * @return The next property expression
162      */
163     public boolean hasNested(final String expression) {
164         if (expression == null || expression.length() == 0) {
165             return false;
166         } else {
167             return (remove(expression) != null);
168         }
169     }
170 
171     /**
172      * Indicate whether the expression is for an indexed property or not.
173      *
174      * @param expression The property expression
175      * @return <code>true</code> if the expresion is indexed,
176      *  otherwise <code>false</code>
177      */
178     public boolean isIndexed(final String expression) {
179         if (expression == null || expression.length() == 0) {
180             return false;
181         }
182         for (int i = 0; i < expression.length(); i++) {
183             final char c = expression.charAt(i);
184             if (c == NESTED || c == MAPPED_START) {
185                 return false;
186             } else if (c == INDEXED_START) {
187                 return true;
188             }
189         }
190         return false;
191     }
192 
193     /**
194      * Indicate whether the expression is for a mapped property or not.
195      *
196      * @param expression The property expression
197      * @return <code>true</code> if the expresion is mapped,
198      *  otherwise <code>false</code>
199      */
200     public boolean isMapped(final String expression) {
201         if (expression == null || expression.length() == 0) {
202             return false;
203         }
204         for (int i = 0; i < expression.length(); i++) {
205             final char c = expression.charAt(i);
206             if (c == NESTED || c == INDEXED_START) {
207                 return false;
208             } else if (c == MAPPED_START) {
209                 return true;
210             }
211         }
212         return false;
213     }
214 
215     /**
216      * Extract the next property expression from the
217      * current expression.
218      *
219      * @param expression The property expression
220      * @return The next property expression
221      */
222     public String next(final String expression) {
223         if (expression == null || expression.length() == 0) {
224             return null;
225         }
226         boolean indexed = false;
227         boolean mapped  = false;
228         for (int i = 0; i < expression.length(); i++) {
229             final char c = expression.charAt(i);
230             if (indexed) {
231                 if (c == INDEXED_END) {
232                     return expression.substring(0, i + 1);
233                 }
234             } else if (mapped) {
235                 if (c == MAPPED_END) {
236                     return expression.substring(0, i + 1);
237                 }
238             } else {
239                 if (c == NESTED) {
240                     return expression.substring(0, i);
241                 } else if (c == MAPPED_START) {
242                     mapped = true;
243                 } else if (c == INDEXED_START) {
244                     indexed = true;
245                 }
246             }
247         }
248         return expression;
249     }
250 
251     /**
252      * Remove the last property expresson from the
253      * current expression.
254      *
255      * @param expression The property expression
256      * @return The new expression value, with first property
257      * expression removed - null if there are no more expressions
258      */
259     public String remove(final String expression) {
260         if (expression == null || expression.length() == 0) {
261             return null;
262         }
263         final String property = next(expression);
264         if (expression.length() == property.length()) {
265             return null;
266         }
267         int start = property.length();
268         if (expression.charAt(start) == NESTED) {
269             start++;
270         }
271         return expression.substring(start);
272     }
273 }