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 
34     /** Configures the option object such that the output format will be the given format */
configureOptionsnull35     fun configureOptions(options: Options, compatibility: Compatibility) {
36         if (this == JDIFF) {
37             return
38         }
39         options.outputFormat = this
40         options.compatOutput = this == V1
41         options.outputKotlinStyleNulls = this >= V3
42         options.outputDefaultValues = this >= V2
43         compatibility.omitCommonPackages = this >= V2
44         options.includeSignatureFormatVersion = this >= V2
45     }
46 
useKotlinStyleNullsnull47     fun useKotlinStyleNulls(): Boolean {
48         return this >= V3
49     }
50 
signatureFormatAsIntnull51     private fun signatureFormatAsInt(): Int {
52         return when (this) {
53             V1 -> 1
54             V2 -> 2
55             V3 -> 3
56 
57             BASELINE,
58             JDIFF,
59             SINCE_XML,
60             UNKNOWN -> error("this method is only allowed on signature formats, was $this")
61         }
62     }
63 
outputFlagnull64     fun outputFlag(): String {
65         return if (isSignatureFormat()) {
66             "$ARG_FORMAT=v${signatureFormatAsInt()}"
67         } else {
68             ""
69         }
70     }
71 
preferredExtensionnull72     fun preferredExtension(): String {
73         return when (this) {
74             V1,
75             V2,
76             V3 -> DOT_TXT
77 
78             BASELINE -> DOT_TXT
79 
80             JDIFF, SINCE_XML -> DOT_XML
81 
82             UNKNOWN -> ""
83         }
84     }
85 
headernull86     fun header(): String? {
87         val prefix = headerPrefix() ?: return null
88         return prefix + version + "\n"
89     }
90 
headerPrefixnull91     private fun headerPrefix(): String? {
92         return when (this) {
93             V1 -> null
94             V2, V3 -> "// Signature format: "
95             BASELINE -> "// Baseline format: "
96             JDIFF, SINCE_XML -> "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
97             UNKNOWN -> null
98         }
99     }
100 
isSignatureFormatnull101     fun isSignatureFormat(): Boolean {
102         return this == V1 || this == V2 || this == V3
103     }
104 
105     companion object {
firstLinenull106         private fun firstLine(s: String): String {
107             val index = s.indexOf('\n')
108             if (index == -1) {
109                 return s
110             }
111             // Chop off \r if a Windows \r\n file
112             val end = if (index > 0 && s[index - 1] == '\r') index - 1 else index
113             return s.substring(0, end)
114         }
115 
parseHeadernull116         fun parseHeader(fileContents: String): FileFormat {
117             val firstLine = firstLine(fileContents)
118             for (format in values()) {
119                 val header = format.header()
120                 if (header == null) {
121                     if (firstLine.startsWith("package ")) {
122                         // Old signature files
123                         return V1
124                     } else if (firstLine.startsWith("<api")) {
125                         return JDIFF
126                     }
127                 } else if (header.startsWith(firstLine)) {
128                     if (format == JDIFF) {
129                         if (!fileContents.contains("<api")) {
130                             // The JDIFF header is the general XML header: don't accept XML documents that
131                             // don't contain an empty API definition
132                             return UNKNOWN
133                         }
134                         // Both JDiff and API-level files use <api> as the root tag (unfortunate but too late to
135                         // change) so distinguish on whether the file contains any since elements
136                         if (fileContents.contains("since=")) {
137                             return SINCE_XML
138                         }
139                     }
140                     return format
141                 }
142             }
143 
144             return UNKNOWN
145         }
146     }
147 }