blob: abb8c86122d8a2cf11f5c02b541fc9ea9b23e7c9 [file] [log] [blame]
using J2N.IO;
using Lucene.Net.Support.Threading;
using System;
using System.IO;
using System.Runtime.CompilerServices;
namespace Lucene.Net.Support.IO
{
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/// <summary>
/// Extension methods that make a <see cref="Stream"/> effectively into a
/// binary serializer with no encoding. We simply convert types into bytes
/// and write them without any concern whether surrogate pairs are respected,
/// similar to what BinaryFormatter does.
/// This makes it possible to serialize/deserialize raw character arrays
/// and get the data back in the same order without any exceptions warning
/// that the order is not valid and without the need for BinaryFormatter.
/// <para/>
/// Byte order is little-endian (same as <see cref="BinaryReader"/> and <see cref="BinaryWriter"/>).
/// </summary>
internal static class StreamExtensions
{
private static readonly ConditionalWeakTable<Stream, object> lockCache = new ConditionalWeakTable<Stream, object>();
/// <summary>
/// Reads a sequence of bytes from a <see cref="Stream"/> to the given <see cref="ByteBuffer"/>, starting at the given position.
/// The <paramref name="stream"/> must be both seekable and readable.
/// </summary>
/// <param name="stream">The stream to read.</param>
/// <param name="destination">The <see cref="ByteBuffer"/> to write to.</param>
/// <param name="position">The file position at which the transfer is to begin; must be non-negative.</param>
/// <returns>The number of bytes read, possibly zero.</returns>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> or <paramref name="destination"/> is <c>null</c></exception>
/// <exception cref="NotSupportedException">
/// <paramref name="stream"/> is not readable.
/// <para/>
/// -or-
/// <para/>
/// <paramref name="stream"/> is not seekable.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="position"/> is less than 0.
/// <para/>
/// -or-
/// <para/>
/// <paramref name="position"/> is greater than the <see cref="Stream.Length"/> of the stream.
/// </exception>
/// <exception cref="IOException">An I/O error occurs.</exception>
/// <exception cref="ObjectDisposedException"><paramref name="stream"/> has already been disposed.</exception>
/// <remarks>
/// This method is atomic when used by itself, but does not synchronize with the rest of the stream methods.
/// </remarks>
public static int Read(this Stream stream, ByteBuffer destination, long position)
{
if (stream is null)
throw new ArgumentNullException(nameof(stream));
if (destination is null)
throw new ArgumentNullException(nameof(destination));
if (position < 0)
throw new ArgumentOutOfRangeException(nameof(position));
if (!stream.CanSeek)
throw new NotSupportedException("Stream does not support seeking.");
if (!stream.CanRead)
throw new NotSupportedException("Stream does not support reading.");
if (position > stream.Length)
return 0;
int read = 0;
object readLock = lockCache.GetOrCreateValue(stream);
UninterruptableMonitor.Enter(readLock);
try
{
long originalPosition = stream.Position;
stream.Seek(position, SeekOrigin.Begin);
if (destination.HasArray)
{
// If the buffer has an array, we can write to it directly and save
// an extra copy operation.
// Read from the stream
read = stream.Read(destination.Array, destination.Position, destination.Remaining);
destination.Position += read;
}
else
{
// If the buffer has no array, we must use a local buffer
byte[] buffer = new byte[destination.Remaining];
// Read from the stream
read = stream.Read(buffer, 0, buffer.Length);
// Write to the byte buffer
destination.Put(buffer, 0, read);
}
// Per Java's FileChannel.Read(), we don't want to alter the position
// of the stream, so we return it as it was originally.
stream.Seek(originalPosition, SeekOrigin.Begin);
}
finally
{
UninterruptableMonitor.Exit(readLock);
}
return read;
}
public static void Write(this Stream stream, char[] chars)
{
if (stream is null)
throw new ArgumentNullException(nameof(stream));
if (chars is null)
throw new ArgumentNullException(nameof(chars));
byte[] newBytes = new byte[chars.Length * 2];
for (int index = 0; index < chars.Length; index++)
{
int newIndex = index == 0 ? index : index * 2;
newBytes[newIndex] = (byte)chars[index];
newBytes[newIndex + 1] = (byte)(chars[index] >> 8);
}
stream.Write(newBytes, 0, newBytes.Length);
}
public static char[] ReadChars(this Stream stream, int count)
{
if (stream is null)
throw new ArgumentNullException(nameof(stream));
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count));
byte[] buff = new byte[2];
char[] newChars = new char[count];
for (int i = 0; i < count; i++)
{
stream.Read(buff, 0, 2);
newChars[i] = (char)((buff[0] & 0xff) | ((buff[1] & 0xff) << 8));
}
return newChars;
}
public static void Write(this Stream stream, int value)
{
if (stream is null)
throw new ArgumentNullException(nameof(stream));
byte[] buff = new byte[4];
buff[0] = (byte)(value);
buff[1] = (byte)(value >> 8);
buff[2] = (byte)(value >> 16);
buff[3] = (byte)(value >> 24);
stream.Write(buff, 0, buff.Length);
}
public static int ReadInt32(this Stream stream)
{
if (stream is null)
throw new ArgumentNullException(nameof(stream));
byte[] buff = new byte[4];
stream.Read(buff, 0, buff.Length);
return (buff[0] & 0xff) | ((buff[1] & 0xff) << 8) |
((buff[2] & 0xff) << 16) | ((buff[3] & 0xff) << 24);
}
public static void Write(this Stream stream, long value)
{
if (stream is null)
throw new ArgumentNullException(nameof(stream));
byte[] buff = new byte[8];
buff[0] = (byte)value;
buff[1] = (byte)(value >> 8);
buff[2] = (byte)(value >> 16);
buff[3] = (byte)(value >> 24);
buff[4] = (byte)(value >> 32);
buff[5] = (byte)(value >> 40);
buff[6] = (byte)(value >> 48);
buff[7] = (byte)(value >> 56);
stream.Write(buff, 0, buff.Length);
}
public static long ReadInt64(this Stream stream)
{
if (stream is null)
throw new ArgumentNullException(nameof(stream));
byte[] buff = new byte[8];
stream.Read(buff, 0, buff.Length);
uint lo = (uint)(buff[0] | buff[1] << 8 |
buff[2] << 16 | buff[3] << 24);
uint hi = (uint)(buff[4] | buff[5] << 8 |
buff[6] << 16 | buff[7] << 24);
return (long)((ulong)hi) << 32 | lo;
}
}
}