javakotlinfilezip4j

Got error "inappropriate format" when returning a file inside of a try block


I am trying to create a password-protected zip file using zip4j that contains several files. This is the code I have so far

fun createZipFile(filename: String, vararg containers: Pair<String, ByteArray?>): ByteArray {
     val out = ByteArrayOutputStream()
     val zipFile = ZipOutputStream(out, passwordGenerator.generate());
     try {
         for ((name, file) in containers.toList()) {
             if (file != null) {
                 val parameters = createZipParameters(name)
                 zipFile.putNextEntry(parameters)
                 zipFile.write(file)
                 zipFile.closeEntry()
             }
         }
         // return here cause error
         return out.toByteArray()
     }
     finally {
         zipFile.close()
         out.close()
     }
 }

I received an "inappropriate format" error when I tried to open the file. However, when I made a small change and moved the return statement after the finally block, it worked fine

   // return out.toByteArray()
}
finally {
   zipFile.close()
   out.close()
}
return out.toByteArray()

Can you explain why this change made a difference and why the original code did not work?


Solution

  • The crucial thing you're missing is that you have heard an oversimplification that is incorrect and has now led you astray. What you think you know/heard is:

    But that is incorrect. The correct thought is:

    As in, first out.toByteArray() is evaluated, then the finally block runs (closing the zip stream, which causes it to write the zip header, because in zip files, those are at the end), then the evaluated-before-running-the-finally byte array is returned. Which doesn't have that header because it didn't yet when it was created. Whereas in your second snippet, the zip stream writes its header out prior to evaluating out.toByteArray().

    For what its worth, try/finally is useless here - none of these things are system resources that demand that you safely close them. The code is a lot simpler to read if you just make it:

    fun createZipFile(filename: String, vararg containers: Pair<String, ByteArray?>): ByteArray {
         val out = ByteArrayOutputStream()
         val zipFile = ZipOutputStream(out, passwordGenerator.generate());
         for ((name, file) in containers.toList()) {
             if (file != null) {
                 val parameters = createZipParameters(name)
                 zipFile.putNextEntry(parameters)
                 zipFile.write(file)
                 zipFile.closeEntry()
             }
         }
         zipFile.close()
         return out.toByteArray()
     }
    

    A few notes on the above: