/*
 *  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 tests.api.java.lang.ref;

import dalvik.annotation.TestTargets;
import dalvik.annotation.TestLevel;
import dalvik.annotation.TestTargetNew;
import dalvik.annotation.TestTargetClass;

import junit.framework.AssertionFailedError;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.Vector;

@TestTargetClass(Reference.class)
public class ReferenceTest extends junit.framework.TestCase {
    Object tmpA, tmpB, tmpC, obj;

    volatile Reference r;

    /*
     * For test_subclass().
     */
    static TestWeakReference twr;
    static AssertionFailedError error;
    static boolean testObjectFinalized;
    static class TestWeakReference<T> extends WeakReference<T> {
        public volatile boolean clearSeen = false;
        public volatile boolean enqueueSeen = false;

        public TestWeakReference(T referent) {
            super(referent);
        }

        public TestWeakReference(T referent, ReferenceQueue<? super T> q) {
            super(referent, q);
        }

        public void clear() {
            clearSeen = true;
            if (testObjectFinalized) {
                error = new AssertionFailedError("Clear should happen " +
                        "before finalization.");
                throw error;
            }
            if (enqueueSeen) {
                error = new AssertionFailedError("Clear should happen " +
                        "before enqueue.");
                throw error;
            }
            super.clear();
        }

        public boolean enqueue() {
            enqueueSeen = true;
            if (!clearSeen) {
                error = new AssertionFailedError("Clear should happen " +
                        "before enqueue.");
                throw error;
            }

            /* Do this last;  it may notify the main test thread,
             * and anything we'd do after it (e.g., setting clearSeen)
             * wouldn't be seen.
             */
            return super.enqueue();
        }
    }

    protected void doneSuite() {
        tmpA = tmpB = obj = null;
    }

    /**
     * @tests java.lang.ref.Reference#clear()
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "clear",
        args = {}
    )
    public void test_clear() {
        tmpA = new Object();
        tmpB = new Object();
        tmpC = new Object();
        SoftReference sr = new SoftReference(tmpA, new ReferenceQueue());
        WeakReference wr = new WeakReference(tmpB, new ReferenceQueue());
        PhantomReference pr = new PhantomReference(tmpC, new ReferenceQueue());
        assertTrue("Start: Object not cleared.", (sr.get() != null)
                && (wr.get() != null));
        assertNull("Referent is not null.", pr.get());
        sr.clear();
        wr.clear();
        pr.clear();
        assertTrue("End: Object cleared.", (sr.get() == null)
                && (wr.get() == null));
        assertNull("Referent is not null.", pr.get());
        // Must reference tmpA and tmpB so the jit does not optimize them away
        assertTrue("should always pass", tmpA != sr.get() && tmpB != wr.get());
    }

    /**
     * @tests java.lang.ref.Reference#enqueue()
     */
    @TestTargets({
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            notes = "",
            method = "enqueue",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.COMPLETE,
            notes = "",
            method = "isEnqueued",
            args = {}
        )
    })
    public void test_enqueue() {
        ReferenceQueue rq = new ReferenceQueue();
        obj = new Object();
        Reference ref = new SoftReference(obj, rq);
        assertTrue("Enqueue failed.", (!ref.isEnqueued())
                && ((ref.enqueue()) && (ref.isEnqueued())));
        assertTrue("Not properly enqueued.", rq.poll().get() == obj);
        // This fails...
        assertTrue("Should remain enqueued.", !ref.isEnqueued());
        assertTrue("Can not enqueue twice.", (!ref.enqueue())
                && (rq.poll() == null));

        rq = new ReferenceQueue();
        obj = new Object();

        ref = new WeakReference(obj, rq);
        assertTrue("Enqueue failed2.", (!ref.isEnqueued())
                && ((ref.enqueue()) && (ref.isEnqueued())));
        assertTrue("Not properly enqueued2.", rq.poll().get() == obj);
        assertTrue("Should remain enqueued2.", !ref.isEnqueued()); // This
        // fails.
        assertTrue("Can not enqueue twice2.", (!ref.enqueue())
                && (rq.poll() == null));

        ref = new PhantomReference(obj, rq);
        assertTrue("Enqueue failed3.", (!ref.isEnqueued())
                && ((ref.enqueue()) && (ref.isEnqueued())));
        assertNull("Not properly enqueued3.", rq.poll().get());
        assertTrue("Should remain enqueued3.", !ref.isEnqueued()); // This
        // fails.
        assertTrue("Can not enqueue twice3.", (!ref.enqueue())
                && (rq.poll() == null));
    }

    /**
     * @tests java.lang.ref.Reference#enqueue()
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "Verifies positive functionality for WeakReference.",
        method = "get",
        args = {}
    )
    public void test_get_WeakReference() {
        // Test the general/overall functionality of Reference.

        class TestObject {
            public boolean finalized;

            public TestObject() {
                finalized = false;
            }

            protected void finalize() {
                finalized = true;
            }
        }

        final ReferenceQueue rq = new ReferenceQueue();

        class TestThread extends Thread {

            public void run() {
                // Create the object in a separate thread to ensure it will be
                // gc'ed
                Object testObj = new TestObject();
                r = new WeakReference(testObj, rq);
                testObj = null;
            }
        }

        Reference ref;

        try {
            TestThread t = new TestThread();
            t.start();
            t.join();
            System.gc();
            System.runFinalization();
            ref = rq.remove();
            assertNotNull("Object not garbage collected1.", ref);
            assertTrue("Unexpected ref1", ref == r);
            assertNull("Object could not be reclaimed1.", r.get());
        } catch (InterruptedException e) {
            fail("InterruptedException : " + e.getMessage());
        }

        try {
            TestThread t = new TestThread();
            t.start();
            t.join();
            System.gc();
            System.runFinalization();
            ref = rq.poll();
            assertNotNull("Object not garbage collected.", ref);
            assertTrue("Unexpected ref2", ref == r);
            assertNull("Object could not be reclaimed.", ref.get());
            // Reference wr so it does not get collected
            assertNull("Object could not be reclaimed.", r.get());
        } catch (Exception e) {
            fail("Exception : " + e.getMessage());
        }
    }


    /**
     * Makes sure that overridden versions of clear() and enqueue()
     * get called, and that clear/enqueue/finalize happen in the
     * right order for WeakReferences.
     *
     * @tests java.lang.ref.Reference#clear()
     * @tests java.lang.ref.Reference#enqueue()
     * @tests java.lang.Object#finalize()
     */
    @TestTargets({
        @TestTargetNew(
            level = TestLevel.PARTIAL_COMPLETE,
            notes = "Makes sure that overridden versions of clear() and enqueue()  " +
                    "get called, and that clear/enqueue/finalize happen in the  " +
                    "right order for WeakReferences.",
            method = "clear",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.PARTIAL_COMPLETE,
            notes = "Makes sure that overridden versions of clear() and enqueue()  " +
                    "get called, and that clear/enqueue/finalize happen in the  " +
                    "right order for WeakReferences.",
            method = "enqueue",
            args = {}
        )
    })
    public void test_subclass() {
        error = null;
        testObjectFinalized = false;
        twr = null;

        class TestObject {
            public TestWeakReference testWeakReference = null;

            public void setTestWeakReference(TestWeakReference twr) {
                testWeakReference = twr;
            }

            protected void finalize() {
                testObjectFinalized = true;
            }
        }

        final ReferenceQueue rq = new ReferenceQueue();

        class TestThread extends Thread {
            public void run() {
                // Create the object in a separate thread to ensure it will be
                // gc'ed
                TestObject testObj = new TestObject();
                twr = new TestWeakReference(testObj, rq);
                testObj.setTestWeakReference(twr);
                testObj = null;
            }
        }

        Reference ref;

        try {
            Thread t = new TestThread();
            t.start();
            t.join();
            System.gc();
            System.runFinalization();
            ref = rq.remove(5000L);    // Give up after five seconds.

            assertNotNull("Object not garbage collected.", ref);
            assertTrue("Unexpected reference.", ref == twr);
            assertNull("Object could not be reclaimed.", twr.get());
            //assertTrue("Overridden clear() should have been called.",
            //       twr.clearSeen);
            //assertTrue("Overridden enqueue() should have been called.",
            //        twr.enqueueSeen);
            assertTrue("finalize() should have been called.",
                    testObjectFinalized);
        } catch (InterruptedException e) {
            fail("InterruptedException : " + e.getMessage());
        }

    }

    /**
     * @tests java.lang.ref.Reference#get()
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "Doesn't check that get() can return null.",
        method = "get",
        args = {}
    )
    public void test_get() {

        Vector<StringBuffer> vec = new Vector<StringBuffer>();
        WeakReference ref = new WeakReference(vec, new ReferenceQueue());
        assertTrue("Get succeeded.", ref.get() == vec);

        Runtime rt =  Runtime.getRuntime();

        long beforeTest = rt.freeMemory();
        while(rt.freeMemory() < beforeTest * 2/3) {
            vec.add(new StringBuffer(1000));
        }
        vec = null;

        System.gc();
        System.runFinalization();
        assertNull("get() doesn't return null after gc for WeakReference",
                    ref.get());

        obj = new Object();
        ref = new WeakReference(obj, new ReferenceQueue());
        ref.clear();
        assertNull("get() doesn't return null after clear for WeakReference",
                ref.get());
    }

    /**
     * @tests java.lang.ref.Reference#isEnqueued()
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "isEnqueued",
        args = {}
    )
    public void test_isEnqueued() {
        ReferenceQueue rq = new ReferenceQueue();
        obj = new Object();
        Reference ref = new SoftReference(obj, rq);
        assertTrue("Should start off not enqueued.", !ref.isEnqueued());
        ref.enqueue();
        assertTrue("Should now be enqueued.", ref.isEnqueued());
        ref.enqueue();
        assertTrue("Should still be enqueued.", ref.isEnqueued());
        rq.poll();
        // This fails ...
        assertTrue("Should now be not enqueued.", !ref.isEnqueued());
    }

    /* Contrives a situation where the only reference to a string
     * is a WeakReference from an object that is being finalized.
     * Checks to make sure that the referent of the WeakReference
     * is still pointing to a valid object.
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "Contrives a situation where the only reference to a string  " +
                "is a WeakReference from an object that is being finalized.  " +
                "Checks to make sure that the referent of the WeakReference  " +
                "is still pointing to a valid object.",
        method = "get",
        args = {}
    )
    public void test_finalizeReferenceInteraction() {
        error = null;
        testObjectFinalized = false;

        class TestObject {
            WeakReference<String> stringRef;

            public TestObject(String referent) {
                stringRef = new WeakReference<String>(referent);
            }

            protected void finalize() {
                try {
                    /* If a VM bug has caused the referent to get
                     * freed without the reference getting cleared,
                     * looking it up, assigning it to a local and
                     * doing a GC should cause some sort of exception.
                     */
                    String s = stringRef.get();
                    System.gc();
                    testObjectFinalized = true;
                } catch (Throwable t) {
                    error = new AssertionFailedError("something threw '" + t +
                            "' in finalize()");
                }
            }
        }

        class TestThread extends Thread {
            public void run() {
                // Create the object in a separate thread to ensure it will be
                // gc'ed
                TestObject testObj = new TestObject(new String("sup /b/"));
            }
        }

        try {
            Thread t = new TestThread();
            t.start();
            t.join();
            System.gc();
            System.runFinalization();
            Thread.sleep(1000);
            if (error != null) {
                throw error;
            }
            assertTrue("finalize() should have been called.",
                    testObjectFinalized);
        } catch (InterruptedException e) {
            fail("InterruptedException : " + e.getMessage());
        }
    }


    protected void setUp() {
    }

    protected void tearDown() {
    }
}
