<commit_before>// @flow

import type { SourceMap } from "convert-source-map";
import sourceMap from "source-map";

export default function mergeSourceMap(
  inputMap: SourceMap,
  map: SourceMap,
): SourceMap {
  const input = buildMappingData(inputMap);
  const output = buildMappingData(map);

  const mergedGenerator = new sourceMap.SourceMapGenerator();
  for (const { source } of input.sources) {
    if (typeof source.content === "string") {
      mergedGenerator.setSourceContent(source.path, source.content);
    }
  }

  if (output.sources.length === 1) {
    const defaultSource = output.sources[0];

    const insertedMappings = new Map();

    // Process each generated range in the input map, e.g. each range over the
    // code that Babel was originally given.
    eachInputGeneratedRange(input, (generated, original, source) => {
      // Then pick out each range over Babel's _output_ that corresponds with
      // the given range on the code given to Babel.
      eachOverlappingGeneratedOutputRange(defaultSource, generated, item => {
        // It's possible that multiple input ranges will overlap the same
        // generated range. Since sourcemap don't traditionally represent
        // generated locations with multiple original locations, we explicitly
        // skip generated locations once we've seen them the first time.
        const key = makeMappingKey(item);
        if (insertedMappings.has(key)) return;
        insertedMappings.set(key, item);

        mergedGenerator.addMapping({
          source: source.path,
          original: {
            line: original.line,
            column: original.columnStart,
          },
          generated: {
            line: item.line,
            column: item.columnStart,
          },
          name: original.name,
        });
      });
    });

    // Since mappings are manipulated using single locations, but are interpreted
    // as ranges, the insertions above may not actually have their ending
    // locations mapped yet. Here be go through each one and ensure that it has
    // a well-defined ending location, if one wasn't already created by the start
    // of a different range.
    for (const item of insertedMappings.values()) {
      if (item.columnEnd === Infinity) {
        continue;
      }

      const clearItem = {
        line: item.line,
        columnStart: item.columnEnd,
      };

      const key = makeMappingKey(clearItem);
      if (insertedMappings.has(key)) {
        continue;
      }

      // Insert mappings with no original position to terminate any mappings
      // that were found above, so that they don't expand beyond their correct
      // range.
      mergedGenerator.addMapping({
        generated: {
          line: clearItem.line,
          column: clearItem.columnStart,
        },
      });
    }
  }

  const result = mergedGenerator.toJSON();
  // addMapping expects a relative path, and setSourceContent expects an
  // absolute path. To avoid this whole confusion, we leave the root out
  // entirely, and add it at the end here.
  if (typeof input.sourceRoot === "string") {
    result.sourceRoot = input.sourceRoot;
  }
  return result;
}

function makeMappingKey(item: { line: number, columnStart: number }) {
  return JSON.stringify([item.line, item.columnStart]);
}

function eachOverlappingGeneratedOutputRange(
  outputFile: ResolvedFileMappings,
  inputGeneratedRange: ResolvedGeneratedRange,
  callback: ResolvedGeneratedRange => mixed,
) {
  // Find the Babel-generated mappings that overlap with this range in the
  // input sourcemap. Generated locations within the input sourcemap
  // correspond with the original locations in the map Babel generates.
  const overlappingOriginal = filterApplicableOriginalRanges(
    outputFile,
    inputGeneratedRange,
  );

  for (const { generated } of overlappingOriginal) {
    for (const item of generated) {
      callback(item);
    }
  }
}

function filterApplicableOriginalRanges(
  { mappings }: ResolvedFileMappings,
  { line, columnStart, columnEnd }: ResolvedGeneratedRange,
): OriginalMappings {
  // The mapping array is sorted by original location, so we can
  // binary-search it for the overlapping ranges.
  return filterSortedArray(mappings, ({ original: outOriginal }) => {
    if (line > outOriginal.line) return -1;
    if (line < outOriginal.line) return 1;

    if (columnStart >= outOriginal.columnEnd) return -1;
    if (columnEnd <= outOriginal.columnStart) return 1;

    return 0;
  });
}

function eachInputGeneratedRange(
  map: ResolvedMappings,
  callback: (
    ResolvedGeneratedRange,
    ResolvedOriginalRange,
    ResolvedSource,
  ) => mixed,
) {
  for (const { source, mappings } of map.sources) {
    for (const { original, generated } of mappings) {
      for (const item of generated) {
        callback(item, original, source);
      }
    }
  }
}

type ResolvedMappings = {|
  file: ?string,
  sourceRoot: ?string,
  sources: Array<ResolvedFileMappings>,
|};
type ResolvedFileMappings = {|
  source: ResolvedSource,
  mappings: OriginalMappings,
|};
type OriginalMappings = Array<{|
  original: ResolvedOriginalRange,
  generated: Array<ResolvedGeneratedRange>,
|}>;
type ResolvedSource = {|
  path: string,
  content: string | null,
|};
type ResolvedOriginalRange = {|
  line: number,
  columnStart: number,
  columnEnd: number,
  name: string | null,
|};
type ResolvedGeneratedRange = {|
  line: number,
  columnStart: number,
  columnEnd: number,
|};

function buildMappingData(map: SourceMap): ResolvedMappings {
  const consumer = new sourceMap.SourceMapConsumer({
    ...map,

    // This is a bit hack. .addMapping expects source values to be relative,
    // but eachMapping returns mappings with absolute paths. To avoid that
    // incompatibility, we leave the sourceRoot out here and add it to the
    // final map at the end instead.
    sourceRoot: null,
  });

  const sources = new Map();
  const mappings = new Map();

  let last = null;

  consumer.computeColumnSpans();

  consumer.eachMapping(
    m => {
      if (m.originalLine === null) return;

      let source = sources.get(m.source);
      if (!source) {
        source = {
          path: m.source,
          content: consumer.sourceContentFor(m.source, true),
        };
        sources.set(m.source, source);
      }

      let sourceData = mappings.get(source);
      if (!sourceData) {
        sourceData = {
          source,
          mappings: [],
        };
        mappings.set(source, sourceData);
      }

      const obj = {
        line: m.originalLine,
        columnStart: m.originalColumn,
        columnEnd: Infinity,
        name: m.name,
      };

      if (
        last &&
        last.source === source &&
        last.mapping.line === m.originalLine
      ) {
        last.mapping.columnEnd = m.originalColumn;
      }

      last = {
        source,
        mapping: obj,
      };

      sourceData.mappings.push({
        original: obj,
        generated: consumer
          .allGeneratedPositionsFor({
            source: m.source,
            line: m.originalLine,
            column: m.originalColumn,
          })
          .map(item => ({
            line: item.line,
            columnStart: item.column,
            // source-map's lastColumn is inclusive, not exclusive, so we need
            // to add 1 to it.
            columnEnd: item.lastColumn + 1,
          })),
      });
    },
    null,
    sourceMap.SourceMapConsumer.ORIGINAL_ORDER,
  );

  return {
    file: map.file,
    sourceRoot: map.sourceRoot,
    sources: Array.from(mappings.values()),
  };
}

function findInsertionLocation<T>(
  array: Array<T>,
  callback: T => number,
): number {
  let left = 0;
  let right = array.length;
  while (left < right) {
    const mid = Math.floor((left + right) / 2);
    const item = array[mid];

    const result = callback(item);
    if (result === 0) {
      left = mid;
      break;
    }
    if (result >= 0) {
      right = mid;
    } else {
      left = mid + 1;
    }
  }

  // Ensure the value is the start of any set of matches.
  let i = left;
  if (i < array.length) {
    while (i >= 0 && callback(array[i]) >= 0) {
      i--;
    }
    return i + 1;
  }

  return i;
}

