<commit_before>export { ShuffleInfo };
export { ShuffleInfoPool };

import * as Pool from "../../util/Pool.js";

/**
 * The information for channel shuffler.
 *
 *
 * @member {number[]} concatenatedShape
 *   An array of integer describes the shape of the reshapeTransposeReshape()'s concatenated input tensor (tensor3d
 * or tensor1d). For example, if reshapeTransposeReshape() will be called with an array of image (i.e. array of
 * tensor3d), concatenatedShape should be [ height, width, totalChannelCount ]. Another example, if the input will
 * be an array of tensor1d, concatenatedShape should be [ totalChannelCount ]. No matter which example, the
 * totalChannelCount should always be the total sum of the last dimension size of all tensors in the
 * concatReshapeTransposeReshape()'s input array.
 *
 * @member {number} outputGroupCount
 *   If greater than 1, the input tensor3d list (after concatenated) will be shuffled and then splitted into so
 * many group. The ( totalChannelCount / outputGroupCount ) should be an integer. If less or equal than 1 (or null),
 * the output tensor3d list will just be an array with only one tensor3d which is the concatenation of all the
 * input tensor3d list (i.e. no shuffle and split).
 *
 * @member {ShuffleInfo} shuffleInfo
 *   The information calculated from concatenatedShape and outputGroupCount. (In fact, it is the this.)
 *
 *
 * @member {number} lastAxisId
 *   The last axis id of reshapeTransposeReshape()'s input tensor. It will be ( concatenatedShape.length - 1 ).
 *
 * @member {number} totalChannelCount
 *   The total channel count when all the concatReshapeTransposeReshape()'s input tensor concatenated. It will be
 * the value of the last element of concatenatedShape (i.e. concatenatedShape[ lastAxisId ]).
 *
 * @member {number} channelCountPerGroup
 *   There will be so many channels in one (output) group.
 *
 * @member {number[]} intermediateShape
 *   Before shuffling, the reshapeTransposeReshape()'s (concatenated) input will be reshaped to this intermediateShape.
 *
 * @member {number[]} transposePermutation
 *   After reshaped to intermediateShape, the (concatenated) input will be transposed according to this
 * transposePermutation (so that they are shuffled).
 *
 *
 * @member {number} tensorWeightCountTotal
 *   The total wieght count used in tensors. Not including Params, because they are not used in tensors. Including inferenced
 * weights, if they are used in tensors.
 *
 * @member {number} tensorWeightCountExtracted
 *   The wieght count extracted from inputFloat32Array and used in tensors. Not including Params, because they are not used in
 * tensors. Not including inferenced weights (even if they are used in tensors), because they are not extracted from inputFloat32Array.
 *
 *
 * @member {function} reshapeTransposeReshape
 *   Permute the input tensor by reshape-transpose-reshape. It is a function pointer to one of this.reshapeTransposeReshape_XXX().
 *
 * @member {function} reshapeTransposeReshapeSplit
 *   Permute and split the input tensor by reshape-transpose-reshape-split. It is a function pointer to one of
 * this.reshapeTransposeReshapeSplit_XXX().
 *
 * @member {function} concatReshapeTransposeReshape
 *   Concatenate and permute the input tensor by concat-reshape-transpose-reshape. It is a function pointer to one of
 * this.concatReshapeTransposeReshape_XXX().
 *
 * @member {function} concatReshapeTransposeReshapeSplit
 *   Concatenate, permute and split the input tensor by concat-reshape-transpose-reshape-split. It is a function pointer to one of
 * this.concatReshapeTransposeReshapeSplit_XXX().
 */
class ShuffleInfo {

  /**
   *
   */
  constructor( concatenatedShape, outputGroupCount ) {
    this.setAsConstructor( concatenatedShape, outputGroupCount );
  }

  /**
   *
   * @return {ShuffleInfo}
   *   Return the this object.
   */
  setAsConstructor( concatenatedShape, outputGroupCount ) {

    outputGroupCount = Math.trunc( outputGroupCount || 1 );
    if ( outputGroupCount < 1 )
      outputGroupCount = 1; // At least one (means: no shuffle and split (i.e. just concatenate only)).

    // Clone it (by shallow-copy) because the outside may modify it.
    {
      this.concatenatedShape = Pool.Array.Singleton.get_or_create_by( concatenatedShape.length );
      for ( let i = 0; i < concatenatedShape.length; ++i ) {
        this.concatenatedShape[ i ] = concatenatedShape[ i ];
      }
    }

    this.outputGroupCount = outputGroupCount;

    this.shuffleInfo = this; // So that all ChannelShuffler.Xxx have property "shuffleInfo".

    let lastAxisId = this.lastAxisId = concatenatedShape.length - 1;
    let totalChannelCount = this.totalChannelCount = concatenatedShape[ lastAxisId ];

    // The channel count of every output group. (It should be an integer.)
    let channelCountPerGroup = this.channelCountPerGroup = totalChannelCount / outputGroupCount;

    // The shape before transpose. For example, if concatenatedShape is [ h, w, c ], the intermediateShape will be
    // [ h, w, outputGroupCount, channelCountPerGroup ]. The last dimension is splitted into two dimensions.
    //
    let intermediateShape;
    {

//!!! (2022/06/24 Remarke) Old Codes
//     let intermediateShape = this.intermediateShape = concatenatedShape.slice( 0, lastAxisId );
//     intermediateShape.push( outputGroupCount, channelCountPerGroup );

      intermediateShape = this.intermediateShape = Pool.Array.Singleton.get_or_create_by( concatenatedShape.length + 1 );
      for ( let i = 0; i < lastAxisId; ++i ) {
        intermediateShape[ i ] = concatenatedShape[ i ];
      }
      intermediateShape[ lastAxisId     ] = outputGroupCount;
      intermediateShape[ lastAxisId + 1 ] = channelCountPerGroup;
    }

    // The axis permutation of transpose.
    //
    // For example, if the intermediateShape is [ h, w, outputGroupCount, channelCountPerGroup ]. Its
    // axis permutation will be [ 0, 1, 3, 2 ] so that the last two dimensions will be swapped.
    //
    let transposePermutation;
    {

//!!! (2022/06/24 Remarke) Old Codes
//    let transposePermutation = this.transposePermutation = new Array( ...intermediateShape.keys() );
//       let last1 = transposePermutation.pop();
//       let last2 = transposePermutation.pop();
//       transposePermutation.push( last1, last2 );

      transposePermutation = this.transposePermutation = Pool.Array.Singleton.get_or_create_by( intermediateShape.length );
      for ( let i = 0; i < transposePermutation.length; ++i ) {
        transposePermutation[ i ] = i;
      }

      let last1 = transposePermutation[ transposePermutation.length - 1 ];
      let last2 = transposePermutation[ transposePermutation.length - 2 ];
      transposePermutation[ transposePermutation.length - 2 ] = last1;
      transposePermutation[ transposePermutation.length - 1 ] = last2;
    }

    this.tensorWeightCountExtracted = 0;
    this.tensorWeightCountTotal = 0;

    this.reshapeTransposeReshape = this.reshapeTransposeReshape_dispose_finally_calls;
    this.reshapeTransposeReshapeSplit = this.reshapeTransposeReshapeSplit_dispose_finally_calls;
    this.concatReshapeTransposeReshape = this.concatReshapeTransposeReshape_dispose_finally_calls;
    this.concatReshapeTransposeReshapeSplit = this.concatReshapeTransposeReshapeSplit_dispose_finally_calls;

    return this;
  }

  /**
   * Release tf.tensor. (In fact, no tensors needed to be disposed in this ShuffleInfo.)
   *
   * Sub-class should override this method (and call super.disposeResources() before return).
   */
  disposeResources() {
    this.concatReshapeTransposeReshapeSplit = null;
    this.concatReshapeTransposeReshape = null;
    this.reshapeTransposeReshapeSplit = null;
    this.reshapeTransposeReshape = null;

    this.tensorWeightCountExtracted = 0;
    this.tensorWeightCountTotal = 0;

    Pool.Array.Singleton.recycle( this.transposePermutation );
    this.transposePermutation = null;

    Pool.Array.Singleton.recycle( this.intermediateShape );
    this.intermediateShape = null;

    this.channelCountPerGroup = 0;
    this.totalChannelCount = 0;
    this.lastAxisId = -1;
    this.shuffleInfo = null;
    this.outputGroupCount = 0;

    Pool.Array.Singleton.recycle( this.concatenatedShape );
    this.concatenatedShape = null;

    //super.disposeResources();
  }

  /**
   * After calling this method, this object should be viewed as disposed and should not be operated again.
   */
  disposeResources_and_recycleToPool() {
    this.disposeResources();
    ShuffleInfoPool.Singleton.recycle( this );
  }

  /** Not dispose the input. */
  reshape_to_intermediateShape_keep_input( t ) {
    return t.reshape( this.intermediateShape );
  }

  reshape_to_intermediateShape_dispose_input( t ) {
    try {
      return t.reshape( this.intermediateShape );
    } finally {
      t.dispose();
    }
  }

  transpose_accordingTo_transposePermutation_dispose_input( t ) {
    try {
      return t.transpose( this.transposePermutation );
    } finally {
      t.dispose();
    }
  }

  reshape_to_concatenatedShape_dispose_input( t ) {
    try {
      return t.reshape( this.concatenatedShape );
    } finally {
      t.dispose();
    }
  }

  split_to_outputGroupCount_along_lastAxisId_dispose_input( t ) {
    try {
      return t.split( this.outputGroupCount, this.lastAxisId );
    } finally {
      t.dispose();
    }
  }

  /**
   * Permute the input tensor by reshape-transpose-reshape.
   *
   * @param {tf.tensor} concatenatedTensor
   *   An single tensor (not array) to be processed. It should conform to this.concatenatedShape.
   *
   * @return {tf.tensor}
   *   A shuffled tensor. Its size is the same as concatenatedTensor but its last dimension is shuffled.
   */
  reshapeTransposeReshape_dispose_finally_calls( concatenatedTensor ) {
    let t1 = this.reshape_to_intermediateShape_keep_input( concatenatedTensor );
    let t2 = this.transpose_accordingTo_transposePermutation_dispose_input( t1 );
    let t3 = this.reshape_to_concatenatedShape_dispose_input( t2 );
    return t3;
  }

  /**
   * Permute and split the input tensor by reshape-transpose-reshape-split.
   *
   * @param {tf.tensor} concatenatedTensor
   *   An single tensor (not array) to be processed. It should conform to this.concatenatedShape.
   *
   * @return {tf.tensor[]}
   *   An array of shuffled tensors. Their total channel count is the same as concatenatedTensor, but their
   * last dimensions are shuffled.
   */
  reshapeTransposeReshapeSplit_dispose_finally_calls( concatenatedTensor ) {
    let t1 = this.reshape_to_intermediateShape_keep_input( concatenatedTensor );
    let t2 = this.transpose_accordingTo_transposePermutation_dispose_input( t1 );
    let t3 = this.reshape_to_concatenatedShape_dispose_input( t2 );
    let t4 = this.split_to_outputGroupCount_along_lastAxisId_dispose_input( t3 );
    return t4;
  }

