194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis/*
2ba381a314edcd57963ed1ac5910595e04faf29ccFilip Pavlis * Copyright 2018 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 *
84a360e3a3af230badc847867c117f605367170aaFilip 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
144a360e3a3af230badc847867c117f605367170aaFilip Pavlis * limitations under the License.
1594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis */
1694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
17ba381a314edcd57963ed1ac5910595e04faf29ccFilip Pavlispackage com.android.tools.build.jetifier.processor.archive
1894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
19ba381a314edcd57963ed1ac5910595e04faf29ccFilip Pavlisimport com.android.tools.build.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
30fc27d8ce0d72c46e5848c72963f7ec531ee929f7Filip Pavlisimport java.nio.file.attribute.FileTime
31fc27d8ce0d72c46e5848c72963f7ec531ee929f7Filip Pavlisimport java.time.Instant
3294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlisimport java.util.zip.ZipEntry
3394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlisimport java.util.zip.ZipInputStream
3494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlisimport java.util.zip.ZipOutputStream
3594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
3694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis/**
3794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis * Represents an archive (zip, jar, aar ...)
3894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis */
3994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlisclass Archive(
404a360e3a3af230badc847867c117f605367170aaFilip Pavlis    override val relativePath: Path,
414a360e3a3af230badc847867c117f605367170aaFilip Pavlis    val files: List<ArchiveItem>
424a360e3a3af230badc847867c117f605367170aaFilip Pavlis) : ArchiveItem {
4394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
4494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    companion object {
4594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        /** Defines file extensions that are recognized as archives */
46780463929260213620120fd7ea7a3d8c225c2a9cFilip Pavlis        val ARCHIVE_EXTENSIONS = listOf(".jar", ".zip", ".aar")
4794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
4894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        const val TAG = "Archive"
4994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    }
5094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
519f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis    override val fileName: String = relativePath.fileName.toString()
529f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis
5349f7010e84f8819274924817443b26fc3a117e22Filip Pavlis    private var targetPath: Path = relativePath
5449f7010e84f8819274924817443b26fc3a117e22Filip Pavlis
5560486c575581dcc40605ea91e1062afa30598e36Filip Pavlis    override val wasChanged: Boolean
5660486c575581dcc40605ea91e1062afa30598e36Filip Pavlis        get() = files.any { it.wasChanged }
5760486c575581dcc40605ea91e1062afa30598e36Filip Pavlis
5849f7010e84f8819274924817443b26fc3a117e22Filip Pavlis    /**
5949f7010e84f8819274924817443b26fc3a117e22Filip Pavlis     * Sets path where the file should be saved after transformation.
6049f7010e84f8819274924817443b26fc3a117e22Filip Pavlis     */
6149f7010e84f8819274924817443b26fc3a117e22Filip Pavlis    fun setTargetPath(path: Path) {
6249f7010e84f8819274924817443b26fc3a117e22Filip Pavlis        targetPath = path
6349f7010e84f8819274924817443b26fc3a117e22Filip Pavlis    }
6449f7010e84f8819274924817443b26fc3a117e22Filip Pavlis
6594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    override fun accept(visitor: ArchiveItemVisitor) {
6694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        visitor.visit(this)
6794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    }
6894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
6994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    @Throws(IOException::class)
701e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston    fun writeSelfToDir(outputDirPath: Path): File {
719f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis        val outputPath = Paths.get(outputDirPath.toString(), fileName)
729f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis
731e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston        return writeSelfToFile(outputPath)
741e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston    }
751e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston
7649f7010e84f8819274924817443b26fc3a117e22Filip Pavlis    fun writeSelf(): File {
7749f7010e84f8819274924817443b26fc3a117e22Filip Pavlis        return writeSelfToFile(targetPath)
7849f7010e84f8819274924817443b26fc3a117e22Filip Pavlis    }
7949f7010e84f8819274924817443b26fc3a117e22Filip Pavlis
801e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston    @Throws(IOException::class)
811e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston    fun writeSelfToFile(outputPath: Path): File {
829f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis        if (Files.exists(outputPath)) {
839f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis            Log.i(TAG, "Deleting old output file")
849f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis            Files.delete(outputPath)
859f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis        }
869f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis
879f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis        // Create directories if they don't exist yet
88db897465e0ef8dae03e72d350a8a7ae2ed6ee35cJeff Gaston        if (outputPath.parent != null) {
89db897465e0ef8dae03e72d350a8a7ae2ed6ee35cJeff Gaston            Files.createDirectories(outputPath.parent)
90db897465e0ef8dae03e72d350a8a7ae2ed6ee35cJeff Gaston        }
9194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
929f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis        Log.i(TAG, "Writing archive: %s", outputPath.toUri())
93d7b0788f3bf58fc26a936c2426ef1214ffb3a180Filip Pavlis        val file = outputPath.toFile()
9494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        Files.createFile(outputPath)
95d7b0788f3bf58fc26a936c2426ef1214ffb3a180Filip Pavlis        val stream = BufferedOutputStream(FileOutputStream(file))
9694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        writeSelfTo(stream)
9794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        stream.close()
98d7b0788f3bf58fc26a936c2426ef1214ffb3a180Filip Pavlis        return file
9994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    }
10094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
10194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    @Throws(IOException::class)
10294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    override fun writeSelfTo(outputStream: OutputStream) {
10394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        val out = ZipOutputStream(outputStream)
10494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
10594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        for (file in files) {
106448359062b9caf5f96c0028dc12cba5d2647ea6fFilip Pavlis            Log.v(TAG, "Writing file: %s", file.relativePath)
10794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
1089f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis            val entry = ZipEntry(file.relativePath.toString())
109fc27d8ce0d72c46e5848c72963f7ec531ee929f7Filip Pavlis            entry.lastModifiedTime = FileTime.from(Instant.now()) // b/78249473
11094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            out.putNextEntry(entry)
11194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            file.writeSelfTo(out)
11294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            out.closeEntry()
11394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        }
11494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        out.finish()
11594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    }
11694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
11794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    object Builder {
11894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
1195dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis        /**
1205dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis         * @param recursive Whether nested archives should be also extracted.
1215dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis         */
12294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        @Throws(IOException::class)
1235dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis        fun extract(archiveFile: File, recursive: Boolean = true): Archive {
124d7b0788f3bf58fc26a936c2426ef1214ffb3a180Filip Pavlis            Log.i(TAG, "Extracting: %s", archiveFile.absolutePath)
12594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
126d7b0788f3bf58fc26a936c2426ef1214ffb3a180Filip Pavlis            val inputStream = FileInputStream(archiveFile)
12794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            inputStream.use {
1285dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis                return extractArchive(it, archiveFile.toPath(), recursive)
12994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            }
13094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        }
13194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
13294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        @Throws(IOException::class)
1335dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis        private fun extractArchive(
1344a360e3a3af230badc847867c117f605367170aaFilip Pavlis            inputStream: InputStream,
1354a360e3a3af230badc847867c117f605367170aaFilip Pavlis            relativePath: Path,
1364a360e3a3af230badc847867c117f605367170aaFilip Pavlis            recursive: Boolean
1375dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis        ): Archive {
13894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            val zipIn = ZipInputStream(inputStream)
13994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            val files = mutableListOf<ArchiveItem>()
14094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
14194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            var entry: ZipEntry? = zipIn.nextEntry
14294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            // iterates over entries in the zip file
14394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            while (entry != null) {
14494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis                if (!entry.isDirectory) {
1459f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis                    val entryPath = Paths.get(entry.name)
1465dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis                    if (isArchive(entry) && recursive) {
1479f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis                        Log.i(TAG, "Extracting nested: %s", entryPath)
1485dd5f2ff288862510dc21dfdf4467c88acfd4d52Filip Pavlis                        files.add(extractArchive(zipIn, entryPath, recursive))
14994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis                    } else {
1509f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis                        files.add(extractFile(zipIn, entryPath))
15194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis                    }
15294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis                }
15394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis                zipIn.closeEntry()
15494567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis                entry = zipIn.nextEntry
15594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            }
15694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            // Cannot close the zip stream at this moment as that would close also any parent zip
15794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            // streams in case we are processing a nested archive.
15894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
15994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            return Archive(relativePath, files.toList())
16094567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        }
16194567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
16294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        @Throws(IOException::class)
1639f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis        private fun extractFile(zipIn: ZipInputStream, relativePath: Path): ArchiveFile {
164448359062b9caf5f96c0028dc12cba5d2647ea6fFilip Pavlis            Log.v(TAG, "Extracting archive: %s", relativePath)
16594567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
16694567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            val data = zipIn.readBytes()
16794567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis            return ArchiveFile(relativePath, data)
16894567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        }
16994567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis
1701e5262179e35a2cecee27a8bda65b4861fc1d58dJeff Gaston        private fun isArchive(zipEntry: ZipEntry): Boolean {
1719f29f4a040c61cdbc2bbb5adb9c90fd5691cac57Filip Pavlis            return ARCHIVE_EXTENSIONS.any { zipEntry.name.endsWith(it, ignoreCase = true) }
17294567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis        }
17394567f47da634d0416d839a926ffd792d8eecaa4Filip Pavlis    }
174db897465e0ef8dae03e72d350a8a7ae2ed6ee35cJeff Gaston}
175