1 /* <lambda>null2 * Copyright (C) 2023 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 package com.android.hoststubgen.utils 17 18 import com.android.hoststubgen.ParseException 19 import com.android.hoststubgen.asm.toHumanReadableClassName 20 import com.android.hoststubgen.asm.toJvmClassName 21 import com.android.hoststubgen.normalizeTextLine 22 import java.io.File 23 24 /** 25 * General purpose filter for class names. 26 */ 27 class ClassFilter private constructor ( 28 val defaultResult: Boolean, 29 ) { 30 private data class FilterElement( 31 val allowed: Boolean, 32 val internalName: String, 33 val isPrefix: Boolean, 34 ) { 35 fun matches(classInternalName: String): Boolean { 36 if (isPrefix) { 37 return classInternalName.startsWith(internalName) 38 } else { 39 return classInternalName == internalName 40 } 41 } 42 } 43 44 private val elements: MutableList<FilterElement> = mutableListOf() 45 46 private val cache: MutableMap<String, Boolean> = mutableMapOf() 47 48 /** 49 * Takes an internal class name (e.g. "com/android/hoststubgen/ClassName") and returns if 50 * matches the filter or not. 51 */ 52 fun matches(classInternalName: String): Boolean { 53 cache[classInternalName]?.let { 54 return it 55 } 56 57 var result = defaultResult 58 run outer@{ 59 elements.forEach { e -> 60 if (e.matches(classInternalName)) { 61 result = e.allowed 62 return@outer // break equivalent. 63 } 64 } 65 } 66 cache[classInternalName] = result 67 68 return result 69 } 70 71 fun getCacheSizeForTest(): Int { 72 return cache.size 73 } 74 75 companion object { 76 /** 77 * Return a filter that alawys returns true or false. 78 */ 79 fun newNullFilter(defaultResult: Boolean): ClassFilter { 80 return ClassFilter(defaultResult) 81 } 82 83 /** Build a filter from a file. */ 84 fun loadFromFile(filename: String, defaultResult: Boolean): ClassFilter { 85 return buildFromString(File(filename).readText(), defaultResult, filename) 86 } 87 88 /** Build a filter from a string (for unit tests). */ 89 fun buildFromString( 90 filterString: String, 91 defaultResult: Boolean, 92 filenameForErrorMessage: String 93 ): ClassFilter { 94 val ret = ClassFilter(defaultResult) 95 96 var lineNo = 0 97 filterString.split('\n').forEach { s -> 98 lineNo++ 99 100 var line = normalizeTextLine(s) 101 102 if (line.isEmpty()) { 103 return@forEach // skip empty lines. 104 } 105 106 line = line.toHumanReadableClassName() // Convert all the slashes to periods. 107 108 var allow = true 109 if (line.startsWith("!")) { 110 allow = false 111 line = line.substring(1).trimStart() 112 } 113 114 // Special case -- matches any class names. 115 if (line == "*") { 116 ret.elements.add(FilterElement(allow, "", true)) 117 return@forEach 118 } 119 120 // Handle wildcard -- e.g. "package.name.*" 121 if (line.endsWith(".*")) { 122 ret.elements.add(FilterElement( 123 allow, line.substring(0, line.length - 2).toJvmClassName(), true)) 124 return@forEach 125 } 126 127 // Any other uses of "*" would be an error. 128 if (line.contains('*')) { 129 throw ParseException( 130 "Wildcard (*) can only show up as the last element", 131 filenameForErrorMessage, 132 lineNo 133 ) 134 } 135 ret.elements.add(FilterElement(allow, line.toJvmClassName(), false)) 136 } 137 138 return ret 139 } 140 } 141 }