  /**
   * Concatenate and permute the input tensor by concat-reshape-transpose-reshape.
   *
   * @param {tf.tensor[]} tensorArray
   *   An array of tensors to be processed. It should conform to this.concatenatedShape.
   *
   * @return {tf.tensor}
   *   A shuffled tensor. Its total channel count is the same as concatenated tensorArray, but their
   * last dimensions are shuffled.
   */
  concatReshapeTransposeReshape_dispose_finally_calls( tensorArray ) {
    let concatenatedTensor = tf.concat( tensorArray, this.lastAxisId );

    let t1 = this.reshape_to_intermediateShape_dispose_input( concatenatedTensor );
    let t2 = this.transpose_accordingTo_transposePermutation_dispose_input( t1 );
    let t3 = this.reshape_to_concatenatedShape_dispose_input( t2 );
    return t3;
  }

  /**
   * Concatenate and permute the input tensor by concat-reshape-transpose-reshape.
   *
   * @param {tf.tensor[]} tensorArray
   *   An array of tensors to be processed. It should conform to this.concatenatedShape.
   *
   * @return {tf.tensor}
   *   A shuffled tensor. Its total channel count is the same as concatenated tensorArray, but their
   * last dimensions are shuffled.
   */
  concatReshapeTransposeReshapeSplit_dispose_finally_calls( tensorArray ) {
    let concatenatedTensor = tf.concat( tensorArray, this.lastAxisId );

    let t1 = this.reshape_to_intermediateShape_dispose_input( concatenatedTensor );
    let t2 = this.transpose_accordingTo_transposePermutation_dispose_input( t1 );
    let t3 = this.reshape_to_concatenatedShape_dispose_input( t2 );
    let t4 = this.split_to_outputGroupCount_along_lastAxisId_dispose_input( t3 );
    return t4;

    // Because every single function try-finally to release tensor, the memory footprint could be reduced.
    // If using nested try-finally in one function to release tensors, the memory footprint will be larger.
  }

}

/**
 * Providing ChannelShuffler.ShuffleInfo
 *
 */
class ShuffleInfoPool extends Pool.Root {

  constructor() {
    super( "ChannelShuffler.ShuffleInfoPool", ShuffleInfo, ShuffleInfo.setAsConstructor );
  }

}

/**
 * Used as default ChannelShuffler.ShuffleInfo provider.
 */
ShuffleInfoPool.Singleton = new ShuffleInfoPool();

<commit_msg>Update ChannelShuffler_ShuffleInfo.js<commit_after>export { ShuffleInfo };
export { ShuffleInfoPool };

import * as Pool from "../../util/Pool.js";

/**
 * The information for channel shuffler.
 *
 *
 * @member {number[]} concatenatedShape
 *   An array of integer describes the shape of the reshapeTransposeReshape()'s concatenated input tensor (tensor3d
 * or tensor1d). For example, if reshapeTransposeReshape() will be called with an array of image (i.e. array of
 * tensor3d), concatenatedShape should be [ height, width, totalChannelCount ]. Another example, if the input will
 * be an array of tensor1d, concatenatedShape should be [ totalChannelCount ]. No matter which example, the
 * totalChannelCount should always be the total sum of the last dimension size of all tensors in the
 * concatReshapeTransposeReshape()'s input array.
 *
 * @member {number} outputGroupCount
 *   If greater than 1, the input tensor3d list (after concatenated) will be shuffled and then splitted into so
 * many group. The ( totalChannelCount / outputGroupCount ) should be an integer. If less or equal than 1 (or null),
 * the output tensor3d list will just be an array with only one tensor3d which is the concatenation of all the
 * input tensor3d list (i.e. no shuffle and split).
 *
 * @member {ShuffleInfo} shuffleInfo
 *   The information calculated from concatenatedShape and outputGroupCount. (In fact, it is the this.)
 *
 *
 * @member {number} lastAxisId
 *   The last axis id of reshapeTransposeReshape()'s input tensor. It will be ( concatenatedShape.length - 1 ).
 *
 * @member {number} totalChannelCount
 *   The total channel count when all the concatReshapeTransposeReshape()'s input tensor concatenated. It will be
 * the value of the last element of concatenatedShape (i.e. concatenatedShape[ lastAxisId ]).
 *
 * @member {number} channelCountPerGroup
 *   There will be so many channels in one (output) group.
 *
 * @member {number[]} intermediateShape
 *   Before shuffling, the reshapeTransposeReshape()'s (concatenated) input will be reshaped to this intermediateShape.
 *
 * @member {number[]} transposePermutation
 *   After reshaped to intermediateShape, the (concatenated) input will be transposed according to this
 * transposePermutation (so that they are shuffled).
 *
 *
 * @member {number} tensorWeightCountTotal
 *   The total wieght count used in tensors. Not including Params, because they are not used in tensors. Including inferenced
 * weights, if they are used in tensors.
 *
 * @member {number} tensorWeightCountExtracted
 *   The wieght count extracted from inputFloat32Array and used in tensors. Not including Params, because they are not used in
 * tensors. Not including inferenced weights (even if they are used in tensors), because they are not extracted from inputFloat32Array.
 *
 *
 * @member {function} reshapeTransposeReshape
 *   Permute the input tensor by reshape-transpose-reshape. It is a function pointer to one of this.reshapeTransposeReshape_XXX().
 *
 * @member {function} reshapeTransposeReshapeSplit
 *   Permute and split the input tensor by reshape-transpose-reshape-split. It is a function pointer to one of
 * this.reshapeTransposeReshapeSplit_XXX().
 *
 * @member {function} concatReshapeTransposeReshape
 *   Concatenate and permute the input tensor by concat-reshape-transpose-reshape. It is a function pointer to one of
 * this.concatReshapeTransposeReshape_XXX().
 *
 * @member {function} concatReshapeTransposeReshapeSplit
 *   Concatenate, permute and split the input tensor by concat-reshape-transpose-reshape-split. It is a function pointer to one of
 * this.concatReshapeTransposeReshapeSplit_XXX().
 */
class ShuffleInfo {

  /**
   *
   */
  constructor( concatenatedShape, outputGroupCount ) {
    this.setAsConstructor( concatenatedShape, outputGroupCount );
  }

  /**
   *
   * @return {ShuffleInfo}
   *   Return the this object.
   */
  setAsConstructor( concatenatedShape, outputGroupCount ) {

    outputGroupCount = Math.trunc( outputGroupCount || 1 );
    if ( outputGroupCount < 1 )
      outputGroupCount = 1; // At least one (means: no shuffle and split (i.e. just concatenate only)).

    // Clone it (by shallow-copy) because the outside may modify it.
    {
      this.concatenatedShape = Pool.Array.Singleton.get_or_create_by( concatenatedShape.length );
      for ( let i = 0; i < concatenatedShape.length; ++i ) {
        this.concatenatedShape[ i ] = concatenatedShape[ i ];
      }
    }

    this.outputGroupCount = outputGroupCount;

    this.shuffleInfo = this; // So that all ChannelShuffler.Xxx have property "shuffleInfo".

    let lastAxisId = this.lastAxisId = concatenatedShape.length - 1;
    let totalChannelCount = this.totalChannelCount = concatenatedShape[ lastAxisId ];

    // The channel count of every output group. (It should be an integer.)
    let channelCountPerGroup = this.channelCountPerGroup = totalChannelCount / outputGroupCount;

    // The shape before transpose. For example, if concatenatedShape is [ h, w, c ], the intermediateShape will be
    // [ h, w, outputGroupCount, channelCountPerGroup ]. The last dimension is splitted into two dimensions.
    //
    let intermediateShape;
    {

//!!! (2022/06/24 Remarke) Old Codes
//     let intermediateShape = this.intermediateShape = concatenatedShape.slice( 0, lastAxisId );
//     intermediateShape.push( outputGroupCount, channelCountPerGroup );

      intermediateShape = this.intermediateShape = Pool.Array.Singleton.get_or_create_by( concatenatedShape.length + 1 );
      for ( let i = 0; i < lastAxisId; ++i ) {
        intermediateShape[ i ] = concatenatedShape[ i ];
      }
      intermediateShape[ lastAxisId     ] = outputGroupCount;
      intermediateShape[ lastAxisId + 1 ] = channelCountPerGroup;
    }

    // The axis permutation of transpose.
    //
    // For example, if the intermediateShape is [ h, w, outputGroupCount, channelCountPerGroup ]. Its
    // axis permutation will be [ 0, 1, 3, 2 ] so that the last two dimensions will be swapped.
    //
    let transposePermutation;
    {

//!!! (2022/06/24 Remarke) Old Codes
//    let transposePermutation = this.transposePermutation = new Array( ...intermediateShape.keys() );
//       let last1 = transposePermutation.pop();
//       let last2 = transposePermutation.pop();
//       transposePermutation.push( last1, last2 );

      transposePermutation = this.transposePermutation = Pool.Array.Singleton.get_or_create_by( intermediateShape.length );
      for ( let i = 0; i < transposePermutation.length; ++i ) {
        transposePermutation[ i ] = i;
      }

      let last1 = transposePermutation[ transposePermutation.length - 1 ];
      let last2 = transposePermutation[ transposePermutation.length - 2 ];
      transposePermutation[ transposePermutation.length - 2 ] = last1;
      transposePermutation[ transposePermutation.length - 1 ] = last2;
    }

    this.tensorWeightCountExtracted = 0;
    this.tensorWeightCountTotal = 0;

    this.reshapeTransposeReshape = this.reshapeTransposeReshape_dispose_finally_calls;
    this.reshapeTransposeReshapeSplit = this.reshapeTransposeReshapeSplit_dispose_finally_calls;
    this.concatReshapeTransposeReshape = this.concatReshapeTransposeReshape_dispose_finally_calls;
    this.concatReshapeTransposeReshapeSplit = this.concatReshapeTransposeReshapeSplit_dispose_finally_calls;

    return this;
  }

  /**
   * Release tf.tensor. (In fact, no tensors needed to be disposed in this ShuffleInfo.)
   *
   * Sub-class should override this method (and call super.disposeResources() before return).
   */
  disposeResources() {
    this.concatReshapeTransposeReshapeSplit = null;
    this.concatReshapeTransposeReshape = null;
    this.reshapeTransposeReshapeSplit = null;
    this.reshapeTransposeReshape = null;

    this.tensorWeightCountTotal = 0;
    this.tensorWeightCountExtracted = 0;

    Pool.Array.Singleton.recycle( this.transposePermutation );
    this.transposePermutation = null;

    Pool.Array.Singleton.recycle( this.intermediateShape );
    this.intermediateShape = null;

    this.channelCountPerGroup = 0;
    this.totalChannelCount = 0;
    this.lastAxisId = -1;
    this.shuffleInfo = null;
    this.outputGroupCount = 0;

    Pool.Array.Singleton.recycle( this.concatenatedShape );
    this.concatenatedShape = null;

    //super.disposeResources();
  }

  /**
   * After calling this method, this object should be viewed as disposed and should not be operated again.
   */
  disposeResources_and_recycleToPool() {
    this.disposeResources();
    ShuffleInfoPool.Singleton.recycle( this );
  }

  /** Not dispose the input. */
  reshape_to_intermediateShape_keep_input( t ) {
    return t.reshape( this.intermediateShape );
  }

  reshape_to_intermediateShape_dispose_input( t ) {
    try {
      return t.reshape( this.intermediateShape );
    } finally {
      t.dispose();
    }
  }

  transpose_accordingTo_transposePermutation_dispose_input( t ) {
    try {
      return t.transpose( this.transposePermutation );
    } finally {
      t.dispose();
    }
  }

  reshape_to_concatenatedShape_dispose_input( t ) {
    try {
      return t.reshape( this.concatenatedShape );
    } finally {
      t.dispose();
    }
  }

