/* Copyright (c) 2007 Timothy Wall, All Rights Reserved
 *
 * The contents of this file is dual-licensed under 2 
 * alternative Open Source/Free licenses: LGPL 2.1 or later and 
 * Apache License 2.0. (starting with JNA version 4.0.0).
 * 
 * You can freely decide which license you want to apply to 
 * the project.
 * 
 * You may obtain a copy of the LGPL License at:
 * 
 * http://www.gnu.org/licenses/licenses.html
 * 
 * A copy is also included in the downloadable source code package
 * containing JNA, in file "LGPL2.1".
 * 
 * You may obtain a copy of the Apache License at:
 * 
 * http://www.apache.org/licenses/
 * 
 * A copy is also included in the downloadable source code package
 * containing JNA, in file "AL2.0".
 */
package com.sun.jna;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.Map;

import junit.framework.TestCase;

public class MemoryTest extends TestCase implements GCWaits {

    public void testAutoFreeMemory() throws Exception {
        final boolean[] flag = { false };
        Memory core = new Memory(10) {
            @Override
            protected void finalize() {
                super.finalize();
                flag[0] = true;
            }
        };
        Pointer shared = core.share(0, 5);
        Reference<Memory> ref = new WeakReference<Memory>(core);

        core = null;
        System.gc();
        assertFalse("Memory prematurely GC'd", flag[0]);
        assertNotNull("Base memory GC'd while shared memory extant", ref.get());
        // Avoid having IBM J9 prematurely nullify "shared"
        shared.setInt(0, 0);

        shared = null;
        long start = System.currentTimeMillis();
        System.gc();
        Memory.purge();
        for (int i=0;i < GC_WAITS && ref.get() != null;i++) {
            Thread.sleep(GC_WAIT_INTERVAL);
            System.gc();
            Memory.purge();
        }
        long end = System.currentTimeMillis();
        assertNull("Memory not GC'd after " + (end - start) + " millis", ref.get());
    }

    public void testShareMemory() {
        Memory base = new Memory(8);
        Pointer shared = base.share(0);
        assertNotSame("Memory share should return a different object", base, shared);
    }

    public void testSharedMemoryBounds() {
        Memory base = new Memory(16);
        Pointer shared = base.share(4, 4);
        shared.getInt(-4);
        try {
            shared.getInt(-8);
            fail("Bounds check should fail");
        }
        catch(IndexOutOfBoundsException e) {
        }
        shared.getInt(8);
        try {
            shared.getInt(12);
            fail("Bounds check should fail");
        }
        catch(IndexOutOfBoundsException e) {
        }
    }

    public void testAlignment() {
        final int SIZE = 1<<16;
        Memory base = new Memory(SIZE);
        for (int align=1;align < SIZE;align *= 2) {
            Memory unaligned = base;
            long mask = ~((long)align - 1);
            if ((base.peer & mask) == base.peer)
                unaligned = (Memory)base.share(1, SIZE-1);
            Pointer aligned = unaligned.align(align);
            assertEquals("Memory not aligned (" + align + ")",
                         aligned.peer & mask, aligned.peer);

            assertSame("Alignment request on aligned memory should no-op",
                       aligned, ((Memory)aligned).align(align));
        }
    }

    public void testNegativeAlignment() {
        final int SIZE = 128;
        Memory base = new Memory(SIZE);
        try {
            base.align(-1);
            fail("Negative alignments not allowed");
        }
        catch(IllegalArgumentException e) { }
    }

    public void testInvalidAlignment() {
        final int SIZE = 128;
        Memory base = new Memory(SIZE);
        int[] alignments = { 0, 3, 5, 9, 13 };
        for (int i=0;i < alignments.length;i++) {
            try {
                base.align(alignments[i]);
                fail("Power-of-two alignments required");
            }
            catch(IllegalArgumentException e) { }
        }
    }

    public void testAvoidGCWithExtantBuffer() throws Exception {
        if (!Platform.HAS_BUFFERS) return;

        Memory m = new Memory(1024);
        m.clear();

        ByteBuffer b = m.getByteBuffer(0, m.size());
        Reference<Memory> ref = new WeakReference<Memory>(m);
        Reference<ByteBuffer> bref = new WeakReference<ByteBuffer>(b);

        // Create a second byte buffer "equal" to the first
        m = new Memory(1024);
        m.clear();
        m.getByteBuffer(0, m.size());

        m = null;
        System.gc();
        Memory.purge();
        for (int i=0;i < GC_WAITS && ref.get() != null;i++) {
            Thread.sleep(GC_WAIT_INTERVAL);
            System.gc();
            Memory.purge();
        }
        assertNotNull("Memory GC'd while NIO Buffer still exists", ref.get());

        // Avoid IBM J9 optimization resulting in premature GC of buffer
        b.put((byte)0);

        b = null;
        System.gc();
        Memory.purge();
        for (int i=0;i < GC_WAITS && (bref.get() != null || ref.get() != null);i++) {
            Thread.sleep(GC_WAIT_INTERVAL);
            System.gc();
            Memory.purge();
        }
        assertNull("Buffer not GC'd\n", bref.get());
        assertNull("Memory not GC'd after buffer GC'd\n", ref.get());
    }

    public void testDump() {
        // test with 15 bytes so last line has less than 4 bytes
        int n = 15;

        Memory m = new Memory(n);

        for (int i = 0; i < n; i++) {
            m.setByte(i, (byte) i);
        }

        String ls = System.getProperty("line.separator");

        assertEquals("memory dump" + ls +
            "[00010203]" + ls +
            "[04050607]" + ls +
            "[08090a0b]" + ls +
            "[0c0d0e]" + ls, m.dump());
    }
    
    public void testRemoveAllocatedMemoryMap() throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        // Make sure there are no remaining allocations
        Memory.disposeAll();

        // get a reference to the allocated memory
        Field allocatedMemoryField = Memory.class.getDeclaredField("allocatedMemory");
        allocatedMemoryField.setAccessible(true);
        Map<Memory, Reference<Memory>> allocatedMemory = (Map<Memory, Reference<Memory>>) allocatedMemoryField.get(null);
        assertEquals(0, allocatedMemory.size());
        
        // Test allocation and ensure it is accounted for
        Memory mem = new Memory(1024);
        assertEquals(1, allocatedMemory.size());
        
        // Dispose memory and ensure allocation is removed from allocatedMemory-Map
        mem.dispose();
        assertEquals(0, allocatedMemory.size());
    }

    public static void main(String[] args) {
        junit.textui.TestRunner.run(MemoryTest.class);
    }
}
