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 */ 017package org.apache.commons.beanutils2.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, with the layout of an identifying String in parentheses: 023 * <ul> 024 * <li><strong>Simple ({@code name})</strong> - The specified {@code name} identifies an individual property of a particular JavaBean. The name of the actual 025 * getter or setter method to be used is determined using standard JavaBeans introspection, so that (unless overridden by a {@code BeanInfo} class, a property 026 * named "xyz" will have a getter method named {@code getXyz()} or (for boolean properties only) {@code isXyz()}, and a setter method named 027 * {@code setXyz()}.</li> 028 * <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 029 * 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 030 * property value that is ultimately retrieved or modified is the one identified by the last name element.</li> 031 * <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 032 * property getter and setter methods. The appropriate (zero-relative) entry in the array is selected. {@code List} objects are now also supported for 033 * read/write. You simply need to define a getter that returns the {@code List}</li> 034 * <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 035 * {@link String}.</li> 036 * <li><strong>Combined ({@code name1.name2[index].name3(key)})</strong> - Combining mapped, nested, and indexed references is also supported.</li> 037 * </ul> 038 * 039 * @since 1.8.0 040 */ 041public class DefaultResolver implements Resolver { 042 043 private static final char NESTED = '.'; 044 private static final char MAPPED_START = '('; 045 private static final char MAPPED_END = ')'; 046 private static final char INDEXED_START = '['; 047 private static final char INDEXED_END = ']'; 048 049 /** 050 * Constructs a new instance. 051 */ 052 public DefaultResolver() { 053 } 054 055 /** 056 * Gets the index value from the property expression or -1. 057 * 058 * @param expression The property expression 059 * @return The index value or -1 if the property is not indexed 060 * @throws IllegalArgumentException If the indexed property is illegally formed or has an invalid (non-numeric) value. 061 */ 062 @Override 063 public int getIndex(final String expression) { 064 if (expression == null || expression.isEmpty()) { 065 return -1; 066 } 067 for (int i = 0; i < expression.length(); i++) { 068 final char c = expression.charAt(i); 069 if (c == NESTED || c == MAPPED_START) { 070 return -1; 071 } 072 if (c == INDEXED_START) { 073 final int end = expression.indexOf(INDEXED_END, i); 074 if (end < 0) { 075 throw new IllegalArgumentException("Missing End Delimiter"); 076 } 077 final String value = expression.substring(i + 1, end); 078 if (value.isEmpty()) { 079 throw new IllegalArgumentException("No Index Value"); 080 } 081 int index = 0; 082 try { 083 index = Integer.parseInt(value, 10); 084 } catch (final Exception e) { 085 throw new IllegalArgumentException("Invalid index value '" + value + "'"); 086 } 087 return index; 088 } 089 } 090 return -1; 091 } 092 093 /** 094 * Gets the map key from the property expression or {@code null}. 095 * 096 * @param expression The property expression 097 * @return The index value 098 * @throws IllegalArgumentException If the mapped property is illegally formed. 099 */ 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}