001: /*
002:
003: Derby - Class org.apache.derby.iapi.types.RawToBinaryFormatStream
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.iapi.types;
023:
024: import java.io.InputStream;
025: import java.io.IOException;
026: import java.io.EOFException;
027:
028: import org.apache.derby.iapi.services.io.DerbyIOException;
029: import org.apache.derby.iapi.services.io.LimitInputStream;
030: import org.apache.derby.iapi.services.i18n.MessageService;
031: import org.apache.derby.iapi.reference.SQLState;
032:
033: /**
034: Stream that takes a raw input stream and converts it
035: to the on-disk format of the binary types by prepending the
036: length of the value.
037: <P>
038: If the length of the stream is known then it is encoded
039: as the first bytes in the stream in the defined format.
040: <BR>
041: If the length is unknown then the first four bytes will
042: be zero, indicating unknown length.
043: <BR>
044: Note: This stream cannot be re-used. Once end of file is
045: reached, the next read call will throw an EOFException
046:
047: @see SQLBinary
048: */
049: public final class RawToBinaryFormatStream extends LimitInputStream {
050:
051: /**
052: * Number of bytes of length encoding.
053: *
054: */
055: private int encodedOffset;
056:
057: /**
058: * Encoding of the length in bytes which will be
059: * seen as the first encodedLength.length bytes of
060: * this stream.
061: */
062: private byte[] encodedLength;
063:
064: // flag to indicate the stream has already been read
065: // and eof reached
066: private boolean eof = false;
067:
068: /**
069: * The length of the stream.
070: * Unknown if less than 0.
071: */
072: private final int length;
073: /**
074: * The maximum allowed length for the stream.
075: * No limit if less than 0.
076: */
077: private final int maximumLength;
078: /**
079: * The type of the column the stream is inserted into.
080: * Used for length less streams, <code>null</code> if not in use.
081: */
082: private final String typeName;
083:
084: /**
085: * Create a binary on-disk stream from the given <code>InputStream</code>.
086: *
087: * The on-disk stream prepends a length encoding, and validates that the
088: * actual length of the stream matches the specified length (as according
089: * to JDBC 3.0).
090: *
091: * @param in application's raw binary stream passed into JDBC layer
092: * @param length length of the stream
093: * @throws IllegalArgumentException if <code>length</code> is negative.
094: * This exception should never be exposed to the user, and seeing it
095: * means a programming error exists in the code.
096: */
097: public RawToBinaryFormatStream(InputStream in, int length) {
098: super (in);
099: if (length < 0) {
100: throw new IllegalArgumentException(
101: "Stream length cannot be negative: " + length);
102: }
103: this .length = length;
104: this .maximumLength = -1;
105: this .typeName = null;
106:
107: setLimit(length);
108:
109: if (length <= 31) {
110: encodedLength = new byte[1];
111: encodedLength[0] = (byte) (0x80 | (length & 0xff));
112: } else if (length <= 0xFFFF) {
113: encodedLength = new byte[3];
114: encodedLength[0] = (byte) 0xA0;
115: encodedLength[1] = (byte) (length >> 8);
116: encodedLength[2] = (byte) (length);
117: } else {
118: encodedLength = new byte[5];
119: encodedLength[0] = (byte) 0xC0;
120: encodedLength[1] = (byte) (length >> 24);
121: encodedLength[2] = (byte) (length >> 16);
122: encodedLength[3] = (byte) (length >> 8);
123: encodedLength[4] = (byte) (length);
124: }
125: }
126:
127: /**
128: * Create a binary on-disk stream from the given <code>InputStream</code>
129: * of unknown length.
130: *
131: * A limit is placed on the maximum length of the stream.
132: *
133: * @param in the application stream
134: * @param maximumLength maximum length of the column data is inserted into
135: * @param typeName type name for the column data is inserted into
136: * @throws IllegalArgumentException if maximum length is negative, or type
137: * name is <code>null<code>. This exception should never be exposed
138: * to the user, and seeing it means a programming error exists in the
139: * code. Although a missing type name is not critical, an exception is
140: * is thrown to signal the intended use of this constructor.
141: */
142: public RawToBinaryFormatStream(InputStream in, int maximumLength,
143: String typeName) {
144: super (in);
145: if (maximumLength < 0) {
146: throw new IllegalArgumentException(
147: "Maximum length for a capped "
148: + "stream cannot be negative: "
149: + maximumLength);
150: }
151: if (typeName == null) {
152: throw new IllegalArgumentException(
153: "Type name cannot be null");
154: }
155: this .length = -1;
156: this .maximumLength = maximumLength;
157: this .typeName = typeName;
158: // Unknown length, four zero bytes.
159: encodedLength = new byte[4];
160: setLimit(maximumLength);
161: }
162:
163: /**
164: Read from the wrapped stream prepending the intial bytes if needed.
165: If stream has been read, and eof reached, in that case any subsequent
166: read will throw an EOFException
167: */
168: public int read() throws IOException {
169:
170: if (eof)
171: throw new EOFException(MessageService
172: .getTextMessage(SQLState.STREAM_EOF));
173:
174: if (encodedOffset < encodedLength.length) {
175: return encodedLength[encodedOffset++] & 0xff;
176: }
177:
178: int ret = super .read();
179:
180: if (ret == -1)
181: checkSufficientData();
182:
183: return ret;
184: }
185:
186: /**
187: JDBC 3.0 (from tutorial book) requires that an
188: input stream has the correct number of bytes in
189: the stream.
190: */
191: private void checkSufficientData() throws IOException {
192: // if we reached here, then read call returned -1, and we
193: // have already reached the end of stream, so set eof=true
194: // so that subsequent reads on this stream will return an
195: // EOFException
196: eof = true;
197: if (!limitInPlace)
198: return;
199:
200: int remainingBytes = clearLimit();
201:
202: if (length > -1 && remainingBytes > 0) {
203: throw new DerbyIOException(
204: MessageService
205: .getTextMessage(SQLState.SET_STREAM_INEXACT_LENGTH_DATA),
206: SQLState.SET_STREAM_INEXACT_LENGTH_DATA);
207: }
208:
209: // if we had a limit try reading one more byte.
210: // JDBC 3.0 states the stream muct have the correct number of characters in it.
211: if (remainingBytes == 0) {
212: int c;
213: try {
214: c = super .read();
215: } catch (IOException ioe) {
216: c = -1;
217: }
218: if (c != -1) {
219: if (length > -1) {
220: // Stream is not capped, and should have matched the
221: // specified length.
222: throw new DerbyIOException(
223: MessageService
224: .getTextMessage(SQLState.SET_STREAM_INEXACT_LENGTH_DATA),
225: SQLState.SET_STREAM_INEXACT_LENGTH_DATA);
226: } else {
227: // Stream is capped, and has exceeded the maximum length.
228: throw new DerbyIOException(MessageService
229: .getTextMessage(
230: SQLState.LANG_STRING_TRUNCATION,
231: typeName, "XXXX", String
232: .valueOf(maximumLength)),
233: SQLState.LANG_STRING_TRUNCATION);
234: }
235: }
236: }
237: }
238:
239: /**
240: Read from the wrapped stream prepending the intial bytes if needed.
241: If stream has been read, and eof reached, in that case any subsequent
242: read will throw an EOFException
243: */
244: public int read(byte b[], int off, int len) throws IOException {
245:
246: if (eof)
247: throw new EOFException(MessageService
248: .getTextMessage(SQLState.STREAM_EOF));
249:
250: int elen = encodedLength.length - encodedOffset;
251:
252: if (elen != 0) {
253: if (len < elen)
254: elen = len;
255: System
256: .arraycopy(encodedLength, encodedOffset, b, off,
257: elen);
258:
259: encodedOffset += elen;
260:
261: off += elen;
262: len -= elen;
263:
264: if (len == 0)
265: return elen;
266: }
267:
268: int realRead = super .read(b, off, len);
269:
270: if (realRead < 0) {
271: if (elen != 0)
272: return elen;
273:
274: checkSufficientData();
275: return realRead;
276: }
277:
278: return elen + realRead;
279: }
280: }
|