View Javadoc
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    *      https://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  
18  package org.apache.commons.jexl3.internal.introspection;
19  
20  import java.beans.IntrospectionException;
21  import java.lang.reflect.Method;
22  
23  import org.apache.commons.jexl3.introspection.JexlPropertyGet;
24  
25  /**
26   * Abstract an indexed property container.
27   * <p>This allows getting properties from expressions like {@code var.container.property}.
28   * This stores the container name and class as well as the list of available getter and setter methods.
29   * It implements JexlPropertyGet since such a container can only be accessed from its owning instance (not set).
30   */
31  public final class IndexedType implements JexlPropertyGet {
32  
33      /**
34       * A generic indexed property container, exposes get(key) and set(key, value)
35       * and solves method call dynamically based on arguments.
36       * <p>Must remain public for introspection purpose.</p>
37       */
38      public static final class IndexedContainer {
39  
40          /** The container instance. */
41          final Object container;
42  
43          /** The container type instance. */
44          final IndexedType type;
45  
46          /**
47           * Creates a new duck container.
48           *
49           * @param theType the container type
50           * @param theContainer the container instance
51           */
52          IndexedContainer(final IndexedType theType, final Object theContainer) {
53              this.type = theType;
54              this.container = theContainer;
55          }
56  
57          /**
58           * Gets a property from this indexed container.
59           *
60           * @param key the property key
61           * @return the property value
62           * @throws Exception if inner invocation fails
63           */
64          public Object get(final Object key) throws Exception {
65              return type.invokeGet(container, key);
66          }
67  
68          /**
69           * Gets the property container class.
70           *
71           * @return the container class
72           */
73          public Class<?> getContainerClass() {
74              return type.clazz;
75          }
76  
77          /**
78           * Gets the property container name.
79           *
80           * @return the container name
81           */
82          public String getContainerName() {
83              return type.container;
84          }
85  
86          /**
87           * Sets a property in this indexed container.
88           *
89           * @param key the property key
90           * @param value the property value
91           * @return the invocation result (frequently null)
92           * @throws Exception if inner invocation fails
93           */
94          public Object set(final Object key, final Object value) throws Exception {
95              return type.invokeSet(container, key, value);
96          }
97      }
98  
99      /**
100      * Attempts to find an indexed-property getter in an object.
101      * The code attempts to find the list of methods getXXX() and setXXX().
102      * Note that this is not equivalent to the strict bean definition of indexed properties; the type of the key
103      * is not necessarily an int and the set/get arrays are not resolved.
104      *
105      * @param is the introspector
106      * @param object the object
107      * @param name the container name
108      * @return a JexlPropertyGet is successful, null otherwise
109      */
110     public static JexlPropertyGet discover(final Introspector is, final Object object, final String name) {
111         if (object != null && name != null && !name.isEmpty()) {
112             final String base = name.substring(0, 1).toUpperCase() + name.substring(1);
113             final Class<?> clazz = object.getClass();
114             final Method[] getters = is.getMethods(object.getClass(), "get" + base);
115             final Method[] setters = is.getMethods(object.getClass(), "set" + base);
116             if (getters != null) {
117                 return new IndexedType(name, clazz, getters, setters);
118             }
119         }
120         return null;
121     }
122 
123     /** The container name. */
124     final String container;
125 
126     /** The container class. */
127     final Class<?> clazz;
128 
129     /** The array of getter methods. */
130     private final Method[] getters;
131 
132     /** Last get method used. */
133     private volatile Method get;
134 
135     /** The array of setter methods. */
136     private final Method[] setters;
137 
138     /** Last set method used. */
139     private volatile Method set;
140 
141     /**
142      * Creates a new indexed property container type.
143      *
144      * @param name the container name
145      * @param c the owning class
146      * @param gets the array of getter methods
147      * @param sets the array of setter methods
148      */
149     private IndexedType(final String name, final Class<?> c, final Method[] gets, final Method[] sets) {
150         this.container = name;
151         this.clazz = c;
152         this.getters = gets;
153         this.setters = sets;
154     }
155 
156     @Override
157     public Object invoke(final Object obj) throws Exception {
158         if (obj != null && clazz.equals(obj.getClass())) {
159             return new IndexedContainer(this, obj);
160         }
161         throw new IntrospectionException("property resolution error");
162     }
163 
164     /**
165      * Gets the value of a property from a container.
166      *
167      * @param object the container instance (not null)
168      * @param key the property key (not null)
169      * @return the property value
170      * @throws Exception if invocation failed;
171      *         IntrospectionException if a property getter could not be found
172      */
173     Object invokeGet(final Object object, final Object key) throws Exception {
174         if (getters != null && getters.length > 0) {
175             Method jm = get;
176             if (jm != null) {
177                 final Class<?>[] types = jm.getParameterTypes();
178                 if (types[0].isAssignableFrom(key.getClass())) {
179                     return jm.invoke(object, key);
180                 }
181             }
182             final Object[] args = {key};
183             final String methodName = getters[0].getName();
184             final MethodKey km = new MethodKey(methodName, args);
185             jm = km.getMostSpecificMethod(getters);
186             if (jm != null) {
187                 final Object invoked = jm.invoke(object, args);
188                 get = jm;
189                 return invoked;
190             }
191         }
192         throw new IntrospectionException("property get error: "
193                 + object.getClass() + "@" + key.toString());
194     }
195 
196     /**
197      * Sets the value of a property in a container.
198      *
199      * @param object the container instance (not null)
200      * @param key the property key (not null)
201      * @param value the property value (not null)
202      * @return the result of the method invocation (frequently null)
203      * @throws Exception if invocation failed;
204      *         IntrospectionException if a property setter could not be found
205      */
206     Object invokeSet(final Object object, final Object key, final Object value) throws Exception {
207         if (setters != null && setters.length > 0) {
208             Method jm = set;
209             if (jm != null) {
210                 final Class<?>[] types = jm.getParameterTypes();
211                 if (types[0].isAssignableFrom(key.getClass())
212                     && (value == null
213                         || types[1].isAssignableFrom(value.getClass()))) {
214                     return jm.invoke(object, key, value);
215                 }
216             }
217             final Object[] args = {key, value};
218             final String methodName = setters[0].getName();
219             final MethodKey km = new MethodKey(methodName, args);
220             jm = km.getMostSpecificMethod(setters);
221             if (jm != null) {
222                 final Object invoked = jm.invoke(object, args);
223                 set = jm;
224                 return invoked;
225             }
226         }
227         throw new IntrospectionException("property set error: "
228                 + object.getClass() + "@" + key.toString());
229     }
230 
231     @Override
232     public boolean isCacheable() {
233         return true;
234     }
235 
236     @Override
237     public boolean tryFailed(final Object val) {
238         return val == Uberspect.TRY_FAILED;
239     }
240 
241     @Override
242     public Object tryInvoke(final Object obj, final Object key) {
243         if (obj != null && key != null
244             && clazz.equals(obj.getClass())
245             && container.equals(key.toString())) {
246             return new IndexedContainer(this, obj);
247         }
248         return Uberspect.TRY_FAILED;
249     }
250 
251 }