CharsetMapToGeneralGroupDataCodingProvider.java

package fr.sii.ogham.sms.sender.impl.cloudhopper.preparator;

import static com.cloudhopper.commons.charset.CharsetUtil.NAME_GSM;
import static com.cloudhopper.commons.charset.CharsetUtil.NAME_GSM7;
import static com.cloudhopper.commons.charset.CharsetUtil.NAME_GSM8;
import static com.cloudhopper.commons.charset.CharsetUtil.NAME_PACKED_GSM;
import static com.cloudhopper.commons.charset.CharsetUtil.NAME_UCS_2;
import static com.cloudhopper.commons.gsm.DataCoding.CHAR_ENC_8BIT;
import static com.cloudhopper.commons.gsm.DataCoding.CHAR_ENC_DEFAULT;
import static com.cloudhopper.commons.gsm.DataCoding.CHAR_ENC_UCS2;
import static java.util.Collections.unmodifiableMap;

import java.util.HashMap;
import java.util.Map;

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

import fr.sii.ogham.sms.encoder.Encoded;
import fr.sii.ogham.sms.sender.impl.cloudhopper.encoder.NamedCharset;
import fr.sii.ogham.sms.sender.impl.cloudhopper.exception.DataCodingException;
import fr.sii.ogham.sms.sender.impl.cloudhopper.exception.UnsupportedCharsetException;

/**
 * Provide a Data Coding Scheme according to charset used to encode the message.
 * The resulting encoding is <b>General Data Coding Group</b>:
 * 
 * <ul>
 * <li>If Bits 4 {@literal &} 5 are 0 {@literal &} 0 then see above "0000
 * Character Encoding Group"</li>
 * <li>Message Class (Bit 0 {@literal &} 1) (default 0, 0)</li>
 * <li>Alphabet (Bit 2 {@literal &} 3) (default 0, 0)</li>
 * <li>Message Class Present (Bit 4) (whether bits 0 {@literal &} 1 have
 * meaning)</li>
 * <li>Compression Flag (Bit 5) (0 - uncompressed, 1 - compressed)</li>
 * </ul>
 * 
 * 
 * <strong>WARNING:</strong> This provider only supports GSM 7-bit, GSM 8-bit
 * and UCS-2 charsets. This is due to use of
 * {@link DataCoding#createGeneralGroup(byte, Byte, boolean)} that only accepts
 * {@link DataCoding#CHAR_ENC_DEFAULT}, {@link DataCoding#CHAR_ENC_8BIT},
 * {@link DataCoding#CHAR_ENC_UCS2}.
 * 
 * 
 * @author Aurélien Baudet
 *
 */
public class CharsetMapToGeneralGroupDataCodingProvider implements DataCodingProvider {
	private final boolean failIfUnknown;
	private final Map<String, Byte> alphabetIndexedByCharsetName;
	private final Byte messageClass;
	private final boolean compressed;

	/**
	 * Provides {@link DataCoding} based on the charset used to encode the
	 * message.
	 * 
	 * <p>
	 * The default map is used (charset name {@literal ->} alphabet):
	 * <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>
	 * 
	 * 
	 * <p>
	 * The message class is set to {@code null}.
	 * 
	 * <p>
	 * The compressed field is set to false.
	 * 
	 * @param failIfUnknown
	 *            if true it throws {@link UnsupportedCharsetException}, if
	 *            false is returns null to let other
	 *            {@link DataCodingProvider}(s) being executed.
	 * 
	 */
	public CharsetMapToGeneralGroupDataCodingProvider(boolean failIfUnknown) {
		this(failIfUnknown, defaultMap());
	}

	/**
	 * Provides {@link DataCoding} based on the charset used to encode the
	 * message.
	 * 
	 * <p>
	 * It uses the provided map to determine alphabet from the charset used to
	 * encode the message.
	 * 
	 * <p>
	 * The message class is set to {@code null}.
	 * 
	 * <p>
	 * The compressed field is set to false.
	 * 
	 * 
	 * 
	 * @param failIfUnknown
	 *            if true it throws {@link UnsupportedCharsetException}, if
	 *            false is returns null to let other
	 *            {@link DataCodingProvider}(s) being executed.
	 * @param alphabetIndexedByCharsetName
	 *            the map used to determine Data Coding Alphabet from charset
	 *            name
	 */
	public CharsetMapToGeneralGroupDataCodingProvider(boolean failIfUnknown, Map<String, Byte> alphabetIndexedByCharsetName) {
		this(failIfUnknown, alphabetIndexedByCharsetName, null, false);
	}

	/**
	 * Provides {@link DataCoding} based on the charset used to encode the
	 * message.
	 * 
	 * <p>
	 * It uses the provided map to determine alphabet from the charset used to
	 * encode the message.
	 * 
	 * <p>
	 * It uses the provided message class to use for the Data Coding scheme. The
	 * Message Class bit and Message Class Present bit are both determined from
	 * the provided class. If {@code null}, the "message class" not active flag
	 * will not be set.
	 * 
	 * <p>
	 * It uses the provided compressed value to indicate if the message is
	 * compressed or not.
	 * 
	 * 
	 * 
	 * @param failIfUnknown
	 *            if true it throws {@link UnsupportedCharsetException}, if
	 *            false is returns null to let other
	 *            {@link DataCodingProvider}(s) being executed.
	 * @param alphabetIndexedByCharsetName
	 *            the map used to determine Data Coding Alphabet from charset
	 *            name
	 * @param messageClass
	 *            the message class value to use
	 * @param compressed
	 *            indicate if message is compressed or not
	 */
	public CharsetMapToGeneralGroupDataCodingProvider(boolean failIfUnknown, Map<String, Byte> alphabetIndexedByCharsetName, Byte messageClass, boolean compressed) {
		super();
		this.failIfUnknown = failIfUnknown;
		this.alphabetIndexedByCharsetName = alphabetIndexedByCharsetName;
		this.messageClass = messageClass;
		this.compressed = compressed;
	}

	@Override
	public DataCoding provide(Encoded encoded) throws DataCodingException {
		NamedCharset charset = NamedCharset.from(encoded.getCharsetName());
		Byte encoding = alphabetIndexedByCharsetName.get(charset.getCharsetName());
		if (encoding == null) {
			if (failIfUnknown) {
				throw new UnsupportedCharsetException(encoded.getCharsetName() + " charset not supported for General Group Data Coding Scheme", encoded);
			}
			return null;
		}
		return DataCoding.createGeneralGroup(encoding, messageClass, compressed);
	}

	/**
	 * Default mapping used to determine {@link DataCoding} encoding value from
	 * {@link Charset}:
	 * <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>
	 * 
	 * @return the mapping
	 */
	public static Map<String, Byte> defaultMap() {
		Map<String, Byte> map = new HashMap<>();
		// @formatter:off
		// standards only are supported (due to DataCoding.createGeneralGroup)
		map.put(NAME_GSM7, 			CHAR_ENC_DEFAULT);
		map.put(NAME_PACKED_GSM, 	CHAR_ENC_DEFAULT);
		map.put(NAME_GSM, 			CHAR_ENC_8BIT);
		map.put(NAME_GSM8, 			CHAR_ENC_8BIT);
		map.put(NAME_UCS_2, 		CHAR_ENC_UCS2);
		// @formatter:on
		return unmodifiableMap(map);
	}

}