    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}