0

I want to use the Apache POI library in a Scala project.

How can I convert a scala.io.BufferedSource type to a java.io.File type?

  val file = Source.fromResource("resource.file") // is type scala.io.BufferedSource
  // how can I do the conversion here
  val workbook = WorkbookFactory.create(file) // requires type java.io.File

This works, but I want to avoid specifying the folder path since the folder structure is handled by sbt convention:

  val file = new File("src/main/resources/resource.file")
  val workbook = WorkbookFactory.create(file)

Thank you for your time

Zhao Li
  • 4,936
  • 8
  • 33
  • 51
  • 3
    Use the version that reads from an `InputStream`: http://poi.apache.org/apidocs/dev/org/apache/poi/ss/usermodel/WorkbookFactory.html#create-java.io.InputStream- and check this: https://stackoverflow.com/questions/793213/getting-the-inputstream-from-a-classpath-resource-xml-file – Luis Miguel Mejía Suárez Nov 16 '21 at 19:08
  • Thanks for the links. I was not able to get an input stream using any of the below statements: Using(Source.fromFile("resource.file")) { source => source.mkString } – Zhao Li Nov 16 '21 at 19:43
  • val file = getClass.getResourceAsStream("resource.file") – Zhao Li Nov 16 '21 at 19:44
  • val file = ClassLoader.getResourceAsStream("resource.file") – Zhao Li Nov 16 '21 at 19:44
  • sorry, I'm not familiar enough with Scala and Java to fill in the missing pieces... – Zhao Li Nov 16 '21 at 19:45
  • 2
    You could consider https://github.com/norbert-radyk/spoiwo – PJ Fanning Nov 16 '21 at 20:03
  • 2
    @Zhao - Scala supports all the Java classes - you can just create a java.io.FileInputStream if that better suits using POI - you don't have to use scala.io.BufferedSource – PJ Fanning Nov 16 '21 at 20:06
  • Thanks @PJFanning. I wanted to avoid specifying the folder path `src/main/resources/` and thought `scala.io.Source` would be a better fit. But now I'm regretting going down that path. – Zhao Li Nov 16 '21 at 20:07
  • 2
    @ZhaoLi `val file = getClass.getResourceAsStream("resource.file")` should return an `InputStream` and if you look at the [`Source` code](https://github.com/scala/scala/blob/v2.13.7/src/library/scala/io/Source.scala#L180) you will see that it does something very similar. If you please the whole exact code should be: `val file = Thread.currentThread.getContextClassLoader.getResourceAsStream("resource.file")` – Luis Miguel Mejía Suárez Nov 16 '21 at 20:53

1 Answers1

4

Note that the folder structure is not handled by sbt.

What happens is that the contents of src/main/resources are shipped into the JAR and are available in your classpath as a resource.

If you do something along the lines of what Source.fromResource does, you should be able to get what you need.

Here is the code snippet for reference:

  /** Reads data from a classpath resource, using either a context classloader (default) or a passed one.
   *
   *  @param  resource     name of the resource to load from the classpath
   *  @param  classLoader  classloader to be used, or context classloader if not specified
   *  @return              the buffered source
   */
  def fromResource(resource: String, classLoader: ClassLoader = Thread.currentThread().getContextClassLoader())(implicit codec: Codec): BufferedSource =
    Option(classLoader.getResourceAsStream(resource)) match {
      case Some(in) => fromInputStream(in)
      case None     => throw new FileNotFoundException(s"resource '$resource' was not found in the classpath from the given classloader.")
    }

This code is found on GitHub and here is the link for a recent HEAD: https://github.com/scala/scala/blob/8a2cf63ee5bad8c8c054f76464de0e10226516a0/src/library/scala/io/Source.scala#L174-L184

In principle, you can load the resource as a stream using the Java API and pass it to the WorkbookFactory.create static method overload that takes an InputStream as an input that someone mentioned in a comment.

Something along the lines of

def maybeResourceAt(path: String): Option[InputStream] =
  Option(
    Thread.
      currentThread().
      getContextClassLoader().
      getResourceAsStream(path)
  )

val input = maybeResourceAt("resource.file").getOrElse(sys.error("oh noes"))
val workbook = WorkbookFactory.create(input)

should get the job done.

I'm not sure who's in charge of closing the InputStream. Intuitively I would say that create should consume it entirely but double check with the library's documentation to be sure.

stefanobaghino
  • 11,253
  • 4
  • 35
  • 63