2016-08-22 105 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) 
}