2015-05-29 14:58:41 -07:00
|
|
|
import {
|
|
|
|
RegExp,
|
|
|
|
RegExpWrapper,
|
|
|
|
StringWrapper,
|
2015-06-30 13:18:51 -07:00
|
|
|
isBlank,
|
2015-05-29 14:58:41 -07:00
|
|
|
isPresent,
|
2015-06-30 13:18:51 -07:00
|
|
|
isType,
|
|
|
|
isStringMap,
|
2015-07-17 13:36:53 -07:00
|
|
|
BaseException,
|
|
|
|
Type
|
2015-05-29 14:58:41 -07:00
|
|
|
} 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-07-17 13:36:53 -07:00
|
|
|
import {PathRecognizer, PathMatch} from './path_recognizer';
|
|
|
|
import {Route, AsyncRoute, AuxRoute, Redirect, RouteDefinition} from './route_config_impl';
|
2015-06-30 13:18:51 -07:00
|
|
|
import {AsyncRouteHandler} from './async_route_handler';
|
|
|
|
import {SyncRouteHandler} from './sync_route_handler';
|
2015-07-17 13:36:53 -07:00
|
|
|
import {Url} from './url_parser';
|
|
|
|
import {ComponentInstruction} from './instruction';
|
|
|
|
|
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-06-29 10:37:55 +02:00
|
|
|
names: Map<string, PathRecognizer> = new Map();
|
2015-04-17 09:59:56 -07:00
|
|
|
|
2015-07-17 13:36:53 -07:00
|
|
|
auxRoutes: Map<string, PathRecognizer> = new Map();
|
|
|
|
|
|
|
|
// TODO: optimize this into a trie
|
|
|
|
matchers: List<PathRecognizer> = [];
|
|
|
|
|
|
|
|
// TODO: optimize this into a trie
|
|
|
|
redirects: List<Redirector> = [];
|
2015-07-21 01:26:43 -07:00
|
|
|
|
2015-07-13 16:12:48 -07:00
|
|
|
config(config: RouteDefinition): boolean {
|
|
|
|
var handler;
|
2015-07-17 13:36:53 -07:00
|
|
|
|
|
|
|
if (config instanceof AuxRoute) {
|
|
|
|
handler = new SyncRouteHandler(config.component);
|
|
|
|
let path = config.path.startsWith('/') ? config.path.substring(1) : config.path;
|
|
|
|
var recognizer = new PathRecognizer(config.path, handler);
|
|
|
|
this.auxRoutes.set(path, recognizer);
|
|
|
|
return recognizer.terminal;
|
|
|
|
}
|
2015-07-13 16:12:48 -07:00
|
|
|
if (config instanceof Redirect) {
|
2015-07-17 13:36:53 -07:00
|
|
|
this.redirects.push(new Redirector(config.path, config.redirectTo));
|
2015-07-13 16:12:48 -07:00
|
|
|
return true;
|
2015-07-17 13:36:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (config instanceof Route) {
|
2015-07-13 16:12:48 -07:00
|
|
|
handler = new SyncRouteHandler(config.component);
|
|
|
|
} else if (config instanceof AsyncRoute) {
|
|
|
|
handler = new AsyncRouteHandler(config.loader);
|
2015-06-17 11:57:38 -07:00
|
|
|
}
|
2015-07-17 13:36:53 -07:00
|
|
|
var recognizer = new PathRecognizer(config.path, handler);
|
|
|
|
|
|
|
|
this.matchers.forEach((matcher) => {
|
|
|
|
if (recognizer.hash == matcher.hash) {
|
2015-05-29 14:58:41 -07:00
|
|
|
throw new BaseException(
|
2015-07-13 16:12:48 -07:00
|
|
|
`Configuration '${config.path}' conflicts with existing route '${matcher.path}'`);
|
2015-05-15 02:05:57 -07:00
|
|
|
}
|
|
|
|
});
|
2015-07-17 13:36:53 -07:00
|
|
|
|
|
|
|
this.matchers.push(recognizer);
|
2015-07-13 16:12:48 -07:00
|
|
|
if (isPresent(config.as)) {
|
|
|
|
this.names.set(config.as, 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-07-17 13:36:53 -07:00
|
|
|
recognize(urlParse: Url): List<PathMatch> {
|
2015-06-17 11:17:21 -07:00
|
|
|
var solutions = [];
|
2015-05-15 02:05:57 -07:00
|
|
|
|
2015-07-17 13:36:53 -07:00
|
|
|
urlParse = this._redirect(urlParse);
|
2015-04-17 09:59:56 -07:00
|
|
|
|
2015-07-17 13:36:53 -07:00
|
|
|
this.matchers.forEach((pathRecognizer: PathRecognizer) => {
|
|
|
|
var pathMatch = pathRecognizer.recognize(urlParse);
|
2015-07-21 01:26:43 -07:00
|
|
|
|
2015-07-17 13:36:53 -07:00
|
|
|
if (isPresent(pathMatch)) {
|
|
|
|
solutions.push(pathMatch);
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return solutions;
|
|
|
|
}
|
|
|
|
|
2015-07-17 13:36:53 -07:00
|
|
|
_redirect(urlParse: Url): Url {
|
|
|
|
for (var i = 0; i < this.redirects.length; i += 1) {
|
|
|
|
let redirector = this.redirects[i];
|
|
|
|
var redirectedUrl = redirector.redirect(urlParse);
|
|
|
|
if (isPresent(redirectedUrl)) {
|
|
|
|
return redirectedUrl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return urlParse;
|
|
|
|
}
|
|
|
|
|
|
|
|
recognizeAuxiliary(urlParse: Url): PathMatch {
|
|
|
|
var pathRecognizer = this.auxRoutes.get(urlParse.path);
|
|
|
|
if (isBlank(pathRecognizer)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return pathRecognizer.recognize(urlParse);
|
|
|
|
}
|
|
|
|
|
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-07-17 13:36:53 -07:00
|
|
|
generate(name: string, params: any): ComponentInstruction {
|
2015-06-30 13:18:51 -07:00
|
|
|
var pathRecognizer: PathRecognizer = this.names.get(name);
|
|
|
|
if (isBlank(pathRecognizer)) {
|
|
|
|
return null;
|
|
|
|
}
|
2015-07-17 13:36:53 -07:00
|
|
|
return pathRecognizer.generate(params);
|
2015-04-17 09:59:56 -07:00
|
|
|
}
|
|
|
|
}
|
2015-05-15 02:05:57 -07:00
|
|
|
|
2015-07-17 13:36:53 -07:00
|
|
|
export class Redirector {
|
|
|
|
segments: List<string> = [];
|
|
|
|
toSegments: List<string> = [];
|
2015-07-21 01:26:43 -07:00
|
|
|
|
2015-07-17 13:36:53 -07:00
|
|
|
constructor(path: string, redirectTo: string) {
|
|
|
|
if (path.startsWith('/')) {
|
|
|
|
path = path.substring(1);
|
|
|
|
}
|
|
|
|
this.segments = path.split('/');
|
|
|
|
if (redirectTo.startsWith('/')) {
|
|
|
|
redirectTo = redirectTo.substring(1);
|
2015-07-21 01:26:43 -07:00
|
|
|
}
|
2015-07-17 13:36:53 -07:00
|
|
|
this.toSegments = redirectTo.split('/');
|
2015-07-21 01:26:43 -07:00
|
|
|
}
|
2015-06-29 10:37:55 +02:00
|
|
|
|
2015-07-17 13:36:53 -07:00
|
|
|
/**
|
|
|
|
* Returns `null` or a `ParsedUrl` representing the new path to match
|
|
|
|
*/
|
|
|
|
redirect(urlParse: Url): Url {
|
|
|
|
for (var i = 0; i < this.segments.length; i += 1) {
|
|
|
|
if (isBlank(urlParse)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
let segment = this.segments[i];
|
|
|
|
if (segment != urlParse.path) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
urlParse = urlParse.child;
|
2015-06-30 13:18:51 -07:00
|
|
|
}
|
2015-07-17 13:36:53 -07:00
|
|
|
|
|
|
|
for (var i = this.toSegments.length - 1; i >= 0; i -= 1) {
|
|
|
|
let segment = this.toSegments[i];
|
|
|
|
urlParse = new Url(segment, urlParse);
|
2015-06-30 13:18:51 -07:00
|
|
|
}
|
2015-07-17 13:36:53 -07:00
|
|
|
return urlParse;
|
2015-05-15 02:05:57 -07:00
|
|
|
}
|
|
|
|
}
|