1 /*
2 * Copyright (C) 2017 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.lint.checks.infrastructure
18
19 import java.util.regex.Pattern
20
21 // Copy in metalava from lint to avoid compilation dependency directly on lint-tests
22
23 /** A pair of package name and class name inferred from Java or Kotlin source code */
24 class ClassName(source: String) {
25 val packageName: String?
26 val className: String?
27
28 init {
29 val withoutComments = stripComments(source)
30 packageName = getPackage(withoutComments)
31 className = getClassName(withoutComments)
32 }
33
34 @Suppress("unused")
packageNameWithDefaultnull35 fun packageNameWithDefault() = packageName ?: ""
36 }
37
38 /**
39 * Strips line and block comments from the given Java or Kotlin source file
40 */
41 @Suppress("LocalVariableName")
42 fun stripComments(source: String, stripLineComments: Boolean = true): String {
43 val sb = StringBuilder(source.length)
44 var state = 0
45 val INIT = 0
46 val INIT_SLASH = 1
47 val LINE_COMMENT = 2
48 val BLOCK_COMMENT = 3
49 val BLOCK_COMMENT_ASTERISK = 4
50 val IN_STRING = 5
51 val IN_STRING_ESCAPE = 6
52 val IN_CHAR = 7
53 val AFTER_CHAR = 8
54 for (c in source) {
55 when (state) {
56 INIT -> {
57 when (c) {
58 '/' -> state = INIT_SLASH
59 '"' -> {
60 state = IN_STRING
61 sb.append(c)
62 }
63 '\'' -> {
64 state = IN_CHAR
65 sb.append(c)
66 }
67 else -> sb.append(c)
68 }
69 }
70 INIT_SLASH -> {
71 when {
72 c == '*' -> state = BLOCK_COMMENT
73 c == '/' && stripLineComments -> state = LINE_COMMENT
74 else -> {
75 state = INIT
76 sb.append('/') // because we skipped it in init
77 sb.append(c)
78 }
79 }
80 }
81 LINE_COMMENT -> {
82 when (c) {
83 '\n' -> state = INIT
84 }
85 }
86 BLOCK_COMMENT -> {
87 when (c) {
88 '*' -> state = BLOCK_COMMENT_ASTERISK
89 }
90 }
91 BLOCK_COMMENT_ASTERISK -> {
92 state = when (c) {
93 '/' -> INIT
94 '*' -> BLOCK_COMMENT_ASTERISK
95 else -> BLOCK_COMMENT
96 }
97 }
98 IN_STRING -> {
99 when (c) {
100 '\\' -> state = IN_STRING_ESCAPE
101 '"' -> state = INIT
102 }
103 sb.append(c)
104 }
105 IN_STRING_ESCAPE -> {
106 sb.append(c)
107 state = IN_STRING
108 }
109 IN_CHAR -> {
110 if (c != '\\') {
111 state = AFTER_CHAR
112 }
113 sb.append(c)
114 }
115 AFTER_CHAR -> {
116 sb.append(c)
117 if (c == '\\') {
118 state = INIT
119 }
120 }
121 }
122 }
123
124 return sb.toString()
125 }
126
127 private val PACKAGE_PATTERN = Pattern.compile("""package\s+([\S&&[^;]]*)""")
128
129 private val CLASS_PATTERN = Pattern.compile(
130 """(class|interface|enum|object)+?\s*([^\s:(]+)""",
131 Pattern.MULTILINE
132 )
133
getPackagenull134 fun getPackage(source: String): String? {
135 val matcher = PACKAGE_PATTERN.matcher(source)
136 return if (matcher.find()) {
137 matcher.group(1).trim()
138 } else {
139 null
140 }
141 }
142
getClassNamenull143 fun getClassName(source: String): String? {
144 val matcher = CLASS_PATTERN.matcher(source.replace('\n', ' '))
145 var start = 0
146 while (matcher.find(start)) {
147 val cls = matcher.group(2)
148 val groupStart = matcher.start(2)
149
150 // Make sure this "class" reference isn't part of an annotation on the class
151 // referencing a class literal -- Foo.class, or in Kotlin, Foo::class.java)
152 if (groupStart == 0 || source[groupStart - 1] != '.' && source[groupStart - 1] != ':') {
153 val trimmed = cls.trim { it <= ' ' }
154 val typeParameter = trimmed.indexOf('<')
155 return if (typeParameter != -1) {
156 trimmed.substring(0, typeParameter)
157 } else {
158 trimmed
159 }
160 }
161 start = matcher.end(2)
162 }
163
164 return null
165 }
166