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}