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 * https://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 }