GSMCharsetEncoder.java
/*
* Copyright (c) 2020, Stein Eldar Johnsen
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.morimekta.strings.enc;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.util.Map;
import java.util.Set;
import static java.lang.Character.isHighSurrogate;
import static java.lang.Character.isLowSurrogate;
import static net.morimekta.strings.internal.GSMCharsetUtil.EXT_CODE;
import static net.morimekta.strings.internal.GSMCharsetUtil.SHIFT_SEPTET;
class GSMCharsetEncoder extends CharsetEncoder {
private final Map<Character, Byte> basic;
private final Set<Character> basicLongChars;
private final Map<String, Byte> basicLongMap;
private final Map<Character, Byte> shift;
private final Set<Character> shiftLongChars;
private final Map<String, Byte> shiftLongMap;
GSMCharsetEncoder(GSMCharset cs) {
super(cs, 1f, 2f);
this.basic = cs.getLockingShift().basicMap;
this.basicLongChars = cs.getLockingShift().basicExtended;
this.basicLongMap = cs.getLockingShift().basicStringCode;
this.shift = cs.getSingleShift().shiftMap;
this.shiftLongChars = cs.getSingleShift().shiftExtended;
this.shiftLongMap = cs.getSingleShift().shiftStringCode;
}
@Override
public boolean canEncode(char c) {
return ((GSMCharset) charset()).canEncode(c);
}
@Override
public boolean canEncode(CharSequence cs) {
return ((GSMCharset) charset()).canEncode(cs);
}
@Override
protected CoderResult encodeLoop(CharBuffer charBuffer, ByteBuffer byteBuffer) {
while (charBuffer.hasRemaining()) {
if (!byteBuffer.hasRemaining()) {
return CoderResult.OVERFLOW;
}
char c = charBuffer.get();
if (c <= EXT_CODE) {
charBuffer.position(charBuffer.position() - 1);
return CoderResult.unmappableForLength(1);
}
// no chars outside the BMP are supported.
if (isHighSurrogate(c)) {
if (charBuffer.remaining() > 0) {
char d = charBuffer.get();
charBuffer.position(charBuffer.position() - 2);
if (isLowSurrogate(d)) {
return CoderResult.unmappableForLength(2);
}
} else {
charBuffer.position(charBuffer.position() - 1);
}
return CoderResult.unmappableForLength(1);
} else if (isLowSurrogate(c)) {
charBuffer.position(charBuffer.position() - 1);
return CoderResult.unmappableForLength(1);
}
if (checkLongChar(c, charBuffer, byteBuffer, false, basicLongChars, basicLongMap)) {
continue;
} else if (checkLongChar(c, charBuffer, byteBuffer, true, shiftLongChars, shiftLongMap)) {
continue;
}
int code = basic.getOrDefault(c, (byte) -1);
if (code < 0) {
if (byteBuffer.remaining() < 2) {
charBuffer.position(charBuffer.position() - 1);
return CoderResult.OVERFLOW;
}
code = shift.getOrDefault(c, (byte) -1);
if (code < 0) {
charBuffer.position(charBuffer.position() - 1);
return CoderResult.unmappableForLength(1);
}
byteBuffer.put(SHIFT_SEPTET);
}
byteBuffer.put((byte) code);
}
return CoderResult.UNDERFLOW;
}
private boolean checkLongChar(char c,
CharBuffer cb,
ByteBuffer bb,
boolean shift,
Set<Character> check,
Map<String, Byte> mapping) {
if (check.contains(c)) {
char n = cb.get();
Byte b = mapping.get("" + c + n);
if (b != null) {
if (shift) {
bb.put(SHIFT_SEPTET);
}
bb.put(b);
return true;
}
cb.position(cb.position() - 1);
}
return false;
}
}