001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.io.channels;
019
020import java.io.IOException;
021import java.nio.ByteBuffer;
022import java.nio.channels.FileChannel;
023import java.nio.channels.ReadableByteChannel;
024import java.nio.channels.SeekableByteChannel;
025import java.util.Objects;
026
027import org.apache.commons.io.IOUtils;
028
029/**
030 * Works with {@link FileChannel}.
031 *
032 * @since 2.15.0
033 */
034public final class FileChannels {
035
036    /**
037     * Tests if two file channel contents are equal starting at their respective current positions.
038     *
039     * @param channel1       A file channel.
040     * @param channel2       Another file channel.
041     * @param bufferCapacity The two internal buffer capacities, in bytes.
042     * @return true if the contents of both RandomAccessFiles are equal, false otherwise.
043     * @throws IOException if an I/O error occurs.
044     * @deprecated Use {@link #contentEquals(SeekableByteChannel, SeekableByteChannel, int)}.
045     */
046    @Deprecated
047    public static boolean contentEquals(final FileChannel channel1, final FileChannel channel2, final int bufferCapacity) throws IOException {
048        return contentEquals((SeekableByteChannel) channel1, channel2, bufferCapacity);
049    }
050
051    /**
052     * Tests if two readable byte channel contents are equal starting at their respective current positions.
053     *
054     * @param channel1       A readable byte channel.
055     * @param channel2       Another readable byte channel.
056     * @param bufferCapacity The two internal buffer capacities, in bytes.
057     * @return true if the contents of both RandomAccessFiles are equal, false otherwise.
058     * @throws IOException if an I/O error occurs or the timeout is met.
059     * @since 2.19.0
060     */
061    public static boolean contentEquals(final ReadableByteChannel channel1, final ReadableByteChannel channel2, final int bufferCapacity) throws IOException {
062        // Before making any changes, please test with org.apache.commons.io.jmh.IOUtilsContentEqualsInputStreamsBenchmark
063        // Short-circuit test
064        if (Objects.equals(channel1, channel2)) {
065            return true;
066        }
067        // Don't use ByteBuffer#compact() to avoid extra copying.
068        final ByteBuffer c1Buffer = ByteBuffer.allocateDirect(bufferCapacity);
069        final ByteBuffer c2Buffer = ByteBuffer.allocateDirect(bufferCapacity);
070        int c1NumRead = 0;
071        int c2NumRead = 0;
072        boolean c1Read0 = false;
073        boolean c2Read0 = false;
074        // If a channel is a non-blocking channel, it may return 0 bytes read for any given call.
075        while (true) {
076            if (!c2Read0) {
077                c1NumRead = readToLimit(channel1, c1Buffer);
078                c1Buffer.clear();
079                c1Read0 = c1NumRead == 0;
080            }
081            if (!c1Read0) {
082                c2NumRead = readToLimit(channel2, c2Buffer);
083                c2Buffer.clear();
084                c2Read0 = c2NumRead == 0;
085            }
086            if (c1NumRead == IOUtils.EOF && c2NumRead == IOUtils.EOF) {
087                return c1Buffer.equals(c2Buffer);
088            }
089            if (c1NumRead == 0 || c2NumRead == 0) {
090                // 0 may be returned from a non-blocking channel.
091                Thread.yield();
092                continue;
093            }
094            if (c1NumRead != c2NumRead || !c1Buffer.equals(c2Buffer)) {
095                return false;
096            }
097        }
098    }
099
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}