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