Calls ajax request for remote validations

@param  {object} ajaxOptions Ajax options
@param  {object} cache Cache object
@param  {object} el processing element
@param  {string} fieldName Field name for make specific caching
@param  {object} e Event object
506145
function( ajaxOptions, cache, el, fieldName, e ) {

      var self = this;

      $( this.tmp.parent ).addClass('validetta-pending');

      // cache xhr
      this.xhr[ fieldName ] = $.ajax( ajaxOptions )
        .done( function( result ) {
          if( typeof result !== 'object' ) result = JSON.parse( result );
          cache.state = 'resolved';
          cache.result = result;
          if ( cache.event === 'submit' ) {
            self.handler = false;
            $( self.form ).trigger('submit');
          }
          else if( result.valid === false ) {
            self.addErrorClass( self.tmp.parent );
            self.window.open.call( self, el, result.message );
          } else {
            self.addValidClass( self.tmp.parent );
          }
        } )
        .fail( function( jqXHR, textStatus ) {
          if ( textStatus !== 'abort' ) { // Dont throw error if request is aborted
            var _msg = 'Ajax request failed for field ('+ fieldName +') : '+ jqXHR.status +' '+ jqXHR.statusText;
            cache.state = 'rejected';
            cache.result = { valid : false, message : _msg };
            throw new Error( _msg );
          }
        } )
        .always( function( result ) { $( self.tmp.parent ).removeClass('validetta-pending'); } );

      this.handler = 'pending';
    }
