[automerger] Merge "Rewrite CheckApiTask to Kotlin" into oc-mr1-support-27.0-dev am: 3dd239634a am: 1cca89bbe3

Change-Id: I39ad16bace330d41331a3d3b322ff36115fee0dc
diff --git a/buildSrc/src/main/groovy/android/support/checkapi/CheckApiTask.groovy b/buildSrc/src/main/groovy/android/support/checkapi/CheckApiTask.groovy
deleted file mode 100644
index 4f829f6..0000000
--- a/buildSrc/src/main/groovy/android/support/checkapi/CheckApiTask.groovy
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.checkapi
-
-import org.gradle.api.DefaultTask
-import org.gradle.api.GradleException
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.InputFiles
-import org.gradle.api.tasks.TaskAction
-import org.gradle.api.tasks.Optional
-import org.gradle.api.tasks.OutputFile
-
-import java.security.MessageDigest
-
-/**
- * Task used to verify changes between two API files.
- * <p>
- * This task may be configured to ignore, warn, or fail with a message for a specific set of
- * Doclava-defined error codes. See {@link com.google.doclava.Errors} for a complete list of
- * supported error codes.
- * <p>
- * Specific failures may be ignored by specifying a list of SHAs in {@link #whitelistErrors}. Each
- * SHA is unique to a specific API change and is logged to the error output on failure.
- */
-public class CheckApiTask extends DefaultTask {
-    /** Character that resets console output color. */
-    private static final String ANSI_RESET = "\u001B[0m";
-
-    /** Character that sets console output color to red. */
-    private static final String ANSI_RED = "\u001B[31m";
-
-    /** Character that sets console output color to yellow. */
-    private static final String ANSI_YELLOW = "\u001B[33m";
-
-    /** API file that represents the existing API surface. */
-    @Optional
-    @InputFile
-    File oldApiFile
-
-    /** API file that represents the existing API surface's removals. */
-    @Optional
-    @InputFile
-    File oldRemovedApiFile
-
-    /** API file that represents the candidate API surface. */
-    @InputFile
-    File newApiFile
-
-    /** API file that represents the candidate API surface's removals. */
-    @Optional
-    @InputFile
-    File newRemovedApiFile
-
-    /** Optional file containing a newline-delimited list of error SHAs to ignore. */
-    File whitelistErrorsFile
-
-    @Optional
-    @InputFile
-    File getWhiteListErrorsFileInput() {
-        // Gradle requires non-null InputFiles to exist -- even with Optional -- so work around that
-        // by returning null for this field if the file doesn't exist.
-        if (whitelistErrorsFile && whitelistErrorsFile.exists()) {
-            return whitelistErrorsFile;
-        }
-        return null;
-    }
-
-    /**
-     * Optional list of packages to ignore.
-     * <p>
-     * Packages names will be matched exactly; sub-packages are not automatically recognized.
-     */
-    @Optional
-    @Input
-    Collection ignoredPackages
-
-    /**
-     * Optional list of classes to ignore.
-     * <p>
-     * Class names will be matched exactly by their fully-qualified names; inner classes are not
-     * automatically recognized.
-     */
-    @Optional
-    @Input
-    Collection ignoredClasses
-
-    /**
-     * Optional set of error SHAs to ignore.
-     * <p>
-     * Each error SHA is unique to a specific API change.
-     */
-    @Optional
-    @Input
-    Set whitelistErrors = []
-
-    // Errors that were both detected and whitelisted.
-    Set detectedWhitelistErrors = []
-
-    @InputFiles
-    Collection<File> doclavaClasspath
-
-    // A dummy output file meant only to tag when this check was last ran.
-    // Without any outputs, Gradle will run this task every time.
-    @Optional
-    private File mOutputFile
-
-    @OutputFile
-    public File getOutputFile() {
-        return mOutputFile ?: new File(project.buildDir, "checkApi/${name}-completed")
-    }
-
-    @Optional
-    public void setOutputFile(File outputFile) {
-        mOutputFile = outputFile
-    }
-
-    /**
-     * List of Doclava error codes to treat as errors.
-     * <p>
-     * See {@link com.google.doclava.Errors} for a complete list of error codes.
-     */
-    @Input
-    Collection checkApiErrors
-
-    /**
-     * List of Doclava error codes to treat as warnings.
-     * <p>
-     * See {@link com.google.doclava.Errors} for a complete list of error codes.
-     */
-    @Input
-    Collection checkApiWarnings
-
-    /**
-     * List of Doclava error codes to ignore.
-     * <p>
-     * See {@link com.google.doclava.Errors} for a complete list of error codes.
-     */
-    @Input
-    Collection checkApiHidden
-
-    /** Message to display on API check failure. */
-    @Input
-    String onFailMessage
-
-    public CheckApiTask() {
-        group = 'Verification'
-        description = 'Invoke Doclava\'s ApiCheck tool to make sure current.txt is up to date.'
-    }
-
-    private Set<File> collectAndVerifyInputs() {
-        if (getOldRemovedApiFile() != null && getNewRemovedApiFile() != null) {
-            return [getOldApiFile(), getNewApiFile(), getOldRemovedApiFile(),
-                    getNewRemovedApiFile()] as Set
-        } else {
-            return [getOldApiFile(), getNewApiFile()] as Set
-        }
-    }
-
-    public void setCheckApiErrors(Collection errors) {
-        // Make it serializable.
-        checkApiErrors = errors as int[]
-    }
-
-    public void setCheckApiWarnings(Collection warnings) {
-        // Make it serializable.
-        checkApiWarnings = warnings as int[]
-    }
-
-    public void setCheckApiHidden(Collection hidden) {
-        // Make it serializable.
-        checkApiHidden = hidden as int[]
-    }
-
-    @TaskAction
-    public void exec() {
-        if (getOldApiFile() == null) {
-            // Nothing to do.
-            return
-        }
-
-        final def apiFiles = collectAndVerifyInputs()
-
-        OutputStream errStream = new ByteArrayOutputStream()
-
-        // If either of those gets tweaked, then this should be refactored to extend JavaExec.
-        project.javaexec {
-            // Put Doclava on the classpath so we can get the ApiCheck class.
-            classpath(getDoclavaClasspath())
-            main = 'com.google.doclava.apicheck.ApiCheck'
-
-            minHeapSize = '128m'
-            maxHeapSize = '1024m'
-
-            // add -error LEVEL for every error level we want to fail the build on.
-            getCheckApiErrors().each { args('-error', it) }
-            getCheckApiWarnings().each { args('-warning', it) }
-            getCheckApiHidden().each { args('-hide', it) }
-
-            Collection ignoredPackages = getIgnoredPackages()
-            if (ignoredPackages) {
-                ignoredPackages.each { args('-ignorePackage', it) }
-            }
-            Collection ignoredClasses = getIgnoredClasses()
-            if (ignoredClasses) {
-                ignoredClasses.each { args('-ignoreClass', it) }
-            }
-
-            args(apiFiles.collect( { it.absolutePath } ))
-
-            // Redirect error output so that we can whitelist specific errors.
-            errorOutput = errStream
-
-            // We will be handling failures ourselves with a custom message.
-            ignoreExitValue = true
-        }
-
-        // Load the whitelist file, if present.
-        if (whitelistErrorsFile && whitelistErrorsFile.exists()) {
-            whitelistErrors += whitelistErrorsFile.readLines()
-        }
-
-        // Parse the error output.
-        Set unparsedErrors = []
-        Set detectedErrors = []
-        Set parsedErrors = []
-        errStream.toString().split("\n").each {
-            if (it) {
-                def matcher = it =~ ~/^(.+):(.+): (\w+) (\d+): (.+)$/
-                if (!matcher) {
-                    unparsedErrors += [it]
-                } else if (matcher[0][3] == "error") {
-                    def hash = getShortHash(matcher[0][5]);
-                    def error = matcher[0][1..-1] + [hash]
-                    if (hash in whitelistErrors) {
-                        detectedErrors += [error]
-                        detectedWhitelistErrors += error[5]
-                    } else {
-                        parsedErrors += [error]
-                    }
-                }
-            }
-        }
-
-        unparsedErrors.each { error -> logger.error "$ANSI_RED$error$ANSI_RESET" }
-        parsedErrors.each { logger.error "$ANSI_RED${it[5]}$ANSI_RESET ${it[4]}"}
-        detectedErrors.each { logger.warn "$ANSI_YELLOW${it[5]}$ANSI_RESET ${it[4]}"}
-
-        if (unparsedErrors || parsedErrors) {
-            throw new GradleException(onFailMessage)
-        }
-
-        // Just create a dummy file upon completion. Without any outputs, Gradle will run this task
-        // every time.
-        File outputFile = getOutputFile()
-        outputFile.parentFile.mkdirs()
-        outputFile.createNewFile()
-    }
-
-    def getShortHash(src) {
-        return MessageDigest.getInstance("SHA-1")
-                .digest(src.toString().bytes)
-                .encodeHex()
-                .toString()[-7..-1]
-    }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/android/support/checkapi/CheckApiTask.kt b/buildSrc/src/main/kotlin/android/support/checkapi/CheckApiTask.kt
new file mode 100644
index 0000000..8f96b2e
--- /dev/null
+++ b/buildSrc/src/main/kotlin/android/support/checkapi/CheckApiTask.kt
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.checkapi
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import java.io.*
+import java.security.MessageDigest
+
+
+/** Character that resets console output color. */
+private const val ANSI_RESET = "\u001B[0m"
+
+/** Character that sets console output color to red. */
+private const val ANSI_RED = "\u001B[31m"
+
+/** Character that sets console output color to yellow. */
+private const val ANSI_YELLOW = "\u001B[33m"
+
+private val ERROR_REGEX = Regex("^(.+):(.+): (\\w+) (\\d+): (.+)$")
+
+private fun ByteArray.encodeHex() = fold(StringBuilder(), { builder, byte ->
+    val hexString = Integer.toHexString(byte.toInt() and 0xFF)
+    if (hexString.length < 2) {
+        builder.append("0")
+    }
+    builder.append(hexString)
+}).toString()
+
+private fun getShortHash(src: String): String {
+    val str = MessageDigest.getInstance("SHA-1")
+            .digest(src.toByteArray()).encodeHex()
+    val len = str.length
+    return str.substring(len - 7, len)
+}
+
+/**
+ * Task used to verify changes between two API files.
+ * <p>
+ * This task may be configured to ignore, warn, or fail with a message for a specific set of
+ * Doclava-defined error codes. See {@link com.google.doclava.Errors} for a complete list of
+ * supported error codes.
+ * <p>
+ * Specific failures may be ignored by specifying a list of SHAs in {@link #whitelistErrors}. Each
+ * SHA is unique to a specific API change and is logged to the error output on failure.
+ */
+open class CheckApiTask : DefaultTask() {
+
+    /** API file that represents the existing API surface. */
+    @Optional
+    @InputFile
+    var oldApiFile: File? = null
+
+    /** API file that represents the existing API surface's removals. */
+    @Optional
+    @InputFile
+    var oldRemovedApiFile: File? = null
+
+    /** API file that represents the candidate API surface. */
+    @InputFile
+    var newApiFile: File? = null
+
+    /** API file that represents the candidate API surface's removals. */
+    @Optional
+    @InputFile
+    var newRemovedApiFile: File? = null
+
+    /** Optional file containing a newline-delimited list of error SHAs to ignore. */
+    var whitelistErrorsFile: File? = null
+
+    @Optional
+    @InputFile
+    fun getWhiteListErrorsFileInput(): File? {
+        // Gradle requires non-null InputFiles to exist -- even with Optional -- so work around that
+        // by returning null for this field if the file doesn't exist.
+        if (whitelistErrorsFile?.exists() == true) {
+            return whitelistErrorsFile
+        }
+        return null
+    }
+
+    /**
+     * Optional set of error SHAs to ignore.
+     * <p>
+     * Each error SHA is unique to a specific API change.
+     */
+    @Optional
+    @Input
+    var whitelistErrors = emptySet<String>()
+
+    var detectedWhitelistErrors = mutableSetOf<String>()
+
+    @InputFiles
+    var doclavaClasspath: Collection<File> = emptyList()
+
+    // A dummy output file meant only to tag when this check was last ran.
+    // Without any outputs, Gradle will run this task every time.
+    @Optional
+    private var mOutputFile: File? = null
+
+    @OutputFile
+    fun getOutputFile(): File {
+        return if (mOutputFile != null)
+            mOutputFile!!
+        else File(project.buildDir, "checkApi/${name}-completed")
+    }
+
+    @Optional
+    fun setOutputFile(outputFile: File) {
+        mOutputFile = outputFile
+    }
+
+    /**
+     * List of Doclava error codes to treat as errors.
+     * <p>
+     * See {@link com.google.doclava.Errors} for a complete list of error codes.
+     */
+    @Input
+    var checkApiErrors: Collection<Int> = emptyList()
+
+    /**
+     * List of Doclava error codes to treat as warnings.
+     * <p>
+     * See {@link com.google.doclava.Errors} for a complete list of error codes.
+     */
+    @Input
+    var checkApiWarnings: Collection<Int> = emptyList()
+
+    /**
+     * List of Doclava error codes to ignore.
+     * <p>
+     * See {@link com.google.doclava.Errors} for a complete list of error codes.
+     */
+    @Input
+    var checkApiHidden: Collection<Int> = emptyList()
+
+    /** Message to display on API check failure. */
+    @Input
+    var onFailMessage: String = "Failed."
+
+    init {
+        group = "Verification"
+        description = "Invoke Doclava\'s ApiCheck tool to make sure current.txt is up to date."
+    }
+
+    private fun collectAndVerifyInputs(): Set<File> {
+        if (oldRemovedApiFile != null && newRemovedApiFile != null) {
+            return setOf(oldApiFile!!, newApiFile!!, oldRemovedApiFile!!, newRemovedApiFile!!)
+        } else {
+            return setOf(oldApiFile!!, newApiFile!!)
+        }
+    }
+
+    @TaskAction
+    fun exec() {
+        if (oldApiFile == null) {
+            // Nothing to do.
+            return
+        }
+
+        val apiFiles = collectAndVerifyInputs()
+
+        val errStream = ByteArrayOutputStream()
+
+        // If either of those gets tweaked, then this should be refactored to extend JavaExec.
+        project.javaexec { spec ->
+            spec.apply {
+                // Put Doclava on the classpath so we can get the ApiCheck class.
+                classpath(doclavaClasspath)
+                main = "com.google.doclava.apicheck.ApiCheck"
+
+                minHeapSize = "128m"
+                maxHeapSize = "1024m"
+
+                // add -error LEVEL for every error level we want to fail the build on.
+                checkApiErrors.forEach { args("-error", it) }
+                checkApiWarnings.forEach { args("-warning", it) }
+                checkApiHidden.forEach { args("-hide", it) }
+
+                spec.args(apiFiles.map { it.absolutePath })
+
+                // Redirect error output so that we can whitelist specific errors.
+                errorOutput = errStream
+                // We will be handling failures ourselves with a custom message.
+                setIgnoreExitValue(true)
+            }
+        }
+
+
+        // Load the whitelist file, if present.
+        val whitelistFile = whitelistErrorsFile
+        if (whitelistFile?.exists() == true) {
+            whitelistErrors += whitelistFile.readLines()
+        }
+
+        // Parse the error output.
+        val unparsedErrors = mutableSetOf<String>()
+        val detectedErrors = mutableSetOf<List<String>>()
+        val parsedErrors = mutableSetOf<List<String>>()
+        ByteArrayInputStream(errStream.toByteArray()).bufferedReader().lines().forEach {
+            val match = ERROR_REGEX.matchEntire(it)
+
+            if (match == null) {
+                unparsedErrors.add(it)
+            } else if (match.groups[3]?.value == "error") {
+                val hash = getShortHash(match.groups[5]?.value!!)
+                val error = match.groupValues.subList(1, match.groupValues.size) + listOf(hash)
+                if (hash in whitelistErrors) {
+                    detectedErrors.add(error)
+                    detectedWhitelistErrors.add(error[5])
+                } else {
+                    parsedErrors.add(error)
+                }
+            }
+        }
+
+        unparsedErrors.forEach { error -> logger.error("$ANSI_RED$error$ANSI_RESET") }
+        parsedErrors.forEach { logger.error("$ANSI_RED${it[5]}$ANSI_RESET ${it[4]}") }
+        detectedErrors.forEach { logger.warn("$ANSI_YELLOW${it[5]}$ANSI_RESET ${it[4]}") }
+
+        if (unparsedErrors.isNotEmpty() || parsedErrors.isNotEmpty()) {
+            throw GradleException(onFailMessage)
+        }
+
+        // Just create a dummy file upon completion. Without any outputs, Gradle will run this task
+        // every time.
+        val outputFile = getOutputFile()
+        outputFile.parentFile.mkdirs()
+        outputFile.createNewFile()
+    }
+}
\ No newline at end of file