The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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; |
Bjorn Bringert | 3a184ef | 2009-04-02 09:16:44 -0700 | [diff] [blame] | 22 | import java.util.List; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 23 | |
| 24 | /** |
| 25 | Utility class to aid in matching URIs in content providers. |
| 26 | |
Daniel Trebbien | 0a675fd | 2010-10-31 07:42:35 -0700 | [diff] [blame] | 27 | <p>To use this class, build up a tree of <code>UriMatcher</code> objects. |
| 28 | For example: |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | |
Daniel Trebbien | 0a675fd | 2010-10-31 07:42:35 -0700 | [diff] [blame] | 50 | private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 51 | |
| 52 | static |
| 53 | { |
Daniel Trebbien | 0a675fd | 2010-10-31 07:42:35 -0700 | [diff] [blame] | 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); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 69 | } |
| 70 | </pre> |
Chiao Cheng | ef23bf1 | 2013-03-20 13:12:41 -0700 | [diff] [blame] | 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> |
Daniel Trebbien | 0a675fd | 2010-10-31 07:42:35 -0700 | [diff] [blame] | 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: |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 80 | <pre> |
Daniel Trebbien | 0a675fd | 2010-10-31 07:42:35 -0700 | [diff] [blame] | 81 | public String getType(Uri url) |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 82 | { |
Daniel Trebbien | 0a675fd | 2010-10-31 07:42:35 -0700 | [diff] [blame] | 83 | int match = sURIMatcher.match(url); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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> |
Daniel Trebbien | 0a675fd | 2010-10-31 07:42:35 -0700 | [diff] [blame] | 99 | instead of: |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 100 | <pre> |
Daniel Trebbien | 0a675fd | 2010-10-31 07:42:35 -0700 | [diff] [blame] | 101 | public String getType(Uri url) |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 102 | { |
Daniel Trebbien | 0a675fd | 2010-10-31 07:42:35 -0700 | [diff] [blame] | 103 | List<String> pathSegments = url.getPathSegments(); |
| 104 | if (pathSegments.size() >= 2) { |
| 105 | if ("people".equals(pathSegments.get(1))) { |
| 106 | if (pathSegments.size() == 2) { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 107 | return "vnd.android.cursor.dir/person"; |
Daniel Trebbien | 0a675fd | 2010-10-31 07:42:35 -0700 | [diff] [blame] | 108 | } else if (pathSegments.size() == 3) { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 109 | return "vnd.android.cursor.item/person"; |
| 110 | ... snip ... |
| 111 | return "vnd.android.cursor.dir/snail-mail"; |
Daniel Trebbien | 0a675fd | 2010-10-31 07:42:35 -0700 | [diff] [blame] | 112 | } else if (pathSegments.size() == 3) { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | */ |
| 129 | public UriMatcher(int code) |
| 130 | { |
| 131 | mCode = code; |
| 132 | mWhich = -1; |
| 133 | mChildren = new ArrayList<UriMatcher>(); |
| 134 | mText = null; |
| 135 | } |
| 136 | |
| 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. |
Chiao Cheng | ef23bf1 | 2013-03-20 13:12:41 -0700 | [diff] [blame] | 150 | * <p> |
| 151 | * Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, |
Newton Allen | 8f8a11b | 2013-11-26 10:25:38 -0800 | [diff] [blame] | 152 | * this method will accept a leading slash in the path. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | */ |
| 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 | } |
Chiao Cheng | ef23bf1 | 2013-03-20 13:12:41 -0700 | [diff] [blame] | 165 | |
| 166 | String[] tokens = null; |
| 167 | if (path != null) { |
| 168 | String newPath = path; |
| 169 | // Strip leading slash if present. |
Hidehiko Tsuchiya | 5acd41d | 2014-03-25 18:27:27 +0900 | [diff] [blame] | 170 | if (path.length() > 1 && path.charAt(0) == '/') { |
Chiao Cheng | ef23bf1 | 2013-03-20 13:12:41 -0700 | [diff] [blame] | 171 | newPath = path.substring(1); |
| 172 | } |
Andreas Gampe | 055678b | 2015-03-06 15:53:06 -0800 | [diff] [blame] | 173 | tokens = newPath.split("/"); |
Chiao Cheng | ef23bf1 | 2013-03-20 13:12:41 -0700 | [diff] [blame] | 174 | } |
| 175 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | { |
Bjorn Bringert | 5d015d7 | 2009-04-02 09:00:58 -0700 | [diff] [blame] | 219 | final List<String> pathSegments = uri.getPathSegments(); |
| 220 | final int li = pathSegments.size(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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++) { |
Bjorn Bringert | 5d015d7 | 2009-04-02 09:00:58 -0700 | [diff] [blame] | 229 | String u = i < 0 ? uri.getAuthority() : pathSegments.get(i); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | } |