/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.common.references;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.facebook.common.internal.Closeables;
import com.facebook.common.internal.Preconditions;
import com.facebook.common.internal.VisibleForTesting;
import com.facebook.common.logging.FLog;
/**
* A smart pointer-like class for Java.
*
* <p>This class allows reference-counting semantics in a Java-friendlier way. A single object
* can have any number of CloseableReferences pointing to it. When all of these have been closed,
* the object either has its {@link Closeable#close} method called, if it implements
* {@link Closeable}, or its designated {@link ResourceReleaser#release},
* if it does not.
*
* <p>Callers can construct a CloseableReference wrapping a {@link Closeable} with:
* <pre>
* Closeable foo;
* CloseableReference c = CloseableReference.of(foo);
* </pre>
* <p>Objects that do not implement Closeable can still use this class, but must supply a
* {@link ResourceReleaser}:
* <pre>
* {@code
* Object foo;
* ResourceReleaser<Object> fooReleaser;
* CloseableReference c = CloseableReference.of(foo, fooReleaser);
* }
* </pre>
* <p>When making a logical copy, callers should call {@link #clone}:
* <pre>
* CloseableReference copy = c.clone();
* </pre>
* <p>
* When each copy of CloseableReference is no longer needed, close should be called:
* <pre>
* copy.close();
* c.close();
* </pre>
*
* <p>As with any Closeable, try-finally semantics may be needed to ensure that close is called.
* <p>Do not rely upon the finalizer; the purpose of this class is for expensive resources to
* be released without waiting for the garbage collector. The finalizer will log an error if
* the close method has not been called.
*/
public final class CloseableReference<T> implements Cloneable, Closeable {
private static Class<CloseableReference> TAG = CloseableReference.class;
@GuardedBy("this")
private boolean mIsClosed = false;
private final SharedReference<T> mSharedReference;
private static final ResourceReleaser<Closeable> DEFAULT_CLOSEABLE_RELEASER =
new ResourceReleaser<Closeable>() {
@Override
public void release(Closeable value) {
try {
Closeables.close(value, true);
} catch (IOException ioe) {
// This will not happen, Closeable.close swallows and logs IOExceptions
}
}
};
private CloseableReference(SharedReference<T> sharedReference) {
mSharedReference = Preconditions.checkNotNull(sharedReference);
sharedReference.addReference();
}
private CloseableReference(T t, ResourceReleaser<T> resourceReleaser) {
mSharedReference = new SharedReference<T>(t, resourceReleaser);
}
/**
* Constructs a CloseableReference.
*
* <p>Returns null if the parameter is null.
*/
public static @Nullable <T extends Closeable> CloseableReference<T> of(@Nullable T t) {
if (t == null) {
return null;
} else {
return new CloseableReference<T>(t, (ResourceReleaser<T>) DEFAULT_CLOSEABLE_RELEASER);
}
}
/**
* Constructs a CloseableReference (wrapping a SharedReference) of T with provided
* ResourceReleaser<T>. If t is null, this will just return null.
*/
public static @Nullable <T> CloseableReference<T> of(
@Nullable T t,
ResourceReleaser<T> resourceReleaser) {
if (t == null) {
return null;
} else {
return new CloseableReference<T>(t, resourceReleaser);
}
}
/**
* Returns the underlying Closeable if this reference is not closed yet.
* Otherwise IllegalStateException is thrown.
*/
public synchronized T get() {
Preconditions.checkState(!mIsClosed);
return mSharedReference.get();
}
/**
* Returns a new CloseableReference to the same underlying SharedReference. The SharedReference
* ref-count is incremented.
*/
public synchronized CloseableReference<T> clone() {
Preconditions.checkState(isValid());
return new CloseableReference<T>(mSharedReference);
}
public synchronized CloseableReference<T> cloneOrNull() {
if (isValid()) {
return clone();
}
return null;
}
/**
* Checks if this closable-reference is valid i.e. is not closed.
* @return true if the closeable reference is valid
*/
public synchronized boolean isValid() {
return !mIsClosed;
}
/**
* A test-only method to get the underlying references.
*
* <p><b>DO NOT USE in application code.</b>
*/
@VisibleForTesting
public synchronized SharedReference<T> getUnderlyingReferenceTestOnly() {
return mSharedReference;
}
/**
* Method used for tracking Closeables pointed by CloseableReference.
* Use only for debugging and logging.
*/
public int getValueHash() {
return isValid() ? System.identityHashCode(mSharedReference.get()) : 0;
}
/**
* Closes this CloseableReference.
*
* <p>Decrements the reference count of the underlying object. If it is zero, the object
* will be released.
*
* <p>This method is idempotent. Calling it multiple times on the same instance has no effect.
*/
@Override
public void close() {
synchronized (this) {
if (mIsClosed) {
return;
}
mIsClosed = true;
}
mSharedReference.deleteReference();
}
/**
* Checks if the closable-reference is valid i.e. is not null, and is not closed.
* @return true if the closeable reference is valid
*/
public static boolean isValid(@Nullable CloseableReference<?> ref) {
return ref != null && ref.isValid();
}
/**
* Returns the cloned reference if valid, null otherwise.
*
* @param ref the reference to clone
*/
@Nullable
public static <T> CloseableReference<T> cloneOrNull(@Nullable CloseableReference<T> ref) {
return (ref != null) ? ref.cloneOrNull() : null;
}
/**
* Clones a collection of references and returns a list. Returns null if the list is null. If
* the list is non-null, clones each reference. If a reference cannot be cloned due to already
* being closed, the list will contain a null value in its place.
*
* @param refs the references to clone
* @return the list of cloned references or null
*/
public static <T> List<CloseableReference<T>> cloneOrNull(
Collection<CloseableReference<T>> refs) {
if (refs == null) {
return null;
}
List<CloseableReference<T>> ret = new ArrayList<>(refs.size());
for (CloseableReference<T> ref : refs) {
ret.add(CloseableReference.cloneOrNull(ref));
}
return ret;
}
/**
* Closes the reference handling null.
*
* @param ref the reference to close
*/
public static void closeSafely(@Nullable CloseableReference<?> ref) {
if (ref != null) {
ref.close();
}
}
/**
* Closes the references in the iterable handling null.
*
* @param references the reference to close
*/
public static void closeSafely(@Nullable Iterable<? extends CloseableReference<?>> references) {
if (references != null) {
for (CloseableReference<?> ref : references) {
closeSafely(ref);
}
}
}
@Override
protected void finalize() throws Throwable {
try {
// We put synchronized here so that lint doesn't warn about accessing mIsClosed, which is
// guarded by this. Lint isn't aware of finalize semantics.
synchronized (this) {
if (mIsClosed) {
return;
}
}
FLog.w(
TAG,
"Finalized without closing: %x %x (type = %s)",
System.identityHashCode(this),
System.identityHashCode(mSharedReference),
mSharedReference.get().getClass().getSimpleName());
close();
} finally {
super.finalize();
}
}
}