js-angusj-clipper
Version:
Polygon and line clipping and offsetting library for Javascript / Typescript - a port of Angus Johnson's clipper to WebAssembly / Asm.JS
494 lines • 65.1 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadNativeClipperLibInstanceAsync = exports.ClipperLibWrapper = exports.ClipperError = exports.PolyTree = exports.PolyNode = exports.PointInPolygonResult = exports.NativeClipperLibRequestedFormat = exports.NativeClipperLibLoadedFormat = exports.PolyFillType = exports.JoinType = exports.EndType = exports.ClipType = void 0;
var clipFunctions_1 = require("./clipFunctions");
var ClipperError_1 = require("./ClipperError");
Object.defineProperty(exports, "ClipperError", { enumerable: true, get: function () { return ClipperError_1.ClipperError; } });
var constants_1 = require("./constants");
var enums_1 = require("./enums");
Object.defineProperty(exports, "ClipType", { enumerable: true, get: function () { return enums_1.ClipType; } });
Object.defineProperty(exports, "EndType", { enumerable: true, get: function () { return enums_1.EndType; } });
Object.defineProperty(exports, "JoinType", { enumerable: true, get: function () { return enums_1.JoinType; } });
Object.defineProperty(exports, "NativeClipperLibLoadedFormat", { enumerable: true, get: function () { return enums_1.NativeClipperLibLoadedFormat; } });
Object.defineProperty(exports, "NativeClipperLibRequestedFormat", { enumerable: true, get: function () { return enums_1.NativeClipperLibRequestedFormat; } });
Object.defineProperty(exports, "PointInPolygonResult", { enumerable: true, get: function () { return enums_1.PointInPolygonResult; } });
Object.defineProperty(exports, "PolyFillType", { enumerable: true, get: function () { return enums_1.PolyFillType; } });
var functions = require("./functions");
var offsetFunctions_1 = require("./offsetFunctions");
var PolyNode_1 = require("./PolyNode");
Object.defineProperty(exports, "PolyNode", { enumerable: true, get: function () { return PolyNode_1.PolyNode; } });
var PolyTree_1 = require("./PolyTree");
Object.defineProperty(exports, "PolyTree", { enumerable: true, get: function () { return PolyTree_1.PolyTree; } });
var wasmModule;
var asmJsModule;
/**
* A wrapper for the Native Clipper Library instance with all the operations available.
*/
var ClipperLibWrapper = /** @class */ (function () {
/**
* Internal constructor. Use loadNativeClipperLibInstanceAsync instead.
*
* @param instance
* @param format
*/
function ClipperLibWrapper(instance, format) {
this.format = format;
this.instance = instance;
}
/**
* Performs a polygon clipping (boolean) operation, returning the resulting Paths or throwing an error if failed.
*
* The solution parameter in this case is a Paths or PolyTree structure. The Paths structure is simpler than the PolyTree structure. Because of this it is
* quicker to populate and hence clipping performance is a little better (it's roughly 10% faster). However, the PolyTree data structure provides more
* information about the returned paths which may be important to users. Firstly, the PolyTree structure preserves nested parent-child polygon relationships
* (ie outer polygons owning/containing holes and holes owning/containing other outer polygons etc). Also, only the PolyTree structure can differentiate
* between open and closed paths since each PolyNode has an IsOpen property. (The Path structure has no member indicating whether it's open or closed.)
* For this reason, when open paths are passed to a Clipper object, the user must use a PolyTree object as the solution parameter, otherwise an exception
* will be raised.
*
* When a PolyTree object is used in a clipping operation on open paths, two ancilliary functions have been provided to quickly separate out open and
* closed paths from the solution - OpenPathsFromPolyTree and ClosedPathsFromPolyTree. PolyTreeToPaths is also available to convert path data to a Paths
* structure (irrespective of whether they're open or closed).
*
* There are several things to note about the solution paths returned:
* - they aren't in any specific order
* - they should never overlap or be self-intersecting (but see notes on rounding)
* - holes will be oriented opposite outer polygons
* - the solution fill type can be considered either EvenOdd or NonZero since it will comply with either filling rule
* - polygons may rarely share a common edge (though this is now very rare as of version 6)
*
* @param params - clipping operation data
* @return {Paths} - the resulting Paths.
*/
ClipperLibWrapper.prototype.clipToPaths = function (params) {
return clipFunctions_1.clipToPaths(this.instance, params);
};
/**
* Performs a polygon clipping (boolean) operation, returning the resulting PolyTree or throwing an error if failed.
*
* The solution parameter in this case is a Paths or PolyTree structure. The Paths structure is simpler than the PolyTree structure. Because of this it is
* quicker to populate and hence clipping performance is a little better (it's roughly 10% faster). However, the PolyTree data structure provides more
* information about the returned paths which may be important to users. Firstly, the PolyTree structure preserves nested parent-child polygon relationships
* (ie outer polygons owning/containing holes and holes owning/containing other outer polygons etc). Also, only the PolyTree structure can differentiate
* between open and closed paths since each PolyNode has an IsOpen property. (The Path structure has no member indicating whether it's open or closed.)
* For this reason, when open paths are passed to a Clipper object, the user must use a PolyTree object as the solution parameter, otherwise an exception
* will be raised.
*
* When a PolyTree object is used in a clipping operation on open paths, two ancilliary functions have been provided to quickly separate out open and
* closed paths from the solution - OpenPathsFromPolyTree and ClosedPathsFromPolyTree. PolyTreeToPaths is also available to convert path data to a Paths
* structure (irrespective of whether they're open or closed).
*
* There are several things to note about the solution paths returned:
* - they aren't in any specific order
* - they should never overlap or be self-intersecting (but see notes on rounding)
* - holes will be oriented opposite outer polygons
* - the solution fill type can be considered either EvenOdd or NonZero since it will comply with either filling rule
* - polygons may rarely share a common edge (though this is now very rare as of version 6)
*
* @param params - clipping operation data
* @return {PolyTree} - the resulting PolyTree or undefined.
*/
ClipperLibWrapper.prototype.clipToPolyTree = function (params) {
return clipFunctions_1.clipToPolyTree(this.instance, params);
};
/**
* Performs a polygon offset operation, returning the resulting Paths or undefined if failed.
*
* This method encapsulates the process of offsetting (inflating/deflating) both open and closed paths using a number of different join types
* and end types.
*
* Preconditions for offsetting:
* 1. The orientations of closed paths must be consistent such that outer polygons share the same orientation, and any holes have the opposite orientation
* (ie non-zero filling). Open paths must be oriented with closed outer polygons.
* 2. Polygons must not self-intersect.
*
* Limitations:
* When offsetting, small artefacts may appear where polygons overlap. To avoid these artefacts, offset overlapping polygons separately.
*
* @param params - offset operation params
* @return {Paths|undefined} - the resulting Paths or undefined if failed.
*/
ClipperLibWrapper.prototype.offsetToPaths = function (params) {
return offsetFunctions_1.offsetToPaths(this.instance, params);
};
/**
* Performs a polygon offset operation, returning the resulting PolyTree or undefined if failed.
*
* This method encapsulates the process of offsetting (inflating/deflating) both open and closed paths using a number of different join types
* and end types.
*
* Preconditions for offsetting:
* 1. The orientations of closed paths must be consistent such that outer polygons share the same orientation, and any holes have the opposite orientation
* (ie non-zero filling). Open paths must be oriented with closed outer polygons.
* 2. Polygons must not self-intersect.
*
* Limitations:
* When offsetting, small artefacts may appear where polygons overlap. To avoid these artefacts, offset overlapping polygons separately.
*
* @param params - offset operation params
* @return {PolyTree|undefined} - the resulting PolyTree or undefined if failed.
*/
ClipperLibWrapper.prototype.offsetToPolyTree = function (params) {
return offsetFunctions_1.offsetToPolyTree(this.instance, params);
};
//noinspection JSMethodCanBeStatic
/**
* This function returns the area of the supplied polygon. It's assumed that the path is closed and does not self-intersect. Depending on orientation,
* this value may be positive or negative. If Orientation is true, then the area will be positive and conversely, if Orientation is false, then the
* area will be negative.
*
* @param path - The path
* @return {number} - Area
*/
ClipperLibWrapper.prototype.area = function (path) {
return functions.area(path);
};
/**
* Removes vertices:
* - that join co-linear edges, or join edges that are almost co-linear (such that if the vertex was moved no more than the specified distance the edges
* would be co-linear)
* - that are within the specified distance of an adjacent vertex
* - that are within the specified distance of a semi-adjacent vertex together with their out-lying vertices
*
* Vertices are semi-adjacent when they are separated by a single (out-lying) vertex.
*
* The distance parameter's default value is approximately √2 so that a vertex will be removed when adjacent or semi-adjacent vertices having their
* corresponding X and Y coordinates differing by no more than 1 unit. (If the egdes are semi-adjacent the out-lying vertex will be removed too.)
*
* @param path - The path to clean
* @param distance - How close points need to be before they are cleaned
* @return {Path} - The cleaned path
*/
ClipperLibWrapper.prototype.cleanPolygon = function (path, distance) {
if (distance === void 0) { distance = 1.1415; }
return functions.cleanPolygon(this.instance, path, distance);
};
/**
* Removes vertices:
* - that join co-linear edges, or join edges that are almost co-linear (such that if the vertex was moved no more than the specified distance the edges
* would be co-linear)
* - that are within the specified distance of an adjacent vertex
* - that are within the specified distance of a semi-adjacent vertex together with their out-lying vertices
*
* Vertices are semi-adjacent when they are separated by a single (out-lying) vertex.
*
* The distance parameter's default value is approximately √2 so that a vertex will be removed when adjacent or semi-adjacent vertices having their
* corresponding X and Y coordinates differing by no more than 1 unit. (If the egdes are semi-adjacent the out-lying vertex will be removed too.)
*
* @param paths - The paths to clean
* @param distance - How close points need to be before they are cleaned
* @return {Paths} - The cleaned paths
*/
ClipperLibWrapper.prototype.cleanPolygons = function (paths, distance) {
if (distance === void 0) { distance = 1.1415; }
return functions.cleanPolygons(this.instance, paths, distance);
};
//noinspection JSMethodCanBeStatic
/**
* This function filters out open paths from the PolyTree structure and returns only closed paths in a Paths structure.
*
* @param polyTree
* @return {Paths}
*/
ClipperLibWrapper.prototype.closedPathsFromPolyTree = function (polyTree) {
return functions.closedPathsFromPolyTree(polyTree);
};
/**
* Minkowski Difference is performed by subtracting each point in a polygon from the set of points in an open or closed path. A key feature of Minkowski
* Difference is that when it's applied to two polygons, the resulting polygon will contain the coordinate space origin whenever the two polygons touch or
* overlap. (This function is often used to determine when polygons collide.)
*
* @param poly1
* @param poly2
* @return {Paths}
*/
ClipperLibWrapper.prototype.minkowskiDiff = function (poly1, poly2) {
return functions.minkowskiDiff(this.instance, poly1, poly2);
};
/**
* Minkowski Addition is performed by adding each point in a polygon 'pattern' to the set of points in an open or closed path. The resulting polygon
* (or polygons) defines the region that the 'pattern' would pass over in moving from the beginning to the end of the 'path'.
*
* @param pattern
* @param path
* @param pathIsClosed
* @return {Paths}
*/
ClipperLibWrapper.prototype.minkowskiSumPath = function (pattern, path, pathIsClosed) {
return functions.minkowskiSumPath(this.instance, pattern, path, pathIsClosed);
};
/**
* Minkowski Addition is performed by adding each point in a polygon 'pattern' to the set of points in an open or closed path. The resulting polygon
* (or polygons) defines the region that the 'pattern' would pass over in moving from the beginning to the end of the 'path'.
*
* @param pattern
* @param paths
* @param pathIsClosed
* @return {Paths}
*/
ClipperLibWrapper.prototype.minkowskiSumPaths = function (pattern, paths, pathIsClosed) {
return functions.minkowskiSumPaths(this.instance, pattern, paths, pathIsClosed);
};
//noinspection JSMethodCanBeStatic
/**
* This function filters out closed paths from the PolyTree structure and returns only open paths in a Paths structure.
*
* @param polyTree
* @return {ReadonlyPath[]}
*/
ClipperLibWrapper.prototype.openPathsFromPolyTree = function (polyTree) {
return functions.openPathsFromPolyTree(polyTree);
};
//noinspection JSMethodCanBeStatic
/**
* Orientation is only important to closed paths. Given that vertices are declared in a specific order, orientation refers to the direction (clockwise or
* counter-clockwise) that these vertices progress around a closed path.
*
* Orientation is also dependent on axis direction:
* - On Y-axis positive upward displays, orientation will return true if the polygon's orientation is counter-clockwise.
* - On Y-axis positive downward displays, orientation will return true if the polygon's orientation is clockwise.
*
* Notes:
* - Self-intersecting polygons have indeterminate orientations in which case this function won't return a meaningful value.
* - The majority of 2D graphic display libraries (eg GDI, GDI+, XLib, Cairo, AGG, Graphics32) and even the SVG file format have their coordinate origins
* at the top-left corner of their respective viewports with their Y axes increasing downward. However, some display libraries (eg Quartz, OpenGL) have their
* coordinate origins undefined or in the classic bottom-left position with their Y axes increasing upward.
* - For Non-Zero filled polygons, the orientation of holes must be opposite that of outer polygons.
* - For closed paths (polygons) in the solution returned by the clip method, their orientations will always be true for outer polygons and false
* for hole polygons (unless the reverseSolution property has been enabled).
*
* @param path - Path
* @return {boolean}
*/
ClipperLibWrapper.prototype.orientation = function (path) {
return functions.orientation(path);
};
//noinspection JSMethodCanBeStatic
/**
* Returns PointInPolygonResult.Outside when false, PointInPolygonResult.OnBoundary when point is on poly and PointInPolygonResult.Inside when point is in
* poly.
*
* It's assumed that 'poly' is closed and does not self-intersect.
*
* @param point
* @param path
* @return {PointInPolygonResult}
*/
ClipperLibWrapper.prototype.pointInPolygon = function (point, path) {
return functions.pointInPolygon(point, path);
};
//noinspection JSMethodCanBeStatic
/**
* This function converts a PolyTree structure into a Paths structure.
*
* @param polyTree
* @return {Paths}
*/
ClipperLibWrapper.prototype.polyTreeToPaths = function (polyTree) {
return functions.polyTreeToPaths(polyTree);
};
//noinspection JSMethodCanBeStatic
/**
* Reverses the vertex order (and hence orientation) in the specified path.
*
* @param path - Path to reverse, which gets overwritten rather than copied
*/
ClipperLibWrapper.prototype.reversePath = function (path) {
functions.reversePath(path);
};
//noinspection JSMethodCanBeStatic
/**
* Reverses the vertex order (and hence orientation) in each contained path.
*
* @param paths - Paths to reverse, which get overwritten rather than copied
*/
ClipperLibWrapper.prototype.reversePaths = function (paths) {
functions.reversePaths(paths);
};
/**
* Removes self-intersections from the supplied polygon (by performing a boolean union operation using the nominated PolyFillType).
* Polygons with non-contiguous duplicate vertices (ie 'touching') will be split into two polygons.
*
* Note: There's currently no guarantee that polygons will be strictly simple since 'simplifying' is still a work in progress.
*
* @param path
* @param fillType
* @return {Paths} - The solution
*/
ClipperLibWrapper.prototype.simplifyPolygon = function (path, fillType) {
if (fillType === void 0) { fillType = enums_1.PolyFillType.EvenOdd; }
return functions.simplifyPolygon(this.instance, path, fillType);
};
/**
* Removes self-intersections from the supplied polygons (by performing a boolean union operation using the nominated PolyFillType).
* Polygons with non-contiguous duplicate vertices (ie 'vertices are touching') will be split into two polygons.
*
* Note: There's currently no guarantee that polygons will be strictly simple since 'simplifying' is still a work in progress.
*
* @param paths
* @param fillType
* @return {Paths} - The solution
*/
ClipperLibWrapper.prototype.simplifyPolygons = function (paths, fillType) {
if (fillType === void 0) { fillType = enums_1.PolyFillType.EvenOdd; }
return functions.simplifyPolygons(this.instance, paths, fillType);
};
//noinspection JSMethodCanBeStatic
/**
* Scales a path by multiplying all its points by a number and then rounding them.
*
* @param path - Path to scale
* @param scale - Scale multiplier
* @return {Path} - The scaled path
*/
ClipperLibWrapper.prototype.scalePath = function (path, scale) {
return functions.scalePath(path, scale);
};
//noinspection JSMethodCanBeStatic
/**
* Scales all inner paths by multiplying all its points by a number and then rounding them.
*
* @param paths - Paths to scale
* @param scale - Scale multiplier
* @return {Paths} - The scaled paths
*/
ClipperLibWrapper.prototype.scalePaths = function (paths, scale) {
return functions.scalePaths(paths, scale);
};
/**
* Max coordinate value (both positive and negative).
*/
ClipperLibWrapper.hiRange = constants_1.hiRange;
return ClipperLibWrapper;
}());
exports.ClipperLibWrapper = ClipperLibWrapper;
/**
* Asynchronously tries to load a new native instance of the clipper library to be shared across all method invocations.
*
* @param format - Format to load, either WasmThenAsmJs, WasmOnly or AsmJsOnly.
* @return {Promise<ClipperLibWrapper>} - Promise that resolves with the wrapper instance.
*/
exports.loadNativeClipperLibInstanceAsync = function (format) { return __awaiter(void 0, void 0, void 0, function () {
function getModuleAsync(initModule) {
return new Promise(function (resolve, reject) {
var finalModule;
//noinspection JSUnusedLocalSymbols
var moduleOverrides = {
noExitRuntime: true,
preRun: function () {
if (finalModule) {
resolve(finalModule);
}
else {
setTimeout(function () {
resolve(finalModule);
}, 1);
}
},
quit: function (code, err) {
reject(err);
}
};
finalModule = initModule(moduleOverrides);
});
}
var tryWasm, tryAsmJs, initModule, err_1, initModule, err_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
switch (format) {
case enums_1.NativeClipperLibRequestedFormat.WasmWithAsmJsFallback:
tryWasm = true;
tryAsmJs = true;
break;
case enums_1.NativeClipperLibRequestedFormat.WasmOnly:
tryWasm = true;
tryAsmJs = false;
break;
case enums_1.NativeClipperLibRequestedFormat.AsmJsOnly:
tryWasm = false;
tryAsmJs = true;
break;
default:
throw new ClipperError_1.ClipperError("unknown native clipper format");
}
if (!tryWasm) return [3 /*break*/, 7];
if (!(wasmModule instanceof Error)) return [3 /*break*/, 1];
return [3 /*break*/, 7];
case 1:
if (!(wasmModule === undefined)) return [3 /*break*/, 6];
_a.label = 2;
case 2:
_a.trys.push([2, 4, , 5]);
initModule = require("./wasm/clipper-wasm").init;
return [4 /*yield*/, getModuleAsync(initModule)];
case 3:
wasmModule = _a.sent();
return [2 /*return*/, new ClipperLibWrapper(wasmModule, enums_1.NativeClipperLibLoadedFormat.Wasm)];
case 4:
err_1 = _a.sent();
wasmModule = err_1;
return [3 /*break*/, 5];
case 5: return [3 /*break*/, 7];
case 6: return [2 /*return*/, new ClipperLibWrapper(wasmModule, enums_1.NativeClipperLibLoadedFormat.Wasm)];
case 7:
if (!tryAsmJs) return [3 /*break*/, 14];
if (!(asmJsModule instanceof Error)) return [3 /*break*/, 8];
return [3 /*break*/, 14];
case 8:
if (!(asmJsModule === undefined)) return [3 /*break*/, 13];
_a.label = 9;
case 9:
_a.trys.push([9, 11, , 12]);
initModule = require("./wasm/clipper").init;
return [4 /*yield*/, getModuleAsync(initModule)];
case 10:
asmJsModule = _a.sent();
return [2 /*return*/, new ClipperLibWrapper(asmJsModule, enums_1.NativeClipperLibLoadedFormat.AsmJs)];
case 11:
err_2 = _a.sent();
asmJsModule = err_2;
return [3 /*break*/, 12];
case 12: return [3 /*break*/, 14];
case 13: return [2 /*return*/, new ClipperLibWrapper(asmJsModule, enums_1.NativeClipperLibLoadedFormat.AsmJs)];
case 14: throw new ClipperError_1.ClipperError("could not load native clipper in the desired format");
}
});
}); };
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsaURBQXFGO0FBQ3JGLCtDQUE4QztBQTBDNUMsNkZBMUNPLDJCQUFZLE9BMENQO0FBekNkLHlDQUFzQztBQUN0QyxpQ0FRaUI7QUFhZix5RkFwQkEsZ0JBQVEsT0FvQkE7QUFDUix3RkFwQkEsZUFBTyxPQW9CQTtBQUNQLHlGQXBCQSxnQkFBUSxPQW9CQTtBQUVSLDZHQXJCQSxvQ0FBNEIsT0FxQkE7QUFDNUIsZ0hBckJBLHVDQUErQixPQXFCQTtBQUMvQixxR0FyQkEsNEJBQW9CLE9BcUJBO0FBSHBCLDZGQWpCQSxvQkFBWSxPQWlCQTtBQWZkLHVDQUF5QztBQUl6QyxxREFBK0Y7QUFHL0YsdUNBQXNDO0FBWXBDLHlGQVpPLG1CQUFRLE9BWVA7QUFYVix1Q0FBc0M7QUFZcEMseUZBWk8sbUJBQVEsT0FZUDtBQWNWLElBQUksVUFBd0QsQ0FBQztBQUM3RCxJQUFJLFdBQWlELENBQUM7QUFFdEQ7O0dBRUc7QUFDSDtJQWdCRTs7Ozs7T0FLRztJQUNILDJCQUFZLFFBQWtDLEVBQUUsTUFBb0M7UUFDbEYsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7UUFDckIsSUFBSSxDQUFDLFFBQVEsR0FBRyxRQUFRLENBQUM7SUFDM0IsQ0FBQztJQUVEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7T0F3Qkc7SUFDSCx1Q0FBVyxHQUFYLFVBQVksTUFBa0I7UUFDNUIsT0FBTywyQkFBVyxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFDNUMsQ0FBQztJQUVEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7T0F3Qkc7SUFDSCwwQ0FBYyxHQUFkLFVBQWUsTUFBa0I7UUFDL0IsT0FBTyw4QkFBYyxDQUFDLElBQUksQ0FBQyxRQUFRLEVB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Qy9FLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDbGlwSW5wdXQsIENsaXBQYXJhbXMsIGNsaXBUb1BhdGhzLCBjbGlwVG9Qb2x5VHJlZSB9IGZyb20gXCIuL2NsaXBGdW5jdGlvbnNcIjtcclxuaW1wb3J0IHsgQ2xpcHBlckVycm9yIH0gZnJvbSBcIi4vQ2xpcHBlckVycm9yXCI7XHJcbmltcG9ydCB7IGhpUmFuZ2UgfSBmcm9tIFwiLi9jb25zdGFudHNcIjtcclxuaW1wb3J0IHtcclxuICBDbGlwVHlwZSxcclxuICBFbmRUeXBlLFxyXG4gIEpvaW5UeXBlLFxyXG4gIE5hdGl2ZUNsaXBwZXJMaWJMb2FkZWRGb3JtYXQsXHJcbiAgTmF0aXZlQ2xpcHBlckxpYlJlcXVlc3RlZEZvcm1hdCxcclxuICBQb2ludEluUG9seWdvblJlc3VsdCxcclxuICBQb2x5RmlsbFR5cGVcclxufSBmcm9tIFwiLi9lbnVtc1wiO1xyXG5pbXBvcnQgKiBhcyBmdW5jdGlvbnMgZnJvbSBcIi4vZnVuY3Rpb25zXCI7XHJcbmltcG9ydCB7IEludFBvaW50IH0gZnJvbSBcIi4vSW50UG9pbnRcIjtcclxuaW1wb3J0IHsgSW50UmVjdCB9IGZyb20gXCIuL0ludFJlY3RcIjtcclxuaW1wb3J0IHsgTmF0aXZlQ2xpcHBlckxpYkluc3RhbmNlIH0gZnJvbSBcIi4vbmF0aXZlL05hdGl2ZUNsaXBwZXJMaWJJbnN0YW5jZVwiO1xyXG5pbXBvcnQgeyBPZmZzZXRJbnB1dCwgT2Zmc2V0UGFyYW1zLCBvZmZzZXRUb1BhdGhzLCBvZmZzZXRUb1BvbHlUcmVlIH0gZnJvbSBcIi4vb2Zmc2V0RnVuY3Rpb25zXCI7XHJcbmltcG9ydCB7IFBhdGgsIFJlYWRvbmx5UGF0aCB9IGZyb20gXCIuL1BhdGhcIjtcclxuaW1wb3J0IHsgUGF0aHMsIFJlYWRvbmx5UGF0aHMgfSBmcm9tIFwiLi9QYXRoc1wiO1xyXG5pbXBvcnQgeyBQb2x5Tm9kZSB9IGZyb20gXCIuL1BvbHlOb2RlXCI7XHJcbmltcG9ydCB7IFBvbHlUcmVlIH0gZnJvbSBcIi4vUG9seVRyZWVcIjtcclxuXHJcbi8vIGV4cG9ydCB0eXBlc1xyXG5leHBvcnQge1xyXG4gIENsaXBUeXBlLFxyXG4gIEVuZFR5cGUsXHJcbiAgSm9pblR5cGUsXHJcbiAgUG9seUZpbGxUeXBlLFxyXG4gIE5hdGl2ZUNsaXBwZXJMaWJMb2FkZWRGb3JtYXQsXHJcbiAgTmF0aXZlQ2xpcHBlckxpYlJlcXVlc3RlZEZvcm1hdCxcclxuICBQb2ludEluUG9seWdvblJlc3VsdCxcclxuICBQb2x5Tm9kZSxcclxuICBQb2x5VHJlZSxcclxuICBJbnRQb2ludCxcclxuICBJbnRSZWN0LFxyXG4gIFBhdGgsXHJcbiAgUmVhZG9ubHlQYXRoLFxyXG4gIFBhdGhzLFxyXG4gIFJlYWRvbmx5UGF0aHMsXHJcbiAgQ2xpcElucHV0LFxyXG4gIENsaXBQYXJhbXMsXHJcbiAgT2Zmc2V0SW5wdXQsXHJcbiAgT2Zmc2V0UGFyYW1zLFxyXG4gIENsaXBwZXJFcnJvclxyXG59O1xyXG5cclxubGV0IHdhc21Nb2R1bGU6IE5hdGl2ZUNsaXBwZXJMaWJJbnN0YW5jZSB8IHVuZGVmaW5lZCB8IEVycm9yO1xyXG5sZXQgYXNtSnNNb2R1bGU6IE5hdGl2ZUNsaXBwZXJMaWJJbnN0YW5jZSB8IHVuZGVmaW5lZDtcclxuXHJcbi8qKlxyXG4gKiBBIHdyYXBwZXIgZm9yIHRoZSBOYXRpdmUgQ2xpcHBlciBMaWJyYXJ5IGluc3RhbmNlIHdpdGggYWxsIHRoZSBvcGVyYXRpb25zIGF2YWlsYWJsZS5cclxuICovXHJcbmV4cG9ydCBjbGFzcyBDbGlwcGVyTGliV3JhcHBlciB7XHJcbiAgLyoqXHJcbiAgICogTWF4IGNvb3JkaW5hdGUgdmFsdWUgKGJvdGggcG9zaXRpdmUgYW5kIG5lZ2F0aXZlKS5cclxuICAgKi9cclxuICBzdGF0aWMgcmVhZG9ubHkgaGlSYW5nZSA9IGhpUmFuZ2U7XHJcblxyXG4gIC8qKlxyXG4gICAqIE5hdGl2ZSBsaWJyYXJ5IGluc3RhbmNlLlxyXG4gICAqL1xyXG4gIHJlYWRvbmx5IGluc3RhbmNlOiBOYXRpdmVDbGlwcGVyTGliSW5zdGFuY2U7XHJcblxyXG4gIC8qKlxyXG4gICAqIE5hdGl2ZSBsaWJyYXJ5IGZvcm1hdC5cclxuICAgKi9cclxuICByZWFkb25seSBmb3JtYXQ6IE5hdGl2ZUNsaXBwZXJMaWJMb2FkZWRGb3JtYXQ7XHJcblxyXG4gIC8qKlxyXG4gICAqIEludGVybmFsIGNvbnN0cnVjdG9yLiBVc2UgbG9hZE5hdGl2ZUNsaXBwZXJMaWJJbnN0YW5jZUFzeW5jIGluc3RlYWQuXHJcbiAgICpcclxuICAgKiBAcGFyYW0gaW5zdGFuY2VcclxuICAgKiBAcGFyYW0gZm9ybWF0XHJcbiAgICovXHJcbiAgY29uc3RydWN0b3IoaW5zdGFuY2U6IE5hdGl2ZUNsaXBwZXJMaWJJbnN0YW5jZSwgZm9ybWF0OiBOYXRpdmVDbGlwcGVyTGliTG9hZGVkRm9ybWF0KSB7XHJcbiAgICB0aGlzLmZvcm1hdCA9IGZvcm1hdDtcclxuICAgIHRoaXMuaW5zdGFuY2UgPSBpbnN0YW5jZTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIFBlcmZvcm1zIGEgcG9seWdvbiBjbGlwcGluZyAoYm9vbGVhbikgb3BlcmF0aW9uLCByZXR1cm5pbmcgdGhlIHJlc3VsdGluZyBQYXRocyBvciB0aHJvd2luZyBhbiBlcnJvciBpZiBmYWlsZWQuXHJcbiAgICpcclxuICAgKiBUaGUgc29sdXRpb24gcGFyYW1ldGVyIGluIHRoaXMgY2FzZSBpcyBhIFBhdGhzIG9yIFBvbHlUcmVlIHN0cnVjdHVyZS4gVGhlIFBhdGhzIHN0cnVjdHVyZSBpcyBzaW1wbGVyIHRoYW4gdGhlIFBvbHlUcmVlIHN0cnVjdHVyZS4gQmVjYXVzZSBvZiB0aGlzIGl0IGlzXHJcbiAgICogcXVpY2tlciB0byBwb3B1bGF0ZSBhbmQgaGVuY2UgY2xpcHBpbmcgcGVyZm9ybWFuY2UgaXMgYSBsaXR0bGUgYmV0dGVyIChpdCdzIHJvdWdobHkgMTAlIGZhc3RlcikuIEhvd2V2ZXIsIHRoZSBQb2x5VHJlZSBkYXRhIHN0cnVjdHVyZSBwcm92aWRlcyBtb3JlXHJcbiAgICogaW5mb3JtYXRpb24gYWJvdXQgdGhlIHJldHVybmVkIHBhdGhzIHdoaWNoIG1heSBiZSBpbXBvcnRhbnQgdG8gdXNlcnMuIEZpcnN0bHksIHRoZSBQb2x5VHJlZSBzdHJ1Y3R1cmUgcHJlc2VydmVzIG5lc3RlZCBwYXJlbnQtY2hpbGQgcG9seWdvbiByZWxhdGlvbnNoaXBzXHJcbiAgICogKGllIG91dGVyIHBvbHlnb25zIG93bmluZy9jb250YWluaW5nIGhvbGVzIGFuZCBob2xlcyBvd25pbmcvY29udGFpbmluZyBvdGhlciBvdXRlciBwb2x5Z29ucyBldGMpLiBBbHNvLCBvbmx5IHRoZSBQb2x5VHJlZSBzdHJ1Y3R1cmUgY2FuIGRpZmZlcmVudGlhdGVcclxuICAgKiBiZXR3ZWVuIG9wZW4gYW5kIGNsb3NlZCBwYXRocyBzaW5jZSBlYWNoIFBvbHlOb2RlIGhhcyBhbiBJc09wZW4gcHJvcGVydHkuIChUaGUgUGF0aCBzdHJ1Y3R1cmUgaGFzIG5vIG1lbWJlciBpbmRpY2F0aW5nIHdoZXRoZXIgaXQncyBvcGVuIG9yIGNsb3NlZC4pXHJcbiAgICogRm9yIHRoaXMgcmVhc29uLCB3aGVuIG9wZW4gcGF0aHMgYXJlIHBhc3NlZCB0byBhIENsaXBwZXIgb2JqZWN0LCB0aGUgdXNlciBtdXN0IHVzZSBhIFBvbHlUcmVlIG9iamVjdCBhcyB0aGUgc29sdXRpb24gcGFyYW1ldGVyLCBvdGhlcndpc2UgYW4gZXhjZXB0aW9uXHJcbiAgICogd2lsbCBiZSByYWlzZWQuXHJcbiAgICpcclxuICAgKiBXaGVuIGEgUG9seVRyZWUgb2JqZWN0IGlzIHVzZWQgaW4gYSBjbGlwcGluZyBvcGVyYXRpb24gb24gb3BlbiBwYXRocywgdHdvIGFuY2lsbGlhcnkgZnVuY3Rpb25zIGhhdmUgYmVlbiBwcm92aWRlZCB0byBxdWlja2x5IHNlcGFyYXRlIG91dCBvcGVuIGFuZFxyXG4gICAqIGNsb3NlZCBwYXRocyBmcm9tIHRoZSBzb2x1dGlvbiAtIE9wZW5QYXRoc0Zyb21Qb2x5VHJlZSBhbmQgQ2xvc2VkUGF0aHNGcm9tUG9seVRyZWUuIFBvbHlUcmVlVG9QYXRocyBpcyBhbHNvIGF2YWlsYWJsZSB0byBjb252ZXJ0IHBhdGggZGF0YSB0byBhIFBhdGhzXHJcbiAgICogc3RydWN0dXJlIChpcnJlc3BlY3RpdmUgb2Ygd2hldGhlciB0aGV5J3JlIG9wZW4gb3IgY2xvc2VkKS5cclxuICAgKlxyXG4gICAqIFRoZXJlIGFyZSBzZXZlcmFsIHRoaW5ncyB0byBub3RlIGFib3V0IHRoZSBzb2x1dGlvbiBwYXRocyByZXR1cm5lZDpcclxuICAgKiAtIHRoZXkgYXJlbid0IGluIGFueSBzcGVjaWZpYyBvcmRlclxyXG4gICAqIC0gdGhleSBzaG91bGQgbmV2ZXIgb3ZlcmxhcCBvciBiZSBzZWxmLWludGVyc2VjdGluZyAoYnV0IHNlZSBub3RlcyBvbiByb3VuZGluZylcclxuICAgKiAtIGhvbGVzIHdpbGwgYmUgb3JpZW50ZWQgb3Bwb3NpdGUgb3V0ZXIgcG9seWdvbnNcclxuICAgKiAtIHRoZSBzb2x1dGlvbiBmaWxsIHR5cGUgY2FuIGJlIGNvbnNpZGVyZWQgZWl0aGVyIEV2ZW5PZGQgb3IgTm9uWmVybyBzaW5jZSBpdCB3aWxsIGNvbXBseSB3aXRoIGVpdGhlciBmaWxsaW5nIHJ1bGVcclxuICAgKiAtIHBvbHlnb25zIG1heSByYXJlbHkgc2hhcmUgYSBjb21tb24gZWRnZSAodGhvdWdoIHRoaXMgaXMgbm93IHZlcnkgcmFyZSBhcyBvZiB2ZXJzaW9uIDYpXHJcbiAgICpcclxuICAgKiBAcGFyYW0gcGFyYW1zIC0gY2xpcHBpbmcgb3BlcmF0aW9uIGRhdGFcclxuICAgKiBAcmV0dXJuIHtQYXRoc30gLSB0aGUgcmVzdWx0aW5nIFBhdGhzLlxyXG4gICAqL1xyXG4gIGNsaXBUb1BhdGhzKHBhcmFtczogQ2xpcFBhcmFtcyk6IFBhdGhzIHwgdW5kZWZpbmVkIHtcclxuICAgIHJldHVybiBjbGlwVG9QYXRocyh0aGlzLmluc3RhbmNlLCBwYXJhbXMpO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogUGVyZm9ybXMgYSBwb2x5Z29uIGNsaXBwaW5nIChib29sZWFuKSBvcGVyYXRpb24sIHJldHVybmluZyB0aGUgcmVzdWx0aW5nIFBvbHlUcmVlIG9yIHRocm93aW5nIGFuIGVycm9yIGlmIGZhaWxlZC5cclxuICAgKlxyXG4gICAqIFRoZSBzb2x1dGlvbiBwYXJhbWV0ZXIgaW4gdGhpcyBjYXNlIGlzIGEgUGF0aHMgb3IgUG9seVRyZWUgc3RydWN0dXJlLiBUaGUgUGF0aHMgc3RydWN0dXJlIGlzIHNpbXBsZXIgdGhhbiB0aGUgUG9seVRyZWUgc3RydWN0dXJlLiBCZWNhdXNlIG9mIHRoaXMgaXQgaXNcclxuICAgKiBxdWlja2VyIHRvIHBvcHVsYXRlIGFuZCBoZW5jZSBjbGlwcGluZyBwZXJmb3JtYW5jZSBpcyBhIGxpdHRsZSBiZXR0ZXIgKGl0J3Mgcm91Z2hseSAxMCUgZmFzdGVyKS4gSG93ZXZlciwgdGhlIFBvbHlUcmVlIGRhdGEgc3RydWN0dXJlIHByb3ZpZGVzIG1vcmVcclxuICAgKiBpbmZvcm1hdGlvbiBhYm91dCB0aGUgcmV0dXJuZWQgcGF0aHMgd2hpY2ggbWF5IGJlIGltcG9ydGFudCB0byB1c2Vycy4gRmlyc3RseSwgdGhlIFBvbHlUcmVlIHN0cnVjdHVyZSBwcmVzZXJ2ZXMgbmVzdGVkIHBhcmVudC1jaGlsZCBwb2x5Z29uIHJlbGF0aW9uc2hpcHNcclxuICAgKiAoaWUgb3V0ZXIgcG9seWdvbnMgb3duaW5nL2NvbnRhaW5pbmcgaG9sZXMgYW5kIGhvbGVzIG93bmluZy9jb250YWluaW5nIG90aGVyIG91dGVyIHBvbHlnb25zIGV0YykuIEFsc28sIG9ubHkgdGhlIFBvbHlUcmVlIHN0cnVjdHVyZSBjYW4gZGlmZmVyZW50aWF0ZVxyXG4gICAqIGJldHdlZW4gb3BlbiBhbmQgY2xvc2VkIHBhdGhzIHNpbmNlIGVhY2ggUG9seU5vZGUgaGFzIGFuIElzT3BlbiBwcm9wZXJ0eS4gKFRoZSBQYXRoIHN0cnVjdHVyZSBoYXMgbm8gbWVtYmVyIGluZGljYXRpbmcgd2hldGhlciBpdCdzIG9wZW4gb3IgY2xvc2VkLilcclxuICAgKiBGb3IgdGhpcyByZWFzb24sIHdoZW4gb3BlbiBwYXRocyBhcmUgcGFzc2VkIHRvIGEgQ2xpcHBlciBvYmplY3QsIHRoZSB1c2VyIG11c3QgdXNlIGEgUG9seVRyZWUgb2JqZWN0IGFzIHRoZSBzb2x1dGlvbiBwYXJhbWV0ZXIsIG90aGVyd2lzZSBhbiBleGNlcHRpb25cclxuICAgKiB3aWxsIGJlIHJhaXNlZC5cclxuICAgKlxyXG4gICAqIFdoZW4gYSBQb2x5VHJlZSBvYmplY3QgaXMgdXNlZCBpbiBhIGNsaXBwaW5nIG9wZXJhdGlvbiBvbiBvcGVuIHBhdGhzLCB0d28gYW5jaWxsaWFyeSBmdW5jdGlvbnMgaGF2ZSBiZWVuIHByb3ZpZGVkIHRvIHF1aWNrbHkgc2VwYXJhdGUgb3V0IG9wZW4gYW5kXHJcbiAgICogY2xvc2VkIHBhdGhzIGZyb20gdGhlIHNvbHV0aW9uIC0gT3BlblBhdGhzRnJvbVBvbHlUcmVlIGFuZCBDbG9zZWRQYXRoc0Zyb21Qb2x5VHJlZS4gUG9seVRyZWVUb1BhdGhzIGlzIGFsc28gYXZhaWxhYmxlIHRvIGNvbnZlcnQgcGF0aCBkYXRhIHRvIGEgUGF0aHNcclxuICAgKiBzdHJ1Y3R1cmUgKGlycmVzcGVjdGl2ZSBvZiB3aGV0aGVyIHRoZXkncmUgb3BlbiBvciBjbG9zZWQpLlxyXG4gICAqXHJcbiAgICogVGhlcmUgYXJlIHNldmVyYWwgdGhpbmdzIHRvIG5vdGUgYWJvdXQgdGhlIHNvbHV0aW9uIHBhdGhzIHJldHVybmVkOlxyXG4gICAqIC0gdGhleSBhcmVuJ3QgaW4gYW55IHNwZWNpZmljIG9yZGVyXHJcbiAgICogLSB0aGV5IHNob3VsZCBuZXZlciBvdmVybGFwIG9yIGJlIHNlbGYtaW50ZXJzZWN0aW5nIChidXQgc2VlIG5vdGVzIG9uIHJvdW5kaW5nKVxyXG4gICAqIC0gaG9sZXMgd2lsbCBiZSBvcmllbnRlZCBvcHBvc2l0ZSBvdXRlciBwb2x5Z29uc1xyXG4gICAqIC0gdGhlIHNvbHV0aW9uIGZpbGwgdHlwZSBjYW4gYmUgY29uc2lkZXJlZCBlaXRoZXIgRXZlbk9kZCBvciBOb25aZXJvIHNpbmNlIGl0IHdpbGwgY29tcGx5IHdpdGggZWl0aGVyIGZpbGxpbmcgcnVsZVxyXG4gICAqIC0gcG9seWdvbnMgbWF5IHJhcmVseSBzaGFyZSBhIGNvbW1vbiBlZGdlICh0aG91Z2ggdGhpcyBpcyBub3cgdmVyeSByYXJlIGFzIG9mIHZlcnNpb24gNilcclxuICAgKlxyXG4gICAqIEBwYXJhbSBwYXJhbXMgLSBjbGlwcGluZyBvcGVyYXRpb24gZGF0YVxyXG4gICAqIEByZXR1cm4ge1BvbHlUcmVlfSAtIHRoZSByZXN1bHRpbmcgUG9seVRyZWUgb3IgdW5kZWZpbmVkLlxyXG4gICAqL1xyXG4gIGNsaXBUb1BvbHlUcmVlKHBhcmFtczogQ2xpcFBhcmFtcyk6IFBvbHlUcmVlIHwgdW5kZWZpbmVkIHtcclxuICAgIHJldHVybiBjbGlwVG9Qb2x5VHJlZSh0aGlzLmluc3RhbmNlLCBwYXJhbXMpO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogUGVyZm9ybXMgYSBwb2x5Z29uIG9mZnNldCBvcGVyYXRpb24sIHJldHVybmluZyB0aGUgcmVzdWx0aW5nIFBhdGhzIG9yIHVuZGVmaW5lZCBpZiBmYWlsZWQuXHJcbiAgICpcclxuICAgKiBUaGlzIG1ldGhvZCBlbmNhcHN1bGF0ZXMgdGhlIHByb2Nlc3Mgb2Ygb2Zmc2V0dGluZyAoaW5mbGF0aW5nL2RlZmxhdGluZykgYm90aCBvcGVuIGFuZCBjbG9zZWQgcGF0aHMgdXNpbmcgYSBudW1iZXIgb2YgZGlmZmVyZW50IGpvaW4gdHlwZXNcclxuICAgKiBhbmQgZW5kIHR5cGVzLlxyXG4gICAqXHJcbiAgICogUHJlY29uZGl0aW9ucyBmb3Igb2Zmc2V0dGluZzpcclxuICAgKiAxLiBUaGUgb3JpZW50YXRpb25zIG9mIGNsb3NlZCBwYXRocyBtdXN0IGJlIGNvbnNpc3RlbnQgc3VjaCB0aGF0IG91dGVyIHBvbHlnb25zIHNoYXJlIHRoZSBzYW1lIG9yaWVudGF0aW9uLCBhbmQgYW55IGhvbGVzIGhhdmUgdGhlIG9wcG9zaXRlIG9yaWVudGF0aW9uXHJcbiAgICogKGllIG5vbi16ZXJvIGZpbGxpbmcpLiBPcGVuIHBhdGhzIG11c3QgYmUgb3JpZW50ZWQgd2l0aCBjbG9zZWQgb3V0ZXIgcG9seWdvbnMuXHJcbiAgICogMi4gUG9seWdvbnMgbXVzdCBub3Qgc2VsZi1pbnRlcnNlY3QuXHJcbiAgICpcclxuICAgKiBMaW1pdGF0aW9uczpcclxuICAgKiBXaGVuIG9mZnNldHRpbmcsIHNtYWxsIGFydGVmYWN0cyBtYXkgYXBwZWFyIHdoZXJlIHBvbHlnb25zIG92ZXJsYXAuIFRvIGF2b2lkIHRoZXNlIGFydGVmYWN0cywgb2Zmc2V0IG92ZXJsYXBwaW5nIHBvbHlnb25zIHNlcGFyYXRlbHkuXHJcbiAgICpcclxuICAgKiBAcGFyYW0gcGFyYW1zIC0gb2Zmc2V0IG9wZXJhdGlvbiBwYXJhbXNcclxuICAgKiBAcmV0dXJuIHtQYXRoc3x1bmRlZmluZWR9IC0gdGhlIHJlc3VsdGluZyBQYXRocyBvciB1bmRlZmluZWQgaWYgZmFpbGVkLlxyXG4gICAqL1xyXG4gIG9mZnNldFRvUGF0aHMocGFyYW1zOiBPZmZzZXRQYXJhbXMpOiBQYXRocyB8IHVuZGVmaW5lZCB7XHJcbiAgICByZXR1cm4gb2Zmc2V0VG9QYXRocyh0aGlzLmluc3RhbmNlLCBwYXJhbXMpO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogUGVyZm9ybXMgYSBwb2x5Z29uIG9mZnNldCBvcGVyYXRpb24sIHJldHVybmluZyB0aGUgcmVzdWx0aW5nIFBvbHlUcmVlIG9yIHVuZGVmaW5lZCBpZiBmYWlsZWQuXHJcbiAgICpcclxuICAgKiBUaGlzIG1ldGhvZCBlbmNhcHN1bGF0ZXMgdGhlIHByb2Nlc3Mgb2Ygb2Zmc2V0dGluZyAoaW5mbGF0aW5nL2RlZmxhdGluZykgYm90aCBvcGVuIGFuZCBjbG9zZWQgcGF0aHMgdXNpbmcgYSBudW1iZXIgb2YgZGlmZmVyZW50IGpvaW4gdHlwZXNcclxuICAgKiBhbmQgZW5kIHR5cGVzLlxyXG4gICAqXHJcbiAgICogUHJlY29uZGl0aW9ucyBmb3Igb2Zmc2V0dGluZzpcclxuICAgKiAxLiBUaGUgb3JpZW50YXRpb25zIG9mIGNsb3NlZCBwYXRocyBtdXN0IGJlIGNvbnNpc3RlbnQgc3VjaCB0aGF0IG91dGVyIHBvbHlnb25zIHNoYXJlIHRoZSBzYW1lIG9yaWVudGF0aW9uLCBhbmQgYW55IGhvbGVzIGhhdmUgdGhlIG9wcG9zaXRlIG9yaWVudGF0aW9uXHJcbiAgICogKGllIG5vbi16ZXJvIGZpbGxpbmcpLiBPcGVuIHBhdGhzIG11c3QgYmUgb3JpZW50ZWQgd2l0aCBjbG9zZWQgb3V0ZXIgcG9seWdvbnMuXHJcbiAgICogMi4gUG9seWdvbnMgbXVzdCBub3Qgc2VsZi1pbnRlcnNlY3QuXHJcbiAgICpcclxuICAgKiBMaW1pdGF0aW9uczpcclxuICAgKiBXaGVuIG9mZnNldHRpbmcsIHNtYWxsIGFydGVmYWN0cyBtYXkgYXBwZWFyIHdoZXJlIHBvbHlnb25zIG92ZXJsYXAuIFRvIGF2b2lkIHRoZXNlIGFydGVmYWN0cywgb2Zmc2V0IG92ZXJsYXBwaW5nIHBvbHlnb25zIHNlcGFyYXRlbHkuXHJcbiAgICpcclxuICAgKiBAcGFyYW0gcGFyYW1zIC0gb2Zmc2V0IG9wZXJhdGlvbiBwYXJhbXNcclxuICAgKiBAcmV0dXJuIHtQb2x5VHJlZXx1bmRlZmluZWR9IC0gdGhlIHJlc3VsdGluZyBQb2x5VHJlZSBvciB1bmRlZmluZWQgaWYgZmFpbGVkLlxyXG4gICAqL1xyXG4gIG9mZnNldFRvUG9seVRyZWUocGFyYW1zOiBPZmZzZXRQYXJhbXMpOiBQb2x5VHJlZSB8IHVuZGVmaW5lZCB7XHJcbiAgICByZXR1cm4gb2Zmc2V0VG9Qb2x5VHJlZSh0aGlzLmluc3RhbmNlLCBwYXJhbXMpO1xyXG4gIH1cclxuXHJcbiAgLy9ub2luc3BlY3Rpb24gSlNNZXRob2RDYW5CZVN0YXRpY1xyXG4gIC8qKlxyXG4gICAqIFRoaXMgZnVuY3Rpb24gcmV0dXJucyB0aGUgYXJlYSBvZiB0aGUgc3VwcGxpZWQgcG9seWdvbi4gSXQncyBhc3N1bWVkIHRoYXQgdGhlIHBhdGggaXMgY2xvc2VkIGFuZCBkb2VzIG5vdCBzZWxmLWludGVyc2VjdC4gRGVwZW5kaW5nIG9uIG9yaWVudGF0aW9uLFxyXG4gICAqIHRoaXMgdmFsdWUgbWF5IGJlIHBvc2l0aXZlIG9yIG5lZ2F0aXZlLiBJZiBPcmllbnRhdGlvbiBpcyB0cnVlLCB0aGVuIHRoZSBhcmVhIHdpbGwgYmUgcG9zaXRpdmUgYW5kIGNvbnZlcnNlbHksIGlmIE9yaWVudGF0aW9uIGlzIGZhbHNlLCB0aGVuIHRoZVxyXG4gICAqIGFyZWEgd2lsbCBiZSBuZWdhdGl2ZS5cclxuICAgKlxyXG4gICAqIEBwYXJhbSBwYXRoIC0gVGhlIHBhdGhcclxuICAgKiBAcmV0dXJuIHtudW1iZXJ9IC0gQXJlYVxyXG4gICAqL1xyXG4gIGFyZWEocGF0aDogUmVhZG9ubHlQYXRoKTogbnVtYmVyIHtcclxuICAgIHJldHVybiBmdW5jdGlvbnMuYXJlYShwYXRoKTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIFJlbW92ZXMgdmVydGljZXM6XHJcbiAgICogLSB0aGF0IGpvaW4gY28tbGluZWFyIGVkZ2VzLCBvciBqb2luIGVkZ2VzIHRoYXQgYXJlIGFsbW9zdCBjby1saW5lYXIgKHN1Y2ggdGhhdCBpZiB0aGUgdmVydGV4IHdhcyBtb3ZlZCBubyBtb3JlIHRoYW4gdGhlIHNwZWNpZmllZCBkaXN0YW5jZSB0aGUgZWRnZXNcclxuICAgKiB3b3VsZCBiZSBjby1saW5lYXIpXHJcbiAgICogLSB0aGF0IGFyZSB3aXRoaW4gdGhlIHNwZWNpZmllZCBkaXN0YW5jZSBvZiBhbiBhZGphY2VudCB2ZXJ0ZXhcclxuICAgKiAtIHRoYXQgYXJlIHdpdGhpbiB0aGUgc3BlY2lmaWVkIGRpc3RhbmNlIG9mIGEgc2VtaS1hZGphY2VudCB2ZXJ0ZXggdG9nZXRoZXIgd2l0aCB0aGVpciBvdXQtbHlpbmcgdmVydGljZXNcclxuICAgKlxyXG4gICAqIFZlcnRpY2VzIGFyZSBzZW1pLWFkamFjZW50IHdoZW4gdGhleSBhcmUgc2VwYXJhdGVkIGJ5IGEgc2luZ2xlIChvdXQtbHlpbmcpIHZlcnRleC5cclxuICAgKlxyXG4gICAqIFRoZSBkaXN0YW5jZSBwYXJhbWV0ZXIncyBkZWZhdWx0IHZhbHVlIGlzIGFwcHJveGltYXRlbHkg4oiaMiBzbyB0aGF0IGEgdmVydGV4IHdpbGwgYmUgcmVtb3ZlZCB3aGVuIGFkamFjZW50IG9yIHNlbWktYWRqYWNlbnQgdmVydGljZXMgaGF2aW5nIHRoZWlyXHJcbiAgICogY29ycmVzcG9uZGluZyBYIGFuZCBZIGNvb3JkaW5hdGVzIGRpZmZlcmluZyBieSBubyBtb3JlIHRoYW4gMSB1bml0LiAoSWYgdGhlIGVnZGVzIGFyZSBzZW1pLWFkamFjZW50IHRoZSBvdXQtbHlpbmcgdmVydGV4IHdpbGwgYmUgcmVtb3ZlZCB0b28uKVxyXG4gICAqXHJcbiAgICogQHBhcmFtIHBhdGggLSBUaGUgcGF0aCB0byBjbGVhblxyXG4gICAqIEBwYXJhbSBkaXN0YW5jZSAtIEhvdyBjbG9zZSBwb2ludHMgbmVlZCB0byBiZSBiZWZvcmUgdGhleSBhcmUgY2xlYW5lZFxyXG4gICAqIEByZXR1cm4ge1BhdGh9IC0gVGhlIGNsZWFuZWQgcGF0aFxyXG4gICAqL1xyXG4gIGNsZWFuUG9seWdvbihwYXRoOiBSZWFkb25seVBhdGgsIGRpc3RhbmNlID0gMS4xNDE1KTogUGF0aCB7XHJcbiAgICByZXR1cm4gZnVuY3Rpb25zLmNsZWFuUG9seWdvbih0aGlzLmluc3RhbmNlLCBwYXRoLCBkaXN0YW5jZSk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBSZW1vdmVzIHZlcnRpY2VzOlxyXG4gICAqIC0gdGhhdCBqb2luIGNvLWxpbmVhciBlZGdlcywgb3Igam9pbiBlZGdlcyB0aGF0IGFyZSBhbG1vc3QgY28tbGluZWFyIChzdWNoIHRoYXQgaWYgdGhlIHZlcnRleCB3YXMgbW92ZWQgbm8gbW9yZSB0aGFuIHRoZSBzcGVjaWZpZWQgZGlzdGFuY2UgdGhlIGVkZ2VzXHJcbiAgICogd291bGQgYmUgY28tbGluZWFyKVxyXG4gICAqIC0gdGhhdCBhcmUgd2l0aGluIHRoZSBzcGVjaWZpZWQgZGlzdGFuY2Ugb2YgYW4gYWRqYWNlbnQgdmVydGV4XHJcbiAgICogLSB0aGF0IGFyZSB3aXRoaW4gdGhlIHNwZWNpZmllZCBkaXN0YW5jZSBvZiBhIHNlbWktYWRqYWNlbnQgdmVydGV4IHRvZ2V0aGVyIHdpdGggdGhlaXIgb3V0LWx5aW5nIHZlcnRpY2VzXHJcbiAgICpcclxuICAgKiBWZXJ0aWNlcyBhcmUgc2VtaS1hZGphY2VudCB3aGVuIHRoZXkgYXJlIHNlcGFyYXRlZCBieSBhIHNpbmdsZSAob3V0LWx5aW5nKSB2ZXJ0ZXguXHJcbiAgICpcclxuICAgKiBUaGUgZGlzdGFuY2UgcGFyYW1ldGVyJ3MgZGVmYXVsdCB2YWx1ZSBpcyBhcHByb3hpbWF0ZWx5IOKImjIgc28gdGhhdCBhIHZlcnRleCB3aWxsIGJlIHJlbW92ZWQgd2hlbiBhZGphY2VudCBvciBzZW1pLWFkamFjZW50IHZlcnRpY2VzIGhhdmluZyB0aGVpclxyXG4gICAqIGNvcnJlc3BvbmRpbmcgWCBhbmQgWSBjb29yZGluYXRlcyBkaWZmZXJpbmcgYnkgbm8gbW9yZSB0aGFuIDEgdW5pdC4gKElmIHRoZSBlZ2RlcyBhcmUgc2VtaS1hZGphY2VudCB0aGUgb3V0LWx5aW5nIHZlcnRleCB3aWxsIGJlIHJlbW92ZWQgdG9vLilcclxuICAgKlxyXG4gICAqIEBwYXJhbSBwYXRocyAtIFRoZSBwYXRocyB0byBjbGVhblxyXG4gICAqIEBwYXJhbSBkaXN0YW5jZSAtIEhvdyBjbG9zZSBwb2ludH