/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2012-2013 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.json;
import javax.json.JsonException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
/**
* A filter stream that detects the unicode encoding for the original
* stream
*
* @author Jitendra Kotamraju
*/
class UnicodeDetectingInputStream extends FilterInputStream {
private static final Charset UTF_8 = Charset.forName("UTF-8");
private static final Charset UTF_16BE = Charset.forName("UTF-16BE");
private static final Charset UTF_16LE = Charset.forName("UTF-16LE");
private static final Charset UTF_32LE = Charset.forName("UTF-32LE");
private static final Charset UTF_32BE = Charset.forName("UTF-32BE");
private static final byte FF = (byte)0xFF;
private static final byte FE = (byte)0xFE;
private static final byte EF = (byte)0xEF;
private static final byte BB = (byte)0xBB;
private static final byte BF = (byte)0xBF;
private static final byte NUL = (byte)0x00;
private final byte[] buf = new byte[4];
private int bufLen;
private int curIndex;
private final Charset charset;
UnicodeDetectingInputStream(InputStream is) {
super(is);
charset = detectEncoding();
}
Charset getCharset() {
return charset;
}
private void fillBuf() {
int b1;
int b2;
int b3;
int b4;
try {
b1 = in.read();
if (b1 == -1) {
return;
}
b2 = in.read();
if (b2 == -1) {
bufLen = 1;
buf[0] = (byte)b1;
return;
}
b3 = in.read();
if (b3 == -1) {
bufLen = 2;
buf[0] = (byte)b1;
buf[1] = (byte)b2;
return;
}
b4 = in.read();
if (b4 == -1) {
bufLen = 3;
buf[0] = (byte)b1;
buf[1] = (byte)b2;
buf[2] = (byte)b3;
return;
}
bufLen = 4;
buf[0] = (byte)b1;
buf[1] = (byte)b2;
buf[2] = (byte)b3;
buf[3] = (byte)b4;
} catch (IOException ioe) {
throw new JsonException("I/O error while auto-detecting the encoding of stream", ioe);
}
}
private Charset detectEncoding() {
fillBuf();
if (bufLen < 2) {
throw new JsonException("Cannot auto-detect encoding, not enough chars");
} else if (bufLen == 4) {
// Use BOM to detect encoding
if (buf[0] == NUL && buf[1] == NUL && buf[2] == FE && buf[3] == FF) {
curIndex = 4;
return UTF_32BE;
} else if (buf[0] == FF && buf[1] == FE && buf[2] == NUL && buf[3] == NUL) {
curIndex = 4;
return UTF_32LE;
} else if (buf[0] == FE && buf[1] == FF) {
curIndex = 2;
return UTF_16BE;
} else if (buf[0] == FF && buf[1] == FE) {
curIndex = 2;
return UTF_16LE;
} else if (buf[0] == EF && buf[1] == BB && buf[2] == BF) {
curIndex = 3;
return UTF_8;
}
// No BOM, just use JSON RFC's encoding algo to auto-detect
if (buf[0] == NUL && buf[1] == NUL && buf[2] == NUL) {
return UTF_32BE;
} else if (buf[0] == NUL && buf[2] == NUL) {
return UTF_16BE;
} else if (buf[1] == NUL && buf[2] == NUL && buf[3] == NUL) {
return UTF_32LE;
} else if (buf[1] == NUL && buf[3] == NUL) {
return UTF_16LE;
}
}
return UTF_8;
}
@Override
public int read() throws IOException {
if (curIndex < bufLen) {
return buf[curIndex++];
}
return in.read();
}
@Override
public int read(byte b[], int off, int len) throws IOException {
if (curIndex < bufLen) {
if (len == 0) {
return 0;
}
if (off < 0 || len < 0 || len > b.length -off) {
throw new IndexOutOfBoundsException();
}
int min = Math.min(bufLen-curIndex, len);
System.arraycopy(buf, curIndex, b, off, min);
curIndex += min;
return min;
}
return in.read(b, off, len);
}
}