ims: move InMemoryStorage from util/

refs #3940

Change-Id: I4468fe9b65ef6263a62800701eb6b03df28fa274
diff --git a/src/ims/in-memory-storage.cpp b/src/ims/in-memory-storage.cpp
new file mode 100644
index 0000000..0dc7df7
--- /dev/null
+++ b/src/ims/in-memory-storage.cpp
@@ -0,0 +1,449 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2017 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "in-memory-storage.hpp"
+#include "in-memory-storage-entry.hpp"
+
+namespace ndn {
+
+const time::milliseconds InMemoryStorage::INFINITE_WINDOW(-1);
+const time::milliseconds InMemoryStorage::ZERO_WINDOW(0);
+
+InMemoryStorage::const_iterator::const_iterator(const Data* ptr, const Cache* cache,
+                                                Cache::index<byFullName>::type::iterator it)
+  : m_ptr(ptr)
+  , m_cache(cache)
+  , m_it(it)
+{
+}
+
+InMemoryStorage::const_iterator&
+InMemoryStorage::const_iterator::operator++()
+{
+  m_it++;
+  if (m_it != m_cache->get<byFullName>().end()) {
+    m_ptr = &((*m_it)->getData());
+  }
+  else {
+    m_ptr = 0;
+  }
+
+  return *this;
+}
+
+InMemoryStorage::const_iterator
+InMemoryStorage::const_iterator::operator++(int)
+{
+  InMemoryStorage::const_iterator i(*this);
+  this->operator++();
+  return i;
+}
+
+const Data&
+InMemoryStorage::const_iterator::operator*()
+{
+  return *m_ptr;
+}
+
+const Data*
+InMemoryStorage::const_iterator::operator->()
+{
+  return m_ptr;
+}
+
+bool
+InMemoryStorage::const_iterator::operator==(const const_iterator& rhs)
+{
+  return m_it == rhs.m_it;
+}
+
+bool
+InMemoryStorage::const_iterator::operator!=(const const_iterator& rhs)
+{
+  return m_it != rhs.m_it;
+}
+
+InMemoryStorage::InMemoryStorage(size_t limit)
+  : m_limit(limit)
+  , m_nPackets(0)
+{
+  init();
+}
+
+InMemoryStorage::InMemoryStorage(boost::asio::io_service& ioService, size_t limit)
+  : m_limit(limit)
+  , m_nPackets(0)
+{
+  m_scheduler = make_unique<Scheduler>(ioService);
+  init();
+}
+
+void
+InMemoryStorage::init()
+{
+  // TODO consider a more suitable initial value
+  m_capacity = 10;
+
+  if (m_limit != std::numeric_limits<size_t>::max() && m_capacity > m_limit) {
+    m_capacity = m_limit;
+  }
+
+  for (size_t i = 0; i < m_capacity; i++) {
+    m_freeEntries.push(new InMemoryStorageEntry());
+  }
+}
+
+InMemoryStorage::~InMemoryStorage()
+{
+  // evict all items from cache
+  Cache::iterator it = m_cache.begin();
+  while (it != m_cache.end()) {
+    it = freeEntry(it);
+  }
+
+  BOOST_ASSERT(m_freeEntries.size() == m_capacity);
+
+  while (!m_freeEntries.empty()) {
+    delete m_freeEntries.top();
+    m_freeEntries.pop();
+  }
+}
+
+void
+InMemoryStorage::setCapacity(size_t capacity)
+{
+  size_t oldCapacity = m_capacity;
+  m_capacity = capacity;
+
+  if (size() > m_capacity) {
+    ssize_t nAllowedFailures = size() - m_capacity;
+    while (size() > m_capacity) {
+      if (!evictItem() && --nAllowedFailures < 0) {
+        BOOST_THROW_EXCEPTION(Error());
+      }
+    }
+  }
+
+  if (m_capacity >= oldCapacity) {
+    for (size_t i = oldCapacity; i < m_capacity; i++) {
+      m_freeEntries.push(new InMemoryStorageEntry());
+    }
+  }
+  else {
+    for (size_t i = oldCapacity; i > m_capacity; i--) {
+      delete m_freeEntries.top();
+      m_freeEntries.pop();
+    }
+  }
+
+  BOOST_ASSERT(size() + m_freeEntries.size() == m_capacity);
+}
+
+void
+InMemoryStorage::insert(const Data& data, const time::milliseconds& mustBeFreshProcessingWindow)
+{
+  //check if identical Data/Name already exists
+  Cache::index<byFullName>::type::iterator it = m_cache.get<byFullName>().find(data.getFullName());
+  if (it != m_cache.get<byFullName>().end())
+    return;
+
+  //if full, double the capacity
+  bool doesReachLimit = (getLimit() == getCapacity());
+  if (isFull() && !doesReachLimit) {
+    // note: This is incorrect if 2*capacity overflows, but memory should run out before that
+    size_t newCapacity = std::min(2 * getCapacity(), getLimit());
+    setCapacity(newCapacity);
+  }
+
+  //if full and reach limitation of the capacity, employ replacement policy
+  if (isFull() && doesReachLimit) {
+    evictItem();
+  }
+
+  //insert to cache
+  BOOST_ASSERT(m_freeEntries.size() > 0);
+  // take entry for the memory pool
+  InMemoryStorageEntry* entry = m_freeEntries.top();
+  m_freeEntries.pop();
+  m_nPackets++;
+  entry->setData(data);
+  if (m_scheduler != nullptr && mustBeFreshProcessingWindow > ZERO_WINDOW) {
+    auto eventId = make_unique<util::scheduler::ScopedEventId>(*m_scheduler);
+    *eventId = m_scheduler->scheduleEvent(mustBeFreshProcessingWindow,
+                                          bind(&InMemoryStorageEntry::markStale, entry));
+    entry->setMarkStaleEventId(std::move(eventId));
+  }
+  m_cache.insert(entry);
+
+  //let derived class do something with the entry
+  afterInsert(entry);
+}
+
+shared_ptr<const Data>
+InMemoryStorage::find(const Name& name)
+{
+  Cache::index<byFullName>::type::iterator it = m_cache.get<byFullName>().lower_bound(name);
+
+  //if not found, return null
+  if (it == m_cache.get<byFullName>().end()) {
+    return shared_ptr<const Data>();
+  }
+
+  //if the given name is not the prefix of the lower_bound, return null
+  if (!name.isPrefixOf((*it)->getFullName())) {
+    return shared_ptr<const Data>();
+  }
+
+  afterAccess(*it);
+  return ((*it)->getData()).shared_from_this();
+}
+
+shared_ptr<const Data>
+InMemoryStorage::find(const Interest& interest)
+{
+  //if the interest contains implicit digest, it is possible to directly locate a packet.
+  Cache::index<byFullName>::type::iterator it = m_cache.get<byFullName>()
+                                                    .find(interest.getName());
+
+  //if a packet is located by its full name, it must be the packet to return.
+  if (it != m_cache.get<byFullName>().end()) {
+    return ((*it)->getData()).shared_from_this();
+  }
+
+  //if the packet is not discovered by last step, either the packet is not in the storage or
+  //the interest doesn't contains implicit digest.
+  it = m_cache.get<byFullName>().lower_bound(interest.getName());
+
+  if (it == m_cache.get<byFullName>().end()) {
+    return shared_ptr<const Data>();
+  }
+
+  //to locate the element that has a just smaller name than the interest's
+  if (it != m_cache.get<byFullName>().begin())
+    it--;
+
+  InMemoryStorageEntry* ret = selectChild(interest, it);
+  if (ret != 0) {
+    //let derived class do something with the entry
+    afterAccess(ret);
+    return ret->getData().shared_from_this();
+  }
+  else {
+    return shared_ptr<const Data>();
+  }
+}
+
+InMemoryStorage::Cache::index<InMemoryStorage::byFullName>::type::iterator
+InMemoryStorage::findNextFresh(Cache::index<byFullName>::type::iterator it) const
+{
+  for (; it != m_cache.get<byFullName>().end(); it++) {
+    if ((*it)->isFresh())
+      return it;
+  }
+
+  return it;
+}
+
+InMemoryStorageEntry*
+InMemoryStorage::selectChild(const Interest& interest,
+                             Cache::index<byFullName>::type::iterator startingPoint) const
+{
+  BOOST_ASSERT(startingPoint != m_cache.get<byFullName>().end());
+
+  if (startingPoint != m_cache.get<byFullName>().begin())
+    {
+      BOOST_ASSERT((*startingPoint)->getFullName() < interest.getName());
+    }
+
+  bool hasLeftmostSelector = (interest.getChildSelector() <= 0);
+  bool hasRightmostSelector = !hasLeftmostSelector;
+
+  // filter out "stale" data
+  if (interest.getMustBeFresh())
+    startingPoint = findNextFresh(startingPoint);
+
+  if (startingPoint == m_cache.get<byFullName>().end()) {
+    return nullptr;
+  }
+
+  if (hasLeftmostSelector)
+    {
+      if (interest.matchesData((*startingPoint)->getData()))
+        {
+          return *startingPoint;
+        }
+    }
+
+  //iterate to the right
+  Cache::index<byFullName>::type::iterator rightmost = startingPoint;
+  if (startingPoint != m_cache.get<byFullName>().end())
+    {
+      Cache::index<byFullName>::type::iterator rightmostCandidate = startingPoint;
+      Name currentChildPrefix("");
+
+      while (true)
+        {
+          ++rightmostCandidate;
+          // filter out "stale" data
+          if (interest.getMustBeFresh())
+            rightmostCandidate = findNextFresh(rightmostCandidate);
+
+          bool isInBoundaries = (rightmostCandidate != m_cache.get<byFullName>().end());
+          bool isInPrefix = false;
+          if (isInBoundaries)
+            {
+              isInPrefix = interest.getName().isPrefixOf((*rightmostCandidate)->getFullName());
+            }
+
+          if (isInPrefix)
+            {
+              if (interest.matchesData((*rightmostCandidate)->getData()))
+                {
+                  if (hasLeftmostSelector)
+                    {
+                      return *rightmostCandidate;
+                    }
+
+                  if (hasRightmostSelector)
+                    {
+                      // get prefix which is one component longer than Interest name
+                      const Name& childPrefix = (*rightmostCandidate)->getFullName()
+                                                  .getPrefix(interest.getName().size() + 1);
+
+                      if (currentChildPrefix.empty() || (childPrefix != currentChildPrefix))
+                        {
+                          currentChildPrefix = childPrefix;
+                          rightmost = rightmostCandidate;
+                        }
+                    }
+                }
+            }
+          else
+            break;
+        }
+    }
+
+  if (rightmost != startingPoint)
+    {
+      return *rightmost;
+    }
+
+  if (hasRightmostSelector) // if rightmost was not found, try starting point
+    {
+      if (interest.matchesData((*startingPoint)->getData()))
+        {
+          return *startingPoint;
+        }
+    }
+
+  return 0;
+}
+
+InMemoryStorage::Cache::iterator
+InMemoryStorage::freeEntry(Cache::iterator it)
+{
+  //push the *empty* entry into mem pool
+  (*it)->release();
+  m_freeEntries.push(*it);
+  m_nPackets--;
+  return m_cache.erase(it);
+}
+
+void
+InMemoryStorage::erase(const Name& prefix, const bool isPrefix)
+{
+  if (isPrefix) {
+    Cache::index<byFullName>::type::iterator it = m_cache.get<byFullName>().lower_bound(prefix);
+
+    while (it != m_cache.get<byFullName>().end() && prefix.isPrefixOf((*it)->getName())) {
+      //let derived class do something with the entry
+      beforeErase(*it);
+      it = freeEntry(it);
+    }
+  }
+  else {
+    Cache::index<byFullName>::type::iterator it = m_cache.get<byFullName>().find(prefix);
+
+    if (it == m_cache.get<byFullName>().end())
+      return;
+
+    //let derived class do something with the entry
+    beforeErase(*it);
+    freeEntry(it);
+  }
+
+  if (m_freeEntries.size() > (2 * size()))
+    setCapacity(getCapacity() / 2);
+}
+
+void
+InMemoryStorage::eraseImpl(const Name& name)
+{
+  Cache::index<byFullName>::type::iterator it = m_cache.get<byFullName>().find(name);
+
+  if (it == m_cache.get<byFullName>().end())
+    return;
+
+  freeEntry(it);
+}
+
+InMemoryStorage::const_iterator
+InMemoryStorage::begin() const
+{
+  Cache::index<byFullName>::type::iterator it = m_cache.get<byFullName>().begin();
+
+  return const_iterator(&((*it)->getData()), &m_cache, it);
+}
+
+InMemoryStorage::const_iterator
+InMemoryStorage::end() const
+{
+  Cache::index<byFullName>::type::iterator it = m_cache.get<byFullName>().end();
+
+  return const_iterator(nullptr, &m_cache, it);
+}
+
+void
+InMemoryStorage::afterInsert(InMemoryStorageEntry* entry)
+{
+}
+
+void
+InMemoryStorage::beforeErase(InMemoryStorageEntry* entry)
+{
+}
+
+void
+InMemoryStorage::afterAccess(InMemoryStorageEntry* entry)
+{
+}
+
+void
+InMemoryStorage::printCache(std::ostream& os) const
+{
+  //start from the upper layer towards bottom
+  const Cache::index<byFullName>::type& cacheIndex = m_cache.get<byFullName>();
+  for (Cache::index<byFullName>::type::iterator it = cacheIndex.begin();
+       it != cacheIndex.end(); it++)
+    os << (*it)->getFullName() << std::endl;
+}
+
+} // namespace ndn