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