701047
function processRequestOptions(req, attrNode, context) {
    if ('id' in req) {
        if (context.attrPath.length > 0) {
            throw new RequestError('ID option only allowed at root (in "' + context.attrPath.join('.') + '")');
        }

        // TODO: Cast req.id to defined type and handle composite primaryKey

        attrNode.many = false;

        context.resourceTree.filter = [
            [
                {
                    attribute: context.resourceTree.primaryKey,
                    operator: 'equal',
                    value: req.id
                }
            ]
        ];
    }

    if ('filter' in req) {
        let requestFilter = req.filter.map(andFilter =>
            andFilter.map(filter => {
                let filteredAttrNode = attrNode;
                const subResourceAttrNodes = [];

                let subContext = Object.assign({}, context);
                filter.attribute.forEach(subAttr => {
                    filteredAttrNode = getAttributeWithAuthCheck([subAttr], filteredAttrNode, subContext);
                    if (filteredAttrNode.dataSources) subResourceAttrNodes.push(filteredAttrNode);
                    subContext.attrPath = subContext.attrPath.concat([subAttr]);
                });

                if (!filteredAttrNode.filter) {
                    throw new RequestError(
                        [
                            'Can not filter by attribute "' + filter.attribute.join('.') + '" ',
                            context.attrPath.length > 0 ? '(in "' + context.attrPath.join('.') + '") ' : ''
                        ].join('')
                    );
                }
                if (filteredAttrNode.filter.indexOf(filter.operator) === -1) {
                    throw new RequestError(
                        [
                            'Can not filter by attribute "' + filter.attribute.join('.') + '" ',
                            context.attrPath.length > 0 ? '(in "' + context.attrPath.join('.') + '") ' : '',
                            'with "' + filter.operator + '" ',
                            '(allowed operators: ' + filteredAttrNode.filter.join(', ') + ')'
                        ].join('')
                    );
                }

                const resolvedFilter = {
                    attribute: filteredAttrNode.map.default,
                    operator: filter.operator,
                    value: filter.value
                };
                let parentFilter = resolvedFilter;

                // resolve filter by sub-resources:
                if (subResourceAttrNodes.length > 0) {
                    let subFilter;
                    if (attrNode.subFilters) {
                        attrNode.subFilters.forEach(_subFilter => {
                            if (filter.attribute.join('.') === _subFilter.attribute.join('.')) {
                                subFilter = _subFilter;
                            }
                        });
                    }

                    if (!subFilter) {
                        throw new RequestError(
                            `Can not filter by sub-resource attribute "${filter.attribute.join('.')}"` +
                                (context.attrPath.length > 0 ? ` (in "${context.attrPath.join('.')}")` : '')
                        );
                    }

                    if (subFilter.filter.indexOf(filter.operator) === -1) {
                        throw new RequestError(
                            `Can not filter by sub-resource attribute "${filter.attribute.join('.')}"` +
                                (context.attrPath.length > 0 ? ` (in "${context.attrPath.join('.')}")` : '') +
                                ` with "${filter.operator}" (allowed operators: ${subFilter.filter.join(', ')})`
                        );
                    }

                    if (subFilter.rewriteTo) {
                        const filterAttrNode = getAttribute(subFilter.rewriteTo, attrNode, context);
                        parentFilter.attribute = filterAttrNode.map.default;
                    } else {
                        let filterContext = context;
                        subResourceAttrNodes.forEach(subResourceAttrNode => {
                            let subFilterTree = {
                                dataSources: subResourceAttrNode.dataSources,
                                attributes: [],
                                filter: null,
                                parentKey: subResourceAttrNode.resolvedParentKey,
                                childKey: subResourceAttrNode.resolvedChildKey
                            };

                            // select childKey from DataSource:
                            subResourceAttrNode.childKey.forEach(childKeyAttrPath => {
                                const childKeyAttrNode = getAttribute(childKeyAttrPath, subResourceAttrNode, context);
                                subFilterTree.attributes.push({
                                    dataSourceMap: childKeyAttrNode.map.default,
                                    attrNode: childKeyAttrNode,
                                    fromDataSource: '#current-primary'
                                });
                            });

                            // modify parent-filter to sub-filter:
                            parentFilter.attribute = subResourceAttrNode.resolvedParentKey;
                            parentFilter.operator = 'equal';
                            delete parentFilter.value;
                            const previousParentFilter = parentFilter;

                            // ... then filter sub-resource by given attribute:
                            parentFilter = {
                                attribute: filteredAttrNode.map.default,
                                operator: filter.operator,
                                value: filter.value
                            };
                            subFilterTree.filter = [[parentFilter]];

                            // handle m:n relations with join-table:
                            if (subResourceAttrNode.joinVia) {
                                const joinViaDataSource = subResourceAttrNode.dataSources[subResourceAttrNode.joinVia];

                                const resolvedJoinParentKey = {};
                                resolvedJoinParentKey[subResourceAttrNode.joinVia] =
                                    joinViaDataSource.resolvedJoinParentKey;
                                const resolvedJoinChildKey = {};
                                resolvedJoinChildKey[subResourceAttrNode.joinVia] =
                                    joinViaDataSource.resolvedJoinChildKey;

                                const joinSubFilterTree = {
                                    dataSources: subResourceAttrNode.dataSources,
                                    primaryName: subResourceAttrNode.joinVia,
                                    attributes: [],
                                    filter: [
                                        [
                                            {
                                                attribute: resolvedJoinChildKey,
                                                operator: 'equal',
                                                valueFromSubFilter: 0
                                            }
                                        ]
                                    ],
                                    parentKey: subFilterTree.parentKey,
                                    childKey: resolvedJoinParentKey
                                };

                                subFilterTree.parentKey = resolvedJoinChildKey;

                                // select joinParentKey from DataSource:
                                joinViaDataSource.joinParentKey.forEach(joinParentKeyAttrPath => {
                                    const joinParentKeyAttrNode = getAttribute(
                                        joinParentKeyAttrPath,
                                        subResourceAttrNode,
                                        context
                                    );
                                    joinSubFilterTree.attributes.push({
                                        dataSourceMap: joinParentKeyAttrNode.map.default,
                                        attrNode: joinParentKeyAttrNode,
                                        fromDataSource: subResourceAttrNode.joinVia
                                    });
                                });

                                // link join-sub-filter in between:
                                joinSubFilterTree.subFilters = [subFilterTree];
                                subFilterTree = joinSubFilterTree;
                            }

                            // link sub-filter to parent:
                            if (!filterContext.resourceTree.subFilters) {
                                filterContext.resourceTree.subFilters = [];
                            }

                            previousParentFilter.valueFromSubFilter = filterContext.resourceTree.subFilters.length;
                            filterContext.resourceTree.subFilters.push(subFilterTree);

                            // switch filter-context:
                            filterContext = Object.assign({}, filterContext);
                            filterContext.resourceTree = subFilterTree;
                        });
                    }
                }

                // TODO: Check type of values

                return resolvedFilter;
            })
        );

        if (context.resourceTree.filter) {
            // generate cross product of OR filters:
            const combinedFilter = [];
            requestFilter.forEach(andFilter => {
                context.resourceTree.filter.forEach(andFilter2 => {
                    combinedFilter.push(cloneDeep(andFilter).concat(cloneDeep(andFilter2)));
                });
            });
            requestFilter = combinedFilter;
        }

        context.resourceTree.filter = requestFilter;
    }

    if ('search' in req) {
        context.resourceTree.search = req.search;
    }

    const order = req.order || attrNode.defaultOrder;
    if (order) {
        context.resourceTree.order = order.map(orderPart => {
            let orderedAttrNode = attrNode;
            let subContext = Object.assign({}, context);
            orderPart.attribute.forEach(subAttr => {
                orderedAttrNode = getAttributeWithAuthCheck([subAttr], orderedAttrNode, subContext);
                if (orderedAttrNode.dataSources) {
                    throw new RequestError(
                        `Can not order by sub-resource attribute "${orderPart.attribute.join('.')}"` +
                            (context.attrPath.length > 0 ? ` (in "${context.attrPath.join('.')}")` : '')
                    );
                }
                subContext.attrPath = subContext.attrPath.concat([subAttr]);
            });

            if (!orderedAttrNode.order) {
                throw new RequestError(
                    [
                        'Attribute "' + orderPart.attribute.join('.') + '" ',
                        context.attrPath.length > 0 ? ' (in "' + context.attrPath.join('.') + '") ' : '',
                        'can not be ordered'
                    ].join('')
                );
            }
            if (orderedAttrNode.order.indexOf(orderPart.direction) === -1) {
                throw new RequestError(
                    [
                        'Attribute "' + orderPart.attribute.join('.') + '" ',
                        context.attrPath.length > 0 ? ' (in "' + context.attrPath.join('.') + '")' : '',
                        'can not be ordered "' +
                            orderPart.direction +
                            '" (allowed: ' +
                            orderedAttrNode.order.join(', ') +
                            ')'
                    ].join('')
                );
            }

            orderPart = Object.assign({}, orderPart);
            orderPart.attribute = orderedAttrNode.map.default;

            return orderPart;
        });
    }

    let limit = null;
    if ('limit' in req) {
        if (!attrNode.many) {
            throw new RequestError(
                'Invalid limit on a single resource' +
                    (context.attrPath.length > 0 ? ` (in "${context.attrPath.join('.')}")` : '')
            );
        }
        limit = req.limit;
    } else if (attrNode.many) {
        limit = attrNode.defaultLimit || attrNode.maxLimit || (context.isMainResource ? 10 : null);
    }
    if (attrNode.maxLimit) {
        if (limit === null || limit > attrNode.maxLimit) {
            throw new RequestError(
                `Invalid limit ${limit !== null ? limit : 'unlimited'}, maxLimit is ${attrNode.maxLimit}` +
                    (context.attrPath.length > 0 ? ` (in "${context.attrPath.join('.')}")` : '')
            );
        }
    }
    if (limit !== null) {
        context.resourceTree.limit = limit;
        if (!context.isMainResource && context.isMany) {
            context.resourceTree.limitPerGroup = true;
        }
    }

    if ('page' in req) {
        if (!('limit' in req)) {
            throw new RequestError(
                'Always specify a fixed limit when requesting page' +
                    (context.attrPath.length > 0 ? ` (in "${context.attrPath.join('.')}")` : '')
            );
        }

        context.resourceTree.page = req.page;
    }
}
961893
function (element, options) {
    this.element  = element;
    this.options  = options;
    this.$overlay = $(options.overlayTemplate);

    if (options.source === '') {
      throw new Error('Source url was not defined. Please specify a url in your BoxRefresh source option.');
    }

    this._setUpListeners();
    this.load();
  }
