1 | package fr.sii.ogham.core.util; | |
2 | ||
3 | import java.lang.reflect.InvocationTargetException; | |
4 | import java.util.Map; | |
5 | import java.util.Map.Entry; | |
6 | ||
7 | import org.apache.commons.beanutils.BeanUtilsBean; | |
8 | import org.apache.commons.beanutils.ConversionException; | |
9 | import org.apache.commons.beanutils.ConvertUtils; | |
10 | import org.apache.commons.beanutils.NestedNullException; | |
11 | import org.slf4j.Logger; | |
12 | import org.slf4j.LoggerFactory; | |
13 | ||
14 | import fr.sii.ogham.core.exception.util.BeanException; | |
15 | import fr.sii.ogham.core.util.bean.MapBeanReadWrapper; | |
16 | import fr.sii.ogham.core.util.converter.EmailAddressConverter; | |
17 | import fr.sii.ogham.core.util.converter.SmsSenderConverter; | |
18 | import fr.sii.ogham.email.message.EmailAddress; | |
19 | import fr.sii.ogham.sms.message.Sender; | |
20 | ||
21 | /** | |
22 | * Helper class for bean management: | |
23 | * <ul> | |
24 | * <li>Converts an object into a map</li> | |
25 | * <li>Fills a bean with values provided in a map</li> | |
26 | * </ul> | |
27 | * <p> | |
28 | * This work can be done by several libraries. The aim of this class is to be | |
29 | * able to change the implementation easily to use another library for example. | |
30 | * </p> | |
31 | * <p> | |
32 | * For example, we could find which library is available in the classpath and | |
33 | * use this library instead of forcing users to include Apache Commons BeanUtils | |
34 | * library. | |
35 | * </p> | |
36 | * | |
37 | * @author Aurélien Baudet | |
38 | * | |
39 | */ | |
40 | public final class BeanUtils { | |
41 | private static final Logger LOG = LoggerFactory.getLogger(BeanUtils.class); | |
42 | ||
43 | static { | |
44 | registerDefaultConverters(); | |
45 | } | |
46 | ||
47 | /** | |
48 | * Register the following converters: | |
49 | * <ul> | |
50 | * <li>{@link EmailAddressConverter}</li> | |
51 | * <li>{@link SmsSenderConverter}</li> | |
52 | * </ul> | |
53 | * | |
54 | * If a conversion error occurs it throws an exception. | |
55 | */ | |
56 | public static void registerDefaultConverters() { | |
57 | // TODO: auto-detect converters in the classpath ? | |
58 | // Add converter for being able to convert string address into | |
59 | // EmailAddress | |
60 |
2
1. registerDefaultConverters : removed call to org/apache/commons/beanutils/ConvertUtils::register → NO_COVERAGE 2. registerDefaultConverters : removed call to org/apache/commons/beanutils/ConvertUtils::register → SURVIVED |
ConvertUtils.register(new EmailAddressConverter(), EmailAddress.class); |
61 | // Add converter for being able to convert string into | |
62 | // SMS sender | |
63 |
2
1. registerDefaultConverters : removed call to org/apache/commons/beanutils/ConvertUtils::register → NO_COVERAGE 2. registerDefaultConverters : removed call to org/apache/commons/beanutils/ConvertUtils::register → SURVIVED |
ConvertUtils.register(new SmsSenderConverter(), Sender.class); |
64 |
2
1. registerDefaultConverters : removed call to org/apache/commons/beanutils/ConvertUtilsBean::register → SURVIVED 2. registerDefaultConverters : removed call to org/apache/commons/beanutils/ConvertUtilsBean::register → NO_COVERAGE |
BeanUtilsBean.getInstance().getConvertUtils().register(true, false, 0); |
65 | } | |
66 | ||
67 | /** | |
68 | * <p> | |
69 | * Convert a Java object into a map. Each property of the bean is added to | |
70 | * the map. The key of each entry is the name of the property. The value of | |
71 | * each entry is the value of the property. | |
72 | * | |
73 | * <p> | |
74 | * There is no copy of values into a new map. Actually, the bean is wrapped | |
75 | * and access to properties is done lazily. Then a special map | |
76 | * implementation decorates the bean wrapper. | |
77 | * | |
78 | * @param bean | |
79 | * the bean to convert into a map | |
80 | * @return the bean as map | |
81 | */ | |
82 | public static Map<String, Object> convert(Object bean) { | |
83 |
2
1. convert : replaced return value with null for fr/sii/ogham/core/util/BeanUtils::convert → NO_COVERAGE 2. convert : replaced return value with null for fr/sii/ogham/core/util/BeanUtils::convert → KILLED |
return new MapBeanReadWrapper(bean); |
84 | } | |
85 | ||
86 | /** | |
87 | * <p> | |
88 | * Fills a Java object with the provided values. The key of the map | |
89 | * corresponds to the name of the property to set. The value of the map | |
90 | * corresponds to the value to set on the Java object. | |
91 | * </p> | |
92 | * <p> | |
93 | * The keys can contain '.' to set nested values. | |
94 | * </p> | |
95 | * Override parameter allows to indicate which source has higher priority: | |
96 | * <ul> | |
97 | * <li>If true, then all values provided in the map will be always set on | |
98 | * the bean</li> | |
99 | * <li>If false then there are two cases: | |
100 | * <ul> | |
101 | * <li>If the property value of the bean is null, then the value that comes | |
102 | * from the map is used</li> | |
103 | * <li>If the property value of the bean is not null, then this value is | |
104 | * unchanged and the value in the map is not used</li> | |
105 | * </ul> | |
106 | * </li> | |
107 | * </ul> | |
108 | * Skip unknown parameter allows to indicate if execution should fail or | |
109 | * not: | |
110 | * <ul> | |
111 | * <li>If true and a property provided in the map doesn't exist, then there | |
112 | * is no failure and no change is applied to the bean</li> | |
113 | * <li>If false and a property provided in the map doesn't exist, then the | |
114 | * method fails immediately.</li> | |
115 | * </ul> | |
116 | * | |
117 | * @param bean | |
118 | * the bean to populate | |
119 | * @param values | |
120 | * the name/value pairs | |
121 | * @param options | |
122 | * options used to | |
123 | * @throws BeanException | |
124 | * when the bean couldn't be populated | |
125 | */ | |
126 | public static void populate(Object bean, Map<String, Object> values, Options options) throws BeanException { | |
127 | for (Entry<String, Object> entry : values.entrySet()) { | |
128 |
2
1. populate : removed call to fr/sii/ogham/core/util/BeanUtils::populate → NO_COVERAGE 2. populate : removed call to fr/sii/ogham/core/util/BeanUtils::populate → KILLED |
populate(bean, entry, options); |
129 | } | |
130 | } | |
131 | ||
132 | /** | |
133 | * <p> | |
134 | * Fills a Java object with the provided value. The key of the entry | |
135 | * corresponds to the name of the property to set. The value of the entry | |
136 | * corresponds to the value to set on the Java object. | |
137 | * </p> | |
138 | * <p> | |
139 | * The keys can contain '.' to set nested values. | |
140 | * </p> | |
141 | * Override parameter allows to indicate which source has higher priority: | |
142 | * <ul> | |
143 | * <li>If true, then the value provided in the entry will be always set on | |
144 | * the bean</li> | |
145 | * <li>If false then there are two cases: | |
146 | * <ul> | |
147 | * <li>If the property value of the bean is null, then the value that comes | |
148 | * from the entry is used</li> | |
149 | * <li>If the property value of the bean is not null, then this value is | |
150 | * unchanged and the value in the entry is not used</li> | |
151 | * </ul> | |
152 | * </li> | |
153 | * </ul> | |
154 | * Skip unknown parameter allows to indicate if execution should fail or | |
155 | * not: | |
156 | * <ul> | |
157 | * <li>If true and a property provided in the entry doesn't exist, then | |
158 | * there is no failure and no change is applied to the bean</li> | |
159 | * <li>If false and a property provided in the entry doesn't exist, then the | |
160 | * method fails immediately.</li> | |
161 | * </ul> | |
162 | * | |
163 | * @param bean | |
164 | * the bean to populate | |
165 | * @param entry | |
166 | * the name/value pair | |
167 | * @param options | |
168 | * options used to | |
169 | * @throws BeanException | |
170 | * when the bean couldn't be populated | |
171 | */ | |
172 | public static void populate(Object bean, Entry<String, Object> entry, Options options) throws BeanException { | |
173 | try { | |
174 | String property = org.apache.commons.beanutils.BeanUtils.getProperty(bean, entry.getKey()); | |
175 |
4
1. populate : negated conditional → NO_COVERAGE 2. populate : negated conditional → NO_COVERAGE 3. populate : negated conditional → KILLED 4. populate : negated conditional → KILLED |
if (options.isOverride() || property == null) { |
176 |
2
1. populate : removed call to org/apache/commons/beanutils/BeanUtils::setProperty → NO_COVERAGE 2. populate : removed call to org/apache/commons/beanutils/BeanUtils::setProperty → KILLED |
org.apache.commons.beanutils.BeanUtils.setProperty(bean, entry.getKey(), entry.getValue()); |
177 | } | |
178 | } catch (NestedNullException | NoSuchMethodException e) { | |
179 |
2
1. populate : removed call to fr/sii/ogham/core/util/BeanUtils::handleUnknown → NO_COVERAGE 2. populate : removed call to fr/sii/ogham/core/util/BeanUtils::handleUnknown → KILLED |
handleUnknown(bean, options, entry, e); |
180 | } catch (ConversionException e) { | |
181 |
2
1. populate : removed call to fr/sii/ogham/core/util/BeanUtils::handleConversion → NO_COVERAGE 2. populate : removed call to fr/sii/ogham/core/util/BeanUtils::handleConversion → KILLED |
handleConversion(bean, options, entry, e); |
182 | } catch (IllegalAccessException | InvocationTargetException e) { | |
183 |
2
1. populate : removed call to fr/sii/ogham/core/util/BeanUtils::handleInvocation → NO_COVERAGE 2. populate : removed call to fr/sii/ogham/core/util/BeanUtils::handleInvocation → KILLED |
handleInvocation(bean, options, entry, e); |
184 | } | |
185 | } | |
186 | ||
187 | /** | |
188 | * <p> | |
189 | * Fills a Java object with the provided values. The key of the map | |
190 | * corresponds to the name of the property to set. The value of the map | |
191 | * corresponds to the value to set on the Java object. | |
192 | * </p> | |
193 | * <p> | |
194 | * The keys can contain '.' to set nested values. | |
195 | * </p> | |
196 | * It doesn't override the value of properties of the bean that are not | |
197 | * null. For example, if the bean looks like: | |
198 | * | |
199 | * <pre> | |
200 | * public class SampleBean { | |
201 | * private String foo = "foo"; | |
202 | * private String bar = null; | |
203 | * | |
204 | * // ... | |
205 | * // getters and setters | |
206 | * // ... | |
207 | * } | |
208 | * </pre> | |
209 | * | |
210 | * If the map is: | |
211 | * | |
212 | * <pre> | |
213 | * Map<String, Object> map = new HashMap<>(); | |
214 | * map.put("foo", "newfoo"); | |
215 | * map.put("bar", "newbar"); | |
216 | * </pre> | |
217 | * | |
218 | * Then the bean will be: | |
219 | * | |
220 | * <pre> | |
221 | * System.out.println(bean.getFoo()); | |
222 | * // foo | |
223 | * System.out.println(bean.getBar()); | |
224 | * // newbar | |
225 | * </pre> | |
226 | * | |
227 | * <p> | |
228 | * It doesn't fail if a property doesn't exist or if the new value can't be | |
229 | * converted or property can't be accessed or set. The new value is just not | |
230 | * set. | |
231 | * </p> | |
232 | * | |
233 | * @param bean | |
234 | * the bean to populate | |
235 | * @param values | |
236 | * the name/value pairs | |
237 | * @throws BeanException | |
238 | * when the bean couldn't be populated | |
239 | */ | |
240 | public static void populate(Object bean, Map<String, Object> values) throws BeanException { | |
241 |
1
1. populate : removed call to fr/sii/ogham/core/util/BeanUtils::populate → NO_COVERAGE |
populate(bean, values, new Options(false, true, true, true)); |
242 | } | |
243 | ||
244 | private static void handleUnknown(Object bean, Options options, Entry<String, Object> entry, Exception e) throws BeanException { | |
245 |
2
1. handleUnknown : negated conditional → NO_COVERAGE 2. handleUnknown : negated conditional → KILLED |
if (options.isSkipUnknown()) { |
246 | LOG.debug("skipping property {}: it doesn't exist or is not accessible", entry.getKey(), e); | |
247 | } else { | |
248 | throw new BeanException("Failed to populate bean due to unknown property", bean, e); | |
249 | } | |
250 | } | |
251 | ||
252 | private static void handleConversion(Object bean, Options options, Entry<String, Object> entry, ConversionException e) throws BeanException { | |
253 |
2
1. handleConversion : negated conditional → NO_COVERAGE 2. handleConversion : negated conditional → KILLED |
if (options.isSkipConversionError()) { |
254 | LOG.debug("skipping property {}: can't convert value", entry.getKey(), e); | |
255 | } else { | |
256 | throw new BeanException("Failed to populate bean due to conversion error", bean, e); | |
257 | } | |
258 | } | |
259 | ||
260 | private static void handleInvocation(Object bean, Options options, Entry<String, Object> entry, ReflectiveOperationException e) throws BeanException { | |
261 |
2
1. handleInvocation : negated conditional → NO_COVERAGE 2. handleInvocation : negated conditional → KILLED |
if (options.isSkipInvocationError()) { |
262 | LOG.debug("skipping property {}: can't set value", entry.getKey(), e); | |
263 | } else { | |
264 | throw new BeanException("Failed to populate bean due to invalid setter call or security retrictions", bean, e); | |
265 | } | |
266 | } | |
267 | ||
268 | /** | |
269 | * Populate options: | |
270 | * <ul> | |
271 | * <li><strong>override</strong>: If true it overrides any previously set | |
272 | * value. If false it set the value only if previous value is not set | |
273 | * (null)</li> | |
274 | * <li><strong>skipUnknown</strong>: If true and a property doesn't exist, | |
275 | * do no fail and log the error. If false and a property doesn't exist, it | |
276 | * fails</li> | |
277 | * <li><strong>skipConversionError</strong>: If true and a property value | |
278 | * can't be set because of invalid value/type, do not fail and log the | |
279 | * error. If false and a property value can't be set because of invalid | |
280 | * value/type, it fails</li> | |
281 | * </ul> | |
282 | * | |
283 | * @author Aurélien Baudet | |
284 | * | |
285 | */ | |
286 | public static class Options { | |
287 | private final boolean override; | |
288 | private final boolean skipUnknown; | |
289 | private final boolean skipConversionError; | |
290 | private final boolean skipInvocationError; | |
291 | ||
292 | public Options(boolean override, boolean skipUnknown, boolean skipConversionError, boolean skipInvocationError) { | |
293 | super(); | |
294 | this.override = override; | |
295 | this.skipUnknown = skipUnknown; | |
296 | this.skipConversionError = skipConversionError; | |
297 | this.skipInvocationError = skipInvocationError; | |
298 | } | |
299 | ||
300 | public boolean isOverride() { | |
301 |
4
1. isOverride : replaced boolean return with false for fr/sii/ogham/core/util/BeanUtils$Options::isOverride → NO_COVERAGE 2. isOverride : replaced boolean return with true for fr/sii/ogham/core/util/BeanUtils$Options::isOverride → NO_COVERAGE 3. isOverride : replaced boolean return with false for fr/sii/ogham/core/util/BeanUtils$Options::isOverride → KILLED 4. isOverride : replaced boolean return with true for fr/sii/ogham/core/util/BeanUtils$Options::isOverride → KILLED |
return override; |
302 | } | |
303 | ||
304 | public boolean isSkipUnknown() { | |
305 |
4
1. isSkipUnknown : replaced boolean return with false for fr/sii/ogham/core/util/BeanUtils$Options::isSkipUnknown → NO_COVERAGE 2. isSkipUnknown : replaced boolean return with true for fr/sii/ogham/core/util/BeanUtils$Options::isSkipUnknown → NO_COVERAGE 3. isSkipUnknown : replaced boolean return with false for fr/sii/ogham/core/util/BeanUtils$Options::isSkipUnknown → KILLED 4. isSkipUnknown : replaced boolean return with true for fr/sii/ogham/core/util/BeanUtils$Options::isSkipUnknown → KILLED |
return skipUnknown; |
306 | } | |
307 | ||
308 | public boolean isSkipConversionError() { | |
309 |
4
1. isSkipConversionError : replaced boolean return with false for fr/sii/ogham/core/util/BeanUtils$Options::isSkipConversionError → NO_COVERAGE 2. isSkipConversionError : replaced boolean return with true for fr/sii/ogham/core/util/BeanUtils$Options::isSkipConversionError → NO_COVERAGE 3. isSkipConversionError : replaced boolean return with false for fr/sii/ogham/core/util/BeanUtils$Options::isSkipConversionError → KILLED 4. isSkipConversionError : replaced boolean return with true for fr/sii/ogham/core/util/BeanUtils$Options::isSkipConversionError → KILLED |
return skipConversionError; |
310 | } | |
311 | ||
312 | public boolean isSkipInvocationError() { | |
313 |
4
1. isSkipInvocationError : replaced boolean return with false for fr/sii/ogham/core/util/BeanUtils$Options::isSkipInvocationError → NO_COVERAGE 2. isSkipInvocationError : replaced boolean return with true for fr/sii/ogham/core/util/BeanUtils$Options::isSkipInvocationError → NO_COVERAGE 3. isSkipInvocationError : replaced boolean return with false for fr/sii/ogham/core/util/BeanUtils$Options::isSkipInvocationError → KILLED 4. isSkipInvocationError : replaced boolean return with true for fr/sii/ogham/core/util/BeanUtils$Options::isSkipInvocationError → KILLED |
return skipInvocationError; |
314 | } | |
315 | } | |
316 | ||
317 | private BeanUtils() { | |
318 | super(); | |
319 | } | |
320 | } | |
Mutations | ||
60 |
1.1 2.2 |
|
63 |
1.1 2.2 |
|
64 |
1.1 2.2 |
|
83 |
1.1 2.2 |
|
128 |
1.1 2.2 |
|
175 |
1.1 2.2 3.3 4.4 |
|
176 |
1.1 2.2 |
|
179 |
1.1 2.2 |
|
181 |
1.1 2.2 |
|
183 |
1.1 2.2 |
|
241 |
1.1 |
|
245 |
1.1 2.2 |
|
253 |
1.1 2.2 |
|
261 |
1.1 2.2 |
|
301 |
1.1 2.2 3.3 4.4 |
|
305 |
1.1 2.2 3.3 4.4 |
|
309 |
1.1 2.2 3.3 4.4 |
|
313 |
1.1 2.2 3.3 4.4 |