diff --git a/epublib/src/main/java/me/ag2s/epublib/util/Android11CloseGuard.java b/epublib/src/main/java/me/ag2s/epublib/util/Android11CloseGuard.java
new file mode 100644
index 000000000..8e2d0ca50
--- /dev/null
+++ b/epublib/src/main/java/me/ag2s/epublib/util/Android11CloseGuard.java
@@ -0,0 +1,33 @@
+package me.ag2s.epublib.util;
+
+import android.os.Build;
+import android.util.CloseGuard;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+@RequiresApi(api = Build.VERSION_CODES.R)
+final class Android11CloseGuard implements AndroidCloseGuard {
+ @NonNull
+ private final CloseGuard mImpl;
+
+
+ public Android11CloseGuard() {
+ mImpl = new CloseGuard();
+ }
+
+ @Override
+ public void open(@NonNull String closeMethodName) {
+ mImpl.open(closeMethodName);
+ }
+
+ @Override
+ public void close() {
+ mImpl.close();
+ }
+
+ @Override
+ public void warnIfOpen() {
+ mImpl.warnIfOpen();
+ }
+}
diff --git a/epublib/src/main/java/me/ag2s/epublib/util/AndroidCloseGuard.java b/epublib/src/main/java/me/ag2s/epublib/util/AndroidCloseGuard.java
new file mode 100644
index 000000000..ba8b963e3
--- /dev/null
+++ b/epublib/src/main/java/me/ag2s/epublib/util/AndroidCloseGuard.java
@@ -0,0 +1,38 @@
+package me.ag2s.epublib.util;
+
+
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+
+public interface AndroidCloseGuard {
+
+
+ public static AndroidCloseGuard getInstance() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ return new Android11CloseGuard();
+ } else {
+ return new AndroidRefCloseGuard();
+ }
+ }
+
+ /**
+ * Initializes the instance with a warning that the caller should have explicitly called the
+ * {@code closeMethodName} method instead of relying on finalization.
+ *
+ * @param closeMethodName non-null name of explicit termination method. Printed by warnIfOpen.
+ * @throws NullPointerException if closeMethodName is null.
+ */
+ void open(@NonNull String closeMethodName);
+
+ /**
+ * Marks this CloseGuard instance as closed to avoid warnings on finalization.
+ */
+ void close();
+
+ /**
+ * Logs a warning if the caller did not properly cleanup by calling an explicit close method
+ * before finalization.
+ */
+ void warnIfOpen();
+}
diff --git a/epublib/src/main/java/me/ag2s/epublib/util/AndroidRefCloseGuard.java b/epublib/src/main/java/me/ag2s/epublib/util/AndroidRefCloseGuard.java
new file mode 100644
index 000000000..8daee7837
--- /dev/null
+++ b/epublib/src/main/java/me/ag2s/epublib/util/AndroidRefCloseGuard.java
@@ -0,0 +1,83 @@
+package me.ag2s.epublib.util;
+
+import androidx.annotation.NonNull;
+
+import java.lang.reflect.Method;
+
+final class AndroidRefCloseGuard implements AndroidCloseGuard {
+ private static Object closeGuardInstance;
+ private static Method getMethod;
+ private static Method closeMethod;
+ private static Method openMethod;
+ private static Method warnIfOpenMethod;
+
+
+ public AndroidRefCloseGuard() {
+
+ if (getMethod == null || closeMethod == null || openMethod == null || warnIfOpenMethod == null) {
+ try {
+ Class> closeGuardClass = Class.forName("dalvik.system.CloseGuard");
+ getMethod = closeGuardClass.getMethod("get");
+ closeMethod = closeGuardClass.getMethod("close");
+ openMethod = closeGuardClass.getMethod("open", String.class);
+ warnIfOpenMethod = closeGuardClass.getMethod("warnIfOpen");
+ } catch (Exception ignored) {
+ getMethod = null;
+ openMethod = null;
+ warnIfOpenMethod = null;
+ }
+ }
+
+
+ }
+
+ Object createAndOpen(String closer) {
+ if (getMethod != null) {
+ try {
+ if (closeGuardInstance == null) {
+ closeGuardInstance = getMethod.invoke(null);
+ }
+
+ openMethod.invoke(closeGuardInstance, closer);
+ return closeGuardInstance;
+ } catch (Exception ignored) {
+ }
+ }
+ return null;
+ }
+
+
+ boolean warnIfOpen(Object closeGuardInstance) {
+ boolean reported = false;
+ if (closeGuardInstance != null) {
+ try {
+ warnIfOpenMethod.invoke(closeGuardInstance);
+ reported = true;
+ } catch (Exception ignored) {
+ }
+ }
+ return reported;
+ }
+
+
+ @Override
+ public void open(@NonNull String closeMethodName) {
+ closeGuardInstance = createAndOpen(closeMethodName);
+ }
+
+ @Override
+ public void close() {
+ if (closeGuardInstance != null) {
+ try {
+ closeMethod.invoke(closeMethod);
+ } catch (Exception ignored) {
+ }
+ }
+
+ }
+
+ @Override
+ public void warnIfOpen() {
+ warnIfOpen(closeGuardInstance);
+ }
+}
diff --git a/epublib/src/main/java/me/ag2s/epublib/zip/AndroidRandomReadableFile.java b/epublib/src/main/java/me/ag2s/epublib/zip/AndroidRandomReadableFile.java
index 1e476191c..6fd7e668e 100644
--- a/epublib/src/main/java/me/ag2s/epublib/zip/AndroidRandomReadableFile.java
+++ b/epublib/src/main/java/me/ag2s/epublib/zip/AndroidRandomReadableFile.java
@@ -2,6 +2,7 @@ package me.ag2s.epublib.zip;
import android.content.Context;
import android.net.Uri;
+import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.OsConstants;
@@ -16,26 +17,30 @@ import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.UTFDataFormatException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
+import me.ag2s.epublib.util.AndroidCloseGuard;
+
+
public class AndroidRandomReadableFile implements DataInput, Closeable {
- private ParcelFileDescriptor pfd;
+ private final ParcelFileDescriptor pfd;
private FileInputStream fis;
- private DataInputStream dis;
- public long pos = 0;
+ private long pos = 0;
+
+ private final AndroidCloseGuard guard = AndroidCloseGuard.getInstance();
public AndroidRandomReadableFile(@NonNull Context context, @NonNull Uri treeUri) throws FileNotFoundException {
- try {
- pfd = context.getContentResolver().openFileDescriptor(treeUri, "r");
- fis = new FileInputStream(pfd.getFileDescriptor());
- dis = new DataInputStream(fis);
- } catch (FileNotFoundException e) {
- throw e;
- }
+ pfd = context.getContentResolver().openFileDescriptor(treeUri, "r");
+ fis = new FileInputStream(pfd.getFileDescriptor());
+ //dis = new DataInputStream(fis);
+ guard.open("close");
+
}
+
public final FileDescriptor getFD() {
return pfd.getFileDescriptor();
}
@@ -48,38 +53,73 @@ public class AndroidRandomReadableFile implements DataInput, Closeable {
}
public final FileInputStream getFileInputStream() {
- if (fis == null || pos != getPos()) {
+ if (fis == null || this.pos != getPos()) {
+ this.pos = getPos();
fis = new FileInputStream(pfd.getFileDescriptor());
}
return fis;
}
+ /**
+ * Reads a byte of data from this input stream. This method blocks
+ * if no input is yet available.
+ *
+ * @return the next byte of data, or -1
if the end of the
+ * file is reached.
+ * @throws IOException if an I/O error occurs.
+ */
public int read() throws IOException {
byte[] b = new byte[1];
return (read(b, 0, 1) != -1) ? b[0] & 0xff : -1;
}
- public int read(byte[] b) {
- try {
- return read(b, 0, b.length);
- } catch (IOException e) {
- e.printStackTrace();
- return -1;
- }
+
+ /**
+ * Reads up to b.length
bytes of data from this input
+ * stream into an array of bytes. This method blocks until some input
+ * is available.
+ *
+ * @param b the buffer into which the data is read.
+ * @return the total number of bytes read into the buffer, or
+ * -1
if there is no more data because the end of
+ * the file has been reached.
+ * @throws IOException if an I/O error occurs.
+ */
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+
}
+ /**
+ * Reads up to len
bytes of data from this input stream
+ * into an array of bytes. If len
is not zero, the method
+ * blocks until some input is available; otherwise, no
+ * bytes are read and 0
is returned.
+ *
+ * @param b the buffer into which the data is read.
+ * @param off the start offset in the destination array b
+ * @param len the maximum number of bytes read.
+ * @return the total number of bytes read into the buffer, or
+ * -1
if there is no more data because the end of
+ * the file has been reached.
+ * @throws NullPointerException If b
is null
.
+ * @throws IndexOutOfBoundsException If off
is negative,
+ * len
is negative, or len
is greater than
+ * b.length - off
+ * @throws IOException if an I/O error occurs.
+ */
public int read(byte[] b, int off, int len) throws IOException {
try {
return android.system.Os.read(pfd.getFileDescriptor(), b, off, len);
} catch (Exception e) {
- return -1;
+ throw new IOException(e);
}
}
private void syncInputStream() {
+ this.pos = getPos();
fis = new FileInputStream(pfd.getFileDescriptor());
- dis = new DataInputStream(fis);
}
@@ -110,6 +150,46 @@ public class AndroidRandomReadableFile implements DataInput, Closeable {
}
+ /**
+ * Reads some bytes from an input
+ * stream and stores them into the buffer
+ * array {@code b}. The number of bytes
+ * read is equal
+ * to the length of {@code b}.
+ *
+ * This method blocks until one of the + * following conditions occurs: + *
+ * If {@code b} is {@code null}, + * a {@code NullPointerException} is thrown. + * If {@code b.length} is zero, then + * no bytes are read. Otherwise, the first + * byte read is stored into element {@code b[0]}, + * the next one into {@code b[1]}, and + * so on. + * If an exception is thrown from + * this method, then it may be that some but + * not all bytes of {@code b} have been + * updated with data from the input stream. + * + * @param b the buffer into which the data is read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ @Override public void readFully(byte[] b) throws IOException { try { @@ -121,6 +201,49 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { } + /** + * Reads {@code len} + * bytes from + * an input stream. + *
+ * This method + * blocks until one of the following conditions + * occurs: + *
+ * If {@code b} is {@code null}, + * a {@code NullPointerException} is thrown. + * If {@code off} is negative, or {@code len} + * is negative, or {@code off+len} is + * greater than the length of the array {@code b}, + * then an {@code IndexOutOfBoundsException} + * is thrown. + * If {@code len} is zero, + * then no bytes are read. Otherwise, the first + * byte read is stored into element {@code b[off]}, + * the next one into {@code b[off+1]}, + * and so on. The number of bytes read is, + * at most, equal to {@code len}. + * + * @param b the buffer into which the data is read. + * @param off an int specifying the offset into the data. + * @param len an int specifying the number of bytes to read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ @Override public void readFully(byte[] b, int off, int len) throws IOException { try { @@ -131,6 +254,27 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { } } + /** + * Makes an attempt to skip over + * {@code n} bytes + * of data from the input + * stream, discarding the skipped bytes. However, + * it may skip + * over some smaller number of + * bytes, possibly zero. This may result from + * any of a + * number of conditions; reaching + * end of file before {@code n} bytes + * have been skipped is + * only one possibility. + * This method never throws an {@code EOFException}. + * The actual + * number of bytes skipped is returned. + * + * @param n the number of bytes to be skipped. + * @return the number of bytes actually skipped. + * @throws IOException if an I/O error occurs. + */ @Override public int skipBytes(int n) throws IOException { try { @@ -142,6 +286,19 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { } + /** + * Reads one input byte and returns + * {@code true} if that byte is nonzero, + * {@code false} if that byte is zero. + * This method is suitable for reading + * the byte written by the {@code writeBoolean} + * method of interface {@code DataOutput}. + * + * @return the {@code boolean} value read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ @Override public boolean readBoolean() throws IOException { int ch = this.read(); @@ -151,6 +308,20 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { } + /** + * Reads and returns one input byte. + * The byte is treated as a signed value in + * the range {@code -128} through {@code 127}, + * inclusive. + * This method is suitable for + * reading the byte written by the {@code writeByte} + * method of interface {@code DataOutput}. + * + * @return the 8-bit value read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ @Override public byte readByte() throws IOException { int ch = this.read(); @@ -160,6 +331,24 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { } + /** + * Reads one input byte, zero-extends + * it to type {@code int}, and returns + * the result, which is therefore in the range + * {@code 0} + * through {@code 255}. + * This method is suitable for reading + * the byte written by the {@code writeByte} + * method of interface {@code DataOutput} + * if the argument to {@code writeByte} + * was intended to be a value in the range + * {@code 0} through {@code 255}. + * + * @return the unsigned 8-bit value read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ @Override public int readUnsignedByte() throws IOException { int ch = this.read(); @@ -170,30 +359,131 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { private final byte[] readBuffer = new byte[8]; + /** + * Reads two input bytes and returns + * a {@code short} value. Let {@code a} + * be the first byte read and {@code b} + * be the second byte. The value + * returned + * is: + *
{@code (short)((a << 8) | (b & 0xff)) + * }+ * This method + * is suitable for reading the bytes written + * by the {@code writeShort} method of + * interface {@code DataOutput}. + * + * @return the 16-bit value read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ @Override public short readShort() throws IOException { readFully(readBuffer, 0, 2); return ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get(); } + /** + * Reads two input bytes and returns + * an {@code int} value in the range {@code 0} + * through {@code 65535}. Let {@code a} + * be the first byte read and + * {@code b} + * be the second byte. The value returned is: + *
{@code (((a & 0xff) << 8) | (b & 0xff)) + * }+ * This method is suitable for reading the bytes + * written by the {@code writeShort} method + * of interface {@code DataOutput} if + * the argument to {@code writeShort} + * was intended to be a value in the range + * {@code 0} through {@code 65535}. + * + * @return the unsigned 16-bit value read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ @Override public int readUnsignedShort() throws IOException { readFully(readBuffer, 0, 2); return ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get() & 0xffff; } + /** + * Reads two input bytes and returns a {@code char} value. + * Let {@code a} + * be the first byte read and {@code b} + * be the second byte. The value + * returned is: + *
{@code (char)((a << 8) | (b & 0xff)) + * }+ * This method + * is suitable for reading bytes written by + * the {@code writeChar} method of interface + * {@code DataOutput}. + * + * @return the {@code char} value read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ @Override public char readChar() throws IOException { readFully(readBuffer, 0, 2); return (char) ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get(); } + /** + * Reads four input bytes and returns an + * {@code int} value. Let {@code a-d} + * be the first through fourth bytes read. The value returned is: + *
{@code + * (((a & 0xff) << 24) | ((b & 0xff) << 16) | + * ((c & 0xff) << 8) | (d & 0xff)) + * }+ * This method is suitable + * for reading bytes written by the {@code writeInt} + * method of interface {@code DataOutput}. + * + * @return the {@code int} value read. + * @throws EOFException if this stream reaches the end before reading + * all the bytes. + * @throws IOException if an I/O error occurs. + */ @Override public int readInt() throws IOException { readFully(readBuffer, 0, 4); return ByteBuffer.wrap(readBuffer).order(ByteOrder.BIG_ENDIAN).asIntBuffer().get(); } + + /** + * Reads eight input bytes and returns + * a {@code long} value. Let {@code a-h} + * be the first through eighth bytes read. + * The value returned is: + *
{@code + * (((long)(a & 0xff) << 56) | + * ((long)(b & 0xff) << 48) | + * ((long)(c & 0xff) << 40) | + * ((long)(d & 0xff) << 32) | + * ((long)(e & 0xff) << 24) | + * ((long)(f & 0xff) << 16) | + * ((long)(g & 0xff) << 8) | + * ((long)(h & 0xff))) + * }+ *
+ * This method is suitable
+ * for reading bytes written by the {@code writeLong}
+ * method of interface {@code DataOutput}.
+ *
+ * @return the {@code long} value read.
+ * @throws EOFException if this stream reaches the end before reading
+ * all the bytes.
+ * @throws IOException if an I/O error occurs.
+ */
@Override
public long readLong() throws IOException {
readFully(readBuffer, 0, 8);
@@ -207,11 +497,47 @@ public class AndroidRandomReadableFile implements DataInput, Closeable {
((readBuffer[7] & 255) << 0));
}
+ /**
+ * Reads four input bytes and returns
+ * a {@code float} value. It does this
+ * by first constructing an {@code int}
+ * value in exactly the manner
+ * of the {@code readInt}
+ * method, then converting this {@code int}
+ * value to a {@code float} in
+ * exactly the manner of the method {@code Float.intBitsToFloat}.
+ * This method is suitable for reading
+ * bytes written by the {@code writeFloat}
+ * method of interface {@code DataOutput}.
+ *
+ * @return the {@code float} value read.
+ * @throws EOFException if this stream reaches the end before reading
+ * all the bytes.
+ * @throws IOException if an I/O error occurs.
+ */
@Override
public float readFloat() throws IOException {
return Float.intBitsToFloat(readInt());
}
+ /**
+ * Reads eight input bytes and returns
+ * a {@code double} value. It does this
+ * by first constructing a {@code long}
+ * value in exactly the manner
+ * of the {@code readLong}
+ * method, then converting this {@code long}
+ * value to a {@code double} in exactly
+ * the manner of the method {@code Double.longBitsToDouble}.
+ * This method is suitable for reading
+ * bytes written by the {@code writeDouble}
+ * method of interface {@code DataOutput}.
+ *
+ * @return the {@code double} value read.
+ * @throws EOFException if this stream reaches the end before reading
+ * all the bytes.
+ * @throws IOException if an I/O error occurs.
+ */
@Override
public double readDouble() throws IOException {
return Double.longBitsToDouble(readLong());
@@ -219,6 +545,32 @@ public class AndroidRandomReadableFile implements DataInput, Closeable {
private char[] lineBuffer;
+ /**
+ * See the general contract of the readLine
+ * method of DataInput
.
+ *
+ * Bytes
+ * for this operation are read from the contained
+ * input stream.
+ *
+ * @return the next line of text from this input stream.
+ * @throws IOException if an I/O error occurs.
+ * @see java.io.BufferedReader#readLine()
+ * @see java.io.FilterInputStream##in
+ * @deprecated This method does not properly convert bytes to characters.
+ * As of JDK 1.1, the preferred way to read lines of text is via the
+ * BufferedReader.readLine()
method. Programs that use the
+ * DataInputStream
class to read lines can be converted to use
+ * the BufferedReader
class by replacing code of the form:
+ *
+ * with: + *+ * DataInputStream d = new DataInputStream(in); + *
+ */ @Deprecated @Override public String readLine() throws IOException { @@ -260,109 +612,67 @@ public class AndroidRandomReadableFile implements DataInput, Closeable { return String.copyValueOf(buf, 0, offset); } + /** + * See the general contract of the+ * BufferedReader d + * = new BufferedReader(new InputStreamReader(in)); + *
readUTF
+ * method of DataInput
.
+ * + * Bytes + * for this operation are read from the contained + * input stream. + * + * @return a Unicode string. + * @throws EOFException if this input stream reaches the end before + * reading all the bytes. + * @throws IOException the stream has been closed and the contained + * input stream does not support reading after close, or + * another I/O error occurs. + * @throws UTFDataFormatException if the bytes do not represent a valid + * modified UTF-8 encoding of a string. + * @see java.io.DataInputStream#readUTF(java.io.DataInput) + */ @Override public String readUTF() throws IOException { - if (pos != getPos()) { - syncInputStream(); - } - return dis.readUTF(); + return DataInputStream.readUTF(this); } -// public final static String readUTF(DataInput in) throws IOException { -// int utflen = in.readUnsignedShort(); -// byte[] bytearr = null; -// char[] chararr = null; -// if (in instanceof DataInputStream) { -// DataInputStream dis = (DataInputStream)in; -// if (dis.bytearr.length < utflen){ -// dis.bytearr = new byte[utflen*2]; -// dis.chararr = new char[utflen*2]; -// } -// chararr = dis.chararr; -// bytearr = dis.bytearr; -// } else { -// bytearr = new byte[utflen]; -// chararr = new char[utflen]; -// } -// -// int c, char2, char3; -// int count = 0; -// int chararr_count=0; -// -// in.readFully(bytearr, 0, utflen); -// -// while (count < utflen) { -// c = (int) bytearr[count] & 0xff; -// if (c > 127) break; -// count++; -// chararr[chararr_count++]=(char)c; -// } -// -// while (count < utflen) { -// c = (int) bytearr[count] & 0xff; -// switch (c >> 4) { -// case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: -// /* 0xxxxxxx*/ -// count++; -// chararr[chararr_count++]=(char)c; -// break; -// case 12: case 13: -// /* 110x xxxx 10xx xxxx*/ -// count += 2; -// if (count > utflen) -// throw new UTFDataFormatException( -// "malformed input: partial character at end"); -// char2 = (int) bytearr[count-1]; -// if ((char2 & 0xC0) != 0x80) -// throw new UTFDataFormatException( -// "malformed input around byte " + count); -// chararr[chararr_count++]=(char)(((c & 0x1F) << 6) | -// (char2 & 0x3F)); -// break; -// case 14: -// /* 1110 xxxx 10xx xxxx 10xx xxxx */ -// count += 3; -// if (count > utflen) -// throw new UTFDataFormatException( -// "malformed input: partial character at end"); -// char2 = (int) bytearr[count-2]; -// char3 = (int) bytearr[count-1]; -// if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80)) -// throw new UTFDataFormatException( -// "malformed input around byte " + (count-1)); -// chararr[chararr_count++]=(char)(((c & 0x0F) << 12) | -// ((char2 & 0x3F) << 6) | -// ((char3 & 0x3F) << 0)); -// break; -// default: -// /* 10xx xxxx, 1111 xxxx */ -// throw new UTFDataFormatException( -// "malformed input around byte " + count); -// } -// } -// // The number of chars produced may be less than utflen -// return new String(chararr, 0, chararr_count); -// } @Override public void close() throws IOException { - try { - dis.close(); - } catch (Exception e) { - e.printStackTrace(); - } + guard.close(); + + if (Build.VERSION.SDK_INT >= 28) { + //Reference.reachabilityFence(this); + } try { - fis.close(); + if (fis != null) { + fis.close(); + } } catch (Exception e) { e.printStackTrace(); } try { - pfd.close(); + if (pfd != null) { + pfd.close(); + } + } catch (Exception e) { e.printStackTrace(); } } + + protected void finalize() throws Throwable { + try { + // Note that guard could be null if the constructor threw. + + guard.warnIfOpen(); + + close(); + + } finally { + super.finalize(); + } + } }