001: /*
002: Derby - Class org.apache.derby.client.am.ByteArrayCombinerStream
003:
004: Licensed to the Apache Software Foundation (ASF) under one
005: or more contributor license agreements. See the NOTICE file
006: distributed with this work for additional information
007: regarding copyright ownership. The ASF licenses this file
008: to you under the Apache License, Version 2.0 (the
009: "License"); you may not use this file except in compliance
010: with 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,
015: software distributed under the License is distributed on an
016: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: KIND, either express or implied. See the License for the
018: specific language governing permissions and limitations
019: under the License.
020: */
021: package org.apache.derby.client.am;
022:
023: import java.io.InputStream;
024: import java.io.IOException;
025: import java.util.ArrayList;
026:
027: /**
028: * A stream whose source is a list of byte arrays.
029: *
030: * This class was created when first implementing the JDBC 4 length less
031: * overloads in the client driver. The reason was missing support for
032: * streaming data with unknown length from the client to the server.
033: *
034: * The purpose of the stream is to avoid having to repeatedly copy data to grow
035: * the byte buffer, or doing a single big copy to combine the byte arrays in
036: * the end. This is important for the temporary solution, since we must
037: * materialize the stream to find the length anyway.
038: *
039: * If there is less data available than the specified length, an exception is
040: * thrown. Available data is determined by the length of the byte arrays, not
041: * the contents of them. A byte array with all 0's is considered valid data.
042: *
043: * Besides from truncation, this stream does not change the underlying data in
044: * any way.
045: */
046: public class ByteArrayCombinerStream extends InputStream {
047:
048: /** A list of the arrays to combine. */
049: private final ArrayList arrays;
050: /** Length of the stream. */
051: private final long specifiedLength;
052: /** Global offset into the whole stream. */
053: private long gOffset = 0;
054: /** Index of the array we are currently reading from. */
055: private int arrayIndex = 0;
056: /** The array we are currently reading from. */
057: private byte[] curArray;
058: /** The local offset into the current array. */
059: private int off = 0;
060:
061: /**
062: * Create a stream whose source is a list of byte arrays.
063: *
064: * @param arraysIn an <code>ArrayList</code> with references to the source
065: * byte arrays. The references are copied to a new
066: * <code>ArrayList</code> instance.
067: * @param length the length of the stream. Never published outside
068: * this object. Note that the length specified can be shorter
069: * than the actual number of bytes in the byte arrays.
070: * @throws IllegalArgumentException if there is less data available than
071: * specified by <code>length</code>, or <code>length</code> is
072: * negative.
073: */
074: public ByteArrayCombinerStream(ArrayList arraysIn, long length) {
075: // Don't allow negative length.
076: if (length < 0) {
077: throw new IllegalArgumentException(
078: "Length cannot be negative: " + length);
079: }
080: this .specifiedLength = length;
081: long tmpRemaining = length;
082: if (arraysIn != null && arraysIn.size() > 0) {
083: // Copy references to the byte arrays to a new ArrayList.
084: int arrayCount = arraysIn.size();
085: byte[] tmpArray;
086: arrays = new ArrayList(arrayCount);
087: // Truncate data if there are more bytes then specified.
088: // Done to simplify boundary checking in the read-methods.
089: for (int i = 0; i < arrayCount && tmpRemaining > 0; i++) {
090: tmpArray = (byte[]) arraysIn.get(i);
091: if (tmpRemaining < tmpArray.length) {
092: // Create a new shrunk array.
093: byte[] shrunkArray = new byte[(int) (tmpRemaining)];
094: System.arraycopy(tmpArray, 0, shrunkArray, 0,
095: shrunkArray.length);
096: arrays.add(shrunkArray);
097: tmpRemaining -= shrunkArray.length;
098: break;
099: } else {
100: // Add the whole array.
101: tmpRemaining -= tmpArray.length;
102: arrays.add(tmpArray);
103: }
104: }
105: // Set the first array as the current one.
106: curArray = nextArray();
107: } else {
108: // Specify gOffset so available returns 0;
109: gOffset = length;
110: arrays = null;
111: }
112: // If we don't have enough data, throw exception.
113: if (tmpRemaining > 0) {
114: throw new IllegalArgumentException("Not enough data, "
115: + tmpRemaining
116: + " bytes short of specified length " + length);
117: }
118: }
119:
120: /**
121: * Read a single byte.
122: *
123: * @return a byte, or <code>-1</code> if the end-of-stream is reached
124: */
125: public int read() throws IOException {
126: if (curArray == null) {
127: return -1;
128: }
129: if (off >= curArray.length) {
130: curArray = nextArray();
131: if (curArray == null) {
132: return -1;
133: }
134: }
135: gOffset++;
136: return curArray[off++];
137: }
138:
139: /**
140: * Reads up to len bytes of data from the input stream into an array of
141: * bytes.
142: * An attempt is made to read as many as <code>len</code> bytes, but
143: * a smaller number may be read. The number of bytes actually read
144: * is returned as an integer.
145: *
146: * @param buf the array to copy bytes into
147: * @param offset offset into the array
148: * @param length the maximum number of bytes to read
149: * @return the number of bytes read, or <code>-1</code> if end-of-stream
150: * is reached
151: */
152: public int read(byte[] buf, int offset, int length)
153: throws IOException {
154: int read = 0;
155: if (curArray == null) {
156: return -1;
157: }
158: if (length <= (curArray.length - off)) {
159: System.arraycopy(curArray, off, buf, offset, length);
160: off += length;
161: gOffset += length;
162: read = length;
163: } else {
164: int toRead = 0;
165: while (curArray != null && read < length) {
166: toRead = Math.min(curArray.length - off, length - read);
167: System.arraycopy(curArray, off, buf, offset + read,
168: toRead);
169: read += toRead;
170: gOffset += toRead;
171: off += toRead;
172: if (off < curArray.length) {
173: break;
174: }
175: curArray = nextArray();
176: }
177: }
178: return read;
179: }
180:
181: /**
182: * Return the number of available bytes.
183: * The method assumes the specified length of the stream is correct.
184: *
185: * @return number of available bytes
186: */
187: public int available() {
188: return (int) (specifiedLength - gOffset);
189: }
190:
191: /**
192: * Fetch the next array to read data from.
193: * The reference in the <code>ArrayList</code> is cleared when the array
194: * is "taken out".
195: *
196: * @return a <code>byte[]</code>-object, or <code>null</code> if there are
197: * no more arrays
198: */
199: private byte[] nextArray() {
200: if (arrayIndex >= arrays.size()) {
201: return null;
202: }
203: byte[] tmp = (byte[]) arrays.get(arrayIndex);
204: arrays.set(arrayIndex++, null);
205: off = 0;
206: return tmp;
207: }
208: } // End of class ByteArrayCombinerStream
|