UNPKG

kepler.gl

Version:

kepler.gl is a webgl based application to visualize large scale location data in the browser

637 lines (613 loc) 81.1 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.adjustValueToAnimationWindow = adjustValueToAnimationWindow; exports.binByTime = binByTime; exports.getAggregationOptiosnBasedOnField = void 0; exports.getBinThresholds = getBinThresholds; exports.getChartTitle = getChartTitle; exports.getDefaultPlotType = getDefaultPlotType; exports.getFilterDataFunc = getFilterDataFunc; exports.getLineChart = getLineChart; exports.getPctChange = getPctChange; exports.getRangeFilterBins = getRangeFilterBins; exports.getTimeBins = getTimeBins; exports.getValueAggrFunc = void 0; exports.histogramFromDomain = histogramFromDomain; exports.histogramFromOrdinal = histogramFromOrdinal; exports.histogramFromThreshold = histogramFromThreshold; exports.histogramFromValues = histogramFromValues; exports.isPercentField = isPercentField; exports.normalizeValue = normalizeValue; exports.runGpuFilterForPlot = runGpuFilterForPlot; exports.snapToMarks = snapToMarks; exports.splitSeries = splitSeries; exports.updateAggregationByField = updateAggregationByField; exports.updateRangeFilterPlotType = updateRangeFilterPlotType; exports.updateTimeFilterPlotType = updateTimeFilterPlotType; exports.validBin = validBin; var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof")); var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _d3Array = require("d3-array"); var _isEqual = _interopRequireDefault(require("lodash/isEqual")); var _time = require("./time"); var _moment = _interopRequireDefault(require("moment")); var _commonUtils = require("@kepler.gl/common-utils"); var _constants = require("@kepler.gl/constants"); var _dataUtils = require("./data-utils"); var _aggregation = require("./aggregation"); var _strings = require("./strings"); var _format = require("./format"); var _colorUtils = require("./color-utils"); function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2["default"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } // SPDX-License-Identifier: MIT // Copyright contributors to the kepler.gl project // TODO kepler-table module isn't accessible from utils. Add compatible interface to types /** * * @param thresholds * @param values * @param indexes */ function histogramFromThreshold(thresholds, values, valueAccessor) { var filterEmptyBins = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; var getBins = (0, _d3Array.histogram)().domain([thresholds[0], thresholds[thresholds.length - 1]]).thresholds(thresholds); if (valueAccessor) { getBins.value(valueAccessor); } // @ts-ignore var bins = getBins(values).map(function (bin) { return { count: bin.length, indexes: bin, x0: bin.x0, x1: bin.x1 }; }); // d3-histogram ignores threshold values outside the domain // The first bin.x0 is always equal to the minimum domain value, and the last bin.x1 is always equal to the maximum domain value. // bins[0].x0 = thresholds[0]; // bins[bins.length - 1].x1 = thresholds[thresholds.length - 1]; // @ts-ignore return filterEmptyBins ? bins.filter(function (b) { return b.count > 0; }) : bins; } /** * * @param values * @param numBins * @param valueAccessor */ function histogramFromValues(values, numBins, valueAccessor) { var getBins = (0, _d3Array.histogram)().thresholds(numBins); if (valueAccessor) { getBins.value(valueAccessor); } // @ts-ignore d3-array types doesn't match return getBins(values).map(function (bin) { return { count: bin.length, indexes: bin, x0: bin.x0, x1: bin.x1 }; }).filter(function (b) { var x0 = b.x0, x1 = b.x1; return (0, _dataUtils.isNumber)(x0) && (0, _dataUtils.isNumber)(x1); }); } function histogramFromOrdinal(domain, values, valueAccessor) { // @ts-expect-error to typed to expect strings var getBins = (0, _d3Array.histogram)().thresholds(domain); if (valueAccessor) { // @ts-expect-error to typed to expect strings getBins.value(valueAccessor); } // @ts-expect-error null values aren't expected var bins = getBins(values); // @ts-ignore d3-array types doesn't match return bins.map(function (bin) { return { count: bin.length, indexes: bin, x0: bin.x0, x1: bin.x0 }; }); } /** * * @param domain * @param values * @param numBins * @param valueAccessor */ function histogramFromDomain(domain, values, numBins, valueAccessor) { var getBins = (0, _d3Array.histogram)().thresholds((0, _d3Array.ticks)(domain[0], domain[1], numBins)).domain(domain); if (valueAccessor) { getBins.value(valueAccessor); } // @ts-ignore d3-array types doesn't match return getBins(values).map(function (bin) { return { count: bin.length, indexes: bin, x0: bin.x0, x1: bin.x1 }; }); } /** * @param filter * @param datasets * @param interval */ function getTimeBins(filter, datasets, interval) { var bins = filter.timeBins || {}; filter.dataId.forEach(function (dataId) { // reuse bins if filterData did not change if (bins[dataId] && bins[dataId][interval]) { return; } var dataset = datasets[dataId]; // do not apply current filter var indexes = runGpuFilterForPlot(dataset, filter); bins = _objectSpread(_objectSpread({}, bins), {}, (0, _defineProperty2["default"])({}, dataId, _objectSpread(_objectSpread({}, bins[dataId]), {}, (0, _defineProperty2["default"])({}, interval, binByTime(indexes, dataset, interval, filter))))); }); return bins; } function binByTime(indexes, dataset, interval, filter) { // gpuFilters need to be apply to filteredIndex var mappedValue = (0, _time.getFilterMappedValue)(dataset, filter); if (!mappedValue) { return null; } var intervalBins = getBinThresholds(interval, filter.domain); var valueAccessor = function valueAccessor(idx) { return mappedValue[idx]; }; var bins = histogramFromThreshold(intervalBins, indexes, valueAccessor); return bins; } function getBinThresholds(interval, domain) { var timeInterval = (0, _time.intervalToFunction)(interval); var _domain = (0, _slicedToArray2["default"])(domain, 2), t0 = _domain[0], t1 = _domain[1]; var floor = timeInterval.floor(t0).getTime(); var ceiling = timeInterval.ceil(t1).getTime(); if (!timeInterval) { // if time interval is not defined // this should not happen return [t0, t0 + _constants.durationDay]; } var binThresholds = timeInterval.range(floor, ceiling + 1).map(function (t) { return _moment["default"].utc(t).valueOf(); }); var lastStep = binThresholds[binThresholds.length - 1]; if (lastStep === t1) { // when last step equal to domain max, add one more step binThresholds.push(_moment["default"].utc(timeInterval.offset(lastStep)).valueOf()); } return binThresholds; } /** * Run GPU filter on current filter result to generate indexes for ploting chart * Skip ruuning for the same field * @param dataset * @param filter */ function runGpuFilterForPlot(dataset, filter) { var skipIndexes = getSkipIndexes(dataset, filter); var _dataset$gpuFilter = dataset.gpuFilter, filterValueUpdateTriggers = _dataset$gpuFilter.filterValueUpdateTriggers, filterRange = _dataset$gpuFilter.filterRange, filterValueAccessor = _dataset$gpuFilter.filterValueAccessor, filteredIndex = dataset.filteredIndex; var getFilterValue = filterValueAccessor(dataset.dataContainer)(); var allChannels = Object.keys(filterValueUpdateTriggers).map(function (_, i) { return i; }).filter(function (i) { return Object.values(filterValueUpdateTriggers)[i]; }); var skipAll = !allChannels.filter(function (i) { return !skipIndexes.includes(i); }).length; if (skipAll) { return filteredIndex; } var filterData = getFilterDataFunc(filterRange, getFilterValue, dataset.dataContainer, skipIndexes); return filteredIndex.filter(filterData); } function getSkipIndexes(dataset, filter) { // array of gpu filter names if (!filter) { return []; } var gpuFilters = Object.values(dataset.gpuFilter.filterValueUpdateTriggers); var valueIndex = filter.dataId.findIndex(function (id) { return id === dataset.id; }); var filterColumn = filter.name[valueIndex]; return gpuFilters.reduce(function (accu, item, idx) { if (item && filterColumn === item.name) { accu.push(idx); } return accu; }, []); } function getFilterDataFunc(filterRange, getFilterValue, dataContainer, skips) { return function (index) { return getFilterValue({ index: index }).every(function (val, i) { return skips.includes(i) || val >= filterRange[i][0] && val <= filterRange[i][1]; }); }; } function validBin(b) { return b.x0 !== undefined && b.x1 !== undefined; } /** * Use in slider, given a number and an array of numbers, return the nears number from the array. * Takes a value, timesteps and return the actual step. * @param value * @param marks */ function snapToMarks(value, marks) { // always use bin x0 if (!marks.length) { // @ts-expect-error looking at the usage null return value isn't expected and requires extra handling in a lot of places return null; } var i = (0, _d3Array.bisectLeft)(marks, value); if (i === 0) { return marks[i]; } else if (i === marks.length) { return marks[i - 1]; } var idx = marks[i] - value < value - marks[i - 1] ? i : i - 1; return marks[idx]; } function normalizeValue(val, minValue, step, marks) { if (marks && marks.length) { return snapToMarks(val, marks); } return (0, _dataUtils.roundValToStep)(minValue, step, val); } function isPercentField(field) { return field.metadata && field.metadata.numerator && field.metadata.denominator; } function updateAggregationByField(field, aggregation) { // shouldn't apply sum to percent fiele type // default aggregation is average return field && isPercentField(field) ? _constants.AGGREGATION_TYPES.average : aggregation || _constants.AGGREGATION_TYPES.average; } var getAgregationType = function getAgregationType(field, aggregation) { if (isPercentField(field)) { return 'mean_of_percent'; } return aggregation; }; var getAggregationAccessor = function getAggregationAccessor(field, dataContainer, fields) { if (isPercentField(field)) { var numeratorIdx = fields.findIndex(function (f) { return f.name === field.metadata.numerator; }); var denominatorIdx = fields.findIndex(function (f) { return f.name === field.metadata.denominator; }); return { getNumerator: function getNumerator(i) { return dataContainer.valueAt(i, numeratorIdx); }, getDenominator: function getDenominator(i) { return dataContainer.valueAt(i, denominatorIdx); } }; } return function (i) { return field.valueAccessor({ index: i }); }; }; var getValueAggrFunc = exports.getValueAggrFunc = function getValueAggrFunc(field, aggregation, dataset) { var dataContainer = dataset.dataContainer, fields = dataset.fields; // The passed-in field might not have all the fields set (e.g. valueAccessor) var datasetField = fields.find(function (f) { return field && (f.name === field || f.name === field.name); }); return datasetField && aggregation ? function (bin) { return (0, _aggregation.aggregate)(bin.indexes, getAgregationType(datasetField, aggregation), // @ts-expect-error can return {getNumerator, getDenominator} getAggregationAccessor(datasetField, dataContainer, fields)); } : function (bin) { return bin.count; }; }; var getAggregationOptiosnBasedOnField = exports.getAggregationOptiosnBasedOnField = function getAggregationOptiosnBasedOnField(field) { if (isPercentField(field)) { // don't show sum return _constants.TIME_AGGREGATION.filter(function (_ref) { var id = _ref.id; return id !== _constants.AGGREGATION_TYPES.sum; }); } return _constants.TIME_AGGREGATION; }; function getDelta(bins, y, _interval) { // if (WOW[interval]) return getWow(bins, y, interval); var lastBin = bins[bins.length - 1]; return { delta: 'last', pct: lastBin ? getPctChange(y, lastBin.y) : null }; } function getPctChange(y, y0) { if (Number.isFinite(y) && Number.isFinite(y0) && y0 !== 0) { return (y - y0) / y0; } return null; } /** * * @param datasets * @param filter */ function getLineChart(datasets, filter) { var _timeBins; var dataId = filter.dataId, yAxis = filter.yAxis, plotType = filter.plotType, lineChart = filter.lineChart; var aggregation = plotType.aggregation, interval = plotType.interval; var seriesDataId = dataId[0]; var bins = (_timeBins = filter.timeBins) === null || _timeBins === void 0 || (_timeBins = _timeBins[seriesDataId]) === null || _timeBins === void 0 ? void 0 : _timeBins[interval]; if (lineChart && lineChart.aggregation === aggregation && lineChart.interval === interval && lineChart.yAxis === (yAxis === null || yAxis === void 0 ? void 0 : yAxis.name) && // we need to make sure we validate bins because of cross filter data changes (0, _isEqual["default"])(bins, lineChart === null || lineChart === void 0 ? void 0 : lineChart.bins)) { // don't update lineChart if plotType hasn't change return lineChart; } var dataset = datasets[seriesDataId]; var getYValue = getValueAggrFunc(yAxis, aggregation, dataset); var init = []; var series = (bins || []).reduce(function (accu, bin) { var y = getYValue(bin); var delta = getDelta(accu, y, interval); accu.push(_objectSpread({ x: bin.x0, y: y }, delta)); return accu; }, init); var yDomain = (0, _d3Array.extent)(series, function (d) { return d.y; }); var xDomain = bins ? [bins[0].x0, bins[bins.length - 1].x1] : []; // treat missing data as another series var split = splitSeries(series); var aggrName = _aggregation.AGGREGATION_NAME[aggregation]; return { // @ts-ignore yDomain: yDomain, // @ts-ignore xDomain: xDomain, interval: interval, aggregation: aggregation, // @ts-ignore series: split, title: "".concat(aggrName, ' of ').concat(yAxis ? yAxis.name : 'Count'), fieldType: yAxis ? yAxis.type : 'integer', yAxis: yAxis ? yAxis.name : null, allTime: { title: "All Time Average", value: (0, _aggregation.aggregate)(series, _constants.AGGREGATION_TYPES.average, function (d) { return d.y; }) }, // @ts-expect-error bins is Bins[], not a Bins map. Refactor to use correct types. bins: bins }; } // split into multiple series when see missing data function splitSeries(series) { var lines = []; var temp = []; for (var i = 0; i < series.length; i++) { var d = series[i]; if (!(0, _commonUtils.notNullorUndefined)(d.y) && temp.length) { // ends temp lines.push(temp); temp = []; } else if ((0, _commonUtils.notNullorUndefined)(d.y)) { temp.push(d); } if (i === series.length - 1 && temp.length) { lines.push(temp); } } var markers = lines.length > 1 ? series.filter(function (d) { return (0, _commonUtils.notNullorUndefined)(d.y); }) : []; return { lines: lines, markers: markers }; } function adjustValueToAnimationWindow(state, filter) { var plotType = filter.plotType, _filter$value = (0, _slicedToArray2["default"])(filter.value, 2), value0 = _filter$value[0], value1 = _filter$value[1], animationWindow = filter.animationWindow; var interval = plotType.interval || (0, _time.getInitialInterval)(filter, state.datasets); var bins = getTimeBins(filter, state.datasets, interval); var datasetBins = bins && Object.keys(bins).length && Object.values(bins)[0][interval]; var thresholds = (datasetBins || []).map(function (b) { return b.x0; }); var val0 = value0; var val1 = value1; var idx; if (animationWindow === _constants.ANIMATION_WINDOW.interval) { val0 = snapToMarks(value1, thresholds); idx = thresholds.indexOf(val0); val1 = idx > -1 ? datasetBins[idx].x1 : NaN; } else { // fit current value to window val0 = snapToMarks(value0, thresholds); val1 = snapToMarks(value1, thresholds); if (val0 === val1) { idx = thresholds.indexOf(val0); if (idx === thresholds.length - 1) { val0 = thresholds[idx - 1]; } else { val1 = thresholds[idx + 1]; } } } var updatedFilter = _objectSpread(_objectSpread({}, filter), {}, { plotType: _objectSpread(_objectSpread({}, filter.plotType), {}, { interval: interval }), timeBins: bins, value: [val0, val1] }); return updatedFilter; } /** * Create or update colors for a filter plot * @param filter * @param datasets * @param oldColorsByDataId */ function getFilterPlotColorsByDataId(filter, datasets, oldColorsByDataId) { var colorsByDataId = oldColorsByDataId || {}; var _iterator = _createForOfIteratorHelper(filter.dataId), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var dataId = _step.value; if (!colorsByDataId[dataId] && datasets[dataId]) { colorsByDataId = _objectSpread(_objectSpread({}, colorsByDataId), {}, (0, _defineProperty2["default"])({}, dataId, (0, _colorUtils.rgbToHex)(datasets[dataId].color))); } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return colorsByDataId; } /** * * @param filter * @param plotType * @param datasets * @param dataId */ function updateTimeFilterPlotType(filter, plotType, datasets, _dataId) { var nextFilter = filter; var nextPlotType = plotType; if ((0, _typeof2["default"])(nextPlotType) !== 'object' || !nextPlotType.aggregation || !nextPlotType.interval) { nextPlotType = getDefaultPlotType(filter, datasets); } if (filter.dataId.length > 1) { nextPlotType = _objectSpread(_objectSpread({}, nextPlotType), {}, { colorsByDataId: getFilterPlotColorsByDataId(filter, datasets, nextPlotType.colorsByDataId) }); } nextFilter = _objectSpread(_objectSpread({}, nextFilter), {}, { plotType: nextPlotType }); var bins = getTimeBins(nextFilter, datasets, nextPlotType.interval); nextFilter = _objectSpread(_objectSpread({}, nextFilter), {}, { timeBins: bins }); if (plotType.type === _constants.PLOT_TYPES.histogram) { // Histogram is calculated and memoized in the chart itself } else if (plotType.type === _constants.PLOT_TYPES.lineChart) { // we should be able to move this into its own component so react will do the shallow comparison for us. nextFilter = _objectSpread(_objectSpread({}, nextFilter), {}, { lineChart: getLineChart(datasets, nextFilter) }); } return nextFilter; } function getRangeFilterBins(filter, datasets, numBins) { var domain = filter.domain; if (!filter.dataId) return null; return filter.dataId.reduce(function (acc, dataId, datasetIdx) { var _filter$bins; if ((_filter$bins = filter.bins) !== null && _filter$bins !== void 0 && _filter$bins[dataId]) { // don't recalculate bins acc[dataId] = filter.bins[dataId]; return acc; } var fieldName = filter.name[datasetIdx]; if (dataId && fieldName) { var dataset = datasets[dataId]; var field = dataset === null || dataset === void 0 ? void 0 : dataset.getColumnField(fieldName); if (dataset && field) { var indexes = runGpuFilterForPlot(dataset, filter); var valueAccessor = function valueAccessor(index) { return field.valueAccessor({ index: index }); }; acc[dataId] = histogramFromDomain(domain, indexes, numBins, valueAccessor); } } return acc; }, {}); } function updateRangeFilterPlotType(filter, plotType, datasets, _dataId) { var nextFilter = _objectSpread(_objectSpread({}, filter), {}, { plotType: plotType }); // if (dataId) { // // clear bins // nextFilter = { // ...nextFilter, // bins: { // ...nextFilter.bins, // [dataId]: null // } // }; // } return _objectSpread(_objectSpread({}, filter), {}, { plotType: plotType, bins: getRangeFilterBins(nextFilter, datasets, _constants.BINS) }); } function getChartTitle(yAxis, plotType) { var yAxisName = yAxis === null || yAxis === void 0 ? void 0 : yAxis.displayName; var aggregation = plotType.aggregation; if (yAxisName) { return (0, _strings.capitalizeFirstLetter)("".concat(aggregation, " ").concat(yAxisName, " over Time")); } return "Count of Rows over Time"; } function getDefaultPlotType(filter, datasets) { var interval = (0, _time.getInitialInterval)(filter, datasets); var defaultTimeFormat = (0, _format.getDefaultTimeFormat)(interval); return { interval: interval, defaultTimeFormat: defaultTimeFormat, type: _constants.PLOT_TYPES.histogram, aggregation: _constants.AGGREGATION_TYPES.sum }; } //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfZDNBcnJheSIsInJlcXVpcmUiLCJfaXNFcXVhbCIsIl9pbnRlcm9wUmVxdWlyZURlZmF1bHQiLCJfdGltZSIsIl9tb21lbnQiLCJfY29tbW9uVXRpbHMiLCJfY29uc3RhbnRzIiwiX2RhdGFVdGlscyIsIl9hZ2dyZWdhdGlvbiIsIl9zdHJpbmdzIiwiX2Zvcm1hdCIsIl9jb2xvclV0aWxzIiwiX2NyZWF0ZUZvck9mSXRlcmF0b3JIZWxwZXIiLCJyIiwiZSIsInQiLCJTeW1ib2wiLCJpdGVyYXRvciIsIkFycmF5IiwiaXNBcnJheSIsIl91bnN1cHBvcnRlZEl0ZXJhYmxlVG9BcnJheSIsImxlbmd0aCIsIl9uIiwiRiIsInMiLCJuIiwiZG9uZSIsInZhbHVlIiwiZiIsIlR5cGVFcnJvciIsIm8iLCJhIiwidSIsImNhbGwiLCJuZXh0IiwiX2FycmF5TGlrZVRvQXJyYXkiLCJ0b1N0cmluZyIsInNsaWNlIiwiY29uc3RydWN0b3IiLCJuYW1lIiwiZnJvbSIsInRlc3QiLCJvd25LZXlzIiwiT2JqZWN0Iiwia2V5cyIsImdldE93blByb3BlcnR5U3ltYm9scyIsImZpbHRlciIsImdldE93blByb3BlcnR5RGVzY3JpcHRvciIsImVudW1lcmFibGUiLCJwdXNoIiwiYXBwbHkiLCJfb2JqZWN0U3ByZWFkIiwiYXJndW1lbnRzIiwiZm9yRWFjaCIsIl9kZWZpbmVQcm9wZXJ0eTIiLCJnZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3JzIiwiZGVmaW5lUHJvcGVydGllcyIsImRlZmluZVByb3BlcnR5IiwiaGlzdG9ncmFtRnJvbVRocmVzaG9sZCIsInRocmVzaG9sZHMiLCJ2YWx1ZXMiLCJ2YWx1ZUFjY2Vzc29yIiwiZmlsdGVyRW1wdHlCaW5zIiwidW5kZWZpbmVkIiwiZ2V0QmlucyIsImQzSGlzdG9ncmFtIiwiZG9tYWluIiwiYmlucyIsIm1hcCIsImJpbiIsImNvdW50IiwiaW5kZXhlcyIsIngwIiwieDEiLCJiIiwiaGlzdG9ncmFtRnJvbVZhbHVlcyIsIm51bUJpbnMiLCJpc051bWJlciIsImhpc3RvZ3JhbUZyb21PcmRpbmFsIiwiaGlzdG9ncmFtRnJvbURvbWFpbiIsInRpY2tzIiwiZ2V0VGltZUJpbnMiLCJkYXRhc2V0cyIsImludGVydmFsIiwidGltZUJpbnMiLCJkYXRhSWQiLCJkYXRhc2V0IiwicnVuR3B1RmlsdGVyRm9yUGxvdCIsImJpbkJ5VGltZSIsIm1hcHBlZFZhbHVlIiwiZ2V0RmlsdGVyTWFwcGVkVmFsdWUiLCJpbnRlcnZhbEJpbnMiLCJnZXRCaW5UaHJlc2hvbGRzIiwiaWR4IiwidGltZUludGVydmFsIiwiaW50ZXJ2YWxUb0Z1bmN0aW9uIiwiX2RvbWFpbiIsIl9zbGljZWRUb0FycmF5MiIsInQwIiwidDEiLCJmbG9vciIsImdldFRpbWUiLCJjZWlsaW5nIiwiY2VpbCIsImR1cmF0aW9uRGF5IiwiYmluVGhyZXNob2xkcyIsInJhbmdlIiwibW9tZW50IiwidXRjIiwidmFsdWVPZiIsImxhc3RTdGVwIiwib2Zmc2V0Iiwic2tpcEluZGV4ZXMiLCJnZXRTa2lwSW5kZXhlcyIsIl9kYXRhc2V0JGdwdUZpbHRlciIsImdwdUZpbHRlciIsImZpbHRlclZhbHVlVXBkYXRlVHJpZ2dlcnMiLCJmaWx0ZXJSYW5nZSIsImZpbHRlclZhbHVlQWNjZXNzb3IiLCJmaWx0ZXJlZEluZGV4IiwiZ2V0RmlsdGVyVmFsdWUiLCJkYXRhQ29udGFpbmVyIiwiYWxsQ2hhbm5lbHMiLCJfIiwiaSIsInNraXBBbGwiLCJpbmNsdWRlcyIsImZpbHRlckRhdGEiLCJnZXRGaWx0ZXJEYXRhRnVuYyIsImdwdUZpbHRlcnMiLCJ2YWx1ZUluZGV4IiwiZmluZEluZGV4IiwiaWQiLCJmaWx0ZXJDb2x1bW4iLCJyZWR1Y2UiLCJhY2N1IiwiaXRlbSIsInNraXBzIiwiaW5kZXgiLCJldmVyeSIsInZhbCIsInZhbGlkQmluIiwic25hcFRvTWFya3MiLCJtYXJrcyIsImJpc2VjdExlZnQiLCJub3JtYWxpemVWYWx1ZSIsIm1pblZhbHVlIiwic3RlcCIsInJvdW5kVmFsVG9TdGVwIiwiaXNQZXJjZW50RmllbGQiLCJmaWVsZCIsIm1ldGFkYXRhIiwibnVtZXJhdG9yIiwiZGVub21pbmF0b3IiLCJ1cGRhdGVBZ2dyZWdhdGlvbkJ5RmllbGQiLCJhZ2dyZWdhdGlvbiIsIkFHR1JFR0FUSU9OX1RZUEVTIiwiYXZlcmFnZSIsImdldEFncmVnYXRpb25UeXBlIiwiZ2V0QWdncmVnYXRpb25BY2Nlc3NvciIsImZpZWxkcyIsIm51bWVyYXRvcklkeCIsImRlbm9taW5hdG9ySWR4IiwiZ2V0TnVtZXJhdG9yIiwidmFsdWVBdCIsImdldERlbm9taW5hdG9yIiwiZ2V0VmFsdWVBZ2dyRnVuYyIsImV4cG9ydHMiLCJkYXRhc2V0RmllbGQiLCJmaW5kIiwiYWdncmVnYXRlIiwiZ2V0QWdncmVnYXRpb25PcHRpb3NuQmFzZWRPbkZpZWxkIiwiVElNRV9BR0dSRUdBVElPTiIsIl9yZWYiLCJzdW0iLCJnZXREZWx0YSIsInkiLCJfaW50ZXJ2YWwiLCJsYXN0QmluIiwiZGVsdGEiLCJwY3QiLCJnZXRQY3RDaGFuZ2UiLCJ5MCIsIk51bWJlciIsImlzRmluaXRlIiwiZ2V0TGluZUNoYXJ0IiwiX3RpbWVCaW5zIiwieUF4aXMiLCJwbG90VHlwZSIsImxpbmVDaGFydCIsInNlcmllc0RhdGFJZCIsImlzRXF1YWwiLCJnZXRZVmFsdWUiLCJpbml0Iiwic2VyaWVzIiwieCIsInlEb21haW4iLCJleHRlbnQiLCJkIiwieERvbWFpbiIsInNwbGl0Iiwic3BsaXRTZXJpZXMiLCJhZ2dyTmFtZSIsIkFHR1JFR0FUSU9OX05BTUUiLCJ0aXRsZSIsImNvbmNhdCIsImZpZWxkVHlwZSIsInR5cGUiLCJhbGxUaW1lIiwibGluZXMiLCJ0ZW1wIiwibm90TnVsbG9yVW5kZWZpbmVkIiwibWFya2VycyIsImFkanVzdFZhbHVlVG9BbmltYXRpb25XaW5kb3ciLCJzdGF0ZSIsIl9maWx0ZXIkdmFsdWUiLCJ2YWx1ZTAiLCJ2YWx1ZTEiLCJhbmltYXRpb25XaW5kb3ciLCJnZXRJbml0aWFsSW50ZXJ2YWwiLCJkYXRhc2V0QmlucyIsInZhbDAiLCJ2YWwxIiwiQU5JTUFUSU9OX1dJTkRPVyIsImluZGV4T2YiLCJOYU4iLCJ1cGRhdGVkRmlsdGVyIiwiZ2V0RmlsdGVyUGxvdENvbG9yc0J5RGF0YUlkIiwib2xkQ29sb3JzQnlEYXRhSWQiLCJjb2xvcnNCeURhdGFJZCIsIl9pdGVyYXRvciIsIl9zdGVwIiwicmdiVG9IZXgiLCJjb2xvciIsImVyciIsInVwZGF0ZVRpbWVGaWx0ZXJQbG90VHlwZSIsIl9kYXRhSWQiLCJuZXh0RmlsdGVyIiwibmV4dFBsb3RUeXBlIiwiX3R5cGVvZjIiLCJnZXREZWZhdWx0UGxvdFR5cGUiLCJQTE9UX1RZUEVTIiwiaGlzdG9ncmFtIiwiZ2V0UmFuZ2VGaWx0ZXJCaW5zIiwiYWNjIiwiZGF0YXNldElkeCIsIl9maWx0ZXIkYmlucyIsImZpZWxkTmFtZSIsImdldENvbHVtbkZpZWxkIiwidXBkYXRlUmFuZ2VGaWx0ZXJQbG90VHlwZSIsIkJJTlMiLCJnZXRDaGFydFRpdGxlIiwieUF4aXNOYW1lIiwiZGlzcGxheU5hbWUiLCJjYXBpdGFsaXplRmlyc3RMZXR0ZXIiLCJkZWZhdWx0VGltZUZvcm1hdCIsImdldERlZmF1bHRUaW1lRm9ybWF0Il0sInNvdXJjZXMiOlsiLi4vc3JjL3Bsb3QudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IE1JVFxuLy8gQ29weXJpZ2h0IGNvbnRyaWJ1dG9ycyB0byB0aGUga2VwbGVyLmdsIHByb2plY3RcblxuaW1wb3J0IHtiaXNlY3RMZWZ0LCBleHRlbnQsIGhpc3RvZ3JhbSBhcyBkM0hpc3RvZ3JhbSwgdGlja3N9IGZyb20gJ2QzLWFycmF5JztcbmltcG9ydCBpc0VxdWFsIGZyb20gJ2xvZGFzaC9pc0VxdWFsJztcbmltcG9ydCB7Z2V0RmlsdGVyTWFwcGVkVmFsdWUsIGdldEluaXRpYWxJbnRlcnZhbCwgaW50ZXJ2YWxUb0Z1bmN0aW9ufSBmcm9tICcuL3RpbWUnO1xuaW1wb3J0IG1vbWVudCBmcm9tICdtb21lbnQnO1xuaW1wb3J0IHtcbiAgQmluLFxuICBUaW1lQmlucyxcbiAgTWlsbGlzZWNvbmQsXG4gIFRpbWVSYW5nZUZpbHRlcixcbiAgUmFuZ2VGaWx0ZXIsXG4gIFBsb3RUeXBlLFxuICBGaWx0ZXIsXG4gIExpbmVDaGFydCxcbiAgRmllbGQsXG4gIFZhbHVlT2YsXG4gIExpbmVEYXR1bVxufSBmcm9tICdAa2VwbGVyLmdsL3R5cGVzJztcbmltcG9ydCB7bm90TnVsbG9yVW5kZWZpbmVkfSBmcm9tICdAa2VwbGVyLmdsL2NvbW1vbi11dGlscyc7XG5pbXBvcnQge1xuICBBTklNQVRJT05fV0lORE9XLFxuICBCSU5TLFxuICBkdXJhdGlvbkRheSxcbiAgVElNRV9BR0dSRUdBVElPTixcbiAgQUdHUkVHQVRJT05fVFlQRVMsXG4gIFBMT1RfVFlQRVMsXG4gIEFnZ3JlZ2F0aW9uVHlwZXNcbn0gZnJvbSAnQGtlcGxlci5nbC9jb25zdGFudHMnO1xuXG5pbXBvcnQge2lzTnVtYmVyLCByb3VuZFZhbFRvU3RlcH0gZnJvbSAnLi9kYXRhLXV0aWxzJztcbmltcG9ydCB7YWdncmVnYXRlLCBBR0dSRUdBVElPTl9OQU1FfSBmcm9tICcuL2FnZ3JlZ2F0aW9uJztcbmltcG9ydCB7Y2FwaXRhbGl6ZUZpcnN0TGV0dGVyfSBmcm9tICcuL3N0cmluZ3MnO1xuaW1wb3J0IHtnZXREZWZhdWx0VGltZUZvcm1hdH0gZnJvbSAnLi9mb3JtYXQnO1xuaW1wb3J0IHtyZ2JUb0hleH0gZnJvbSAnLi9jb2xvci11dGlscyc7XG5pbXBvcnQge0RhdGFDb250YWluZXJJbnRlcmZhY2V9IGZyb20gJy4nO1xuaW1wb3J0IHtLZXBsZXJUYWJsZU1vZGVsfSBmcm9tICcuL3R5cGVzJztcblxuLy8gVE9ETyBrZXBsZXItdGFibGUgbW9kdWxlIGlzbid0IGFjY2Vzc2libGUgZnJvbSB1dGlscy4gQWRkIGNvbXBhdGlibGUgaW50ZXJmYWNlIHRvIHR5cGVzXG50eXBlIERhdGFzZXRzID0gYW55O1xuXG4vKipcbiAqXG4gKiBAcGFyYW0gdGhyZXNob2xkc1xuICogQHBhcmFtIHZhbHVlc1xuICogQHBhcmFtIGluZGV4ZXNcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGhpc3RvZ3JhbUZyb21UaHJlc2hvbGQoXG4gIHRocmVzaG9sZHM6IG51bWJlcltdLFxuICB2YWx1ZXM6IG51bWJlcltdLFxuICB2YWx1ZUFjY2Vzc29yPzogKGQ6IHVua25vd24pID0+IG51bWJlcixcbiAgZmlsdGVyRW1wdHlCaW5zID0gdHJ1ZVxuKTogQmluW10ge1xuICBjb25zdCBnZXRCaW5zID0gZDNIaXN0b2dyYW0oKVxuICAgIC5kb21haW4oW3RocmVzaG9sZHNbMF0sIHRocmVzaG9sZHNbdGhyZXNob2xkcy5sZW5ndGggLSAxXV0pXG4gICAgLnRocmVzaG9sZHModGhyZXNob2xkcyk7XG5cbiAgaWYgKHZhbHVlQWNjZXNzb3IpIHtcbiAgICBnZXRCaW5zLnZhbHVlKHZhbHVlQWNjZXNzb3IpO1xuICB9XG5cbiAgLy8gQHRzLWlnbm9yZVxuICBjb25zdCBiaW5zID0gZ2V0Qmlucyh2YWx1ZXMpLm1hcChiaW4gPT4gKHtcbiAgICBjb3VudDogYmluLmxlbmd0aCxcbiAgICBpbmRleGVzOiBiaW4sXG4gICAgeDA6IGJpbi54MCxcbiAgICB4MTogYmluLngxXG4gIH0pKTtcblxuICAvLyBkMy1oaXN0b2dyYW0gaWdub3JlcyB0aHJlc2hvbGQgdmFsdWVzIG91dHNpZGUgdGhlIGRvbWFpblxuICAvLyBUaGUgZmlyc3QgYmluLngwIGlzIGFsd2F5cyBlcXVhbCB0byB0aGUgbWluaW11bSBkb21haW4gdmFsdWUsIGFuZCB0aGUgbGFzdCBiaW4ueDEgaXMgYWx3YXlzIGVxdWFsIHRvIHRoZSBtYXhpbXVtIGRvbWFpbiB2YWx1ZS5cblxuICAvLyBiaW5zWzBdLngwID0gdGhyZXNob2xkc1swXTtcbiAgLy8gYmluc1tiaW5zLmxlbmd0aCAtIDFdLngxID0gdGhyZXNob2xkc1t0aHJlc2hvbGRzLmxlbmd0aCAtIDFdO1xuXG4gIC8vIEB0cy1pZ25vcmVcbiAgcmV0dXJuIGZpbHRlckVtcHR5QmlucyA/IGJpbnMuZmlsdGVyKGIgPT4gYi5jb3VudCA+IDApIDogYmlucztcbn1cblxuLyoqXG4gKlxuICogQHBhcmFtIHZhbHVlc1xuICogQHBhcmFtIG51bUJpbnNcbiAqIEBwYXJhbSB2YWx1ZUFjY2Vzc29yXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBoaXN0b2dyYW1Gcm9tVmFsdWVzKFxuICB2YWx1ZXM6IChNaWxsaXNlY29uZCB8IG51bGwgfCBudW1iZXIpW10sXG4gIG51bUJpbnM6IG51bWJlcixcbiAgdmFsdWVBY2Nlc3Nvcj86IChkOiBudW1iZXIpID0+IG51bWJlclxuKSB7XG4gIGNvbnN0IGdldEJpbnMgPSBkM0hpc3RvZ3JhbSgpLnRocmVzaG9sZHMobnVtQmlucyk7XG5cbiAgaWYgKHZhbHVlQWNjZXNzb3IpIHtcbiAgICBnZXRCaW5zLnZhbHVlKHZhbHVlQWNjZXNzb3IpO1xuICB9XG5cbiAgLy8gQHRzLWlnbm9yZSBkMy1hcnJheSB0eXBlcyBkb2Vzbid0IG1hdGNoXG4gIHJldHVybiBnZXRCaW5zKHZhbHVlcylcbiAgICAubWFwKGJpbiA9PiAoe1xuICAgICAgY291bnQ6IGJpbi5sZW5ndGgsXG4gICAgICBpbmRleGVzOiBiaW4sXG4gICAgICB4MDogYmluLngwLFxuICAgICAgeDE6IGJpbi54MVxuICAgIH0pKVxuICAgIC5maWx0ZXIoYiA9PiB7XG4gICAgICBjb25zdCB7eDAsIHgxfSA9IGI7XG4gICAgICByZXR1cm4gaXNOdW1iZXIoeDApICYmIGlzTnVtYmVyKHgxKTtcbiAgICB9KTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGhpc3RvZ3JhbUZyb21PcmRpbmFsKFxuICBkb21haW46IFtzdHJpbmddLFxuICB2YWx1ZXM6IChNaWxsaXNlY29uZCB8IG51bGwgfCBudW1iZXIpW10sXG4gIHZhbHVlQWNjZXNzb3I/OiAoZDogdW5rbm93bikgPT4gc3RyaW5nXG4pOiBCaW5bXSB7XG4gIC8vIEB0cy1leHBlY3QtZXJyb3IgdG8gdHlwZWQgdG8gZXhwZWN0IHN0cmluZ3NcbiAgY29uc3QgZ2V0QmlucyA9IGQzSGlzdG9ncmFtKCkudGhyZXNob2xkcyhkb21haW4pO1xuICBpZiAodmFsdWVBY2Nlc3Nvcikge1xuICAgIC8vIEB0cy1leHBlY3QtZXJyb3IgdG8gdHlwZWQgdG8gZXhwZWN0IHN0cmluZ3NcbiAgICBnZXRCaW5zLnZhbHVlKHZhbHVlQWNjZXNzb3IpO1xuICB9XG5cbiAgLy8gQHRzLWV4cGVjdC1lcnJvciBudWxsIHZhbHVlcyBhcmVuJ3QgZXhwZWN0ZWRcbiAgY29uc3QgYmlucyA9IGdldEJpbnModmFsdWVzKTtcblxuICAvLyBAdHMtaWdub3JlIGQzLWFycmF5IHR5cGVzIGRvZXNuJ3QgbWF0Y2hcbiAgcmV0dXJuIGJpbnMubWFwKGJpbiA9PiAoe1xuICAgIGNvdW50OiBiaW4ubGVuZ3RoLFxuICAgIGluZGV4ZXM6IGJpbixcbiAgICB4MDogYmluLngwLFxuICAgIHgxOiBiaW4ueDBcbiAgfSkpO1xufVxuXG4vKipcbiAqXG4gKiBAcGFyYW0gZG9tYWluXG4gKiBAcGFyYW0gdmFsdWVzXG4gKiBAcGFyYW0gbnVtQmluc1xuICogQHBhcmFtIHZhbHVlQWNjZXNzb3JcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGhpc3RvZ3JhbUZyb21Eb21haW4oXG4gIGRvbWFpbjogW251bWJlciwgbnVtYmVyXSxcbiAgdmFsdWVzOiAoTWlsbGlzZWNvbmQgfCBudWxsIHwgbnVtYmVyKVtdLFxuICBudW1CaW5zOiBudW1iZXIsXG4gIHZhbHVlQWNjZXNzb3I/OiAoZDogdW5rbm93bikgPT4gbnVtYmVyXG4pOiBCaW5bXSB7XG4gIGNvbnN0IGdldEJpbnMgPSBkM0hpc3RvZ3JhbSgpLnRocmVzaG9sZHModGlja3MoZG9tYWluWzBdLCBkb21haW5bMV0sIG51bUJpbnMpKS5kb21haW4oZG9tYWluKTtcbiAgaWYgKHZhbHVlQWNjZXNzb3IpIHtcbiAgICBnZXRCaW5zLnZhbHVlKHZhbHVlQWNjZXNzb3IpO1xuICB9XG5cbiAgLy8gQHRzLWlnbm9yZSBkMy1hcnJheSB0eXBlcyBkb2Vzbid0IG1hdGNoXG4gIHJldHVybiBnZXRCaW5zKHZhbHVlcykubWFwKGJpbiA9PiAoe1xuICAgIGNvdW50OiBiaW4ubGVuZ3RoLFxuICAgIGluZGV4ZXM6IGJpbixcbiAgICB4MDogYmluLngwLFxuICAgIHgxOiBiaW4ueDFcbiAgfSkpO1xufVxuXG4vKipcbiAqIEBwYXJhbSBmaWx0ZXJcbiAqIEBwYXJhbSBkYXRhc2V0c1xuICogQHBhcmFtIGludGVydmFsXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRUaW1lQmlucyhcbiAgZmlsdGVyOiBUaW1lUmFuZ2VGaWx0ZXIsXG4gIGRhdGFzZXRzOiBEYXRhc2V0cyxcbiAgaW50ZXJ2YWw6IFBsb3RUeXBlWydpbnRlcnZhbCddXG4pOiBUaW1lQmlucyB7XG4gIGxldCBiaW5zID0gZmlsdGVyLnRpbWVCaW5zIHx8IHt9O1xuXG4gIGZpbHRlci5kYXRhSWQuZm9yRWFjaChkYXRhSWQgPT4ge1xuICAgIC8vIHJldXNlIGJpbnMgaWYgZmlsdGVyRGF0YSBkaWQgbm90IGNoYW5nZVxuICAgIGlmIChiaW5zW2RhdGFJZF0gJiYgYmluc1tkYXRhSWRdW2ludGVydmFsXSkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBkYXRhc2V0ID0gZGF0YXNldHNbZGF0YUlkXTtcblxuICAgIC8vIGRvIG5vdCBhcHBseSBjdXJyZW50IGZpbHRlclxuICAgIGNvbnN0IGluZGV4ZXMgPSBydW5HcHVGaWx0ZXJGb3JQbG90KGRhdGFzZXQsIGZpbHRlcik7XG5cbiAgICBiaW5zID0ge1xuICAgICAgLi4uYmlucyxcbiAgICAgIFtkYXRhSWRdOiB7XG4gICAgICAgIC4uLmJpbnNbZGF0YUlkXSxcbiAgICAgICAgW2ludGVydmFsXTogYmluQnlUaW1lKGluZGV4ZXMsIGRhdGFzZXQsIGludGVydmFsLCBmaWx0ZXIpXG4gICAgICB9XG4gICAgfTtcbiAgfSk7XG5cbiAgcmV0dXJuIGJpbnM7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBiaW5CeVRpbWUoaW5kZXhlcywgZGF0YXNldCwgaW50ZXJ2YWwsIGZpbHRlcikge1xuICAvLyBncHVGaWx0ZXJzIG5lZWQgdG8gYmUgYXBwbHkgdG8gZmlsdGVyZWRJbmRleFxuICBjb25zdCBtYXBwZWRWYWx1ZSA9IGdldEZpbHRlck1hcHBlZFZhbHVlKGRhdGFzZXQsIGZpbHRlcik7XG4gIGlmICghbWFwcGVkVmFsdWUpIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuICBjb25zdCBpbnRlcnZhbEJpbnMgPSBnZXRCaW5UaHJlc2hvbGRzKGludGVydmFsLCBmaWx0ZXIuZG9tYWluKTtcbiAgY29uc3QgdmFsdWVBY2Nlc3NvciA9IGlkeCA9PiBtYXBwZWRWYWx1ZVtpZHhdO1xuICBjb25zdCBiaW5zID0gaGlzdG9ncmFtRnJvbVRocmVzaG9sZChpbnRlcnZhbEJpbnMsIGluZGV4ZXMsIHZhbHVlQWNjZXNzb3IpO1xuXG4gIHJldHVybiBiaW5zO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0QmluVGhyZXNob2xkcyhpbnRlcnZhbDogc3RyaW5nLCBkb21haW46IG51bWJlcltdKTogbnVtYmVyW10ge1xuICBjb25zdCB0aW1lSW50ZXJ2YWwgPSBpbnRlcnZhbFRvRnVuY3Rpb24oaW50ZXJ2YWwpO1xuICBjb25zdCBbdDAsIHQxXSA9IGRvbWFpbjtcbiAgY29uc3QgZmxvb3IgPSB0aW1lSW50ZXJ2YWwuZmxvb3IodDApLmdldFRpbWUoKTtcbiAgY29uc3QgY2VpbGluZyA9IHRpbWVJbnRlcnZhbC5jZWlsKHQxKS5nZXRUaW1lKCk7XG5cbiAgaWYgKCF0aW1lSW50ZXJ2YWwpIHtcbiAgICAvLyBpZiB0aW1lIGludGVydmFsIGlzIG5vdCBkZWZpbmVkXG4gICAgLy8gdGhpcyBzaG91bGQgbm90IGhhcHBlblxuICAgIHJldHVybiBbdDAsIHQwICsgZHVyYXRpb25EYXldO1xuICB9XG4gIGNvbnN0IGJpblRocmVzaG9sZHMgPSB0aW1lSW50ZXJ2YWwucmFuZ2UoZmxvb3IsIGNlaWxpbmcgKyAxKS5tYXAodCA9PiBtb21lbnQudXRjKHQpLnZhbHVlT2YoKSk7XG4gIGNvbnN0IGxhc3RTdGVwID0gYmluVGhyZXNob2xkc1tiaW5UaHJlc2hvbGRzLmxlbmd0aCAtIDFdO1xuICBpZiAobGFzdFN0ZXAgPT09IHQxKSB7XG4gICAgLy8gd2hlbiBsYXN0IHN0ZXAgZXF1YWwgdG8gZG9tYWluIG1heCwgYWRkIG9uZSBtb3JlIHN0ZXBcbiAgICBiaW5UaHJlc2hvbGRzLnB1c2gobW9tZW50LnV0Yyh0aW1lSW50ZXJ2YWwub2Zmc2V0KGxhc3RTdGVwKSkudmFsdWVPZigpKTtcbiAgfVxuXG4gIHJldHVybiBiaW5UaHJlc2hvbGRzO1xufVxuXG4vKipcbiAqIFJ1biBHUFUgZmlsdGVyIG9uIGN1cnJlbnQgZmlsdGVyIHJlc3VsdCB0byBnZW5lcmF0ZSBpbmRleGVzIGZvciBwbG90aW5nIGNoYXJ0XG4gKiBTa2lwIHJ1dW5pbmcgZm9yIHRoZSBzYW1lIGZpZWxkXG4gKiBAcGFyYW0gZGF0YXNldFxuICogQHBhcmFtIGZpbHRlclxuICovXG5leHBvcnQgZnVuY3Rpb24gcnVuR3B1RmlsdGVyRm9yUGxvdDxLIGV4dGVuZHMgS2VwbGVyVGFibGVNb2RlbDxLLCBMPiwgTD4oXG4gIGRhdGFzZXQ6IEssXG4gIGZpbHRlcj86IEZpbHRlclxuKTogbnVtYmVyW10ge1xuICBjb25zdCBza2lwSW5kZXhlcyA9IGdldFNraXBJbmRleGVzKGRhdGFzZXQsIGZpbHRlcik7XG5cbiAgY29uc3Qge1xuICAgIGdwdUZpbHRlcjoge2ZpbHRlclZhbHVlVXBkYXRlVHJpZ2dlcnMsIGZpbHRlclJhbmdlLCBmaWx0ZXJWYWx1ZUFjY2Vzc29yfSxcbiAgICBmaWx0ZXJlZEluZGV4XG4gIH0gPSBkYXRhc2V0O1xuICBjb25zdCBnZXRGaWx0ZXJWYWx1ZSA9IGZpbHRlclZhbHVlQWNjZXNzb3IoZGF0YXNldC5kYXRhQ29udGFpbmVyKSgpO1xuXG4gIGNvbnN0IGFsbENoYW5uZWxzID0gT2JqZWN0LmtleXMoZmlsdGVyVmFsdWVVcGRhdGVUcmlnZ2VycylcbiAgICAubWFwKChfLCBpKSA9PiBpKVxuICAgIC5maWx0ZXIoaSA9PiBPYmplY3QudmFsdWVzKGZpbHRlclZhbHVlVXBkYXRlVHJpZ2dlcnMpW2ldKTtcbiAgY29uc3Qgc2tpcEFsbCA9ICFhbGxDaGFubmVscy5maWx0ZXIoaSA9PiAhc2tpcEluZGV4ZXMuaW5jbHVkZXMoaSkpLmxlbmd0aDtcbiAgaWYgKHNraXBBbGwpIHtcbiAgICByZXR1cm4gZmlsdGVyZWRJbmRleDtcbiAgfVxuXG4gIGNvbnN0IGZpbHRlckRhdGEgPSBnZXRGaWx0ZXJEYXRhRnVuYyhcbiAgICBmaWx0ZXJSYW5nZSxcbiAgICBnZXRGaWx0ZXJWYWx1ZSxcbiAgICBkYXRhc2V0LmRhdGFDb250YWluZXIsXG4gICAgc2tpcEluZGV4ZXNcbiAgKTtcblxuICByZXR1cm4gZmlsdGVyZWRJbmRleC5maWx0ZXIoZmlsdGVyRGF0YSk7XG59XG5cbmZ1bmN0aW9uIGdldFNraXBJbmRleGVzKGRhdGFzZXQsIGZpbHRlcikge1xuICAvLyBhcnJheSBvZiBncHUgZmlsdGVyIG5hbWVzXG4gIGlmICghZmlsdGVyKSB7XG4gICAgcmV0dXJuIFtdO1xuICB9XG4gIGNvbnN0IGdwdUZpbHRlcnMgPSBPYmplY3QudmFsdWVzKGRhdGFzZXQuZ3B1RmlsdGVyLmZpbHRlclZhbHVlVXBkYXRlVHJpZ2dlcnMpIGFzICh7XG4gICAgbmFtZTogc3RyaW5nO1xuICB9IHwgbnVsbClbXTtcbiAgY29uc3QgdmFsdWVJbmRleCA9IGZpbHRlci5kYXRhSWQuZmluZEluZGV4KGlkID0+IGlkID09PSBkYXRhc2V0LmlkKTtcbiAgY29uc3QgZmlsdGVyQ29sdW1uID0gZmlsdGVyLm5hbWVbdmFsdWVJbmRleF07XG5cbiAgcmV0dXJuIGdwdUZpbHRlcnMucmVkdWNlKChhY2N1LCBpdGVtLCBpZHgpID0+IHtcbiAgICBpZiAoaXRlbSAmJiBmaWx0ZXJDb2x1bW4gPT09IGl0ZW0ubmFtZSkge1xuICAgICAgYWNjdS5wdXNoKGlkeCk7XG4gICAgfVxuICAgIHJldHVybiBhY2N1O1xuICB9LCBbXSBhcyBudW1iZXJbXSk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBnZXRGaWx0ZXJEYXRhRnVuYyhcbiAgZmlsdGVyUmFuZ2UsXG4gIGdldEZpbHRlclZhbHVlLFxuICBkYXRhQ29udGFpbmVyOiBEYXRhQ29udGFpbmVySW50ZXJmYWNlLFxuICBza2lwc1xuKSB7XG4gIHJldHVybiBpbmRleCA9PlxuICAgIGdldEZpbHRlclZhbHVlKHtpbmRleH0pLmV2ZXJ5KFxuICAgICAgKHZhbCwgaSkgPT4gc2tpcHMuaW5jbHVkZXMoaSkgfHwgKHZhbCA+PSBmaWx0ZXJSYW5nZVtpXVswXSAmJiB2YWwgPD0gZmlsdGVyUmFuZ2VbaV1bMV0pXG4gICAgKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHZhbGlkQmluKGIpIHtcbiAgcmV0dXJuIGIueDAgIT09IHVuZGVmaW5lZCAmJiBiLngxICE9PSB1bmRlZmluZWQ7XG59XG5cbi8qKlxuICogVXNlIGluIHNsaWRlciwgZ2l2ZW4gYSBudW1iZXIgYW5kIGFuIGFycmF5IG9mIG51bWJlcnMsIHJldHVybiB0aGUgbmVhcnMgbnVtYmVyIGZyb20gdGhlIGFycmF5LlxuICogVGFrZXMgYSB2YWx1ZSwgdGltZXN0ZXBzIGFuZCByZXR1cm4gdGhlIGFjdHVhbCBzdGVwLlxuICogQHBhcmFtIHZhbHVlXG4gKiBAcGFyYW0gbWFya3NcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNuYXBUb01hcmtzKHZhbHVlOiBudW1iZXIsIG1hcmtzOiBudW1iZXJbXSk6IG51bWJlciB7XG4gIC8vIGFsd2F5cyB1c2UgYmluIHgwXG4gIGlmICghbWFya3MubGVuZ3RoKSB7XG4gICAgLy8gQHRzLWV4cGVjdC1lcnJvciBsb29raW5nIGF0IHRoZSB1c2FnZSBudWxsIHJldHVybiB2YWx1ZSBpc24ndCBleHBlY3RlZCBhbmQgcmVxdWlyZXMgZXh0cmEgaGFuZGxpbmcgaW4gYSBsb3Qgb2YgcGxhY2VzXG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbiAgY29uc3QgaSA9IGJpc2VjdExlZnQobWFya3MsIHZhbHVlKTtcbiAgaWYgKGkgPT09IDApIHtcbiAgICByZXR1cm4gbWFya3NbaV07XG4gIH0gZWxzZSBpZiAoaSA9PT0gbWFya3MubGVuZ3RoKSB7XG4gICAgcmV0dXJuIG1hcmtzW2kgLSAxXTtcbiAgfVxuICBjb25zdCBpZHggPSBtYXJrc1tpXSAtIHZhbHVlIDwgdmFsdWUgLSBtYXJrc1tpIC0gMV0gPyBpIDogaSAtIDE7XG4gIHJldHVybiBtYXJrc1tpZHhdO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gbm9ybWFsaXplVmFsdWUodmFsLCBtaW5WYWx1ZSwgc3RlcCwgbWFya3MpIHtcbiAgaWYgKG1hcmtzICYmIG1hcmtzLmxlbmd0aCkge1xuICAgIHJldHVybiBzbmFwVG9NYXJrcyh2YWwsIG1hcmtzKTtcbiAgfVxuXG4gIHJldHVybiByb3VuZFZhbFRvU3RlcChtaW5WYWx1ZSwgc3RlcCwgdmFsKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGlzUGVyY2VudEZpZWxkKGZpZWxkKSB7XG4gIHJldHVybiBmaWVsZC5tZXRhZGF0YSAmJiBmaWVsZC5tZXRhZGF0YS5udW1lcmF0b3IgJiYgZmllbGQubWV0YWRhdGEuZGVub21pbmF0b3I7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiB1cGRhdGVBZ2dyZWdhdGlvbkJ5RmllbGQoZmllbGQ6IEZpZWxkLCBhZ2dyZWdhdGlvbjogVmFsdWVPZjxBZ2dyZWdhdGlvblR5cGVzPikge1xuICAvLyBzaG91bGRuJ3QgYXBwbHkgc3VtIHRvIHBlcmNlbnQgZmllbGUgdHlwZVxuICAvLyBkZWZhdWx0IGFnZ3JlZ2F0aW9uIGlzIGF2ZXJhZ2VcbiAgcmV0dXJuIGZpZWxkICYmIGlzUGVyY2VudEZpZWxkKGZpZWxkKVxuICAgID8gQUdHUkVHQVRJT05fVFlQRVMuYXZlcmFnZVxuICAgIDogYWdncmVnYXRpb24gfHwgQUdHUkVHQVRJT05fVFlQRVMuYXZlcmFnZTtcbn1cblxuY29uc3QgZ2V0QWdyZWdhdGlvblR5cGUgPSAoZmllbGQsIGFnZ3JlZ2F0aW9uKSA9PiB7XG4gIGlmIChpc1BlcmNlbnRGaWVsZChmaWVsZCkpIHtcbiAgICByZXR1cm4gJ21lYW5fb2ZfcGVyY2VudCc7XG4gIH1cbiAgcmV0dXJuIGFnZ3JlZ2F0aW9uO1xufTtcblxuY29uc3QgZ2V0QWdncmVnYXRpb25BY2Nlc3NvciA9IChmaWVsZCwgZGF0YUNvbnRhaW5lcjogRGF0YUNvbnRhaW5lckludGVyZmFjZSwgZmllbGRzKSA9PiB7XG4gIGlmIChpc1BlcmNlbnRGaWVsZChmaWVsZCkpIHtcbiAgICBjb25zdCBudW1lcmF0b3JJZHggPSBmaWVsZHMuZmluZEluZGV4KGYgPT4gZi5uYW1lID09PSBmaWVsZC5tZXRhZGF0YS5udW1lcmF0b3IpO1xuICAgIGNvbnN0IGRlbm9taW5hdG9ySWR4ID0gZmllbGRzLmZpbmRJbmRleChmID0+IGYubmFtZSA9PT0gZmllbGQubWV0YWRhdGEuZGVub21pbmF0b3IpO1xuXG4gICAgcmV0dXJuIHtcbiAgICAgIGdldE51bWVyYXRvcjogaSA9PiBkYXRhQ29udGFpbmVyLnZhbHVlQXQoaSwgbnVtZXJhdG9ySWR4KSxcbiAgICAgIGdldERlbm9taW5hdG9yOiBpID0+IGRhdGFDb250YWluZXIudmFsdWVBdChpLCBkZW5vbWluYXRvcklkeClcbiAgICB9O1xuICB9XG5cbiAgcmV0dXJuIGkgPT4gZmllbGQudmFsdWVBY2Nlc3Nvcih7aW5kZXg6IGl9KTtcbn07XG5cbmV4cG9ydCBjb25zdCBnZXRWYWx1ZUFnZ3JGdW5jID0gKFxuICBmaWVsZDogRmllbGQgfCBzdHJpbmcgfCBudWxsLFxuICBhZ2dyZWdhdGlvbjogc3RyaW5nLFxuICBkYXRhc2V0OiBLZXBsZXJUYWJsZU1vZGVsPGFueSwgYW55PlxuKTogKChiaW46IEJpbikgPT4gbnVtYmVyKSA9PiB7XG4gIGNvbnN0IHtkYXRhQ29udGFpbmVyLCBmaWVsZHN9ID0gZGF0YXNldDtcblxuICAvLyBUaGUgcGFzc2VkLWluIGZpZWxkIG1pZ2h0IG5vdCBoYXZlIGFsbCB0aGUgZmllbGRzIHNldCAoZS5nLiB2YWx1ZUFjY2Vzc29yKVxuICBjb25zdCBkYXRhc2V0RmllbGQgPSBmaWVsZHMuZmluZChcbiAgICBmID0+IGZpZWxkICYmIChmLm5hbWUgPT09IGZpZWxkIHx8IGYubmFtZSA9PT0gKGZpZWxkIGFzIEZpZWxkKS5uYW1lKVxuICApO1xuXG4gIHJldHVybiBkYXRhc2V0RmllbGQgJiYgYWdncmVnYXRpb25cbiAgICA/IGJpbiA9PlxuICAgICAgICBhZ2dyZWdhdGUoXG4gICAgICAgICAgYmluLmluZGV4ZXMsXG4gICAgICAgICAgZ2V0QWdyZWdhdGlvblR5cGUoZGF0YXNldEZpZWxkLCBhZ2dyZWdhdGlvbiksXG4gICAgICAgICAgLy8gQHRzLWV4cGVjdC1lcnJvciBjYW4gcmV0dXJuIHtnZXROdW1lcmF0b3IsIGdldERlbm9taW5hdG9yfVxuICAgICAgICAgIGdldEFnZ3JlZ2F0aW9uQWNjZXNzb3IoZGF0YXNldEZpZWxkLCBkYXRhQ29udGFpbmVyLCBmaWVsZHMpXG4gICAgICAgIClcbiAgICA6IGJpbiA9PiBiaW4uY291bnQ7XG59O1xuXG5leHBvcnQgY29uc3QgZ2V0QWdncmVnYXRpb25PcHRpb3NuQmFzZWRPbkZpZWxkID0gZmllbGQgPT4ge1xuICBpZiAoaXNQZXJjZW50RmllbGQoZmllbGQpKSB7XG4gICAgLy8gZG9uJ3Qgc2hvdyBzdW1cbiAgICByZXR1cm4gVElNRV9BR0dSRUdBVElPTi5maWx0ZXIoKHtpZH0pID0+IGlkICE9PSBBR0dSRUdBVElPTl9UWVBFUy5zdW0pO1xuICB9XG4gIHJldHVybiBUSU1FX0FHR1JFR0FUSU9OO1xufTtcblxuZnVuY3Rpb24gZ2V0RGVsdGEoXG4gIGJpbnM6IExpbmVEYXR1bVtdLFxuICB5OiBudW1iZXIsXG4gIF9pbnRlcnZhbDogUGxvdFR5cGVbJ2ludGVydmFsJ11cbik6IFBhcnRpYWw8TGluZURhdHVtPiAmIHtkZWx0YTogJ2xhc3QnOyBwY3Q6IG51bWJlciB8IG51bGx9IHtcbiAgLy8gaWYgKFdPV1tpbnRlcnZhbF0pIHJldHVybiBnZXRXb3coYmlucywgeSwgaW50ZXJ2YWwpO1xuICBjb25zdCBsYXN0QmluID0gYmluc1tiaW5zLmxlbmd0aCAtIDFdO1xuXG4gIHJldHVybiB7XG4gICAgZGVsdGE6ICdsYXN0JyxcbiAgICBwY3Q6IGxhc3RCaW4gPyBnZXRQY3RDaGFuZ2UoeSwgbGFzdEJpbi55KSA6IG51bGxcbiAgfTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGdldFBjdENoYW5nZSh5OiB1bmtub3duLCB5MDogdW5rbm93bik6IG51bWJlciB8IG51bGwge1xuICBpZiAoTnVtYmVyLmlzRmluaXRlKHkpICYmIE51bWJlci5pc0Zpbml0ZSh5MCkgJiYgeTAgIT09IDApIHtcbiAgICByZXR1cm4gKCh5IGFzIG51bWJlcikgLSAoeTAgYXMgbnVtYmVyKSkgLyAoeTAgYXMgbnVtYmVyKTtcbiAgfVxuICByZXR1cm4gbnVsbDtcbn1cblxuLyoqXG4gKlxuICogQHBhcmFtIGRhdGFzZXRzXG4gKiBAcGFyYW0gZmlsdGVyXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRMaW5lQ2hhcnQoZGF0YXNldHM6IERhdGFzZXRzLCBmaWx0ZXI6IEZpbHRlcik6IExpbmVDaGFydCB7XG4gIGNvbnN0IHtkYXRhSWQsIHlBeGlzLCBwbG90VHlwZSwgbGluZUNoYXJ0fSA9IGZpbHRlcjtcbiAgY29uc3Qge2FnZ3JlZ2F0aW9uLCBpbnRlcnZhbH0gPSBwbG90VHlwZTtcbiAgY29uc3Qgc2VyaWVzRGF0YUlkID0gZGF0YUlkWzBdO1xuICBjb25zdCBiaW5zID0gKGZpbHRlciBhcyBUaW1lUmFuZ2VGaWx0ZXIpLnRpbWVCaW5zPy5bc2VyaWVzRGF0YUlkXT8uW2ludGVydmFsXTtcblxuICBpZiAoXG4gICAgbGluZUNoYXJ0ICYmXG4gICAgbGluZUNoYXJ0LmFnZ3JlZ2F0aW9uID09PSBhZ2dyZWdhdGlvbiAmJlxuICAgIGxpbmVDaGFydC5pbnRlcnZhbCA9PT0gaW50ZXJ2YWwgJiZcbiAgICBsaW5lQ2hhcnQueUF4aXMgPT09IHlBeGlzPy5uYW1lICYmXG4gICAgLy8gd2UgbmVlZCB0byBtYWtlIHN1cmUgd2UgdmFsaWRhdGUgYmlucyBiZWNhdXNlIG9mIGNyb3NzIGZpbHRlciBkYXRhIGNoYW5nZXNcbiAgICBpc0VxdWFsKGJpbnMsIGxpbmVDaGFydD8uYmlucylcbiAgKSB7XG4gICAgLy8gZG9uJ3QgdXBkYXRlIGxpbmVDaGFydCBpZiBwbG90VHlwZSBoYXNuJ3QgY2hhbmdlXG4gICAgcmV0dXJuIGxpbmVDaGFydDtcbiAgfVxuXG4gIGNvbnN0IGRhdGFzZXQgPSBkYXRhc2V0c1tzZXJpZXNEYXRhSWRdO1xuICBjb25zdCBnZXRZVmFsdWUgPSBnZXRWYWx1ZUFnZ3JGdW5jKHlBeGlzLCBhZ2dyZWdhdGlvbiwgZGF0YXNldCk7XG5cbiAgY29uc3QgaW5pdDogTGluZURhdHVtW10gPSBbXTtcbiAgY29uc3Qgc2VyaWVzID0gKGJpbnMgfHwgW10pLnJlZHVjZSgoYWNjdSwgYmluKSA9PiB7XG4gICAgY29uc3QgeSA9IGdldFlWYWx1ZShiaW4pO1xuICAgIGNvbnN0IGRlbHRhID0gZ2V0RGVsdGEoYWNjdSwgeSwgaW50ZXJ2YWwpO1xuICAgIGFjY3UucHVzaCh7XG4gICAgICB4OiBiaW4ueDAsXG4gICAgICB5LFxuICAgICAgLi4uZGVsdGFcbiAgICB9KTtcbiAgICByZXR1cm4gYWNjdTtcbiAgfSwgaW5pdCk7XG5cbiAgY29uc3QgeURvbWFpbiA9IGV4dGVudDx7eTogYW55fT4oc2VyaWVzLCBkID0+IGQueSk7XG4gIGNvbnN0IHhEb21haW4gPSBiaW5zID8gW2JpbnNbMF0ueDAsIGJpbnNbYmlucy5sZW5ndGggLSAxXS54MV0gOiBbXTtcblxuICAvLyB0cmVhdCBtaXNzaW5nIGRhdGEgYXMgYW5vdGhlciBzZXJpZXNcbiAgY29uc3Qgc3BsaXQgPSBzcGxpdFNlcmllcyhzZXJpZXMpO1xuICBjb25zdCBhZ2dyTmFtZSA9IEFHR1JFR0FUSU9OX05BTUVbYWdncmVnYXRpb25dO1xuXG4gIHJldHVybiB7XG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIHlEb21haW4sXG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIHhEb21haW4sXG4gICAgaW50ZXJ2YWwsXG4gICAgYWdncmVnYXRpb24sXG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIHNlcmllczogc3BsaXQsXG4gICAgdGl0bGU6IGAke2FnZ3JOYW1lfSR7JyBvZiAnfSR7eUF4aXMgPyB5QXhpcy5uYW1lIDogJ0NvdW50J31gLFxuICAgIGZpZWxkVHlwZTogeUF4aXMgPyB5QXhpcy50eXBlIDogJ2ludGVnZXInLFxuICAgIHlBeGlzOiB5QXhpcyA/IHlBeGlzLm5hbWUgOiBudWxsLFxuICAgIGFsbFRpbWU6IHtcbiAgICAgIHRpdGxlOiBgQWxsIFRpbWUgQXZlcmFnZWAsXG4gICAgICB2YWx1ZTogYWdncmVnYXRlKHNlcmllcywgQUdHUkVHQVRJT05fVFlQRVMuYXZlcmFnZSwgZCA9PiBkLnkpXG4gICAgfSxcbiAgICAvLyBAdHMtZXhwZWN0LWVycm9yIGJpbnMgaXMgQmluc1tdLCBub3QgYSBCaW5zIG1hcC4gUmVmYWN0b3IgdG8gdXNlIGNvcnJlY3QgdHlwZXMuXG4gICAgYmluc1xuICB9O1xufVxuXG4vLyBzcGxpdCBpbnRvIG11bHRpcGxlIHNlcmllcyB3aGVuIHNlZSBtaXNzaW5nIGRhdGFcbmV4cG9ydCBmdW5jdGlvbiBzcGxpdFNlcmllcyhzZXJpZXMpIHtcbiAgY29uc3QgbGluZXM6IGFueVtdID0gW107XG4gIGxldCB0ZW1wOiBhbnlbXSA9IFtdO1xuICBmb3IgKGxldCBpID0gMDsgaSA8IHNlcmllcy5sZW5ndGg7IGkrKykge1xuICAgIGNvbnN0IGQgPSBzZXJpZXNbaV07XG4gICAgaWYgKCFub3ROdWxsb3JVbmRlZmluZWQoZC55KSAmJiB0ZW1wLmxlbmd0aCkge1xuICAgICAgLy8gZW5kcyB0ZW1wXG4gICAgICBsaW5lcy5wdXNoKHRlbXApO1xuICAgICAgdGVtcCA9IFtdO1xuICAgIH0gZWxzZSBpZiAobm90TnVsbG9yVW5kZWZpbmVkKGQueSkpIHtcbiAgICAgIHRlbXAucHVzaChkKTtcbiAgICB9XG5cbiAgICBpZiAoaSA9PT0gc2VyaWVzLmxlbmd0aCAtIDEgJiYgdGVtcC5sZW5ndGgpIHtcbiAgICAgIGxpbmVzLnB1c2godGVtcCk7XG4gICAgfVxuICB9XG5cbiAgY29uc3QgbWFya2VycyA9IGxpbmVzLmxlbmd0aCA+IDEgPyBzZXJpZXMuZmlsdGVyKGQgPT4gbm90TnVsbG9yVW5kZWZpbmVkKGQueSkpIDogW107XG5cbiAgcmV0dXJuIHtsaW5lcywgbWFya2Vyc307XG59XG5cbnR5cGUgTWluVmlzU3RhdGVGb3JBbmltYXRpb25XaW5kb3cgPSB7XG4gIGRhdGFzZXRzOiBEYXRhc2V0cztcbn07XG5cbmV4cG9ydCBmdW5jdGlvbiBhZGp1c3RWYWx1ZVRvQW5pbWF0aW9uV2luZG93PFMgZXh0ZW5kcyBNaW5WaXNTdGF0ZUZvckFuaW1hdGlvbldpbmRvdz4oXG4gIHN0YXRlOiBTLFxuICBmaWx0ZXI6IFRpbWVSYW5nZUZpbHRlclxuKSB7XG4gIGNvbnN0IHtcbiAgICBwbG90VHlwZSxcbiAgICB2YWx1ZTogW3ZhbHVlMCwgdmFsdWUxXSxcbiAgICBhbmltYXRpb25XaW5kb3dcbiAgfSA9IGZpbHRlcjtcblxuICBjb25zdCBpbnRlcnZhbCA9IHBsb3RUeXBlLmludGVydmFsIHx8IGdldEluaXRpYWxJbnRlcnZhbChmaWx0ZXIsIHN0YXRlLmRhdGFzZXRzKTtcbiAgY29uc3QgYmlucyA9IGdldFRpbWVCaW5zKGZpbHRlciwgc3RhdGUuZGF0YXNldHMsIGludGVydmFsKTtcbiAgY29uc3QgZGF0YXNldEJpbnMgPSBiaW5zICYmIE9iamVjdC5rZXlzKGJpbnMpLmxlbmd0aCAmJiBPYmplY3QudmFsdWVzKGJpbnMpWzBdW2ludGVydmFsXTtcbiAgY29uc3QgdGhyZXNob2xkcyA9IChkYXRhc2V0QmlucyB8fCBbXSkubWFwKGIgPT4gYi54MCk7XG5cbiAgbGV0IHZhbDAgPSB2YWx1ZTA7XG4gIGxldCB2YWwxID0gdmFsdWUxO1xuICBsZXQgaWR4O1xuICBpZiAoYW5pbWF0aW9uV2luZG93ID09PSBBTklNQVRJT05fV0lORE9XLmludGVydmFsKSB7XG4gICAgdmFsMCA9IHNuYXBUb01hcmtzKHZhbHVlMSwgdGhyZXNob2xkcyk7XG4gICAgaWR4ID0gdGhyZXNob2xkcy5pbmRleE9mKHZhbDApO1xuICAgIHZhbDEgPSBpZHggPiAtMSA/IGRhdGFzZXRCaW5zW2lkeF0ueDEgOiBOYU47XG4gIH0gZWxzZSB7XG4gICAgLy8gZml0IGN1cnJlbnQgdmFsdWUgdG8gd2luZG93XG4gICAgdmFsMCA9IHNuYXBUb01hcmtzKHZhbHVlMCwgdGhyZXNob2xkcyk7XG4gICAgdmFsMSA9IHNuYXBUb01hcmtzKHZhbHVlMSwgdGhyZXNob2xkcyk7XG5cbiAgICBpZiAodmFsMCA9PT0gdmFsMSkge1xuICAgICAgaWR4ID0gdGhyZXNob2xkcy5pbmRleE9mKHZhbDApO1xuICAgICAgaWYgKGlkeCA9PT0gdGhyZXNob2xkcy5sZW5ndGggLSAxKSB7XG4gICAgICAgIHZhbDAgPSB0aHJlc2hvbGRzW2lkeCAtIDFdO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdmFsMSA9IHRocmVzaG9sZHNbaWR4ICsgMV07XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgY29uc3QgdXBkYXRlZEZpbHRlciA9IHtcbiAgICAuLi5maWx0ZXIsXG4gICAgcGxvdFR5cGU6IHtcbiAgICAgIC4uLmZpbHRlci5wbG90VHlwZSxcbiAgICAgIGludGVydmFsXG4gICAgfSxcbiAgICB0aW1lQmluczogYmlucyxcbiAgICB2YWx1ZTogW3ZhbDAsIHZhbDFdXG4gIH07XG5cbiAgcmV0dXJuIHVwZGF0ZWRGaWx0ZXI7XG59XG5cbi8qKlxuICogQ3JlYXRlIG9yIHVwZGF0ZSBjb2xvcnMgZm9yIGEgZmlsdGVyIHBsb3RcbiAqIEBwYXJhbSBmaWx0ZXJcbiAqIEBwYXJhbSBkYXRhc2V0c1xuICogQHBhcmFtIG9sZENvbG9yc0J5RGF0YUlkXG4gKi9cbmZ1bmN0aW9uIGdldEZpbHRlclBsb3RDb2xvcnNCeURhdGFJZChmaWx0ZXIsIGRhdGFzZXRzLCBvbGRDb2xvcnNCeURhdGFJZCkge1xuICBsZXQgY29sb3JzQnlEYXRhSWQgPSBvbGRDb2xvcnNCeURhdGFJZCB8fCB7fTtcbiAgZm9yIChjb25zdCBkYXRhSWQgb2YgZmlsdGVyLmRhdGFJZCkge1xuICAgIGlmICghY29sb3JzQnlEYXRhSWRbZGF0YUlkXSAmJiBkYXRhc2V0c1tkYXRhSWRdKSB7XG4gICAgICBjb2xvcnNCeURhdGFJZCA9IHtcbiAgICAgICAgLi4uY29sb3JzQnlEYXRhSWQsXG4gICAgICAgIFtkYXRhSWRdOiByZ2JUb0hleChkYXRhc2V0c1tkYXRhSWRdLmNvbG9yKVxuICAgICAgfTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIGNvbG9yc0J5RGF0YUlkO1xufVxuXG4vKipcbiAqXG4gKiBAcGFyYW0gZmlsdGVyXG4gKiBAcGFyYW0gcGxvdFR5cGVcbiAqIEBwYXJhbSBkYXRhc2V0c1xuICogQHBhcmFtIGRhdGFJZFxuICovXG5leHBvcnQgZnVuY3Rpb24gdXBkYXRlVGltZUZpbHRlclBsb3RUeXBlKFxuICBmaWx0ZXI6IFRpbWVSYW5nZUZpbHRlcixcbiAgcGxvdFR5cGU6IFRpbWVSYW5nZUZpbHRlclsncGxvdFR5cGUnXSxcbiAgZGF0YXNldHM6IERhdGFzZXRzLFxuICBfZGF0YUlkPzogc3RyaW5nXG4pOiBUaW1lUmFuZ2VGaWx0ZXIge1xuICBsZXQgbmV4dEZpbHRlciA9IGZpbHRlcjtcbiAgbGV0IG5leHRQbG90VHlwZSA9IHBsb3RUeXBlO1xuICBpZiAodHlwZW9mIG5leHRQbG90VHlwZSAhPT0gJ29iamVjdCcgfHwgIW5leHRQbG90VHlwZS5hZ2dyZWdhdGlvbiB8fCAhbmV4dFBsb3RUeXBlLmludGVydmFsKSB7XG4gICAgbmV4dFBsb3RUeXBlID0gZ2V0RGVmYXVsdFBsb3RUeXBlKGZpbHRlciwgZGF0YXNldHMpO1xuICB9XG5cbiAgaWYgKGZpbHRlci5kYXRhSWQubGVuZ3RoID4gMSkge1xuICAgIG5leHRQbG90VHlwZSA9IHtcbiAgICAgIC4uLm5leHRQbG90VHlwZSxcbiAgICAgIGNvbG9yc0J5RGF0YUlkOiBnZXRGaWx0ZXJQbG90Q29sb3JzQnlEYXRhSWQoZmlsdGVyLCBkYXRhc2V0cywgbmV4dFBsb3RUeXBlLmNvbG9yc0J5RGF0YUlkKVxuICAgIH07XG4gIH1cbiAgbmV4dEZpbHRlciA9IHtcbiAgICAuLi5uZXh0RmlsdGVyLFxuICAgIHBsb3RUeXBlOiBuZXh0UGxvdFR5cGVcbiAgfTtcblxuICBjb25zdCBiaW5zID0gZ2V0VGltZUJpbnMobmV4dEZpbHRlciwgZGF0YXNldHMsIG5leHRQbG90VHlwZS5pbnRlcnZhbCk7XG5cbiAgbmV4dEZpbHRlciA9IHtcbiAgICAuLi5uZ