text:
import Ember from 'ember';
import DS from 'ember-data';

import SnapshotTransform from '../utils/snapshot-transform';
import ODataQueryAdapter from '../query/odata-adapter';
import { capitalize, camelize, odataPluralize } from '../utils/string-functions';
import isUUID from '../utils/is-uuid';
import generateUniqueId from '../utils/generate-unique-id';
import { getResponseMeta, getBatchResponses, parseBatchResponse } from '../utils/batch-queries';
import Builder from '../query/builder';

const { getOwner } = Ember;

/**
 * The OData adapter class.
 * Uses Flexberry Query as a language for requesting server.
 *
 * @module ember-flexberry-data
 * @namespace Adapter
 * @class OData
 * @extends DS.RESTAdapter
 * @public
 */
export default DS.RESTAdapter.extend({
  headers: {
    'OData-Version': '4.0',
    Prefer: 'return=representation'
  },

  /**
    Timeout for AJAX-requests.

    @property timeout
    @type Number
    @default 0
  */
  timeout: 0,

  /**
    Overloaded method from `RESTAdapter` (Ember Data).
    Called by the sore in order to fetch data from the server.

    @method query
    @param {DS.Store} store
    @param {DS.Model} type
    @param {Query} query Flexberry Query object.
    @return {Promise}
  */
  query(store, type, query) {
    if (!this.store) {
      this.store = store;
    }

    let url = this._buildURL(query.modelName);
    let builder = new ODataQueryAdapter(url, store);
    let data = builder.getODataQuery(query);
    let timeout = this.get('timeout');

    if (this.sortQueryParams) {
      data = this.sortQueryParams(data);
    }

    Ember.debug(`Flexberry ODataAdapter::query '${type}'`, data);

    //f TODO: think about moving request execution into query adapter
    return this.ajax(url, 'GET', { data: data, timeout: timeout, dataType: query.dataType || 'json' });
  },

  /**
    Overloaded method from `RESTAdapter` (Ember Data).
    Customizes ajax options for the requests.

    @method ajaxOptions
    @param {String} url
    @param {DS.Model} type
    @param {Object} options
    @return {Object}
  */
  ajaxOptions: function ajaxOptions(url, type, options) {
    let dataType = options.dataType;
    let hash = this._super(...arguments);
    if (dataType === 'blob') {
      hash.dataType = dataType;
    }

    return hash;
  },

  /**
    Takes an ajax response, and returns the json payload or an error.

    @method handleResponse
    @param {Number} status
    @param {Object} headers
    @param {Object} payload
  */
  handleResponse(status, headers, payload) {
    if (!this.isSuccess(status, headers, payload) && typeof payload === 'object' && payload.error) {
      return new DS.AdapterError(payload.error.details, payload.error.message);
    }

    return this._super(...arguments);
  },

  /**
   * Overloaded method from `RESTAdapter` (Ember Data).
   * Called by the sore in order to fetch single record from the server.
   *
   * @method queryRecord
   * @param {DS.Store} store
   * @param {DS.Model} type
   * @param {Query} query
   * @return {Promise} promise
   */
  queryRecord(store, type, query) {
    Ember.debug(`Flexberry ODataAdapter::queryRecord '${type}'`, query);

    // TODO: query support for direct calls
    return this._super.apply(this, arguments);
  },

  /**
   * Overloaded method from `RESTAdapter` (Ember Data).
   * Called by the sore in order to fetch single record by ID from the server.
   *
   * @method findRecord
   * @param {DS.Store} store
   * @param {DS.Model} type
   * @param {String} id
   * @param {DS.Snapshot} snapshot
   * @return {Promise} promise
  */
  findRecord(store, type, id, snapshot) {
    Ember.debug(`Flexberry ODataAdapter::findRecord '${type}(${id})'`);

    // TODO: query support for direct calls
    return this._super.apply(this, arguments);
  },

  /**
   * Overloaded method from `RESTAdapter` (Ember Data).
   * Called by the sore in order to fetch all records from the server.
   *
   * @method findAll
   * @param {DS.Store} store
   * @param {DS.Model} type
   * @param {String} sinceToken
   * @param {DS.SnapshotRecordArray} snapshotRecordArray
   * @return {Promise} promise
   */
  findAll(store, type, sinceToken, snapshotRecordArray) {
    Ember.debug(`Flexberry ODataAdapter::findAll '${type}'`);

    // TODO: query support for direct calls
    return this._super.apply(this, arguments);
  },

  /**
   * Overloaded method from `build-url-mixin` (Ember Data), that determines the pathname for a given type.
   * Additionally capitalizes the type name (requirement of Flexberry OData Server).
   *
   * @method pathForType
   * @param {String} modelName
   * @return {String} The path for a given type.
   */
  pathForType(modelName) {
    var camelized = camelize(modelName);
    var capitalized = capitalize(camelized);
    return odataPluralize(capitalized);
  },

  /**
   * A method to make ajax requests.
   *
   * @method makeRequest
   * @param {Object} params
   * @public
   */
  makeRequest(params) {
    Ember.assert('You should specify both method and url', params.method || params.url);
    return Ember.$.ajax(params);
  },

  /**
   * A method to generate url for ajax request to odata function.
   *
   * @param {String} functionName
   * @param {Object} params
   * @param {String} url
   * @return {String}
   */
  generateFunctionUrl(functionName, params, url) {
    const config = getOwner(this)._lookupFactory('config:environment');
    if (Ember.isNone(url)) {
      url = `${config.APP.backendUrls.api}`;
    }

    let resultUrl = `${url}/${functionName}(`;
    let counter = 0;
    for (var key in params) {
      counter++;
    }

    let resultParams = {};
    if (!Ember.isNone(params)) {
      resultParams = params;
    }

    let i = 0;
    for (key in resultParams) {
      //TODO: Check types and ''
      if (typeof resultParams[key] === 'number' || isUUID(resultParams[key])) {
        resultUrl = resultUrl + `${key}=${resultParams[key]}`;
      } else {
        resultUrl = resultUrl + `${key}='${resultParams[key]}'`;
      }

      i++;
      if (i < counter) {
        resultUrl += ',';
      } else {
        resultUrl += ')';
      }
    }

    if (resultUrl[resultUrl.length - 1] !== ')') {
      resultUrl += ')';
    }

    return resultUrl;
  },

  /**
    A method to generate url for ajax request to odata action.

    @method generateActionUrl
    @param {String} actionName
    @param {Object} data
    @param {String} url
    @return {String}
  */
  generateActionUrl(actionName, data, url) {
    const config = getOwner(this)._lookupFactory('config:environment');
    if (Ember.isNone(url)) {
      url = `${config.APP.backendUrls.api}`;
    }

    const resultUrl = `${url}/${actionName}`;

    return resultUrl;
  },

  /**
    A method to call OData functions that returns model records using ajax request.

    @method callEmberOdataFunction
    @param {Object} args Method arguments.
    @param {Object} args.functionName OData functioin name (required).
    @param {Object} args.params OData function parameters.
    @param {String} args.url Backend url.
    @param {Object} args.fields Request's xhrFields.
    @param {Object} args.store Ember's store.
    @param {String} args.modelName Model name.
    @param {String} args.modelProjection Model projection.
    @param {Function} args.successCallback Success callback.
    @param {Function} args.failCallback Fail callback.
    @param {Function} args.alwaysCallback Always callback.
    @return {Promise}
  */
  callEmberOdataFunction(args) {
    Ember.deprecate('callEmberOdataFunction is deprecated. Use callFunction with single args argument instead.', false, {
      id: 'adapter.odata.callEmberOdataFunction',
      until: '3.5.0',
    });

    if (arguments.length > 1 || typeof args !== 'object') {
      args = this._getODataArgs(arguments, true);
    }

    return this.callFunction(args);
  },

  /**
    A method to call OData functions using ajax request.

    @method callFunction
    @param {Object} args Method arguments.
    @param {Object} args.functionName OData functioin name (required).
    @param {Object} args.params OData function parameters.
    @param {String} args.url Backend url.
    @param {Object} args.fields Request's xhrFields.
    @param {Object} args.store Ember's store, needed when results are ember models.
    @param {String} args.modelName Model name, needed when results are ember models.
    @param {String} args.modelProjection Model projection, needed when results are ember models.
    @param {Function} args.successCallback Success callback.
    @param {Function} args.failCallback Fail callback.
    @param {Function} args.alwaysCallback Always callback.
    @return {Promise}
  */
  callFunction(args) {
    if (arguments.length > 1 || typeof args !== 'object') {
      args = this._getODataArgs(arguments);
    }

    if (args.modelProjection && args.modelName && args.store) {
      const builder = new Builder(args.store, args.modelName).selectByProjection(args.modelProjection);
      args.queryParams = builder.build();
    }

    let resultUrl = this.generateFunctionUrl(args.functionName, args.params, args.url);

    if (args.queryParams && args.store) {
      const queryParamsForUrl = this._generateFunctionQueryString(args.queryParams, args.store);
      resultUrl += queryParamsForUrl;
    }

    const headers = Ember.$.extend({}, this.get('headers'));

    return this._callAjax(
      { url: resultUrl, method: 'GET', headers, xhrFields: args.fields ? args.fields : {} },
      args.store, args.modelName, args.successCallback, args.failCallback, args.alwaysCallback);
  },

  /**
    A method to call OData actions that returns model records using ajax request.

    @method callEmberOdataAction
    @param {Object} args Method arguments.
    @param {Object} args.actionName OData action name (required).
    @param {Object} args.data OData action data.
    @param {String} args.url Backend url.
    @param {Object} args.fields Request's xhrFields.
    @param {Object} args.store Ember's store.
    @param {String} args.modelName Model name.
    @param {Function} args.successCallback Success callback.
    @param {Function} args.failCallback Fail callback.
    @param {Function} args.alwaysCallback Always callback.
    @return {Promise}
  */
  callEmberOdataAction(args) {
    Ember.deprecate('callEmberOdataAction is deprecated. Use callAction with single args argument instead.', false, {
      id: 'adapter.odata.callEmberOdataAction',
      until: '3.5.0',
    });

    if (arguments.length > 1 || typeof args !== 'object') {
      args = this._getODataArgs(arguments, true, true);
    }

    return this.callAction(args);
  },

  /**
    A method to call OData actions using ajax request.

    @method callAction
    @param {Object} args Method arguments.
    @param {Object} args.actionName OData action name (required).
    @param {Object} args.data OData action data.
    @param {String} args.url Backend url.
    @param {Object} args.fields Request's xhrFields.
    @param {Object} args.store Ember's store, needed when results are ember models.
    @param {String} args.modelName Model name, needed when results are ember models.
    @param {Function} args.successCallback Success callback.
    @param {Function} args.failCallback Fail callback.
    @param {Function} args.alwaysCallback Always callback.
    @return {Promise}
  */
  callAction(args) {
    if (arguments.length > 1 || typeof args !== 'object') {
      args = this._getODataArgs(arguments, false, true);
    }

    const resultUrl = this.generateActionUrl(args.actionName, args.data, args.url);
    const headers = Ember.$.extend({}, this.get('headers'));

    return this._callAjax(
      {
        data: JSON.stringify(args.data || {}),
        url: resultUrl,
        method: 'POST',
        contentType: 'application/json; charset=utf-8',
        dataType: 'json',
        headers,
        xhrFields: args.fields ? args.fields : {}
      }, args.store, args.modelName, args.successCallback, args.failCallback, args.alwaysCallback);
  },

  /**
    A method to send batch update, create or delete models in single transaction.

    All models saving using this method must have identifiers.

    The array which fulfilled the promise may contain the following values:
    - `same model object` - for created, updated or unaltered records.
    - `null` - for deleted records.

    @method batchUpdate
    @param {DS.Store} store The store.
    @param {DS.Model[]|DS.Model} models Is array of models or single model for batch update.
    @return {Promise} A promise that fulfilled with an array of models in the new state.
  */
  batchUpdate(store, models) {
    if (Ember.isEmpty(models)) {
      return Ember.RSVP.resolve(models);
    }

    models = Ember.isArray(models) ? models : Ember.A([models]);

    return Ember.RSVP.all(models.map((m) => m.save({ softSave: true }))).then(() => {
      const boundary = `batch_${generateUniqueId()}`;

      let requestBody = '--' + boundary + '\r\n';

      const changeSetBoundary = `changeset_${generateUniqueId()}`;
      requestBody += 'Content-Type: multipart/mixed;boundary=' + changeSetBoundary + '\r\n\r\n';

      let contentId = 0;
      const getQueries = [];
      models.forEach((model) => {
        if (!model.get('id')) {
          throw new Error(`Models saved using the 'batchUpdate' method must be created with identifiers.`);
        }

        let modelDirtyType = model.get('dirtyType');

        if (!modelDirtyType) {
          if (model.hasChangedBelongsTo() || model.hasChangedHasMany()) {
            modelDirtyType = 'updated';
          } else {
            return;
          }
        }

        requestBody += '--' + changeSetBoundary + '\r\n';
        requestBody += 'Content-Type: application/http\r\n';
        requestBody += 'Content-Transfer-Encoding: binary\r\n';

        contentId++;
        requestBody += 'Content-ID: ' + contentId + '\r\n\r\n';

        const skipUnchangedAttrs = true;
        const snapshot = model._createSnapshot();
        SnapshotTransform.transformForSerialize(snapshot, skipUnchangedAttrs);

        let modelHttpMethod = 'POST';
        switch (modelDirtyType) {
          case 'created':
            modelHttpMethod = 'POST';
            break;

          case 'updated':
            modelHttpMethod = skipUnchangedAttrs ? 'PATCH' : 'PUT';
            break;

          case 'deleted':
            modelHttpMethod = 'DELETE';
            break;

          default:
            throw new Error(`Unknown requestType: ${modelDirtyType}`);
        }

        if (!this.store) {
          this.store = store;
        }

        const modelUrl =  this._buildURL(snapshot.type.modelName, modelDirtyType === 'created' ? undefined : model.get('id'));

        requestBody += modelHttpMethod + ' ' + modelUrl + ' HTTP/1.1\r\n';
        requestBody += 'Content-Type: application/json;type=entry\r\n';
        requestBody += 'Prefer: return=representation\r\n\r\n';

        // Don't need to send any data for deleting.
        if (modelDirtyType !== 'deleted') {
          const serializer = store.serializerFor(snapshot.type.modelName);
          const data = {};
          serializer.serializeIntoHash(data, snapshot.type, snapshot);
          requestBody += JSON.stringify(data) + '\r\n';

          // Add a GET request for created or updated models.
          let getQuery = '\r\n--' + boundary + '\r\n';
          getQuery += 'Content-Type: application/http\r\n';
          getQuery += 'Content-Transfer-Encoding: binary\r\n';

          const relationships = [];
          model.eachRelationship((name) => {
            // If attr serializable value hadn't been set up, it'll be { serialize: 'id' } by default from DS.EmbeddedRecordsMixin.
            let attrSerializeVal =
              serializer && serializer.attrs && serializer.attrs[name] && serializer.attrs[name].serialize;

            if (attrSerializeVal !== false) {
              relationships.push(`${name}.id`);
            }
          });

          const getUrl = this._buildURL(snapshot.type.modelName, model.get('id'));

          let expand;
          if (relationships.length) {
            const query = new Builder(store, snapshot.type.modelName).select(relationships.join(',')).build();
            const queryAdapter = new ODataQueryAdapter(getUrl, store);
            expand = queryAdapter.getODataQuery(query).$expand;
          }

          getQuery += '\r\nGET ' + getUrl + (expand ? '?$expand=' + expand : '') + ' HTTP/1.1\r\n';
          getQuery += 'Content-Type: application/json;type=entry\r\n';
          getQuery += 'Prefer: return=representation\r\n';
          getQueries.push(getQuery);
        }
      });

      requestBody += '--' + changeSetBoundary + '--';
      requestBody += getQueries.join('');
      requestBody += '\r\n--' + boundary + '--';

      const url = `${this._buildURL()}/$batch`;

      const headers = Ember.$.extend({}, this.get('headers'));
      headers['Content-Type'] = `multipart/mixed;boundary=${boundary}`;
      const httpMethod = 'POST';

      const options = {
        method: httpMethod,
        headers,
        dataType: 'text',
        data: requestBody,
      };

      return new Ember.RSVP.Promise((resolve, reject) => Ember.$.ajax(url, options).done((response, statusText, xhr) => {
        const meta = getResponseMeta(xhr.getResponseHeader('Content-Type'));
        if (meta.contentType !== 'multipart/mixed') {
          return reject(new DS.AdapterError('Invalid response type.'));
        }

        const batchResponses = getBatchResponses(response, meta.boundary).map(parseBatchResponse);
        const getResponses = batchResponses.filter(r => r.contentType === 'application/http');
        const updateResponse = batchResponses.find(r => r.contentType === 'multipart/mixed');

        const errorsChangesets = updateResponse.changesets.filter(c => !this.isSuccess(c.meta.status));
        if (errorsChangesets.length) {
          return reject(errorsChangesets.map(c => new DS.AdapterError(c.body)));
        }

        const errors = [];
        const result = [];
        models.forEach((model) => {
          const modelDirtyType = model.get('dirtyType');
          if (modelDirtyType === 'deleted') {
            Ember.run(store, store.unloadRecord, model);
          } else if (modelDirtyType === 'created' || modelDirtyType === 'updated' || model.hasChangedBelongsTo()) {
            const { response } = getResponses.shift();
            if (this.isSuccess(response.meta.status)) {
              const modelName = model.constructor.modelName;
              const payload = { [modelName]: response.body };
              Ember.run(() => {
                store.pushPayload(modelName, payload);
                model.rollbackAttributes(); // forced adjustment of model state
              });
            } else {
              errors.push(new DS.AdapterError(response.body));
            }
          }

          result.push(modelDirtyType === 'deleted' ? null : model);
        });

        return errors.length ? reject(errors) : resolve(result);
      }).fail(reject));
    });
  },

  /**
    A method to generate url part from queryParams.

    @method _generateFunctionQueryString
    @param {Object} queryParams
    @param {DS.Store} store
    @return {String}
    @private
  */
  _generateFunctionQueryString(queryParams, store) {
    const adapter = new ODataQueryAdapter('1', store);
    let queryUrlString = '';

    if (queryParams.expand || queryParams.select) {
      queryUrlString = adapter.getODataFunctionQuery(queryParams);
    }

    return queryUrlString;
  },

  /**
    A method to make args object function/action call method's.

    @method _getODataArgs
    @param {Object} params
    @param {Boolean} isEmber
    @param {Boolean} isAction
    @return {Object}
    @private
  */
  _getODataArgs(params, isEmber, isAction) {
    const paramsLastIndex = isEmber ? 8 : 6;
    let args = {};
    args[isAction ? 'actionName' : 'functionName'] = params[0];
    args[isAction ? 'data' : 'params'] = params[1];
    args.url = params[2];
    args.fields = params[3];
    if (isEmber) {
      args.store = params[4];
      args.modelName = params[5];
    }

    args.successCallback = params[paramsLastIndex - 2];
    args.failCallback = params[paramsLastIndex - 1];
    args.alwaysCallback = params[paramsLastIndex];

    return args;
  },

  /**
   * A method to make ajax requests.
   *
   * @param {Object} params
   * @param {Store} store
   * @param {String} modelname
   * @param {Function} successCallback
   * @param {Function} failCallback
   * @param {Function} alwaysCallback
   * @return {Promise}
   * @private
   */
  _callAjax(params, store, modelname, successCallback, failCallback, alwaysCallback) {
    Ember.assert('Params must be Object!', typeof params === 'object');
    Ember.assert('params.method or params.url is not defined.', !(Ember.isNone(params.method) || Ember.isNone(params.url)));

    return new Ember.RSVP.Promise(function (resolve, reject) {
      Ember.$.ajax(params).done((msg) => {
        if (!Ember.isNone(store) && !Ember.isNone(modelname)) {
          const normalizedRecords = { data: Ember.A(), included: Ember.A() };
          Object.values(msg.value).forEach(record => {
            const normalized = store.normalize(modelname, record);
            normalizedRecords.data.addObject(normalized.data);
            if (normalized.included) {
              normalizedRecords.included.addObjects(normalized.included);
            }
          });
          Ember.run.join(() => { msg = store.push(normalizedRecords); });
        }

        if (!Ember.isNone(successCallback)) {
          if (typeof successCallback.then === 'function') {
            if (!Ember.isNone(alwaysCallback)) {
              if (typeof alwaysCallback.then === 'function') {
                successCallback(msg).then(() => { alwaysCallback(msg).then(resolve(msg)); });
              } else {
                successCallback(msg).then(alwaysCallback(msg)).then(resolve(msg));
              }
            } else {
              successCallback(msg).then(resolve(msg));
            }
          } else {
            successCallback(msg);
            if (!Ember.isNone(alwaysCallback)) {
              if (typeof alwaysCallback.then === 'function') {
                alwaysCallback(msg).then(resolve(msg));
              } else {
                alwaysCallback(msg);
                resolve(msg);
              }
            } else {
              resolve(msg);
            }
          }
        } else {
          if (!Ember.isNone(alwaysCallback)) {
            if (typeof alwaysCallback.then === 'function') {
              alwaysCallback(msg).then(resolve(msg));
            } else {
              alwaysCallback(msg);
              resolve(msg);
            }
          } else {
            resolve(msg);
          }
        }
      }).fail((msg) => {
        if (!Ember.isNone(failCallback)) {
          if (typeof failCallback.then === 'function') {
            if (!Ember.isNone(alwaysCallback)) {
              if (typeof alwaysCallback.then === 'function') {
                failCallback(msg).then(() => { alwaysCallback(msg).then(reject(msg)); });
              } else {
                failCallback(msg).then(alwaysCallback(msg)).then(reject(msg));
              }

            } else {
              failCallback(msg).then(reject(msg));
            }

          } else {
            failCallback(msg);
            if (!Ember.isNone(alwaysCallback)) {
              if (typeof alwaysCallback === 'function') {
                alwaysCallback(msg).then(reject(msg));
              } else {
                alwaysCallback(msg);
                reject(msg);
              }
            } else {
              reject(msg);
            }
          }
        } else {
          if (!Ember.isNone(alwaysCallback)) {
            if (typeof alwaysCallback.then === 'function') {
              alwaysCallback(msg).then(reject(msg));
            } else {
              alwaysCallback(msg);
              reject(msg);
            }
          } else {
            reject(msg);
          }
        }
      });
    });
  },

  /**
   * Overloaded method from `build-url-mixin` (Ember Data), taht builds URL to OData feed.
   * Appends id as `(id)` (OData specification) instead of `/id`.
   *
   * @method _buildURL
   * @param {String} modelName
   * @param {String} id
   * @return {String}
   * @private
   */
  _buildURL(modelName, id) {
    var url = [];
    var host = Ember.get(this, 'host');
    var prefix = this.urlPrefix();
    var path;

    if (modelName) {
      path = this.pathForType(modelName);
      if (path) {
        url.push(path);
      }
    }

    if (prefix) {
      url.unshift(prefix);
    }

    url = url.join('/');
    if (!host && url && url.charAt(0) !== '/') {
      url = '/' + url;
    }

    if (id != null) {
      // Append id as `(id)` (OData specification) instead of `/id`.
      url = this._appendIdToURL(id, url, modelName);
    }

    return url;
  },

  /**
   * Appends id to URL according to the OData specification.
   *
   * @method _appendIdToURL
   * @param {String} id
   * @param {String} url
   * @private
   */
  _appendIdToURL(id, url, modelName) {
    let encId = encodeURIComponent(id);

    if (!this.store) {
      throw new Error('No store.');
    }

    let model = this.store.modelFor(modelName);
    if (model.idType === 'string') {
      encId = `'${encId}'`;
    }

    url += '(' + encId + ')';
    return url;
  },

  createRecord(store, type, snapshot) {
    return this._sendRecord(store, type, snapshot, 'createRecord');
  },

  updateRecord(store, type, snapshot) {
    return this._sendRecord(store, type, snapshot, 'updateRecord');
  },

  deleteRecord(store, type, snapshot) {
    return this._sendRecord(store, type, snapshot, 'deleteRecord');
  },

  deleteAllRecords(store, modelName, filter) {
    if (!this.store) {
      this.store = store;
    }

    let url = this._buildURL(modelName);
    let pathName = this.pathForType(modelName);
    let builder = new ODataQueryAdapter(url, store);
    let filterVelue = builder._buildODataFilters(filter);
    let filterQuery = !Ember.isNone(filterVelue) ? '$filter=' + filterVelue : '';
    let data = { pathName: pathName, filterQuery: filterQuery };

    return this.callAction('DeleteAllSelect', data, null, { withCredentials: true });
  },

  /**
   * Makes HTTP request for creating, updating or deleting the record.
   *
   * @method _sendRecord
   * @private
   */
  _sendRecord(store, type, snapshot, requestType) {
    // TODO: maybe move it into serializer (serialize or serializeIntoHash)?
    let skipUnchangedAttrs = true;
    SnapshotTransform.transformForSerialize(snapshot, skipUnchangedAttrs);

    // NOTE: for newly created records id is not defined.
    let url = this.buildURL(type.modelName, snapshot.id, snapshot, requestType);

    let httpMethod;
    switch (requestType) {
      case 'createRecord':
        httpMethod = 'POST';
        break;

      case 'updateRecord':
        httpMethod = skipUnchangedAttrs ? 'PATCH' : 'PUT';
        break;

      case 'deleteRecord':
        httpMethod = 'DELETE';
        break;

      default:
        throw new Error(`Unknown requestType: ${requestType}`);
    }

    let data;

    // Don't need to send any data for deleting.
    if (requestType !== 'deleteRecord') {
      let serializer = store.serializerFor(type.modelName);
      data = {};
      serializer.serializeIntoHash(data, type, snapshot);
    }

    return this.ajax(url, httpMethod, { data: data }).then(function (response) {
      return response;
    });
  }
});


text:
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreatePortServicesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('port_services', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
            $table->integer('port_id')->unsigned();
            $table->string('service');
            $table->string('mac_address');

            $table->foreign('port_id')
                ->references('id')
                ->on('ports')
                ->onDelete('cascade');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('port_services');
    }
}

redpajama_set_name: RedPajamaGithub


text:
package edu.berkeley.compbio.sequtils.strings;

import com.davidsoergel.dsutils.AbstractGenericFactoryAware;
import com.davidsoergel.dsutils.DSArrayUtils;
import com.davidsoergel.dsutils.LabellableImpl;
import com.davidsoergel.dsutils.collections.MutableWeightedSet;
import com.davidsoergel.dsutils.collections.WeightedSet;
import com.davidsoergel.dsutils.math.MathUtils;
import com.davidsoergel.stats.DistributionException;
import com.davidsoergel.stats.Multinomial;
import com.davidsoergel.stats.MutableDistribution;
import edu.berkeley.compbio.sequtils.FilterException;
import edu.berkeley.compbio.sequtils.NotEnoughSequenceException;
import edu.berkeley.compbio.sequtils.SequenceReader;
import org.apache.commons.collections.buffer.CircularFifoBuffer;
import org.apache.commons.lang.NotImplementedException;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Formatter;
import java.util.List;
import java.util.Locale;

