• 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(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 }