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