/**
 * A node in a tree where each transition is labeled by a byte and has a local probability (that is, conditional on the
 * parent node).  If the tree is completely filled out with respect to its alphabet up to a depth k, then it represents
 * a k-order Markov model.  If it is incompletely filled out, it may represent a Variable Memory Markov model (VMM).
 *
 * @author David Soergel
 * @version $Id: MarkovTreeNode.java 519 2009-10-08 07:01:28Z soergel $
 * @see RonPSANode
 *

 *      This class should not be used to represent a Probabilistic Suffix Tree, because that is likely to be confusing.
 */
public class MarkovTreeNode extends AbstractGenericFactoryAware
		implements SequenceSpectrum<MarkovTreeNode>, MutableDistribution
	{
// ------------------------------ FIELDS ------------------------------

	private LabellableImpl<String> labels = new LabellableImpl<String>();

	public void doneLabelling()
		{
		labels.doneLabelling();
		}

	@NotNull
	public WeightedSet<String> getImmutableWeightedLabels()
		{
		return labels.getImmutableWeightedLabels();
		}

	@NotNull
	public MutableWeightedSet<String> getMutableWeightedLabels()
		{
		return labels.getMutableWeightedLabels();
		}

	public int getItemCount()
		{
		return labels.getItemCount();
		}

	private static final Logger logger = Logger.getLogger(MarkovTreeNode.class);
	protected byte[] id;
	protected byte[] alphabet;

	// use the alphabet to get indexes into the child array, instead of the HashM'p, for efficiency (no autoboxing, etc)

	//	private Map<Byte, MarkovTreeNode> children = new HashMap<Byte, MarkovTreeNode>();

	// includes only the children, with nulls where the tree does not continue
	protected MarkovTreeNode[] children;

	protected Multinomial<Byte> probs = new Multinomial<Byte>();

	protected boolean leaf = true;
	private double[] logprobs;

	private String label;

//	private final MutableWeightedSet<String> weightedLabels = new ConcurrentHashWeightedSet<String>();

	/**
	 * Returns the maximum length of substrings considered in computing this statistical model of the sequence.  Our
	 * implicit assumption is that the sequences being modeled have some correlation length, and thus that statistical
	 * models of them can be built from substrings up to that length.  Thus, this method tells the maximum correlation
	 * length provided by the model.  A manifestation of this is that conditional probabilities of symbols given a prefix
	 * will cease to change as the prefix is lengthened (to the left) past this length.
	 *
	 * @return the maximum correlation length considered in the model.
	 */
	/*	public int getMaxDepth()
	   {
	   // inefficient; could be cached

	   int result = 0;
	   if (probs.size() > 0)
		   {
		   result = 1;
		   }
	   for (MarkovTreeNode child : children)//.values())
		   {
		   if (child != null)
			   {
			   result = Math.max(result, child.getMaxDepth() + 1);
			   }
		   }
	   return result;
	   }*/

	// diagnostics
	private int total = 0, leaves = 0, maxdepth = 0;

	private double avgdepth = 0;

	private long originalSequenceLength;
	private long unknownCount;

// --------------------------- CONSTRUCTORS ---------------------------

	public MarkovTreeNode()
		{
		}

	/**
	 * Constructs a new MarkovTreeNode with the given identifier
	 *
	 * @param id the sequence of symbols leading to this node
	 */
	public MarkovTreeNode(final byte[] id, final byte[] alphabet)
		{
		this.id = id;
		setAlphabet(alphabet);
		}

	public void setAlphabet(final byte[] alphabet)
		{
		this.alphabet = alphabet;
		logprobs = new double[alphabet.length];
		children = new MarkovTreeNode[alphabet.length];
		}

// --------------------- GETTER / SETTER METHODS ---------------------

	/**
	 * Returns the alphabet of this SequenceSpectrum object.
	 *
	 * @return the alphabet (type byte[]) of this SequenceSpectrum object.
	 */
	public byte[] getAlphabet()
		{
		return alphabet;
		}

	public MarkovTreeNode[] getChildren()
		{
		return children;
		}

	/**
	 * Returns the number of samples on which this spectrum is based.
	 *
	 * @return The number of samples
	 */
	/*	public int getNumberOfSamples()
		 {
		 throw new NotImplementedException();
		 }
*/
	public long getOriginalSequenceLength()
		{
		return originalSequenceLength;
		}

	public void setOriginalSequenceLength(final long originalSequenceLength)
		{
		this.originalSequenceLength = originalSequenceLength;
		}

	public long getUnknownCount()
		{
		return unknownCount;
		}

	public void setUnknownCount(final long unknownCount)
		{
		this.unknownCount = unknownCount;
		}

	/**
	 * Returns a mapping of symbols to child nodes, describing the possible transitions from this node to its children.
	 * Does not provide any information about the probabilities associated with those transitions.
	 *
	 * @return a Map<Byte, MarkovTreeNode> describing the possible transitions from this node to its children.
	 */
	/*	public Map<Byte, MarkovTreeNode> getChildren()
	   {
	   return children;
	   }*/
	public Multinomial<Byte> getProbs()
		{
		return probs;
		}

	public boolean isLeaf()
		{
		return leaf;
		}

	public void setId(final byte[] id)
		{
		this.id = id;
		}

	public void setLabel(final String label)
		{
		this.label = label;
		}

// ------------------------ CANONICAL METHODS ------------------------

	/**
	 * Performs a deep copy of this node.
	 *
	 * @return a copy of this node, including a deep copy of its children and their probabilities
	 */
	@Override
	public MarkovTreeNode clone()
		{
		try
			{
			final MarkovTreeNode result = (MarkovTreeNode) super.clone();//new MarkovTreeNode(id, alphabet);
			result.setAlphabet(alphabet);
			result.probs = probs.clone();
			for (final byte b : alphabet)//children.keySet())
				{
				final MarkovTreeNode child = getChild(b);
				if (child != null)
					{
					result.addChild(b, child.clone());
					}
				}
			return result;
			}
		catch (CloneNotSupportedException e)
			{
			logger.error("Error", e);
			throw new Error("Impossible");
			}
		/*		try
					   {
					   result.setProb(b, probs.get(b));
					   }
				   catch (DistributionException e)
					   {
					   // no problem, just leave it empty then
					   }*/

		/*	try
				   {
				   result.normalize();
				   }
			   catch (DistributionException e)
				   {
				   throw new SequenceSpectrumRuntimeException(e);
				   }*/
		}

	/**
	 * Gets the child node associated with the given sequence, if it exists
	 *
	 * @param sigma the symbol to follow from this node
	 * @return the node pointed to, or null if that leaf does not exist
	 */
	public MarkovTreeNode getChild(final byte sigma)//throws SequenceSpectrumException
		{
		//return children == null ? null :
		return children[DSArrayUtils.indexOf(alphabet, sigma)];
		}

	private void addChild(final byte b, final MarkovTreeNode child)
		{
		leaf = false;
		final int childIndex = DSArrayUtils.indexOf(alphabet, b);
		children[childIndex] = child;
		}

// ------------------------ INTERFACE METHODS ------------------------

// --------------------- Interface AdditiveClusterable ---------------------

	public void decrementBy(final MarkovTreeNode object)
		{
		throw new NotImplementedException();
		}

	public void decrementByWeighted(final MarkovTreeNode object, final double weight)
		{
		throw new NotImplementedException();
		}

	public void incrementBy(final MarkovTreeNode object)
		{
		throw new NotImplementedException();
		}

	public void incrementByWeighted(final MarkovTreeNode object, final double weight)
		{
		throw new NotImplementedException();
		}

	public MarkovTreeNode minus(final MarkovTreeNode object)
		{
		throw new NotImplementedException();
		}

	public void multiplyBy(final double v)
		{
		throw new NotImplementedException();
		}

	public MarkovTreeNode plus(final MarkovTreeNode object)
		{
		throw new NotImplementedException();
		}

	public MarkovTreeNode times(final double v)
		{
		throw new NotImplementedException();
		}

// --------------------- Interface Clusterable ---------------------

	/**
	 * Recursively tests the deep equality of two PSTNodes.  Requires that the id, the alphabet, the tree structure, and
	 * the transition probabilities are identical.
	 *
	 * @param other the MarkovTreeNode to compare
	 * @return true if the trees have the same structure and transition probabilities, false otherwise
	 */
	public boolean equalValue(final MarkovTreeNode other)
		{
		if (!Arrays.equals(id, other.id))
			{
			return false;
			}
		if (!Arrays.equals(alphabet, other.alphabet))
			{
			return false;
			}
		/*	if (children.size() != other.children.size())
		   {
		   return false;
		   }*/
		for (final byte b : alphabet)//children.keySet())
			{
			double v = 0;
			int unknown = 0;
			try
				{
				v = probs.get(b);
				}
			catch (DistributionException e)
				{
				unknown++;
				}
			double v1 = 0;
			try
				{
				v1 = other.probs.get(b);
				}
			catch (DistributionException e1)
				{
				unknown++;
				}
			if (unknown == 1)
				{
				return false;
				}
			if (unknown == 0 && !MathUtils.equalWithinFPError(v, v1))
				{
				return false;
				}
			//MarkovTreeNode node = null;
			//MarkovTreeNode node1 = null;
			//	try
			//		{
			final MarkovTreeNode node = getChild(b);
			final MarkovTreeNode node1 = other.getChild(b);
			//		}
			//	catch (SequenceSpectrumException e)
			//		{
			//		throw new Error("Impossible");
			//		}
			if (node != null && node1 == null)
				{
				return false;
				}
			if (node == null && node1 != null)
				{
				return false;
				}
			if (node != null && node1 != null && !node.equalValue(node1))
				{
				return false;
				}
			}
		return true;
		}

	/**
	 * Returns a String representing the sequence of symbols leading to this node in the tree. The id is internally a
	 * byte[]; we just make a String out of it for the sake of the interface.
	 *
	 * @return a unique identifier for this object
	 */
	public String getId()
		{
		return new String(id);
		//throw new NotImplementedException();
		}

// --------------------- Interface SequenceSpectrum ---------------------

	/**
	 * Computes the conditional probability distribution of symbols given a prefix under the model.
	 *
	 * @param prefix a byte array providing the conditioning prefix
	 * @return the Multinomial conditional distribution of symbols following the given prefix
	 * @throws edu.berkeley.compbio.sequtils.strings.SequenceSpectrumException
	 *          when anything goes wrong
	 */
	public Multinomial<Byte> conditionalsFrom(final byte[] prefix) throws SequenceSpectrumException
		{
		final MarkovTreeNode node = get(prefix);
		if (node == null || node.probs == null)
			{
			throw new SequenceSpectrumException("Unknown probabilities at " + Arrays.toString(prefix));
			}
		return node.probs;
		}

	/**
	 * Computes the total log probability of generating the given sequence fragment under the model.  This differs from
	 * {@link #totalProbability(byte[])} in that the sequence fragment is not given explicitly but only as metadata.  Thus
	 * its probability may be computed from summary statistics that are already available in the given SequenceFragment
	 * rather than from the raw sequence.  Also, because these probabilities are typically very small, the result is
	 * returned in log space (indeed implementations will likely compute them in log space).
	 *
	 * @param sequenceFragment the SequenceFragment whose probability is to be computed
	 * @return the natural logarithm of the conditional probability (a double value between 0 and 1, inclusive)
	 */
	public double fragmentLogProbability(final SequenceFragment sequenceFragment, final boolean perSample)
			throws SequenceSpectrumException
		{
		// the RonPSA implementation uses backlinks and so is vastly more efficient.
		// We can't use backlinks here because they might point to nodes outside of this subtree

		synchronized (sequenceFragment.getReaderForSynchronizing())  // because of resetting the reader
			{
			final SequenceReader in;
			try
				{
				in = sequenceFragment.getResetReader();
				}
			catch (NotEnoughSequenceException e)
				{
				throw new SequenceSpectrumRuntimeException(e);
				}
			final int requiredPrefixLength = getMaxDepth() - 1;
			double logprob = 0;
			final CircularFifoBuffer prefix = new CircularFifoBuffer(requiredPrefixLength);

			int samples = 0;
			while (true)
				{
				try
					{
					final byte c = in.read();

					try
						{
						// PERF converting array prefix from circularFifoBuffer to byte[] is terribly inefficient
						final byte[] prefixAsBytes =
								DSArrayUtils.toPrimitive((Byte[]) prefix.toArray(new Byte[prefix.size()]));

						// these log probabilities could be cached, e.g. logConditionalProbability(c, prefix)
						logprob += MathUtils.approximateLog(conditionalProbability(c, prefixAsBytes));

						samples++;

						prefix.add(c);
						}
					catch (SequenceSpectrumException e)
						{
						// probably just an invalid character
						logger.debug("Invalid character " + (char) c);
						// ignore this character as far as the probability is concerned
						prefix.clear();
						}
					}
				catch (NotEnoughSequenceException e)
					{
					break;
					}
				catch (IOException e)
					{
					logger.error("Error", e);
					throw new SequenceSpectrumException(e);
					}
				catch (FilterException e)
					{
					logger.error("Error", e);
					throw new SequenceSpectrumException(e);
					}
				}

			if (perSample)
				{
				// we have ln(product(p) == sum(ln(p)).
				// The geometric mean is exp(sum(ln(p))/n), so to get ln(geometric mean) we need only divide by n.
				logprob /= samples;
				}

			return logprob;
			}
		}

	/**
	 * Careful-- this gets cached on the first call so will be invalid later if the tree is changed
	 *
	 * @return
	 */
	public int getMaxDepth()
		{
		if (maxdepth == 0)  // not computed yet;  node has a depth of at least 1
			{
			diagnostics();
			}
		return maxdepth;
		}

	/**
	 * Chooses a random symbol according to the conditional probabilities of symbols following the given prefix.  Shortcut
	 * equivalent to conditionalsFrom(prefix).sample().byteValue()
	 *
	 * @param prefix a byte array providing the conditioning prefix
	 * @return the chosen symbol
	 */
	public byte sample(final byte[] prefix) throws SequenceSpectrumRuntimeException
		{
		try
			{
			return conditionalsFrom(prefix).sample();
			//return probs.sample();
			}
		catch (SequenceSpectrumException e)
			{
			return sample(DSArrayUtils.suffix(prefix, 1));
			/*
						logger.error("Error", e);
						throw new SequenceSpectrumRuntimeException(e);*/
			}
		catch (DistributionException e)
			{
			logger.error("Error", e);
			throw new SequenceSpectrumRuntimeException(e);
			}
		}

	/**
	 * Chooses a random string according to the conditional probabilities of symbols.
	 *
	 * @param length the length of the desired random string
	 * @return a byte[] of the desired length sampled from this distribution
	 */
	public byte[] sample(final int length) throws SequenceSpectrumException
		{
		throw new NotImplementedException();
		/*
			 byte[] result = new byte[length];
			 for (int i = 0; i < length; i++)
				 {

				 }*/
		}

	/*
	 public void runBeginTrainingProcessor() throws DistributionProcessorException
		 {
		 // do nothing
		 }

	 public void runFinishTrainingProcessor() throws DistributionProcessorException
		 {
		 // do nothing
		 }
 */
	public void setIgnoreEdges(final boolean ignoreEdges)
		{
		// not relevant here...
		}

	// note that the probabilities should exist even if there are no corresponding child nodes!

	// There is no point in having a node with no associated probabilities, except temporarily

	// during the learning process before the probabilities have been filled in.

	// avoid holding a parent link; we don't need it so far

	public void setImmutable()
		{
		// not relevant here
		}

	/**
	 * Test whether the given sequence statistics are equivalent to this one.  Differs from equals() in that
	 * implementations of this interface may contain additional state which make them not strictly equal; here we're only
	 * interested in whether they're equal as far as this interface is concerned.
	 *

	 * Naive implementations will simply test for exact equality; more sophisticated implementations ought to use a more
	 * rigorous idea of "statistically equivalent", though in that case we'll probabably need to provide more parameters,
	 * such as a p-value threshold to use.  Note that the spectra know the number of samples used to generate them, so at
	 * least that's covered.
	 *
	 * @param spectrum the SequenceSpectrum to compare
	 * @return True if the spectra are equivalent, false otherwise
	 */
	public boolean spectrumEquals(final SequenceSpectrum spectrum)
		{
		throw new NotImplementedException();
		}

// -------------------------- OTHER METHODS --------------------------

	/**
	 * Gets the child node associated with the given sequence, creating it (and nodes along the way) as needed
	 *
	 * @param prefix the sequence of symbols to follow from this node
	 * @return the node at the end of the chain of transitions
	 */
	public MarkovTreeNode add(final byte[] prefix) throws SequenceSpectrumException
		{
		if (prefix.length == 0)
			{
			// this should probably never occur
			return this;
			}
		else if (prefix.length == 1)
			{
			return addChild(prefix[0]);
			}
		else if (prefix.length >= 1)
			{
			return addChild(prefix[0]).add(DSArrayUtils.suffix(prefix, 1));
			}
		throw new Error("Impossible");
		}

	/**
	 * Gets the child node associated with the given symbol, creating it first if needed.
	 *
	 * @param sigma the transition to follow from this node
	 * @return the node at the other end of the transition
	 */
	public MarkovTreeNode addChild(final byte sigma)
		{
		leaf = false;
		final int index = DSArrayUtils.indexOf(alphabet, sigma);
		MarkovTreeNode result = children[index];
		if (result == null)
			{
			result = new MarkovTreeNode(DSArrayUtils.append(id, sigma), alphabet);
			children[index] = result;
			}
		return result;
		}

	public void addPseudocounts()
		{
		throw new NotImplementedException();
		}

	public double conditionalProbability(final byte sigma) throws SequenceSpectrumException
		{
		try
			{
			return probs.get(sigma);
			}
		catch (DistributionException e)
			{
			logger.error("Error", e);
			throw new SequenceSpectrumException(e);
			}
		}

	public double conditionalProbability(final byte[] suffix, final byte[] prefix) throws SequenceSpectrumException
		{
		if (suffix.length == 0)
			{
			return 1;
			}
		final byte sigma = suffix[0];
		if (suffix.length == 1)
			{
			return conditionalProbability(sigma, prefix);
			}
		return conditionalProbability(sigma, prefix) * conditionalProbability(DSArrayUtils.suffix(suffix, 1),
		                                                                      DSArrayUtils.append(prefix, sigma));
		}

	public double conditionalProbabilityByAlphabetIndex(final int c)
		{
		try
			{
			return probs.get(alphabet[c]);
			}
		catch (DistributionException e)
			{
			logger.error("Error", e);
			throw new Error(e);
			}
		}

	/**
	 * Recursively fills out the tree so that each node has a complete set of children, or none at all.  That is: if this
	 * node has any children at all, ensures that there is a child for each symbol in the alphabet.
	 */
	/*	public void complete()
		   {
		   if (children == null || children.size() == 0)
			   {
			   return;
			   }

		   // we only get here if there is at least one child.  In that case, the get() calls will create all the other children, as needed.
		   for (byte sigma : getAlphabet())
			   {
			   add(sigma).complete();
			   }
		   }*/

	/**
	 * Recursively fills out the tree so that each node has either a complete set of children or none at all, and assigns
	 * conditional probabilities according to the given spectrum.  That is: if this node has any children, ensures that
	 * there is a child for each symbol in the alphabet.
	 */
	public void copyProbsFromSpectrumRecursively(final SequenceSpectrum spectrum)
	//throws SequenceSpectrumException//DistributionException,
		{
		for (final byte sigma : getAlphabet())
			{
			// copy the probabilities regardless
			double prob = 0;
			try
				{
				prob = spectrum.conditionalProbability(sigma, id);
				}
			catch (SequenceSpectrumException e)
				{
				//logger.error("Error", e);
				// no worries, just let prob=0 then
				}
			try
				{
				setProb(sigma, prob);
				}
			catch (DistributionException e)
				{
				throw new SequenceSpectrumRuntimeException(e);
				}

			// Nooo!  We already made sure that we have probability entries for each possible child.
			// But we only want a full-fledged node if the child itself has children.
			/*

								// if there are any children, make sure there are all children
							   if (children != null && children.size() != 0)
								   {
								   add(sigma).copyProbsFrom(spectrum);
								   }

								   */
			}
		try
			{
			probs.normalize();
			}
		catch (DistributionException e)
			{
			throw new SequenceSpectrumRuntimeException(e);
			}

		// okay, but we still need to recurse to the children that do exist
		for (final MarkovTreeNode child : children)//.values())
			{
			if (child != null)
				{
				child.copyProbsFromSpectrumRecursively(spectrum);
				}
			}
		}

	private void setProb(final byte b, final double prob) throws DistributionException
		{
		probs.put(b, prob);
		}

	public int countChildren()
		{
		int result = 0;
		for (final MarkovTreeNode n : children)
			{
			if (n != null)
				{
				result++;
				}
			}
		return result;
		}

	protected void diagnostics()
		{
		for (final MarkovTreeNode node : getAllDownstreamNodes())
			{
			total++;
			if (node.isLeaf())
				{
				leaves++;
				final int depth = node.getIdBytes().length;//length();
				avgdepth += depth;
				maxdepth = Math.max(maxdepth, depth);
				}
			}
		maxdepth -= getIdBytes().length;  // the symbols upstream of this node don't count
		maxdepth += 1;
		avgdepth /= leaves;
		avgdepth += 1;
		//	logger.info("Learned Ron PST using params " + branchAbsoluteMin + " " + branchConditionalMin + " " + pRatioMinMax
		//			+ " " + l_max);
		logger.debug("Learned Ron PSA with " + total + " nodes, " + leaves + " leaves, avg depth " + avgdepth
		             + ", max depth " + maxdepth);
		if (logger.isTraceEnabled())
			{
			logger.trace("\n" + toLongString());
			}
		}

	/**
	 * Gets a List<RonPSTNode> of all nodes in the subtree starting from this node, including this node.
	 *
	 * @return a List of all nodes in the subtree starting from this node, including this node.
	 */
	public List<MarkovTreeNode> getAllDownstreamNodes()
		{
		final List<MarkovTreeNode> result = new ArrayList<MarkovTreeNode>();

		collectDownstreamNodes(result);
		return result;
		}

	/**
	 * Recursively append this node and all its children (upstream nodes) to the provided List<RonPSTNode>
	 *
	 * @param nodeList a List to which to add this node and its children
	 */
	private void collectDownstreamNodes(final List<MarkovTreeNode> nodeList)
		{
		nodeList.add(this);
		for (final MarkovTreeNode n : children)
			{
			if (n != null)
				{
				n.collectDownstreamNodes(nodeList);
				}
			}
		}

	public byte[] getIdBytes()
		{
		return id;
		}

	public String toLongString()
		{
		final StringBuffer sb = new StringBuffer();
		final Formatter formatter = new Formatter(sb, Locale.US);
		appendString(formatter, "");
		return sb.toString();
		}

	public void appendString(final Formatter formatter, final String indent)
		{
		for (int i = 0; i < alphabet.length; i++)
			{
			final byte b = alphabet[i];
			try
				{
				formatter.format("%s %3.3g -> %c\n", indent, probs.get(b), b);
				//append(indent + probs.get(b) + " -> " + (char)b.byteValue() + "\n");
				final MarkovTreeNode child = children[i];
				if (child != null && child.getId().length() > getId().length())
					{
					child.appendString(formatter, indent + "     | ");
					}
				}
			catch (DistributionException e)
				{
				//sb.append(indent + "ERROR ->" + b);
				formatter.format("%s %s -> %c\n", indent, "ERROR", b);
				}
			}
		}

	/**
	 * Computes the total log probability of generating the given sequence fragment under the model.  This differs from
	 * {@link #totalProbability(byte[])} in that the sequence fragment is not given explicitly but only as metadata.  Thus
	 * its probability may be computed from summary statistics that are already available in the given SequenceFragment
	 * rather than from the raw sequence.  Also, because these probabilities are typically very small, the result is
	 * returned in log space (indeed implementations will likely compute them in log space).
	 *
	 * @param sequenceFragment the SequenceFragment whose probability is to be computed
	 * @return the natural logarithm of the conditional probability (a double value between 0 and 1, inclusive)
	 */
	public double fragmentLogProbabilityFromIntKcount(final SequenceFragment sequenceFragment)
			throws SequenceSpectrumException
		{
		final IntKcount kc = (IntKcount) sequenceFragment.getSpectrum(IntKcount.class, null);
		//int k = kc.getK();

		double result = 0;

		for (final byte[] firstWord : sequenceFragment.getFirstWords(kc.getK()))
			{
			result += MathUtils.approximateLog(totalProbability(firstWord));
			}

		/*	try
		   {
		   byte[] prefix = sequenceFragment.getPrefix(kc.getK());
		   result += MathUtils.approximateLog(totalProbability(prefix));
		   }
	   catch (NotEnoughSequenceException e)
		   {
		   logger.error("Error", e);
		   throw new SequenceSpectrumException(e);
		   }*/

		final int[] counts = kc.getArray();

		for (int id = 0; id < counts.length; id++)
			{
			if (counts[id] > 0)
				{
				final byte[] prefix = kc.prefixForId(id);
				final byte sigma = kc.lastSymbolForId(id);

				result += counts[id] * MathUtils.approximateLog(conditionalProbability(sigma, prefix));
				}
			}

		return result;
		}

	/**
	 * Computes the probability of generating the given sequence under the model.
	 *
	 * @param s a byte array
	 * @return the probability, a double value between 0 and 1, inclusive
	 */
	public double totalProbability(final byte[] s) throws SequenceSpectrumException
		{
		if (s.length == 0)
			{
			return 1;
			}
		return conditionalProbability(s, DSArrayUtils.EMPTY_BYTE_ARRAY);
		}

	public double conditionalProbability(final byte sigma, final byte[] prefix) throws SequenceSpectrumException
		{
		final MarkovTreeNode node = get(prefix);
		if (node == null || node.probs == null)
			{
			throw new SequenceSpectrumException("Unknown probability: " + Arrays.toString(prefix) + " -> " + sigma);
			}
		try
			{
			return node.probs.get(sigma);
			}
		catch (DistributionException e)
			{
			logger.error("Error", e);
			throw new SequenceSpectrumException(e);
			}
		}

	/**
	 * Gets the child node associated with the given sequence, if it exists
	 *
	 * @param seq the sequence of symbols to follow from this node
	 * @return the node at the end of the chain of transitions, or null if that leaf does not exist
	 */
	public MarkovTreeNode get(final byte[] seq) throws SequenceSpectrumException
		{
		if (seq.length == 0)
			{
			// this should probably never occur
			return this;
			}

		final MarkovTreeNode nextChild = children[DSArrayUtils.indexOf(alphabet, seq[0])];

		if (nextChild == null)
			{
			return null;
			}
		if (seq.length == 1)
			{
			return nextChild;
			}
		else if (seq.length >= 1)
			{
			return nextChild.get(DSArrayUtils.suffix(seq, 1));
			}
		throw new Error("Impossible");
		}

	public double getAvgDepth()
		{
		return avgdepth;
		}

	/**
	 * gets the node associated with the the given sequence, or null if that node does not exist
	 *
	 * @param descendantId the sequence to walk
	 * @return the MarkovTreeNode
	 */
	public MarkovTreeNode getDescendant(final byte[] descendantId)//throws SequenceSpectrumException
		{
		MarkovTreeNode result = this;

		// this could also have been recursive, but that would have involved making each suffix byte[] explicitly
		for (final byte b : descendantId)
			{
			MarkovTreeNode next = result.getChild(b);
			if (next == null)
				{
				return null;
				}
			else
				{
				result = next;
				}
			}

		return result;
		}

	public String getExclusiveLabel()
		{
		return label;
		}

	public List<byte[]> getFirstWords(final int k)
		{
		throw new NotImplementedException();
		}

	/**
	 * gets the node associated with the longest available prefix of the given sequence.
	 *
	 * @param prefix the sequence to walk
	 * @return the MarkovTreeNode
	 */
	public MarkovTreeNode getLongestPrefix(final byte[] prefix)
		{
		MarkovTreeNode result = this;

		// this could also have been recursive, but that would have involved making each suffix byte[] explicitly
		for (final byte b : prefix)
			{
			MarkovTreeNode next = result.getChild(b);
			if (next == null)
				{
				return result;
				}
			else
				{
				result = next;
				}
			}

		return result;
		}

	public int getNumLeaves()
		{
		return leaves;
		}

	public int getNumNodes()
		{
		return total;
		}

	public String getSourceId()
		{
		throw new NotImplementedException();
		}

	public int getSubtreeNodes()
		{
		int result = 1;// this
		for (final MarkovTreeNode child : children)//.values())
			{
			if (child != null)
				{
				result += child.getSubtreeNodes();
				}
			}
		return result;
		}

	/**
	 * Returns the conditional probability of the given symbol from this node.  {@link #updateLogProbs()} must have been
	 * run first.
	 *
	 * @param sigma
	 * @return
	 * @throws SequenceSpectrumException
	 */
	public double logConditionalProbability(final byte sigma) throws SequenceSpectrumException
		{
		try
			{
			return logprobs[DSArrayUtils.indexOf(alphabet, sigma)];//alphabetIndexForSymbol(sigma)];
			}
		catch (IndexOutOfBoundsException e)
			{
			throw new SequenceSpectrumException("Symbol " + (char) sigma + " not in alphabet");
			}
		}

	public double logConditionalProbabilityByAlphabetIndex(final int c)
		{
		return logprobs[c];
		}

	/*
   public Set<MarkovTreeNode> getAllNodes()
	   {
	   Set<MarkovTreeNode> result = new HashSet<MarkovTreeNode>();
	   result.add(this);
	   for (MarkovTreeNode child : children.values())
		   {
		   child.addAllNodes(result);
		   }
	   return result;
	   }

   public void addAllNodes(Set<MarkovTreeNode> result)
	   {
	   result.add(this);
	   for (MarkovTreeNode child : children.values())
		   {
		   child.addAllNodes(result);
		   }
	   }*/

	private void normalize() throws DistributionException
		{
		probs.normalize();
		}

	public void updateLogProbsRecursive()
		{
		updateLogProbs();
		for (final MarkovTreeNode child : children)//.values())
			{
			if (child != null)
				{
				child.updateLogProbsRecursive();
				}
			}
		}

	public void updateLogProbs()
		{
		try
			{
			for (int i = 0; i < alphabet.length; i++)
				{
				logprobs[i] = MathUtils.approximateLog(probs.get(alphabet[i]));
				}
			}
		catch (DistributionException e)
			{
			logger.error("Error", e);
			throw new SequenceSpectrumRuntimeException(e);
			}
		}
	}


text:
/**
 *     This code was auto-generated by a Codezu.
 *
 *     Changes to this file may cause incorrect behavior and will be lost if
 *     the code is regenerated.
 */
package com.mozu.api.resources.commerce.settings.checkout;

import com.mozu.api.ApiContext;
import java.util.List;
import java.util.ArrayList;
import com.mozu.api.MozuClient;
import com.mozu.api.MozuClientFactory;
import com.mozu.api.MozuUrl;
import com.mozu.api.Headers;
import org.joda.time.DateTime;
import com.mozu.api.security.AuthTicket;
import org.apache.commons.lang.StringUtils;

/**
 * Use the Payment Settings resource to specify settings when creating payments for order checkout on the site.
 *
 */
public class PaymentSettingsResource {
	///
	/// <see cref="Mozu.Api.ApiContext"/>
	///
	private ApiContext _apiContext;

	public PaymentSettingsResource(ApiContext apiContext)
	{
		_apiContext = apiContext;
	}

	/**
	 * Retrieves the schema and definition along with the actual instance values of the third-party payment service workflow configured for the corresponding fully qualified name.
	 *

	 *	PaymentSettings paymentsettings = new PaymentSettings();
	 *	ExternalPaymentWorkflowDefinition externalPaymentWorkflowDefinition = paymentsettings.getThirdPartyPaymentWorkflowWithValues( fullyQualifiedName);
	 *

	 * @param fullyQualifiedName Fully qualified name of the attribute for the third-party payment workflow.
	 * @return com.mozu.api.contracts.sitesettings.order.ExternalPaymentWorkflowDefinition
	 * @see com.mozu.api.contracts.sitesettings.order.ExternalPaymentWorkflowDefinition
	 */
	public com.mozu.api.contracts.sitesettings.order.ExternalPaymentWorkflowDefinition getThirdPartyPaymentWorkflowWithValues(String fullyQualifiedName) throws Exception
	{
		return getThirdPartyPaymentWorkflowWithValues( fullyQualifiedName,  null);
	}

	/**
	 * Retrieves the schema and definition along with the actual instance values of the third-party payment service workflow configured for the corresponding fully qualified name.
	 *

	 *	PaymentSettings paymentsettings = new PaymentSettings();
	 *	ExternalPaymentWorkflowDefinition externalPaymentWorkflowDefinition = paymentsettings.getThirdPartyPaymentWorkflowWithValues( fullyQualifiedName,  responseFields);
	 *

	 * @param fullyQualifiedName Fully qualified name of the attribute for the third-party payment workflow.
	 * @param responseFields Filtering syntax appended to an API call to increase or decrease the amount of data returned inside a JSON object. This parameter should only be used to retrieve data. Attempting to update data using this parameter may cause data loss.
	 * @return com.mozu.api.contracts.sitesettings.order.ExternalPaymentWorkflowDefinition
	 * @see com.mozu.api.contracts.sitesettings.order.ExternalPaymentWorkflowDefinition
	 */
	public com.mozu.api.contracts.sitesettings.order.ExternalPaymentWorkflowDefinition getThirdPartyPaymentWorkflowWithValues(String fullyQualifiedName, String responseFields) throws Exception
	{
		MozuClient<com.mozu.api.contracts.sitesettings.order.ExternalPaymentWorkflowDefinition> client = com.mozu.api.clients.commerce.settings.checkout.PaymentSettingsClient.getThirdPartyPaymentWorkflowWithValuesClient( fullyQualifiedName,  responseFields);
		client.setContext(_apiContext);
		client.executeRequest();
		return client.getResult();

	}

