1 /* 2 * Copyright (C) 2006 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 android.content; 18 19 import android.net.Uri; 20 21 import java.util.ArrayList; 22 import java.util.List; 23 import java.util.regex.Pattern; 24 25 /** 26 Utility class to aid in matching URIs in content providers. 27 28 <p>To use this class, build up a tree of <code>UriMatcher</code> objects. 29 For example: 30 <pre> 31 private static final int PEOPLE = 1; 32 private static final int PEOPLE_ID = 2; 33 private static final int PEOPLE_PHONES = 3; 34 private static final int PEOPLE_PHONES_ID = 4; 35 private static final int PEOPLE_CONTACTMETHODS = 7; 36 private static final int PEOPLE_CONTACTMETHODS_ID = 8; 37 38 private static final int DELETED_PEOPLE = 20; 39 40 private static final int PHONES = 9; 41 private static final int PHONES_ID = 10; 42 private static final int PHONES_FILTER = 14; 43 44 private static final int CONTACTMETHODS = 18; 45 private static final int CONTACTMETHODS_ID = 19; 46 47 private static final int CALLS = 11; 48 private static final int CALLS_ID = 12; 49 private static final int CALLS_FILTER = 15; 50 51 private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); 52 53 static 54 { 55 sURIMatcher.addURI("contacts", "people", PEOPLE); 56 sURIMatcher.addURI("contacts", "people/#", PEOPLE_ID); 57 sURIMatcher.addURI("contacts", "people/#/phones", PEOPLE_PHONES); 58 sURIMatcher.addURI("contacts", "people/#/phones/#", PEOPLE_PHONES_ID); 59 sURIMatcher.addURI("contacts", "people/#/contact_methods", PEOPLE_CONTACTMETHODS); 60 sURIMatcher.addURI("contacts", "people/#/contact_methods/#", PEOPLE_CONTACTMETHODS_ID); 61 sURIMatcher.addURI("contacts", "deleted_people", DELETED_PEOPLE); 62 sURIMatcher.addURI("contacts", "phones", PHONES); 63 sURIMatcher.addURI("contacts", "phones/filter/*", PHONES_FILTER); 64 sURIMatcher.addURI("contacts", "phones/#", PHONES_ID); 65 sURIMatcher.addURI("contacts", "contact_methods", CONTACTMETHODS); 66 sURIMatcher.addURI("contacts", "contact_methods/#", CONTACTMETHODS_ID); 67 sURIMatcher.addURI("call_log", "calls", CALLS); 68 sURIMatcher.addURI("call_log", "calls/filter/*", CALLS_FILTER); 69 sURIMatcher.addURI("call_log", "calls/#", CALLS_ID); 70 } 71 </pre> 72 <p>Then when you need to match against a URI, call {@link #match}, providing 73 the URL that you have been given. You can use the result to build a query, 74 return a type, insert or delete a row, or whatever you need, without duplicating 75 all of the if-else logic that you would otherwise need. For example: 76 <pre> 77 public String getType(Uri url) 78 { 79 int match = sURIMatcher.match(url); 80 switch (match) 81 { 82 case PEOPLE: 83 return "vnd.android.cursor.dir/person"; 84 case PEOPLE_ID: 85 return "vnd.android.cursor.item/person"; 86 ... snip ... 87 return "vnd.android.cursor.dir/snail-mail"; 88 case PEOPLE_ADDRESS_ID: 89 return "vnd.android.cursor.item/snail-mail"; 90 default: 91 return null; 92 } 93 } 94 </pre> 95 instead of: 96 <pre> 97 public String getType(Uri url) 98 { 99 List<String> pathSegments = url.getPathSegments(); 100 if (pathSegments.size() >= 2) { 101 if ("people".equals(pathSegments.get(1))) { 102 if (pathSegments.size() == 2) { 103 return "vnd.android.cursor.dir/person"; 104 } else if (pathSegments.size() == 3) { 105 return "vnd.android.cursor.item/person"; 106 ... snip ... 107 return "vnd.android.cursor.dir/snail-mail"; 108 } else if (pathSegments.size() == 3) { 109 return "vnd.android.cursor.item/snail-mail"; 110 } 111 } 112 } 113 return null; 114 } 115 </pre> 116 */ 117 public class UriMatcher 118 { 119 public static final int NO_MATCH = -1; 120 /** 121 * Creates the root node of the URI tree. 122 * 123 * @param code the code to match for the root URI 124 */ UriMatcher(int code)125 public UriMatcher(int code) 126 { 127 mCode = code; 128 mWhich = -1; 129 mChildren = new ArrayList<UriMatcher>(); 130 mText = null; 131 } 132 UriMatcher()133 private UriMatcher() 134 { 135 mCode = NO_MATCH; 136 mWhich = -1; 137 mChildren = new ArrayList<UriMatcher>(); 138 mText = null; 139 } 140 141 /** 142 * Add a URI to match, and the code to return when this URI is 143 * matched. URI nodes may be exact match string, the token "*" 144 * that matches any text, or the token "#" that matches only 145 * numbers. 146 * 147 * @param authority the authority to match 148 * @param path the path to match. * may be used as a wild card for 149 * any text, and # may be used as a wild card for numbers. 150 * @param code the code that is returned when a URI is matched 151 * against the given components. Must be positive. 152 */ addURI(String authority, String path, int code)153 public void addURI(String authority, String path, int code) 154 { 155 if (code < 0) { 156 throw new IllegalArgumentException("code " + code + " is invalid: it must be positive"); 157 } 158 String[] tokens = path != null ? PATH_SPLIT_PATTERN.split(path) : null; 159 int numTokens = tokens != null ? tokens.length : 0; 160 UriMatcher node = this; 161 for (int i = -1; i < numTokens; i++) { 162 String token = i < 0 ? authority : tokens[i]; 163 ArrayList<UriMatcher> children = node.mChildren; 164 int numChildren = children.size(); 165 UriMatcher child; 166 int j; 167 for (j = 0; j < numChildren; j++) { 168 child = children.get(j); 169 if (token.equals(child.mText)) { 170 node = child; 171 break; 172 } 173 } 174 if (j == numChildren) { 175 // Child not found, create it 176 child = new UriMatcher(); 177 if (token.equals("#")) { 178 child.mWhich = NUMBER; 179 } else if (token.equals("*")) { 180 child.mWhich = TEXT; 181 } else { 182 child.mWhich = EXACT; 183 } 184 child.mText = token; 185 node.mChildren.add(child); 186 node = child; 187 } 188 } 189 node.mCode = code; 190 } 191 192 static final Pattern PATH_SPLIT_PATTERN = Pattern.compile("/"); 193 194 /** 195 * Try to match against the path in a url. 196 * 197 * @param uri The url whose path we will match against. 198 * 199 * @return The code for the matched node (added using addURI), 200 * or -1 if there is no matched node. 201 */ 202 public int match(Uri uri) 203 { 204 final List<String> pathSegments = uri.getPathSegments(); 205 final int li = pathSegments.size(); 206 207 UriMatcher node = this; 208 209 if (li == 0 && uri.getAuthority() == null) { 210 return this.mCode; 211 } 212 213 for (int i=-1; i<li; i++) { 214 String u = i < 0 ? uri.getAuthority() : pathSegments.get(i); 215 ArrayList<UriMatcher> list = node.mChildren; 216 if (list == null) { 217 break; 218 } 219 node = null; 220 int lj = list.size(); 221 for (int j=0; j<lj; j++) { 222 UriMatcher n = list.get(j); 223 which_switch: 224 switch (n.mWhich) { 225 case EXACT: 226 if (n.mText.equals(u)) { 227 node = n; 228 } 229 break; 230 case NUMBER: 231 int lk = u.length(); 232 for (int k=0; k<lk; k++) { 233 char c = u.charAt(k); 234 if (c < '0' || c > '9') { 235 break which_switch; 236 } 237 } 238 node = n; 239 break; 240 case TEXT: 241 node = n; 242 break; 243 } 244 if (node != null) { 245 break; 246 } 247 } 248 if (node == null) { 249 return NO_MATCH; 250 } 251 } 252 253 return node.mCode; 254 } 255 256 private static final int EXACT = 0; 257 private static final int NUMBER = 1; 258 private static final int TEXT = 2; 259 260 private int mCode; 261 private int mWhich; 262 private String mText; 263 private ArrayList<UriMatcher> mChildren; 264 } 265