2016-08-22 103 views
1

我正在使用一個csv庫,它接受一個case類並將其變成行以供我閱讀。動態創建案例類

的語法是非常接近File(path).asCsvReader[caseClass]。 鏈接到庫here

然而,問題是我不希望我的數據庫中的表生成我的案例類。我可以接收我的數據庫中的表以及它們列的類型(Int,Long,Double,String等),但我不知道如何動態創建具有該數據的case類,因爲我不知道編譯時的信息。

正是因爲這一點,我不能使用宏要麼因爲我沒有在宏觀的編譯時間知道表數據。

那麼我將如何去動態地創建這種情況下,一旦類我收到的表格數據,然後這種情況下,類傳遞到CSV庫?

+1

您如何看待您在程序中操作這些案例類而不知道它們是什麼樣的?一旦你回答了這個問題,你就知道你想要使用的結構的形狀。 – Alec

+0

我只會訪問傳遞給它們的變量。例如case class blah(val s:String)我只會訪問s變量。 – kylepotts

+0

在這種情況下,使用不同的庫(或同一庫中的不同方法)可能更容易,它提供CSV記錄的通用表示(例如元組或無形'HList')。 – devkat

回答

0

所以基本上你需要一個關於你的案例類的運行時信息?我猜你應該使用ClassTag

import scala.reflect._ 

def asCsvReader[T: ClassTag]: T = { 
    classTag[T].runtimeClass.getDeclaredConstructor(...).newInstance(...) 
    ... 
} 

這將允許您在運行時實例化您的案例類。

由於您可以計算出CSV列的類型,因此您可以在getDeclaredConstructornewInstance方法中提供相應的類型。

+0

對不起,我不太確定這是做什麼。你能解釋一下嗎? – kylepotts

+0

對不起,也許我錯了你的問題。你想生成特定案例類的實例嗎?還是案例類本身?如果您問的是案例類的生成,因爲Scala是靜態類型語言,所以這是不可能的。您無法在運行時生成新成員(或Python中的屬性)。但是如果你想在運行時創建一個由泛型T表示的特定case類的實例,我的建議將對你有用。 – Zyoma

+0

我基本上想生成一個不存在的案例類。 – kylepotts

1

如果您正在使用Scala的2.10,您可以使用類在scala.tools.nsc.interpreter包來完成。請注意,這不再適用於Scala 2.11。我詢問了一個new question,希望我們能得到答案。

在斯卡拉2.10,使用該解釋就可以編譯外部類文件中,幾乎加載它。

的步驟是:

  • 圖出你的類的名稱根據約定
  • 解析您CSV標題就知道字段名和數據類型
  • 生成的情況下,類以上信息寫入磁盤上的文件
  • 加載生成的源文件並使用解釋器編譯它們
  • 類現在可以使用了。

我建立了一個小的演示,應該幫助。

/** 
    * Location to store temporary scala source files with generated case classes 
    */ 
val classLocation: String = "/tmp/dynacode" 

/** 
    * Package name to store the case classes 
    */ 
val dynaPackage: String = "com.example.dynacsv" 

/** 
    * Construct this data based on your data model e.g. see data type for Person and Address below. 
    * Notice the format of header, it can be substituted directly in a case class definition. 
    */ 
val personCsv: String = "PersonData.csv" 
val personHeader: String = "title: String, firstName: String, lastName: String, age: Int, height: Int, gender: Int" 

val addressCsv: String = "AddressData.csv" 
val addressHeader: String = "street1: String, street2: String, city: String, state: String, zipcode: String" 

/** 
    * Utility method to extract class name from CSV file 
    * @param filename CSV file 
    * @return Class name extracted from csv file name stripping out ".ext" 
    */ 
def getClassName(filename: String): String = filename.split("\\.")(0) 

/** 
    * Generate a case class and persist to file 
    * @param file External file to write to 
    * @param className Class name 
    * @param header case class parameters 
    */ 
def writeCaseClass(file: File, className: String, header: String): Unit = { 
    val writer: PrintWriter = new PrintWriter(file) 
    writer.println("package " + dynaPackage) 
    writer.println("case class " + className + "(") 
    writer.println(header) 
    writer.println(") {}") 
    writer.flush() 
    writer.close() 
} 

/** 
    * Generate case class and write to file 
    * @param filename CSV File name (should be named ClassName.csv) 
    * @param header Case class parameters. Format is comma separated: name: DataType 
    * @throws IOException if there is problem writing the file 
    */ 
@throws[IOException] 
private def generateClass(filename: String, header: String) { 
    val className: String = getClassName(filename) 
    val fileDir: String = classLocation + File.separator + dynaPackage.replace('.', File.separatorChar) 
    new File(fileDir).mkdirs 
    val classFile: String = fileDir + File.separator + className + ".scala" 
    val file: File = new File(classFile) 

    writeCaseClass(file, className, header) 
} 

/** 
    * Helper method to search code files in directory 
    * @param dir Directory to search in 
    * @return 
    */ 
def recursiveListFiles(dir: File): Array[File] = { 
    val these = dir.listFiles 
    these ++ these.filter(_.isDirectory).flatMap(recursiveListFiles) 
} 

