@nova-ui/charts
Version:
Nova Charts is a library created to provide potential consumers with solutions for various data visualizations that conform with the Nova Design Language. It's designed to solve common patterns identified by UX designers, but also be very flexible so that
392 lines • 67.4 kB
JavaScript
// © 2022 SolarWinds Worldwide, LLC. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import { Injectable } from "@angular/core";
import cloneDeep from "lodash/cloneDeep";
import sortBy from "lodash/sortBy";
import unionWith from "lodash/unionWith";
import values from "lodash/values";
import { LoggerService } from "@nova-ui/bits";
import { ZoneBoundary } from "./types";
import { isBandScale } from "../core/common/scales/types";
import { AreaAccessors, } from "../renderers/area/area-accessors";
import { statusAccessors } from "../renderers/bar/accessors/status-accessors-fn";
import { BarRenderer } from "../renderers/bar/bar-renderer";
import { THRESHOLDS_MAIN_CHART_RENDERER_CONFIG } from "../renderers/constants";
import { LineAccessors, } from "../renderers/line/line-accessors";
import { LineRenderer } from "../renderers/line/line-renderer";
import { SideIndicatorAccessors, SideIndicatorRenderer, } from "../renderers/side-indicator-renderer";
import * as i0 from "@angular/core";
import * as i1 from "@nova-ui/bits";
/**
* This service provides functionality that facilitates the creation of thresholds related visual element on charts.
*/
export class ThresholdsService {
loggerService;
static SERIES_ID_SUFFIX = "__thresholds-background";
constructor(loggerService) {
this.loggerService = loggerService;
}
/**
* Calculates the threshold "statuses" - from/to and what zone we're in. It is the first step that is required before the usage of other methods. Having
* the threshold "statuses" assigned to each data point enables us to access the threshold information not only in other methods for threshold
* calculation, but also in other places, like legend or tooltips.
*
* @param dataSeries source data series that will be enhanced with threshold metadata
* @param zones zones defined as data series with IAreaAccessors
*/
injectThresholdsData(dataSeries, zones) {
const zoneIndexes = {};
zones.forEach((z) => (z.entered = false));
for (let i = 0; i < dataSeries.data.length; i++) {
const d = dataSeries.data[i];
const x = dataSeries.accessors.data.x(d, i, dataSeries.data, dataSeries);
const y = dataSeries.accessors.data.y(d, i, dataSeries.data, dataSeries);
d.__thresholds = {};
const zone = this.getZoneByXY(zones, zoneIndexes, x, y);
if (zone) {
zone.entered = true;
d.__thresholds.status = zone.value;
}
}
}
/**
* This method creates a background series for TimeInterval or continuous (currently only works with time) scale.
* If the series is continuous it detects intersection of source line data with given threshold zones and generates start / end for status periods.
* Otherwise it will use the start and the end of the band.
*
* The default value of the rendererConfig parameter is {@link THRESHOLDS_MAIN_CHART_RENDERER_CONFIG}. For a summary chart, usually a smaller status
* chart positioned below a main chart, use {@link THRESHOLDS_SUMMARY_RENDERER_CONFIG}.
*
* @param sourceSeries The data series from which to derive a background visualization
* @param zones Zones defined as data series with IAreaAccessors
* @param scales The scales to be used for the background visualization
* @param colorProvider A value provider for the colors to be used for each status
* @param thicknessMap Map of status to number specifying a custom height per status for the background visualization.
* If a status is not specified in the map, the default thickness is the full height of the rendering area.
* @param rendererConfig The renderer's configuration
*/
getBackgrounds(sourceSeries, zones, scales, colorProvider, thicknessMap = {}, rendererConfig = cloneDeep(THRESHOLDS_MAIN_CHART_RENDERER_CONFIG)) {
if (sourceSeries.data.length > 0 &&
typeof sourceSeries.data[0].__thresholds === "undefined") {
this.loggerService.warn("Thresholds metadata is not defined on provided data series", sourceSeries);
}
let data;
const accessors = statusAccessors(colorProvider);
accessors.data.thickness = (d) => thicknessMap[d.status];
if (isBandScale(scales.x)) {
data = cloneDeep(sourceSeries.data);
accessors.data.start = sourceSeries.accessors.data.x;
accessors.data.end = sourceSeries.accessors.data.x;
accessors.data.color = (d) => d.__thresholds && d.__thresholds.status
? colorProvider.get(d.__thresholds.status)
: "transparent";
accessors.data.thickness = (d) => thicknessMap[d.__thresholds.status];
}
else {
data = this.getBackgroundsDataForContinuousScale(sourceSeries, zones);
}
return {
id: sourceSeries.id + ThresholdsService.SERIES_ID_SUFFIX,
data,
accessors,
scales,
renderer: new BarRenderer(rendererConfig),
showInLegend: false,
};
}
/**
* Calculates background data for continuous scale based on intersections between the source data and zone definitions.
*
* @param sourceSeries source data series from which to derive background data
* @param zones zones defined as data series with IAreaAccessors
*/
getBackgroundsDataForContinuousScale(sourceSeries, zones) {
const backgroundData = [];
const breakPoints = this.getBreakPoints(sourceSeries, zones);
const zoneIndexes = {};
for (let i = 0; i < breakPoints.length - 1; i++) {
const p1 = breakPoints[i];
const p2 = breakPoints[i + 1];
if (!p1 || !p2) {
throw new Error("p1 or p2 are undefined");
}
const midPoint = {
x: (p1.x + p2.x) / 2,
y: (p1.y + p2.y) / 2,
};
const zone = this.getZoneByXY(zones, zoneIndexes, midPoint.x, midPoint.y);
if (zone) {
const lastBgPoint = backgroundData[backgroundData.length - 1];
if (lastBgPoint &&
lastBgPoint.end?.valueOf() === p1.x &&
lastBgPoint.status === zone.value) {
// Extend previous data point instead of creating a new one
lastBgPoint.end = new Date(p2.x);
}
else {
backgroundData.push({
status: zone.value,
start: new Date(p1.x),
end: new Date(p2.x),
});
}
}
}
return backgroundData;
}
/**
* Calculate all "interesting" points along the data series that will be important to calculate the threshold status changes
*
* @param sourceSeries source data series from which to derive the break points
* @param zones zones defined as data series with IAreaAccessors
*/
getBreakPoints(sourceSeries, zones) {
const sourceAccessors = sourceSeries.accessors.data;
let breakPoints = [];
for (let i = 0; i < sourceSeries.data.length - 1; i++) {
// take two subsequent data points
const d1 = sourceSeries.data[i];
const d2 = sourceSeries.data[i + 1];
// calculate their positions
const xy1 = {
x: sourceAccessors
.x(d1, i, sourceSeries.data, sourceSeries)
.valueOf(),
y: sourceAccessors
.y(d1, i, sourceSeries.data, sourceSeries)
.valueOf(),
};
const xy2 = {
x: sourceAccessors
.x(d2, i + 1, sourceSeries.data, sourceSeries)
.valueOf(),
y: sourceAccessors
.y(d2, i + 1, sourceSeries.data, sourceSeries)
.valueOf(),
};
// try every zone to see if it's limits intersect the line between our two data points
for (let j = 0; j < zones.length; j++) {
const zone = zones[j];
// TODO: so far only works with constant thresholds
const zoneStartY = zone.accessors.data.start?.(zone.data[0], 0, zone.data, zone);
const zoneEndY = zone.accessors.data.end?.(zone.data[0], 0, zone.data, zone);
const zoneCrossPoints = [
zoneStartY,
zoneEndY,
]
.map((y) => this.getCrossPointWithY(xy1, xy2, y)) // calculate cross points for both limits
.filter((c) => c); // take only those that actually intersect
[xy1, xy2].forEach((xy) => {
if (xy.y === zoneStartY || xy.y === zoneEndY) {
xy.isZoneEdge = true;
}
});
// add all crossPoints, dataPoints, but only those with unique "x" coordinate
breakPoints = unionWith(breakPoints, zoneCrossPoints, [xy1, xy2], (a, b) => a?.x === b?.x && a?.isZoneEdge === b?.isZoneEdge);
}
}
return sortBy(breakPoints, "x");
}
/**
* Generates threshold zone series from simplified zone definition. Simplified definition provides only two numbers to define the zone interval.
* Creating these series manually is also possible for dynamic threshold limits.
*
* @param sourceSeries source data series from which to derive the zones
* @param simpleZones a collection of zones that are each defined by a start value and/or an end value. (A missing
start or end value indicates an infinite zone.)
* @param colorProvider A value provider for the colors to be used for each status
*/
getThresholdZones(sourceSeries, simpleZones, colorProvider) {
const areaAccessors = new AreaAccessors();
areaAccessors.data.start = (d) => d.start;
areaAccessors.data.end = (d) => d.end;
areaAccessors.series.color = (seriesId, series) => colorProvider.get(series.value);
return simpleZones.map((z, i) => ({
id: sourceSeries.id + "__" + z.status + "-" + i,
value: z.status,
data: [{ start: z.start, end: z.end }],
scales: sourceSeries.scales,
accessors: areaAccessors,
}));
}
/**
* Creates data series representing threshold lines on the chart. Solves collision between zones, where multiple zones are using the same limit value.
*
* @param zones Zones defined as data series with IAreaAccessors
*/
getThresholdLines(zones) {
if (-1 === zones.findIndex((z) => z.entered)) {
return [];
}
// TODO: works only for static thresholds
const limits = {};
function addLimitEntry(accessor, zone, zoneBoundary) {
const value = accessor(zone.data[0], 0, zone.data, zone);
if (typeof value !== "undefined" &&
typeof limits[value] === "undefined") {
limits[value] = { series: zone, accessor, zoneBoundary };
}
}
for (const z of zones) {
if (z.accessors.data.start && z.accessors.data.end) {
addLimitEntry(z.accessors.data.start, z, ZoneBoundary.Start);
addLimitEntry(z.accessors.data.end, z, ZoneBoundary.End);
}
}
return values(limits).map((limit) => this.getThresholdLine(limit.series, limit.accessor, limit.zoneBoundary));
}
/**
* Creates a IChartAssistSeries that represents a threshold zone limit defined by given valueAccessor
*
* @param zone A zone defined as a data series with IAreaAccessors
* @param valueAccessor Accessor for the threshold limit
* @param zoneBoundary The zone boundary represented by the line. Default is ZoneBoundary.Start.
*/
getThresholdLine(zone, valueAccessor, zoneBoundary = ZoneBoundary.Start) {
const accessors = new LineAccessors();
accessors.data.y = valueAccessor;
accessors.series.color = zone.accessors.series.color;
const renderer = new LineRenderer({
interactive: false,
strokeWidth: 1,
strokeStyle: LineRenderer.getStrokeStyleDashed(1),
stateStyles: cloneDeep(THRESHOLDS_MAIN_CHART_RENDERER_CONFIG.stateStyles),
ignoreForDomainCalculation: true,
});
return {
id: `${zone.id}__${zoneBoundary}__threshold-line`,
data: zone.data,
value: zone.value,
accessors,
renderer,
scales: zone.scales,
showInLegend: false,
};
}
/**
* Calculates the point where the data series line will cross a threshold line using the two closest points for each
*
* @param {IPosition} dataFrom Data starting point
* @param {IPosition} dataTo Data ending point
* @param {IPosition} thresholdFrom Threshold starting point
* @param {IPosition} thresholdTo Threshold ending point
* @returns {IPosition} the position of the cross point or `null` if lines don't cross
*/
getCrossPoint(dataFrom, dataTo, thresholdFrom, thresholdTo) {
/* Inspired by https://stackoverflow.com/a/1968345 */
const dataShift = {
x: dataTo.x - dataFrom.x,
y: dataTo.y - dataFrom.y,
};
const thresholdShift = {
x: thresholdTo.x - thresholdFrom.x,
y: thresholdTo.y - thresholdFrom.y,
};
const s = (-dataShift.y * (dataFrom.x - thresholdFrom.x) +
dataShift.x * (dataFrom.y - thresholdFrom.y)) /
(-thresholdShift.x * dataShift.y + dataShift.x * thresholdShift.y);
const t = (thresholdShift.x * (dataFrom.y - thresholdFrom.y) -
thresholdShift.y * (dataFrom.x - thresholdFrom.x)) /
(-thresholdShift.x * dataShift.y + dataShift.x * thresholdShift.y);
if (!(s >= 0 && s <= 1 && t >= 0 && t <= 1)) {
// No cross point detected
return undefined;
}
return {
x: dataFrom.x + t * dataShift.x,
y: dataFrom.y + t * dataShift.y,
};
}
/**
* Find the zone that is relevant for current input data point defined by x, y coordinates.
* Keep increasing the index while the next x value of threshold zone is still less than input x
*
* @param zones Zones defined as data series with IAreaAccessors
* @param zoneIndexes provided indexes keep the last used value for searching for the given x coordinate, so it is expected that provided data points
* are ordered by x in ascending order.
* @param x The x value of the zone
* @param y The y value of the zone
*/
getZoneByXY(zones, zoneIndexes, x, y) {
for (const zone of zones) {
if (!zoneIndexes[zone.id]) {
zoneIndexes[zone.id] = 0;
}
const zoneIndex = this.moveZoneIndex(zoneIndexes[zone.id], zone, x);
zoneIndexes[zone.id] = zoneIndex;
const zoneDataPoint = zone.data[zoneIndex];
const zDataAccessors = zone.accessors.data;
const start = zDataAccessors.start?.(zoneDataPoint, zoneIndex, zone.data, zone);
const end = zDataAccessors.end?.(zoneDataPoint, zoneIndex, zone.data, zone);
// TODO: this is not going to work for dynamic thresholds, so still have some work to do here
if ((typeof start === "undefined" || y >= start) &&
(typeof end === "undefined" || y <= end)) {
return zone;
}
}
return null;
}
moveZoneIndex(zoneIndex, zone, x) {
while (zoneIndex < zone.data.length - 1 &&
zone.accessors.data.x(zone.data[zoneIndex + 1], zoneIndex + 1, zone.data, zone) < x) {
zoneIndex++;
}
return zoneIndex;
}
getCrossPointWithY(dataFrom, dataTo, y) {
return this.getCrossPoint(dataFrom, dataTo, { x: dataFrom.x, y }, { x: dataTo.x, y });
}
/**
* Creates side indicator data series for given threshold zones
*
* @param zones Zones defined as data series with IAreaAccessors
* @param scales The scales to be used for the side indicators
* @param rendererConfig Configuration for the renderer. Default is the exported constant 'THRESHOLDS_MAIN_CHART_RENDERER_CONFIG'
*/
getSideIndicators(zones, scales, rendererConfig) {
const sideIndicators = [];
const renderer = new SideIndicatorRenderer(rendererConfig || cloneDeep(THRESHOLDS_MAIN_CHART_RENDERER_CONFIG));
const indicatorsActive = -1 !== zones.findIndex((z) => z.entered);
for (const z of zones) {
const sideIndicatorAccessors = new SideIndicatorAccessors();
sideIndicatorAccessors.series = {
start: () => z.accessors.data.start?.(z.data[0], 0, z.data, z),
end: () => z.accessors.data.end?.(z.data[0], 0, z.data, z),
activeColor: () => z.accessors.series.color?.(z.id, z),
};
const sideIndicator = {
id: z.id + "__side-indicator",
data: [{ active: indicatorsActive }],
accessors: sideIndicatorAccessors,
scales,
renderer,
showInLegend: false,
};
sideIndicators.push(sideIndicator);
}
return sideIndicators;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ThresholdsService, deps: [{ token: i1.LoggerService }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ThresholdsService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ThresholdsService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i1.LoggerService }] });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGhyZXNob2xkcy1zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3RocmVzaG9sZHMvdGhyZXNob2xkcy1zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLHlEQUF5RDtBQUN6RCxFQUFFO0FBQ0YsK0VBQStFO0FBQy9FLDRFQUE0RTtBQUM1RSw4RUFBOEU7QUFDOUUsK0VBQStFO0FBQy9FLDhFQUE4RTtBQUM5RSw0REFBNEQ7QUFDNUQsRUFBRTtBQUNGLDZFQUE2RTtBQUM3RSx1REFBdUQ7QUFDdkQsRUFBRTtBQUNGLDZFQUE2RTtBQUM3RSw0RUFBNEU7QUFDNUUsK0VBQStFO0FBQy9FLDBFQUEwRTtBQUMxRSxpRkFBaUY7QUFDakYsNkVBQTZFO0FBQzdFLGlCQUFpQjtBQUVqQixPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRTNDLE9BQU8sU0FBUyxNQUFNLGtCQUFrQixDQUFDO0FBQ3pDLE9BQU8sTUFBTSxNQUFNLGVBQWUsQ0FBQztBQUNuQyxPQUFPLFNBQVMsTUFBTSxrQkFBa0IsQ0FBQztBQUN6QyxPQUFPLE1BQU0sTUFBTSxlQUFlLENBQUM7QUFFbkMsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUU5QyxPQUFPLEVBQXdCLFlBQVksRUFBYSxNQUFNLFNBQVMsQ0FBQztBQUN4RSxPQUFPLEVBQUUsV0FBVyxFQUFVLE1BQU0sNkJBQTZCLENBQUM7QUFVbEUsT0FBTyxFQUNILGFBQWEsR0FFaEIsTUFBTSxrQ0FBa0MsQ0FBQztBQUUxQyxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sZ0RBQWdELENBQUM7QUFDakYsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBQzVELE9BQU8sRUFBRSxxQ0FBcUMsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQy9FLE9BQU8sRUFFSCxhQUFhLEdBQ2hCLE1BQU0sa0NBQWtDLENBQUM7QUFDMUMs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QUN0QixDQUFDO1lBRUYsY0FBYyxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsQ0FBQztTQUN0QztRQUVELE9BQU8sY0FBYyxDQUFDO0lBQzFCLENBQUM7d0dBaGhCUSxpQkFBaUI7NEdBQWpCLGlCQUFpQjs7NEZBQWpCLGlCQUFpQjtrQkFEN0IsVUFBVSIsInNvdXJjZXNDb250ZW50IjpbIi8vIMKpIDIwMjIgU29sYXJXaW5kcyBXb3JsZHdpZGUsIExMQy4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbi8vXG4vLyBQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5XG4vLyAgb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgXCJTb2Z0d2FyZVwiKSwgdG9cbi8vICBkZWFsIGluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZVxuLy8gIHJpZ2h0cyB0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vclxuLy8gIHNlbGwgY29waWVzIG9mIHRoZSBTb2Z0d2FyZSwgYW5kIHRvIHBlcm1pdCBwZXJzb25zIHRvIHdob20gdGhlIFNvZnR3YXJlIGlzXG4vLyAgZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczpcbi8vXG4vLyBUaGUgYWJvdmUgY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBzaGFsbCBiZSBpbmNsdWRlZCBpblxuLy8gIGFsbCBjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLlxuLy9cbi8vIFRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCBcIkFTIElTXCIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1Jcbi8vICBJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSxcbi8vICBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBTkQgTk9OSU5GUklOR0VNRU5ULiBJTiBOTyBFVkVOVCBTSEFMTCBUSEVcbi8vICBBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSXG4vLyAgTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSxcbi8vICBPVVQgT0YgT1IgSU4gQ09OTkVDVElPTiBXSVRIIFRIRSBTT0ZUV0FSRSBPUiBUSEUgVVNFIE9SIE9USEVSIERFQUxJTkdTIElOXG4vLyAgVEhFIFNPRlRXQVJFLlxuXG5pbXBvcnQgeyBJbmplY3RhYmxlIH0gZnJvbSBcIkBhbmd1bGFyL2NvcmVcIjtcbmltcG9ydCB7IE51bWVyaWMgfSBmcm9tIFwiZDMtYXJyYXlcIjtcbmltcG9ydCBjbG9uZURlZXAgZnJvbSBcImxvZGFzaC9jbG9uZURlZXBcIjtcbmltcG9ydCBzb3J0QnkgZnJvbSBcImxvZGFzaC9zb3J0QnlcIjtcbmltcG9ydCB1bmlvbldpdGggZnJvbSBcImxvZGFzaC91bmlvbldpdGhcIjtcbmltcG9ydCB2YWx1ZXMgZnJvbSBcImxvZGFzaC92YWx1ZXNcIjtcblxuaW1wb3J0IHsgTG9nZ2VyU2VydmljZSB9IGZyb20gXCJAbm92YS11aS9iaXRzXCI7XG5cbmltcG9ydCB7IElTaW1wbGVUaHJlc2hvbGRab25lLCBab25lQm91bmRhcnksIFpvbmVDcm9zcyB9IGZyb20gXCIuL3R5cGVzXCI7XG5pbXBvcnQgeyBpc0JhbmRTY2FsZSwgU2NhbGVzIH0gZnJvbSBcIi4uL2NvcmUvY29tbW9uL3NjYWxlcy90eXBlc1wiO1xuaW1wb3J0IHtcbiAgICBEYXRhQWNjZXNzb3IsXG4gICAgSUNoYXJ0QXNzaXN0U2VyaWVzLFxuICAgIElEYXRhU2VyaWVzLFxuICAgIElQb3NpdGlvbixcbiAgICBJUmVuZGVyZXJDb25maWcsXG4gICAgSVZhbHVlUHJvdmlkZXIsXG4gICAgSVpvbmVDcm9zc1BvaW50LFxufSBmcm9tIFwiLi4vY29yZS9jb21tb24vdHlwZXNcIjtcbmltcG9ydCB7XG4gICAgQXJlYUFjY2Vzc29ycyxcbiAgICBJQXJlYUFjY2Vzc29ycyxcbn0gZnJvbSBcIi4uL3JlbmRlcmVycy9hcmVhL2FyZWEtYWNjZXNzb3JzXCI7XG5pbXBvcnQgeyBJU3RhdHVzQWNjZXNzb3JzIH0gZnJvbSBcIi4uL3JlbmRlcmVycy9iYXIvYWNjZXNzb3JzL3N0YXR1cy1hY2Nlc3NvcnNcIjtcbmltcG9ydCB7IHN0YXR1c0FjY2Vzc29ycyB9IGZyb20gXCIuLi9yZW5kZXJlcnMvYmFyL2FjY2Vzc29ycy9zdGF0dXMtYWNjZXNzb3JzLWZuXCI7XG5pbXBvcnQgeyBCYXJSZW5kZXJlciB9IGZyb20gXCIuLi9yZW5kZXJlcnMvYmFyL2Jhci1yZW5kZXJlclwiO1xuaW1wb3J0IHsgVEhSRVNIT0xEU19NQUlOX0NIQVJUX1JFTkRFUkVSX0NPTkZJRyB9IGZyb20gXCIuLi9yZW5kZXJlcnMvY29uc3RhbnRzXCI7XG5pbXBvcnQge1xuICAgIElMaW5lQWNjZXNzb3JzLFxuICAgIExpbmVBY2Nlc3NvcnMsXG59IGZyb20gXCIuLi9yZW5kZXJlcnMvbGluZS9saW5lLWFjY2Vzc29yc1wiO1xuaW1wb3J0IHsgTGluZVJlbmRlcmVyIH0gZnJvbSBcIi4uL3JlbmRlcmVycy9saW5lL2xpbmUtcmVuZGVyZXJcIjtcbmltcG9ydCB7XG4gICAgSVNpZGVJbmRpY2F0b3JBY2Nlc3NvcnMsXG4gICAgU2lkZUluZGljYXRvckFjY2Vzc29ycyxcbiAgICBTaWRlSW5kaWNhdG9yUmVuZGVyZXIsXG59IGZyb20gXCIuLi9yZW5kZXJlcnMvc2lkZS1pbmRpY2F0b3ItcmVuZGVyZXJcIjtcblxuLyoqXG4gKiBUaGlzIHNlcnZpY2UgcHJvdmlkZXMgZnVuY3Rpb25hbGl0eSB0aGF0IGZhY2lsaXRhdGVzIHRoZSBjcmVhdGlvbiBvZiB0aHJlc2hvbGRzIHJlbGF0ZWQgdmlzdWFsIGVsZW1lbnQgb24gY2hhcnRzLlxuICovXG5ASW5qZWN0YWJsZSgpXG5leHBvcnQgY2xhc3MgVGhyZXNob2xkc1NlcnZpY2Uge1xuICAgIHB1YmxpYyBzdGF0aWMgU0VSSUVTX0lEX1NVRkZJWCA9IFwiX190aHJlc2hvbGRzLWJhY2tncm91bmRcIjtcblxuICAgIGNvbnN0cnVjdG9yKHByaXZhdGUgbG9nZ2VyU2VydmljZTogTG9nZ2VyU2VydmljZSkge31cblxuICAgIC8qKlxuICAgICAqIENhbGN1bGF0ZXMgdGhlIHRocmVzaG9sZCBcInN0YXR1c2VzXCIgLSBmcm9tL3RvIGFuZCB3aGF0IHpvbmUgd2UncmUgaW4uIEl0IGlzIHRoZSBmaXJzdCBzdGVwIHRoYXQgaXMgcmVxdWlyZWQgYmVmb3JlIHRoZSB1c2FnZSBvZiBvdGhlciBtZXRob2RzLiBIYXZpbmdcbiAgICAgKiB0aGUgdGhyZXNob2xkIFwic3RhdHVzZXNcIiBhc3NpZ25lZCB0byBlYWNoIGRhdGEgcG9pbnQgZW5hYmxlcyB1cyB0byBhY2Nlc3MgdGhlIHRocmVzaG9sZCBpbmZvcm1hdGlvbiBub3Qgb25seSBpbiBvdGhlciBtZXRob2RzIGZvciB0aHJlc2hvbGRcbiAgICAgKiBjYWxjdWxhdGlvbiwgYnV0IGFsc28gaW4gb3RoZXIgcGxhY2VzLCBsaWtlIGxlZ2VuZCBvciB0b29sdGlwcy5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBkYXRhU2VyaWVzIHNvdXJjZSBkYXRhIHNlcmllcyB0aGF0IHdpbGwgYmUgZW5oYW5jZWQgd2l0aCB0aHJlc2hvbGQgbWV0YWRhdGFcbiAgICAgKiBAcGFyYW0gem9uZXMgem9uZXMgZGVmaW5lZCBhcyBkYXRhIHNlcmllcyB3aXRoIElBcmVhQWNjZXNzb3JzXG4gICAgICovXG4gICAgcHVibGljIGluamVjdFRocmVzaG9sZHNEYXRhKFxuICAgICAgICBkYXRhU2VyaWVzOiBJRGF0YVNlcmllczxJTGluZUFjY2Vzc29ycz4sXG4gICAgICAgIHpvbmVzOiBJRGF0YVNlcmllczxJQXJlYUFjY2Vzc29ycz5bXVxuICAgICk6IHZvaWQge1xuICAgICAgICBjb25zdCB6b25lSW5kZXhlczogUmVjb3JkPHN0cmluZywgbnVtYmVyPiA9IHt9O1xuICAgICAgICB6b25lcy5mb3JFYWNoKCh6KSA9PiAoei5lbnRlcmVkID0gZmFsc2UpKTtcblxuICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IGRhdGFTZXJpZXMuZGF0YS5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgY29uc3QgZCA9IGRhdGFTZXJpZXMuZGF0YVtpXTtcbiAgICAgICAgICAgIGNvbnN0IHg6IE51bWVyaWMgPSBkYXRhU2VyaWVzLmFjY2Vzc29ycy5kYXRhLngoXG4gICAgICAgICAgICAgICAgZCxcbiAgICAgICAgICAgICAgICBpLFxuICAgICAgICAgICAgICAgIGRhdGFTZXJpZXMuZGF0YSxcbiAgICAgICAgICAgICAgICBkYXRhU2VyaWVzXG4gICAgICAgICAgICApO1xuICAgICAgICAgICAgY29uc3QgeTogTnVtZXJpYyA9IGRhdGFTZXJpZXMuYWNjZXNzb3JzLmRhdGEueShcbiAgICAgICAgICAgICAgICBkLFxuICAgICAgICAgICAgICAgIGksXG4gICAgICAgICAgICAgICAgZGF0YVNlcmllcy5kYXRhLFxuICAgICAgICAgICAgICAgIGRhdGFTZXJpZXNcbiAgICAgICAgICAgICk7XG5cbiAgICAgICAgICAgIGQuX190aHJlc2hvbGRzID0ge307XG5cbiAgICAgICAgICAgIGNvbnN0IHpvbmUgPSB0aGlzLmdldFpvbmVCeVhZKHpvbmVzLCB6b25lSW5kZXhlcywgeCwgeSk7XG4gICAgICAgICAgICBpZiAoem9uZSkge1xuICAgICAgICAgICAgICAgIHpvbmUuZW50ZXJlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgZC5fX3RocmVzaG9sZHMuc3RhdHVzID0gem9uZS52YWx1ZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFRoaXMgbWV0aG9kIGNyZWF0ZXMgYSBiYWNrZ3JvdW5kIHNlcmllcyBmb3IgVGltZUludGVydmFsIG9yIGNvbnRpbnVvdXMgKGN1cnJlbnRseSBvbmx5IHdvcmtzIHdpdGggdGltZSkgc2NhbGUuXG4gICAgICogSWYgdGhlIHNlcmllcyBpcyBjb250aW51b3VzIGl0IGRldGVjdHMgaW50ZXJzZWN0aW9uIG9mIHNvdXJjZSBsaW5lIGRhdGEgd2l0aCBnaXZlbiB0aHJlc2hvbGQgem9uZXMgYW5kIGdlbmVyYXRlcyBzdGFydCAvIGVuZCBmb3Igc3RhdHVzIHBlcmlvZHMuXG4gICAgICogT3RoZXJ3aXNlIGl0IHdpbGwgdXNlIHRoZSBzdGFydCBhbmQgdGhlIGVuZCBvZiB0aGUgYmFuZC5cbiAgICAgKlxuICAgICAqIFRoZSBkZWZhdWx0IHZhbHVlIG9mIHRoZSByZW5kZXJlckNvbmZpZyBwYXJhbWV0ZXIgaXMge0BsaW5rIFRIUkVTSE9MRFNfTUFJTl9DSEFSVF9SRU5ERVJFUl9DT05GSUd9LiBGb3IgYSBzdW1tYXJ5IGNoYXJ0LCB1c3VhbGx5IGEgc21hbGxlciBzdGF0dXNcbiAgICAgKiBjaGFydCBwb3NpdGlvbmVkIGJlbG93IGEgbWFpbiBjaGFydCwgdXNlIHtAbGluayBUSFJFU0hPTERTX1NVTU1BUllfUkVOREVSRVJfQ09ORklHfS5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBzb3VyY2VTZXJpZXMgVGhlIGRhdGEgc2VyaWVzIGZyb20gd2hpY2ggdG8gZGVyaXZlIGEgYmFja2dyb3VuZCB2aXN1YWxpemF0aW9uXG4gICAgICogQHBhcmFtIHpvbmVzIFpvbmVzIGRlZmluZWQgYXMgZGF0YSBzZXJpZXMgd2l0aCBJQXJlYUFjY2Vzc29yc1xuICAgICAqIEBwYXJhbSBzY2FsZXMgVGhlIHNjYWxlcyB0byBiZSB1c2VkIGZvciB0aGUgYmFja2dyb3VuZCB2aXN1YWxpemF0aW9uXG4gICAgICogQHBhcmFtIGNvbG9yUHJvdmlkZXIgQSB2YWx1ZSBwcm92aWRlciBmb3IgdGhlIGNvbG9ycyB0byBiZSB1c2VkIGZvciBlYWNoIHN0YXR1c1xuICAgICAqIEBwYXJhbSB0aGlja25lc3NNYXAgTWFwIG9mIHN0YXR1cyB0byBudW1iZXIgc3BlY2lmeWluZyBhIGN1c3RvbSBoZWlnaHQgcGVyIHN0YXR1cyBmb3IgdGhlIGJhY2tncm91bmQgdmlzdWFsaXphdGlvbi5cbiAgICAgKiAgICAgICAgICAgICAgICAgICAgIElmIGEgc3RhdHVzIGlzIG5vdCBzcGVjaWZpZWQgaW4gdGhlIG1hcCwgdGhlIGRlZmF1bHQgdGhpY2tuZXNzIGlzIHRoZSBmdWxsIGhlaWdodCBvZiB0aGUgcmVuZGVyaW5nIGFyZWEuXG4gICAgICogQHBhcmFtIHJlbmRlcmVyQ29uZmlnIFRoZSByZW5kZXJlcidzIGNvbmZpZ3VyYXRpb25cbiAgICAgKi9cbiAgICBwdWJsaWMgZ2V0QmFja2dyb3VuZHMoXG4gICAgICAgIHNvdXJjZVNlcmllczogSURhdGFTZXJpZXM8SUxpbmVBY2Nlc3NvcnM+LFxuICAgICAgICB6b25lczogSURhdGFTZXJpZXM8SUFyZWFBY2Nlc3NvcnM+W10sXG4gICAgICAgIHNjYWxlczogU2NhbGVzLFxuICAgICAgICBjb2xvclByb3ZpZGVyOiBJVmFsdWVQcm92aWRlcjxzdHJpbmc+LFxuICAgICAgICB0aGlja25lc3NNYXA6IFJlY29yZDxzdHJpbmcsIG51bWJlcj4gPSB7fSxcbiAgICAgICAgcmVuZGVyZXJDb25maWc6IElSZW5kZXJlckNvbmZpZyA9IGNsb25lRGVlcChcbiAgICAgICAgICAgIFRIUkVTSE9MRFNfTUFJTl9DSEFSVF9SRU5ERVJFUl9DT05GSUdcbiAgICAgICAgKVxuICAgICk6IElDaGFydEFzc2lzdFNlcmllczxJU3RhdHVzQWNjZXNzb3JzPiB7XG4gICAgICAgIGlmIChcbiAgICAgICAgICAgIHNvdXJjZVNlcmllcy5kYXRhLmxlbmd0aCA+IDAgJiZcbiAgICAgICAgICAgIHR5cGVvZiBzb3VyY2VTZXJpZXMuZGF0YVswXS5fX3RocmVzaG9sZHMgPT09IFwidW5kZWZpbmVkXCJcbiAgICAgICAgKSB7XG4gICAgICAgICAgICB0aGlzLmxvZ2dlclNlcnZpY2Uud2FybihcbiAgICAgICAgICAgICAgICBcIlRocmVzaG9sZHMgbWV0YWRhdGEgaXMgbm90IGRlZmluZWQgb24gcHJvdmlkZWQgZGF0YSBzZXJpZXNcIixcbiAgICAgICAgICAgICAgICBzb3VyY2VTZXJpZXNcbiAgICAgICAgICAgICk7XG4gICAgICAgIH1cblxuICAgICAgICBsZXQgZGF0YTogYW55W107XG4gICAgICAgIGNvbnN0IGFjY2Vzc29ycyA9IHN0YXR1c0FjY2Vzc29ycyhjb2xvclByb3ZpZGVyKTtcbiAgICAgICAgYWNjZXNzb3JzLmRhdGEudGhpY2tuZXNzID0gKGQpID0+IHRoaWNrbmVzc01hcFtkLnN0YXR1c107XG4gICAgICAgIGlmIChpc0JhbmRTY2FsZShzY2FsZXMueCkpIHtcbiAgICAgICAgICAgIGRhdGEgPSBjbG9uZURlZXAoc291cmNlU2VyaWVzLmRhdGEpO1xuICAgICAgICAgICAgYWNjZXNzb3JzLmRhdGEuc3RhcnQgPSBzb3VyY2VTZXJpZXMuYWNjZXNzb3JzLmRhdGEueDtcbiAgICAgICAgICAgIGFjY2Vzc29ycy5kYXRhLmVuZCA9IHNvdXJjZVNlcmllcy5hY2Nlc3NvcnMuZGF0YS54O1xuICAgICAgICAgICAgYWNjZXNzb3JzLmRhdGEuY29sb3IgPSAoZCkgPT5cbiAgICAgICAgICAgICAgICBkLl9fdGhyZXNob2xkcyAmJiBkLl9fdGhyZXNob2xkcy5zdGF0dXNcbiAgICAgICAgICAgICAgICAgICAgPyBjb2xvclByb3ZpZGVyLmdldChkLl9fdGhyZXNob2xkcy5zdGF0dXMpXG4gICAgICAgICAgICAgICAgICAgIDogXCJ0cmFuc3BhcmVudFwiO1xuICAgICAgICAgICAgYWNjZXNzb3JzLmRhdGEudGhpY2tuZXNzID0gKGQpID0+XG4gICAgICAgICAgICAgICAgdGhpY2tuZXNzTWFwW2QuX190aHJlc2hvbGRzLnN0YXR1c107XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBkYXRhID0gdGhpcy5nZXRCYWNrZ3JvdW5kc0RhdGFGb3JDb250aW51b3VzU2NhbGUoXG4gICAgICAgICAgICAgICAgc291cmNlU2VyaWVzLFxuICAgICAgICAgICAgICAgIHpvbmVzXG4gICAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICBpZDogc291cmNlU2VyaWVzLmlkICsgVGhyZXNob2xkc1NlcnZpY2UuU0VSSUVTX0lEX1NVRkZJWCxcbiAgICAgICAgICAgIGRhdGEsXG4gICAgICAgICAgICBhY2Nlc3NvcnMsXG4gICAgICAgICAgICBzY2FsZXMsXG4gICAgICAgICAgICByZW5kZXJlcjogbmV3IEJhclJlbmRlcmVyKHJlbmRlcmVyQ29uZmlnKSxcbiAgICAgICAgICAgIHNob3dJbkxlZ2VuZDogZmFsc2UsXG4gICAgICAgIH07XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ2FsY3VsYXRlcyBiYWNrZ3JvdW5kIGRhdGEgZm9yIGNvbnRpbnVvdXMgc2NhbGUgYmFzZWQgb24gaW50ZXJzZWN0aW9ucyBiZXR3ZWVuIHRoZSBzb3VyY2UgZGF0YSBhbmQgem9uZSBkZWZpbml0aW9ucy5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBzb3VyY2VTZXJpZXMgc291cmNlIGRhdGEgc2VyaWVzIGZyb20gd2hpY2ggdG8gZGVyaXZlIGJhY2tncm91bmQgZGF0YVxuICAgICAqIEBwYXJhbSB6b25lcyB6b25lcyBkZWZpbmVkIGFzIGRhdGEgc2VyaWVzIHdpdGggSUFyZWFBY2Nlc3NvcnNcbiAgICAgKi9cbiAgICBwcml2YXRlIGdldEJhY2tncm91bmRzRGF0YUZvckNvbnRpbnVvdXNTY2FsZShcbiAgICAgICAgc291cmNlU2VyaWVzOiBJRGF0YVNlcmllczxJTGluZUFjY2Vzc29ycz4sXG4gICAgICAgIHpvbmVzOiBJRGF0YVNlcmllczxJQXJlYUFjY2Vzc29ycz5bXVxuICAgICkge1xuICAgICAgICBjb25zdCBiYWNrZ3JvdW5kRGF0YTogWm9uZUNyb3NzW10gPSBbXTtcbiAgICAgICAgY29uc3QgYnJlYWtQb2ludHMgPSB0aGlzLmdldEJyZWFrUG9pbnRzKHNvdXJjZVNlcmllcywgem9uZXMpO1xuXG4gICAgICAgIGNvbnN0IHpvbmVJbmRleGVzOiBSZWNvcmQ8c3RyaW5nLCBudW1iZXI+ID0ge307XG4gICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgYnJlYWtQb2ludHMubGVuZ3RoIC0gMTsgaSsrKSB7XG4gICAgICAgICAgICBjb25zdCBwMSA9IGJyZWFrUG9pbnRzW2ldO1xuICAgICAgICAgICAgY29uc3QgcDIgPSBicmVha1BvaW50c1tpICsgMV07XG5cbiAgICAgICAgICAgIGlmICghcDEgfHwgIXAyKSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwicDEgb3IgcDIgYXJlIHVuZGVmaW5lZFwiKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgY29uc3QgbWlkUG9pbnQ6IElQb3NpdGlvbiA9IHtcbiAgICAgICAgICAgICAgICB4OiAocDEueCArIHAyLngpIC8gMixcbiAgICAgICAgICAgICAgICB5OiAocDEueSArIHAyLnkpIC8gMixcbiAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgIGNvbnN0IHpvbmUgPSB0aGlzLmdldFpvbmVCeVhZKFxuICAgICAgICAgICAgICAgIHpvbmVzLFxuICAgICAgICAgICAgICAgIHpvbmVJbmRleGVzLFxuICAgICAgICAgICAgICAgIG1pZFBvaW50LngsXG4gICAgICAgICAgICAgICAgbWlkUG9pbnQueVxuICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIGlmICh6b25lKSB7XG4gICAgICAgICAgICAgICAgY29uc3QgbGFzdEJnUG9pbnQgPSBiYWNrZ3JvdW5kRGF0YVtiYWNrZ3JvdW5kRGF0YS5sZW5ndGggLSAxXTtcbiAgICAgICAgICAgICAgICBpZiAoXG4gICAgICAgICAgICAgICAgICAgIGxhc3RCZ1BvaW50ICYmXG4gICAgICAgICAgICAgICAgICAgIGxhc3RCZ1BvaW50LmVuZD8udmFsdWVPZigpID09PSBwMS54ICYmXG4gICAgICAgICAgICAgICAgICAgIGxhc3RCZ1BvaW50LnN0YXR1cyA9PT0gem9uZS52YWx1ZVxuICAgICAgICAgICAgICAgICkge1xuICAgICAgICAgICAgICAgICAgICAvLyBFeHRlbmQgcHJldmlvdXMgZGF0YSBwb2ludCBpbnN0ZWFkIG9mIGNyZWF0aW5nIGEgbmV3IG9uZVxuICAgICAgICAgICAgICAgICAgICBsYXN0QmdQb2ludC5lbmQgPSBuZXcgRGF0ZShwMi54KTtcbiAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kRGF0YS5wdXNoKHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHN0YXR1czogem9uZS52YWx1ZSxcbiAgICAgICAgICAgICAgICAgICAgICAgIHN0YXJ0OiBuZXcgRGF0ZShwMS54KSxcbiAgICAgICAgICAgICAgICAgICAgICAgIGVuZDogbmV3IERhdGUocDIueCksXG4gICAgICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gYmFja2dyb3VuZERhdGE7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ2FsY3VsYXRlIGFsbCBcImludGVyZXN0aW5nXCIgcG9pbnRzIGFsb25nIHRoZSBkYXRhIHNlcmllcyB0aGF0IHdpbGwgYmUgaW1wb3J0YW50IHRvIGNhbGN1bGF0ZSB0aGUgdGhyZXNob2xkIHN0YXR1cyBjaGFuZ2VzXG4gICAgICpcbiAgICAgKiBAcGFyYW0gc291cmNlU2VyaWVzIHNvdXJjZSBkYXRhIHNlcmllcyBmcm9tIHdoaWNoIHRvIGRlcml2ZSB0aGUgYnJlYWsgcG9pbnRzXG4gICAgICogQHBhcmFtIHpvbmVzIHpvbmVzIGRlZmluZWQgYXMgZGF0YSBzZXJpZXMgd2l0aCBJQXJlYUFjY2Vzc29yc1xuICAgICAqL1xuICAgIHByaXZhdGUgZ2V0QnJlYWtQb2ludHMoXG4gICAgICAgIHNvdXJjZVNlcmllczogSURhdGFTZXJpZXM8SUxpbmVBY2Nlc3NvcnM+LFxuICAgICAgICB6b25lczogSURhdGFTZXJpZXM8SUFyZWFBY2Nlc3NvcnM+W11cbiAgICApOiAoSVpvbmVDcm9zc1BvaW50IHwgdW5kZWZpbmVkKVtdIHtcbiAgICAgICAgY29uc3Qgc291cmNlQWNjZXNzb3JzID0gc291cmNlU2VyaWVzLmFjY2Vzc29ycy5kYXRhO1xuXG4gICAgICAgIGxldCBicmVha1BvaW50czogKElab25lQ3Jvc3NQb2ludCB8IHVuZGVmaW5lZClbXSA9IFtdO1xuXG4gICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgc291cmNlU2VyaWVzLmRhdGEubGVuZ3RoIC0gMTsgaSsrKSB7XG4gICAgICAgICAgICAvLyB0YWtlIHR3byBzdW