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