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 || !c1Buffer.equals(c2Buffer)) {
95 return false;
96 }
97 }
98 }
99
100 /**
101 * Tests if two seekable byte channel contents are equal starting at their respective current positions.
102 * <p>
103 * If the two channels have different sizes, no content comparison takes place, and this method returns false.
104 * </p>
105 *
106 * @param channel1 A seekable byte channel.
107 * @param channel2 Another seekable byte channel.
108 * @param bufferCapacity The two internal buffer capacities, in bytes.
109 * @return true if the contents of both RandomAccessFiles are equal, false otherwise.
110 * @throws IOException if an I/O error occurs or the timeout is met.
111 * @since 2.19.0
112 */
113 public static boolean contentEquals(final SeekableByteChannel channel1, final SeekableByteChannel channel2, final int bufferCapacity) throws IOException {
114 // Short-circuit test
115 if (Objects.equals(channel1, channel2)) {
116 return true;
117 }
118 // Short-circuit test
119 final long size1 = size(channel1);
120 final long size2 = size(channel2);
121 if (size1 != size2) {
122 return false;
123 }
124 return size1 == 0 && size2 == 0 || contentEquals((ReadableByteChannel) channel1, channel2, bufferCapacity);
125 }
126
127 /**
128 * 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.
129 * <p>
130 * The buffer's limit is not changed.
131 * </p>
132 *
133 * @param channel The source channel.
134 * @param dst The buffer into which bytes are to be transferred.
135 * @return The number of bytes read, <em>never</em> zero, or {@code -1} if the channel has reached end-of-stream.
136 * @throws IOException If some other I/O error occurs.
137 * @throws IllegalArgumentException If there is room in the given buffer.
138 */
139 private static int readToLimit(final ReadableByteChannel channel, final ByteBuffer dst) throws IOException {
140 if (!dst.hasRemaining()) {
141 throw new IllegalArgumentException();
142 }
143 int totalRead = 0;
144 while (dst.hasRemaining()) {
145 final int numRead;
146 if ((numRead = channel.read(dst)) == IOUtils.EOF) {
147 break;
148 }
149 if (numRead == 0) {
150 // 0 may be returned from a non-blocking channel.
151 Thread.yield();
152 } else {
153 totalRead += numRead;
154 }
155 }
156 return totalRead != 0 ? totalRead : IOUtils.EOF;
157 }
158
159 private static long size(final SeekableByteChannel channel) throws IOException {
160 return channel != null ? channel.size() : 0;
161 }
162
163 /**
164 * Don't instantiate.
165 */
166 private FileChannels() {
167 // no-op
168 }
169 }