  split_to_outputGroupCount_along_lastAxisId_dispose_input( t ) {
    try {
      return t.split( this.outputGroupCount, this.lastAxisId );
    } finally {
      t.dispose();
    }
  }

  /**
   * Permute the input tensor by reshape-transpose-reshape.
   *
   * @param {tf.tensor} concatenatedTensor
   *   An single tensor (not array) to be processed. It should conform to this.concatenatedShape.
   *
   * @return {tf.tensor}
   *   A shuffled tensor. Its size is the same as concatenatedTensor but its last dimension is shuffled.
   */
  reshapeTransposeReshape_dispose_finally_calls( concatenatedTensor ) {
    let t1 = this.reshape_to_intermediateShape_keep_input( concatenatedTensor );
    let t2 = this.transpose_accordingTo_transposePermutation_dispose_input( t1 );
    let t3 = this.reshape_to_concatenatedShape_dispose_input( t2 );
    return t3;
  }

  /**
   * Permute and split the input tensor by reshape-transpose-reshape-split.
   *
   * @param {tf.tensor} concatenatedTensor
   *   An single tensor (not array) to be processed. It should conform to this.concatenatedShape.
   *
   * @return {tf.tensor[]}
   *   An array of shuffled tensors. Their total channel count is the same as concatenatedTensor, but their
   * last dimensions are shuffled.
   */
  reshapeTransposeReshapeSplit_dispose_finally_calls( concatenatedTensor ) {
    let t1 = this.reshape_to_intermediateShape_keep_input( concatenatedTensor );
    let t2 = this.transpose_accordingTo_transposePermutation_dispose_input( t1 );
    let t3 = this.reshape_to_concatenatedShape_dispose_input( t2 );
    let t4 = this.split_to_outputGroupCount_along_lastAxisId_dispose_input( t3 );
    return t4;
  }

  /**
   * Concatenate and permute the input tensor by concat-reshape-transpose-reshape.
   *
   * @param {tf.tensor[]} tensorArray
   *   An array of tensors to be processed. It should conform to this.concatenatedShape.
   *
   * @return {tf.tensor}
   *   A shuffled tensor. Its total channel count is the same as concatenated tensorArray, but their
   * last dimensions are shuffled.
   */
  concatReshapeTransposeReshape_dispose_finally_calls( tensorArray ) {
    let concatenatedTensor = tf.concat( tensorArray, this.lastAxisId );

    let t1 = this.reshape_to_intermediateShape_dispose_input( concatenatedTensor );
    let t2 = this.transpose_accordingTo_transposePermutation_dispose_input( t1 );
    let t3 = this.reshape_to_concatenatedShape_dispose_input( t2 );
    return t3;
  }

  /**
   * Concatenate and permute the input tensor by concat-reshape-transpose-reshape.
   *
   * @param {tf.tensor[]} tensorArray
   *   An array of tensors to be processed. It should conform to this.concatenatedShape.
   *
   * @return {tf.tensor}
   *   A shuffled tensor. Its total channel count is the same as concatenated tensorArray, but their
   * last dimensions are shuffled.
   */
  concatReshapeTransposeReshapeSplit_dispose_finally_calls( tensorArray ) {
    let concatenatedTensor = tf.concat( tensorArray, this.lastAxisId );

    let t1 = this.reshape_to_intermediateShape_dispose_input( concatenatedTensor );
    let t2 = this.transpose_accordingTo_transposePermutation_dispose_input( t1 );
    let t3 = this.reshape_to_concatenatedShape_dispose_input( t2 );
    let t4 = this.split_to_outputGroupCount_along_lastAxisId_dispose_input( t3 );
    return t4;

    // Because every single function try-finally to release tensor, the memory footprint could be reduced.
    // If using nested try-finally in one function to release tensors, the memory footprint will be larger.
  }

}

/**
 * Providing ChannelShuffler.ShuffleInfo
 *
 */
class ShuffleInfoPool extends Pool.Root {

  constructor() {
    super( "ChannelShuffler.ShuffleInfoPool", ShuffleInfo, ShuffleInfo.setAsConstructor );
  }

}

/**
 * Used as default ChannelShuffler.ShuffleInfo provider.
 */
ShuffleInfoPool.Singleton = new ShuffleInfoPool();

<|endoftext|>


<commit_before>/* Copyright 2012 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License 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.
 */

import { Dict, isDict } from "./primitives.js";
import { CCITTFaxDecoder } from "./ccitt.js";
import { DecodeStream } from "./stream.js";

const CCITTFaxStream = (function CCITTFaxStreamClosure() {
  // eslint-disable-next-line no-shadow
  function CCITTFaxStream(str, maybeLength, params) {
    this.str = str;
    this.dict = str.dict;

    if (!isDict(params)) {
      params = Dict.empty;
    }

    const source = {
      next() {
        return str.getByte();
      },
    };
    this.ccittFaxDecoder = new CCITTFaxDecoder(source, {
      K: params.get("K"),
      EndOfLine: params.get("EndOfLine"),
      EncodedByteAlign: params.get("EncodedByteAlign"),
      Columns: params.get("Columns"),
      Rows: params.get("Rows"),
      EndOfBlock: params.get("EndOfBlock"),
      BlackIs1: params.get("BlackIs1"),
    });

    DecodeStream.call(this, maybeLength);
  }

  CCITTFaxStream.prototype = Object.create(DecodeStream.prototype);

  CCITTFaxStream.prototype.readBlock = function () {
    while (!this.eof) {
      const c = this.ccittFaxDecoder.readNextChar();
      if (c === -1) {
        this.eof = true;
        return;
      }
      this.ensureBuffer(this.bufferLength + 1);
      this.buffer[this.bufferLength++] = c;
    }
  };

  return CCITTFaxStream;
})();

export { CCITTFaxStream };
<commit_msg>Convert `src/core/ccitt_stream.js` to use standard classes<commit_after>/* Copyright 2012 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License 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.
 */

import { Dict, isDict } from "./primitives.js";
import { CCITTFaxDecoder } from "./ccitt.js";
import { DecodeStream } from "./stream.js";

class CCITTFaxStream extends DecodeStream {
  constructor(str, maybeLength, params) {
    super(maybeLength);

    this.str = str;
    this.dict = str.dict;

    if (!isDict(params)) {
      params = Dict.empty;
    }

    const source = {
      next() {
        return str.getByte();
      },
    };
    this.ccittFaxDecoder = new CCITTFaxDecoder(source, {
      K: params.get("K"),
      EndOfLine: params.get("EndOfLine"),
      EncodedByteAlign: params.get("EncodedByteAlign"),
      Columns: params.get("Columns"),
      Rows: params.get("Rows"),
      EndOfBlock: params.get("EndOfBlock"),
      BlackIs1: params.get("BlackIs1"),
    });
  }

  readBlock() {
    while (!this.eof) {
      const c = this.ccittFaxDecoder.readNextChar();
      if (c === -1) {
        this.eof = true;
        return;
      }
      this.ensureBuffer(this.bufferLength + 1);
      this.buffer[this.bufferLength++] = c;
    }
  }
}

export { CCITTFaxStream };
<|endoftext|>


<commit_before>'use strict';

/**
 * Module that contains some useful helper functions.
 */

