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 24 /** 25 Utility class to aid in matching URIs in content providers. 26 27 <p>To use this class, build up a tree of <code>UriMatcher</code> objects. 28 For example: 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(UriMatcher.NO_MATCH); 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>Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, paths can start 72 with a leading slash. For example: 73 <pre> 74 sURIMatcher.addURI("contacts", "/people", PEOPLE); 75 </pre> 76 <p>Then when you need to match against a URI, call {@link #match}, providing 77 the URL that you have been given. You can use the result to build a query, 78 return a type, insert or delete a row, or whatever you need, without duplicating 79 all of the if-else logic that you would otherwise need. For example: 80 <pre> 81 public String getType(Uri url) 82 { 83 int match = sURIMatcher.match(url); 84 switch (match) 85 { 86 case PEOPLE: 87 return "vnd.android.cursor.dir/person"; 88 case PEOPLE_ID: 89 return "vnd.android.cursor.item/person"; 90 ... snip ... 91 return "vnd.android.cursor.dir/snail-mail"; 92 case PEOPLE_ADDRESS_ID: 93 return "vnd.android.cursor.item/snail-mail"; 94 default: 95 return null; 96 } 97 } 98 </pre> 99 instead of: 100 <pre> 101 public String getType(Uri url) 102 { 103 List<String> pathSegments = url.getPathSegments(); 104 if (pathSegments.size() >= 2) { 105 if ("people".equals(pathSegments.get(1))) { 106 if (pathSegments.size() == 2) { 107 return "vnd.android.cursor.dir/person"; 108 } else if (pathSegments.size() == 3) { 109 return "vnd.android.cursor.item/person"; 110 ... snip ... 111 return "vnd.android.cursor.dir/snail-mail"; 112 } else if (pathSegments.size() == 3) { 113 return "vnd.android.cursor.item/snail-mail"; 114 } 115 } 116 } 117 return null; 118 } 119 </pre> 120 */ 121 public class UriMatcher 122 { 123 public static final int NO_MATCH = -1; 124 /** 125 * Creates the root node of the URI tree. 126 * 127 * @param code the code to match for the root URI 128 */ UriMatcher(int code)129 public UriMatcher(int code) 130 { 131 mCode = code; 132 mWhich = -1; 133 mChildren = new ArrayList<UriMatcher>(); 134 mText = null; 135 } 136 UriMatcher()137 private UriMatcher() 138 { 139 mCode = NO_MATCH; 140 mWhich = -1; 141 mChildren = new ArrayList<UriMatcher>(); 142 mText = null; 143 } 144 145 /** 146 * Add a URI to match, and the code to return when this URI is 147 * matched. URI nodes may be exact match string, the token "*" 148 * that matches any text, or the token "#" that matches only 149 * numbers. 150 * <p> 151 * Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, 152 * this method will accept a leading slash in the path. 153 * 154 * @param authority the authority to match 155 * @param path the path to match. * may be used as a wild card for 156 * any text, and # may be used as a wild card for numbers. 157 * @param code the code that is returned when a URI is matched 158 * against the given components. Must be positive. 159 */ addURI(String authority, String path, int code)160 public void addURI(String authority, String path, int code) 161 { 162 if (code < 0) { 163 throw new IllegalArgumentException("code " + code + " is invalid: it must be positive"); 164 } 165 166 String[] tokens = null; 167 if (path != null) { 168 String newPath = path; 169 // Strip leading slash if present. 170 if (path.length() > 1 && path.charAt(0) == '/') { 171 newPath = path.substring(1); 172 } 173 tokens = newPath.split("/"); 174 } 175 176 int numTokens = tokens != null ? tokens.length : 0; 177 UriMatcher node = this; 178 for (int i = -1; i < numTokens; i++) { 179 String token = i < 0 ? authority : tokens[i]; 180 ArrayList<UriMatcher> children = node.mChildren; 181 int numChildren = children.size(); 182 UriMatcher child; 183 int j; 184 for (j = 0; j < numChildren; j++) { 185 child = children.get(j); 186 if (token.equals(child.mText)) { 187 node = child; 188 break; 189 } 190 } 191 if (j == numChildren) { 192 // Child not found, create it 193 child = new UriMatcher(); 194 if (token.equals("#")) { 195 child.mWhich = NUMBER; 196 } else if (token.equals("*")) { 197 child.mWhich = TEXT; 198 } else { 199 child.mWhich = EXACT; 200 } 201 child.mText = token; 202 node.mChildren.add(child); 203 node = child; 204 } 205 } 206 node.mCode = code; 207 } 208 209 /** 210 * Try to match against the path in a url. 211 * 212 * @param uri The url whose path we will match against. 213 * 214 * @return The code for the matched node (added using addURI), 215 * or -1 if there is no matched node. 216 */ 217 public int match(Uri uri) 218 { 219 final List<String> pathSegments = uri.getPathSegments(); 220 final int li = pathSegments.size(); 221 222 UriMatcher node = this; 223 224 if (li == 0 && uri.getAuthority() == null) { 225 return this.mCode; 226 } 227 228 for (int i=-1; i<li; i++) { 229 String u = i < 0 ? uri.getAuthority() : pathSegments.get(i); 230 ArrayList<UriMatcher> list = node.mChildren; 231 if (list == null) { 232 break; 233 } 234 node = null; 235 int lj = list.size(); 236 for (int j=0; j<lj; j++) { 237 UriMatcher n = list.get(j); 238 which_switch: 239 switch (n.mWhich) { 240 case EXACT: 241 if (n.mText.equals(u)) { 242 node = n; 243 } 244 break; 245 case NUMBER: 246 int lk = u.length(); 247 for (int k=0; k<lk; k++) { 248 char c = u.charAt(k); 249 if (c < '0' || c > '9') { 250 break which_switch; 251 } 252 } 253 node = n; 254 break; 255 case TEXT: 256 node = n; 257 break; 258 } 259 if (node != null) { 260 break; 261 } 262 } 263 if (node == null) { 264 return NO_MATCH; 265 } 266 } 267 268 return node.mCode; 269 } 270 271 private static final int EXACT = 0; 272 private static final int NUMBER = 1; 273 private static final int TEXT = 2; 274 275 private int mCode; 276 private int mWhich; 277 private String mText; 278 private ArrayList<UriMatcher> mChildren; 279 } 280