1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.commons.geometry.io.core.internal; 18 19 import java.io.BufferedReader; 20 import java.io.BufferedWriter; 21 import java.io.Closeable; 22 import java.io.IOException; 23 import java.io.InputStream; 24 import java.io.InputStreamReader; 25 import java.io.OutputStreamWriter; 26 import java.io.UncheckedIOException; 27 import java.net.URL; 28 import java.nio.charset.Charset; 29 import java.nio.file.Path; 30 import java.util.stream.Stream; 31 32 import org.apache.commons.geometry.io.core.input.GeometryInput; 33 import org.apache.commons.geometry.io.core.output.GeometryOutput; 34 35 /** Internal class containing utility methods for IO operations. 36 */ 37 public final class GeometryIOUtils { 38 39 /** Path separator character used on Unix-like systems. */ 40 private static final char UNIX_PATH_SEP = '/'; 41 42 /** Path separator character used on Windows. */ 43 private static final char WINDOWS_PATH_SEP = '\\'; 44 45 /** Utility class; no instantiation. */ 46 private GeometryIOUtils() {} 47 48 /** Get the file name of the given path or null if one does not exist 49 * or is the empty string. 50 * @param path path to get the file name of 51 * @return file name of the given path 52 */ 53 public static String getFileName(final Path path) { 54 if (path != null) { 55 return getFileName(path.toString()); 56 } 57 58 return null; 59 } 60 61 /** Get the file name of the given url or null if one does not exist or is 62 * the empty string. 63 * @param url url to get the file name of 64 * @return file name of the given url 65 */ 66 public static String getFileName(final URL url) { 67 if (url != null) { 68 return getFileName(url.getPath()); 69 } 70 71 return null; 72 } 73 74 /** Get the file name from the given path string, defined as 75 * the substring following the last path separator character. 76 * Null is returned if the argument is null or the file name is 77 * the empty string. 78 * @param path path to get the file name from 79 * @return file name of the given path string or null if a 80 * non-empty file name does not exist 81 */ 82 public static String getFileName(final String path) { 83 if (path != null) { 84 final int lastSep = Math.max( 85 path.lastIndexOf(UNIX_PATH_SEP), 86 path.lastIndexOf(WINDOWS_PATH_SEP)); 87 88 if (lastSep < path.length() - 1) { 89 return path.substring(lastSep + 1); 90 } 91 } 92 93 return null; 94 } 95 96 /** Get the part of the file name after the last dot. 97 * @param fileName file name to get the extension for 98 * @return the extension of the file name, the empty string if no extension is found, or 99 * null if the argument is null 100 */ 101 public static String getFileExtension(final String fileName) { 102 if (fileName != null) { 103 final int idx = fileName.lastIndexOf('.'); 104 if (idx > -1) { 105 return fileName.substring(idx + 1); 106 } 107 108 return ""; 109 } 110 111 return null; 112 } 113 114 /** Create a {@link BufferedReader} for reading from the given input. The charset used is the charset 115 * defined in {@code input} or {@code defaultCharset} if null. 116 * @param input input to read from 117 * @param defaultCharset charset to use if no charset is defined in the input 118 * @return new reader instance 119 * @throws UncheckedIOException if an I/O error occurs 120 */ 121 public static BufferedReader createBufferedReader(final GeometryInput input, final Charset defaultCharset) { 122 final Charset charset = input.getCharset() != null ? 123 input.getCharset() : 124 defaultCharset; 125 126 return new BufferedReader(new InputStreamReader(input.getInputStream(), charset)); 127 } 128 129 /** Create a {@link BufferedWriter} for writing to the given output. The charset used is the charset 130 * defined in {@code output} or {@code defaultCharset} if null. 131 * @param output output to write to 132 * @param defaultCharset charset to use if no charset is defined in the output 133 * @return new writer instance 134 * @throws UncheckedIOException if an I/O error occurs 135 */ 136 public static BufferedWriter createBufferedWriter(final GeometryOutput output, final Charset defaultCharset) { 137 final Charset charset = output.getCharset() != null ? 138 output.getCharset() : 139 defaultCharset; 140 141 return new BufferedWriter(new OutputStreamWriter(output.getOutputStream(), charset)); 142 } 143 144 /** Get a value from {@code supplier}, wrapping any {@link IOException} with 145 * {@link UncheckedIOException}. 146 * @param <T> returned type 147 * @param supplier object supplying the return value 148 * @return supplied value 149 * @throws UncheckedIOException if an I/O error occurs 150 */ 151 public static <T> T getUnchecked(final IOSupplier<T> supplier) { 152 try { 153 return supplier.get(); 154 } catch (IOException exc) { 155 throw createUnchecked(exc); 156 } 157 } 158 159 /** Pass the given argument to the consumer, wrapping any {@link IOException} with 160 * {@link UncheckedIOException}. 161 * @param <T> argument type 162 * @param consumer function to call 163 * @param arg function argument 164 * @throws UncheckedIOException if an I/O error occurs 165 */ 166 public static <T> void acceptUnchecked(final IOConsumer<T> consumer, final T arg) { 167 try { 168 consumer.accept(arg); 169 } catch (IOException exc) { 170 throw createUnchecked(exc); 171 } 172 } 173 174 /** Call the given function with the argument and return the {@code int} result, wrapping any 175 * {@link IOException} with {@link UncheckedIOException}. 176 * @param <T> argument type 177 * @param fn function to call 178 * @param arg function argument 179 * @return int value 180 * @throws UncheckedIOException if an I/O error occurs 181 */ 182 public static <T> int applyAsIntUnchecked(final IOToIntFunction<T> fn, final T arg) { 183 try { 184 return fn.applyAsInt(arg); 185 } catch (IOException exc) { 186 throw createUnchecked(exc); 187 } 188 } 189 190 /** Close the argument, wrapping any IO exceptions with {@link UncheckedIOException}. 191 * @param closeable argument to close 192 * @throws UncheckedIOException if an I/O error occurs 193 */ 194 public static void closeUnchecked(final Closeable closeable) { 195 try { 196 closeable.close(); 197 } catch (IOException exc) { 198 throw createUnchecked(exc); 199 } 200 } 201 202 /** Create an unchecked exception from the given checked exception. The message of the 203 * returned exception contains the original exception's type and message. 204 * @param exc exception to wrap in an unchecked exception 205 * @return the unchecked exception 206 */ 207 public static UncheckedIOException createUnchecked(final IOException exc) { 208 final String msg = exc.getClass().getSimpleName() + ": " + exc.getMessage(); 209 return new UncheckedIOException(msg, exc); 210 } 211 212 /** Create an exception indicating a parsing or syntax error. 213 * @param msg exception message 214 * @return an exception indicating a parsing or syntax error 215 */ 216 public static IllegalStateException parseError(final String msg) { 217 return parseError(msg, null); 218 } 219 220 /** Create an exception indicating a parsing or syntax error. 221 * @param msg exception message 222 * @param cause exception cause 223 * @return an exception indicating a parsing or syntax error 224 */ 225 public static IllegalStateException parseError(final String msg, final Throwable cause) { 226 return new IllegalStateException(msg, cause); 227 } 228 229 /** Pass a supplied {@link Closeable} instance to {@code function} and return the result. 230 * The {@code Closeable} instance returned by the supplier is closed if function execution 231 * fails, otherwise the instance is <em>not</em> closed. 232 * @param <T> Return type 233 * @param <C> Closeable type 234 * @param function function called with the supplied Closeable instance 235 * @param closeableSupplier supplier used to obtain a Closeable instance 236 * @return result of calling {@code function} with a supplied Closeable instance 237 * @throws java.io.UncheckedIOException if an I/O error occurs 238 */ 239 public static <T, C extends Closeable> T tryApplyCloseable(final IOFunction<C, T> function, 240 final IOSupplier<? extends C> closeableSupplier) { 241 C closeable = null; 242 RuntimeException exc; 243 try { 244 closeable = closeableSupplier.get(); 245 return function.apply(closeable); 246 } catch (RuntimeException e) { 247 exc = e; 248 } catch (IOException e) { 249 exc = createUnchecked(e); 250 } 251 252 if (closeable != null) { 253 try { 254 closeable.close(); 255 } catch (IOException suppressed) { 256 exc.addSuppressed(suppressed); 257 } 258 } 259 260 throw exc; 261 } 262 263 /** Create a stream associated with an input stream. The input stream is closed when the 264 * stream is closed and also closed if stream creation fails. Any {@link IOException} thrown 265 * when the input stream is closed after the return of this method are wrapped with {@link UncheckedIOException}. 266 * @param <T> Stream element type 267 * @param <I> Input stream type 268 * @param streamFunction function accepting an input stream and returning a stream 269 * @param inputStreamSupplier supplier used to obtain the input stream 270 * @return stream associated with the input stream return by the supplier 271 * @throws java.io.UncheckedIOException if an I/O error occurs during input stream and stream creation 272 */ 273 public static <T, I extends InputStream> Stream<T> createCloseableStream( 274 final IOFunction<I, Stream<T>> streamFunction, final IOSupplier<? extends I> inputStreamSupplier) { 275 return tryApplyCloseable( 276 in -> streamFunction.apply(in).onClose(closeAsUncheckedRunnable(in)), 277 inputStreamSupplier); 278 } 279 280 /** Return a {@link Runnable} that calls {@link Closeable#getClass() close()} on the argument, 281 * wrapping any {@link IOException} with {@link UncheckedIOException}. 282 * @param closeable instance to be closed 283 * @return runnable that calls {@code close()) on the argument 284 */ 285 private static Runnable closeAsUncheckedRunnable(final Closeable closeable) { 286 return () -> closeUnchecked(closeable); 287 } 288 }