View Javadoc
1   /*
2    *    Copyright 2018-2022 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.mybatis.scripting.thymeleaf;
17  
18  import java.beans.BeanInfo;
19  import java.beans.IntrospectionException;
20  import java.beans.Introspector;
21  import java.beans.PropertyDescriptor;
22  import java.lang.reflect.InvocationTargetException;
23  import java.util.Map;
24  import java.util.Optional;
25  import java.util.Set;
26  import java.util.concurrent.ConcurrentHashMap;
27  import java.util.stream.Collectors;
28  import java.util.stream.Stream;
29  
30  /**
31   * The interface for accessing a property. <br>
32   * If you want to customize a default {@code PropertyAccessor}, you implements class of this interface and you need to
33   * specify to a {@link SqlGenerator}. <br>
34   *
35   * @author Kazuki Shimizu
36   *
37   * @version 1.0.2
38   */
39  public interface PropertyAccessor {
40  
41    /**
42     * Get property names of specified type.
43     *
44     * @param type
45     *          a target type
46     *
47     * @return property names
48     */
49    Set<String> getPropertyNames(Class<?> type);
50  
51    /**
52     * Get a property type of specified property.
53     *
54     * @param type
55     *          a target type
56     * @param name
57     *          a property name
58     *
59     * @return a property type
60     */
61    Class<?> getPropertyType(Class<?> type, String name);
62  
63    /**
64     * Get a property value from specified target object.
65     *
66     * @param target
67     *          a target object
68     * @param name
69     *          a property name
70     *
71     * @return a property value
72     */
73    Object getPropertyValue(Object target, String name);
74  
75    /**
76     * Set a property value to the specified target object.
77     *
78     * @param target
79     *          a target object
80     * @param name
81     *          a property name
82     * @param value
83     *          a property value
84     */
85    void setPropertyValue(Object target, String name, Object value);
86  
87    /**
88     * The built-in property accessors.
89     */
90    enum BuiltIn implements PropertyAccessor {
91  
92      /**
93       * The implementation using Java Beans API provided by JDK.
94       */
95      STANDARD(new StandardPropertyAccessor());
96  
97      private final PropertyAccessor delegate;
98  
99      BuiltIn(PropertyAccessor delegate) {
100       this.delegate = delegate;
101     }
102 
103     /**
104      * {@inheritDoc}
105      */
106     @Override
107     public Set<String> getPropertyNames(Class<?> type) {
108       return delegate.getPropertyNames(type);
109     }
110 
111     /**
112      * {@inheritDoc}
113      */
114     @Override
115     public Class<?> getPropertyType(Class<?> type, String name) {
116       return delegate.getPropertyType(type, name);
117     }
118 
119     /**
120      * {@inheritDoc}
121      */
122     @Override
123     public Object getPropertyValue(Object target, String name) {
124       return delegate.getPropertyValue(target, name);
125     }
126 
127     /**
128      * {@inheritDoc}
129      */
130     @Override
131     public void setPropertyValue(Object target, String name, Object value) {
132       delegate.setPropertyValue(target, name, value);
133     }
134 
135     static class StandardPropertyAccessor implements PropertyAccessor {
136 
137       private static Map<Class<?>, Map<String, PropertyDescriptor>> cache = new ConcurrentHashMap<>();
138 
139       /**
140        * {@inheritDoc}
141        */
142       @Override
143       public Set<String> getPropertyNames(Class<?> type) {
144         return getPropertyDescriptors(type).keySet();
145       }
146 
147       /**
148        * {@inheritDoc}
149        */
150       @Override
151       public Class<?> getPropertyType(Class<?> type, String name) {
152         return Optional.ofNullable(getPropertyDescriptors(type).get(name))
153             .orElseThrow(() -> new IllegalArgumentException(String.format(
154                 "Does not get a property type because property '%s' not found on '%s' class.", name, type.getName())))
155             .getPropertyType();
156       }
157 
158       /**
159        * {@inheritDoc}
160        */
161       @Override
162       public Object getPropertyValue(Object target, String name) {
163         try {
164           return Optional.ofNullable(getPropertyDescriptors(target.getClass()).get(name))
165               .map(PropertyDescriptor::getReadMethod)
166               .orElseThrow(() -> new IllegalArgumentException(
167                   String.format("Does not get a property value because property '%s' not found on '%s' class.", name,
168                       target.getClass().getName())))
169               .invoke(target);
170         } catch (IllegalAccessException | InvocationTargetException e) {
171           throw new IllegalStateException(e);
172         }
173       }
174 
175       /**
176        * {@inheritDoc}
177        */
178       @Override
179       public void setPropertyValue(Object target, String name, Object value) {
180         try {
181           Optional.ofNullable(getPropertyDescriptors(target.getClass()).get(name))
182               .map(PropertyDescriptor::getWriteMethod)
183               .orElseThrow(() -> new IllegalArgumentException(
184                   String.format("Does not set a property value because property '%s' not found on '%s' class.", name,
185                       target.getClass().getName())))
186               .invoke(target, value);
187         } catch (IllegalAccessException | InvocationTargetException e) {
188           throw new IllegalStateException(e);
189         }
190       }
191 
192       /**
193        * Clear cache.
194        * <p>
195        * This method use by internal processing.
196        * </p>
197        */
198       static void clearCache() {
199         cache.clear();
200       }
201 
202       private static Map<String, PropertyDescriptor> getPropertyDescriptors(Class<?> type) {
203         return cache.computeIfAbsent(type, key -> {
204           try {
205             BeanInfo beanInfo = Introspector.getBeanInfo(type);
206             return Stream.of(beanInfo.getPropertyDescriptors()).filter(x -> !x.getName().equals("class"))
207                 .collect(Collectors.toMap(PropertyDescriptor::getName, v -> v));
208           } catch (IntrospectionException e) {
209             throw new IllegalStateException(e);
210           } finally {
211             Introspector.flushFromCaches(type);
212           }
213         });
214       }
215 
216     }
217 
218   }
219 
220 }