/*
 *  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.
 */
/** 
 * @author Intel, Pavel Afremov
 */

#define LOG_DOMAIN "vm.object_queue"
#include "cxxlog.h"

#include "open/types.h"

#include "lock_manager.h"
#include "object_layout.h"
#include "jthread.h"

#include "vm_process.h"
#include "Class.h"
#include "environment.h"
#include "ini.h"
#include "m2n.h"
#include "exceptions.h"
#include "compile.h"
#include "nogc.h"
#include "jit_runtime_support.h"
#include "finalize.h"
#include "vtable.h"
#include "jit_import_rt.h"
#include "finalizer_thread.h"     /* added for NATIVE FINALIZER THREAD */
#include "ref_enqueue_thread.h"   /* added for NATIVE REFERENCE ENQUEUE THREAD */
#include "classloader.h"
#include "thread_generic.h"

#ifndef USE_GC_STATIC
__declspec(dllexport) 
#endif

// FINALIZER_THREAD means that thread is finalizer thread
#define FINALIZER_THREAD    0x1

// FINALIZER_STARTER means that thread is running inside run_finalizers function
#define FINALIZER_STARTER   0x2

//
// This code holds the logic that deals with finalization as well as weak/soft/phantom 
// references. Finalization runs arbitrary code that can include synchronization logic.
//
// There are two different times that finalizers and enqueues can be run.
//
// The first is to have a separate thread that receives the objects that need to be
// finalized and executes them sometime after the GC has completed. This is probable
// what was intended by the designers of the finalization and weak/soft/phantom 
// reference features of the language. 
//
// The second approach that can be taken is to run the finalization or enqueues code  
// immediately after the gc is complete prior to resuming the thread that caused the gc
// to happen.
//
// The first scheme was implemented for finalization and second is used for 
// weak/soft/phantom references.
//

hylatch_t begin_run_finalizer;

class Object_Queue
{
    ManagedObject **objects;
    unsigned capacity;
    unsigned num_objects;

    Lock_Manager objects_lock;
    const char*  log_domain;

    void reallocate(unsigned new_capacity);
    
public:
    Object_Queue();
    Object_Queue(const char* log_domain);
    ~Object_Queue(){
        STD_FREE(objects);
    }
    void add_object(ManagedObject *p_obj);
    ManagedObject* remove_object();
    int getLength();
    
    void enumerate_for_gc();
}; //Object_Queue

class Objects_To_Finalize: public Object_Queue
{
    // workaround part to ignore classes java.nio.charset.CharsetEncoder
    // java.io.FileDescriptor & java.io.FileOutputStream during finalization on exit
    bool is_class_ignored(Class* test);
    bool classes_cached;
    Lock_Manager ignore_lock;
    Class* FileDescriptor;
    Class* FileOutputStream;

    //jobject get_work_lock();
    //jboolean* get_work_lock();
    //jboolean* get_work_lock();
    bool fields_obtained;
    void check_fields_obtained();
    Lock_Manager obtain_fields_lock;
    jobject work_lock;
    jboolean* shutdown;
    jboolean* on_exit;
public:
    Objects_To_Finalize() : Object_Queue("finalize") {
        classes_cached = false;
        fields_obtained = false;
        work_lock = NULL;
        on_exit = NULL;
    };
    // redefine of add method 
    void add_object(ManagedObject *p_obj);
    
    void run_finalizers();
    int do_finalization(int quantity);
    void obtain_fields();
}; //Objects_To_Finalize

class References_To_Enqueue: public Object_Queue
{
public:
    References_To_Enqueue() : Object_Queue("ref") {};
    void enqueue_references();
}; //References_To_Enqueue



Object_Queue::Object_Queue() {
    Object_Queue("unknown");
}

Object_Queue::Object_Queue(const char* log_domain)
{
    objects     = 0;
    capacity    = 0;
    num_objects = 0;
    this-> log_domain =  log_domain;
    reallocate(128);
} //Object_Queue::Object_Queue


/**
 * Allocates array to save objects. Should be called from synchronized block.
 * Now it's called from add_object only.
 */
 
