1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 @file:JvmName("DalComponentParser")
18 
19 package com.android.statementservice.parser
20 
21 import android.os.PatternMatcher.PATTERN_ADVANCED_GLOB
22 import android.os.PatternMatcher.PATTERN_LITERAL
23 import android.os.PatternMatcher.PATTERN_PREFIX
24 import android.os.PatternMatcher.PATTERN_SIMPLE_GLOB
25 
26 /**
27  * Parses a DAL component matching expression to Android's {@link android.os.PatternMatcher} type
28  * and pattern. Matching expressions support the following wildcards:
29  *
30  *  1) An asterisk (*) matches zero to as many characters as possible
31  *  2) A question mark (?) matches any single character.
32  *
33  * Matching one to many characters can be done with a question mark followed by an asterisk (?+).
34  *
35  * @param expression A matching expression string from a DAL relation extension component used for
36  *                   matching a URI part. This must be a non-empty string and all characters in the
37  *                   string should be decoded.
38  *
39  * @return Returns a Pair containing a {@link android.os.PatternMatcher} type and pattern.
40  */
parseMatchingExpressionnull41 fun parseMatchingExpression(expression: String): Pair<Int, String> {
42     if (expression.isNullOrEmpty()) {
43         throw IllegalArgumentException("Matching expressions cannot be an empty string")
44     }
45     var count = 0
46     var isAdvanced = expression.contains("?*")
47     val pattern = buildString {
48         for (char in expression) {
49             when (char) {
50                 '*' -> {
51                     if (this.endsWith('.') && !this.endsWith("\\.")) {
52                         append('+')
53                     } else {
54                         count += 1
55                         append(".*")
56                     }
57                 }
58                 '?' -> {
59                     count += 1
60                     append('.')
61                 }
62                 '.' -> {
63                     append("\\.")
64                 }
65                 '[', ']', '{', '}' -> {
66                     if (isAdvanced) {
67                         append('\\')
68                     }
69                     append(char)
70                 }
71                 else -> append(char)
72             }
73         }
74     }
75     if (count == 0) {
76         return Pair(PATTERN_LITERAL, pattern)
77     }
78     if (count == 1 && pattern.endsWith(".*")) {
79         return Pair(PATTERN_PREFIX, pattern.dropLast(2))
80     }
81     if (isAdvanced) {
82         return Pair(PATTERN_ADVANCED_GLOB, pattern)
83     }
84     return Pair(PATTERN_SIMPLE_GLOB, pattern)
85 }