Skip to content

Instantly share code, notes, and snippets.

@scf37
Created February 21, 2017 06:11
package me.scf37.mongo
import java.util
import com.mongodb.async.SingleResultCallback
import com.mongodb.async.client.MongoIterable
import me.scf37.fine.util.Local
import org.bson.Document
import scala.collection.JavaConverters._
import scala.concurrent.Future
import scala.concurrent.Promise
/**
* Created by asm on 12.03.16.
*/
object Implicits {
implicit class JsonHelper(private val sc: StringContext) extends AnyVal {
def m(args: Any*): Document = parse(sc.parts, args)
}
implicit class PromiseOptionalValueCallback[T](p: Promise[Option[T]]) extends SingleResultCallback[T] {
private[this] val ctx = Local.save()
override def onResult(result: T, t: Throwable): Unit = {
(result, t) match {
case (null, null) => Local.let(ctx) {p.success(None)}
case (v, null) => Local.let(ctx) {p.success(Some(v))}
case (null, e) => Local.let(ctx) {p.failure(e)}
case _ => throw new IllegalStateException(s"Both result and error are present: $result, $t")
}
}
}
implicit class PromiseUnitCallback(p: Promise[Unit]) extends SingleResultCallback[Void] {
private[this] val ctx = Local.save()
override def onResult(result: Void, t: Throwable): Unit = {
(result, t) match {
case (null, e) => Local.let(ctx) {p.failure(e)}
case _ => Local.let(ctx) {p.success(Unit)}
}
}
}
implicit class AsyncIterable[T](it: MongoIterable[T]) {
import me.scf37.fine.util.ExecutionContexts.default
def toList: Future[List[T]] = {
val p = Promise[Option[util.ArrayList[T]]]
it.into(new util.ArrayList[T](), p)
p.future.map {
case None => Nil
case Some(list) => list.asScala.toList
}
}
}
private[this] def parse(parts: Seq[String], args: Seq[Any]): Document = {
val tit = if (parts.nonEmpty && !parts.head.trim.startsWith("{") && !parts.last.trim.endsWith("}")) {
//shortcut form - no outer curly bracers
var bracedParts = parts.patch(0, Seq("{" + parts.head), 1)
bracedParts = bracedParts.patch(bracedParts.length - 1, Seq(bracedParts.last + "}"), 1)
new TokenIterator(bracedParts, args)
} else new TokenIterator(parts, args)
parseObject(tit)
}
private[this] def parseObject(tit: TokenIterator, firstTokenProcessed: Boolean = false): Document = {
if (!firstTokenProcessed) {
expect(tit, TOpenCurlyBrace)
}
val obj = new Document()
var t = tit.next()
while (t != TCloseCurlyBrace) {
if (t != TString && t != TQuotedString) {
throw new IllegalArgumentException(s"Expected object field name but got $t at ${tit.context}")
}
val fieldName = tit.token[String]()
expect(tit, TSemicolon)
val fieldValue = parseValue(tit, tit.next())
obj.append(fieldName, fieldValue)
t = tit.next()
if (t == TComma) {
t = tit.next()
} else if (t != TCloseCurlyBrace) {
throw new IllegalArgumentException(s"Expected comma or closed curly brace but got $t at ${tit.context}")
}
}
obj
}
private[this] def parseValue(tit: TokenIterator, tokenType: TokenType): Any = {
tokenType match {
case TQuotedString =>
tit.token[String]()
case TString =>
tit.token[String]() match {
case "true" => true
case "false" => false
case "null" => null
case number => number.toDouble
}
case TValue => tit.token[Any]()
case TOpenCurlyBrace => parseObject(tit, firstTokenProcessed = true)
case TOpenSqBracket => parseList(tit, firstTokenProcessed = true)
case t =>
throw new IllegalArgumentException(s"Unable to parse field value, got $t at ${tit.context}")
}
}
private[this] def parseList(tit: TokenIterator, firstTokenProcessed: Boolean): util.List[_] = {
if (!firstTokenProcessed) {
expect(tit, TOpenSqBracket)
}
val list = new util.ArrayList[Any]()
var t = tit.next()
while (t != TCloseSqBracket) {
val value = parseValue(tit, t)
list.add(value)
t = tit.next()
if (t == TComma) {
t = tit.next()
} else if (t != TCloseSqBracket) {
throw new IllegalArgumentException(s"Expected comma or closed sq bracket but got $t at ${tit.context}")
}
}
list
}
private[this] def expect(tit: TokenIterator, tpe: TokenType): Unit = {
val t = tit.next()
if (t ne tpe) {
throw new IllegalArgumentException(s"Expected $tpe but got $t at ${tit.context}")
}
}
}
private sealed trait TokenType
private case object TOpenSqBracket extends TokenType
private case object TCloseSqBracket extends TokenType
private case object TOpenCurlyBrace extends TokenType
private case object TCloseCurlyBrace extends TokenType
private case object TComma extends TokenType
private case object TSemicolon extends TokenType
//seq of characters, except of delims
private case object TString extends TokenType
//quoted seq of characters
private case object TQuotedString extends TokenType
//typed value passed in via args
private case object TValue extends TokenType
private case object TEOF extends TokenType
/**
* analyzes provided parts and args from m"..." string and returns tokens one-by-one
*
* @param parts parts from StringContext
* @param args args from StringContext
*/
private class TokenIterator(parts: Seq[String], args: Seq[Any]) {
private[this] val partsIt = parts.iterator
private[this] val argsIt = args.iterator
private[this] var currentPart: String = partsIt.next()
private[this] var currentPartIx: Int = 0
private[this] var currentToken: Any = null
def context = currentPart + ", ix=" + currentPartIx
def token[T](): T = currentToken.asInstanceOf[T]
def next(): TokenType = {
if (currentPartIx == currentPart.length) {
if (partsIt.hasNext) {
currentPart = partsIt.next()
currentPartIx = 0
}
if (argsIt.hasNext) {
currentToken = argsIt.next()
return TValue
} else {
return TEOF
}
}
currentPartIx += 1
currentPart(currentPartIx - 1) match {
case '{' => TOpenCurlyBrace
case '}' => TCloseCurlyBrace
case '[' => TOpenSqBracket
case ']' => TCloseSqBracket
case ',' => TComma
case ':' => TSemicolon
case ' ' | '\r' | '\n' | '\t' => next()
case '"' => //quoted string
var endIx = currentPartIx
while (endIx < currentPart.length && currentPart(endIx) != '"' && currentPart(endIx) != '\n') {
if (currentPart(endIx) == '\\') {
endIx += 1 //to avoid escaped double quotes
}
endIx += 1
}
if (endIx >= currentPart.length || currentPart(endIx) != '"') {
throw new IllegalArgumentException(s"Unclosed string literal: '$currentPart'")
}
currentToken = currentPart.substring(currentPartIx, endIx)
.replaceAll("\\\\\"", "\"")
.replaceAll("\\\\r", "\r")
.replaceAll("\\\\n", "\n")
.replaceAll("\\\\t", "\t")
.replaceAll("\\\\(.)", "$1")
currentPartIx = endIx + 1
TQuotedString
case c => //unquoted string
var endIx = currentPartIx
while (endIx < currentPart.length && Character.isJavaIdentifierPart(currentPart(endIx))) {
endIx += 1
}
currentToken = currentPart.substring(currentPartIx - 1, endIx)
currentPartIx = endIx
TString
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment