Merge "Add --delete-empty-removed-signatures"
diff --git a/src/main/java/com/android/tools/metalava/Driver.kt b/src/main/java/com/android/tools/metalava/Driver.kt
index 60f27d8..9ed9598 100644
--- a/src/main/java/com/android/tools/metalava/Driver.kt
+++ b/src/main/java/com/android/tools/metalava/Driver.kt
@@ -59,6 +59,7 @@
 import java.io.OutputStream
 import java.io.OutputStreamWriter
 import java.io.PrintWriter
+import java.io.StringWriter
 import java.util.concurrent.TimeUnit.SECONDS
 import java.util.function.Predicate
 import kotlin.system.exitProcess
@@ -324,8 +325,8 @@
         val removedEmit = apiType.getEmitFilter()
         val removedReference = apiType.getReferenceFilter()
 
-        createReportFile(unfiltered, apiFile, "removed API") { printWriter ->
-            SignatureWriter(printWriter, removedEmit, removedReference, codebase.original != null)
+        createReportFile(unfiltered, apiFile, "removed API", options.deleteEmptyRemovedSignatures) { printWriter ->
+            SignatureWriter(printWriter, removedEmit, removedReference, codebase.original != null, options.includeSignatureFormatVersionRemoved)
         }
     }
 
@@ -1057,6 +1058,7 @@
     codebase: Codebase,
     apiFile: File,
     description: String?,