const Util = {
	youtubeTitleRegExps: [
		// Artist "Track", Artist: "Track", Artist - "Track", etc.
		{
			pattern: /(.+?)(\s|-|—|:)+\s*"(.+?)"/,
			groups: { artist: 1, track: 3 }
		},
		// Artist「Track」 (Japanese tracks)
		{
			pattern: /(.+?)「(.+?)」/,
			groups: { artist: 1, track: 2 }
		},
		// Track (... by Artist)
		{
			pattern: /([\w\d][\w\d\s]*?)\s+\([^)]*\s*by\s*([^)]+)+\)/,
			groups: { artist: 2, track: 1 }
		}
	],

	/**
	 * Extract artist and track from Youtube video title.
	 * @param  {String} videoTitle Youtube video title
	 * @param  {Array} separators Array of separators
	 * @return {Object} Object contains artist and track fields
	 */
	processYoutubeVideoTitle(videoTitle) {
		if (!videoTitle) {
			return this.makeEmptyArtistTrack();
		}

		// Remove [genre] or 【genre】 from the beginning of the title
		let title = videoTitle.replace(/^((\[[^\]]+\])|(【[^】]+】))\s*-*\s*/i, '');

		let [artist, track] = [null, null];

		// Try to match one of the regexps
		for (let regExp of Util.youtubeTitleRegExps) {
			let artistTrack = title.match(regExp.pattern);
			if (artistTrack) {
				artist = artistTrack[regExp.groups.artist];
				track = artistTrack[regExp.groups.track];
				break;
			}
		}

		// No match? Try splitting, then.
		if (artist === null && track === null) {
			({ artist, track } = this.splitArtistTrack(title));
		}

		return { artist, track };
	},

	/**
	 * Parse given video URL and return video ID.
	 * @param  {String} videoUrl Video URL
	 * @return {String} Video ID
	 */
	getYoutubeVideoIdFromUrl(videoUrl) {
		if (!videoUrl) {
			return null;
		}

		let match = videoUrl.match(Util.videoIdRegExp);
		if (match) {
			return match[7];
		}

		return null;
	},

	/**
	 * Normalize given URL. Currently it only normalizes
	 * protocol-relative links.
	 * @param  {String} url URL, which is possibly protocol-relative
	 * @return {String} Normalized URL
	 */
	normalizeUrl(url) {
		if (!url) {
			return null;
		}

		return url.startsWith('//') ? location.protocol + url : url;
	},

	/**
	 * Convert given time-string into seconds.
	 * @param  {String} str Time-string in h:m:s format
	 * @return {Number} Seconds
	 */
	stringToSeconds(str) {
		if (!str) {
			return 0;
		}

		let s = str.toString().trim();
		let val = 0;
		let seconds = 0;

		let isNegative = s.startsWith('-');
		if (isNegative) {
			s = s.substr(1);
		}

		for (let i = 0; i < 3; i++) {
			let idx = s.lastIndexOf(':');
			if (idx > -1) {
				val = parseInt(s.substr(idx + 1));
				seconds += val * Math.pow(60, i);
				s = s.substr(0, idx);
			} else {
				val = parseInt(s);
				seconds += val * Math.pow(60, i);
				break;
			}
		}

		if (isNegative) {
			seconds = -seconds;
		}

		return seconds || 0;
	},

	/**
	 * Find first occurence of possible separator in given string
	 * and return separator's position and size in chars or null.
	 * @param  {String} str String contains separator
	 * @param  {Array} separators Array of separators
	 * @return {Object} Object contains position and width of separator
	 */
	findSeparator(str, separators = null) {
		if (str === null || str.length === 0) {
			return null;
		}

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

		for (let sep of separators) {
			let index = str.indexOf(sep);
			if (index > -1) {
				return { index, length: sep.length };
			}
		}

		return null;
	},

	/**
	 * Join array of artist name into a string. The array must contain objects
	 * that have 'textContent' property (DOM node).
	 * @param  {Array} artists Array of DOM nodes
	 * @return {String} String joined by separator
	 */
	joinArtists(artists) {
		return artists.map((artist) => {
			return artist.textContent;
		}).join(this.ARTIST_SEPARATOR);
	},

	/**
	 * Split string to artist and track.
	 * @param  {String} str String contains artist and track
	 * @param  {Array} separators Array of separators
	 * @param  {Boolean} swap Swap artist and track values
	 * @return {Object} Object contains artist and track fields
	 */
	splitArtistTrack(str, separators = null, swap = false) {
		let [artist, track] = this.splitString(str, separators, swap);
		return { artist, track };
	},

	/**
	 * Split string to current time and duration.
	 * @param  {String} str String contains current time and duration
	 * @param  {String} sep Separator
	 * @param  {Boolean} swap Swap currentTime and duration values
	 * @return {Object} Array ontains 'currentTime' and 'duration' fields
	 */
	splitTimeInfo(str, sep = '/', swap = false) {
		let [currentTime, duration] = this.splitString(str, [sep], swap);
		if (currentTime) {
			currentTime = this.stringToSeconds(currentTime);
		}
		if (duration) {
			duration = this.stringToSeconds(duration);
		}

		return { currentTime, duration };
	},

	/**
	 * Split string to two ones using array of separators.
	 * @param  {String} str Any string
	 * @param  {Array} separators Array of separators
	 * @param  {Boolean} swap Swap values
	 * @return {Array} Array of strings splitted by separator
	 */
	splitString(str, separators = null, swap = false) {
		let first = null;
		let second = null;

		if (str) {
			let separator = this.findSeparator(str, separators);

			if (separator !== null) {
				first = str.substr(0, separator.index);
				second = str.substr(separator.index + separator.length);

				if (swap) {
					[second, first] = [first, second];
				}
			}
		}

		return [first, second];
	},

	/**
	 * Verify time value and return time as a Number object.
	 * Return null value if time value is not a number.
	 * @param  {Any} time Time value
	 * @return {Number} time value as a Number object
	 */
	escapeBadTimeValues(time) {
		if (typeof time !== 'number') {
			return null;
		}
		if (isNaN(time) || !isFinite(time)) {
			return null;
		}
		return Math.round(time);
	},

	/**
	 * Extract track art URL from CSS property.
	 * @param  {String} cssProperty CSS property
	 * @return {String} Track art URL
	 */
	extractUrlFromCssProperty(cssProperty) {
		let match = /url\((['"]?)(.*)\1\)/.exec(cssProperty);
		if (match) {
			return match[2].trim();
		}
		return null;
	},

	/**
	 * Inject script into document.
	 * @param {String} scriptUrl script URL
	 */
	injectScriptIntoDocument(scriptUrl) {
		let script = document.createElement('script');
		script.src = scriptUrl;
		script.onload = function() {
			this.parentNode.removeChild(this);
		};
		(document.head || document.documentElement).appendChild(script);
	},

	/**
	 * Returns a function, that, when invoked, will only be triggered
	 * at most once during a given window of time.
	 *
	 * Taken from Underscore library.
	 *
	 * @param  {Function} func Function to be throttled
	 * @param  {Number} wait Time before function calls
	 * @param  {Object} options Options
	 * @return {Function} Throttled function
	 */
	throttle(func, wait, options = {}) {
		let context;
		let args;
		let result;
		let timeout = null;
		let previous = 0;

		function later() {
			previous = options.leading === false ? 0 : Date.now();
			timeout = null;
			result = func.apply(context, args);
			if (!timeout) {
				context = args = null;
			}
		}
		return function() {
			let now = Date.now();
			if (!previous && options.leading === false) {
				previous = now;
			}
			let remaining = wait - (now - previous);
			context = this;
			args = arguments;
			if (remaining <= 0 || remaining > wait) {
				if (timeout) {
					clearTimeout(timeout);
					timeout = null;
				}
				previous = now;
				result = func.apply(context, args);
				if (!timeout) {
					context = args = null;
				}
			} else if (!timeout && options.trailing !== false) {
				timeout = setTimeout(later, remaining);
			}
			return result;
		};
	},

	/**
	 * Check if given 'artistTrack' object is empty. The object means empty
	 * if its 'artist' and 'track' properties are undefined, null or empty.
	 * @param  {Object}  artistTrack Object contains artist and track info
	 * @return {Boolean} True if object is empty; false otherwise
	 */
	isArtistTrackEmpty(artistTrack) {
		return !(artistTrack.artist && artistTrack.track);
	},

	/**
	 * Default array of separators used to split artist and track info.
	 * Push new separators in the implementation if required.
	 *
	 * @type {Array}
	 */
	separators: [
		' -- ', '--', ' - ', ' – ', ' — ',
		' // ', '-', '–', '—', ':', '|', '///', '/'
	],

	/**
	 * Create an object that contains no artist and track info.
	 * @return {Object} Object contains no artist and track info
	 */
	makeEmptyArtistTrack() {
		return { artist: null, track: null };
	},

	/**
	 * Regular expression used to get Youtube video ID from URL. It covers
	 * default, shortened and embed URLs.
	 * @type {RegExp}
	 */
	videoIdRegExp: /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?.*v=))([^#&?]*).*/,

	/**
	 * Separator used to join array of artist names into a single string.
	 * @type {String}
	 */
	ARTIST_SEPARATOR: ', ',
};

/**
 * Export Util object if script is executed in Node.js context.
 */
if (typeof module !== 'undefined') {
	module.exports = Util;
}
<commit_msg>Provide base when using `parseInt` function<commit_after>'use strict';

/**
 * Module that contains some useful helper functions.
 */

const Util = {
	youtubeTitleRegExps: [
		// Artist "Track", Artist: "Track", Artist - "Track", etc.
		{
			pattern: /(.+?)(\s|-|—|:)+\s*"(.+?)"/,
			groups: { artist: 1, track: 3 }
		},
		// Artist「Track」 (Japanese tracks)
		{
			pattern: /(.+?)「(.+?)」/,
			groups: { artist: 1, track: 2 }
		},
		// Track (... by Artist)
		{
			pattern: /([\w\d][\w\d\s]*?)\s+\([^)]*\s*by\s*([^)]+)+\)/,
			groups: { artist: 2, track: 1 }
		}
	],

	/**
	 * Extract artist and track from Youtube video title.
	 * @param  {String} videoTitle Youtube video title
	 * @param  {Array} separators Array of separators
	 * @return {Object} Object contains artist and track fields
	 */
	processYoutubeVideoTitle(videoTitle) {
		if (!videoTitle) {
			return this.makeEmptyArtistTrack();
		}

		// Remove [genre] or 【genre】 from the beginning of the title
		let title = videoTitle.replace(/^((\[[^\]]+\])|(【[^】]+】))\s*-*\s*/i, '');

		let [artist, track] = [null, null];

		// Try to match one of the regexps
		for (let regExp of Util.youtubeTitleRegExps) {
			let artistTrack = title.match(regExp.pattern);
			if (artistTrack) {
				artist = artistTrack[regExp.groups.artist];
				track = artistTrack[regExp.groups.track];
				break;
			}
		}

		// No match? Try splitting, then.
		if (artist === null && track === null) {
			({ artist, track } = this.splitArtistTrack(title));
		}

		return { artist, track };
	},

	/**
	 * Parse given video URL and return video ID.
	 * @param  {String} videoUrl Video URL
	 * @return {String} Video ID
	 */
	getYoutubeVideoIdFromUrl(videoUrl) {
		if (!videoUrl) {
			return null;
		}

		let match = videoUrl.match(Util.videoIdRegExp);
		if (match) {
			return match[7];
		}

		return null;
	},

	/**
	 * Normalize given URL. Currently it only normalizes
	 * protocol-relative links.
	 * @param  {String} url URL, which is possibly protocol-relative
	 * @return {String} Normalized URL
	 */
	normalizeUrl(url) {
		if (!url) {
			return null;
		}

		return url.startsWith('//') ? location.protocol + url : url;
	},

	/**
	 * Convert given time-string into seconds.
	 * @param  {String} str Time-string in h:m:s format
	 * @return {Number} Seconds
	 */
	stringToSeconds(str) {
		if (!str) {
			return 0;
		}

		let s = str.toString().trim();
		let val = 0;
		let seconds = 0;

		let isNegative = s.startsWith('-');
		if (isNegative) {
			s = s.substr(1);
		}

		for (let i = 0; i < 3; i++) {
			let idx = s.lastIndexOf(':');
			if (idx > -1) {
				val = parseInt(s.substr(idx + 1), 10);
				seconds += val * Math.pow(60, i);
				s = s.substr(0, idx);
			} else {
				val = parseInt(s, 10);
				seconds += val * Math.pow(60, i);
				break;
			}
		}

		if (isNegative) {
			seconds = -seconds;
		}

		return seconds || 0;
	},

	/**
	 * Find first occurence of possible separator in given string
	 * and return separator's position and size in chars or null.
	 * @param  {String} str String contains separator
	 * @param  {Array} separators Array of separators
	 * @return {Object} Object contains position and width of separator
	 */
	findSeparator(str, separators = null) {
		if (str === null || str.length === 0) {
			return null;
		}

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

		for (let sep of separators) {
			let index = str.indexOf(sep);
			if (index > -1) {
				return { index, length: sep.length };
			}
		}

		return null;
	},

	/**
	 * Join array of artist name into a string. The array must contain objects
	 * that have 'textContent' property (DOM node).
	 * @param  {Array} artists Array of DOM nodes
	 * @return {String} String joined by separator
	 */
	joinArtists(artists) {
		return artists.map((artist) => {
			return artist.textContent;
		}).join(this.ARTIST_SEPARATOR);
	},

	/**
	 * Split string to artist and track.
	 * @param  {String} str String contains artist and track
	 * @param  {Array} separators Array of separators
	 * @param  {Boolean} swap Swap artist and track values
	 * @return {Object} Object contains artist and track fields
	 */
	splitArtistTrack(str, separators = null, swap = false) {
		let [artist, track] = this.splitString(str, separators, swap);
		return { artist, track };
	},

	/**
	 * Split string to current time and duration.
	 * @param  {String} str String contains current time and duration
	 * @param  {String} sep Separator
	 * @param  {Boolean} swap Swap currentTime and duration values
	 * @return {Object} Array ontains 'currentTime' and 'duration' fields
	 */
	splitTimeInfo(str, sep = '/', swap = false) {
		let [currentTime, duration] = this.splitString(str, [sep], swap);
		if (currentTime) {
			currentTime = this.stringToSeconds(currentTime);
		}
		if (duration) {
			duration = this.stringToSeconds(duration);
		}

		return { currentTime, duration };
	},

	/**
	 * Split string to two ones using array of separators.
	 * @param  {String} str Any string
	 * @param  {Array} separators Array of separators
	 * @param  {Boolean} swap Swap values
	 * @return {Array} Array of strings splitted by separator
	 */
	splitString(str, separators = null, swap = false) {
		let first = null;
		let second = null;

		if (str) {
			let separator = this.findSeparator(str, separators);

			if (separator !== null) {
				first = str.substr(0, separator.index);
				second = str.substr(separator.index + separator.length);

				if (swap) {
					[second, first] = [first, second];
				}
			}
		}

		return [first, second];
	},

	/**
	 * Verify time value and return time as a Number object.
	 * Return null value if time value is not a number.
	 * @param  {Any} time Time value
	 * @return {Number} time value as a Number object
	 */
	escapeBadTimeValues(time) {
		if (typeof time !== 'number') {
			return null;
		}
		if (isNaN(time) || !isFinite(time)) {
			return null;
		}
		return Math.round(time);
	},

	/**
	 * Extract track art URL from CSS property.
	 * @param  {String} cssProperty CSS property
	 * @return {String} Track art URL
	 */
	extractUrlFromCssProperty(cssProperty) {
		let match = /url\((['"]?)(.*)\1\)/.exec(cssProperty);
		if (match) {
			return match[2].trim();
		}
		return null;
	},

	/**
	 * Inject script into document.
	 * @param {String} scriptUrl script URL
	 */
	injectScriptIntoDocument(scriptUrl) {
		let script = document.createElement('script');
		script.src = scriptUrl;
		script.onload = function() {
			this.parentNode.removeChild(this);
		};
		(document.head || document.documentElement).appendChild(script);
	},

	/**
	 * Returns a function, that, when invoked, will only be triggered
	 * at most once during a given window of time.
	 *
	 * Taken from Underscore library.
	 *
	 * @param  {Function} func Function to be throttled
	 * @param  {Number} wait Time before function calls
	 * @param  {Object} options Options
	 * @return {Function} Throttled function
	 */
	throttle(func, wait, options = {}) {
		let context;
		let args;
		let result;
		let timeout = null;
		let previous = 0;

		function later() {
			previous = options.leading === false ? 0 : Date.now();
			timeout = null;
			result = func.apply(context, args);
			if (!timeout) {
				context = args = null;
			}
		}
		return function() {
			let now = Date.now();
			if (!previous && options.leading === false) {
				previous = now;
			}
			let remaining = wait - (now - previous);
			context = this;
			args = arguments;
			if (remaining <= 0 || remaining > wait) {
				if (timeout) {
					clearTimeout(timeout);
					timeout = null;
				}
				previous = now;
				result = func.apply(context, args);
				if (!timeout) {
					context = args = null;
				}
			} else if (!timeout && options.trailing !== false) {
				timeout = setTimeout(later, remaining);
			}
			return result;
		};
	},

	/**
	 * Check if given 'artistTrack' object is empty. The object means empty
	 * if its 'artist' and 'track' properties are undefined, null or empty.
	 * @param  {Object}  artistTrack Object contains artist and track info
	 * @return {Boolean} True if object is empty; false otherwise
	 */
	isArtistTrackEmpty(artistTrack) {
		return !(artistTrack.artist && artistTrack.track);
	},

	/**
	 * Default array of separators used to split artist and track info.
	 * Push new separators in the implementation if required.
	 *
	 * @type {Array}
	 */
	separators: [
		' -- ', '--', ' - ', ' – ', ' — ',
		' // ', '-', '–', '—', ':', '|', '///', '/'
	],

	/**
	 * Create an object that contains no artist and track info.
	 * @return {Object} Object contains no artist and track info
	 */
	makeEmptyArtistTrack() {
		return { artist: null, track: null };
	},

	/**
	 * Regular expression used to get Youtube video ID from URL. It covers
	 * default, shortened and embed URLs.
	 * @type {RegExp}
	 */
	videoIdRegExp: /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?.*v=))([^#&?]*).*/,

	/**
	 * Separator used to join array of artist names into a single string.
	 * @type {String}
	 */
	ARTIST_SEPARATOR: ', ',
};

/**
 * Export Util object if script is executed in Node.js context.
 */
if (typeof module !== 'undefined') {
	module.exports = Util;
}
<|endoftext|>


<commit_before>var inherit = require('../util').inherit;
var feature = require('./feature');

//////////////////////////////////////////////////////////////////////////////
/**
 * Create a new instance of class pointFeature
 *
 * @class geo.pointFeature
 * @param {object} arg Options object
 * @param {boolean} arg.clustering Enable point clustering
 * @extends geo.feature
 * @returns {geo.pointFeature}
 */
//////////////////////////////////////////////////////////////////////////////
var pointFeature = function (arg) {
  'use strict';
  if (!(this instanceof pointFeature)) {
    return new pointFeature(arg);
  }
  arg = arg || {};
  feature.call(this, arg);

  var $ = require('jquery');
  var timestamp = require('./timestamp');
  var clustering = require('../util/clustering');
  var geo_event = require('./event');
  var util = require('../util');
  var wigglemaps = require('../util/wigglemaps');

  ////////////////////////////////////////////////////////////////////////////
  /**
   * @private
   */
  ////////////////////////////////////////////////////////////////////////////
  var m_this = this,
      s_init = this._init,
      m_rangeTree = null,
      m_rangeTreeTime = timestamp(),
      s_data = this.data,
      m_maxRadius = 0,
      m_clustering = arg.clustering,
      m_clusterTree = null,
      m_allData = [],
      m_lastZoom = null,
      m_ignoreData = false; // flag to ignore data() calls made locally

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get/Set clustering option
   *
   * @returns {geo.pointFeature|boolean}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.clustering = function (val) {
    if (val === undefined) {
      return m_clustering;
    }
    if (m_clustering && !val) {
      // Throw out the cluster tree and reset the data
      m_clusterTree = null;
      m_clustering = false;
      s_data(m_allData);
      m_allData = null;
    } else if (!m_clustering && val) {
      // Generate the cluster tree
      m_clustering = true;
      m_this._clusterData();
    }
    return m_this;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Generate the clustering tree from positions.  This might be async in the
   * future.
   */
  ////////////////////////////////////////////////////////////////////////////
  this._clusterData = function () {
    if (!m_clustering) {
      // clustering is not enabled, so this is a no-op
      return;
    }

    // set clustering options to default if an options argument wasn't supplied
    var opts = m_clustering === true ? {radius: 0.01} : m_clustering;

    // generate the cluster tree from the raw data
    var position = m_this.position();
    m_clusterTree = new clustering.ClusterGroup(
        opts, m_this.layer().width(), m_this.layer().height());

    m_allData.forEach(function (d, i) {

      // for each point in the data set normalize the coordinate
      // representation and add the point to the cluster treee
      var pt = util.normalizeCoordinates(position(d, i));
      pt.index = i;
      m_clusterTree.addPoint(pt);
    });

    // reset the last zoom state and trigger a redraw at the current zoom level
    m_lastZoom = null;
    m_this._handleZoom(m_this.layer().map().zoom());
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Handle zoom events for clustering.  This keeps track of the last
   * clustering level, and only regenerates the displayed points when the
   * zoom level changes.
   */
  ////////////////////////////////////////////////////////////////////////////
  this._handleZoom = function (zoom) {
    // get the current zoom level rounded down
    var z = Math.floor(zoom);

    if (!m_clustering || z === m_lastZoom) {
      // short cut when there is nothing to do
      return;
    }

    // store the current zoom level privately
    m_lastZoom = z;

    // get the raw data elements for the points at the current level
    var data = m_clusterTree.points(z).map(function (d) {
      return m_allData[d.index];
    });

    // append the clusters at the current level
    m_clusterTree.clusters(z).forEach(function (d) {
      // mark the datum as a cluster for accessor methods
      d.__cluster = true;

      // store all of the data objects for each point in the cluster as __data
      d.__data = [];
      d.obj.each(function (e) {
        d.__data.push(m_allData[e.index]);
      });
      data.push(d);
    });

    // prevent recomputing the clustering and set the new data array
    m_ignoreData = true;
    m_this.data(data);
    m_this.layer().map().draw(); // replace with m_this.draw() when gl is fixed
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get/Set position
   *
   * @returns {geo.pointFeature}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.position = function (val) {
    if (val === undefined) {
      return m_this.style('position');
    } else {
      val = util.ensureFunction(val);
      m_this.style('position', function (d, i) {
        if (d.__cluster) {
          return d;
        } else {
          return val(d, i);
        }
      });
      m_this.dataTime().modified();
      m_this.modified();
    }
    return m_this;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Update the current range tree object.  Should be called whenever the
   * data changes.
   */
  ////////////////////////////////////////////////////////////////////////////
  this._updateRangeTree = function () {
    if (m_rangeTreeTime.getMTime() >= m_this.dataTime().getMTime()) {
      return;
    }
    var pts, position,
        radius = m_this.style.get('radius'),
        stroke = m_this.style.get('stroke'),
        strokeWidth = m_this.style.get('strokeWidth');

    position = m_this.position();

    m_maxRadius = 0;

    // create an array of positions in geo coordinates
    pts = m_this.data().map(function (d, i) {
      var pt = position(d);
      pt.idx = i;

      // store the maximum point radius
      m_maxRadius = Math.max(
        m_maxRadius,
        radius(d, i) + (stroke(d, i) ? strokeWidth(d, i) : 0)
      );

      return pt;
    });

    m_rangeTree = new wigglemaps.RangeTree(pts);
    m_rangeTreeTime.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Returns an array of datum indices that contain the given point.
   * Largely adapted from wigglemaps pointQuerier:
   *
   * https://github.com/dotskapes/wigglemaps/blob/cf5bed3fbfe2c3e48d31799462a80c564be1fb60/src/query/PointQuerier.js
   */
  ////////////////////////////////////////////////////////////////////////////
  this.pointSearch = function (p) {
    var min, max, data, idx = [], box, found = [], ifound = [], map, pt,
        corners,
        stroke = m_this.style.get('stroke'),
        strokeWidth = m_this.style.get('strokeWidth'),
        radius = m_this.style.get('radius');

    if (!m_this.selectionAPI()) {
      return [];
    }

    data = m_this.data();
    if (!data || !data.length) {
      return {
        found: [],
        index: []
      };
    }

    map = m_this.layer().map();
    pt = map.gcsToDisplay(p);
    // check all corners to make sure we handle rotations
    corners = [
      map.displayToGcs({x: pt.x - m_maxRadius, y: pt.y - m_maxRadius}),
      map.displayToGcs({x: pt.x + m_maxRadius, y: pt.y - m_maxRadius}),
      map.displayToGcs({x: pt.x - m_maxRadius, y: pt.y + m_maxRadius}),
      map.displayToGcs({x: pt.x + m_maxRadius, y: pt.y + m_maxRadius})
    ];
    min = {
      x: Math.min(corners[0].x, corners[1].x, corners[2].x, corners[3].x),
      y: Math.min(corners[0].y, corners[1].y, corners[2].y, corners[3].y)
    };
    max = {
      x: Math.max(corners[0].x, corners[1].x, corners[2].x, corners[3].x),
      y: Math.max(corners[0].y, corners[1].y, corners[2].y, corners[3].y)
    };

    // Find points inside the bounding box
    box = new wigglemaps.Box(
      wigglemaps.vect(min.x, min.y),
      wigglemaps.vect(max.x, max.y)
    );
    m_this._updateRangeTree();
    m_rangeTree.search(box).forEach(function (q) {
      idx.push(q.idx);
    });

    // Filter by circular region
    idx.forEach(function (i) {
      var d = data[i],
          p = m_this.position()(d, i),
          dx, dy, rad, rad2;

      rad = radius(data[i], i);
      rad += stroke(data[i], i) ? strokeWidth(data[i], i) : 0;
      rad2 = rad * rad;
      p = map.gcsToDisplay(p);
      dx = p.x - pt.x;
      dy = p.y - pt.y;
      if (dx * dx + dy * dy <= rad2) {
        found.push(d);
        ifound.push(i);
      }
    });

    return {
      data: found,
      index: ifound
    };
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Returns an array of datum indices that are contained in the given box.
   */
  ////////////////////////////////////////////////////////////////////////////
  this.boxSearch = function (lowerLeft, upperRight) {
    var pos = m_this.position(),
        idx = [];
    // TODO: use the range tree
    m_this.data().forEach(function (d, i) {
      var p = pos(d);
      if (p.x >= lowerLeft.x &&
          p.x <= upperRight.x &&
          p.y >= lowerLeft.y &&
          p.y <= upperRight.y
      ) {
        idx.push(i);
      }
    });
    return idx;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Overloaded data method that updates the internal range tree on write.
   */
  ////////////////////////////////////////////////////////////////////////////
  this.data = function (data) {
    if (data === undefined) {
      return s_data();
    }
    if (m_clustering && !m_ignoreData) {
      m_allData = data;
      m_this._clusterData();
    } else {
      s_data(data);
    }
    m_ignoreData = false;
    return m_this;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Returns the bounding box for a given datum in screen coordinates as an
   * object: ::
   *
   *   {
   *     min: {
   *       x: value,
   *       y: value
   *     },
   *     max: {
   *       x: value,
   *       y: value
   *     }
   *   }
   *
   * @returns {object}
   */
  ////////////////////////////////////////////////////////////////////////////
  this._boundingBox = function (d) {
    var pt, radius;

    // get the position in geo coordinates
    pt = m_this.position()(d);

    // convert to screen coordinates
    pt = m_this.layer().map().gcsToDisplay(pt);

    // get the radius of the points (should we add stroke width?)
    radius = m_this.style().radius(d);

    return {
      min: {
        x: pt.x - radius,
        y: pt.y - radius
      },
      max: {
        x: pt.x + radius,
        y: pt.y + radius
      }
    };
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Initialize
   */
  ////////////////////////////////////////////////////////////////////////////
  this._init = function (arg) {
    s_init.call(m_this, arg);

    var defaultStyle = $.extend(
        {},
        {
          radius: 5.0,
          stroke: true,
          strokeColor: { r: 0.851, g: 0.604, b: 0.0 },
          strokeWidth: 1.25,
          strokeOpacity: 1.0,
          fillColor: { r: 1.0, g: 0.839, b: 0.439 },
          fill: true,
          fillOpacity: 0.8,
          sprites: false,
          sprites_image: null,
          position: function (d) { return d; }
        },
      arg.style === undefined ? {} : arg.style
    );

    if (arg.position !== undefined) {
      defaultStyle.position = arg.position;
    }

    m_this.style(defaultStyle);
    m_this.dataTime().modified();

    // bind to the zoom handler for point clustering
    m_this.geoOn(geo_event.zoom, function (evt) {
      m_this._handleZoom(evt.zoomLevel);
    });
  };

  return m_this;
};

/**
 * Object specification for a point feature.
 *
 * @extends geo.feature.spec // need to make a jsdoc plugin for this to work
 * @typedef geo.pointFeature.spec
 * @type {object}
 */

/**
 * Create a pointFeature from an object.
 * @see {@link geo.feature.create}
 * @param {geo.layer} layer The layer to add the feature to
 * @param {geo.pointFeature.spec} spec The object specification
 * @returns {geo.pointFeature|null}
 */
pointFeature.create = function (layer, renderer, spec) {
  'use strict';

  spec.type = 'point';
  return feature.create(layer, spec);
};

inherit(pointFeature, feature);
module.exports = pointFeature;
<commit_msg>Fix clustering selenium test<commit_after>var inherit = require('../util').inherit;
var feature = require('./feature');

//////////////////////////////////////////////////////////////////////////////
/**
 * Create a new instance of class pointFeature
 *
 * @class geo.pointFeature
 * @param {object} arg Options object
 * @param {boolean} arg.clustering Enable point clustering
 * @extends geo.feature
 * @returns {geo.pointFeature}
 */
//////////////////////////////////////////////////////////////////////////////
var pointFeature = function (arg) {
  'use strict';
  if (!(this instanceof pointFeature)) {
    return new pointFeature(arg);
  }
  arg = arg || {};
  feature.call(this, arg);

  var $ = require('jquery');
  var timestamp = require('./timestamp');
  var ClusterGroup = require('../util/clustering');
  var geo_event = require('./event');
  var util = require('../util');
  var wigglemaps = require('../util/wigglemaps');

  ////////////////////////////////////////////////////////////////////////////
  /**
   * @private
   */
  ////////////////////////////////////////////////////////////////////////////
  var m_this = this,
      s_init = this._init,
      m_rangeTree = null,
      m_rangeTreeTime = timestamp(),
      s_data = this.data,
      m_maxRadius = 0,
      m_clustering = arg.clustering,
      m_clusterTree = null,
      m_allData = [],
      m_lastZoom = null,
      m_ignoreData = false; // flag to ignore data() calls made locally

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get/Set clustering option
   *
   * @returns {geo.pointFeature|boolean}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.clustering = function (val) {
    if (val === undefined) {
      return m_clustering;
    }
    if (m_clustering && !val) {
      // Throw out the cluster tree and reset the data
      m_clusterTree = null;
      m_clustering = false;
      s_data(m_allData);
      m_allData = null;
    } else if (!m_clustering && val) {
      // Generate the cluster tree
      m_clustering = true;
      m_this._clusterData();
    }
    return m_this;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Generate the clustering tree from positions.  This might be async in the
   * future.
   */
  ////////////////////////////////////////////////////////////////////////////
  this._clusterData = function () {
    if (!m_clustering) {
      // clustering is not enabled, so this is a no-op
      return;
    }

    // set clustering options to default if an options argument wasn't supplied
    var opts = m_clustering === true ? {radius: 0.01} : m_clustering;

    // generate the cluster tree from the raw data
    var position = m_this.position();
    m_clusterTree = new ClusterGroup(
        opts, m_this.layer().width(), m_this.layer().height());

    m_allData.forEach(function (d, i) {

      // for each point in the data set normalize the coordinate
      // representation and add the point to the cluster treee
      var pt = util.normalizeCoordinates(position(d, i));
      pt.index = i;
      m_clusterTree.addPoint(pt);
    });

    // reset the last zoom state and trigger a redraw at the current zoom level
    m_lastZoom = null;
    m_this._handleZoom(m_this.layer().map().zoom());
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Handle zoom events for clustering.  This keeps track of the last
   * clustering level, and only regenerates the displayed points when the
   * zoom level changes.
   */
  ////////////////////////////////////////////////////////////////////////////
  this._handleZoom = function (zoom) {
    // get the current zoom level rounded down
    var z = Math.floor(zoom);

    if (!m_clustering || z === m_lastZoom) {
      // short cut when there is nothing to do
      return;
    }

    // store the current zoom level privately
    m_lastZoom = z;

    // get the raw data elements for the points at the current level
    var data = m_clusterTree.points(z).map(function (d) {
      return m_allData[d.index];
    });

    // append the clusters at the current level
    m_clusterTree.clusters(z).forEach(function (d) {
      // mark the datum as a cluster for accessor methods
      d.__cluster = true;

      // store all of the data objects for each point in the cluster as __data
      d.__data = [];
      d.obj.each(function (e) {
        d.__data.push(m_allData[e.index]);
      });
      data.push(d);
    });

    // prevent recomputing the clustering and set the new data array
    m_ignoreData = true;
    m_this.data(data);
    m_this.layer().map().draw(); // replace with m_this.draw() when gl is fixed
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Get/Set position
   *
   * @returns {geo.pointFeature}
   */
  ////////////////////////////////////////////////////////////////////////////
  this.position = function (val) {
    if (val === undefined) {
      return m_this.style('position');
    } else {
      val = util.ensureFunction(val);
      m_this.style('position', function (d, i) {
        if (d.__cluster) {
          return d;
        } else {
          return val(d, i);
        }
      });
      m_this.dataTime().modified();
      m_this.modified();
    }
    return m_this;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Update the current range tree object.  Should be called whenever the
   * data changes.
   */
  ////////////////////////////////////////////////////////////////////////////
  this._updateRangeTree = function () {
    if (m_rangeTreeTime.getMTime() >= m_this.dataTime().getMTime()) {
      return;
    }
    var pts, position,
        radius = m_this.style.get('radius'),
        stroke = m_this.style.get('stroke'),
        strokeWidth = m_this.style.get('strokeWidth');

    position = m_this.position();

    m_maxRadius = 0;

    // create an array of positions in geo coordinates
    pts = m_this.data().map(function (d, i) {
      var pt = position(d);
      pt.idx = i;

      // store the maximum point radius
      m_maxRadius = Math.max(
        m_maxRadius,
        radius(d, i) + (stroke(d, i) ? strokeWidth(d, i) : 0)
      );

      return pt;
    });

    m_rangeTree = new wigglemaps.RangeTree(pts);
    m_rangeTreeTime.modified();
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Returns an array of datum indices that contain the given point.
   * Largely adapted from wigglemaps pointQuerier:
   *
   * https://github.com/dotskapes/wigglemaps/blob/cf5bed3fbfe2c3e48d31799462a80c564be1fb60/src/query/PointQuerier.js
   */
  ////////////////////////////////////////////////////////////////////////////
  this.pointSearch = function (p) {
    var min, max, data, idx = [], box, found = [], ifound = [], map, pt,
        corners,
        stroke = m_this.style.get('stroke'),
        strokeWidth = m_this.style.get('strokeWidth'),
        radius = m_this.style.get('radius');

    if (!m_this.selectionAPI()) {
      return [];
    }

    data = m_this.data();
    if (!data || !data.length) {
      return {
        found: [],
        index: []
      };
    }

    map = m_this.layer().map();
    pt = map.gcsToDisplay(p);
    // check all corners to make sure we handle rotations
    corners = [
      map.displayToGcs({x: pt.x - m_maxRadius, y: pt.y - m_maxRadius}),
      map.displayToGcs({x: pt.x + m_maxRadius, y: pt.y - m_maxRadius}),
      map.displayToGcs({x: pt.x - m_maxRadius, y: pt.y + m_maxRadius}),
      map.displayToGcs({x: pt.x + m_maxRadius, y: pt.y + m_maxRadius})
    ];
    min = {
      x: Math.min(corners[0].x, corners[1].x, corners[2].x, corners[3].x),
      y: Math.min(corners[0].y, corners[1].y, corners[2].y, corners[3].y)
    };
    max = {
      x: Math.max(corners[0].x, corners[1].x, corners[2].x, corners[3].x),
      y: Math.max(corners[0].y, corners[1].y, corners[2].y, corners[3].y)
    };

    // Find points inside the bounding box
    box = new wigglemaps.Box(
      wigglemaps.vect(min.x, min.y),
      wigglemaps.vect(max.x, max.y)
    );
    m_this._updateRangeTree();
    m_rangeTree.search(box).forEach(function (q) {
      idx.push(q.idx);
    });

    // Filter by circular region
    idx.forEach(function (i) {
      var d = data[i],
          p = m_this.position()(d, i),
          dx, dy, rad, rad2;

      rad = radius(data[i], i);
      rad += stroke(data[i], i) ? strokeWidth(data[i], i) : 0;
      rad2 = rad * rad;
      p = map.gcsToDisplay(p);
      dx = p.x - pt.x;
      dy = p.y - pt.y;
      if (dx * dx + dy * dy <= rad2) {
        found.push(d);
        ifound.push(i);
      }
    });

    return {
      data: found,
      index: ifound
    };
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Returns an array of datum indices that are contained in the given box.
   */
  ////////////////////////////////////////////////////////////////////////////
  this.boxSearch = function (lowerLeft, upperRight) {
    var pos = m_this.position(),
        idx = [];
    // TODO: use the range tree
    m_this.data().forEach(function (d, i) {
      var p = pos(d);
      if (p.x >= lowerLeft.x &&
          p.x <= upperRight.x &&
          p.y >= lowerLeft.y &&
          p.y <= upperRight.y
      ) {
        idx.push(i);
      }
    });
    return idx;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Overloaded data method that updates the internal range tree on write.
   */
  ////////////////////////////////////////////////////////////////////////////
  this.data = function (data) {
    if (data === undefined) {
      return s_data();
    }
    if (m_clustering && !m_ignoreData) {
      m_allData = data;
      m_this._clusterData();
    } else {
      s_data(data);
    }
    m_ignoreData = false;
    return m_this;
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Returns the bounding box for a given datum in screen coordinates as an
   * object: ::
   *
   *   {
   *     min: {
   *       x: value,
   *       y: value
   *     },
   *     max: {
   *       x: value,
   *       y: value
   *     }
   *   }
   *
   * @returns {object}
   */
  ////////////////////////////////////////////////////////////////////////////
  this._boundingBox = function (d) {
    var pt, radius;

    // get the position in geo coordinates
    pt = m_this.position()(d);

    // convert to screen coordinates
    pt = m_this.layer().map().gcsToDisplay(pt);

    // get the radius of the points (should we add stroke width?)
    radius = m_this.style().radius(d);

    return {
      min: {
        x: pt.x - radius,
        y: pt.y - radius
      },
      max: {
        x: pt.x + radius,
        y: pt.y + radius
      }
    };
  };

  ////////////////////////////////////////////////////////////////////////////
  /**
   * Initialize
   */
  ////////////////////////////////////////////////////////////////////////////
  this._init = function (arg) {
    s_init.call(m_this, arg);

    var defaultStyle = $.extend(
        {},
        {
          radius: 5.0,
          stroke: true,
          strokeColor: { r: 0.851, g: 0.604, b: 0.0 },
          strokeWidth: 1.25,
          strokeOpacity: 1.0,
          fillColor: { r: 1.0, g: 0.839, b: 0.439 },
          fill: true,
          fillOpacity: 0.8,
          sprites: false,
          sprites_image: null,
          position: function (d) { return d; }
        },
      arg.style === undefined ? {} : arg.style
    );

    if (arg.position !== undefined) {
      defaultStyle.position = arg.position;
    }

    m_this.style(defaultStyle);
    m_this.dataTime().modified();

    // bind to the zoom handler for point clustering
    m_this.geoOn(geo_event.zoom, function (evt) {
      m_this._handleZoom(evt.zoomLevel);
    });
  };

  return m_this;
};

/**
 * Object specification for a point feature.
 *
 * @extends geo.feature.spec // need to make a jsdoc plugin for this to work
 * @typedef geo.pointFeature.spec
 * @type {object}
 */

/**
 * Create a pointFeature from an object.
 * @see {@link geo.feature.create}
 * @param {geo.layer} layer The layer to add the feature to
 * @param {geo.pointFeature.spec} spec The object specification
 * @returns {geo.pointFeature|null}
 */
pointFeature.create = function (layer, renderer, spec) {
  'use strict';

  spec.type = 'point';
  return feature.create(layer, spec);
};

inherit(pointFeature, feature);
module.exports = pointFeature;
<|endoftext|>


<commit_before>var MIN_WORD_LENGTH_BOUNDRY = 5;

var SETTING_DEFAULT_ASYNC = false,
  SETTING_DEFAULT_DEBUG = false,
  SETTING_DEFAULT_HTML = false,
  SETTING_DEFAULT_HYPH_CHAR = "\u00AD",
  SETTING_NAME_ASYNC = "async",
  SETTING_NAME_DEBUG = "debug",
  SETTING_NAME_HTML = "html",
  SETTING_NAME_HYPH_CHAR = "hyphenChar",
  SETTING_NAME_MIN_WORD_LENGTH = "minWordLength";

var _global =
  typeof global === "object"
    ? global
    : typeof window === "object"
    ? window
    : typeof this === "object"
    ? this
    : {};

function cloneObj(source) {
  var target = {};
  for (var key in source) {
    target[key] = source[key];
  }
  return target;
}

function keyOrDefault(object, key, defaultValue) {
  if (key in object) {
    return object[key];
  }
  return defaultValue;
}

function exceptionsFromDefinition(patternsDefinition, hyphenChar) {
  return patternsDefinition.exceptions.reduce(function (exceptions, exception) {
    exceptions[exception.replace(/\-/g, "")] = exception.replace(
      /\-/g,
      hyphenChar
    );
    return exceptions;
  }, {});
}

function createHyphenator(patternsDefinition, options) {
  options = options || {};
  var //
    asyncMode = keyOrDefault(
      options,
      SETTING_NAME_ASYNC,
      SETTING_DEFAULT_ASYNC
    ),
    caches = {},
    debug = keyOrDefault(options, SETTING_NAME_DEBUG, SETTING_DEFAULT_DEBUG),
    exceptions = {},
    hyphenChar = keyOrDefault(
      options,
      SETTING_NAME_HYPH_CHAR,
      SETTING_DEFAULT_HYPH_CHAR
    ),
    patterns = patternsDefinition.patterns.map(preprocessPattern),
    minWordLength = Math.max(
      options[SETTING_NAME_MIN_WORD_LENGTH] >> 0,
      MIN_WORD_LENGTH_BOUNDRY
    ),
    skipHTML = keyOrDefault(options, SETTING_NAME_HTML, SETTING_DEFAULT_HTML);

  // Prepare cache
  var cacheKey = hyphenChar + minWordLength;
  exceptions[cacheKey] = exceptionsFromDefinition(
    patternsDefinition,
    hyphenChar
  );
  caches[cacheKey] = cloneObj(exceptions[cacheKey]);

  if (asyncMode && !("Promise" in _global)) {
    throw new Error(
      "Failed to create hyphenator: Could not find global Promise object, needed for hyphenator to work in async mode"
    );
  }

  return function (text, options) {
    options = options || {};

    var localDebug = keyOrDefault(options, SETTING_NAME_DEBUG, debug),
      localHyphenChar = keyOrDefault(
        options,
        SETTING_NAME_HYPH_CHAR,
        hyphenChar
      ),
      localMinWordLength = Math.max(
        options[SETTING_NAME_MIN_WORD_LENGTH] >> 0,
        minWordLength
      ),
      cacheKey = localHyphenChar + localMinWordLength;

    if (!exceptions[cacheKey]) {
      exceptions[cacheKey] = exceptionsFromDefinition(
        patternsDefinition,
        localHyphenChar
      );
    }

    if (!caches[cacheKey]) {
      caches[cacheKey] = cloneObj(exceptions[cacheKey]);
    }

    return start(
      text,
      patterns,
      caches[cacheKey],
      localDebug,
      localHyphenChar,
      skipHTML,
      localMinWordLength,
      asyncMode
    );
  };
}
<commit_msg>Get rid of MIN_WORD_LENGTH_BOUNDRY in favor of SETTING_DEFAULT_MIN_WORD_LENGTH<commit_after>var SETTING_DEFAULT_ASYNC = false,
  SETTING_DEFAULT_DEBUG = false,
  SETTING_DEFAULT_HTML = false,
  SETTING_DEFAULT_HYPH_CHAR = "\u00AD",
  SETTING_DEFAULT_MIN_WORD_LENGTH = 5,
  SETTING_NAME_ASYNC = "async",
  SETTING_NAME_DEBUG = "debug",
  SETTING_NAME_HTML = "html",
  SETTING_NAME_HYPH_CHAR = "hyphenChar",
  SETTING_NAME_MIN_WORD_LENGTH = "minWordLength";

var _global =
  typeof global === "object"
    ? global
    : typeof window === "object"
    ? window
    : typeof this === "object"
    ? this
    : {};

function cloneObj(source) {
  var target = {};
  for (var key in source) {
    target[key] = source[key];
  }
  return target;
}

function keyOrDefault(object, key, defaultValue) {
  if (key in object) {
    return object[key];
  }
  return defaultValue;
}

function exceptionsFromDefinition(patternsDefinition, hyphenChar) {
  return patternsDefinition.exceptions.reduce(function (exceptions, exception) {
    exceptions[exception.replace(/\-/g, "")] = exception.replace(
      /\-/g,
      hyphenChar
    );
    return exceptions;
  }, {});
}

function createHyphenator(patternsDefinition, options) {
  options = options || {};
  var //
    asyncMode = keyOrDefault(
      options,
      SETTING_NAME_ASYNC,
      SETTING_DEFAULT_ASYNC
    ),
    caches = {},
    debug = keyOrDefault(options, SETTING_NAME_DEBUG, SETTING_DEFAULT_DEBUG),
    exceptions = {},
    hyphenChar = keyOrDefault(
      options,
      SETTING_NAME_HYPH_CHAR,
      SETTING_DEFAULT_HYPH_CHAR
    ),
    patterns = patternsDefinition.patterns.map(preprocessPattern),
    minWordLength = keyOrDefault(
      options,
      SETTING_NAME_MIN_WORD_LENGTH,
      SETTING_DEFAULT_MIN_WORD_LENGTH
    ),
    skipHTML = keyOrDefault(options, SETTING_NAME_HTML, SETTING_DEFAULT_HTML);

  // Prepare cache
  var cacheKey = hyphenChar + minWordLength;
  exceptions[cacheKey] = exceptionsFromDefinition(
    patternsDefinition,
    hyphenChar
  );
  caches[cacheKey] = cloneObj(exceptions[cacheKey]);

  if (asyncMode && !("Promise" in _global)) {
    throw new Error(
      "Failed to create hyphenator: Could not find global Promise object, needed for hyphenator to work in async mode"
    );
  }

  return function (text, options) {
    options = options || {};

    var localDebug = keyOrDefault(options, SETTING_NAME_DEBUG, debug),
      localHyphenChar = keyOrDefault(
        options,
        SETTING_NAME_HYPH_CHAR,
        hyphenChar
      ),
      localMinWordLength = Math.max(
        options[SETTING_NAME_MIN_WORD_LENGTH] >> 0,
        minWordLength
      ),
      cacheKey = localHyphenChar + localMinWordLength;

    if (!exceptions[cacheKey]) {
      exceptions[cacheKey] = exceptionsFromDefinition(
        patternsDefinition,
        localHyphenChar
      );
    }

    if (!caches[cacheKey]) {
      caches[cacheKey] = cloneObj(exceptions[cacheKey]);
    }

    return start(
      text,
      patterns,
      caches[cacheKey],
      localDebug,
      localHyphenChar,
      skipHTML,
      localMinWordLength,
      asyncMode
    );
  };
}
<|endoftext|>


<commit_before>// ----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ----------------------------------------------------------------------------
var statements = require('./statements'),
    serializeModule = require('./serialize'),
    dynamicSchemaModule = require('./dynamicSchema'),
    schemaModule = require('./schema'),
    columnsModule = require('./columns'),
    connections = require('./connections'),
    log = require('../../logger'),
    assert = require('../../utilities/assert').argument,
    promises = require('../../utilities/promises'),
    queries = require('../../query'),
    uuid = require('node-uuid');

module.exports = function (configuration) {
    configuration = configuration || {};

    var connection = connections(configuration),
        serialize = serializeModule(connection),
        schema = schemaModule(connection, serialize),
        columns = columnsModule(connection, serialize);

    var tableAccess = function (table) {
        assert(table, 'A table was not specified');

        var dynamicSchema = dynamicSchemaModule(connection, table, serialize);

        // set execute functions based on dynamic schema and operation
        var read, update, insert, del;
        if (table.dynamicSchema !== false) {
            read = dynamicSchema.read;
            update = insert = del = dynamicSchema.execute;
        } else {
            read = update = insert = del = serialize;
        }

        return {
            read: function (query) {
                return columns.for(table).then(function () {
                    query = query || queries.create(table.containerName);
                    return read(statements.read(query, table));
                });
            },
            update: function (item, query) {
                assert(item, 'An item to update was not provided');
                return columns.for(table).then(function () {
                    return update(statements.update(table, item, query), item);
                });
            },
            insert: function (item) {
                assert(item, 'An item to insert was not provided');
                return columns.for(table).then(function () {
                    item.id = item.id || uuid.v4();
                    return insert(statements.insert(table, item), item);
                });
            },
            delete: function (query, version) {
                assert(query, 'The delete query was not provided');
                return columns.for(table).then(function () {
                    return del(statements.delete(table, query, version));
                });
            },
            undelete: function (query, version) {
                assert(query, 'The undelete query was not provided');
                return columns.for(table).then(function () {
                    return del(statements.undelete(table, query, version));
                });
            },
            truncate: function () {
                return serialize(statements.truncate(table));
            },
            initialize: function () {
                return schema.initialize(table).catch(function (error) {
                    log.error('Error occurred during table initialization', error);
                    throw error;
                });
            },
            schema: function () {
                return schema.get(table);
            }
        };
    };

    // expose a method to allow direct execution of SQL queries
    tableAccess.execute = function (statement) {
        assert(statement, 'A SQL statement was not provided');
        return serialize(statement);
    };

    return tableAccess;
};
<commit_msg>Fix lint issue<commit_after>// ----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ----------------------------------------------------------------------------
var statements = require('./statements'),
    serializeModule = require('./serialize'),
    dynamicSchemaModule = require('./dynamicSchema'),
    schemaModule = require('./schema'),
    columnsModule = require('./columns'),
    connections = require('./connections'),
    log = require('../../logger'),
    assert = require('../../utilities/assert').argument,
    queries = require('../../query'),
    uuid = require('node-uuid');

module.exports = function (configuration) {
    configuration = configuration || {};

    var connection = connections(configuration),
        serialize = serializeModule(connection),
        schema = schemaModule(connection, serialize),
        columns = columnsModule(connection, serialize);

    var tableAccess = function (table) {
        assert(table, 'A table was not specified');

        var dynamicSchema = dynamicSchemaModule(connection, table, serialize);

        // set execute functions based on dynamic schema and operation
        var read, update, insert, del;
        if (table.dynamicSchema !== false) {
            read = dynamicSchema.read;
            update = insert = del = dynamicSchema.execute;
        } else {
            read = update = insert = del = serialize;
        }

        return {
            read: function (query) {
                return columns.for(table).then(function () {
                    query = query || queries.create(table.containerName);
                    return read(statements.read(query, table));
                });
            },
            update: function (item, query) {
                assert(item, 'An item to update was not provided');
                return columns.for(table).then(function () {
                    return update(statements.update(table, item, query), item);
                });
            },
            insert: function (item) {
                assert(item, 'An item to insert was not provided');
                return columns.for(table).then(function () {
                    item.id = item.id || uuid.v4();
                    return insert(statements.insert(table, item), item);
                });
            },
            delete: function (query, version) {
                assert(query, 'The delete query was not provided');
                return columns.for(table).then(function () {
                    return del(statements.delete(table, query, version));
                });
            },
            undelete: function (query, version) {
                assert(query, 'The undelete query was not provided');
                return columns.for(table).then(function () {
                    return del(statements.undelete(table, query, version));
                });
            },
            truncate: function () {
                return serialize(statements.truncate(table));
            },
            initialize: function () {
                return schema.initialize(table).catch(function (error) {
                    log.error('Error occurred during table initialization', error);
                    throw error;
                });
            },
            schema: function () {
                return schema.get(table);
            }
        };
    };

    // expose a method to allow direct execution of SQL queries
    tableAccess.execute = function (statement) {
        assert(statement, 'A SQL statement was not provided');
        return serialize(statement);
    };

    return tableAccess;
};
<|endoftext|>


<commit_before>"use strict"

const expect = require('expect')
const detectFormat = require('./detectFormat')

{
    // can detect v1 BOX.RAW
    const isBoxRaw = {length: 320 * 66}
    const isNotBoxRaw = {length: 1}

    expect(detectFormat(isBoxRaw)).toBe('v1boxraw')
    expect(detectFormat(isNotBoxRaw)).toBe('unknown')
}

{
    // can detect v1 CHR
    const isV1Chr = {length: 16 * 32 * 30}
    const isAlsoV1Chr = {length: 16 * 32 * 20}
    const isNotV1Chr = {length: 1}

    expect(detectFormat(isV1Chr)).toBe('v1chr')
    expect(detectFormat(isAlsoV1Chr)).toBe('v1chr')
    expect(detectFormat(isNotV1Chr)).toBe('unknown')
}<commit_msg>formatting<commit_after>"use strict"

const expect = require('expect')
const detectFormat = require('./detectFormat')

{
  // can detect v1 BOX.RAW
  const isBoxRaw = {length: 320 * 66}
  const isNotBoxRaw = {length: 1}

  expect(detectFormat(isBoxRaw)).toBe('v1boxraw')
  expect(detectFormat(isNotBoxRaw)).toBe('unknown')
}

{
  // can detect v1 CHR
  const isV1Chr = {length: 16 * 32 * 30}
  const isAlsoV1Chr = {length: 16 * 32 * 20}
  const isNotV1Chr = {length: 1}

  expect(detectFormat(isV1Chr)).toBe('v1chr')
  expect(detectFormat(isAlsoV1Chr)).toBe('v1chr')
  expect(detectFormat(isNotV1Chr)).toBe('unknown')
}<|endoftext|>


<commit_before>(function( $, pm )
{
    pm.directive( 'Basket', function( BasketService )
    {

        return {
            addBasketItem     : addBasketItem,
            changeItemQuantity: changeItemQuantity,
            setItemQuantity   : setItemQuantity
        };

        function addBasketItem( e, button )
        {
            e.preventDefault();
            //init
            var basketItemsList = {};
            var parentForm      = $( button ).parents( 'form' );

            basketItemsList.BasketItemItemID   = parentForm.find( '[name="ArticleID"]' ).val();
            basketItemsList.BasketItemPriceID  = parentForm.find( '[name="SYS_P_ID"]' ).val();
            basketItemsList.BasketItemQuantity = parentForm.find( '[name="ArticleQuantity"]' ).val();
            basketItemsList.BasketItemBranchID = parentForm.find( '[name="source_category"]' ).val();

            //attributes
            var attributeInputsList = parentForm.find( '[name^="ArticleAttribute"]' );
            var attributesList      = [];

            $.each( attributeInputsList, function( idx, elem )
            {
                var match = elem.name.match( /^ArticleAttribute\[\d+]\[\d+]\[(\d+)]$/ );
                if ( match && match[1] )
                {
                    attributesList.push( {
                        BasketItemAttributeID     : match[1],
                        BasketItemAttributeValueID: $( elem ).val()
                    } );
                }
            } );

            if ( attributesList.length != 0 )
            {
                basketItemsList.BasketItemAttributesList = attributesList;
            }

            //add basketItem and refresh previewLists
            BasketService.addItem( [basketItemsList] );

        }

        function changeItemQuantity( elem, increment )
        {
            var $elem         = $( elem );
            var quantityInput = $elem.parent().find( 'input' );
            var maxLength     = parseInt( quantityInput.attr( 'maxlength' ) ) || 5;
            var value         = parseInt( quantityInput.val() ) + increment;

            if ( (value + '').length <= maxLength && value > 1 )
            {
                quantityInput.val( value );

                var isBasketView = elem.parents( '[data-basket-item-id]' ).length > 0;
                if ( isBasketView )
                {
                    var timeout = elem.data( 'timeout' );

                    if ( !!timeout )
                    {
                        window.clearTimeout( timeout );
                    }

                    timeout = window.setTimeout( function()
                    {
                        quantityInput.trigger( 'change' );
                    }, 1000 );

                    elem.data( 'timeout', timeout );
                }
            }
        }

        function setItemQuantity( basketItemID, input )
        {
            BasketService.setItemQuantity(
                basketItemID,
                parseInt( $( input ).val() )
            ).fail(function() {
                // reset input's value on cancel
                var basketItem = BasketService.getItem( basketItemID );
                $( input ).val( basketItem.BasketItemQuantity );
            });
        }

    }, ['BasketService'] );
}( jQuery, PlentyFramework ));<commit_msg>FIX basket directive: allow decreasing item quantity to 0<commit_after>(function( $, pm )
{
    pm.directive( 'Basket', function( BasketService )
    {

        return {
            addBasketItem     : addBasketItem,
            changeItemQuantity: changeItemQuantity,
            setItemQuantity   : setItemQuantity
        };

        function addBasketItem( e, button )
        {
            e.preventDefault();
            //init
            var basketItemsList = {};
            var parentForm      = $( button ).parents( 'form' );

            basketItemsList.BasketItemItemID   = parentForm.find( '[name="ArticleID"]' ).val();
            basketItemsList.BasketItemPriceID  = parentForm.find( '[name="SYS_P_ID"]' ).val();
            basketItemsList.BasketItemQuantity = parentForm.find( '[name="ArticleQuantity"]' ).val();
            basketItemsList.BasketItemBranchID = parentForm.find( '[name="source_category"]' ).val();

            //attributes
            var attributeInputsList = parentForm.find( '[name^="ArticleAttribute"]' );
            var attributesList      = [];

            $.each( attributeInputsList, function( idx, elem )
            {
                var match = elem.name.match( /^ArticleAttribute\[\d+]\[\d+]\[(\d+)]$/ );
                if ( match && match[1] )
                {
                    attributesList.push( {
                        BasketItemAttributeID     : match[1],
                        BasketItemAttributeValueID: $( elem ).val()
                    } );
                }
            } );

            if ( attributesList.length != 0 )
            {
                basketItemsList.BasketItemAttributesList = attributesList;
            }

            //add basketItem and refresh previewLists
            BasketService.addItem( [basketItemsList] );

        }

        function changeItemQuantity( elem, increment )
        {
            var $elem         = $( elem );
            var quantityInput = $elem.parent().find( 'input' );
            var maxLength     = parseInt( quantityInput.attr( 'maxlength' ) ) || 5;
            var value         = parseInt( quantityInput.val() ) + increment;

            if ( (value + '').length <= maxLength && value >= 0 )
            {
                quantityInput.val( value );

                var isBasketView = elem.parents( '[data-basket-item-id]' ).length > 0;
                if ( isBasketView )
                {
                    var timeout = elem.data( 'timeout' );

                    if ( !!timeout )
                    {
                        window.clearTimeout( timeout );
                    }

                    timeout = window.setTimeout( function()
                    {
                        quantityInput.trigger( 'change' );
                    }, 1000 );

                    elem.data( 'timeout', timeout );
                }
            }
        }

        function setItemQuantity( basketItemID, input )
        {
            BasketService.setItemQuantity(
                basketItemID,
                parseInt( $( input ).val() )
            ).fail(function() {
                // reset input's value on cancel
                var basketItem = BasketService.getItem( basketItemID );
                $( input ).val( basketItem.BasketItemQuantity );
            });
        }

    }, ['BasketService'] );
}( jQuery, PlentyFramework ));<|endoftext|>