DataCodingSchemeBuilder.java

package fr.sii.ogham.sms.builder.cloudhopper;

import java.util.function.Supplier;

import com.cloudhopper.commons.charset.Charset;
import com.cloudhopper.commons.charset.CharsetUtil;
import com.cloudhopper.commons.gsm.DataCoding;
import com.cloudhopper.smpp.SmppConstants;

import fr.sii.ogham.core.builder.Builder;
import fr.sii.ogham.core.builder.configuration.ConfigurationValueBuilder;
import fr.sii.ogham.core.builder.configuration.ConfigurationValueBuilderHelper;
import fr.sii.ogham.core.builder.configurer.Configurer;
import fr.sii.ogham.core.builder.context.BuildContext;
import fr.sii.ogham.core.builder.env.EnvironmentBuilder;
import fr.sii.ogham.core.fluent.AbstractParent;
import fr.sii.ogham.sms.sender.impl.cloudhopper.preparator.CharsetMapToCharacterEncodingGroupDataCodingProvider;
import fr.sii.ogham.sms.sender.impl.cloudhopper.preparator.CharsetMapToGeneralGroupDataCodingProvider;
import fr.sii.ogham.sms.sender.impl.cloudhopper.preparator.DataCodingProvider;
import fr.sii.ogham.sms.sender.impl.cloudhopper.preparator.FirstSupportingDataCodingProvider;
import fr.sii.ogham.sms.sender.impl.cloudhopper.preparator.FixedByteValueDataCodingProvider;

/**
 * Data Coding Scheme is a one-octet field in Short Messages (SM) and Cell
 * Broadcast Messages (CB) which carries a basic information how the recipient
 * handset should process the received message. The information includes:
 * <ul>
 * <li>the character set or message coding which determines the encoding of the
 * message user data</li>
 * <li>the message class which determines to which component of the Mobile
 * Station (MS) or User Equipment (UE) should be the message delivered</li>
 * <li>the request to automatically delete the message after reading</li>
 * <li>the state of flags indicating presence of unread voicemail, fax, e-mail
 * or other messages</li>
 * <li>the indication that the message content is compressed</li>
 * <li>the language of the cell broadcast message</li>
 * </ul>
 * The field is described in 3GPP 23.040 and 3GPP 23.038 under the name TP-DCS
 * (see <a href=
 * "https://en.wikipedia.org/wiki/Data_Coding_Scheme#SMS_Data_Coding_Scheme">SMS
 * Data Coding Scheme</a>).
 * 
 * <p>
 * Configures how Cloudhopper determines the Data Coding Scheme to use:
 * <ul>
 * <li>Automatic mode:
 * <ul>
 * <li>If SMPP v3.3 is used then
 * {@link CharsetMapToGeneralGroupDataCodingProvider} generates a
 * {@link DataCoding} with
 * {@link DataCoding#createGeneralGroup(byte, Byte, boolean)}</li>
 * <li>If SMPP v3.4+ is used then
 * {@link CharsetMapToCharacterEncodingGroupDataCodingProvider} generates a
 * {@link DataCoding} with
 * {@link DataCoding#createCharacterEncodingGroup(byte)}</li>
 * </ul>
 * </li>
 * <li>Allow registration of custom {@link DataCodingProvider}</li>
 * </ul>
 * 
 * @author Aurélien Baudet
 */
public class DataCodingSchemeBuilder extends AbstractParent<CloudhopperBuilder> implements Builder<DataCodingProvider> {
	private final BuildContext buildContext;
	private final Supplier<Byte> interfaceVersionProvider;
	private final ConfigurationValueBuilderHelper<DataCodingSchemeBuilder, Boolean> autoValueBuilder;
	private final ConfigurationValueBuilderHelper<DataCodingSchemeBuilder, Byte> dcsValueBuilder;
	private DataCodingProvider custom;