657160
function processAfterStateChange(el) {
    const optionValue = el.value;
    const optionIndex = Array.prototype.slice.call(el.parentNode.children).indexOf(el);
    this.setSelectedOption(optionValue);
    emitAndFire(this, 'select-change', {
        index: optionIndex,
        selected: [optionValue],
        el
    });
}
1094019
function (el, data_attr_name) {
        data_attr_name = data_attr_name || 'options';
        var opts = {}, ii, p, opts_arr,
            data_options = function (el) {
              var namespace = Foundation.global.namespace;

              if (namespace.length > 0) {
                return el.data(namespace + '-' + data_attr_name);
              }

              return el.data(data_attr_name);
            };

        var cached_options = data_options(el);

        if (typeof cached_options === 'object') {
          return cached_options;
        }

        opts_arr = (cached_options || ':').split(';');
        ii = opts_arr.length;

        function isNumber (o) {
          return !isNaN (o - 0) && o !== null && o !== '' && o !== false && o !== true;
        }

        function trim (str) {
          if (typeof str === 'string') {
            return $.trim(str);
          }
          return str;
        }

        while (ii--) {
          p = opts_arr[ii].split(':');
          p = [p[0], p.slice(1).join(':')];

          if (/true/i.test(p[1])) {
            p[1] = true;
          }
          if (/false/i.test(p[1])) {
            p[1] = false;
          }
          if (isNumber(p[1])) {
            if (p[1].indexOf('.') === -1) {
              p[1] = parseInt(p[1], 10);
            } else {
              p[1] = parseFloat(p[1]);
            }
          }

          if (p.length === 2 && p[0].length > 0) {
            opts[trim(p[0])] = trim(p[1]);
          }
        }

        return opts;
      }
