text:
using namespace std;
using namespace Eigen;

template <typename CoefficientType>
bool Polynomial<CoefficientType>::Monomial::hasSameExponents(
    const Monomial& other) {
  if (terms.size() != other.terms.size()) return false;

  for (typename vector<Term>::const_iterator iter = terms.begin();
       iter != terms.end(); iter++) {
    typename vector<Term>::const_iterator match =
        find(other.terms.begin(), other.terms.end(), *iter);
    if (match == other.terms.end()) return false;
  }
  return true;
}

template <typename CoefficientType>
Polynomial<CoefficientType>::Polynomial(const CoefficientType& scalar) {
  Monomial m;
  m.coefficient = scalar;
  monomials.push_back(m);
  is_univariate = true;
}

template <typename CoefficientType>
Polynomial<CoefficientType>::Polynomial(const CoefficientType coefficient,
                                        const vector<Term>& terms) {
  Monomial m;
  m.coefficient = coefficient;
  m.terms = terms;

  is_univariate = true;
  for (int i = (int)m.terms.size() - 1; i >= 0; i--) {
    if ((i > 0) && (m.terms[i].var != m.terms[0].var)) {
      is_univariate = false;
    }
    for (int j = 0; j < (i - 1); j++) {  // merge any duplicate vars
      if (m.terms[i].var == m.terms[j].var) {
        m.terms[j].power += m.terms[i].power;
        m.terms.erase(m.terms.begin() + i);
        break;
      }
    }
  }

  monomials.push_back(m);
}

template <typename CoefficientType>
Polynomial<CoefficientType>::Polynomial(
    typename vector<
        typename Polynomial<CoefficientType>::Monomial>::const_iterator start,
    typename vector<typename Polynomial<CoefficientType>::Monomial>::
        const_iterator finish) {
  is_univariate = true;
  for (
      typename vector<
          typename Polynomial<CoefficientType>::Monomial>::const_iterator iter =
          start;
      iter != finish; iter++)
    monomials.push_back(*iter);
  makeMonomialsUnique();
}

template <typename CoefficientType>
Polynomial<CoefficientType>::Polynomial(const string varname,
                                        const unsigned int num) {
  Monomial m;
  m.coefficient = (CoefficientType)1;
  Term t;
  t.var = variableNameToId(varname, num);
  t.power = 1;
  m.terms.push_back(t);
  monomials.push_back(m);
  is_univariate = true;
}

template <typename CoefficientType>
Polynomial<CoefficientType>::Polynomial(const CoefficientType& coeff,
                                        const VarType& v) {
  Monomial m;
  m.coefficient = coeff;
  Term t;
  t.var = v;
  t.power = 1;
  m.terms.push_back(t);
  monomials.push_back(m);
  is_univariate = true;
}

template <typename CoefficientType>
int Polynomial<CoefficientType>::getNumberOfCoefficients() const {
  return static_cast<int>(monomials.size());
}

template <typename CoefficientType>
int Polynomial<CoefficientType>::Monomial::getDegree() const {
  if (terms.empty()) return 0;
  int degree = terms[0].power;
  for (int i = 1; i < terms.size(); i++) degree *= terms[i].power;
  return degree;
}

template <typename CoefficientType>
int Polynomial<CoefficientType>::getDegree() const {
  int max_degree = 0;
  for (typename vector<Monomial>::const_iterator iter = monomials.begin();
       iter != monomials.end(); iter++) {
    int monomial_degree = iter->getDegree();
    if (monomial_degree > max_degree) max_degree = monomial_degree;
  }

  return max_degree;
}

template <typename CoefficientType>
typename Polynomial<CoefficientType>::VarType
Polynomial<CoefficientType>::getSimpleVariable() const {
  if (monomials.size() != 1) return 0;
  if (monomials[0].terms.size() != 1) return 0;
  if (monomials[0].terms[0].power != 1) return 0;
  return monomials[0].terms[0].var;
}

template <typename CoefficientType>
const std::vector<typename Polynomial<CoefficientType>::Monomial>&
Polynomial<CoefficientType>::getMonomials() const {
  return monomials;
}

template <typename CoefficientType>
Matrix<CoefficientType, Dynamic, 1>
Polynomial<CoefficientType>::getCoefficients() const {
  if (!is_univariate)
    throw runtime_error(
        "getCoefficients is only defined for univariate polynomials");

  int deg = getDegree();

  Matrix<CoefficientType, Dynamic, 1> coefficients =
      Matrix<CoefficientType, Dynamic, 1>::Zero(deg + 1);
  for (typename vector<Monomial>::const_iterator iter = monomials.begin();
       iter != monomials.end(); iter++) {
    if (iter->terms.empty())
      coefficients[0] = iter->coefficient;
    else
      coefficients[iter->terms[0].power] = iter->coefficient;
  }
  return coefficients;
}

template <typename CoefficientType>
void Polynomial<CoefficientType>::subs(const VarType& orig,
                                       const VarType& replacement) {
  for (typename vector<Monomial>::iterator iter = monomials.begin();
       iter != monomials.end(); iter++) {
    for (typename vector<Term>::iterator t = iter->terms.begin();
         t != iter->terms.end(); t++) {
      if (t->var == orig) t->var = replacement;
    }
  }
}

template <typename CoefficientType>
Polynomial<CoefficientType> Polynomial<CoefficientType>::derivative(
    unsigned int derivative_order) const {
  if (!is_univariate)
    throw runtime_error(
        "getCoefficients is only defined for univariate polynomials");

  Polynomial<CoefficientType> ret;

  for (typename vector<Monomial>::const_iterator iter = monomials.begin();
       iter != monomials.end(); iter++) {
    if (!iter->terms.empty() && iter->terms[0].power >= derivative_order) {
      Monomial m = *iter;
      for (unsigned int k = 0; k < derivative_order;
           k++) {  // take the remaining derivatives
        m.coefficient = m.coefficient * m.terms[0].power;
        m.terms[0].power -= 1;
      }
      if (m.terms[0].power < 1) m.terms.erase(m.terms.begin());
      ret.monomials.push_back(m);
    }
  }
  ret.is_univariate = true;
  return ret;
}

template <typename CoefficientType>
Polynomial<CoefficientType> Polynomial<CoefficientType>::integral(
    const CoefficientType& integration_constant) const {
  if (!is_univariate)
    throw runtime_error(
        "getCoefficients is only defined for univariate polynomials");
  Polynomial<CoefficientType> ret = *this;

  for (typename vector<Monomial>::iterator iter = ret.monomials.begin();
       iter != ret.monomials.end(); iter++) {
    if (iter->terms.empty()) {
      Term t;
      t.var = 0;
      for (typename vector<Monomial>::iterator iterB = ret.monomials.begin();
           iterB != ret.monomials.end(); iterB++) {
        if (!iterB->terms.empty()) {
          t.var = iterB->terms[0].var;
          break;
        }
      }
      if (t.var < 1) throw runtime_error("don't know the variable name");
      t.power = 1;
      iter->terms.push_back(t);
    } else {
      iter->coefficient /= (RealScalar)(iter->terms[0].power + 1);
      iter->terms[0].power += (PowerType)1;
    }
  }
  Monomial m;
  m.coefficient = integration_constant;
  ret.is_univariate = true;
  ret.monomials.push_back(m);
  return ret;
}

template <typename CoefficientType>
Polynomial<CoefficientType>& Polynomial<CoefficientType>::operator+=(
    const Polynomial<CoefficientType>& other) {
  for (typename vector<Monomial>::const_iterator iter = other.monomials.begin();
       iter != other.monomials.end(); iter++) {
    monomials.push_back(*iter);
  }
  makeMonomialsUnique();  // also sets is_univariate false if necessary
  return *this;
}

template <typename CoefficientType>
Polynomial<CoefficientType>& Polynomial<CoefficientType>::operator-=(
    const Polynomial<CoefficientType>& other) {
  for (typename vector<Monomial>::const_iterator iter = other.monomials.begin();
       iter != other.monomials.end(); iter++) {
    monomials.push_back(*iter);
    monomials.back().coefficient *= (CoefficientType)(-1);
  }
  makeMonomialsUnique();  // also sets is_univariate false if necessary
  return *this;
}

template <typename CoefficientType>
Polynomial<CoefficientType>& Polynomial<CoefficientType>::operator*=(
    const Polynomial<CoefficientType>& other) {
  vector<Monomial> new_monomials;

  for (typename vector<Monomial>::const_iterator iter = monomials.begin();
       iter != monomials.end(); iter++) {
    for (typename vector<Monomial>::const_iterator other_iter =
             other.monomials.begin();
         other_iter != other.monomials.end(); other_iter++) {
      Monomial m;
      m.coefficient = iter->coefficient * other_iter->coefficient;
      m.terms = iter->terms;
      for (int i = 0; i < other_iter->terms.size(); i++) {
        bool new_var = true;
        for (int j = 0; j < m.terms.size(); j++) {
          if (m.terms[j].var == other_iter->terms[i].var) {
            m.terms[j].power += other_iter->terms[i].power;
            new_var = false;
            break;
          }
        }
        if (new_var) {
          m.terms.push_back(other_iter->terms[i]);
        }
      }
      new_monomials.push_back(m);
    }
  }
  monomials = new_monomials;

  makeMonomialsUnique();  // also sets is_univariate false if necessary
  return *this;
}

template <typename CoefficientType>
Polynomial<CoefficientType>& Polynomial<CoefficientType>::operator+=(
    const CoefficientType& scalar) {
  // add to the constant monomial if I have one
  for (typename vector<Monomial>::iterator iter = monomials.begin();
       iter != monomials.end(); iter++) {
    if (iter->terms.empty()) {
      iter->coefficient += scalar;
      return *this;
    }
  }

  // otherwise create the constant monomial
  Monomial m;
  m.coefficient = scalar;
  monomials.push_back(m);
  return *this;
}

template <typename CoefficientType>
Polynomial<CoefficientType>& Polynomial<CoefficientType>::operator-=(
    const CoefficientType& scalar) {
  // add to the constant monomial if I have one
  for (typename vector<Monomial>::iterator iter = monomials.begin();
       iter != monomials.end(); iter++) {
    if (iter->terms.empty()) {
      iter->coefficient -= scalar;
      return *this;
    }
  }

  // otherwise create the constant monomial
  Monomial m;
  m.coefficient = -scalar;
  monomials.push_back(m);
  return *this;
}

template <typename CoefficientType>
Polynomial<CoefficientType>& Polynomial<CoefficientType>::operator*=(
    const CoefficientType& scalar) {
  for (typename vector<Monomial>::iterator iter = monomials.begin();
       iter != monomials.end(); iter++) {
    iter->coefficient *= scalar;
  }
  return *this;
}

template <typename CoefficientType>
Polynomial<CoefficientType>& Polynomial<CoefficientType>::operator/=(
    const CoefficientType& scalar) {
  for (typename vector<Monomial>::iterator iter = monomials.begin();
       iter != monomials.end(); iter++) {
    iter->coefficient /= scalar;
  }
  return *this;
}

template <typename CoefficientType>
const Polynomial<CoefficientType> Polynomial<CoefficientType>::operator+(
    const Polynomial& other) const {
  Polynomial<CoefficientType> ret = *this;
  ret += other;
  return ret;
}

template <typename CoefficientType>
const Polynomial<CoefficientType> Polynomial<CoefficientType>::operator-(
    const Polynomial& other) const {
  Polynomial<CoefficientType> ret = *this;
  ret -= other;
  return ret;
}

