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