	/**
	 * Retrieves the schema and definition of all third-party payment service workflows configured for the site.
	 *

	 *	PaymentSettings paymentsettings = new PaymentSettings();
	 *	ExternalPaymentWorkflowDefinition externalPaymentWorkflowDefinition = paymentsettings.getThirdPartyPaymentWorkflows();
	 *

	 * @return List<com.mozu.api.contracts.sitesettings.order.ExternalPaymentWorkflowDefinition>
	 * @see com.mozu.api.contracts.sitesettings.order.ExternalPaymentWorkflowDefinition
	 */
	public List<com.mozu.api.contracts.sitesettings.order.ExternalPaymentWorkflowDefinition> getThirdPartyPaymentWorkflows() throws Exception
	{
		MozuClient<List<com.mozu.api.contracts.sitesettings.order.ExternalPaymentWorkflowDefinition>> client = com.mozu.api.clients.commerce.settings.checkout.PaymentSettingsClient.getThirdPartyPaymentWorkflowsClient();
		client.setContext(_apiContext);
		client.executeRequest();
		return client.getResult();

	}

	/**
	 * Adds a third-party payment workflow to the site. A third-party payment workflow is a definition of a process by which a third-party payment provider (such as Amazon Payments or PayPal Express) interacts with the Mozu platform.
	 *

	 *	PaymentSettings paymentsettings = new PaymentSettings();
	 *	paymentsettings.addThirdPartyPaymentWorkflow( definition);
	 *

	 * @param definition Properties of an external payment processing workflow defined for the site. At this time, only PayPal Express is supported.
	 * @return
	 * @see com.mozu.api.contracts.sitesettings.order.ExternalPaymentWorkflowDefinition
	 */
	public void addThirdPartyPaymentWorkflow(com.mozu.api.contracts.sitesettings.order.ExternalPaymentWorkflowDefinition definition) throws Exception
	{
		MozuClient client = com.mozu.api.clients.commerce.settings.checkout.PaymentSettingsClient.addThirdPartyPaymentWorkflowClient( definition);
		client.setContext(_apiContext);
		client.executeRequest();
		client.cleanupHttpConnection();

	}

	/**
	 * Deletes a third-party payment workflow. A third-party payment workflow is a definition of a process by which a third-party payment provider (such as Amazon Payments or PayPal Express) interacts with the Mozu platform.
	 *

	 *	PaymentSettings paymentsettings = new PaymentSettings();
	 *	paymentsettings.deleteThirdPartyPaymentWorkflow( fullyQualifiedName);
	 *

	 * @param fullyQualifiedName Fully qualified name of the attribute for the third-party payment workflow.
	 * @return
	 */
	public void deleteThirdPartyPaymentWorkflow(String fullyQualifiedName) throws Exception
	{
		MozuClient client = com.mozu.api.clients.commerce.settings.checkout.PaymentSettingsClient.deleteThirdPartyPaymentWorkflowClient( fullyQualifiedName);
		client.setContext(_apiContext);
		client.executeRequest();
		client.cleanupHttpConnection();

	}

}


text:
package brut.androlib;

import brut.directory.ExtFile;
import brut.common.BrutException;
import brut.util.OS;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class ProviderAttributeTest {

    @BeforeClass
    public static void beforeClass() throws BrutException {
        TestUtils.cleanFrameworkFile();
        sTmpDir = new ExtFile(OS.createTempDirectory());
        TestUtils.copyResourceDir(ProviderAttributeTest.class, "brut/apktool/issue636/", sTmpDir);
    }

    @AfterClass
    public static void afterClass() throws BrutException {
        OS.rmdir(sTmpDir);
    }

    @Test
    public void isProviderStringReplacementWorking() throws BrutException, IOException {
        String apk = "issue636.apk";

        // decode issue636.apk
        ApkDecoder apkDecoder = new ApkDecoder(new File(sTmpDir + File.separator + apk));
        apkDecoder.setOutDir(new File(sTmpDir + File.separator + apk + ".out"));
        apkDecoder.decode();

        // build issue636
        ExtFile testApk = new ExtFile(sTmpDir, apk + ".out");
        new Androlib().build(testApk, null);
        String newApk = apk + ".out" + File.separator + "dist" + File.separator + apk;
        assertTrue(fileExists(newApk));

        // decode issues636 again
        apkDecoder = new ApkDecoder(new File(sTmpDir + File.separator + newApk));
        apkDecoder.setOutDir(new File(sTmpDir + File.separator + apk + ".out.two"));
        apkDecoder.decode();

        String expected = TestUtils.replaceNewlines("<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n" +
                "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" package=\"com.ibotpeaches.issue636\" platformBuildVersionCode=\"23\" platformBuildVersionName=\"6.0-2438415\">\n" +
                "    <application android:allowBackup=\"true\" android:debuggable=\"true\" android:icon=\"@mipmap/ic_launcher\" android:label=\"@string/app_name\" android:theme=\"@style/AppTheme\">\n" +
                "        <provider android:authorities=\"com.ibotpeaches.issue636.Provider\" android:exported=\"false\" android:grantUriPermissions=\"true\" android:label=\"@string/app_name\" android:multiprocess=\"false\" android:name=\"com.ibotpeaches.issue636.Provider\"/>\n" +
                "        <provider android:authorities=\"com.ibotpeaches.issue636.ProviderTwo\" android:exported=\"false\" android:grantUriPermissions=\"true\" android:label=\"@string/app_name\" android:multiprocess=\"false\" android:name=\"com.ibotpeaches.issue636.ProviderTwo\"/>\n" +
                "    </application>\n" +
                "</manifest>");

        byte[] encoded = Files.readAllBytes(Paths.get(sTmpDir + File.separator + apk + ".out.two" + File.separator + "AndroidManifest.xml"));
        String obtained = TestUtils.replaceNewlines(new String(encoded));
        assertEquals(expected, obtained);
    }

    private boolean fileExists(String filepath) {
        return Files.exists(Paths.get(sTmpDir.getAbsolutePath() + File.separator + filepath));
    }

    private static ExtFile sTmpDir;
}


text:
use std::io::prelude::*;
use std::path;
use std::fs::File;
use rustc_serialize::json;
use regex::Regex;

#[derive(RustcEncodable, RustcDecodable)]
struct Config {
    token: String,
    user: String
}

#[derive(Debug, PartialEq)]
pub enum ReadError {
    NoConfig,
    JsonError,
    FileError,
    InvalidApiToken(String),
    InvalidUserKey(String)
}

#[derive(Debug, PartialEq)]
pub enum WriteError {
    InvalidApiToken(String),
    InvalidUserKey(String),
    FileError
}

fn valid_token(token: &str) -> bool {
    let re = Regex::new(r"[A-Za-z0-9]").unwrap();
    token.len() == 30 && re.is_match(token)
}

pub fn read(path: &path::Path) -> Result<(String, String), ReadError> {
    let file = File::open(path);

    match file {
        Ok(mut f) => {
            let mut buf = String::new();
            match f.read_to_string(&mut buf) {
                Ok(_) => {
                    let config: Config = json::decode(&buf).unwrap();
                    Ok((config.token, config.user))
                },
                Err(_) => Err(ReadError::FileError)
            }
        },
        Err(_) => Err(ReadError::NoConfig)
    }
}

pub fn write(token: &str, user: &str,
             path: &path::Path) -> Result<(), WriteError> {
    if !valid_token(token) {
        Err(WriteError::InvalidApiToken(token.to_string()))
    }
    else if !valid_token(user) {
        Err(WriteError::InvalidUserKey(user.to_string()))
    }
    else {
        let config = Config {
            token: token.to_string(),
            user: user.to_string()
        };
        let config_json = json::encode(&config).unwrap();

        let file = File::create(path);
        match file {
            Ok(mut f) => {
                match f.write_all(config_json.into_bytes().as_ref()) {
                    Ok(_) => Ok(()),
                    Err(_) => Err(WriteError::FileError)
                }
            },
            Err(_) => Err(WriteError::FileError)
        }
    }
}


text:
namespace content {

namespace {

static const char kCanvasPageString[] =
    "<body>"
    "  <canvas id=\"canvas\" width=\"64\" height=\"64\""
    "    style=\"position:absolute;top:0px;left:0px;width:100%;"
    "    height=100%;margin:0;padding:0;\">"
    "  </canvas>"
    "  <script>"
    "    window.ctx = document.getElementById(\"canvas\").getContext(\"2d\");"
    "    function fillWithColor(color) {"
    "      ctx.fillStyle = color;"
    "      ctx.fillRect(0, 0, 64, 64);"
    "      window.domAutomationController.send(color);"
    "    }"
    "    var offset = 150;"
    "    function openNewWindow() {"
    "      window.open(\"/test\", \"\", "
    "          \"top=\"+offset+\",left=\"+offset+\",width=200,height=200\");"
    "      offset += 50;"
    "      window.domAutomationController.send(true);"
    "    }"
    "    window.document.title = \"Ready\";"
    "  </script>"
    "</body>";
}

class SnapshotBrowserTest : public ContentBrowserTest {
 public:
  SnapshotBrowserTest() {}

  void SetUpCommandLine(base::CommandLine* command_line) override {
    // Use a smaller browser window to speed up the snapshots.
    command_line->AppendSwitchASCII(::switches::kContentShellHostWindowSize,
                                    "200x200");
  }

  void SetUp() override {
    // These tests rely on the harness producing pixel output.
    EnablePixelOutput();
    ContentBrowserTest::SetUp();
  }

  content::WebContentsImpl* GetWebContents(Shell* browser) {
    return static_cast<content::WebContentsImpl*>(browser->web_contents());
  }

  content::RenderWidgetHostImpl* GetRenderWidgetHostImpl(Shell* browser) {
    return GetWebContents(browser)->GetRenderViewHost()->GetWidget();
  }

  void SetupTestServer() {
    // Use an embedded test server so we can open multiple windows in
    // the same renderer process, all referring to the same origin.
    embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
        &SnapshotBrowserTest::HandleRequest, base::Unretained(this)));
    ASSERT_TRUE(embedded_test_server()->Start());

    ASSERT_TRUE(
        NavigateToURL(shell(), embedded_test_server()->GetURL("/test")));
  }

  std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
      const net::test_server::HttpRequest& request) {
    GURL absolute_url = embedded_test_server()->GetURL(request.relative_url);
    if (absolute_url.path() != "/test")
      return nullptr;

    std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
        new net::test_server::BasicHttpResponse());
    http_response->set_code(net::HTTP_OK);
    http_response->set_content(kCanvasPageString);
    http_response->set_content_type("text/html");
    return http_response;
  }

  void WaitForAllWindowsToBeReady() {
    const std::u16string expected_title = u"Ready";
    // The subordinate windows may load asynchronously. Wait for all of
    // them to execute their script before proceeding.
    auto browser_list = Shell::windows();
    for (Shell* browser : browser_list) {
      TitleWatcher watcher(GetWebContents(browser), expected_title);
      const std::u16string& actual_title = watcher.WaitAndGetTitle();
      EXPECT_EQ(expected_title, actual_title);
    }
  }

  struct ExpectedColor {
    ExpectedColor() : r(0), g(0), b(0) {}
    bool operator==(const ExpectedColor& other) const {
      return (r == other.r && g == other.g && b == other.b);
    }
    uint8_t r;
    uint8_t g;
    uint8_t b;
  };

  void PickRandomColor(ExpectedColor* expected) {
    expected->r = static_cast<uint8_t>(base::RandInt(0, 256));
    expected->g = static_cast<uint8_t>(base::RandInt(0, 256));
    expected->b = static_cast<uint8_t>(base::RandInt(0, 256));
  }

  struct SerialSnapshot {
    SerialSnapshot() : host(nullptr) {}

    content::RenderWidgetHost* host;
    ExpectedColor color;
  };
  std::vector<SerialSnapshot> expected_snapshots_;

  void SyncSnapshotCallback(content::RenderWidgetHostImpl* rwhi,
                            const gfx::Image& image) {
    for (auto iter = expected_snapshots_.begin();
         iter != expected_snapshots_.end(); ++iter) {
      const SerialSnapshot& expected = *iter;
      if (expected.host == rwhi) {
        const SkBitmap* bitmap = image.ToSkBitmap();
        SkColor color = bitmap->getColor(1, 1);

        EXPECT_EQ(static_cast<int>(SkColorGetR(color)),
                  static_cast<int>(expected.color.r))
            << "Red channels differed";
        EXPECT_EQ(static_cast<int>(SkColorGetG(color)),
                  static_cast<int>(expected.color.g))
            << "Green channels differed";
        EXPECT_EQ(static_cast<int>(SkColorGetB(color)),
                  static_cast<int>(expected.color.b))
            << "Blue channels differed";

        expected_snapshots_.erase(iter);
        break;
      }
    }
  }

  std::map<content::RenderWidgetHost*, std::vector<ExpectedColor>>
      expected_async_snapshots_map_;
  int num_remaining_async_snapshots_ = 0;

  void AsyncSnapshotCallback(content::RenderWidgetHostImpl* rwhi,
                             const gfx::Image& image) {
    --num_remaining_async_snapshots_;
    auto iterator = expected_async_snapshots_map_.find(rwhi);
    ASSERT_NE(iterator, expected_async_snapshots_map_.end());
    std::vector<ExpectedColor>& expected_snapshots = iterator->second;
    const SkBitmap* bitmap = image.ToSkBitmap();
    SkColor color = bitmap->getColor(1, 1);
    bool found = false;
    // Find first instance of this color in the list and clear out all
    // of the entries before that point. If it's not found, report
    // failure.
    for (auto iter = expected_snapshots.begin();
         iter != expected_snapshots.end(); ++iter) {
      const ExpectedColor& expected = *iter;
      if (SkColorGetR(color) == expected.r &&
          SkColorGetG(color) == expected.g &&
          SkColorGetB(color) == expected.b) {
        // Erase everything up to this color, but not this color
        // itself, since it might be returned again later on
        // subsequent snapshot requests.
        expected_snapshots.erase(expected_snapshots.begin(), iter);
        found = true;
        break;
      }
    }

    EXPECT_TRUE(found) << "Did not find color ("
                       << static_cast<int>(SkColorGetR(color)) << ", "
                       << static_cast<int>(SkColorGetG(color)) << ", "
                       << static_cast<int>(SkColorGetB(color))
                       << ") in expected snapshots for RWH 0x" << rwhi;
  }
};

