<commit_before>var AWS = require('./core');
var SequentialExecutor = require('./sequential_executor');

/**
 * The namespace used to register global event listeners for request building
 * and sending.
 */
AWS.EventListeners = {
  /**
   * @!attribute VALIDATE_CREDENTIALS
   *   A request listener that validates whether the request is being
   *   sent with credentials.
   *   Handles the {AWS.Request~validate 'validate' Request event}
   *   @example Sending a request without validating credentials
   *     var listener = AWS.EventListeners.Core.VALIDATE_CREDENTIALS;
   *     request.removeListener('validate', listener);
   *   @readonly
   *   @return [Function]
   * @!attribute VALIDATE_REGION
   *   A request listener that validates whether the region is set
   *   for a request.
   *   Handles the {AWS.Request~validate 'validate' Request event}
   *   @example Sending a request without validating region configuration
   *     var listener = AWS.EventListeners.Core.VALIDATE_REGION;
   *     request.removeListener('validate', listener);
   *   @readonly
   *   @return [Function]
   * @!attribute VALIDATE_PARAMETERS
   *   A request listener that validates input parameters in a request.
   *   Handles the {AWS.Request~validate 'validate' Request event}
   *   @example Sending a request without validating parameters
   *     var listener = AWS.EventListeners.Core.VALIDATE_PARAMETERS;
   *     request.removeListener('validate', listener);
   *   @example Disable parameter validation globally
   *     AWS.EventListeners.Core.removeListener('validate',
   *       AWS.EventListeners.Core.VALIDATE_REGION);
   *   @readonly
   *   @return [Function]
   * @!attribute SEND
   *   A request listener that initiates the HTTP connection for a
   *   request being sent. Handles the {AWS.Request~send 'send' Request event}
   *   @example Replacing the HTTP handler
   *     var listener = AWS.EventListeners.Core.SEND;
   *     request.removeListener('send', listener);
   *     request.on('send', function(response) {
   *       customHandler.send(response);
   *     });
   *   @return [Function]
   *   @readonly
   * @!attribute HTTP_DATA
   *   A request listener that reads data from the HTTP connection in order
   *   to build the response data.
   *   Handles the {AWS.Request~httpData 'httpData' Request event}.
   *   Remove this handler if you are overriding the 'httpData' event and
   *   do not want extra data processing and buffering overhead.
   *   @example Disabling default data processing
   *     var listener = AWS.EventListeners.Core.HTTP_DATA;
   *     request.removeListener('httpData', listener);
   *   @return [Function]
   *   @readonly
   */
  Core: {} /* doc hack */
};

AWS.EventListeners = {
  Core: new SequentialExecutor().addNamedListeners(function(add, addAsync) {
    addAsync('VALIDATE_CREDENTIALS', 'validate',
        function VALIDATE_CREDENTIALS(req, done) {
      req.service.config.getCredentials(function(err) {
        if (err) {
          req.response.err = AWS.util.error(err,
            {code: 'SigningError', message: 'Missing credentials in config'});
        }
        done();
      });
    });

    add('VALIDATE_REGION', 'validate', function VALIDATE_REGION(req) {
      if (!req.service.config.region && !req.service.hasGlobalEndpoint()) {
        req.response.error = AWS.util.error(new Error(),
          {code: 'SigningError', message: 'Missing region in config'});
      }
    });

    add('VALIDATE_PARAMETERS', 'validate', function VALIDATE_PARAMETERS(req) {
      var rules = req.service.api.operations[req.operation].input;
      new AWS.ParamValidator().validate(rules, req.params);
    });

    add('SET_CONTENT_LENGTH', 'afterBuild', function SET_CONTENT_LENGTH(req) {
      if (req.httpRequest.headers['Content-Length'] === undefined) {
        var length = AWS.util.string.byteLength(req.httpRequest.body);
        req.httpRequest.headers['Content-Length'] = length;
      }
    });

    add('SET_HTTP_HOST', 'afterBuild', function SET_HTTP_HOST(req) {
      req.httpRequest.headers['Host'] = req.httpRequest.endpoint.host;
    });

    addAsync('SIGN', 'sign', function SIGN(req, done) {
      if (!req.service.api.signatureVersion) return done(); // none

      req.service.config.getCredentials(function (err, credentials) {
        if (err) {
          req.response.error = err;
          return done();
        }

        try {
          var date = AWS.util.date.getDate();
          var SignerClass = req.service.getSignerClass(req);
          var signer = new SignerClass(req.httpRequest,
            req.service.api.signingName || req.service.api.endpointPrefix);

          // clear old authorization headers
          delete req.httpRequest.headers['Authorization'];
          delete req.httpRequest.headers['Date'];
          delete req.httpRequest.headers['X-Amz-Date'];

          // add new authorization
          signer.addAuthorization(credentials, date);
          req.signedAt = date;
        } catch (e) {
          req.response.error = e;
        }
        done();
      });
    });

    add('VALIDATE_RESPONSE', 'validateResponse', function VALIDATE_RESPONSE(resp) {
      if (this.service.successfulResponse(resp, this)) {
        resp.data = {};
        resp.error = null;
      } else {
        resp.data = null;
        resp.error = AWS.util.error(new Error(),
          {code: 'UnknownError', message: 'An unknown error occurred.'});
      }
    });

    addAsync('SEND', 'send', function SEND(resp, done) {
      resp.httpResponse._abortCallback = done;
      resp.error = null;
      resp.data = null;

      function callback(httpResp) {
        resp.httpResponse.stream = httpResp;

        httpResp.on('headers', function onHeaders(statusCode, headers) {
          resp.request.emit('httpHeaders', [statusCode, headers, resp]);

          if (!resp.request.httpRequest._streaming) {
            if (AWS.HttpClient.streamsApiVersion === 2) { // streams2 API check
              httpResp.on('readable', function onReadable() {
                var data = httpResp.read();
                if (data !== null) {
                  resp.request.emit('httpData', [data, resp]);
                }
              });
            } else { // legacy streams API
              httpResp.on('data', function onData(data) {
                resp.request.emit('httpData', [data, resp]);
              });
            }
          }
        });

        httpResp.on('end', function onEnd() {
          resp.request.emit('httpDone');
          done();
        });
      }

      function progress(httpResp) {
        httpResp.on('sendProgress', function onSendProgress(progress) {
          resp.request.emit('httpUploadProgress', [progress, resp]);
        });

        httpResp.on('receiveProgress', function onReceiveProgress(progress) {
          resp.request.emit('httpDownloadProgress', [progress, resp]);
        });
      }

      function error(err) {
        resp.error = AWS.util.error(err, {
          code: 'NetworkingError',
          region: resp.request.httpRequest.region,
          hostname: resp.request.httpRequest.endpoint.hostname,
          retryable: true
        });
        resp.request.emit('httpError', [resp.error, resp], function() {
          done();
        });
      }

      function executeSend() {
        var http = AWS.HttpClient.getInstance();
        var httpOptions = resp.request.service.config.httpOptions || {};
        var stream = http.handleRequest(resp.request.httpRequest, httpOptions,
                                        callback, error);
        progress(stream);
      }

      var timeDiff = (AWS.util.date.getDate() - this.signedAt) / 1000;
      if (timeDiff >= 60 * 10) { // if we signed 10min ago, re-sign
        this.emit('sign', [this], function(err) {
          if (err) done(err);
          else executeSend();
        });
      } else {
        executeSend();
      }
    });

    add('HTTP_HEADERS', 'httpHeaders',
        function HTTP_HEADERS(statusCode, headers, resp) {
      resp.httpResponse.statusCode = statusCode;
      resp.httpResponse.headers = headers;
      resp.httpResponse.body = new AWS.util.Buffer('');
      resp.httpResponse.buffers = [];
      resp.httpResponse.numBytes = 0;
    });

    add('HTTP_DATA', 'httpData', function HTTP_DATA(chunk, resp) {
      if (chunk) {
        if (AWS.util.isNode()) {
          resp.httpResponse.numBytes += chunk.length;

          var total = resp.httpResponse.headers['content-length'];
          var progress = { loaded: resp.httpResponse.numBytes, total: total };
          resp.request.emit('httpDownloadProgress', [progress, resp]);
        }

        resp.httpResponse.buffers.push(new AWS.util.Buffer(chunk));
      }
    });

    add('HTTP_DONE', 'httpDone', function HTTP_DONE(resp) {
      // convert buffers array into single buffer
      if (resp.httpResponse.buffers && resp.httpResponse.buffers.length > 0) {
        var body = AWS.util.buffer.concat(resp.httpResponse.buffers);
        resp.httpResponse.body = body;
      }
      delete resp.httpResponse.numBytes;
      delete resp.httpResponse.buffers;
    });

    add('FINALIZE_ERROR', 'retry', function FINALIZE_ERROR(resp) {
      if (resp.httpResponse.statusCode) {
        resp.error.statusCode = resp.httpResponse.statusCode;
        if (resp.error.retryable === undefined) {
          resp.error.retryable = this.service.retryableError(resp.error, this);
        }
      }
    });

    add('INVALIDATE_CREDENTIALS', 'retry', function INVALIDATE_CREDENTIALS(resp) {
      switch (resp.error.code) {
        case 'RequestExpired': // EC2 only
        case 'ExpiredTokenException':
        case 'ExpiredToken':
          resp.error.retryable = true;
          resp.request.service.config.credentials.expired = true;
      }
    });

    add('REDIRECT', 'retry', function REDIRECT(resp) {
      if (resp.error && resp.error.statusCode >= 300 &&
          resp.error.statusCode < 400 && resp.httpResponse.headers['location']) {
        this.httpRequest.endpoint =
          new AWS.Endpoint(resp.httpResponse.headers['location']);
        resp.error.redirect = true;
        resp.error.retryable = true;
      }
    });

    add('RETRY_CHECK', 'retry', function RETRY_CHECK(resp) {
      if (resp.error) {
        if (resp.error.redirect && resp.redirectCount < resp.maxRedirects) {
          resp.error.retryDelay = 0;
        } else if (resp.error.retryable && resp.retryCount < resp.maxRetries) {
          var delays = this.service.retryDelays();
          resp.error.retryDelay = delays[resp.retryCount] || 0;
        }
      }
    });

    addAsync('RESET_RETRY_STATE', 'afterRetry', function RESET_RETRY_STATE(resp, done) {
      var delay, willRetry = false;

      if (resp.error) {
        delay = resp.error.retryDelay || 0;
        if (resp.error.retryable && resp.retryCount < resp.maxRetries) {
          resp.retryCount++;
          willRetry = true;
        } else if (resp.error.redirect && resp.redirectCount < resp.maxRedirects) {
          resp.redirectCount++;
          willRetry = true;
        }
      }

      if (willRetry) {
        resp.error = null;
        setTimeout(done, delay);
      } else {
        done();
      }
    });
  }),

  CorePost: new SequentialExecutor().addNamedListeners(function(add) {
    add('EXTRACT_REQUEST_ID', 'extractData', function EXTRACT_REQUEST_ID(resp) {
      resp.requestId = resp.httpResponse.headers['x-amz-request-id'] ||
                       resp.httpResponse.headers['x-amzn-requestid'];

      if (!resp.requestId && resp.data && resp.data.ResponseMetadata) {
        resp.requestId = resp.data.ResponseMetadata.RequestId;
      }
    });
  }),

  Logger: new SequentialExecutor().addNamedListeners(function(add) {
    add('LOG_REQUEST', 'complete', function LOG_REQUEST(resp) {
      var req = resp.request;
      var logger = req.service.config.logger;
      if (!logger) return;

      function buildMessage() {
        var time = AWS.util.date.getDate().getTime();
        var delta = (time - req.startTime.getTime()) / 1000;
        var ansi = logger.isTTY ? true : false;
        var status = resp.httpResponse.statusCode;
        var params = require('util').inspect(req.params, true, true);

        var message = '';
        if (ansi) message += '\x1B[33m';
        message += '[AWS ' + req.service.serviceIdentifier + ' ' + status;
        message += ' ' + delta.toString() + 's ' + resp.retryCount + ' retries]';
        if (ansi) message += '\x1B[0;1m';
        message += ' ' + AWS.util.string.lowerFirst(req.operation);
        message += '(' + params + ')';
        if (ansi) message += '\x1B[0m';
        return message;
      }

      var line = buildMessage();
      if (typeof logger.log === 'function') {
        logger.log(line);
      } else if (typeof logger.write === 'function') {
        logger.write(line + '\n');
      }
    });
  }),

  Json: new SequentialExecutor().addNamedListeners(function(add) {
    var svc = require('./protocol/json');
    add('BUILD', 'build', svc.buildRequest);
    add('EXTRACT_DATA', 'extractData', svc.extractData);
    add('EXTRACT_ERROR', 'extractError', svc.extractError);
  }),

  Rest: new SequentialExecutor().addNamedListeners(function(add) {
    var svc = require('./protocol/rest');
    add('BUILD', 'build', svc.buildRequest);
    add('EXTRACT_DATA', 'extractData', svc.extractData);
    add('EXTRACT_ERROR', 'extractError', svc.extractError);
  }),

  RestJson: new SequentialExecutor().addNamedListeners(function(add) {
    var svc = require('./protocol/rest_json');
    add('BUILD', 'build', svc.buildRequest);
    add('EXTRACT_DATA', 'extractData', svc.extractData);
    add('EXTRACT_ERROR', 'extractError', svc.extractError);
  }),

  RestXml: new SequentialExecutor().addNamedListeners(function(add) {
    var svc = require('./protocol/rest_xml');
    add('BUILD', 'build', svc.buildRequest);
    add('EXTRACT_DATA', 'extractData', svc.extractData);
    add('EXTRACT_ERROR', 'extractError', svc.extractError);
  }),

  Query: new SequentialExecutor().addNamedListeners(function(add) {
    var svc = require('./protocol/query');
    add('BUILD', 'build', svc.buildRequest);
    add('EXTRACT_DATA', 'extractData', svc.extractData);
    add('EXTRACT_ERROR', 'extractError', svc.extractError);
  })
};
<commit_msg>Fix possible failure when error was reset in the middle of a retry event<commit_after>var AWS = require('./core');
var SequentialExecutor = require('./sequential_executor');