template <typename CoefficientType>
const Polynomial<CoefficientType> Polynomial<CoefficientType>::operator-()
    const {
  Polynomial<CoefficientType> ret = *this;
  for (typename vector<Monomial>::iterator iter = ret.monomials.begin();
       iter != ret.monomials.end(); iter++) {
    iter->coefficient = -iter->coefficient;
  }
  return ret;
}

template <typename CoefficientType>
const Polynomial<CoefficientType> Polynomial<CoefficientType>::operator*(
    const Polynomial<CoefficientType>& other) const {
  Polynomial<CoefficientType> ret = *this;
  ret *= other;
  return ret;
}

template <typename CoefficientType>
const Polynomial<CoefficientType> Polynomial<CoefficientType>::operator/(
    const CoefficientType& scalar) const {
  Polynomial<CoefficientType> ret = *this;
  ret /= scalar;
  return ret;
}

template <typename CoefficientType>
typename Polynomial<CoefficientType>::RootsType
Polynomial<CoefficientType>::roots() const {
  if (!is_univariate)
    throw runtime_error(
        "getCoefficients is only defined for univariate polynomials");

  auto coefficients = getCoefficients();

  // need to handle degree 0 and 1 explicitly because Eigen's polynomial solver
  // doesn't work for these
  int degree = static_cast<int>(coefficients.size()) - 1;
  switch (degree) {
    case 0:
      return Polynomial<CoefficientType>::RootsType(degree);
    case 1: {
      Polynomial<CoefficientType>::RootsType ret(degree);
      ret[0] = -coefficients[0] / coefficients[1];
      return ret;
    }
    default: {
      PolynomialSolver<RealScalar, Eigen::Dynamic> solver;
      solver.compute(coefficients);
      return solver.roots();
      break;
    }
  }
}

template <typename CoefficientType>
bool Polynomial<CoefficientType>::isApprox(const Polynomial& other,
                                           const RealScalar& tol) const {
  return getCoefficients().isApprox(other.getCoefficients(), tol);
}

const string name_chars = "@#_.abcdefghijklmnopqrstuvwxyz";
const unsigned int num_name_chars = 30;  // length of the string above
const unsigned int name_length = 4;
const unsigned int max_name_part = 923521;  // (num_name_chars+1)^name_length;

template <typename CoefficientType>
bool Polynomial<CoefficientType>::isValidVariableName(const string name) {
  size_t len = name.length();
  if (len < 1) return false;
  for (int i = 0; i < len; i++)
    if (name_chars.find(name[i]) == string::npos) return false;
  return true;
}

template <typename CoefficientType>
typename Polynomial<CoefficientType>::VarType
Polynomial<CoefficientType>::variableNameToId(const string name,
                                              const unsigned int m) {
  unsigned int exponent = 1;
  VarType name_part = 0;
  for (int i = (int)name.size() - 1; i >= 0; i--) {
    exponent *= num_name_chars + 1;
    name_part += ((VarType)name_chars.find(name[i]) + 1) * exponent;
  }
  const VarType maxId = std::numeric_limits<VarType>::max() / 2 / max_name_part;
  if (m > maxId) throw runtime_error("name exceeds max ID");
  if (m < 1) throw runtime_error("m must be >0");
  return (VarType)2 * (name_part + max_name_part * (m - 1));
}

template <typename CoefficientType>
string Polynomial<CoefficientType>::idToVariableName(const VarType id) {
  VarType name_part = (id / 2) % max_name_part;  // id/2 to be compatible w/
                                                 // msspoly, even though I'm not
                                                 // doing the trig support here

  unsigned int m = id / 2 / max_name_part;
  unsigned int exponent = (unsigned int)std::pow((double)(num_name_chars + 1),
                                                 (int)(name_length - 1));
  char name[name_length + 1];
  int j = 0;
  for (int i = 0; i < name_length; i++) {
    unsigned int name_ind = (name_part / exponent) % (num_name_chars + 1);
    if (name_ind > 0) name[j++] = name_chars[name_ind - 1];
    exponent /= num_name_chars + 1;
  }
  if (j == 0) name[j++] = name_chars[0];
  name[j] = '\0';
  return string(name) +
         std::to_string(static_cast<unsigned long long>(m + 1));  // for msvc
                                                                  // http://stackoverflow.com/questions/10664699/stdto-string-more-than-instance-of-overloaded-function-matches-the-argument
}

template <typename CoefficientType>
void Polynomial<CoefficientType>::makeMonomialsUnique(void) {
  VarType unique_var = 0;  // also update the univariate flag
  for (ptrdiff_t i = monomials.size() - 1; i >= 0; i--) {
    Monomial& mi = monomials[i];
    if (!mi.terms.empty()) {
      if (mi.terms.size() > 1) is_univariate = false;
      if (mi.terms[0].var != unique_var) {
        if (unique_var > 0) {
          is_univariate = false;
        } else {
          unique_var = mi.terms[0].var;
        }
      }
    }
    for (ptrdiff_t j = 0; j < (i - 1); j++) {
      Monomial& mj = monomials[j];
      if (mi.hasSameExponents(mj)) {
        // it's a match, so delete monomial i
        monomials[j].coefficient += monomials[i].coefficient;
        monomials.erase(monomials.begin() + i);
        break;
      }
    }
  }
}

template class DRAKEPOLYNOMIAL_EXPORT Polynomial<double>;

// template class DRAKEPOLYNOMIAL_EXPORT Polynomial<std::complex<double>>; //
// doesn't work yet because the roots solver can't handle it


text:
#include "io_realm_internal_OsSharedRealm.h"
#if REALM_ENABLE_SYNC
#include "object-store/src/sync/sync_manager.hpp"
#include "object-store/src/sync/sync_config.hpp"
#include "object-store/src/sync/sync_session.hpp"
#include "object-store/src/results.hpp"
#include "object-store/src/sync/partial_sync.hpp"

#include "observable_collection_wrapper.hpp"
#endif

#include <realm/util/assert.hpp>

#include <shared_realm.hpp>

#include "java_accessor.hpp"
#include "java_binding_context.hpp"
#include "java_exception_def.hpp"
#include "object_store.hpp"
#include "util.hpp"
#include "jni_util/java_method.hpp"
#include "jni_util/java_class.hpp"
#include "jni_util/java_exception_thrower.hpp"

using namespace realm;
using namespace realm::_impl;
using namespace realm::jni_util;

static const char* c_table_name_exists_exception_msg = "Class already exists: '%1'.";

#if REALM_ENABLE_SYNC // used only for partial sync now
typedef ObservableCollectionWrapper<Results> ResultsWrapper;
#endif

JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeInit(JNIEnv* env, jclass,
                                                                     jstring temporary_directory_path)
{
    TR_ENTER()

    try {
        JStringAccessor path(env, temporary_directory_path);    // throws
        SharedGroupOptions::set_sys_tmp_dir(std::string(path)); // throws
    }
    CATCH_STD()
}

JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetSharedRealm(JNIEnv* env, jclass, jlong config_ptr,
                                                                                jobject realm_notifier)
{
    TR_ENTER_PTR(config_ptr)

    auto& config = *reinterpret_cast<Realm::Config*>(config_ptr);
    try {
        auto shared_realm = Realm::get_shared_realm(config);
        // The migration callback & initialization callback could throw.
        if (env->ExceptionCheck()) {
            return reinterpret_cast<jlong>(nullptr);
        }
        shared_realm->m_binding_context = JavaBindingContext::create(env, realm_notifier);
        return reinterpret_cast<jlong>(new SharedRealm(std::move(shared_realm)));
    }
    catch (SchemaMismatchException& e) {
        // An exception has been thrown in the migration block.
        if (env->ExceptionCheck()) {
            return reinterpret_cast<jlong>(nullptr);
        }
        static JavaClass migration_needed_class(env, JavaExceptionDef::RealmMigrationNeeded);
        static JavaMethod constructor(env, migration_needed_class, "<init>",
                                      "(Ljava/lang/String;Ljava/lang/String;)V");

        jstring message = to_jstring(env, e.what());
        jstring path = to_jstring(env, config.path);
        jobject migration_needed_exception = env->NewObject(migration_needed_class, constructor, path, message);
        env->Throw(reinterpret_cast<jthrowable>(migration_needed_exception));
    }
    catch (InvalidSchemaVersionException& e) {
        // An exception has been thrown in the migration block.
        if (env->ExceptionCheck()) {
            return reinterpret_cast<jlong>(nullptr);
        }
        // To match the old behaviour. Otherwise it will be converted to ISE in the CATCH_STD.
        ThrowException(env, IllegalArgument, e.what());
    }
    CATCH_STD()
    return reinterpret_cast<jlong>(nullptr);
}

JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeCloseSharedRealm(JNIEnv*, jclass,
                                                                                jlong shared_realm_ptr)
{
    TR_ENTER_PTR(shared_realm_ptr)

    auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
    // Close the SharedRealm only. Let the finalizer daemon thread free the SharedRealm
    if (!shared_realm->is_closed()) {
        shared_realm->close();
    }
}

JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeBeginTransaction(JNIEnv* env, jclass,
                                                                                jlong shared_realm_ptr)
{
    TR_ENTER_PTR(shared_realm_ptr)

    auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
    try {
        shared_realm->begin_transaction();
    }
    CATCH_STD()
}

JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeCommitTransaction(JNIEnv* env, jclass,
                                                                                jlong shared_realm_ptr)
{
    TR_ENTER_PTR(shared_realm_ptr)

    auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
    try {
        shared_realm->commit_transaction();
        // Realm could be closed in the RealmNotifier.didChange().
        if (!shared_realm->is_closed()) {
            // To trigger async queries, so the UI can be refreshed immediately to avoid inconsistency.
            // See more discussion on https://github.com/realm/realm-java/issues/4245
            shared_realm->refresh();
        }
    }
    CATCH_STD()
}

JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeCancelTransaction(JNIEnv* env, jclass,
                                                                                jlong shared_realm_ptr)
{
    TR_ENTER_PTR(shared_realm_ptr)

    auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
    try {
        shared_realm->cancel_transaction();
    }
    CATCH_STD()
}

JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsInTransaction(JNIEnv*, jclass,
                                                                                jlong shared_realm_ptr)
{
    TR_ENTER_PTR(shared_realm_ptr)

    auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
    return static_cast<jboolean>(shared_realm->is_in_transaction());
}

JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsEmpty(JNIEnv* env, jclass,
                                                                            jlong shared_realm_ptr)
{
    TR_ENTER_PTR(shared_realm_ptr)

    auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
    try {
        return static_cast<jboolean>(ObjectStore::is_empty(shared_realm->read_group()));
    }
    CATCH_STD()
    return JNI_FALSE;
}

JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeRefresh(JNIEnv* env, jclass, jlong shared_realm_ptr)
{
    TR_ENTER_PTR(shared_realm_ptr)

    auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
    try {
        shared_realm->refresh();
    }
    CATCH_STD()
}

JNIEXPORT jlongArray JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetVersionID(JNIEnv* env, jclass,
                                                                                jlong shared_realm_ptr)
{
    TR_ENTER_PTR(shared_realm_ptr)

    auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
    try {
        using rf = realm::_impl::RealmFriend;
        SharedGroup::VersionID version_id = rf::get_shared_group(*shared_realm).get_version_of_current_transaction();

        jlong version_array[2];
        version_array[0] = static_cast<jlong>(version_id.version);
        version_array[1] = static_cast<jlong>(version_id.index);

        jlongArray version_data = env->NewLongArray(2);
        if (version_data == NULL) {
            ThrowException(env, OutOfMemory, "Could not allocate memory to return versionID.");
            return NULL;
        }
        env->SetLongArrayRegion(version_data, 0, 2, version_array);

        return version_data;
    }
    CATCH_STD()

    return NULL;
}

JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsClosed(JNIEnv*, jclass, jlong shared_realm_ptr)
{
    TR_ENTER_PTR(shared_realm_ptr)

    auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
    return static_cast<jboolean>(shared_realm->is_closed());
}

JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetTable(JNIEnv* env, jclass, jlong shared_realm_ptr,
                                                                          jstring table_name)
{
    TR_ENTER_PTR(shared_realm_ptr)

    try {
        JStringAccessor name(env, table_name); // throws
        auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
        if (!shared_realm->read_group().has_table(name)) {
            std::string name_str = name;
            if (name_str.find(TABLE_PREFIX) == 0) {
                name_str = name_str.substr(TABLE_PREFIX.length());
            }
            THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalArgument,
                                 format("The class '%1' doesn't exist in this Realm.", name_str));
        }
        Table* table = LangBindHelper::get_table(shared_realm->read_group(), name);
        return reinterpret_cast<jlong>(table);
    }
    CATCH_STD()

    return reinterpret_cast<jlong>(nullptr);
}

JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeCreateTable(JNIEnv* env, jclass,
                                                                             jlong shared_realm_ptr,
                                                                             jstring j_table_name)
{
    TR_ENTER_PTR(shared_realm_ptr)

    std::string table_name;
    try {
        table_name = JStringAccessor(env, j_table_name); // throws
        auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
        shared_realm->verify_in_write(); // throws
        Table* table;
        auto& group = shared_realm->read_group();
#if REALM_ENABLE_SYNC
        // Sync doesn't throw when table exists.
        if (group.has_table(table_name)) {
            THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalArgument,
                                 format(c_table_name_exists_exception_msg, table_name.substr(TABLE_PREFIX.length())));
        }
        auto table_ref = sync::create_table(group, table_name); // throws
        table = LangBindHelper::get_table(group, table_ref->get_index_in_group());
#else
        table = LangBindHelper::add_table(group, table_name); // throws
#endif
        return reinterpret_cast<jlong>(table);
    }
    catch (TableNameInUse& e) {
        // We need to print the table name, so catch the exception here.
        std::string class_name_str(table_name.substr(TABLE_PREFIX.length()));
        ThrowException(env, IllegalArgument, format(c_table_name_exists_exception_msg, class_name_str));
    }
    CATCH_STD()

    return reinterpret_cast<jlong>(nullptr);
}

JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeCreateTableWithPrimaryKeyField(
    JNIEnv* env, jclass, jlong shared_realm_ptr, jstring j_table_name, jstring j_field_name, jboolean is_string_type,
    jboolean is_nullable)
{
    TR_ENTER_PTR(shared_realm_ptr)

    std::string class_name_str;
    try {
        std::string table_name(JStringAccessor(env, j_table_name));
        class_name_str = std::string(table_name.substr(TABLE_PREFIX.length()));
        JStringAccessor field_name(env, j_field_name); // throws
        auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
        shared_realm->verify_in_write(); // throws
        DataType pkType = is_string_type ? DataType::type_String : DataType::type_Int;
        Table* table;
        auto& group = shared_realm->read_group();
#if REALM_ENABLE_SYNC
        // Sync doesn't throw when table exists.
        if (group.has_table(table_name)) {
            THROW_JAVA_EXCEPTION(env, JavaExceptionDef::IllegalArgument,
                                 format(c_table_name_exists_exception_msg, class_name_str));
        }
        auto table_ref =
            sync::create_table_with_primary_key(group, table_name, pkType, field_name, is_nullable);
        table = LangBindHelper::get_table(group, table_ref->get_index_in_group());
#else
        table = LangBindHelper::add_table(group, table_name);
        size_t column_idx = table->add_column(pkType, field_name, is_nullable);
        table->add_search_index(column_idx);
#endif
        ObjectStore::set_primary_key_for_object(group, class_name_str, field_name);
        return reinterpret_cast<jlong>(table);
    }
    catch (TableNameInUse& e) {
        // We need to print the table name, so catch the exception here.
        ThrowException(env, IllegalArgument, format(c_table_name_exists_exception_msg, class_name_str));
    }
    CATCH_STD()

    return reinterpret_cast<jlong>(nullptr);
}

JNIEXPORT jstring JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetTableName(JNIEnv* env, jclass,
                                                                                jlong shared_realm_ptr, jint index)
{

    TR_ENTER_PTR(shared_realm_ptr)

    auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
    try {
        return to_jstring(env, shared_realm->read_group().get_table_name(static_cast<size_t>(index)));
    }
    CATCH_STD()
    return NULL;
}

JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeHasTable(JNIEnv* env, jclass,
                                                                             jlong shared_realm_ptr,
                                                                             jstring table_name)
{
    TR_ENTER_PTR(shared_realm_ptr)

    auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
    try {
        JStringAccessor name(env, table_name);
        return static_cast<jboolean>(shared_realm->read_group().has_table(name));
    }
    CATCH_STD()
    return JNI_FALSE;
}

JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeRenameTable(JNIEnv* env, jclass,
                                                                            jlong shared_realm_ptr,
                                                                            jstring old_table_name,
                                                                            jstring new_table_name)
{
    TR_ENTER_PTR(shared_realm_ptr)

    auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
    try {
        JStringAccessor old_name(env, old_table_name);
        if (!shared_realm->is_in_transaction()) {
            std::ostringstream ss;
            ss << "Class " << old_name << " cannot be removed when the realm is not in transaction.";
            ThrowException(env, IllegalState, ss.str());
            return;
        }
        JStringAccessor new_name(env, new_table_name);
        shared_realm->read_group().rename_table(old_name, new_name);
    }
    CATCH_STD()
}

JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeSize(JNIEnv* env, jclass, jlong shared_realm_ptr)
{
    TR_ENTER_PTR(shared_realm_ptr)

    auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
    try {
        return static_cast<jlong>(shared_realm->read_group().size());
    }
    CATCH_STD()

    return 0;
}

JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeWriteCopy(JNIEnv* env, jclass, jlong shared_realm_ptr,
                                                                          jstring path, jbyteArray key)
{
    TR_ENTER_PTR(shared_realm_ptr);

    auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
    try {
        JStringAccessor path_str(env, path);
        JByteArrayAccessor jarray_accessor(env, key);
        shared_realm->write_copy(path_str, jarray_accessor.transform<BinaryData>());
    }
    CATCH_STD()
}

JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeWaitForChange(JNIEnv* env, jclass,
                                                                                jlong shared_realm_ptr)
{
    TR_ENTER_PTR(shared_realm_ptr);

    auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
    try {
        using rf = realm::_impl::RealmFriend;
        return static_cast<jboolean>(rf::get_shared_group(*shared_realm).wait_for_change());
    }
    CATCH_STD()

    return JNI_FALSE;
}

JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeStopWaitForChange(JNIEnv* env, jclass,
                                                                                jlong shared_realm_ptr)
{
    TR_ENTER_PTR(shared_realm_ptr);

    auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
    try {
        using rf = realm::_impl::RealmFriend;
        rf::get_shared_group(*shared_realm).wait_for_change_release();
    }
    CATCH_STD()
}

JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeCompact(JNIEnv* env, jclass,
                                                                            jlong shared_realm_ptr)
{
    TR_ENTER_PTR(shared_realm_ptr);

    auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
    try {
        return static_cast<jboolean>(shared_realm->compact());
    }
    CATCH_STD()

    return JNI_FALSE;
}

static void finalize_shared_realm(jlong ptr)
{
    TR_ENTER_PTR(ptr)
    delete reinterpret_cast<SharedRealm*>(ptr);
}

JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetFinalizerPtr(JNIEnv*, jclass)
{
    TR_ENTER()
    return reinterpret_cast<jlong>(&finalize_shared_realm);
}

JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeSetAutoRefresh(JNIEnv* env, jclass,
                                                                               jlong shared_realm_ptr,
                                                                               jboolean enabled)
{
    TR_ENTER_PTR(shared_realm_ptr)
    try {
        auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
        shared_realm->set_auto_refresh(to_bool(enabled));
    }
    CATCH_STD()
}

JNIEXPORT jboolean JNICALL Java_io_realm_internal_OsSharedRealm_nativeIsAutoRefresh(JNIEnv* env, jclass,
                                                                                jlong shared_realm_ptr)
{
    TR_ENTER_PTR(shared_realm_ptr)
    try {
        auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
        return to_jbool(shared_realm->auto_refresh());
    }
    CATCH_STD()
    return JNI_FALSE;
}

JNIEXPORT jlong JNICALL Java_io_realm_internal_OsSharedRealm_nativeGetSchemaInfo(JNIEnv*, jclass,
                                                                               jlong shared_realm_ptr)
{
    TR_ENTER_PTR(shared_realm_ptr)

    // No throws
    auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
    return reinterpret_cast<jlong>(&shared_realm->schema());
}

JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeRegisterSchemaChangedCallback(
    JNIEnv* env, jclass, jlong shared_realm_ptr, jobject j_schema_changed_callback)
{
    TR_ENTER_PTR(shared_realm_ptr)

    // No throws
    auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
    JavaGlobalWeakRef callback_weak_ref(env, j_schema_changed_callback);
    if (shared_realm->m_binding_context) {
        JavaBindingContext& java_binding_context =
            *(static_cast<JavaBindingContext*>(shared_realm->m_binding_context.get()));
        java_binding_context.set_schema_changed_callback(env, j_schema_changed_callback);
    }
}

JNIEXPORT void JNICALL Java_io_realm_internal_OsSharedRealm_nativeRegisterPartialSyncQuery(
    REALM_UNUSED JNIEnv* env, REALM_UNUSED jobject j_shared_realm_instance, REALM_UNUSED jlong shared_realm_ptr, REALM_UNUSED jstring j_class_name,
    REALM_UNUSED jstring j_query, REALM_UNUSED jobject j_callback)
{
    TR_ENTER_PTR(shared_realm_ptr)

#if REALM_ENABLE_SYNC

    auto& shared_realm = *(reinterpret_cast<SharedRealm*>(shared_realm_ptr));
    try {
        JStringAccessor class_name(env, j_class_name);               // throws
        JStringAccessor query(env, j_query);                         // throws

        // The lambda will capture the copied reference and it will be unreferenced when the lambda's life cycle is over.
        // That happens when the Realm is closed or the callback has been triggered once.
        JavaGlobalRef j_callback_ref(env, j_callback);
        JavaGlobalWeakRef j_shared_realm_instance_ref(env, j_shared_realm_instance);

        static JavaClass shared_realm_class(env, "io/realm/internal/OsSharedRealm");
        static JavaMethod partial_sync_cb(env, shared_realm_class, "runPartialSyncRegistrationCallback",
                                          "(Ljava/lang/String;JLio/realm/internal/OsSharedRealm$PartialSyncCallback;)V");

        auto cb = [j_callback_ref, j_shared_realm_instance_ref](Results results, std::exception_ptr err) {
            JNIEnv* env = JniUtils::get_env(true);
            j_shared_realm_instance_ref.call_with_local_ref(env, [&](JNIEnv*, jobject row_obj) {
                if (err) {
                    try {
                        std::rethrow_exception(err);
                    }
                    catch (const std::exception& e) {
                        env->CallVoidMethod(row_obj, partial_sync_cb, to_jstring(env, e.what()),
                                            reinterpret_cast<jlong>(nullptr), j_callback_ref.get());
                    }
                    return;
                }

                auto wrapper = new ResultsWrapper(results);
                env->CallVoidMethod(row_obj, partial_sync_cb, nullptr, reinterpret_cast<jlong>(wrapper),
                                    j_callback_ref.get());
            });
        };

        partial_sync::register_query(shared_realm, class_name, query, std::move(cb));
    }
    CATCH_STD()
#else
    REALM_TERMINATE("Unsupported operation. Only available when used with the Realm Object Server");
#endif
}

