/* UTF_8.java -- Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. This file is part of GNU Classpath. GNU Classpath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. GNU Classpath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Classpath; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Linking this library statically or dynamically with other modules is making a combined work based on this library. Thus, the terms and conditions of the GNU General Public License cover the whole combination. As a special exception, the copyright holders of this library give you permission to link this library with independent modules to produce an executable, regardless of the license terms of these independent modules, and to copy and distribute the resulting executable under terms of your choice, provided that you also meet, for each linked independent module, the terms and conditions of the license of that module. An independent module is a module which is not derived from or based on this library. If you modify this library, you may extend this exception to your version of the library, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ package gnu.java.nio.charset; 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; /** * UTF-8 charset. * * <p> UTF-8 references: * <ul> * <li> <a href="http://ietf.org/rfc/rfc2279.txt">RFC 2279</a> * <li> The <a href="http://www.unicode.org/unicode/standard/standard.html"> * Unicode standard</a> and * <a href="http://www.unicode.org/versions/corrigendum1.html"> * Corrigendum</a> * </ul> * * @author Jesse Rosenstock */ final class UTF_8 extends Charset { UTF_8 () { super ("UTF-8", new String[] { /* These names are provided by * http://oss.software.ibm.com/cgi-bin/icu/convexp?s=ALL */ "ibm-1208", "ibm-1209", "ibm-5304", "ibm-5305", "windows-65001", "cp1208", // see http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html "UTF8" }); } public boolean contains (Charset cs) { return cs instanceof US_ASCII || cs instanceof ISO_8859_1 || cs instanceof UTF_8 || cs instanceof UTF_16BE || cs instanceof UTF_16LE || cs instanceof UTF_16; } public CharsetDecoder newDecoder () { return new Decoder (this); } public CharsetEncoder newEncoder () { return new Encoder (this); } private static final class Decoder extends CharsetDecoder { // Package-private to avoid a trampoline constructor. Decoder (Charset cs) { super (cs, 1f, 1f); } protected CoderResult decodeLoop (ByteBuffer in, CharBuffer out) { // TODO: Optimize this in the case in.hasArray() / out.hasArray() int inPos = in.position(); try { while (in.hasRemaining ()) { char c; byte b1 = in.get (); int highNibble = ((b1 & 0xFF) >> 4) & 0xF; switch (highNibble) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: if (out.remaining () < 1) return CoderResult.OVERFLOW; out.put ((char) b1); inPos++; break; case 0xC: case 0xD: byte b2; if (in.remaining () < 1) return CoderResult.UNDERFLOW; if (out.remaining () < 1) return CoderResult.OVERFLOW; if (!isContinuation (b2 = in.get ())) return CoderResult.malformedForLength (1); c = (char) (((b1 & 0x1F) << 6) | (b2 & 0x3F)); // check that we had the shortest encoding if (c <= 0x7F) return CoderResult.malformedForLength (2); out.put (c); inPos += 2; break; case 0xE: byte b3; if (in.remaining () < 2) return CoderResult.UNDERFLOW; if (out.remaining () < 1) return CoderResult.OVERFLOW; if (!isContinuation (b2 = in.get ())) return CoderResult.malformedForLength (1); if (!isContinuation (b3 = in.get ())) return CoderResult.malformedForLength (1); c = (char) (((b1 & 0x0F) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F)); // check that we had the shortest encoding if (c <= 0x7FF) return CoderResult.malformedForLength (3); out.put (c); inPos += 3; break; case 0xF: byte b4; if (in.remaining () < 3) return CoderResult.UNDERFLOW; if((b1&0x0F) > 4) return CoderResult.malformedForLength (4); if (out.remaining () < 2) return CoderResult.OVERFLOW; if (!isContinuation (b2 = in.get ())) return CoderResult.malformedForLength (3); if (!isContinuation (b3 = in.get ())) return CoderResult.malformedForLength (2); if (!isContinuation (b4 = in.get ())) return CoderResult.malformedForLength (1); int n = (((b1 & 0x3) << 18) | ((b2 & 0x3F) << 12) | ((b3 & 0x3F) << 6) | (b4 & 0x3F)) - 0x10000; char c1 = (char)(0xD800 | (n & 0xFFC00)>>10); char c2 = (char)(0xDC00 | (n & 0x003FF)); out.put (c1); out.put (c2); inPos += 4; break; default: return CoderResult.malformedForLength (1); } } return CoderResult.UNDERFLOW; } finally { // In case we did a get(), then encountered an error, reset the // position to before the error. If there was no error, this // will benignly reset the position to the value it already has. in.position (inPos); } } private static boolean isContinuation (byte b) { return (b & 0xC0) == 0x80; } } private static final class Encoder extends CharsetEncoder { // Package-private to avoid a trampoline constructor. Encoder (Charset cs) { // According to // http://www-106.ibm.com/developerworks/unicode/library/utfencodingforms/index.html // On average, English takes slightly over one unit per code point. // Most Latin-script languages take about 1.1 bytes. Greek, Russian, // Arabic and Hebrew take about 1.7 bytes, and most others (including // Japanese, Chinese, Korean and Hindi) take about 3 bytes. // We assume we will be dealing with latin scripts, and use 1.1 // for averageBytesPerChar. super (cs, 1.1f, 4.0f); } protected CoderResult encodeLoop (CharBuffer in, ByteBuffer out) { int inPos = in.position(); try { // TODO: Optimize this in the case in.hasArray() / out.hasArray() while (in.hasRemaining ()) { int remaining = out.remaining (); char c = in.get (); // UCS-4 range (hex.) UTF-8 octet sequence (binary) // 0000 0000-0000 007F 0xxxxxxx // 0000 0080-0000 07FF 110xxxxx 10xxxxxx // 0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx // Scalar Value UTF-16 byte 1 byte 2 byte 3 byte 4 // 0000 0000 0xxx xxxx 0000 0000 0xxx xxxx 0xxx xxxx // 0000 0yyy yyxx xxxx 0000 0yyy yyxx xxxx 110y yyyy 10xx xxxx // zzzz yyyy yyxx xxxx zzzz yyyy yyxx xxxx 1110 zzzz 10yy yyyy 10xx xxxx // u uuuu zzzz yyyy yyxx xxxx 1101 10ww wwzz zzyy 1111 0uuu 10uu zzzz 10yy yyyy 10xx xxxx // + 1101 11yy yyxx xxxx // Note: uuuuu = wwww + 1 if (c <= 0x7F) { if (remaining < 1) return CoderResult.OVERFLOW; out.put ((byte) c); inPos++; } else if (c <= 0x7FF) { if (remaining < 2) return CoderResult.OVERFLOW; out.put ((byte) (0xC0 | (c >> 6))); out.put ((byte) (0x80 | (c & 0x3F))); inPos++; } else if (0xD800 <= c && c <= 0xDFFF) { if (remaining < 4) return CoderResult.OVERFLOW; // we got a low surrogate without a preciding high one if (c > 0xDBFF) return CoderResult.malformedForLength (1); // high surrogates if (!in.hasRemaining ()) return CoderResult.UNDERFLOW; char d = in.get (); // make sure d is a low surrogate if (d < 0xDC00 || d > 0xDFFF) return CoderResult.malformedForLength (1); // make the 32 bit value // int value2 = (c - 0xD800) * 0x400 + (d - 0xDC00) + 0x10000; int value = (((c & 0x3FF) << 10) | (d & 0x3FF)) + 0x10000; // assert value == value2; out.put ((byte) (0xF0 | ((value >> 18) & 0x07))); out.put ((byte) (0x80 | ((value >> 12) & 0x3F))); out.put ((byte) (0x80 | ((value >> 6) & 0x3F))); out.put ((byte) (0x80 | ((value ) & 0x3F))); inPos += 2; } else { if (remaining < 3) return CoderResult.OVERFLOW; out.put ((byte) (0xE0 | (c >> 12))); out.put ((byte) (0x80 | ((c >> 6) & 0x3F))); out.put ((byte) (0x80 | (c & 0x3F))); inPos++; } } return CoderResult.UNDERFLOW; } finally { // In case we did a get(), then encountered an error, reset the // position to before the error. If there was no error, this // will benignly reset the position to the value it already has. in.position (inPos); } } } }