Archive.kt revision 5dd5f2ff288862510dc21dfdf4467c88acfd4d52
194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis/*
294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis * Copyright (C) 2017 The Android Open Source Project
394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis *
494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis * Licensed under the Apache License, Version 2.0 (the "License");
594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis * you may not use this file except in compliance with the License.
694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis * You may obtain a copy of the License at
794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis *
894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis *       http://www.apache.org/licenses/LICENSE-2.0
994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis *
1094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis * Unless required by applicable law or agreed to in writing, software
1194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis * distributed under the License is distributed on an "AS IS" BASIS,
1294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis * See the License for the specific language governing permissions and
1494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis * limitations under the License
1594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis */
1694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
1794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlispackage android.support.tools.jetifier.core.archive
1894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
1994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlisimport android.support.tools.jetifier.core.utils.Log
2094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlisimport java.io.BufferedOutputStream
21d7b0788f3bf58fc26a936c2426ef1214ffb3a180Filip Pavlisimport java.io.File
2294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlisimport java.io.FileInputStream
2394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlisimport java.io.FileOutputStream
2494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlisimport java.io.IOException
2594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlisimport java.io.InputStream
2694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlisimport java.io.OutputStream
2794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlisimport java.nio.file.Files
2894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlisimport java.nio.file.Path
299f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlisimport java.nio.file.Paths
3094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlisimport java.util.zip.ZipEntry
3194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlisimport java.util.zip.ZipInputStream
3294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlisimport java.util.zip.ZipOutputStream
3394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
3494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis/**
3594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis * Represents an archive (zip, jar, aar ...)
3694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis */
3794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlisclass Archive(
389f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis        override val relativePath: Path,
3994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        val files: List<ArchiveItem>)
4094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    : ArchiveItem {
4194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
4294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    companion object {
4394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        /** Defines file extensions that are recognized as archives */
44780463929260213620120fd7ea7a3d8c225c2a9cFilip Pavlis        val ARCHIVE_EXTENSIONS = listOf(".jar", ".zip", ".aar")
4594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
4694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        const val TAG = "Archive"
4794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    }
4894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
499f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis    override val fileName: String = relativePath.fileName.toString()
509f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis
5149f7010e84f8819274924817443b26fc3a117e22Filip Pavlis    private var targetPath: Path = relativePath
5249f7010e84f8819274924817443b26fc3a117e22Filip Pavlis
5360486c575581dcc40605ea91e1062afa30598e36Filip Pavlis    override val wasChanged: Boolean
5460486c575581dcc40605ea91e1062afa30598e36Filip Pavlis        get() = files.any { it.wasChanged }
5560486c575581dcc40605ea91e1062afa30598e36Filip Pavlis
5649f7010e84f8819274924817443b26fc3a117e22Filip Pavlis    /**
5749f7010e84f8819274924817443b26fc3a117e22Filip Pavlis     * Sets path where the file should be saved after transformation.
5849f7010e84f8819274924817443b26fc3a117e22Filip Pavlis     */
5949f7010e84f8819274924817443b26fc3a117e22Filip Pavlis    fun setTargetPath(path: Path) {
6049f7010e84f8819274924817443b26fc3a117e22Filip Pavlis        targetPath = path
6149f7010e84f8819274924817443b26fc3a117e22Filip Pavlis    }
6249f7010e84f8819274924817443b26fc3a117e22Filip Pavlis
6394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    override fun accept(visitor: ArchiveItemVisitor) {
6494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        visitor.visit(this)
6594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    }
6694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
6794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    @Throws(IOException::class)
681e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston    fun writeSelfToDir(outputDirPath: Path): File {
699f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis        val outputPath = Paths.get(outputDirPath.toString(), fileName)
709f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis
711e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston        return writeSelfToFile(outputPath)
721e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston    }
731e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston
7449f7010e84f8819274924817443b26fc3a117e22Filip Pavlis    fun writeSelf(): File {
7549f7010e84f8819274924817443b26fc3a117e22Filip Pavlis        return writeSelfToFile(targetPath)
7649f7010e84f8819274924817443b26fc3a117e22Filip Pavlis    }
7749f7010e84f8819274924817443b26fc3a117e22Filip Pavlis
781e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston    @Throws(IOException::class)
791e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston    fun writeSelfToFile(outputPath: Path): File {
809f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis        if (Files.exists(outputPath)) {
819f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis            Log.i(TAG, "Deleting old output file")
829f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis            Files.delete(outputPath)
839f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis        }
849f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis
859f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis        // Create directories if they don't exist yet
861e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston        Files.createDirectories(outputPath.parent)
8794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
889f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis        Log.i(TAG, "Writing archive: %s", outputPath.toUri())
89d7b0788f3bf58fc26a936c2426ef1214ffb3a180Filip Pavlis        val file = outputPath.toFile()
9094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        Files.createFile(outputPath)
91d7b0788f3bf58fc26a936c2426ef1214ffb3a180Filip Pavlis        val stream = BufferedOutputStream(FileOutputStream(file))
9294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        writeSelfTo(stream)
9394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        stream.close()
94d7b0788f3bf58fc26a936c2426ef1214ffb3a180Filip Pavlis        return file
9594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    }
9694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
9794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    @Throws(IOException::class)
9894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    override fun writeSelfTo(outputStream: OutputStream) {
9994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        val out = ZipOutputStream(outputStream)
10094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
10194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        for (file in files) {
10294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            Log.d(TAG, "Writing file: %s", file.relativePath)
10394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
1049f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis            val entry = ZipEntry(file.relativePath.toString())
10594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            out.putNextEntry(entry)
10694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            file.writeSelfTo(out)
10794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            out.closeEntry()
10894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        }
10994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        out.finish()
11094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    }
11194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
11294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    object Builder {
11394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
1145dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis        /**
1155dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis         * @param recursive Whether nested archives should be also extracted.
1165dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis         */
11794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        @Throws(IOException::class)
1185dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis        fun extract(archiveFile: File, recursive: Boolean = true): Archive {
119d7b0788f3bf58fc26a936c2426ef1214ffb3a180Filip Pavlis            Log.i(TAG, "Extracting: %s", archiveFile.absolutePath)
12094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
121d7b0788f3bf58fc26a936c2426ef1214ffb3a180Filip Pavlis            val inputStream = FileInputStream(archiveFile)
12294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            inputStream.use {
1235dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis                return extractArchive(it, archiveFile.toPath(), recursive)
12494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            }
12594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        }
12694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
12794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        @Throws(IOException::class)
1285dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis        private fun extractArchive(
1295dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis                inputStream: InputStream,
1305dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis                relativePath: Path,
1315dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis                recursive: Boolean
1325dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis        ): Archive {
13394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            val zipIn = ZipInputStream(inputStream)
13494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            val files = mutableListOf<ArchiveItem>()
13594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
13694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            var entry: ZipEntry? = zipIn.nextEntry
13794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            // iterates over entries in the zip file
13894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            while (entry != null) {
13994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis                if (!entry.isDirectory) {
1409f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis                    val entryPath = Paths.get(entry.name)
1415dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis                    if (isArchive(entry) && recursive) {
1429f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis                        Log.i(TAG, "Extracting nested: %s", entryPath)
1435dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis                        files.add(extractArchive(zipIn, entryPath, recursive))
14494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis                    } else {
1459f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis                        files.add(extractFile(zipIn, entryPath))
14694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis                    }
14794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis                }
14894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis                zipIn.closeEntry()
14994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis                entry = zipIn.nextEntry
15094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            }
15194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            // Cannot close the zip stream at this moment as that would close also any parent zip
15294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            // streams in case we are processing a nested archive.
15394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
15494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            return Archive(relativePath, files.toList())
15594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        }
15694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
15794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        @Throws(IOException::class)
1589f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis        private fun extractFile(zipIn: ZipInputStream, relativePath: Path): ArchiveFile {
15994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            Log.d(TAG, "Extracting archive: %s", relativePath)
16094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
16194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            val data = zipIn.readBytes()
16294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            return ArchiveFile(relativePath, data)
16394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        }
16494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
1651e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston        private fun isArchive(zipEntry: ZipEntry): Boolean {
1669f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis            return ARCHIVE_EXTENSIONS.any { zipEntry.name.endsWith(it, ignoreCase = true) }
16794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        }
16894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    }
16994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis}