/MongoImplicits.scala Secret
Created
February 21, 2017 06:11
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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