MessageSplitterBuilder.java

1
package fr.sii.ogham.sms.builder.cloudhopper;
2
3
import static com.cloudhopper.commons.charset.CharsetUtil.NAME_GSM7;
4
import static com.cloudhopper.commons.charset.CharsetUtil.NAME_GSM8;
5
import static com.cloudhopper.commons.charset.CharsetUtil.NAME_ISO_8859_1;
6
import static com.cloudhopper.commons.charset.CharsetUtil.NAME_UCS_2;
7
import static fr.sii.ogham.sms.SmsConstants.SmppSplitConstants.SEGMENT_SIZE_GSM_7BIT_SMS_PACKING_MODE;
8
import static fr.sii.ogham.sms.SmsConstants.SmppSplitConstants.SEGMENT_SIZE_GSM_8BIT;
9
import static fr.sii.ogham.sms.SmsConstants.SmppSplitConstants.SEGMENT_SIZE_UCS2;
10
11
import java.util.Random;
12
13
import fr.sii.ogham.core.builder.Builder;
14
import fr.sii.ogham.core.builder.configuration.ConfigurationValueBuilder;
15
import fr.sii.ogham.core.builder.configuration.ConfigurationValueBuilderHelper;
16
import fr.sii.ogham.core.builder.configurer.Configurer;
17
import fr.sii.ogham.core.builder.context.BuildContext;
18
import fr.sii.ogham.core.builder.env.EnvironmentBuilder;
19
import fr.sii.ogham.core.exception.builder.BuildException;
20
import fr.sii.ogham.core.fluent.AbstractParent;
21
import fr.sii.ogham.core.util.PriorizedList;
22
import fr.sii.ogham.sms.SmsConstants.SmppSplitConstants.SegmentSizes;
23
import fr.sii.ogham.sms.encoder.Encoder;
24
import fr.sii.ogham.sms.encoder.SupportingEncoder;
25
import fr.sii.ogham.sms.sender.impl.cloudhopper.encoder.CloudhopperCharsetSupportingEncoder;
26
import fr.sii.ogham.sms.sender.impl.cloudhopper.encoder.NamedCharset;
27
import fr.sii.ogham.sms.sender.impl.cloudhopper.splitter.SupportedEncoderConditionalSplitter;
28
import fr.sii.ogham.sms.splitter.FirstSupportingMessageSplitter;
29
import fr.sii.ogham.sms.splitter.GsmMessageSplitter;
30
import fr.sii.ogham.sms.splitter.MessageSplitter;
31
import fr.sii.ogham.sms.splitter.RandomReferenceNumberGenerator;
32
import fr.sii.ogham.sms.splitter.ReferenceNumberGenerator;
33
import fr.sii.ogham.sms.splitter.SupportingSplitter;
34
35
/**
36
 * Configures how Cloudhopper will split messages.
37
 * 
38
 * <p>
39
 * The splitter will check if the whole message can fit in a single segment. If
40
 * not the splitter will split the whole message in several segments with a
41
 * header to indicate splitting information such as number of segments,
42
 * reference number and current segment number.
43
 * 
44
 * <p>
45
 * {@link Encoder} configured using {@link CloudhopperBuilder#encoder()} is used
46
 * to encode each segment.
47
 * 
48
 * <p>
49
 * If automatic guessing of best standard encoder is enabled for {@link Encoder}
50
 * (using {@code encoder().autoGuess(true)}), and message splitting is enabled,
51
 * then standard message splitting is configured such as:
52
 * <ul>
53
 * <li>If GSM 7-bit encoder is enabled, {@link GsmMessageSplitter} is used to
54
 * split messages that support this encoding. If whole message can fit in a
55
 * single segment of 160 characters. Longer message is split into segments of
56
 * either 153 characters or 152 characters (depending on reference number
57
 * generation, see {@link ReferenceNumberGenerator})</li>
58
 * <li>If GSM 8-bit encoder is enabled, {@link GsmMessageSplitter} is used to
59
 * split messages that support this encoding. If whole message can fit in a
60
 * single segment of 140 characters. Longer message is split into segments of
61
 * either 134 characters or 133 characters (depending on reference number
62
 * generation, see {@link ReferenceNumberGenerator})</li>
63
 * <li>If UCS-2 encoder is enabled, {@link GsmMessageSplitter} is used to split
64
 * messages that support this encoding. If whole message can fit in a single
65
 * segment of 70 characters. Longer message is split into segments of either 67
66
 * characters or 66 characters (depending on reference number generation, see
67
 * {@link ReferenceNumberGenerator})</li>
68
 * </ul>
69
 * 
70
 * Each registered splitter uses the same priority as associated
71
 * {@link Encoder}.
72
 * 
73
 * If you don't want standard message splitting based on supported
74
 * {@link Encoder}s, you can either disable message splitting or provide a
75
 * custom splitter with higher priority.
76
 * 
77
 * <p>
78
 * This builder allows to configure:
79
 * <ul>
80
 * <li>Enable/disable message splitting</li>
81
 * <li>Provide a custom split strategy</li>
82
 * <li>Choose strategy for reference number generation</li>
83
 * </ul>
84
 * 
85
 * <pre>
86
 * {@code
87
 * .splitter()
88
 *   .enable()
89
 *     .properties("${ogham.sms.cloudhopper.split.enable}", "${ogham.sms.split.enable}")
90
 *     .devaultValue(true)
91
 *     .and()
92
 *   .customSplitter(new MyCustomSplitter(), 100000)
93
 *   .referenceNumber()
94
 *     .random()
95
 *     .random(new Random())
96
 *     .generator(new MyCustomReferenceNumberGenerator())
97
 * }
98
 * </pre>
99
 * 
100
 * @author Aurélien Baudet
101
 *
102
 */