function filterSortedArray<T>(
  array: Array<T>,
  callback: T => number,
): Array<T> {
  const start = findInsertionLocation(array, callback);

  const results = [];
  for (let i = start; i < array.length && callback(array[i]) === 0; i++) {
    results.push(array[i]);
  }

  return results;
}
<commit_msg>Improved build performance by optimizing makeMappingKey method. (#8744)<commit_after>// @flow

import type { SourceMap } from "convert-source-map";
import sourceMap from "source-map";

export default function mergeSourceMap(
  inputMap: SourceMap,
  map: SourceMap,
): SourceMap {
  const input = buildMappingData(inputMap);
  const output = buildMappingData(map);

  const mergedGenerator = new sourceMap.SourceMapGenerator();
  for (const { source } of input.sources) {
    if (typeof source.content === "string") {
      mergedGenerator.setSourceContent(source.path, source.content);
    }
  }

  if (output.sources.length === 1) {
    const defaultSource = output.sources[0];

    const insertedMappings = new Map();

    // Process each generated range in the input map, e.g. each range over the
    // code that Babel was originally given.
    eachInputGeneratedRange(input, (generated, original, source) => {
      // Then pick out each range over Babel's _output_ that corresponds with
      // the given range on the code given to Babel.
      eachOverlappingGeneratedOutputRange(defaultSource, generated, item => {
        // It's possible that multiple input ranges will overlap the same
        // generated range. Since sourcemap don't traditionally represent
        // generated locations with multiple original locations, we explicitly
        // skip generated locations once we've seen them the first time.
        const key = makeMappingKey(item);
        if (insertedMappings.has(key)) return;
        insertedMappings.set(key, item);

        mergedGenerator.addMapping({
          source: source.path,
          original: {
            line: original.line,
            column: original.columnStart,
          },
          generated: {
            line: item.line,
            column: item.columnStart,
          },
          name: original.name,
        });
      });
    });

    // Since mappings are manipulated using single locations, but are interpreted
    // as ranges, the insertions above may not actually have their ending
    // locations mapped yet. Here be go through each one and ensure that it has
    // a well-defined ending location, if one wasn't already created by the start
    // of a different range.
    for (const item of insertedMappings.values()) {
      if (item.columnEnd === Infinity) {
        continue;
      }

      const clearItem = {
        line: item.line,
        columnStart: item.columnEnd,
      };

      const key = makeMappingKey(clearItem);
      if (insertedMappings.has(key)) {
        continue;
      }

      // Insert mappings with no original position to terminate any mappings
      // that were found above, so that they don't expand beyond their correct
      // range.
      mergedGenerator.addMapping({
        generated: {
          line: clearItem.line,
          column: clearItem.columnStart,
        },
      });
    }
  }

  const result = mergedGenerator.toJSON();
  // addMapping expects a relative path, and setSourceContent expects an
  // absolute path. To avoid this whole confusion, we leave the root out
  // entirely, and add it at the end here.
  if (typeof input.sourceRoot === "string") {
    result.sourceRoot = input.sourceRoot;
  }
  return result;
}

function makeMappingKey(item: { line: number, columnStart: number }) {
  return `${item.line}/${item.columnStart}`;
}

function eachOverlappingGeneratedOutputRange(
  outputFile: ResolvedFileMappings,
  inputGeneratedRange: ResolvedGeneratedRange,
  callback: ResolvedGeneratedRange => mixed,
) {
  // Find the Babel-generated mappings that overlap with this range in the
  // input sourcemap. Generated locations within the input sourcemap
  // correspond with the original locations in the map Babel generates.
  const overlappingOriginal = filterApplicableOriginalRanges(
    outputFile,
    inputGeneratedRange,
  );

  for (const { generated } of overlappingOriginal) {
    for (const item of generated) {
      callback(item);
    }
  }
}

function filterApplicableOriginalRanges(
  { mappings }: ResolvedFileMappings,
  { line, columnStart, columnEnd }: ResolvedGeneratedRange,
): OriginalMappings {
  // The mapping array is sorted by original location, so we can
  // binary-search it for the overlapping ranges.
  return filterSortedArray(mappings, ({ original: outOriginal }) => {
    if (line > outOriginal.line) return -1;
    if (line < outOriginal.line) return 1;

    if (columnStart >= outOriginal.columnEnd) return -1;
    if (columnEnd <= outOriginal.columnStart) return 1;

    return 0;
  });
}

function eachInputGeneratedRange(
  map: ResolvedMappings,
  callback: (
    ResolvedGeneratedRange,
    ResolvedOriginalRange,
    ResolvedSource,
  ) => mixed,
) {
  for (const { source, mappings } of map.sources) {
    for (const { original, generated } of mappings) {
      for (const item of generated) {
        callback(item, original, source);
      }
    }
  }
}

type ResolvedMappings = {|
  file: ?string,
  sourceRoot: ?string,
  sources: Array<ResolvedFileMappings>,
|};
type ResolvedFileMappings = {|
  source: ResolvedSource,
  mappings: OriginalMappings,
|};
type OriginalMappings = Array<{|
  original: ResolvedOriginalRange,
  generated: Array<ResolvedGeneratedRange>,
|}>;
type ResolvedSource = {|
  path: string,
  content: string | null,
|};
type ResolvedOriginalRange = {|
  line: number,
  columnStart: number,
  columnEnd: number,
  name: string | null,
|};
type ResolvedGeneratedRange = {|
  line: number,
  columnStart: number,
  columnEnd: number,
|};

function buildMappingData(map: SourceMap): ResolvedMappings {
  const consumer = new sourceMap.SourceMapConsumer({
    ...map,

    // This is a bit hack. .addMapping expects source values to be relative,
    // but eachMapping returns mappings with absolute paths. To avoid that
    // incompatibility, we leave the sourceRoot out here and add it to the
    // final map at the end instead.
    sourceRoot: null,
  });

  const sources = new Map();
  const mappings = new Map();

  let last = null;

  consumer.computeColumnSpans();

  consumer.eachMapping(
    m => {
      if (m.originalLine === null) return;

      let source = sources.get(m.source);
      if (!source) {
        source = {
          path: m.source,
          content: consumer.sourceContentFor(m.source, true),
        };
        sources.set(m.source, source);
      }

      let sourceData = mappings.get(source);
      if (!sourceData) {
        sourceData = {
          source,
          mappings: [],
        };
        mappings.set(source, sourceData);
      }

      const obj = {
        line: m.originalLine,
        columnStart: m.originalColumn,
        columnEnd: Infinity,
        name: m.name,
      };

      if (
        last &&
        last.source === source &&
        last.mapping.line === m.originalLine
      ) {
        last.mapping.columnEnd = m.originalColumn;
      }

      last = {
        source,
        mapping: obj,
      };

      sourceData.mappings.push({
        original: obj,
        generated: consumer
          .allGeneratedPositionsFor({
            source: m.source,
            line: m.originalLine,
            column: m.originalColumn,
          })
          .map(item => ({
            line: item.line,
            columnStart: item.column,
            // source-map's lastColumn is inclusive, not exclusive, so we need
            // to add 1 to it.
            columnEnd: item.lastColumn + 1,
          })),
      });
    },
    null,
    sourceMap.SourceMapConsumer.ORIGINAL_ORDER,
  );

  return {
    file: map.file,
    sourceRoot: map.sourceRoot,
    sources: Array.from(mappings.values()),
  };
}

function findInsertionLocation<T>(
  array: Array<T>,
  callback: T => number,
): number {
  let left = 0;
  let right = array.length;
  while (left < right) {
    const mid = Math.floor((left + right) / 2);
    const item = array[mid];

    const result = callback(item);
    if (result === 0) {
      left = mid;
      break;
    }
    if (result >= 0) {
      right = mid;
    } else {
      left = mid + 1;
    }
  }

  // Ensure the value is the start of any set of matches.
  let i = left;
  if (i < array.length) {
    while (i >= 0 && callback(array[i]) >= 0) {
      i--;
    }
    return i + 1;
  }

  return i;
}

function filterSortedArray<T>(
  array: Array<T>,
  callback: T => number,
): Array<T> {
  const start = findInsertionLocation(array, callback);

  const results = [];
  for (let i = start; i < array.length && callback(array[i]) === 0; i++) {
    results.push(array[i]);
  }

  return results;
}
<|endoftext|>


<commit_before>// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

'use strict';

base.requireStylesheet('cc.picture_ops_chart_view');

base.exportTo('cc', function() {

  var BAR_PADDING = 1;
  var BAR_WIDTH = 5;
  var CHART_PADDING_LEFT = 65;
  var CHART_PADDING_RIGHT = 30;
  var CHART_PADDING_BOTTOM = 35;
  var CHART_PADDING_TOP = 20;
  var AXIS_PADDING_LEFT = 55;
  var AXIS_PADDING_RIGHT = 30;
  var AXIS_PADDING_BOTTOM = 35;
  var AXIS_PADDING_TOP = 20;
  var AXIS_TICK_SIZE = 5;
  var AXIS_LABEL_PADDING = 5;
  var VERTICAL_TICKS = 5;
  var HUE_CHAR_CODE_ADJUSTMENT = 5.7;

  /**
   * Provides a chart showing the cumulative time spent in Skia operations
   * during picture rasterization.
   *
   * @constructor
   */
  var PictureOpsChartView = ui.define('picture-ops-chart-view');

  PictureOpsChartView.prototype = {
    __proto__: HTMLUnknownElement.prototype,

    decorate: function() {
      this.picture_ = undefined;
      this.pictureOps_ = undefined;
      this.opCosts_ = undefined;

      this.chartScale_ = window.devicePixelRatio;

      this.chart_ = document.createElement('canvas');
      this.chartCtx_ = this.chart_.getContext('2d');
      this.appendChild(this.chart_);

      this.selectedOpIndex_ = undefined;
      this.chartWidth_ = 0;
      this.chartHeight_ = 0;
      this.dimensionsHaveChanged_ = true;

      this.currentBarMouseOverTarget_ = undefined;

      this.ninetyFifthPercentileCost_ = 0;
      this.totalOpCost_ = 0;

      this.chart_.addEventListener('click', this.onClick_.bind(this));
      this.chart_.addEventListener('mousemove', this.onMouseMove_.bind(this));

    },

    get dimensionsHaveChanged() {
      return this.dimensionsHaveChanged_;
    },

    set dimensionsHaveChanged(dimensionsHaveChanged) {
      this.dimensionsHaveChanged_ = dimensionsHaveChanged;
    },

    get selectedOpIndex() {
      return this.selectedOpIndex_;
    },

    set selectedOpIndex(selectedOpIndex) {
      this.selectedOpIndex_ = selectedOpIndex;
    },

    get picture() {
      return this.picture_;
    },

    set picture(picture) {
      this.picture_ = picture;
      this.pictureOps_ = picture.tagOpsWithTimings(picture.getOps());
      this.processPictureData_();
    },

    processPictureData_: function() {

      var totalOpCost = 0;

      // Take a copy of the picture ops data for sorting.
      this.opCosts_ = this.pictureOps_.map(function(op) {
        totalOpCost += op.cmd_time;
        return op.cmd_time;
      });
      this.opCosts_.sort();

      var ninetyFifthPercentileCostIndex = Math.floor(
          this.opCosts_.length * 0.95);
      this.ninetyFifthPercentileCost_ =
          this.opCosts_[ninetyFifthPercentileCostIndex];

      this.totalOpCost_ = totalOpCost;
    },

    extractBarIndex_: function(e) {

      var index = undefined;

      if (this.pictureOps_.length === 0)
        return index;

      var x = e.offsetX;
      var y = e.offsetY;

      var totalBarWidth = (BAR_WIDTH + BAR_PADDING) * this.pictureOps_.length;

      var chartLeft = CHART_PADDING_LEFT;
      var chartTop = 0;
      var chartBottom = this.chartHeight_ - CHART_PADDING_BOTTOM;
      var chartRight = chartLeft + totalBarWidth;

      if (x < chartLeft || x > chartRight || y < chartTop || y > chartBottom)
        return index;

      index = Math.floor((x - chartLeft) / totalBarWidth *
          this.pictureOps_.length);

      index = base.clamp(index, 0, this.pictureOps_.length - 1);

      return index;
    },

    onClick_: function(e) {

      var barClicked = this.extractBarIndex_(e);

      if (barClicked === undefined)
        return;

      // If we click on the already selected item we should deselect.
      if (barClicked === this.selectedOpIndex)
        this.selectedOpIndex = undefined;
      else
        this.selectedOpIndex = barClicked;

      e.preventDefault();

      base.dispatchSimpleEvent(this, 'selection-changed', false);
    },

    onMouseMove_: function(e) {

      var lastBarMouseOverTarget = this.currentBarMouseOverTarget_;
      this.currentBarMouseOverTarget_ = this.extractBarIndex_(e);

      if (this.currentBarMouseOverTarget_ === lastBarMouseOverTarget)
        return;

      this.drawChartContents_();
    },

    scrollSelectedItemIntoViewIfNecessary: function() {

      if (this.selectedOpIndex === undefined)
        return;

      var width = this.offsetWidth;
      var left = this.scrollLeft;
      var right = left + width;
      var targetLeft = CHART_PADDING_LEFT +
          (BAR_WIDTH + BAR_PADDING) * this.selectedOpIndex;

      if (targetLeft > left && targetLeft < right)
        return;

      this.scrollLeft = (targetLeft - width * 0.5);
    },

    updateChartContents: function() {

      if (this.dimensionsHaveChanged)
        this.updateChartDimensions_();

      this.drawChartContents_();
    },

    updateChartDimensions_: function() {

      if (!this.pictureOps_)
        return;

      var width = CHART_PADDING_LEFT + CHART_PADDING_RIGHT +
          ((BAR_WIDTH + BAR_PADDING) * this.pictureOps_.length);

      if (width < this.offsetWidth)
        width = this.offsetWidth;

      // Allow the element to be its natural size as set by flexbox, then lock
      // the width in before we set the width of the canvas.
      this.style.width = 'auto';
      this.chart_.style.display = 'none';
      this.style.width = this.offsetWidth + 'px';
      this.chart_.style.display = 'block';

      this.chartWidth_ = width;
      this.chartHeight_ = this.offsetHeight;

      // Scale up the canvas according to the devicePixelRatio, then reduce it
      // down again via CSS. Finally we apply a scale to the canvas so that
      // things are drawn at the correct size.
      this.chart_.width = this.chartWidth_ * this.chartScale_;
      this.chart_.height = this.chartHeight_ * this.chartScale_;

      this.chart_.style.width = this.chartWidth_ + 'px';
      this.chart_.style.height = this.chartHeight_ + 'px';

      this.chartCtx_.scale(this.chartScale_, this.chartScale_);

      this.dimensionsHaveChanged = false;
    },

    drawChartContents_: function() {

      this.clearChartContents_();

      if (this.pictureOps_.length === 0 ||
          this.pictureOps_[0].cmd_time === undefined) {

        this.showNoTimingDataMessage_();
        return;
      }

      this.drawSelection_();
      this.drawBars_();
      this.drawChartAxes_();
      this.drawLinesAtTickMarks_();
      this.drawLineAtBottomOfChart_();

      if (this.currentBarMouseOverTarget_ === undefined)
        return;

      this.drawTooltip_();
    },

    drawSelection_: function() {

      if (this.selectedOpIndex === undefined)
        return;

      var width = (BAR_WIDTH + BAR_PADDING) * this.selectedOpIndex;
      this.chartCtx_.fillStyle = 'rgb(223, 235, 230)';
      this.chartCtx_.fillRect(CHART_PADDING_LEFT, CHART_PADDING_TOP,
          width, this.chartHeight_ - CHART_PADDING_TOP - CHART_PADDING_BOTTOM);
    },

    drawChartAxes_: function() {

      var min = this.opCosts_[0];
      var max = this.opCosts_[this.opCosts_.length - 1];
      var height = this.chartHeight_ - AXIS_PADDING_TOP - AXIS_PADDING_BOTTOM;

      var tickYInterval = height / (VERTICAL_TICKS - 1);
      var tickYPosition = 0;
      var tickValInterval = (max - min) / (VERTICAL_TICKS - 1);
      var tickVal = 0;

      this.chartCtx_.fillStyle = '#333';
      this.chartCtx_.strokeStyle = '#777';
      this.chartCtx_.save();

      // Translate half a pixel to avoid blurry lines.
      this.chartCtx_.translate(0.5, 0.5);

      // Sides.
      this.chartCtx_.beginPath();
      this.chartCtx_.moveTo(AXIS_PADDING_LEFT, AXIS_PADDING_TOP);
      this.chartCtx_.lineTo(AXIS_PADDING_LEFT, this.chartHeight_ -
          AXIS_PADDING_BOTTOM);
      this.chartCtx_.lineTo(this.chartWidth_ - AXIS_PADDING_RIGHT,
          this.chartHeight_ - AXIS_PADDING_BOTTOM);
      this.chartCtx_.stroke();
      this.chartCtx_.closePath();

      // Y-axis ticks.
      this.chartCtx_.translate(AXIS_PADDING_LEFT, AXIS_PADDING_TOP);

      this.chartCtx_.font = '10px Arial';
      this.chartCtx_.textAlign = 'right';
      this.chartCtx_.textBaseline = 'middle';

      this.chartCtx_.beginPath();
      for (var t = 0; t < VERTICAL_TICKS; t++) {

        tickYPosition = Math.round(t * tickYInterval);
        tickVal = (max - t * tickValInterval).toFixed(4);

        this.chartCtx_.moveTo(0, tickYPosition);
        this.chartCtx_.lineTo(-AXIS_TICK_SIZE, tickYPosition);
        this.chartCtx_.fillText(tickVal,
            -AXIS_TICK_SIZE - AXIS_LABEL_PADDING, tickYPosition);

      }

      this.chartCtx_.stroke();
      this.chartCtx_.closePath();

      this.chartCtx_.restore();
    },

    drawLinesAtTickMarks_: function() {

      var height = this.chartHeight_ - AXIS_PADDING_TOP - AXIS_PADDING_BOTTOM;
      var width = this.chartWidth_ - AXIS_PADDING_LEFT - AXIS_PADDING_RIGHT;
      var tickYInterval = height / (VERTICAL_TICKS - 1);
      var tickYPosition = 0;

      this.chartCtx_.save();

      this.chartCtx_.translate(AXIS_PADDING_LEFT + 0.5, AXIS_PADDING_TOP + 0.5);
      this.chartCtx_.beginPath();
      this.chartCtx_.strokeStyle = 'rgba(0,0,0,0.05)';

      for (var t = 0; t < VERTICAL_TICKS; t++) {
        tickYPosition = Math.round(t * tickYInterval);

        this.chartCtx_.moveTo(0, tickYPosition);
        this.chartCtx_.lineTo(width, tickYPosition);
        this.chartCtx_.stroke();
      }

      this.chartCtx_.restore();
      this.chartCtx_.closePath();
    },

    drawLineAtBottomOfChart_: function() {
      this.chartCtx_.strokeStyle = '#AAA';
      this.chartCtx_.beginPath();
      this.chartCtx_.moveTo(0, this.chartHeight_ - 0.5);
      this.chartCtx_.lineTo(this.chartWidth_, this.chartHeight_ - 0.5);
      this.chartCtx_.stroke();
      this.chartCtx_.closePath();
    },

    drawTooltip_: function() {

      var tooltipData = this.pictureOps_[this.currentBarMouseOverTarget_];
      var tooltipTitle = tooltipData.cmd_string;
      var tooltipTime = tooltipData.cmd_time.toFixed(4);
      var toolTipTimePercentage =
          ((tooltipData.cmd_time / this.totalOpCost_) * 100).toFixed(2);

      var tooltipWidth = 120;
      var tooltipHeight = 40;
      var chartInnerWidth = this.chartWidth_ - CHART_PADDING_RIGHT -
          CHART_PADDING_LEFT;
      var barWidth = BAR_WIDTH + BAR_PADDING;
      var tooltipOffset = Math.round((tooltipWidth - barWidth) * 0.5);

      var left = CHART_PADDING_LEFT + this.currentBarMouseOverTarget_ *
          barWidth - tooltipOffset;
      var top = Math.round((this.chartHeight_ - tooltipHeight) * 0.5);

      this.chartCtx_.save();

      this.chartCtx_.shadowOffsetX = 0;
      this.chartCtx_.shadowOffsetY = 5;
      this.chartCtx_.shadowBlur = 4;
      this.chartCtx_.shadowColor = 'rgba(0,0,0,0.4)';

      this.chartCtx_.strokeStyle = '#888';
      this.chartCtx_.fillStyle = '#EEE';
      this.chartCtx_.fillRect(left, top, tooltipWidth, tooltipHeight);

      this.chartCtx_.shadowColor = 'transparent';
      this.chartCtx_.translate(0.5, 0.5);
      this.chartCtx_.strokeRect(left, top, tooltipWidth, tooltipHeight);

      this.chartCtx_.restore();

      this.chartCtx_.fillStyle = '#222';
      this.chartCtx_.textBaseline = 'top';
      this.chartCtx_.font = '800 12px Arial';
      this.chartCtx_.fillText(tooltipTitle, left + 8, top + 8);

      this.chartCtx_.fillStyle = '#555';
      this.chartCtx_.textBaseline = 'top';
      this.chartCtx_.font = '400 italic 10px Arial';
      this.chartCtx_.fillText(tooltipTime + 'ms (' +
          toolTipTimePercentage + '%)', left + 8, top + 22);
    },

    drawBars_: function() {

      var op;
      var opColor = 0;
      var opHeight = 0;
      var opWidth = BAR_WIDTH + BAR_PADDING;
      var opHover = false;

      var bottom = this.chartHeight_ - CHART_PADDING_BOTTOM;
      var maxHeight = this.chartHeight_ - CHART_PADDING_BOTTOM -
          CHART_PADDING_TOP;

      for (var b = 0; b < this.pictureOps_.length; b++) {

        op = this.pictureOps_[b];
        opHeight = Math.round(
            (op.cmd_time / this.ninetyFifthPercentileCost_) * maxHeight);
        opHeight = Math.max(opHeight, 1);
        opHover = (b === this.currentBarMouseOverTarget_);
        opColor = this.getOpColor_(op.cmd_string, opHover);

        if (b === this.selectedOpIndex)
          this.chartCtx_.fillStyle = '#FFFF00';
        else
          this.chartCtx_.fillStyle = opColor;

        this.chartCtx_.fillRect(CHART_PADDING_LEFT + b * opWidth,
            bottom - opHeight, BAR_WIDTH, opHeight);
      }

    },

    getOpColor_: function(opName, hover) {

      var characters = opName.split('');

      var hue = characters.reduce(this.reduceNameToHue, 0) % 360;
      var saturation = 30;
      var lightness = hover ? '75%' : '50%';

      return 'hsl(' + hue + ', ' + saturation + '%, ' + lightness + '%)';
    },

    reduceNameToHue: function(previousValue, currentValue, index, array) {
      // Get the char code and apply a magic adjustment value so we get
      // pretty colors from around the rainbow.
      return Math.round(previousValue + currentValue.charCodeAt(0) *
          HUE_CHAR_CODE_ADJUSTMENT);
    },

    clearChartContents_: function() {
      this.chartCtx_.clearRect(0, 0, this.chartWidth_, this.chartHeight_);
    },

    showNoTimingDataMessage_: function() {
      this.chartCtx_.font = '800 italic 14px Arial';
      this.chartCtx_.fillStyle = '#333';
      this.chartCtx_.textAlign = 'center';
      this.chartCtx_.textBaseline = 'middle';
      this.chartCtx_.fillText('No timing data available.',
          this.chartWidth_ * 0.5, this.chartHeight_ * 0.5);
    }
  };

  return {
    PictureOpsChartView: PictureOpsChartView
  };
});
<commit_msg>Fixes intermittent text align issue in chart tooltips.<commit_after>// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

'use strict';

base.requireStylesheet('cc.picture_ops_chart_view');

base.exportTo('cc', function() {

  var BAR_PADDING = 1;
  var BAR_WIDTH = 5;
  var CHART_PADDING_LEFT = 65;
  var CHART_PADDING_RIGHT = 30;
  var CHART_PADDING_BOTTOM = 35;
  var CHART_PADDING_TOP = 20;
  var AXIS_PADDING_LEFT = 55;
  var AXIS_PADDING_RIGHT = 30;
  var AXIS_PADDING_BOTTOM = 35;
  var AXIS_PADDING_TOP = 20;
  var AXIS_TICK_SIZE = 5;
  var AXIS_LABEL_PADDING = 5;
  var VERTICAL_TICKS = 5;
  var HUE_CHAR_CODE_ADJUSTMENT = 5.7;

  /**
   * Provides a chart showing the cumulative time spent in Skia operations
   * during picture rasterization.
   *
   * @constructor
   */
  var PictureOpsChartView = ui.define('picture-ops-chart-view');

  PictureOpsChartView.prototype = {
    __proto__: HTMLUnknownElement.prototype,

    decorate: function() {
      this.picture_ = undefined;
      this.pictureOps_ = undefined;
      this.opCosts_ = undefined;

      this.chartScale_ = window.devicePixelRatio;

      this.chart_ = document.createElement('canvas');
      this.chartCtx_ = this.chart_.getContext('2d');
      this.appendChild(this.chart_);

      this.selectedOpIndex_ = undefined;
      this.chartWidth_ = 0;
      this.chartHeight_ = 0;
      this.dimensionsHaveChanged_ = true;

      this.currentBarMouseOverTarget_ = undefined;

      this.ninetyFifthPercentileCost_ = 0;
      this.totalOpCost_ = 0;

      this.chart_.addEventListener('click', this.onClick_.bind(this));
      this.chart_.addEventListener('mousemove', this.onMouseMove_.bind(this));

    },

    get dimensionsHaveChanged() {
      return this.dimensionsHaveChanged_;
    },

    set dimensionsHaveChanged(dimensionsHaveChanged) {
      this.dimensionsHaveChanged_ = dimensionsHaveChanged;
    },

    get selectedOpIndex() {
      return this.selectedOpIndex_;
    },

    set selectedOpIndex(selectedOpIndex) {
      this.selectedOpIndex_ = selectedOpIndex;
    },

    get picture() {
      return this.picture_;
    },

    set picture(picture) {
      this.picture_ = picture;
      this.pictureOps_ = picture.tagOpsWithTimings(picture.getOps());
      this.processPictureData_();
    },

    processPictureData_: function() {

      var totalOpCost = 0;

      // Take a copy of the picture ops data for sorting.
      this.opCosts_ = this.pictureOps_.map(function(op) {
        totalOpCost += op.cmd_time;
        return op.cmd_time;
      });
      this.opCosts_.sort();

      var ninetyFifthPercentileCostIndex = Math.floor(
          this.opCosts_.length * 0.95);
      this.ninetyFifthPercentileCost_ =
          this.opCosts_[ninetyFifthPercentileCostIndex];

      this.totalOpCost_ = totalOpCost;
    },

    extractBarIndex_: function(e) {

      var index = undefined;

      if (this.pictureOps_.length === 0)
        return index;

      var x = e.offsetX;
      var y = e.offsetY;

      var totalBarWidth = (BAR_WIDTH + BAR_PADDING) * this.pictureOps_.length;

      var chartLeft = CHART_PADDING_LEFT;
      var chartTop = 0;
      var chartBottom = this.chartHeight_ - CHART_PADDING_BOTTOM;
      var chartRight = chartLeft + totalBarWidth;

      if (x < chartLeft || x > chartRight || y < chartTop || y > chartBottom)
        return index;

      index = Math.floor((x - chartLeft) / totalBarWidth *
          this.pictureOps_.length);

      index = base.clamp(index, 0, this.pictureOps_.length - 1);

      return index;
    },

    onClick_: function(e) {

      var barClicked = this.extractBarIndex_(e);

      if (barClicked === undefined)
        return;

      // If we click on the already selected item we should deselect.
      if (barClicked === this.selectedOpIndex)
        this.selectedOpIndex = undefined;
      else
        this.selectedOpIndex = barClicked;

      e.preventDefault();

      base.dispatchSimpleEvent(this, 'selection-changed', false);
    },

    onMouseMove_: function(e) {

      var lastBarMouseOverTarget = this.currentBarMouseOverTarget_;
      this.currentBarMouseOverTarget_ = this.extractBarIndex_(e);

      if (this.currentBarMouseOverTarget_ === lastBarMouseOverTarget)
        return;

      this.drawChartContents_();
    },

    scrollSelectedItemIntoViewIfNecessary: function() {

      if (this.selectedOpIndex === undefined)
        return;

      var width = this.offsetWidth;
      var left = this.scrollLeft;
      var right = left + width;
      var targetLeft = CHART_PADDING_LEFT +
          (BAR_WIDTH + BAR_PADDING) * this.selectedOpIndex;

      if (targetLeft > left && targetLeft < right)
        return;

      this.scrollLeft = (targetLeft - width * 0.5);
    },

    updateChartContents: function() {

      if (this.dimensionsHaveChanged)
        this.updateChartDimensions_();

      this.drawChartContents_();
    },

    updateChartDimensions_: function() {

      if (!this.pictureOps_)
        return;

      var width = CHART_PADDING_LEFT + CHART_PADDING_RIGHT +
          ((BAR_WIDTH + BAR_PADDING) * this.pictureOps_.length);

      if (width < this.offsetWidth)
        width = this.offsetWidth;

      // Allow the element to be its natural size as set by flexbox, then lock
      // the width in before we set the width of the canvas.
      this.style.width = 'auto';
      this.chart_.style.display = 'none';
      this.style.width = this.offsetWidth + 'px';
      this.chart_.style.display = 'block';

      this.chartWidth_ = width;
      this.chartHeight_ = this.offsetHeight;

      // Scale up the canvas according to the devicePixelRatio, then reduce it
      // down again via CSS. Finally we apply a scale to the canvas so that
      // things are drawn at the correct size.
      this.chart_.width = this.chartWidth_ * this.chartScale_;
      this.chart_.height = this.chartHeight_ * this.chartScale_;

      this.chart_.style.width = this.chartWidth_ + 'px';
      this.chart_.style.height = this.chartHeight_ + 'px';

      this.chartCtx_.scale(this.chartScale_, this.chartScale_);

      this.dimensionsHaveChanged = false;
    },

    drawChartContents_: function() {

      this.clearChartContents_();

      if (this.pictureOps_.length === 0 ||
          this.pictureOps_[0].cmd_time === undefined) {

        this.showNoTimingDataMessage_();
        return;
      }

      this.drawSelection_();
      this.drawBars_();
      this.drawChartAxes_();
      this.drawLinesAtTickMarks_();
      this.drawLineAtBottomOfChart_();

      if (this.currentBarMouseOverTarget_ === undefined)
        return;

      this.drawTooltip_();
    },

    drawSelection_: function() {

      if (this.selectedOpIndex === undefined)
        return;

      var width = (BAR_WIDTH + BAR_PADDING) * this.selectedOpIndex;
      this.chartCtx_.fillStyle = 'rgb(223, 235, 230)';
      this.chartCtx_.fillRect(CHART_PADDING_LEFT, CHART_PADDING_TOP,
          width, this.chartHeight_ - CHART_PADDING_TOP - CHART_PADDING_BOTTOM);
    },

    drawChartAxes_: function() {

      var min = this.opCosts_[0];
      var max = this.opCosts_[this.opCosts_.length - 1];
      var height = this.chartHeight_ - AXIS_PADDING_TOP - AXIS_PADDING_BOTTOM;

      var tickYInterval = height / (VERTICAL_TICKS - 1);
      var tickYPosition = 0;
      var tickValInterval = (max - min) / (VERTICAL_TICKS - 1);
      var tickVal = 0;

      this.chartCtx_.fillStyle = '#333';
      this.chartCtx_.strokeStyle = '#777';
      this.chartCtx_.save();

      // Translate half a pixel to avoid blurry lines.
      this.chartCtx_.translate(0.5, 0.5);

      // Sides.
      this.chartCtx_.beginPath();
      this.chartCtx_.moveTo(AXIS_PADDING_LEFT, AXIS_PADDING_TOP);
      this.chartCtx_.lineTo(AXIS_PADDING_LEFT, this.chartHeight_ -
          AXIS_PADDING_BOTTOM);
      this.chartCtx_.lineTo(this.chartWidth_ - AXIS_PADDING_RIGHT,
          this.chartHeight_ - AXIS_PADDING_BOTTOM);
      this.chartCtx_.stroke();
      this.chartCtx_.closePath();

      // Y-axis ticks.
      this.chartCtx_.translate(AXIS_PADDING_LEFT, AXIS_PADDING_TOP);

      this.chartCtx_.font = '10px Arial';
      this.chartCtx_.textAlign = 'right';
      this.chartCtx_.textBaseline = 'middle';

      this.chartCtx_.beginPath();
      for (var t = 0; t < VERTICAL_TICKS; t++) {

        tickYPosition = Math.round(t * tickYInterval);
        tickVal = (max - t * tickValInterval).toFixed(4);

        this.chartCtx_.moveTo(0, tickYPosition);
        this.chartCtx_.lineTo(-AXIS_TICK_SIZE, tickYPosition);
        this.chartCtx_.fillText(tickVal,
            -AXIS_TICK_SIZE - AXIS_LABEL_PADDING, tickYPosition);

      }

      this.chartCtx_.stroke();
      this.chartCtx_.closePath();

      this.chartCtx_.restore();
    },

    drawLinesAtTickMarks_: function() {

      var height = this.chartHeight_ - AXIS_PADDING_TOP - AXIS_PADDING_BOTTOM;
      var width = this.chartWidth_ - AXIS_PADDING_LEFT - AXIS_PADDING_RIGHT;
      var tickYInterval = height / (VERTICAL_TICKS - 1);
      var tickYPosition = 0;

      this.chartCtx_.save();

      this.chartCtx_.translate(AXIS_PADDING_LEFT + 0.5, AXIS_PADDING_TOP + 0.5);
      this.chartCtx_.beginPath();
      this.chartCtx_.strokeStyle = 'rgba(0,0,0,0.05)';

      for (var t = 0; t < VERTICAL_TICKS; t++) {
        tickYPosition = Math.round(t * tickYInterval);

        this.chartCtx_.moveTo(0, tickYPosition);
        this.chartCtx_.lineTo(width, tickYPosition);
        this.chartCtx_.stroke();
      }

      this.chartCtx_.restore();
      this.chartCtx_.closePath();
    },

    drawLineAtBottomOfChart_: function() {
      this.chartCtx_.strokeStyle = '#AAA';
      this.chartCtx_.beginPath();
      this.chartCtx_.moveTo(0, this.chartHeight_ - 0.5);
      this.chartCtx_.lineTo(this.chartWidth_, this.chartHeight_ - 0.5);
      this.chartCtx_.stroke();
      this.chartCtx_.closePath();
    },

    drawTooltip_: function() {

      var tooltipData = this.pictureOps_[this.currentBarMouseOverTarget_];
      var tooltipTitle = tooltipData.cmd_string;
      var tooltipTime = tooltipData.cmd_time.toFixed(4);
      var toolTipTimePercentage =
          ((tooltipData.cmd_time / this.totalOpCost_) * 100).toFixed(2);

      var tooltipWidth = 120;
      var tooltipHeight = 40;
      var chartInnerWidth = this.chartWidth_ - CHART_PADDING_RIGHT -
          CHART_PADDING_LEFT;
      var barWidth = BAR_WIDTH + BAR_PADDING;
      var tooltipOffset = Math.round((tooltipWidth - barWidth) * 0.5);

      var left = CHART_PADDING_LEFT + this.currentBarMouseOverTarget_ *
          barWidth - tooltipOffset;
      var top = Math.round((this.chartHeight_ - tooltipHeight) * 0.5);

      this.chartCtx_.save();

      this.chartCtx_.shadowOffsetX = 0;
      this.chartCtx_.shadowOffsetY = 5;
      this.chartCtx_.shadowBlur = 4;
      this.chartCtx_.shadowColor = 'rgba(0,0,0,0.4)';

      this.chartCtx_.strokeStyle = '#888';
      this.chartCtx_.fillStyle = '#EEE';
      this.chartCtx_.fillRect(left, top, tooltipWidth, tooltipHeight);

      this.chartCtx_.shadowColor = 'transparent';
      this.chartCtx_.translate(0.5, 0.5);
      this.chartCtx_.strokeRect(left, top, tooltipWidth, tooltipHeight);

      this.chartCtx_.restore();

      this.chartCtx_.fillStyle = '#222';
      this.chartCtx_.textAlign = 'left';
      this.chartCtx_.textBaseline = 'top';
      this.chartCtx_.font = '800 12px Arial';
      this.chartCtx_.fillText(tooltipTitle, left + 8, top + 8);

      this.chartCtx_.fillStyle = '#555';
      this.chartCtx_.font = '400 italic 10px Arial';
      this.chartCtx_.fillText(tooltipTime + 'ms (' +
          toolTipTimePercentage + '%)', left + 8, top + 22);
    },

    drawBars_: function() {

      var op;
      var opColor = 0;
      var opHeight = 0;
      var opWidth = BAR_WIDTH + BAR_PADDING;
      var opHover = false;

      var bottom = this.chartHeight_ - CHART_PADDING_BOTTOM;
      var maxHeight = this.chartHeight_ - CHART_PADDING_BOTTOM -
          CHART_PADDING_TOP;

      for (var b = 0; b < this.pictureOps_.length; b++) {

        op = this.pictureOps_[b];
        opHeight = Math.round(
            (op.cmd_time / this.ninetyFifthPercentileCost_) * maxHeight);
        opHeight = Math.max(opHeight, 1);
        opHover = (b === this.currentBarMouseOverTarget_);
        opColor = this.getOpColor_(op.cmd_string, opHover);

        if (b === this.selectedOpIndex)
          this.chartCtx_.fillStyle = '#FFFF00';
        else
          this.chartCtx_.fillStyle = opColor;

        this.chartCtx_.fillRect(CHART_PADDING_LEFT + b * opWidth,
            bottom - opHeight, BAR_WIDTH, opHeight);
      }

    },

    getOpColor_: function(opName, hover) {

      var characters = opName.split('');

      var hue = characters.reduce(this.reduceNameToHue, 0) % 360;
      var saturation = 30;
      var lightness = hover ? '75%' : '50%';

      return 'hsl(' + hue + ', ' + saturation + '%, ' + lightness + '%)';
    },

    reduceNameToHue: function(previousValue, currentValue, index, array) {
      // Get the char code and apply a magic adjustment value so we get
      // pretty colors from around the rainbow.
      return Math.round(previousValue + currentValue.charCodeAt(0) *
          HUE_CHAR_CODE_ADJUSTMENT);
    },

    clearChartContents_: function() {
      this.chartCtx_.clearRect(0, 0, this.chartWidth_, this.chartHeight_);
    },

    showNoTimingDataMessage_: function() {
      this.chartCtx_.font = '800 italic 14px Arial';
      this.chartCtx_.fillStyle = '#333';
      this.chartCtx_.textAlign = 'center';
      this.chartCtx_.textBaseline = 'middle';
      this.chartCtx_.fillText('No timing data available.',
          this.chartWidth_ * 0.5, this.chartHeight_ * 0.5);
    }
  };

  return {
    PictureOpsChartView: PictureOpsChartView
  };
});
<|endoftext|>


<commit_before>/**
 *
 * @param query the raw request query from express
 * @return {*} an object that contains specific queries for ORMS
 * like mongoose or sequelize.
 */

class CriteriaBuilder {
    constructor() {}

    default() {
        return {
            limit: undefined,
            skip: undefined,
            where: {},
            sort: undefined,
            includes: []
        };
    }

    build(query) {
        const criteria = this.default();

        try {
            const raw = JSON.parse(query.criteria);

            this.buildLimit(raw, criteria);
            this.buildOffset(raw, criteria);
            this.buildWhere(raw, criteria);
            this.buildSort(raw, criteria);
            this.buildIncludes(raw, criteria);

            return criteria;
        }
        catch (error) {
            return criteria
        }
    }

    /**
     *
     * @param query     {Object} The request filter's query.
     * @param criteria  {Object} The raw criteria.
     * @return {Object} Mongoose decorated criteria from request's query.
     */
    buildSort(query, criteria) {
        if (!query.sort)
            return criteria;

        criteria.sort = query.sort;
        return criteria;
    }

    /**
     *
     * @param query     {Object} The request filter's query.
     * @param criteria  {Object} The raw criteria.
     * @return {Object} Mongoose decorated criteria from request's query.
     */
    buildWhere(query, criteria) {
        if (!query.where)
            return criteria;

        criteria.where = query.where;
        return criteria;
    }

    /**
     *
     * @param query     {Object} The request filter's query.
     * @param criteria  {Object} The raw criteria.
     * @return {Object} Mongoose decorated criteria from request's query.
     */
    buildLimit(query, criteria) {
        if (!query.limit)
            return criteria;

        criteria.limit = parseInt(query.limit, 10);
        return criteria;
    }

    /**
     *
     * @param query     {Object} The request filter's query.
     * @param criteria  {Object} The raw criteria.
     * @return {Object} Mongoose decorated criteria from request's query.
     */
    buildOffset(query, criteria) {
        if (!query.offset)
            return criteria;

        criteria.skip = parseInt(query.offset, 10);
        return criteria;
    }

    /**
     *
     * @param query     {Object} The request filter's query.
     * @param criteria  {Object} The raw criteria.
     * @return {Object} Mongoose decorated criteria from request's query.
     */
    buildIncludes(query, criteria) {
        if (!query.includes)
            return criteria;

        query.includes.forEach((i) => {
            let include = parse(i, {});
            criteria.includes.push(include);
        });

        return criteria;

        /**
         *
         * @param query     An include query.
         * @param criteria  {Object} The raw criteria.
         * @return {Object} Mongoose decorated criteria from request's query.
         */
        function parse(query, crit) {
            crit.path = crit.path || '';

            if (query instanceof Array) {
                crit.path += `${query.join(' ')}`;
            }

            else if (typeof query === 'string') {
                crit.path += `${query}`;
            }

            else if (query.toString() === '[object Object]') {
                for (let item in query) {
                    if (query.hasOwnProperty(item)) {
                        crit.path += `${item}`;
                        crit.populate = crit.populate || {};
                        parse(query[item], crit.populate)
                    }
                }
            }

            return crit;
        }
    }
}

module.exports = CriteriaBuilder;<commit_msg>refactoring, increasing code quality<commit_after>/**
 *
 * @param query the raw request query from express
 * @return {*} an object that contains specific queries for ORMS
 * like mongoose or sequelize.
 */

class CriteriaBuilder {
    constructor() {}

    default() {
        return {
            limit: undefined,
            skip: undefined,
            where: {},
            sort: undefined,
            includes: []
        };
    }

    build(query) {
        const criteria = this.default();

        try {
            const raw = JSON.parse(query.criteria);

            this.buildLimit(raw, criteria);
            this.buildOffset(raw, criteria);
            this.buildWhere(raw, criteria);
            this.buildSort(raw, criteria);
            this.buildIncludes(raw, criteria);

            return criteria;
        }
        catch (error) {
            return criteria
        }
    }

    /**
     *
     * @param query     {Object} The request filter's query.
     * @param criteria  {Object} The raw criteria.
     * @return {Object} Mongoose decorated criteria from request's query.
     */
    buildSort(query, criteria) {
        if (!query.sort)
            return criteria;

        criteria.sort = query.sort;
        return criteria;
    }

    /**
     *
     * @param query     {Object} The request filter's query.
     * @param criteria  {Object} The raw criteria.
     * @return {Object} Mongoose decorated criteria from request's query.
     */
    buildWhere(query, criteria) {
        if (!query.where)
            return criteria;

        criteria.where = query.where;
        return criteria;
    }

    /**
     *
     * @param query     {Object} The request filter's query.
     * @param criteria  {Object} The raw criteria.
     * @return {Object} Mongoose decorated criteria from request's query.
     */
    buildLimit(query, criteria) {
        if (!query.limit)
            return criteria;

        criteria.limit = parseInt(query.limit, 10);
        return criteria;
    }

    /**
     *
     * @param query     {Object} The request filter's query.
     * @param criteria  {Object} The raw criteria.
     * @return {Object} Mongoose decorated criteria from request's query.
     */
    buildOffset(query, criteria) {
        if (!query.offset)
            return criteria;

        criteria.skip = parseInt(query.offset, 10);
        return criteria;
    }

    /**
     *
     * @param query     {Object} The request filter's query.
     * @param criteria  {Object} The raw criteria.
     * @return {Object} Mongoose decorated criteria from request's query.
     */
    buildIncludes(query, criteria) {
        if (!query.includes)
            return criteria;

        query.includes.forEach((i) => {
            let include = parse(i, {});
            criteria.includes.push(include);
        });

        return criteria;

        /**
         *
         * @param query     An include query.
         * @param criteria  {Object} The raw criteria.
         * @return {Object} Mongoose decorated criteria from request's query.
         */
        function parse(query, criteria) {
            criteria.path = criteria.path || '';

            if (query instanceof Array) {
                criteria.path += `${query.join(' ')}`;
            }

            else if (typeof query === 'string') {
                criteria.path += `${query}`;
            }

            else if (query.toString() === '[object Object]') {
                for (let item in query) {
                    if (query.hasOwnProperty(item)) {
                        criteria.path += `${item}`;
                        criteria.populate = criteria.populate || {};
                        parse(query[item], criteria.populate)
                    }
                }
            }

            return criteria;
        }
    }
}

module.exports = CriteriaBuilder;<|endoftext|>


<commit_before>/* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the Clear BSD license.
 * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 */

/**
 * Class: OpenLayers.Icon
 *
 * The icon represents a graphical icon on the screen.  Typically used in
 * conjunction with a <OpenLayers.Marker> to represent markers on a screen.
 *
 * An icon has a url, size and position.  It also contains an offset which
 * allows the center point to be represented correctly.  This can be
 * provided either as a fixed offset or a function provided to calculate
 * the desired offset.
 *
 */
OpenLayers.Icon = OpenLayers.Class({

    /**
     * Property: url
     * {String}  image url
     */
    url: null,

    /**
     * Property: size
     * {<OpenLayers.Size>}
     */
    size: null,

    /**
     * Property: offset
     * {<OpenLayers.Pixel>} distance in pixels to offset the image when being rendered
     */
    offset: null,

    /**
     * Property: calculateOffset
     * {<OpenLayers.Pixel>} Function to calculate the offset (based on the size)
     */
    calculateOffset: null,

    /**
     * Property: imageDiv
     * {DOMElement}
     */
    imageDiv: null,

    /**
     * Property: px
     * {<OpenLayers.Pixel>}
     */
    px: null,

    /**
     * Constructor: OpenLayers.Icon
     * Creates an icon, which is an image tag in a div.
     *
     * url - {String}
     * size - {<OpenLayers.Size>}
     * offset - {<OpenLayers.Pixel>}
     * calculateOffset - {Function}
     */
    initialize: function(url, size, offset, calculateOffset) {
        this.url = url;
        this.size = (size) ? size : new OpenLayers.Size(20,20);
        this.offset = offset ? offset : new OpenLayers.Pixel(-(this.size.w/2), -(this.size.h/2));
        this.calculateOffset = calculateOffset;

        var id = OpenLayers.Util.createUniqueID("OL_Icon_");
        this.imageDiv = OpenLayers.Util.createAlphaImageDiv(id);
    },

    /**
     * Method: destroy
     * Nullify references and remove event listeners to prevent circular
     * references and memory leaks
     */
    destroy: function() {
        // erase any drawn elements
        this.erase();

        OpenLayers.Event.stopObservingElement(this.imageDiv.firstChild);
        this.imageDiv.innerHTML = "";
        this.imageDiv = null;
    },

    /**
     * Method: clone
     *
     * Returns:
     * {<OpenLayers.Icon>} A fresh copy of the icon.
     */
    clone: function() {
        return new OpenLayers.Icon(this.url,
                                   this.size,
                                   this.offset,
                                   this.calculateOffset);
    },

    /**
     * Method: setSize
     *
     * Parameters:
     * size - {<OpenLayers.Size>}
     */
    setSize: function(size) {
        if (size != null) {
            this.size = size;
        }
        this.draw();
    },

    /**
     * Method: setUrl
     *
     * Parameters:
     * url - {String}
     */
    setUrl: function(url) {
        if (url != null) {
            this.url = url;
        }
        this.draw();
    },

    /**
     * Method: draw
     * Move the div to the given pixel.
     *
     * Parameters:
     * px - {<OpenLayers.Pixel>}
     *
     * Returns:
     * {DOMElement} A new DOM Image of this icon set at the location passed-in
     */
    draw: function(px) {
        OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv,
                                            null,
                                            null,
                                            this.size,
                                            this.url,
                                            "absolute");
        this.moveTo(px);
        return this.imageDiv;
    },

    /**
     * Method: erase
     * Erase the underlying image element.
     *
     */
    erase: function() {
        if (this.imageDiv != null && this.imageDiv.parentNode != null) {
            OpenLayers.Element.remove(this.imageDiv);
        }
    },

    /**
     * Method: setOpacity
     * Change the icon's opacity
     *
     * Parameters:
     * opacity - {float}
     */
    setOpacity: function(opacity) {
        OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, null, null,
                                            null, null, null, null, opacity);

    },

    /**
     * Method: moveTo
     * move icon to passed in px.
     *
     * Parameters:
     * px - {<OpenLayers.Pixel>}
     */
    moveTo: function (px) {
        //if no px passed in, use stored location
        if (px != null) {
            this.px = px;
        }

        if (this.imageDiv != null) {
            if (this.px == null) {
                this.display(false);
            } else {
                if (this.calculateOffset) {
                    this.offset = this.calculateOffset(this.size);
                }
                var offsetPx = this.px.offset(this.offset);
                OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, offsetPx);
            }
        }
    },

    /**
     * Method: display
     * Hide or show the icon
     *
     * Parameters:
     * display - {Boolean}
     */
    display: function(display) {
        this.imageDiv.style.display = (display) ? "" : "none";
    },

    /**
     * APIMethod: isDrawn
     *
     * Returns:
     * {Boolean} Whether or not the icon is drawn.
     */
    isDrawn: function() {
        // nodeType 11 for ie, whose nodes *always* have a parentNode
        // (of type document fragment)
        var isDrawn = (this.imageDiv && this.imageDiv.parentNode &&
                       (this.imageDiv.parentNode.nodeType != 11));

        return isDrawn;
    },

    CLASS_NAME: "OpenLayers.Icon"
});
<commit_msg>doc typo<commit_after>/* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for
 * full list of contributors). Published under the Clear BSD license.
 * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
 * full text of the license. */

