Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.269
diff -u -p -r1.269 bootstrap.inc
--- includes/bootstrap.inc	31 Jan 2009 16:50:56 -0000	1.269
+++ includes/bootstrap.inc	9 Feb 2009 02:08:22 -0000
@@ -1615,3 +1615,118 @@ function registry_rebuild() {
 /**
  * @} End of "ingroup registry".
  */
+
+/**
+ * @ingroup handlers
+ * @{
+ */
+
+/**
+ * Request a handler object.
+ *
+ * @param $slot_id
+ *   The internal ID of the slot for which we want to retrieve a handler.
+ * @param $target
+ *   The target for which we want the attached handler.
+ * @return
+ *   The associated handler object.
+ */
+function handler($slot_id, $target = 'default') {
+  static $handlers;
+
+  if (empty($handlers[$slot_id][$target])) {
+    // Try to get the associated handler.  If the first query finds an associated
+    // handler, we use that.  If not, the second query will always find the
+    // default handler.  By UNIONing them together we get the fallback default
+    // behavior without having to issue a second request to the database.
+    $record = db_query_range("SELECT class, reuse FROM {handler_attachments} WHERE slot = :slot_1 AND target = :target
+      UNION SELECT class, reuse FROM {handler_attachments} WHERE slot = :slot_2 AND target = 'default'", array(
+      ':slot_1' => $slot_id,
+      ':slot_2' => $slot_id,
+      ':target' => $target,
+    ), 0, 1)->fetchObject();
+
+    // It's possible that this function will be called before the handler system
+    // has first been initialized. That's especially the case for early-running
+    // systems such as cache or path, as they operate during the bootstrap phase.
+    // If we have no record at all, we first rebuild the registry and then try
+    // again. That should at least always give us the slot-defined default
+    // and allow the system to proceed.
+    if (!$record) {
+      registry_rebuild();
+      handlers_rebuild();
+      $record = db_query_range("SELECT class, reuse FROM {handler_attachments} WHERE slot = :slot_1 AND target = :target
+        UNION SELECT class, reuse FROM {handler_attachments} WHERE slot = :slot_2 AND target = 'default'", array(
+        ':slot_1' => $slot_id,
+        ':slot_2' => $slot_id,
+        ':target' => $target,
+      ), 0, 1)->fetchObject();
+    }
+
+    // Create the handler.
+    $handler = new $record->class($target);
+
+    // Cache the handler for later requests only if this slot is defined to do so.
+    if ($record->reuse) {
+      $handlers[$slot_id][$target] = $handler;
+    }
+  }
+  else {
+    $handler = $handlers[$slot_id][$target];
+  }
+
+  return $handler;
+}
+
+/**
+ * Rebuild the handler and slot registries.
+ */
+function handlers_rebuild() {
+  require_once DRUPAL_ROOT . '/includes/handlers.inc';
+  handler_slot_build();
+  handler_handler_build();
+  handler_ensure_defaults();
+}
+
+/**
+ * Interface for all Handler classes for any slot.
+ *
+ * This is a very thin interface, but does serve to help standardize handler
+ * behavior and a way to type-check a handler object.  Interfaces for specific
+ * slots should extend this interface.
+ */
+interface HandlerInterface {
+
+  /**
+   * Constructor
+   *
+   * @param $target
+   *   The target for which this handler is being called. Some handlers may
+   *   require this information in order to route commands properly.
+   */
+  function __construct($target = 'default');
+}
+
+/**
+ * Base implementation of a handler object.
+ *
+ * Simple handler objects may choose to inherit from a base class in order to
+ * not reimplement routine functionality. Alternatively they may simply implement
+ * the appropriate interface and implement their own version of common
+ * functionality. Both methods are acceptable depending on the use case.
+ */
+abstract class HandlerBase implements HandlerInterface {
+
+  /**
+   * The target for which this handler is active.
+   */
+  protected $target;
+
+  function __construct($target = 'default') {
+    $this->target = $target;
+  }
+}
+
+/**
+ * @} End of "ingroup handlers".
+ */
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.864
diff -u -p -r1.864 common.inc
--- includes/common.inc	5 Feb 2009 01:21:16 -0000	1.864
+++ includes/common.inc	9 Feb 2009 02:08:22 -0000
@@ -151,7 +151,7 @@ function drupal_get_html_head() {
  * Reset the static variable which holds the aliases mapped for this request.
  */
 function drupal_clear_path_cache() {
-  drupal_lookup_path('wipe');
+  handler('path_lookup')->clearCache();
 }
 
 /**
@@ -3254,7 +3254,7 @@ function drupal_render_page($page) {
  *
  * Recursively iterates over each of the array elements, generating HTML code.
  *
- * HTML generation is controlled by two properties containing theme functions, 
+ * HTML generation is controlled by two properties containing theme functions,
  * #theme and #theme_wrapper.
  *
  * #theme is the theme function called first. If it is set and the element has any
@@ -3265,13 +3265,13 @@ function drupal_render_page($page) {
  *
  * The theme function in #theme_wrapper will be called after #theme has run. It
  * can be used to add further markup around the rendered children, e.g. fieldsets
- * add the required markup for a fieldset around their rendered child elements. 
+ * add the required markup for a fieldset around their rendered child elements.
  * A wrapper theme function always has to include the element's #children property
- * in its output, as this contains the rendered children. 
+ * in its output, as this contains the rendered children.
  *
  * For example, for the form element type, by default only the #theme_wrapper
  * property is set, which adds the form markup around the rendered child elements
- * of the form. This allows you to set the #theme property on a specific form to 
+ * of the form. This allows you to set the #theme property on a specific form to
  * a custom theme function, giving you complete control over the placement of the
  * form's children while not at all having to deal with the form markup itself.
  *
@@ -3305,7 +3305,7 @@ function drupal_render(&$elements) {
   else {
     $elements += element_basic_defaults();
   }
-  
+
   // If #markup is not empty and no theme function is set, use theme_markup.
   // This allows to specify just #markup on an element without setting the #type.
   if (!empty($elements['#markup']) && empty($elements['#theme'])) {
@@ -3479,7 +3479,7 @@ function element_child($key) {
 function element_children($element) {
   $keys = array();
   foreach(array_keys($element) as $key) {
-    if ($key[0] !== '#') { 
+    if ($key[0] !== '#') {
       $keys[] = $key;
     }
   }
@@ -4161,6 +4161,7 @@ function drupal_flush_all_caches() {
   _drupal_flush_css_js();
 
   registry_rebuild();
+  handlers_rebuild();
   drupal_clear_css_cache();
   drupal_clear_js_cache();
 
@@ -4203,3 +4204,107 @@ function _drupal_flush_css_js() {
   }
   variable_set('css_js_query_string', $new_character . substr($string_history, 0, 19));
 }
+
+/**
+ * @ingroup handlers
+ * @{
+ */
+
+/**
+ * Associate a given handler to a give slot for the specified target.
+ *
+ * @param $slot_id
+ *   The internal ID of the slot.
+ * @param $handler_id
+ *   The internal ID of the hand
+ * @param $target
+ *   The target for which to associate this handler to this slot.
+ */
+function handler_attach($slot_id, $handler_id, $target = 'default') {
+
+  // Lazy-load the utility function.
+  drupal_function_exists('handler_load');
+
+  $handler = handler_load($slot_id, $handler_id);
+  $slot = slot_load($slot_id);
+
+  if ($handler) {
+    // We store the class in the database rather than the handler ID so that
+    // we can, on lookup, get back the class to instantiate and immediately
+    // do so.
+    db_merge('handler_attachments')
+     ->key(array(
+       'slot' => $slot_id,
+       'target' => $target,
+     ))
+     ->fields(array(
+       'handler' => $handler->handler,
+       'class' => $handler->class,
+       'reuse' => (int)$slot->reuse,
+     ))
+     ->execute();
+  }
+}
+
+/**
+ * Revert a given slot and target to the default handler.
+ *
+ * @param $slot_id
+ *   The slot to reset to the default handler.
+ * @param $target
+ *   The target to reset to the default handler.
+ */
+function handler_detach($slot_id, $target = 'default') {
+
+  // Delete the handler attachment.
+  db_delete('handler_attachments')
+    ->condition('slot', $slot_id)
+    ->condition('target', $target)
+    ->execute();
+
+  // If we just deleted the default target for this slot, reattach the
+  // slot-defined default handler.  That ensures that we always have at least
+  // one handler configured.
+  if ($target = 'default') {
+    if (drupal_function_exists('slot_load')) {
+      $slot = slot_load($slot_id);
+      handler_attach($slot_id, $slot->default_handler, 'default');
+    }
+  }
+}
+
+/**
+ * Retrieve a list of all available handlers for a given slot.
+ *
+ * @param $slot_id
+ *   The slot for which we want a list of available handlers.
+ * @return unknown_type
+ */
+function handler_get_available_handlers($slot_id) {
+  return db_query('SELECT handler, title, description, class FROM {handler_info} WHERE slot = :slot ORDER BY title', array(':slot' => $slot_id))->fetchAllAssoc('handler');
+}
+
+/**
+ * Get the handler object for the handler associated to this slot/target.
+ *
+ * @param $slot_id
+ *   The slot for which we want to look up a handler.
+ * @param $target
+ *   The target for which we want to look up a handler.
+ * @return
+ *   A loaded handler object.
+ */
+function handler_get_attached_handler($slot_id, $target = 'default') {
+  $handler_id = db_query_range("SELECT handler FROM {handler_attachments} WHERE slot = :slot_1 AND target = :target
+      UNION SELECT handler FROM {handler_attachments} WHERE slot = :slot_2 AND target = 'default'", array(
+      ':slot_1' => $slot_id,
+      ':slot_2' => $slot_id,
+      ':target' => $target,
+    ), 0, 1)->fetchField();
+
+  return handler_load($slot_id, $handler_id);
+}
+
+/**
+ * @} End of "ingroup handlers".
+ */
Index: includes/handlers.inc
===================================================================
RCS file: includes/handlers.inc
diff -N includes/handlers.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/handlers.inc	9 Feb 2009 02:08:22 -0000
@@ -0,0 +1,274 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Handler utility functions.
+ *
+ * These functions are only needed when actually triggering a handler, so don't
+ * include them unless needed.
+ */
+
+/**
+ * @defgroup handlers Handlers system
+ * @{
+ * Handlers are a standardized mechanism for swappable subsystems.  Whereas
+ * hooks are useful for allowing any and all other modules to respond to or
+ * modify some event, Handlers are a way for code to delegate certain behavior
+ * to an easily swappable object.  That could be anything from a simple string
+ * operation up through an entire rendering engine.
+ *
+ * The Handlers system includes a number of key parts:
+ *
+ * Slot
+ * A slot is a system or subsystem which we want to be easily swappable. A slot
+ * is defined by a name and an interface. For example, the Cache system is a
+ * "slot".  All handlers for a given slot must conform to the PHP interface
+ * defined by the slot.
+ *
+ * Handler
+ * A Handler is a class that implements the interface for a given slot. For example,
+ * a database cache implementation and a memcache cache implementation are both
+ * handlers for the Cache slot.  Handlers may be transparently swapped out for
+ * one another.
+ *
+ * Targets
+ * A target is a specific case within a slot. Every slot may have an unlimited
+ * number of arbitrarily defined targets, and every target may have a different
+ * handler attached to it.  For example, "page", "menu", and "filter" are
+ * different targets used by Drupal for the Cache slot.
+ *
+ * Slots and Handlers are defined by hook_slot_info and hook_handler_info(),
+ * respectively. Once defined, a handler may be "attached" to a specific target
+ * like so:
+ *
+ * @code
+ * handler_attach('slot_name', 'handler_name', 'target');
+ * @endcode
+ *
+ * That will overwrite any previously associated handler. A handler may then
+ * be requested using the handler() factory function:
+ *
+ * @code
+ * $handler = handler('slot_name', 'target');
+ * $handler->someMethod();
+ * @endcode
+ *
+ * If no handler has been attached to that slot and target, the handler attached
+ * to the target "default" is used instead.  A slot/target may also be "reset"
+ * to the default handler:
+ *
+ * @code
+ * handler_detach('slot_name', 'target');
+ * @endcode
+ *
+ * If no target is specified, "default" is assumed.  That allows for a given
+ * slot to not make use of the system's multi-target capabilities by simply
+ * omitting the target whenever it is called, which results in a target of
+ * "default" being used in all cases.
+ */
+
+/**
+ * Rebuild the handler slot info registry.
+ *
+ * @return
+ *   The array of slots just declared.
+ */
+function handler_slot_build() {
+  $slots = module_invoke_all('slot_info');
+
+  // Set default values, to avoid NULL issues if nothing else.
+  foreach ($slots as $slot => $info) {
+    $slots[$slot] += handler_slot_defaults();
+  }
+
+  // Let other modules alter the handler target registry if needed.
+  drupal_alter('slot_info', $slots);
+
+  if ($slots) {
+    // Build the target data.
+    $insert = db_insert('handler_slot_info')->fields(array('slot', 'title', 'interface', 'reuse', 'default_handler', 'description'));
+
+    foreach ($slots as $slot => $info) {
+      $insert->values(array(
+       'slot' => $slot,
+       'title' => $info['title'],
+       'interface' => $info['interface'],
+       'reuse' => (int)$info['reuse'],
+       'default_handler' => $info['default_handler'],
+       'description' => $info['description'],
+      ));
+    }
+
+    // Don't do the deletion until after we've gotten the new data prepared.  That
+    // reduces the potential race condition window.
+    db_delete('handler_slot_info')->execute();
+    $insert->execute();
+  }
+
+  return $slots;
+}
+
+/**
+ * Rebuild the handler info registry.
+ *
+ * @return
+ *   The array of handlers just defined.
+ */
+function handler_handler_build() {
+  $handlers = module_invoke_all('handler_info');
+
+  // Set default values, to avoid NULL issues if nothing else.
+  foreach ($handlers as $slot => $handler_info) {
+    foreach ($handler_info as $handler => $info) {
+      $handlers[$slot][$handler] += handler_handler_defaults();
+    }
+  }
+
+  // Let other modules alter the handler registry if needed.
+  drupal_alter('handler_info', $handlers);
+
+  if ($handlers) {
+    // Build the handler data.
+    $insert = db_insert('handler_info')->fields(array('handler', 'slot', 'title', 'class', 'description'));
+    foreach ($handlers as $slot => $handler_info) {
+      foreach ($handler_info as $handler => $info) {
+        $insert->values(array(
+          'handler' => $handler,
+          'slot' => $slot,
+          'title' => $info['title'],
+          'class' => $info['class'],
+          'description' => $info['description'],
+        ));
+      }
+    }
+
+    // Don't do the deletion until after we've gotten the new data.  That reduces
+    // the potential race condition window.
+    db_delete('handler_info')->execute();
+    $insert->execute();
+  }
+
+  return $handlers;
+}
+
+/**
+ * Ensure that every slot has a handler attached for the default target.
+ *
+ * For every slot, attach whatever handler is declared the default handler
+ * to the default target unless one has already been defined.  That ensures
+ * that the attachment table always has at least that default record in it for
+ * each slot, which greatly simplifies the lookup logic.
+ */
+function handler_ensure_defaults() {
+  $result = db_query("SELECT hsi.slot, reuse, class, handler
+   FROM {handler_slot_info} hsi
+     INNER JOIN {handler_info} hi ON hsi.slot = hi.slot AND hsi.default_handler = hi.handler");
+
+  foreach ($result as $record) {
+    // We can't exclude all fields or the query breaks.  However, setting
+    // reuse to itself has no effect on the table so it's safe to do.
+    db_merge('handler_attachments')
+     ->key(array(
+       'slot' => $record->slot,
+       'target' => 'default',
+     ))
+     ->fields(array(
+       'handler' => $record->handler,
+       'class' => $record->class,
+       'reuse' => $record->reuse,
+     ))
+     ->updateExcept('class', 'handler')
+    ->execute();
+  }
+}
+
+/**
+ * Define various default values for handler slots.
+ *
+ * @return
+ *   The default "empty" slot definition.
+ */
+function handler_slot_defaults() {
+  return array(
+    'slot' => '',
+    'title' => '',
+    'interface' => 'HandlerInterface',
+    'reuse' => 1,
+    'default_handler' => 'default',
+    'description' => '',
+  );
+}
+
+/**
+ * Define various default values for handlers.
+ *
+ * @return
+ *   The default "empty" handler definition.
+ */
+function handler_handler_defaults() {
+  return array(
+    'handler' => '',
+    'slot' => '',
+    'title' => '',
+    'class' => '',
+    'description' => '',
+  );
+}
+
+/**
+ * Load function for slot info objects.
+ *
+ * @param $slot_id
+ *   The internal slot ID.
+ * @param $refresh
+ *   If this is set to TRUE, the internal slot cache will be flushed.
+ * @return
+ *   The slot object or NULL if it is not defined.
+ */
+function slot_load($slot_id, $refresh = FALSE) {
+  static $slots;
+
+  if ($refresh) {
+    $slots = array();
+  }
+
+  if (empty($slots[$slot_id])) {
+    $slots[$slot_id] = db_query("SELECT slot, title, interface, reuse, default_handler, description FROM {handler_slot_info} WHERE slot = :slot_id", array(':slot_id' => $slot_id))->fetchObject();
+  }
+
+  return $slots[$slot_id];
+}
+
+/**
+ * Load function for handler info objects.
+ *
+ * @param $slot_id
+ *   The internal slot ID.  All handlers are namespaced within their slot.
+ * @param $handler_id
+ *   The internal handler ID.
+ * @param $refresh
+ *   If this is set to TRUE, the internal handler cache will be flushed.
+ * @return
+ *   The handler object or NULL if not defined.
+ */
+function handler_load($slot_id, $handler_id, $refresh = FALSE) {
+  static $handlers;
+
+  if ($refresh) {
+    $handlers = array();
+  }
+
+  if (empty($handlers[$slot_id][$handler_id])) {
+    $handlers[$slot_id][$handler_id] = db_query("SELECT handler, slot, title, class, description FROM {handler_info} WHERE slot = :slot_id AND handler = :handler_id", array(
+      ':slot_id' => $slot_id,
+      ':handler_id' => $handler_id,
+    ))->fetchObject();
+  }
+
+  return $handlers[$slot_id][$handler_id];
+}
+
+/**
+ * @} End of "ingroup handlers".
+ */
Index: includes/path.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/path.inc,v
retrieving revision 1.32
diff -u -p -r1.32 path.inc
--- includes/path.inc	4 Jan 2009 20:04:32 -0000	1.32
+++ includes/path.inc	9 Feb 2009 02:08:23 -0000
@@ -23,84 +23,6 @@ function drupal_init_path() {
 }
 
 /**
- * Given an alias, return its Drupal system URL if one exists. Given a Drupal
- * system URL return one of its aliases if such a one exists. Otherwise,
- * return FALSE.
- *
- * @param $action
- *   One of the following values:
- *   - wipe: delete the alias cache.
- *   - alias: return an alias for a given Drupal system path (if one exists).
- *   - source: return the Drupal system URL for a path alias (if one exists).
- * @param $path
- *   The path to investigate for corresponding aliases or system URLs.
- * @param $path_language
- *   Optional language code to search the path with. Defaults to the page language.
- *   If there's no path defined for that language it will search paths without
- *   language.
- *
- * @return
- *   Either a Drupal system path, an aliased path, or FALSE if no path was
- *   found.
- */
-function drupal_lookup_path($action, $path = '', $path_language = '') {
-  global $language;
-  // $map is an array with language keys, holding arrays of Drupal paths to alias relations
-  static $map = array(), $no_src = array(), $count;
-
-  $path_language = $path_language ? $path_language : $language->language;
-
-  // Use $count to avoid looking up paths in subsequent calls if there simply are no aliases
-  if (!isset($count)) {
-    $count = db_query('SELECT COUNT(pid) FROM {url_alias}')->fetchField();
-  }
-
-  if ($action == 'wipe') {
-    $map = array();
-    $no_src = array();
-    $count = NULL;
-  }
-  elseif ($count > 0 && $path != '') {
-    if ($action == 'alias') {
-      if (isset($map[$path_language][$path])) {
-        return $map[$path_language][$path];
-      }
-      // Get the most fitting result falling back with alias without language
-      $alias = db_query("SELECT dst FROM {url_alias} WHERE src = :src AND language IN(:language, '') ORDER BY language DESC", array(
-        ':src' => $path,
-        ':language' => $path_language))
-        ->fetchField();
-      $map[$path_language][$path] = $alias;
-      return $alias;
-    }
-    // Check $no_src for this $path in case we've already determined that there
-    // isn't a path that has this alias
-    elseif ($action == 'source' && !isset($no_src[$path_language][$path])) {
-      // Look for the value $path within the cached $map
-      $src = '';
-      if (!isset($map[$path_language]) || !($src = array_search($path, $map[$path_language]))) {
-        // Get the most fitting result falling back with alias without language
-        if ($src = db_query("SELECT src FROM {url_alias} WHERE dst = :dst AND language IN(:language, '') ORDER BY language DESC", array(
-                     ':dst' => $path,
-                     ':language' => $path_language))
-            ->fetchField()) {
-          $map[$path_language][$src] = $path;
-        }
-        else {
-          // We can't record anything into $map because we do not have a valid
-          // index and there is no need because we have not learned anything
-          // about any Drupal path. Thus cache to $no_src.
-          $no_src[$path_language][$path] = TRUE;
-        }
-      }
-      return $src;
-    }
-  }
-
-  return FALSE;
-}
-
-/**
  * Given an internal Drupal path, return the alias set by the administrator.
  *
  * @param $path
@@ -113,11 +35,15 @@ function drupal_lookup_path($action, $pa
  *   found.
  */
 function drupal_get_path_alias($path, $path_language = '') {
-  $result = $path;
-  if ($alias = drupal_lookup_path('alias', $path, $path_language)) {
-    $result = $alias;
+  static $handler;
+
+  // Static cache the handler so that we avoid the lookup costs on
+  // subsequent calls.
+  if (empty($handler)) {
+    $handler = handler('path_lookup');
   }
-  return $result;
+
+  return $handler->lookupAlias($path, $path_language);
 }
 
 /**
@@ -127,16 +53,23 @@ function drupal_get_path_alias($path, $p
  *   A Drupal path alias.
  * @param $path_language
  *   An optional language code to look up the path in.
- *
  * @return
  *   The internal path represented by the alias, or the original alias if no
  *   internal path was found.
  */
 function drupal_get_normal_path($path, $path_language = '') {
-  $result = $path;
-  if ($src = drupal_lookup_path('source', $path, $path_language)) {
-    $result = $src;
+  static $handler;
+
+  // Static cache the handler so that we avoid the lookup costs on
+  // subsequent calls.
+  if (empty($handler)) {
+    $handler = handler('path_lookup');
   }
+
+  $result = $handler->lookupPath($path, $path_language);
+
+  // @todo: Remove this once we confirm that it's not needed, as alternate
+  // handler implementations can probably handle it.
   if (function_exists('custom_url_rewrite_inbound')) {
     // Modules may alter the inbound request path by reference.
     custom_url_rewrite_inbound($result, $path, $path_language);
Index: includes/registry.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/registry.inc,v
retrieving revision 1.11
diff -u -p -r1.11 registry.inc
--- includes/registry.inc	20 Dec 2008 18:24:33 -0000	1.11
+++ includes/registry.inc	9 Feb 2009 02:08:23 -0000
@@ -37,6 +37,11 @@ function _registry_rebuild() {
   require_once DRUPAL_ROOT . '/includes/database/select.inc';
   require_once DRUPAL_ROOT . '/includes/database/' . $driver . '/query.inc';
 
+  // If called before bootstrap completes, these files may not be available yet.
+  require_once DRUPAL_ROOT . '/includes/common.inc';
+  require_once DRUPAL_ROOT . '/includes/file.inc';
+  require_once DRUPAL_ROOT . '/modules/system/system.module';
+
   // Reset the resources cache.
   _registry_get_resource_name();
   // Get the list of files we are going to parse.
Index: modules/path/path.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/path/path.test,v
retrieving revision 1.6
diff -u -p -r1.6 path.test
--- modules/path/path.test	9 Jan 2009 07:44:00 -0000	1.6
+++ modules/path/path.test	9 Feb 2009 02:08:23 -0000
@@ -174,6 +174,7 @@ class PathLanguageTestCase extends Drupa
 
     // Confirm that the alias works.
     $this->drupalGet($edit['path']);
+
     $this->assertText($english_node->title, 'Alias works.');
 
     // Translate the node into French.
@@ -186,7 +187,7 @@ class PathLanguageTestCase extends Drupa
     $this->drupalPost(NULL, $edit, t('Save'));
 
     // Clear the path lookup cache.
-    drupal_lookup_path('wipe');
+    drupal_clear_path_cache();
 
     // Ensure the node was created.
     // Check to make sure the node was created.
Index: modules/simpletest/tests/handlers.test
===================================================================
RCS file: modules/simpletest/tests/handlers.test
diff -N modules/simpletest/tests/handlers.test
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/handlers.test	9 Feb 2009 02:08:23 -0000
@@ -0,0 +1,204 @@
+<?php
+// $Id$
+
+/**
+ * Test class for the Handlers system.
+ */
+class HandlersNotReusedTestCase extends DrupalWebTestCase {
+
+  protected $sampleString = 'Hello World';
+
+  function getInfo() {
+    return array(
+      'name' => t('Handlers, Nonreusable'),
+      'description' => t('Test the Handler routing system'),
+      'group' => t('Handlers'),
+    );
+  }
+
+  function setUp() {
+    parent::setUp('handlers_test');
+
+    // We need to rebuild the index at least once, because the module_enable()
+    // routine called by the parent method doesn't call drupal_flush_all_caches().
+    handlers_rebuild();
+  }
+
+  /**
+   * Test that we can retrieve the default handler if no target is defined.
+   */
+  function testDefaultHandler() {
+    // 'default' has not been attached to anything, so should return the
+    // default implementation.
+    $handler = handler('fancystring', 'default');
+
+    $this->assertTrue($handler instanceof FancystringInterface, t('Returned handler has the correct interface.'));
+    $this->assertTrue($handler instanceof FancystringDefault, t('Correct handler object returned.'));
+
+    if ($handler instanceof HandlerInterface) {
+      $handler->setString($this->sampleString);
+      $mangled = $handler->getString();
+
+      $this->assertEqual($this->sampleString, $mangled, t('Default handler returned correct string.'));
+    }
+  }
+
+  /**
+   * Test that we can specify a handler for a target and it loads correctly.
+   */
+  function testSpecifiedHandler() {
+
+    // Specify a specific handler for a given target.
+    handler_attach('fancystring', 'rot13', 'foo');
+
+    // Now request the handler.
+    $handler = handler('fancystring', 'foo');
+
+    $this->assertTrue($handler instanceof FancystringInterface, t('Returned handler has the correct interface.'));
+    $this->assertTrue($handler instanceof FancystringRot13, t('Correct handler object returned.'));
+
+    if ($handler instanceof HandlerInterface) {
+      $handler->setString($this->sampleString);
+      $mangled = $handler->getString();
+
+      $this->assertEqual(str_rot13($this->sampleString), $mangled, t('Specified handler returned correct string.'));
+    }
+  }
+
+  /**
+   * Test that we can skip using target on a slot and everything still works.
+   */
+  function testUndefinedHandler() {
+    // An undefined target should always give us the default handler defined by the slot.
+    $handler = handler('fancystring');
+
+    $this->assertTrue($handler instanceof FancystringInterface, t('Returned handler has the correct interface.'));
+    $this->assertTrue($handler instanceof FancystringDefault, t('Correct handler object returned.'));
+
+    if ($handler instanceof HandlerInterface) {
+      $handler->setString($this->sampleString);
+      $mangled = $handler->getString();
+
+      $this->assertEqual($this->sampleString, $mangled, t('Default handler returned correct string.'));
+    }
+  }
+
+  /**
+   * Test a context-sensitive handler.
+   */
+  function testContextSensitiveHandler() {
+
+    // This is a poor way of mocking up the variable system, but it will have
+    // to do for now.
+    $map = array(
+      'Hello' => 'Goodbye',
+      'World' => 'Cruel World',
+    );
+    $GLOBALS['conf']['fancystring_translate'] = $map;
+
+    // Specify a specific handler for a given target.
+    handler_attach('fancystring', 'custom_translate', 'bar');
+
+    // Now request the handler.
+    $handler = handler('fancystring', 'bar');
+
+    $this->assertTrue($handler instanceof FancystringInterface, t('Returned handler has the correct interface.'));
+    $this->assertTrue($handler instanceof FancystringCustom, t('Correct handler object returned.'));
+
+    if ($handler instanceof HandlerInterface) {
+      $handler->setString($this->sampleString);
+      $mangled = $handler->getString();
+
+      $this->assertEqual(strtr($this->sampleString, $map), $mangled, t('Specified handler returned correct string.'));
+    }
+
+    unset ($GLOBALS['conf']['fancystring_translate']);
+  }
+
+  /**
+   * Test that we can successfully unset a handler attachment.
+   */
+  function testUnsettingHandler() {
+
+    // Specify a specific handler for a given target.
+    handler_attach('fancystring', 'rot13', 'foo');
+
+    // Now unset that attachment.
+    handler_detach('fancystring', 'foo');
+
+    // Now request the handler.  We should get back the default.
+    $handler = handler('fancystring', 'foo');
+
+    $this->assertTrue($handler instanceof FancystringInterface, t('Returned handler has the correct interface.'));
+    $this->assertTrue($handler instanceof FancystringDefault, t('Correct handler object returned.'));
+
+    if ($handler instanceof HandlerInterface) {
+      $handler->setString($this->sampleString);
+      $mangled = $handler->getString();
+
+      $this->assertEqual($this->sampleString, $mangled, t('Unspecified handler returned correct string.'));
+    }
+  }
+
+  /**
+   * Test that a non-reusable slot returns a new object eacn time.
+   */
+  function testHandlerReuse() {
+
+    // Specify a specific handler for a given target.
+    handler_attach('fancystring', 'rot13', 'foo');
+
+    // Now request the handler.
+    $handler_1 = handler('fancystring', 'foo');
+
+    $this->assertTrue($handler_1 instanceof FancystringInterface, t('Returned handler has the correct interface.'));
+    $this->assertTrue($handler_1 instanceof FancystringRot13, t('Correct handler object returned.'));
+
+    $handler_2 = handler('fancystring', 'foo');
+
+    $this->assertNotIdentical($handler_1, $handler_2, t('A new object was returned.'));
+  }
+}
+
+/**
+ * Test class for the Handlers system, part 2.
+ */
+class HandlersReusableTestCase extends DrupalWebTestCase {
+
+  protected $sampleString = 'Hello World';
+
+  function getInfo() {
+    return array(
+      'name' => t('Handlers, Reusable'),
+      'description' => t('Test the Handler routing system for reusable handlers.'),
+      'group' => t('Handlers'),
+    );
+  }
+
+  function setUp() {
+    parent::setUp('handlers_test');
+
+    // We need to rebuild the index at least once, because the module_enable()
+    // routine called by the parent method doesn't call drupal_flush_all_caches().
+    handlers_rebuild();
+  }
+
+  /**
+   * Test that a reusable slot returns the exact same object each time.
+   */
+  function testHandlerReuse() {
+
+    // Specify a specific handler for a given target.
+    handler_attach('thingie', 'default', 'foo');
+
+    // Now request the handler.
+    $handler_1 = handler('thingie', 'foo');
+
+    $this->assertTrue($handler_1 instanceof ThingieInterface, t('Returned handler has the correct interface.'));
+    $this->assertTrue($handler_1 instanceof ThingieDefault, t('Correct handler object returned.'));
+
+    $handler_2 = handler('thingie', 'foo');
+
+    $this->assertIdentical($handler_1, $handler_2, t('The same handler object was returned.'));
+  }
+}
Index: modules/simpletest/tests/handlers_test.info
===================================================================
RCS file: modules/simpletest/tests/handlers_test.info
diff -N modules/simpletest/tests/handlers_test.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/handlers_test.info	9 Feb 2009 02:08:23 -0000
@@ -0,0 +1,8 @@
+; $Id$
+name = "Handlers Test"
+description = "Support module for Handlers tests."
+core = 7.x
+package = Testing
+files[] = handlers_test.module
+version = VERSION
+;hidden = TRUE
Index: modules/simpletest/tests/handlers_test.module
===================================================================
RCS file: modules/simpletest/tests/handlers_test.module
diff -N modules/simpletest/tests/handlers_test.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/handlers_test.module	9 Feb 2009 02:08:23 -0000
@@ -0,0 +1,167 @@
+<?php
+// $Id$
+
+/**
+ * Implementation of hook_slot_info().
+ */
+function handlers_test_slot_info() {
+  return array(
+    'fancystring' => array(
+      'title' => 'Fancy string',
+      'description' => 'Do fancy stuff to strings',
+      'interface' => 'FancystringInterface',
+      'default_handler' => 'default',
+      'reuse' => FALSE,
+    ),
+    'thingie' => array(
+      'title' => 'Other slot',
+      'description' => 'This slot does nothing.  It\'s just to test lookup behavior of reusable slots.',
+      'interface' => 'ThingieInterface',
+    ),
+  );
+}
+
+/**
+ * Implementation of hook_handler_info().
+ */
+function handlers_test_handler_info() {
+  return array(
+    'fancystring' => array(
+       'default' => array(
+         // Translate on load, not define, like hook_menu().
+         'title' => 'No string mutation',
+         'class' => 'FancystringDefault',
+         'description' => "This handler doesn't do anything to the string.  It passes through unaltered.",
+       ),
+       'rot13' => array(
+         'title' => 'Rot13 translation',
+         'class' => 'FancystringRot13',
+         'description' => 'This handler ROT13 encrypts a string.',
+       ),
+       'custom_translate' => array(
+         'title' => 'Custom mapping',
+         'class' => 'FancystringCustom',
+         'description' => 'This handler uses a user-specified mapping array.',
+       ),
+     ),
+    'thingie' => array(
+      'default' => array(
+        'title' => 'Do nothing handler',
+        'description' => 'This handler does nothing',
+        'class' => 'ThingieDefault',
+      )
+    ),
+  );
+}
+
+/**
+ * Interface for the example string mangling handler.
+ */
+interface FancystringInterface extends HandlerInterface {
+
+/**
+ * Set the string for this object to mangle.
+ * @param $string
+ *   The string to mangle.
+ */
+  public function setString($string);
+
+  /**
+   * Get the string back, mangled according to this handler.
+   * @return
+   *   The mangled string.
+   */
+  public function getString();
+}
+
+/**
+ * Base implementation of Fancystring handler.
+ */
+abstract class FancystringBase implements FancystringInterface {
+
+  protected $target;
+
+  protected $string = '';
+
+  function __construct($target = 'default') {
+    $this->target = $target;
+  }
+
+  public function setString($string) {
+    $this->string = $string;
+  }
+}
+
+/**
+ * Default implementation of Fancystring handler.
+ */
+class FancystringDefault extends FancystringBase {
+
+  public function getString() {
+    return $this->string;
+  }
+}
+
+/**
+ * Rot13 implementation of Fancystring handler.
+ */
+class FancystringRot13 extends FancystringBase {
+
+  public function getString() {
+    return str_rot13($this->string);
+  }
+}
+
+/**
+ * Custom replacement implementation of Fancystring handler.
+ *
+ * Note that we are implementing the FancystringInterface here directly
+ * rather than inheriting from the base class, mostly to show that we can.
+ * There's no obligation that handlers do anything other than support the
+ * appropriate interface.  How they do so is entirely up to them.
+ *
+ */
+class FancystringCustom implements FancystringInterface {
+
+  protected $string = '';
+
+  protected $target;
+
+  function __construct($target = 'default') {
+    $this->target = $target;
+  }
+
+  public function setString($string) {
+    $this->string = $string;
+  }
+
+  public function getString() {
+    $map = variable_get('fancystring_translate', array());
+
+    return strtr($this->string, $map);
+  }
+}
+
+/**
+ * Interface for the do-nothing test handler.
+ */
+interface ThingieInterface extends HandlerInterface {
+
+  /**
+   * Do nothing.
+   */
+  function noop();
+}
+
+class ThingieDefault implements ThingieInterface {
+
+  protected $target;
+
+  function __construct($target = 'default') {
+    $this->target = $target;
+  }
+
+  function noop() {
+    return;
+  }
+}
Index: modules/system/system.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v
retrieving revision 1.124
diff -u -p -r1.124 system.admin.inc
--- modules/system/system.admin.inc	3 Feb 2009 18:55:31 -0000	1.124
+++ modules/system/system.admin.inc	9 Feb 2009 02:08:23 -0000
@@ -1417,6 +1417,46 @@ function system_clear_cache_submit(&$for
   drupal_set_message(t('Caches cleared.'));
 }
 
+
+/**
+ * Form builder; Configure the path alias lookup engine.
+ *
+ * @ingroup forms
+ */
+function system_path_lookup_form() {
+  $form = array();
+
+  $options = array();
+  foreach (handler_get_available_handlers('path_lookup') as $handler => $info) {
+    $options[$handler] = t('@title (%description)', array(
+      '@title' => t($info->title),
+      '%description' => t($info->description),
+    ));
+  }
+
+  $handler = handler_get_attached_handler('path_lookup');
+
+  $form['path_lookup'] = array(
+    '#title' => 'Path lookup mechanism',
+    '#type' => 'radios',
+    '#options' => $options,
+    '#default_value' => $handler->handler,
+  );
+
+  $form['buttons']['submit'] = array('#type' => 'submit', '#value' => t('Save configuration') );
+
+  return $form;
+}
+
+/**
+ * Submit callback; save the path lookup handler selection.
+ *
+ * @ingroup forms
+ */
+function system_path_lookup_form_submit($form, &$form_state) {
+  handler_attach('path_lookup', $form_state['values']['path_lookup']);
+}
+
 /**
  * Form builder; Configure the site file handling.
  *
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.17
diff -u -p -r1.17 system.api.php
--- modules/system/system.api.php	31 Jan 2009 16:50:57 -0000	1.17
+++ modules/system/system.api.php	9 Feb 2009 02:08:23 -0000
@@ -212,7 +212,7 @@ function hook_js_alter(&$javascript) {
  *   $page['content']['nodes'][$nid]['#node']
  *   // The results pager.
  *   $page['content']['pager']
- * @code 
+ * @code
  *
  * Blocks may be referenced by their module/delta pair within a region:
  * @code
@@ -1636,5 +1636,142 @@ function hook_disable() {
 }
 
 /**
+ * Define one or more slots for the handlers system.
+ *
+ * This hook should return a nested array of slot definitions.  Each key
+ * in the array is the machine-readable slot name, and its value is an array
+ * of values that define the slot.
+ *
+ * title
+ *   This is the human-readable name of the slot. Note that this value
+ *   should not be run through t() as it will be stored in the database. It
+ *   should be translated when displayed to the user.
+ * description
+ *   A human-readable description of what the slot is for. Note that this value
+ *   should not be run through t() as it will be stored in the database. It
+ *   should be translated when displayed to the user.
+ * interface
+ *   The PHP interface that defines this slot.  The interface must extend
+ *   HandlerInterface, but otherwise may define whatever methods it wants.
+ *   It may also be placed in any file, depending on what would provide the
+ *   most performance for autoloading.
+ * default_handler (optional)
+ *   The machine-readable name of the default handler for this slot, as defined
+ *   in hook_handler_info().  All slots must have at least one handler
+ *   available.  If not specified, "default" is used.
+ * reuse (optional)
+ *   By default, if a given target on a slot is requested multiple times in
+ *   the same page request the same object will be returned each time.  The
+ *   object will be statically cached.  To disable that behavior and return
+ *   a newly instantiated object for every request, set this value to FALSE.
+ *
+ * This is a registry-style function.  It should normally be placed in the
+ * file $module.registry.inc.
+ *
+ * @return
+ *   A slot definition array.
+ */
+function hook_slot_info() {
+  return array(
+    'fancystring' => array(
+      'title' => 'Fancy string',
+      'description' => 'Do fancy stuff to strings',
+      'interface' => 'FancystringInterface',
+      'default_handler' => 'default',
+      'reuse' => FALSE,
+    ),
+    'thingie' => array(
+      'title' => 'Other slot',
+      'description' => 'This slot does nothing.  It\'s just to test lookup behavior of reusable slots.',
+      'interface' => 'ThingieInterface',
+    ),
+  );
+}
+
+/**
+ * Alter the slot definitions of another module.
+ *
+ * @param $info
+ *   The complete slot definition array from hook_slot_info().
+ */
+function hook_slot_info_alter(&$info) {
+  // Change the default handler.
+  $info['fancystring']['default_handler'] = 'string_mangler';
+}
+
+/**
+ * Define one or more handlers in the system.
+ *
+ * This hook should return a doubly-nested array of handler definitions.  The
+ * first key is an existing slot.  Its value is an associative array of handler
+ * machine-readable names and handler definitions.
+ *
+ * Each handler definition is an associative array of values that define the
+ * handler.
+ *
+ * title
+ *   This is the human-readable name of the handler. Note that this value
+ *   should not be run through t() as it will be stored in the database. It
+ *   should be translated when displayed to the user.
+ * description
+ *   A human-readable description of the handler. Note that this value
+ *   should not be run through t() as it will be stored in the database. It
+ *   should be translated when displayed to the user.
+ * class
+ *   The PHP class that defines this handler.  The class must implement the
+ *   interface defined for this slot, but otherwise may be defined in any way
+ *   desired, including implementing the interface directly or indirectly by
+ *   subclassing another class that implements that interface. It may also be
+ *   placed in any file, depending on what would provide the most performance
+ *   for autoloading.
+ *
+ * This is a registry-style function.  It should normally be placed in the
+ * file $module.registry.inc.
+ *
+ * @return
+ *   A handler definition array.
+ */
+function hook_handler_info() {
+  return array(
+    'fancystring' => array(
+       'default' => array(
+         // Translate on load, not define, like hook_menu().
+         'title' => 'No string mutation',
+         'class' => 'FancystringDefault',
+         'description' => "This handler doesn't do anything to the string.  It passes through unaltered.",
+       ),
+       'rot13' => array(
+         'title' => 'Rot13 translation',
+         'class' => 'FancystringRot13',
+         'description' => 'This handler ROT13 encrypts a string.',
+       ),
+       'custom_translate' => array(
+         'title' => 'Custom mapping',
+         'class' => 'FancystringCustom',
+         'description' => 'This handler uses a user-specified mapping array.',
+       ),
+     ),
+    'thingie' => array(
+      'default' => array(
+        'title' => 'Do nothing handler',
+        'description' => 'This handler does nothing',
+        'class' => 'ThingieDefault',
+      )
+    ),
+  );
+}
+
+/**
+ * Alter the handler definitions of another module.
+ *
+ * @param $info
+ *   The complete handler definition array from hook_handler_info().
+ */
+function hook_handler_info_alter(&$info) {
+  // change the class used by the rot13 handler.
+  $info['fancystring']['rot13']['class'] = 'FancystringRot13Alternate';
+}
+
+/**
  * @} End of "addtogroup hooks".
  */
Index: modules/system/system.info
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.info,v
retrieving revision 1.11
diff -u -p -r1.11 system.info
--- modules/system/system.info	12 Oct 2008 01:23:06 -0000	1.11
+++ modules/system/system.info	9 Feb 2009 02:08:23 -0000
@@ -8,4 +8,6 @@ files[] = system.module
 files[] = system.admin.inc
 files[] = image.gd.inc
 files[] = system.install
+files[] = system.path_lazy.inc
+files[] = system.path_precache.inc
 required = TRUE
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.306
diff -u -p -r1.306 system.install
--- modules/system/system.install	3 Feb 2009 12:30:14 -0000	1.306
+++ modules/system/system.install	9 Feb 2009 02:08:24 -0000
@@ -726,6 +726,122 @@ function system_schema() {
       'nid' => array('nid'),
     ),
   );
+
+  $schema['handler_attachments'] = array(
+    'description' => 'Maps slots and targets to their configured handler.',
+    'fields' => array(
+      'slot' => array(
+        'description' => 'The machine-readable name of the slot.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'target' => array(
+        'description' => 'The target value.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'handler' => array(
+        'description' => 'The handler attached to this slot/target.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'class' => array(
+        'description' => 'The PHP class for the handler attached to this slot/target.  It is stored in this table to make lookups faster.',
+        'type' => 'varchar',
+        'length' => '100',
+        'not null' => TRUE,
+      ),
+      'reuse' => array(
+        'description' => 'Whether or not this handler may be reused.  It is stored in this table to make lookups faster.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 1,
+      ),
+    ),
+    'primary key' => array('slot', 'target'),
+  );
+
+  $schema['handler_info'] = array(
+    'description' => 'Tracks all available handlers in the system.',
+    'fields' => array(
+      'handler' => array(
+        'description' => 'The machine-readable name of the handler.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'slot' => array(
+        'description' => 'The machine-readable name of the slot this handler is for.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'title' => array(
+        'description' => 'The human-readable name of the handler.',
+        'type' => 'varchar',
+        'length' => '100',
+        'not null' => TRUE,
+      ),
+      'class' => array(
+        'description' => 'The PHP class that defines this handler.',
+        'type' => 'varchar',
+        'length' => '100',
+        'not null' => TRUE,
+      ),
+      'description' => array(
+        'description' => 'A human-readable description of this handler.',
+        'type' => 'text',
+        'not null' => TRUE,
+      )
+    ),
+    'primary key' => array('handler', 'slot'),
+  );
+
+  $schema['handler_slot_info'] = array(
+    'description' => 'Tracks all registered slots in the system that handlers can fulfill.',
+    'fields' => array(
+      'slot' => array(
+        'description' => 'The machine-readable name of the slot.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'title' => array(
+        'description' => 'The human-readable name of the slot.',
+        'type' => 'varchar',
+        'length' => '100',
+        'not null' => TRUE,
+      ),
+      'interface' => array(
+        'description' => 'The PHP interface that defines this slot.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'reuse' => array(
+        'description' => 'Whether or not to reuse handler objects on this slot when requested multiple times.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 1,
+      ),
+      'default_handler' => array(
+        'description' => 'The default handler id for this slot if one is not otherwise configured.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'description' => array(
+        'description' => 'A human-readable description of this slot.',
+        'type' => 'text',
+        'not null' => FALSE,
+      ),
+    ),
+    'primary key' => array('slot'),
+  );
+
   $schema['menu_router'] = array(
     'description' => 'Maps paths to various callbacks (access, page and title)',
     'fields' => array(
@@ -3213,6 +3329,134 @@ function system_update_7018() {
 }
 
 /**
+ * Add the new tables to support handlers.
+ */
+function system_update_7019() {
+  $ret = array();
+
+  $schema['handler_attachments'] = array(
+    'description' => 'Maps slots and targets to their configured handler.',
+    'fields' => array(
+      'slot' => array(
+        'description' => 'The machine-readable name of the slot.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'target' => array(
+        'description' => 'The target value.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'handler' => array(
+        'description' => 'The handler attached to this slot/target.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'class' => array(
+        'description' => 'The PHP class for the handler attached to this slot/target.  It is stored in this table to make lookups faster.',
+        'type' => 'varchar',
+        'length' => '100',
+        'not null' => TRUE,
+      ),
+      'reuse' => array(
+        'description' => 'Whether or not this handler may be reused.  It is stored in this table to make lookups faster.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 1,
+      ),
+    ),
+    'primary key' => array('slot', 'target'),
+  );
+
+  $schema['handler_info'] = array(
+    'description' => 'Tracks all available handlers in the system.',
+    'fields' => array(
+      'handler' => array(
+        'description' => 'The machine-readable name of the handler.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'slot' => array(
+        'description' => 'The machine-readable name of the slot this handler is for.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'title' => array(
+        'description' => 'The human-readable name of the handler.',
+        'type' => 'varchar',
+        'length' => '100',
+        'not null' => TRUE,
+      ),
+      'class' => array(
+        'description' => 'The PHP class that defines this handler.',
+        'type' => 'varchar',
+        'length' => '100',
+        'not null' => TRUE,
+      ),
+      'description' => array(
+        'description' => 'A human-readable description of this handler.',
+        'type' => 'text',
+        'not null' => TRUE,
+      )
+    ),
+    'primary key' => array('handler', 'slot'),
+  );
+
+  $schema['handler_slot_info'] = array(
+    'description' => 'Tracks all registered slots in the system that handlers can fulfill.',
+    'fields' => array(
+      'slot' => array(
+        'description' => 'The machine-readable name of the slot.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'title' => array(
+        'description' => 'The human-readable name of the slot.',
+        'type' => 'varchar',
+        'length' => '100',
+        'not null' => TRUE,
+      ),
+      'interface' => array(
+        'description' => 'The PHP interface that defines this slot.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'reuse' => array(
+        'description' => 'Whether or not to reuse handler objects on this slot when requested multiple times.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 1,
+      ),
+      'default_handler' => array(
+        'description' => 'The default handler id for this slot if one is not otherwise configured.',
+        'type' => 'varchar',
+        'length' => '50',
+        'not null' => TRUE,
+      ),
+      'description' => array(
+        'description' => 'A human-readable description of this slot.',
+        'type' => 'text',
+        'not null' => FALSE,
+      ),
+    ),
+    'primary key' => array('slot'),
+  );
+
+  foreach ($schema as $table => $info) {
+    db_create_table($ret, $table, $info);
+  }
+
+  return $ret;
+}
+
+/**
  * @} End of "defgroup updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
  */
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.666
diff -u -p -r1.666 system.module
--- modules/system/system.module	3 Feb 2009 18:55:31 -0000	1.666
+++ modules/system/system.module	9 Feb 2009 02:08:24 -0000
@@ -720,6 +720,12 @@ function system_menu() {
     'access callback' => TRUE,
     'type' => MENU_CALLBACK,
   );
+  $items['admin/settings/path-lookup'] = array(
+    'title' => 'Path lookup',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('system_path_lookup_form'),
+    'access arguments' => array('administer site configuration'),
+  );
 
   // Reports:
   $items['admin/reports'] = array(
@@ -2312,3 +2318,102 @@ function theme_meta_generator_header($ve
 function system_image_toolkits() {
   return array('gd');
 }
+
+/**
+ * Implementation of hook_slot_info().
+ */
+function system_slot_info() {
+  $slots['path_lookup'] = array(
+    'title' => 'Path aliases',
+    'description' => 'Lookup engine for path aliases',
+    'interface' => 'PathAliasInterface',
+    'default_handler' => 'lazy',
+  );
+
+  return $slots;
+}
+
+/**
+ * Implementation of hook_handler_info().
+ */
+function system_handler_info() {
+  $handlers['path_lookup']['lazy'] = array(
+    'title' => 'Lazy lookup',
+    'description' => 'Aliases are looked up one at a time as they are requested.  Good for sites with a very large number of path aliases.',
+    'class' => 'PathAliasLazy',
+  );
+  $handlers['path_lookup']['precache'] = array(
+    'title' => 'Pre-caching',
+    'description' => 'All aliases are loaded from the database at once and then looked up in memory.  Good for sites with a much smaller number of path aliases than pages.',
+    'class' => 'PathAliasPrecache',
+  );
+
+  return $handlers;
+}
+
+/**
+ * Interface for path alias lookup handlers.
+ */
+interface PathAliasInterface extends HandlerInterface {
+
+  /**
+   * Given a system path, get the appropriate alias if any.
+   *
+   * @param $path
+   *   The system path for which we want the appropriate alias.
+   * @param path_language
+   *   Optional language code to search the path with. Defaults to the page language.
+   *   If there's no path defined for that language it will search paths without
+   *   language.
+   * @return
+   *   An aliased path if one was found, or the original path if no alias was
+   *   found.
+   */
+  public function lookupAlias($path = '', $path_language = '');
+
+  /**
+   * Given an aliased path, get the corresponding system path, if any.
+   *
+   * @param $dest
+   *   The alias for which we want the Drupal system path.
+   * @param path_language
+   *   Optional language code to search the path with. Defaults to the page language.
+   *   If there's no path defined for that language it will search paths without
+   *   language.
+   * @return
+   *   The internal path represented by the alias, or the original alias if no
+   *   internal path was found.
+   */
+  function lookupPath($dest = '', $path_language = '');
+
+  /**
+   * Clear the cache of looked up aliases so far this request.
+   */
+  public function clearCache();
+
+}
+
+abstract class PathAliasBase extends HandlerBase implements PathAliasInterface {
+
+  /**
+   * An array with language keys, holding arrays of Drupal paths to alias relations.
+   *
+   * @var array
+   */
+  protected $map;
+
+  /**
+   * Keeps track of the number of paths in the system.
+   *
+   * If this value is 0, we know to not bother looking up any paths.
+   *
+   * @var int
+   */
+  protected $count;
+
+  public function clearCache() {
+    $this->map = array();
+    $this->noSrc = array();
+    $this->count = NULL;
+  }
+}
Index: modules/system/system.path_lazy.inc
===================================================================
RCS file: modules/system/system.path_lazy.inc
diff -N modules/system/system.path_lazy.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/system/system.path_lazy.inc	9 Feb 2009 02:08:24 -0000
@@ -0,0 +1,74 @@
+<?php
+// $Id$
+
+/**
+ * Lazy-lookup path alias lookup handler.
+ */
+class PathAliasLazy extends PathAliasBase {
+
+  public function lookupAlias($path = '', $path_language = '') {
+    global $language;
+
+    // Use $count to avoid looking up paths in subsequent calls if there simply are no aliases
+    if (empty($this->count)) {
+      $this->count = db_query('SELECT COUNT(pid) FROM {url_alias}')->fetchField();
+    }
+
+    if (!$this->count) {
+      return $path;
+    }
+
+    $path_language = $path_language ? $path_language : $language->language;
+
+    if (empty($this->map[$path_language][$path])) {
+    // Get the most fitting result falling back with alias without language
+      $alias = db_query("SELECT dst FROM {url_alias} WHERE src = :src AND language IN(:language, '') ORDER BY language DESC", array(
+        ':src' => $path,
+        ':language' => $path_language,
+      ))->fetchField();
+
+      // If there was no alias, the path translates back to itself.
+      $this->map[$path_language][$path] = $alias ? $alias : $path;
+    }
+
+    return $this->map[$path_language][$path];
+  }
+
+  public function lookupPath($dest = '', $path_language = '') {
+    global $language;
+
+    // Use $count to avoid looking up paths in subsequent calls if there simply are no aliases
+    if (empty($this->count)) {
+      $this->count = db_query('SELECT COUNT(pid) FROM {url_alias}')->fetchField();
+    }
+
+    if (!$this->count) {
+      return $dest;
+    }
+
+    $path_language = $path_language ? $path_language : $language->language;
+
+    // If we already know that there is no path, simply return now.
+    if (isset($this->noSrc[$path_language][$dest])) {
+      return $dest;
+    }
+
+    // Reverse-lookup the path from the provided alias.
+    $src = db_query("SELECT src FROM {url_alias} WHERE dst = :dst AND language IN(:language, '') ORDER BY language DESC", array(
+      ':dst' => $dest,
+      ':language' => $path_language,
+    ))->fetchField();
+
+    if (!$src) {
+      $this->noSrc[$path_language][$dest] = TRUE;
+      return $dest;
+    }
+    else {
+      // Now that we know the path, we can also cache it for forward lookups.
+      $this->map[$path_language][$src] = $dest;
+
+      // Return the path we found.
+      return $src;
+    }
+  }
+}
Index: modules/system/system.path_precache.inc
===================================================================
RCS file: modules/system/system.path_precache.inc
diff -N modules/system/system.path_precache.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ modules/system/system.path_precache.inc	9 Feb 2009 02:08:24 -0000
@@ -0,0 +1,74 @@
+<?php
+// $Id$
+
+/**
+ * Pre-caching path alias lookup handler.
+ */
+class PathAliasPrecache extends PathAliasBase {
+
+  public function lookupAlias($path = '', $path_language = '') {
+    global $language;
+
+    // Use $count to avoid looking up paths in subsequent calls if there simply are no aliases
+    if (empty($this->count)) {
+      $this->count = db_query('SELECT COUNT(pid) FROM {url_alias}')->fetchField();
+    }
+
+    if (!$this->count) {
+      return $path;
+    }
+
+    $path_language = $path_language ? $path_language : $language->language;
+
+    // Pre-fetch the entire path lookup table so that we never need to check
+    // the database again.
+    // @todo: This could probably leverage the cache system for even more
+    // speed, and then we override clearCache() as well.
+    if (empty($this->map)) {
+      $result = db_query("SELECT src, dst, language FROM {url_alias}");
+      foreach ($result as $record) {
+        $this->map[$record->language][$record->src] = $record->dst;
+      }
+    }
+
+    return isset($this->map[$path_language][$path]) ? $this->map[$path_language][$path] : $path;
+  }
+
+  public function lookupPath($dest = '', $path_language = '') {
+    global $language;
+
+    // Use $count to avoid looking up paths in subsequent calls if there simply are no aliases
+    if (empty($this->count)) {
+      $this->count = db_query('SELECT COUNT(pid) FROM {url_alias}')->fetchField();
+    }
+
+    if (!$this->count) {
+      return $dest;
+    }
+
+    $path_language = $path_language ? $path_language : $language->language;
+
+    // If we already know that there is no path, simply return now.
+    if (isset($this->noSrc[$path_language][$dest])) {
+      return $dest;
+    }
+
+    // Reverse-lookup the path from the provided alias.
+    $src = db_query("SELECT src FROM {url_alias} WHERE dst = :dst AND language IN(:language, '') ORDER BY language DESC", array(
+      ':dst' => $dest,
+      ':language' => $path_language,
+    ))->fetchField();
+
+    if (!$src) {
+      $this->noSrc[$path_language][$dest] = TRUE;
+      return $dest;
+    }
+    else {
+      // Now that we know the path, we can also cache it for forward lookups.
+      $this->map[$path_language][$src] = $dest;
+
+      // Return the path we found.
+      return $src;
+    }
+  }
+}