	/**
	 * Initializes the builder with a parent builder. The parent builder is used
	 * when calling {@link #and()} method. The {@link EnvironmentBuilder} is
	 * used to evaluate properties when {@link #build()} method is called.
	 * 
	 * @param parent
	 *            the parent builder
	 * @param buildContext
	 *            for registering instances and property evaluation
	 * @param interfaceVersionProvider
	 *            A function used to retrieve the value of the interface
	 *            version. This is needed when {@link #auto(Boolean)} mode is
	 *            enabled.
	 */
	public DataCodingSchemeBuilder(CloudhopperBuilder parent, BuildContext buildContext, Supplier<Byte> interfaceVersionProvider) {
		super(parent);
		this.buildContext = buildContext;
		this.interfaceVersionProvider = interfaceVersionProvider;
		this.autoValueBuilder = buildContext.newConfigurationValueBuilder(this, Boolean.class);
		this.dcsValueBuilder = buildContext.newConfigurationValueBuilder(this, Byte.class);
	}

	/**
	 * * Enable/disable automatic mode based on SMPP interface version.
	 * 
	 * <p>
	 * {@link DataCodingProvider} implementation is selected based on SMPP
	 * interface version. SMPP v3.3 Data Coding Scheme values are defined in
	 * <a href=
	 * "https://en.wikipedia.org/wiki/Data_Coding_Scheme#SMS_Data_Coding_Scheme">SMS
	 * Data Coding Scheme</a>. SMPP 3.4 introduced a new list of data_coding
	 * values (<a href=
	 * "https://en.wikipedia.org/wiki/Short_Message_Peer-to-Peer#PDU_body">PDU
	 * body</a>).
	 * </p>
	 * 
	 * <strong>SMPP v3.3</strong>
	 * <p>
	 * The text message is encoded using {@link Charset}. According to that
	 * charset, the Data Coding Scheme is determined using the <strong>General
	 * Data Coding group</strong> table. Therefore, a simple mapping is applied:
	 * <ul>
	 * <li>{@link CharsetUtil#NAME_GSM7} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_DEFAULT}</li>
	 * <li>{@link CharsetUtil#NAME_PACKED_GSM} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_DEFAULT}</li>
	 * <li>{@link CharsetUtil#NAME_GSM} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_8BIT}</li>
	 * <li>{@link CharsetUtil#NAME_GSM8} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_8BIT}</li>
	 * <li>{@link CharsetUtil#NAME_UCS_2} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_UCS2}</li>
	 * </ul>
	 * 
	 * 
	 * <strong>SMPP v3.4+</strong>
	 * <p>
	 * The text message is encoded using {@link Charset}. According to that
	 * charset, the Data Coding Scheme is determined using only the
	 * <strong>Alphabet</strong> table. Therefore, a simple mapping is applied:
	 * <ul>
	 * <li>{@link CharsetUtil#NAME_GSM7} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_DEFAULT}</li>
	 * <li>{@link CharsetUtil#NAME_PACKED_GSM} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_DEFAULT}</li>
	 * <li>{@link CharsetUtil#NAME_GSM} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_8BIT}</li>
	 * <li>{@link CharsetUtil#NAME_GSM8} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_8BIT}</li>
	 * <li>{@link CharsetUtil#NAME_ISO_8859_1} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_LATIN1}</li>
	 * <li>{@link CharsetUtil#NAME_UCS_2} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_UCS2}</li>
	 * </ul>
	 * 
	 * 
	 * <p>
	 * The value set using this method takes precedence over any property and
	 * default value configured using {@link #auto()}.
	 * 
	 * <pre>
	 * .auto(false)
	 * .auto()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue(true)
	 * </pre>
	 * 
	 * <pre>
	 * .auto(false)
	 * .auto()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue(true)
	 * </pre>
	 * 
	 * In both cases, {@code auto(false)} is used.
	 * 
	 * <p>
	 * If this method is called several times, only the last value is used.
	 * 
	 * <p>
	 * If {@code null} value is set, it is like not setting a value at all. The
	 * property/default value configuration is applied.
	 * 
	 * @param enable
	 *            enable or disable automatic Data Coding Scheme detection
	 * @return this instance for fluent chaining
	 */
	public DataCodingSchemeBuilder auto(Boolean enable) {
		autoValueBuilder.setValue(enable);
		return this;
	}

