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 * https://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 }