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