BuilderUtils.java

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
Location : getDefaultProperties
Killed by :
replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::getDefaultProperties → RUN_ERROR

56

1.1
Location : evaluate
Killed by :
negated conditional → RUN_ERROR

57

1.1
Location : evaluate
Killed by :
replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::evaluate → RUN_ERROR

59

1.1
Location : evaluate
Killed by :
replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::evaluate → NO_COVERAGE

84

1.1
Location : evaluate
Killed by :
negated conditional → RUN_ERROR

89

1.1
Location : evaluate
Killed by :
negated conditional → RUN_ERROR

90

1.1
Location : evaluate
Killed by :
replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::evaluate → RUN_ERROR

104

1.1
Location : getPropertyKey
Killed by :
Replaced integer subtraction with addition → RUN_ERROR

2.2
Location : getPropertyKey
Killed by :
replaced return value with "" for fr/sii/ogham/core/util/BuilderUtils::getPropertyKey → RUN_ERROR

116

1.1
Location : isExpression
Killed by :
negated conditional → RUN_ERROR

2.2
Location : isExpression
Killed by :
negated conditional → RUN_ERROR

3.3
Location : isExpression
Killed by :
replaced boolean return with true for fr/sii/ogham/core/util/BuilderUtils::isExpression → RUN_ERROR

4.4
Location : isExpression
Killed by :
negated conditional → RUN_ERROR

211

1.1
Location : instantiateBuilder
Killed by :
replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::instantiateBuilder → RUN_ERROR

235

1.1
Location : build
Killed by :
negated conditional → RUN_ERROR

238

1.1
Location : build
Killed by :
replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::build → RUN_ERROR

244

1.1
Location : instantiate
Killed by :
replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::instantiate → RUN_ERROR

249

1.1
Location : instantiate
Killed by :
replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::instantiate → NO_COVERAGE

254

1.1
Location : instantiate
Killed by :
replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::instantiate → NO_COVERAGE

259

1.1
Location : instantiate
Killed by :
replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::instantiate → RUN_ERROR

272

1.1
Location : getConverter
Killed by :
negated conditional → NO_COVERAGE

275

1.1
Location : getConverter
Killed by :
replaced return value with null for fr/sii/ogham/core/util/BuilderUtils::getConverter → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.13.1