text: /**
   * Copyright 2009 Google Inc.
   * 
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   * 
   *      http://www.apache.org/licenses/LICENSE-2.0
   * 
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS-IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  /**
   * @fileOverview Dispatching for dynamic pages and static files rendered from disk.
   */
  
  import("jsutils.eachProperty");
  import("stringutils");
  
  jimport("java.lang.System.out.println");
  
  //----------------------------------------------------------------
  // Util
  //----------------------------------------------------------------
  
  function PrefixMatcher(p) {
    var rs = p.replace(/([\[\]\^\$\\\.\*\+\?\(\)\{\}\|])/g, "\\$1");
    var r = new RegExp('^' + rs + '(.*)$');
    return function(path) {
      return r.exec(path);
    }
  }
  
  // Like PrefixMatcher, but makes trailing '/' optional, as in /ep/admin or /ep/admin/.
  // If trailing '/' is omitted, will redirect to same path with trailing /.
  function DirMatcher(p) {
    if (p.substr(-1) == '/') {
      p = p.substr(0, p.length-1);
    }
    var prefixMatcher = PrefixMatcher(p+'/');
    return function(path) {
      if (path == p) {
        response.redirect(p+'/');
      }
      return prefixMatcher(path);
    }
  }
  
  function _pathMatches(p, loc) {
    // returns a regex-result kind of array with length >= 1, or null
    if (typeof(loc) == 'string') {
      return (p == loc) ? [loc] : null;
    }
    if (typeof(loc) == 'function') {
      return (loc(p) || null);
    }
    if (loc.exec) { // regexp
      var r = loc.exec(p);
      return r || null;
    }
    throw new Error('Uknown type of location: '+loc);
  }
  
  //----------------------------------------------------------------
  // Dispatcher
  //----------------------------------------------------------------
  
  var Dispatcher = function() {
    this._routes = [];  // Array([location, (local file path or function)])
  };
  
  Dispatcher.prototype.addLocations = function(l) {
    var that = this;
    l.forEach(function(x) { that._routes.push(x); });
  };
  
  Dispatcher.prototype.dispatch = function() {
    var p = request.path;
    var served = false;
  
    for (var i = 0; (i < this._routes.length) && (served == false); i++) {
      var loc = this._routes[i][0];
      var dst = this._routes[i][1];
  
      var match = _pathMatches(p, loc);
      if (match) {
        if (typeof(dst) != 'function') {
  	throw new Error('dispatch only dispatches to functions, and this is not a function: '+typeof(dst));
        }
        
        // call dst(group1, group2, group3, ...)
        served = dst.apply(this, Array.prototype.slice.call(match, 1));
      }
    };
  
    return served;
  };
  
  //----------------------------------------------------------------
  // fdisp
  //----------------------------------------------------------------
  
  function forward(module) {
    return function(name) {
      if (name === "") {
        name = "main";
      }
      if (name) {
        name = name.replace(/\-/g, '_');
      }
      var onreq = module['onRequest'];
      var f = module['render_'+name];
      var fg = module['render_'+name+'_get'];
      var fp = module['render_'+name+'_post'];
  
      var served = false;
      
      if (onreq) { 
        served = onreq(name);
      }
  
      if (served) {
        return true;
      }
  
      var method = request.method;
      if (method == "HEAD") {
        method = "GET";
      }
      
      if (f) { 
        f();
        served = true;
      } else if (method == "GET" && fg) {
        fg();
        served = true;
      } else if (method == "POST" && fp) {
        fp();
        served = true;
      }
  
      return served;
    };
  }
  
  