+    deleteEmptyFiles: Boolean = false,
     createVisitor: (PrintWriter) -> ApiVisitor
 ) {
     if (description != null) {
@@ -1064,11 +1066,16 @@
     }
     val localTimer = Stopwatch.createStarted()
     try {
-        val writer = PrintWriter(Files.asCharSink(apiFile, UTF_8).openBufferedStream())
+        val stringWriter = StringWriter()
+        val writer = PrintWriter(stringWriter)
         writer.use { printWriter ->
             val apiWriter = createVisitor(printWriter)
             codebase.accept(apiWriter)
         }
+        val text = stringWriter.toString()
+        if (text.length > 0 || !deleteEmptyFiles) {
+            apiFile.writeText(text)
+        }
     } catch (e: IOException) {
         reporter.report(Issues.IO_ERROR, apiFile, "Cannot open file for write.")
     }
diff --git a/src/main/java/com/android/tools/metalava/Options.kt b/src/main/java/com/android/tools/metalava/Options.kt
index 866d01f..76396c4 100644
--- a/src/main/java/com/android/tools/metalava/Options.kt
+++ b/src/main/java/com/android/tools/metalava/Options.kt
@@ -161,6 +161,7 @@
 const val ARG_STUB_PACKAGES = "--stub-packages"
 const val ARG_STUB_IMPORT_PACKAGES = "--stub-import-packages"
 const val ARG_DELETE_EMPTY_BASELINES = "--delete-empty-baselines"
+const val ARG_DELETE_EMPTY_REMOVED_SIGNATURES = "--delete-empty-removed-signatures"
 const val ARG_SUBTRACT_API = "--subtract-api"
 const val ARG_TYPEDEFS_IN_SIGNATURES = "--typedefs-in-signatures"
 const val ARG_FORCE_CONVERT_TO_WARNING_NULLABILITY_ANNOTATIONS = "--force-convert-to-warning-nullability-annotations"
@@ -570,9 +571,29 @@
     /** Level to include for javadoc */
     var docLevel = DocLevel.PROTECTED
 
-    /** Whether to include the signature file format version header in signature files */
+    /** Whether to include the signature file format version header in most signature files */
     var includeSignatureFormatVersion: Boolean = true
 
+    /** Whether to include the signature file format version header in removed signature files */
+    val includeSignatureFormatVersionNonRemoved: EmitFileHeader get() =
+            if (includeSignatureFormatVersion) {
+                EmitFileHeader.ALWAYS
+            } else {
+                EmitFileHeader.NEVER
+            }
+
+    /** Whether to include the signature file format version header in removed signature files */
+    val includeSignatureFormatVersionRemoved: EmitFileHeader get() =
+            if (includeSignatureFormatVersion) {
+                if (deleteEmptyRemovedSignatures) {
+                    EmitFileHeader.IF_NONEMPTY_FILE
+                } else {
+                    EmitFileHeader.ALWAYS
+                }
+            } else {
+                EmitFileHeader.NEVER
+            }
+
     /** A baseline to check against */
     var baseline: Baseline? = null
 
@@ -625,6 +646,9 @@
     /** If updating baselines and the baseline is empty, delete the file */
     var deleteEmptyBaselines = false
 
+    /** If generating a removed signature file and it is empty, delete it */
+    var deleteEmptyRemovedSignatures = false
+
     /** Whether the baseline should only contain errors */
     var baselineErrorsOnly = false
 
@@ -1023,6 +1047,7 @@
 
                 ARG_PASS_BASELINE_UPDATES -> passBaselineUpdates = true
                 ARG_DELETE_EMPTY_BASELINES -> deleteEmptyBaselines = true
+                ARG_DELETE_EMPTY_REMOVED_SIGNATURES -> deleteEmptyRemovedSignatures = true
 
                 ARG_PUBLIC, "-public" -> docLevel = DocLevel.PUBLIC
                 ARG_PROTECTED, "-protected" -> docLevel = DocLevel.PROTECTED
diff --git a/src/main/java/com/android/tools/metalava/SignatureWriter.kt b/src/main/java/com/android/tools/metalava/SignatureWriter.kt
index aebd5b6..9d7a90a 100644
--- a/src/main/java/com/android/tools/metalava/SignatureWriter.kt
+++ b/src/main/java/com/android/tools/metalava/SignatureWriter.kt
@@ -35,7 +35,8 @@
     private val writer: PrintWriter,
     filterEmit: Predicate<Item>,
     filterReference: Predicate<Item>,
-    private val preFiltered: Boolean
+    private val preFiltered: Boolean,
+    var emitHeader: EmitFileHeader = options.includeSignatureFormatVersionNonRemoved
 ) : ApiVisitor(
     visitConstructorsAsMethods = false,
     nestInnerClasses = false,
@@ -47,101 +48,112 @@
     showUnannotated = options.showUnannotated
 ) {
     init {
-        if (options.includeSignatureFormatVersion) {
+        if (emitHeader == EmitFileHeader.ALWAYS) {
             writer.print(options.outputFormat.header())
+            emitHeader = EmitFileHeader.NEVER
         }
     }
 
+    fun write(text: String) {
+        if (emitHeader == EmitFileHeader.IF_NONEMPTY_FILE) {
+            if (options.includeSignatureFormatVersion) {
+                writer.print(options.outputFormat.header())
+            }
+            emitHeader = EmitFileHeader.NEVER
+        }
+        writer.print(text)
+    }
+
     override fun visitPackage(pkg: PackageItem) {
-        writer.print("package ")
+        write("package ")
         writeModifiers(pkg)
-        writer.print("${pkg.qualifiedName()} {\n\n")
+        write("${pkg.qualifiedName()} {\n\n")
     }
 
     override fun afterVisitPackage(pkg: PackageItem) {
-        writer.print("}\n\n")
+        write("}\n\n")
     }
 
     override fun visitConstructor(constructor: ConstructorItem) {
-        writer.print("    ctor ")
+        write("    ctor ")
         writeModifiers(constructor)
         // Note - we don't write out the type parameter list (constructor.typeParameterList()) in signature files!
         // writeTypeParameterList(constructor.typeParameterList(), addSpace = true)
-        writer.print(constructor.containingClass().fullName())
+        write(constructor.containingClass().fullName())
         writeParameterList(constructor)
         writeThrowsList(constructor)
-        writer.print(";\n")
+        write(";\n")
     }
 
     override fun visitField(field: FieldItem) {
         val name = if (field.isEnumConstant()) "enum_constant" else "field"
-        writer.print("    ")
-        writer.print(name)
-        writer.print(" ")
+        write("    ")
+        write(name)
+        write(" ")
         writeModifiers(field)
         writeType(field, field.type())
-        writer.print(' ')
-        writer.print(field.name())
+        write(" ")
+        write(field.name())
         field.writeValueWithSemicolon(writer, allowDefaultValue = false, requireInitialValue = false)
-        writer.print("\n")
+        write("\n")
     }
 
     override fun visitProperty(property: PropertyItem) {
-        writer.print("    property ")
+        write("    property ")
         writeModifiers(property)
         writeType(property, property.type())
-        writer.print(' ')
-        writer.print(property.name())
-        writer.print(";\n")
+        write(" ")
+        write(property.name())
+        write(";\n")
     }
 
     override fun visitMethod(method: MethodItem) {
-        writer.print("    method ")
+        write("    method ")
         writeModifiers(method)
         writeTypeParameterList(method.typeParameterList(), addSpace = true)
 
         writeType(method, method.returnType())
-        writer.print(' ')
-        writer.print(method.name())
+        write(" ")
+        write(method.name())
         writeParameterList(method)
         writeThrowsList(method)
 
         if (method.containingClass().isAnnotationType()) {
             val default = method.defaultValue()
             if (default.isNotEmpty()) {
-                writer.print(" default ")
-                writer.print(default)
+                write(" default ")
+                write(default)
             }
         }
 
-        writer.print(";\n")
+        write(";\n")
     }
 
     override fun visitClass(cls: ClassItem) {
-        writer.print("  ")
+        write("  ")
 
         writeModifiers(cls)
 
         if (cls.isAnnotationType()) {
-            writer.print("@interface")
+            write("@interface")
         } else if (cls.isInterface()) {
-            writer.print("interface")
+            write("interface")
         } else if (cls.isEnum()) {
-            writer.print("enum")
+            write("enum")
         } else {
-            writer.print("class")
+            write("class")
         }
-        writer.print(" ")
-        writer.print(cls.fullName())
+        write(" ")
+        write(cls.fullName())
         writeTypeParameterList(cls.typeParameterList(), addSpace = false)
         writeSuperClassStatement(cls)
         writeInterfaceList(cls)
 
-        writer.print(" {\n")
+        write(" {\n")
     }
 
     override fun afterVisitClass(cls: ClassItem) {
-        writer.print("  }\n\n")
+        write("  }\n\n")
     }
 
     private fun writeModifiers(item: Item) {
@@ -171,8 +183,8 @@
                     context = superClass.asClass(),
                     filter = filterReference
                 )
-            writer.print(" extends ")
-            writer.print(superClassString)
+            write(" extends ")
+            write(superClassString)
         }
     }
 
@@ -200,10 +212,10 @@
                 } else {
                     " implements"
                 }
-            writer.print(label)
+            write(label)
             interfaces.sortedWith(TypeItem.comparator).forEach { item ->
-                writer.print(" ")
-                writer.print(
+                write(" ")
+                write(
                     item.toTypeString(
                         kotlinStyleNulls = false,
                         context = item.asClass(),
@@ -217,48 +229,48 @@
     private fun writeTypeParameterList(typeList: TypeParameterList, addSpace: Boolean) {
         val typeListString = typeList.toString()
         if (typeListString.isNotEmpty()) {
-            writer.print(typeListString)
+            write(typeListString)
             if (addSpace) {
-                writer.print(' ')
+                write(" ")
             }
         }
     }
 
     private fun writeParameterList(method: MethodItem) {
-        writer.print("(")
+        write("(")
         method.parameters().asSequence().forEachIndexed { i, parameter ->
             if (i > 0) {
-                writer.print(", ")
+                write(", ")
             }
             if (parameter.hasDefaultValue() &&
                 options.outputDefaultValues &&
                 options.outputConciseDefaultValues
             ) {
                 // Concise representation of a parameter with a default
-                writer.print("optional ")
+                write("optional ")
             }
             writeModifiers(parameter)
             writeType(parameter, parameter.type())
             val name = parameter.publicName()
             if (name != null) {
-                writer.print(" ")
-                writer.print(name)
+                write(" ")
+                write(name)
             }
             if (parameter.isDefaultValueKnown() &&
                 options.outputDefaultValues &&
                 !options.outputConciseDefaultValues
             ) {
-                writer.print(" = ")
+                write(" = ")
                 val defaultValue = parameter.defaultValue()
                 if (defaultValue != null) {
-                    writer.print(defaultValue)
+                    write(defaultValue)
                 } else {
                     // null is a valid default value!
-                    writer.print("null")
+                    write("null")
                 }
             }
         }
-        writer.print(")")
+        write(")")
     }
 
     private fun writeType(
@@ -280,7 +292,7 @@
         // Strip java.lang. prefix
         typeString = TypeItem.shortenTypes(typeString)
 
-        writer.print(typeString)
+        write(typeString)
     }
 
     private fun writeThrowsList(method: MethodItem) {
@@ -289,13 +301,19 @@
             else -> method.filteredThrowsTypes(filterReference).asSequence()
         }
         if (throws.any()) {
-            writer.print(" throws ")
+            write(" throws ")
             throws.asSequence().sortedWith(ClassItem.fullNameComparator).forEachIndexed { i, type ->
                 if (i > 0) {
-                    writer.print(", ")
+                    write(", ")
                 }
-                writer.print(type.qualifiedName())
+                write(type.qualifiedName())
             }
         }
     }
 }
+
+enum class EmitFileHeader {
+    ALWAYS,
+    NEVER,
+    IF_NONEMPTY_FILE
+}