void Object_Queue::reallocate(unsigned new_capacity)
{
    ManagedObject **new_table =
        (ManagedObject **)STD_MALLOC(new_capacity * sizeof(ManagedObject *));
        
    // print trace info about queue enlarge
    TRACE2( log_domain, "The " <<  log_domain << " queue capacity is enlarged to " 
            << new_capacity << " units.");
        
    // asserts that queue information is correct 
    assert(new_table);
    assert(num_objects <= capacity);
    
    // if queue already contains objects, copies it to new array and frees memory for previous one.
    if(objects) {
        memcpy(new_table, objects, num_objects * sizeof(ManagedObject *));
        STD_FREE(objects);
    }
    
    // saves new queue information
    objects = new_table;
    capacity = new_capacity;
} //Object_Queue::reallocate


/**
 * Adds object to queue
 */
void Object_Queue::add_object(ManagedObject *p_obj)
{
    objects_lock._lock();
    
    // reallocates if there is not enough place to save new one
    if(num_objects >= capacity) {
        reallocate(capacity * 2);
    }
    
    // asserts that queue information is correct 
    assert(num_objects < capacity);

    // adds object to queue
    objects[num_objects++] = p_obj;
    objects_lock._unlock();
} //Object_Queue::add_object

/**
 * Removes latest object from queue and returns it as result.
 */
ManagedObject*
Object_Queue::remove_object() {
    ManagedObject* removed_object;
    objects_lock._lock();

    // if there is any objects in queue, remove the latest from there to return as result
    if (0 < num_objects) {
        removed_object = this->objects[--num_objects];
    } else {
        removed_object = NULL;
    }
    objects_lock._unlock();
    return removed_object;
} //Object_Queue::remove_object

/**
 * Returns length of queue
 */
int Object_Queue::getLength() {
   unsigned result;
   
   // synchronization used there to avoid of return very old value
   objects_lock._lock();
   result = this->num_objects;
   objects_lock._unlock();
   
   // there unfresh value can be returned, but this value was correct after function start 
   return result;
}

/**
 * Enumerates queue for GC
 */
void Object_Queue::enumerate_for_gc()
{
    // locks here because some native code can work during gc until tmn_suspend_disable() call
    objects_lock._lock();

    // print trace info about queue enlarge
    TRACE2( log_domain, "The " <<  log_domain << " queue length is " << num_objects << " units");

    // enumerate elements in the queue
    for(unsigned i = 0; i < num_objects; i++) {
        vm_enumerate_root_reference((void **)&(objects[i]), FALSE);
    }
    
    // unlock 
    objects_lock._unlock();
} //Object_Queue::enumerate_for_gc

void Objects_To_Finalize::add_object(ManagedObject *p_obj)
{
    Class* finalizer_thread =
        VM_Global_State::loader_env->java_lang_FinalizerThread_Class;

    if (!finalizer_thread) {
        return;
    } else {
        Object_Queue::add_object(p_obj);
    }
} //Objects_To_Finalize::add_object

// workaround method to ignore classes
// java.io.FileDescriptor & java.io.FileOutputStream during finalization on exit
bool Objects_To_Finalize::is_class_ignored(Class* test) {
    assert(!hythread_is_suspend_enabled());
    
    if (!classes_cached) {
    
        tmn_suspend_enable();
        
        String* FileDescriptorName =
                VM_Global_State::loader_env->string_pool.lookup("java/io/FileDescriptor");
        String* FileOutputStreamName =
                VM_Global_State::loader_env->string_pool.lookup("java/io/FileOutputStream");
        
        Class* FileDescriptor = 
            VM_Global_State::loader_env->bootstrap_class_loader->LoadVerifyAndPrepareClass(
                VM_Global_State::loader_env, FileDescriptorName);
        Class* FileOutputStream = 
            VM_Global_State::loader_env->bootstrap_class_loader->LoadVerifyAndPrepareClass(
                VM_Global_State::loader_env, FileOutputStreamName);
        
        tmn_suspend_disable();
        ignore_lock._lock();
        this->FileDescriptor = FileDescriptor;
        this->FileOutputStream = FileOutputStream;
        classes_cached = true;
        ignore_lock._unlock();
    }
    return ((test==FileDescriptor) || (test==FileOutputStream));
}

