blob: ae599981875a3dbcf14f840b13dd15bbffbedf11 [file] [log] [blame]
using J2N;
using Lucene.Net.Diagnostics;
using Lucene.Net.Support;
using Lucene.Net.Support.IO;
using Lucene.Net.Support.Text;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Lucene.Net.Util
{
/*
* 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.
*/
using Directory = Lucene.Net.Store.Directory;
/// <summary>
/// This class emulates the new Java 7 "Try-With-Resources" statement.
/// Remove once Lucene is on Java 7.
/// <para/>
/// @lucene.internal
/// </summary>
[ExceptionToClassNameConvention]
public static class IOUtils // LUCENENET specific - made static
{
/// <summary>
/// UTF-8 <see cref="Encoding"/> instance to prevent repeated
/// <see cref="Encoding.UTF8"/> lookups and match Java's behavior
/// with respect to a lack of a byte-order mark (BOM).
/// <para />
/// It is important to use this encoding over <see cref="Encoding.UTF8"/>
/// particularly when writing data, to ensure that the BOM is not written.
/// For reading data, either this or <see cref="Encoding.UTF8"/> can be used,
/// as both will correctly interpret data with or without a BOM.
/// </summary>
public static readonly Encoding ENCODING_UTF_8_NO_BOM = new UTF8Encoding(
encoderShouldEmitUTF8Identifier: false,
throwOnInvalidBytes: true);
/// <summary>
/// UTF-8 charset string.
/// <para/>Where possible, use <see cref="ENCODING_UTF_8_NO_BOM"/> instead,
/// as using the <see cref="string"/> constant may slow things down. </summary>
/// <seealso cref="ENCODING_UTF_8_NO_BOM"/>
public static readonly string UTF_8 = "UTF-8";
/// <summary>
/// Closes all given <see cref="ICloseable"/>s. Some of the
/// <see cref="ICloseable"/>s may be <c>null</c>; they are
/// ignored. After everything is closed, the method either
/// throws the first exception it hit while closing, or
/// completes normally if there were no exceptions.
/// </summary>
/// <param name="objects">Objects to call <see cref="ICloseable.Close()"/> on</param>
public static void Close(params ICloseable[] objects)
{
Exception th = null;
foreach (ICloseable @object in objects)
{
try
{
@object?.Close();
}
catch (Exception t) when (t.IsThrowable())
{
AddSuppressed(th, t);
if (th is null)
{
th = t;
}
}
}
ReThrow(th);
}
/// <summary>
/// Closes all given <see cref="ICloseable"/>s.
/// </summary>
/// <seealso cref="Close(ICloseable[])"/>
public static void Close(IEnumerable<ICloseable> objects)
{
Exception th = null;
foreach (ICloseable @object in objects)
{
try
{
@object?.Close();
}
catch (Exception t) when (t.IsThrowable())
{
AddSuppressed(th, t);
if (th is null)
{
th = t;
}
}
}
ReThrow(th);
}
/// <summary>
/// <para>Closes all given <see cref="ICloseable">ICloseable</see>s, suppressing all thrown exceptions. Some of the <see cref="ICloseable">ICloseable</see>s
/// may be <c>null</c>, they are ignored. After everything is closed, method either throws <paramref name="priorException"/>,
/// if one is supplied, or the first of suppressed exceptions, or completes normally.</para>
/// <para>Sample usage:
/// <code>
/// IDisposable resource1 = null, resource2 = null, resource3 = null;
/// ExpectedException priorE = null;
/// try
/// {
/// resource1 = ...; resource2 = ...; resource3 = ...; // Acquisition may throw ExpectedException
/// ..do..stuff.. // May throw ExpectedException
/// }
/// catch (ExpectedException e)
/// {
/// priorE = e;
/// }
/// finally
/// {
/// IOUtils.CloseWhileHandlingException(priorE, resource1, resource2, resource3);
/// }
/// </code>
/// </para>
/// </summary>
/// <param name="priorException"> <c>null</c> or an exception that will be rethrown after method completion. </param>
/// <param name="objects"> Objects to call <see cref="ICloseable.Close()"/> on. </param>
public static void CloseWhileHandlingException(Exception priorException, params ICloseable[] objects)
{
Exception th = null;
foreach (ICloseable @object in objects)
{
try
{
@object?.Close();
}
catch (Exception t) when (t.IsThrowable())
{
AddSuppressed(priorException ?? th, t);
if (th is null)
{
th = t;
}
}
}
if (priorException != null)
{
ExceptionDispatchInfo.Capture(priorException).Throw(); // LUCENENET: Rethrow to preserve stack details from the original throw
}
else
{
ReThrow(th);
}
}
/// <summary>
/// Closes all given <see cref="ICloseable"/>s, suppressing all thrown exceptions.
/// </summary>
/// <seealso cref="CloseWhileHandlingException(Exception, ICloseable[])"/>
public static void CloseWhileHandlingException(Exception priorException, IEnumerable<ICloseable> objects)
{
Exception th = null;
foreach (ICloseable @object in objects)
{
try
{
@object?.Close();
}
catch (Exception t) when (t.IsThrowable())
{
AddSuppressed(priorException ?? th, t);
if (th is null)
{
th = t;
}
}
}
if (priorException != null)
{
ExceptionDispatchInfo.Capture(priorException).Throw(); // LUCENENET: Rethrow to preserve stack details from the original throw
}
else
{
ReThrow(th);
}
}
/// <summary>
/// Closes all given <see cref="ICloseable"/>s, suppressing all thrown exceptions.
/// Some of the <see cref="ICloseable"/>s may be <c>null</c>, they are ignored.
/// </summary>
/// <param name="objects">Objects to call <see cref="ICloseable.Close()"/> on</param>
public static void CloseWhileHandlingException(params ICloseable[] objects)
{
foreach (ICloseable @object in objects)
{
try
{
@object?.Close();
}
catch (Exception t) when (t.IsThrowable())
{
// eat it
}
}
}
/// <summary>
/// Closes all given <see cref="ICloseable"/>s, suppressing all thrown exceptions.
/// </summary>
/// <seealso cref="CloseWhileHandlingException(IEnumerable{ICloseable})"/>
/// <seealso cref="CloseWhileHandlingException(ICloseable[])"/>
public static void CloseWhileHandlingException(IEnumerable<ICloseable> objects)
{
foreach (ICloseable @object in objects)
{
try
{
@object?.Close();
}
catch (Exception t) when (t.IsThrowable())
{
// eat it
}
}
}
// LUCENENET specific - added overloads starting with Dispose... instead of Close...
/// <summary>
/// <para>Disposes all given <c>IDisposable</c>s, suppressing all thrown exceptions. Some of the <c>IDisposable</c>s
/// may be <c>null</c>, they are ignored. After everything is disposed, method either throws <paramref name="priorException"/>,
/// if one is supplied, or the first of suppressed exceptions, or completes normally.</para>
/// <para>Sample usage:
/// <code>
/// IDisposable resource1 = null, resource2 = null, resource3 = null;
/// ExpectedException priorE = null;
/// try
/// {
/// resource1 = ...; resource2 = ...; resource3 = ...; // Acquisition may throw ExpectedException
/// ..do..stuff.. // May throw ExpectedException
/// }
/// catch (ExpectedException e)
/// {
/// priorE = e;
/// }
/// finally
/// {
/// IOUtils.DisposeWhileHandlingException(priorE, resource1, resource2, resource3);
/// }
/// </code>
/// </para>
/// </summary>
/// <param name="priorException"> <c>null</c> or an exception that will be rethrown after method completion. </param>
/// <param name="objects"> Objects to call <see cref="IDisposable.Dispose()"/> on. </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void DisposeWhileHandlingException(Exception priorException, params IDisposable[] objects)
{
Exception th = null;
foreach (IDisposable @object in objects)
{
try
{
@object?.Dispose();
}
catch (Exception t) when (t.IsThrowable())
{
AddSuppressed(priorException ?? th, t);
if (th is null)
{
th = t;
}
}
}
if (priorException != null)
{
ExceptionDispatchInfo.Capture(priorException).Throw(); // LUCENENET: Rethrow to preserve stack details from the original throw
}
else
{
ReThrow(th);
}
}
/// <summary>
/// Disposes all given <see cref="IDisposable"/>s, suppressing all thrown exceptions. </summary>
/// <seealso cref="DisposeWhileHandlingException(Exception, IDisposable[])"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void DisposeWhileHandlingException(Exception priorException, IEnumerable<IDisposable> objects)
{
Exception th = null;
foreach (IDisposable @object in objects)
{
try
{
@object?.Dispose();
}
catch (Exception t) when (t.IsThrowable())
{
AddSuppressed(priorException ?? th, t);
if (th is null)
{
th = t;
}
}
}
if (priorException != null)
{
ExceptionDispatchInfo.Capture(priorException).Throw(); // LUCENENET: Rethrow to preserve stack details from the original throw
}
else
{
ReThrow(th);
}
}
/// <summary>
/// Disposes all given <see cref="IDisposable"/>s. Some of the
/// <see cref="IDisposable"/>s may be <c>null</c>; they are
/// ignored. After everything is closed, the method either
/// throws the first exception it hit while closing, or
/// completes normally if there were no exceptions.
/// </summary>
/// <param name="objects">
/// Objects to call <see cref="IDisposable.Dispose()"/> on </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Dispose(params IDisposable[] objects)
{
Exception th = null;
foreach (IDisposable @object in objects)
{
try
{
@object?.Dispose();
}
catch (Exception t) when (t.IsThrowable())
{
AddSuppressed(th, t);
if (th is null)
{
th = t;
}
}
}
ReThrow(th);
}
/// <summary>
/// Disposes all given <see cref="IDisposable"/>s. </summary>
/// <seealso cref="Dispose(IDisposable[])"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Dispose(IEnumerable<IDisposable> objects)
{
Exception th = null;
foreach (IDisposable @object in objects)
{
try
{
@object?.Dispose();
}
catch (Exception t) when (t.IsThrowable())
{
AddSuppressed(th, t);
if (th is null)
{
th = t;
}
}
}
ReThrow(th);
}
/// <summary>
/// Disposes all given <see cref="IDisposable"/>s, suppressing all thrown exceptions.
/// Some of the <see cref="IDisposable"/>s may be <c>null</c>, they are ignored.
/// </summary>
/// <param name="objects">
/// Objects to call <see cref="IDisposable.Dispose()"/> on </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void DisposeWhileHandlingException(params IDisposable[] objects)
{
foreach (var o in objects)
{
try
{
o?.Dispose();
}
catch (Exception t) when (t.IsThrowable())
{
//eat it
}
}
}
/// <summary>
/// Disposes all given <see cref="IDisposable"/>s, suppressing all thrown exceptions. </summary>
/// <seealso cref="DisposeWhileHandlingException(IDisposable[])"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void DisposeWhileHandlingException(IEnumerable<IDisposable> objects)
{
foreach (IDisposable @object in objects)
{
try
{
@object?.Dispose();
}
catch (Exception t) when (t.IsThrowable())
{
//eat it
}
}
}
/// <summary>
/// Since there's no C# equivalent of Java's Exception.AddSuppressed, we add the
/// suppressed exceptions to a data field via the
/// <see cref="ExceptionExtensions.AddSuppressed(Exception, Exception)"/> method.
/// <para/>
/// The exceptions can be retrieved by calling <see cref="ExceptionExtensions.GetSuppressed(Exception)"/>
/// or <see cref="ExceptionExtensions.GetSuppressedAsList(Exception)"/>.
/// </summary>
/// <param name="exception"> this exception should get the suppressed one added </param>
/// <param name="suppressed"> the suppressed exception </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void AddSuppressed(Exception exception, Exception suppressed)
{
if (exception != null && suppressed != null)
{
exception.AddSuppressed(suppressed);
}
}
/// <summary>
/// Wrapping the given <see cref="Stream"/> in a reader using a <see cref="Encoding"/>.
/// Unlike Java's defaults this reader will throw an exception if your it detects
/// the read charset doesn't match the expected <see cref="Encoding"/>.
/// <para/>
/// Decoding readers are useful to load configuration files, stopword lists or synonym files
/// to detect character set problems. However, its not recommended to use as a common purpose
/// reader.
/// </summary>
/// <param name="stream"> The stream to wrap in a reader </param>
/// <param name="charSet"> The expected charset </param>
/// <returns> A wrapping reader </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TextReader GetDecodingReader(Stream stream, Encoding charSet)
{
var charSetDecoder = charSet.WithDecoderExceptionFallback();
return new StreamReader(stream, charSetDecoder);
}
/// <summary>
/// Opens a <see cref="TextReader"/> for the given <see cref="FileInfo"/> using a <see cref="Encoding"/>.
/// Unlike Java's defaults this reader will throw an exception if your it detects
/// the read charset doesn't match the expected <see cref="Encoding"/>.
/// <para/>
/// Decoding readers are useful to load configuration files, stopword lists or synonym files
/// to detect character set problems. However, its not recommended to use as a common purpose
/// reader. </summary>
/// <param name="file"> The file to open a reader on </param>
/// <param name="charSet"> The expected charset </param>
/// <returns> A reader to read the given file </returns>
public static TextReader GetDecodingReader(FileInfo file, Encoding charSet)
{
FileStream stream = null;
bool success = false;
try
{
stream = file.OpenRead();
TextReader reader = GetDecodingReader(stream, charSet);
success = true;
return reader;
}
finally
{
if (!success)
{
IOUtils.Dispose(stream);
}
}
}
/// <summary>
/// Opens a <see cref="TextReader"/> for the given resource using a <see cref="Encoding"/>.
/// Unlike Java's defaults this reader will throw an exception if your it detects
/// the read charset doesn't match the expected <see cref="Encoding"/>.
/// <para/>
/// Decoding readers are useful to load configuration files, stopword lists or synonym files
/// to detect character set problems. However, its not recommended to use as a common purpose
/// reader. </summary>
/// <param name="clazz"> The class used to locate the resource </param>
/// <param name="resource"> The resource name to load </param>
/// <param name="charSet"> The expected charset </param>
/// <returns> A reader to read the given file </returns>
public static TextReader GetDecodingReader(Type clazz, string resource, Encoding charSet)
{
Stream stream = null;
bool success = false;
try
{
stream = clazz.FindAndGetManifestResourceStream(resource);
TextReader reader = GetDecodingReader(stream, charSet);
success = true;
return reader;
}
finally
{
if (!success)
{
IOUtils.Dispose(stream);
}
}
}
/// <summary>
/// Deletes all given files, suppressing all thrown <see cref="Exception"/>s.
/// <para/>
/// Note that the files should not be <c>null</c>.
/// </summary>
public static void DeleteFilesIgnoringExceptions(Directory dir, params string[] files)
{
foreach (string name in files)
{
try
{
dir.DeleteFile(name);
}
catch (Exception ignored) when (ignored.IsThrowable())
{
// ignore
}
}
}
/// <summary>
/// Copy one file's contents to another file. The target will be overwritten
/// if it exists. The source must exist.
/// </summary>
public static void Copy(FileInfo source, FileInfo target)
{
FileStream fis = null;
FileStream fos = null;
try
{
fis = source.OpenRead();
fos = target.OpenWrite();
byte[] buffer = new byte[1024 * 8];
int len;
while ((len = fis.Read(buffer, 0, buffer.Length)) > 0)
{
fos.Write(buffer, 0, len);
}
}
finally
{
Dispose(fis, fos);
}
}
/// <summary>
/// Simple utilty method that takes a previously caught
/// <see cref="Exception"/> and rethrows either
/// <see cref="IOException"/> or an unchecked exception. If the
/// argument is <c>null</c> then this method does nothing.
/// </summary>
public static void ReThrow(Exception th)
{
if (th != null)
{
if (th.IsIOException())
{
ExceptionDispatchInfo.Capture(th).Throw(); // LUCENENET: Rethrow to preserve stack details from the original throw
}
ReThrowUnchecked(th);
}
}
/// <summary>
/// Simple utilty method that takes a previously caught
/// <see cref="Exception"/> and rethrows it as an unchecked exception.
/// If the argument is <c>null</c> then this method does nothing.
/// </summary>
public static void ReThrowUnchecked(Exception th)
{
if (th != null)
{
if (th.IsRuntimeException())
ExceptionDispatchInfo.Capture(th).Throw(); // LUCENENET: Rethrow to preserve stack details from the original throw
if (th.IsError())
ExceptionDispatchInfo.Capture(th).Throw(); // LUCENENET: Rethrow to preserve stack details from the original throw
throw RuntimeException.Create(th);
}
}
// LUCENENET specific: using string instead of FileSystemInfo to avoid extra allocation
public static void Fsync(string fileToSync, bool isDir)
{
// LUCENENET NOTE: there is a bug in 4.8 where it tries to fsync a directory on Windows,
// which is not supported in OpenJDK. This change adopts the latest Lucene code as of 9.10
// and only fsyncs directories on Linux and macOS.
// If the file is a directory we have to open read-only, for regular files we must open r/w for
// the fsync to have an effect.
// See http://blog.httrack.com/blog/2013/11/15/everything-you-always-wanted-to-know-about-fsync/
if (isDir && Constants.WINDOWS)
{
// opening a directory on Windows fails, directories can not be fsynced there
if (System.IO.Directory.Exists(fileToSync) == false)
{
// yet do not suppress trying to fsync directories that do not exist
throw new DirectoryNotFoundException($"Directory does not exist: {fileToSync}");
}
return;
}
try
{
// LUCENENET specific: was: file.force(true);
// We must call fsync on the parent directory, requiring some custom P/Invoking
if (Constants.WINDOWS)
{
WindowsFsyncSupport.Fsync(fileToSync, isDir);
}
else
{
PosixFsyncSupport.Fsync(fileToSync, isDir);
}
}
catch (Exception e) when (e.IsIOException() && isDir && e is not DirectoryNotFoundException)
{
// LUCENENET specific - make catch specific to IOExceptions when it's a directory,
// but allow DirectoryNotFoundException to pass through as an equivalent would normally be
// thrown by the FileChannel.open call in Java which is outside the try block.
if (Debugging.AssertsEnabled)
{
Debugging.Assert((Constants.LINUX || Constants.MAC_OS_X) == false,
"On Linux and MacOSX fsyncing a directory should not throw IOException, we just don't want to rely on that in production (undocumented). Got: {0}",
e);
}
// Ignore exception if it is a directory
}
}
}
}