• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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