| 1 | package fr.sii.ogham.core.util; | |
| 2 | ||
| 3 | import java.lang.reflect.InvocationTargetException; | |
| 4 | import java.util.List; | |
| 5 | import java.util.Properties; | |
| 6 | import java.util.StringJoiner; | |
| 7 | ||
| 8 | import fr.sii.ogham.core.builder.Builder; | |
| 9 | import fr.sii.ogham.core.builder.context.BuildContext; | |
| 10 | import fr.sii.ogham.core.convert.Converter; | |
| 11 | import fr.sii.ogham.core.convert.DefaultConverter; | |
| 12 | import fr.sii.ogham.core.env.PropertyResolver; | |
| 13 | import fr.sii.ogham.core.exception.builder.BuildException; | |
| 14 | import fr.sii.ogham.core.fluent.AbstractParent; | |
| 15 | import fr.sii.ogham.core.fluent.Parent; | |
| 16 | import fr.sii.ogham.email.builder.EmailBuilder; | |
| 17 | ||
| 18 | /** | |
| 19 | * Helper class for {@link Builder} implementations. It separates the builder | |
| 20 | * implementations from the environment. | |
| 21 | * | |
| 22 | * @author Aurélien Baudet | |
| 23 | * @see Builder | |
| 24 | */ | |
| 25 | public final class BuilderUtils { | |
| 26 | private static Converter converter; | |
| 27 | ||
| 28 | /** | |
| 29 | * Provide the default properties. For now, it provides only | |
| 30 | * {@link System#getProperties()}. But according to the environment or the | |
| 31 | * future of the module, properties may come from other source. | |
| 32 | * | |
| 33 | * @return the default properties | |
| 34 | */ | |
| 35 | public static Properties getDefaultProperties() { | |
| 36 |
1
1. getDefaultProperties : replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::getDefaultProperties → RUN_ERROR |
return System.getProperties(); |
| 37 | } | |
| 38 | ||
| 39 | /** | |
| 40 | * If the property value is an expression ({@code "${property.key}"}), then | |
| 41 | * it is evaluated to get the value of "property.key". If the value is not | |
| 42 | * an expression, the value is returned and converted to the result class. | |
| 43 | * | |
| 44 | * @param <T> | |
| 45 | * the type of the resulting property | |
| 46 | * @param property | |
| 47 | * the property that may be an expression | |
| 48 | * @param propertyResolver | |
| 49 | * the property resolver used to find property value (if it is an | |
| 50 | * expression) | |
| 51 | * @param resultClass | |
| 52 | * the result class | |
| 53 | * @return the resulting value of the expression, the value or null | |
| 54 | */ | |
| 55 | public static <T> T evaluate(String property, PropertyResolver propertyResolver, Class<T> resultClass) { | |
| 56 |
1
1. evaluate : negated conditional → RUN_ERROR |
if (isExpression(property)) { |
| 57 |
1
1. evaluate : replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::evaluate → RUN_ERROR |
return propertyResolver.getProperty(getPropertyKey(property), resultClass); |
| 58 | } | |
| 59 |
1
1. evaluate : replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::evaluate → NO_COVERAGE |
return getConverter().convert(property, resultClass); |
| 60 | } | |
| 61 | ||
| 62 | /** | |
| 63 | * Evaluate a list of properties that may contain expressions. It internally | |
| 64 | * calls {@link #evaluate(String, PropertyResolver, Class)}. It tries on | |
| 65 | * first property in the list. If {@code null} value is returned then the | |
| 66 | * next property is tried and so on until one property returns a non-null | |
| 67 | * value. | |
| 68 | * | |
| 69 | * <p> | |
| 70 | * If all properties return null, it returns null. | |
| 71 | * | |
| 72 | * @param <T> | |
| 73 | * the type of resulting value | |
| 74 | * @param properties | |
| 75 | * the list of properties to try in sequence | |
| 76 | * @param propertyResolver | |
| 77 | * the property resolver used to find property value (if it is an | |
| 78 | * expression) | |
| 79 | * @param resultClass | |
| 80 | * the result class | |
| 81 | * @return the resulting value or null | |
| 82 | */ | |
| 83 | public static <T> T evaluate(List<String> properties, PropertyResolver propertyResolver, Class<T> resultClass) { | |
| 84 |
1
1. evaluate : negated conditional → RUN_ERROR |
if (properties == null) { |
| 85 | return null; | |
| 86 | } | |
| 87 | for (String prop : properties) { | |
| 88 | T value = evaluate(prop, propertyResolver, resultClass); | |
| 89 |
1
1. evaluate : negated conditional → RUN_ERROR |
if (value != null) { |
| 90 |
1
1. evaluate : replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::evaluate → RUN_ERROR |
return value; |
| 91 | } | |
| 92 | } | |
| 93 | return null; | |
| 94 | } | |
| 95 | ||
| 96 | /** | |
| 97 | * Get the property of inside the expression | |
| 98 | * | |
| 99 | * @param expression | |
| 100 | * the property expression | |
| 101 | * @return the property key | |
| 102 | */ | |
| 103 | public static String getPropertyKey(String expression) { | |
| 104 |
2
1. getPropertyKey : Replaced integer subtraction with addition → RUN_ERROR 2. getPropertyKey : replaced return value with "" for fr/sii/ogham/core/util/BuilderUtils::getPropertyKey → RUN_ERROR |
return expression.substring(2, expression.length() - 1); |
| 105 | } | |
| 106 | ||
| 107 | /** | |
| 108 | * Indicates if the property is the form of an expression | |
| 109 | * ("${property.key}") or not. | |
| 110 | * | |
| 111 | * @param property | |
| 112 | * the property that may be an expression | |
| 113 | * @return true if it is an expression, false otherwise | |
| 114 | */ | |
| 115 | public static boolean isExpression(String property) { | |
| 116 |
4
1. isExpression : negated conditional → RUN_ERROR 2. isExpression : negated conditional → RUN_ERROR 3. isExpression : replaced boolean return with true for fr/sii/ogham/core/util/BuilderUtils::isExpression → RUN_ERROR 4. isExpression : negated conditional → RUN_ERROR |
return property != null && property.startsWith("${") && property.endsWith("}"); |
| 117 | } | |
| 118 | ||
| 119 | /** | |
| 120 | * Change the converter used by ByulderUtils | |
| 121 | * | |
| 122 | * @param converter | |
| 123 | * the new converter | |
| 124 | */ | |
| 125 | public static void setConverter(Converter converter) { | |
| 126 | BuilderUtils.converter = converter; | |
| 127 | } | |
| 128 | ||
| 129 | // @formatter:off | |
| 130 | /** | |
| 131 | * Utility method used to dynamically instantiate a builder instance. | |
| 132 | * | |
| 133 | * <p> | |
| 134 | * If you want fluent chaining, your builder class <strong>MUST</strong> | |
| 135 | * declare parent of type {@code P} as first parameter. The builder can | |
| 136 | * implement {@link Parent} or even extend {@link AbstractParent}. For | |
| 137 | * example, if builder is a child of {@link EmailBuilder}: | |
| 138 | * | |
| 139 | * <pre> | |
| 140 | * {@code | |
| 141 | * class MyBuilder extends AbstractParent<EmailBuilder> implements Builder<Foo> { | |
| 142 | * public MyBuilder(EmailBuilder parent) { | |
| 143 | * super(parent); | |
| 144 | * } | |
| 145 | * } | |
| 146 | * }</pre> | |
| 147 | * | |
| 148 | * | |
| 149 | * <p> | |
| 150 | * You may need {@link BuildContext} in order to be able to evaluate | |
| 151 | * properties in your {@link Builder#build()} method. Just declare a | |
| 152 | * parameter of type {@link BuildContext} either as first parameter if | |
| 153 | * you don't want fluent chaining: | |
| 154 | * | |
| 155 | * <pre> | |
| 156 | * {@code | |
| 157 | * class MyBuilder implements Builder<Foo> { | |
| 158 | * public MyBuilder(BuildContext buildContext) { | |
| 159 | * this.buildContext = buildContext; | |
| 160 | * } | |
| 161 | * } | |
| 162 | * }</pre> | |
| 163 | * | |
| 164 | * or as second parameter if you want fluent chaining: | |
| 165 | * | |
| 166 | * <pre> | |
| 167 | * {@code | |
| 168 | * class MyBuilder extends AbstractParent<EmailBuilder> implements Builder<Foo> { | |
| 169 | * public MyBuilder(EmailBuilder parent, BuildContext buildContext) { | |
| 170 | * super(parent); | |
| 171 | * this.buildContext = buildContext; | |
| 172 | * } | |
| 173 | * } | |
| 174 | * }</pre> | |
| 175 | * | |
| 176 | * | |
| 177 | * <p> | |
| 178 | * If you need none of these features, you still have to provide a public | |
| 179 | * default constructor. | |
| 180 | * | |
| 181 | * <p> | |
| 182 | * If several constructors exist, the following order is used (first | |
| 183 | * matching constructor is used): | |
| 184 | * <ul> | |
| 185 | * <li>{@code contructor(P parent, BuildContext buildContext)}</li> | |
| 186 | * <li>{@code contructor(P parent}</li> | |
| 187 | * <li>{@code contructor(BuildContext buildContext)}</li> | |
| 188 | * <li>{@code contructor(}</li> | |
| 189 | * </ul> | |
| 190 | * | |
| 191 | * @param <T> | |
| 192 | * The type of the built object | |
| 193 | * @param <B> | |
| 194 | * The type of the builder that builds T | |
| 195 | * @param <P> | |
| 196 | * The type of the parent builder (used for fluent chaining) | |
| 197 | * @param builderClass | |
| 198 | * The builder class to instantiate | |
| 199 | * @param parent | |
| 200 | * The parent builder for fluent chaining | |
| 201 | * @param buildContext | |
| 202 | * The current build context | |
| 203 | * @return the builder instance | |
| 204 | * @throws BuildException | |
| 205 | * when builder can't be instantiated | |
| 206 | */ | |
| 207 | // @formatter:on | |
| 208 | @SuppressWarnings("squid:RedundantThrowsDeclarationCheck") | |
| 209 | public static <T, B extends Builder<? extends T>, P> B instantiateBuilder(Class<B> builderClass, P parent, BuildContext buildContext) throws BuildException { | |
| 210 | try { | |
| 211 |
1
1. instantiateBuilder : replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::instantiateBuilder → RUN_ERROR |
return instantiate(builderClass, parent, buildContext); |
| 212 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException | SecurityException | IllegalArgumentException e) { | |
| 213 | throw new BuildException("Can't instantiate builder from class " + builderClass.getSimpleName(), e); | |
| 214 | } | |
| 215 | } | |
| 216 | ||
| 217 | /** | |
| 218 | * Build the instance using the provided builder. | |
| 219 | * | |
| 220 | * <p> | |
| 221 | * If builder is {@code null}, it returns {@code null}. | |
| 222 | * | |
| 223 | * <p> | |
| 224 | * If builder is not {@code null}, the value of {@link Builder#build()} is | |
| 225 | * used. The returned value may be {@code null}. | |
| 226 | * | |
| 227 | * @param <T> | |
| 228 | * the type of the built instance | |
| 229 | * @param builder | |
| 230 | * the builder | |
| 231 | * @return the built instance or null if builder is null or if it returns | |
| 232 | * null | |
| 233 | */ | |
| 234 | public static <T> T build(Builder<T> builder) { | |
| 235 |
1
1. build : negated conditional → RUN_ERROR |
if (builder == null) { |
| 236 | return null; | |
| 237 | } | |
| 238 |
1
1. build : replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::build → RUN_ERROR |
return builder.build(); |
| 239 | } | |
| 240 | ||
| 241 | private static <T, B extends Builder<? extends T>, P> B instantiate(Class<B> builderClass, P parent, BuildContext buildContext) | |
| 242 | throws InstantiationException, IllegalAccessException, InvocationTargetException { | |
| 243 | try { | |
| 244 |
1
1. instantiate : replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::instantiate → RUN_ERROR |
return builderClass.getConstructor(parent.getClass(), BuildContext.class).newInstance(parent, buildContext); |
| 245 | } catch (NoSuchMethodException e) { | |
| 246 | // skip | |
| 247 | } | |
| 248 | try { | |
| 249 |
1
1. instantiate : replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::instantiate → NO_COVERAGE |
return builderClass.getConstructor(parent.getClass()).newInstance(parent); |
| 250 | } catch (NoSuchMethodException e) { | |
| 251 | // skip | |
| 252 | } | |
| 253 | try { | |
| 254 |
1
1. instantiate : replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::instantiate → NO_COVERAGE |
return builderClass.getConstructor(BuildContext.class).newInstance(buildContext); |
| 255 | } catch (NoSuchMethodException e) { | |
| 256 | // skip | |
| 257 | } | |
| 258 | try { | |
| 259 |
1
1. instantiate : replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::instantiate → RUN_ERROR |
return builderClass.getConstructor().newInstance(); |
| 260 | } catch (NoSuchMethodException e) { | |
| 261 | // skip | |
| 262 | } | |
| 263 | StringJoiner joiner = new StringJoiner("\n- ", "\n- ", "\n"); | |
| 264 | joiner.add("constructor(" + parent.getClass().getName() + ", " + BuildContext.class.getName() + ")\n if you want fluent chaining and inherit current build context"); | |
| 265 | joiner.add("constructor(" + parent.getClass().getName() + ")\n if you want fluent chaining"); | |
| 266 | joiner.add("constructor(" + BuildContext.class.getName() + ")\n if you don't want fluent chaining but inherit current build context"); | |
| 267 | joiner.add("constructor()\n if you don't want fluent chaining and inherit current build context"); | |
| 268 | throw new BuildException("No matching constructor found. The builder implementation must provide one of following constructors:" + joiner.toString()); | |
| 269 | } | |
| 270 | ||
| 271 | private static Converter getConverter() { | |
| 272 |
1
1. getConverter : negated conditional → NO_COVERAGE |
if (converter == null) { |
| 273 | converter = new DefaultConverter(); | |
| 274 | } | |
| 275 |
1
1. getConverter : replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::getConverter → NO_COVERAGE |
return converter; |
| 276 | } | |
| 277 | ||
| 278 | private BuilderUtils() { | |
| 279 | super(); | |
| 280 | } | |
| 281 | ||
| 282 | } | |
Mutations | ||
| 36 |
1.1 |
|
| 56 |
1.1 |
|
| 57 |
1.1 |
|
| 59 |
1.1 |
|
| 84 |
1.1 |
|
| 89 |
1.1 |
|
| 90 |
1.1 |
|
| 104 |
1.1 2.2 |
|
| 116 |
1.1 2.2 3.3 4.4 |
|
| 211 |
1.1 |
|
| 235 |
1.1 |
|
| 238 |
1.1 |
|
| 244 |
1.1 |
|
| 249 |
1.1 |
|
| 254 |
1.1 |
|
| 259 |
1.1 |
|
| 272 |
1.1 |
|
| 275 |
1.1 |