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 (i in 0 until source.length) {
55 val c = source[i]
56 when (state) {
57 INIT -> {
58 when (c) {
59 '/' -> state = INIT_SLASH
60 '"' -> {
61 state = IN_STRING
62 sb.append(c)
63 }
64 '\'' -> {
65 state = IN_CHAR
66 sb.append(c)
67 }
68 else -> sb.append(c)
69 }
70 }
71 INIT_SLASH -> {
72 when {
73 c == '*' -> state = BLOCK_COMMENT
74 c == '/' && stripLineComments -> state = LINE_COMMENT
75 else -> {
76 state = INIT
77 sb.append('/') // because we skipped it in init
78 sb.append(c)
79 }
80 }
81 }
82 LINE_COMMENT -> {
83 when (c) {
84 '\n' -> state = INIT
85 }
86 }
87 BLOCK_COMMENT -> {
88 when (c) {
89 '*' -> state = BLOCK_COMMENT_ASTERISK
90 }
91 }
92 BLOCK_COMMENT_ASTERISK -> {
93 state = when (c) {
94 '/' -> INIT
95 '*' -> BLOCK_COMMENT_ASTERISK
96 else -> BLOCK_COMMENT
97 }
98 }
99 IN_STRING -> {
100 when (c) {
101 '\\' -> state = IN_STRING_ESCAPE
102 '"' -> state = INIT
103 }
104 sb.append(c)
105 }
106 IN_STRING_ESCAPE -> {
107 sb.append(c)
108 state = IN_STRING
109 }
110 IN_CHAR -> {
111 if (c != '\\') {
112 state = AFTER_CHAR
113 }
114 sb.append(c)
115 }
116 AFTER_CHAR -> {
117 sb.append(c)
118 if (c == '\\') {
119 state = INIT
120 }
121 }
122 }
123 }
124
125 return sb.toString()
126 }
127
128 private val PACKAGE_PATTERN = Pattern.compile("""package\s+([\S&&[^;]]*)""")
129
130 private val CLASS_PATTERN = Pattern.compile(
131 """(class|interface|enum|object)+?\s*([^\s:(]+)""",
132 Pattern.MULTILINE
133 )
134
getPackagenull135 fun getPackage(source: String): String? {
136 val matcher = PACKAGE_PATTERN.matcher(source)
137 return if (matcher.find()) {
138 matcher.group(1).trim()
139 } else {
140 null
141 }
142 }
143
getClassNamenull144 fun getClassName(source: String): String? {
145 val matcher = CLASS_PATTERN.matcher(source.replace('\n', ' '))
146 var start = 0
147 while (matcher.find(start)) {
148 val cls = matcher.group(2)
149 val groupStart = matcher.start(2)
150
151 // Make sure this "class" reference isn't part of an annotation on the class
152 // referencing a class literal -- Foo.class, or in Kotlin, Foo::class.java)
153 if (groupStart == 0 || source[groupStart - 1] != '.' && source[groupStart - 1] != ':') {
154 val trimmed = cls.trim { it <= ' ' }
155 val typeParameter = trimmed.indexOf('<')
156 return if (typeParameter != -1) {
157 trimmed.substring(0, typeParameter)
158 } else {
159 trimmed
160 }
161 }
162 start = matcher.end(2)
163 }
164
165 return null
166 }
167