    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 *      http://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) {
    095                return false;
    096            }
    097            if (!c1Buffer.equals(c2Buffer)) {
    098                return false;
    099            }
    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}