	/**
	 * Enable/disable automatic mode based on SMPP interface version.
	 * 
	 * <p>
	 * {@link DataCodingProvider} implementation is selected based on SMPP
	 * interface version. SMPP v3.3 Data Coding Scheme values are defined in
	 * <a href=
	 * "https://en.wikipedia.org/wiki/Data_Coding_Scheme#SMS_Data_Coding_Scheme">SMS
	 * Data Coding Scheme</a>. SMPP 3.4 introduced a new list of data_coding
	 * values (<a href=
	 * "https://en.wikipedia.org/wiki/Short_Message_Peer-to-Peer#PDU_body">PDU
	 * body</a>).
	 * </p>
	 * 
	 * <strong>SMPP v3.3</strong>
	 * <p>
	 * The text message is encoded using {@link Charset}. According to that
	 * charset, the Data Coding Scheme is determined using the <strong>General
	 * Data Coding group</strong> table. Therefore, a simple mapping is applied:
	 * <ul>
	 * <li>{@link CharsetUtil#NAME_GSM7} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_DEFAULT}</li>
	 * <li>{@link CharsetUtil#NAME_PACKED_GSM} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_DEFAULT}</li>
	 * <li>{@link CharsetUtil#NAME_GSM} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_8BIT}</li>
	 * <li>{@link CharsetUtil#NAME_GSM8} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_8BIT}</li>
	 * <li>{@link CharsetUtil#NAME_UCS_2} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_UCS2}</li>
	 * </ul>
	 * 
	 * 
	 * <strong>SMPP v3.4+</strong>
	 * <p>
	 * The text message is encoded using {@link Charset}. According to that
	 * charset, the Data Coding Scheme is determined using only the
	 * <strong>Alphabet</strong> table. Therefore, a simple mapping is applied:
	 * <ul>
	 * <li>{@link CharsetUtil#NAME_GSM7} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_DEFAULT}</li>
	 * <li>{@link CharsetUtil#NAME_PACKED_GSM} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_DEFAULT}</li>
	 * <li>{@link CharsetUtil#NAME_GSM} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_8BIT}</li>
	 * <li>{@link CharsetUtil#NAME_GSM8} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_8BIT}</li>
	 * <li>{@link CharsetUtil#NAME_ISO_8859_1} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_LATIN1}</li>
	 * <li>{@link CharsetUtil#NAME_UCS_2} {@literal ->}
	 * {@link DataCoding#CHAR_ENC_UCS2}</li>
	 * </ul>
	 * 
	 * 
	 * <strong>Custom {@link DataCodingProvider} also configured</strong>
	 * <p>
	 * If a custom {@link DataCodingProvider} instance is registered, this
	 * instance is tried first (see {@link #custom(DataCodingProvider)} for more
	 * information). The automatic behavior is applied after if returned value
	 * of custom {@link DataCodingProvider} is {@code null}.
	 * 
	 * <strong>Fixed value also configured</strong>
	 * <p>
	 * If a fixed value is configured then it preempts any other configuration (
	 * {@link #auto(Boolean)} and {@link #custom(DataCodingProvider)} are not
	 * used at all).
	 * 
	 * 
	 * <p>
	 * This method is mainly used by {@link Configurer}s to register some
	 * property keys and/or a default value. The aim is to let developer be able
	 * to externalize its configuration (using system properties, configuration
	 * file or anything else). If the developer doesn't configure any value for
	 * the registered properties, the default value is used (if set).
	 * 
	 * <pre>
	 * .auto()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue(true)
	 * </pre>
	 * 
	 * <p>
	 * Non-null value set using {@link #auto(Boolean)} takes precedence over
	 * property values and default value.
	 * 
	 * <pre>
	 * .auto(false)
	 * .auto()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue(true)
	 * </pre>
	 * 
	 * The value {@code false} is used regardless of the value of the properties
	 * and default value.
	 * 
	 * <p>
	 * See {@link ConfigurationValueBuilder} for more information.
	 * 
	 * 
	 * @return the builder to configure property keys/default value
	 */
	public ConfigurationValueBuilder<DataCodingSchemeBuilder, Boolean> auto() {
		return autoValueBuilder;
	}

