open class IEquality {
  fun equals(other : Any) : Boolean
    = (this as java.lang.Object).equals(other as java.lang.Object)
}

open class IHashable : IEquality {
  val hashCode : Integer
    get() = (this as java.lang.Object).hashCode()

}

open class IMap<K, V> {
  fun get(key : K) : V
  fun set(key : K, value : V) : V
  fun remove(key : K) : V
  fun containsKey(key : K) : Boolean
}

class HashableWrapper(val obj : Any) : IHashable
  // equals and hashCode implementations are inherited

@[inline] fun Any.hashable() : HashableWrapper = HashableWrapper(this)

open class IHashingStrategy<K> {
  fun equals(a : K, b : K) : Boolean
  fun hashCode(a : K) : Integer
}

class DefaultHashingStrategy<in K : IHashable> : IHashingStrategy<K> {
  override fun equals(a : K, b : K) : Boolean = a.equals(b)
  override fun hashCode(a : K) : Integer = a.hashCode
}

class JavaObjectHashingStrategy<K> : IHashingStrategy<K> {
  override fun equals(a : K, b : K) : Boolean
    = a.hashable().equals(b)
  override fun hashCode(a : K) : Integer
    = a.hashable().hashCode
}

class HashMap<K, V> : IMap<K, V> {
  private @[inline] fun hashCode(a : K) = a.hashable().hashCode
  private @[inline] fun equals(a : K, b : K) = a.hashable() == b

  // everything else uses these equals() and hashCode()... 

}

class StrategyHashMap<K, V>(hashingStrategy : IHashingStrategy<K>) : IMap<K, V> {


  // where !(K : IHashable)
  //this() : this(JavaObjectHashingStrategy<K>()) {}

  //this() where (K : IHashable) : this(DefaultHashingStrategy<K>()) {}
  //...

}