117789
function( elem, context, xml ) {
			var data, cache, outerCache,
				dirkey = dirruns + " " + doneName;

			// We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
			if ( xml ) {
				while ( (elem = elem[ dir ]) ) {
					if ( elem.nodeType === 1 || checkNonElements ) {
						if ( matcher( elem, context, xml ) ) {
							return true;
						}
					}
				}
			} else {
				while ( (elem = elem[ dir ]) ) {
					if ( elem.nodeType === 1 || checkNonElements ) {
						outerCache = elem[ expando ] || (elem[ expando ] = {});
						if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) {
							if ( (data = cache[1]) === true || data === cachedruns ) {
								return data === true;
							}
						} else {
							cache = outerCache[ dir ] = [ dirkey ];
							cache[1] = matcher( elem, context, xml ) || cachedruns;
							if ( cache[1] === true ) {
								return true;
							}
						}
					}
				}
			}
		}
854112
function(options, params) {
        var ajax = {};

        // Determine base options
        if ($.type(this.options.ajax) === 'object') {
            ajax = this.options.ajax;
        }

        // Set default options
        if ($.type(options) === 'string') {
            ajax.url = options;

        } else {
            $.extend(ajax, options);
        }

        // Prepare XHR object
        ajax.context = this;
        ajax.beforeSend = function(xhr) {
            xhr.url = ajax.url;
            xhr.cache = ((!ajax.type || ajax.type.toUpperCase() === 'GET') && this.options.cache);
            xhr.settings = ajax;
            xhr.params = params || {};

            this.onRequestBefore.call(this, xhr);
        };

        return $.ajax(ajax)
            .done(this.onRequestDone)
            .fail(this.onRequestFail);
    }
1055821
public void remoteInvalidate(String sessionId, boolean backendUpdate) {
        // backendUpdate not used here...there is no backend for in-memory
        if (com.ibm.ejs.ras.TraceComponent.isAnyTracingEnabled() && LoggingUtil.SESSION_LOGGER_CORE.isLoggable(Level.FINE)) {
            StringBuffer sb = new StringBuffer("{").append(sessionId).append(",").append(backendUpdate).append("} ").append(appNameForLogging);
            LoggingUtil.SESSION_LOGGER_CORE.logp(Level.FINE, methodClassName, methodNames[REMOVE_INVALIDATE], sb.toString());
        }
        ISession iSess = (ISession) _sessions.get(sessionId);
        if (iSess != null) {
            iSess.setMaxInactiveInterval(0); // let invalidator whack it
        }
    }
973611
function(el) {
            var self = this;

            self.callActions('beforeCacheDom', arguments);

            self.dom.el = el;

            self.callActions('afterCacheDom', arguments);
        }
1146373
private void executeValidate(FacesContext context) {
        try {
            validate(context);
        } catch (RuntimeException e) {
            context.renderResponse();
            throw e;
        }

        if (!isValid()) {
            context.validationFailed();
            context.renderResponse();
        }
    }
1180939
function(elem, target) {
				this.selectDateOrig(elem, target);
				var inst = $.calendarsPicker._getInst(elem);
				if (!inst.inline && $.fn.validate) {
					var validation = $(elem).parents('form').validate();
					if (validation) {
						validation.element('#' + elem.id);
					}
				}
			}
