net.dankito.utils.resources.ResourceFilesExtractor.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-utils Show documentation
Show all versions of java-utils Show documentation
Some basic utils needed in many projects
package net.dankito.utils.resources
import net.dankito.utils.PackageInfo
import net.dankito.utils.io.FileUtils
import org.slf4j.LoggerFactory
import java.io.*
import java.util.*
import java.util.jar.JarEntry
import java.util.jar.JarFile
import kotlin.concurrent.thread
open class ResourceFilesExtractor {
companion object {
private val log = LoggerFactory.getLogger(ResourceFilesExtractor::class.java)
}
open fun extractAsync(resourceFilesToExtract: List, destinationDirectory: File,
extractionDone: (() -> Unit)? = null) {
thread {
extract(resourceFilesToExtract, destinationDirectory)
extractionDone?.invoke()
}
}
open fun extract(resourceFilesToExtract: List, destinationDirectory: File) {
val absoluteDestinationDirectory = destinationDirectory.absoluteFile
if(absoluteDestinationDirectory.exists() == false) {
absoluteDestinationDirectory.mkdirs()
}
copyResourceFilesToDestination(resourceFilesToExtract, absoluteDestinationDirectory)
}
/***
* [skipAlreadyExtractedFiles]: If set to true: If destination file exists and has the same size as source file, then extracting file is skipped.
* Has been essential for Elster Tax lib that already extracted (and may loaded!) file didn't get replaced.
*/
open fun extractFileFromJar(aClassFromJarFile: Class<*>, fileInJarRelativePath: String, destinationDirectory: File, skipAlreadyExtractedFiles: Boolean = false): File? {
destinationDirectory.mkdirs()
aClassFromJarFile.classLoader.getResource(fileInJarRelativePath)?.let { fileUrl ->
if (fileUrl.protocol == "file") { // in this case there's no need to extract file, we can access it directly
return File(fileUrl.toURI())
}
else if (fileUrl.protocol == "jar") {
PackageInfo().getClassJarPath(aClassFromJarFile)?.let { jarPath ->
return extractFileFromJar(jarPath, fileInJarRelativePath, destinationDirectory, skipAlreadyExtractedFiles)
}
}
}
return null
}
protected open fun extractFileFromJar(jarPath: File, fileInJarRelativePath: String, destinationDirectory: File, skipAlreadyExtractedFiles: Boolean = false): File? {
val jar = JarFile(jarPath)
jar.entries().toList().forEach { entry ->
if (entry.name.equals(fileInJarRelativePath, true)) {
if (entry.isDirectory == false) {
val destinationFile = File(destinationDirectory, fileInJarRelativePath)
extractFileFromJar(jar, entry, destinationFile, skipAlreadyExtractedFiles)
return if (destinationFile.exists()) destinationFile else null
}
}
}
return null
}
open fun extractAllFilesFromJarAsync(aClassFromJarFile: Class<*>, folderInJar: File, destinationDirectory: File,
skipAlreadyExtractedFiles: Boolean = false, extractionDone: (() -> Unit)? = null) {
thread {
extractAllFilesFromJar(aClassFromJarFile, folderInJar, destinationDirectory)
extractionDone?.invoke()
}
}
/***
* [skipAlreadyExtractedFiles]: If set to true: If destination file exists and has the same size as source file, then extracting file is skipped.
* Has been essential for Elster Tax lib that already extracted (and may loaded!) file didn't get replaced.
*/
open fun extractAllFilesFromJar(aClassFromJarFile: Class<*>, folderInJar: File, destinationDirectory: File, skipAlreadyExtractedFiles: Boolean = false) {
destinationDirectory.mkdirs()
PackageInfo().getClassJarPath(aClassFromJarFile)?.let { jarPath ->
log.debug("Jar file: ${jarPath.absolutePath}")
if (jarPath.isFile) {
extractAllFilesFromJar(jarPath, folderInJar, destinationDirectory, skipAlreadyExtractedFiles)
}
else { // when running project in IDE files aren't packaged into a .jar -> copy them from file system to destinationDirectory
extractAllFilesFromFolder(jarPath, folderInJar, destinationDirectory, skipAlreadyExtractedFiles)
}
}
}
protected open fun extractAllFilesFromJar(jarPath: File, folderInJar: File, destinationDirectory: File, skipAlreadyExtractedFiles: Boolean = false) {
val jar = JarFile(jarPath)
val subDirectory = folderInJar.path.replace("\\", "/") // necessary as Windows uses '\' where as paths in jar are separated with '/'
jar.entries().toList().forEach { entry ->
if (entry.name.startsWith(subDirectory)) {
if (entry.isDirectory == false) {
val relativePath = entry.name.substring(subDirectory.length)
val destinationFile = File(destinationDirectory, relativePath)
extractFileFromJar(jar, entry, destinationFile, skipAlreadyExtractedFiles)
}
}
}
}
protected open fun extractFileFromJar(jar: JarFile, entry: JarEntry, destinationFile: File, skipAlreadyExtractedFiles: Boolean) {
if (skipAlreadyExtractedFiles && destinationFile.exists() && entry.size == destinationFile.length()) {
log.debug("Skipping entry ${entry.name} as file with same size already exists in ${destinationFile.parent}")
}
else {
log.debug("Going to copy Jar entry ${entry.name} to $destinationFile")
writeToFile(jar.getInputStream(entry), destinationFile)
}
}
protected open fun extractAllFilesFromFolder(jarPath: File, folder: File, destinationDirectory: File, skipAlreadyExtractedFiles: Boolean = false) {
var sourceFolder = File(jarPath, folder.path)
val fileUtils = FileUtils()
var filesInSourceFolder = fileUtils.getFilesOfDirectory(sourceFolder, folderDepth = Int.MAX_VALUE)
if (filesInSourceFolder == null) {
if (jarPath.name == "classes") {
sourceFolder = File(File(jarPath.parent, "resources"), folder.path)
}
else if (jarPath.path.endsWith("classes/kotlin/test")) {
sourceFolder = File(File(File(jarPath.parentFile.parentFile.parent, "resources"), "test"), folder.path)
}
else if (jarPath.path.endsWith("classes/kotlin/main")) {
sourceFolder = File(jarPath.parentFile.parentFile.parentFile.parentFile, folder.path)
log.debug("Set sourceFolder to ${sourceFolder.absolutePath}")
}
filesInSourceFolder = fileUtils.getFilesOfDirectory(sourceFolder, folderDepth = Int.MAX_VALUE)
}
filesInSourceFolder?.forEach { sourceFile ->
if (sourceFile.isDirectory) {
log.debug("Skipping directory $sourceFile")
return@forEach
}
val relativeFilePath = sourceFile.relativeTo(sourceFolder).path
val destinationFile = File(destinationDirectory, relativeFilePath) // TODO: if this causes now any errors then re-add 'folder.path' again
extractFileFromFolder(sourceFile, destinationFile, skipAlreadyExtractedFiles)
}
}
protected open fun extractFileFromFolder(sourceFile: File, destinationFile: File, skipAlreadyExtractedFiles: Boolean) {
if (skipAnAlreadyExtractedFile(skipAlreadyExtractedFiles, sourceFile, destinationFile)) {
log.debug("Skipping file '${sourceFile.absolutePath}' as file with same size and modification date already exists in ${destinationFile.parent}")
}
else {
log.debug("Writing file $sourceFile to $destinationFile")
writeToFile(FileInputStream(sourceFile), destinationFile)
}
}
protected open fun skipAnAlreadyExtractedFile(skipAlreadyExtractedFiles: Boolean, sourceFile: File, destinationFile: File): Boolean {
return skipAlreadyExtractedFiles
&& destinationFile.exists()
&& sourceFile.length() == destinationFile.length()
&& sourceFile.lastModified() == destinationFile.lastModified()
}
protected open fun copyResourceFilesToDestination(resourceFilesToExtract: List, destinationDirectory: File) {
val classLoader = ResourceFilesExtractor::class.java.classLoader
resourceFilesToExtract.forEach { filename ->
try {
copyResourceFileToDestination(destinationDirectory, filename, classLoader)
} catch(e: Exception) {
log.error("Could not copy resource file $filename to destination directory $destinationDirectory", e)
}
}
}
protected open fun copyResourceFileToDestination(destinationDirectory: File, filename: String,
classLoader: ClassLoader) {
// TODO: what to do if resource couldn't be found?
classLoader.getResourceAsStream(filename)?.let { inputStream ->
val destination = File(destinationDirectory, filename)
writeToFile(inputStream, destination)
}
}
@Throws(Exception::class)
private fun writeToFile(inputStream: InputStream, destinationFile: File) {
try {
destinationFile.parentFile.mkdirs()
} catch (e: Exception) { log.error("Could not create parent directory for $destinationFile")}
var outputStream: OutputStream? = null
try {
outputStream = BufferedOutputStream(FileOutputStream(destinationFile))
inputStream.buffered().copyTo(outputStream, FileUtils.DefaultFileOperationBufferSize)
} catch (e: IOException) {
log.error("Could not write InputStream to file " + destinationFile.absolutePath, e)
throw e
} finally {
try {
outputStream?.flush()
outputStream?.close()
inputStream.close()
} catch (e: IOException) { }
}
}
}