Archive.kt revision 60486c575581dcc40605ea91e1062afa30598e36
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
5160486c575581dcc40605ea91e1062afa30598e36Filip Pavlis    override val wasChanged: Boolean
5260486c575581dcc40605ea91e1062afa30598e36Filip Pavlis        get() = files.any { it.wasChanged }
5360486c575581dcc40605ea91e1062afa30598e36Filip Pavlis
5494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    override fun accept(visitor: ArchiveItemVisitor) {
5594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        visitor.visit(this)
5694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    }
5794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
5894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    @Throws(IOException::class)
591e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston    fun writeSelfToDir(outputDirPath: Path): File {
609f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis        val outputPath = Paths.get(outputDirPath.toString(), fileName)
619f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis
621e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston        return writeSelfToFile(outputPath)
631e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston    }
641e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston
651e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston    @Throws(IOException::class)
661e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston    fun writeSelfToFile(outputPath: Path): File {
679f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis        if (Files.exists(outputPath)) {
689f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis            Log.i(TAG, "Deleting old output file")
699f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis            Files.delete(outputPath)
709f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis        }
719f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis
729f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis        // Create directories if they don't exist yet
731e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston        Files.createDirectories(outputPath.parent)
7494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
759f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis        Log.i(TAG, "Writing archive: %s", outputPath.toUri())
76d7b0788f3bf58fc26a936c2426ef1214ffb3a180Filip Pavlis        val file = outputPath.toFile()
7794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        Files.createFile(outputPath)
78d7b0788f3bf58fc26a936c2426ef1214ffb3a180Filip Pavlis        val stream = BufferedOutputStream(FileOutputStream(file))
7994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        writeSelfTo(stream)
8094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        stream.close()
81d7b0788f3bf58fc26a936c2426ef1214ffb3a180Filip Pavlis        return file
8294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    }
8394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
8494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    @Throws(IOException::class)
8594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    override fun writeSelfTo(outputStream: OutputStream) {
8694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        val out = ZipOutputStream(outputStream)
8794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
8894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        for (file in files) {
8994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            Log.d(TAG, "Writing file: %s", file.relativePath)
9094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
919f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis            val entry = ZipEntry(file.relativePath.toString())
9294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            out.putNextEntry(entry)
9394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            file.writeSelfTo(out)
9494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            out.closeEntry()
9594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        }
9694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        out.finish()
9794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    }
9894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
9994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    object Builder {
10094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
10194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        @Throws(IOException::class)
102d7b0788f3bf58fc26a936c2426ef1214ffb3a180Filip Pavlis        fun extract(archiveFile: File): Archive {
103d7b0788f3bf58fc26a936c2426ef1214ffb3a180Filip Pavlis            Log.i(TAG, "Extracting: %s", archiveFile.absolutePath)
10494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
105d7b0788f3bf58fc26a936c2426ef1214ffb3a180Filip Pavlis            val inputStream = FileInputStream(archiveFile)
10694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            inputStream.use {
107d7b0788f3bf58fc26a936c2426ef1214ffb3a180Filip Pavlis                return extractArchive(it, archiveFile.toPath())
10894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            }
10994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        }
11094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
11194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        @Throws(IOException::class)
1121e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston        private fun extractArchive(inputStream: InputStream, relativePath: Path): Archive {
11394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            val zipIn = ZipInputStream(inputStream)
11494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            val files = mutableListOf<ArchiveItem>()
11594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
11694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            var entry: ZipEntry? = zipIn.nextEntry
11794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            // iterates over entries in the zip file
11894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            while (entry != null) {
11994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis                if (!entry.isDirectory) {
1209f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis                    val entryPath = Paths.get(entry.name)
12194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis                    if (isArchive(entry)) {
1229f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis                        Log.i(TAG, "Extracting nested: %s", entryPath)
1239f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis                        files.add(extractArchive(zipIn, entryPath))
12494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis                    } else {
1259f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis                        files.add(extractFile(zipIn, entryPath))
12694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis                    }
12794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis                }
12894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis                zipIn.closeEntry()
12994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis                entry = zipIn.nextEntry
13094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            }
13194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            // Cannot close the zip stream at this moment as that would close also any parent zip
13294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            // streams in case we are processing a nested archive.
13394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
13494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            return Archive(relativePath, files.toList())
13594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        }
13694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
13794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        @Throws(IOException::class)
1389f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis        private fun extractFile(zipIn: ZipInputStream, relativePath: Path): ArchiveFile {
13994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            Log.d(TAG, "Extracting archive: %s", relativePath)
14094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
14194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            val data = zipIn.readBytes()
14294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            return ArchiveFile(relativePath, data)
14394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        }
14494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
1451e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston        private fun isArchive(zipEntry: ZipEntry): Boolean {
1469f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis            return ARCHIVE_EXTENSIONS.any { zipEntry.name.endsWith(it, ignoreCase = true) }
14794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        }
14894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    }
14994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis}