| 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 |