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(val description: String, val version: String? = null) { 24 UNKNOWN("?"), 25 JDIFF("JDiff"), 26 BASELINE("Metalava baseline file", "1.0"), 27 SINCE_XML("Metalava API-level file", "1.0"), 28 29 // signature formats should be last to make comparisons work (for example in [configureOptions]) 30 V1("Doclava signature file", "1.0"), 31 V2("Metalava signature file", "2.0"), 32 V3("Metalava signature file", "3.0"), 33 V4("Metalava signature file", "4.0"); 34 35 /** Configures the option object such that the output format will be the given format */ configureOptionsnull36 fun configureOptions(options: Options, compatibility: Compatibility) { 37 if (this == JDIFF) { 38 return 39 } 40 options.outputFormat = this 41 options.compatOutput = this == V1 42 options.outputKotlinStyleNulls = this >= V3 43 options.outputDefaultValues = this >= V2 44 options.outputConciseDefaultValues = this >= V4 45 compatibility.omitCommonPackages = this >= V2 46 options.includeSignatureFormatVersion = this >= V2 47 } 48 useKotlinStyleNullsnull49 fun useKotlinStyleNulls(): Boolean { 50 return this >= V3 51 } 52 signatureFormatAsIntnull53 private fun signatureFormatAsInt(): Int { 54 return when (this) { 55 V1 -> 1 56 V2 -> 2 57 V3 -> 3 58 V4 -> 4 59 60 BASELINE, 61 JDIFF, 62 SINCE_XML, 63 UNKNOWN -> error("this method is only allowed on signature formats, was $this") 64 } 65 } 66 outputFlagnull67 fun outputFlag(): String { 68 return if (isSignatureFormat()) { 69 "$ARG_FORMAT=v${signatureFormatAsInt()}" 70 } else { 71 "" 72 } 73 } 74 preferredExtensionnull75 fun preferredExtension(): String { 76 return when (this) { 77 V1, 78 V2, 79 V3, 80 V4 -> DOT_TXT 81 82 BASELINE -> DOT_TXT 83 84 JDIFF, SINCE_XML -> DOT_XML 85 86 UNKNOWN -> "" 87 } 88 } 89 headernull90 fun header(): String? { 91 val prefix = headerPrefix() ?: return null 92 return prefix + version + "\n" 93 } 94 headerPrefixnull95 private fun headerPrefix(): String? { 96 return when (this) { 97 V1 -> null 98 V2, V3, V4 -> "// Signature format: " 99 BASELINE -> "// Baseline format: " 100 JDIFF, SINCE_XML -> "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" 101 UNKNOWN -> null 102 } 103 } 104 isSignatureFormatnull105 fun isSignatureFormat(): Boolean { 106 return this == V1 || this == V2 || this == V3 || this == V4 107 } 108 109 companion object { firstLinenull110 private fun firstLine(s: String): String { 111 val index = s.indexOf('\n') 112 if (index == -1) { 113 return s 114 } 115 // Chop off \r if a Windows \r\n file 116 val end = if (index > 0 && s[index - 1] == '\r') index - 1 else index 117 return s.substring(0, end) 118 } 119 parseHeadernull120 fun parseHeader(fileContents: String): FileFormat { 121 val firstLine = firstLine(fileContents) 122 for (format in values()) { 123 val header = format.header() 124 if (header == null) { 125 if (firstLine.startsWith("package ")) { 126 // Old signature files 127 return V1 128 } else if (firstLine.startsWith("<api")) { 129 return JDIFF 130 } 131 } else if (header.startsWith(firstLine)) { 132 if (format == JDIFF) { 133 if (!fileContents.contains("<api")) { 134 // The JDIFF header is the general XML header: don't accept XML documents that 135 // don't contain an empty API definition 136 return UNKNOWN 137 } 138 // Both JDiff and API-level files use <api> as the root tag (unfortunate but too late to 139 // change) so distinguish on whether the file contains any since elements 140 if (fileContents.contains("since=")) { 141 return SINCE_XML 142 } 143 } 144 return format 145 } 146 } 147 148 return UNKNOWN 149 } 150 } 151 }