--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.iapi.types.ReaderToUTF8Stream\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.iapi.types;\r
+\r
+import java.io.InputStream;\r
+import java.io.IOException;\r
+import java.io.EOFException;\r
+import java.io.Reader;\r
+import java.io.UTFDataFormatException;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.services.i18n.MessageService;\r
+import org.apache.derby.iapi.services.io.DerbyIOException;\r
+import org.apache.derby.iapi.services.io.LimitReader;\r
+import org.apache.derby.iapi.types.TypeId;\r
+\r
+/**\r
+ Converts a java.io.Reader to the on-disk UTF8 format used by Derby\r
+ for character types.\r
+*/\r
+public final class ReaderToUTF8Stream\r
+ extends InputStream\r
+{\r
+ /**\r
+ * Application's reader wrapped in a LimitReader.\r
+ */\r
+ private LimitReader reader;\r
+\r
+ private byte[] buffer;\r
+ private int boff;\r
+ private int blen;\r
+ private boolean eof;\r
+ private boolean multipleBuffer;\r
+ // buffer to hold the data read from stream \r
+ // and converted to UTF8 format\r
+ private final static int BUFSIZE = 32768;\r
+ \r
+ /** Number of characters to truncate from this stream\r
+ The SQL standard allows for truncation of trailing spaces \r
+ for clobs,varchar,char.\r
+ If zero, no characters are truncated.\r
+ */\r
+ private final int charsToTruncate;\r
+ private static final char SPACE = ' ';\r
+ \r
+ /**\r
+ * Length of the final value, after truncation if any,\r
+ * in characters.\r
+ this stream needs to fit into a column of colWidth\r
+ if truncation error happens ,then the error message includes \r
+ information about the column width.\r
+ */\r
+ private final int valueLength; \r
+ /** The maximum allowed length of the stream. */\r
+ private final int maximumLength;\r
+ /** The type name for the column data is inserted into. */\r
+ private final String typeName;\r
+ \r
+ /**\r
+ * Create a stream that will truncate trailing blanks if required/allowed.\r
+ *\r
+ * If the stream must be truncated, the number of blanks to truncate\r
+ * is specified to allow the stream to be checked for exact length, as\r
+ * required by JDBC 3.0. If the stream is shorter or longer than specified,\r
+ * an exception is thrown during read.\r
+ *\r
+ * @param appReader application reader\r
+ * @param valueLength the length of the reader in characters\r
+ * @param numCharsToTruncate the number of trailing blanks to truncate\r
+ * @param typeName type name of the column data is inserted into\r
+ */\r
+ public ReaderToUTF8Stream(Reader appReader,\r
+ int valueLength,\r
+ int numCharsToTruncate,\r
+ String typeName) {\r
+ this.reader = new LimitReader(appReader);\r
+ reader.setLimit(valueLength);\r
+ buffer = new byte[BUFSIZE];\r
+ blen = -1; \r
+ this.charsToTruncate = numCharsToTruncate;\r
+ this.valueLength = valueLength;\r
+ this.maximumLength = -1;\r
+ this.typeName = typeName;\r
+ }\r
+\r
+ /**\r
+ * Create a UTF-8 stream for a length less application reader.\r
+ *\r
+ * A limit is placed on the length of the reader. If the reader exceeds\r
+ * the maximum length, truncation of trailing blanks is attempted. If\r
+ * truncation fails, an exception is thrown.\r
+ *\r
+ * @param appReader application reader\r
+ * @param maximumLength maximum allowed length in number of characters for\r
+ * the reader\r
+ * @param typeName type name of the column data is inserted into\r
+ * @throws IllegalArgumentException if maximum length is negative, or type\r
+ * name is <code>null<code>\r
+ */\r
+ public ReaderToUTF8Stream(Reader appReader,\r
+ int maximumLength,\r
+ String typeName) {\r
+ if (maximumLength < 0) {\r
+ throw new IllegalArgumentException("Maximum length for a capped " +\r
+ "stream cannot be negative: " + maximumLength);\r
+ }\r
+ if (typeName == null) {\r
+ throw new IllegalArgumentException("Type name cannot be null");\r
+ }\r
+ this.reader = new LimitReader(appReader);\r
+ reader.setLimit(maximumLength);\r
+ buffer = new byte[BUFSIZE];\r
+ blen = -1;\r
+ this.maximumLength = maximumLength;\r
+ this.typeName = typeName;\r
+ this.charsToTruncate = -1;\r
+ this.valueLength = -1;\r
+ }\r
+\r
+ /**\r
+ * read from stream; characters converted to utf-8 derby specific encoding.\r
+ * If stream has been read, and eof reached, in that case any subsequent\r
+ * read will throw an EOFException\r
+ * @see java.io.InputStream#read()\r
+ */\r
+ public int read() throws IOException {\r
+\r
+ // when stream has been read and eof reached, stream is closed\r
+ // and buffer is set to null ( see close() method)\r
+ // since stream cannot be re-used, check if stream is closed and \r
+ // if so throw an EOFException\r
+ if ( buffer == null)\r
+ throw new EOFException(MessageService.getTextMessage(SQLState.STREAM_EOF));\r
+\r
+ \r
+ // first read\r
+ if (blen < 0)\r
+ fillBuffer(2);\r
+\r
+ while (boff == blen)\r
+ {\r
+ // reached end of buffer, read more?\r
+ if (eof)\r
+ {\r
+ // we have reached the end of this stream\r
+ // cleanup here and return -1 indicating \r
+ // eof of stream\r
+ close();\r
+ return -1;\r
+ }\r
+ \r
+\r
+ fillBuffer(0);\r
+ }\r
+\r
+ return buffer[boff++] & 0xff;\r
+\r
+ }\r
+\r
+ public int read(byte b[], int off, int len) throws IOException {\r
+ \r
+ // when stream has been read and eof reached, stream is closed\r
+ // and buffer is set to null ( see close() method)\r
+ // since stream cannot be re-used, check if stream is closed and \r
+ // if so throw an EOFException\r
+ if ( buffer == null )\r
+ throw new EOFException(MessageService.getTextMessage\r
+ (SQLState.STREAM_EOF));\r
+\r
+ // first read\r
+ if (blen < 0)\r
+ fillBuffer(2);\r
+\r
+ int readCount = 0;\r
+\r
+ while (len > 0)\r
+ {\r
+\r
+ int copyBytes = blen - boff;\r
+\r
+ // buffer empty?\r
+ if (copyBytes == 0)\r
+ {\r
+ if (eof)\r
+ {\r
+ if (readCount > 0)\r
+ {\r
+ return readCount;\r
+ }\r
+ else\r
+ {\r
+ // we have reached the eof, so close the stream\r
+ close();\r
+ return -1; \r
+ }\r
+ \r
+ }\r
+ fillBuffer(0);\r
+ continue;\r
+ }\r
+\r
+ if (len < copyBytes)\r
+ copyBytes = len;\r
+\r
+ System.arraycopy(buffer, boff, b, off, copyBytes);\r
+ boff += copyBytes;\r
+ len -= copyBytes;\r
+ readCount += copyBytes;\r
+ off += copyBytes;\r
+\r
+ }\r
+ return readCount;\r
+ }\r
+\r
+ private void fillBuffer(int startingOffset) throws IOException\r
+ {\r
+ int off = boff = startingOffset;\r
+\r
+ if (off == 0)\r
+ multipleBuffer = true;\r
+\r
+ // 6! need to leave room for a three byte UTF8 encoding\r
+ // and 3 bytes for our special end of file marker.\r
+ for (; off <= buffer.length - 6; )\r
+ {\r
+ int c = reader.read();\r
+ if (c < 0) {\r
+ eof = true;\r
+ break;\r
+ }\r
+\r
+ if ((c >= 0x0001) && (c <= 0x007F))\r
+ {\r
+ buffer[off++] = (byte) c;\r
+ }\r
+ else if (c > 0x07FF)\r
+ {\r
+ buffer[off++] = (byte) (0xE0 | ((c >> 12) & 0x0F));\r
+ buffer[off++] = (byte) (0x80 | ((c >> 6) & 0x3F));\r
+ buffer[off++] = (byte) (0x80 | ((c >> 0) & 0x3F));\r
+ }\r
+ else\r
+ {\r
+ buffer[off++] = (byte) (0xC0 | ((c >> 6) & 0x1F));\r
+ buffer[off++] = (byte) (0x80 | ((c >> 0) & 0x3F));\r
+ }\r
+ }\r
+\r
+ blen = off;\r
+ boff = 0;\r
+\r
+ if (eof)\r
+ checkSufficientData();\r
+ }\r
+\r
+ /**\r
+ * Validate the length of the stream, take corrective action if allowed.\r
+ *\r
+ * JDBC 3.0 (from tutorial book) requires that an input stream has the\r
+ * correct number of bytes in the stream.\r
+ * If the stream is too long, trailing blank truncation is attempted if\r
+ * allowed. If truncation fails, or is disallowed, an exception is thrown.\r
+ *\r
+ * @throws IOException if an errors occurs in the application stream\r
+ * @throws DerbyIOException if Derby finds a problem with the stream;\r
+ * stream is too long and cannot be truncated, or the stream length\r
+ * does not match the specified length\r
+ */\r
+ private void checkSufficientData() throws IOException\r
+ {\r
+ // now that we finished reading from the stream; the amount\r
+ // of data that we can insert,start check for trailing spaces\r
+ if (charsToTruncate > 0)\r
+ {\r
+ reader.setLimit(charsToTruncate);\r
+ truncate();\r
+ }\r
+ \r
+ // A length less stream that is capped, will return 0 even if there\r
+ // are more bytes in the application stream.\r
+ int remainingBytes = reader.clearLimit();\r
+ if (remainingBytes > 0 && valueLength > 0) {\r
+ // If we had a specified length, throw exception.\r
+ throw new DerbyIOException(\r
+ MessageService.getTextMessage(\r
+ SQLState.SET_STREAM_INEXACT_LENGTH_DATA),\r
+ SQLState.SET_STREAM_INEXACT_LENGTH_DATA);\r
+ }\r
+\r
+ // if we had a limit try reading one more character.\r
+ // JDBC 3.0 states the stream must have the correct number of\r
+ // characters in it.\r
+ if (remainingBytes == 0 && reader.read() >= 0) {\r
+ if (valueLength > -1) {\r
+ throw new DerbyIOException(\r
+ MessageService.getTextMessage(\r
+ SQLState.SET_STREAM_INEXACT_LENGTH_DATA),\r
+ SQLState.SET_STREAM_INEXACT_LENGTH_DATA);\r
+ } else {\r
+ // Stream was capped (length less) and too long.\r
+ // Try to truncate if allowed, or else throw exception.\r
+ if (canTruncate()) {\r
+ truncate();\r
+ } else {\r
+ throw new DerbyIOException(\r
+ MessageService.getTextMessage(\r
+ SQLState.LANG_STRING_TRUNCATION),\r
+ SQLState.LANG_STRING_TRUNCATION);\r
+ }\r
+ }\r
+ }\r
+ \r
+ // can put the correct length into the stream.\r
+ if (!multipleBuffer)\r
+ {\r
+ int utflen = blen - 2;\r
+\r
+ buffer[0] = (byte) ((utflen >>> 8) & 0xFF);\r
+ buffer[1] = (byte) ((utflen >>> 0) & 0xFF);\r
+\r
+ }\r
+ else\r
+ {\r
+ buffer[blen++] = (byte) 0xE0;\r
+ buffer[blen++] = (byte) 0x00;\r
+ buffer[blen++] = (byte) 0x00;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Determine if trailing blank truncation is allowed.\r
+ */\r
+ private boolean canTruncate() {\r
+ // Only a few types can be truncated, default is to not allow.\r
+ if (typeName.equals(TypeId.CLOB_NAME)) {\r
+ return true;\r
+ } else if (typeName.equals(TypeId.VARCHAR_NAME)) {\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * Attempt to truncate the stream by removing trailing blanks.\r
+ */\r
+ private void truncate()\r
+ throws IOException {\r
+ int c = 0;\r
+ for (;;) {\r
+ c = reader.read();\r
+\r
+ if (c < 0) {\r
+ break;\r
+ } else if (c != SPACE) {\r
+ throw new DerbyIOException(\r
+ MessageService.getTextMessage(\r
+ SQLState.LANG_STRING_TRUNCATION,\r
+ typeName, \r
+ "XXXX", \r
+ String.valueOf(valueLength)),\r
+ SQLState.LANG_STRING_TRUNCATION);\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * return resources \r
+ */\r
+ public void close() throws IOException\r
+ {\r
+ // since stream has been read and eof reached, return buffer back to \r
+ // the vm.\r
+ // Instead of using another variable to indicate stream is closed\r
+ // a check on (buffer==null) is used instead. \r
+ buffer = null;\r
+ }\r
+\r
+ /**\r
+ * Return an optimized version of bytes available to read from \r
+ * the stream \r
+ * Note, it is not exactly per java.io.InputStream#available()\r
+ */\r
+ public final int available()\r
+ {\r
+ int remainingBytes = reader.getLimit();\r
+ // this object buffers BUFSIZE bytes that can be read \r
+ // and when that is finished it reads the next available bytes\r
+ // from the reader object \r
+ // reader.getLimit() returns the remaining bytes available\r
+ // on this stream\r
+ return (BUFSIZE > remainingBytes ? remainingBytes : BUFSIZE);\r
+ }\r
+ }\r
+\r