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.beanutils2.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, with the layout of an identifying String in parentheses:
23   * <ul>
24   * <li><strong>Simple ({@code name})</strong> - The specified {@code name} identifies an individual property of a particular JavaBean. The name of the actual
25   * getter or setter method to be used is determined using standard JavaBeans introspection, so that (unless overridden by a {@code BeanInfo} class, a property
26   * named "xyz" will have a getter method named {@code getXyz()} or (for boolean properties only) {@code isXyz()}, and a setter method named
27   * {@code setXyz()}.</li>
28   * <li><strong>Nested ({@code name1.name2.name3})</strong> The first name element is used to select a property getter, as for simple references above. The
29   * object returned for this property is then consulted, using the same approach, for a property getter for a property named {@code name2}, and so on. The
30   * property value that is ultimately retrieved or modified is the one identified by the last name element.</li>
31   * <li><strong>Indexed ({@code name[index]})</strong> - The underlying property value is assumed to be an array, or this JavaBean is assumed to have indexed
32   * property getter and setter methods. The appropriate (zero-relative) entry in the array is selected. {@code List} objects are now also supported for
33   * read/write. You simply need to define a getter that returns the {@code List}</li>
34   * <li><strong>Mapped ({@code name(key)})</strong> - The JavaBean is assumed to have an property getter and setter methods with an additional attribute of type
35   * {@link String}.</li>
36   * <li><strong>Combined ({@code name1.name2[index].name3(key)})</strong> - Combining mapped, nested, and indexed references is also supported.</li>
37   * </ul>
38   *
39   * @since 1.8.0
40   */
41  public class DefaultResolver implements Resolver {
42  
43      private static final char NESTED = '.';
44      private static final char MAPPED_START = '(';
45      private static final char MAPPED_END = ')';
46      private static final char INDEXED_START = '[';
47      private static final char INDEXED_END = ']';
48  
49      /**
50       * Constructs a new instance.
51       */
52      public DefaultResolver() {
53      }
54  
55      /**
56       * Gets the index value from the property expression or -1.
57       *
58       * @param expression The property expression
59       * @return The index value or -1 if the property is not indexed
60       * @throws IllegalArgumentException If the indexed property is illegally formed or has an invalid (non-numeric) value.
61       */
62      @Override
63      public int getIndex(final String expression) {
64          if (expression == null || expression.isEmpty()) {
65              return -1;
66          }
67          for (int i = 0; i < expression.length(); i++) {
68              final char c = expression.charAt(i);
69              if (c == NESTED || c == MAPPED_START) {
70                  return -1;
71              }
72              if (c == INDEXED_START) {
73                  final int end = expression.indexOf(INDEXED_END, i);
74                  if (end < 0) {
75                      throw new IllegalArgumentException("Missing End Delimiter");
76                  }
77                  final String value = expression.substring(i + 1, end);
78                  if (value.isEmpty()) {
79                      throw new IllegalArgumentException("No Index Value");
80                  }
81                  int index = 0;
82                  try {
83                      index = Integer.parseInt(value, 10);
84                  } catch (final Exception e) {
85                      throw new IllegalArgumentException("Invalid index value '" + value + "'");
86                  }
87                  return index;
88              }
89          }
90          return -1;
91      }
92  
93      /**
94       * Gets the map key from the property expression or {@code null}.
95       *
96       * @param expression The property expression
97       * @return The index value
98       * @throws IllegalArgumentException If the mapped property is illegally formed.
99       */
100     @Override
101     public String getKey(final String expression) {
102         if (expression == null || expression.isEmpty()) {
103             return null;
104         }
105         for (int i = 0; i < expression.length(); i++) {
106             final char c = expression.charAt(i);
107             if (c == NESTED || c == INDEXED_START) {
108                 return null;
109             }
110             if (c == MAPPED_START) {
111                 final int end = expression.indexOf(MAPPED_END, i);
112                 if (end < 0) {
113                     throw new IllegalArgumentException("Missing End Delimiter");
114                 }
115                 return expression.substring(i + 1, end);
116             }
117         }
118         return null;
119     }
120 
121     /**
122      * Gets the property name from the property expression.
123      *
124      * @param expression The property expression
125      * @return The property name
126      */
127     @Override
128     public String getProperty(final String expression) {
129         if (expression == null || expression.isEmpty()) {
130             return expression;
131         }
132         for (int i = 0; i < expression.length(); i++) {
133             final char c = expression.charAt(i);
134             if (c == NESTED || c == MAPPED_START || c == INDEXED_START) {
135                 return expression.substring(0, i);
136             }
137         }
138         return expression;
139     }
140 
141     /**
142      * Indicates whether or not the expression contains nested property expressions or not.
143      *
144      * @param expression The property expression
145      * @return The next property expression
146      */
147     @Override
148     public boolean hasNested(final String expression) {
149         if (expression == null || expression.isEmpty()) {
150             return false;
151         }
152         return remove(expression) != null;
153     }
154 
155     /**
156      * Indicate whether the expression is for an indexed property or not.
157      *
158      * @param expression The property expression
159      * @return {@code true} if the expression is indexed, otherwise {@code false}
160      */
161     @Override
162     public boolean isIndexed(final String expression) {
163         if (expression == null || expression.isEmpty()) {
164             return false;
165         }
166         for (int i = 0; i < expression.length(); i++) {
167             final char c = expression.charAt(i);
168             if (c == NESTED || c == MAPPED_START) {
169                 return false;
170             }
171             if (c == INDEXED_START) {
172                 return true;
173             }
174         }
175         return false;
176     }
177 
178     /**
179      * Indicate whether the expression is for a mapped property or not.
180      *
181      * @param expression The property expression
182      * @return {@code true} if the expression is mapped, otherwise {@code false}
183      */
184     @Override
185     public boolean isMapped(final String expression) {
186         if (expression == null || expression.isEmpty()) {
187             return false;
188         }
189         for (int i = 0; i < expression.length(); i++) {
190             final char c = expression.charAt(i);
191             if (c == NESTED || c == INDEXED_START) {
192                 return false;
193             }
194             if (c == MAPPED_START) {
195                 return true;
196             }
197         }
198         return false;
199     }
200 
201     /**
202      * Extract the next property expression from the current expression.
203      *
204      * @param expression The property expression
205      * @return The next property expression
206      */
207     @Override
208     public String next(final String expression) {
209         if (expression == null || expression.isEmpty()) {
210             return null;
211         }
212         boolean indexed = false;
213         boolean mapped = false;
214         for (int i = 0; i < expression.length(); i++) {
215             final char c = expression.charAt(i);
216             if (indexed) {
217                 if (c == INDEXED_END) {
218                     return expression.substring(0, i + 1);
219                 }
220             } else if (mapped) {
221                 if (c == MAPPED_END) {
222                     return expression.substring(0, i + 1);
223                 }
224             } else {
225                 switch (c) {
226                 case NESTED:
227                     return expression.substring(0, i);
228                 case MAPPED_START:
229                     mapped = true;
230                     break;
231                 case INDEXED_START:
232                     indexed = true;
233                     break;
234                 default:
235                     break;
236                 }
237             }
238         }
239         return expression;
240     }
241 
242     /**
243      * Remove the last property expression from the current expression.
244      *
245      * @param expression The property expression
246      * @return The new expression value, with first property expression removed - null if there are no more expressions
247      */
248     @Override
249     public String remove(final String expression) {
250         if (expression == null || expression.isEmpty()) {
251             return null;
252         }
253         final String property = next(expression);
254         if (expression.length() == property.length()) {
255             return null;
256         }
257         int start = property.length();
258         if (expression.charAt(start) == NESTED) {
259             start++;
260         }
261         return expression.substring(start);
262     }
263 }