// Even the single-window test doesn't work on Android yet. It's expected
// that the multi-window tests would never work on that platform.
#if !defined(OS_ANDROID)

IN_PROC_BROWSER_TEST_F(SnapshotBrowserTest, SingleWindowTest) {
  SetupTestServer();

  content::RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl(shell());

  for (int i = 0; i < 40; ++i) {
    SerialSnapshot expected;
    expected.host = rwhi;
    PickRandomColor(&expected.color);

    std::string colorString = base::StringPrintf(
        "#%02x%02x%02x", expected.color.r, expected.color.g, expected.color.b);
    std::string script = std::string("fillWithColor(\"") + colorString + "\");";
    EXPECT_EQ(colorString, EvalJs(GetWebContents(shell()), script,
                                  EXECUTE_SCRIPT_USE_MANUAL_REPLY));

    expected_snapshots_.push_back(expected);

    // Get the snapshot from the surface rather than the window. The
    // on-screen display path is verified by the GPU tests, and it
    // seems difficult to figure out the colorspace transformation
    // required to make these color comparisons.
    rwhi->GetSnapshotFromBrowser(
        base::BindOnce(&SnapshotBrowserTest::SyncSnapshotCallback,
                       base::Unretained(this), base::Unretained(rwhi)),
        true);
    while (expected_snapshots_.size() > 0) {
      base::RunLoop().RunUntilIdle();
    }
  }
}

// Timing out either all the time, or infrequently, apparently because
// they're too slow, on the following configurations:
//   Windows Debug
//   Linux Chromium OS ASAN LSAN Tests (1)
//   Linux TSAN Tests
// See crbug.com/771119
#if (defined(OS_WIN) && !defined(NDEBUG)) || (BUILDFLAG(IS_CHROMEOS_ASH)) || \
    ((defined(OS_LINUX) || defined(OS_CHROMEOS)) && defined(THREAD_SANITIZER))
#define MAYBE_SyncMultiWindowTest DISABLED_SyncMultiWindowTest
#define MAYBE_AsyncMultiWindowTest DISABLED_AsyncMultiWindowTest
#else
#define MAYBE_SyncMultiWindowTest SyncMultiWindowTest
#define MAYBE_AsyncMultiWindowTest AsyncMultiWindowTest
#endif

IN_PROC_BROWSER_TEST_F(SnapshotBrowserTest, MAYBE_SyncMultiWindowTest) {
  SetupTestServer();

  for (int i = 0; i < 3; ++i) {
    EXPECT_EQ(true, EvalJs(GetWebContents(shell()), "openNewWindow()",
                           EXECUTE_SCRIPT_USE_MANUAL_REPLY));
  }

  base::RunLoop().RunUntilIdle();

  WaitForAllWindowsToBeReady();

  auto browser_list = Shell::windows();
  EXPECT_EQ(4u, browser_list.size());

  for (int i = 0; i < 20; ++i) {
    for (int j = 0; j < 4; j++) {
      // Start each iteration by taking a snapshot with a different
      // browser instance.
      int browser_index = (i + j) % 4;
      Shell* browser = browser_list[browser_index];
      content::RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl(browser);

      SerialSnapshot expected;
      expected.host = rwhi;
      PickRandomColor(&expected.color);

      std::string colorString =
          base::StringPrintf("#%02x%02x%02x", expected.color.r,
                             expected.color.g, expected.color.b);
      std::string script =
          std::string("fillWithColor(\"") + colorString + "\");";
      EXPECT_EQ(colorString, EvalJs(GetWebContents(browser), script,
                                    EXECUTE_SCRIPT_USE_MANUAL_REPLY));
      expected_snapshots_.push_back(expected);
      // Get the snapshot from the surface rather than the window. The
      // on-screen display path is verified by the GPU tests, and it
      // seems difficult to figure out the colorspace transformation
      // required to make these color comparisons.
      rwhi->GetSnapshotFromBrowser(
          base::BindOnce(&SnapshotBrowserTest::SyncSnapshotCallback,
                         base::Unretained(this), base::Unretained(rwhi)),
          true);
    }

    while (expected_snapshots_.size() > 0) {
      base::RunLoop().RunUntilIdle();
    }
  }
}

IN_PROC_BROWSER_TEST_F(SnapshotBrowserTest, MAYBE_AsyncMultiWindowTest) {
  SetupTestServer();

  for (int i = 0; i < 3; ++i) {
    EXPECT_EQ(true, EvalJs(GetWebContents(shell()), "openNewWindow()",
                           EXECUTE_SCRIPT_USE_MANUAL_REPLY));
  }

  base::RunLoop().RunUntilIdle();

  WaitForAllWindowsToBeReady();

  auto browser_list = Shell::windows();
  EXPECT_EQ(4u, browser_list.size());

  // This many pending snapshots per window will be put on the queue
  // before draining the requests. Anything more than 1 seems to catch
  // bugs which might otherwise be introduced in LatencyInfo's
  // propagation of the BROWSER_SNAPSHOT_FRAME_NUMBER_COMPONENT
  // component type.
  int divisor = 3;

  for (int i = 0; i < 10 * divisor; ++i) {
    for (int j = 0; j < 4; j++) {
      // Start each iteration by taking a snapshot with a different
      // browser instance.
      int browser_index = (i + j) % 4;
      Shell* browser = browser_list[browser_index];
      content::RenderWidgetHostImpl* rwhi = GetRenderWidgetHostImpl(browser);

      std::vector<ExpectedColor>& expected_snapshots =
          expected_async_snapshots_map_[rwhi];

      // Pick a unique random color.
      ExpectedColor expected;
      do {
        PickRandomColor(&expected);
      } while (base::Contains(expected_snapshots, expected));
      expected_snapshots.push_back(expected);

      std::string colorString = base::StringPrintf("#%02x%02x%02x", expected.r,
                                                   expected.g, expected.b);
      std::string script =
          std::string("fillWithColor(\"") + colorString + "\");";
      EXPECT_EQ(colorString, EvalJs(GetWebContents(browser), script,
                                    EXECUTE_SCRIPT_USE_MANUAL_REPLY));
      // Get the snapshot from the surface rather than the window. The
      // on-screen display path is verified by the GPU tests, and it
      // seems difficult to figure out the colorspace transformation
      // required to make these color comparisons.
      rwhi->GetSnapshotFromBrowser(
          base::BindOnce(&SnapshotBrowserTest::AsyncSnapshotCallback,
                         base::Unretained(this), base::Unretained(rwhi)),
          true);
      ++num_remaining_async_snapshots_;
    }

    // Periodically yield and drain the async snapshot requests.
    if ((i % divisor) == 0) {
      bool drained;
      do {
        drained = true;
        for (auto iter = expected_async_snapshots_map_.begin();
             iter != expected_async_snapshots_map_.end(); ++iter) {
          if (iter->second.size() > 1) {
            drained = false;
            break;
          }
        }
        if (!drained) {
          base::RunLoop().RunUntilIdle();
        }
      } while (!drained);
    }
  }

  // At the end of the test, cooperatively wait for all of the snapshot
  // requests to be drained before exiting. This works around crashes
  // seen when tearing down the compositor while these requests are in
  // flight. Likely, user-visible APIs that use this facility are safe
  // in this regard.
  while (num_remaining_async_snapshots_ > 0) {
    base::RunLoop().RunUntilIdle();
  }
}

#endif  // !defined(OS_ANDROID)

}  // namespace content


text:
<?php

namespace App\Phansible\Roles;

use App\Phansible\Role;
use PHPUnit\Framework\TestCase;

class PgsqlTest extends TestCase
{
    private $role;

    public function setUp(): void
    {
        $this->role = new Pgsql();
    }

    public function tearDown(): void
    {
        unset($this->role);
    }

    /**
     * @covers \App\Phansible\Roles\Pgsql
     */
    public function testShouldInstanceOf(): void
    {
        $this->assertInstanceOf(Role::class, $this->role);
    }

    /**
     * @covers \App\Phansible\Roles\Pgsql::getName
     */
    public function testShouldGetName(): void
    {
        $this->assertEquals('PostgreSQL', $this->role->getName());
    }

    /**
     * @covers \App\Phansible\Roles\Pgsql::getSlug
     */
    public function testShouldGetSlug(): void
    {
        $this->assertEquals('pgsql', $this->role->getSlug());
    }

    /**
     * @covers \App\Phansible\Roles\Pgsql::getRole
     */
    public function testShouldGetRole(): void
    {
        $this->assertEquals('pgsql', $this->role->getRole());
    }

    /**
     * @covers \App\Phansible\Roles\Pgsql::getInitialValues
     */
    public function testShouldGetInitialValues(): void
    {
        $expected = [
            'install'       => 0,
            'root_password' => 123,
            'databases'     => [
                'name'     => 'dbname',
                'user'     => 'name',
                'password' => 123,
            ],
        ];

        $this->assertEquals($expected, $this->role->getInitialValues());
    }
}