    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.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}