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
JavaScript
"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