View Javadoc
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 }