All Downloads are FREE. Search and download functionalities are using the official Maven repository.

datomisca.attribute2EntityReader.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2012 Pellucid and Zenexity
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package datomisca

import scala.annotation.implicitNotFound


/** A type class to convert an [[Attribute]] into an [[EntityReader]].
  *
  * This type class will determine a unique type `T` given a Datomic
  * type `DD` and cardinality `Card`.
  *
  * @tparam DD
  *     the Datomic value type of the attribute (see [[SchemaType]]).
  * @tparam Card
  *     the cardinality of the attribute (see [[Cardinality]]).
  * @tparam T
  *     the Scala type that the [[EntityReader]] will read.
  */
@implicitNotFound("There is no unique reader for type ${T} given an attribute with Datomic type ${DD} and cardinality ${Card} to type ${T}")
sealed trait Attribute2EntityReaderInj[DD <: AnyRef, Card <: Cardinality, T] {

  /** Convert an [[Attribute]] into an [[EntityReader]]
    *
    * @param attr
    *     the attribute with value type `DD` and cardinality `Card` to convert.
    * @return an entity reader that will read the value for attribute `attr`
    *     given an entity with that attribute.
    */
  def convert(attr: Attribute[DD, Card]): EntityReader[T]
}

object Attribute2EntityReaderInj {

  /** A cardinality one reference attribute could return either an
    * [[Entity]] or a [[Keyword]], therefore we can only say that
    * the return type is `Any`.
    */
  implicit val attr2EntityReaderDRef2DD: datomisca.Attribute2EntityReaderInj[datomisca.DatomicRef.type,datomisca.Cardinality.one.type,Any] =
    new Attribute2EntityReaderInj[DatomicRef.type, Cardinality.one.type, Any] {
      override def convert(attr: Attribute[DatomicRef.type, Cardinality.one.type]) = new EntityReader[Any] {
        override def read(entity: Entity) = entity(attr.ident)
      }
    }


  /** A cardinality many reference attribute could return a set of
    * [[Entity]]s or [[Keyword]]s, therefore we can only say that
    * the return type is a `Set` of `Any`.
    */
  implicit val attr2EntityReaderManyDRef2DD: Attribute2EntityReaderInj[DatomicRef.type, Cardinality.many.type, Set[Any]] =
    new Attribute2EntityReaderInj[DatomicRef.type, Cardinality.many.type, Set[Any]] {
      override def convert(attr: Attribute[DatomicRef.type, Cardinality.many.type]): EntityReader[Set[Any]] = new EntityReader[Set[Any]] {
        override def read(entity: Entity): Set[Any] = {
          entity.get(attr.ident) match {
            case Some(c: Iterable[Any]) => c.toSet
            case None => Set.empty
            case Some(_) => throw new EntityMappingException("Expected an iterable collection for cardinality many attribute")
          }
        }
      }
    }


  /** If there is a unique conversion for `DD` to `A`,
    * then we can read an `A`
    * for an attribute with value type `DD`.
    */
  implicit def attr2EntityReaderOne[DD <: AnyRef, A](implicit conv: FromDatomicInj[DD, A]): Attribute2EntityReaderInj[DD, Cardinality.one.type, A] =
    new Attribute2EntityReaderInj[DD, Cardinality.one.type, A] {
      override def convert(attr: Attribute[DD, Cardinality.one.type]): EntityReader[A] = new EntityReader[A] {
        override def read(entity: Entity): A = {
          val o = entity.entity.get(attr.ident)
          if (o ne null)
            conv.from(o.asInstanceOf[DD])
          else
            throw new EntityKeyNotFoundException(attr.ident.toString)
        }
      }
    }


  /** If there is a unique conversion for `DD` to `A`,
    * then we can read a `Set` of `A`
    * for a many attribute with value type `DD`.
    */
  implicit def attr2EntityReaderMany[DD <: AnyRef, A](implicit conv: FromDatomicInj[DD, A]): Attribute2EntityReaderInj[DD, Cardinality.many.type, Set[A]] =
    new Attribute2EntityReaderInj[DD, Cardinality.many.type, Set[A]] {
      override def convert(attr: Attribute[DD, Cardinality.many.type]): EntityReader[Set[A]] = new EntityReader[Set[A]] {
        override def read(entity: Entity): Set[A] = {
          val o = entity.entity.get(attr.ident)
          if (o ne null)
            o match {
              case coll: java.util.Collection[_] =>
                val builder = Set.newBuilder[A]
                val iter = coll.iterator
                while (iter.hasNext) {
                  builder += conv.from(iter.next().asInstanceOf[DD])
                }
                builder.result()
              case _ =>
                throw new EntityMappingException("Expected a collection for cardinality many attribute")
            }
          else
            Set.empty[A]
        }
      }
    }

}



