| /* |
| * 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 java.io; |
| |
| import java.nio.ByteBuffer; |
| import java.nio.CharBuffer; |
| import java.nio.charset.Charset; |
| import java.nio.charset.CharsetDecoder; |
| import java.nio.charset.CoderResult; |
| import java.nio.charset.CodingErrorAction; |
| import java.nio.charset.MalformedInputException; |
| import java.nio.charset.UnmappableCharacterException; |
| import java.security.AccessController; |
| import java.util.HashMap; |
| |
| import org.apache.harmony.luni.util.Msg; |
| import org.apache.harmony.luni.util.PriviAction; |
| |
| // BEGIN android-note |
| // Later changes from Harmony have been integrated into this version. |
| // END android-note |
| |
| /** |
| * A class for turning a byte stream into a character stream. Data read from the |
| * source input stream is converted into characters by either a default or a |
| * provided character converter. The default encoding is taken from the |
| * "file.encoding" system property. {@code InputStreamReader} contains a buffer |
| * of bytes read from the source stream and converts these into characters as |
| * needed. The buffer size is 8K. |
| * |
| * @see OutputStreamWriter |
| * |
| * @since Android 1.0 |
| */ |
| public class InputStreamReader extends Reader { |
| private InputStream in; |
| |
| private static final int BUFFER_SIZE = 8192; |
| |
| private boolean endOfInput = false; |
| |
| CharsetDecoder decoder; |
| |
| ByteBuffer bytes = ByteBuffer.allocate(BUFFER_SIZE); |
| |
| /** |
| * Constructs a new {@code InputStreamReader} on the {@link InputStream} |
| * {@code in}. This constructor sets the character converter to the encoding |
| * specified in the "file.encoding" property and falls back to ISO 8859_1 |
| * (ISO-Latin-1) if the property doesn't exist. |
| * |
| * @param in |
| * the input stream from which to read characters. |
| * @since Android 1.0 |
| */ |
| public InputStreamReader(InputStream in) { |
| super(in); |
| this.in = in; |
| String encoding = AccessController |
| .doPrivileged(new PriviAction<String>( |
| "file.encoding", "ISO8859_1")); //$NON-NLS-1$//$NON-NLS-2$ |
| decoder = Charset.forName(encoding).newDecoder().onMalformedInput( |
| CodingErrorAction.REPLACE).onUnmappableCharacter( |
| CodingErrorAction.REPLACE); |
| bytes.limit(0); |
| } |
| |
| /** |
| * Constructs a new InputStreamReader on the InputStream {@code in}. The |
| * character converter that is used to decode bytes into characters is |
| * identified by name by {@code enc}. If the encoding cannot be found, an |
| * UnsupportedEncodingException error is thrown. |
| * |
| * @param in |
| * the InputStream from which to read characters. |
| * @param enc |
| * identifies the character converter to use. |
| * @throws NullPointerException |
| * if {@code enc} is {@code null}. |
| * @throws UnsupportedEncodingException |
| * if the encoding specified by {@code enc} cannot be found. |
| * @since Android 1.0 |
| */ |
| public InputStreamReader(InputStream in, final String enc) |
| throws UnsupportedEncodingException { |
| super(in); |
| if (enc == null) { |
| throw new NullPointerException(); |
| } |
| this.in = in; |
| try { |
| decoder = Charset.forName(enc).newDecoder().onMalformedInput( |
| CodingErrorAction.REPLACE).onUnmappableCharacter( |
| CodingErrorAction.REPLACE); |
| } catch (IllegalArgumentException e) { |
| throw (UnsupportedEncodingException) |
| new UnsupportedEncodingException().initCause(e); |
| } |
| bytes.limit(0); |
| } |
| |
| /** |
| * Constructs a new InputStreamReader on the InputStream {@code in} and |
| * CharsetDecoder {@code dec}. |
| * |
| * @param in |
| * the source InputStream from which to read characters. |
| * @param dec |
| * the CharsetDecoder used by the character conversion. |
| * @since Android 1.0 |
| */ |
| public InputStreamReader(InputStream in, CharsetDecoder dec) { |
| super(in); |
| dec.averageCharsPerByte(); |
| this.in = in; |
| decoder = dec; |
| bytes.limit(0); |
| } |
| |
| /** |
| * Constructs a new InputStreamReader on the InputStream {@code in} and |
| * Charset {@code charset}. |
| * |
| * @param in |
| * the source InputStream from which to read characters. |
| * @param charset |
| * the Charset that defines the character converter |
| * @since Android 1.0 |
| */ |
| public InputStreamReader(InputStream in, Charset charset) { |
| super(in); |
| this.in = in; |
| decoder = charset.newDecoder().onMalformedInput( |
| CodingErrorAction.REPLACE).onUnmappableCharacter( |
| CodingErrorAction.REPLACE); |
| bytes.limit(0); |
| } |
| |
| /** |
| * Closes this reader. This implementation closes the source InputStream and |
| * releases all local storage. |
| * |
| * @throws IOException |
| * if an error occurs attempting to close this reader. |
| * @since Android 1.0 |
| */ |
| @Override |
| public void close() throws IOException { |
| synchronized (lock) { |
| // BEGIN android-added |
| if (decoder != null) { |
| decoder.reset(); |
| } |
| // END android-added |
| decoder = null; |
| if (in != null) { |
| in.close(); |
| in = null; |
| } |
| } |
| } |
| |
| /** |
| * Returns the name of the encoding used to convert bytes into characters. |
| * The value {@code null} is returned if this reader has been closed. |
| * |
| * @return the name of the character converter or {@code null} if this |
| * reader is closed. |
| * @since Android 1.0 |
| */ |
| public String getEncoding() { |
| if (!isOpen()) { |
| return null; |
| } |
| return HistoricalNamesUtil.getHistoricalName(decoder.charset().name()); |
| } |
| |
| /* |
| * helper for getEncoding() |
| */ |
| @SuppressWarnings("nls") |
| static class HistoricalNamesUtil { |
| private static HashMap<String, String> historicalNames = new HashMap<String, String>(); |
| static { |
| historicalNames.put("Big5-HKSCS", "Big5_HKSCS"); |
| historicalNames.put("EUC-JP", "EUC_JP"); |
| historicalNames.put("EUC-KR", "EUC_KR"); |
| historicalNames.put("GB2312", "EUC_CN"); |
| historicalNames.put("IBM-Thai", "Cp838"); |
| historicalNames.put("IBM00858", "Cp858"); |
| historicalNames.put("IBM01140", "Cp1140"); |
| historicalNames.put("IBM01141", "Cp1141"); |
| historicalNames.put("IBM01142", "Cp1142"); |
| historicalNames.put("IBM01143", "Cp1143"); |
| historicalNames.put("IBM01144", "Cp1144"); |
| historicalNames.put("IBM01145", "Cp1145"); |
| historicalNames.put("IBM01146", "Cp1146"); |
| historicalNames.put("IBM01147", "Cp1147"); |
| historicalNames.put("IBM01148", "Cp1148"); |
| historicalNames.put("IBM01149", "Cp1149"); |
| historicalNames.put("IBM037", "Cp037"); |
| historicalNames.put("IBM1026", "Cp1026"); |
| historicalNames.put("IBM1047", "Cp1047"); |
| historicalNames.put("IBM273", "Cp273"); |
| historicalNames.put("IBM277", "Cp277"); |
| historicalNames.put("IBM278", "Cp278"); |
| historicalNames.put("IBM280", "Cp280"); |
| historicalNames.put("IBM284", "Cp284"); |
| historicalNames.put("IBM285", "Cp285"); |
| historicalNames.put("IBM297", "Cp297"); |
| historicalNames.put("IBM420", "Cp420"); |
| historicalNames.put("IBM424", "Cp424"); |
| historicalNames.put("IBM437", "Cp437"); |
| historicalNames.put("IBM500", "Cp500"); |
| historicalNames.put("IBM775", "Cp775"); |
| historicalNames.put("IBM850", "Cp850"); |
| historicalNames.put("IBM852", "Cp852"); |
| historicalNames.put("IBM855", "Cp855"); |
| historicalNames.put("IBM857", "Cp857"); |
| historicalNames.put("IBM860", "Cp860"); |
| historicalNames.put("IBM861", "Cp861"); |
| historicalNames.put("IBM862", "Cp862"); |
| historicalNames.put("IBM863", "Cp863"); |
| historicalNames.put("IBM864", "Cp864"); |
| historicalNames.put("IBM865", "Cp865"); |
| historicalNames.put("IBM866", "Cp866"); |
| historicalNames.put("IBM868", "Cp868"); |
| historicalNames.put("IBM869", "Cp869"); |
| historicalNames.put("IBM870", "Cp870"); |
| historicalNames.put("IBM871", "Cp871"); |
| historicalNames.put("IBM918", "Cp918"); |
| historicalNames.put("ISO-2022-CN", "ISO2022CN"); |
| historicalNames.put("ISO-2022-JP", "ISO2022JP"); |
| historicalNames.put("ISO-2022-KR", "ISO2022KR"); |
| historicalNames.put("ISO-8859-1", "ISO8859_1"); |
| historicalNames.put("ISO-8859-13", "ISO8859_13"); |
| historicalNames.put("ISO-8859-15", "ISO8859_15"); |
| historicalNames.put("ISO-8859-2", "ISO8859_2"); |
| historicalNames.put("ISO-8859-3", "ISO8859_3"); |
| historicalNames.put("ISO-8859-4", "ISO8859_4"); |
| historicalNames.put("ISO-8859-5", "ISO8859_5"); |
| historicalNames.put("ISO-8859-6", "ISO8859_6"); |
| historicalNames.put("ISO-8859-7", "ISO8859_7"); |
| historicalNames.put("ISO-8859-8", "ISO8859_8"); |
| historicalNames.put("ISO-8859-9", "ISO8859_9"); |
| historicalNames.put("KOI8-R", "KOI8_R"); |
| historicalNames.put("Shift_JIS", "SJIS"); |
| historicalNames.put("TIS-620", "TIS620"); |
| historicalNames.put("US-ASCII", "ASCII"); |
| historicalNames.put("UTF-16BE", "UnicodeBigUnmarked"); |
| historicalNames.put("UTF-16LE", "UnicodeLittleUnmarked"); |
| historicalNames.put("UTF-8", "UTF8"); |
| historicalNames.put("windows-1250", "Cp1250"); |
| historicalNames.put("windows-1251", "Cp1251"); |
| historicalNames.put("windows-1252", "Cp1252"); |
| historicalNames.put("windows-1253", "Cp1253"); |
| historicalNames.put("windows-1254", "Cp1254"); |
| historicalNames.put("windows-1255", "Cp1255"); |
| historicalNames.put("windows-1256", "Cp1256"); |
| historicalNames.put("windows-1257", "Cp1257"); |
| historicalNames.put("windows-1258", "Cp1258"); |
| historicalNames.put("windows-31j", "MS932"); |
| historicalNames.put("x-Big5-Solaris", "Big5_Solaris"); |
| historicalNames.put("x-euc-jp-linux", "EUC_JP_LINUX"); |
| historicalNames.put("x-EUC-TW", "EUC_TW"); |
| historicalNames.put("x-eucJP-Open", "EUC_JP_Solaris"); |
| historicalNames.put("x-IBM1006", "Cp1006"); |
| historicalNames.put("x-IBM1025", "Cp1025"); |
| historicalNames.put("x-IBM1046", "Cp1046"); |
| historicalNames.put("x-IBM1097", "Cp1097"); |
| historicalNames.put("x-IBM1098", "Cp1098"); |
| historicalNames.put("x-IBM1112", "Cp1112"); |
| historicalNames.put("x-IBM1122", "Cp1122"); |
| historicalNames.put("x-IBM1123", "Cp1123"); |
| historicalNames.put("x-IBM1124", "Cp1124"); |
| historicalNames.put("x-IBM1381", "Cp1381"); |
| historicalNames.put("x-IBM1383", "Cp1383"); |
| historicalNames.put("x-IBM33722", "Cp33722"); |
| historicalNames.put("x-IBM737", "Cp737"); |
| historicalNames.put("x-IBM856", "Cp856"); |
| historicalNames.put("x-IBM874", "Cp874"); |
| historicalNames.put("x-IBM875", "Cp875"); |
| historicalNames.put("x-IBM921", "Cp921"); |
| historicalNames.put("x-IBM922", "Cp922"); |
| historicalNames.put("x-IBM930", "Cp930"); |
| historicalNames.put("x-IBM933", "Cp933"); |
| historicalNames.put("x-IBM935", "Cp935"); |
| historicalNames.put("x-IBM937", "Cp937"); |
| historicalNames.put("x-IBM939", "Cp939"); |
| historicalNames.put("x-IBM942", "Cp942"); |
| historicalNames.put("x-IBM942C", "Cp942C"); |
| historicalNames.put("x-IBM943", "Cp943"); |
| historicalNames.put("x-IBM943C", "Cp943C"); |
| historicalNames.put("x-IBM948", "Cp948"); |
| historicalNames.put("x-IBM949", "Cp949"); |
| historicalNames.put("x-IBM949C", "Cp949C"); |
| historicalNames.put("x-IBM950", "Cp950"); |
| historicalNames.put("x-IBM964", "Cp964"); |
| historicalNames.put("x-IBM970", "Cp970"); |
| historicalNames.put("x-ISCII91", "ISCII91"); |
| historicalNames.put("x-ISO-2022-CN-CNS", "ISO2022CN"); |
| historicalNames.put("x-ISO-2022-CN-GB", "ISO2022CN"); |
| historicalNames.put("x-JISAutoDetect", "JISAutoDetect"); |
| historicalNames.put("x-MacArabic", "MacArabic"); |
| historicalNames.put("x-MacCentralEurope", "MacCentralEurope"); |
| historicalNames.put("x-MacCroatian", "MacCroatian"); |
| historicalNames.put("x-MacCyrillic", "MacCyrillic"); |
| historicalNames.put("x-MacDingbat", "MacDingbat"); |
| historicalNames.put("x-MacGreek", "MacGreek"); |
| historicalNames.put("x-MacHebrew", "MacHebrew"); |
| historicalNames.put("x-MacIceland", "MacIceland"); |
| historicalNames.put("x-MacRoman", "MacRoman"); |
| historicalNames.put("x-MacRomania", "MacRomania"); |
| historicalNames.put("x-MacSymbol", "MacSymbol"); |
| historicalNames.put("x-MacThai", "MacThai"); |
| historicalNames.put("x-MacTurkish", "MacTurkish"); |
| historicalNames.put("x-MacUkraine", "MacUkraine"); |
| historicalNames.put("x-MS950-HKSCS", "MS950_HKSCS"); |
| historicalNames.put("x-mswin-936", "MS936"); |
| historicalNames.put("x-PCK", "PCK"); |
| historicalNames.put("x-windows-874", "MS874"); |
| historicalNames.put("x-windows-949", "MS949"); |
| historicalNames.put("x-windows-950", "MS950"); |
| } |
| |
| public static String getHistoricalName(String name) { |
| return (!historicalNames.containsKey(name) ? name : historicalNames |
| .get(name)); |
| } |
| } |
| |
| /** |
| * Reads a single character from this reader and returns it as an integer |
| * with the two higher-order bytes set to 0. Returns -1 if the end of the |
| * reader has been reached. The byte value is either obtained from |
| * converting bytes in this reader's buffer or by first filling the buffer |
| * from the source InputStream and then reading from the buffer. |
| * |
| * @return the character read or -1 if the end of the reader has been |
| * reached. |
| * @throws IOException |
| * if this reader is closed or some other I/O error occurs. |
| * @since Android 1.0 |
| */ |
| @Override |
| public int read() throws IOException { |
| synchronized (lock) { |
| if (!isOpen()) { |
| // K0070=InputStreamReader is closed. |
| throw new IOException(Msg.getString("K0070")); //$NON-NLS-1$ |
| } |
| |
| char buf[] = new char[1]; |
| return read(buf, 0, 1) != -1 ? buf[0] : -1; |
| } |
| } |
| |
| /** |
| * Reads at most {@code length} characters from this reader and stores them |
| * at position {@code offset} in the character array {@code buf}. Returns |
| * the number of characters actually read or -1 if the end of the reader has |
| * been reached. The bytes are either obtained from converting bytes in this |
| * reader's buffer or by first filling the buffer from the source |
| * InputStream and then reading from the buffer. |
| * |
| * @param buf |
| * the array to store the characters read. |
| * @param offset |
| * the initial position in {@code buf} to store the characters |
| * read from this reader. |
| * @param length |
| * the maximum number of characters to read. |
| * @return the number of characters read or -1 if the end of the reader has |
| * been reached. |
| * @throws IndexOutOfBoundsException |
| * if {@code offset < 0} or {@code length < 0}, or if |
| * {@code offset + length} is greater than the length of |
| * {@code buf}. |
| * @throws IOException |
| * if this reader is closed or some other I/O error occurs. |
| * @since Android 1.0 |
| */ |
| @Override |
| public int read(char[] buf, int offset, int length) throws IOException { |
| synchronized (lock) { |
| if (!isOpen()) { |
| // K0070=InputStreamReader is closed. |
| throw new IOException(Msg.getString("K0070")); //$NON-NLS-1$ |
| } |
| // BEGIN android-changed |
| // Exception priorities (in case of multiple errors) differ from |
| // RI, but are spec-compliant. |
| // made implicit null check explicit, used (offset | length) < 0 |
| // instead of (offset < 0) || (length < 0) to safe one operation |
| if (buf == null) { |
| throw new NullPointerException(Msg.getString("K0047")); //$NON-NLS-1$ |
| } |
| if ((offset | length) < 0 || offset > buf.length - length) { |
| throw new IndexOutOfBoundsException(Msg.getString("K002f")); //$NON-NLS-1$ |
| } |
| // END android-changed |
| if (length == 0) { |
| return 0; |
| } |
| |
| CharBuffer out = CharBuffer.wrap(buf, offset, length); |
| CoderResult result = CoderResult.UNDERFLOW; |
| |
| // bytes.remaining() indicates number of bytes in buffer |
| // when 1-st time entered, it'll be equal to zero |
| boolean needInput = !bytes.hasRemaining(); |
| |
| while (out.hasRemaining()) { |
| // fill the buffer if needed |
| if (needInput) { |
| if ((in.available() == 0) && (out.position() > offset)) { |
| // we could return the result without blocking read |
| break; |
| } |
| |
| int to_read = bytes.capacity() - bytes.limit(); |
| int off = bytes.arrayOffset() + bytes.limit(); |
| int was_red = in.read(bytes.array(), off, to_read); |
| |
| if (was_red == -1) { |
| endOfInput = true; |
| break; |
| } else if (was_red == 0) { |
| break; |
| } |
| bytes.limit(bytes.limit() + was_red); |
| needInput = false; |
| } |
| |
| // decode bytes |
| result = decoder.decode(bytes, out, false); |
| |
| if (result.isUnderflow()) { |
| // compact the buffer if no space left |
| if (bytes.limit() == bytes.capacity()) { |
| bytes.compact(); |
| bytes.limit(bytes.position()); |
| bytes.position(0); |
| } |
| needInput = true; |
| } else { |
| break; |
| } |
| } |
| |
| if (result == CoderResult.UNDERFLOW && endOfInput) { |
| result = decoder.decode(bytes, out, true); |
| // FIXME: should flush at first, but seems ICU has a bug that it |
| // will throw IAE if some malform/unmappable bytes found during |
| // decoding |
| // result = decoder.flush(out); |
| decoder.reset(); |
| } |
| if (result.isMalformed()) { |
| throw new MalformedInputException(result.length()); |
| } else if (result.isUnmappable()) { |
| throw new UnmappableCharacterException(result.length()); |
| } |
| |
| return out.position() - offset == 0 ? -1 : out.position() - offset; |
| } |
| } |
| |
| /* |
| * Answer a boolean indicating whether or not this InputStreamReader is |
| * open. |
| */ |
| private boolean isOpen() { |
| return in != null; |
| } |
| |
| /** |
| * Indicates whether this reader is ready to be read without blocking. If |
| * the result is {@code true}, the next {@code read()} will not block. If |
| * the result is {@code false} then this reader may or may not block when |
| * {@code read()} is called. This implementation returns {@code true} if |
| * there are bytes available in the buffer or the source stream has bytes |
| * available. |
| * |
| * @return {@code true} if the receiver will not block when {@code read()} |
| * is called, {@code false} if unknown or blocking will occur. |
| * @throws IOException |
| * if this reader is closed or some other I/O error occurs. |
| * @since Android 1.0 |
| */ |
| @Override |
| public boolean ready() throws IOException { |
| synchronized (lock) { |
| if (in == null) { |
| // K0070=InputStreamReader is closed. |
| throw new IOException(Msg.getString("K0070")); //$NON-NLS-1$ |
| } |
| try { |
| return bytes.hasRemaining() || in.available() > 0; |
| } catch (IOException e) { |
| return false; |
| } |
| } |
| } |
| } |