/**
 * @requires OpenLayers/BaseTypes/Class.js
 */

/**
 * Class: OpenLayers.Icon
 *
 * The icon represents a graphical icon on the screen.  Typically used in
 * conjunction with a <OpenLayers.Marker> to represent markers on a screen.
 *
 * An icon has a url, size and position.  It also contains an offset which
 * allows the center point to be represented correctly.  This can be
 * provided either as a fixed offset or a function provided to calculate
 * the desired offset.
 *
 */
OpenLayers.Icon = OpenLayers.Class({

    /**
     * Property: url
     * {String}  image url
     */
    url: null,

    /**
     * Property: size
     * {<OpenLayers.Size>}
     */
    size: null,

    /**
     * Property: offset
     * {<OpenLayers.Pixel>} distance in pixels to offset the image when being rendered
     */
    offset: null,

    /**
     * Property: calculateOffset
     * {Function} Function to calculate the offset (based on the size)
     */
    calculateOffset: null,

    /**
     * Property: imageDiv
     * {DOMElement}
     */
    imageDiv: null,

    /**
     * Property: px
     * {<OpenLayers.Pixel>}
     */
    px: null,

    /**
     * Constructor: OpenLayers.Icon
     * Creates an icon, which is an image tag in a div.
     *
     * url - {String}
     * size - {<OpenLayers.Size>}
     * offset - {<OpenLayers.Pixel>}
     * calculateOffset - {Function}
     */
    initialize: function(url, size, offset, calculateOffset) {
        this.url = url;
        this.size = (size) ? size : new OpenLayers.Size(20,20);
        this.offset = offset ? offset : new OpenLayers.Pixel(-(this.size.w/2), -(this.size.h/2));
        this.calculateOffset = calculateOffset;

        var id = OpenLayers.Util.createUniqueID("OL_Icon_");
        this.imageDiv = OpenLayers.Util.createAlphaImageDiv(id);
    },

    /**
     * Method: destroy
     * Nullify references and remove event listeners to prevent circular
     * references and memory leaks
     */
    destroy: function() {
        // erase any drawn elements
        this.erase();

        OpenLayers.Event.stopObservingElement(this.imageDiv.firstChild);
        this.imageDiv.innerHTML = "";
        this.imageDiv = null;
    },

    /**
     * Method: clone
     *
     * Returns:
     * {<OpenLayers.Icon>} A fresh copy of the icon.
     */
    clone: function() {
        return new OpenLayers.Icon(this.url,
                                   this.size,
                                   this.offset,
                                   this.calculateOffset);
    },

    /**
     * Method: setSize
     *
     * Parameters:
     * size - {<OpenLayers.Size>}
     */
    setSize: function(size) {
        if (size != null) {
            this.size = size;
        }
        this.draw();
    },

    /**
     * Method: setUrl
     *
     * Parameters:
     * url - {String}
     */
    setUrl: function(url) {
        if (url != null) {
            this.url = url;
        }
        this.draw();
    },

    /**
     * Method: draw
     * Move the div to the given pixel.
     *
     * Parameters:
     * px - {<OpenLayers.Pixel>}
     *
     * Returns:
     * {DOMElement} A new DOM Image of this icon set at the location passed-in
     */
    draw: function(px) {
        OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv,
                                            null,
                                            null,
                                            this.size,
                                            this.url,
                                            "absolute");
        this.moveTo(px);
        return this.imageDiv;
    },

    /**
     * Method: erase
     * Erase the underlying image element.
     *
     */
    erase: function() {
        if (this.imageDiv != null && this.imageDiv.parentNode != null) {
            OpenLayers.Element.remove(this.imageDiv);
        }
    },

    /**
     * Method: setOpacity
     * Change the icon's opacity
     *
     * Parameters:
     * opacity - {float}
     */
    setOpacity: function(opacity) {
        OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, null, null,
                                            null, null, null, null, opacity);

    },

    /**
     * Method: moveTo
     * move icon to passed in px.
     *
     * Parameters:
     * px - {<OpenLayers.Pixel>}
     */
    moveTo: function (px) {
        //if no px passed in, use stored location
        if (px != null) {
            this.px = px;
        }

        if (this.imageDiv != null) {
            if (this.px == null) {
                this.display(false);
            } else {
                if (this.calculateOffset) {
                    this.offset = this.calculateOffset(this.size);
                }
                var offsetPx = this.px.offset(this.offset);
                OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, offsetPx);
            }
        }
    },

    /**
     * Method: display
     * Hide or show the icon
     *
     * Parameters:
     * display - {Boolean}
     */
    display: function(display) {
        this.imageDiv.style.display = (display) ? "" : "none";
    },

    /**
     * APIMethod: isDrawn
     *
     * Returns:
     * {Boolean} Whether or not the icon is drawn.
     */
    isDrawn: function() {
        // nodeType 11 for ie, whose nodes *always* have a parentNode
        // (of type document fragment)
        var isDrawn = (this.imageDiv && this.imageDiv.parentNode &&
                       (this.imageDiv.parentNode.nodeType != 11));

        return isDrawn;
    },

    CLASS_NAME: "OpenLayers.Icon"
});
<|endoftext|>


