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}