/**
 * The namespace used to register global event listeners for request building
 * and sending.
 */
AWS.EventListeners = {
  /**
   * @!attribute VALIDATE_CREDENTIALS
   *   A request listener that validates whether the request is being
   *   sent with credentials.
   *   Handles the {AWS.Request~validate 'validate' Request event}
   *   @example Sending a request without validating credentials
   *     var listener = AWS.EventListeners.Core.VALIDATE_CREDENTIALS;
   *     request.removeListener('validate', listener);
   *   @readonly
   *   @return [Function]
   * @!attribute VALIDATE_REGION
   *   A request listener that validates whether the region is set
   *   for a request.
   *   Handles the {AWS.Request~validate 'validate' Request event}
   *   @example Sending a request without validating region configuration
   *     var listener = AWS.EventListeners.Core.VALIDATE_REGION;
   *     request.removeListener('validate', listener);
   *   @readonly
   *   @return [Function]
   * @!attribute VALIDATE_PARAMETERS
   *   A request listener that validates input parameters in a request.
   *   Handles the {AWS.Request~validate 'validate' Request event}
   *   @example Sending a request without validating parameters
   *     var listener = AWS.EventListeners.Core.VALIDATE_PARAMETERS;
   *     request.removeListener('validate', listener);
   *   @example Disable parameter validation globally
   *     AWS.EventListeners.Core.removeListener('validate',
   *       AWS.EventListeners.Core.VALIDATE_REGION);
   *   @readonly
   *   @return [Function]
   * @!attribute SEND
   *   A request listener that initiates the HTTP connection for a
   *   request being sent. Handles the {AWS.Request~send 'send' Request event}
   *   @example Replacing the HTTP handler
   *     var listener = AWS.EventListeners.Core.SEND;
   *     request.removeListener('send', listener);
   *     request.on('send', function(response) {
   *       customHandler.send(response);
   *     });
   *   @return [Function]
   *   @readonly
   * @!attribute HTTP_DATA
   *   A request listener that reads data from the HTTP connection in order
   *   to build the response data.
   *   Handles the {AWS.Request~httpData 'httpData' Request event}.
   *   Remove this handler if you are overriding the 'httpData' event and
   *   do not want extra data processing and buffering overhead.
   *   @example Disabling default data processing
   *     var listener = AWS.EventListeners.Core.HTTP_DATA;
   *     request.removeListener('httpData', listener);
   *   @return [Function]
   *   @readonly
   */
  Core: {} /* doc hack */
};