<commit_before>/*
* @class
*/
OpenLayers.Util=new Object();

/**
* @class This class represents a screen coordinate, in x and y coordinates
*/
OpenLayers.Pixel = Class.create();
OpenLayers.Pixel.prototype = {

    /**
    * @param {int} x
    * @param {int} y
    */
    initialize: function(x, y) {
        this.x = x;
        this.y = y;
    },

    /**
    * @return {str} ex: "x=200,y=242"
    */
    toString:function() {
        return ("x="+this.x+",y="+this.y);
    },

    /**
    * @return {OpenLayers.Pixel}
    */
    copyOf:function() {
        return new OpenLayers.Pixel(this.x, this.y);
    },

    /** returns a Size object with the difference
    *    between the two points
    *
    * @return {OpenLayers.Size}
    */
    diff:function(pt) {          // subtract pt from this
        return new OpenLayers.Size(this.x - pt.x, this.y - pt.y);
    },

    /** returns a Size object with the absolute difference
    *    between the two points
    *
    * @param {OpenLayers.Pixel} pt
    *
    * @return {OpenLayers.Size}
    */
    absDiff:function(pt) {
        return new OpenLayers.Size(Math.abs(this.x - pt.x),
                                   Math.abs(this.y - pt.y));
    },

    /** returns a Pixel object with the difference
    *    between the two points
    *
    * @param {OpenLayers.Pixel} pt
    *
    * @return {OpenLayers.Pixel}
    */
    diffPt:function(pt) {
        var sz = this.diff(pt);
        return new OpenLayers.Pixel(sz.w, sz.h);
    },

    /** returns whether or not the two points are equal
    *
    * @param {OpenLayers.Pixel} pt
    *
    * @return {bool}
    */
    equal:function(pt) {
        var d = this.diff(pt);
        return ((d.w==0) && (d.h==0));
    },

    /** Return a new Pixel with this pixel's x&y augmented by
    *    the Size value passed in.
    *
    * @param {OpenLayers.Size} sz
    *
    * @return {OpenLayers.Pixel}
    */
    addSize:function(sz) {
        return new OpenLayers.Pixel(this.x+sz.w, this.y+sz.h);
    },

    /** Return a new Pixel with this pixel's x&y augmented by
    *    the Size value passed in.
    *
    * @param {int} w
    *
    * @return {OpenLayers.Pixel}
    */
    addX:function(w) {
        return new OpenLayers.Pixel(this.x+w, this.y);
    },

    /** Return a new Pixel with this pixel's y augmented by
    *    the int value passed in.
    *
    * @param {int} h
    *
    * @return {OpenLayers.Pixel}
    */
    addY:function(h) {
        return new OpenLayers.Pixel(this.x, this.y+h);
    },

    /** Return a new Pixel with this pixel's x&y augmented by
    *    the int values passed in.
    *
    * @param {int} x
    * @param {int} x
    *
    * @return {OpenLayers.Pixel}
    */
    add:function(x, y) {
        return new OpenLayers.Pixel(this.x + x, this.y + y);
    },

    ////////////////////
    who:function(){return "OpenLayers.Pixel";}
};