/** A type class to convert an [[Attribute]] into an [[EntityReader]].
  *
  * This type class will determine if it is safe to cast to type `T`
  * given a Datomic type `DD` and cardinality `Card`.
  *
  * @tparam DD
  *     the Datomic value type of the attribute (see [[SchemaType]]).
  * @tparam Card
  *     the cardinality of the attribute (see [[Cardinality]]).
  * @tparam T
  *     the Scala type that the [[EntityReader]] will read.
  */
@implicitNotFound("There is no type-casting reader for type ${T} given an attribute with Datomic type ${DD} and cardinality ${Card} to type ${T}")
trait Attribute2EntityReaderCast[DD <: AnyRef, Card <: Cardinality, T] {

  /** Convert an [[Attribute]] into an [[EntityReader]]
    *
    * @param attr
    *     the attribute with value type `DD` and cardinality `Card` to convert.
    * @return an entity reader that will read the value for attribute `attr`
    *     given an entity with that attribute.
    */
  def convert(attr: Attribute[DD, Card]): EntityReader[T]
}

object Attribute2EntityReaderCast {

  /** If there is a conversion for `DD` to `A`
    * (see [[FromDatomic]]) then we can read an `A`
    * for an attribute with value type `DD`.
    */
  implicit def attr2EntityReaderCastOne[DD <: AnyRef, A](implicit conv: FromDatomic[DD, A]): Attribute2EntityReaderCast[DD, Cardinality.one.type, A] =
    new Attribute2EntityReaderCast[DD, Cardinality.one.type, A] {
      override def convert(attr: Attribute[DD, Cardinality.one.type]): EntityReader[A] = new EntityReader[A] {
        override def read(entity: Entity): A = {
          val o = entity.entity.get(attr.ident)
          if (o ne null)
            conv.from(o.asInstanceOf[DD])
          else
            throw new EntityKeyNotFoundException(attr.ident.toString)
        }
      }
    }


  /** If there is a conversion for `DD` to `A`
    * (see [[FromDatomic]]) then we can read a `Set` of `A`
    * for a many attribute with value type `DD`.
    */
  implicit def attr2EntityReaderCastMany[DD <: AnyRef, A](implicit conv: FromDatomic[DD, A]): Attribute2EntityReaderCast[DD, Cardinality.many.type, Set[A]] =
    new Attribute2EntityReaderCast[DD, Cardinality.many.type, Set[A]] {
      override def convert(attr: Attribute[DD, Cardinality.many.type]): EntityReader[Set[A]] = new EntityReader[Set[A]] {
        override def read(entity: Entity): Set[A] = {
          val o = entity.entity.get(attr.ident)
          if (o ne null)
            o match {
              case coll: java.util.Collection[_] =>
                val builder = Set.newBuilder[A]
                val iter = coll.iterator
                while (iter.hasNext) {
                  builder += conv.from(iter.next().asInstanceOf[DD])
                }
                builder.result()
              case _ =>
                throw new EntityMappingException("Expected a collection for cardinality many attribute")
            }
          else
            Set.empty[A]
        }
      }
    }


  /** Given a cardinality one reference attribute, we can read the entity id of
    * the entity that is referenced.
    *
    * If the referenced entity is an ident entity, then we can still get the
    * entity id from the ident keyword.
    */
  implicit val attr2EntityReaderCastIdOnly: Attribute2EntityReaderCast[DatomicRef.type, Cardinality.one.type, Long] =
    new Attribute2EntityReaderCast[DatomicRef.type, Cardinality.one.type, Long] {
      override def convert(attr: Attribute[DatomicRef.type, Cardinality.one.type]): EntityReader[Long] = new EntityReader[Long] {
        override def read(entity: Entity) = {
          val o = entity.entity.get(attr.ident)
          if (o ne null)
            o match {
              case e: datomic.Entity =>
                e.get(clojure.lang.Keyword.intern("db", "id")).asInstanceOf[Long]
              case k: clojure.lang.Keyword =>
                val db = entity.entity.db()
                db.entid(k).asInstanceOf[Long]
              case _ =>
                throw new EntityMappingException("Expected an entity or keyword for a reference type attribute")
            }
          else
            throw new EntityKeyNotFoundException(attr.ident.toString)
        }
      }
    }