AWS.EventListeners = {
  Core: new SequentialExecutor().addNamedListeners(function(add, addAsync) {
    addAsync('VALIDATE_CREDENTIALS', 'validate',
        function VALIDATE_CREDENTIALS(req, done) {
      req.service.config.getCredentials(function(err) {
        if (err) {
          req.response.err = AWS.util.error(err,
            {code: 'SigningError', message: 'Missing credentials in config'});
        }
        done();
      });
    });

    add('VALIDATE_REGION', 'validate', function VALIDATE_REGION(req) {
      if (!req.service.config.region && !req.service.hasGlobalEndpoint()) {
        req.response.error = AWS.util.error(new Error(),
          {code: 'SigningError', message: 'Missing region in config'});
      }
    });

    add('VALIDATE_PARAMETERS', 'validate', function VALIDATE_PARAMETERS(req) {
      var rules = req.service.api.operations[req.operation].input;
      new AWS.ParamValidator().validate(rules, req.params);
    });

    add('SET_CONTENT_LENGTH', 'afterBuild', function SET_CONTENT_LENGTH(req) {
      if (req.httpRequest.headers['Content-Length'] === undefined) {
        var length = AWS.util.string.byteLength(req.httpRequest.body);
        req.httpRequest.headers['Content-Length'] = length;
      }
    });

    add('SET_HTTP_HOST', 'afterBuild', function SET_HTTP_HOST(req) {
      req.httpRequest.headers['Host'] = req.httpRequest.endpoint.host;
    });

    addAsync('SIGN', 'sign', function SIGN(req, done) {
      if (!req.service.api.signatureVersion) return done(); // none

      req.service.config.getCredentials(function (err, credentials) {
        if (err) {
          req.response.error = err;
          return done();
        }

        try {
          var date = AWS.util.date.getDate();
          var SignerClass = req.service.getSignerClass(req);
          var signer = new SignerClass(req.httpRequest,
            req.service.api.signingName || req.service.api.endpointPrefix);

          // clear old authorization headers
          delete req.httpRequest.headers['Authorization'];
          delete req.httpRequest.headers['Date'];
          delete req.httpRequest.headers['X-Amz-Date'];

          // add new authorization
          signer.addAuthorization(credentials, date);
          req.signedAt = date;
        } catch (e) {
          req.response.error = e;
        }
        done();
      });
    });

    add('VALIDATE_RESPONSE', 'validateResponse', function VALIDATE_RESPONSE(resp) {
      if (this.service.successfulResponse(resp, this)) {
        resp.data = {};
        resp.error = null;
      } else {
        resp.data = null;
        resp.error = AWS.util.error(new Error(),
          {code: 'UnknownError', message: 'An unknown error occurred.'});
      }
    });

    addAsync('SEND', 'send', function SEND(resp, done) {
      resp.httpResponse._abortCallback = done;
      resp.error = null;
      resp.data = null;

      function callback(httpResp) {
        resp.httpResponse.stream = httpResp;

        httpResp.on('headers', function onHeaders(statusCode, headers) {
          resp.request.emit('httpHeaders', [statusCode, headers, resp]);

          if (!resp.request.httpRequest._streaming) {
            if (AWS.HttpClient.streamsApiVersion === 2) { // streams2 API check
              httpResp.on('readable', function onReadable() {
                var data = httpResp.read();
                if (data !== null) {
                  resp.request.emit('httpData', [data, resp]);
                }
              });
            } else { // legacy streams API
              httpResp.on('data', function onData(data) {
                resp.request.emit('httpData', [data, resp]);
              });
            }
          }
        });

        httpResp.on('end', function onEnd() {
          resp.request.emit('httpDone');
          done();
        });
      }

      function progress(httpResp) {
        httpResp.on('sendProgress', function onSendProgress(progress) {
          resp.request.emit('httpUploadProgress', [progress, resp]);
        });

        httpResp.on('receiveProgress', function onReceiveProgress(progress) {
          resp.request.emit('httpDownloadProgress', [progress, resp]);
        });
      }

      function error(err) {
        resp.error = AWS.util.error(err, {
          code: 'NetworkingError',
          region: resp.request.httpRequest.region,
          hostname: resp.request.httpRequest.endpoint.hostname,
          retryable: true
        });
        resp.request.emit('httpError', [resp.error, resp], function() {
          done();
        });
      }

      function executeSend() {
        var http = AWS.HttpClient.getInstance();
        var httpOptions = resp.request.service.config.httpOptions || {};
        var stream = http.handleRequest(resp.request.httpRequest, httpOptions,
                                        callback, error);
        progress(stream);
      }

      var timeDiff = (AWS.util.date.getDate() - this.signedAt) / 1000;
      if (timeDiff >= 60 * 10) { // if we signed 10min ago, re-sign
        this.emit('sign', [this], function(err) {
          if (err) done(err);
          else executeSend();
        });
      } else {
        executeSend();
      }
    });

    add('HTTP_HEADERS', 'httpHeaders',
        function HTTP_HEADERS(statusCode, headers, resp) {
      resp.httpResponse.statusCode = statusCode;
      resp.httpResponse.headers = headers;
      resp.httpResponse.body = new AWS.util.Buffer('');
      resp.httpResponse.buffers = [];
      resp.httpResponse.numBytes = 0;
    });

    add('HTTP_DATA', 'httpData', function HTTP_DATA(chunk, resp) {
      if (chunk) {
        if (AWS.util.isNode()) {
          resp.httpResponse.numBytes += chunk.length;

          var total = resp.httpResponse.headers['content-length'];
          var progress = { loaded: resp.httpResponse.numBytes, total: total };
          resp.request.emit('httpDownloadProgress', [progress, resp]);
        }

        resp.httpResponse.buffers.push(new AWS.util.Buffer(chunk));
      }
    });

    add('HTTP_DONE', 'httpDone', function HTTP_DONE(resp) {
      // convert buffers array into single buffer
      if (resp.httpResponse.buffers && resp.httpResponse.buffers.length > 0) {
        var body = AWS.util.buffer.concat(resp.httpResponse.buffers);
        resp.httpResponse.body = body;
      }
      delete resp.httpResponse.numBytes;
      delete resp.httpResponse.buffers;
    });

    add('FINALIZE_ERROR', 'retry', function FINALIZE_ERROR(resp) {
      if (resp.httpResponse.statusCode) {
        resp.error.statusCode = resp.httpResponse.statusCode;
        if (resp.error.retryable === undefined) {
          resp.error.retryable = this.service.retryableError(resp.error, this);
        }
      }
    });

    add('INVALIDATE_CREDENTIALS', 'retry', function INVALIDATE_CREDENTIALS(resp) {
      if (!resp.error) return;
      switch (resp.error.code) {
        case 'RequestExpired': // EC2 only
        case 'ExpiredTokenException':
        case 'ExpiredToken':
          resp.error.retryable = true;
          resp.request.service.config.credentials.expired = true;
      }
    });

    add('REDIRECT', 'retry', function REDIRECT(resp) {
      if (resp.error && resp.error.statusCode >= 300 &&
          resp.error.statusCode < 400 && resp.httpResponse.headers['location']) {
        this.httpRequest.endpoint =
          new AWS.Endpoint(resp.httpResponse.headers['location']);
        resp.error.redirect = true;
        resp.error.retryable = true;
      }
    });

    add('RETRY_CHECK', 'retry', function RETRY_CHECK(resp) {
      if (resp.error) {
        if (resp.error.redirect && resp.redirectCount < resp.maxRedirects) {
          resp.error.retryDelay = 0;
        } else if (resp.error.retryable && resp.retryCount < resp.maxRetries) {
          var delays = this.service.retryDelays();
          resp.error.retryDelay = delays[resp.retryCount] || 0;
        }
      }
    });

    addAsync('RESET_RETRY_STATE', 'afterRetry', function RESET_RETRY_STATE(resp, done) {
      var delay, willRetry = false;

      if (resp.error) {
        delay = resp.error.retryDelay || 0;
        if (resp.error.retryable && resp.retryCount < resp.maxRetries) {
          resp.retryCount++;
          willRetry = true;
        } else if (resp.error.redirect && resp.redirectCount < resp.maxRedirects) {
          resp.redirectCount++;
          willRetry = true;
        }
      }

      if (willRetry) {
        resp.error = null;
        setTimeout(done, delay);
      } else {
        done();
      }
    });
  }),

  CorePost: new SequentialExecutor().addNamedListeners(function(add) {
    add('EXTRACT_REQUEST_ID', 'extractData', function EXTRACT_REQUEST_ID(resp) {
      resp.requestId = resp.httpResponse.headers['x-amz-request-id'] ||
                       resp.httpResponse.headers['x-amzn-requestid'];

      if (!resp.requestId && resp.data && resp.data.ResponseMetadata) {
        resp.requestId = resp.data.ResponseMetadata.RequestId;
      }
    });
  }),

  Logger: new SequentialExecutor().addNamedListeners(function(add) {
    add('LOG_REQUEST', 'complete', function LOG_REQUEST(resp) {
      var req = resp.request;
      var logger = req.service.config.logger;
      if (!logger) return;

      function buildMessage() {
        var time = AWS.util.date.getDate().getTime();
        var delta = (time - req.startTime.getTime()) / 1000;
        var ansi = logger.isTTY ? true : false;
        var status = resp.httpResponse.statusCode;
        var params = require('util').inspect(req.params, true, true);

        var message = '';
        if (ansi) message += '\x1B[33m';
        message += '[AWS ' + req.service.serviceIdentifier + ' ' + status;
        message += ' ' + delta.toString() + 's ' + resp.retryCount + ' retries]';
        if (ansi) message += '\x1B[0;1m';
        message += ' ' + AWS.util.string.lowerFirst(req.operation);
        message += '(' + params + ')';
        if (ansi) message += '\x1B[0m';
        return message;
      }

      var line = buildMessage();
      if (typeof logger.log === 'function') {
        logger.log(line);
      } else if (typeof logger.write === 'function') {
        logger.write(line + '\n');
      }
    });
  }),

  Json: new SequentialExecutor().addNamedListeners(function(add) {
    var svc = require('./protocol/json');
    add('BUILD', 'build', svc.buildRequest);
    add('EXTRACT_DATA', 'extractData', svc.extractData);
    add('EXTRACT_ERROR', 'extractError', svc.extractError);
  }),

  Rest: new SequentialExecutor().addNamedListeners(function(add) {
    var svc = require('./protocol/rest');
    add('BUILD', 'build', svc.buildRequest);
    add('EXTRACT_DATA', 'extractData', svc.extractData);
    add('EXTRACT_ERROR', 'extractError', svc.extractError);
  }),

  RestJson: new SequentialExecutor().addNamedListeners(function(add) {
    var svc = require('./protocol/rest_json');
    add('BUILD', 'build', svc.buildRequest);
    add('EXTRACT_DATA', 'extractData', svc.extractData);
    add('EXTRACT_ERROR', 'extractError', svc.extractError);
  }),

  RestXml: new SequentialExecutor().addNamedListeners(function(add) {
    var svc = require('./protocol/rest_xml');
    add('BUILD', 'build', svc.buildRequest);
    add('EXTRACT_DATA', 'extractData', svc.extractData);
    add('EXTRACT_ERROR', 'extractError', svc.extractError);
  }),

  Query: new SequentialExecutor().addNamedListeners(function(add) {
    var svc = require('./protocol/query');
    add('BUILD', 'build', svc.buildRequest);
    add('EXTRACT_DATA', 'extractData', svc.extractData);
    add('EXTRACT_ERROR', 'extractError', svc.extractError);
  })
};
<|endoftext|>


<commit_before>/**
 * @author TyLindberg / https://github.com/TyLindberg
 */

Sidebar.Settings.Shortcuts = function ( editor ) {

	const IS_MAC = navigator.platform.toUpperCase().indexOf( 'MAC' ) >= 0;

	function isValidKeyBinding( key ) {

		return key.match( /^[A-Za-z0-9]$/i ); // Can't use z currently due to undo/redo

	}

	var config = editor.config;
	var signals = editor.signals;

	var container = new UI.Div();
	container.add( new UI.Break() );

	var shortcuts = [ 'translate', 'rotate', 'scale', 'undo', 'focus' ];

	for ( var i = 0; i < shortcuts.length; i ++ ) {

		let name = shortcuts[ i ];

		let configName = 'settings/shortcuts/' + name;
		let shortcutRow = new UI.Row();

		let shortcutInput = new UI.Input().setWidth( '150px' ).setFontSize( '12px' );
		shortcutInput.setTextTransform( 'lowercase' );
		shortcutInput.onChange( function () {

			var value = shortcutInput.getValue().toLowerCase();

			if ( isValidKeyBinding( value ) ) {

				config.setKey( configName, value );

			}

		} );

		// Automatically highlight when selecting an input field
		shortcutInput.dom.addEventListener( 'click', function () {

			shortcutInput.dom.select();

		} );

		// If the value of the input field is invalid, revert the input field
		// to contain the key binding stored in config
		shortcutInput.dom.addEventListener( 'blur', function () {

			if ( ! isValidKeyBinding( shortcutInput.getValue() ) ) {

				shortcutInput.setValue( config.getKey( configName ) );

			}

		} );

		// If a valid key binding character is entered, blur the input field
		shortcutInput.dom.addEventListener( 'keyup', function ( event ) {

			if ( isValidKeyBinding( event.key ) ) {

				shortcutInput.dom.blur();

			}

		} );

		if ( config.getKey( configName ) !== undefined ) {

			shortcutInput.setValue( config.getKey( configName ) );

		}

		shortcutInput.dom.maxLength = 1;
		shortcutRow.add( new UI.Text( name ).setTextTransform( 'capitalize' ).setWidth( '90px' ) );
		shortcutRow.add( shortcutInput );

		container.add( shortcutRow );

	}

	document.addEventListener( 'keydown', function ( event ) {

		switch ( event.key.toLowerCase() ) {

			case 'backspace':

				event.preventDefault(); // prevent browser back

				// fall-through

			case 'delete':

				var object = editor.selected;

				if ( object === null ) return;

				var parent = object.parent;
				if ( parent !== null ) editor.execute( new RemoveObjectCommand( object ) );

				break;

			case config.getKey( 'settings/shortcuts/translate' ):

				signals.transformModeChanged.dispatch( 'translate' );

				break;

			case config.getKey( 'settings/shortcuts/rotate' ):

				signals.transformModeChanged.dispatch( 'rotate' );

				break;

			case config.getKey( 'settings/shortcuts/scale' ):

				signals.transformModeChanged.dispatch( 'scale' );

				break;

			case config.getKey( 'settings/shortcuts/undo' ):

				if ( IS_MAC ? event.metaKey : event.ctrlKey ) {

					event.preventDefault(); // Prevent browser specific hotkeys

					if ( event.shiftKey ) {

						editor.redo();

					} else {

						editor.undo();

					}

				}

				break;

			case config.getKey( 'settings/shortcuts/focus' ):

				if ( editor.selected !== null ) {

					editor.focus( editor.selected );

				}

				break;

		}

	}, false );

	return container;

};
<commit_msg>Editor: Removed usage of let. Fixes Safari/iOS9 support.<commit_after>/**
 * @author TyLindberg / https://github.com/TyLindberg
 */

Sidebar.Settings.Shortcuts = function ( editor ) {

	const IS_MAC = navigator.platform.toUpperCase().indexOf( 'MAC' ) >= 0;

	function isValidKeyBinding( key ) {

		return key.match( /^[A-Za-z0-9]$/i ); // Can't use z currently due to undo/redo

	}

	var config = editor.config;
	var signals = editor.signals;

	var container = new UI.Div();
	container.add( new UI.Break() );

	var shortcuts = [ 'translate', 'rotate', 'scale', 'undo', 'focus' ];

	function createShortcutInput( name ) {

		var configName = 'settings/shortcuts/' + name;
		var shortcutRow = new UI.Row();

		var shortcutInput = new UI.Input().setWidth( '150px' ).setFontSize( '12px' );
		shortcutInput.setTextTransform( 'lowercase' );
		shortcutInput.onChange( function () {

			var value = shortcutInput.getValue().toLowerCase();

			if ( isValidKeyBinding( value ) ) {

				config.setKey( configName, value );

			}

		} );

		// Automatically highlight when selecting an input field
		shortcutInput.dom.addEventListener( 'click', function () {

			shortcutInput.dom.select();

		} );

		// If the value of the input field is invalid, revert the input field
		// to contain the key binding stored in config
		shortcutInput.dom.addEventListener( 'blur', function () {

			if ( ! isValidKeyBinding( shortcutInput.getValue() ) ) {

				shortcutInput.setValue( config.getKey( configName ) );

			}

		} );

		// If a valid key binding character is entered, blur the input field
		shortcutInput.dom.addEventListener( 'keyup', function ( event ) {

			if ( isValidKeyBinding( event.key ) ) {

				shortcutInput.dom.blur();

			}

		} );

		if ( config.getKey( configName ) !== undefined ) {

			shortcutInput.setValue( config.getKey( configName ) );

		}

		shortcutInput.dom.maxLength = 1;
		shortcutRow.add( new UI.Text( name ).setTextTransform( 'capitalize' ).setWidth( '90px' ) );
		shortcutRow.add( shortcutInput );

		container.add( shortcutRow );

	}

	for ( var i = 0; i < shortcuts.length; i ++ ) {

		createShortcutInput( shortcuts[ i ] );

	}

	document.addEventListener( 'keydown', function ( event ) {

		switch ( event.key.toLowerCase() ) {

			case 'backspace':

				event.preventDefault(); // prevent browser back

				// fall-through

			case 'delete':

				var object = editor.selected;

				if ( object === null ) return;

				var parent = object.parent;
				if ( parent !== null ) editor.execute( new RemoveObjectCommand( object ) );

				break;

			case config.getKey( 'settings/shortcuts/translate' ):

				signals.transformModeChanged.dispatch( 'translate' );

				break;

			case config.getKey( 'settings/shortcuts/rotate' ):

				signals.transformModeChanged.dispatch( 'rotate' );

				break;

			case config.getKey( 'settings/shortcuts/scale' ):

				signals.transformModeChanged.dispatch( 'scale' );

				break;

			case config.getKey( 'settings/shortcuts/undo' ):

				if ( IS_MAC ? event.metaKey : event.ctrlKey ) {

					event.preventDefault(); // Prevent browser specific hotkeys

					if ( event.shiftKey ) {

						editor.redo();

					} else {

						editor.undo();

					}

				}

				break;

			case config.getKey( 'settings/shortcuts/focus' ):

				if ( editor.selected !== null ) {

					editor.focus( editor.selected );

				}

				break;

		}

	}, false );

	return container;

};
<|endoftext|>


<commit_before>/**
 * Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You
 * may not use this file except in compliance with the License. A copy of
 * the License is located at
 *
 *     http://aws.amazon.com/apache2.0/
 *
 * or in the "license" file accompanying this file. This file is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
 * ANY KIND, either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 */

var AWS = require('./core');
require('./event_emitter');
require('./service_interface/json');
require('./service_interface/query');
require('./service_interface/rest');
require('./service_interface/rest_xml');

/**
 * @api private
 */
