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

io.github.zeal18.zio.mongodb.bson.collection.BaseDocument.scala Maven / Gradle / Ivy

/*
 * Copyright 2008-present MongoDB, Inc.
 *
 * 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 io.github.zeal18.zio.mongodb.bson.collection

import scala.annotation.nowarn
import scala.collection.Iterable
import scala.jdk.CollectionConverters.*
import scala.reflect.ClassTag
import scala.util.Failure
import scala.util.Success
import scala.util.Try

import io.github.zeal18.zio.mongodb.bson.BsonMagnets
import io.github.zeal18.zio.mongodb.bson.DefaultHelper.*
import io.github.zeal18.zio.mongodb.bson.*
import io.github.zeal18.zio.mongodb.bson.conversions.Bson
import org.bson.codecs.configuration.CodecRegistry
import org.bson.json.JsonWriterSettings

/** Base Document trait.
  *
  * A strictly typed `Iterable[(String, BsonValue)]` and provides the underlying immutable document behaviour.
  * See [[immutable.Document]] or [[mutable.Document]] for the concrete implementations.
  *
  * @tparam T The concrete Document implementation
  */
private[bson] trait BaseDocument[T] extends Iterable[(String, BsonValue)] with Bson {

  import BsonMagnets.*

  /** The underlying bson document
    *
    * Restricted access to the underlying BsonDocument
    */
  protected[mongodb] val underlying: BsonDocument

  /** Create a concrete document instance
    *
    * @param underlying the underlying BsonDocument
    * @return a concrete document instance
    */
  protected[mongodb] def apply(underlying: BsonDocument): T

  /** Retrieves the value which is associated with the given key or throws a `NoSuchElementException`.
    *
    * @param  key the key
    * @return     the value associated with the given key, or throws `NoSuchElementException`.
    */
  def apply[TResult <: BsonValue](
    key: String,
  )(implicit e: TResult DefaultsTo BsonValue, ct: ClassTag[TResult]): TResult =
    get[TResult](key) match {
      case Some(value) => value
      case None        => throw new NoSuchElementException("key not found: " + key) // scalafix:ok
    }

  /** Returns the value associated with a key, or a default value if the key is not contained in the map.
    * @param   key      the key.
    * @param   default  The default value in case no binding for `key` is found in the Document.
    *                   This can be any [[BsonValue]] type or any native type that has an implicit [[BsonTransformer]] in scope.
    * @tparam  B        the result type of the default computation.
    * @return  the value associated with `key` if it exists,
    *          otherwise the result of the `default` computation.
    */
  def getOrElse[B >: BsonValue](key: String, default: CanBeBsonValue): B = get(key) match {
    case Some(v) => v
    case None    => default.value
  }

  /** Creates a new document containing a new key/value and all the existing key/values.
    *
    * Mapping `kv` will override existing mappings from this document with the same key.
    *
    * @param elems the key/value mapping to be added. This can be any valid `(String, BsonValue)` pair that can be transformed into a
    *              [[BsonElement]] via [[BsonMagnets.CanBeBsonElement]] implicits and any [[BsonTransformer]]s that are in scope.
    * @return      a new document containing mappings of this document and the mapping `kv`.
    */
  def +(elems: CanBeBsonElement*): T = {
    val bsonDocument: BsonDocument = copyBsonDocument()
    elems.foreach(elem => bsonDocument.put(elem.key, elem.value))
    apply(bsonDocument)
  }

  /** Removes one or more elements to this document and returns a new document.
    *
    * @param elems the remaining elements to remove.
    * @return A new document with the keys removed.
    */
  def -(elems: String*): T = --(elems)

  /** Removes a number of elements provided by a traversable object and returns a new document without the removed elements.
    *
    * @param xs      the traversable object consisting of key-value pairs.
    * @return        a new document with the bindings of this document and those from `xs`.
    */
  def --(xs: Iterable[String]): T = {
    val keysToIgnore  = xs.toList
    val newUnderlying = new BsonDocument()
    for ((k, v) <- iterator if !keysToIgnore.contains(k))
      newUnderlying.put(k, v)
    apply(newUnderlying)
  }

  /** Creates a new Document consisting of all key/value pairs of the current document
    * plus a new pair of a given key and value.
    *
    * @param key    The key to add
    * @param value  The new value
    * @return       A fresh immutable document with the binding from `key` to `value` added to the new document.
    */
  def updated[B](key: String, value: B)(implicit transformer: BsonTransformer[B]): T =
    this + ((key, value))

  /** Creates a new Document consisting of all key/value pairs of the current document
    * plus a new pair of a given key and value.
    *
    * @param elems  The key/values to add. This can be any valid `(String, BsonValue)` pair that can be transformed into a
    *              [[BsonElement]] via [[BsonMagnets.CanBeBsonElement]] implicits and any [[BsonTransformer]]s that are in scope.
    * @return       A fresh immutable document with the binding from `key` to `value` added to the new document.
    */
  def updated(elems: CanBeBsonElement*): T = this.+(elems*)

  /** Optionally returns the value associated with a key.
    *
    * @param  key  the key we want to lookup
    * @return an option value containing the value associated with `key` in this document,
    *         or `None` if none exists.
    */
  @nowarn("cat=unused")
  def get[TResult <: BsonValue](
    key: String,
  )(implicit e: TResult DefaultsTo BsonValue, ct: ClassTag[TResult]): Option[TResult] =
    underlying.containsKey(key) match {
      case true =>
        Try(ct.runtimeClass.cast(underlying.get(key))) match {
          case Success(v) => Some(v.asInstanceOf[TResult]) // scalafix:ok
          case Failure(_) => None
        }
      case false => None
    }

  /** Creates a new iterator over all key/value pairs in this document
    *
    * @return the new iterator
    */
  def iterator: Iterator[(String, BsonValue)] = underlying.asScala.iterator

  /** Filters this document by retaining only keys satisfying a predicate.
    * @param  p   the predicate used to test keys
    * @return a new document consisting only of those key value pairs of this map where the key satisfies
    *         the predicate `p`.
    */
  def filterKeys(p: String => Boolean): T = this -- keys.filterNot(p)

  /** Tests whether this map contains a binding for a key
    *
    * @param key the key
    * @return true if there is a binding for key in this document, false otherwise.
    */
  def contains(key: String): Boolean = underlying.containsKey(key)

  /** Collects all keys of this document in a set.
    *
    * @return  a set containing all keys of this document.
    */
  def keySet: Set[String] = underlying.keySet().asScala.toSet

  /** Collects all keys of this document in an iterable collection.
    *
    * @return the keys of this document as an iterable.
    */
  def keys: Iterable[String] = keySet

  /** Creates an iterator for all keys.
    *
    * @return an iterator over all keys.
    */
  def keysIterator: Iterator[String] = keySet.iterator

  /** Collects all values of this document in an iterable collection.
    *
    * @return the values of this document as an iterable.
    */
  def values: Iterable[BsonValue] = underlying.values().asScala

  /** Creates an iterator for all values in this document.
    *
    * @return an iterator over all values that are associated with some key in this document.
    */
  def valuesIterator: Iterator[BsonValue] = values.iterator

  /** Gets a JSON representation of this document
    *
    * @return a JSON representation of this document
    */
  def toJson(): String = underlying.toJson

  /** Gets a JSON representation of this document using the given `JsonWriterSettings`.
    * @param settings the JSON writer settings
    * @return a JSON representation of this document
    */
  def toJson(settings: JsonWriterSettings): String = underlying.toJson(settings)

  override def toBsonDocument: BsonDocument = underlying

  override def toBsonDocument[TDocument](
    documentClass: Class[TDocument],
    codecRegistry: CodecRegistry,
  ): BsonDocument =
    underlying

  /** Copies the BsonDocument
    * @return the copied BsonDocument
    */
  private[collection] def copyBsonDocument(): BsonDocument = {
    val bsonDocument = BsonDocument()
    for (entry <- underlying.entrySet().asScala) bsonDocument.put(entry.getKey, entry.getValue)
    bsonDocument
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy