View Javadoc

1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the 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 io.netty.channel;
17  
18  import io.netty.util.AbstractReferenceCounted;
19  import io.netty.util.IllegalReferenceCountException;
20  import io.netty.util.internal.logging.InternalLogger;
21  import io.netty.util.internal.logging.InternalLoggerFactory;
22  
23  import java.io.File;
24  import java.io.IOException;
25  import java.io.RandomAccessFile;
26  import java.nio.channels.FileChannel;
27  import java.nio.channels.WritableByteChannel;
28  
29  /**
30   * Default {@link FileRegion} implementation which transfer data from a {@link FileChannel} or {@link File}.
31   *
32   * Be aware that the {@link FileChannel} will be automatically closed once {@link #refCnt()} returns
33   * {@code 0}.
34   */
35  public class DefaultFileRegion extends AbstractReferenceCounted implements FileRegion {
36  
37      private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultFileRegion.class);
38      private final File f;
39      private final long position;
40      private final long count;
41      private long transfered;
42      private FileChannel file;
43  
44      /**
45       * Create a new instance
46       *
47       * @param file      the {@link FileChannel} which should be transfered
48       * @param position  the position from which the transfer should start
49       * @param count     the number of bytes to transfer
50       */
51      public DefaultFileRegion(FileChannel file, long position, long count) {
52          if (file == null) {
53              throw new NullPointerException("file");
54          }
55          if (position < 0) {
56              throw new IllegalArgumentException("position must be >= 0 but was " + position);
57          }
58          if (count < 0) {
59              throw new IllegalArgumentException("count must be >= 0 but was " + count);
60          }
61          this.file = file;
62          this.position = position;
63          this.count = count;
64          f = null;
65      }
66  
67      /**
68       * Create a new instance using the given {@link File}. The {@link File} will be opened lazily or
69       * explicitly via {@link #open()}.
70       *
71       * @param f         the {@link File} which should be transfered
72       * @param position  the position from which the transfer should start
73       * @param count     the number of bytes to transfer
74       */
75      public DefaultFileRegion(File f, long position, long count) {
76          if (f == null) {
77              throw new NullPointerException("f");
78          }
79          if (position < 0) {
80              throw new IllegalArgumentException("position must be >= 0 but was " + position);
81          }
82          if (count < 0) {
83              throw new IllegalArgumentException("count must be >= 0 but was " + count);
84          }
85          this.position = position;
86          this.count = count;
87          this.f = f;
88      }
89  
90      /**
91       * Returns {@code true} if the {@link FileRegion} has a open file-descriptor
92       */
93      public boolean isOpen() {
94          return file != null;
95      }
96  
97      /**
98       * Explicitly open the underlying file-descriptor if not done yet.
99       */
100     public void open() throws IOException {
101         if (!isOpen() && refCnt() > 0) {
102             // Only open if this DefaultFileRegion was not released yet.
103             file = new RandomAccessFile(f, "r").getChannel();
104         }
105     }
106 
107     @Override
108     public long position() {
109         return position;
110     }
111 
112     @Override
113     public long count() {
114         return count;
115     }
116 
117     @Override
118     public long transfered() {
119         return transfered;
120     }
121 
122     @Override
123     public long transferTo(WritableByteChannel target, long position) throws IOException {
124         long count = this.count - position;
125         if (count < 0 || position < 0) {
126             throw new IllegalArgumentException(
127                     "position out of range: " + position +
128                     " (expected: 0 - " + (this.count - 1) + ')');
129         }
130         if (count == 0) {
131             return 0L;
132         }
133         if (refCnt() == 0) {
134             throw new IllegalReferenceCountException(0);
135         }
136         // Call open to make sure fc is initialized. This is a no-oop if we called it before.
137         open();
138 
139         long written = file.transferTo(this.position + position, count, target);
140         if (written > 0) {
141             transfered += written;
142         }
143         return written;
144     }
145 
146     @Override
147     protected void deallocate() {
148         FileChannel file = this.file;
149 
150         if (file == null) {
151             return;
152         }
153         this.file = null;
154 
155         try {
156             file.close();
157         } catch (IOException e) {
158             if (logger.isWarnEnabled()) {
159                 logger.warn("Failed to close a file.", e);
160             }
161         }
162     }
163 }