void Objects_To_Finalize::check_fields_obtained() {
    if (!fields_obtained) {
        obtain_fields();
    }
}

void Objects_To_Finalize::obtain_fields() {
    obtain_fields_lock._lock();

    if (!fields_obtained) {
        Class* finalizer_thread =
            VM_Global_State::loader_env->java_lang_FinalizerThread_Class;

        if (!finalizer_thread) {
            obtain_fields_lock._unlock();
            return;
        }

        Field* work_lock_field = class_lookup_field_recursive(
                finalizer_thread,
                "workLock", "Ljava/lang/Object;");
        Field* shutdown_field = class_lookup_field_recursive(
                finalizer_thread,
                "shutdown", "Z");
        Field* on_exit_field = class_lookup_field_recursive(
                finalizer_thread,
                "onExit", "Z");

        assert(work_lock_field);
        assert(shutdown_field);
        assert(on_exit_field);

        tmn_suspend_disable();
        ManagedObject* work_lock_addr = get_raw_reference_pointer(
            (ManagedObject **)work_lock_field->get_address());
        assert(work_lock_addr);
        work_lock = oh_allocate_global_handle();
        work_lock->object = work_lock_addr;
        assert(work_lock);
        tmn_suspend_enable();

        shutdown = (jboolean*) shutdown_field->get_address();
        on_exit = (jboolean*) on_exit_field->get_address();
        assert(shutdown);
        assert(on_exit);

        fields_obtained = true;
    }
    obtain_fields_lock._unlock();
}

void Objects_To_Finalize::run_finalizers()
{
    assert(hythread_is_suspend_enabled());

    Class* finalizer_thread =
        VM_Global_State::loader_env->java_lang_FinalizerThread_Class;

    if (!finalizer_thread) {
        return;
    }

    check_fields_obtained();

    int num_objects = getLength() + vm_get_references_quantity();

    if (num_objects == 0) {
        return;
    }

    if ((p_TLS_vmthread->finalize_thread_flags & (FINALIZER_STARTER | FINALIZER_THREAD)) != 0) {
        TRACE2("finalize", "recursive finalization prevented");
        return;
    }

    p_TLS_vmthread->finalize_thread_flags |= FINALIZER_STARTER;
    TRACE2("finalize", "run_finalizers() started");

    // saves curent thread exception risen before running finalizers
    // if any and clears it to allow java to work
    jthrowable cte = exn_get();
    exn_clear();
    
    if (FRAME_COMPILATION ==
            (FRAME_COMPILATION & m2n_get_frame_type(m2n_get_last_frame()))) {
        assert(work_lock);

        IDATA r = jthread_monitor_try_enter(work_lock);

        if (r == TM_ERROR_NONE) {
            jthread_monitor_notify_all(work_lock);
            jthread_monitor_exit(work_lock);
        }
    } else {
        Method* finalize_meth = class_lookup_method_recursive(finalizer_thread,
            "startFinalization", "(Z)V");
        assert(finalize_meth);

        jvalue args[1];
        args[0].z = false;

        tmn_suspend_disable();
        vm_execute_java_method_array((jmethodID) finalize_meth, 0, args);
        tmn_suspend_enable();
    }

#ifndef NDEBUG
    if (exn_raised()) {
        INFO2("finalize", "Uncaught exception "
            << exn_get_name()
            << " while running a wakeFinalization in FinalizerThread");        
    }
#endif
    exn_clear();

    // restores curent thread exception risen before if any
    if (NULL != cte) {
        exn_raise_object(cte);
    }
    p_TLS_vmthread->finalize_thread_flags &= ~FINALIZER_STARTER;
} //Objects_To_Finalize::run_finalizers