AWS.EventListeners = {
  Core: new AWS.EventEmitter().addNamedListeners(function(add) {
    add('VALIDATE_CREDENTIALS', 'validate', function VALIDATE_CREDENTIALS(req) {
      if (!req.client.config.credentials.accessKeyId ||
          !req.client.config.credentials.secretAccessKey) {
        throw AWS.util.error(new Error(),
          {code: 'SigningError', message: 'Missing credentials in config'});
      }
    });

    add('VALIDATE_REGION', 'validate', function VALIDATE_REGION(req) {
      if (!req.client.config.region) {
        throw AWS.util.error(new Error(),
          {code: 'SigningError', message: 'Missing region in config'});
      }
    });

    add('SIGN', 'sign', function SIGN(req) {
      var date = AWS.util.date.getDate();
      var credentials = AWS.util.copy(req.client.config.credentials);
      var SignerClass = AWS.RequestSigner.getVersion(req.client.api.signatureVersion);
      var signer = new SignerClass(req.httpRequest,
        req.client.api.signingName || req.client.api.serviceName);

      // clear old authorization headers
      delete req.httpRequest.headers['Authorization'];
      delete req.httpRequest.headers['Date'];
      delete req.httpRequest.headers['X-Amz-Date'];

      // add new authorization
      signer.addAuthorization(credentials, date);
    });

    add('SETUP_ERROR', 'extractError', function SETUP_ERROR(resp) {
      if (this.client.successfulResponse(resp, this)) {
        // throwing null will stop the error extraction chain
        // but will not set an error for data extraction
        throw null;
      }

      resp.error = AWS.util.error(new Error(),
        {code: 'UnknownError', message: 'An unknown error occurred.'});
      resp.data = null;
    });

    add('SETUP_DATA', 'extractData', function SETUP_DATA(resp) {
      resp.data = {};
      resp.error = null;
    });
  }),

  Http: new AWS.EventEmitter().addNamedListeners(function(add) {
    add('SEND', 'send', function SEND(resp) {
      AWS.HttpClient.getInstance().handleRequest(this, resp);
    });

    add('HTTP_HEADERS', 'httpHeaders',
        function HTTP_HEADERS(statusCode, headers, resp) {
      resp.httpResponse.statusCode = statusCode;
      resp.httpResponse.headers = headers;
      resp.httpResponse.body = new Buffer('');
      resp.httpResponse.buffers = [];
    });

    add('HTTP_DATA', 'httpData', function HTTP_DONE(chunk, resp) {
      resp.httpResponse.buffers.push(chunk);
    });

    add('HTTP_DONE', 'httpDone', function HTTP_DONE(resp) {
      // convert buffers array into single buffer
      resp.httpResponse.body = Buffer.concat(resp.httpResponse.buffers);
      delete resp.httpResponse.buffers;

      this.completeRequest(resp);
    });

    add('HTTP_ERROR', 'httpError', function HTTP_ERROR(error, resp) {
      resp.error = error;
      this.completeRequest(resp);
    });

    add('FINALIZE_ERROR', 'retry', function FINALIZE_ERROR(resp) {
      resp.error.statusCode = resp.httpResponse.statusCode;
      if (resp.error.retryable === undefined) {
        resp.error.retryable = this.client.retryableError(resp.error, this);
      }
    });

    add('REDIRECT', 'retry', function REDIRECT(resp) {
      if (resp.error && resp.error.statusCode == 307) {
        this.httpRequest.endpoint =
          new AWS.Endpoint(resp.httpResponse.headers['location']);
        resp.error.retryable = true;
      }
    });

    add('RETRY_CHECK', 'retry', function RETRY_CHECK(resp) {
      if (resp.error) {
        if (!resp.error.retryable)
          throw resp.error;
        if (resp.retryCount >= this.client.numRetries())
          throw resp.error;

        resp.retryCount++;
      }
    });

    add('RETRY_SIGN', 'retry', function RETRY_SIGN(resp) {
      this.emitEvents(resp, 'sign');
    });

    add('RETRY_DELAY_SEND', 'retry', function RETRY_DELAY_SEND(resp) {
      var delay = 0;
      if (resp.error) {
        delay = this.client.retryDelays()[resp.retryCount-1] || 0;
      }

      resp.error = null;
      resp.data = null;

      setTimeout(function() { resp.request.emitEvents(resp, 'send'); }, delay);
    });

    // Allow streaming by disabling HTTP_DATA callback if
    // any httpData event is added
    add('DISABLE_HTTP_DATA', 'newListener', function DISABLE_HTTP_DATA(event) {
      if (event === 'httpData') {
        this.removeListener('httpData', AWS.EventListeners.Http.HTTP_DATA);
      }
    });
  }),

  Json: new AWS.EventEmitter().addNamedListeners(function(add) {
    var svc = AWS.ServiceInterface.Json;
    add('BUILD', 'build', svc.buildRequest);
    add('EXTRACT_DATA', 'extractData', svc.extractData);
    add('EXTRACT_ERROR', 'extractError', svc.extractError);
  }),

  Rest: new AWS.EventEmitter().addNamedListeners(function(add) {
    var svc = AWS.ServiceInterface.Rest;
    add('BUILD', 'build', svc.buildRequest);
    add('EXTRACT_DATA', 'extractData', svc.extractData);
    add('EXTRACT_ERROR', 'extractError', svc.extractError);
  }),

  RestXml: new AWS.EventEmitter().addNamedListeners(function(add) {
    var svc = AWS.ServiceInterface.RestXml;
    add('BUILD', 'build', svc.buildRequest);
    add('EXTRACT_DATA', 'extractData', svc.extractData);
    add('EXTRACT_ERROR', 'extractError', svc.extractError);
  }),

  Query: new AWS.EventEmitter().addNamedListeners(function(add) {
    var svc = AWS.ServiceInterface.Query;
    add('BUILD', 'build', svc.buildRequest);
    add('EXTRACT_DATA', 'extractData', svc.extractData);
    add('EXTRACT_ERROR', 'extractError', svc.extractError);
  })
};
<commit_msg>HTTP_DONE event should only reset body if buffers is filled<commit_after>/**
 * Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You
 * may not use this file except in compliance with the License. A copy of
 * the License is located at
 *
 *     http://aws.amazon.com/apache2.0/
 *
 * or in the "license" file accompanying this file. This file is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
 * ANY KIND, either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 */

var AWS = require('./core');
require('./event_emitter');
require('./service_interface/json');
require('./service_interface/query');
require('./service_interface/rest');
require('./service_interface/rest_xml');

/**
 * @api private
 */
AWS.EventListeners = {
  Core: new AWS.EventEmitter().addNamedListeners(function(add) {
    add('VALIDATE_CREDENTIALS', 'validate', function VALIDATE_CREDENTIALS(req) {
      if (!req.client.config.credentials.accessKeyId ||
          !req.client.config.credentials.secretAccessKey) {
        throw AWS.util.error(new Error(),
          {code: 'SigningError', message: 'Missing credentials in config'});
      }
    });

    add('VALIDATE_REGION', 'validate', function VALIDATE_REGION(req) {
      if (!req.client.config.region) {
        throw AWS.util.error(new Error(),
          {code: 'SigningError', message: 'Missing region in config'});
      }
    });

    add('SIGN', 'sign', function SIGN(req) {
      var date = AWS.util.date.getDate();
      var credentials = AWS.util.copy(req.client.config.credentials);
      var SignerClass = AWS.RequestSigner.getVersion(req.client.api.signatureVersion);
      var signer = new SignerClass(req.httpRequest,
        req.client.api.signingName || req.client.api.serviceName);

      // clear old authorization headers
      delete req.httpRequest.headers['Authorization'];
      delete req.httpRequest.headers['Date'];
      delete req.httpRequest.headers['X-Amz-Date'];

      // add new authorization
      signer.addAuthorization(credentials, date);
    });

    add('SETUP_ERROR', 'extractError', function SETUP_ERROR(resp) {
      if (this.client.successfulResponse(resp, this)) {
        // throwing null will stop the error extraction chain
        // but will not set an error for data extraction
        throw null;
      }

      resp.error = AWS.util.error(new Error(),
        {code: 'UnknownError', message: 'An unknown error occurred.'});
      resp.data = null;
    });

    add('SETUP_DATA', 'extractData', function SETUP_DATA(resp) {
      resp.data = {};
      resp.error = null;
    });
  }),

  Http: new AWS.EventEmitter().addNamedListeners(function(add) {
    add('SEND', 'send', function SEND(resp) {
      AWS.HttpClient.getInstance().handleRequest(this, resp);
    });

    add('HTTP_HEADERS', 'httpHeaders',
        function HTTP_HEADERS(statusCode, headers, resp) {
      resp.httpResponse.statusCode = statusCode;
      resp.httpResponse.headers = headers;
      resp.httpResponse.body = new Buffer('');
      resp.httpResponse.buffers = [];
    });

    add('HTTP_DATA', 'httpData', function HTTP_DATA(chunk, resp) {
      resp.httpResponse.buffers.push(chunk);
    });

    add('HTTP_DONE', 'httpDone', function HTTP_DONE(resp) {
      // convert buffers array into single buffer
      if (resp.httpResponse.buffers.length > 0) {
        resp.httpResponse.body = Buffer.concat(resp.httpResponse.buffers);
      }
      delete resp.httpResponse.buffers;

      this.completeRequest(resp);
    });

    add('HTTP_ERROR', 'httpError', function HTTP_ERROR(error, resp) {
      resp.error = error;
      this.completeRequest(resp);
    });

    add('FINALIZE_ERROR', 'retry', function FINALIZE_ERROR(resp) {
      resp.error.statusCode = resp.httpResponse.statusCode;
      if (resp.error.retryable === undefined) {
        resp.error.retryable = this.client.retryableError(resp.error, this);
      }
    });

    add('REDIRECT', 'retry', function REDIRECT(resp) {
      if (resp.error && resp.error.statusCode == 307) {
        this.httpRequest.endpoint =
          new AWS.Endpoint(resp.httpResponse.headers['location']);
        resp.error.retryable = true;
      }
    });

    add('RETRY_CHECK', 'retry', function RETRY_CHECK(resp) {
      if (resp.error) {
        if (!resp.error.retryable)
          throw resp.error;
        if (resp.retryCount >= this.client.numRetries())
          throw resp.error;

        resp.retryCount++;
      }
    });

    add('RETRY_SIGN', 'retry', function RETRY_SIGN(resp) {
      this.emitEvents(resp, 'sign');
    });

    add('RETRY_DELAY_SEND', 'retry', function RETRY_DELAY_SEND(resp) {
      var delay = 0;
      if (resp.error) {
        delay = this.client.retryDelays()[resp.retryCount-1] || 0;
      }

      resp.error = null;
      resp.data = null;

      setTimeout(function() { resp.request.emitEvents(resp, 'send'); }, delay);
    });

    // Allow streaming by disabling HTTP_DATA callback if
    // any httpData event is added
    add('DISABLE_HTTP_DATA', 'newListener', function DISABLE_HTTP_DATA(event) {
      if (event === 'httpData') {
        this.removeListener('httpData', AWS.EventListeners.Http.HTTP_DATA);
      }
    });
  }),

  Json: new AWS.EventEmitter().addNamedListeners(function(add) {
    var svc = AWS.ServiceInterface.Json;
    add('BUILD', 'build', svc.buildRequest);
    add('EXTRACT_DATA', 'extractData', svc.extractData);
    add('EXTRACT_ERROR', 'extractError', svc.extractError);
  }),

  Rest: new AWS.EventEmitter().addNamedListeners(function(add) {
    var svc = AWS.ServiceInterface.Rest;
    add('BUILD', 'build', svc.buildRequest);
    add('EXTRACT_DATA', 'extractData', svc.extractData);
    add('EXTRACT_ERROR', 'extractError', svc.extractError);
  }),

  RestXml: new AWS.EventEmitter().addNamedListeners(function(add) {
    var svc = AWS.ServiceInterface.RestXml;
    add('BUILD', 'build', svc.buildRequest);
    add('EXTRACT_DATA', 'extractData', svc.extractData);
    add('EXTRACT_ERROR', 'extractError', svc.extractError);
  }),

  Query: new AWS.EventEmitter().addNamedListeners(function(add) {
    var svc = AWS.ServiceInterface.Query;
    add('BUILD', 'build', svc.buildRequest);
    add('EXTRACT_DATA', 'extractData', svc.extractData);
    add('EXTRACT_ERROR', 'extractError', svc.extractError);
  })
};
<|endoftext|>


