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    *   https://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.ObjectUtil;
21  import io.netty.util.internal.logging.InternalLogger;
22  import io.netty.util.internal.logging.InternalLoggerFactory;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.io.RandomAccessFile;
27  import java.nio.channels.FileChannel;
28  import java.nio.channels.WritableByteChannel;
29  
30  import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
31  
32  /**
33   * Default {@link FileRegion} implementation which transfer data from a {@link FileChannel} or {@link File}.
34   *
35   * Be aware that the {@link FileChannel} will be automatically closed once {@link #refCnt()} returns
36   * {@code 0}.
37   */
38  public class DefaultFileRegion extends AbstractReferenceCounted implements FileRegion {
39  
40      private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultFileRegion.class);
41      private final File f;
42      private final long position;
43      private final long count;
44      private long transferred;
45      private FileChannel file;
46  
47      /**
48       * Create a new instance
49       *
50       * @param fileChannel      the {@link FileChannel} which should be transferred
51       * @param position         the position from which the transfer should start
52       * @param count            the number of bytes to transfer
53       */
54      public DefaultFileRegion(FileChannel fileChannel, long position, long count) {
55          this.file = ObjectUtil.checkNotNull(fileChannel, "fileChannel");
56          this.position = checkPositiveOrZero(position, "position");
57          this.count = checkPositiveOrZero(count, "count");
58          this.f = null;
59      }
60  
61      /**
62       * Create a new instance using the given {@link File}. The {@link File} will be opened lazily or
63       * explicitly via {@link #open()}.
64       *
65       * @param file         the {@link File} which should be transferred
66       * @param position     the position from which the transfer should start
67       * @param count        the number of bytes to transfer
68       */
69      public DefaultFileRegion(File file, long position, long count) {
70          this.f = ObjectUtil.checkNotNull(file, "file");
71          this.position = checkPositiveOrZero(position, "position");
72          this.count = checkPositiveOrZero(count, "count");
73      }
74  
75      /**
76       * Returns {@code true} if the {@link FileRegion} has a open file-descriptor
77       */
78      public boolean isOpen() {
79          return file != null;
80      }
81  
82      /**
83       * Explicitly open the underlying file-descriptor if not done yet.
84       */
85      public void open() throws IOException {
86          if (!isOpen() && refCnt() > 0) {
87              // Only open if this DefaultFileRegion was not released yet.
88              file = new RandomAccessFile(f, "r").getChannel();
89          }
90      }
91  
92      @Override
93      public long position() {
94          return position;
95      }
96  
97      @Override
98      public long count() {
99          return count;
100     }
101 
102     @Deprecated
103     @Override
104     public long transfered() {
105         return transferred;
106     }
107 
108     @Override
109     public long transferred() {
110         return transferred;
111     }
112 
113     @Override
114     public long transferTo(WritableByteChannel target, long position) throws IOException {
115         long count = this.count - position;
116         if (count < 0 || position < 0) {
117             throw new IllegalArgumentException(
118                     "position out of range: " + position +
119                     " (expected: 0 - " + (this.count - 1) + ')');
120         }
121         if (count == 0) {
122             return 0L;
123         }
124         if (refCnt() == 0) {
125             throw new IllegalReferenceCountException(0);
126         }
127         // Call open to make sure fc is initialized. This is a no-oop if we called it before.
128         open();
129 
130         long written = file.transferTo(this.position + position, count, target);
131         if (written > 0) {
132             transferred += written;
133         } else if (written == 0) {
134             // If the amount of written data is 0 we need to check if the requested count is bigger then the
135             // actual file itself as it may have been truncated on disk.
136             //
137             // See https://github.com/netty/netty/issues/8868
138             validate(this, position);
139         }
140         return written;
141     }
142 
143     @Override
144     protected void deallocate() {
145         FileChannel file = this.file;
146 
147         if (file == null) {
148             return;
149         }
150         this.file = null;
151 
152         try {
153             file.close();
154         } catch (IOException e) {
155             logger.warn("Failed to close a file.", e);
156         }
157     }
158 
159     @Override
160     public FileRegion retain() {
161         super.retain();
162         return this;
163     }
164 
165     @Override
166     public FileRegion retain(int increment) {
167         super.retain(increment);
168         return this;
169     }
170 
171     @Override
172     public FileRegion touch() {
173         return this;
174     }
175 
176     @Override
177     public FileRegion touch(Object hint) {
178         return this;
179     }
180 
181     static void validate(DefaultFileRegion region, long position) throws IOException {
182         // If the amount of written data is 0 we need to check if the requested count is bigger then the
183         // actual file itself as it may have been truncated on disk.
184         //
185         // See https://github.com/netty/netty/issues/8868
186         long size = region.file.size();
187         long count = region.count - position;
188         if (region.position + count + position > size) {
189             throw new IOException("Underlying file size " + size + " smaller then requested count " + region.count);
190         }
191     }
192 }