--- /dev/null
+package org.apache.lucene.util;
+
+/**
+ * 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.
+ */
+
+import java.io.BufferedReader;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.lang.reflect.Method;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CodingErrorAction;
+
+/** This class emulates the new Java 7 "Try-With-Resources" statement.
+ * Remove once Lucene is on Java 7.
+ * @lucene.internal */
+public final class IOUtils {
+
+ /**
+ * UTF-8 charset string
+ * @see Charset#forName(String)
+ */
+ public static final String UTF_8 = "UTF-8";
+
+ /**
+ * UTF-8 {@link Charset} instance to prevent repeated
+ * {@link Charset#forName(String)} lookups
+ */
+ public static final Charset CHARSET_UTF_8 = Charset.forName("UTF-8");
+ private IOUtils() {} // no instance
+
+ /**
+ * <p>Closes all given <tt>Closeable</tt>s, suppressing all thrown exceptions. Some of the <tt>Closeable</tt>s
+ * may be null, they are ignored. After everything is closed, method either throws <tt>priorException</tt>,
+ * if one is supplied, or the first of suppressed exceptions, or completes normally.</p>
+ * <p>Sample usage:<br/>
+ * <pre>
+ * Closeable resource1 = null, resource2 = null, resource3 = null;
+ * ExpectedException priorE = null;
+ * try {
+ * resource1 = ...; resource2 = ...; resource3 = ...; // Acquisition may throw ExpectedException
+ * ..do..stuff.. // May throw ExpectedException
+ * } catch (ExpectedException e) {
+ * priorE = e;
+ * } finally {
+ * closeSafely(priorE, resource1, resource2, resource3);
+ * }
+ * </pre>
+ * </p>
+ * @param priorException <tt>null</tt> or an exception that will be rethrown after method completion
+ * @param objects objects to call <tt>close()</tt> on
+ */
+ public static <E extends Exception> void closeWhileHandlingException(E priorException, Closeable... objects) throws E, IOException {
+ Throwable th = null;
+
+ for (Closeable object : objects) {
+ try {
+ if (object != null) {
+ object.close();
+ }
+ } catch (Throwable t) {
+ addSuppressed((priorException == null) ? th : priorException, t);
+ if (th == null) {
+ th = t;
+ }
+ }
+ }
+
+ if (priorException != null) {
+ throw priorException;
+ } else if (th != null) {
+ if (th instanceof IOException) throw (IOException) th;
+ if (th instanceof RuntimeException) throw (RuntimeException) th;
+ if (th instanceof Error) throw (Error) th;
+ throw new RuntimeException(th);
+ }
+ }
+
+ /** @see #closeWhileHandlingException(Exception, Closeable...) */
+ public static <E extends Exception> void closeWhileHandlingException(E priorException, Iterable<Closeable> objects) throws E, IOException {
+ Throwable th = null;
+
+ for (Closeable object : objects) {
+ try {
+ if (object != null) {
+ object.close();
+ }
+ } catch (Throwable t) {
+ addSuppressed((priorException == null) ? th : priorException, t);
+ if (th == null) {
+ th = t;
+ }
+ }
+ }
+
+ if (priorException != null) {
+ throw priorException;
+ } else if (th != null) {
+ if (th instanceof IOException) throw (IOException) th;
+ if (th instanceof RuntimeException) throw (RuntimeException) th;
+ if (th instanceof Error) throw (Error) th;
+ throw new RuntimeException(th);
+ }
+ }
+
+ /**
+ * Closes all given <tt>Closeable</tt>s. Some of the
+ * <tt>Closeable</tt>s may be null; they are
+ * ignored. After everything is closed, the method either
+ * throws the first exception it hit while closing, or
+ * completes normally if there were no exceptions.
+ *
+ * @param objects
+ * objects to call <tt>close()</tt> on
+ */
+ public static void close(Closeable... objects) throws IOException {
+ Throwable th = null;
+
+ for (Closeable object : objects) {
+ try {
+ if (object != null) {
+ object.close();
+ }
+ } catch (Throwable t) {
+ addSuppressed(th, t);
+ if (th == null) {
+ th = t;
+ }
+ }
+ }
+
+ if (th != null) {
+ if (th instanceof IOException) throw (IOException) th;
+ if (th instanceof RuntimeException) throw (RuntimeException) th;
+ if (th instanceof Error) throw (Error) th;
+ throw new RuntimeException(th);
+ }
+ }
+
+ /**
+ * @see #close(Closeable...)
+ */
+ public static void close(Iterable<? extends Closeable> objects) throws IOException {
+ Throwable th = null;
+
+ for (Closeable object : objects) {
+ try {
+ if (object != null) {
+ object.close();
+ }
+ } catch (Throwable t) {
+ addSuppressed(th, t);
+ if (th == null) {
+ th = t;
+ }
+ }
+ }
+
+ if (th != null) {
+ if (th instanceof IOException) throw (IOException) th;
+ if (th instanceof RuntimeException) throw (RuntimeException) th;
+ if (th instanceof Error) throw (Error) th;
+ throw new RuntimeException(th);
+ }
+ }
+
+ /**
+ * Closes all given <tt>Closeable</tt>s, suppressing all thrown exceptions.
+ * Some of the <tt>Closeable</tt>s may be null, they are ignored.
+ *
+ * @param objects
+ * objects to call <tt>close()</tt> on
+ */
+ public static void closeWhileHandlingException(Closeable... objects) throws IOException {
+ for (Closeable object : objects) {
+ try {
+ if (object != null) {
+ object.close();
+ }
+ } catch (Throwable t) {
+ }
+ }
+ }
+
+ /**
+ * @see #closeWhileHandlingException(Closeable...)
+ */
+ public static void closeWhileHandlingException(Iterable<? extends Closeable> objects) throws IOException {
+ for (Closeable object : objects) {
+ try {
+ if (object != null) {
+ object.close();
+ }
+ } catch (Throwable t) {
+ }
+ }
+ }
+
+ /** This reflected {@link Method} is {@code null} before Java 7 */
+ private static final Method SUPPRESS_METHOD;
+ static {
+ Method m;
+ try {
+ m = Throwable.class.getMethod("addSuppressed", Throwable.class);
+ } catch (Exception e) {
+ m = null;
+ }
+ SUPPRESS_METHOD = m;
+ }
+
+ /** adds a Throwable to the list of suppressed Exceptions of the first Throwable (if Java 7 is detected)
+ * @param exception this exception should get the suppressed one added
+ * @param suppressed the suppressed exception
+ */
+ private static final void addSuppressed(Throwable exception, Throwable suppressed) {
+ if (SUPPRESS_METHOD != null && exception != null && suppressed != null) {
+ try {
+ SUPPRESS_METHOD.invoke(exception, suppressed);
+ } catch (Exception e) {
+ // ignore any exceptions caused by invoking (e.g. security constraints)
+ }
+ }
+ }
+
+ /**
+ * Wrapping the given {@link InputStream} in a reader using a {@link CharsetDecoder}.
+ * Unlike Java's defaults this reader will throw an exception if your it detects
+ * the read charset doesn't match the expected {@link Charset}.
+ * <p>
+ * Decoding readers are useful to load configuration files, stopword lists or synonym files
+ * to detect character set problems. However, its not recommended to use as a common purpose
+ * reader.
+ *
+ * @param stream the stream to wrap in a reader
+ * @param charSet the expected charset
+ * @return a wrapping reader
+ */
+ public static Reader getDecodingReader(InputStream stream, Charset charSet) {
+ final CharsetDecoder charSetDecoder = charSet.newDecoder()
+ .onMalformedInput(CodingErrorAction.REPORT)
+ .onUnmappableCharacter(CodingErrorAction.REPORT);
+ return new BufferedReader(new InputStreamReader(stream, charSetDecoder));
+ }
+
+ /**
+ * Opens a Reader for the given {@link File} using a {@link CharsetDecoder}.
+ * Unlike Java's defaults this reader will throw an exception if your it detects
+ * the read charset doesn't match the expected {@link Charset}.
+ * <p>
+ * Decoding readers are useful to load configuration files, stopword lists or synonym files
+ * to detect character set problems. However, its not recommended to use as a common purpose
+ * reader.
+ * @param file the file to open a reader on
+ * @param charSet the expected charset
+ * @return a reader to read the given file
+ */
+ public static Reader getDecodingReader(File file, Charset charSet) throws IOException {
+ FileInputStream stream = null;
+ boolean success = false;
+ try {
+ stream = new FileInputStream(file);
+ final Reader reader = getDecodingReader(stream, charSet);
+ success = true;
+ return reader;
+
+ } finally {
+ if (!success) {
+ IOUtils.close(stream);
+ }
+ }
+ }
+
+ /**
+ * Opens a Reader for the given resource using a {@link CharsetDecoder}.
+ * Unlike Java's defaults this reader will throw an exception if your it detects
+ * the read charset doesn't match the expected {@link Charset}.
+ * <p>
+ * Decoding readers are useful to load configuration files, stopword lists or synonym files
+ * to detect character set problems. However, its not recommended to use as a common purpose
+ * reader.
+ * @param clazz the class used to locate the resource
+ * @param resource the resource name to load
+ * @param charSet the expected charset
+ * @return a reader to read the given file
+ *
+ */
+ public static Reader getDecodingReader(Class<?> clazz, String resource, Charset charSet) throws IOException {
+ InputStream stream = null;
+ boolean success = false;
+ try {
+ stream = clazz
+ .getResourceAsStream(resource);
+ final Reader reader = getDecodingReader(stream, charSet);
+ success = true;
+ return reader;
+ } finally {
+ if (!success) {
+ IOUtils.close(stream);
+ }
+ }
+ }
+
+
+}