View Javadoc

1   /*
2    * Copyright 2009 Red Hat, Inc.
3    *
4    * Red Hat licenses this file to you under the Apache License, version 2.0
5    * (the "License"); you may not use this file except in compliance with the
6    * License.  You may obtain a copy of the License at:
7    *
8    *    http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package org.jboss.netty.handler.stream;
17  
18  import static org.jboss.netty.buffer.ChannelBuffers.*;
19  
20  import java.io.File;
21  import java.io.FileInputStream;
22  import java.io.IOException;
23  import java.nio.ByteBuffer;
24  import java.nio.channels.FileChannel;
25  
26  import org.jboss.netty.channel.FileRegion;
27  
28  /**
29   * A {@link ChunkedInput} that fetches data from a file chunk by chunk using
30   * NIO {@link FileChannel}.
31   * <p>
32   * If your operating system supports
33   * <a href="http://en.wikipedia.org/wiki/Zero-copy">zero-copy file transfer</a>
34   * such as {@code sendfile()}, you might want to use {@link FileRegion} instead.
35   *
36   * @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
37   * @author <a href="http://gleamynode.net/">Trustin Lee</a>
38   * @author Frederic Bregier
39   * @version $Rev: 2243 $, $Date: 2010-04-16 14:01:55 +0900 (Fri, 16 Apr 2010) $
40   */
41  public class ChunkedNioFile implements ChunkedInput {
42  
43      private final FileChannel in;
44      private long startOffset;
45      private final long endOffset;
46      private final int chunkSize;
47      private volatile long offset;
48  
49      /**
50       * Creates a new instance that fetches data from the specified file.
51       */
52      public ChunkedNioFile(File in) throws IOException {
53          this(new FileInputStream(in).getChannel());
54      }
55  
56      /**
57       * Creates a new instance that fetches data from the specified file.
58       *
59       * @param chunkSize the number of bytes to fetch on each
60       *                  {@link #nextChunk()} call
61       */
62      public ChunkedNioFile(File in, int chunkSize) throws IOException {
63          this(new FileInputStream(in).getChannel(), chunkSize);
64      }
65  
66      /**
67       * Creates a new instance that fetches data from the specified file.
68       */
69      public ChunkedNioFile(FileChannel in) throws IOException {
70          this(in, ChunkedStream.DEFAULT_CHUNK_SIZE);
71      }
72  
73      /**
74       * Creates a new instance that fetches data from the specified file.
75       *
76       * @param chunkSize the number of bytes to fetch on each
77       *                  {@link #nextChunk()} call
78       */
79      public ChunkedNioFile(FileChannel in, int chunkSize) throws IOException {
80          this(in, 0, in.size(), chunkSize);
81      }
82  
83      /**
84       * Creates a new instance that fetches data from the specified file.
85       *
86       * @param offset the offset of the file where the transfer begins
87       * @param length the number of bytes to transfer
88       * @param chunkSize the number of bytes to fetch on each
89       *                  {@link #nextChunk()} call
90       */
91      public ChunkedNioFile(FileChannel in, long offset, long length, int chunkSize)
92              throws IOException {
93          if (in == null) {
94              throw new NullPointerException("in");
95          }
96          if (offset < 0) {
97              throw new IllegalArgumentException(
98                      "offset: " + offset + " (expected: 0 or greater)");
99          }
100         if (length < 0) {
101             throw new IllegalArgumentException(
102                     "length: " + length + " (expected: 0 or greater)");
103         }
104         if (chunkSize <= 0) {
105             throw new IllegalArgumentException(
106                     "chunkSize: " + chunkSize +
107                     " (expected: a positive integer)");
108         }
109 
110         if (offset != 0) {
111             in.position(offset);
112         }
113         this.in = in;
114         this.chunkSize = chunkSize;
115         this.offset = startOffset = offset;
116         endOffset = offset + length;
117     }
118 
119     /**
120      * Returns the offset in the file where the transfer began.
121      */
122     public long getStartOffset() {
123         return startOffset;
124     }
125 
126     /**
127      * Returns the offset in the file where the transfer will end.
128      */
129     public long getEndOffset() {
130         return endOffset;
131     }
132 
133     /**
134      * Returns the offset in the file where the transfer is happening currently.
135      */
136     public long getCurrentOffset() {
137         return offset;
138     }
139 
140     public boolean hasNextChunk() throws Exception {
141         return offset < endOffset && in.isOpen();
142     }
143 
144     public boolean isEndOfInput() throws Exception {
145         return !hasNextChunk();
146     }
147 
148     public void close() throws Exception {
149         in.close();
150     }
151 
152     public Object nextChunk() throws Exception {
153         long offset = this.offset;
154         if (offset >= endOffset) {
155             return null;
156         }
157 
158         int chunkSize = (int) Math.min(this.chunkSize, endOffset - offset);
159         byte[] chunkArray = new byte[chunkSize];
160         ByteBuffer chunk = ByteBuffer.wrap(chunkArray);
161         int readBytes = 0;
162         for (;;) {
163             int localReadBytes = in.read(chunk);
164             if (localReadBytes < 0) {
165                 break;
166             }
167             readBytes += localReadBytes;
168             if (readBytes == chunkSize) {
169                 break;
170             }
171         }
172 
173         this.offset += readBytes;
174         return wrappedBuffer(chunkArray);
175     }
176 }