import java.util.function.BiFunction;

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

class ReferencesTest {
    private static SomeObject resurrectedObject = null;
    private static int nextAttemptToKillCounter = 0;
    
    private static void referenceTest(
            BiFunction<SomeObject, ReferenceQueue<? super SomeObject>,
                Reference<? extends SomeObject>> constr, int delay)
            throws InterruptedException {
        ReferenceQueue<SomeObject> rq = new ReferenceQueue<>();
        SomeObject strongRef = new SomeObject();
        Reference<? extends SomeObject> ref = constr.apply(strongRef, rq);

        // delete the strong reference
        strongRef = null;
        // try to trigger the GC
        System.gc();
        while (true) {
            // try to poll something from the RQ
            Reference<? extends SomeObject> pulledRef = rq.poll();
            System.out.printf("poll(): %s\n", pulledRef);
            if (pulledRef != null) {
                System.out.printf("ref.get(): %s\n",
                    ref.get());
                System.out.printf("resurrectedObject = %s\n",
                    resurrectedObject);
                break;
            } 

            if (resurrectedObject != null
                    && nextAttemptToKillCounter == 0) {
                System.out.println(
                    "*exterminate the resurrected object*");
                resurrectedObject = null;
                // try to trigger the GC
                System.gc();
            }
            if (nextAttemptToKillCounter > 0) {
                System.out.printf(
                    "*next attempt to kill in %d steps*\n",
                    nextAttemptToKillCounter);
                nextAttemptToKillCounter--;
            }

            Thread.sleep(delay);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("**phantom reference test**");
        referenceTest(PhantomReference::new, 250);
        
        System.out.println("\n**weak reference test**");
        referenceTest(WeakReference::new, 250);
    }

    private static class SomeObject {
        @Override
        public void finalize() throws Throwable {
            System.out.println("*at the finalize method*");
            resurrectedObject = this;
            nextAttemptToKillCounter = 10;
            System.out.println("*resurrection*");
        }
    }
}