redpajama_set_name: RedPajamaGithub


text:
//#define LOG

using System;
using System.Collections.Generic;
using System.Text;

using System.IO;
using ICSharpCode.SharpZipLib;
using ICSharpCode.SharpZipLib.Core;
using ICSharpCode.SharpZipLib.GZip;

namespace ICSimulator {

    public class InstructionWindow {

#if LOG
        StreamWriter sw;
#endif

        //private CPU m_cpu;

        public static readonly ulong NULL_ADDRESS = ulong.MaxValue;

        public int windowFree { get { return addresses.Length - load; } }

        private int load;
        private ulong[] addresses;
        private bool[] writes;
        private bool[] ready;
        private Request[] requests;
        private int next, oldest;

        private ulong[] issueT, headT;

        public ulong instrNr;
        public ulong totalInstructionsRetired;

        public ulong stallStart;

        public int outstandingReqs;

        public BinaryReader m_readlog;
        public BinaryWriter m_writelog;
        public string m_readfile, m_writefile;
        public bool readLog, writeLog;
        public int readlog_delta;

        public ulong oldestT; // kept in sync with retire point in corresponding annotate-log
        public ulong oldestT_base; // when wrapping to beginning, keep
                                   // the virtual retire time stream going

        ulong m_match_mask;

        public InstructionWindow(CPU cpu) {
            //m_cpu = cpu;
            next = oldest = 0;
            addresses = new ulong[Config.proc.instWindowSize];
            writes = new bool[Config.proc.instWindowSize];
            ready = new bool[Config.proc.instWindowSize];
            requests = new Request[Config.proc.instWindowSize];
            issueT = new ulong[Config.proc.instWindowSize];
            headT = new ulong[Config.proc.instWindowSize];

            m_match_mask = ~ ( ((ulong)1 << Config.cache_block) - 1 );

            for (int i = 0; i < Config.proc.instWindowSize; i++) {
                addresses[i] = NULL_ADDRESS;
                writes[i] = false;
                ready[i] = false;
                requests[i] = null;
            }

            outstandingReqs = 0;

            readLog = writeLog = false;

            if (Config.writelog != "" && Config.writelog_node == cpu.node.coord.ID)
                SetWrite(Config.writelog);
            if (Config.readlog != "")
            {
                if (Config.readlog.StartsWith("auto"))
                {
                    string prefix = Config.readlog.StartsWith("auto_") ?
                        Config.readlog.Substring(5) :
                        Config.router.algorithm.ToString();
                    SetRead(Simulator.network.workload.getLogFile(cpu.node.coord.ID, prefix));
                }
                else
                    SetRead(Config.readlog.Split(' ')[cpu.node.coord.ID]);
            }

#if LOG
            if (Simulator.sources.soloMode && Simulator.sources.solo.ID == procID)
            {
                sw = new StreamWriter ("insn.log");
                sw.WriteLine("# cycle instrNr req_seq bank netlat injlat");
            }
            else
                sw = null;
#endif

        }

        public void SetRead(string filename)
        {
            if (!File.Exists(filename))
                filename = Config.logpath + "/" + filename;
            if (!File.Exists(filename))
            {
                Console.Error.WriteLine("WARNING: could not find annotation log {0}", filename);
                return;
            }

            m_readfile = filename;
            readLog = true;
            openReadStream();
        }

        public void openReadStream()
        {
            if (m_readlog != null) m_readlog.Close();
            m_readlog = new BinaryReader(new GZipInputStream(File.OpenRead(m_readfile)));

            readlog_delta = (int) m_readlog.ReadUInt64();
        }

        public void SetWrite(string filename)
        {
            m_writefile = filename;
            writeLog = true;
            //m_writelog = new BinaryWriter(new GZipOutputStream(File.Open(filename, FileMode.Create)));
            m_writelog = new BinaryWriter(File.Open(filename, FileMode.Create));
            m_writelog.Write((ulong)Config.writelog_delta);
        }

        public void SeekLog(ulong insns)
        {
            if (!readLog) return;

            long count = (long)insns;
            while (count > 0)
            {
                advanceReadLog();
                count -= readlog_delta;
            }
        }

        public bool isFull() {
            return (load == Config.proc.instWindowSize);
        }

        public bool isEmpty() {
            return (load == 0);
        }

        public void fetch(Request r, ulong address, bool isWrite, bool isReady) {

            //Console.WriteLine("pr{0}: fetch addr {1:X}, write {2}, isReady {3}, next {4}",
            //        m_cpu.ID, address, isWrite, isReady, next);

            if (load < Config.proc.instWindowSize) {
                load++;
                addresses[next] = address;
                writes[next] = isWrite;
                ready[next] = isReady;
                requests[next] = r;
                issueT[next] = Simulator.CurrentRound;
                headT[next] = Simulator.CurrentRound;
                if (isReady && r != null) r.service();
                //Console.WriteLine("pr{0}: new req in slot {1}: addr {2}, write {3} ready {4}", m_cpu.ID, next, address, isWrite, isReady);
                next++;
                if (!isReady) outstandingReqs++;
                //Console.WriteLine("pr{0}: outstanding reqs now at {1}", m_cpu.ID, outstandingReqs);
                if (next == Config.proc.instWindowSize) next = 0;
            }
            else throw new Exception("Instruction Window is full!");

        }

        public int retire(int n) {
            int i = 0;

            while (i < n && load > 0 && ready[oldest])
            {
                if (requests[oldest] != null)
                    requests[oldest].retire();

                ulong deadline = headT[oldest] - issueT[oldest];
                Simulator.stats.deadline.Add(deadline);

                oldest++;
                if (oldest == Config.proc.instWindowSize) oldest = 0;
                headT[oldest] = Simulator.CurrentRound;
                i++;
                load--;
                instrNr++;
                totalInstructionsRetired++;

                if (writeLog)
                {
                    if (instrNr % (ulong)Config.writelog_delta == 0)
                    {
                        m_writelog.Write((ulong)Simulator.CurrentRound);
                        m_writelog.Flush();
                    }
                }
                if (readLog)
                {
                    if (instrNr % (ulong)readlog_delta == 0)
                        advanceReadLog();
                }
            }
            if (load > 0 && i < n)
                requests[oldest].backStallsCaused += (1.0 * n - i) / n;
            return i;
        }

        void advanceReadLog()
        {
            ulong old_oldestT = oldestT;
            try
            {
                oldestT = oldestT_base + m_readlog.ReadUInt64();
            }
            catch (EndOfStreamException)
            {
                openReadStream();
                oldestT_base = old_oldestT;
                oldestT = oldestT_base + m_readlog.ReadUInt64();
            }
            catch (SharpZipBaseException)
            {
                openReadStream();
                oldestT_base = old_oldestT;
                oldestT = oldestT_base + m_readlog.ReadUInt64();
            }
        }

        public bool contains(ulong address, bool write) {
            int i = oldest;
            while (i != next) {
                if ((addresses[i] >> Config.cache_block) == (address >> Config.cache_block)) {
                    // this new request will be satisfied by outstanding request i if:
                    // 1. this new request is not a write (i.e., will be satisfied by R or W completion), OR
                    // 2. there is an outstanding write (i.e., a write completion will satisfy any pending req)
                    if (!write || writes[i])
                        return true;
                }
                i++;
                if (i == Config.proc.instWindowSize) i = 0;
            }
            return false;
        }

        public void setReady(ulong address, bool write) {
            //Console.WriteLine("pr{0}: ready {1:X} (block {2:X}) write {3}", m_cpu.ID, address,
            //        address >> Config.cache_block, write);

            if (isEmpty()) throw new Exception("Instruction Window is empty!");

            for (int i = 0; i < Config.proc.instWindowSize; i++) {
                if ((addresses[i] & m_match_mask) == (address & m_match_mask) && !ready[i]) {
                    // this completion does not satisfy outstanding req i if and only if
                    // 1. the outstanding req is a write, AND
                    // 2. the completion is a read completion.
                    if (writes[i] && !write) continue;
                    requests[i].service();
                    ready[i] = true;
                    addresses[i] = NULL_ADDRESS;
                    outstandingReqs--;
                    //Console.WriteLine("pr{0}: set slot {1} ready, now {2} outstanding", m_cpu.ID, i, outstandingReqs);
                }
            }
        }

        public bool isOldestAddrAndStalled(ulong address, out ulong stallCount)
        {
            if (!ready[oldest] && addresses[oldest] == address)
            {
                stallCount = Simulator.CurrentRound - stallStart;
                return true;
            }
            else
            {
                stallCount = 0;
                return false;
            }
        }

        /**
         * Returns true if the oldest instruction in the window is a non-ready memory request
         */
        public bool isOldestReady() {
            return ready[oldest];
        }

        public ulong stallingAddr()
        {
            return addresses[oldest];
        }

        public void close()
        {
            if (m_writelog != null)
            {
                m_writelog.Flush();
                m_writelog.Close();
            }
        }

        public void dumpOutstanding()
        {
            Console.Write("Pending blocks: ");
            for (int i = 0; i < Config.proc.instWindowSize; i++)
            {
                if (addresses[i] != NULL_ADDRESS && !ready[i])
                    Console.Write("{0:X} ", addresses[i] >> Config.cache_block);
            }
            Console.WriteLine();
        }
    }
}


text:
package org.sakaiproject.tool.impl;

import org.apache.commons.lang.mutable.MutableLong;
import org.jmock.Expectations;
import org.sakaiproject.id.api.IdManager;
import org.sakaiproject.thread_local.api.ThreadLocalManager;
import org.sakaiproject.tool.api.*;

import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import java.util.*;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.Callable;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;

/**
 * Verifies behavior of {@link MySession}, which
 * is the standard implementation of {@link Session}.
 *
 *
Tests are not comprehensive. Where an implementation is simple
 * field access, the corresponding test has typically been skipped.

 *
 *
Read on for more design notes.

 *
 *
Theoretically, this guards against regressions in this module.
 * In reality, though, this test will eventually fail as-is b/c it
 * actually does test MySession as a unit as opposed to,
 * for example, relying on factory methods like
 * {@link SessionComponent#startSession()}. Thus, were
 * MySession refactored to a top-level class, which we
 * expect, this test will need to be modified accordingly. So we were
 * forced to choose between "pure" unit tests, which break
 * (paradoxically) if the relationship between MySession
 * and SessionComponent changes, and black box-ish tests
 * which intentionally obscure the division of responsibilities between
 * MySession and SessionComponent.

 *
 *
We decided to pursue "pure" unit tests in combination with a small
 * quantity of black box-ish tests in {@link SessionComponentRegressionTest}
 * to guard against complete failure. For example, see
 * {@link SessionComponentRegressionTest#testGetSessionReturnsNullIfSessionExpired()},
 * which implicitly tests callbacks to SessionComponent
 * from {@link Session#invalidate()}. We felt this was the correct
 * decision because testing the behavior of factory methods should be a
 * separate concern from testing the behavior of the created object
 * itself. Such a design improves the overall quality of the code base
 * while still satisfying our original goal of supporting anticipated
 * modifications to both {@link SessionComponent} and its internal
 * implementations of the Sessions domain.

 *
 *
 * @author dmccallum@unicon.net
 *
 */
public class MySessionTest extends BaseSessionComponentTest {

	public void testCreatedInExpectedState() throws Exception {
		final String sessionId = "SESSION_ID";
		doTestCreatedInExpectedState(sessionId, new Callable<MySession>() {
			public MySession call() throws Exception {
				return createSession(sessionId);
			}
		});
	}

