2015-05-29 14:58:41 -07:00
|
|
|
import {
|
|
|
|
RegExp,
|
|
|
|
RegExpWrapper,
|
|
|
|
StringWrapper,
|
|
|
|
isPresent,
|
|
|
|
BaseException
|
|
|
|
} from 'angular2/src/facade/lang';
|
|
|
|
import {
|
|
|
|
Map,
|
|
|
|
MapWrapper,
|
|
|
|
List,
|
|
|
|
ListWrapper,
|
|
|
|
StringMap,
|
|
|
|
StringMapWrapper
|
|
|
|
} from 'angular2/src/facade/collection';
|
2015-04-17 09:59:56 -07:00
|
|
|
|
2015-06-17 11:57:38 -07:00
|
|
|
import {PathRecognizer, ContinuationSegment} from './path_recognizer';
|
2015-04-17 09:59:56 -07:00
|
|
|
|
2015-05-15 02:05:57 -07:00
|
|
|
/**
|
|
|
|
* `RouteRecognizer` is responsible for recognizing routes for a single component.
|
2015-05-29 14:58:41 -07:00
|
|
|
* It is consumed by `RouteRegistry`, which knows how to recognize an entire hierarchy of
|
|
|
|
* components.
|
2015-05-15 02:05:57 -07:00
|
|
|
*/
|
2015-04-17 09:59:56 -07:00
|
|
|
export class RouteRecognizer {
|
2015-05-29 14:58:41 -07:00
|
|
|
names: Map<string, PathRecognizer>;
|
|
|
|
redirects: Map<string, string>;
|
|
|
|
matchers: Map<RegExp, PathRecognizer>;
|
2015-04-17 09:59:56 -07:00
|
|
|
|
|
|
|
constructor() {
|
2015-06-17 16:21:40 -07:00
|
|
|
this.names = new Map();
|
|
|
|
this.matchers = new Map();
|
|
|
|
this.redirects = new Map();
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
|
|
|
|
2015-06-17 11:57:38 -07:00
|
|
|
addRedirect(path: string, target: string): void {
|
|
|
|
if (path == '/') {
|
|
|
|
path = '';
|
|
|
|
}
|
|
|
|
this.redirects.set(path, target);
|
|
|
|
}
|
2015-04-17 09:59:56 -07:00
|
|
|
|
2015-06-17 11:57:38 -07:00
|
|
|
addConfig(path: string, handler: any, alias: string = null): boolean {
|
2015-04-17 09:59:56 -07:00
|
|
|
var recognizer = new PathRecognizer(path, handler);
|
2015-05-15 02:05:57 -07:00
|
|
|
MapWrapper.forEach(this.matchers, (matcher, _) => {
|
|
|
|
if (recognizer.regex.toString() == matcher.regex.toString()) {
|
2015-05-29 14:58:41 -07:00
|
|
|
throw new BaseException(
|
|
|
|
`Configuration '${path}' conflicts with existing route '${matcher.path}'`);
|
2015-05-15 02:05:57 -07:00
|
|
|
}
|
|
|
|
});
|
2015-06-17 16:21:40 -07:00
|
|
|
this.matchers.set(recognizer.regex, recognizer);
|
2015-04-17 09:59:56 -07:00
|
|
|
if (isPresent(alias)) {
|
2015-06-17 16:21:40 -07:00
|
|
|
this.names.set(alias, recognizer);
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
2015-06-17 11:57:38 -07:00
|
|
|
return recognizer.terminal;
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
|
|
|
|
2015-05-15 02:05:57 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a URL, returns a list of `RouteMatch`es, which are partial recognitions for some route.
|
|
|
|
*
|
|
|
|
*/
|
2015-05-29 14:58:41 -07:00
|
|
|
recognize(url: string): List<RouteMatch> {
|
2015-06-17 11:17:21 -07:00
|
|
|
var solutions = [];
|
2015-06-17 11:57:38 -07:00
|
|
|
if (url.length > 0 && url[url.length - 1] == '/') {
|
|
|
|
url = url.substring(0, url.length - 1);
|
|
|
|
}
|
2015-05-15 02:05:57 -07:00
|
|
|
|
2015-04-17 09:59:56 -07:00
|
|
|
MapWrapper.forEach(this.redirects, (target, path) => {
|
2015-06-15 00:45:02 -07:00
|
|
|
// "/" redirect case
|
|
|
|
if (path == '/' || path == '') {
|
|
|
|
if (path == url) {
|
|
|
|
url = target;
|
|
|
|
}
|
|
|
|
} else if (StringWrapper.startsWith(url, path)) {
|
2015-04-17 09:59:56 -07:00
|
|
|
url = target + StringWrapper.substring(url, path.length);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
MapWrapper.forEach(this.matchers, (pathRecognizer, regex) => {
|
|
|
|
var match;
|
|
|
|
if (isPresent(match = RegExpWrapper.firstMatch(regex, url))) {
|
2015-05-29 14:58:41 -07:00
|
|
|
// TODO(btford): determine a good generic way to deal with terminal matches
|
2015-05-15 02:05:57 -07:00
|
|
|
var matchedUrl = '/';
|
|
|
|
var unmatchedUrl = '';
|
|
|
|
if (url != '/') {
|
|
|
|
matchedUrl = match[0];
|
|
|
|
unmatchedUrl = StringWrapper.substring(url, match[0].length);
|
2015-04-29 15:47:12 -07:00
|
|
|
}
|
2015-06-17 11:17:21 -07:00
|
|
|
solutions.push(new RouteMatch({
|
|
|
|
specificity: pathRecognizer.specificity,
|
|
|
|
handler: pathRecognizer.handler,
|
|
|
|
params: pathRecognizer.parseParams(url),
|
|
|
|
matchedUrl: matchedUrl,
|
|
|
|
unmatchedUrl: unmatchedUrl
|
|
|
|
}));
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return solutions;
|
|
|
|
}
|
|
|
|
|
2015-06-17 21:42:56 -07:00
|
|
|
hasRoute(name: string): boolean { return this.names.has(name); }
|
2015-04-17 09:59:56 -07:00
|
|
|
|
2015-05-29 14:58:41 -07:00
|
|
|
generate(name: string, params: any): string {
|
2015-06-17 16:21:40 -07:00
|
|
|
var pathRecognizer = this.names.get(name);
|
2015-05-14 15:24:35 +02:00
|
|
|
return isPresent(pathRecognizer) ? pathRecognizer.generate(params) : null;
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
|
|
|
}
|
2015-05-15 02:05:57 -07:00
|
|
|
|
|
|
|
export class RouteMatch {
|
2015-05-29 14:58:41 -07:00
|
|
|
specificity: number;
|
|
|
|
handler: StringMap<string, any>;
|
|
|
|
params: StringMap<string, string>;
|
|
|
|
matchedUrl: string;
|
|
|
|
unmatchedUrl: string;
|
|
|
|
constructor({specificity, handler, params, matchedUrl, unmatchedUrl}: {
|
|
|
|
specificity?: number,
|
|
|
|
handler?: StringMap<string, any>,
|
|
|
|
params?: StringMap<string, string>,
|
|
|
|
matchedUrl?: string,
|
|
|
|
unmatchedUrl?: string
|
|
|
|
} = {}) {
|
2015-05-15 02:05:57 -07:00
|
|
|
this.specificity = specificity;
|
|
|
|
this.handler = handler;
|
|
|
|
this.params = params;
|
|
|
|
this.matchedUrl = matchedUrl;
|
|
|
|
this.unmatchedUrl = unmatchedUrl;
|
|
|
|
}
|
|
|
|
}
|