View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.io.channels;
19  
20  import java.io.IOException;
21  import java.nio.ByteBuffer;
22  import java.nio.channels.FileChannel;
23  import java.nio.channels.ReadableByteChannel;
24  import java.nio.channels.SeekableByteChannel;
25  import java.util.Objects;
26  
27  import org.apache.commons.io.IOUtils;
28  
29  /**
30   * Works with {@link FileChannel}.
31   *
32   * @since 2.15.0
33   */
34  public final class FileChannels {
35  
36      /**
37       * Tests if two file channel contents are equal starting at their respective current positions.
38       *
39       * @param channel1       A file channel.
40       * @param channel2       Another file channel.
41       * @param bufferCapacity The two internal buffer capacities, in bytes.
42       * @return true if the contents of both RandomAccessFiles are equal, false otherwise.
43       * @throws IOException if an I/O error occurs.
44       * @deprecated Use {@link #contentEquals(SeekableByteChannel, SeekableByteChannel, int)}.
45       */
46      @Deprecated
47      public static boolean contentEquals(final FileChannel channel1, final FileChannel channel2, final int bufferCapacity) throws IOException {
48          return contentEquals((SeekableByteChannel) channel1, channel2, bufferCapacity);
49      }
50  
51      /**
52       * Tests if two readable byte channel contents are equal starting at their respective current positions.
53       *
54       * @param channel1       A readable byte channel.
55       * @param channel2       Another readable byte channel.
56       * @param bufferCapacity The two internal buffer capacities, in bytes.
57       * @return true if the contents of both RandomAccessFiles are equal, false otherwise.
58       * @throws IOException if an I/O error occurs or the timeout is met.
59       * @since 2.19.0
60       */
61      public static boolean contentEquals(final ReadableByteChannel channel1, final ReadableByteChannel channel2, final int bufferCapacity) throws IOException {
62          // Before making any changes, please test with org.apache.commons.io.jmh.IOUtilsContentEqualsInputStreamsBenchmark
63          // Short-circuit test
64          if (Objects.equals(channel1, channel2)) {
65              return true;
66          }
67          // Don't use ByteBuffer#compact() to avoid extra copying.
68          final ByteBuffer c1Buffer = ByteBuffer.allocateDirect(bufferCapacity);
69          final ByteBuffer c2Buffer = ByteBuffer.allocateDirect(bufferCapacity);
70          int c1NumRead = 0;
71          int c2NumRead = 0;
72          boolean c1Read0 = false;
73          boolean c2Read0 = false;
74          // If a channel is a non-blocking channel, it may return 0 bytes read for any given call.
75          while (true) {
76              if (!c2Read0) {
77                  c1NumRead = readToLimit(channel1, c1Buffer);
78                  c1Buffer.clear();
79                  c1Read0 = c1NumRead == 0;
80              }
81              if (!c1Read0) {
82                  c2NumRead = readToLimit(channel2, c2Buffer);
83                  c2Buffer.clear();
84                  c2Read0 = c2NumRead == 0;
85              }
86              if (c1NumRead == IOUtils.EOF && c2NumRead == IOUtils.EOF) {
87                  return c1Buffer.equals(c2Buffer);
88              }
89              if (c1NumRead == 0 || c2NumRead == 0) {
90                  // 0 may be returned from a non-blocking channel.
91                  Thread.yield();
92                  continue;
93              }
94              if (c1NumRead != c2NumRead) {
95                  return false;
96              }
97              if (!c1Buffer.equals(c2Buffer)) {
98                  return false;
99              }
100         }
101     }
102 
103     /**
104      * Tests if two seekable byte channel contents are equal starting at their respective current positions.
105      * <p>
106      * If the two channels have different sizes, no content comparison takes place, and this method returns false.
107      * </p>
108      *
109      * @param channel1       A seekable byte channel.
110      * @param channel2       Another seekable byte channel.
111      * @param bufferCapacity The two internal buffer capacities, in bytes.
112      * @return true if the contents of both RandomAccessFiles are equal, false otherwise.
113      * @throws IOException if an I/O error occurs or the timeout is met.
114      * @since 2.19.0
115      */
116     public static boolean contentEquals(final SeekableByteChannel channel1, final SeekableByteChannel channel2, final int bufferCapacity) throws IOException {
117         // Short-circuit test
118         if (Objects.equals(channel1, channel2)) {
119             return true;
120         }
121         // Short-circuit test
122         final long size1 = size(channel1);
123         final long size2 = size(channel2);
124         if (size1 != size2) {
125             return false;
126         }
127         return size1 == 0 && size2 == 0 || contentEquals((ReadableByteChannel) channel1, channel2, bufferCapacity);
128     }
129 
130     /**
131      * Reads a sequence of bytes from a channel into the given buffer until the buffer reaches its limit or the channel has reaches end-of-stream.
132      * <p>
133      * The buffer's limit is not changed.
134      * </p>
135      *
136      * @param channel The source channel.
137      * @param dst     The buffer into which bytes are to be transferred.
138      * @return The number of bytes read, <em>never</em> zero, or {@code -1} if the channel has reached end-of-stream
139      * @throws IOException              If some other I/O error occurs.
140      * @throws IllegalArgumentException If there is room in the given buffer.
141      */
142     private static int readToLimit(final ReadableByteChannel channel, final ByteBuffer dst) throws IOException {
143         if (!dst.hasRemaining()) {
144             throw new IllegalArgumentException();
145         }
146         int totalRead = 0;
147         while (dst.hasRemaining()) {
148             final int numRead;
149             if ((numRead = channel.read(dst)) == IOUtils.EOF) {
150                 break;
151             }
152             if (numRead == 0) {
153                 // 0 may be returned from a non-blocking channel.
154                 Thread.yield();
155             } else {
156                 totalRead += numRead;
157             }
158         }
159         return totalRead != 0 ? totalRead : IOUtils.EOF;
160     }
161 
162     private static long size(final SeekableByteChannel channel) throws IOException {
163         return channel != null ? channel.size() : 0;
164     }
165 
166     /**
167      * Don't instantiate.
168      */
169     private FileChannels() {
170         // no-op
171     }
172 }