DefaultResolver.java

  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.  *      http://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.  * Default Property Name Expression {@link Resolver} Implementation.
  20.  * <p>
  21.  * This class assists in resolving property names in the following five formats, with the layout of an identifying String in parentheses:
  22.  * <ul>
  23.  * <li><strong>Simple ({@code name})</strong> - The specified {@code name} identifies an individual property of a particular JavaBean. The name of the actual
  24.  * getter or setter method to be used is determined using standard JavaBeans introspection, so that (unless overridden by a {@code BeanInfo} class, a property
  25.  * named "xyz" will have a getter method named {@code getXyz()} or (for boolean properties only) {@code isXyz()}, and a setter method named
  26.  * {@code setXyz()}.</li>
  27.  * <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
  28.  * 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
  29.  * property value that is ultimately retrieved or modified is the one identified by the last name element.</li>
  30.  * <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
  31.  * property getter and setter methods. The appropriate (zero-relative) entry in the array is selected. {@code List} objects are now also supported for
  32.  * read/write. You simply need to define a getter that returns the {@code List}</li>
  33.  * <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
  34.  * {@link String}.</li>
  35.  * <li><strong>Combined ({@code name1.name2[index].name3(key)})</strong> - Combining mapped, nested, and indexed references is also supported.</li>
  36.  * </ul>
  37.  *
  38.  * @since 1.8.0
  39.  */
  40. public class DefaultResolver implements Resolver {

  41.     private static final char NESTED = '.';
  42.     private static final char MAPPED_START = '(';
  43.     private static final char MAPPED_END = ')';
  44.     private static final char INDEXED_START = '[';
  45.     private static final char INDEXED_END = ']';

  46.     /**
  47.      * Constructs a new instance.
  48.      */
  49.     public DefaultResolver() {
  50.     }

  51.     /**
  52.      * Gets the index value from the property expression or -1.
  53.      *
  54.      * @param expression The property expression
  55.      * @return The index value or -1 if the property is not indexed
  56.      * @throws IllegalArgumentException If the indexed property is illegally formed or has an invalid (non-numeric) value.
  57.      */
  58.     @Override
  59.     public int getIndex(final String expression) {
  60.         if (expression == null || expression.isEmpty()) {
  61.             return -1;
  62.         }
  63.         for (int i = 0; i < expression.length(); i++) {
  64.             final char c = expression.charAt(i);
  65.             if (c == NESTED || c == MAPPED_START) {
  66.                 return -1;
  67.             }
  68.             if (c == INDEXED_START) {
  69.                 final int end = expression.indexOf(INDEXED_END, i);
  70.                 if (end < 0) {
  71.                     throw new IllegalArgumentException("Missing End Delimiter");
  72.                 }
  73.                 final String value = expression.substring(i + 1, end);
  74.                 if (value.isEmpty()) {
  75.                     throw new IllegalArgumentException("No Index Value");
  76.                 }
  77.                 int index = 0;
  78.                 try {
  79.                     index = Integer.parseInt(value, 10);
  80.                 } catch (final Exception e) {
  81.                     throw new IllegalArgumentException("Invalid index value '" + value + "'");
  82.                 }
  83.                 return index;
  84.             }
  85.         }
  86.         return -1;
  87.     }

  88.     /**
  89.      * Gets the map key from the property expression or {@code null}.
  90.      *
  91.      * @param expression The property expression
  92.      * @return The index value
  93.      * @throws IllegalArgumentException If the mapped property is illegally formed.
  94.      */
  95.     @Override
  96.     public String getKey(final String expression) {
  97.         if (expression == null || expression.isEmpty()) {
  98.             return null;
  99.         }
  100.         for (int i = 0; i < expression.length(); i++) {
  101.             final char c = expression.charAt(i);
  102.             if (c == NESTED || c == INDEXED_START) {
  103.                 return null;
  104.             }
  105.             if (c == MAPPED_START) {
  106.                 final int end = expression.indexOf(MAPPED_END, i);
  107.                 if (end < 0) {
  108.                     throw new IllegalArgumentException("Missing End Delimiter");
  109.                 }
  110.                 return expression.substring(i + 1, end);
  111.             }
  112.         }
  113.         return null;
  114.     }

  115.     /**
  116.      * Gets the property name from the property expression.
  117.      *
  118.      * @param expression The property expression
  119.      * @return The property name
  120.      */
  121.     @Override
  122.     public String getProperty(final String expression) {
  123.         if (expression == null || expression.isEmpty()) {
  124.             return expression;
  125.         }
  126.         for (int i = 0; i < expression.length(); i++) {
  127.             final char c = expression.charAt(i);
  128.             if (c == NESTED || c == MAPPED_START || c == INDEXED_START) {
  129.                 return expression.substring(0, i);
  130.             }
  131.         }
  132.         return expression;
  133.     }

  134.     /**
  135.      * Indicates whether or not the expression contains nested property expressions or not.
  136.      *
  137.      * @param expression The property expression
  138.      * @return The next property expression
  139.      */
  140.     @Override
  141.     public boolean hasNested(final String expression) {
  142.         if (expression == null || expression.isEmpty()) {
  143.             return false;
  144.         }
  145.         return remove(expression) != null;
  146.     }

  147.     /**
  148.      * Indicate whether the expression is for an indexed property or not.
  149.      *
  150.      * @param expression The property expression
  151.      * @return {@code true} if the expression is indexed, otherwise {@code false}
  152.      */
  153.     @Override
  154.     public boolean isIndexed(final String expression) {
  155.         if (expression == null || expression.isEmpty()) {
  156.             return false;
  157.         }
  158.         for (int i = 0; i < expression.length(); i++) {
  159.             final char c = expression.charAt(i);
  160.             if (c == NESTED || c == MAPPED_START) {
  161.                 return false;
  162.             }
  163.             if (c == INDEXED_START) {
  164.                 return true;
  165.             }
  166.         }
  167.         return false;
  168.     }

  169.     /**
  170.      * Indicate whether the expression is for a mapped property or not.
  171.      *
  172.      * @param expression The property expression
  173.      * @return {@code true} if the expression is mapped, otherwise {@code false}
  174.      */
  175.     @Override
  176.     public boolean isMapped(final String expression) {
  177.         if (expression == null || expression.isEmpty()) {
  178.             return false;
  179.         }
  180.         for (int i = 0; i < expression.length(); i++) {
  181.             final char c = expression.charAt(i);
  182.             if (c == NESTED || c == INDEXED_START) {
  183.                 return false;
  184.             }
  185.             if (c == MAPPED_START) {
  186.                 return true;
  187.             }
  188.         }
  189.         return false;
  190.     }

  191.     /**
  192.      * Extract the next property expression from the current expression.
  193.      *
  194.      * @param expression The property expression
  195.      * @return The next property expression
  196.      */
  197.     @Override
  198.     public String next(final String expression) {
  199.         if (expression == null || expression.isEmpty()) {
  200.             return null;
  201.         }
  202.         boolean indexed = false;
  203.         boolean mapped = false;
  204.         for (int i = 0; i < expression.length(); i++) {
  205.             final char c = expression.charAt(i);
  206.             if (indexed) {
  207.                 if (c == INDEXED_END) {
  208.                     return expression.substring(0, i + 1);
  209.                 }
  210.             } else if (mapped) {
  211.                 if (c == MAPPED_END) {
  212.                     return expression.substring(0, i + 1);
  213.                 }
  214.             } else {
  215.                 switch (c) {
  216.                 case NESTED:
  217.                     return expression.substring(0, i);
  218.                 case MAPPED_START:
  219.                     mapped = true;
  220.                     break;
  221.                 case INDEXED_START:
  222.                     indexed = true;
  223.                     break;
  224.                 default:
  225.                     break;
  226.                 }
  227.             }
  228.         }
  229.         return expression;
  230.     }

  231.     /**
  232.      * Remove the last property expression from the current expression.
  233.      *
  234.      * @param expression The property expression
  235.      * @return The new expression value, with first property expression removed - null if there are no more expressions
  236.      */
  237.     @Override
  238.     public String remove(final String expression) {
  239.         if (expression == null || expression.isEmpty()) {
  240.             return null;
  241.         }
  242.         final String property = next(expression);
  243.         if (expression.length() == property.length()) {
  244.             return null;
  245.         }
  246.         int start = property.length();
  247.         if (expression.charAt(start) == NESTED) {
  248.             start++;
  249.         }
  250.         return expression.substring(start);
  251.     }
  252. }