All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.dankito.utils.resources.ResourceFilesExtractor.kt Maven / Gradle / Ivy

There is a newer version: 1.0.20
Show newest version
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) { }
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy