1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tools.metalava 18 19 import com.android.SdkConstants.DOT_TXT 20 import com.android.SdkConstants.DOT_XML 21 22 /** File formats that metalava can emit APIs to */ 23 enum class FileFormat( 24 val description: String, 25 val version: String? = null, 26 val conciseDefaultValues: Boolean = false 27 ) { 28 UNKNOWN("?"), 29 JDIFF("JDiff"), 30 BASELINE("Metalava baseline file", "1.0"), 31 SINCE_XML("Metalava API-level file", "1.0"), 32 33 // signature formats should be last to make comparisons work (for example in [configureOptions]) 34 V1("Doclava signature file", "1.0"), 35 V2("Metalava signature file", "2.0"), 36 V3("Metalava signature file", "3.0"), 37 V4("Metalava signature file", "4.0", conciseDefaultValues = true); 38 39 /** Configures the option object such that the output format will be the given format */ configureOptionsnull40 fun configureOptions(options: Options) { 41 if (this == JDIFF) { 42 return 43 } 44 options.outputFormat = this 45 options.outputKotlinStyleNulls = this >= V3 46 options.outputDefaultValues = this >= V2 47 options.includeSignatureFormatVersion = this >= V2 48 } 49 useKotlinStyleNullsnull50 fun useKotlinStyleNulls(): Boolean { 51 return this >= V3 52 } 53 signatureFormatAsIntnull54 private fun signatureFormatAsInt(): Int { 55 return when (this) { 56 V1 -> 1 57 V2 -> 2 58 V3 -> 3 59 V4 -> 4 60 61 BASELINE, 62 JDIFF, 63 SINCE_XML, 64 UNKNOWN -> error("this method is only allowed on signature formats, was $this") 65 } 66 } 67 outputFlagnull68 fun outputFlag(): String { 69 return if (isSignatureFormat()) { 70 "$ARG_FORMAT=v${signatureFormatAsInt()}" 71 } else { 72 "" 73 } 74 } 75 preferredExtensionnull76 fun preferredExtension(): String { 77 return when (this) { 78 V1, 79 V2, 80 V3, 81 V4 -> DOT_TXT 82 83 BASELINE -> DOT_TXT 84 85 JDIFF, SINCE_XML -> DOT_XML 86 87 UNKNOWN -> "" 88 } 89 } 90 headernull91 fun header(): String? { 92 val prefix = headerPrefix() ?: return null 93 return prefix + version + "\n" 94 } 95 headerPrefixnull96 private fun headerPrefix(): String? { 97 return when (this) { 98 V1 -> null 99 V2, V3, V4 -> "// Signature format: " 100 BASELINE -> "// Baseline format: " 101 JDIFF, SINCE_XML -> "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" 102 UNKNOWN -> null 103 } 104 } 105 isSignatureFormatnull106 fun isSignatureFormat(): Boolean { 107 return this == V1 || this == V2 || this == V3 || this == V4 108 } 109 110 companion object { 111 /** The recommended signature file version, equivalent to --format=recommended */ 112 val recommended = V2 113 114 /** The latest signature file version, equivalent to --format=latest */ 115 val latest = values().maxOrNull()!! 116 firstLinenull117 private fun firstLine(s: String): String { 118 val index = s.indexOf('\n') 119 if (index == -1) { 120 return s 121 } 122 // Chop off \r if a Windows \r\n file 123 val end = if (index > 0 && s[index - 1] == '\r') index - 1 else index 124 return s.substring(0, end) 125 } 126 parseHeadernull127 fun parseHeader(fileContents: String): FileFormat { 128 val firstLine = firstLine(fileContents) 129 for (format in values()) { 130 val header = format.header() 131 if (header == null) { 132 if (firstLine.startsWith("package ")) { 133 // Old signature files 134 return V1 135 } else if (firstLine.startsWith("<api")) { 136 return JDIFF 137 } 138 } else if (header.startsWith(firstLine)) { 139 if (format == JDIFF) { 140 if (!fileContents.contains("<api")) { 141 // The JDIFF header is the general XML header: don't accept XML documents that 142 // don't contain an empty API definition 143 return UNKNOWN 144 } 145 // Both JDiff and API-level files use <api> as the root tag (unfortunate but too late to 146 // change) so distinguish on whether the file contains any since elements 147 if (fileContents.contains("since=")) { 148 return SINCE_XML 149 } 150 } 151 return format 152 } 153 } 154 155 return UNKNOWN 156 } 157 } 158 } 159