int Objects_To_Finalize::do_finalization(int quantity) {
    /* BEGIN: added for NATIVE FINALIZER THREAD */
    Boolean native_finalizer_thread_flag = get_native_finalizer_thread_flag();
    Boolean native_finalizer_shutdown, native_finalizer_on_exit;
    /* END: added for NATIVE FINALIZER THREAD */
    
    //SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_HIGHEST);
    if(!native_finalizer_thread_flag)    // added for NATIVE FINALIZER THREAD
        p_TLS_vmthread->finalize_thread_flags = FINALIZER_THREAD;

    int i;
    tmn_suspend_disable();
    ObjectHandle handle = oh_allocate_local_handle();
    tmn_suspend_enable();
    jvalue args[1];
    args[0].l = (jobject) handle;

    /* BEGIN: modified for NATIVE FINALIZER THREAD */
    if(!native_finalizer_thread_flag){
        assert(VM_Global_State::loader_env->java_lang_FinalizerThread_Class);
        check_fields_obtained();
        assert(shutdown);
        assert(on_exit);
        native_finalizer_shutdown = (Boolean)*shutdown;
        native_finalizer_on_exit = (Boolean)*on_exit;
    }
    /* END: modified for NATIVE FINALIZER THREAD */

    for (i=0; ((i<quantity)||(0==quantity)); i++) {
    
        // shutdown flag in FinalizerThread set after finalization on exit is completed
        /* BEGIN: modified for NATIVE FINALIZER THREAD */
        if(native_finalizer_thread_flag)
            native_finalizer_shutdown = get_finalizer_shutdown_flag();
        if(native_finalizer_shutdown)
            return i;
        /* END: modified for NATIVE FINALIZER THREAD */

        tmn_suspend_disable();
        ManagedObject* object = remove_object();
        handle->object = object;
        tmn_suspend_enable();

        if (object == NULL) {
            return i;
        }
        
        if(get_native_finalizer_thread_flag()){
            int finalizable_obj_num = getLength();
            sched_heavy_finalizer_in_finalization(finalizable_obj_num, i);
        }
        
        tmn_suspend_disable();
        assert(handle->object->vt()->clss);
        Class *clss = handle->object->vt()->clss;
        assert(clss);
        
        /* BEGIN: modified for NATIVE FINALIZER THREAD */
        if(native_finalizer_thread_flag) {
            native_finalizer_on_exit = get_finalizer_on_exit_flag();
            if(native_finalizer_on_exit  && is_class_ignored(clss)) {
                tmn_suspend_enable();
                continue;
            }
        }
        /* END: modified for NATIVE FINALIZER THREAD */
        if ((*on_exit)  && is_class_ignored(clss)) {
            tmn_suspend_enable();
            continue;
        }
        
        Method *finalize = class_lookup_method_recursive(clss,
            VM_Global_State::loader_env->FinalizeName_String,
            VM_Global_State::loader_env->VoidVoidDescriptor_String);

        assert(finalize);
        TRACE2("finalize", "finalize object " << handle->object->vt()->clss);
        vm_execute_java_method_array( (jmethodID) finalize, 0, args);
        tmn_suspend_enable();

#ifndef NDEBUG
        if (exn_raised()) {
            tmn_suspend_disable();
            assert(handle->object->vt()->clss);
            INFO2("finalize", "Uncaught exception "
                << exn_get_name()
                << " while running a finalize of the object"
                << object->vt()->clss << ".");
            tmn_suspend_enable();            
        }
#endif
        exn_clear();
    }
    return i;
} //Objects_To_Finalize::do_finalization

void References_To_Enqueue::enqueue_references()
{
    TRACE2("ref", "enqueue_references() started");

    jvalue args[1], r;

    tmn_suspend_disable();
    ObjectHandle handle = oh_allocate_local_handle();
    tmn_suspend_enable();

    args[0].l = (jobject) handle;

    // saves curent thread exception risen before enqueuing references
    // if any and clears it to allow java to work
    jthrowable cte = exn_get();
    exn_clear();

    while(true) {
        tmn_suspend_disable();
        ManagedObject* object = remove_object();
        handle->object = object;
        tmn_suspend_enable();

        if (object == NULL) {
            // restores curent thread exception risen before if any
            if (NULL != cte) {
                exn_raise_object(cte);
            }
            return;
        }
        tmn_suspend_disable();
        assert(handle->object->vt()->clss);
        Class *clss = handle->object->vt()->clss;
        TRACE2("ref","Enqueueing reference " << (handle->object));
        Method *enqueue = class_lookup_method_recursive(clss,
            VM_Global_State::loader_env->EnqueueName_String,
            VM_Global_State::loader_env->VoidBooleanDescriptor_String);
        assert(enqueue);
        vm_execute_java_method_array( (jmethodID) enqueue, &r, args);
        tmn_suspend_enable();

#ifndef NDEBUG
        if (exn_raised()) {
            tmn_suspend_disable();
            assert(object->vt()->clss);
            INFO2("ref", "Uncaught exception "
                << exn_get_name()
                << " while running a enqueue method of the object"
                << object->vt()->clss << ".");
            tmn_suspend_enable();
            
        }
#endif
        exn_clear();
    }
    // restores curent thread exception risen before if any
    if (NULL != cte) {
        exn_raise_object(cte);
    }

    TRACE2("ref", "enqueue_references() completed");
} //Objects_To_Finalize::notify_reference_queues