OpenLayers.Size = Class.create();
OpenLayers.Size.prototype = {
    initialize: function(w,h) {
        this.w=w;
        this.h=h;
    },
    toString:function(){
        return ("w="+this.w+",h="+this.h);
    },
    copyOf:function(){
        return new OpenLayers.Size(this.w, this.h);
    },
    sameSize:function(pt){
        return (this.w==pt.w && this.h==pt.h);
    }
};

OpenLayers.LatLon = Class.create();
OpenLayers.LatLon.prototype = {
    initialize: function(lat,lon) {
        this.lat=lat;
        this.lon=lon;
    },
    toString:function(){
        return ("lat="+this.lat+",lon="+this.lon);
    },
    copyOf:function(){
        return new OpenLayers.LatLon(this.lat, this.lon);
    },
    toShortString:function(){
        return (this.lat+", "+this.lon);
    },
    diff:function(pt){          // subtract pt from this
        return new OpenLayers.LatLon(this.lat - pt.lat,this.lon - pt.lon);
    },
    samePT:function(pt){
        return (this.lat==pt.lat && this.lon==pt.lon);
    }
};
OpenLayers.LatLon.fromString=function(str){
    var pairs=str.split(",");
    return new OpenLayers.LatLon(pairs[1],pairs[0]);
};
OpenLayers.Bounds = Class.create();
OpenLayers.Bounds.prototype = {
    initialize: function(minlat,minlon,maxlat,maxlon){
        this.minlat=minlat;
        this.minlon=minlon;
        this.maxlat=maxlat;
        this.maxlon=maxlon;
        this.width = Math.abs(this.maxlon - this.minlon);
        this.height = Math.abs(this.maxlat - this.minlat);
    },
    toString:function(){
        return ("Min lat/lon=" + this.minlat +"/"+ this.minlon
                + " Max lat/lon=" + this.maxlat +"/"+ this.maxlon
                + " width:" + this.width + " height:" + this.height);
    },
    copyOf:function(){
        return new OpenLayers.Bounds(this.minlat, this.minlon,
                                    this.maxlat, this.maxlon);
    },
    toBBOX:function(){
        return (this.minlon+","+this.minlat+","+this.maxlon+","+this.maxlat);
    },
    contains:function(pt){
        return (pt.lon >=this.minlon && pt.lon <=this.maxlon
            && pt.lat >=this.minlat && pt.lat <= this.maxlat)
    }

};
/** Create bounds from coordinates in a string:-180.000000,-90.000000 180.000000,90.000000
*/
OpenLayers.Bounds.fromString=function(str){
    var pairs=str.split(" ");
    var min=pairs[0];
    var max=pairs[1];

    var latlon = min.split(",");
    var latlon2 = max.split(",");
    return new OpenLayers.Bounds(latlon[1],latlon[0],latlon2[1],latlon2[0]);
};

