--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.jdbc.UTF8Reader\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.jdbc;\r
+\r
+import java.io.BufferedInputStream;\r
+import java.io.InputStream;\r
+import java.io.Reader;\r
+import java.io.IOException;\r
+import java.io.UTFDataFormatException;\r
+import java.io.EOFException;\r
+import java.sql.SQLException;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+import org.apache.derby.iapi.types.Resetable;\r
+\r
+/**\r
+*/\r
+public final class UTF8Reader extends Reader\r
+{\r
+\r
+ private InputStream in;\r
+ /** Stream store that can reposition itself on request. */\r
+ private final PositionedStoreStream positionedIn;\r
+ /** Store last visited position in the store stream. */\r
+ private long rawStreamPos = 0L;\r
+ private final long utfLen; // bytes\r
+ private long utfCount; // bytes\r
+ private long readerCharCount; // characters\r
+ private long maxFieldSize; // characeters\r
+\r
+ private char[] buffer = new char[8 * 1024];\r
+ private int charactersInBuffer; // within buffer\r
+ private int readPositionInBuffer;\r
+\r
+ private boolean noMoreReads;\r
+\r
+ // maintain a reference to the parent object so that it can't get\r
+ // garbage collected until we are done with the stream.\r
+ private ConnectionChild parent;\r
+\r
+ public UTF8Reader(\r
+ InputStream in,\r
+ long maxFieldSize,\r
+ ConnectionChild parent,\r
+ Object synchronization)\r
+ throws IOException, SQLException\r
+ {\r
+ super(synchronization);\r
+ this.maxFieldSize = maxFieldSize;\r
+ this.parent = parent;\r
+\r
+ parent.setupContextStack();\r
+ try {\r
+ synchronized (lock) { // Synchronize access to store.\r
+ if (in instanceof PositionedStoreStream) {\r
+ this.positionedIn = (PositionedStoreStream)in;\r
+ // This stream is already buffered, and buffering it again\r
+ // this high up complicates the handling a lot. Must\r
+ // implement a special buffered reader to buffer again.\r
+ // Note that buffering this UTF8Reader again, does not\r
+ // cause any trouble...\r
+ this.in = in;\r
+ try {\r
+ this.positionedIn.resetStream();\r
+ } catch (StandardException se) {\r
+ IOException ioe = new IOException(se.getMessage());\r
+ ioe.initCause(se);\r
+ throw ioe;\r
+ }\r
+ } else {\r
+ this.positionedIn = null;\r
+ // Buffer this for improved performance.\r
+ this.in = new BufferedInputStream (in);\r
+ }\r
+ this.utfLen = readUnsignedShort();\r
+ // Even if we are reading the encoded length, the stream may\r
+ // not be a positioned stream. This is currently true when a\r
+ // stream is passed in after a ResetSet.getXXXStream method.\r
+ if (this.positionedIn != null) {\r
+ this.rawStreamPos = this.positionedIn.getPosition();\r
+ }\r
+ } // End synchronized block\r
+ } finally {\r
+ parent.restoreContextStack();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Constructs a <code>UTF8Reader</code> using a stream.\r
+ * <p>\r
+ * This consturctor accepts the stream size as parameter and doesn't\r
+ * attempt to read the length from the stream.\r
+ *\r
+ * @param in the underlying stream\r
+ * @param maxFieldSize the maximum allowed length for the associated column\r
+ * @param streamSize size of the underlying stream in bytes\r
+ * @param parent the connection child this stream is associated with\r
+ * @param synchronization object to synchronize on\r
+ */\r
+ public UTF8Reader(\r
+ InputStream in,\r
+ long maxFieldSize,\r
+ long streamSize,\r
+ ConnectionChild parent,\r
+ Object synchronization)\r
+ throws IOException {\r
+ super(synchronization);\r
+ this.maxFieldSize = maxFieldSize;\r
+ this.parent = parent;\r
+ this.utfLen = streamSize;\r
+ this.positionedIn = null;\r
+\r
+ if (SanityManager.DEBUG) {\r
+ // Do not allow the inputstream here to be a Resetable, as this\r
+ // means (currently, not by design...) that the length is encoded in\r
+ // the stream and we can't pass that out as data to the user.\r
+ SanityManager.ASSERT(!(in instanceof Resetable));\r
+ }\r
+ // Buffer this for improved performance.\r
+ this.in = new BufferedInputStream(in);\r
+ }\r
+\r
+ /*\r
+ ** Reader implemention.\r
+ */\r
+ public int read() throws IOException\r
+ {\r
+ synchronized (lock) {\r
+\r
+ // check if closed..\r
+ if (noMoreReads)\r
+ throw new IOException();\r
+\r
+ if (readPositionInBuffer >= charactersInBuffer) {\r
+ if (fillBuffer()) {\r
+ return -1;\r
+ }\r
+ readPositionInBuffer = 0;\r
+ }\r
+\r
+ return buffer[readPositionInBuffer++];\r
+ }\r
+ }\r
+\r
+ public int read(char[] cbuf, int off, int len) throws IOException\r
+ {\r
+ synchronized (lock) {\r
+ // check if closed..\r
+ if (noMoreReads)\r
+ throw new IOException();\r
+\r
+ if (readPositionInBuffer >= charactersInBuffer) {\r
+ if (fillBuffer()) {\r
+ return -1;\r
+ }\r
+ readPositionInBuffer = 0;\r
+ }\r
+\r
+ int remainingInBuffer = charactersInBuffer - readPositionInBuffer;\r
+\r
+ if (len > remainingInBuffer)\r
+ len = remainingInBuffer;\r
+\r
+ System.arraycopy(buffer, readPositionInBuffer, cbuf, off, len);\r
+ readPositionInBuffer += len;\r
+\r
+ return len;\r
+ }\r
+ }\r
+\r
+ public long skip(long len) throws IOException {\r
+ if (len < 0) {\r
+ throw new IllegalArgumentException(\r
+ "Number of characters to skip must be positive: " + len);\r
+ }\r
+ synchronized (lock) {\r
+ // check if closed..\r
+ if (noMoreReads)\r
+ throw new IOException();\r
+\r
+ if (readPositionInBuffer >= charactersInBuffer) {\r
+ // do somthing\r
+ if (fillBuffer()) {\r
+ return 0L;\r
+ }\r
+ readPositionInBuffer = 0;\r
+ }\r
+\r
+ int remainingInBuffer = charactersInBuffer - readPositionInBuffer;\r
+\r
+ if (len > remainingInBuffer)\r
+ len = remainingInBuffer;\r
+\r
+ readPositionInBuffer += len;\r
+\r
+ return len;\r
+ }\r
+\r
+ }\r
+\r
+ public void close()\r
+ {\r
+ synchronized (lock) {\r
+ closeIn();\r
+ parent = null;\r
+ noMoreReads = true;\r
+ }\r
+ }\r
+\r
+ /*\r
+ ** Methods just for Derby's JDBC driver\r
+ */\r
+ public int readInto(StringBuffer sb, int len) throws IOException {\r
+\r
+ synchronized (lock) {\r
+ if (readPositionInBuffer >= charactersInBuffer) {\r
+ if (fillBuffer()) {\r
+ return -1;\r
+ }\r
+ readPositionInBuffer = 0;\r
+ }\r
+\r
+ int remainingInBuffer = charactersInBuffer - readPositionInBuffer;\r
+\r
+ if (len > remainingInBuffer)\r
+ len = remainingInBuffer;\r
+ sb.append(buffer, readPositionInBuffer, len);\r
+\r
+ readPositionInBuffer += len;\r
+\r
+ return len;\r
+ }\r
+ }\r
+\r
+ int readAsciiInto(byte[] abuf, int off, int len) throws IOException {\r
+\r
+ synchronized (lock) {\r
+ if (readPositionInBuffer >= charactersInBuffer) {\r
+ if (fillBuffer()) {\r
+ return -1;\r
+ }\r
+ readPositionInBuffer = 0;\r
+ }\r
+\r
+ int remainingInBuffer = charactersInBuffer - readPositionInBuffer;\r
+\r
+ if (len > remainingInBuffer)\r
+ len = remainingInBuffer;\r
+\r
+ char[] lbuffer = buffer;\r
+ for (int i = 0; i < len; i++) {\r
+ char c = lbuffer[readPositionInBuffer + i];\r
+ byte cb;\r
+ if (c <= 255)\r
+ cb = (byte) c;\r
+ else\r
+ cb = (byte) '?'; // Question mark - out of range character.\r
+\r
+ abuf[off + i] = cb;\r
+ }\r
+\r
+ readPositionInBuffer += len;\r
+\r
+ return len;\r
+ }\r
+ }\r
+\r
+ /*\r
+ ** internal implementation\r
+ */\r
+\r
+ private void closeIn() {\r
+ if (in != null) {\r
+ try {\r
+ in.close();\r
+ } catch (IOException ioe) {\r
+ } finally {\r
+ in = null;\r
+ }\r
+ }\r
+ }\r
+\r
+ private IOException utfFormatException(String s) {\r
+ noMoreReads = true;\r
+ closeIn();\r
+ return new UTFDataFormatException(s);\r
+ }\r
+\r
+ private IOException utfFormatException() {\r
+ noMoreReads = true;\r
+ closeIn();\r
+ return new UTFDataFormatException();\r
+ }\r
+\r
+ /**\r
+ Fill the buffer, return true if eof has been reached.\r
+ */\r
+ //@GuardedBy("lock")\r
+ private boolean fillBuffer() throws IOException\r
+ {\r
+ if (in == null)\r
+ return true;\r
+\r
+ charactersInBuffer = 0;\r
+\r
+ try {\r
+ try {\r
+ parent.setupContextStack();\r
+ // If we are operating on a positioned stream, reposition it to\r
+ // continue reading at the position we stopped last time.\r
+ if (this.positionedIn != null) {\r
+ try {\r
+ this.positionedIn.reposition(this.rawStreamPos);\r
+ } catch (StandardException se) {\r
+ throw Util.generateCsSQLException(se);\r
+ }\r
+ }\r
+readChars:\r
+ while (\r
+ (charactersInBuffer < buffer.length) &&\r
+ ((utfCount < utfLen) || (utfLen == 0)) &&\r
+ ((maxFieldSize == 0) || (readerCharCount < maxFieldSize))\r
+ )\r
+ {\r
+ int c = in.read();\r
+ if (c == -1) {\r
+ if (utfLen == 0) {\r
+ closeIn();\r
+ break readChars;\r
+ }\r
+ throw utfFormatException();\r
+ }\r
+\r
+ int finalChar;\r
+ switch (c >> 4) {\r
+ case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:\r
+ // 0xxxxxxx\r
+ utfCount++;\r
+ finalChar = c;\r
+ break;\r
+\r
+ case 12: case 13:\r
+ {\r
+ // 110x xxxx 10xx xxxx\r
+ utfCount += 2;\r
+ int char2 = in.read();\r
+ if (char2 == -1)\r
+ throw utfFormatException();\r
+\r
+ if ((char2 & 0xC0) != 0x80)\r
+ throw utfFormatException();\r
+ finalChar = (((c & 0x1F) << 6) | (char2 & 0x3F));\r
+ break;\r
+ }\r
+\r
+ case 14:\r
+ {\r
+ // 1110 xxxx 10xx xxxx 10xx xxxx\r
+ utfCount += 3;\r
+ int char2 = in.read();\r
+ int char3 = in.read();\r
+ if (char2 == -1 || char3 == -1)\r
+ throw utfFormatException();\r
+\r
+ if ((c == 0xE0) && (char2 == 0) && (char3 == 0))\r
+ {\r
+ if (utfLen == 0) {\r
+ // we reached the end of a long string,\r
+ // that was terminated with\r
+ // (11100000, 00000000, 00000000)\r
+ closeIn();\r
+ break readChars;\r
+ }\r
+ throw utfFormatException();\r
+ }\r
+\r
+ if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80))\r
+ throw utfFormatException();\r
+\r
+ finalChar = (((c & 0x0F) << 12) |\r
+ ((char2 & 0x3F) << 6) |\r
+ ((char3 & 0x3F) << 0));\r
+ }\r
+ break;\r
+\r
+ default:\r
+ // 10xx xxxx, 1111 xxxx\r
+ throw utfFormatException();\r
+ }\r
+\r
+ buffer[charactersInBuffer++] = (char) finalChar;\r
+ readerCharCount++;\r
+ }\r
+ if (utfLen != 0 && utfCount > utfLen)\r
+ throw utfFormatException("utfCount " + utfCount + " utfLen " + utfLen);\r
+\r
+ if (charactersInBuffer != 0) {\r
+ if (this.positionedIn != null) {\r
+ // Save the last visisted position so we can start reading where\r
+ // we let go the next time we fill the buffer.\r
+ this.rawStreamPos = this.positionedIn.getPosition();\r
+ }\r
+ return false;\r
+ }\r
+\r
+ closeIn();\r
+ return true;\r
+ } finally {\r
+ parent.restoreContextStack();\r
+ }\r
+ } catch (SQLException sqle) {\r
+ IOException ioe =\r
+ new IOException(sqle.getSQLState() + ": " + sqle.getMessage());\r
+ ioe.initCause(sqle);\r
+ throw ioe;\r
+ }\r
+ }\r
+\r
+ // this method came from java.io.DataInputStream\r
+ private final int readUnsignedShort() throws IOException {\r
+ int ch1 = in.read();\r
+ int ch2 = in.read();\r
+ if ((ch1 | ch2) < 0)\r
+ throw new EOFException();\r
+\r
+ return (ch1 << 8) + (ch2 << 0);\r
+ }\r
+}\r