docs: function UIMap()
  {
      // the singleton pattern, split into two parts so that "new" can still
      // be used, in addition to "getInstance()"
      UIMap.self = this;
      
      // need to attach variables directly to the Editor object in order for them
      // to be in scope for Editor methods
      if (is_IDE()) {
          Editor.uiMap = this;
          Editor.UI_PREFIX = UI_GLOBAL.UI_PREFIX;
      }
      
      this.pagesets = new Object();
      
      
      
      /**
       * pageset[pagesetName]
       *   regexp
       *   elements[elementName]
       *     UIElement
       */
      this.addPageset = function(pagesetShorthand)
      {
          try {
              var pageset = new Pageset(pagesetShorthand);
          }
          catch (e) {
              safe_alert("Could not create pageset from shorthand:\n"
                  + print_r(pagesetShorthand) + "\n" + e.message);
              return false;
          }
          
          if (this.pagesets[pageset.name]) {
              safe_alert('Could not add pageset "' + pageset.name
                  + '": a pageset with that name already exists!');
              return false;
          }
          
          this.pagesets[pageset.name] = pageset;
          return true;
      };
      
      
      
      /**
       * @param pagesetName
       * @param uiElementShorthand  a representation of a UIElement object in
       *                            shorthand JSON.
       */
      this.addElement = function(pagesetName, uiElementShorthand)
      {
          try {
              var uiElement = new UIElement(uiElementShorthand);
          }
          catch (e) {
              safe_alert("Could not create UI element from shorthand:\n"
                  + print_r(uiElementShorthand) + "\n" + e.message);
              return false;
          }
          
          // run the element's unit tests only for the IDE, and only when the
          // IDE is starting. Make a rough guess as to the latter condition.
          if (is_IDE() && !editor.selDebugger && !uiElement.test()) {
              safe_alert('Could not add UI element "' + uiElement.name
                  + '": failed testcases!');
              return false;
          }
          
          try {
              this.pagesets[pagesetName].uiElements[uiElement.name] = uiElement;
          }
          catch (e) {
              safe_alert("Could not add UI element '" + uiElement.name
                  + "' to pageset '" + pagesetName + "':\n" + e.message);
              return false;
          }
          
          return true;
      };
      
      
      
      /**
       * Returns the pageset for a given UI specifier string.
       *
       * @param uiSpecifierString
       * @return  a pageset object
       */
      this.getPageset = function(uiSpecifierString)
      {
          try {
              var uiSpecifier = new UISpecifier(uiSpecifierString);
              return this.pagesets[uiSpecifier.pagesetName];
          }
          catch (e) {
              return null;
          }
      }
      
      
      
      /**
       * Returns the UIElement that a UISpecifierString or pageset and element
       * pair refer to.
       *
       * @param pagesetNameOrUISpecifierString
       * @return  a UIElement, or null if none is found associated with
       *          uiSpecifierString
       */
      this.getUIElement = function(pagesetNameOrUISpecifierString, uiElementName)
      {
          var pagesetName = pagesetNameOrUISpecifierString;
          if (arguments.length == 1) {
              var uiSpecifierString = pagesetNameOrUISpecifierString;
              try {
                  var uiSpecifier = new UISpecifier(uiSpecifierString);
                  pagesetName = uiSpecifier.pagesetName;
                  var uiElementName = uiSpecifier.elementName;
              }
              catch (e) {
                  return null;
              }
          }
          try {
              return this.pagesets[pagesetName].uiElements[uiElementName];
          }
          catch (e) {
              return null;
          }
      };
      
      
      
      /**
       * Returns a list of pagesets that "contains" the provided page,
       * represented as a document object. Containership is defined by the
       * Pageset object's contain() method.
       *
       * @param inDocument  the page to get pagesets for
       * @return            a list of pagesets
       */
      this.getPagesetsForPage = function(inDocument)
      {
          var pagesets = [];
          for (var pagesetName in this.pagesets) {
              var pageset = this.pagesets[pagesetName];
              if (pageset.contains(inDocument)) {
                  pagesets.push(pageset);
              }
          }
          return pagesets;
      };
      
      
      
      /**
       * Returns a list of all pagesets.
       *
       * @return  a list of pagesets
       */
      this.getPagesets = function()
      {
          var pagesets = [];
          for (var pagesetName in this.pagesets) {
              pagesets.push(this.pagesets[pagesetName]);
          }
          return pagesets;
      };
      
      
      
      /**
       * Returns a list of elements on a page that a given UI specifier string,
       * maps to. If no elements are mapped to, returns an empty list..
       *
       * @param   uiSpecifierString  a String that specifies a UI element with
       *                             attendant argument values
       * @param   inDocument         the document object the specified UI element
       *                             appears in
       * @return                     a potentially-empty list of elements
       *                             specified by uiSpecifierString
       */
      this.getPageElements = function(uiSpecifierString, inDocument)
      {
          var locator = this.getLocator(uiSpecifierString);
          var results = locator ? eval_locator(locator, inDocument) : [];
          return results;
      };
      
      
      
      /**
       * Returns the locator string that a given UI specifier string maps to, or
       * null if it cannot be mapped.
       *
       * @param uiSpecifierString
       */
      this.getLocator = function(uiSpecifierString)
      {
          try {
              var uiSpecifier = new UISpecifier(uiSpecifierString);
          }
          catch (e) {
              safe_alert('Could not create UISpecifier for string "'
                  + uiSpecifierString + '": ' + e.message);
              return null;
          }
          
          var uiElement = this.getUIElement(uiSpecifier.pagesetName,
              uiSpecifier.elementName);
          try {
              return uiElement.getLocator(uiSpecifier.args);
          }
          catch (e) {
              return null;
          }
      }
      
      
      
      /**
       * Finds and returns a UI specifier string given an element and the page
       * that it appears on.
       *
       * @param pageElement  the document element to map to a UI specifier
       * @param inDocument   the document the element appears in
       * @return             a UI specifier string, or false if one cannot be
       *                     constructed
       */
      this.getUISpecifierString = function(pageElement, inDocument)
      {
          var is_fuzzy_match =
              BrowserBot.prototype.locateElementByUIElement.is_fuzzy_match;
          var pagesets = this.getPagesetsForPage(inDocument);
          
          for (var i = 0; i < pagesets.length; ++i) {
              var pageset = pagesets[i];
              var uiElements = pageset.getUIElements();
              
              for (var j = 0; j < uiElements.length; ++j) {
                  var uiElement = uiElements[j];
                  
                  // first test against the generic locator, if there is one.
                  // This should net some performance benefit when recording on
                  // more complicated pages.
                  if (uiElement.getGenericLocator) {
                      var passedTest = false;
                      var results =
                          eval_locator(uiElement.getGenericLocator(), inDocument);
                      for (var i = 0; i < results.length; ++i) {
                          if (results[i] == pageElement) {
                              passedTest = true;
                              break;
                          }
                      }
                      if (!passedTest) {
                          continue;
                      }
                  }
                  
                  var defaultLocators;
                  if (uiElement.isDefaultLocatorConstructionDeferred) {
                      defaultLocators = uiElement.getDefaultLocators(inDocument);
                  }
                  else {
                      defaultLocators = uiElement.defaultLocators;
                  }
                  
                  //safe_alert(print_r(uiElement.defaultLocators));
                  for (var locator in defaultLocators) {
                      var locatedElements = eval_locator(locator, inDocument);
                      if (locatedElements.length) {
                          var locatedElement = locatedElements[0];
                      }
                      else {
                          continue;
                      }
                      
                      // use a heuristic to determine whether the element
                      // specified is the "same" as the element we're matching
                      if (is_fuzzy_match) {
                          if (is_fuzzy_match(locatedElement, pageElement)) {
                              return UI_GLOBAL.UI_PREFIX + '=' +
                                  new UISpecifier(pageset.name, uiElement.name,
                                      defaultLocators[locator]);
                          }
                      }
                      else {
                          if (locatedElement == pageElement) {
                              return UI_GLOBAL.UI_PREFIX + '=' +
                                  new UISpecifier(pageset.name, uiElement.name,
                                      defaultLocators[locator]);
                          }
                      }
                      
                      // ok, matching the element failed. See if an offset
                      // locator can complete the match.
                      if (uiElement.getOffsetLocator) {
                          for (var k = 0; k < locatedElements.length; ++k) {
                              var offsetLocator = uiElement
                                  .getOffsetLocator(locatedElements[k], pageElement);
                              if (offsetLocator) {
                                  return UI_GLOBAL.UI_PREFIX + '=' +
                                      new UISpecifier(pageset.name,
                                          uiElement.name,
                                          defaultLocators[locator])
                                      + '->' + offsetLocator;
                              }
                          }
                      }
                  }
              }
          }
          return false;
      };
      
      
      
      /**
       * Returns a sorted list of UI specifier string stubs representing possible
       * UI elements for all pagesets, paired the their descriptions. Stubs
       * contain all required arguments, but leave argument values blank.
       *
       * @return  a list of UI specifier string stubs
       */
      this.getUISpecifierStringStubs = function() {
          var stubs = [];
          var pagesets = this.getPagesets();
          for (var i = 0; i < pagesets.length; ++i) {
              stubs = stubs.concat(pagesets[i].getUISpecifierStringStubs());
          }
          stubs.sort(function(a, b) {
              if (a[0] < b[0]) {
                  return -1;
              }
              return a[0] == b[0] ? 0 : 1;
          });
          return stubs;
      }
  }