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}