OpenLayers.Box = Class.create();
OpenLayers.Box.prototype = {
    initialize: function(x,y,w,h){
        this.xy=new OpenLayers.Pixel(x,y);
        this.sz=new OpenLayers.Size(w,h);
        this.c = new OpenLayers.Pixel(x+(w/2), y+(h/2));
        this.br = new OpenLayers.Pixel(x+w-1,y+h-1);
    },

    /* offset box by df
    * @df(OpenLayers.Size)
    */
    offset:function(df){
        this.xy=new OpenLayers.Pixel(this.xy.x+df.w,this.xy.y+df.h);
        this.c = new OpenLayers.Pixel(this.xy.x+(this.sz.w/2), this.xy.y+(this.sz.h/2));
        var x=this.xy.x;
        var y=this.xy.y;
        var w=this.sz.w;
        var h=this.sz.h;
        this.br = new OpenLayers.Pixel(x+w-1,y+h-1);
    },

    getOrigin:function(){return this.xy;},
    getSize:function(){return this.sz;},
    getCenter:function(){return this.c;},
    getBotRight:function(){return this.br;},
    getWidth:function(){return this.sz.w;},
    getHeight:function(){return this.sz.h;},
    getSize:function(){return this.sz;},

    toString:function(){
        return (this.xy.toString() + " " + this.sz.toString());
    },
    copyOf:function(){
        return new OpenLayers.Box(this.xy.x, this.xy.y, this.sz.w, this.sz.h);
    },
    toCoordsString:function(){
        var x1 = this.xy.x;
        var x2 = this.xy.x+this.sz.w-1;
        var y1 = this.xy.y;
        var y2 = this.xy.y+this.sz.h-1;
        return (x1+","+y1+","+x2+","+y2);
    },
    contains:function(pt){
        var returnVal = false;

        var lx=this.xy.x;
        var ly=this.xy.y;
        var rx=lx+this.sz.w-1;
        var ry=ly+this.sz.h-1;

        if (pt != null) {
            //upper left in
            returnVal =  (pt.x >=lx && pt.x <=rx && pt.y >=ly && pt.y <= ry);
        }
        return returnVal;
    },

    /**
    * @param {ol.Box} box
    * @param {bool} partial - if true, returns whether any part of the passed
    *                         in box is within the calling box. otherwise box
    *                         must contain entire box to return true.
    *
    * @return {bool}
    */
    containsBox:function(box, partial) {
        var contains = false;

        var mainTop = this.xy.y;
        var mainLeft = this.xy.x;
        var mainBottom = this.xy.y + this.sz.h;
        var mainRight = this.xy.x + this.sz.w;

        var top = box.xy.y;
        var left = box.xy.x;
        var bottom = box.xy.y + box.sz.h;
        var right = box.xy.x + box.sz.w;

        var inTop = (top >= mainTop) && (top <= mainBottom);
        var inLeft = (left >= mainLeft) && (left <= mainRight);
        var inBottom = (bottom >= mainTop) && (bottom <= mainBottom);
        var inRight= (right >= mainLeft) && (right <= mainRight);

        if (partial) {
            contains = (inTop || inBottom) && (inLeft || inRight );
        } else {
            contains = (inTop && inLeft && inBottom && inRight);
        }

        return contains;
    },

    // is this point the center of the box, +-2 pixels (@todo fix that number!)
    isCenter:function(pt){
        var size = this.c.absDiff(pt);
        return (size.w <= 2 && size.h <= 2);
    }
};