  /** Given a cardinality many reference attribute, we can read the set of entity ids of
    * the entities that are referenced.
    *
    * If the referenced entities are ident entities, then we can still get the
    * entity ids from the ident keywords.
    */
  implicit val attr2EntityReaderCastManyIdOnly: Attribute2EntityReaderCast[DatomicRef.type, Cardinality.many.type, Set[Long]] =
    new Attribute2EntityReaderCast[DatomicRef.type, Cardinality.many.type, Set[Long]] {
      override def convert(attr: Attribute[DatomicRef.type, Cardinality.many.type]): EntityReader[Set[Long]] = new EntityReader[Set[Long]] {
        override def read(entity: Entity) = {
          val o = entity.entity.get(attr.ident)
          if (o ne null)
            o match {
              case coll: java.util.Collection[_] =>
                val builder = Set.newBuilder[Long]
                val iter = coll.iterator
                while (iter.hasNext) {
                  iter.next() match {
                    case e: datomic.Entity =>
                      builder += e.get(clojure.lang.Keyword.intern("db", "id")).asInstanceOf[Long]
                    case k: clojure.lang.Keyword =>
                      val db = entity.entity.db()
                      builder += db.entid(k).asInstanceOf[Long]
                    case _ =>
                      throw new EntityMappingException("Expected an entity or keyword for a reference type attribute")
                  }
                }
                builder.result()
              case _ =>
                throw new EntityMappingException("Expected a collection for cardinality many attribute")
            }
          else
            Set.empty[Long]
        }
      }
    }


  /** Given a cardinality one reference attribute, we can read the ident keyword of
    * the ident entity that is referenced.
    *
    * And if we have an implicit view from [[Keyword]] to a type `K`, then we can
    * get the result of this view, rather than the keyword.
    *
    * If the referenced entity is not an ident entity, then an exception will be thrown.
    */
  implicit def attr2EntityReaderCastKeyword[K](implicit fromKeyword: Keyword => K): Attribute2EntityReaderCast[DatomicRef.type, Cardinality.one.type, K] =
    new Attribute2EntityReaderCast[DatomicRef.type, Cardinality.one.type, K] {
      override def convert(attr: Attribute[DatomicRef.type, Cardinality.one.type]): EntityReader[K] = new EntityReader[K] {
        override def read(entity: Entity) = {
          val o = entity.entity.get(attr.ident)
          if (o ne null)
            o match {
              case k: clojure.lang.Keyword =>
                fromKeyword(k)
              case e: datomic.Entity =>
                throw new EntityMappingException("Expected an ident entity for a reference type attribute, not a regular entity")
              case _ =>
                throw new EntityMappingException("Expected an ident entity for a reference type attribute")
            }
          else
            throw new EntityKeyNotFoundException(attr.ident.toString)
        }
      }
    }


  /** Given a cardinality many reference attribute, we can read set of ident keywords of
    * the ident entities that are referenced.
    *
    * And if we have an implicit view from [[Keyword]] to a type `K`, then we can
    * get the result of this view, rather than the keyword.
    *
    * If the referenced entities are not all ident entities, then an exception will be thrown.
    */
  implicit def attr2EntityReaderCastManyKeyword[K](implicit fromKeyword: Keyword => K): Attribute2EntityReaderCast[DatomicRef.type, Cardinality.many.type, Set[K]] =
    new Attribute2EntityReaderCast[DatomicRef.type, Cardinality.many.type, Set[K]] {
      override def convert(attr: Attribute[DatomicRef.type, Cardinality.many.type]): EntityReader[Set[K]] = new EntityReader[Set[K]] {
        override def read(entity: Entity) = {
          val o  = entity.entity.get(attr.ident)
          if (o ne null)
            o match {
              case coll: java.util.Collection[_] =>
                val builder = Set.newBuilder[K]
                val iter = coll.iterator
                while (iter.hasNext) {
                  iter.next() match {
                    case k: clojure.lang.Keyword =>
                      builder += fromKeyword(k)
                    case e: datomic.Entity =>
                      throw new EntityMappingException("Expected an ident entity for a reference type attribute, not a regular entity")
                    case _ =>
                      throw new EntityMappingException("Expected an ident entity for a reference type attribute")
                  }
                }
                builder.result()
              case _ =>
                throw new EntityMappingException("Expected a collection for cardinality many attribute")
            }
          else
            Set.empty[K]
        }
      }
    }


  /** If there is an [[EntityReader]] for type `A`
    * then we can read the entity referenced by a
    * cardinality one reference attribute as an `A`.
    */
  implicit def attr2EntityReaderOneObj[A](implicit er: EntityReader[A]): Attribute2EntityReaderCast[DatomicRef.type, Cardinality.one.type, A] =
    new Attribute2EntityReaderCast[DatomicRef.type, Cardinality.one.type, A] {
      override def convert(attr: Attribute[DatomicRef.type, Cardinality.one.type]): EntityReader[A] = new EntityReader[A] {
        override def read(entity: Entity) = {
          val o = entity.entity.get(attr.ident)
          if (o ne null)
            o match {
              case e: datomic.Entity =>
                er.read(new Entity(e))
              case k: clojure.lang.Keyword =>
                throw new EntityMappingException("Expected a regular entity for a reference type attribute, not an ident entity")
              case _ =>
                throw new EntityMappingException("Expected an entity or keyword for a reference type attribute")
            }
          else
            throw new EntityKeyNotFoundException(attr.ident.toString)
        }
      }
    }


