    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     */
    017    package 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     * @version $Revision: 473888 $ $Date: 2006-11-12 06:21:24 +0000 (Sun, 12 Nov 2006) $
    055     * @since 1.8.0
    056     */
    057    public class DefaultResolver implements Resolver {
    058
    059        private static final char NESTED        = '.';
    060        private static final char MAPPED_START  = '(';
    061        private static final char MAPPED_END    = ')';
    062        private static final char INDEXED_START = '[';
    063        private static final char INDEXED_END   = ']';
    064
    065        /**
    066         * Default Constructor.
    067         */
    068        public DefaultResolver() {
    069        }
    070
    071        /**
    072         * Return the index value from the property expression or -1.
    073         *
    074         * @param expression The property expression
    075         * @return The index value or -1 if the property is not indexed
    076         * @throws IllegalArgumentException If the indexed property is illegally
    077         * formed or has an invalid (non-numeric) value.
    078         */
    079        public int getIndex(String expression) {
    080            if (expression == null || expression.length() == 0) {
    081                return -1;
    082            }
    083            for (int i = 0; i < expression.length(); i++) {
    084                char c = expression.charAt(i);
    085                if (c == NESTED || c == MAPPED_START) {
    086                    return -1;
    087                } else if (c == INDEXED_START) {
    088                    int end = expression.indexOf(INDEXED_END, i);
    089                    if (end < 0) {
    090                        throw new IllegalArgumentException("Missing End Delimiter");
    091                    }
    092                    String value = expression.substring(i + 1, end);
    093                    if (value.length() == 0) {
    094                        throw new IllegalArgumentException("No Index Value");
    095                    }
    096                    int index = 0;
    097                    try {
    098                        index = Integer.parseInt(value, 10);
    099                    } catch (Exception e) {
    100                        throw new IllegalArgumentException("Invalid index value '"
    101                                + value + "'");
    102                    }
    103                    return index;
    104                }
    105            }
    106            return -1;
    107        }
    108
    109        /**
    110         * Return the map key from the property expression or <code>null</code>.
    111         *
    112         * @param expression The property expression
    113         * @return The index value
    114         * @throws IllegalArgumentException If the mapped property is illegally formed.
    115         */
    116        public String getKey(String expression) {
    117            if (expression == null || expression.length() == 0) {
    118                return null;
    119            }
    120            for (int i = 0; i < expression.length(); i++) {
    121                char c = expression.charAt(i);
    122                if (c == NESTED || c == INDEXED_START) {
    123                    return null;
    124                } else if (c == MAPPED_START) {
    125                    int end = expression.indexOf(MAPPED_END, i);
    126                    if (end < 0) {
    127                        throw new IllegalArgumentException("Missing End Delimiter");
    128                    }
    129                    return expression.substring(i + 1, end);
    130                }
    131            }
    132            return null;
    133        }
    134
    135        /**
    136         * Return the property name from the property expression.
    137         *
    138         * @param expression The property expression
    139         * @return The property name
    140         */
    141        public String getProperty(String expression) {
    142            if (expression == null || expression.length() == 0) {
    143                return expression;
    144            }
    145            for (int i = 0; i < expression.length(); i++) {
    146                char c = expression.charAt(i);
    147                if (c == NESTED) {
    148                    return expression.substring(0, i);
    149                } else if (c == MAPPED_START || c == INDEXED_START) {
    150                    return expression.substring(0, i);
    151                }
    152            }
    153            return expression;
    154        }
    155
    156        /**
    157         * Indicates whether or not the expression
    158         * contains nested property expressions or not.
    159         *
    160         * @param expression The property expression
    161         * @return The next property expression
    162         */
    163        public boolean hasNested(String expression) {
    164            if (expression == null || expression.length() == 0) {
    165                return false;
    166            } else {
    167                return (remove(expression) != null);
    168            }
    169        }
    170
    171        /**
    172         * Indicate whether the expression is for an indexed property or not.
    173         *
    174         * @param expression The property expression
    175         * @return <code>true</code> if the expresion is indexed,
    176         *  otherwise <code>false</code>
    177         */
    178        public boolean isIndexed(String expression) {
    179            if (expression == null || expression.length() == 0) {
    180                return false;
    181            }
    182            for (int i = 0; i < expression.length(); i++) {
    183                char c = expression.charAt(i);
    184                if (c == NESTED || c == MAPPED_START) {
    185                    return false;
    186                } else if (c == INDEXED_START) {
    187                    return true;
    188                }
    189            }
    190            return false;
    191        }
    192
    193        /**
    194         * Indicate whether the expression is for a mapped property or not.
    195         *
    196         * @param expression The property expression
    197         * @return <code>true</code> if the expresion is mapped,
    198         *  otherwise <code>false</code>
    199         */
    200        public boolean isMapped(String expression) {
    201            if (expression == null || expression.length() == 0) {
    202                return false;
    203            }
    204            for (int i = 0; i < expression.length(); i++) {
    205                char c = expression.charAt(i);
    206                if (c == NESTED || c == INDEXED_START) {
    207                    return false;
    208                } else if (c == MAPPED_START) {
    209                    return true;
    210                }
    211            }
    212            return false;
    213        }
    214
    215        /**
    216         * Extract the next property expression from the
    217         * current expression.
    218         *
    219         * @param expression The property expression
    220         * @return The next property expression
    221         */
    222        public String next(String expression) {
    223            if (expression == null || expression.length() == 0) {
    224                return null;
    225            }
    226            boolean indexed = false;
    227            boolean mapped  = false;
    228            for (int i = 0; i < expression.length(); i++) {
    229                char c = expression.charAt(i);
    230                if (indexed) {
    231                    if (c == INDEXED_END) {
    232                        return expression.substring(0, i + 1);
    233                    }
    234                } else if (mapped) {
    235                    if (c == MAPPED_END) {
    236                        return expression.substring(0, i + 1);
    237                    }
    238                } else {
    239                    if (c == NESTED) {
    240                        return expression.substring(0, i);
    241                    } else if (c == MAPPED_START) {
    242                        mapped = true;
    243                    } else if (c == INDEXED_START) {
    244                        indexed = true;
    245                    }
    246                }
    247            }
    248            return expression;
    249        }
    250
    251        /**
    252         * Remove the last property expresson from the
    253         * current expression.
    254         *
    255         * @param expression The property expression
    256         * @return The new expression value, with first property
    257         * expression removed - null if there are no more expressions
    258         */
    259        public String remove(String expression) {
    260            if (expression == null || expression.length() == 0) {
    261                return null;
    262            }
    263            String property = next(expression);
    264            if (expression.length() == property.length()) {
    265                return null;
    266            }
    267            int start = property.length();
    268            if (expression.charAt(start) == NESTED) {
    269                start++;
    270            }
    271            return expression.substring(start);
    272        }
    273    }