	public void testCreatedInExpectedStateWithClientSpecifiedId()
			throws Exception {
		final String sessionId = "SESSION_ID";
		doTestCreatedInExpectedState(sessionId, new Callable<MySession>() {
			public MySession call() throws Exception {
				return createSessionWithClientSpecifiedId(sessionId);
			}
		});
	}

	protected void doTestCreatedInExpectedState(String sessionId,
			Callable<MySession> factoryCallback) throws Exception {
		MySession session = factoryCallback.call();
		assertEquals(sessionId, session.getId());
		assertTrue(session.getCreationTime() > 0);
		assertEquals(session.getCreationTime(), session.getLastAccessedTime());
		assertEquals(sessionComponent.m_defaultInactiveInterval, session
				.getMaxInactiveInterval());
		assertNull(session.getUserId());
		assertNull(session.getUserEid());
		assertFalse(session.getAttributeNames().hasMoreElements());
	}

	/**
	 * {@link MySession#invalidate()} should behave like
	 * {@link MySession#clear()} but with extra logic for unsetting itself as
	 * the "current" session.
	 *
	 * @see #doTestSessionClear(org.sakaiproject.tool.impl.MySessionTest.MyTestableSession,
	 *      Runnable)
	 */
	public void testInvalidateClearsSessionAndUnsetsItselfAsCurrent() {
		allowToolCheck("simple.unit.test");
		allowPlacementCheck("sakai-context-1");
		final MyTestableSession session = createSession();
		doTestSessionClear(session, new Runnable() {
			public void run() {
				expectGetAndUnsetCurrentSession(session);
				session.invalidate();
			}
		});
	}

	/**
	 * Exactly the same as {@link #testInvalidateClearsSessionAndUnsetsItselfAsCurrent()}
	 * except that the session's currentness should not be affected.
	 *
	 * @see #doTestSessionClear(org.sakaiproject.tool.impl.MySessionTest.MyTestableSession, Runnable)
	 */
	public void testClearUnbindsAttributes() {
		allowToolCheck("simple.unit.test");
		allowPlacementCheck("sakai-context-1");
		final MyTestableSession session = createSession();
		doTestSessionClear(session, new Runnable() {
			public void run() {
				session.clear();
			}
		});
	}

	protected void doTestSessionClear(final MyTestableSession session, Runnable codeExerciseCallback) {
		ContextSession contextSession = getContextSession(session, "CONTEXT_SESSION_ID");
		ToolSession toolSession = getToolSession(session, "PLACEMENT_ID");

		final String sessionAttribKey = "SESSION_ATTRIB_KEY";
		final ListeningAttribValue sessionAttribValue =
			setNewListeningAttribValue(session, sessionAttribKey);
		setNewListeningAttribValue(contextSession, "CONTEXT_SESSION_ATTRIB_KEY");
		setNewListeningAttribValue(toolSession, "TOOL_SESSION_ATTRIB_KEY");

		codeExerciseCallback.run();

		assertHasNoAttributes(session);
		assertHasNoAttributes(toolSession);
		assertHasNoAttributes(contextSession);

		assertEquals(new HashMap<String,Object>() {{
				put(sessionAttribKey, sessionAttribValue);
			}}, session.unbindInvokedWith);

		// Oddly, we can still use the session as a factory even after its been
		// invalidated. Were this to change, we'd need to devise a more clever
		// mechanism for detecting cascaded context/tool session invalidation.
		assertNotSame(contextSession, session.getContextSession(contextSession.getContextId()));
		assertNotSame(toolSession, session.getToolSession(toolSession.getPlacementId()));
	}

	/**
	 * Hard to believe this is the correct behavior, but this is what
	 * the "legacy" implementation does. The {@link Session}'s own attributes
	 * are selectively filtered, retaining only those named by the specified
	 * Collection, but all child {@link ToolSession} and
	 * {@link ContextSession} attributes are wiped away by virtue of a call to
	 * {@link Session#clear()}.
	 */
	public void testClearExceptFiltersSessionAttribsButClearsAllToolAndContextSessionAttribs() {
		MyTestableSession session = createSession();
		ContextSession contextSession = getContextSession(session, "CONTEXT_SESSION_ID");
		allowToolCheck("simple.unit.test");
		allowPlacementCheck("PLACEMENT_ID");
		ToolSession toolSession = getToolSession(session, "PLACEMENT_ID");

		final String sessionAttribKey1 = "SESSION_ATTRIB_KEY_1";
		final String sessionAttribKey2 = "SESSION_ATTRIB_KEY_2";
		final ListeningAttribValue sessionAttribValue1 =
			setNewListeningAttribValue(session, sessionAttribKey1);
		final ListeningAttribValue sessionAttribValue2 =
			setNewListeningAttribValue(session, sessionAttribKey2);
		setNewListeningAttribValue(contextSession, "CONTEXT_SESSION_ATTRIB_KEY");
		setNewListeningAttribValue(toolSession, "TOOL_SESSION_ATTRIB_KEY");
		session.clearExcept(new HashSet<String>() {{ add(sessionAttribKey1); }});

		assertEquals(sessionAttribValue1, session.getAttribute(sessionAttribKey1));
		assertNull(session.getAttribute(sessionAttribKey2));
		assertEquals(new HashMap<String,Object>() {{
			put(sessionAttribKey2, sessionAttribValue2);
		}}, session.unbindInvokedWith);
		assertHasNoAttributes(toolSession);
		assertHasNoAttributes(contextSession);

	}

	public void testRemoveAttributeReleasesAttributeAndFiresUnbind() {
		MyTestableSession session = createSession();
		final String sessionAttribKey = "SESSION_ATTRIB_KEY";
		final ListeningAttribValue sessionAttribValue =
			setNewListeningAttribValue(session, sessionAttribKey);
		session.removeAttribute(sessionAttribKey);
		assertNull(session.getAttribute(sessionAttribKey));
		assertHasNoAttributes(session);
		assertEquals(new HashMap<String,Object>() {{
			put(sessionAttribKey, sessionAttribValue);
		}}, session.unbindInvokedWith);
	}

	public void testSetAndRemoveAttributeDoNotUpdateLastAccessedTime()
	throws InterruptedException {
		MyTestableSession session = createSession();
		long origLastAccessed = session.getLastAccessedTime();
		final String sessionAttribKey = "SESSION_ATTRIB_KEY";
		final ListeningAttribValue sessionAttribValue =
			setNewListeningAttribValue(session, sessionAttribKey);
		Thread.sleep(2); // we might execute too quickly to affect lastAccessedTime
		session.removeAttribute(sessionAttribKey);
		assertEquals(origLastAccessed, session.getLastAccessedTime());
	}

	public void testSettingNullAttributeValueReleasesAttributeAndFiresUnbind() {
		MyTestableSession session = createSession();
		final String sessionAttribKey = "SESSION_ATTRIB_KEY";
		final ListeningAttribValue sessionAttribValue =
			setNewListeningAttribValue(session, sessionAttribKey);
		session.setAttribute(sessionAttribKey, null);
		assertNull(session.getAttribute(sessionAttribKey));
		assertHasNoAttributes(session);
		assertEquals(new HashMap<String,Object>() {{
			put(sessionAttribKey, sessionAttribValue);
		}}, session.unbindInvokedWith);
	}

	public void testSetAttributeCachesAttributeAndFiresBind() {
		MyTestableSession session = createSession();
		final String sessionAttribKey = "SESSION_ATTRIB_KEY";
		final ListeningAttribValue sessionAttribValue =
			setNewListeningAttribValue(session, sessionAttribKey);
		assertEquals(sessionAttribValue, session.getAttribute(sessionAttribKey));
		assertEquals(sessionAttribKey, session.getAttributeNames().nextElement());
		assertEquals(new HashMap<String,Object>() {{
			put(sessionAttribKey, sessionAttribValue);
		}}, session.bindInvokedWith);
	}

	public void testSetAttributeOverwritesExistingAttributeAndFiresBindAndUnbind() {
		MyTestableSession session = createSession();
		final String sessionAttribKey = "SESSION_ATTRIB_KEY";
		final ListeningAttribValue sessionAttribValue1 =
			setNewListeningAttribValue(session, sessionAttribKey);
		final ListeningAttribValue sessionAttribValue2 =
			setNewListeningAttribValue(session, sessionAttribKey);
		assertEquals(sessionAttribValue2, session.getAttribute(sessionAttribKey));
		assertEquals(sessionAttribKey, session.getAttributeNames().nextElement());
		assertEquals(new HashMap<String,Object>() {{
			put(sessionAttribKey, sessionAttribValue1);
		}}, session.unbindInvokedWith);
		assertEquals(new HashMap<String,Object>() {{
			put(sessionAttribKey, sessionAttribValue2);
		}}, session.bindInvokedWith);
	}

	public void testLazilyCreatesToolSessionInExpectedState() {
		MySession session = createSession();
		session.setUserEid("USER_EID");
		session.setUserId("USER_ID");
		String toolSessionId = "TOOL_SESSION_ID";
		String placementId = "TOOL_PLACEMENT_ID";
		allowToolCheck("sakai.tool.id");
		allowPlacementCheck(placementId);
		ToolSession toolSession = getToolSession(session, placementId, toolSessionId);
		assertEquals(toolSessionId, toolSession.getId());
		assertEquals(placementId, toolSession.getPlacementId());
		assertTrue(toolSession.getCreationTime() > 0);
		assertTrue(toolSession.getLastAccessedTime() >= toolSession.getCreationTime());
		assertEquals(session.getUserEid(), toolSession.getUserEid());
		assertEquals(session.getUserId(), toolSession.getUserId());
		assertHasNoAttributes(toolSession);
	}

	public void testLazilyCreatesContextSessionInExpectedState() {
		MySession session = createSession();
		session.setUserEid("USER_EID");
		session.setUserId("USER_ID");
		String contextSessionId = "CONTEXT_SESSION_ID";
		String contextId = "CONTEXT_ID";
		ContextSession contextSession = getContextSession(session, contextId, contextSessionId);
		assertEquals(contextSessionId, contextSession.getId());
		assertEquals(contextId, contextSession.getContextId());
		assertTrue(contextSession.getCreationTime() > 0);
		assertTrue(contextSession.getLastAccessedTime() >= contextSession.getCreationTime());
		assertEquals(session.getUserEid(), contextSession.getUserEid());
		assertEquals(session.getUserId(), contextSession.getUserId());
		assertHasNoAttributes(contextSession);
	}

	// note this is part of the protected API
	public void testIsInactive() throws InterruptedException {
		//return ((m_inactiveInterval > 0) && (System.currentTimeMillis() > (m_accessed + (m_inactiveInterval * 1000))));
		MySession session = createSession();
		int inactivityThreshold = 1;
		session.setMaxInactiveInterval(1);
		session.setActive();
		Thread.sleep(inactivityThreshold * 2000);
		assertTrue(session.isInactive());
	}

	public void testNeverInactiveIfMaxInactiveIntervalLteZero() {
		MySession session = createSession();
		session.setMaxInactiveInterval(0);
		assertFalse(session.isInactive());
		session.setMaxInactiveInterval(-1);
		assertFalse(session.isInactive());
	}

	public void testSetActiveDoesNotUpdateTimeExpirationSuggestion() {
		// inactivity in seconds
		int inactivityThreshold = 30;
		// original expirationTimeSuggestion value gets set to current time + MaxInActive
		MySession session = createSessionSetMaxInActive(inactivityThreshold);

		long originalValue = session.expirationTimeSuggestion.longValue();
		// setActive should force expirationTimeSuggestion value to be updated
		// if the difference between currentTime and expiry time is smaller then inactive period/2
		session.setActive();
		long newValue = session.expirationTimeSuggestion.longValue();

		// make sure the expiry value was not updated (because we let no time expire)
		// by asserting the original and new value are the same
		assertEquals(originalValue, newValue);
	}

