| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.beanutils2.expression; |
| |
| /** |
| * Default Property Name Expression {@link Resolver} Implementation. |
| * <p> |
| * This class assists in resolving property names in the following five formats, with the layout of an identifying String in parentheses: |
| * <ul> |
| * <li><strong>Simple ({@code name})</strong> - The specified {@code name} identifies an individual property of a particular JavaBean. The name of the actual |
| * getter or setter method to be used is determined using standard JavaBeans introspection, so that (unless overridden by a {@code BeanInfo} class, a property |
| * named "xyz" will have a getter method named {@code getXyz()} or (for boolean properties only) {@code isXyz()}, and a setter method named |
| * {@code setXyz()}.</li> |
| * <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 |
| * 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 |
| * property value that is ultimately retrieved or modified is the one identified by the last name element.</li> |
| * <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 |
| * property getter and setter methods. The appropriate (zero-relative) entry in the array is selected. {@code List} objects are now also supported for |
| * read/write. You simply need to define a getter that returns the {@code List}</li> |
| * <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 |
| * {@link String}.</li> |
| * <li><strong>Combined ({@code name1.name2[index].name3(key)})</strong> - Combining mapped, nested, and indexed references is also supported.</li> |
| * </ul> |
| * |
| * @since 1.8.0 |
| */ |
| public class DefaultResolver implements Resolver { |
| |
| private static final char NESTED = '.'; |
| private static final char MAPPED_START = '('; |
| private static final char MAPPED_END = ')'; |
| private static final char INDEXED_START = '['; |
| private static final char INDEXED_END = ']'; |
| |
| /** |
| * Constructs a new instance. |
| */ |
| public DefaultResolver() { |
| } |
| |
| /** |
| * Gets the index value from the property expression or -1. |
| * |
| * @param expression The property expression |
| * @return The index value or -1 if the property is not indexed |
| * @throws IllegalArgumentException If the indexed property is illegally formed or has an invalid (non-numeric) value. |
| */ |
| @Override |
| public int getIndex(final String expression) { |
| if (expression == null || expression.isEmpty()) { |
| return -1; |
| } |
| for (int i = 0; i < expression.length(); i++) { |
| final char c = expression.charAt(i); |
| if (c == NESTED || c == MAPPED_START) { |
| return -1; |
| } |
| if (c == INDEXED_START) { |
| final int end = expression.indexOf(INDEXED_END, i); |
| if (end < 0) { |
| throw new IllegalArgumentException("Missing End Delimiter"); |
| } |
| final String value = expression.substring(i + 1, end); |
| if (value.isEmpty()) { |
| throw new IllegalArgumentException("No Index Value"); |
| } |
| int index = 0; |
| try { |
| index = Integer.parseInt(value, 10); |
| } catch (final Exception e) { |
| throw new IllegalArgumentException("Invalid index value '" + value + "'"); |
| } |
| return index; |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * Gets the map key from the property expression or {@code null}. |
| * |
| * @param expression The property expression |
| * @return The index value |
| * @throws IllegalArgumentException If the mapped property is illegally formed. |
| */ |
| @Override |
| public String getKey(final String expression) { |
| if (expression == null || expression.isEmpty()) { |
| return null; |
| } |
| for (int i = 0; i < expression.length(); i++) { |
| final char c = expression.charAt(i); |
| if (c == NESTED || c == INDEXED_START) { |
| return null; |
| } |
| if (c == MAPPED_START) { |
| final int end = expression.indexOf(MAPPED_END, i); |
| if (end < 0) { |
| throw new IllegalArgumentException("Missing End Delimiter"); |
| } |
| return expression.substring(i + 1, end); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Gets the property name from the property expression. |
| * |
| * @param expression The property expression |
| * @return The property name |
| */ |
| @Override |
| public String getProperty(final String expression) { |
| if (expression == null || expression.isEmpty()) { |
| return expression; |
| } |
| for (int i = 0; i < expression.length(); i++) { |
| final char c = expression.charAt(i); |
| if (c == NESTED || c == MAPPED_START || c == INDEXED_START) { |
| return expression.substring(0, i); |
| } |
| } |
| return expression; |
| } |
| |
| /** |
| * Indicates whether or not the expression contains nested property expressions or not. |
| * |
| * @param expression The property expression |
| * @return The next property expression |
| */ |
| @Override |
| public boolean hasNested(final String expression) { |
| if (expression == null || expression.isEmpty()) { |
| return false; |
| } |
| return remove(expression) != null; |
| } |
| |
| /** |
| * Indicate whether the expression is for an indexed property or not. |
| * |
| * @param expression The property expression |
| * @return {@code true} if the expression is indexed, otherwise {@code false} |
| */ |
| @Override |
| public boolean isIndexed(final String expression) { |
| if (expression == null || expression.isEmpty()) { |
| return false; |
| } |
| for (int i = 0; i < expression.length(); i++) { |
| final char c = expression.charAt(i); |
| if (c == NESTED || c == MAPPED_START) { |
| return false; |
| } |
| if (c == INDEXED_START) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Indicate whether the expression is for a mapped property or not. |
| * |
| * @param expression The property expression |
| * @return {@code true} if the expression is mapped, otherwise {@code false} |
| */ |
| @Override |
| public boolean isMapped(final String expression) { |
| if (expression == null || expression.isEmpty()) { |
| return false; |
| } |
| for (int i = 0; i < expression.length(); i++) { |
| final char c = expression.charAt(i); |
| if (c == NESTED || c == INDEXED_START) { |
| return false; |
| } |
| if (c == MAPPED_START) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Extract the next property expression from the current expression. |
| * |
| * @param expression The property expression |
| * @return The next property expression |
| */ |
| @Override |
| public String next(final String expression) { |
| if (expression == null || expression.isEmpty()) { |
| return null; |
| } |
| boolean indexed = false; |
| boolean mapped = false; |
| for (int i = 0; i < expression.length(); i++) { |
| final char c = expression.charAt(i); |
| if (indexed) { |
| if (c == INDEXED_END) { |
| return expression.substring(0, i + 1); |
| } |
| } else if (mapped) { |
| if (c == MAPPED_END) { |
| return expression.substring(0, i + 1); |
| } |
| } else { |
| switch (c) { |
| case NESTED: |
| return expression.substring(0, i); |
| case MAPPED_START: |
| mapped = true; |
| break; |
| case INDEXED_START: |
| indexed = true; |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| return expression; |
| } |
| |
| /** |
| * Remove the last property expression from the current expression. |
| * |
| * @param expression The property expression |
| * @return The new expression value, with first property expression removed - null if there are no more expressions |
| */ |
| @Override |
| public String remove(final String expression) { |
| if (expression == null || expression.isEmpty()) { |
| return null; |
| } |
| final String property = next(expression); |
| if (expression.length() == property.length()) { |
| return null; |
| } |
| int start = property.length(); |
| if (expression.charAt(start) == NESTED) { |
| start++; |
| } |
| return expression.substring(start); |
| } |
| } |