/** 
    * Compile scala files and keep them loaded in memory 
    * @param classDir Directory storing the generated scala files 
    * @throws IOException if there is problem reading the source files 
    * @return Classloader that contains the compiled external classes 
    */ 
@throws[IOException] 
def compileFiles(classDir: String): AbstractFileClassLoader = { 
    val files = recursiveListFiles(new File(classDir)) 
        .filter(_.getName.endsWith("scala")) 
    println("Loaded files: \n" + files.mkString("[", ",\n", "]")) 

    val settings: GenericRunnerSettings = new GenericRunnerSettings(err => println("Interpretor error: " + err)) 
    settings.usejavacp.value = true 
    val interpreter: IMain = new IMain(settings) 
    files.foreach(f => { 
    interpreter.compileSources(new BatchSourceFile(AbstractFile.getFile(f))) 
    }) 

    interpreter.getInterpreterClassLoader() 
} 

//Test Address class 
def testAddress(classLoader: AbstractFileClassLoader) = { 
    val addressClass = classLoader.findClass(dynaPackage + "." + getClassName(addressCsv)) 
    val ctor = addressClass.getDeclaredConstructors()(0) 
    val instance = ctor.newInstance("123 abc str", "apt 1", "Hello world", "HW", "12345") 
    println("Instantiated class: " + instance.getClass.getCanonicalName) 
    println(instance.toString) 
} 

//Test person class 
def testPerson(classLoader: AbstractFileClassLoader) = { 
    val personClass = classLoader.findClass(dynaPackage + "." + getClassName(personCsv)) 
    val ctor = personClass.getDeclaredConstructors()(0) 
    val instance = ctor.newInstance("Mr", "John", "Doe", 25: java.lang.Integer, 165: java.lang.Integer, 1: java.lang.Integer) 
    println("Instantiated class: " + instance.getClass.getCanonicalName) 
    println(instance.toString) 
} 

//Test generated classes 
def testClasses(classLoader: AbstractFileClassLoader) = { 
    testAddress(classLoader) 
    testPerson(classLoader) 
} 

//Main method 
def main(args: Array[String]) { 
    try { 
    generateClass(personCsv, personHeader) 
    generateClass(addressCsv, addressHeader) 
    val classLoader = compileFiles(classLocation) 
    testClasses(classLoader) 
    } 
    catch { 
    case e: Exception => e.printStackTrace() 
    } 
} 

}

輸出:

Loaded files: 
[/tmp/dynacode/com/example/dynacsv/AddressData.scala, 
/tmp/dynacode/com/example/dynacsv/PersonData.scala] 
Instantiated class: com.example.dynacsv.AddressData 
AddressData(123 abc str,apt 1,Hello world,HW,12345) 
Instantiated class: com.example.dynacsv.PersonData 
PersonData(Mr,John,Doe,25,165,1) 
2

dveim提示在this answer,我補充說,無論是在斯卡拉2.10和2.11的作品,並使用Scala的Toolbox第二方案。不幸的是,生成的案例類在默認包中。

使用工具箱

/** 
    * Helper method to search code files in directory 
    * @param dir Directory to search in 
    * @return 
    */ 
def recursiveListFiles(dir: File): Array[File] = { 
    val these = dir.listFiles 
    these ++ these.filter(_.isDirectory).flatMap(recursiveListFiles) 
} 

/** 
    * Compile scala files and keep them loaded in memory 
    * @param classDir Directory storing the generated scala files 
    * @throws IOException if there is problem reading the source files 
    * @return Map containing Class name -> Compiled Class Reference 
    */ 
@throws[IOException] 
def compileFiles(classDir: String): Map[String, Class[_]] = { 
    val tb = universe.runtimeMirror(getClass.getClassLoader).mkToolBox() 

    val files = recursiveListFiles(new File(classDir)) 
    .filter(_.getName.endsWith("scala")) 
    println("Loaded files: \n" + files.mkString("[", ",\n", "]")) 

    files.map(f => { 
    val src = Source.fromFile(f).mkString 
    val clazz = tb.compile(tb.parse(src))().asInstanceOf[Class[_]] 
    getClassName(f.getName) -> clazz 
    }).toMap 
} 

實例化生成案例類

/** 
    * Generate a case class and persist to file 
    * Can't add package to the external class 
    * @param file External file to write to 
    * @param className Class name 
    * @param header case class parameters 
    */ 
def writeCaseClass(file: File, className: String, header: String): Unit = { 
    val writer: PrintWriter = new PrintWriter(file) 
    writer.println("case class " + className + "(") 
    writer.println(header) 
    writer.println(") {}") 
    writer.println("\nscala.reflect.classTag[" + className + "].runtimeClass") 
    writer.flush() 
    writer.close() 
} 

編譯外部類和使用編譯的類

編譯的類可以從地圖獲得,並且在需要時使用例如

//Test Address class 
def testAddress(map: Map[String, Class[_]]) = { 
    val addressClass = map("AddressData") 
    val ctor = addressClass.getDeclaredConstructors()(0) 
    val instance = ctor.newInstance("123 abc str", "apt 1", "Hello world", "HW", "12345") 
    //println("Instantiated class: " + instance.getClass.getName) 
    println(instance.toString) 
}