From cc5225a2a6ab85b885a624a69b912016ba300bba Mon Sep 17 00:00:00 2001 From: konsoletyper Date: Sun, 22 Mar 2015 15:57:49 +0300 Subject: [PATCH] UTF-8 support test passes --- .../classlib/java/nio/charset/TCharset.java | 20 +- .../java/nio/charset/TCharsetDecoder.java | 4 +- .../java/nio/charset/TCoderResult.java | 16 + .../nio/charset/impl/TBufferedDecoder.java | 124 ++++++++ .../nio/charset/impl/TBufferedEncoder.java | 128 ++++++++ .../{UTF8Charset.java => TUTF8Charset.java} | 8 +- .../java/nio/charset/impl/TUTF8Decoder.java | 93 ++++++ .../java/nio/charset/impl/TUTF8Encoder.java | 95 ++++++ .../java/nio/charset/impl/UTF8Decoder.java | 83 ------ .../classlib/java/nio/charset/UTF8Test.java | 274 ++++++++++++++++++ 10 files changed, 747 insertions(+), 98 deletions(-) create mode 100644 teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/TBufferedDecoder.java create mode 100644 teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/TBufferedEncoder.java rename teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/{UTF8Charset.java => TUTF8Charset.java} (88%) create mode 100644 teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/TUTF8Decoder.java create mode 100644 teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/TUTF8Encoder.java delete mode 100644 teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/UTF8Decoder.java create mode 100644 teavm-tests/src/test/java/org/teavm/classlib/java/nio/charset/UTF8Test.java diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/TCharset.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/TCharset.java index 22ae9cad8..2bdf66b2e 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/TCharset.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/TCharset.java @@ -15,12 +15,10 @@ */ package org.teavm.classlib.java.nio.charset; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; +import java.util.*; import org.teavm.classlib.java.nio.TByteBuffer; import org.teavm.classlib.java.nio.TCharBuffer; -import org.teavm.classlib.java.nio.charset.impl.UTF8Charset; +import org.teavm.classlib.java.nio.charset.impl.TUTF8Charset; /** * @@ -30,6 +28,11 @@ public abstract class TCharset implements Comparable { private String canonicalName; private String[] aliases; private Set aliasSet; + private static final Map charsets = new HashMap<>(); + + static { + charsets.put("UTF-8", new TUTF8Charset()); + } protected TCharset(String canonicalName, String[] aliases) { checkCanonicalName(canonicalName); @@ -74,12 +77,11 @@ public abstract class TCharset implements Comparable { throw new IllegalArgumentException("charsetName is null"); } checkCanonicalName(charsetName); - switch (charsetName.toUpperCase()) { - case "UTF-8": - return new UTF8Charset(); - default: - throw new TUnsupportedCharsetException(charsetName); + TCharset charset = charsets.get(charsetName.toUpperCase()); + if (charset == null) { + throw new TUnsupportedCharsetException(charsetName); } + return charset; } public final String name() { diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/TCharsetDecoder.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/TCharsetDecoder.java index 3447037ca..bf9e50d99 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/TCharsetDecoder.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/TCharsetDecoder.java @@ -27,7 +27,7 @@ public abstract class TCharsetDecoder { private static final int INIT = 0; private static final int IN_PROGRESS = 1; private static final int END = 2; - private static final int FLUSH = 2; + private static final int FLUSH = 3; private TCharset charset; private float averageCharsPerByte; private float maxCharsPerByte; @@ -125,7 +125,7 @@ public abstract class TCharsetDecoder { if (result.isOverflow()) { return result; } else if (result.isUnderflow()) { - if (endOfInput) { + if (endOfInput && in.hasRemaining()) { state = END; return TCoderResult.malformedForLength(in.remaining()); } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/TCoderResult.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/TCoderResult.java index 5bedfd3d3..240674f1a 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/TCoderResult.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/TCoderResult.java @@ -78,4 +78,20 @@ public class TCoderResult { throw new TUnmappableCharacterException(length); } } + + @Override + public String toString() { + switch (kind) { + case 0: + return "UNDERFLOW"; + case 1: + return "OVERFLOW"; + case 2: + return "MALFORMED " + length; + case 3: + return "UNMAPPABLE " + length; + default: + throw new AssertionError(); + } + } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/TBufferedDecoder.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/TBufferedDecoder.java new file mode 100644 index 000000000..bcd0611a4 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/TBufferedDecoder.java @@ -0,0 +1,124 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * Licensed 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 org.teavm.classlib.java.nio.charset.impl; + +import org.teavm.classlib.java.nio.TByteBuffer; +import org.teavm.classlib.java.nio.TCharBuffer; +import org.teavm.classlib.java.nio.charset.TCharset; +import org.teavm.classlib.java.nio.charset.TCharsetDecoder; +import org.teavm.classlib.java.nio.charset.TCoderResult; + +/** + * + * @author Alexey Andreev + */ +public abstract class TBufferedDecoder extends TCharsetDecoder { + public TBufferedDecoder(TCharset cs, float averageCharsPerByte, float maxCharsPerByte) { + super(cs, averageCharsPerByte, maxCharsPerByte); + } + + @Override + protected TCoderResult decodeLoop(TByteBuffer in, TCharBuffer out) { + // Use intermediate array to batch buffer operations + int outPos = 0; + byte[] inArray = new byte[Math.min(in.remaining(), 512)]; + int inPos = 0; + int inSize = 0; + char[] outArray = new char[Math.min(out.remaining(), 512)]; + TCoderResult result = null; + + while (true) { + // If there were remaining bytes in input buffer, copy them to the beginning of input array + // so the next iteration will process these bytes again + if (inPos + 32 > inSize && in.hasRemaining()) { + for (int i = inPos; i < inSize; ++i) { + inArray[i - inPos] = inArray[i]; + } + inPos = inSize - inPos; + inSize = Math.min(in.remaining() + inPos, inArray.length); + in.get(inArray, inPos, inSize - inPos); + inPos = 0; + } + + if (!out.hasRemaining()) { + result = !in.hasRemaining() && inPos >= inSize ? TCoderResult.UNDERFLOW : TCoderResult.OVERFLOW; + break; + } + + // Perform iteration + outPos = 0; + int outSize = Math.min(out.remaining(), outArray.length); + Controller controller = new Controller(in, out); + result = arrayDecode(inArray, inPos, inSize, outArray, outPos, outSize, controller); + inPos = controller.inPosition; + if (result == null && outPos == controller.outPosition) { + result = TCoderResult.UNDERFLOW; + } + outPos = controller.outPosition; + + // Write any output characters to out buffer + out.put(outArray, 0, outPos); + if (result != null) { + break; + } + } + + in.position(in.position() - (inSize - inPos)); + + return result; + } + + protected abstract TCoderResult arrayDecode(byte[] inArray, int inPos, int inSize, + char[] outArray, int outPos, int outSize, + Controller controller); + + public static class Controller { + private TByteBuffer in; + private TCharBuffer out; + int inPosition; + int outPosition; + + Controller(TByteBuffer in, TCharBuffer out) { + super(); + this.in = in; + this.out = out; + } + + public boolean hasMoreInput() { + return in.hasRemaining(); + } + + public boolean hasMoreInput(int sz) { + return in.remaining() >= sz; + } + + public boolean hasMoreOutput() { + return out.hasRemaining(); + } + + public boolean hasMoreOutput(int sz) { + return out.remaining() >= sz; + } + + public void setInPosition(int inPosition) { + this.inPosition = inPosition; + } + + public void setOutPosition(int outPosition) { + this.outPosition = outPosition; + } + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/TBufferedEncoder.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/TBufferedEncoder.java new file mode 100644 index 000000000..cad59e5e8 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/TBufferedEncoder.java @@ -0,0 +1,128 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * Licensed 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 org.teavm.classlib.java.nio.charset.impl; + +import org.teavm.classlib.java.nio.TByteBuffer; +import org.teavm.classlib.java.nio.TCharBuffer; +import org.teavm.classlib.java.nio.charset.TCharset; +import org.teavm.classlib.java.nio.charset.TCharsetEncoder; +import org.teavm.classlib.java.nio.charset.TCoderResult; + +/** + * + * @author Alexey Andreev + */ +public abstract class TBufferedEncoder extends TCharsetEncoder { + public TBufferedEncoder(TCharset cs, float averageBytesPerChar, float maxBytesPerChar, byte[] replacement) { + super(cs, averageBytesPerChar, maxBytesPerChar, replacement); + } + + public TBufferedEncoder(TCharset cs, float averageBytesPerChar, float maxBytesPerChar) { + super(cs, averageBytesPerChar, maxBytesPerChar); + } + + @Override + protected TCoderResult encodeLoop(TCharBuffer in, TByteBuffer out) { + // Use intermediate array to batch buffer operations + int outPos = 0; + char[] inArray = new char[Math.min(in.remaining(), 512)]; + int inPos = 0; + int inSize = 0; + byte[] outArray = new byte[Math.min(out.remaining(), 512)]; + TCoderResult result = null; + + while (true) { + // If there were remaining bytes in input buffer, copy them to the beginning of input array + // so the next iteration will process these bytes again + if (inPos + 32 > inSize && in.hasRemaining()) { + for (int i = inPos; i < inSize; ++i) { + inArray[i - inPos] = inArray[i]; + } + inPos = inSize - inPos; + inSize = Math.min(in.remaining() + inPos, inArray.length); + in.get(inArray, inPos, inSize - inPos); + inPos = 0; + } + + if (!out.hasRemaining()) { + result = !in.hasRemaining() && inPos >= inSize ? TCoderResult.UNDERFLOW : TCoderResult.OVERFLOW; + break; + } + + // Perform iteration + outPos = 0; + int outSize = Math.min(out.remaining(), outArray.length); + Controller controller = new Controller(in, out); + result = arrayEncode(inArray, inPos, inSize, outArray, outPos, outSize, controller); + inPos = controller.inPosition; + if (result == null && outPos == controller.outPosition) { + result = TCoderResult.UNDERFLOW; + } + outPos = controller.outPosition; + + // Write any output characters to out buffer + out.put(outArray, 0, outPos); + if (result != null) { + break; + } + } + + in.position(in.position() - (inSize - inPos)); + + return result; + } + + protected abstract TCoderResult arrayEncode(char[] inArray, int inPos, int inSize, + byte[] outArray, int outPos, int outSize, + Controller controller); + + public static class Controller { + private TCharBuffer in; + private TByteBuffer out; + int inPosition; + int outPosition; + + Controller(TCharBuffer in, TByteBuffer out) { + super(); + this.in = in; + this.out = out; + } + + public boolean hasMoreInput() { + return in.hasRemaining(); + } + + public boolean hasMoreInput(int sz) { + return in.remaining() >= sz; + } + + public boolean hasMoreOutput() { + return out.hasRemaining(); + } + + public boolean hasMoreOutput(int sz) { + return out.remaining() >= sz; + } + + public void setInPosition(int inPosition) { + this.inPosition = inPosition; + } + + public void setOutPosition(int outPosition) { + this.outPosition = outPosition; + } + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/UTF8Charset.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/TUTF8Charset.java similarity index 88% rename from teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/UTF8Charset.java rename to teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/TUTF8Charset.java index d9d8449d0..959268ea9 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/UTF8Charset.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/TUTF8Charset.java @@ -23,8 +23,8 @@ import org.teavm.classlib.java.nio.charset.TCharsetEncoder; * * @author Alexey Andreev */ -public class UTF8Charset extends TCharset { - public UTF8Charset() { +public class TUTF8Charset extends TCharset { + public TUTF8Charset() { super("UTF-8", new String[0]); } @@ -35,11 +35,11 @@ public class UTF8Charset extends TCharset { @Override public TCharsetDecoder newDecoder() { - return new UTF8Decoder(this); + return new TUTF8Decoder(this); } @Override public TCharsetEncoder newEncoder() { - return null; + return new TUTF8Encoder(this); } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/TUTF8Decoder.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/TUTF8Decoder.java new file mode 100644 index 000000000..a9c8e4659 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/TUTF8Decoder.java @@ -0,0 +1,93 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * Licensed 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 org.teavm.classlib.java.nio.charset.impl; + +import org.teavm.classlib.impl.charset.UTF16Helper; +import org.teavm.classlib.java.nio.charset.TCharset; +import org.teavm.classlib.java.nio.charset.TCoderResult; + +/** + * + * @author Alexey Andreev + */ +public class TUTF8Decoder extends TBufferedDecoder { + public TUTF8Decoder(TCharset cs) { + super(cs, 1f / 3, 0.5f); + } + + @Override + protected TCoderResult arrayDecode(byte[] inArray, int inPos, int inSize, char[] outArray, int outPos, int outSize, + Controller controller) { + TCoderResult result = null; + while (inPos < inSize && outPos < outSize) { + int b = inArray[inPos++] & 0xFF; + if ((b & 0x80) == 0) { + outArray[outPos++] = (char)b; + } else if ((b & 0xE0) == 0xC0) { + if (inPos >= inSize) { + --inPos; + if (!controller.hasMoreInput()) { + result = TCoderResult.UNDERFLOW; + } + break; + } + outArray[outPos++] = (char)(((b & 0x1F) << 6) | (inArray[inPos++] & 0x3F)); + } else if ((b & 0xF0) == 0xE0) { + if (inPos + 2 > inSize) { + --inPos; + if (!controller.hasMoreInput()) { + result = TCoderResult.UNDERFLOW; + } + break; + } + byte b2 = inArray[inPos++]; + byte b3 = inArray[inPos++]; + char c = (char)(((b & 0x0F) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3F)); + if (Character.isSurrogate(c)) { + inPos -= 3; + result = TCoderResult.malformedForLength(3); + break; + } + outArray[outPos++] = c; + } else if ((b & 0xF8) == 0xF0) { + if (inPos + 3 > inSize) { + --inPos; + if (!controller.hasMoreInput()) { + result = TCoderResult.UNDERFLOW; + } + break; + } + if (outPos + 2 > outSize) { + --inPos; + if (!controller.hasMoreOutput()) { + result = TCoderResult.OVERFLOW; + } + break; + } + byte b2 = inArray[inPos++]; + byte b3 = inArray[inPos++]; + byte b4 = inArray[inPos++]; + int code = ((b & 0x07) << 18) | ((b2 & 0x3f) << 12) | ((b3 & 0x3F) << 6) | (b4 & 0x3F); + outArray[outPos++] = UTF16Helper.highSurrogate(code); + outArray[outPos++] = UTF16Helper.lowSurrogate(code); + } + } + + controller.setInPosition(inPos); + controller.setOutPosition(outPos); + return result; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/TUTF8Encoder.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/TUTF8Encoder.java new file mode 100644 index 000000000..0da2118dd --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/TUTF8Encoder.java @@ -0,0 +1,95 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * Licensed 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 org.teavm.classlib.java.nio.charset.impl; + +import org.teavm.classlib.impl.charset.UTF16Helper; +import org.teavm.classlib.java.nio.charset.TCharset; +import org.teavm.classlib.java.nio.charset.TCoderResult; + +/** + * + * @author Alexey Andreev + */ +public class TUTF8Encoder extends TBufferedEncoder { + TUTF8Encoder(TCharset cs) { + super(cs, 2, 4); + } + + @Override + protected TCoderResult arrayEncode(char[] inArray, int inPos, int inSize, byte[] outArray, int outPos, int outSize, + Controller controller) { + TCoderResult result = null; + while (inPos < inSize && outPos < outSize) { + char ch = inArray[inPos++]; + if (ch < 0x80) { + outArray[outPos++] = (byte)ch; + } else if (ch < 0x800) { + if (outPos + 2 > outSize) { + --inPos; + if (!controller.hasMoreOutput(2)) { + result = TCoderResult.OVERFLOW; + } + break; + } + outArray[outPos++] = (byte)(0xC0 | (ch >> 6)); + outArray[outPos++] = (byte)(0x80 | (ch & 0x3F)); + } else if (!Character.isSurrogate(ch)) { + if (outPos + 3 > outSize) { + --inPos; + if (!controller.hasMoreOutput(3)) { + result = TCoderResult.OVERFLOW; + } + break; + } + outArray[outPos++] = (byte)(0xE0 | (ch >> 12)); + outArray[outPos++] = (byte)(0x80 | ((ch >> 6) & 0x3F)); + outArray[outPos++] = (byte)(0x80 | (ch & 0x3F)); + } else if (UTF16Helper.isHighSurrogate(ch)) { + if (inPos >= inSize) { + if (!controller.hasMoreInput()) { + result = TCoderResult.UNDERFLOW; + } + break; + } + char low = inArray[inPos++]; + if (!UTF16Helper.isLowSurrogate(low)) { + inPos -= 2; + result = TCoderResult.malformedForLength(2); + break; + } + if (outPos + 4 > outSize) { + inPos -= 2; + if (!controller.hasMoreOutput(4)) { + result = TCoderResult.OVERFLOW; + } + break; + } + int codePoint = UTF16Helper.buildCodePoint(ch, low); + outArray[outPos++] = (byte)(0xF0 | (codePoint >> 18)); + outArray[outPos++] = (byte)(0x80 | ((codePoint >> 12) & 0x3F)); + outArray[outPos++] = (byte)(0x80 | ((codePoint >> 6) & 0x3F)); + outArray[outPos++] = (byte)(0x80 | (codePoint & 0x3F)); + } else { + result = TCoderResult.malformedForLength(1); + break; + } + } + + controller.setInPosition(inPos); + controller.setOutPosition(outPos); + return result; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/UTF8Decoder.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/UTF8Decoder.java deleted file mode 100644 index b7c93bb17..000000000 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/nio/charset/impl/UTF8Decoder.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2015 Alexey Andreev. - * - * Licensed 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 org.teavm.classlib.java.nio.charset.impl; - -import org.teavm.classlib.impl.charset.UTF16Helper; -import org.teavm.classlib.java.nio.TByteBuffer; -import org.teavm.classlib.java.nio.TCharBuffer; -import org.teavm.classlib.java.nio.charset.TCharset; -import org.teavm.classlib.java.nio.charset.TCharsetDecoder; -import org.teavm.classlib.java.nio.charset.TCoderResult; - -/** - * - * @author Alexey Andreev - */ -public class UTF8Decoder extends TCharsetDecoder { - public UTF8Decoder(TCharset cs) { - super(cs, 1f / 3, 0.5f); - } - - @Override - protected TCoderResult decodeLoop(TByteBuffer in, TCharBuffer out) { - while (true) { - if (in.remaining() < 4) { - return TCoderResult.UNDERFLOW; - } - if (!out.hasRemaining()) { - return TCoderResult.OVERFLOW; - } - int b = in.get() & 0xFF; - if ((b & 0x80) == 0) { - out.put((char)b); - } else if ((b & 0xE0) == 0xC0) { - if (!in.hasRemaining()) { - in.position(in.position() - 1); - return TCoderResult.UNDERFLOW; - } - out.put((char)(((b & 0x1F) << 6) | (in.get() & 0x3F))); - } else if ((b & 0xF0) == 0xE0) { - if (in.remaining() < 2) { - in.position(in.position() - 1); - return TCoderResult.UNDERFLOW; - } - byte b2 = in.get(); - byte b3 = in.get(); - char c = (char)(((b & 0x0F) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3F)); - if (Character.isSurrogate(c)) { - in.position(in.position() - 2); - return TCoderResult.malformedForLength(3); - } - out.put(c); - } else if ((b & 0xF8) == 0xF0) { - if (in.remaining() < 3) { - in.position(in.position() - 1); - return TCoderResult.UNDERFLOW; - } - if (out.remaining() < 3) { - in.position(in.position() - 1); - return TCoderResult.OVERFLOW; - } - byte b2 = in.get(); - byte b3 = in.get(); - byte b4 = in.get(); - int code = ((b & 0x07) << 18) | ((b2 & 0x3f) << 12) | ((b3 & 0x3F) << 6) | (b4 & 0x3F); - out.put(UTF16Helper.highSurrogate(code)); - out.put(UTF16Helper.lowSurrogate(code)); - } - } - } -} diff --git a/teavm-tests/src/test/java/org/teavm/classlib/java/nio/charset/UTF8Test.java b/teavm-tests/src/test/java/org/teavm/classlib/java/nio/charset/UTF8Test.java new file mode 100644 index 000000000..5017a335b --- /dev/null +++ b/teavm-tests/src/test/java/org/teavm/classlib/java/nio/charset/UTF8Test.java @@ -0,0 +1,274 @@ +package org.teavm.classlib.java.nio.charset; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; +import java.util.Arrays; +import org.junit.Test; + +/** + * + * @author Alexey Andreev + */ +public class UTF8Test { + @Test + public void encode1() { + runEncode(600, 600); + } + + @Test + public void encode2() { + runEncode(600, 100); + } + + @Test + public void encode3() { + runEncode(100, 600); + } + + @Test + public void decode1() { + runDecode(600, 600); + } + + @Test + public void decode2() { + runDecode(600, 100); + } + + @Test + public void decode3() { + runDecode(100, 600); + } + + private void runEncode(int inSize, int outSize) { + char[] input = text.toCharArray(); + byte[] output = new byte[16384]; + int inPos = 0; + int outPos = 0; + CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder(); + CoderResult result = CoderResult.UNDERFLOW; + + while (true) { + int inLen = Math.min(inSize, input.length - inPos); + CharBuffer in = CharBuffer.wrap(input, inPos, inLen); + int outLen = Math.min(outSize, output.length - outPos); + ByteBuffer out = ByteBuffer.wrap(output, outPos, outLen); + result = encoder.encode(in, out, inPos + inLen >= input.length); + inPos = in.position(); + outPos = out.position(); + if (result.isError() || inPos >= input.length) { + break; + } + } + + assertTrue("Should be UNDERFLOW after encoding", result.isUnderflow()); + + while (true) { + int outLen = Math.min(outSize, output.length - outPos); + ByteBuffer out = ByteBuffer.wrap(output, outPos, outLen); + result = encoder.flush(out); + outPos = out.position(); + if (result.isUnderflow()) { + break; + } + } + + assertTrue("Should be UNDERFLOW after flushing", result.isUnderflow()); + output = Arrays.copyOf(output, outPos); + assertEquals(hex, bytesToHex(output)); + } + + private void runDecode(int inSize, int outSize) { + byte[] input = hexToBytes(hex); + char[] output = new char[16384]; + int inPos = 0; + int outPos = 0; + CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder(); + CoderResult result = CoderResult.UNDERFLOW; + + while (true) { + int inLen = Math.min(inSize, input.length - inPos); + ByteBuffer in = ByteBuffer.wrap(input, inPos, inLen); + int outLen = Math.min(outSize, output.length - outPos); + CharBuffer out = CharBuffer.wrap(output, outPos, outLen); + result = decoder.decode(in, out, inPos + inLen >= input.length); + inPos = in.position(); + outPos = out.position(); + if (result.isError() || inPos >= input.length) { + break; + } + } + + assertTrue("Should be UNDERFLOW after encoding", result.isUnderflow()); + + while (true) { + int outLen = Math.min(outSize, output.length - outPos); + CharBuffer out = CharBuffer.wrap(output, outPos, outLen); + result = decoder.flush(out); + outPos = out.position(); + if (result.isUnderflow()) { + break; + } + } + + assertTrue("Should be UNDERFLOW after flushing", result.isUnderflow()); + output = Arrays.copyOf(output, outPos); + assertEquals(text, new String(output)); + } + + private String bytesToHex(byte[] bytes) { + char[] result = new char[bytes.length * 2]; + int j = 0; + for (int i = 0; i < bytes.length; ++i) { + int b = bytes[i] & 0xFF; + result[j++] = hexDigits[b >> 4]; + result[j++] = hexDigits[b & 0xF]; + } + return new String(result); + } + + private byte[] hexToBytes(String hex) { + char[] chars = hex.toCharArray(); + byte[] result = new byte[chars.length / 2]; + int j = 0; + for (int i = 0; i < chars.length; i += 2) { + char hi = chars[i]; + char lo = chars[i + 1]; + result[j++] = (byte)((digit(hi) << 4) | digit(lo)); + } + return result; + } + + private static int digit(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + return c - 'A' + 10; + } + + private static char[] hexDigits = "0123456789ABCDEF".toCharArray(); + + // Fragment from "The Idiot" by F. Dostoevsky + private String text = + "Здесь в моем объяснении я отмечаю все эти цифры и числа. Мне, конечно, всё равно будет, но теперь " + + "(и, может быть, только в эту минуту) я желаю, чтобы те, которые будут судить мой поступок, могли ясно " + + "видеть, из какой логической цепи выводов вышло мое „последнее убеждение“. Я написал сейчас выше, что " + + "окончательная решимость, которой недоставало мне для исполнения моего „последнего убеждения“, произошла " + + "во мне, кажется, вовсе не из логического вывода, а от какого-то странного толчка, от одного странного " + + "обстоятельства, может быть вовсе не связанного ничем с ходом дела. Дней десять назад зашел ко мне Рогожин, " + + "по одному своему делу, о котором здесь лишнее распространяться. Я никогда не видал Рогожина прежде, " + + "но слышал о нем очень многое. Я дал ему все нужные справки, и он скоро ушел, а так как он и приходил " + + "только за справками, то тем бы дело между нами и кончилось. Но он слишком заинтересовал меня, " + + "и весь этот день я был под влиянием странных мыслей, так что решился пойти к нему на другой день сам, " + + "отдать визит. Рогожин был мне очевидно не рад и даже „деликатно“ намекнул, что нам нечего продолжать " + + "знакомство; но все-таки я провел очень любопытный час, как, вероятно, и он. Между нами был такой контраст, " + + "который не мог не сказаться нам обоим, особенно мне: я был человек, уже сосчитавший дни свои, а он - " + + "живущий самою полною, непосредственною жизнью, настоящею минутой, без всякой заботы о „последних“ " + + "выводах, цифрах или о чем бы то ни было, не касающемся того, на чем... на чем... ну хоть на чем он " + + "помешан; пусть простит мне это выражение господин Рогожин, пожалуй хоть как плохому литератору, не " + + "умевшему выразить свою мысль. Несмотря на всю его нелюбезность, мне показалось, что он человек с умом и " + + "может многое понимать, хотя его мало что интересует из постороннего. Я не намекал ему о моем „последнем " + + "убеждении“, но мне почему-то показалось, что он, слушая меня, угадал его. Он промолчал, он ужасно молчалив. " + + "Я намекнул ему, уходя, что, несмотря на всю между нами разницу и на все противоположности, - " + + "les extrémités se touchent 1 (я растолковал ему это по-русски), так что, может быть, он и сам вовсе не " + + "так далек от моего „последнего убеждения“, как кажется. На это он ответил мне очень угрюмою и кислою " + + "гримасой, встал, сам сыскал мне мою фуражку, сделав вид, будто бы я сам ухожу, и просто-запросто вывел " + + "меня из своего мрачного дома под видом того, что провожает меня из учтивости. Дом его поразил меня; " + + "похож на кладбище, а ему, кажется, нравится, что, впрочем, понятно: такая полная, непосредственная " + + "жизнь, которою он живет, слишком полна сама по себе, чтобы нуждаться в обстановке."; + private String hex = + "D097D0B4D0B5D181D18C20D0B220D0BCD0BED0B5D0BC20D0BED0B1D18AD18FD181D0BDD0B5D0BDD0B8D0B820D18F20D0BED182D0BCD" + + "0B5D187D0B0D18E20D0B2D181D0B520D18DD182D0B820D186D0B8D184D180D18B20D0B820D187D0B8D181D0BBD0B02E20D09CD0BDD0" + + "B52C20D0BAD0BED0BDD0B5D187D0BDD0BE2C20D0B2D181D19120D180D0B0D0B2D0BDD0BE20D0B1D183D0B4D0B5D1822C20D0BDD0BE2" + + "0D182D0B5D0BFD0B5D180D18C2028D0B82C20D0BCD0BED0B6D0B5D18220D0B1D18BD182D18C2C20D182D0BED0BBD18CD0BAD0BE20D0" + + "B220D18DD182D18320D0BCD0B8D0BDD183D182D1832920D18F20D0B6D0B5D0BBD0B0D18E2C20D187D182D0BED0B1D18B20D182D0B52" + + "C20D0BAD0BED182D0BED180D18BD0B520D0B1D183D0B4D183D18220D181D183D0B4D0B8D182D18C20D0BCD0BED0B920D0BFD0BED181" + + "D182D183D0BFD0BED0BA2C20D0BCD0BED0B3D0BBD0B820D18FD181D0BDD0BE20D0B2D0B8D0B4D0B5D182D18C2C20D0B8D0B720D0BAD" + + "0B0D0BAD0BED0B920D0BBD0BED0B3D0B8D187D0B5D181D0BAD0BED0B920D186D0B5D0BFD0B820D0B2D18BD0B2D0BED0B4D0BED0B220" + + "D0B2D18BD188D0BBD0BE20D0BCD0BED0B520E2809ED0BFD0BED181D0BBD0B5D0B4D0BDD0B5D0B520D183D0B1D0B5D0B6D0B4D0B5D0B" + + "DD0B8D0B5E2809C2E20D0AF20D0BDD0B0D0BFD0B8D181D0B0D0BB20D181D0B5D0B9D187D0B0D18120D0B2D18BD188D0B52C20D187D1" + + "82D0BE20D0BED0BAD0BED0BDD187D0B0D182D0B5D0BBD18CD0BDD0B0D18F20D180D0B5D188D0B8D0BCD0BED181D182D18C2C20D0BAD" + + "0BED182D0BED180D0BED0B920D0BDD0B5D0B4D0BED181D182D0B0D0B2D0B0D0BBD0BE20D0BCD0BDD0B520D0B4D0BBD18F20D0B8D181" + + "D0BFD0BED0BBD0BDD0B5D0BDD0B8D18F20D0BCD0BED0B5D0B3D0BE20E2809ED0BFD0BED181D0BBD0B5D0B4D0BDD0B5D0B3D0BE20D18" + + "3D0B1D0B5D0B6D0B4D0B5D0BDD0B8D18FE2809C2C20D0BFD180D0BED0B8D0B7D0BED188D0BBD0B020D0B2D0BE20D0BCD0BDD0B52C20" + + "D0BAD0B0D0B6D0B5D182D181D18F2C20D0B2D0BED0B2D181D0B520D0BDD0B520D0B8D0B720D0BBD0BED0B3D0B8D187D0B5D181D0BAD" + + "0BED0B3D0BE20D0B2D18BD0B2D0BED0B4D0B02C20D0B020D0BED18220D0BAD0B0D0BAD0BED0B3D0BE2DD182D0BE20D181D182D180D0" + + "B0D0BDD0BDD0BED0B3D0BE20D182D0BED0BBD187D0BAD0B02C20D0BED18220D0BED0B4D0BDD0BED0B3D0BE20D181D182D180D0B0D0B" + + "DD0BDD0BED0B3D0BE20D0BED0B1D181D182D0BED18FD182D0B5D0BBD18CD181D182D0B2D0B02C20D0BCD0BED0B6D0B5D18220D0B1D1" + + "8BD182D18C20D0B2D0BED0B2D181D0B520D0BDD0B520D181D0B2D18FD0B7D0B0D0BDD0BDD0BED0B3D0BE20D0BDD0B8D187D0B5D0BC2" + + "0D18120D185D0BED0B4D0BED0BC20D0B4D0B5D0BBD0B02E20D094D0BDD0B5D0B920D0B4D0B5D181D18FD182D18C20D0BDD0B0D0B7D0" + + "B0D0B420D0B7D0B0D188D0B5D0BB20D0BAD0BE20D0BCD0BDD0B520D0A0D0BED0B3D0BED0B6D0B8D0BD2C20D0BFD0BE20D0BED0B4D0B" + + "DD0BED0BCD18320D181D0B2D0BED0B5D0BCD18320D0B4D0B5D0BBD1832C20D0BE20D0BAD0BED182D0BED180D0BED0BC20D0B7D0B4D0" + + "B5D181D18C20D0BBD0B8D188D0BDD0B5D0B520D180D0B0D181D0BFD180D0BED181D182D180D0B0D0BDD18FD182D18CD181D18F2E20D" + + "0AF20D0BDD0B8D0BAD0BED0B3D0B4D0B020D0BDD0B520D0B2D0B8D0B4D0B0D0BB20D0A0D0BED0B3D0BED0B6D0B8D0BDD0B020D0BFD1" + + "80D0B5D0B6D0B4D0B52C20D0BDD0BE20D181D0BBD18BD188D0B0D0BB20D0BE20D0BDD0B5D0BC20D0BED187D0B5D0BDD18C20D0BCD0B" + + "DD0BED0B3D0BED0B52E20D0AF20D0B4D0B0D0BB20D0B5D0BCD18320D0B2D181D0B520D0BDD183D0B6D0BDD18BD0B520D181D0BFD180" + + "D0B0D0B2D0BAD0B82C20D0B820D0BED0BD20D181D0BAD0BED180D0BE20D183D188D0B5D0BB2C20D0B020D182D0B0D0BA20D0BAD0B0D" + + "0BA20D0BED0BD20D0B820D0BFD180D0B8D185D0BED0B4D0B8D0BB20D182D0BED0BBD18CD0BAD0BE20D0B7D0B020D181D0BFD180D0B0" + + "D0B2D0BAD0B0D0BCD0B82C20D182D0BE20D182D0B5D0BC20D0B1D18B20D0B4D0B5D0BBD0BE20D0BCD0B5D0B6D0B4D18320D0BDD0B0D" + + "0BCD0B820D0B820D0BAD0BED0BDD187D0B8D0BBD0BED181D18C2E20D09DD0BE20D0BED0BD20D181D0BBD0B8D188D0BAD0BED0BC20D0" + + "B7D0B0D0B8D0BDD182D0B5D180D0B5D181D0BED0B2D0B0D0BB20D0BCD0B5D0BDD18F2C20D0B820D0B2D0B5D181D18C20D18DD182D0B" + + "ED18220D0B4D0B5D0BDD18C20D18F20D0B1D18BD0BB20D0BFD0BED0B420D0B2D0BBD0B8D18FD0BDD0B8D0B5D0BC20D181D182D180D0" + + "B0D0BDD0BDD18BD18520D0BCD18BD181D0BBD0B5D0B92C20D182D0B0D0BA20D187D182D0BE20D180D0B5D188D0B8D0BBD181D18F20D" + + "0BFD0BED0B9D182D0B820D0BA20D0BDD0B5D0BCD18320D0BDD0B020D0B4D180D183D0B3D0BED0B920D0B4D0B5D0BDD18C20D181D0B0" + + "D0BC2C20D0BED182D0B4D0B0D182D18C20D0B2D0B8D0B7D0B8D1822E20D0A0D0BED0B3D0BED0B6D0B8D0BD20D0B1D18BD0BB20D0BCD" + + "0BDD0B520D0BED187D0B5D0B2D0B8D0B4D0BDD0BE20D0BDD0B520D180D0B0D0B420D0B820D0B4D0B0D0B6D0B520E2809ED0B4D0B5D0" + + "BBD0B8D0BAD0B0D182D0BDD0BEE2809C20D0BDD0B0D0BCD0B5D0BAD0BDD183D0BB2C20D187D182D0BE20D0BDD0B0D0BC20D0BDD0B5D" + + "187D0B5D0B3D0BE20D0BFD180D0BED0B4D0BED0BBD0B6D0B0D182D18C20D0B7D0BDD0B0D0BAD0BED0BCD181D182D0B2D0BE3B20D0BD" + + "D0BE20D0B2D181D0B52DD182D0B0D0BAD0B820D18F20D0BFD180D0BED0B2D0B5D0BB20D0BED187D0B5D0BDD18C20D0BBD18ED0B1D0B" + + "ED0BFD18BD182D0BDD18BD0B920D187D0B0D1812C20D0BAD0B0D0BA2C20D0B2D0B5D180D0BED18FD182D0BDD0BE2C20D0B820D0BED0" + + "BD2E20D09CD0B5D0B6D0B4D18320D0BDD0B0D0BCD0B820D0B1D18BD0BB20D182D0B0D0BAD0BED0B920D0BAD0BED0BDD182D180D0B0D" + + "181D1822C20D0BAD0BED182D0BED180D18BD0B920D0BDD0B520D0BCD0BED0B320D0BDD0B520D181D0BAD0B0D0B7D0B0D182D18CD181" + + "D18F20D0BDD0B0D0BC20D0BED0B1D0BED0B8D0BC2C20D0BED181D0BED0B1D0B5D0BDD0BDD0BE20D0BCD0BDD0B53A20D18F20D0B1D18" + + "BD0BB20D187D0B5D0BBD0BED0B2D0B5D0BA2C20D183D0B6D0B520D181D0BED181D187D0B8D182D0B0D0B2D188D0B8D0B920D0B4D0BD" + + "D0B820D181D0B2D0BED0B82C20D0B020D0BED0BD202D20D0B6D0B8D0B2D183D189D0B8D0B920D181D0B0D0BCD0BED18E20D0BFD0BED" + + "0BBD0BDD0BED18E2C20D0BDD0B5D0BFD0BED181D180D0B5D0B4D181D182D0B2D0B5D0BDD0BDD0BED18E20D0B6D0B8D0B7D0BDD18CD1" + + "8E2C20D0BDD0B0D181D182D0BED18FD189D0B5D18E20D0BCD0B8D0BDD183D182D0BED0B92C20D0B1D0B5D0B720D0B2D181D18FD0BAD" + + "0BED0B920D0B7D0B0D0B1D0BED182D18B20D0BE20E2809ED0BFD0BED181D0BBD0B5D0B4D0BDD0B8D185E2809C20D0B2D18BD0B2D0BE" + + "D0B4D0B0D1852C20D186D0B8D184D180D0B0D18520D0B8D0BBD0B820D0BE20D187D0B5D0BC20D0B1D18B20D182D0BE20D0BDD0B820D" + + "0B1D18BD0BBD0BE2C20D0BDD0B520D0BAD0B0D181D0B0D18ED189D0B5D0BCD181D18F20D182D0BED0B3D0BE2C20D0BDD0B020D187D0" + + "B5D0BC2E2E2E20D0BDD0B020D187D0B5D0BC2E2E2E20D0BDD18320D185D0BED182D18C20D0BDD0B020D187D0B5D0BC20D0BED0BD20D" + + "0BFD0BED0BCD0B5D188D0B0D0BD3B20D0BFD183D181D182D18C20D0BFD180D0BED181D182D0B8D18220D0BCD0BDD0B520D18DD182D0" + + "BE20D0B2D18BD180D0B0D0B6D0B5D0BDD0B8D0B520D0B3D0BED181D0BFD0BED0B4D0B8D0BD20D0A0D0BED0B3D0BED0B6D0B8D0BD2C2" + + "0D0BFD0BED0B6D0B0D0BBD183D0B920D185D0BED182D18C20D0BAD0B0D0BA20D0BFD0BBD0BED185D0BED0BCD18320D0BBD0B8D182D0" + + "B5D180D0B0D182D0BED180D1832C20D0BDD0B520D183D0BCD0B5D0B2D188D0B5D0BCD18320D0B2D18BD180D0B0D0B7D0B8D182D18C2" + + "0D181D0B2D0BED18E20D0BCD18BD181D0BBD18C2E20D09DD0B5D181D0BCD0BED182D180D18F20D0BDD0B020D0B2D181D18E20D0B5D0" + + "B3D0BE20D0BDD0B5D0BBD18ED0B1D0B5D0B7D0BDD0BED181D182D18C2C20D0BCD0BDD0B520D0BFD0BED0BAD0B0D0B7D0B0D0BBD0BED" + + "181D18C2C20D187D182D0BE20D0BED0BD20D187D0B5D0BBD0BED0B2D0B5D0BA20D18120D183D0BCD0BED0BC20D0B820D0BCD0BED0B6" + + "D0B5D18220D0BCD0BDD0BED0B3D0BED0B520D0BFD0BED0BDD0B8D0BCD0B0D182D18C2C20D185D0BED182D18F20D0B5D0B3D0BE20D0B" + + "CD0B0D0BBD0BE20D187D182D0BE20D0B8D0BDD182D0B5D180D0B5D181D183D0B5D18220D0B8D0B720D0BFD0BED181D182D0BED180D0" + + "BED0BDD0BDD0B5D0B3D0BE2E20D0AF20D0BDD0B520D0BDD0B0D0BCD0B5D0BAD0B0D0BB20D0B5D0BCD18320D0BE20D0BCD0BED0B5D0B" + + "C20E2809ED0BFD0BED181D0BBD0B5D0B4D0BDD0B5D0BC20D183D0B1D0B5D0B6D0B4D0B5D0BDD0B8D0B8E2809C2C20D0BDD0BE20D0BC" + + "D0BDD0B520D0BFD0BED187D0B5D0BCD1832DD182D0BE20D0BFD0BED0BAD0B0D0B7D0B0D0BBD0BED181D18C2C20D187D182D0BE20D0B" + + "ED0BD2C20D181D0BBD183D188D0B0D18F20D0BCD0B5D0BDD18F2C20D183D0B3D0B0D0B4D0B0D0BB20D0B5D0B3D0BE2E20D09ED0BD20" + + "D0BFD180D0BED0BCD0BED0BBD187D0B0D0BB2C20D0BED0BD20D183D0B6D0B0D181D0BDD0BE20D0BCD0BED0BBD187D0B0D0BBD0B8D0B" + + "22E20D0AF20D0BDD0B0D0BCD0B5D0BAD0BDD183D0BB20D0B5D0BCD1832C20D183D185D0BED0B4D18F2C20D187D182D0BE2C20D0BDD0" + + "B5D181D0BCD0BED182D180D18F20D0BDD0B020D0B2D181D18E20D0BCD0B5D0B6D0B4D18320D0BDD0B0D0BCD0B820D180D0B0D0B7D0B" + + "DD0B8D186D18320D0B820D0BDD0B020D0B2D181D0B520D0BFD180D0BED182D0B8D0B2D0BED0BFD0BED0BBD0BED0B6D0BDD0BED181D1" + + "82D0B82C202D206C65732065787472C3A96D6974C3A97320736520746F756368656E7420312028D18F20D180D0B0D181D182D0BED0B" + + "BD0BAD0BED0B2D0B0D0BB20D0B5D0BCD18320D18DD182D0BE20D0BFD0BE2DD180D183D181D181D0BAD0B8292C20D182D0B0D0BA20D1" + + "87D182D0BE2C20D0BCD0BED0B6D0B5D18220D0B1D18BD182D18C2C20D0BED0BD20D0B820D181D0B0D0BC20D0B2D0BED0B2D181D0B52" + + "0D0BDD0B520D182D0B0D0BA20D0B4D0B0D0BBD0B5D0BA20D0BED18220D0BCD0BED0B5D0B3D0BE20E2809ED0BFD0BED181D0BBD0B5D0" + + "B4D0BDD0B5D0B3D0BE20D183D0B1D0B5D0B6D0B4D0B5D0BDD0B8D18FE2809C2C20D0BAD0B0D0BA20D0BAD0B0D0B6D0B5D182D181D18" + + "F2E20D09DD0B020D18DD182D0BE20D0BED0BD20D0BED182D0B2D0B5D182D0B8D0BB20D0BCD0BDD0B520D0BED187D0B5D0BDD18C20D1" + + "83D0B3D180D18ED0BCD0BED18E20D0B820D0BAD0B8D181D0BBD0BED18E20D0B3D180D0B8D0BCD0B0D181D0BED0B92C20D0B2D181D18" + + "2D0B0D0BB2C20D181D0B0D0BC20D181D18BD181D0BAD0B0D0BB20D0BCD0BDD0B520D0BCD0BED18E20D184D183D180D0B0D0B6D0BAD1" + + "832C20D181D0B4D0B5D0BBD0B0D0B220D0B2D0B8D0B42C20D0B1D183D0B4D182D0BE20D0B1D18B20D18F20D181D0B0D0BC20D183D18" + + "5D0BED0B6D1832C20D0B820D0BFD180D0BED181D182D0BE2DD0B7D0B0D0BFD180D0BED181D182D0BE20D0B2D18BD0B2D0B5D0BB20D0" + + "BCD0B5D0BDD18F20D0B8D0B720D181D0B2D0BED0B5D0B3D0BE20D0BCD180D0B0D187D0BDD0BED0B3D0BE20D0B4D0BED0BCD0B020D0B" + + "FD0BED0B420D0B2D0B8D0B4D0BED0BC20D182D0BED0B3D0BE2C20D187D182D0BE20D0BFD180D0BED0B2D0BED0B6D0B0D0B5D18220D0" + + "BCD0B5D0BDD18F20D0B8D0B720D183D187D182D0B8D0B2D0BED181D182D0B82E20D094D0BED0BC20D0B5D0B3D0BE20D0BFD0BED180D" + + "0B0D0B7D0B8D0BB20D0BCD0B5D0BDD18F3B20D0BFD0BED185D0BED0B620D0BDD0B020D0BAD0BBD0B0D0B4D0B1D0B8D189D0B52C20D0" + + "B020D0B5D0BCD1832C20D0BAD0B0D0B6D0B5D182D181D18F2C20D0BDD180D0B0D0B2D0B8D182D181D18F2C20D187D182D0BE2C20D0B" + + "2D0BFD180D0BED187D0B5D0BC2C20D0BFD0BED0BDD18FD182D0BDD0BE3A20D182D0B0D0BAD0B0D18F20D0BFD0BED0BBD0BDD0B0D18F" + + "2C20D0BDD0B5D0BFD0BED181D180D0B5D0B4D181D182D0B2D0B5D0BDD0BDD0B0D18F20D0B6D0B8D0B7D0BDD18C2C20D0BAD0BED182D" + + "0BED180D0BED18E20D0BED0BD20D0B6D0B8D0B2D0B5D1822C20D181D0BBD0B8D188D0BAD0BED0BC20D0BFD0BED0BBD0BDD0B020D181" + + "D0B0D0BCD0B020D0BFD0BE20D181D0B5D0B1D0B52C20D187D182D0BED0B1D18B20D0BDD183D0B6D0B4D0B0D182D18CD181D18F20D0B" + + "220D0BED0B1D181D182D0B0D0BDD0BED0B2D0BAD0B52E"; +}