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}