static Objects_To_Finalize objects_to_finalize;

void vm_finalize_object(Managed_Object_Handle p_obj)
{
    objects_to_finalize.add_object((ManagedObject *)p_obj);
    if(get_native_finalizer_thread_flag()){
        int finalizable_obj_num = objects_to_finalize.getLength();
        sched_heavy_finalizer(finalizable_obj_num);
    }
} //vm_finalize_object

void vm_run_pending_finalizers()
{
    NativeObjectHandles nhs;
    assert(hythread_is_suspend_enabled());
    /* BEGIN: modified for NATIVE FINALIZER THREAD */
    if(get_native_finalizer_thread_flag()) {
        activate_finalizer_threads(FALSE);
    }
    else {
        objects_to_finalize.run_finalizers();
    }
    /* END: modified for NATIVE FINALIZER THREAD */
} //vm_run_pending_finalizers

int vm_do_finalization(int quantity)
{
    assert(hythread_is_suspend_enabled());
    return objects_to_finalize.do_finalization(quantity);
} //vm_do_finalization

bool is_it_finalize_thread() {
    return ((p_TLS_vmthread->finalize_thread_flags & FINALIZER_THREAD) != 0);
}

void vm_enumerate_objects_to_be_finalized()
{
    TRACE2("enumeration", "enumeration objects to be finalized");
    INFO2("stats.finalize", "Enumerating finalize queue");
    objects_to_finalize.enumerate_for_gc();
} //vm_enumerate_objects_to_be_finalized

int vm_get_finalizable_objects_quantity()
{
    return objects_to_finalize.getLength();
}

/* returns true if finalization system is turned on, and false otherwise */
bool vm_finalization_is_enabled()
{
    return VM_Global_State::loader_env->java_lang_FinalizerThread_Class != NULL;
}

void vm_obtain_finalizer_fields() {
    objects_to_finalize.obtain_fields();
}

// -- Code to deal with Reference Queues that need to be notified.

static References_To_Enqueue references_to_enqueue;

void vm_enumerate_references_to_enqueue()
{
    TRACE2("enumeration", "enumeration pending references to be enqueued");
    INFO2("stats.finalize", "Enumerating reference queue");
    references_to_enqueue.enumerate_for_gc();
} //vm_enumerate_references_to_enqueue

void vm_enqueue_reference(Managed_Object_Handle obj)
{
    TRACE2("ref", obj << " is being added to enqueue list");
    references_to_enqueue.add_object((ManagedObject *)obj);
} // vm_enqueue_reference

void vm_activate_ref_enqueue_thread()
{
    if(get_native_ref_enqueue_thread_flag())
        activate_ref_enqueue_thread(FALSE);
}

void vm_enqueue_references()
{
    /* BEGIN: modified for NATIVE REFERENCE ENQUEUE THREAD */
    if(get_native_ref_enqueue_thread_flag())
        activate_ref_enqueue_thread(FALSE);
    else
        references_to_enqueue.enqueue_references();
    /* END: modified for NATIVE REFERENCE ENQUEUE THREAD */
} //vm_enqueue_references

int vm_get_references_quantity()
{
    return references_to_enqueue.getLength();
}

/* added for NATIVE REFERENCE ENQUEUE THREAD */
void vm_ref_enqueue_func(void)
{
    references_to_enqueue.enqueue_references();
}
