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