001    package org.apache.commons.ognl;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import java.beans.IntrospectionException;
023    import java.beans.PropertyDescriptor;
024    import java.lang.reflect.Method;
025    
026    /**
027     * <p>
028     * PropertyDescriptor subclass that describes an indexed set of read/write methods to get a property. Unlike
029     * IndexedPropertyDescriptor this allows the "key" to be an arbitrary object rather than just an int. Consequently it
030     * does not have a "readMethod" or "writeMethod" because it only expects a pattern like:
031     * </p>
032     * 
033     * <pre>
034     *    public void set<i>Property</i>(<i>KeyType</i>, <i>ValueType</i>);
035     *    public <i>ValueType</i> get<i>Property</i>(<i>KeyType</i>);
036     * </pre>
037     * <p>
038     * and does not require the methods that access it as an array. OGNL can get away with this without losing functionality
039     * because if the object does expose the properties they are most probably in a Map and that case is handled by the
040     * normal OGNL property accessors.
041     * </p>
042     * <p>
043     * For example, if an object were to have methods that accessed and "attributes" property it would be natural to index
044     * them by String rather than by integer and expose the attributes as a map with a different property name:
045     * 
046     * <pre>
047     * public void setAttribute( String name, Object value );
048     * 
049     * public Object getAttribute( String name );
050     * 
051     * public Map getAttributes();
052     * </pre>
053     * <p>
054     * Note that the index get/set is called get/set <code>Attribute</code> whereas the collection getter is called
055     * <code>Attributes</code>. This case is handled unambiguously by the OGNL property accessors because the set/get
056     * <code>Attribute</code> methods are detected by this object and the "attributes" case is handled by the
057     * <code>MapPropertyAccessor</code>. Therefore OGNL expressions calling this code would be handled in the following way:
058     * </p>
059     * <table>
060     * <tr>
061     * <th>OGNL Expression</th>
062     * <th>Handling</th>
063     * </tr>
064     * <tr>
065     * <td><code>attribute["name"]</code></td>
066     * <td>Handled by an index getter, like <code>getAttribute(String)</code>.</td>
067     * </tr>
068     * <tr>
069     * <td><code>attribute["name"] = value</code></td>
070     * <td>Handled by an index setter, like <code>setAttribute(String, Object)</code>.</td>
071     * </tr>
072     * <tr>
073     * <td><code>attributes["name"]</code></td>
074     * <td>Handled by <code>MapPropertyAccessor</code> via a <code>Map.get()</code>. This will <b>not</b> go through the
075     * index get accessor.</td>
076     * </tr>
077     * <tr>
078     * <td><code>attributes["name"] = value</code></td>
079     * <td>Handled by <code>MapPropertyAccessor</code> via a <code>Map.put()</code>. This will <b>not</b> go through the
080     * index set accessor.</td>
081     * </tr>
082     * </table>
083     * 
084     * @author Luke Blanshard (blanshlu@netscape.net)
085     * @author Drew Davidson (drew@ognl.org)
086     */
087    public class ObjectIndexedPropertyDescriptor
088        extends PropertyDescriptor
089    {
090        private Method indexedReadMethod;
091    
092        private Method indexedWriteMethod;
093    
094        private Class<?> propertyType;
095    
096        public ObjectIndexedPropertyDescriptor( String propertyName, Class<?> propertyType, Method indexedReadMethod,
097                                                Method indexedWriteMethod )
098            throws IntrospectionException
099        {
100            super( propertyName, null, null );
101            this.propertyType = propertyType;
102            this.indexedReadMethod = indexedReadMethod;
103            this.indexedWriteMethod = indexedWriteMethod;
104        }
105    
106        public Method getIndexedReadMethod()
107        {
108            return indexedReadMethod;
109        }
110    
111        public Method getIndexedWriteMethod()
112        {
113            return indexedWriteMethod;
114        }
115    
116        @Override
117        public Class<?> getPropertyType()
118        {
119            return propertyType;
120        }
121    
122        @Override
123        public boolean equals(Object o) {
124            if (this == o)
125            {
126                return true;
127            }
128    
129            if (!(o instanceof ObjectIndexedPropertyDescriptor))
130            {
131                return false;
132            }
133    
134            if (!super.equals(o))
135            {
136                return false;
137            }
138    
139            ObjectIndexedPropertyDescriptor that = (ObjectIndexedPropertyDescriptor) o;
140    
141            if (indexedReadMethod != null ? !indexedReadMethod.equals(that.indexedReadMethod) : that.indexedReadMethod != null)
142            {
143                return false;
144            }
145    
146            if (indexedWriteMethod != null ? !indexedWriteMethod.equals(that.indexedWriteMethod) : that.indexedWriteMethod != null)
147            {
148                return false;
149            }
150    
151            if (propertyType != null ? !propertyType.equals(that.propertyType) : that.propertyType != null)
152            {
153                return false;
154            }
155    
156            return true;
157        }
158    
159        @Override
160        public int hashCode() {
161            int result = super.hashCode();
162            result = 31 * result + (indexedReadMethod != null ? indexedReadMethod.hashCode() : 0);
163            result = 31 * result + (indexedWriteMethod != null ? indexedWriteMethod.hashCode() : 0);
164            result = 31 * result + (propertyType != null ? propertyType.hashCode() : 0);
165            return result;
166        }
167    }