GeometryIOUtils.java
/*
* 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.
*/
package org.apache.commons.geometry.io.core.internal;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UncheckedIOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.stream.Stream;
import org.apache.commons.geometry.io.core.input.GeometryInput;
import org.apache.commons.geometry.io.core.output.GeometryOutput;
/** Internal class containing utility methods for IO operations.
*/
public final class GeometryIOUtils {
/** Path separator character used on Unix-like systems. */
private static final char UNIX_PATH_SEP = '/';
/** Path separator character used on Windows. */
private static final char WINDOWS_PATH_SEP = '\\';
/** Utility class; no instantiation. */
private GeometryIOUtils() {}
/** Get the file name of the given path or null if one does not exist
* or is the empty string.
* @param path path to get the file name of
* @return file name of the given path
*/
public static String getFileName(final Path path) {
if (path != null) {
return getFileName(path.toString());
}
return null;
}
/** Get the file name of the given url or null if one does not exist or is
* the empty string.
* @param url url to get the file name of
* @return file name of the given url
*/
public static String getFileName(final URL url) {
if (url != null) {
return getFileName(url.getPath());
}
return null;
}
/** Get the file name from the given path string, defined as
* the substring following the last path separator character.
* Null is returned if the argument is null or the file name is
* the empty string.
* @param path path to get the file name from
* @return file name of the given path string or null if a
* non-empty file name does not exist
*/
public static String getFileName(final String path) {
if (path != null) {
final int lastSep = Math.max(
path.lastIndexOf(UNIX_PATH_SEP),
path.lastIndexOf(WINDOWS_PATH_SEP));
if (lastSep < path.length() - 1) {
return path.substring(lastSep + 1);
}
}
return null;
}
/** Get the part of the file name after the last dot.
* @param fileName file name to get the extension for
* @return the extension of the file name, the empty string if no extension is found, or
* null if the argument is null
*/
public static String getFileExtension(final String fileName) {
if (fileName != null) {
final int idx = fileName.lastIndexOf('.');
if (idx > -1) {
return fileName.substring(idx + 1);
}
return "";
}
return null;
}
/** Create a {@link BufferedReader} for reading from the given input. The charset used is the charset
* defined in {@code input} or {@code defaultCharset} if null.
* @param input input to read from
* @param defaultCharset charset to use if no charset is defined in the input
* @return new reader instance
* @throws UncheckedIOException if an I/O error occurs
*/
public static BufferedReader createBufferedReader(final GeometryInput input, final Charset defaultCharset) {
final Charset charset = input.getCharset() != null ?
input.getCharset() :
defaultCharset;
return new BufferedReader(new InputStreamReader(input.getInputStream(), charset));
}
/** Create a {@link BufferedWriter} for writing to the given output. The charset used is the charset
* defined in {@code output} or {@code defaultCharset} if null.
* @param output output to write to
* @param defaultCharset charset to use if no charset is defined in the output
* @return new writer instance
* @throws UncheckedIOException if an I/O error occurs
*/
public static BufferedWriter createBufferedWriter(final GeometryOutput output, final Charset defaultCharset) {
final Charset charset = output.getCharset() != null ?
output.getCharset() :
defaultCharset;
return new BufferedWriter(new OutputStreamWriter(output.getOutputStream(), charset));
}
/** Get a value from {@code supplier}, wrapping any {@link IOException} with
* {@link UncheckedIOException}.
* @param <T> returned type
* @param supplier object supplying the return value
* @return supplied value
* @throws UncheckedIOException if an I/O error occurs
*/
public static <T> T getUnchecked(final IOSupplier<T> supplier) {
try {
return supplier.get();
} catch (IOException exc) {
throw createUnchecked(exc);
}
}
/** Pass the given argument to the consumer, wrapping any {@link IOException} with
* {@link UncheckedIOException}.
* @param <T> argument type
* @param consumer function to call
* @param arg function argument
* @throws UncheckedIOException if an I/O error occurs
*/
public static <T> void acceptUnchecked(final IOConsumer<T> consumer, final T arg) {
try {
consumer.accept(arg);
} catch (IOException exc) {
throw createUnchecked(exc);
}
}
/** Call the given function with the argument and return the {@code int} result, wrapping any
* {@link IOException} with {@link UncheckedIOException}.
* @param <T> argument type
* @param fn function to call
* @param arg function argument
* @return int value
* @throws UncheckedIOException if an I/O error occurs
*/
public static <T> int applyAsIntUnchecked(final IOToIntFunction<T> fn, final T arg) {
try {
return fn.applyAsInt(arg);
} catch (IOException exc) {
throw createUnchecked(exc);
}
}
/** Close the argument, wrapping any IO exceptions with {@link UncheckedIOException}.
* @param closeable argument to close
* @throws UncheckedIOException if an I/O error occurs
*/
public static void closeUnchecked(final Closeable closeable) {
try {
closeable.close();
} catch (IOException exc) {
throw createUnchecked(exc);
}
}
/** Create an unchecked exception from the given checked exception. The message of the
* returned exception contains the original exception's type and message.
* @param exc exception to wrap in an unchecked exception
* @return the unchecked exception
*/
public static UncheckedIOException createUnchecked(final IOException exc) {
final String msg = exc.getClass().getSimpleName() + ": " + exc.getMessage();
return new UncheckedIOException(msg, exc);
}
/** Create an exception indicating a parsing or syntax error.
* @param msg exception message
* @return an exception indicating a parsing or syntax error
*/
public static IllegalStateException parseError(final String msg) {
return parseError(msg, null);
}
/** Create an exception indicating a parsing or syntax error.
* @param msg exception message
* @param cause exception cause
* @return an exception indicating a parsing or syntax error
*/
public static IllegalStateException parseError(final String msg, final Throwable cause) {
return new IllegalStateException(msg, cause);
}
/** Pass a supplied {@link Closeable} instance to {@code function} and return the result.
* The {@code Closeable} instance returned by the supplier is closed if function execution
* fails, otherwise the instance is <em>not</em> closed.
* @param <T> Return type
* @param <C> Closeable type
* @param function function called with the supplied Closeable instance
* @param closeableSupplier supplier used to obtain a Closeable instance
* @return result of calling {@code function} with a supplied Closeable instance
* @throws java.io.UncheckedIOException if an I/O error occurs
*/
public static <T, C extends Closeable> T tryApplyCloseable(final IOFunction<C, T> function,
final IOSupplier<? extends C> closeableSupplier) {
C closeable = null;
RuntimeException exc;
try {
closeable = closeableSupplier.get();
return function.apply(closeable);
} catch (RuntimeException e) {
exc = e;
} catch (IOException e) {
exc = createUnchecked(e);
}
if (closeable != null) {
try {
closeable.close();
} catch (IOException suppressed) {
exc.addSuppressed(suppressed);
}
}
throw exc;
}
/** Create a stream associated with an input stream. The input stream is closed when the
* stream is closed and also closed if stream creation fails. Any {@link IOException} thrown
* when the input stream is closed after the return of this method are wrapped with {@link UncheckedIOException}.
* @param <T> Stream element type
* @param <I> Input stream type
* @param streamFunction function accepting an input stream and returning a stream
* @param inputStreamSupplier supplier used to obtain the input stream
* @return stream associated with the input stream return by the supplier
* @throws java.io.UncheckedIOException if an I/O error occurs during input stream and stream creation
*/
public static <T, I extends InputStream> Stream<T> createCloseableStream(
final IOFunction<I, Stream<T>> streamFunction, final IOSupplier<? extends I> inputStreamSupplier) {
return tryApplyCloseable(
in -> streamFunction.apply(in).onClose(closeAsUncheckedRunnable(in)),
inputStreamSupplier);
}
/** Return a {@link Runnable} that calls {@link Closeable#getClass() close()} on the argument,
* wrapping any {@link IOException} with {@link UncheckedIOException}.
* @param closeable instance to be closed
* @return runnable that calls {@code close()) on the argument
*/
private static Runnable closeAsUncheckedRunnable(final Closeable closeable) {
return () -> closeUnchecked(closeable);
}
}