001: /*
002: * $RCSfile: FileChannelImageInputStream.java,v $
003: *
004: *
005: * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * - Redistribution of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * - Redistribution in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * Neither the name of Sun Microsystems, Inc. or the names of
020: * contributors may be used to endorse or promote products derived
021: * from this software without specific prior written permission.
022: *
023: * This software is provided "AS IS," without a warranty of any
024: * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
025: * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
026: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
027: * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
028: * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
029: * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
030: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
031: * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
032: * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
033: * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
034: * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
035: * POSSIBILITY OF SUCH DAMAGES.
036: *
037: * You acknowledge that this software is not designed or intended for
038: * use in the design, construction, operation or maintenance of any
039: * nuclear facility.
040: *
041: * $Revision: 1.2 $
042: * $Date: 2007/09/14 22:57:55 $
043: * $State: Exp $
044: */
045: package com.sun.media.imageio.stream;
046:
047: import java.io.File;
048: import java.io.EOFException;
049: import java.io.IOException;
050: import java.nio.Buffer;
051: import java.nio.ByteBuffer;
052: import java.nio.ByteOrder;
053: import java.nio.CharBuffer;
054: import java.nio.DoubleBuffer;
055: import java.nio.FloatBuffer;
056: import java.nio.IntBuffer;
057: import java.nio.LongBuffer;
058: import java.nio.MappedByteBuffer;
059: import java.nio.ShortBuffer;
060: import java.nio.channels.FileChannel;
061: import javax.imageio.stream.ImageInputStreamImpl;
062:
063: /**
064: * A class which implements <code>ImageInputStream</code> using a
065: * <code>FileChannel</code> as the eventual data source. The channel
066: * contents are assumed to be stable during the lifetime of the object.
067: *
068: * <p>Memory mapping and new I/O view <code>Buffer</code>s are used to
069: * read the data. Only methods which provide significant performance
070: * improvement with respect to the superclass implementation are overridden.
071: * Overridden methods are not commented individually unless some noteworthy
072: * aspect of the implementation must be described.</p>
073: *
074: * <p>The methods of this class are <b>not</b> synchronized.</p>
075: *
076: * @see javax.imageio.stream.ImageInputStream
077: * @see java.nio
078: * @see java.nio.channels.FileChannel
079: */
080: public class FileChannelImageInputStream extends ImageInputStreamImpl {
081:
082: /** The <code>FileChannel</code> data source. */
083: private FileChannel channel;
084:
085: /** A memory mapping of all or part of the channel. */
086: private MappedByteBuffer mappedBuffer;
087:
088: /** The stream position of the mapping. */
089: private long mappedPos;
090:
091: /** The stream position least upper bound of the mapping. */
092: private long mappedUpperBound;
093:
094: /**
095: * Constructs a <code>FileChannelImageInputStream</code> from a
096: * <code>FileChannel</code>. The initial position of the stream
097: * stream is taken to be the position of the <code>FileChannel</code>
098: * parameter when this constructor is invoked. The stream and flushed
099: * positions are therefore both initialized to
100: * <code>channel.position()</code>.
101: *
102: * @param channel the source <code>FileChannel</code>.
103: *
104: * @throws IllegalArgumentException if <code>channel</code> is
105: * <code>null</code> or is not open.
106: * @throws IOException if a method invoked on <code>channel</code>
107: * throws an <code>IOException</code>.
108: */
109: public FileChannelImageInputStream(FileChannel channel)
110: throws IOException {
111:
112: // Check the parameter.
113: if (channel == null) {
114: throw new IllegalArgumentException("channel == null");
115: } else if (!channel.isOpen()) {
116: throw new IllegalArgumentException(
117: "channel.isOpen() == false");
118: }
119:
120: // Save the channel reference.
121: this .channel = channel;
122:
123: // Get the channel position.
124: long channelPosition = channel.position();
125:
126: // Set stream and flushed positions to initial channel position.
127: this .streamPos = this .flushedPos = channelPosition;
128:
129: // Determine the size of the mapping.
130: long fullSize = channel.size() - channelPosition;
131: long mappedSize = Math.min(fullSize, Integer.MAX_VALUE);
132:
133: // Set the mapped position and upper bound.
134: this .mappedPos = 0;
135: this .mappedUpperBound = mappedPos + mappedSize;
136:
137: // Map the file.
138: this .mappedBuffer = channel.map(FileChannel.MapMode.READ_ONLY,
139: channelPosition, mappedSize);
140: }
141:
142: /**
143: * Returns a <code>MappedByteBuffer</code> which memory maps
144: * at least from the channel position corresponding to the
145: * current stream position to <code>len</code> bytes beyond.
146: * A new buffer is created only if necessary.
147: *
148: * @param len The number of bytes required beyond the current stream
149: * position.
150: */
151: private MappedByteBuffer getMappedBuffer(int len)
152: throws IOException {
153: // If request is outside mapped region, map a new region.
154: if (streamPos < mappedPos
155: || streamPos + len >= mappedUpperBound) {
156:
157: // Set the map position.
158: mappedPos = streamPos;
159:
160: // Determine the map size.
161: long mappedSize = Math.min(channel.size() - mappedPos,
162: Integer.MAX_VALUE);
163:
164: // Set the mapped upper bound.
165: mappedUpperBound = mappedPos + mappedSize;
166:
167: // Map the file.
168: mappedBuffer = channel.map(FileChannel.MapMode.READ_ONLY,
169: mappedPos, mappedSize);
170:
171: mappedBuffer.order(super .getByteOrder());
172:
173: }
174:
175: return mappedBuffer;
176: }
177:
178: // --- Implementation of superclass abstract methods. ---
179:
180: public int read() throws IOException {
181: checkClosed();
182: bitOffset = 0;
183:
184: // Get the mapped buffer.
185: ByteBuffer byteBuffer = getMappedBuffer(1);
186:
187: // Check number of bytes left.
188: if (byteBuffer.remaining() < 1) {
189: // Return EOF.
190: return -1;
191: }
192:
193: // Get the byte from the buffer.
194: int value = byteBuffer.get() & 0xff;
195:
196: // Increment the stream position.
197: streamPos++;
198:
199: //System.out.println("value = "+value);
200:
201: return value;
202: }
203:
204: public int read(byte[] b, int off, int len) throws IOException {
205: if (off < 0 || len < 0 || off + len > b.length) {
206: // NullPointerException will be thrown before this if b is null.
207: throw new IndexOutOfBoundsException(
208: "off < 0 || len < 0 || off + len > b.length");
209: } else if (len == 0) {
210: return 0;
211: }
212:
213: checkClosed();
214: bitOffset = 0;
215:
216: // Get the mapped buffer.
217: ByteBuffer byteBuffer = getMappedBuffer(len);
218:
219: // Get the number of bytes remaining.
220: int numBytesRemaining = byteBuffer.remaining();
221:
222: // Check number of bytes left.
223: if (numBytesRemaining < 1) {
224: // Return EOF.
225: return -1;
226: } else if (len > numBytesRemaining) {
227: // Clamp 'len' to number of bytes in Buffer. Apparently some
228: // readers (JPEG) request data beyond the end of the file.
229: len = numBytesRemaining;
230: }
231:
232: // Read the data from the buffer.
233: byteBuffer.get(b, off, len);
234:
235: // Increment the stream position.
236: streamPos += len;
237:
238: return len;
239: }
240:
241: // --- Overriding of superclass methods. ---
242:
243: /**
244: * Invokes the superclass method and sets the internal reference
245: * to the source <code>FileChannel</code> to <code>null</code>.
246: * The source <code>FileChannel</code> is not closed.
247: *
248: * @exception IOException if an error occurs.
249: */
250: public void close() throws IOException {
251: super .close();
252: channel = null;
253: }
254:
255: public void readFully(char[] c, int off, int len)
256: throws IOException {
257: if (off < 0 || len < 0 || off + len > c.length) {
258: // NullPointerException will be thrown before this if c is null.
259: throw new IndexOutOfBoundsException(
260: "off < 0 || len < 0 || off + len > c.length");
261: } else if (len == 0) {
262: return;
263: }
264:
265: // Determine the requested length in bytes.
266: int byteLen = 2 * len;
267:
268: // Get the mapped buffer.
269: ByteBuffer byteBuffer = getMappedBuffer(byteLen);
270:
271: // Ensure enough bytes remain.
272: if (byteBuffer.remaining() < byteLen) {
273: throw new EOFException();
274: }
275:
276: // Get the view Buffer.
277: CharBuffer viewBuffer = byteBuffer.asCharBuffer();
278:
279: // Get the chars.
280: viewBuffer.get(c, off, len);
281:
282: // Update the position.
283: seek(streamPos + byteLen);
284: }
285:
286: public void readFully(short[] s, int off, int len)
287: throws IOException {
288: if (off < 0 || len < 0 || off + len > s.length) {
289: // NullPointerException will be thrown before this if s is null.
290: throw new IndexOutOfBoundsException(
291: "off < 0 || len < 0 || off + len > s.length");
292: } else if (len == 0) {
293: return;
294: }
295:
296: // Determine the requested length in bytes.
297: int byteLen = 2 * len;
298:
299: // Get the mapped buffer.
300: ByteBuffer byteBuffer = getMappedBuffer(byteLen);
301:
302: // Ensure enough bytes remain.
303: if (byteBuffer.remaining() < byteLen) {
304: throw new EOFException();
305: }
306:
307: // Get the view Buffer.
308: ShortBuffer viewBuffer = byteBuffer.asShortBuffer();
309:
310: // Get the shorts.
311: viewBuffer.get(s, off, len);
312:
313: // Update the position.
314: seek(streamPos + byteLen);
315: }
316:
317: public void readFully(int[] i, int off, int len) throws IOException {
318: if (off < 0 || len < 0 || off + len > i.length) {
319: // NullPointerException will be thrown before this if i is null.
320: throw new IndexOutOfBoundsException(
321: "off < 0 || len < 0 || off + len > i.length");
322: } else if (len == 0) {
323: return;
324: }
325:
326: // Determine the requested length in bytes.
327: int byteLen = 4 * len;
328:
329: // Get the mapped buffer.
330: ByteBuffer byteBuffer = getMappedBuffer(byteLen);
331:
332: // Ensure enough bytes remain.
333: if (byteBuffer.remaining() < byteLen) {
334: throw new EOFException();
335: }
336:
337: // Get the view Buffer.
338: IntBuffer viewBuffer = byteBuffer.asIntBuffer();
339:
340: // Get the ints.
341: viewBuffer.get(i, off, len);
342:
343: // Update the position.
344: seek(streamPos + byteLen);
345: }
346:
347: public void readFully(long[] l, int off, int len)
348: throws IOException {
349: if (off < 0 || len < 0 || off + len > l.length) {
350: // NullPointerException will be thrown before this if l is null.
351: throw new IndexOutOfBoundsException(
352: "off < 0 || len < 0 || off + len > l.length");
353: } else if (len == 0) {
354: return;
355: }
356:
357: // Determine the requested length in bytes.
358: int byteLen = 8 * len;
359:
360: // Get the mapped buffer.
361: ByteBuffer byteBuffer = getMappedBuffer(byteLen);
362:
363: // Ensure enough bytes remain.
364: if (byteBuffer.remaining() < byteLen) {
365: throw new EOFException();
366: }
367:
368: // Get the view Buffer.
369: LongBuffer viewBuffer = byteBuffer.asLongBuffer();
370:
371: // Get the longs.
372: viewBuffer.get(l, off, len);
373:
374: // Update the position.
375: seek(streamPos + byteLen);
376: }
377:
378: public void readFully(float[] f, int off, int len)
379: throws IOException {
380: if (off < 0 || len < 0 || off + len > f.length) {
381: // NullPointerException will be thrown before this if f is null.
382: throw new IndexOutOfBoundsException(
383: "off < 0 || len < 0 || off + len > f.length");
384: } else if (len == 0) {
385: return;
386: }
387:
388: // Determine the requested length in bytes.
389: int byteLen = 4 * len;
390:
391: // Get the mapped buffer.
392: ByteBuffer byteBuffer = getMappedBuffer(byteLen);
393:
394: // Ensure enough bytes remain.
395: if (byteBuffer.remaining() < byteLen) {
396: throw new EOFException();
397: }
398:
399: // Get the view Buffer.
400: FloatBuffer viewBuffer = byteBuffer.asFloatBuffer();
401:
402: // Get the floats.
403: viewBuffer.get(f, off, len);
404:
405: // Update the position.
406: seek(streamPos + byteLen);
407: }
408:
409: public void readFully(double[] d, int off, int len)
410: throws IOException {
411: if (off < 0 || len < 0 || off + len > d.length) {
412: // NullPointerException will be thrown before this if d is null.
413: throw new IndexOutOfBoundsException(
414: "off < 0 || len < 0 || off + len > d.length");
415: } else if (len == 0) {
416: return;
417: }
418:
419: // Determine the requested length in bytes.
420: int byteLen = 8 * len;
421:
422: // Get the mapped buffer.
423: ByteBuffer byteBuffer = getMappedBuffer(byteLen);
424:
425: // Ensure enough bytes remain.
426: if (byteBuffer.remaining() < byteLen) {
427: throw new EOFException();
428: }
429:
430: // Get the view Buffer.
431: DoubleBuffer viewBuffer = byteBuffer.asDoubleBuffer();
432:
433: // Get the doubles.
434: viewBuffer.get(d, off, len);
435:
436: // Update the position.
437: seek(streamPos + byteLen);
438: }
439:
440: /**
441: * Returns the number of bytes currently in the <code>FileChannel</code>.
442: * If an <code>IOException</code> is encountered when querying the
443: * channel's size, -1L will be returned.
444: *
445: * @return The number of bytes in the channel
446: * -1L to indicate unknown length.
447: */
448: public long length() {
449: // Initialize to value indicating unknown length.
450: long length = -1L;
451:
452: // Set length to current size with respect to initial position.
453: try {
454: length = channel.size();
455: } catch (IOException e) {
456: // Default to unknown length.
457: }
458:
459: return length;
460: }
461:
462: /**
463: * Invokes the superclass method and sets the position within the
464: * memory mapped buffer. A new region is mapped if necessary. The
465: * position of the source <code>FileChannel</code> is not changed, i.e.,
466: * {@link java.nio.channels.FileChannel#position(long)} is not invoked.
467: */
468: public void seek(long pos) throws IOException {
469: super .seek(pos);
470:
471: if (pos >= mappedPos && pos < mappedUpperBound) {
472: // Seeking to location within mapped buffer: set buffer position.
473: mappedBuffer.position((int) (pos - mappedPos));
474: } else {
475: // Seeking to location outside mapped buffer: get a new mapped
476: // buffer at current position with maximal size.
477: int len = (int) Math.min(channel.size() - pos,
478: Integer.MAX_VALUE);
479: mappedBuffer = getMappedBuffer(len);
480: }
481: }
482:
483: public void setByteOrder(ByteOrder networkByteOrder) {
484: super.setByteOrder(networkByteOrder);
485: mappedBuffer.order(networkByteOrder);
486: }
487: }
|