001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.beanutils.expression;
018:
019: /**
020: * Default Property Name Expression {@link Resolver} Implementation.
021: * <p>
022: * This class assists in resolving property names in the following five formats,
023: * with the layout of an identifying String in parentheses:
024: * <ul>
025: * <li><strong>Simple (<code>name</code>)</strong> - The specified
026: * <code>name</code> identifies an individual property of a particular
027: * JavaBean. The name of the actual getter or setter method to be used
028: * is determined using standard JavaBeans instrospection, so that (unless
029: * overridden by a <code>BeanInfo</code> class, a property named "xyz"
030: * will have a getter method named <code>getXyz()</code> or (for boolean
031: * properties only) <code>isXyz()</code>, and a setter method named
032: * <code>setXyz()</code>.</li>
033: * <li><strong>Nested (<code>name1.name2.name3</code>)</strong> The first
034: * name element is used to select a property getter, as for simple
035: * references above. The object returned for this property is then
036: * consulted, using the same approach, for a property getter for a
037: * property named <code>name2</code>, and so on. The property value that
038: * is ultimately retrieved or modified is the one identified by the
039: * last name element.</li>
040: * <li><strong>Indexed (<code>name[index]</code>)</strong> - The underlying
041: * property value is assumed to be an array, or this JavaBean is assumed
042: * to have indexed property getter and setter methods. The appropriate
043: * (zero-relative) entry in the array is selected. <code>List</code>
044: * objects are now also supported for read/write. You simply need to define
045: * a getter that returns the <code>List</code></li>
046: * <li><strong>Mapped (<code>name(key)</code>)</strong> - The JavaBean
047: * is assumed to have an property getter and setter methods with an
048: * additional attribute of type <code>java.lang.String</code>.</li>
049: * <li><strong>Combined (<code>name1.name2[index].name3(key)</code>)</strong> -
050: * Combining mapped, nested, and indexed references is also
051: * supported.</li>
052: * </ul>
053: *
054: * @version $Revision: 473888 $ $Date: 2006-11-12 06:21:24 +0000 (Sun, 12 Nov 2006) $
055: * @since 1.8.0
056: */
057: public class DefaultResolver implements Resolver {
058:
059: private static final char NESTED = '.';
060: private static final char MAPPED_START = '(';
061: private static final char MAPPED_END = ')';
062: private static final char INDEXED_START = '[';
063: private static final char INDEXED_END = ']';
064:
065: /**
066: * Default Constructor.
067: */
068: public DefaultResolver() {
069: }
070:
071: /**
072: * Return the index value from the property expression or -1.
073: *
074: * @param expression The property expression
075: * @return The index value or -1 if the property is not indexed
076: * @throws IllegalArgumentException If the indexed property is illegally
077: * formed or has an invalid (non-numeric) value.
078: */
079: public int getIndex(String expression) {
080: if (expression == null || expression.length() == 0) {
081: return -1;
082: }
083: for (int i = 0; i < expression.length(); i++) {
084: char c = expression.charAt(i);
085: if (c == NESTED || c == MAPPED_START) {
086: return -1;
087: } else if (c == INDEXED_START) {
088: int end = expression.indexOf(INDEXED_END, i);
089: if (end < 0) {
090: throw new IllegalArgumentException(
091: "Missing End Delimiter");
092: }
093: String value = expression.substring(i + 1, end);
094: if (value.length() == 0) {
095: throw new IllegalArgumentException("No Index Value");
096: }
097: int index = 0;
098: try {
099: index = Integer.parseInt(value, 10);
100: } catch (Exception e) {
101: throw new IllegalArgumentException(
102: "Invalid index value '" + value + "'");
103: }
104: return index;
105: }
106: }
107: return -1;
108: }
109:
110: /**
111: * Return the map key from the property expression or <code>null</code>.
112: *
113: * @param expression The property expression
114: * @return The index value
115: * @throws IllegalArgumentException If the mapped property is illegally formed.
116: */
117: public String getKey(String expression) {
118: if (expression == null || expression.length() == 0) {
119: return null;
120: }
121: for (int i = 0; i < expression.length(); i++) {
122: char c = expression.charAt(i);
123: if (c == NESTED || c == INDEXED_START) {
124: return null;
125: } else if (c == MAPPED_START) {
126: int end = expression.indexOf(MAPPED_END, i);
127: if (end < 0) {
128: throw new IllegalArgumentException(
129: "Missing End Delimiter");
130: }
131: return expression.substring(i + 1, end);
132: }
133: }
134: return null;
135: }
136:
137: /**
138: * Return the property name from the property expression.
139: *
140: * @param expression The property expression
141: * @return The property name
142: */
143: public String getProperty(String expression) {
144: if (expression == null || expression.length() == 0) {
145: return expression;
146: }
147: for (int i = 0; i < expression.length(); i++) {
148: char c = expression.charAt(i);
149: if (c == NESTED) {
150: return expression.substring(0, i);
151: } else if (c == MAPPED_START || c == INDEXED_START) {
152: return expression.substring(0, i);
153: }
154: }
155: return expression;
156: }
157:
158: /**
159: * Indicates whether or not the expression
160: * contains nested property expressions or not.
161: *
162: * @param expression The property expression
163: * @return The next property expression
164: */
165: public boolean hasNested(String expression) {
166: if (expression == null || expression.length() == 0) {
167: return false;
168: } else {
169: return (remove(expression) != null);
170: }
171: }
172:
173: /**
174: * Indicate whether the expression is for an indexed property or not.
175: *
176: * @param expression The property expression
177: * @return <code>true</code> if the expresion is indexed,
178: * otherwise <code>false</code>
179: */
180: public boolean isIndexed(String expression) {
181: if (expression == null || expression.length() == 0) {
182: return false;
183: }
184: for (int i = 0; i < expression.length(); i++) {
185: char c = expression.charAt(i);
186: if (c == NESTED || c == MAPPED_START) {
187: return false;
188: } else if (c == INDEXED_START) {
189: return true;
190: }
191: }
192: return false;
193: }
194:
195: /**
196: * Indicate whether the expression is for a mapped property or not.
197: *
198: * @param expression The property expression
199: * @return <code>true</code> if the expresion is mapped,
200: * otherwise <code>false</code>
201: */
202: public boolean isMapped(String expression) {
203: if (expression == null || expression.length() == 0) {
204: return false;
205: }
206: for (int i = 0; i < expression.length(); i++) {
207: char c = expression.charAt(i);
208: if (c == NESTED || c == INDEXED_START) {
209: return false;
210: } else if (c == MAPPED_START) {
211: return true;
212: }
213: }
214: return false;
215: }
216:
217: /**
218: * Extract the next property expression from the
219: * current expression.
220: *
221: * @param expression The property expression
222: * @return The next property expression
223: */
224: public String next(String expression) {
225: if (expression == null || expression.length() == 0) {
226: return null;
227: }
228: boolean indexed = false;
229: boolean mapped = false;
230: for (int i = 0; i < expression.length(); i++) {
231: char c = expression.charAt(i);
232: if (indexed) {
233: if (c == INDEXED_END) {
234: return expression.substring(0, i + 1);
235: }
236: } else if (mapped) {
237: if (c == MAPPED_END) {
238: return expression.substring(0, i + 1);
239: }
240: } else {
241: if (c == NESTED) {
242: return expression.substring(0, i);
243: } else if (c == MAPPED_START) {
244: mapped = true;
245: } else if (c == INDEXED_START) {
246: indexed = true;
247: }
248: }
249: }
250: return expression;
251: }
252:
253: /**
254: * Remove the last property expresson from the
255: * current expression.
256: *
257: * @param expression The property expression
258: * @return The new expression value, with first property
259: * expression removed - null if there are no more expressions
260: */
261: public String remove(String expression) {
262: if (expression == null || expression.length() == 0) {
263: return null;
264: }
265: String property = next(expression);
266: if (expression.length() == property.length()) {
267: return null;
268: }
269: int start = property.length();
270: if (expression.charAt(start) == NESTED) {
271: start++;
272: }
273: return expression.substring(start);
274: }
275: }
|