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}