text: /**
   * This class provides name derivation methods for use by a `Schema`.
   *
   * # Caching
   *
   * Because most name derivations are only textual manipulations of input strings, the
   * results can be cached. This is handled by the `apply` method by giving it the name of
   * the method to call. For example:
   *
   *      var str = namer.capitalize('foo'); //  = "Foo"
   *
   *      var str = namer.apply('capitalize', 'foo');
   *
   * The return value of the second call (using `apply`) is the same as the first, however,
   * the results of `capitalize` are cached. This allows repeated calls to `apply` given the
   * same operation and string to avoid the extra string manipulation.
   *
   * # Usage
   *
   * This class is not intended to be created by application code. It is created by `Schema`
   * instances as directed by the `namer` config. Application code can derive from this
   * class and set the `namer` config to customize naming conventions used by the `Schema`.
   *
   * @protected
   */
  Ext.define('Ext.data.schema.Namer', {
      mixins: [
          'Ext.mixin.Factoryable'
      ],
  
      requires: [
          'Ext.util.Inflector'
      ],
  
      alias: 'namer.default', // also configures Factoryable
  
      isNamer: true,
  
      //-------------------------------------------------------------------------
      // Cacheable methods
  
      capitalize: function (name) {
          return Ext.String.capitalize(name);
      },
  
      /**
       * Given the name of a foreign key field, return the role of the related entity. For
       * example, fields like "fooId" or "foo_id" this implementation returns "foo".
       * @template
       */
      fieldRole: function (name) {
          var match = name.match(this.endsWithIdRe, '');
          if (match) {
              name = name.substr(0, name.length - (match[1] || match[2]).length);
          }
          return this.apply('uncapitalize', name);
      },
  
      idField: function (name) {
          // ex: User ==> userId
          return this.apply('uncapitalize,singularize', name) + 'Id';
      },
  
      instanceName: function (roleName) {
          return this.apply('underscore', roleName);
      },
  
      multiRole: function (name) {
          return this.apply('undotted,uncapitalize,pluralize', name);
      },
  
      pluralize: function (name) {
          return Ext.util.Inflector.pluralize(name);
      },
  
      readerRoot: function (roleName) {
          return this.apply('uncapitalize', roleName);
      },
  
      singularize: function (name) {
          return Ext.util.Inflector.singularize(name);
      },
  
      storeName: function (roleName) {
          return this.apply('underscore', roleName);
      },
  
      uncapitalize: function (name) {
          return Ext.String.uncapitalize(name);
      },
  
      underscore: function (name) {
          return '_' + name;
      },
  
      uniRole: function (name) {
          return this.apply('undotted,uncapitalize,singularize', name);
      },
  
      undotted: function (name) {
          if (name.indexOf('.') < 0) {
              return name;
          }
  
          var parts = name.split('.'),
              index = parts.length;
  
          while (index-- > 1) {
              parts[index] = this.apply('capitalize', parts[index]);
          }
  
          return parts.join('');
      },
  
      //-------------------------------------------------------------------------
      // Non-Cacheable methods
  
      getterName: function (role) {
          var name = role.role;
  
          if (role && role.isMany) {
              //return this.apply('uncapitalize,pluralize', name);
              return name;
          }
  
          //return this.apply('capitalize,singularize', name);
          return 'get' + this.apply('capitalize', name);
      },
  
      inverseFieldRole: function (leftType, unique, rightRole, rightType) {
          // In a FK association, the left side may be unique in which case we have a
          // one-to-one otherwise we have a one-to-many. If the FK field is just the
          // name of the right side class (e.g., if it is "order"), then we don't want
          // to include the field name in the left role.
          var me = this,
              leftRole = me.apply(unique ? 'uniRole' : 'multiRole', leftType),
              s1 = me.apply('pluralize', rightRole),
              s2 = me.apply('undotted,pluralize', rightType);
  
          if (s1.toLowerCase() !== s2.toLowerCase()) {
              // Otherwise, we have something like "creatorId" on Ticket that holds a
              // reference to User. This makes the right role "creator" so rather than
              // make the left role "tickets" we make it "creatorTickets".
              leftRole = rightRole + me.apply('capitalize', leftRole);
          }
  
          return leftRole;
      },
  
      manyToMany: function (relation, leftType, rightType) {
          var me = this,
          // ex: UserGroups
              ret = me.apply('undotted,capitalize,singularize', leftType) +
                  me.apply('undotted,capitalize,pluralize', rightType);
  
          if (relation) {
              ret = me.apply('capitalize', relation + ret);
          }
  
          return ret;
      },
  
      /**
       * Returns the name for a one-to-many association given the left and right type and
       * the associating `role`.
       *
       * In many cases the `role` matches the target type. For example, an OrderItem might
       * have an "orderId" field which would have a `role` of "order". If this is a reference
       * to an Order entity then the association name will be "OrderOrderItems".
       *
       * When the `role` does not match, it is included in the association name. For example,
       * consider a Ticket entity with a "creatorId" field that references a User entity.
       * The `role` of that field will (by default) be "creator". The returned association
       * name will be "UserCreatorTickets".
       */
      manyToOne: function (leftType, leftRole, rightType, rightRole) {
          // ex: OrderItem -> Order  ==> OrderOrderItems
          //  Ticket (creator) -> User ==> UserCreatorTickets
          return this.apply('capitalize,singularize', rightType) +
              this.apply('capitalize', leftRole);
      },
  
      matrixRole: function (relation, entityType) {
          var ret = this.apply(relation ? 'multiRole,capitalize' : 'multiRole', entityType);
          return relation ? relation + ret : ret;
      },
  
      oneToOne: function (leftType, leftRole, rightType, rightRole) {
          return this.apply('undotted,capitalize,singularize', rightType) +
              this.apply('capitalize', leftRole);
      },
  
      setterName: function (role) {
          return 'set' + this.apply('capitalize', role.role);
      },
  
      //-------------------------------------------------------------------------
      // Private
  
      endsWithIdRe: /(?:(_id)|[^A-Z](Id))$/,
  
      cache: {},
  
      apply: function (operation, name) {
          var me = this,
              cache = me.cache,
              entry = cache[name] || (cache[name] = {}),
              ret = entry[operation],
              i, length, operations;
  
          if (!ret) {
              if (operation.indexOf(',') < 0) {
                  ret = me[operation](name);
              } else {
                  length = (operations = operation.split(',')).length;
                  ret = name;
                  for (i = 0; i < length; ++i) {
                      ret = me.apply(operations[i], ret);
                  }
              }
  
              entry[operation] = ret;
          }
  
          return ret;
      }
  });
  