103
public class MessageSplitterBuilder extends AbstractParent<CloudhopperBuilder> implements Builder<MessageSplitter> {
104
	private final BuildContext buildContext;
105
	private final ReadableEncoderBuilder encoderBuilder;
106
	private final ConfigurationValueBuilderHelper<MessageSplitterBuilder, Boolean> enableValueBuilder;
107
	private final PriorizedList<MessageSplitter> customSplitters;
108
	private MessageSplitter customSplitter;
109
	private ReferenceNumberGeneratorBuilder referenceNumberBuilder;
110
111
	/**
112
	 * Initializes the builder with a parent builder. The parent builder is used
113
	 * when calling {@link #and()} method. The {@link EnvironmentBuilder} is
114
	 * used to evaluate properties when {@link #build()} method is called.
115
	 * 
116
	 * @param parent
117
	 *            the parent builder
118
	 * @param buildContext
119
	 *            for registering instances and property evaluation
120
	 * @param encoderBuilder
121
	 *            the encoder builder that is used to configure standard message
122
	 *            splitting based on encoding charset
123
	 */
124
	public MessageSplitterBuilder(CloudhopperBuilder parent, BuildContext buildContext, ReadableEncoderBuilder encoderBuilder) {
125
		super(parent);
126
		this.buildContext = buildContext;
127
		this.encoderBuilder = encoderBuilder;
128
		enableValueBuilder = buildContext.newConfigurationValueBuilder(this, Boolean.class);
129
		customSplitters = new PriorizedList<>();
130
	}
131
132
	/**
133
	 * Enable/disable message splitting.
134
	 * 
135
	 * <p>
136
	 * The value set using this method takes precedence over any property and
137
	 * default value configured using {@link #enable()}.
138
	 * 
139
	 * <pre>
140
	 * .enable(false)
141
	 * .enable()
142
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
143
	 *   .defaultValue(true)
144
	 * </pre>
145
	 * 
146
	 * <pre>
147
	 * .enable(false)
148
	 * .enable()
149
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
150
	 *   .defaultValue(true)
151
	 * </pre>
152
	 * 
153
	 * In both cases, {@code enable(false)} is used.
154
	 * 
155
	 * <p>
156
	 * If this method is called several times, only the last value is used.
157
	 * 
158
	 * <p>
159
	 * If {@code null} value is set, it is like not setting a value at all. The
160
	 * property/default value configuration is applied.
161
	 * 
162
	 * @param enable
163
	 *            enable or disable message splitting
164
	 * @return this instance for fluent chaining
165
	 */
166
	public MessageSplitterBuilder enable(Boolean enable) {
167 1 1. enable : removed call to fr/sii/ogham/core/builder/configuration/ConfigurationValueBuilderHelper::setValue → NO_COVERAGE
		enableValueBuilder.setValue(enable);
168 1 1. enable : replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::enable → NO_COVERAGE
		return this;
169
	}
170
171
	/**
172
	 * Enable/disable message splitting.
173
	 * 
174
	 * <p>
175
	 * This method is mainly used by {@link Configurer}s to register some
176
	 * property keys and/or a default value. The aim is to let developer be able
177
	 * to externalize its configuration (using system properties, configuration
178
	 * file or anything else). If the developer doesn't configure any value for
179
	 * the registered properties, the default value is used (if set).
180
	 * 
181
	 * <pre>
182
	 * .enable()
183
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
184
	 *   .defaultValue(true)
185
	 * </pre>
186
	 * 
187
	 * <p>
188
	 * Non-null value set using {@link #enable(Boolean)} takes precedence over
189
	 * property values and default value.
190
	 * 
191
	 * <pre>
192
	 * .enable(false)
193
	 * .enable()
194
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
195
	 *   .defaultValue(true)
196
	 * </pre>
197
	 * 
198
	 * The value {@code false} is used regardless of the value of the properties
199
	 * and default value.
200
	 * 
201
	 * <p>
202
	 * See {@link ConfigurationValueBuilder} for more information.
203
	 * 
204
	 * 
205
	 * @return the builder to configure property keys/default value
206
	 */
207
	public ConfigurationValueBuilder<MessageSplitterBuilder, Boolean> enable() {
208 1 1. enable : replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::enable → RUN_ERROR
		return enableValueBuilder;
209
	}
210
211
	/**
212
	 * Configures how Cloudhopper should generate a reference number.
213
	 * 
214
	 * <p>
215
	 * Reference number is used to identify segments that belong to the same
216
	 * message. Every segment of the split message must have the same reference
217
	 * number.
218
	 * 
219
	 * <p>
220
	 * This builder allows to configure:
221
	 * <ul>
222
	 * <li>Enable random generation strategy</li>
223
	 * <li>Customize random generation by providing a custom {@link Random}</li>
224
	 * <li>Provide a custom generator</li>
225
	 * </ul>
226
	 * 
227
	 * <pre>
228
	 * {@code
229
	 *   .referenceNumber()
230
	 *     .random()
231
	 *     .random(new Random())
232
	 *     .generator(new MyCustomReferenceNumberGenerator())
233
	 * }
234
	 * </pre>
235
	 * 
236
	 * 
237
	 * @return the builder to configure reference number generation
238
	 * @see ReferenceNumberGenerator
239
	 */
240
	public ReferenceNumberGeneratorBuilder referenceNumber() {
241 1 1. referenceNumber : negated conditional → RUN_ERROR
		if (referenceNumberBuilder == null) {
242
			referenceNumberBuilder = new ReferenceNumberGeneratorBuilder(this, buildContext);
243
		}
244 1 1. referenceNumber : replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::referenceNumber → RUN_ERROR
		return referenceNumberBuilder;
245
	}
246
247
	/**
248
	 * Register a custom splitter strategy.
249
	 * 
250
	 * <p>
251
	 * Using this method totally disable all other features. Only the provided
252
	 * splitter is used.
253
	 * 
254
	 * <p>
255
	 * If this method is called several times, only the last one is used.
256
	 * 
257
	 * <p>
258
	 * If {@code null} value is provided, then custom splitting is disabled.
259
	 * 
260
	 * @param splitter
261
	 *            the splitter to use
262
	 * @return this instance for fluent chaining
263
	 */
264
	public MessageSplitterBuilder customSplitter(MessageSplitter splitter) {
265
		this.customSplitter = splitter;
266 1 1. customSplitter : replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::customSplitter → NO_COVERAGE
		return this;
267
	}
268
269
	/**
270
	 * Register a custom splitter strategy in the chain of splitters.
271
	 * 
272
	 * <p>
273
	 * It is possible to register several custom splitters.
274
	 * 
275
	 * <p>
276
	 * The priority is used to indicate in which order the custom splitter must
277
	 * be applied in the chain.
278
	 * 
279
	 * <p>
280
	 * If the custom splitter implements {@link SupportingSplitter}, then the
281
	 * splitter can indicate if it is able to handle to message to split. If the
282
	 * splitter can't handle the message, then the next splitter is tried.
283
	 * 
284
	 * <p>
285
	 * If the custom splitter doesn't implement {@link SupportingSplitter}, then
286
	 * the splitter is considered as able to handle the message. Splitting is
287
	 * used with this splitter. So if such splitter is registered with a higher
288
	 * priority than others, no other splitter will be tried.
289
	 * 
290
	 * @param splitter
291
	 *            the splitter to register
292
	 * @param priority
293
	 *            the associated priority (greater value means higher priority)
294
	 * @return this instance for fluent chaining
295
	 */
296
	public MessageSplitterBuilder customSplitter(MessageSplitter splitter, int priority) {
297
		customSplitters.register(splitter, priority);
298 1 1. customSplitter : replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::customSplitter → NO_COVERAGE
		return this;
299
	}
300
301
	@Override
302
	public MessageSplitter build() {
303 1 1. build : negated conditional → RUN_ERROR
		if (customSplitter != null) {
304 1 1. build : replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::build → NO_COVERAGE
			return customSplitter;
305
		}
306 1 1. build : negated conditional → RUN_ERROR
		if (!splittingEnabled()) {
307
			return null;
308
		}
309 1 1. build : negated conditional → RUN_ERROR
		if (encoderBuilder.autoGuessEnabled()) {
310 1 1. build : replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::build → RUN_ERROR
			return buildAutoGuessSplitter();
311
		}
312 1 1. build : negated conditional → NO_COVERAGE
		if (!customSplitters.isEmpty()) {
313 1 1. build : replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::build → NO_COVERAGE
			return buildContext.register(new FirstSupportingMessageSplitter(customSplitters.getOrdered()));
314
		}
315
		throw new BuildException("Split of SMS is enabled but no splitter is configured");
316
	}
317
318
	private boolean splittingEnabled() {
319 2 1. splittingEnabled : replaced boolean return with false for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::splittingEnabled → RUN_ERROR
2. splittingEnabled : replaced boolean return with true for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::splittingEnabled → RUN_ERROR
		return enableValueBuilder.getValue(false);
320
	}
321
322
	private MessageSplitter buildAutoGuessSplitter() {
323
		PriorizedList<MessageSplitter> registry = new PriorizedList<>();
324 1 1. buildAutoGuessSplitter : removed call to fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::registerStandardSplitter → RUN_ERROR
		registerStandardSplitter(encoderBuilder.getGsm7Priorities(), NAME_GSM7, SEGMENT_SIZE_GSM_7BIT_SMS_PACKING_MODE, registry);
325 1 1. buildAutoGuessSplitter : removed call to fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::registerStandardSplitter → RUN_ERROR
		registerStandardSplitter(encoderBuilder.getGsm8Priorities(), NAME_GSM8, SEGMENT_SIZE_GSM_8BIT, registry);
326 1 1. buildAutoGuessSplitter : removed call to fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::registerStandardSplitter → RUN_ERROR
		registerStandardSplitter(encoderBuilder.getLatin1Priorities(), NAME_ISO_8859_1, SEGMENT_SIZE_GSM_8BIT, registry);
327 1 1. buildAutoGuessSplitter : removed call to fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::registerStandardSplitter → RUN_ERROR
		registerStandardSplitter(encoderBuilder.getUcs2Priorities(), NAME_UCS_2, SEGMENT_SIZE_UCS2, registry);
328
		registry.register(customSplitters);
329 1 1. buildAutoGuessSplitter : replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::buildAutoGuessSplitter → RUN_ERROR
		return buildContext.register(new FirstSupportingMessageSplitter(registry.getOrdered()));
330
	}
331
332
	private void registerStandardSplitter(StandardEncodingHelper priorities, String supportedCharsetName, SegmentSizes maxSizes, PriorizedList<MessageSplitter> registry) {
333
		Integer priority = priorities.getValue();
334 3 1. registerStandardSplitter : changed conditional boundary → RUN_ERROR
2. registerStandardSplitter : negated conditional → RUN_ERROR
3. registerStandardSplitter : negated conditional → RUN_ERROR
		if (priority == null || priority <= 0) {
335
			return;
336
		}
337
		registry.register(buildStandardSplitter(supportedCharsetName, maxSizes), priority);
338
	}
339
340
	private MessageSplitter buildStandardSplitter(String supportingCharset, SegmentSizes maxSizes) {
341
		SupportingEncoder encoder = buildContext.register(new CloudhopperCharsetSupportingEncoder(NamedCharset.from(supportingCharset)));
342 1 1. buildStandardSplitter : replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::buildStandardSplitter → RUN_ERROR
		return buildContext.register(new SupportedEncoderConditionalSplitter(encoder, buildContext.register(new GsmMessageSplitter(encoder, maxSizes, buildReferenceNumberGenerator()))));
343
	}
344
345
	private ReferenceNumberGenerator buildReferenceNumberGenerator() {
346 1 1. buildReferenceNumberGenerator : negated conditional → RUN_ERROR
		if (referenceNumberBuilder != null) {
347 1 1. buildReferenceNumberGenerator : replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::buildReferenceNumberGenerator → RUN_ERROR
			return referenceNumberBuilder.build();
348
		}
349 1 1. buildReferenceNumberGenerator : replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::buildReferenceNumberGenerator → NO_COVERAGE
		return buildContext.register(new RandomReferenceNumberGenerator());
350
	}
351
352
}

