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 }