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 */ 017package org.apache.commons.geometry.io.core.internal; 018 019import java.io.BufferedReader; 020import java.io.BufferedWriter; 021import java.io.Closeable; 022import java.io.IOException; 023import java.io.InputStream; 024import java.io.InputStreamReader; 025import java.io.OutputStreamWriter; 026import java.io.UncheckedIOException; 027import java.net.URL; 028import java.nio.charset.Charset; 029import java.nio.file.Path; 030import java.util.stream.Stream; 031 032import org.apache.commons.geometry.io.core.input.GeometryInput; 033import org.apache.commons.geometry.io.core.output.GeometryOutput; 034 035/** Internal class containing utility methods for IO operations. 036 */ 037public final class GeometryIOUtils { 038 039 /** Path separator character used on Unix-like systems. */ 040 private static final char UNIX_PATH_SEP = '/'; 041 042 /** Path separator character used on Windows. */ 043 private static final char WINDOWS_PATH_SEP = '\\'; 044 045 /** Utility class; no instantiation. */ 046 private GeometryIOUtils() {} 047 048 /** Get the file name of the given path or null if one does not exist 049 * or is the empty string. 050 * @param path path to get the file name of 051 * @return file name of the given path 052 */ 053 public static String getFileName(final Path path) { 054 if (path != null) { 055 return getFileName(path.toString()); 056 } 057 058 return null; 059 } 060 061 /** Get the file name of the given url or null if one does not exist or is 062 * the empty string. 063 * @param url url to get the file name of 064 * @return file name of the given url 065 */ 066 public static String getFileName(final URL url) { 067 if (url != null) { 068 return getFileName(url.getPath()); 069 } 070 071 return null; 072 } 073 074 /** Get the file name from the given path string, defined as 075 * the substring following the last path separator character. 076 * Null is returned if the argument is null or the file name is 077 * the empty string. 078 * @param path path to get the file name from 079 * @return file name of the given path string or null if a 080 * non-empty file name does not exist 081 */ 082 public static String getFileName(final String path) { 083 if (path != null) { 084 final int lastSep = Math.max( 085 path.lastIndexOf(UNIX_PATH_SEP), 086 path.lastIndexOf(WINDOWS_PATH_SEP)); 087 088 if (lastSep < path.length() - 1) { 089 return path.substring(lastSep + 1); 090 } 091 } 092 093 return null; 094 } 095 096 /** Get the part of the file name after the last dot. 097 * @param fileName file name to get the extension for 098 * @return the extension of the file name, the empty string if no extension is found, or 099 * 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}