[add] added full yml parsing to scala collection objects
- added a simple parsing test - added conversion utils for Any to Map and nested objects - added a quick indented printout util for scala collection objects
This commit is contained in:
@@ -1,17 +1,120 @@
|
||||
package scim
|
||||
package lib
|
||||
|
||||
// java imports
|
||||
import java.io.{File, FileInputStream}
|
||||
|
||||
// scala imports
|
||||
import scala.collection.convert.wrapAll._
|
||||
import scala.collection.mutable
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
// external imports
|
||||
import org.yaml.snakeyaml.Yaml
|
||||
|
||||
package object printUtil {
|
||||
def indentCollection(s: String) : String = {
|
||||
var indent = 0
|
||||
var skip = false
|
||||
var bracket = false
|
||||
var output = ""
|
||||
|
||||
def closureIndentCollection(c: Char) : Unit = {
|
||||
if(c == '(') {
|
||||
indent += 1
|
||||
output += "(\n" + "\t" * indent
|
||||
} else if(c == ')') {
|
||||
indent -= 1
|
||||
if(bracket) output += "\n" + "\t" * indent
|
||||
output += ")"
|
||||
bracket = true
|
||||
} else if(skip) {
|
||||
skip = false
|
||||
} else if(c == ',') {
|
||||
skip = true
|
||||
bracket = false
|
||||
output += "," + "\n" + "\t" * indent
|
||||
} else {
|
||||
output += c.toString
|
||||
}
|
||||
}
|
||||
|
||||
s.foreach(c => closureIndentCollection(c))
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
package object convertUtil {
|
||||
def javaArrayListToSeq(javaArray: Any) : Option[Seq[Any]] = {
|
||||
javaArray match {
|
||||
/* While LinearSeq(List) for array.length == 2 is faster than than IndexedSeq(Vector) when accessed via tail/head
|
||||
* this would convert a lot of elements to List that are accessed via foreach and not tail/head lookup
|
||||
* as we don't know what they're going to be used for at this point, it makes more sense to just use vectors
|
||||
* https://docs.scala-lang.org/tutorials/FAQ/collections.html
|
||||
* https://docs.scala-lang.org/overviews/collections/performance-characteristics.html
|
||||
*/
|
||||
case array: java.util.ArrayList[_] => Some(array.asScala.toIndexedSeq)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
package object mapUtil {
|
||||
|
||||
def javaHashMapToScalaMap[K, V](javaMap: java.util.LinkedHashMap[_, _]) : Map[K, V] = {
|
||||
mapAsScalaMap(javaMap).toMap.asInstanceOf[Map[K, V]]
|
||||
}
|
||||
|
||||
def anyToNestedMap[K](map: Any, depthLimit: Int = 100) : Map[K, Any] = {
|
||||
|
||||
def closureAnyToNestedMap(nestedValue: Any, depth: Int = 0) : Any = {
|
||||
if(depth > depthLimit) return nestedValue // crude way to avoid a circular map
|
||||
anyToMap[K, Any](nestedValue) match {
|
||||
case Some(nestedMap) => nestedMap.map(kv => kv._1 -> closureAnyToNestedMap(kv._2, depth + 1))
|
||||
case _ =>
|
||||
convertUtil.javaArrayListToSeq(nestedValue) match {
|
||||
case Some(nestedSeq) => nestedSeq.map(v => closureAnyToNestedMap(v, depth + 1))
|
||||
case _ => nestedValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anyToMap[K, Any](map) match {
|
||||
case Some(nestedMap: Map[K, Any]) => nestedMap.map(kv => (kv._1 -> closureAnyToNestedMap(kv._2)))
|
||||
case _ => Map[K, Any]() // if no map was given
|
||||
}
|
||||
}
|
||||
|
||||
def anyToMutableMap[K, V](mapAny: Any) : Option[mutable.Map[K, V]] = {
|
||||
// TODO: use TypeTag to work around type erasure to check for T[K, V]
|
||||
// http://squidarth.com/scala/types/2019/01/11/type-erasure-scala.html
|
||||
mapAny match {
|
||||
case map: Map[_, _] => Some(mutable.Map() ++ map.asInstanceOf[Map[K, V]])
|
||||
case map: mutable.Map[_, _] => Some(map.asInstanceOf[mutable.Map[K, V]])
|
||||
case map: java.util.LinkedHashMap[_, _] => Some(mapAsScalaMap(map).asInstanceOf[mutable.Map[K, V]])
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
def anyToMap[K, V](mapAny: Any) : Option[Map[K, V]] = {
|
||||
// TODO: use TypeTag to work around type erasure to check for T[K, V]
|
||||
// http://squidarth.com/scala/types/2019/01/11/type-erasure-scala.html
|
||||
mapAny match {
|
||||
case map: Map[_, _] => Some(map.asInstanceOf[Map[K, V]])
|
||||
case map: mutable.Map[_, _] => Some(map.toMap.asInstanceOf[Map[K, V]])
|
||||
case map: java.util.LinkedHashMap[_, _] => Some(javaHashMapToScalaMap[K, V](map))
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package object yml {
|
||||
val yaml = new Yaml
|
||||
// TODO: handle errors (invalid file/format)
|
||||
def parse(file:String) : Map[Nothing, Nothing] = {
|
||||
def parse(file:String) : Map[String, Any] = {
|
||||
val fis = new FileInputStream(new File(file))
|
||||
val list = mapAsScalaMap(yaml.load(fis))
|
||||
return list.toMap
|
||||
mapUtil.anyToNestedMap[String](yaml.load(fis))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ import scim.lib._
|
||||
object Main {
|
||||
def main(args: Array[String]): Unit = {
|
||||
val config = "docs/wiki/config/config.yml"
|
||||
val list = yml.parse(config)
|
||||
println(list)
|
||||
val list: Map[String, Any] = yml.parse(config)
|
||||
|
||||
println(printUtil.indentCollection(list.toString()).toString)
|
||||
}
|
||||
}
|
||||
28
src/test/scala/scim/TestSuite.scala
Normal file
28
src/test/scala/scim/TestSuite.scala
Normal file
@@ -0,0 +1,28 @@
|
||||
package scim
|
||||
|
||||
import org.scalatest.FunSuite
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatestplus.junit.JUnitRunner
|
||||
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class TestSuite extends FunSuite {
|
||||
|
||||
var testObject = "scim.lib.yml.parse"
|
||||
test(testObject + ": parsing test") {
|
||||
// test configuration
|
||||
val config = "src/test/scala/scim/files/test.yml"
|
||||
val mapCheck: Map[String, Any] =
|
||||
Map(
|
||||
"simulation" -> Map("duration" -> 500, "tickfactor" -> 10, "loop" -> true, "loopcount" -> 20),
|
||||
"sensors" -> Map("blocks" -> List(Map("start" -> Vector(8.2, 1, 5), "end" -> List(3, 3), "count" -> 30)))
|
||||
)
|
||||
// sanity check, to prevent a false positive if both are empty
|
||||
assert(!mapCheck.isEmpty, testObject + ": check list is empty")
|
||||
|
||||
// test
|
||||
val mapTest: Map[String, Any] = scim.lib.yml.parse(config)
|
||||
// Note: will assert to true even if numerical type doesnt match (Int, Float, Double; eg: 5.0 == 5 -> true)
|
||||
assert(mapTest == mapCheck, testObject + ": test map is not equal to check map")
|
||||
}
|
||||
|
||||
}
|
||||
10
src/test/scala/scim/files/test.yml
Normal file
10
src/test/scala/scim/files/test.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
simulation:
|
||||
duration: 500
|
||||
tickfactor: 10
|
||||
loop: true
|
||||
loopcount: 20
|
||||
sensors:
|
||||
blocks:
|
||||
- start: [8.2, 1, 5]
|
||||
end: [3, 3]
|
||||
count: 30
|
||||
Reference in New Issue
Block a user