	public void testSetActiveUpdatesTimeExpirationSuggestion() {
		// inactivity in seconds
		int inactivityThreshold = 30;
		// original expirationTimeSuggestion value gets set to current time + MaxInActive
		// Simulate some time has past since the session was last accessed by setting the accessed time
		long pastTime = now() - ((inactivityThreshold*1000)+5000);
		MySession session = createSessionSetMaxInActiveAndAccessTime(inactivityThreshold,pastTime);

		long originalValue = session.expirationTimeSuggestion.longValue();
		// setActive should force expirationTimeSuggestion value to be updated
		// if the difference between currentTime and expiry time is smaller then inactive period/2
		session.setActive();
		long newValue = session.expirationTimeSuggestion.longValue();

		// make sure the expiry value was not updated (because we let no time expire)
		// by asserting the original and new value are the same
		assertNotSame(Long.valueOf(originalValue), Long.valueOf(newValue));
	}

	public MySession createSessionSetMaxInActive(int maxInactive) {
		MySession session = createSession();
		session.setMaxInactiveInterval(maxInactive);
		return session;
	}

	public MySession createSessionSetMaxInActiveAndAccessTime(int maxInactive, long accessedTime) {
		MySession session = createSessionSetMaxInActive(maxInactive);
		session.m_accessed = accessedTime;
		return session;
	}

	public long now() {
		return System.currentTimeMillis();
	}

	public void testEqualsMatchesAnySessionImplementorHavingSameId() {
		// attributes added in for a little noise, to be "extra sure" we only care about IDs.
		final MySession session1 = createSession();
		setNewListeningAttribValue(session1, "SESSION_ATTRIB_KEY_1");
		MySession session2 = createSession(session1.getId());
		setNewListeningAttribValue(session2, "SESSION_ATTRIB_KEY_2");
		assertEquals(session1, session2);
		// now lets see if it cares about a sibling implementation
		final Session session3 = mock(Session.class);
		checking(new Expectations(){{
			one(session3).getId();
			will(returnValue(session1.getId()));
		}});
		assertEquals(session1, session3);
	}

	public void testUnbindNotifiesValueIfIsSessionBindingListener() {
		MySession session = createSession();
		ListeningAttribValue attribValue = new ListeningAttribValue();
		session.unBind("SESSION_KEY", attribValue);
		assertEquals(1, attribValue.sessionValueUnboundInvokedWith.size());
		SessionBindingEvent event = attribValue.sessionValueUnboundInvokedWith.get(0);
		assertEventState(event, "SESSION_KEY", session, attribValue);
	}

	public void testUnbindNotifiesValueIfIsHttpSessionBindingListener() {
		MySession session = createSession();
		ListeningAttribValue attribValue = new ListeningAttribValue();
		session.unBind("SESSION_KEY", attribValue);
		assertEquals(1, attribValue.httpSessionValueUnboundInvokedWith.size());
		HttpSessionBindingEvent event = attribValue.httpSessionValueUnboundInvokedWith.get(0);
		assertEventState(event, "SESSION_KEY", session, attribValue);
	}

	public void testBindNotifiesValueIfIsSessionBindingListener() {
		MySession session = createSession();
		ListeningAttribValue attribValue = new ListeningAttribValue();
		session.bind("SESSION_KEY", attribValue);
		assertEquals(1, attribValue.sessionValueBoundInvokedWith.size());
		SessionBindingEvent event = attribValue.sessionValueBoundInvokedWith.get(0);
		assertEventState(event, "SESSION_KEY", session, attribValue);
	}

	public void testBindNotifiesValueIfIsHttpSessionBindingListener() {
		MySession session = createSession();
		ListeningAttribValue attribValue = new ListeningAttribValue();
		session.bind("SESSION_KEY", attribValue);
		assertEquals(1, attribValue.httpSessionValueBoundInvokedWith.size());
		HttpSessionBindingEvent event = attribValue.httpSessionValueBoundInvokedWith.get(0);
		assertEventState(event, "SESSION_KEY", session, attribValue);
	}

	public void testNonPortableAttributesStoreAndRetrieve() {
		MySession session = createSession();
		String value = "VALUE";
		System.setProperty("sakai.cluster.terracotta","true");
		session.setAttribute("SESSION_KEY", value);
		System.setProperty("sakai.cluster.terracotta","false");
		session.setAttribute("SESSION_KEY_2", value);
		assertEquals(value,session.getAttribute("SESSION_KEY"));
		assertEquals(value,session.getAttribute("SESSION_KEY_2"));
	}

	public void testNonPortableBindNotifiesValueIfIsSessionBindingListener() {
		MySession session = createSession();
		System.setProperty("sakai.cluster.terracotta","true");
		ListeningAttribValue attribValue = new ListeningAttribValue();
		session.bind("SESSION_KEY", attribValue);
		assertEquals(1, attribValue.sessionValueBoundInvokedWith.size());
		SessionBindingEvent event = attribValue.sessionValueBoundInvokedWith.get(0);
		assertEventState(event, "SESSION_KEY", session, attribValue);
		System.setProperty("sakai.cluster.terracotta","false");
	}

	public void testNonPortableUnbindNotifiesValueIfIsSessionBindingListener() {
		MySession session = createSession();
		System.setProperty("sakai.cluster.terracotta","true");
		ListeningAttribValue attribValue = new ListeningAttribValue();
		session.unBind("SESSION_KEY", attribValue);
		assertEquals(1, attribValue.sessionValueUnboundInvokedWith.size());
		SessionBindingEvent event = attribValue.sessionValueUnboundInvokedWith.get(0);
		assertEventState(event, "SESSION_KEY", session, attribValue);
		System.setProperty("sakai.cluster.terracotta","false");
	}

	public void testNonPortableRemoveAttributeReleasesAttributeAndFiresUnbind() {
		System.setProperty("sakai.cluster.terracotta","true");
		MyTestableSession session = createSession();
		allowToolCheck("simple.unit.test");
		final String sessionAttribKey = "SESSION_ATTRIB_KEY";
		final ListeningAttribValue sessionAttribValue =
			setNewListeningAttribValue(session, sessionAttribKey);
		session.removeAttribute(sessionAttribKey);
		assertNull(session.getAttribute(sessionAttribKey));
		assertHasNoAttributes(session);
		assertEquals(new HashMap<String,Object>() {{
			put(sessionAttribKey, sessionAttribValue);
		}}, session.unbindInvokedWith);
		System.setProperty("sakai.cluster.terracotta","false");
	}

	public void testNonPortableClearUnbindsAttributes() {
		final MyTestableSession session = createSession();
		System.setProperty("sakai.cluster.terracotta","true");
		allowToolCheck("simple.unit.test");
		allowPlacementCheck("sakai-context-1");
		doTestSessionClear(session, new Runnable() {
			public void run() {
				session.clear();
			}
		});
		System.setProperty("sakai.cluster.terracotta","false");
	}

	protected void assertEventState(SessionBindingEvent event, String name,
			MySession session, Object value) {
		assertEquals(name, event.getName());
		assertEquals(session, event.getSession());
		assertEquals(value, event.getValue());
	}

	protected void assertEventState(HttpSessionBindingEvent event, String name,
			MySession session, Object value) {
		assertEquals(name, event.getName());
		assertEquals(session, event.getSession());
		assertEquals(value, event.getValue());
	}

	protected ContextSession getContextSession(MySession session,
			String contextId) {
		allowCreateUuidRequest();
		return session.getContextSession(contextId);
	}

	protected ContextSession getContextSession(MySession session,
			String contextId, String contextSessionId) {
		expectCreateUuidRequest(contextSessionId); // mtd signature implies we must _expect_ the IdManager call
		return session.getContextSession(contextId);
	}

	protected ToolSession getToolSession(MySession session,
			String placementId) {
		allowCreateUuidRequest();
		return session.getToolSession(placementId);
	}

	protected ToolSession getToolSession(MySession session,
			String placementId, String toolSessionId) {
		expectCreateUuidRequest(toolSessionId); // mtd signature implies we must _expect_ the IdManager call
		return session.getToolSession(placementId);
	}

	protected MyTestableSession createSession() {
		String uuid = nextUuid();
		return new MyTestableSession(sessionComponent,uuid,threadLocalManager,idManager,sessionListener,new MyNonPortableSession());
	}

	protected MyTestableSession createSession(String sessionId) {
		return new MyTestableSession(sessionComponent,sessionId,threadLocalManager,idManager,sessionListener,new MyNonPortableSession());
	}

	/**
	 * Like {@link #createSession(String)} but assigns the given ID
	 * directly, rather than allowing the created session to
	 * retrieve an ID from the IdManager. This allows
	 * us to test a different constructor than does
	 * {@link #createSession(String)}.
	 *
	 * @param sessionId
	 * @return
	 */
	protected MyTestableSession createSessionWithClientSpecifiedId(String sessionId) {
		return new MyTestableSession(sessionComponent, sessionId, threadLocalManager, idManager, sessionListener, new MyNonPortableSession());
	}

	protected ListeningAttribValue setNewListeningAttribValue(
			ToolSession toolSession, String key) {
		ListeningAttribValue value = new ListeningAttribValue();
		toolSession.setAttribute(key, value);
		return value;
	}

	protected ListeningAttribValue setNewListeningAttribValue(
			ContextSession contextSession, String key) {
		ListeningAttribValue value = new ListeningAttribValue();
		contextSession.setAttribute(key, value);
		return value;
	}

	protected ListeningAttribValue setNewListeningAttribValue(MySession session,
			String key) {
		ListeningAttribValue value = new ListeningAttribValue();
		session.setAttribute(key, value);
		return value;
	}

	protected void assertHasNoAttributes(ContextSession contextSession) {
		assertFalse(contextSession.getAttributeNames().hasMoreElements());
	}

	protected void assertHasNoAttributes(ToolSession toolSession) {
		assertFalse(toolSession.getAttributeNames().hasMoreElements());
	}

	protected void assertHasNoAttributes(MySession session) {
		assertFalse(session.getAttributeNames().hasMoreElements());
	}

	/**
	 * Verifies that multiple {@link MySession#invalidate()} calls can proceed
	 * concurrently without error and with the session properly invalidated
	 * after all threads return. This turns out to be quite difficult to test
	 * reliably, even with knowledge of the implementation. In fact, you can't get
	 * the following test to fail, even if you remove all explicit concurrency
	 * precautions in MySession.invalidate(). It will fail, though,
	 * if sleep times are injected after taking a local copies of the attribute
	 * map, though. So, at best this test will catch truly gross errors, but
	 * for the most part is dead-weight.
	 */
	public void testConcurrentInvalidation() {
		final MyTestableSession session = createSession();
		Collection<ListeningAttribValue> attribValues =
			new ArrayList<ListeningAttribValue>();
		attribValues.add(setNewListeningAttribValue(session, "SESSION_ATTRIB_KEY_1"));
		attribValues.add(setNewListeningAttribValue(session, "SESSION_ATTRIB_KEY_2"));
		attribValues.add(setNewListeningAttribValue(session, "SESSION_ATTRIB_KEY_3"));
		attribValues.add(setNewListeningAttribValue(session, "SESSION_ATTRIB_KEY_4"));
		attribValues.add(setNewListeningAttribValue(session, "SESSION_ATTRIB_KEY_5"));
		final Set<Throwable> failures = Collections.synchronizedSet(new HashSet<Throwable>());
		final int workersCnt = 5;
		final CyclicBarrier invalidateBarrier = new CyclicBarrier(workersCnt);
		final CyclicBarrier testExitBarrier = new CyclicBarrier(workersCnt + 1);
		class Worker extends Thread {
			public void run() {
				try {
					invalidateBarrier.await(10, TimeUnit.SECONDS);
					session.invalidate();
				} catch ( Throwable t ) {
					failures.add(t);
				} finally {
					try {
						testExitBarrier.await();
					} catch ( Throwable t ) {}
				}
			}
		}
		allowGetAndUnsetCurrentSession(session);
		Worker[] workers = new Worker[workersCnt];
		for ( int p = 0; p < workersCnt; p++) {
			workers[p] = new Worker();
			workers[p].start();
		}
		try {
			testExitBarrier.await();
		} catch ( InterruptedException e ) {
		} catch ( BrokenBarrierException e ) {}
		assertEquals(Collections.synchronizedSet(new HashSet<Throwable>()), failures);
		for ( ListeningAttribValue attribValue : attribValues ) {
			assertEquals(1, attribValue.httpSessionValueUnboundInvokedWith.size());
			assertEquals(1, attribValue.sessionValueUnboundInvokedWith.size());
		}
	}