<commit_before>'use strict';

require('../../extend')('get', null, function (args, resolve) {
	return resolve(this.failed ? this.promise : this.value[args[0]]);
});

module.exports = require('../../deferred');
<commit_msg>Documentation<commit_after>// 'get(name)' - resolves with property of object that would be result
//               of promise

'use strict';

require('../../extend')('get', null, function (args, resolve) {
	return resolve(this.failed ? this.promise : this.value[args[0]]);
});

module.exports = require('../../deferred');
<|endoftext|>


<commit_before>
var results = [];

exports.formatMessage = function (filename, issues) {
    var that = this;
    var result = {
        filePath: filename,
        messages: issues,
    };
    results.push(result);
};

exports.done = function (errorCount, fileCount) {
    console.log(JSON.stringify(results));
};
<commit_msg>Add json format with message<commit_after>
var results = [];

exports.formatMessage = function (filename, issues) {
    var that = this;
    var result = {
        filePath: filename,
        messages: issues.map(function (issue) {
            return Object.assign({}, issue, {
                message: that.messages.renderIssue(issue),
            });
        }),
    };
    results.push(result);
};

exports.done = function (errorCount, fileCount) {
    console.log(JSON.stringify(results));
};
<|endoftext|>


<commit_before>/** api: module = layer */

/** api: synopsis
 *  Layer related functionality.
 */

/** api: summary
 *  The :mod:`layer` module provides a constructor for Layer objects.
 *
 *  .. code-block:: javascript
 *
 *      js> var LAYER = require("geoscript/layer");
 */

var UTIL = require("./util");
var Registry = require("./registry").Registry;
var Factory = require("./factory").Factory;

var Feature = require("./feature").Feature;
var FILTER = require("./filter");
var Schema = require("./feature").Schema;
var Cursor = require("./cursor").Cursor;
var WORKSPACE = require("./workspace");
var PROJ = require("./proj");
var GEOM = require("./geom");
var STYLE = require("./style");

var geotools = Packages.org.geotools;
var DefaultQuery = geotools.data.DefaultQuery;
var Query = geotools.data.Query;
var Transaction = geotools.data.Transaction;
var FeatureCollections = geotools.feature.FeatureCollections;
var CQL = geotools.filter.text.cql2.CQL;

// TODO: remove when changed in GeoTools
// supress info about "Building quadtree spatial index with depth 3 for file"
var logger = geotools.util.logging.Logging.getLogger(
    "org.geotools.data.shapefile"
);
logger.setLevel(java.util.logging.Level.WARNING);

var getTempName = function() {
    var count = WORKSPACE.memory.names.length;
    var name = "layer_" + count;
    while (WORKSPACE.memory.names.indexOf(name) > -1) {
        ++count;
        name = "layer_" + count;
    }
    return name;
};

/** api: class = Layer */
var Layer = UTIL.extend(Object, {

    /** private: property[cache]
     *  ``Object``
     */
    cache: null,

    /** api: constructor
     *  .. class:: Layer
     *
     *      Create a new layer.  If a workspace is not provided, a temporary
     *      layer will be created.  If a layer is created without a schema, a
     *      default schema will be applied.
     *
     *      The following are equivalent ways of constructing a temporary layer
     *      with a simple schema:
     *
     *      .. code-block:: javascript
     *
     *          js> var layer = new LAYER.Layer({name: "temp"});
     *
     *          js> var layer = new LAYER.Layer({
     *            >     name: "temp",
     *            >     fields: [{name: "geom", type: "Geometry"}]
     *            > });
     *
     *          js> var FEATURE = require("geoscript/feature");
     *          js> var schema = new FEATURE.Schema({
     *            >     name: "temp",
     *            >     fields: [{name: "geom", type: "Geometry"}]
     *            > });
     *          js> var layer = new LAYER.Layer({schema: schema});
     */
    constructor: function Layer(config) {
        this.cache = {};
        var name;
        if (config) {
            var schema = config.schema;
            var name = config.name || getTempName();
            if (!schema) {
                schema = new Schema({
                    name: name,
                    fields: config.fields || [{name: "geom", type: "Geometry"}]
                });
            }
            if (config.workspace) {
                if (config.workspace instanceof WORKSPACE.Workspace) {
                    this.workspace = config.workspace;
                } else {
                    this.workspace = WORKSPACE.create(config.workspace);
                }
                name = config.name;
            } else {
                this.workspace = WORKSPACE.memory;
                this.workspace._store.createSchema(schema._schema);
                name = schema.name;
            }
            if (!name) {
                throw "Layer configuration must include a name or a named schema.";
            }
            this._source = this.workspace._store.getFeatureSource(name);
            var projection = config.projection;
            if (projection) {
                this.projection = projection;
            }
            if (config.style) {
                this.style = config.style;
            }
        }
    },

    /** api: method[get]
     *  :arg id: ``String`` Feature identifier.
     *  :returns: :class:`feature.Feature`
     *
     *  Get a single feature using the feature id.
     */
    get: function(id) {
        var filter = FILTER.fids([id]);
        var cursor = this.query(filter);
        var feature = cursor.next();
        cursor.close();
        return feature;
    },

    /** api: method[clone]
     *  :arg name: ``String`` New layer name.  If not provided, one will be
     *      generated.
     *  :returns: :class:`layer.Layer` The layer clone.
     *
     *  Create a temporary copy of this layer.
     */
    clone: function(name) {
        name = name || getTempName();
        if (WORKSPACE.memory.names.indexOf(name) > -1) {
            throw "Layer named '" + name + "' already exists.";
        }
        var schema = this.schema.clone({name: name});
        var layer = new Layer({schema: schema});
        this.features.forEach(layer.add, layer);
        return layer;
    },

    /** api: property[style]
     *  :class:`style.Style`
     *  The style to be used when rendering this layer as part of a map.
     */
    set style(style) {
        if (!(style instanceof STYLE.Style)) {
            if (!style.defaultSymbolizerType) {
                style.defaultSymbolizerType = STYLE.getSymbolizerTypeFromGeometryType(
                    this.schema.geometry.type
                );
            }
            style = new STYLE.Style(style);
        }
        this.cache.style = style;
    },
    get style() {
        if (!this.cache.style) {
            // set up the default style
            this.style = {};
        }
        return this.cache.style;
    },

    /** api: property[schema]
     *  :class:`feature.Schema`
     *  The schema for this layer.
     */
    get schema() {
        if (!this.cache.schema) {
            this.cache.schema = Schema.from_(this._source.getSchema());
        }
        return this.cache.schema;
    },

    /** api: property[projection]
     *  :class:`proj.Projection`
     *  Optional projection for the layer.  If set, any features added to the
     *  layer will be tranformed to this projection if they are in a different
     *  projection.  This must be set before features are added to the layer.
     */
    get projection() {
        var projection = this.cache.projection;
        if (!projection && this.schema) {
            var field = this.schema.geometry;
            if (field) {
                projection = field.projection;
                this.cache.projection = projection;
            }
        }
        return projection;
    },
    set projection(projection) {
        if (!(projection instanceof PROJ.Projection)) {
            projection = new PROJ.Projection(projection);
        }
        if (this.projection && !projection.equals(this.projection)) {
            throw "Layer projection already set: " + this.projection.id;
        }
        this.cache.projection = projection;
    },

    /** api: property[temporary]
     *  ``Boolean``
     *  The layer has not been persisted to a workspace.
     */
    get temporary() {
        return (this.workspace instanceof WORKSPACE.Memory);
    },

    /** api: property[name]
     *  ``String``
     *  The layer name.
     */
    get name() {
        return String(this._source.getName().getLocalPart());
    },

    /** api: method[getCount]
     *  :arg filter: :class:`filter.Filter` Optional filter or CQL string.
     *  :returns: ``Number``
     *
     *  Get the number of features on the layer matching the given filter.
     */
    getCount: function(filter) {
        if (filter) {
            if (!(filter instanceof FILTER.Filter)) {
                filter = new FILTER.Filter(filter);
            }
        } else {
            filter = FILTER.Filter.PASS;
        }
        var count = this._source.getCount(new DefaultQuery(this.name, filter._filter));
        if (count === -1) {
            // count manually for layers that don't support this query
            count = 0;
            this.features.forEach(function(feature) {
                if (filter.evaluate(feature)) {
                    ++count;
                }
            });
        }
        return count;
    },

    /** api: property[count]
     *  ``Number``
     *  The number of features contained in the layer.
     */
    get count() {
        return this._source.getCount(Query.ALL);
    },

    /** api: method[getBounds]
     *  :arg filter: :class:`filter.Filter` Optional filter or CQL string.
     *  :returns: :class:`geom.Bounds`
     *
     *  Get the bounds for all features on the layer.  Optionally, the bounds
     *  can be generated for all features that match the given filter.
     */
    getBounds: function(filter) {
        if (filter) {
            if (!(filter instanceof FILTER.Filter)) {
                filter = new FILTER.Filter(filter);
            }
        } else {
            filter = FILTER.Filter.PASS;
        }
        var bounds;
        var _bounds = this._source.getBounds(new DefaultQuery(this.name, filter._filter));
        if (_bounds) {
            bounds = GEOM.Bounds.from_(_bounds);
        } else {
            // manually calculate bounds for layers that don't support getBounds with a filter
            this.features.forEach(function(feature) {
                if (filter.evaluate(feature)) {
                    if (!bounds) {
                        bounds = feature.bounds.clone();
                    } else {
                        bounds.include(feature.bounds);
                    }
                }
            });

        }
        return bounds;
    },

    /** api: property[bounds]
     *  :class:`geom.Bounds`
     *  The bounds for all features on this layer.
     */
    get bounds() {
        return this.getBounds();
    },

    /** api: method[query]
     *  :arg filter: ``filter.Filter or String`` A filter or a CQL string.
     *  :returns: :class:`cursor.Cursor` A cursor for accessing queried features.
     *
     *  Query for features from the layer.  The return will be an object with
     *  ``forEach``, ``hasNext``, and ``next`` methods.  If no filter is
     *  provided, all features will be included in the results.
     *
     *  Example use:
     *
     *  .. code-block:: javascript
     *
     *      js> layer.query("name = 'foo'").forEach(function(feature) {
     *        >     print(feature.toString());
     *        > });
     */
    query: function(filter) {
        if (!filter) {
            filter = FILTER.Filter.PASS;
        } else {
            if (!(filter instanceof FILTER.Filter)) {
                // must be CQL string
                filter = new FILTER.Filter(filter);
            }
        }
        var query = new DefaultQuery(this.name, filter._filter);
        var results = this._source.dataStore.getFeatureReader(query, Transaction.AUTO_COMMIT);
        var _schema = this.schema._schema;

        var cursor = new Cursor(results, function(_feature) {
            return Feature.from_(_feature, _schema);
        });

        return cursor;
    },

    /** api: property[features]
     *  :class:`cursor.Cursor`
     *  A cursor object for accessing all features on the layer.
     *
     *
     *  Example use:
     *
     *  .. code-block:: javascript
     *
     *      js> layer.features.forEach(function(feature) {
     *        >     print(feature.toString());
     *        > });
     */
    get features() {
        return this.query();
    },

    /** api: method[add]
     *  :arg obj: ``Object`` A :class:`feature.Feature` or a feature attribute
     *      values object.
     *
     *  Add a feature to a layer.  Optionally, an object with feature attribute
     *  values may be provided.
     *
     *  Example use:
     *
     *  .. code-block:: javascript
     *
     *      js> var GEOM = require("geoscript/geom");
     *      js> layer.add({geom: new GEOM.Point([0, 1])});
     *
     */
    add: function(obj) {
        var feature;
        if (obj instanceof Feature) {
            feature = obj;
        } else {
            // has to be a values object
            feature = new Feature({schema: this.schema, values: obj});
        }
        if (this.projection) {
            if (feature.projection) {
                if (!this.projection.equals(feature.projection)) {
                    feature.geometry = PROJ.transform(
                        feature.geometry,
                        feature.projection,
                        this.projection
                    );
                }
            } else {
                feature.projection = this.projection;
            }
        }
        this.workspace._onFeatureAdd(feature);
        var collection = FeatureCollections.newCollection();
        collection.add(feature._feature);
        this._source.addFeatures(collection);
    },

    /** api: method[remove]
     *  :arg filter: :class:`filter.Filter` or ``String``
     *
     *  Remove features from a layer that match the given filter or CQL string.
     *
     *  Example use:
     *
     *  .. code-block:: javascript
     *
     *      js> var GEOM = require("geoscript/geom");
     *      js> layer.add({geom: new GEOM.Point([1, 2])});
     *      js> layer.remove("INTERSECTS(geom, POINT(1 2))");
     *
     */
    remove: function(filter) {
        if (!filter) {
            filter = FILTER.Filter.FAIL;
        }
        if (!(filter instanceof FILTER.Filter)) {
            filter = new FILTER.Filter(filter);
        }
        this._source.removeFeatures(filter._filter);
    },

    /** private: property[config]
     */
    get config() {
        var config = {
            type: "Layer",
            name: this.name
        };
        if (this.temporary) {
            config.schema = this.schema.config;
        } else {
            config.workspace = this.workspace.config;
        }
        return config;
    },

    /** api: property[json]
     *  ``String``
     *  The JSON representation of this layer.  This representation does not
     *  include members for each feature in the layer.
     */
    get json() {
        return JSON.stringify(this.config);
    },

    /** private: method[toFullString]
     */
    toFullString: function() {
        return "name: " + this.name + ", count: " + this.count;
    }

});

Layer.from_ = function(_source, workspace) {
    var layer = new Layer();
    layer._source = _source;
    layer.workspace = workspace;
    return layer;
}

/** api: example
 *  Sample code to create a temporary layer:
 *
 *  .. code-block:: javascript
 *
 *      js> var layer = new LAYER.Layer({name: "temp"});
 */

exports.Layer = Layer;

/** private: method[create]
 *  :arg config: ``Object`` Configuration object.
 *  :returns: :class:`layer.Layer`
 *
 *  Create a layer given a configuration object.
 */
var registry = new Registry();
exports.create = registry.create;

// register a layer factory for the module
registry.register(new Factory(Layer, {
    handles: function(config) {
        return true;
    }
}));
<commit_msg>Set layer title.<commit_after>/** api: module = layer */

/** api: synopsis
 *  Layer related functionality.
 */

/** api: summary
 *  The :mod:`layer` module provides a constructor for Layer objects.
 *
 *  .. code-block:: javascript
 *
 *      js> var LAYER = require("geoscript/layer");
 */

var UTIL = require("./util");
var Registry = require("./registry").Registry;
var Factory = require("./factory").Factory;

var Feature = require("./feature").Feature;
var FILTER = require("./filter");
var Schema = require("./feature").Schema;
var Cursor = require("./cursor").Cursor;
var WORKSPACE = require("./workspace");
var PROJ = require("./proj");
var GEOM = require("./geom");
var STYLE = require("./style");

var geotools = Packages.org.geotools;
var DefaultQuery = geotools.data.DefaultQuery;
var Query = geotools.data.Query;
var Transaction = geotools.data.Transaction;
var FeatureCollections = geotools.feature.FeatureCollections;
var CQL = geotools.filter.text.cql2.CQL;

// TODO: remove when changed in GeoTools
// supress info about "Building quadtree spatial index with depth 3 for file"
var logger = geotools.util.logging.Logging.getLogger(
    "org.geotools.data.shapefile"
);
logger.setLevel(java.util.logging.Level.WARNING);

var getTempName = function() {
    var count = WORKSPACE.memory.names.length;
    var name = "layer_" + count;
    while (WORKSPACE.memory.names.indexOf(name) > -1) {
        ++count;
        name = "layer_" + count;
    }
    return name;
};

