• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * 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 
17 package com.android.server.sdksandbox.verifier;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 
22 import java.util.Arrays;
23 import java.util.LinkedHashMap;
24 import java.util.List;
25 import java.util.Map;
26 
27 /**
28  * StringTrie is a string equality version of {@link com.android.tradefed.util.RegexTrie} It
29  * supports wildcard matching, null being the wildcard key. Wildcard can be in leaf nodes and inner
30  * nodes in the trie. This variant favors exact matching over wildcard matching. Matches by wildcard
31  * will be added to the captures. This structure is not thread safe.
32  *
33  * @param <V> type of the values contained in the trie
34  * @hide
35  */
36 public class StringTrie<V> {
37     private V mValue = null;
38     private Map<String, StringTrie<V>> mChildren = new LinkedHashMap<String, StringTrie<V>>();
39 
40     /** Clears the trie */
clear()41     public void clear() {
42         mValue = null;
43         for (StringTrie<V> child : mChildren.values()) {
44             child.clear();
45         }
46         mChildren.clear();
47     }
48 
containsKey(String... strings)49     boolean containsKey(String... strings) {
50         return retrieve(strings) != null;
51     }
52 
53     @Nullable
recursivePut(V value, List<String> keys)54     V recursivePut(V value, List<String> keys) {
55         // Cases:
56         // 1) keys is empty -- set our value
57         // 2) keys is non-empty -- recurse downward, creating a child if necessary
58         if (keys.isEmpty()) {
59             V oldValue = mValue;
60             mValue = value;
61             return oldValue;
62         } else {
63             String curKey = keys.get(0);
64             List<String> nextKeys = keys.subList(1, keys.size());
65 
66             // Create a new child to handle
67             StringTrie<V> nextChild = mChildren.get(curKey);
68             if (nextChild == null) {
69                 nextChild = new StringTrie<V>();
70                 mChildren.put(curKey, nextChild);
71             }
72             return nextChild.recursivePut(value, nextKeys);
73         }
74     }
75 
76     /**
77      * Add an entry to the trie.
78      *
79      * @param value The value to set
80      * @param keys The sequence of {@link Strings}s that must be sequentially matched to retrieve
81      *     the associated {@code value}
82      * @return previous value stored for the given key sequence or null if no value was stored
83      */
put(V value, String... keys)84     public @Nullable V put(V value, String... keys) {
85         if (keys.length == 0) {
86             throw new IllegalArgumentException("string list must be non-empty.");
87         }
88         List<String> kList = Arrays.asList(keys);
89         return recursivePut(value, kList);
90     }
91 
92     @Nullable
recursiveRetrieve(List<String> captures, List<String> strings)93     V recursiveRetrieve(List<String> captures, List<String> strings) {
94         // Cases:
95         // 1) strings is empty -- return the value of this node
96         // 2) strings is non-empty -- find the first child that matches, recurse downward
97         if (strings.isEmpty()) {
98             return mValue;
99 
100         } else {
101             String curKey = strings.get(0);
102             List<String> nextKeys = strings.subList(1, strings.size());
103 
104             // a more specific match takes precedence over a wildcard match
105             if (mChildren.containsKey(curKey)) {
106                 V exactMatch = mChildren.get(curKey).recursiveRetrieve(captures, nextKeys);
107                 if (exactMatch != null) {
108                     return exactMatch;
109                 }
110             }
111 
112             V wildcardMatch = null;
113 
114             if (mChildren.containsKey(null)) {
115                 if (captures != null) {
116                     captures.add(curKey);
117                 }
118                 if (!nextKeys.isEmpty()) {
119                     // if there is a match after the wildcard continue down the tree
120                     if (mChildren.get(null).mChildren.containsKey(nextKeys.get(0))) {
121                         wildcardMatch = mChildren.get(null).recursiveRetrieve(captures, nextKeys);
122                     } else {
123                         // otherwise, keep matching wildcard
124                         wildcardMatch = recursiveRetrieve(captures, nextKeys);
125                     }
126                 } else {
127                     // last key to be matched with wildcard
128                     wildcardMatch = mChildren.get(null).recursiveRetrieve(captures, nextKeys);
129                 }
130             }
131 
132             return wildcardMatch;
133         }
134     }
135 
136     /**
137      * Fetch a value from the trie, by matching the provided sequence of {@link String}s to a
138      * sequence stored in the trie.
139      *
140      * @param strings A sequence of {@link String}s to match
141      * @return The associated value, or {@code null} if no value was found
142      */
retrieve(@onNull String... strings)143     public @Nullable V retrieve(@NonNull String... strings) {
144         return retrieve(null, strings);
145     }
146 
147     /**
148      * Fetch a value from the trie, by matching the provided sequence of {@link String}s to a
149      * sequence of {@link Strings}s stored in the trie. This version of the method also returns a
150      * {@link List} of matched captures.
151      *
152      * <p>For each level, the matched strings will be stored. Note that {@code captures} will be
153      * {@link List#clear()}ed before the retrieval begins. Also, if the retrieval fails after a
154      * partial sequence of matches, {@code captures} will still reflect the capture groups from the
155      * partial match.
156      *
157      * @param captures A {@code List<String>} through which wildcard matched keys will be returned.
158      * @param strings A sequence of {@link String}s to match
159      * @return The associated value, or {@code null} if no value was found
160      */
retrieve(@ullable List<String> captures, @NonNull String... strings)161     public @Nullable V retrieve(@Nullable List<String> captures, @NonNull String... strings) {
162         if (strings == null || strings.length == 0) {
163             throw new IllegalArgumentException("string list must be non-empty");
164         }
165         List<String> sList = Arrays.asList(strings);
166         if (captures != null) {
167             captures.clear();
168         }
169         return recursiveRetrieve(captures, sList);
170     }
171 
172     @Override
toString()173     public String toString() {
174         return String.format("{V: %s, C: %s}", mValue, mChildren);
175     }
176 }
177