  /** If there is an [[EntityReader]] for type `A`
    * then we can read set of entities referenced by a
    * cardinality many reference attribute as a `Set` of `A`.
    */
  implicit def attr2EntityReaderManyObj[A](implicit er: EntityReader[A]): Attribute2EntityReaderCast[DatomicRef.type, Cardinality.many.type, Set[A]] =
    new Attribute2EntityReaderCast[DatomicRef.type, Cardinality.many.type, Set[A]] {
      override def convert(attr: Attribute[DatomicRef.type, Cardinality.many.type]): EntityReader[Set[A]] = new EntityReader[Set[A]] {
        override def read(entity: Entity) = {
          val o = entity.entity.get(attr.ident)
          if (o ne null)
            o match {
              case coll: java.util.Collection[_] =>
                val builder = Set.newBuilder[A]
                val iter = coll.iterator
                while (iter.hasNext) {
                  iter.next() match {
                    case e: datomic.Entity =>
                      builder += er.read(new Entity(e))
                    case k: clojure.lang.Keyword =>
                      throw new EntityMappingException("Expected a regular entity for a reference type attribute, not an ident entity")
                    case _ =>
                      throw new EntityMappingException("Expected an entity or keyword for a reference type attribute")
                  }
                }
                builder.result()
              case _ =>
                throw new EntityMappingException("Expected a collection for cardinality many attribute")
            }
          else
            Set.empty[A]
        }
      }
    }


  /** If there is an [[EntityReader]] for type `A`
    * then we can read the entity referenced by a
    * cardinality one reference attribute as an [[IdView]] of `A`.
    */
  implicit def attr2EntityReaderOneIdView[A](implicit er: EntityReader[A]): Attribute2EntityReaderCast[DatomicRef.type, Cardinality.one.type, IdView[A]] =
    new Attribute2EntityReaderCast[DatomicRef.type, Cardinality.one.type, IdView[A]] {
      override def convert(attr: Attribute[DatomicRef.type, Cardinality.one.type]): EntityReader[IdView[A]] = new EntityReader[IdView[A]] {
        override def read(entity: Entity) = {
          val o = entity.entity.get(attr.ident)
          if (o ne null)
            o match {
              case e: datomic.Entity =>
                val id = e.get(clojure.lang.Keyword.intern("db", "id")).asInstanceOf[Long]
                new IdView(er.read(new Entity(e)), id)
              case k: clojure.lang.Keyword =>
                throw new EntityMappingException("Expected a regular entity for a reference type attribute, not an ident entity")
              case _ =>
                throw new EntityMappingException("Expected an entity or keyword for a reference type attribute")
            }
          else
            throw new EntityKeyNotFoundException(attr.ident.toString)
        }
      }
    }


  /** If there is an [[EntityReader]] for type `A`
    * then we can read set of entities referenced by a
    * cardinality many reference attribute as a `Set` of [[IdView]] of `A`.
    */
  implicit def attr2EntityReaderManyIdView[A](implicit er: EntityReader[A]): Attribute2EntityReaderCast[DatomicRef.type, Cardinality.many.type, Set[IdView[A]]] =
    new Attribute2EntityReaderCast[DatomicRef.type, Cardinality.many.type, Set[IdView[A]]] {
      override def convert(attr: Attribute[DatomicRef.type, Cardinality.many.type]): EntityReader[Set[IdView[A]]] = new EntityReader[Set[IdView[A]]] {
        override def read(entity: Entity) = {
          val o = entity.entity.get(attr.ident)
          if (o ne null)
            o match {
              case coll: java.util.Collection[_] =>
                val builder = Set.newBuilder[IdView[A]]
                val iter = coll.iterator
                while (iter.hasNext) {
                  iter.next() match {
                    case e: datomic.Entity =>
                      val id = e.get(clojure.lang.Keyword.intern("db", "id")).asInstanceOf[Long]
                      builder += new IdView(er.read(new Entity(e)), id)
                    case k: clojure.lang.Keyword =>
                      throw new EntityMappingException("Expected a regular entity for a reference type attribute, not an ident entity")
                    case _ =>
                      throw new EntityMappingException("Expected an entity or keyword for a reference type attribute")
                  }
                }
                builder.result()
              case _ =>
                throw new EntityMappingException("Expected a collection for cardinality many attribute")
            }
          else
            Set.empty[IdView[A]]
        }
      }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy