From cddecb2414bb707a646114cdca713459ab4de9a0 Mon Sep 17 00:00:00 2001 From: Na Lee Ha Date: Mon, 18 Dec 2023 13:40:49 +0000 Subject: [PATCH] #1052 refactored to separated generic file reader vs basket specific logic --- .../core/module/basket/csv/BasketLoader.scala | 56 +++++++++++++++++ .../core/module/basket/csv/CsvContent.scala | 17 +++++ .../core/module/basket/csv/FileLoader.scala | 31 +++++++++ .../org/finos/vuu/csv/BasketLoaderTests.scala | 63 +++++++++++++++++++ 4 files changed, 167 insertions(+) create mode 100644 example/basket/src/main/scala/org/finos/vuu/core/module/basket/csv/BasketLoader.scala create mode 100644 example/basket/src/main/scala/org/finos/vuu/core/module/basket/csv/CsvContent.scala create mode 100644 example/basket/src/main/scala/org/finos/vuu/core/module/basket/csv/FileLoader.scala create mode 100644 example/basket/src/test/scala/org/finos/vuu/csv/BasketLoaderTests.scala diff --git a/example/basket/src/main/scala/org/finos/vuu/core/module/basket/csv/BasketLoader.scala b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/csv/BasketLoader.scala new file mode 100644 index 000000000..5b1984c76 --- /dev/null +++ b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/csv/BasketLoader.scala @@ -0,0 +1,56 @@ +package org.finos.vuu.core.module.basket.csv + +import com.typesafe.scalalogging.StrictLogging + +import scala.util.control.NonFatal + +class BasketLoader(resourcePath: Option[String] = None) extends StrictLogging { + def loadBasketIds(): Array[String] = { + FileLoader.getFileNames(getPath(resourcePath), ".csv") + .map(fileName => "." + fileName.replace(".csv", "").toUpperCase) + } + + def loadConstituents(basketId: String): Array[Map[String, Any]] = { + try { + val csvFiles = FileLoader.getFiles(getPath(resourcePath), getBasketFileName(basketId)) + + if (csvFiles.isEmpty) { + logger.error(s"Failed to find constituents file for $basketId") + Array.empty + } + else { + val csvFile = csvFiles(0) + logger.info("Loading basket static:" + basketId + "(" + csvFile + ")") + val csvContent = FileLoader.readCsvContent(csvFile) + + logger.info(s"Found ${csvContent.dataRows.length} constituents for basket $basketId") + + csvContent.dataRows.map(row => toConstituent(csvContent, row)) + } + } + catch { + case NonFatal(t) => logger.error(s"Failed to parse constituents for $basketId", t) + Array.empty + } + } + + private def toConstituent(csvContent: CsvContent, row: Array[String]) = { + Map[String, Any]( + "Symbol" -> csvContent.getValue("Symbol", row), + "Last Trade" -> csvContent.getValue("Last Trade", row), + "Name" -> csvContent.getValue("Name", row), + "Weighting" -> csvContent.getValueAsDouble("Weighting", row, 0.0D), + "Volume" -> csvContent.getValue("Volume", row), + "Change" -> csvContent.getValue("Change", row) + ) + } + + private def getBasketFileName(basketId: String) = { + basketId.replace(".", "").toLowerCase + ".csv" + } + + private def getPath(resourcePath: Option[String] = None): String = { + if (resourcePath.isDefined) resourcePath.get + else getClass.getResource("/static").getPath + } +} diff --git a/example/basket/src/main/scala/org/finos/vuu/core/module/basket/csv/CsvContent.scala b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/csv/CsvContent.scala new file mode 100644 index 000000000..f589b7776 --- /dev/null +++ b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/csv/CsvContent.scala @@ -0,0 +1,17 @@ +package org.finos.vuu.core.module.basket.csv + +class CsvContent(data: Array[Array[String]]) { + + private val header = data(0) + val dataRows: Array[Array[String]] = data.tail + + def getValue(headerName: String, row: Array[String]) = { + val index = header.indexOf(headerName) + if (row.length > index && index > -1) row(index) else null + } + + def getValueAsDouble(headerName: String, row: Array[String], defaultValue: Double) = { + val x = getValue(headerName, row) + if (x == null) defaultValue else x.toDouble + } +} diff --git a/example/basket/src/main/scala/org/finos/vuu/core/module/basket/csv/FileLoader.scala b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/csv/FileLoader.scala new file mode 100644 index 000000000..028ccd3f8 --- /dev/null +++ b/example/basket/src/main/scala/org/finos/vuu/core/module/basket/csv/FileLoader.scala @@ -0,0 +1,31 @@ +package org.finos.vuu.core.module.basket.csv + +import java.io.File +import scala.io.Source + +object FileLoader { + def getFileNames(folderPath: String, extensionFilter: String): Array[String] = { + getFiles(folderPath) + .filter(_.getName.endsWith(extensionFilter)) + .map(file => file.getName) + } + + def getFiles(folderPath: String, fileName: String): Array[File] = { + getFiles(folderPath) + .filter(_.getName.equals(fileName)) + } + + def readCsvContent(file: File): CsvContent = { + val bufferedSource = Source.fromFile(file) + val csv = for (line <- bufferedSource.getLines) yield line.split(",").map(_.trim) + val array = csv.toArray + bufferedSource.close + new CsvContent(array) + } + + private def getFiles(folderPath: String): Array[File] = { + val dir = new File(folderPath) + if (dir.listFiles == null) Array.empty + else dir.listFiles.filter(_.isFile) + } +} diff --git a/example/basket/src/test/scala/org/finos/vuu/csv/BasketLoaderTests.scala b/example/basket/src/test/scala/org/finos/vuu/csv/BasketLoaderTests.scala new file mode 100644 index 000000000..41a0dd780 --- /dev/null +++ b/example/basket/src/test/scala/org/finos/vuu/csv/BasketLoaderTests.scala @@ -0,0 +1,63 @@ +package org.finos.vuu.csv + +import org.finos.vuu.core.module.basket.csv.BasketLoader +import org.scalatest.featurespec.AnyFeatureSpec +class BasketLoaderTests extends AnyFeatureSpec { + + Feature("Basket ids loading Test Case") { + + Scenario("Can successfully get basket ids from files") { + val testResourcePath = this.getClass.getResource("/constituents").getPath + val basketLoader = new BasketLoader(Some(testResourcePath)) + val basketIds = basketLoader.loadBasketIds() + + assert(basketIds.length == 2) + assert(basketIds.contains(".FTSE100")) + assert(basketIds.contains(".FTSEWITHERROR")) + } + + Scenario("when no file found return empty") { + val testResourcePath = this.getClass.getResource("/constituents").getPath + "/doesNotExist" + val basketLoader = new BasketLoader(Some(testResourcePath)) + val basketIds = basketLoader.loadBasketIds() + + assert(basketIds.length == 0) + } + } + + Feature("Basket constituent loading Test Case") { + + val testResourcePath = this.getClass.getResource("/constituents").getPath + val basketLoader = new BasketLoader(Some(testResourcePath)) + + Scenario("Can successfully load and parse basket constituents") { + + val constituents = basketLoader.loadConstituents(".FTSE100") + + assert(constituents.length == 3) + val firstRow = constituents.head + assert(firstRow("Symbol") == "AAL.L") + assert(firstRow("Last Trade") == "436.35") + assert(firstRow("Name") == "Anglo American PLC") + assert(firstRow("Weighting") == 0.0278736825813547) + assert(firstRow("Volume") == "5799089") + assert(firstRow("Change") == "5.35") + } + + Scenario("When parsing basket constituents fails return empty") { + + val constituents = basketLoader.loadConstituents(".FTSEWithError") + + assert(constituents.length == 0) + } + + + Scenario("When no matching basket constituents file return empty") { + + val constituents = basketLoader.loadConstituents(".NoSuchFile") + + assert(constituents.length == 0) + } + + } +} \ No newline at end of file