/*
 *  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.
 */

package org.apache.harmony.tests.java.lang.ref;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import libcore.java.lang.ref.FinalizationTester;

//TODO: write a test to verify that the referent's finalize() happens
//      before the PhantomReference is enqueued.

public class PhantomReferenceTest extends junit.framework.TestCase {
    static Boolean bool;
    public boolean isCalled = false;
    protected void doneSuite() {
        bool = null;
    }

    /**
     * java.lang.ref.PhantomReference#get()
     */
    public void test_get() {
        ReferenceQueue rq = new ReferenceQueue();
        bool = new Boolean(false);
        PhantomReference pr = new PhantomReference(bool, rq);
        assertNull("get() should return null.", pr.get());
        pr.enqueue();
        assertNull("get() should return null.", pr.get());
        pr.clear();
        assertNull("get() should return null.", pr.get());
    }

    /**
     * java.lang.Runtime#gc()
     */
    public void test_gcInteraction_Runtime() {
        check_gcInteraction(() -> { Runtime.getRuntime().gc(); } );
    }

    /**
     * Checks that the sequence {@link System#gc()}, {@link System#runFinalization()}}
     * also has the effect as asserted for {@link Runtime#gc()} elsewhere. The
     * conditions under which System.gc() results in a garbage collection are an
     * implementation detail not guaranteed by documentation.
     */
    public void test_gcInteraction_System() {
        check_gcInteraction(() -> {
            System.gc();
            System.runFinalization();
        } );
    }

    private void check_gcInteraction(Runnable gc) {
        class TestPhantomReference<T> extends PhantomReference<T> {
            public TestPhantomReference(T referent,
                    ReferenceQueue<? super T> q) {
                super(referent, q);
            }
            public boolean enqueue() {
                // Initiate another GC from inside enqueue() to
                // see if it causes any problems inside the VM.
                gc.run();
                return super.enqueue();
            }
        }

        final ReferenceQueue rq = new ReferenceQueue();
        final PhantomReference[] tprs = new PhantomReference[4];

        class TestThread extends Thread {
            public void run() {
                // Create the object in a separate thread to ensure
                // it will be gc'ed.
                Object obj = new Object();
                tprs[0] = new TestPhantomReference(obj, rq);
                tprs[1] = new TestPhantomReference(obj, rq);
                tprs[2] = new TestPhantomReference(obj, rq);
                tprs[3] = new TestPhantomReference(obj, rq);
            }
        }

        try {
            Thread t = new TestThread();
            t.start();
            t.join();

            FinalizationTester.induceFinalization();

            assertNull("get() should return null.", tprs[0].get());
            assertNull("get() should return null.", tprs[1].get());
            assertNull("get() should return null.", tprs[2].get());
            assertNull("get() should return null.", tprs[3].get());

            for (int i = 0; i < 4; i++) {
                Reference r = rq.remove(100L);
                assertNotNull("Reference should have been enqueued.", r);
            }

            // These are to make sure that tprs and its elements don't get
            // optimized out.
            assertNull("get() should return null.", tprs[0].get());
            assertNull("get() should return null.", tprs[1].get());
            assertNull("get() should return null.", tprs[2].get());
            assertNull("get() should return null.", tprs[3].get());
        } catch (InterruptedException e) {
            fail("InterruptedException : " + e.getMessage());
        }
    }

    /**
     * java.lang.ref.PhantomReference#PhantomReference(java.lang.Object,
     *        java.lang.ref.ReferenceQueue)
     */
    public void test_ConstructorLjava_lang_ObjectLjava_lang_ref_ReferenceQueue() {
        ReferenceQueue rq = new ReferenceQueue();
        bool = new Boolean(true);
        try {
            PhantomReference pr = new PhantomReference(bool, rq);
            // Allow the finalizer to run to potentially enqueue
            Thread.sleep(1000);
            assertTrue("Initialization failed.", !pr.isEnqueued());
        } catch (Exception e) {
            fail("Exception during test : " + e.getMessage());
        }
        // need a reference to bool so the jit does not optimize it away
        assertTrue("should always pass", bool.booleanValue());

        boolean exception = false;
        try {
            new PhantomReference(bool, null);
        } catch (NullPointerException e) {
            exception = true;
        }
        assertTrue("Should not throw NullPointerException", !exception);
    }

    protected void setUp() {
    }

    protected void tearDown() {
    }
}
