UTF-8 support test passes

This commit is contained in:
konsoletyper 2015-03-22 15:57:49 +03:00
parent 0be769f74d
commit cc5225a2a6
10 changed files with 747 additions and 98 deletions

View File

@ -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<TCharset> {
private String canonicalName;
private String[] aliases;
private Set<String> aliasSet;
private static final Map<String, TCharset> 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<TCharset> {
throw new IllegalArgumentException("charsetName is null");
}
checkCanonicalName(charsetName);
switch (charsetName.toUpperCase()) {
case "UTF-8":
return new UTF8Charset();
default:
TCharset charset = charsets.get(charsetName.toUpperCase());
if (charset == null) {
throw new TUnsupportedCharsetException(charsetName);
}
return charset;
}
public final String name() {

View File

@ -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());
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}
}
}

View File

@ -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";
}