/* Copyright 2010 Aaron J. Radke 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 cc.drx // import scala.language.dynamics trait StringMap{ //-----required interfaces /** the key get interface to parse as you would like*/ def getString(key:String):Option[String] //TODO maybe needs a different name to support backward compatibility /** list of the available keys (the sort order provides listing features)*/ def keys:List[String] //----default interfaces get and split can be overriden for optimzation def get[T](key:String) (implicit parser:Parsable[T]):Option[T] = (this getString key map parser.apply) def get[A,B](keyA:String,keyB:String) (implicit parserA:Parsable[A], parserB:Parsable[B]):Option[(A,B)] = for(a <- this getString keyA; b <- this getString keyB) yield ( parserA(a), parserB(b) ) def get[A,B,C](keyA:String,keyB:String,keyC:String) (implicit parserA:Parsable[A], parserB:Parsable[B], parserC:Parsable[C]):Option[(A,B,C)] = for(a <- this getString keyA; b <- this getString keyB; c <- this getString keyC) yield ( parserA(a), parserB(b), parserC(c) ) def get[A,B,C,D](keyA:String,keyB:String,keyC:String,keyD:String) (implicit parserA:Parsable[A], parserB:Parsable[B], parserC:Parsable[C], parserD:Parsable[D]):Option[(A,B,C,D)] = for(a <- this getString keyA; b <- this getString keyB; c <- this getString keyC; d <- this getString keyD) yield ( parserA(a), parserB(b), parserC(c), parserD(d) ) /** split */ def split[T](key:String,sep:String)(implicit parser:Parsable[T]):Vector[T] = getString(key) map { parser.split(_,sep) } getOrElse Vector[T]() def split[T](key:String)(implicit parser:Parsable[T]):Vector[T] = getString(key) map { parser.split(_) } getOrElse Vector[T]() /**default as a path separator of '.' but this is configurable (over-ridable) */ def mkPath(keys:Seq[String]):String = keys.map{_.replace("/",".")}.mkString(".") 2017-07-30("use mkPath with a list of Keys instead", "dj") def mkPath(parent:String,child:String):String = parent + "." + child.replace("/",".") // final def apply[T](key:String)(implicit parser:Parsable[T]):Option[T] = (this get key).get // 2017-07-30("dangerous so, insead of this(key) use more explicit this.get(key) .as[Int] use direct this.get[Int].get","v0.2.13") //----derived classes, but overidable (not final) def toList:List[(String,String)] = for(k <- keys; v <- getString(k)) yield (k,v) //----derived classes final def toMap:Map[String,String] = toList.toMap /**dangerous option, either use modadic or defaults with getOrElse*/ final def apply[T](key:String)(implicit parser:Parsable[T]):T = { val o = get(key) if(o.isEmpty) throw Exception.NoSuchKey(key) else o.get } // def split[T](key:String)(implicit parser:Parsable[T]):Vector[T] = splitWithSep(key, """\s+""") final def contains(key:String):Boolean = getString(key).isDefined def /(name:String):StringMap.Scoped = StringMap.Scoped(this, name) //FIXME conform to the path notation def /(symbol:Symbol):StringMap.Scoped = this / symbol.name def mergeWith(that:StringMap):StringMap = StringMap.Merged(this,that) /*where the default specifies the parse type*/ final def getOrElse[T](key:String,default:T)(implicit parser:Parsable[T]):T = this get key getOrElse default } object StringMap{ def apply(map:Map[String,String]):StringMap = new StringMap{ private lazy val sortedKeys = map.keys.toList.sorted def getString(key:String) = map.get(key) def keys = sortedKeys } def collect(pf: PartialFunction[String,String]):StringMap = new StringMap{ def getString(key:String) = pf lift key def keys = Nil } case class Merged(a:StringMap,b:StringMap) extends StringMap{ def getString(key:String) = a.getString(key) orElse b.getString(key) def keys = a.keys ++ b.keys } /**scoped to lazily build up a key and provide nice syntax for defaults*/ case class Scoped(ctx:StringMap, key:String) extends StringMap{ override def /(name:String) = Scoped(ctx, ctx.mkPath(List(key,name)) ) def getString(subkey:String):Option[String] = ctx get ctx.mkPath(List(key,subkey)) def keys:List[String] = { //TODO add a cache here .. val pre = key + "." val preSize = pre.size ctx.keys.filter(_ startsWith pre).map{_ drop preSize}.toList } def get[T](implicit parser:Parsable[T]):Option[T] = ctx get key 2017-07-30("dangerous so, insead of this.as[Int] use direct this.get[Int].get","v0.2.13") def as[T](implicit parser:Parsable[T]):T = ctx.get(key).get //Note: dangerous def split[T](implicit parser:Parsable[T]):Vector[T] = ctx.split(key) //autosplit override def split[T](sep:String)(implicit parser:Parsable[T]):Vector[T] = ctx.split(key,sep) //specify a split /** A syntax sugar that coincides nicely with the stringMap key lookup to simultaneously provide a default and implicit value to type inferencing magic*/ //TODO switch this to be macro based like || is now def |[T](default:T)(implicit parser:Parsable[T]):T = ctx.getOrElse(key,default) def getOrElse[T](default:T)(implicit parser:Parsable[T]):T = ctx.getOrElse(key,default) } /* /** provides A syntax sugar like: kson.field.name.as[String] instead of kson[String]("field.name") */ trait StringMapDynamic extends StringMap with Dynamic{ def selectDynamic(name:String):StringMapScoped = this / name //this guy is dangerous and a number of runtime bugs that are normally caught by the compiler happened } */ /**Cached provides a nice way to make a fast lookup and parse inline*/ trait Cached extends StringMap{ //---data cache private val cache = TrieMap.empty[String,Any] private val stringCache = TrieMap.empty[String,String] private val splitCache = TrieMap.empty[String,Any] private val nullCache = TrieMap.empty[String,None.type] //-----required interfaces def getStringNoCache(key:String):Option[String] //-----optimized interfaces /**specialized get method for strings so parsing is not required (this also provides a cache mechanism without type encoding)*/ final def getString(key:String):Option[String] = { if(stringCache contains key) Some(stringCache(key)) else if (nullCache contains key) None else { val res = getStringNoCache(key) //this is the single line of logic wrapped by the caching logic if(res.isDefined) stringCache(key) = res.get else nullCache(key) = None res } } /**get a key while parsing it via to a viable type inference value and caching it as so*/ final override def get[T](key:String)(implicit parser:Parsable[T]):Option[T] = { if(cache contains key) Some(cache(key).asInstanceOf[T]) else if (nullCache contains key) None else { // val got = getFromPath(key,0)//TODO init with zero but can this be used elsewhere? val got = getStringNoCache(key) val res = got map parser.apply //this is the single line of logic wrapped by the caching logic if(res.isDefined) cache(key) = res.get else nullCache(key) = None res } } private def splitToCache[T](key:String, splitter:String => Vector[T]):Vector[T] = { if(splitCache contains key) splitCache(key).asInstanceOf[Vector[T]] else if (nullCache contains key) Vector[T]() else { val res:Vector[T] = getString(key) map { splitter } getOrElse Vector[T]() splitCache(key) = res res } } final override def split[T](key:String)(implicit parser:Parsable[T]):Vector[T] = splitToCache(key, parser.split(_)) final override def split[T](key:String,sep:String)(implicit parser:Parsable[T]):Vector[T] = splitToCache(key, parser.split(_,sep)) /* //override because this is a fast cached implementation final override def split[T](key:String,sep:String=Parse.defaultSplitSep)(implicit parser:Parsable[T]):Vector[T] = { if(splitCache contains key) splitCache(key).asInstanceOf[Vector[T]] else if (nullCache contains key) Vector[T]() else { val res:Vector[T] = getString(key) map { parser.split(_,sep) } getOrElse Vector[T]() splitCache(key) = res res } } */ } }