Mutations

167

1.1
Location : enable
Killed by :
removed call to fr/sii/ogham/core/builder/configuration/ConfigurationValueBuilderHelper::setValue → NO_COVERAGE

168

1.1
Location : enable
Killed by :
replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::enable → NO_COVERAGE

208

1.1
Location : enable
Killed by :
replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::enable → RUN_ERROR

241

1.1
Location : referenceNumber
Killed by :
negated conditional → RUN_ERROR

244

1.1
Location : referenceNumber
Killed by :
replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::referenceNumber → RUN_ERROR

266

1.1
Location : customSplitter
Killed by :
replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::customSplitter → NO_COVERAGE

298

1.1
Location : customSplitter
Killed by :
replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::customSplitter → NO_COVERAGE

303

1.1
Location : build
Killed by :
negated conditional → RUN_ERROR

304

1.1
Location : build
Killed by :
replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::build → NO_COVERAGE

306

1.1
Location : build
Killed by :
negated conditional → RUN_ERROR

309

1.1
Location : build
Killed by :
negated conditional → RUN_ERROR

310

1.1
Location : build
Killed by :
replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::build → RUN_ERROR

312

1.1
Location : build
Killed by :
negated conditional → NO_COVERAGE

313

1.1
Location : build
Killed by :
replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::build → NO_COVERAGE

