Implemented URL query parsing for initial token /opa/?token=abcde
[src/app-framework-demo.git] / afb-client / bower_components / angular-ui-router / src / urlMatcherFactory.js
1 var $$UMFP; // reference to $UrlMatcherFactoryProvider
2
3 /**
4  * @ngdoc object
5  * @name ui.router.util.type:UrlMatcher
6  *
7  * @description
8  * Matches URLs against patterns and extracts named parameters from the path or the search
9  * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list
10  * of search parameters. Multiple search parameter names are separated by '&'. Search parameters
11  * do not influence whether or not a URL is matched, but their values are passed through into
12  * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}.
13  *
14  * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace
15  * syntax, which optionally allows a regular expression for the parameter to be specified:
16  *
17  * * `':'` name - colon placeholder
18  * * `'*'` name - catch-all placeholder
19  * * `'{' name '}'` - curly placeholder
20  * * `'{' name ':' regexp|type '}'` - curly placeholder with regexp or type name. Should the
21  *   regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash.
22  *
23  * Parameter names may contain only word characters (latin letters, digits, and underscore) and
24  * must be unique within the pattern (across both path and search parameters). For colon
25  * placeholders or curly placeholders without an explicit regexp, a path parameter matches any
26  * number of characters other than '/'. For catch-all placeholders the path parameter matches
27  * any number of characters.
28  *
29  * Examples:
30  *
31  * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for
32  *   trailing slashes, and patterns have to match the entire path, not just a prefix.
33  * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or
34  *   '/user/bob/details'. The second path segment will be captured as the parameter 'id'.
35  * * `'/user/{id}'` - Same as the previous example, but using curly brace syntax.
36  * * `'/user/{id:[^/]*}'` - Same as the previous example.
37  * * `'/user/{id:[0-9a-fA-F]{1,8}}'` - Similar to the previous example, but only matches if the id
38  *   parameter consists of 1 to 8 hex digits.
39  * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the
40  *   path into the parameter 'path'.
41  * * `'/files/*path'` - ditto.
42  * * `'/calendar/{start:date}'` - Matches "/calendar/2014-11-12" (because the pattern defined
43  *   in the built-in  `date` Type matches `2014-11-12`) and provides a Date object in $stateParams.start
44  *
45  * @param {string} pattern  The pattern to compile into a matcher.
46  * @param {Object} config  A configuration object hash:
47  * @param {Object=} parentMatcher Used to concatenate the pattern/config onto
48  *   an existing UrlMatcher
49  *
50  * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`.
51  * * `strict` - `false` if matching against a URL with a trailing slash should be treated as equivalent to a URL without a trailing slash, the default value is `true`.
52  *
53  * @property {string} prefix  A static prefix of this pattern. The matcher guarantees that any
54  *   URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns
55  *   non-null) will start with this prefix.
56  *
57  * @property {string} source  The pattern that was passed into the constructor
58  *
59  * @property {string} sourcePath  The path portion of the source property
60  *
61  * @property {string} sourceSearch  The search portion of the source property
62  *
63  * @property {string} regex  The constructed regex that will be used to match against the url when
64  *   it is time to determine which url will match.
65  *
66  * @returns {Object}  New `UrlMatcher` object
67  */
68 function UrlMatcher(pattern, config, parentMatcher) {
69   config = extend({ params: {} }, isObject(config) ? config : {});
70
71   // Find all placeholders and create a compiled pattern, using either classic or curly syntax:
72   //   '*' name
73   //   ':' name
74   //   '{' name '}'
75   //   '{' name ':' regexp '}'
76   // The regular expression is somewhat complicated due to the need to allow curly braces
77   // inside the regular expression. The placeholder regexp breaks down as follows:
78   //    ([:*])([\w\[\]]+)              - classic placeholder ($1 / $2) (search version has - for snake-case)
79   //    \{([\w\[\]]+)(?:\:\s*( ... ))?\}  - curly brace placeholder ($3) with optional regexp/type ... ($4) (search version has - for snake-case
80   //    (?: ... | ... | ... )+         - the regexp consists of any number of atoms, an atom being either
81   //    [^{}\\]+                       - anything other than curly braces or backslash
82   //    \\.                            - a backslash escape
83   //    \{(?:[^{}\\]+|\\.)*\}          - a matched set of curly braces containing other atoms
84   var placeholder       = /([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
85       searchPlaceholder = /([:]?)([\w\[\].-]+)|\{([\w\[\].-]+)(?:\:\s*((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
86       compiled = '^', last = 0, m,
87       segments = this.segments = [],
88       parentParams = parentMatcher ? parentMatcher.params : {},
89       params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet(),
90       paramNames = [];
91
92   function addParameter(id, type, config, location) {
93     paramNames.push(id);
94     if (parentParams[id]) return parentParams[id];
95     if (!/^\w+([-.]+\w+)*(?:\[\])?$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'");
96     if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'");
97     params[id] = new $$UMFP.Param(id, type, config, location);
98     return params[id];
99   }
100
101   function quoteRegExp(string, pattern, squash, optional) {
102     var surroundPattern = ['',''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&");
103     if (!pattern) return result;
104     switch(squash) {
105       case false: surroundPattern = ['(', ')' + (optional ? "?" : "")]; break;
106       case true:
107         result = result.replace(/\/$/, '');
108         surroundPattern = ['(?:\/(', ')|\/)?'];
109       break;
110       default:    surroundPattern = ['(' + squash + "|", ')?']; break;
111     }
112     return result + surroundPattern[0] + pattern + surroundPattern[1];
113   }
114
115   this.source = pattern;
116
117   // Split into static segments separated by path parameter placeholders.
118   // The number of segments is always 1 more than the number of parameters.
119   function matchDetails(m, isSearch) {
120     var id, regexp, segment, type, cfg, arrayMode;
121     id          = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null
122     cfg         = config.params[id];
123     segment     = pattern.substring(last, m.index);
124     regexp      = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null);
125
126     if (regexp) {
127       type      = $$UMFP.type(regexp) || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp, config.caseInsensitive ? 'i' : undefined) });
128     }
129
130     return {
131       id: id, regexp: regexp, segment: segment, type: type, cfg: cfg
132     };
133   }
134
135   var p, param, segment;
136   while ((m = placeholder.exec(pattern))) {
137     p = matchDetails(m, false);
138     if (p.segment.indexOf('?') >= 0) break; // we're into the search part
139
140     param = addParameter(p.id, p.type, p.cfg, "path");
141     compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash, param.isOptional);
142     segments.push(p.segment);
143     last = placeholder.lastIndex;
144   }
145   segment = pattern.substring(last);
146
147   // Find any search parameter names and remove them from the last segment
148   var i = segment.indexOf('?');
149
150   if (i >= 0) {
151     var search = this.sourceSearch = segment.substring(i);
152     segment = segment.substring(0, i);
153     this.sourcePath = pattern.substring(0, last + i);
154
155     if (search.length > 0) {
156       last = 0;
157       while ((m = searchPlaceholder.exec(search))) {
158         p = matchDetails(m, true);
159         param = addParameter(p.id, p.type, p.cfg, "search");
160         last = placeholder.lastIndex;
161         // check if ?&
162       }
163     }
164   } else {
165     this.sourcePath = pattern;
166     this.sourceSearch = '';
167   }
168
169   compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$';
170   segments.push(segment);
171
172   this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined);
173   this.prefix = segments[0];
174   this.$$paramNames = paramNames;
175 }
176
177 /**
178  * @ngdoc function
179  * @name ui.router.util.type:UrlMatcher#concat
180  * @methodOf ui.router.util.type:UrlMatcher
181  *
182  * @description
183  * Returns a new matcher for a pattern constructed by appending the path part and adding the
184  * search parameters of the specified pattern to this pattern. The current pattern is not
185  * modified. This can be understood as creating a pattern for URLs that are relative to (or
186  * suffixes of) the current pattern.
187  *
188  * @example
189  * The following two matchers are equivalent:
190  * <pre>
191  * new UrlMatcher('/user/{id}?q').concat('/details?date');
192  * new UrlMatcher('/user/{id}/details?q&date');
193  * </pre>
194  *
195  * @param {string} pattern  The pattern to append.
196  * @param {Object} config  An object hash of the configuration for the matcher.
197  * @returns {UrlMatcher}  A matcher for the concatenated pattern.
198  */
199 UrlMatcher.prototype.concat = function (pattern, config) {
200   // Because order of search parameters is irrelevant, we can add our own search
201   // parameters to the end of the new pattern. Parse the new pattern by itself
202   // and then join the bits together, but it's much easier to do this on a string level.
203   var defaultConfig = {
204     caseInsensitive: $$UMFP.caseInsensitive(),
205     strict: $$UMFP.strictMode(),
206     squash: $$UMFP.defaultSquashPolicy()
207   };
208   return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, extend(defaultConfig, config), this);
209 };
210
211 UrlMatcher.prototype.toString = function () {
212   return this.source;
213 };
214
215 /**
216  * @ngdoc function
217  * @name ui.router.util.type:UrlMatcher#exec
218  * @methodOf ui.router.util.type:UrlMatcher
219  *
220  * @description
221  * Tests the specified path against this matcher, and returns an object containing the captured
222  * parameter values, or null if the path does not match. The returned object contains the values
223  * of any search parameters that are mentioned in the pattern, but their value may be null if
224  * they are not present in `searchParams`. This means that search parameters are always treated
225  * as optional.
226  *
227  * @example
228  * <pre>
229  * new UrlMatcher('/user/{id}?q&r').exec('/user/bob', {
230  *   x: '1', q: 'hello'
231  * });
232  * // returns { id: 'bob', q: 'hello', r: null }
233  * </pre>
234  *
235  * @param {string} path  The URL path to match, e.g. `$location.path()`.
236  * @param {Object} searchParams  URL search parameters, e.g. `$location.search()`.
237  * @returns {Object}  The captured parameter values.
238  */
239 UrlMatcher.prototype.exec = function (path, searchParams) {
240   var m = this.regexp.exec(path);
241   if (!m) return null;
242   searchParams = searchParams || {};
243
244   var paramNames = this.parameters(), nTotal = paramNames.length,
245     nPath = this.segments.length - 1,
246     values = {}, i, j, cfg, paramName;
247
248   if (nPath !== m.length - 1) throw new Error("Unbalanced capture group in route '" + this.source + "'");
249
250   function decodePathArray(string) {
251     function reverseString(str) { return str.split("").reverse().join(""); }
252     function unquoteDashes(str) { return str.replace(/\\-/g, "-"); }
253
254     var split = reverseString(string).split(/-(?!\\)/);
255     var allReversed = map(split, reverseString);
256     return map(allReversed, unquoteDashes).reverse();
257   }
258
259   var param, paramVal;
260   for (i = 0; i < nPath; i++) {
261     paramName = paramNames[i];
262     param = this.params[paramName];
263     paramVal = m[i+1];
264     // if the param value matches a pre-replace pair, replace the value before decoding.
265     for (j = 0; j < param.replace.length; j++) {
266       if (param.replace[j].from === paramVal) paramVal = param.replace[j].to;
267     }
268     if (paramVal && param.array === true) paramVal = decodePathArray(paramVal);
269     if (isDefined(paramVal)) paramVal = param.type.decode(paramVal);
270     values[paramName] = param.value(paramVal);
271   }
272   for (/**/; i < nTotal; i++) {
273     paramName = paramNames[i];
274     values[paramName] = this.params[paramName].value(searchParams[paramName]);
275     param = this.params[paramName];
276     paramVal = searchParams[paramName];
277     for (j = 0; j < param.replace.length; j++) {
278       if (param.replace[j].from === paramVal) paramVal = param.replace[j].to;
279     }
280     if (isDefined(paramVal)) paramVal = param.type.decode(paramVal);
281     values[paramName] = param.value(paramVal);
282   }
283
284   return values;
285 };
286
287 /**
288  * @ngdoc function
289  * @name ui.router.util.type:UrlMatcher#parameters
290  * @methodOf ui.router.util.type:UrlMatcher
291  *
292  * @description
293  * Returns the names of all path and search parameters of this pattern in an unspecified order.
294  *
295  * @returns {Array.<string>}  An array of parameter names. Must be treated as read-only. If the
296  *    pattern has no parameters, an empty array is returned.
297  */
298 UrlMatcher.prototype.parameters = function (param) {
299   if (!isDefined(param)) return this.$$paramNames;
300   return this.params[param] || null;
301 };
302
303 /**
304  * @ngdoc function
305  * @name ui.router.util.type:UrlMatcher#validates
306  * @methodOf ui.router.util.type:UrlMatcher
307  *
308  * @description
309  * Checks an object hash of parameters to validate their correctness according to the parameter
310  * types of this `UrlMatcher`.
311  *
312  * @param {Object} params The object hash of parameters to validate.
313  * @returns {boolean} Returns `true` if `params` validates, otherwise `false`.
314  */
315 UrlMatcher.prototype.validates = function (params) {
316   return this.params.$$validates(params);
317 };
318
319 /**
320  * @ngdoc function
321  * @name ui.router.util.type:UrlMatcher#format
322  * @methodOf ui.router.util.type:UrlMatcher
323  *
324  * @description
325  * Creates a URL that matches this pattern by substituting the specified values
326  * for the path and search parameters. Null values for path parameters are
327  * treated as empty strings.
328  *
329  * @example
330  * <pre>
331  * new UrlMatcher('/user/{id}?q').format({ id:'bob', q:'yes' });
332  * // returns '/user/bob?q=yes'
333  * </pre>
334  *
335  * @param {Object} values  the values to substitute for the parameters in this pattern.
336  * @returns {string}  the formatted URL (path and optionally search part).
337  */
338 UrlMatcher.prototype.format = function (values) {
339   values = values || {};
340   var segments = this.segments, params = this.parameters(), paramset = this.params;
341   if (!this.validates(values)) return null;
342
343   var i, search = false, nPath = segments.length - 1, nTotal = params.length, result = segments[0];
344
345   function encodeDashes(str) { // Replace dashes with encoded "\-"
346     return encodeURIComponent(str).replace(/-/g, function(c) { return '%5C%' + c.charCodeAt(0).toString(16).toUpperCase(); });
347   }
348
349   for (i = 0; i < nTotal; i++) {
350     var isPathParam = i < nPath;
351     var name = params[i], param = paramset[name], value = param.value(values[name]);
352     var isDefaultValue = param.isOptional && param.type.equals(param.value(), value);
353     var squash = isDefaultValue ? param.squash : false;
354     var encoded = param.type.encode(value);
355
356     if (isPathParam) {
357       var nextSegment = segments[i + 1];
358       var isFinalPathParam = i + 1 === nPath;
359
360       if (squash === false) {
361         if (encoded != null) {
362           if (isArray(encoded)) {
363             result += map(encoded, encodeDashes).join("-");
364           } else {
365             result += encodeURIComponent(encoded);
366           }
367         }
368         result += nextSegment;
369       } else if (squash === true) {
370         var capture = result.match(/\/$/) ? /\/?(.*)/ : /(.*)/;
371         result += nextSegment.match(capture)[1];
372       } else if (isString(squash)) {
373         result += squash + nextSegment;
374       }
375
376       if (isFinalPathParam && param.squash === true && result.slice(-1) === '/') result = result.slice(0, -1);
377     } else {
378       if (encoded == null || (isDefaultValue && squash !== false)) continue;
379       if (!isArray(encoded)) encoded = [ encoded ];
380       if (encoded.length === 0) continue;
381       encoded = map(encoded, encodeURIComponent).join('&' + name + '=');
382       result += (search ? '&' : '?') + (name + '=' + encoded);
383       search = true;
384     }
385   }
386
387   return result;
388 };
389
390 /**
391  * @ngdoc object
392  * @name ui.router.util.type:Type
393  *
394  * @description
395  * Implements an interface to define custom parameter types that can be decoded from and encoded to
396  * string parameters matched in a URL. Used by {@link ui.router.util.type:UrlMatcher `UrlMatcher`}
397  * objects when matching or formatting URLs, or comparing or validating parameter values.
398  *
399  * See {@link ui.router.util.$urlMatcherFactory#methods_type `$urlMatcherFactory#type()`} for more
400  * information on registering custom types.
401  *
402  * @param {Object} config  A configuration object which contains the custom type definition.  The object's
403  *        properties will override the default methods and/or pattern in `Type`'s public interface.
404  * @example
405  * <pre>
406  * {
407  *   decode: function(val) { return parseInt(val, 10); },
408  *   encode: function(val) { return val && val.toString(); },
409  *   equals: function(a, b) { return this.is(a) && a === b; },
410  *   is: function(val) { return angular.isNumber(val) isFinite(val) && val % 1 === 0; },
411  *   pattern: /\d+/
412  * }
413  * </pre>
414  *
415  * @property {RegExp} pattern The regular expression pattern used to match values of this type when
416  *           coming from a substring of a URL.
417  *
418  * @returns {Object}  Returns a new `Type` object.
419  */
420 function Type(config) {
421   extend(this, config);
422 }
423
424 /**
425  * @ngdoc function
426  * @name ui.router.util.type:Type#is
427  * @methodOf ui.router.util.type:Type
428  *
429  * @description
430  * Detects whether a value is of a particular type. Accepts a native (decoded) value
431  * and determines whether it matches the current `Type` object.
432  *
433  * @param {*} val  The value to check.
434  * @param {string} key  Optional. If the type check is happening in the context of a specific
435  *        {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the
436  *        parameter in which `val` is stored. Can be used for meta-programming of `Type` objects.
437  * @returns {Boolean}  Returns `true` if the value matches the type, otherwise `false`.
438  */
439 Type.prototype.is = function(val, key) {
440   return true;
441 };
442
443 /**
444  * @ngdoc function
445  * @name ui.router.util.type:Type#encode
446  * @methodOf ui.router.util.type:Type
447  *
448  * @description
449  * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the
450  * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it
451  * only needs to be a representation of `val` that has been coerced to a string.
452  *
453  * @param {*} val  The value to encode.
454  * @param {string} key  The name of the parameter in which `val` is stored. Can be used for
455  *        meta-programming of `Type` objects.
456  * @returns {string}  Returns a string representation of `val` that can be encoded in a URL.
457  */
458 Type.prototype.encode = function(val, key) {
459   return val;
460 };
461
462 /**
463  * @ngdoc function
464  * @name ui.router.util.type:Type#decode
465  * @methodOf ui.router.util.type:Type
466  *
467  * @description
468  * Converts a parameter value (from URL string or transition param) to a custom/native value.
469  *
470  * @param {string} val  The URL parameter value to decode.
471  * @param {string} key  The name of the parameter in which `val` is stored. Can be used for
472  *        meta-programming of `Type` objects.
473  * @returns {*}  Returns a custom representation of the URL parameter value.
474  */
475 Type.prototype.decode = function(val, key) {
476   return val;
477 };
478
479 /**
480  * @ngdoc function
481  * @name ui.router.util.type:Type#equals
482  * @methodOf ui.router.util.type:Type
483  *
484  * @description
485  * Determines whether two decoded values are equivalent.
486  *
487  * @param {*} a  A value to compare against.
488  * @param {*} b  A value to compare against.
489  * @returns {Boolean}  Returns `true` if the values are equivalent/equal, otherwise `false`.
490  */
491 Type.prototype.equals = function(a, b) {
492   return a == b;
493 };
494
495 Type.prototype.$subPattern = function() {
496   var sub = this.pattern.toString();
497   return sub.substr(1, sub.length - 2);
498 };
499
500 Type.prototype.pattern = /.*/;
501
502 Type.prototype.toString = function() { return "{Type:" + this.name + "}"; };
503
504 /** Given an encoded string, or a decoded object, returns a decoded object */
505 Type.prototype.$normalize = function(val) {
506   return this.is(val) ? val : this.decode(val);
507 };
508
509 /*
510  * Wraps an existing custom Type as an array of Type, depending on 'mode'.
511  * e.g.:
512  * - urlmatcher pattern "/path?{queryParam[]:int}"
513  * - url: "/path?queryParam=1&queryParam=2
514  * - $stateParams.queryParam will be [1, 2]
515  * if `mode` is "auto", then
516  * - url: "/path?queryParam=1 will create $stateParams.queryParam: 1
517  * - url: "/path?queryParam=1&queryParam=2 will create $stateParams.queryParam: [1, 2]
518  */
519 Type.prototype.$asArray = function(mode, isSearch) {
520   if (!mode) return this;
521   if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only");
522
523   function ArrayType(type, mode) {
524     function bindTo(type, callbackName) {
525       return function() {
526         return type[callbackName].apply(type, arguments);
527       };
528     }
529
530     // Wrap non-array value as array
531     function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); }
532     // Unwrap array value for "auto" mode. Return undefined for empty array.
533     function arrayUnwrap(val) {
534       switch(val.length) {
535         case 0: return undefined;
536         case 1: return mode === "auto" ? val[0] : val;
537         default: return val;
538       }
539     }
540     function falsey(val) { return !val; }
541
542     // Wraps type (.is/.encode/.decode) functions to operate on each value of an array
543     function arrayHandler(callback, allTruthyMode) {
544       return function handleArray(val) {
545         if (isArray(val) && val.length === 0) return val;
546         val = arrayWrap(val);
547         var result = map(val, callback);
548         if (allTruthyMode === true)
549           return filter(result, falsey).length === 0;
550         return arrayUnwrap(result);
551       };
552     }
553
554     // Wraps type (.equals) functions to operate on each value of an array
555     function arrayEqualsHandler(callback) {
556       return function handleArray(val1, val2) {
557         var left = arrayWrap(val1), right = arrayWrap(val2);
558         if (left.length !== right.length) return false;
559         for (var i = 0; i < left.length; i++) {
560           if (!callback(left[i], right[i])) return false;
561         }
562         return true;
563       };
564     }
565
566     this.encode = arrayHandler(bindTo(type, 'encode'));
567     this.decode = arrayHandler(bindTo(type, 'decode'));
568     this.is     = arrayHandler(bindTo(type, 'is'), true);
569     this.equals = arrayEqualsHandler(bindTo(type, 'equals'));
570     this.pattern = type.pattern;
571     this.$normalize = arrayHandler(bindTo(type, '$normalize'));
572     this.name = type.name;
573     this.$arrayMode = mode;
574   }
575
576   return new ArrayType(this, mode);
577 };
578
579
580
581 /**
582  * @ngdoc object
583  * @name ui.router.util.$urlMatcherFactory
584  *
585  * @description
586  * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory
587  * is also available to providers under the name `$urlMatcherFactoryProvider`.
588  */
589 function $UrlMatcherFactory() {
590   $$UMFP = this;
591
592   var isCaseInsensitive = false, isStrictMode = true, defaultSquashPolicy = false;
593
594   // Use tildes to pre-encode slashes.
595   // If the slashes are simply URLEncoded, the browser can choose to pre-decode them,
596   // and bidirectional encoding/decoding fails.
597   // Tilde was chosen because it's not a RFC 3986 section 2.2 Reserved Character
598   function valToString(val) { return val != null ? val.toString().replace(/~/g, "~~").replace(/\//g, "~2F") : val; }
599   function valFromString(val) { return val != null ? val.toString().replace(/~2F/g, "/").replace(/~~/g, "~") : val; }
600
601   var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = {
602     "string": {
603       encode: valToString,
604       decode: valFromString,
605       // TODO: in 1.0, make string .is() return false if value is undefined/null by default.
606       // In 0.2.x, string params are optional by default for backwards compat
607       is: function(val) { return val == null || !isDefined(val) || typeof val === "string"; },
608       pattern: /[^/]*/
609     },
610     "int": {
611       encode: valToString,
612       decode: function(val) { return parseInt(val, 10); },
613       is: function(val) { return isDefined(val) && this.decode(val.toString()) === val; },
614       pattern: /\d+/
615     },
616     "bool": {
617       encode: function(val) { return val ? 1 : 0; },
618       decode: function(val) { return parseInt(val, 10) !== 0; },
619       is: function(val) { return val === true || val === false; },
620       pattern: /0|1/
621     },
622     "date": {
623       encode: function (val) {
624         if (!this.is(val))
625           return undefined;
626         return [ val.getFullYear(),
627           ('0' + (val.getMonth() + 1)).slice(-2),
628           ('0' + val.getDate()).slice(-2)
629         ].join("-");
630       },
631       decode: function (val) {
632         if (this.is(val)) return val;
633         var match = this.capture.exec(val);
634         return match ? new Date(match[1], match[2] - 1, match[3]) : undefined;
635       },
636       is: function(val) { return val instanceof Date && !isNaN(val.valueOf()); },
637       equals: function (a, b) { return this.is(a) && this.is(b) && a.toISOString() === b.toISOString(); },
638       pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/,
639       capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/
640     },
641     "json": {
642       encode: angular.toJson,
643       decode: angular.fromJson,
644       is: angular.isObject,
645       equals: angular.equals,
646       pattern: /[^/]*/
647     },
648     "any": { // does not encode/decode
649       encode: angular.identity,
650       decode: angular.identity,
651       equals: angular.equals,
652       pattern: /.*/
653     }
654   };
655
656   function getDefaultConfig() {
657     return {
658       strict: isStrictMode,
659       caseInsensitive: isCaseInsensitive
660     };
661   }
662
663   function isInjectable(value) {
664     return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1])));
665   }
666
667   /**
668    * [Internal] Get the default value of a parameter, which may be an injectable function.
669    */
670   $UrlMatcherFactory.$$getDefaultValue = function(config) {
671     if (!isInjectable(config.value)) return config.value;
672     if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
673     return injector.invoke(config.value);
674   };
675
676   /**
677    * @ngdoc function
678    * @name ui.router.util.$urlMatcherFactory#caseInsensitive
679    * @methodOf ui.router.util.$urlMatcherFactory
680    *
681    * @description
682    * Defines whether URL matching should be case sensitive (the default behavior), or not.
683    *
684    * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`;
685    * @returns {boolean} the current value of caseInsensitive
686    */
687   this.caseInsensitive = function(value) {
688     if (isDefined(value))
689       isCaseInsensitive = value;
690     return isCaseInsensitive;
691   };
692
693   /**
694    * @ngdoc function
695    * @name ui.router.util.$urlMatcherFactory#strictMode
696    * @methodOf ui.router.util.$urlMatcherFactory
697    *
698    * @description
699    * Defines whether URLs should match trailing slashes, or not (the default behavior).
700    *
701    * @param {boolean=} value `false` to match trailing slashes in URLs, otherwise `true`.
702    * @returns {boolean} the current value of strictMode
703    */
704   this.strictMode = function(value) {
705     if (isDefined(value))
706       isStrictMode = value;
707     return isStrictMode;
708   };
709
710   /**
711    * @ngdoc function
712    * @name ui.router.util.$urlMatcherFactory#defaultSquashPolicy
713    * @methodOf ui.router.util.$urlMatcherFactory
714    *
715    * @description
716    * Sets the default behavior when generating or matching URLs with default parameter values.
717    *
718    * @param {string} value A string that defines the default parameter URL squashing behavior.
719    *    `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL
720    *    `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the
721    *             parameter is surrounded by slashes, squash (remove) one slash from the URL
722    *    any other string, e.g. "~": When generating an href with a default parameter value, squash (remove)
723    *             the parameter value from the URL and replace it with this string.
724    */
725   this.defaultSquashPolicy = function(value) {
726     if (!isDefined(value)) return defaultSquashPolicy;
727     if (value !== true && value !== false && !isString(value))
728       throw new Error("Invalid squash policy: " + value + ". Valid policies: false, true, arbitrary-string");
729     defaultSquashPolicy = value;
730     return value;
731   };
732
733   /**
734    * @ngdoc function
735    * @name ui.router.util.$urlMatcherFactory#compile
736    * @methodOf ui.router.util.$urlMatcherFactory
737    *
738    * @description
739    * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern.
740    *
741    * @param {string} pattern  The URL pattern.
742    * @param {Object} config  The config object hash.
743    * @returns {UrlMatcher}  The UrlMatcher.
744    */
745   this.compile = function (pattern, config) {
746     return new UrlMatcher(pattern, extend(getDefaultConfig(), config));
747   };
748
749   /**
750    * @ngdoc function
751    * @name ui.router.util.$urlMatcherFactory#isMatcher
752    * @methodOf ui.router.util.$urlMatcherFactory
753    *
754    * @description
755    * Returns true if the specified object is a `UrlMatcher`, or false otherwise.
756    *
757    * @param {Object} object  The object to perform the type check against.
758    * @returns {Boolean}  Returns `true` if the object matches the `UrlMatcher` interface, by
759    *          implementing all the same methods.
760    */
761   this.isMatcher = function (o) {
762     if (!isObject(o)) return false;
763     var result = true;
764
765     forEach(UrlMatcher.prototype, function(val, name) {
766       if (isFunction(val)) {
767         result = result && (isDefined(o[name]) && isFunction(o[name]));
768       }
769     });
770     return result;
771   };
772
773   /**
774    * @ngdoc function
775    * @name ui.router.util.$urlMatcherFactory#type
776    * @methodOf ui.router.util.$urlMatcherFactory
777    *
778    * @description
779    * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to
780    * generate URLs with typed parameters.
781    *
782    * @param {string} name  The type name.
783    * @param {Object|Function} definition   The type definition. See
784    *        {@link ui.router.util.type:Type `Type`} for information on the values accepted.
785    * @param {Object|Function} definitionFn (optional) A function that is injected before the app
786    *        runtime starts.  The result of this function is merged into the existing `definition`.
787    *        See {@link ui.router.util.type:Type `Type`} for information on the values accepted.
788    *
789    * @returns {Object}  Returns `$urlMatcherFactoryProvider`.
790    *
791    * @example
792    * This is a simple example of a custom type that encodes and decodes items from an
793    * array, using the array index as the URL-encoded value:
794    *
795    * <pre>
796    * var list = ['John', 'Paul', 'George', 'Ringo'];
797    *
798    * $urlMatcherFactoryProvider.type('listItem', {
799    *   encode: function(item) {
800    *     // Represent the list item in the URL using its corresponding index
801    *     return list.indexOf(item);
802    *   },
803    *   decode: function(item) {
804    *     // Look up the list item by index
805    *     return list[parseInt(item, 10)];
806    *   },
807    *   is: function(item) {
808    *     // Ensure the item is valid by checking to see that it appears
809    *     // in the list
810    *     return list.indexOf(item) > -1;
811    *   }
812    * });
813    *
814    * $stateProvider.state('list', {
815    *   url: "/list/{item:listItem}",
816    *   controller: function($scope, $stateParams) {
817    *     console.log($stateParams.item);
818    *   }
819    * });
820    *
821    * // ...
822    *
823    * // Changes URL to '/list/3', logs "Ringo" to the console
824    * $state.go('list', { item: "Ringo" });
825    * </pre>
826    *
827    * This is a more complex example of a type that relies on dependency injection to
828    * interact with services, and uses the parameter name from the URL to infer how to
829    * handle encoding and decoding parameter values:
830    *
831    * <pre>
832    * // Defines a custom type that gets a value from a service,
833    * // where each service gets different types of values from
834    * // a backend API:
835    * $urlMatcherFactoryProvider.type('dbObject', {}, function(Users, Posts) {
836    *
837    *   // Matches up services to URL parameter names
838    *   var services = {
839    *     user: Users,
840    *     post: Posts
841    *   };
842    *
843    *   return {
844    *     encode: function(object) {
845    *       // Represent the object in the URL using its unique ID
846    *       return object.id;
847    *     },
848    *     decode: function(value, key) {
849    *       // Look up the object by ID, using the parameter
850    *       // name (key) to call the correct service
851    *       return services[key].findById(value);
852    *     },
853    *     is: function(object, key) {
854    *       // Check that object is a valid dbObject
855    *       return angular.isObject(object) && object.id && services[key];
856    *     }
857    *     equals: function(a, b) {
858    *       // Check the equality of decoded objects by comparing
859    *       // their unique IDs
860    *       return a.id === b.id;
861    *     }
862    *   };
863    * });
864    *
865    * // In a config() block, you can then attach URLs with
866    * // type-annotated parameters:
867    * $stateProvider.state('users', {
868    *   url: "/users",
869    *   // ...
870    * }).state('users.item', {
871    *   url: "/{user:dbObject}",
872    *   controller: function($scope, $stateParams) {
873    *     // $stateParams.user will now be an object returned from
874    *     // the Users service
875    *   },
876    *   // ...
877    * });
878    * </pre>
879    */
880   this.type = function (name, definition, definitionFn) {
881     if (!isDefined(definition)) return $types[name];
882     if ($types.hasOwnProperty(name)) throw new Error("A type named '" + name + "' has already been defined.");
883
884     $types[name] = new Type(extend({ name: name }, definition));
885     if (definitionFn) {
886       typeQueue.push({ name: name, def: definitionFn });
887       if (!enqueue) flushTypeQueue();
888     }
889     return this;
890   };
891
892   // `flushTypeQueue()` waits until `$urlMatcherFactory` is injected before invoking the queued `definitionFn`s
893   function flushTypeQueue() {
894     while(typeQueue.length) {
895       var type = typeQueue.shift();
896       if (type.pattern) throw new Error("You cannot override a type's .pattern at runtime.");
897       angular.extend($types[type.name], injector.invoke(type.def));
898     }
899   }
900
901   // Register default types. Store them in the prototype of $types.
902   forEach(defaultTypes, function(type, name) { $types[name] = new Type(extend({name: name}, type)); });
903   $types = inherit($types, {});
904
905   /* No need to document $get, since it returns this */
906   this.$get = ['$injector', function ($injector) {
907     injector = $injector;
908     enqueue = false;
909     flushTypeQueue();
910
911     forEach(defaultTypes, function(type, name) {
912       if (!$types[name]) $types[name] = new Type(type);
913     });
914     return this;
915   }];
916
917   this.Param = function Param(id, type, config, location) {
918     var self = this;
919     config = unwrapShorthand(config);
920     type = getType(config, type, location);
921     var arrayMode = getArrayMode();
922     type = arrayMode ? type.$asArray(arrayMode, location === "search") : type;
923     if (type.name === "string" && !arrayMode && location === "path" && config.value === undefined)
924       config.value = ""; // for 0.2.x; in 0.3.0+ do not automatically default to ""
925     var isOptional = config.value !== undefined;
926     var squash = getSquashPolicy(config, isOptional);
927     var replace = getReplace(config, arrayMode, isOptional, squash);
928
929     function unwrapShorthand(config) {
930       var keys = isObject(config) ? objectKeys(config) : [];
931       var isShorthand = indexOf(keys, "value") === -1 && indexOf(keys, "type") === -1 &&
932                         indexOf(keys, "squash") === -1 && indexOf(keys, "array") === -1;
933       if (isShorthand) config = { value: config };
934       config.$$fn = isInjectable(config.value) ? config.value : function () { return config.value; };
935       return config;
936     }
937
938     function getType(config, urlType, location) {
939       if (config.type && urlType) throw new Error("Param '"+id+"' has two type configurations.");
940       if (urlType) return urlType;
941       if (!config.type) return (location === "config" ? $types.any : $types.string);
942
943       if (angular.isString(config.type))
944         return $types[config.type];
945       if (config.type instanceof Type)
946         return config.type;
947       return new Type(config.type);
948     }
949
950     // array config: param name (param[]) overrides default settings.  explicit config overrides param name.
951     function getArrayMode() {
952       var arrayDefaults = { array: (location === "search" ? "auto" : false) };
953       var arrayParamNomenclature = id.match(/\[\]$/) ? { array: true } : {};
954       return extend(arrayDefaults, arrayParamNomenclature, config).array;
955     }
956
957     /**
958      * returns false, true, or the squash value to indicate the "default parameter url squash policy".
959      */
960     function getSquashPolicy(config, isOptional) {
961       var squash = config.squash;
962       if (!isOptional || squash === false) return false;
963       if (!isDefined(squash) || squash == null) return defaultSquashPolicy;
964       if (squash === true || isString(squash)) return squash;
965       throw new Error("Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string");
966     }
967
968     function getReplace(config, arrayMode, isOptional, squash) {
969       var replace, configuredKeys, defaultPolicy = [
970         { from: "",   to: (isOptional || arrayMode ? undefined : "") },
971         { from: null, to: (isOptional || arrayMode ? undefined : "") }
972       ];
973       replace = isArray(config.replace) ? config.replace : [];
974       if (isString(squash))
975         replace.push({ from: squash, to: undefined });
976       configuredKeys = map(replace, function(item) { return item.from; } );
977       return filter(defaultPolicy, function(item) { return indexOf(configuredKeys, item.from) === -1; }).concat(replace);
978     }
979
980     /**
981      * [Internal] Get the default value of a parameter, which may be an injectable function.
982      */
983     function $$getDefaultValue() {
984       if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
985       var defaultValue = injector.invoke(config.$$fn);
986       if (defaultValue !== null && defaultValue !== undefined && !self.type.is(defaultValue))
987         throw new Error("Default value (" + defaultValue + ") for parameter '" + self.id + "' is not an instance of Type (" + self.type.name + ")");
988       return defaultValue;
989     }
990
991     /**
992      * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the
993      * default value, which may be the result of an injectable function.
994      */
995     function $value(value) {
996       function hasReplaceVal(val) { return function(obj) { return obj.from === val; }; }
997       function $replace(value) {
998         var replacement = map(filter(self.replace, hasReplaceVal(value)), function(obj) { return obj.to; });
999         return replacement.length ? replacement[0] : value;
1000       }
1001       value = $replace(value);
1002       return !isDefined(value) ? $$getDefaultValue() : self.type.$normalize(value);
1003     }
1004
1005     function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; }
1006
1007     extend(this, {
1008       id: id,
1009       type: type,
1010       location: location,
1011       array: arrayMode,
1012       squash: squash,
1013       replace: replace,
1014       isOptional: isOptional,
1015       value: $value,
1016       dynamic: undefined,
1017       config: config,
1018       toString: toString
1019     });
1020   };
1021
1022   function ParamSet(params) {
1023     extend(this, params || {});
1024   }
1025
1026   ParamSet.prototype = {
1027     $$new: function() {
1028       return inherit(this, extend(new ParamSet(), { $$parent: this}));
1029     },
1030     $$keys: function () {
1031       var keys = [], chain = [], parent = this,
1032         ignore = objectKeys(ParamSet.prototype);
1033       while (parent) { chain.push(parent); parent = parent.$$parent; }
1034       chain.reverse();
1035       forEach(chain, function(paramset) {
1036         forEach(objectKeys(paramset), function(key) {
1037             if (indexOf(keys, key) === -1 && indexOf(ignore, key) === -1) keys.push(key);
1038         });
1039       });
1040       return keys;
1041     },
1042     $$values: function(paramValues) {
1043       var values = {}, self = this;
1044       forEach(self.$$keys(), function(key) {
1045         values[key] = self[key].value(paramValues && paramValues[key]);
1046       });
1047       return values;
1048     },
1049     $$equals: function(paramValues1, paramValues2) {
1050       var equal = true, self = this;
1051       forEach(self.$$keys(), function(key) {
1052         var left = paramValues1 && paramValues1[key], right = paramValues2 && paramValues2[key];
1053         if (!self[key].type.equals(left, right)) equal = false;
1054       });
1055       return equal;
1056     },
1057     $$validates: function $$validate(paramValues) {
1058       var keys = this.$$keys(), i, param, rawVal, normalized, encoded;
1059       for (i = 0; i < keys.length; i++) {
1060         param = this[keys[i]];
1061         rawVal = paramValues[keys[i]];
1062         if ((rawVal === undefined || rawVal === null) && param.isOptional)
1063           break; // There was no parameter value, but the param is optional
1064         normalized = param.type.$normalize(rawVal);
1065         if (!param.type.is(normalized))
1066           return false; // The value was not of the correct Type, and could not be decoded to the correct Type
1067         encoded = param.type.encode(normalized);
1068         if (angular.isString(encoded) && !param.type.pattern.exec(encoded))
1069           return false; // The value was of the correct type, but when encoded, did not match the Type's regexp
1070       }
1071       return true;
1072     },
1073     $$parent: undefined
1074   };
1075
1076   this.ParamSet = ParamSet;
1077 }
1078
1079 // Register as a provider so it's available to other providers
1080 angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory);
1081 angular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]);