2016-02-09 11:12:41 -08:00
import { isBlank , isPresent , isFunction } from 'angular2/src/facade/lang' ;
import { BaseException , WrappedException } from 'angular2/src/facade/exceptions' ;
import { Map , MapWrapper , ListWrapper , StringMapWrapper } from 'angular2/src/facade/collection' ;
import { PromiseWrapper } from 'angular2/src/facade/async' ;
import { AbstractRule , RouteRule , RedirectRule , RouteMatch , PathMatch } from './rules' ;
2016-04-12 09:40:37 -07:00
import {
Route ,
AsyncRoute ,
AuxRoute ,
Redirect ,
RouteDefinition
} from '../route_config/route_config_impl' ;
2016-02-09 11:12:41 -08:00
import { AsyncRouteHandler } from './route_handlers/async_route_handler' ;
import { SyncRouteHandler } from './route_handlers/sync_route_handler' ;
import { RoutePath } from './route_paths/route_path' ;
import { ParamRoutePath } from './route_paths/param_route_path' ;
import { RegexRoutePath } from './route_paths/regex_route_path' ;
import { Url } from '../url_parser' ;
import { ComponentInstruction } from '../instruction' ;
/ * *
* A ` RuleSet ` is responsible for recognizing routes for a particular component .
* It is consumed by ` RouteRegistry ` , which knows how to recognize an entire hierarchy of
* components .
* /
export class RuleSet {
rulesByName = new Map < string , RouteRule > ( ) ;
// map from name to rule
auxRulesByName = new Map < string , RouteRule > ( ) ;
// map from starting path to rule
auxRulesByPath = new Map < string , RouteRule > ( ) ;
// TODO: optimize this into a trie
rules : AbstractRule [ ] = [ ] ;
// the rule to use automatically when recognizing or generating from this rule set
defaultRule : RouteRule = null ;
/ * *
* Configure additional rules in this rule set from a route definition
* @returns { boolean } true if the config is terminal
* /
config ( config : RouteDefinition ) : boolean {
let handler ;
if ( isPresent ( config . name ) && config . name [ 0 ] . toUpperCase ( ) != config . name [ 0 ] ) {
let suggestedName = config . name [ 0 ] . toUpperCase ( ) + config . name . substring ( 1 ) ;
throw new BaseException (
` Route " ${ config . path } " with name " ${ config . name } " does not begin with an uppercase letter. Route names should be CamelCase like " ${ suggestedName } ". ` ) ;
}
if ( config instanceof AuxRoute ) {
handler = new SyncRouteHandler ( config . component , config . data ) ;
let routePath = this . _getRoutePath ( config ) ;
2016-03-30 11:26:31 -07:00
let auxRule = new RouteRule ( routePath , handler , config . name ) ;
2016-02-09 11:12:41 -08:00
this . auxRulesByPath . set ( routePath . toString ( ) , auxRule ) ;
if ( isPresent ( config . name ) ) {
this . auxRulesByName . set ( config . name , auxRule ) ;
}
return auxRule . terminal ;
}
let useAsDefault = false ;
if ( config instanceof Redirect ) {
let routePath = this . _getRoutePath ( config ) ;
let redirector = new RedirectRule ( routePath , config . redirectTo ) ;
this . _assertNoHashCollision ( redirector . hash , config . path ) ;
this . rules . push ( redirector ) ;
return true ;
}
if ( config instanceof Route ) {
handler = new SyncRouteHandler ( config . component , config . data ) ;
useAsDefault = isPresent ( config . useAsDefault ) && config . useAsDefault ;
} else if ( config instanceof AsyncRoute ) {
handler = new AsyncRouteHandler ( config . loader , config . data ) ;
useAsDefault = isPresent ( config . useAsDefault ) && config . useAsDefault ;
}
let routePath = this . _getRoutePath ( config ) ;
2016-03-30 11:26:31 -07:00
let newRule = new RouteRule ( routePath , handler , config . name ) ;
2016-02-09 11:12:41 -08:00
this . _assertNoHashCollision ( newRule . hash , config . path ) ;
if ( useAsDefault ) {
if ( isPresent ( this . defaultRule ) ) {
throw new BaseException ( ` Only one route can be default ` ) ;
}
this . defaultRule = newRule ;
}
this . rules . push ( newRule ) ;
if ( isPresent ( config . name ) ) {
this . rulesByName . set ( config . name , newRule ) ;
}
return newRule . terminal ;
}
/ * *
* Given a URL , returns a list of ` RouteMatch ` es , which are partial recognitions for some route .
* /
recognize ( urlParse : Url ) : Promise < RouteMatch > [ ] {
var solutions = [ ] ;
this . rules . forEach ( ( routeRecognizer : AbstractRule ) = > {
var pathMatch = routeRecognizer . recognize ( urlParse ) ;
if ( isPresent ( pathMatch ) ) {
solutions . push ( pathMatch ) ;
}
} ) ;
// handle cases where we are routing just to an aux route
if ( solutions . length == 0 && isPresent ( urlParse ) && urlParse . auxiliary . length > 0 ) {
return [ PromiseWrapper . resolve ( new PathMatch ( null , null , urlParse . auxiliary ) ) ] ;
}
return solutions ;
}
recognizeAuxiliary ( urlParse : Url ) : Promise < RouteMatch > [ ] {
var routeRecognizer : RouteRule = this . auxRulesByPath . get ( urlParse . path ) ;
if ( isPresent ( routeRecognizer ) ) {
return [ routeRecognizer . recognize ( urlParse ) ] ;
}
return [ PromiseWrapper . resolve ( null ) ] ;
}
hasRoute ( name : string ) : boolean { return this . rulesByName . has ( name ) ; }
componentLoaded ( name : string ) : boolean {
return this . hasRoute ( name ) && isPresent ( this . rulesByName . get ( name ) . handler . componentType ) ;
}
loadComponent ( name : string ) : Promise < any > {
return this . rulesByName . get ( name ) . handler . resolveComponentType ( ) ;
}
generate ( name : string , params : any ) : ComponentInstruction {
var rule : RouteRule = this . rulesByName . get ( name ) ;
if ( isBlank ( rule ) ) {
return null ;
}
return rule . generate ( params ) ;
}
generateAuxiliary ( name : string , params : any ) : ComponentInstruction {
var rule : RouteRule = this . auxRulesByName . get ( name ) ;
if ( isBlank ( rule ) ) {
return null ;
}
return rule . generate ( params ) ;
}
private _assertNoHashCollision ( hash : string , path ) {
this . rules . forEach ( ( rule ) = > {
if ( hash == rule . hash ) {
throw new BaseException (
` Configuration ' ${ path } ' conflicts with existing route ' ${ rule . path } ' ` ) ;
}
} ) ;
}
private _getRoutePath ( config : RouteDefinition ) : RoutePath {
if ( isPresent ( config . regex ) ) {
if ( isFunction ( config . serializer ) ) {
return new RegexRoutePath ( config . regex , config . serializer ) ;
} else {
throw new BaseException (
` Route provides a regex property, ' ${ config . regex } ', but no serializer property ` ) ;
}
}
if ( isPresent ( config . path ) ) {
// Auxiliary routes do not have a slash at the start
let path = ( config instanceof AuxRoute && config . path . startsWith ( '/' ) ) ?
2016-04-12 09:40:37 -07:00
config . path . substring ( 1 ) :
config . path ;
2016-02-09 11:12:41 -08:00
return new ParamRoutePath ( path ) ;
}
throw new BaseException ( 'Route must provide either a path or regex property' ) ;
}
}