/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed 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 com.android.tools.perflib.heap;

import com.android.tools.perflib.captures.MemoryMappedFileBuffer;

import junit.framework.TestCase;

import java.io.File;
import java.util.Collection;
import java.util.List;

public class HprofParserTest extends TestCase {

    Snapshot mSnapshot;

    @Override
    protected void setUp() throws Exception {
        super.setUp();

        File file = new File(getClass().getResource("/dialer.android-hprof").getFile());
        mSnapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(file));
        mSnapshot.resolveReferences();
    }

    @Override
    protected void tearDown() throws Exception {
        super.tearDown();

        mSnapshot.dispose();
        mSnapshot = null;
    }

    public void testHierarchy() {
        ClassObj application = mSnapshot.findClass("android.app.Application");
        assertNotNull(application);

        ClassObj contextWrapper = application.getSuperClassObj();
        assertNotNull(contextWrapper);
        assertEquals("android.content.ContextWrapper", contextWrapper.getClassName());
        contextWrapper.getSubclasses().contains(application);

        ClassObj context = contextWrapper.getSuperClassObj();
        assertNotNull(context);
        assertEquals("android.content.Context", context.getClassName());
        context.getSubclasses().contains(contextWrapper);

        ClassObj object = context.getSuperClassObj();
        assertNotNull(object);
        assertEquals("java.lang.Object", object.getClassName());
        object.getSubclasses().contains(context);

        ClassObj none = object.getSuperClassObj();
        assertNull(none);
    }

    public void testClassLoaders() {
        ClassObj application = mSnapshot.findClass("android.app.Application");
        assertNull(application.getClassLoader());

        ClassObj dialer = mSnapshot.findClass("com.android.dialer.DialerApplication");
        Instance classLoader = dialer.getClassLoader();
        assertNotNull(classLoader);
        assertEquals("dalvik.system.PathClassLoader", classLoader.getClassObj().getClassName());
    }

    public void testPrimitiveArrays() {
        ClassObj byteArray = mSnapshot.findClass("byte[]");
        assertEquals(1406, byteArray.getInstancesList().size());
        assertEquals(0, byteArray.getInstanceSize());
        assertEquals(681489, byteArray.getShallowSize());

        ArrayInstance byteArrayInstance = (ArrayInstance) mSnapshot.findInstance(0xB0D60401);
        assertEquals(byteArray, byteArrayInstance.getClassObj());
        assertEquals(43224, byteArrayInstance.getSize());
        assertEquals(43224, byteArrayInstance.getCompositeSize());

        ClassObj intArrayArray = mSnapshot.findClass("int[][]");
        assertEquals(37, intArrayArray.getInstancesList().size());

        ArrayInstance intArrayInstance = (ArrayInstance) mSnapshot.findInstance(0xB0F69F58);
        assertEquals(intArrayArray, intArrayInstance.getClassObj());
        assertEquals(40, intArrayInstance.getSize());
        assertEquals(52, intArrayInstance.getCompositeSize());

        ClassObj stringArray = mSnapshot.findClass("java.lang.String[]");
        assertEquals(1396, stringArray.getInstancesList().size());
    }

    /**
     * Tests the creation of an Enum class which covers static values, fields of type references,
     * strings and primitive values.
     */
    public void testObjectConstruction() {
        ClassObj clazz = mSnapshot.findClass("java.lang.Thread$State");
        assertNotNull(clazz);

        Object object = clazz.getStaticField(Type.OBJECT, "$VALUES");
        assertTrue(object instanceof ArrayInstance);
        ArrayInstance array = (ArrayInstance) object;
        Object[] values = array.getValues();
        assertEquals(6, values.length);

        Collection<Instance> instances = clazz.getInstancesList();
        for (Object value : values) {
            assertTrue(value instanceof Instance);
            assertTrue(instances.contains(value));
        }

        Object enumValue = clazz.getStaticField(Type.OBJECT, "NEW");
        assertTrue(enumValue instanceof ClassInstance);
        ClassInstance instance = (ClassInstance) enumValue;
        assertSame(clazz, instance.getClassObj());

        List<ClassInstance.FieldValue> fields = instance.getFields("name");
        assertEquals(1, fields.size());
        assertEquals(Type.OBJECT, fields.get(0).getField().getType());
        Object name = fields.get(0).getValue();

        assertTrue(name instanceof ClassInstance);
        ClassInstance string = (ClassInstance) name;
        assertEquals("java.lang.String", string.getClassObj().getClassName());
        fields = string.getFields("value");
        assertEquals(1, fields.size());
        assertEquals(Type.OBJECT, fields.get(0).getField().getType());
        Object value = fields.get(0).getValue();
        assertTrue(value instanceof ArrayInstance);
        Object[] data = ((ArrayInstance) value).getValues();
        assertEquals(3, data.length);
        assertEquals('N', data[0]);
        assertEquals('E', data[1]);
        assertEquals('W', data[2]);

        fields = instance.getFields("ordinal");
        assertEquals(1, fields.size());
        assertEquals(Type.INT, fields.get(0).getField().getType());
        assertEquals(0, fields.get(0).getValue());
    }

    /**
     * Tests getValues to make sure it's not adding duplicate entries to the back references.
     */
    public void testDuplicateEntries() {
        mSnapshot = new SnapshotBuilder(2).addReferences(1, 2).addRoot(1).build();
        mSnapshot.computeDominators();

        assertEquals(2, mSnapshot.getReachableInstances().size());
        ClassInstance parent = (ClassInstance)mSnapshot.findInstance(1);
        List<ClassInstance.FieldValue> firstGet = parent.getValues();
        List<ClassInstance.FieldValue> secondGet = parent.getValues();
        assertEquals(1, firstGet.size());
        assertEquals(firstGet.size(), secondGet.size());
        Instance child = mSnapshot.findInstance(2);
        assertEquals(1, child.getHardReverseReferences().size());
    }

    public void testResolveReferences() {
        mSnapshot = new SnapshotBuilder(1).addRoot(1).build();
        ClassObj subSoftReferenceClass = new ClassObj(98, null, "SubSoftReference", 0);
        subSoftReferenceClass.setSuperClassId(SnapshotBuilder.SOFT_REFERENCE_ID);
        ClassObj subSubSoftReferenceClass = new ClassObj(97, null, "SubSubSoftReference", 0);
        subSubSoftReferenceClass.setSuperClassId(98);

        mSnapshot.findClass(SnapshotBuilder.SOFT_REFERENCE_ID).addSubclass(subSoftReferenceClass);
        subSoftReferenceClass.addSubclass(subSubSoftReferenceClass);

        mSnapshot.addClass(98, subSoftReferenceClass);
        mSnapshot.addClass(97, subSubSoftReferenceClass);

        mSnapshot.identifySoftReferences();

        assertTrue(subSoftReferenceClass.getIsSoftReference());
        assertTrue(subSubSoftReferenceClass.getIsSoftReference());
    }
}