402896
function run_search(query) {
      	 var cache_key = query + computeURL();
          //var cached_results = cache.get(cache_key);
          /*if (cached_results) {
              if ($.isFunction($(input).data("settings").onCachedResult)) {
                cached_results = $(input).data("settings").onCachedResult.call(hidden_input, cached_results);
              }
              populate_dropdown(query, cached_results);
          } else {*/
              // Are we doing an ajax search or local data search?
              if($(input).data("settings").url) {
                  var url = computeURL();
                  // Extract existing get params
                  var ajax_params = {};
                  ajax_params.data = {};
                  if(url.indexOf("?") > -1) {
                      var parts = url.split("?");
                      ajax_params.url = parts[0];

                      var param_array = parts[1].split("&");
                      $.each(param_array, function (index, value) {
                          var kv = value.split("=");
                          ajax_params.data[kv[0]] = kv[1];
                      });
                  } else {
                      ajax_params.url = url;
                  }

                  // Prepare the request
                  ajax_params.data[$(input).data("settings").queryParam] = query;
                  ajax_params.type = $(input).data("settings").method;
                  ajax_params.dataType = $(input).data("settings").contentType;
                  if ($(input).data("settings").crossDomain) {
                      ajax_params.dataType = "jsonp";
                  }

                  // exclude current tokens?
                  // send exclude list to the server, so it can also exclude existing tokens
                  if ($(input).data("settings").excludeCurrent) {
                      var currentTokens = $(input).data("tokenInputObject").getTokens();
                      var tokenList = $.map(currentTokens, function (el) {
                          if(typeof $(input).data("settings").tokenValue == 'function')
                              return $(input).data("settings").tokenValue.call(this, el);

                          return el[$(input).data("settings").tokenValue];
                      });

                      ajax_params.data[$(input).data("settings").excludeCurrentParameter] = tokenList.join($(input).data("settings").tokenDelimiter);
                  }

                  // Attach the success callback
                  ajax_params.success = function(results) {
                    cache.add(cache_key, $(input).data("settings").jsonContainer ? results[$(input).data("settings").jsonContainer] : results);
                    if($.isFunction($(input).data("settings").onResult)) {
                        results = $(input).data("settings").onResult.call(hidden_input, results);
                    }

                    // only populate the dropdown if the results are associated with the active search query
                    if(input_box.val() === query) {
                        populate_dropdown(query, $(input).data("settings").jsonContainer ? results[$(input).data("settings").jsonContainer] : results);
                    }
                  };

                  // Provide a beforeSend callback
                  if (settings.onSend) {
                    settings.onSend(ajax_params);
                  }

                  // Make the request
                  $.ajax(ajax_params);
              } else if($(input).data("settings").local_data) {
                  // Do the search through local data
                 var results = $.grep($(input).data("settings").local_data, function (row) {
                      return row[$(input).data("settings").propertyToSearch].toLowerCase().indexOf(query.toLowerCase()) > -1;
                  });

                  cache.add(cache_key, results);
                  if($.isFunction($(input).data("settings").onResult)) {
                      results = $(input).data("settings").onResult.call(hidden_input, results);
                  }
                  populate_dropdown(query, results);
              }
          //}
      }
1151356
function (success, error) {
            //run source if it function
            var source;
            if ($.isFunction(this.options.source)) {
                source = this.options.source.call(this.options.scope);
                this.sourceData = null;
                //note: if function returns the same source as URL - sourceData will be taken from cahce and no extra request performed
            } else {
                source = this.options.source;
            }            
            
            //if allready loaded just call success
            if(this.options.sourceCache && $.isArray(this.sourceData)) {
                success.call(this);
                return; 
            }

            //try parse json in single quotes (for double quotes jquery does automatically)
            try {
                source = $.fn.editableutils.tryParseJson(source, false);
            } catch (e) {
                error.call(this);
                return;
            }

            //loading from url
            if (typeof source === 'string') {
                //try to get sourceData from cache
                if(this.options.sourceCache) {
                    var cacheID = source,
                    cache;

                    if (!$(document).data(cacheID)) {
                        $(document).data(cacheID, {});
                    }
                    cache = $(document).data(cacheID);

                    //check for cached data
                    if (cache.loading === false && cache.sourceData) { //take source from cache
                        this.sourceData = cache.sourceData;
                        this.doPrepend();
                        success.call(this);
                        return;
                    } else if (cache.loading === true) { //cache is loading, put callback in stack to be called later
                        cache.callbacks.push($.proxy(function () {
                            this.sourceData = cache.sourceData;
                            this.doPrepend();
                            success.call(this);
                        }, this));

                        //also collecting error callbacks
                        cache.err_callbacks.push($.proxy(error, this));
                        return;
                    } else { //no cache yet, activate it
                        cache.loading = true;
                        cache.callbacks = [];
                        cache.err_callbacks = [];
                    }
                }
                
                //loading sourceData from server
                $.ajax({
                    url: source,
                    type: 'get',
                    cache: false,
                    dataType: 'json',
                    success: $.proxy(function (data) {
                        if(cache) {
                            cache.loading = false;
                        }
                        this.sourceData = this.makeArray(data);
                        if($.isArray(this.sourceData)) {
                            if(cache) {
                                //store result in cache
                                cache.sourceData = this.sourceData;
                                //run success callbacks for other fields waiting for this source
                                $.each(cache.callbacks, function () { this.call(); }); 
                            }
                            this.doPrepend();
                            success.call(this);
                        } else {
                            error.call(this);
                            if(cache) {
                                //run error callbacks for other fields waiting for this source
                                $.each(cache.err_callbacks, function () { this.call(); }); 
                            }
                        }
                    }, this),
                    error: $.proxy(function () {
                        error.call(this);
                        if(cache) {
                             cache.loading = false;
                             //run error callbacks for other fields
                             $.each(cache.err_callbacks, function () { this.call(); }); 
                        }
                    }, this)
                });
            } else { //options as json/array
                this.sourceData = this.makeArray(source);
                    
                if($.isArray(this.sourceData)) {
                    this.doPrepend();
                    success.call(this);   
                } else {
                    error.call(this);
                }
            }
        }