	/**
	 * Use the same Data Coding Scheme value for all messages.
	 * 
	 * <p>
	 * The value set using this method takes precedence over any property and
	 * default value configured using {@link #value()}.
	 * 
	 * <pre>
	 * .value((byte) 0x10)
	 * .value()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue((byte) 0)
	 * </pre>
	 * 
	 * <pre>
	 * .value((byte) 0x10)
	 * .value()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue((byte) 0)
	 * </pre>
	 * 
	 * In both cases, {@code value((byte) 0x10)} is used.
	 * 
	 * <p>
	 * If this method is called several times, only the last value is used.
	 * 
	 * <p>
	 * If {@code null} value is set, it is like not setting a value at all. The
	 * property/default value configuration is applied.
	 * 
	 * @param value
	 *            the Data Coding Scheme value for all messages
	 * @return this instance for fluent chaining
	 */
	public DataCodingSchemeBuilder value(Byte value) {
		this.dcsValueBuilder.setValue(value);
		return this;
	}

	/**
	 * Use the same Data Coding Scheme value for all messages.
	 * 
	 * <p>
	 * This method is mainly used by {@link Configurer}s to register some
	 * property keys and/or a default value. The aim is to let developer be able
	 * to externalize its configuration (using system properties, configuration
	 * file or anything else). If the developer doesn't configure any value for
	 * the registered properties, the default value is used (if set).
	 * 
	 * <pre>
	 * .value()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue((byte) 0)
	 * </pre>
	 * 
	 * <p>
	 * Non-null value set using {@link #value(Byte)} takes precedence over
	 * property values and default value.
	 * 
	 * <pre>
	 * .value((byte) 0x10)
	 * .value()
	 *   .properties("${custom.property.high-priority}", "${custom.property.low-priority}")
	 *   .defaultValue((byte) 0)
	 * </pre>
	 * 
	 * The value {@code (byte) 0x10} is used regardless of the value of the
	 * properties and default value.
	 * 
	 * <p>
	 * See {@link ConfigurationValueBuilder} for more information.
	 * 
	 * 
	 * @return the builder to configure property keys/default value
	 */
	public ConfigurationValueBuilder<DataCodingSchemeBuilder, Byte> value() {
		return dcsValueBuilder;
	}

	/**
	 * Register a custom strategy to determine Data Coding Scheme value.
	 * 
	 * <p>
	 * Automatic behavior (see {@link #auto(Boolean)} is still active but custom
	 * strategy is executed first. As the custom {@link DataCodingProvider} can
	 * return {@code null}, the automatic behavior is executed in that case.
	 * 
	 * <p>
	 * If {@code null} value is provided, custom strategy is disabled.
	 * 
	 * @param custom
	 *            the Strategy to determine Data Coding Scheme value.
	 * @return this instance for fluent chaining
	 */
	public DataCodingSchemeBuilder custom(DataCodingProvider custom) {
		this.custom = custom;
		return this;
	}

	@Override
	@SuppressWarnings("squid:S5411")
	public DataCodingProvider build() {
		Byte dataCodingValue = dcsValueBuilder.getValue();
		if (dataCodingValue != null) {
			return buildContext.register(new FixedByteValueDataCodingProvider(dataCodingValue));
		}
		FirstSupportingDataCodingProvider firstSupporting = buildContext.register(new FirstSupportingDataCodingProvider());
		if (custom != null) {
			firstSupporting.register(custom);
		}
		if (autoValueBuilder.getValue(false)) {
			registerAuto(firstSupporting);
		}
		return firstSupporting;
	}

	private void registerAuto(FirstSupportingDataCodingProvider firstSupporting) {
		Byte interfaceVersion = interfaceVersionProvider.get();
		if (interfaceVersion == SmppConstants.VERSION_3_3) {
			firstSupporting.register(buildContext.register(new CharsetMapToGeneralGroupDataCodingProvider(false)));
			return;
		}
		// 3.4+
		firstSupporting.register(buildContext.register(new CharsetMapToCharacterEncodingGroupDataCodingProvider(false)));
	}

}