319

1.1
Location : splittingEnabled
Killed by :
replaced boolean return with false for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::splittingEnabled → RUN_ERROR

2.2
Location : splittingEnabled
Killed by :
replaced boolean return with true for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::splittingEnabled → RUN_ERROR

324

1.1
Location : buildAutoGuessSplitter
Killed by :
removed call to fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::registerStandardSplitter → RUN_ERROR

325

1.1
Location : buildAutoGuessSplitter
Killed by :
removed call to fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::registerStandardSplitter → RUN_ERROR

326

1.1
Location : buildAutoGuessSplitter
Killed by :
removed call to fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::registerStandardSplitter → RUN_ERROR

327

1.1
Location : buildAutoGuessSplitter
Killed by :
removed call to fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::registerStandardSplitter → RUN_ERROR

329

1.1
Location : buildAutoGuessSplitter
Killed by :
replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::buildAutoGuessSplitter → RUN_ERROR

334

1.1
Location : registerStandardSplitter
Killed by :
changed conditional boundary → RUN_ERROR

2.2
Location : registerStandardSplitter
Killed by :
negated conditional → RUN_ERROR

3.3
Location : registerStandardSplitter
Killed by :
negated conditional → RUN_ERROR

342

1.1
Location : buildStandardSplitter
Killed by :
replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::buildStandardSplitter → RUN_ERROR

346

1.1
Location : buildReferenceNumberGenerator
Killed by :
negated conditional → RUN_ERROR

347

1.1
Location : buildReferenceNumberGenerator
Killed by :
replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::buildReferenceNumberGenerator → RUN_ERROR

349

1.1
Location : buildReferenceNumberGenerator
Killed by :
replaced return value with null for fr/sii/ogham/sms/builder/cloudhopper/MessageSplitterBuilder::buildReferenceNumberGenerator → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.13.1