import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

/**
 * @author Peter Chng
 */
class Ideone {

  // Just so we have a non-primitive, non-interned object that will be GC'd.
  public static class SampleObject<T> {
    private final T value;

    public SampleObject(final T value) {
      this.value = value;
    }

    @Override
    public String toString() {
      return String.valueOf(this.value);
    }
  }

  public static class PhantomReferenceMetadata<T, M> extends PhantomReference<T> {
    // Some metadata stored about the object that will be used during some cleanup actions.
    private final M metadata;

    public PhantomReferenceMetadata(final T referent, final ReferenceQueue<? super T> q,
        final M metadata) {
      super(referent, q);
      this.metadata = metadata;
    }

    public M getMetadata() {
      return this.metadata;
    }
  }

  public static void main(final String[] args) {
    // The object whose GC lifecycle we want to track.
    SampleObject<String> helloObject = new SampleObject<>("Hello");

    // Reference queue that the phantom references will be registered to.
    // They will be enqueued here when the appropriate reachability changes are detected by the JVM.
    final ReferenceQueue<SampleObject<String>> refQueue = new ReferenceQueue<>();

    // In this case, the metadata we associate with the object is some name.
    final PhantomReferenceMetadata<SampleObject<String>, String> helloPhantomReference = new PhantomReferenceMetadata<>(
        helloObject, refQueue, "helloObject");

    new Thread(() -> {
      System.out.println("Starting ReferenceQueue consumer thread.");
      final int numToDequeue = 1;
      int numDequed = 0;
      while (numDequed < numToDequeue) {
        // Unfortunately, need to downcast to the appropriate type.
        try {
          @SuppressWarnings("unchecked")
          final PhantomReferenceMetadata<SampleObject<String>, String> reference = (PhantomReferenceMetadata<SampleObject<String>, String>) refQueue
              .remove();

          // At this point, we know the object referred to by the PhantomReference has been finalized.
          // So, we can do any other clean-up that might be allowed, such as cleaning up some temporary files
          // associated with the object.
          // The metadata stored in PhantomReferenceMetadata could be used to determine which temporary files
          // should be cleaned up.
          // You probably shouldn't rely on this as the ONLY method to clean up those temporary files, however.
          System.out.println(reference.getMetadata() + " has been finalized.");
        } catch (final InterruptedException e) {
          // Just for the purpose of this example.
          break;
        }
        ++numDequed;
      }
      System.out.println("Finished ReferenceQueue consumer thread.");
    }).start();

    // Lose the strong reference to the object.
    helloObject = null;

    // Attempt to trigger a GC.
    System.gc();
  }
}
