/*
 * Copyright (C) 2016 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 "class_table-inl.h"

#include "art_field-inl.h"
#include "art_method-inl.h"
#include "class_linker-inl.h"
#include "common_runtime_test.h"
#include "dex_file.h"
#include "gc/accounting/card_table-inl.h"
#include "gc/heap.h"
#include "handle_scope-inl.h"
#include "mirror/class-inl.h"
#include "obj_ptr.h"
#include "scoped_thread_state_change-inl.h"

namespace art {
namespace mirror {

class CollectRootVisitor {
 public:
  CollectRootVisitor() {}

  template <class MirrorType>
  ALWAYS_INLINE void VisitRootIfNonNull(GcRoot<MirrorType>& root) const
      REQUIRES_SHARED(Locks::mutator_lock_) {
    if (!root.IsNull()) {
      VisitRoot(root);
    }
  }

  template <class MirrorType>
  ALWAYS_INLINE void VisitRootIfNonNull(mirror::CompressedReference<MirrorType>* root) const
      REQUIRES_SHARED(Locks::mutator_lock_) {
    if (!root->IsNull()) {
      VisitRoot(root);
    }
  }

  template <class MirrorType>
  void VisitRoot(GcRoot<MirrorType>& root) const REQUIRES_SHARED(Locks::mutator_lock_) {
    VisitRoot(root.AddressWithoutBarrier());
  }

  template <class MirrorType>
  void VisitRoot(mirror::CompressedReference<MirrorType>* root) const
      REQUIRES_SHARED(Locks::mutator_lock_) {
    roots_.insert(root->AsMirrorPtr());
  }

  mutable std::set<mirror::Object*> roots_;
};


class ClassTableTest : public CommonRuntimeTest {};

TEST_F(ClassTableTest, ClassTable) {
  ScopedObjectAccess soa(Thread::Current());
  jobject jclass_loader = LoadDex("XandY");
  VariableSizedHandleScope hs(soa.Self());
  Handle<ClassLoader> class_loader(hs.NewHandle(soa.Decode<ClassLoader>(jclass_loader)));
  const char* descriptor_x = "LX;";
  const char* descriptor_y = "LY;";
  Handle<mirror::Class> h_X(
      hs.NewHandle(class_linker_->FindClass(soa.Self(), descriptor_x, class_loader)));
  Handle<mirror::Class> h_Y(
      hs.NewHandle(class_linker_->FindClass(soa.Self(), descriptor_y, class_loader)));
  Handle<mirror::Object> obj_X = hs.NewHandle(h_X->AllocObject(soa.Self()));
  ASSERT_TRUE(obj_X != nullptr);
  ClassTable table;
  EXPECT_EQ(table.NumZygoteClasses(class_loader.Get()), 0u);
  EXPECT_EQ(table.NumNonZygoteClasses(class_loader.Get()), 0u);

  // Add h_X to the class table.
  table.Insert(h_X.Get());
  EXPECT_EQ(table.LookupByDescriptor(h_X.Get()), h_X.Get());
  EXPECT_EQ(table.Lookup(descriptor_x, ComputeModifiedUtf8Hash(descriptor_x)), h_X.Get());
  EXPECT_EQ(table.Lookup("NOT_THERE", ComputeModifiedUtf8Hash("NOT_THERE")), nullptr);
  EXPECT_EQ(table.NumZygoteClasses(class_loader.Get()), 0u);
  EXPECT_EQ(table.NumNonZygoteClasses(class_loader.Get()), 1u);

  // Create the zygote snapshot and ensure the accounting is correct.
  table.FreezeSnapshot();
  EXPECT_EQ(table.NumZygoteClasses(class_loader.Get()), 1u);
  EXPECT_EQ(table.NumNonZygoteClasses(class_loader.Get()), 0u);

  // Test inserting and related lookup functions.
  EXPECT_EQ(table.LookupByDescriptor(h_Y.Get()), nullptr);
  EXPECT_FALSE(table.Contains(h_Y.Get()));
  table.Insert(h_Y.Get());
  EXPECT_EQ(table.LookupByDescriptor(h_X.Get()), h_X.Get());
  EXPECT_EQ(table.LookupByDescriptor(h_Y.Get()), h_Y.Get());
  EXPECT_TRUE(table.Contains(h_X.Get()));
  EXPECT_TRUE(table.Contains(h_Y.Get()));

  EXPECT_EQ(table.NumZygoteClasses(class_loader.Get()), 1u);
  EXPECT_EQ(table.NumNonZygoteClasses(class_loader.Get()), 1u);

  // Test adding / clearing strong roots.
  EXPECT_TRUE(table.InsertStrongRoot(obj_X.Get()));
  EXPECT_FALSE(table.InsertStrongRoot(obj_X.Get()));
  table.ClearStrongRoots();
  EXPECT_TRUE(table.InsertStrongRoot(obj_X.Get()));

  // Collect all the roots and make sure there is nothing missing.
  CollectRootVisitor roots;
  table.VisitRoots(roots);
  EXPECT_TRUE(roots.roots_.find(h_X.Get()) != roots.roots_.end());
  EXPECT_TRUE(roots.roots_.find(h_Y.Get()) != roots.roots_.end());
  EXPECT_TRUE(roots.roots_.find(obj_X.Get()) != roots.roots_.end());

  // Checks that vising only classes works.
  std::set<mirror::Class*> classes;
  table.Visit([&classes](ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) {
    classes.insert(klass.Ptr());
    return true;
  });
  EXPECT_TRUE(classes.find(h_X.Get()) != classes.end());
  EXPECT_TRUE(classes.find(h_Y.Get()) != classes.end());
  EXPECT_EQ(classes.size(), 2u);
  classes.clear();
  table.Visit([&classes](ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) {
    classes.insert(klass.Ptr());
    // Return false to exit the Visit early.
    return false;
  });
  EXPECT_EQ(classes.size(), 1u);

  // Test remove.
  table.Remove(descriptor_x);
  EXPECT_FALSE(table.Contains(h_X.Get()));

  // Test that WriteToMemory and ReadFromMemory work.
  table.Insert(h_X.Get());
  const size_t count = table.WriteToMemory(nullptr);
  std::unique_ptr<uint8_t[]> buffer(new uint8_t[count]());
  ASSERT_EQ(table.WriteToMemory(&buffer[0]), count);
  ClassTable table2;
  size_t count2 = table2.ReadFromMemory(&buffer[0]);
  EXPECT_EQ(count, count2);
  // Strong roots are not serialized, only classes.
  EXPECT_TRUE(table2.Contains(h_X.Get()));
  EXPECT_TRUE(table2.Contains(h_Y.Get()));

  // TODO: Add tests for UpdateClass, InsertOatFile.
}

}  // namespace mirror
}  // namespace art