768668
function(options){
        this.type = options.type || EJS.type;
		this.cache = options.cache != null ? options.cache : EJS.cache;
		this.text = options.text || null;
		this.name =  options.name || null;
		this.ext = options.ext || EJS.ext;
		this.extMatch = new RegExp(this.ext.replace(/\./, '\.'));
	}
110907
private void handleOption(Option option) {
        if (LIST_CACHE_OPTION.equals(option.getOpt())) {
            handleListCache();
        } else if (CACHE_RESOURCE_OPTION.equals(option.getOpt())) {
            handleLoadResource(option);
        } else if (CACHE_SYSCONFIG_FILE_OPTION.equals(option.getOpt())) {
            handleLoadDefaultIndex();
        } else if (CACHE_INDEX_FILE.equals(option.getOpt())) {
            handleLoadIndex(option.getValue());
        } else if (PURGE_CACHE_OPTION.equals(option.getOpt())) {
            handlePurgeCache();
        } else if (GENERATE_CHECKSUM_OPTION.equals(option.getOpt())) {
            handleGenerateHash(option);
        }
    }
1366629
function() {
        var term = this.term,
            options = this.options,
            sourceType = $.type(options.source);

        // Check the cache first
        if (this.cache[term.toLowerCase()]) {
            this.source(this.cache[term.toLowerCase()]);

        // Use the response of an AJAX request
        } else if (sourceType === 'string') {
            var url = options.source,
                cache = this.cache[url];

            if (cache) {
                this.source(cache);
            } else {
                var query = options.query;
                query.term = term;

                $.getJSON(url, query, this.source.bind(this));
            }

        // Use a literal array list
        } else if (sourceType === 'array') {
            this.source(options.source);

        // Use the return of a function
        } else if (sourceType === 'function') {
            var response = options.source.call(this);

            if (response) {
                this.source(response);
            }
        } else {
            throw new Error('Invalid TypeAhead source type');
        }
    }
1021429
function validateOptions(options) {
  if (!options) {
    return;
  }

  utils.each(['varControls', 'tagControls', 'cmtControls'], function (key) {
    if (!options.hasOwnProperty(key)) {
      return;
    }
    if (!utils.isArray(options[key]) || options[key].length !== 2) {
      throw new Error('Option "' + key + '" must be an array containing 2 different control strings.');
    }
    if (options[key][0] === options[key][1]) {
      throw new Error('Option "' + key + '" open and close controls must not be the same.');
    }
    utils.each(options[key], function (a, i) {
      if (a.length < 2) {
        throw new Error('Option "' + key + '" ' + ((i) ? 'open ' : 'close ') + 'control must be at least 2 characters. Saw "' + a + '" instead.');
      }
    });
  });

  if (options.hasOwnProperty('cache')) {
    if (options.cache && options.cache !== 'memory') {
      if (!options.cache.get || !options.cache.set) {
        throw new Error('Invalid cache option ' + JSON.stringify(options.cache) + ' found. Expected "memory" or { get: function (key) { ... }, set: function (key, value) { ... } }.');
      }
    }
  }
  if (options.hasOwnProperty('loader')) {
    if (options.loader) {
      if (!options.loader.load || !options.loader.resolve) {
        throw new Error('Invalid loader option ' + JSON.stringify(options.loader) + ' found. Expected { load: function (pathname, cb) { ... }, resolve: function (to, from) { ... } }.');
      }
    }
  }

}