	private static class MyTestableSession extends MySession {

		private Map<String,Object> unbindInvokedWith = new HashMap<String,Object>();
		private Map<String,Object> bindInvokedWith = new HashMap<String,Object>();

		public MyTestableSession(SessionComponent outer, String sessionId, ThreadLocalManager threadLocalManager,
				IdManager idManager, SessionAttributeListener sessionListener,  NonPortableSession nps) {
			super(outer, sessionId, threadLocalManager, idManager, outer, sessionListener, outer.getInactiveInterval(),nps,new MutableLong(System.currentTimeMillis()), null);
		}

		@Override
		protected void unBind(String name, Object value) {
			unbindInvokedWith.put(name, value);
			super.unBind(name, value);
		}

		@Override
		protected void bind(String name, Object value) {
			bindInvokedWith.put(name, value);
			super.bind(name, value);
		}
	}

	private static class ListeningAttribValue implements SessionBindingListener, HttpSessionBindingListener {

		private List<SessionBindingEvent> sessionValueBoundInvokedWith =
			Collections.synchronizedList(new ArrayList<SessionBindingEvent>());
		private List<SessionBindingEvent> sessionValueUnboundInvokedWith =
			Collections.synchronizedList(new ArrayList<SessionBindingEvent>());
		private List<HttpSessionBindingEvent> httpSessionValueBoundInvokedWith =
			Collections.synchronizedList(new ArrayList<HttpSessionBindingEvent>());
		private List<HttpSessionBindingEvent> httpSessionValueUnboundInvokedWith =
			Collections.synchronizedList(new ArrayList<HttpSessionBindingEvent>());

		public void valueBound(SessionBindingEvent event) {
			this.sessionValueBoundInvokedWith.add(event);
		}

		public void valueUnbound(SessionBindingEvent event) {
			this.sessionValueUnboundInvokedWith.add(event);
		}

		public void valueBound(HttpSessionBindingEvent event) {
			this.httpSessionValueBoundInvokedWith.add(event);
		}

		public void valueUnbound(HttpSessionBindingEvent event) {
			this.httpSessionValueUnboundInvokedWith.add(event);
		}

	}

	/* Questioning the cost/benefit ration of the following:
	public void testConcurrentInvalidationAndSetAttribute() {
		fail("implement me");
	}

	public void testConcurrentClear() {
		fail("implement me");
	}

	public void testConcurrentSetAttributeAndGetAttributeNames() {
		fail("implement me");
	}

	public void testConcurrentClearExcept() {
		fail("implement me");
	}
	*/

}


text:
function searchTags(tagTerm, pageNumber, filter) {

    function ajaxRequest() {
        var activexmodes = ["Msxml2.XMLHTTP", "Microsoft.XMLHTTP"]; //activeX versions to check for in IE
        if (window.ActiveXObject) { //Test for support for ActiveXObject in IE first (as XMLHttpRequest in IE7 is broken)
            for (var i = 0; i < activexmodes.length; i++) {
                try {
                    return new ActiveXObject(activexmodes[i]);
                }
                catch (e) {
                    //suppress error
                }
            }
        }
        else if (window.XMLHttpRequest) // if Mozilla, Safari etc
            return new XMLHttpRequest();
        else
            return false
    }

    var mygetrequest = new ajaxRequest();

    mygetrequest.onreadystatechange = function () {
        var tagArray = [];
        if (mygetrequest.readyState == 4) {
            if (mygetrequest.status == 200 || window.location.href.indexOf("http") == -1) {
                var responseParsed = eval('(' + mygetrequest.responseText + ')');
                var tags = responseParsed.items;

                for (i = 0; i < tags.length; i++) {
                    var newTagID = '{"tagID":"' + tags[i].ItemId + '",';
                    var newTagDisplayText = '"tagDisplayText":"' + tags[i].Name + '",';
                    var bucketValue = '"tagBucket":"' + tags[i].Bucket + '",';
                    var newTagLanguage = '"tagLanguage":"' + tags[i].Language + '",';
                    var newTagtagPopularity = '"tagPopularity":"unknown"}';
                    var newTagObject = jQuery.parseJSON(newTagID + newTagDisplayText + bucketValue + newTagLanguage + newTagtagPopularity);
                    tagArray.push(newTagObject);
                }
            }
            populateSuggestedTagsDiv(tagArray);
        }
    }

    mygetrequest.open("GET", "/sitecore/shell/Applications/Buckets/Services/Search.ashx?fromBucketListField=" + tagTerm + '&pageNumber=' + pageNumber + filter, true);
    mygetrequest.send(null);
}

//Setup functions on page load

//dummy data & functions
function dummyReturnNewTagID(tagText) {
    tempID = random = Math.ceil(Math.random() * 1000);
    return "{" + tempID + "}";
}

