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 }