/*
 * Copyright (C) 2013 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.
 */

#include "object_registry.h"

#include "../scoped_thread_state_change.h"

namespace art {

mirror::Object* const ObjectRegistry::kInvalidObject = reinterpret_cast<mirror::Object*>(1);

std::ostream& operator<<(std::ostream& os, const ObjectRegistryEntry& rhs) {
  os << "ObjectRegistryEntry[" << rhs.jni_reference_type
     << ",reference=" << rhs.jni_reference
     << ",count=" << rhs.reference_count
     << ",id=" << rhs.id << "]";
  return os;
}

ObjectRegistry::ObjectRegistry()
    : lock_("ObjectRegistry lock", kJdwpObjectRegistryLock), next_id_(1) {
}

JDWP::RefTypeId ObjectRegistry::AddRefType(mirror::Class* c) {
  return InternalAdd(c);
}

JDWP::ObjectId ObjectRegistry::Add(mirror::Object* o) {
  return InternalAdd(o);
}

JDWP::ObjectId ObjectRegistry::InternalAdd(mirror::Object* o) {
  if (o == NULL) {
    return 0;
  }

  ScopedObjectAccessUnchecked soa(Thread::Current());
  MutexLock mu(soa.Self(), lock_);
  ObjectRegistryEntry dummy;
  dummy.jni_reference_type = JNIWeakGlobalRefType;
  dummy.jni_reference = NULL;
  dummy.reference_count = 0;
  dummy.id = 0;
  std::pair<object_iterator, bool> result = object_to_entry_.insert(std::make_pair(o, dummy));
  ObjectRegistryEntry& entry = result.first->second;
  if (!result.second) {
    // This object was already in our map.
    entry.reference_count += 1;
    return entry.id;
  }

  // This object isn't in the registry yet, so add it.
  JNIEnv* env = soa.Env();

  jobject local_reference = soa.AddLocalReference<jobject>(o);

  entry.jni_reference_type = JNIWeakGlobalRefType;
  entry.jni_reference = env->NewWeakGlobalRef(local_reference);
  entry.reference_count = 1;
  entry.id = next_id_++;

  id_to_entry_.Put(entry.id, &entry);

  env->DeleteLocalRef(local_reference);

  return entry.id;
}

bool ObjectRegistry::Contains(mirror::Object* o) {
  Thread* self = Thread::Current();
  MutexLock mu(self, lock_);
  return (object_to_entry_.find(o) != object_to_entry_.end());
}

void ObjectRegistry::Clear() {
  Thread* self = Thread::Current();
  MutexLock mu(self, lock_);
  VLOG(jdwp) << "Object registry contained " << object_to_entry_.size() << " entries";

  // Delete all the JNI references.
  JNIEnv* env = self->GetJniEnv();
  for (object_iterator it = object_to_entry_.begin(); it != object_to_entry_.end(); ++it) {
    ObjectRegistryEntry& entry = (it->second);
    if (entry.jni_reference_type == JNIWeakGlobalRefType) {
      env->DeleteWeakGlobalRef(entry.jni_reference);
    } else {
      env->DeleteGlobalRef(entry.jni_reference);
    }
  }

  // Clear the maps.
  object_to_entry_.clear();
  id_to_entry_.clear();
}

mirror::Object* ObjectRegistry::InternalGet(JDWP::ObjectId id) {
  Thread* self = Thread::Current();
  MutexLock mu(self, lock_);
  id_iterator it = id_to_entry_.find(id);
  if (it == id_to_entry_.end()) {
    return kInvalidObject;
  }
  ObjectRegistryEntry& entry = *(it->second);
  return self->DecodeJObject(entry.jni_reference);
}

jobject ObjectRegistry::GetJObject(JDWP::ObjectId id) {
  Thread* self = Thread::Current();
  MutexLock mu(self, lock_);
  id_iterator it = id_to_entry_.find(id);
  CHECK(it != id_to_entry_.end()) << id;
  ObjectRegistryEntry& entry = *(it->second);
  return entry.jni_reference;
}

void ObjectRegistry::DisableCollection(JDWP::ObjectId id) {
  Thread* self = Thread::Current();
  MutexLock mu(self, lock_);
  id_iterator it = id_to_entry_.find(id);
  if (it == id_to_entry_.end()) {
    return;
  }
  Promote(*(it->second));
}

void ObjectRegistry::EnableCollection(JDWP::ObjectId id) {
  Thread* self = Thread::Current();
  MutexLock mu(self, lock_);
  id_iterator it = id_to_entry_.find(id);
  if (it == id_to_entry_.end()) {
    return;
  }
  Demote(*(it->second));
}

void ObjectRegistry::Demote(ObjectRegistryEntry& entry) {
  if (entry.jni_reference_type == JNIGlobalRefType) {
    Thread* self = Thread::Current();
    JNIEnv* env = self->GetJniEnv();
    jobject global = entry.jni_reference;
    entry.jni_reference = env->NewWeakGlobalRef(entry.jni_reference);
    entry.jni_reference_type = JNIWeakGlobalRefType;
    env->DeleteGlobalRef(global);
  }
}

void ObjectRegistry::Promote(ObjectRegistryEntry& entry) {
  if (entry.jni_reference_type == JNIWeakGlobalRefType) {
    Thread* self = Thread::Current();
    JNIEnv* env = self->GetJniEnv();
    jobject weak = entry.jni_reference;
    entry.jni_reference = env->NewGlobalRef(entry.jni_reference);
    entry.jni_reference_type = JNIGlobalRefType;
    env->DeleteWeakGlobalRef(weak);
  }
}

bool ObjectRegistry::IsCollected(JDWP::ObjectId id) {
  Thread* self = Thread::Current();
  MutexLock mu(self, lock_);
  id_iterator it = id_to_entry_.find(id);
  if (it == id_to_entry_.end()) {
    return true;  // TODO: can we report that this was an invalid id?
  }

  ObjectRegistryEntry& entry = *(it->second);
  if (entry.jni_reference_type == JNIWeakGlobalRefType) {
    JNIEnv* env = self->GetJniEnv();
    return env->IsSameObject(entry.jni_reference, NULL);  // Has the jweak been collected?
  } else {
    return false;  // We hold a strong reference, so we know this is live.
  }
}

void ObjectRegistry::DisposeObject(JDWP::ObjectId id, uint32_t reference_count) {
  Thread* self = Thread::Current();
  MutexLock mu(self, lock_);
  id_iterator it = id_to_entry_.find(id);
  if (it == id_to_entry_.end()) {
    return;
  }

  ObjectRegistryEntry& entry = *(it->second);
  entry.reference_count -= reference_count;
  if (entry.reference_count <= 0) {
    JNIEnv* env = self->GetJniEnv();
    mirror::Object* object = self->DecodeJObject(entry.jni_reference);
    if (entry.jni_reference_type == JNIWeakGlobalRefType) {
      env->DeleteWeakGlobalRef(entry.jni_reference);
    } else {
      env->DeleteGlobalRef(entry.jni_reference);
    }
    object_to_entry_.erase(object);
    id_to_entry_.erase(id);
  }
}

}  // namespace art