//Tag input functions
TagField = function (_fieldRootId, _hiddenAppliedIdsDiv, _appliedDisplayNameDiv, _addTagButton, _userInputDiv, _suggestedTagsDiv, _filter, _messagesDiv) {
    var filter = _filter;

    var fieldRootId = _fieldRootId;
    var tagFieldRoot = jQuery(_fieldRootId);
    //Tag Suggestion Setup
    //These variables will be set from the arguments passed
    var hiddenAppliedIdsDiv = _hiddenAppliedIdsDiv; //the div that will contain the hidden id of the applied tags
    var appliedDisplayNameDiv = _appliedDisplayNameDiv; //the div that will contain the display text of the applied tags
    var addTagButton = _addTagButton; //the button the user clicks to add a tag
    var userInputDiv = _userInputDiv; //the input that the user will enter text into
    var messagesDiv = _messagesDiv || null; //the div messages will be added into

    var suggestedTagsDiv = _suggestedTagsDiv; //the div that will contain the suggested tags

    var charactersBeforeSuggestions = 1; //how many characters should the user enter before suggestions are made
    var newTagsAllowed = true; //value should be true or false - is the user allowed to create their own new tags(true), or only allowed to use suggested tags(false)

    if (messagesDiv === null) {
        var messagesAllowed = false;
    }
    else {
        var messagesAllowed = true;
    }

    //messages values;
    var msgnewTagsAllowedFalse = "You do not have permission to create new tags, please use suggested tags";
    var msgTagAdded = " tag has been added"; //tag display text to be displayed first
    var msgNoTagsFound = "No tags have been found";
    var msgTagAlreadyAdded = " tag is already added"; //tag display text to be displayed first
    var msgTagRemoved = "Tag removed";
    var msgNoTextEntered = "Please type in a tag, or select a suggested tag";

    var appliedTagsArray = []; //an array of json tag objects
    var suggestedTagsArray = []; //an array of suggested json tag objects

    var closeButtonHtml = '<' + '/a>';

    //displays helpfull messages to user if messages allowed
    //function argument possibilities:
    //	displayMessageToUser([a message selected from possible messages, or a custom message], [a string that will appear before the message])
    //		eg. ("thisIsATagValue", msgTagAlreadyAdded)   will display: | thisIsATagValue tag is already added
    //	displayMessageToUser([a message selected from possible messages, or a custom message])
    //		eg. (msgNoTextEntered)   will display: | Please type in a tag, or select a suggested tag
    //	displayMessageToUser()
    //		will clear the message
    displayMessageToUser = function (msgValue, tagDisplayText) {
        if (messagesAllowed) {
            tagDisplayText = tagDisplayText || "";
            msgValue = msgValue || " ";
            if (msgValue === " ") {
                tagFieldRoot.find(messagesDiv).html(" ");
                tagFieldRoot.find(messagesDiv).css({ 'display': 'none' });
            }
            else {
                tagFieldRoot.find(messagesDiv).css({ 'display': 'inline-block' });
                tagFieldRoot.find(messagesDiv).html(tagDisplayText + msgValue);
            }
        }
    }

    setCurrentFieldRoots = function (fieldRoot) {
        tagFieldRoot = fieldRoot;
        appliedTagsArray = [];
        grabAddedTags();
        displayMessageToUser();
    }

    jQuery(fieldRootId).hover(function () {
        setCurrentFieldRoots(jQuery(this));
    });

    //adds suggested tags to the suggested tags list
    //takes array of JSON objects
    populateSuggestedTagsDiv = function (tagArray) {
        suggestedTagsArray = tagArray;
        if (suggestedTagsArray !== null) {
            tagFieldRoot.find(suggestedTagsDiv).find('.suggestedTagItem').remove();
            tagFieldRoot.find(".closebtn").css({ 'display': 'none' });
            tagFieldRoot.find('.tagSuggestionBox_wrap').css({ 'display': 'none' });
            for (var i = 0; i < suggestedTagsArray.length; i++) {
                tagFieldRoot.find('.tagSuggestionBox_wrap').css({ 'display': 'block' });
                tagFieldRoot.find(suggestedTagsDiv).append('' + suggestedTagsArray[i].tagDisplayText + " (Language: " + suggestedTagsArray[i].tagLanguage + " | Bucket: " + suggestedTagsArray[i].tagBucket + ')<' + '/span>');
                tagFieldRoot.find('.closebtn').css({ 'display': 'block' });

                tagFieldRoot.find(suggestedTagsDiv).find('.suggestedTagItem:nth-child(' + (i + 1) + ')').hover(function (event) {
                    tagFieldRoot.find(suggestedTagsDiv).find('.suggestedTagItem').removeClass("hover mouseHover");
                    tagFieldRoot.find(this).addClass("hover mouseHover");
                }, function (event) { tagFieldRoot.find(suggestedTagsDiv).find('.suggestedTagItem').removeClass("hover mouseHover"); });

                tagFieldRoot.find(suggestedTagsDiv).find('.suggestedTagItem:nth-child(' + (i + 1) + ')').click(function (event) {
                    event.preventDefault();
                    addThisTag = jQuery(this).attr("data-displayText");
                    tagFieldRoot.find(userInputDiv).val(addThisTag);
                    tagFieldRoot.find(suggestedTagsDiv).find('.suggestedTagItem').remove();
                    tagFieldRoot.find(".closebtn").css({ 'display': 'none' });
                    tagFieldRoot.find('.tagSuggestionBox_wrap').css({ 'display': 'none' });
                    tagFieldRoot.find(userInputDiv).focus();
                });
            }
        }

    }

    getTagSuggestions = function (userInputString) {
        searchTags(userInputString, 1, filter);//aString(the user input) gets passed, and an array of JSON objects of suggested tags gets returned
    }

    addNewTag = function (userInputString) {
        //*NOTE: JASONS CODE HERE//
        //*****USE userInputString AS THE NEW TAG DISPLAY TEXT*****//
        //*****PUT THE CORRECT DATA TO REPLACE dummyReturnNewTagID(userInputString) *****//
        var newTagID = '{"tagID":"' + dummyReturnNewTagID(userInputString) + '",'; //<--Replace Here
        var newTagDisplayText = '"tagDisplayText":"' + userInputString + '"}';
        var newTagObject = jQuery.parseJSON(newTagID + newTagDisplayText);
        return newTagObject;
    }

    //checks to see if the tag is in the added tags list
    //takes string, returns JSON object or false
    checkTagAlreadyAdded = function (userInputString) {
        var tagAlreadyAdded = false;
        if (appliedTagsArray === null || appliedTagsArray === undefined || appliedTagsArray.length < 0) { }//empty if because IE 9 doesn't like !==
        else {
            for (var i = 0; i < appliedTagsArray.length; i++) {
                if (appliedTagsArray[i].tagDisplayText === userInputString) {
                    tagAlreadyAdded = true;
                }
            }
        }
        return tagAlreadyAdded;
    }

    //checks to see if the tag is in the suggested tags list
    //takes string, returns JSON object or false
    checkTagInSuggestedList = function (userInputString) {
        var tagInSuggestList = false;
        if (suggestedTagsArray === null || suggestedTagsArray === undefined || suggestedTagsArray.length < 0) { }//empty if because IE 9 doesn't like !==
        else {
            for (var i = 0; i < suggestedTagsArray.length; i++) {
                if (suggestedTagsArray[i].tagDisplayText === userInputString) {
                    tagInSuggestList = suggestedTagsArray[i];
                }
            }

        }
        return tagInSuggestList;
    }

    //clears and then re-populates the hidden id div and tag text fields with updated tags added
    populateTagDivs = function () {
        if (appliedTagsArray === null || appliedTagsArray === undefined || appliedTagsArray.length < 1) {
            tagFieldRoot.find(hiddenAppliedIdsDiv).val("");
            tagFieldRoot.find(appliedDisplayNameDiv).find(".addedTagItem").remove();
        }
        else {
            var tagIdDivStringVal = "";

            tagFieldRoot.find(appliedDisplayNameDiv).find(".addedTagItem").remove();
            for (var i = 0; i < appliedTagsArray.length; i++) {

                tagFieldRoot.find(appliedDisplayNameDiv).append('' + appliedTagsArray[i].tagDisplayText + '<' + '/span>' + closeButtonHtml + '<' + '/span>');

                if (i == 0) {
                    tagIdDivStringVal = appliedTagsArray[i].tagID;
                }
                else {
                    tagIdDivStringVal = tagIdDivStringVal + "|" + appliedTagsArray[i].tagID;
                }
            }
            tagFieldRoot.find(hiddenAppliedIdsDiv).val(tagIdDivStringVal);

            for (var i = 0; i < appliedTagsArray.length + 1; i++) {
                tagFieldRoot.find(appliedDisplayNameDiv).find('.addedTagItem:nth-child(' + (i + 1) + ')').find('.addedTagItemCloseBtn').bind("click", function () {
                    removeTagButtonClicked(jQuery(this).parent());
                });
            }

            //tagFieldRoot.find(appliedDisplayNameDiv).find('.addedTagItemCloseBtn').each(function() {
            //    jQuery(this).bind("click", function() {
            //        removeTagButtonClicked(jQuery(this).parent());
            //    });
            //});
        }
    }

    //on load, grabs the tags that have already been added
    grabAddedTags = function () {
        tagFieldRoot.find(appliedDisplayNameDiv).find(".addedTagItem").each(function () {
            // *NOTE: if backend populates a different div with the tag names, change '.selectbox'
            // if div that gets populated is the same div passed in for _appliedDisplayNameDiv, change '.selectbox' to appliedDisplayNameDiv
            var newTagObject = jQuery.parseJSON('{"tagID":"' + jQuery(this).attr('data-id') + '",' + '"tagDisplayText":"' + jQuery(this).find('.displayText').html() + '"}');
            appliedTagsArray.push(newTagObject);
        });
        populateTagDivs();

    }

    //takes the clicked remove button's parent div, addedTagItem
    removeTagButtonClicked = function (clickedTagItem) {
        var removeDisplayText = clickedTagItem.find('.displayText').html();
        for (var i = 0; i < appliedTagsArray.length; i++) {
            if (appliedTagsArray[i].tagDisplayText === removeDisplayText) {
                appliedTagsArray.splice(i, 1);
            }
        }
        populateTagDivs();
    }

    addTagButtonClicked = function (userInputString) {
        //check to see if the tag has already been added, we don't want repeating tags
        if (checkTagAlreadyAdded(userInputString)) {
            //the tag has been added, if messages are allowed, the user will be informed
            displayMessageToUser(msgTagAlreadyAdded, userInputString);
        }
            //the tag has not yet been added
        else {
            var addTagObject = false;
            //if the tag is in the suggested tags list, we'll pull the details from there
            var tagInSuggestList = checkTagInSuggestedList(userInputString);
            if ((suggestedTagsArray !== null || suggestedTagsArray !== undefined || suggestedTagsArray.length > 0) && tagInSuggestList !== false) {
                addTagObject = tagInSuggestList;
            }
                //the tag suggestion list is either empty, or none of them match
                //the tag has not already been added, and it hasn't been suggested, so a new tag needs to be made
            else {
                //check if the user is allowed to add new tags
                if (newTagsAllowed === false) {
                    //the user isnt allowed to add new tags, if messages are allowed, the user will be informed
                    displayMessageToUser(msgnewTagsAllowedFalse);
                }
                else {
                    //a new tag will now be created
                    addTagObject = addNewTag(userInputString);
                }
            }
            //if a tag was pulled from the suggested list, or created, we need to add it
            if (addTagObject !== false) {
                if (appliedTagsArray === null || appliedTagsArray === undefined || appliedTagsArray.length < 1) {
                    //there's no tags yet added, so lets add the first one
                    appliedTagsArray = [addTagObject];
                }
                else {
                    //add the new tag to the lis of tags already added
                    appliedTagsArray.push(addTagObject);
                }
                populateTagDivs(); //still runs if new tag & allowed = null
            }

        }
        tagFieldRoot.find(userInputDiv).val("");
        suggestedTagsArray = [];
    }

    checkUserInputString = function (uis) {
        if (uis.length < 1 || uis === "" || uis === " " || uis === undefined) {
            tagFieldRoot.find(addTagButton).removeClass('notEmpty');
        }
        else {
            tagFieldRoot.find(addTagButton).addClass('notEmpty');
        }
    }

    jQuery(addTagButton).click(function (event) {
        event.preventDefault();
        displayMessageToUser();
        tagFieldRoot.find(suggestedTagsDiv).find('.suggestedTagItem').remove();
        tagFieldRoot.find('.tagSuggestionBox_wrap').css({ 'display': 'none' });
        checkUserInputString(" ");
        userInputString = tagFieldRoot.find(userInputDiv).val();
        if (userInputString === null || userInputString === undefined || userInputString === "" || userInputString === " ") {
            displayMessageToUser(msgNoTextEntered);
        }
        else {
            addTagButtonClicked(userInputString);
        }
    });

    jQuery(".closebtn").click(function () {
        displayMessageToUser();
        jQuery(this).css({ 'display': 'none' });
        tagFieldRoot.find(suggestedTagsDiv).find('.suggestedTagItem').remove();
        tagFieldRoot.find('.tagSuggestionBox_wrap').css({ 'display': 'none' });
    });

    jQuery(userInputDiv).keyup(function (event) {
        displayMessageToUser();
        var userInputString = jQuery(this).val();
        //up
        if (event.keyCode == 38) {
            event.preventDefault();
            var mh = false;
            var th = false;
            tagFieldRoot.find(suggestedTagsDiv).find(".suggestedTagItem").each(function () {
                if (jQuery(this).hasClass('mouseHover')) {
                    mh = true;
                }
                else if (jQuery(this).hasClass('hover')) {
                    th = true;
                }
            });
            if (mh === false && suggestedTagsArray.length > 0) {
                if (th === false) { hoverElement = suggestedTagsArray.length; }
                else {
                    for (var i = 1 ; i < suggestedTagsArray.length + 1; i++) {
                        var target = "'.suggestedTagItem:nth-child(" + (i) + ")'";
                        if (tagFieldRoot.find(target).hasClass('hover')) {
                            tagFieldRoot.find(target).removeClass('hover');
                            if (i - 1 === 0) {
                                hoverElement = i;
                            }
                            else {
                                hoverElement = i - 1;
                            }
                        }

                    }
                }
                tagFieldRoot.find("'.suggestedTagItem:nth-child(" + (hoverElement) + ")'").addClass('hover');
            }
        }
            //down
        else if (event.keyCode == 40) {
            event.preventDefault();
            var mh = false;
            var th = false;
            tagFieldRoot.find(suggestedTagsDiv).find(".suggestedTagItem").each(function () {
                if (jQuery(this).hasClass('mouseHover')) {
                    mh = true;
                }
                else if (jQuery(this).hasClass('hover')) {
                    th = true;
                }
            });
            if (mh === false && suggestedTagsArray.length > 0) {
                if (th === false) { hoverElement = 1; }
                else {
                    for (var i = 1 ; i < suggestedTagsArray.length + 1; i++) {
                        var target = "'.suggestedTagItem:nth-child(" + (i) + ")'";
                        if (tagFieldRoot.find(target).hasClass('hover')) {
                            tagFieldRoot.find(target).removeClass('hover');
                            if (i + 1 === suggestedTagsArray.length + 1) {
                                hoverElement = i;
                            }
                            else {
                                hoverElement = i + 1;
                            }
                        }

                    }
                }
                tagFieldRoot.find("'.suggestedTagItem:nth-child(" + (hoverElement) + ")'").addClass('hover');
            }
        }
            //enter
        else if (event.keyCode == 13) {
            checkUserInputString(userInputString);
            th = false;
            for (var i = 1; i < suggestedTagsArray.length + 1; i++) {
                if (tagFieldRoot.find("'.suggestedTagItem:nth-child(" + (i) + ")'").hasClass('hover')) {

                    tagFieldRoot.find("'.suggestedTagItem:nth-child(" + (i) + ")'").css({ 'border': '1px solid red' })
                    th = tagFieldRoot.find("'.suggestedTagItem:nth-child(" + (i) + ")'");

                }
            }
            if ((suggestedTagsArray.length < 1 || th === false) && tagFieldRoot.find(addTagButton).hasClass('notEmpty')) {
                tagFieldRoot.find(addTagButton).click();
            }
            else if (th !== false) {
                addThisTag = th.attr("data-displayText");
                tagFieldRoot.find(userInputDiv).val(addThisTag);
                tagFieldRoot.find(suggestedTagsDiv).find('.suggestedTagItem').remove();
                tagFieldRoot.find(".closebtn").css({ 'display': 'none' });
                tagFieldRoot.find('.tagSuggestionBox_wrap').css({ 'display': 'none' });
                tagFieldRoot.find(userInputDiv).focus();
            }

        }
            //escape
        else if (event.keyCode == 27) {
            if (suggestedTagsArray.length > 0 && tagFieldRoot.find('.closebtn').css('display') !== 'none') {
                tagFieldRoot.find('.closebtn').click();
                tagFieldRoot.find(userInputDiv).focus();
            }
            else { tagFieldRoot.find(userInputDiv).val(""); }

        }
        else {

            checkUserInputString(userInputString);
            if (userInputString.length >= charactersBeforeSuggestions) {
                getTagSuggestions(userInputString);
            }
        }
    });

    //setCurrentFieldRoots(jQuery(fieldRootId));
    checkUserInputString(" ");
    displayMessageToUser();
    grabAddedTags();
}