UNPKG

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