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 * https://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 */ 017package 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 * @since 1.8.0 055 */ 056public class DefaultResolver implements Resolver { 057 058 private static final char NESTED = '.'; 059 private static final char MAPPED_START = '('; 060 private static final char MAPPED_END = ')'; 061 private static final char INDEXED_START = '['; 062 private static final char INDEXED_END = ']'; 063 064 /** 065 * Constructs a new instance. 066 */ 067 public DefaultResolver() { 068 } 069 070 /** 071 * Return the index value from the property expression or -1. 072 * 073 * @param expression The property expression 074 * @return The index value or -1 if the property is not indexed 075 * @throws IllegalArgumentException If the indexed property is illegally 076 * formed or has an invalid (non-numeric) value. 077 */ 078 @Override 079 public int getIndex(final String expression) { 080 if (expression == null || expression.length() == 0) { 081 return -1; 082 } 083 for (int i = 0; i < expression.length(); i++) { 084 final char c = expression.charAt(i); 085 if (c == NESTED || c == MAPPED_START) { 086 return -1; 087 } 088 if (c == INDEXED_START) { 089 final int end = expression.indexOf(INDEXED_END, i); 090 if (end < 0) { 091 throw new IllegalArgumentException("Missing End Delimiter"); 092 } 093 final 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 (final Exception e) { 101 throw new IllegalArgumentException("Invalid index value '" 102 + 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 @Override 118 public String getKey(final String expression) { 119 if (expression == null || expression.length() == 0) { 120 return null; 121 } 122 for (int i = 0; i < expression.length(); i++) { 123 final char c = expression.charAt(i); 124 if (c == NESTED || c == INDEXED_START) { 125 return null; 126 } 127 if (c == MAPPED_START) { 128 final int end = expression.indexOf(MAPPED_END, i); 129 if (end < 0) { 130 throw new IllegalArgumentException("Missing End Delimiter"); 131 } 132 return expression.substring(i + 1, end); 133 } 134 } 135 return null; 136 } 137 138 /** 139 * Return the property name from the property expression. 140 * 141 * @param expression The property expression 142 * @return The property name 143 */ 144 @Override 145 public String getProperty(final String expression) { 146 if (expression == null || expression.length() == 0) { 147 return expression; 148 } 149 for (int i = 0; i < expression.length(); i++) { 150 final char c = expression.charAt(i); 151 if (c == NESTED || 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 @Override 166 public boolean hasNested(final String expression) { 167 if (expression == null || expression.length() == 0) { 168 return false; 169 } 170 return remove(expression) != null; 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 @Override 181 public boolean isIndexed(final String expression) { 182 if (expression == null || expression.length() == 0) { 183 return false; 184 } 185 for (int i = 0; i < expression.length(); i++) { 186 final char c = expression.charAt(i); 187 if (c == NESTED || c == MAPPED_START) { 188 return false; 189 } 190 if (c == INDEXED_START) { 191 return true; 192 } 193 } 194 return false; 195 } 196 197 /** 198 * Indicate whether the expression is for a mapped property or not. 199 * 200 * @param expression The property expression 201 * @return <code>true</code> if the expresion is mapped, 202 * otherwise <code>false</code> 203 */ 204 @Override 205 public boolean isMapped(final String expression) { 206 if (expression == null || expression.length() == 0) { 207 return false; 208 } 209 for (int i = 0; i < expression.length(); i++) { 210 final char c = expression.charAt(i); 211 if (c == NESTED || c == INDEXED_START) { 212 return false; 213 } 214 if (c == MAPPED_START) { 215 return true; 216 } 217 } 218 return false; 219 } 220 221 /** 222 * Extract the next property expression from the 223 * current expression. 224 * 225 * @param expression The property expression 226 * @return The next property expression 227 */ 228 @Override 229 public String next(final String expression) { 230 if (expression == null || expression.length() == 0) { 231 return null; 232 } 233 boolean indexed = false; 234 boolean mapped = false; 235 for (int i = 0; i < expression.length(); i++) { 236 final char c = expression.charAt(i); 237 if (indexed) { 238 if (c == INDEXED_END) { 239 return expression.substring(0, i + 1); 240 } 241 } else if (mapped) { 242 if (c == MAPPED_END) { 243 return expression.substring(0, i + 1); 244 } 245 } else { 246 switch (c) { 247 case NESTED: 248 return expression.substring(0, i); 249 case MAPPED_START: 250 mapped = true; 251 break; 252 case INDEXED_START: 253 indexed = true; 254 break; 255 default: 256 break; 257 } 258 } 259 } 260 return expression; 261 } 262 263 /** 264 * Remove the last property expresson from the 265 * current expression. 266 * 267 * @param expression The property expression 268 * @return The new expression value, with first property 269 * expression removed - null if there are no more expressions 270 */ 271 @Override 272 public String remove(final String expression) { 273 if (expression == null || expression.length() == 0) { 274 return null; 275 } 276 final String property = next(expression); 277 if (expression.length() == property.length()) { 278 return null; 279 } 280 int start = property.length(); 281 if (expression.charAt(start) == NESTED) { 282 start++; 283 } 284 return expression.substring(start); 285 } 286}