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  package org.apache.commons.jexl3.jexl342;
18  
19  import java.lang.ref.Reference;
20  import java.util.Arrays;
21  import java.util.Collections;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Objects;
26  import java.util.Optional;
27  import java.util.concurrent.atomic.AtomicReference;
28  
29  import org.apache.commons.jexl3.JexlArithmetic;
30  import org.apache.commons.jexl3.JexlEngine;
31  import org.apache.commons.jexl3.JexlException;
32  import org.apache.commons.jexl3.JexlOperator;
33  import org.apache.commons.jexl3.introspection.JexlMethod;
34  import org.apache.commons.jexl3.introspection.JexlPropertyGet;
35  import org.apache.commons.jexl3.introspection.JexlPropertySet;
36  import org.apache.commons.jexl3.introspection.JexlUberspect;
37  
38  /**
39   * An Uberspect that handles references (java.lang.ref.Reference) and optionals (java.util.Optional).
40   * <p>This illustrates JEXL&quot;s low level customization capabilities.</p>
41   * see JEXL-342.
42   */
43  public class ReferenceUberspect implements JexlUberspect {
44      /**
45       * Duck-reference handler, calls get().
46       */
47      @FunctionalInterface
48      interface ReferenceHandler {
49          /**
50           * Performs a call to get().
51           * @param ref the reference
52           * @return the value pointed by the reference
53           */
54          Object callGet(Object ref);
55      }
56      /** A static signature for method(). */
57      private static final Object[] EMPTY_PARAMS = {};
58      /**
59       * Discovers an optional getter.
60       * <p>The method to be found should be named "{find}{P,p}property and return an Optional&lt;?&gt;.</p>
61       *
62       * @param is the uberspector
63       * @param clazz the class to find the get method from
64       * @param property the property name to find
65       * @return the executor if found, null otherwise
66       */
67      private static JexlPropertyGet discoverFind(final JexlUberspect is, final Class<?> clazz, final String property) {
68          if (property == null || property.isEmpty()) {
69              return null;
70          }
71          //  this is gross and linear, but it keeps it straightforward.
72          JexlMethod method;
73          final int start = 4; // "find".length() == 4
74          // start with get<Property>
75          final StringBuilder sb = new StringBuilder("find");
76          sb.append(property);
77          // uppercase nth char
78          final char c = sb.charAt(start);
79          sb.setCharAt(start, Character.toUpperCase(c));
80          method = is.getMethod(clazz, sb.toString(), EMPTY_PARAMS);
81          //lowercase nth char
82          if (method == null) {
83              sb.setCharAt(start, Character.toLowerCase(c));
84              method = is.getMethod(clazz, sb.toString(), EMPTY_PARAMS);
85          }
86          if (method != null && Optional.class.equals(method.getReturnType())) {
87              final JexlMethod getter = method;
88              final String name = sb.toString();
89              return new JexlPropertyGet() {
90                  @Override
91                  public Object invoke(final Object obj) throws Exception {
92                      return getter.invoke(obj);
93                  }
94  
95                  @Override
96                  public boolean isCacheable() {
97                      return getter.isCacheable();
98                  }
99  
100                 @Override
101                 public boolean tryFailed(final Object rval) {
102                     return rval == JexlEngine.TRY_FAILED;
103                 }
104 
105                 @Override
106                 public Object tryInvoke(final Object obj, final Object key) throws JexlException.TryFailed {
107                     return !Objects.equals(property, key) ? JexlEngine.TRY_FAILED : getter.tryInvoke(name, obj);
108                 }
109             };
110         }
111         return null;
112     }
113     /**
114      * Find a reference handler for a given instance.
115      * @param ref the reference
116      * @return the handler or null if object cannot be handled
117      */
118     private static ReferenceHandler discoverHandler(final Object ref) {
119         // optional support
120         if (ref instanceof Optional<?>) {
121             return ReferenceUberspect::handleOptional;
122         }
123         // atomic ref
124         if (ref instanceof AtomicReference<?>) {
125             return ReferenceUberspect::handleAtomic;
126         }
127         // a reference
128         if (ref instanceof Reference<?>) {
129             return ReferenceUberspect::handleReference;
130         }
131         // delegate
132         return null;
133     }
134 
135     /**
136      * Cast ref to atomic reference, call get().
137      * @param ref the reference
138      * @return the get() result if not null, ref otherwise
139      */
140     static Object handleAtomic(final Object ref) {
141         final Object obj = ((AtomicReference<?>) ref).get();
142         return obj == null ? ref : obj;
143     }
144 
145     /**
146      * Cast ref to optional, call isPresent()/get().
147      * @param ref the reference
148      * @return the get() result
149      */
150     static Object handleOptional(final Object ref) {
151         // optional support
152         final Optional<?> optional = (Optional<?>) ref;
153         return optional.isPresent() ? optional.get() : null;
154     }
155 
156     /**
157      * Cast ref to reference, call get().
158      * @param ref the reference
159      * @return the get() result if not null, ref otherwise
160      */
161     static Object handleReference(final Object ref) {
162         final Object obj = ((Reference<?>) ref).get();
163         return obj == null ? ref : obj;
164     }
165 
166     /** The uberspect we delegate to. */
167     private final JexlUberspect uberspect;
168 
169     /**
170      * The pojo resolver list strategy.
171      */
172     private final List<PropertyResolver> pojoStrategy;
173 
174     /**
175      * The map resolver list strategy.
176      */
177     private final List<PropertyResolver> mapStrategy;
178 
179     /**
180      * Constructs a new instance.
181      * @param jexlUberspect the uberspect to delegate to
182      */
183     public ReferenceUberspect(final JexlUberspect jexlUberspect) {
184         uberspect = jexlUberspect;
185         final PropertyResolver find = new PropertyResolver() {
186             @Override
187             public JexlPropertyGet getPropertyGet(final JexlUberspect uber, final Object obj, final Object identifier) {
188                 return discoverFind(uberspect, obj.getClass(), identifier.toString());
189             }
190 
191             @Override
192             public JexlPropertySet getPropertySet(final JexlUberspect uber, final Object obj, final Object identifier, final Object arg) {
193                 return null;
194             }
195         };
196         pojoStrategy = Arrays.asList(
197             JexlResolver.PROPERTY,
198             find,
199             JexlResolver.MAP,
200             JexlResolver.LIST,
201             JexlResolver.DUCK,
202             JexlResolver.FIELD,
203             JexlResolver.CONTAINER);
204         mapStrategy = Arrays.asList(
205             JexlResolver.MAP,
206             JexlResolver.LIST,
207             JexlResolver.DUCK,
208             JexlResolver.PROPERTY,
209             find,
210             JexlResolver.FIELD,
211             JexlResolver.CONTAINER);
212     }
213 
214     @Override
215     public JexlArithmetic.Uberspect getArithmetic(final JexlArithmetic arithmetic) {
216         return getOperator(arithmetic);
217     }
218 
219     @Override
220     public JexlOperator.Uberspect getOperator(final JexlArithmetic arithmetic) {
221         return uberspect.getOperator(arithmetic);
222     }
223 
224     @Override
225     public Class<?> getClassByName(final String className) {
226         return uberspect.getClassByName(className);
227     }
228 
229     @Override
230     public ClassLoader getClassLoader() {
231         return uberspect.getClassLoader();
232     }
233 
234     @Override
235     public JexlMethod getConstructor(final Object ctorHandle, final Object... args) {
236         return uberspect.getConstructor(ctorHandle, args);
237     }
238 
239     @Override
240     public Iterator<?> getIterator(final Object ref) {
241         // is this is a reference of some kind?
242         final ReferenceHandler handler = discoverHandler(ref);
243         if (handler == null) {
244             return uberspect.getIterator(ref);
245         }
246         // do we have an object referenced ?
247         final Object obj = handler.callGet(ref);
248         if (ref == obj) {
249             return null;
250         }
251         if (obj == null) {
252             return Collections.emptyIterator();
253         }
254         return uberspect.getIterator(obj);
255     }
256 
257     @Override
258     public JexlMethod getMethod(final Object ref, final String method, final Object... args) {
259         // is this is a reference of some kind?
260         final ReferenceHandler handler = discoverHandler(ref);
261         if (handler == null) {
262             return uberspect.getMethod(ref, method, args);
263         }
264         // do we have an object referenced ?
265         final Object obj = handler.callGet(ref);
266         if (ref == obj) {
267             return null;
268         }
269         JexlMethod jexlMethod = null;
270         if (obj != null) {
271             jexlMethod = uberspect.getMethod(obj, method, args);
272             if (jexlMethod == null) {
273                 throw new JexlException.Method(null, method, args, null);
274             }
275         } else {
276             jexlMethod = new OptionalNullMethod(uberspect, method);
277         }
278         return new ReferenceMethodExecutor(handler, jexlMethod);
279     }
280 
281     @Override
282     public JexlPropertyGet getPropertyGet(final List<PropertyResolver> resolvers, final Object ref, final Object identifier) {
283         // is this is a reference of some kind?
284         final ReferenceHandler handler = discoverHandler(ref);
285         if (handler == null) {
286             return uberspect.getPropertyGet(resolvers, ref, identifier);
287         }
288         // do we have an object referenced ?
289         final Object obj = handler.callGet(ref);
290         if (ref == obj) {
291             return null;
292         }
293         // obj is null means proper dereference of an optional; we don't have an object,
294         // we cannot determine jexlGet, not a pb till we call with a not-null object
295         // since the result is likely to be not null... TryInvoke will fail and invoke will throw.
296         // from that object, get the property getter if any
297         JexlPropertyGet jexlGet = null;
298         if (obj != null) {
299             jexlGet = uberspect.getPropertyGet(resolvers, obj, identifier);
300             if (jexlGet == null) {
301                 throw new JexlException.Property(null, Objects.toString(identifier), false, null);
302             }
303         } else {
304             jexlGet = new OptionalNullGetter(uberspect, identifier);
305         }
306         return new ReferenceGetExecutor(handler, jexlGet);
307     }
308 
309     @Override
310     public JexlPropertyGet getPropertyGet(final Object obj, final Object identifier) {
311         return getPropertyGet(null, obj, identifier);
312     }
313 
314     @Override
315     public JexlPropertySet getPropertySet(final List<PropertyResolver> resolvers, final Object ref, final Object identifier, final Object arg) {
316         // is this is a reference of some kind?
317         final ReferenceHandler handler = discoverHandler(ref);
318         if (handler == null) {
319             return uberspect.getPropertySet(resolvers, ref, identifier, arg);
320         }
321         // do we have an object referenced ?
322         final Object obj = handler.callGet(ref);
323         if (ref  == obj) {
324             return null;
325         }
326         // from that object, get the property setter if any
327         JexlPropertySet jexlSet = null;
328         if (obj != null) {
329             jexlSet = uberspect.getPropertySet(resolvers, obj, identifier, arg);
330             if (jexlSet == null) {
331                 throw new JexlException.Property(null, Objects.toString(identifier), false, null);
332             }
333         } else {
334             // postpone resolution till not null
335             jexlSet = new OptionalNullSetter(uberspect, identifier);
336         }
337         return new ReferenceSetExecutor(handler, jexlSet);
338     }
339 
340     @Override
341     public JexlPropertySet getPropertySet(final Object obj, final Object identifier, final Object arg) {
342         return getPropertySet(null, obj, identifier, arg);
343     }
344 
345     /**
346      * A JEXL strategy improved with optionals/references.
347      */
348     @Override
349     public List<PropertyResolver> getResolvers(final JexlOperator op, final Object obj) {
350         if (op == JexlOperator.ARRAY_GET) {
351             return mapStrategy;
352         }
353         if (op == JexlOperator.ARRAY_SET) {
354             return mapStrategy;
355         }
356         if (op == null && obj instanceof Map) {
357             return mapStrategy;
358         }
359         return pojoStrategy;
360     }
361 
362     @Override
363     public int getVersion() {
364         return uberspect.getVersion();
365     }
366 
367     @Override
368     public void setClassLoader(final ClassLoader loader) {
369         uberspect.setClassLoader(loader);
370     }
371 
372 }