// Some other helpful things
String.prototype.startsWith = function(sStart){
    return (this.substr(0,sStart.length)==sStart);
};
String.prototype.trim = function() {
    var b=0,e=this.length -1;
    while(this.substr(b,1) == " ") {b++;}
    while(this.substr(e,1) == " ") {e--;}
    return this.substring(b,e+1);
};
Array.prototype.remove = function(rem) {
    for(var i=0; i<this.length; i++) {
        if(this[i]==rem) {
            this.splice(i,1);
			//break;more than once??
        }
    }
    return this;
}
Array.prototype.copy = function() {
  var copy = new Array();
  for (var i = 0; i < this.length; i++) {
      copy[i] = this[i];
  }
  return copy;
};

Array.prototype.prepend = function(the_item) {
    this.splice(0,0,the_item);
};
Array.prototype.append=function(the_item){
    this[this.length]=the_item;
};
Array.prototype.clear=function() {this.length=0;};

/** Create a child element (a div currently) that
* is a proper child of the supplied parent, is invisible,
* positioned as requested within the parent, etc
*
* zIndex is NOT set
*
* @param {str} id - HTML ID of new element, if empty something is made up
* @param {OpenLayers.Pixel} pt - x,y point if missing 0,0 is used
* @param {OpenLayers.Size} sz - size else size of parent is used
* @param {str} overflow - behavior of clipped/overflow content
* @param {str} img - background image url
* @param {str} position - relative or absolute?
*
* @return {DOM}
*/
OpenLayers.Util.createDiv = function(id, pt, sz, overflow, img, position) {
    var x,y,w,h;

    if (pt){
        x = pt.x;
        y = pt.y;
    } else {
        x = y = 0;
    }

    if (!position){
        position = "absolute";
    }

    if (!id){
        id = "OpenLayersDiv" + (Math.random()*10000%10000);
    }

    var dom = document.createElement('div');
    dom.id = id;
    if (overflow){
        dom.style.overflow = overflow;
    }
    if (sz) {
        dom.style.width = sz.w+"px";
        dom.style.height = sz.h+"px";
    }
    dom.style.position = position;
    dom.style.top = y;
    dom.style.left = x;
    dom.style.padding = "0";
    dom.style.margin = "0";
    dom.style.cursor = "inherit";

    if (img){
        dom.style.backgroundImage = 'url(' + img + ')';
    }

    return dom;
};

OpenLayers.Util.ImageHTML=function(img,sz,alt){
    return "<img src='" + img + "' padding='0' border='0' alt='" + alt + "' width=" + sz.w + "px height=" + sz.h + "px/>";
};

/**
* @param {str} img - src URL
* @param {OpenLayers.Size} sz
* @param {OpenLayers.Pixel} xy
* @param {str} position
* @param {str} id
* @param {int} border
*/
OpenLayers.Util.createImage = function(img, sz, xy, position, id, border) {
    image = document.createElement("img");
    if (id) {
        image.id = id;
        image.style.alt = id;
    }
    if (xy) {
        image.style.left = xy.x;
        image.style.top = xy.y;
    }
    if (sz) {
        image.style.width = sz.w;
        image.style.height = sz.h;
    }
    if (position) {
        image.style.position = position;
    } else {
        image.style.position = "relative";
    }
    if (border) {
        image.style.border = border + "px solid";
    } else {
        image.style.border = 0;
    }
    image.style.cursor = "inherit";
    image.src = img;
    return image;
};

/** returns a concatenation of the properties of an object in
*    http parameter notation. ex:
*
*    "key1=value1&key2=value2&key3=value3"
*
* @param {Object} params
*
* @return {str}
*/
OpenLayers.getParameterString = function(params) {
    paramsArray = new Array();

    for (var key in params) {
        var value = params[key];
        //skip functions
        if (typeof value == 'function') continue;

        paramsArray.push(key + "=" + value);
    }

    return paramsArray.join("&");
};

OpenLayers.Util.getImagesLocation = function () {
    return "img/";
};
<commit_msg>copy/paste oversight<commit_after>/*
* @class
*/
OpenLayers.Util=new Object();

/**
* @class This class represents a screen coordinate, in x and y coordinates
*/
OpenLayers.Pixel = Class.create();
OpenLayers.Pixel.prototype = {

    /**
    * @param {int} x
    * @param {int} y
    */
    initialize: function(x, y) {
        this.x = x;
        this.y = y;
    },

    /**
    * @return {str} ex: "x=200,y=242"
    */
    toString:function() {
        return ("x="+this.x+",y="+this.y);
    },

    /**
    * @return {OpenLayers.Pixel}
    */
    copyOf:function() {
        return new OpenLayers.Pixel(this.x, this.y);
    },

    /** returns a Size object with the difference
    *    between the two points
    *
    * @return {OpenLayers.Size}
    */
    diff:function(pt) {          // subtract pt from this
        return new OpenLayers.Size(this.x - pt.x, this.y - pt.y);
    },

    /** returns a Size object with the absolute difference
    *    between the two points
    *
    * @param {OpenLayers.Pixel} pt
    *
    * @return {OpenLayers.Size}
    */
    absDiff:function(pt) {
        return new OpenLayers.Size(Math.abs(this.x - pt.x),
                                   Math.abs(this.y - pt.y));
    },

    /** returns a Pixel object with the difference
    *    between the two points
    *
    * @param {OpenLayers.Pixel} pt
    *
    * @return {OpenLayers.Pixel}
    */
    diffPt:function(pt) {
        var sz = this.diff(pt);
        return new OpenLayers.Pixel(sz.w, sz.h);
    },

    /** returns whether or not the two points are equal
    *
    * @param {OpenLayers.Pixel} pt
    *
    * @return {bool}
    */
    equal:function(pt) {
        var d = this.diff(pt);
        return ((d.w==0) && (d.h==0));
    },

    /** Return a new Pixel with this pixel's x&y augmented by
    *    the Size value passed in.
    *
    * @param {OpenLayers.Size} sz
    *
    * @return {OpenLayers.Pixel}
    */
    addSize:function(sz) {
        return new OpenLayers.Pixel(this.x+sz.w, this.y+sz.h);
    },

    /** Return a new Pixel with this pixel's x&y augmented by
    *    the Size value passed in.
    *
    * @param {int} w
    *
    * @return {OpenLayers.Pixel}
    */
    addX:function(w) {
        return new OpenLayers.Pixel(this.x+w, this.y);
    },

    /** Return a new Pixel with this pixel's y augmented by
    *    the int value passed in.
    *
    * @param {int} h
    *
    * @return {OpenLayers.Pixel}
    */
    addY:function(h) {
        return new OpenLayers.Pixel(this.x, this.y+h);
    },

    /** Return a new Pixel with this pixel's x&y augmented by
    *    the int values passed in.
    *
    * @param {int} x
    * @param {int} y
    *
    * @return {OpenLayers.Pixel}
    */
    add:function(x, y) {
        return new OpenLayers.Pixel(this.x + x, this.y + y);
    },

    ////////////////////
    who:function(){return "OpenLayers.Pixel";}
};