/** api: class = Layer */
var Layer = UTIL.extend(Object, {

    /** private: property[cache]
     *  ``Object``
     */
    cache: null,

    /** api: constructor
     *  .. class:: Layer
     *
     *      Create a new layer.  If a workspace is not provided, a temporary
     *      layer will be created.  If a layer is created without a schema, a
     *      default schema will be applied.
     *
     *      The following are equivalent ways of constructing a temporary layer
     *      with a simple schema:
     *
     *      .. code-block:: javascript
     *
     *          js> var layer = new LAYER.Layer({name: "temp"});
     *
     *          js> var layer = new LAYER.Layer({
     *            >     name: "temp",
     *            >     fields: [{name: "geom", type: "Geometry"}]
     *            > });
     *
     *          js> var FEATURE = require("geoscript/feature");
     *          js> var schema = new FEATURE.Schema({
     *            >     name: "temp",
     *            >     fields: [{name: "geom", type: "Geometry"}]
     *            > });
     *          js> var layer = new LAYER.Layer({schema: schema});
     */
    constructor: function Layer(config) {
        this.cache = {};
        var name;
        if (config) {
            var schema = config.schema;
            var name = config.name || getTempName();
            if (!schema) {
                schema = new Schema({
                    name: name,
                    fields: config.fields || [{name: "geom", type: "Geometry"}]
                });
            }
            if (config.workspace) {
                if (config.workspace instanceof WORKSPACE.Workspace) {
                    this.workspace = config.workspace;
                } else {
                    this.workspace = WORKSPACE.create(config.workspace);
                }
                name = config.name;
            } else {
                this.workspace = WORKSPACE.memory;
                this.workspace._store.createSchema(schema._schema);
                name = schema.name;
            }
            if (!name) {
                throw "Layer configuration must include a name or a named schema.";
            }
            this._source = this.workspace._store.getFeatureSource(name);
            var projection = config.projection;
            if (projection) {
                this.projection = projection;
            }
            if (config.title) {
                this.title = config.title;
            }
            if (config.style) {
                this.style = config.style;
            }
        }
    },

    /** api: method[get]
     *  :arg id: ``String`` Feature identifier.
     *  :returns: :class:`feature.Feature`
     *
     *  Get a single feature using the feature id.
     */
    get: function(id) {
        var filter = FILTER.fids([id]);
        var cursor = this.query(filter);
        var feature = cursor.next();
        cursor.close();
        return feature;
    },

    /** api: method[clone]
     *  :arg name: ``String`` New layer name.  If not provided, one will be
     *      generated.
     *  :returns: :class:`layer.Layer` The layer clone.
     *
     *  Create a temporary copy of this layer.
     */
    clone: function(name) {
        name = name || getTempName();
        if (WORKSPACE.memory.names.indexOf(name) > -1) {
            throw "Layer named '" + name + "' already exists.";
        }
        var schema = this.schema.clone({name: name});
        var layer = new Layer({schema: schema});
        this.features.forEach(layer.add, layer);
        return layer;
    },

    /** api: config[style]
     *  :class:`style.Style`
     *  Optional style to be used when rendering this layer as part of a map.
     */
    set style(style) {
        if (!(style instanceof STYLE.Style)) {
            if (!style.defaultSymbolizerType) {
                style.defaultSymbolizerType = STYLE.getSymbolizerTypeFromGeometryType(
                    this.schema.geometry.type
                );
            }
            style = new STYLE.Style(style);
        }
        this.cache.style = style;
    },
    /** api: property[style]
     *  :class:`style.Style`
     *  The style to be used when rendering this layer as part of a map.
     */
    get style() {
        if (!this.cache.style) {
            // set up the default style
            this.style = {};
        }
        return this.cache.style;
    },

    /** api: property[schema]
     *  :class:`feature.Schema`
     *  The schema for this layer.
     */
    get schema() {
        if (!this.cache.schema) {
            this.cache.schema = Schema.from_(this._source.getSchema());
        }
        return this.cache.schema;
    },

    /** api: property[projection]
     *  :class:`proj.Projection`
     *  Optional projection for the layer.  If set, any features added to the
     *  layer will be tranformed to this projection if they are in a different
     *  projection.  This must be set before features are added to the layer.
     */
    get projection() {
        var projection = this.cache.projection;
        if (!projection && this.schema) {
            var field = this.schema.geometry;
            if (field) {
                projection = field.projection;
                this.cache.projection = projection;
            }
        }
        return projection;
    },
    set projection(projection) {
        if (!(projection instanceof PROJ.Projection)) {
            projection = new PROJ.Projection(projection);
        }
        if (this.projection && !projection.equals(this.projection)) {
            throw "Layer projection already set: " + this.projection.id;
        }
        this.cache.projection = projection;
    },

    /** api: property[temporary]
     *  ``Boolean``
     *  The layer has not been persisted to a workspace.
     */
    get temporary() {
        return (this.workspace instanceof WORKSPACE.Memory);
    },

    /** api: property[name]
     *  ``String``
     *  The layer name.
     */
    get name() {
        return String(this._source.getName().getLocalPart());
    },

    /** api: config[title]
     *  ``String``
     *  Optional title for the layer.
     */
    set title(title) {
        this.cache.title = title;
    },
    /** api: property[title]
     *  ``String``
     *  The layer title.  Defaults to the layer name.
     */
    get title() {
        var title = this.cache.title;
        if (!title) {
            title = this.name;
        }
        return title;
    },

    /** api: method[getCount]
     *  :arg filter: :class:`filter.Filter` Optional filter or CQL string.
     *  :returns: ``Number``
     *
     *  Get the number of features on the layer matching the given filter.
     */
    getCount: function(filter) {
        if (filter) {
            if (!(filter instanceof FILTER.Filter)) {
                filter = new FILTER.Filter(filter);
            }
        } else {
            filter = FILTER.Filter.PASS;
        }
        var count = this._source.getCount(new DefaultQuery(this.name, filter._filter));
        if (count === -1) {
            // count manually for layers that don't support this query
            count = 0;
            this.features.forEach(function(feature) {
                if (filter.evaluate(feature)) {
                    ++count;
                }
            });
        }
        return count;
    },

    /** api: property[count]
     *  ``Number``
     *  The number of features contained in the layer.
     */
    get count() {
        return this._source.getCount(Query.ALL);
    },

    /** api: method[getBounds]
     *  :arg filter: :class:`filter.Filter` Optional filter or CQL string.
     *  :returns: :class:`geom.Bounds`
     *
     *  Get the bounds for all features on the layer.  Optionally, the bounds
     *  can be generated for all features that match the given filter.
     */
    getBounds: function(filter) {
        if (filter) {
            if (!(filter instanceof FILTER.Filter)) {
                filter = new FILTER.Filter(filter);
            }
        } else {
            filter = FILTER.Filter.PASS;
        }
        var bounds;
        var _bounds = this._source.getBounds(new DefaultQuery(this.name, filter._filter));
        if (_bounds) {
            bounds = GEOM.Bounds.from_(_bounds);
        } else {
            // manually calculate bounds for layers that don't support getBounds with a filter
            this.features.forEach(function(feature) {
                if (filter.evaluate(feature)) {
                    if (!bounds) {
                        bounds = feature.bounds.clone();
                    } else {
                        bounds.include(feature.bounds);
                    }
                }
            });

        }
        return bounds;
    },

    /** api: property[bounds]
     *  :class:`geom.Bounds`
     *  The bounds for all features on this layer.
     */
    get bounds() {
        return this.getBounds();
    },

    /** api: method[query]
     *  :arg filter: ``filter.Filter or String`` A filter or a CQL string.
     *  :returns: :class:`cursor.Cursor` A cursor for accessing queried features.
     *
     *  Query for features from the layer.  The return will be an object with
     *  ``forEach``, ``hasNext``, and ``next`` methods.  If no filter is
     *  provided, all features will be included in the results.
     *
     *  Example use:
     *
     *  .. code-block:: javascript
     *
     *      js> layer.query("name = 'foo'").forEach(function(feature) {
     *        >     print(feature.toString());
     *        > });
     */
    query: function(filter) {
        if (!filter) {
            filter = FILTER.Filter.PASS;
        } else {
            if (!(filter instanceof FILTER.Filter)) {
                // must be CQL string
                filter = new FILTER.Filter(filter);
            }
        }
        var query = new DefaultQuery(this.name, filter._filter);
        var results = this._source.dataStore.getFeatureReader(query, Transaction.AUTO_COMMIT);
        var _schema = this.schema._schema;

        var cursor = new Cursor(results, function(_feature) {
            return Feature.from_(_feature, _schema);
        });

        return cursor;
    },

    /** api: property[features]
     *  :class:`cursor.Cursor`
     *  A cursor object for accessing all features on the layer.
     *
     *
     *  Example use:
     *
     *  .. code-block:: javascript
     *
     *      js> layer.features.forEach(function(feature) {
     *        >     print(feature.toString());
     *        > });
     */
    get features() {
        return this.query();
    },

    /** api: method[add]
     *  :arg obj: ``Object`` A :class:`feature.Feature` or a feature attribute
     *      values object.
     *
     *  Add a feature to a layer.  Optionally, an object with feature attribute
     *  values may be provided.
     *
     *  Example use:
     *
     *  .. code-block:: javascript
     *
     *      js> var GEOM = require("geoscript/geom");
     *      js> layer.add({geom: new GEOM.Point([0, 1])});
     *
     */
    add: function(obj) {
        var feature;
        if (obj instanceof Feature) {
            feature = obj;
        } else {
            // has to be a values object
            feature = new Feature({schema: this.schema, values: obj});
        }
        if (this.projection) {
            if (feature.projection) {
                if (!this.projection.equals(feature.projection)) {
                    feature.geometry = PROJ.transform(
                        feature.geometry,
                        feature.projection,
                        this.projection
                    );
                }
            } else {
                feature.projection = this.projection;
            }
        }
        this.workspace._onFeatureAdd(feature);
        var collection = FeatureCollections.newCollection();
        collection.add(feature._feature);
        this._source.addFeatures(collection);
    },

    /** api: method[remove]
     *  :arg filter: :class:`filter.Filter` or ``String``
     *
     *  Remove features from a layer that match the given filter or CQL string.
     *
     *  Example use:
     *
     *  .. code-block:: javascript
     *
     *      js> var GEOM = require("geoscript/geom");
     *      js> layer.add({geom: new GEOM.Point([1, 2])});
     *      js> layer.remove("INTERSECTS(geom, POINT(1 2))");
     *
     */
    remove: function(filter) {
        if (!filter) {
            filter = FILTER.Filter.FAIL;
        }
        if (!(filter instanceof FILTER.Filter)) {
            filter = new FILTER.Filter(filter);
        }
        this._source.removeFeatures(filter._filter);
    },

    /** private: property[config]
     */
    get config() {
        var config = {
            type: "Layer",
            name: this.name
        };
        if (this.temporary) {
            config.schema = this.schema.config;
        } else {
            config.workspace = this.workspace.config;
        }
        return config;
    },

    /** api: property[json]
     *  ``String``
     *  The JSON representation of this layer.  This representation does not
     *  include members for each feature in the layer.
     */
    get json() {
        return JSON.stringify(this.config);
    },

    /** private: method[toFullString]
     */
    toFullString: function() {
        return "name: " + this.name + ", count: " + this.count;
    }

});

Layer.from_ = function(_source, workspace) {
    var layer = new Layer();
    layer._source = _source;
    layer.workspace = workspace;
    return layer;
}

/** api: example
 *  Sample code to create a temporary layer:
 *
 *  .. code-block:: javascript
 *
 *      js> var layer = new LAYER.Layer({name: "temp"});
 */

exports.Layer = Layer;

/** private: method[create]
 *  :arg config: ``Object`` Configuration object.
 *  :returns: :class:`layer.Layer`
 *
 *  Create a layer given a configuration object.
 */
var registry = new Registry();
exports.create = registry.create;

// register a layer factory for the module
registry.register(new Factory(Layer, {
    handles: function(config) {
        return true;
    }
}));
<|endoftext|>