OpenLayers.Size = Class.create();
OpenLayers.Size.prototype = {
    initialize: function(w,h) {
        this.w=w;
        this.h=h;
    },
    toString:function(){
        return ("w="+this.w+",h="+this.h);
    },
    copyOf:function(){
        return new OpenLayers.Size(this.w, this.h);
    },
    sameSize:function(pt){
        return (this.w==pt.w && this.h==pt.h);
    }
};

OpenLayers.LatLon = Class.create();
OpenLayers.LatLon.prototype = {
    initialize: function(lat,lon) {
        this.lat=lat;
        this.lon=lon;
    },
    toString:function(){
        return ("lat="+this.lat+",lon="+this.lon);
    },
    copyOf:function(){
        return new OpenLayers.LatLon(this.lat, this.lon);
    },
    toShortString:function(){
        return (this.lat+", "+this.lon);
    },
    diff:function(pt){          // subtract pt from this
        return new OpenLayers.LatLon(this.lat - pt.lat,this.lon - pt.lon);
    },
    samePT:function(pt){
        return (this.lat==pt.lat && this.lon==pt.lon);
    }
};
OpenLayers.LatLon.fromString=function(str){
    var pairs=str.split(",");
    return new OpenLayers.LatLon(pairs[1],pairs[0]);
};
OpenLayers.Bounds = Class.create();
OpenLayers.Bounds.prototype = {
    initialize: function(minlat,minlon,maxlat,maxlon){
        this.minlat=minlat;
        this.minlon=minlon;
        this.maxlat=maxlat;
        this.maxlon=maxlon;
        this.width = Math.abs(this.maxlon - this.minlon);
        this.height = Math.abs(this.maxlat - this.minlat);
    },
    toString:function(){
        return ("Min lat/lon=" + this.minlat +"/"+ this.minlon
                + " Max lat/lon=" + this.maxlat +"/"+ this.maxlon
                + " width:" + this.width + " height:" + this.height);
    },
    copyOf:function(){
        return new OpenLayers.Bounds(this.minlat, this.minlon,
                                    this.maxlat, this.maxlon);
    },
    toBBOX:function(){
        return (this.minlon+","+this.minlat+","+this.maxlon+","+this.maxlat);
    },
    contains:function(pt){
        return (pt.lon >=this.minlon && pt.lon <=this.maxlon
            && pt.lat >=this.minlat && pt.lat <= this.maxlat)
    }

};
/** Create bounds from coordinates in a string:-180.000000,-90.000000 180.000000,90.000000
*/
OpenLayers.Bounds.fromString=function(str){
    var pairs=str.split(" ");
    var min=pairs[0];
    var max=pairs[1];

    var latlon = min.split(",");
    var latlon2 = max.split(",");
    return new OpenLayers.Bounds(latlon[1],latlon[0],latlon2[1],latlon2[0]);
};

OpenLayers.Box = Class.create();
OpenLayers.Box.prototype = {
    initialize: function(x,y,w,h){
        this.xy=new OpenLayers.Pixel(x,y);
        this.sz=new OpenLayers.Size(w,h);
        this.c = new OpenLayers.Pixel(x+(w/2), y+(h/2));
        this.br = new OpenLayers.Pixel(x+w-1,y+h-1);
    },

    /* offset box by df
    * @df(OpenLayers.Size)
    */
    offset:function(df){
        this.xy=new OpenLayers.Pixel(this.xy.x+df.w,this.xy.y+df.h);
        this.c = new OpenLayers.Pixel(this.xy.x+(this.sz.w/2), this.xy.y+(this.sz.h/2));
        var x=this.xy.x;
        var y=this.xy.y;
        var w=this.sz.w;
        var h=this.sz.h;
        this.br = new OpenLayers.Pixel(x+w-1,y+h-1);
    },

    getOrigin:function(){return this.xy;},
    getSize:function(){return this.sz;},
    getCenter:function(){return this.c;},
    getBotRight:function(){return this.br;},
    getWidth:function(){return this.sz.w;},
    getHeight:function(){return this.sz.h;},
    getSize:function(){return this.sz;},

    toString:function(){
        return (this.xy.toString() + " " + this.sz.toString());
    },
    copyOf:function(){
        return new OpenLayers.Box(this.xy.x, this.xy.y, this.sz.w, this.sz.h);
    },
    toCoordsString:function(){
        var x1 = this.xy.x;
        var x2 = this.xy.x+this.sz.w-1;
        var y1 = this.xy.y;
        var y2 = this.xy.y+this.sz.h-1;
        return (x1+","+y1+","+x2+","+y2);
    },
    contains:function(pt){
        var returnVal = false;

        var lx=this.xy.x;
        var ly=this.xy.y;
        var rx=lx+this.sz.w-1;
        var ry=ly+this.sz.h-1;

        if (pt != null) {
            //upper left in
            returnVal =  (pt.x >=lx && pt.x <=rx && pt.y >=ly && pt.y <= ry);
        }
        return returnVal;
    },

    /**
    * @param {ol.Box} box
    * @param {bool} partial - if true, returns whether any part of the passed
    *                         in box is within the calling box. otherwise box
    *                         must contain entire box to return true.
    *
    * @return {bool}
    */
    containsBox:function(box, partial) {
        var contains = false;

        var mainTop = this.xy.y;
        var mainLeft = this.xy.x;
        var mainBottom = this.xy.y + this.sz.h;
        var mainRight = this.xy.x + this.sz.w;

        var top = box.xy.y;
        var left = box.xy.x;
        var bottom = box.xy.y + box.sz.h;
        var right = box.xy.x + box.sz.w;

        var inTop = (top >= mainTop) && (top <= mainBottom);
        var inLeft = (left >= mainLeft) && (left <= mainRight);
        var inBottom = (bottom >= mainTop) && (bottom <= mainBottom);
        var inRight= (right >= mainLeft) && (right <= mainRight);

        if (partial) {
            contains = (inTop || inBottom) && (inLeft || inRight );
        } else {
            contains = (inTop && inLeft && inBottom && inRight);
        }

        return contains;
    },

    // is this point the center of the box, +-2 pixels (@todo fix that number!)
    isCenter:function(pt){
        var size = this.c.absDiff(pt);
        return (size.w <= 2 && size.h <= 2);
    }
};

// Some other helpful things
String.prototype.startsWith = function(sStart){
    return (this.substr(0,sStart.length)==sStart);
};
String.prototype.trim = function() {
    var b=0,e=this.length -1;
    while(this.substr(b,1) == " ") {b++;}
    while(this.substr(e,1) == " ") {e--;}
    return this.substring(b,e+1);
};
Array.prototype.remove = function(rem) {
    for(var i=0; i<this.length; i++) {
        if(this[i]==rem) {
            this.splice(i,1);
			//break;more than once??
        }
    }
    return this;
}
Array.prototype.copy = function() {
  var copy = new Array();
  for (var i = 0; i < this.length; i++) {
      copy[i] = this[i];
  }
  return copy;
};

Array.prototype.prepend = function(the_item) {
    this.splice(0,0,the_item);
};
Array.prototype.append=function(the_item){
    this[this.length]=the_item;
};
Array.prototype.clear=function() {this.length=0;};

/** Create a child element (a div currently) that
* is a proper child of the supplied parent, is invisible,
* positioned as requested within the parent, etc
*
* zIndex is NOT set
*
* @param {str} id - HTML ID of new element, if empty something is made up
* @param {OpenLayers.Pixel} pt - x,y point if missing 0,0 is used
* @param {OpenLayers.Size} sz - size else size of parent is used
* @param {str} overflow - behavior of clipped/overflow content
* @param {str} img - background image url
* @param {str} position - relative or absolute?
*
* @return {DOM}
*/
OpenLayers.Util.createDiv = function(id, pt, sz, overflow, img, position) {
    var x,y,w,h;

    if (pt){
        x = pt.x;
        y = pt.y;
    } else {
        x = y = 0;
    }

    if (!position){
        position = "absolute";
    }

    if (!id){
        id = "OpenLayersDiv" + (Math.random()*10000%10000);
    }

    var dom = document.createElement('div');
    dom.id = id;
    if (overflow){
        dom.style.overflow = overflow;
    }
    if (sz) {
        dom.style.width = sz.w+"px";
        dom.style.height = sz.h+"px";
    }
    dom.style.position = position;
    dom.style.top = y;
    dom.style.left = x;
    dom.style.padding = "0";
    dom.style.margin = "0";
    dom.style.cursor = "inherit";

    if (img){
        dom.style.backgroundImage = 'url(' + img + ')';
    }

    return dom;
};

OpenLayers.Util.ImageHTML=function(img,sz,alt){
    return "<img src='" + img + "' padding='0' border='0' alt='" + alt + "' width=" + sz.w + "px height=" + sz.h + "px/>";
};

/**
* @param {str} img - src URL
* @param {OpenLayers.Size} sz
* @param {OpenLayers.Pixel} xy
* @param {str} position
* @param {str} id
* @param {int} border
*/
OpenLayers.Util.createImage = function(img, sz, xy, position, id, border) {
    image = document.createElement("img");
    if (id) {
        image.id = id;
        image.style.alt = id;
    }
    if (xy) {
        image.style.left = xy.x;
        image.style.top = xy.y;
    }
    if (sz) {
        image.style.width = sz.w;
        image.style.height = sz.h;
    }
    if (position) {
        image.style.position = position;
    } else {
        image.style.position = "relative";
    }
    if (border) {
        image.style.border = border + "px solid";
    } else {
        image.style.border = 0;
    }
    image.style.cursor = "inherit";
    image.src = img;
    return image;
};

/** returns a concatenation of the properties of an object in
*    http parameter notation. ex:
*
*    "key1=value1&key2=value2&key3=value3"
*
* @param {Object} params
*
* @return {str}
*/
OpenLayers.getParameterString = function(params) {
    paramsArray = new Array();

    for (var key in params) {
        var value = params[key];
        //skip functions
        if (typeof value == 'function') continue;

        paramsArray.push(key + "=" + value);
    }

    return paramsArray.join("&");
};

OpenLayers.Util.getImagesLocation = function () {
    return "img/";
};
<|endoftext|>