kepler.gl
Version:
kepler.gl is a webgl based application to visualize large scale location data in the browser
470 lines (455 loc) • 60.6 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.COLORMAP_TEXTURE_PARAMETERS = void 0;
exports.generateCategoricalColormapTexture = generateCategoricalColormapTexture;
exports.getCombineBandsModule = getCombineBandsModule;
exports.getImageMaskModule = getImageMaskModule;
exports.getModules = getModules;
exports.loadImage = loadImage;
exports.loadNpyArray = loadNpyArray;
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _core = require("@loaders.gl/core");
var _images = require("@loaders.gl/images");
var _textures = require("@loaders.gl/textures");
var _constants = _interopRequireDefault(require("@luma.gl/constants"));
var _constants2 = require("@kepler.gl/constants");
var _deckglLayers = require("@kepler.gl/deckgl-layers");
var _rasterTileUtils = require("./raster-tile-utils");
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2["default"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } // SPDX-License-Identifier: MIT
// Copyright contributors to the kepler.gl project
/**
* Functions and constants for handling webgl/luma.gl/deck.gl entities
*/
var combineBandsFloat = _deckglLayers.RasterWebGL.combineBandsFloat,
combineBandsInt = _deckglLayers.RasterWebGL.combineBandsInt,
combineBandsUint = _deckglLayers.RasterWebGL.combineBandsUint,
maskFloat = _deckglLayers.RasterWebGL.maskFloat,
maskInt = _deckglLayers.RasterWebGL.maskInt,
maskUint = _deckglLayers.RasterWebGL.maskUint,
linearRescale = _deckglLayers.RasterWebGL.linearRescale,
gammaContrast = _deckglLayers.RasterWebGL.gammaContrast,
sigmoidalContrast = _deckglLayers.RasterWebGL.sigmoidalContrast,
normalizedDifference = _deckglLayers.RasterWebGL.normalizedDifference,
enhancedVegetationIndex = _deckglLayers.RasterWebGL.enhancedVegetationIndex,
soilAdjustedVegetationIndex = _deckglLayers.RasterWebGL.soilAdjustedVegetationIndex,
modifiedSoilAdjustedVegetationIndex = _deckglLayers.RasterWebGL.modifiedSoilAdjustedVegetationIndex,
colormapModule = _deckglLayers.RasterWebGL.colormap,
filter = _deckglLayers.RasterWebGL.filter,
saturation = _deckglLayers.RasterWebGL.saturation,
reorderBands = _deckglLayers.RasterWebGL.reorderBands,
rgbaImage = _deckglLayers.RasterWebGL.rgbaImage;
/**
* Describe WebGL2 Texture parameters to use for given input data type
*/
/**
* Convert TypedArray to WebGL2 Texture Parameters
*/
function getWebGL2TextureParameters(data) {
if (data instanceof Uint8Array || data instanceof Uint8ClampedArray) {
return {
// Note: texture data has no auto-rescaling; pixel values stay as 0-255
format: _constants["default"].R8UI,
dataFormat: _constants["default"].RED_INTEGER,
type: _constants["default"].UNSIGNED_BYTE
};
}
if (data instanceof Uint16Array) {
return {
format: _constants["default"].R16UI,
dataFormat: _constants["default"].RED_INTEGER,
type: _constants["default"].UNSIGNED_SHORT
};
}
if (data instanceof Uint32Array) {
return {
format: _constants["default"].R32UI,
dataFormat: _constants["default"].RED_INTEGER,
type: _constants["default"].UNSIGNED_INT
};
}
if (data instanceof Int8Array) {
return {
format: _constants["default"].R8I,
dataFormat: _constants["default"].RED_INTEGER,
type: _constants["default"].BYTE
};
}
if (data instanceof Int16Array) {
return {
format: _constants["default"].R16I,
dataFormat: _constants["default"].RED_INTEGER,
type: _constants["default"].SHORT
};
}
if (data instanceof Int32Array) {
return {
format: _constants["default"].R32I,
dataFormat: _constants["default"].RED_INTEGER,
type: _constants["default"].INT
};
}
if (data instanceof Float32Array) {
return {
format: _constants["default"].R32F,
dataFormat: _constants["default"].RED,
type: _constants["default"].FLOAT
};
}
if (data instanceof Float64Array) {
return {
format: _constants["default"].R32F,
dataFormat: _constants["default"].RED,
type: _constants["default"].FLOAT
};
}
// For exhaustive check above; following should never occur
// https://stackoverflow.com/a/58009992
var unexpectedInput = data;
throw new Error(unexpectedInput);
}
/**
* Discrete-valued colormaps (e.g. from the output of
* classification algorithms) in the raster layer. Previously, the values passed to
* `TEXTURE_MIN_FILTER` and `TEXTURE_MAG_FILTER` were `GL.LINEAR`, which meant that the GPU would
* linearly interpolate values between two neighboring colormap pixel values. Setting these values
* to NEAREST means that the GPU will choose the nearest value on the texture2D lookup operation,
* which fixes precision issues for discrete-valued colormaps. This should be ok for continuous
* colormaps as long as the color difference between each pixel on the colormap is small.
*/
var COLORMAP_TEXTURE_PARAMETERS = exports.COLORMAP_TEXTURE_PARAMETERS = (0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])({}, _constants["default"].TEXTURE_MIN_FILTER, _constants["default"].NEAREST), _constants["default"].TEXTURE_MAG_FILTER, _constants["default"].NEAREST), _constants["default"].TEXTURE_WRAP_S, _constants["default"].CLAMP_TO_EDGE), _constants["default"].TEXTURE_WRAP_T, _constants["default"].CLAMP_TO_EDGE);
var DEFAULT_8BIT_TEXTURE_PARAMETERS = (0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])({}, _constants["default"].TEXTURE_MIN_FILTER, _constants["default"].LINEAR_MIPMAP_LINEAR), _constants["default"].TEXTURE_MAG_FILTER, _constants["default"].LINEAR), _constants["default"].TEXTURE_WRAP_S, _constants["default"].CLAMP_TO_EDGE), _constants["default"].TEXTURE_WRAP_T, _constants["default"].CLAMP_TO_EDGE);
var DEFAULT_HIGH_BIT_TEXTURE_PARAMETERS = (0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])({}, _constants["default"].TEXTURE_MIN_FILTER, _constants["default"].NEAREST), _constants["default"].TEXTURE_MAG_FILTER, _constants["default"].NEAREST), _constants["default"].TEXTURE_WRAP_S, _constants["default"].CLAMP_TO_EDGE), _constants["default"].TEXTURE_WRAP_T, _constants["default"].CLAMP_TO_EDGE);
/**
* Select correct module type for "combineBands"
*
* combineBands joins up to four 2D arrays (contained in imageBands) into a single "rgba" image
* texture on the GPU. That shader code needs to have the same data type as the actual image data.
* E.g. for float data the texture needs to be `sampler2D`, for uint data the texture needs to be
* `usampler2D` and for int data the texture needs to be `isampler2D`.
*/
function getCombineBandsModule(imageBands) {
// Each image array is expected/required to be of the same data type
switch (imageBands[0].format) {
case _constants["default"].R8UI:
return combineBandsUint;
case _constants["default"].R16UI:
return combineBandsUint;
case _constants["default"].R32UI:
return combineBandsUint;
case _constants["default"].R8I:
return combineBandsInt;
case _constants["default"].R16I:
return combineBandsInt;
case _constants["default"].R32I:
return combineBandsInt;
case _constants["default"].R32F:
return combineBandsFloat;
default:
throw new Error('bad format');
}
}
/** Select correct image masking shader module for mask data type
* The imageMask could (at least in the future, theoretically) be of a different data format than
* the imageBands data itself.
*/
function getImageMaskModule(imageMask) {
switch (imageMask.format) {
case _constants["default"].R8UI:
return maskUint;
case _constants["default"].R16UI:
return maskUint;
case _constants["default"].R32UI:
return maskUint;
case _constants["default"].R8I:
return maskInt;
case _constants["default"].R16I:
return maskInt;
case _constants["default"].R32I:
return maskInt;
case _constants["default"].R32F:
return maskFloat;
default:
throw new Error('bad format');
}
}
/**
* Load image and wrap with default WebGL texture parameters
*
* @param url URL to load image
* @param textureParams parameters to pass to Texture2D
*
* @return image object to pass to Texture2D constructor
*/
function loadImage(_x) {
return _loadImage.apply(this, arguments);
}
function _loadImage() {
_loadImage = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(url) {
var textureParams,
requestOptions,
response,
image,
_args = arguments;
return _regenerator["default"].wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
textureParams = _args.length > 1 && _args[1] !== undefined ? _args[1] : {};
requestOptions = _args.length > 2 && _args[2] !== undefined ? _args[2] : {};
_context.next = 4;
return (0, _core.fetchFile)(url, requestOptions);
case 4:
response = _context.sent;
_context.next = 7;
return (0, _core.parse)(response, _images.ImageLoader);
case 7:
image = _context.sent;
return _context.abrupt("return", _objectSpread({
data: image,
parameters: DEFAULT_8BIT_TEXTURE_PARAMETERS,
format: _constants["default"].RGB
}, textureParams));
case 9:
case "end":
return _context.stop();
}
}, _callee);
}));
return _loadImage.apply(this, arguments);
}
/**
* Load NPY Array
*
* The NPY format is described here: https://numpy.org/doc/stable/reference/generated/numpy.lib.format.html.
* It's designed to be a very simple file format to hold an N-dimensional block of data. The header describes the data type, shape, and order (either C or Fortran) of the array.
*
* @param url URL to load NPY Array
* @param split Whether to split single typed array representing an N-dimensional array into an Array with each dimension as its own typed array
*
* @return image object to pass to Texture2D constructor
*/
function loadNpyArray(_x2, _x3, _x4) {
return _loadNpyArray.apply(this, arguments);
}
/**
* Create texture data for categorical colormap scale
* @param categoricalOptions - color map configuration and min-max values of categorical band
* @returns texture data
*/
function _loadNpyArray() {
_loadNpyArray = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2(request, split, options) {
var _request$options$sign, _getLoaderOptions, npyOptions, response, data, header, shape, _getWebGL2TexturePara, format, dataFormat, type, _shape, z, height, width, mipmaps, parameters, channels, channelSize, i;
return _regenerator["default"].wrap(function _callee2$(_context2) {
while (1) switch (_context2.prev = _context2.next) {
case 0:
_context2.prev = 0;
_getLoaderOptions = (0, _constants2.getLoaderOptions)(), npyOptions = _getLoaderOptions.npy;
_context2.next = 4;
return (0, _core.load)(request.url, _textures.NPYLoader, {
npy: npyOptions,
fetch: options === null || options === void 0 ? void 0 : options.fetch
});
case 4:
response = _context2.sent;
if (!(!response || !response.data || (_request$options$sign = request.options.signal) !== null && _request$options$sign !== void 0 && _request$options$sign.aborted)) {
_context2.next = 7;
break;
}
return _context2.abrupt("return", null);
case 7:
// Float64 data needs to be coerced to Float32 for the GPU
if (response.data instanceof Float64Array) {
response.data = Float32Array.from(response.data);
}
data = response.data, header = response.header;
shape = header.shape;
_getWebGL2TexturePara = getWebGL2TextureParameters(data), format = _getWebGL2TexturePara.format, dataFormat = _getWebGL2TexturePara.dataFormat, type = _getWebGL2TexturePara.type; // TODO: check height-width or width-height
// Regardless, images usually square
// TODO: handle cases of 256x256x1 instead of 1x256x256
_shape = (0, _slicedToArray2["default"])(shape, 3), z = _shape[0], height = _shape[1], width = _shape[2]; // Since we now use WebGL2 data types for 8-bit textures, we set the following for all textures
mipmaps = false;
parameters = DEFAULT_HIGH_BIT_TEXTURE_PARAMETERS;
if (split) {
_context2.next = 16;
break;
}
return _context2.abrupt("return", {
data: data,
width: width,
height: height,
format: format,
dataFormat: dataFormat,
type: type,
parameters: parameters,
mipmaps: mipmaps
});
case 16:
// Split into individual arrays
channels = [];
channelSize = height * width;
for (i = 0; i < z; i++) {
channels.push({
data: data.subarray(i * channelSize, (i + 1) * channelSize),
width: width,
height: height,
format: format,
dataFormat: dataFormat,
type: type,
parameters: parameters,
mipmaps: mipmaps
});
}
return _context2.abrupt("return", channels);
case 22:
_context2.prev = 22;
_context2.t0 = _context2["catch"](0);
return _context2.abrupt("return", null);
case 25:
case "end":
return _context2.stop();
}
}, _callee2, null, [[0, 22]]);
}));
return _loadNpyArray.apply(this, arguments);
}
function generateCategoricalColormapTexture(categoricalOptions) {
var data = (0, _rasterTileUtils.generateCategoricalBitmapArray)(categoricalOptions);
return {
data: data,
width: _rasterTileUtils.CATEGORICAL_TEXTURE_WIDTH,
height: 1,
format: _constants["default"].RGBA,
dataFormat: _constants["default"].RGBA,
type: _constants["default"].UNSIGNED_BYTE,
parameters: COLORMAP_TEXTURE_PARAMETERS,
mipmaps: false
};
}
// TODO: would probably be simpler to only pass in the props actually used by this function. That
// would mean a smaller object than RenderSubLayersProps
// eslint-disable-next-line max-statements, complexity
function getModules(_ref) {
var images = _ref.images,
props = _ref.props;
var moduleProps = {};
// Array of luma.gl WebGL modules to pass to the RasterLayer
var modules = [];
// use rgba image directly. Used for raster .pmtiles rendering
if (images.imageRgba) {
modules.push(rgbaImage);
// no support for other modules atm for direct rgba mode
return {
modules: modules,
moduleProps: moduleProps
};
}
if (!props) {
return {
modules: modules,
moduleProps: moduleProps
};
}
var renderBandIndexes = props.renderBandIndexes,
nonLinearRescaling = props.nonLinearRescaling,
linearRescalingFactor = props.linearRescalingFactor,
minPixelValue = props.minPixelValue,
maxPixelValue = props.maxPixelValue,
gammaContrastFactor = props.gammaContrastFactor,
sigmoidalContrastFactor = props.sigmoidalContrastFactor,
sigmoidalBiasFactor = props.sigmoidalBiasFactor,
saturationValue = props.saturationValue,
bandCombination = props.bandCombination,
filterEnabled = props.filterEnabled,
filterRange = props.filterRange,
dataType = props.dataType,
minCategoricalBandValue = props.minCategoricalBandValue,
maxCategoricalBandValue = props.maxCategoricalBandValue,
hasCategoricalColorMap = props.hasCategoricalColorMap;
if (Array.isArray(images.imageBands) && images.imageBands.length > 0) {
modules.push(getCombineBandsModule(images.imageBands));
}
if (images.imageMask) {
modules.push(getImageMaskModule(images.imageMask));
// In general, data masks are 0 for nodata and the maximum value for valid data, e.g. 255 or
// 65535 for uint8 or uint16 data, respectively
moduleProps.maskKeepMin = 1;
}
if (Array.isArray(renderBandIndexes)) {
modules.push(reorderBands);
moduleProps.ordering = renderBandIndexes;
}
var globalRange = maxPixelValue - minPixelValue;
// Fix rescaling if we are sure that dataset is categorical
if (hasCategoricalColorMap) {
modules.push(linearRescale);
moduleProps.linearRescaleScaler = 1 / maxPixelValue;
moduleProps.linearRescaleOffset = 0;
} else if ((0, _rasterTileUtils.isRescalingAllowed)(bandCombination)) {
if (!nonLinearRescaling) {
var _linearRescalingFacto = (0, _slicedToArray2["default"])(linearRescalingFactor, 2),
min = _linearRescalingFacto[0],
max = _linearRescalingFacto[1];
var localRange = max - min;
// Add linear rescaling module
modules.push(linearRescale);
// Divide by local range * global range
moduleProps.linearRescaleScaler = 1 / (localRange * globalRange);
// Subtract off the local min
moduleProps.linearRescaleOffset = -min;
// Clamp to [0, 1] done automatically?
} else {
modules.push(linearRescale);
moduleProps.linearRescaleScaler = 1 / maxPixelValue;
moduleProps.linearRescaleOffset = 0;
modules.push(gammaContrast);
moduleProps.gammaContrastValue = gammaContrastFactor;
modules.push(sigmoidalContrast);
moduleProps.sigmoidalContrast = sigmoidalContrastFactor;
moduleProps.sigmoidalBias = sigmoidalBiasFactor;
}
if (Number.isFinite(saturationValue) && saturationValue !== 1) {
modules.push(saturation);
moduleProps.saturationValue = saturationValue;
}
}
switch (bandCombination) {
case 'normalizedDifference':
modules.push(normalizedDifference);
break;
case 'enhancedVegetationIndex':
modules.push(enhancedVegetationIndex);
break;
case 'soilAdjustedVegetationIndex':
modules.push(soilAdjustedVegetationIndex);
break;
case 'modifiedSoilAdjustedVegetationIndex':
modules.push(modifiedSoilAdjustedVegetationIndex);
break;
default:
break;
}
if ((0, _rasterTileUtils.isFilterAllowed)(bandCombination) && filterEnabled) {
modules.push(filter);
moduleProps.filterMin1 = filterRange[0];
moduleProps.filterMax1 = filterRange[1];
}
// Apply colormap
if ((0, _rasterTileUtils.isColormapAllowed)(bandCombination) && images.imageColormap) {
modules.push(colormapModule);
moduleProps.minCategoricalBandValue = minCategoricalBandValue;
moduleProps.maxCategoricalBandValue = maxCategoricalBandValue;
moduleProps.dataTypeMaxValue = _rasterTileUtils.dtypeMaxValue[dataType];
moduleProps.maxPixelValue = maxPixelValue;
}
return {
modules: modules,
moduleProps: moduleProps
};
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfY29yZSIsInJlcXVpcmUiLCJfaW1hZ2VzIiwiX3RleHR1cmVzIiwiX2NvbnN0YW50cyIsIl9pbnRlcm9wUmVxdWlyZURlZmF1bHQiLCJfY29uc3RhbnRzMiIsIl9kZWNrZ2xMYXllcnMiLCJfcmFzdGVyVGlsZVV0aWxzIiwib3duS2V5cyIsImUiLCJyIiwidCIsIk9iamVjdCIsImtleXMiLCJnZXRPd25Qcm9wZXJ0eVN5bWJvbHMiLCJvIiwiZmlsdGVyIiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yIiwiZW51bWVyYWJsZSIsInB1c2giLCJhcHBseSIsIl9vYmplY3RTcHJlYWQiLCJhcmd1bWVudHMiLCJsZW5ndGgiLCJmb3JFYWNoIiwiX2RlZmluZVByb3BlcnR5MiIsImdldE93blByb3BlcnR5RGVzY3JpcHRvcnMiLCJkZWZpbmVQcm9wZXJ0aWVzIiwiZGVmaW5lUHJvcGVydHkiLCJjb21iaW5lQmFuZHNGbG9hdCIsIlJhc3RlcldlYkdMIiwiY29tYmluZUJhbmRzSW50IiwiY29tYmluZUJhbmRzVWludCIsIm1hc2tGbG9hdCIsIm1hc2tJbnQiLCJtYXNrVWludCIsImxpbmVhclJlc2NhbGUiLCJnYW1tYUNvbnRyYXN0Iiwic2lnbW9pZGFsQ29udHJhc3QiLCJub3JtYWxpemVkRGlmZmVyZW5jZSIsImVuaGFuY2VkVmVnZXRhdGlvbkluZGV4Iiwic29pbEFkanVzdGVkVmVnZXRhdGlvbkluZGV4IiwibW9kaWZpZWRTb2lsQWRqdXN0ZWRWZWdldGF0aW9uSW5kZXgiLCJjb2xvcm1hcE1vZHVsZSIsImNvbG9ybWFwIiwic2F0dXJhdGlvbiIsInJlb3JkZXJCYW5kcyIsInJnYmFJbWFnZSIsImdldFdlYkdMMlRleHR1cmVQYXJhbWV0ZXJzIiwiZGF0YSIsIlVpbnQ4QXJyYXkiLCJVaW50OENsYW1wZWRBcnJheSIsImZvcm1hdCIsIkdMIiwiUjhVSSIsImRhdGFGb3JtYXQiLCJSRURfSU5URUdFUiIsInR5cGUiLCJVTlNJR05FRF9CWVRFIiwiVWludDE2QXJyYXkiLCJSMTZVSSIsIlVOU0lHTkVEX1NIT1JUIiwiVWludDMyQXJyYXkiLCJSMzJVSSIsIlVOU0lHTkVEX0lOVCIsIkludDhBcnJheSIsIlI4SSIsIkJZVEUiLCJJbnQxNkFycmF5IiwiUjE2SSIsIlNIT1JUIiwiSW50MzJBcnJheSIsIlIzMkkiLCJJTlQiLCJGbG9hdDMyQXJyYXkiLCJSMzJGIiwiUkVEIiwiRkxPQVQiLCJGbG9hdDY0QXJyYXkiLCJ1bmV4cGVjdGVkSW5wdXQiLCJFcnJvciIsIkNPTE9STUFQX1RFWFRVUkVfUEFSQU1FVEVSUyIsImV4cG9ydHMiLCJURVhUVVJFX01JTl9GSUxURVIiLCJORUFSRVNUIiwiVEVYVFVSRV9NQUdfRklMVEVSIiwiVEVYVFVSRV9XUkFQX1MiLCJDTEFNUF9UT19FREdFIiwiVEVYVFVSRV9XUkFQX1QiLCJERUZBVUxUXzhCSVRfVEVYVFVSRV9QQVJBTUVURVJTIiwiTElORUFSX01JUE1BUF9MSU5FQVIiLCJMSU5FQVIiLCJERUZBVUxUX0hJR0hfQklUX1RFWFRVUkVfUEFSQU1FVEVSUyIsImdldENvbWJpbmVCYW5kc01vZHVsZSIsImltYWdlQmFuZHMiLCJnZXRJbWFnZU1hc2tNb2R1bGUiLCJpbWFnZU1hc2siLCJsb2FkSW1hZ2UiLCJfeCIsIl9sb2FkSW1hZ2UiLCJfYXN5bmNUb0dlbmVyYXRvcjIiLCJfcmVnZW5lcmF0b3IiLCJtYXJrIiwiX2NhbGxlZSIsInVybCIsInRleHR1cmVQYXJhbXMiLCJyZXF1ZXN0T3B0aW9ucyIsInJlc3BvbnNlIiwiaW1hZ2UiLCJfYXJncyIsIndyYXAiLCJfY2FsbGVlJCIsIl9jb250ZXh0IiwicHJldiIsIm5leHQiLCJ1bmRlZmluZWQiLCJmZXRjaEZpbGUiLCJzZW50IiwicGFyc2UiLCJJbWFnZUxvYWRlciIsImFicnVwdCIsInBhcmFtZXRlcnMiLCJSR0IiLCJzdG9wIiwibG9hZE5weUFycmF5IiwiX3gyIiwiX3gzIiwiX3g0IiwiX2xvYWROcHlBcnJheSIsIl9jYWxsZWUyIiwicmVxdWVzdCIsInNwbGl0Iiwib3B0aW9ucyIsIl9yZXF1ZXN0JG9wdGlvbnMkc2lnbiIsIl9nZXRMb2FkZXJPcHRpb25zIiwibnB5T3B0aW9ucyIsImhlYWRlciIsInNoYXBlIiwiX2dldFdlYkdMMlRleHR1cmVQYXJhIiwiX3NoYXBlIiwieiIsImhlaWdodCIsIndpZHRoIiwibWlwbWFwcyIsImNoYW5uZWxzIiwiY2hhbm5lbFNpemUiLCJpIiwiX2NhbGxlZTIkIiwiX2NvbnRleHQyIiwiZ2V0TG9hZGVyT3B0aW9ucyIsIm5weSIsImxvYWQiLCJOUFlMb2FkZXIiLCJmZXRjaCIsInNpZ25hbCIsImFib3J0ZWQiLCJmcm9tIiwiX3NsaWNlZFRvQXJyYXkyIiwic3ViYXJyYXkiLCJ0MCIsImdlbmVyYXRlQ2F0ZWdvcmljYWxDb2xvcm1hcFRleHR1cmUiLCJjYXRlZ29yaWNhbE9wdGlvbnMiLCJnZW5lcmF0ZUNhdGVnb3JpY2FsQml0bWFwQXJyYXkiLCJDQVRFR09SSUNBTF9URVhUVVJFX1dJRFRIIiwiUkdCQSIsImdldE1vZHVsZXMiLCJfcmVmIiwiaW1hZ2VzIiwicHJvcHMiLCJtb2R1bGVQcm9wcyIsIm1vZHVsZXMiLCJpbWFnZVJnYmEiLCJyZW5kZXJCYW5kSW5kZXhlcyIsIm5vbkxpbmVhclJlc2NhbGluZyIsImxpbmVhclJlc2NhbGluZ0ZhY3RvciIsIm1pblBpeGVsVmFsdWUiLCJtYXhQaXhlbFZhbHVlIiwiZ2FtbWFDb250cmFzdEZhY3RvciIsInNpZ21vaWRhbENvbnRyYXN0RmFjdG9yIiwic2lnbW9pZGFsQmlhc0ZhY3RvciIsInNhdHVyYXRpb25WYWx1ZSIsImJhbmRDb21iaW5hdGlvbiIsImZpbHRlckVuYWJsZWQiLCJmaWx0ZXJSYW5nZSIsImRhdGFUeXBlIiwibWluQ2F0ZWdvcmljYWxCYW5kVmFsdWUiLCJtYXhDYXRlZ29yaWNhbEJhbmRWYWx1ZSIsImhhc0NhdGVnb3JpY2FsQ29sb3JNYXAiLCJBcnJheSIsImlzQXJyYXkiLCJtYXNrS2VlcE1pbiIsIm9yZGVyaW5nIiwiZ2xvYmFsUmFuZ2UiLCJsaW5lYXJSZXNjYWxlU2NhbGVyIiwibGluZWFyUmVzY2FsZU9mZnNldCIsImlzUmVzY2FsaW5nQWxsb3dlZCIsIl9saW5lYXJSZXNjYWxpbmdGYWN0byIsIm1pbiIsIm1heCIsImxvY2FsUmFuZ2UiLCJnYW1tYUNvbnRyYXN0VmFsdWUiLCJzaWdtb2lkYWxCaWFzIiwiTnVtYmVyIiwiaXNGaW5pdGUiLCJpc0ZpbHRlckFsbG93ZWQiLCJmaWx0ZXJNaW4xIiwiZmlsdGVyTWF4MSIsImlzQ29sb3JtYXBBbGxvd2VkIiwiaW1hZ2VDb2xvcm1hcCIsImRhdGFUeXBlTWF4VmFsdWUiLCJkdHlwZU1heFZhbHVlIl0sInNvdXJjZXMiOlsiLi4vLi4vc3JjL3Jhc3Rlci10aWxlL2dwdS11dGlscy50cyJdLCJzb3VyY2VzQ29udGVudCI6WyIvLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogTUlUXG4vLyBDb3B5cmlnaHQgY29udHJpYnV0b3JzIHRvIHRoZSBrZXBsZXIuZ2wgcHJvamVjdFxuXG4vKipcbiAqIEZ1bmN0aW9ucyBhbmQgY29uc3RhbnRzIGZvciBoYW5kbGluZyB3ZWJnbC9sdW1hLmdsL2RlY2suZ2wgZW50aXRpZXNcbiAqL1xuXG5pbXBvcnQge3BhcnNlLCBmZXRjaEZpbGUsIGxvYWR9IGZyb20gJ0Bsb2FkZXJzLmdsL2NvcmUnO1xuaW1wb3J0IHtJbWFnZUxvYWRlcn0gZnJvbSAnQGxvYWRlcnMuZ2wvaW1hZ2VzJztcbmltcG9ydCB7TlBZTG9hZGVyfSBmcm9tICdAbG9hZGVycy5nbC90ZXh0dXJlcyc7XG5pbXBvcnQgR0wgZnJvbSAnQGx1bWEuZ2wvY29uc3RhbnRzJztcbmltcG9ydCB7VGV4dHVyZTJEUHJvcHN9IGZyb20gJ0BsdW1hLmdsL3dlYmdsJztcblxuaW1wb3J0IHtnZXRMb2FkZXJPcHRpb25zfSBmcm9tICdAa2VwbGVyLmdsL2NvbnN0YW50cyc7XG5pbXBvcnQge1Jhc3RlcldlYkdMfSBmcm9tICdAa2VwbGVyLmdsL2RlY2tnbC1sYXllcnMnO1xuXG50eXBlIFNoYWRlck1vZHVsZSA9IFJhc3RlcldlYkdMLlNoYWRlck1vZHVsZTtcbmNvbnN0IHtcbiAgY29tYmluZUJhbmRzRmxvYXQsXG4gIGNvbWJpbmVCYW5kc0ludCxcbiAgY29tYmluZUJhbmRzVWludCxcbiAgbWFza0Zsb2F0LFxuICBtYXNrSW50LFxuICBtYXNrVWludCxcbiAgbGluZWFyUmVzY2FsZSxcbiAgZ2FtbWFDb250cmFzdCxcbiAgc2lnbW9pZGFsQ29udHJhc3QsXG4gIG5vcm1hbGl6ZWREaWZmZXJlbmNlLFxuICBlbmhhbmNlZFZlZ2V0YXRpb25JbmRleCxcbiAgc29pbEFkanVzdGVkVmVnZXRhdGlvbkluZGV4LFxuICBtb2RpZmllZFNvaWxBZGp1c3RlZFZlZ2V0YXRpb25JbmRleCxcbiAgY29sb3JtYXA6IGNvbG9ybWFwTW9kdWxlLFxuICBmaWx0ZXIsXG4gIHNhdHVyYXRpb24sXG4gIHJlb3JkZXJCYW5kcyxcbiAgcmdiYUltYWdlXG59ID0gUmFzdGVyV2ViR0w7XG5cbmltcG9ydCB7XG4gIENBVEVHT1JJQ0FMX1RFWFRVUkVfV0lEVEgsXG4gIGR0eXBlTWF4VmFsdWUsXG4gIGdlbmVyYXRlQ2F0ZWdvcmljYWxCaXRtYXBBcnJheSxcbiAgaXNDb2xvcm1hcEFsbG93ZWQsXG4gIGlzRmlsdGVyQWxsb3dlZCxcbiAgaXNSZXNjYWxpbmdBbGxvd2VkXG59IGZyb20gJy4vcmFzdGVyLXRpbGUtdXRpbHMnO1xuaW1wb3J0IHtcbiAgQ2F0ZWdvcmljYWxDb2xvcm1hcE9wdGlvbnMsXG4gIEltYWdlRGF0YSxcbiAgTlBZTG9hZGVyRGF0YVR5cGVzLFxuICBOUFlMb2FkZXJSZXNwb25zZSxcbiAgUmVuZGVyU3ViTGF5ZXJzUHJvcHNcbn0gZnJvbSAnLi90eXBlcyc7XG5cbi8qKlxuICogRGVzY3JpYmUgV2ViR0wyIFRleHR1cmUgcGFyYW1ldGVycyB0byB1c2UgZm9yIGdpdmVuIGlucHV0IGRhdGEgdHlwZVxuICovXG5pbnRlcmZhY2UgV2ViR0xUZXh0dXJlRm9ybWF0IHtcbiAgZm9ybWF0OiBudW1iZXI7XG4gIGRhdGFGb3JtYXQ6IG51bWJlcjtcbiAgdHlwZTogbnVtYmVyO1xufVxuXG4vKipcbiAqIENvbnZlcnQgVHlwZWRBcnJheSB0byBXZWJHTDIgVGV4dHVyZSBQYXJhbWV0ZXJzXG4gKi9cbmZ1bmN0aW9uIGdldFdlYkdMMlRleHR1cmVQYXJhbWV0ZXJzKGRhdGE6IE5QWUxvYWRlckRhdGFUeXBlcyk6IFdlYkdMVGV4dHVyZUZvcm1hdCB8IG5ldmVyIHtcbiAgaWYgKGRhdGEgaW5zdGFuY2VvZiBVaW50OEFycmF5IHx8IGRhdGEgaW5zdGFuY2VvZiBVaW50OENsYW1wZWRBcnJheSkge1xuICAgIHJldHVybiB7XG4gICAgICAvLyBOb3RlOiB0ZXh0dXJlIGRhdGEgaGFzIG5vIGF1dG8tcmVzY2FsaW5nOyBwaXhlbCB2YWx1ZXMgc3RheSBhcyAwLTI1NVxuICAgICAgZm9ybWF0OiBHTC5SOFVJLFxuICAgICAgZGF0YUZvcm1hdDogR0wuUkVEX0lOVEVHRVIsXG4gICAgICB0eXBlOiBHTC5VTlNJR05FRF9CWVRFXG4gICAgfTtcbiAgfVxuXG4gIGlmIChkYXRhIGluc3RhbmNlb2YgVWludDE2QXJyYXkpIHtcbiAgICByZXR1cm4ge1xuICAgICAgZm9ybWF0OiBHTC5SMTZVSSxcbiAgICAgIGRhdGFGb3JtYXQ6IEdMLlJFRF9JTlRFR0VSLFxuICAgICAgdHlwZTogR0wuVU5TSUdORURfU0hPUlRcbiAgICB9O1xuICB9XG5cbiAgaWYgKGRhdGEgaW5zdGFuY2VvZiBVaW50MzJBcnJheSkge1xuICAgIHJldHVybiB7XG4gICAgICBmb3JtYXQ6IEdMLlIzMlVJLFxuICAgICAgZGF0YUZvcm1hdDogR0wuUkVEX0lOVEVHRVIsXG4gICAgICB0eXBlOiBHTC5VTlNJR05FRF9JTlRcbiAgICB9O1xuICB9XG5cbiAgaWYgKGRhdGEgaW5zdGFuY2VvZiBJbnQ4QXJyYXkpIHtcbiAgICByZXR1cm4ge1xuICAgICAgZm9ybWF0OiBHTC5SOEksXG4gICAgICBkYXRhRm9ybWF0OiBHTC5SRURfSU5URUdFUixcbiAgICAgIHR5cGU6IEdMLkJZVEVcbiAgICB9O1xuICB9XG5cbiAgaWYgKGRhdGEgaW5zdGFuY2VvZiBJbnQxNkFycmF5KSB7XG4gICAgcmV0dXJuIHtcbiAgICAgIGZvcm1hdDogR0wuUjE2SSxcbiAgICAgIGRhdGFGb3JtYXQ6IEdMLlJFRF9JTlRFR0VSLFxuICAgICAgdHlwZTogR0wuU0hPUlRcbiAgICB9O1xuICB9XG4gIGlmIChkYXRhIGluc3RhbmNlb2YgSW50MzJBcnJheSkge1xuICAgIHJldHVybiB7XG4gICAgICBmb3JtYXQ6IEdMLlIzMkksXG4gICAgICBkYXRhRm9ybWF0OiBHTC5SRURfSU5URUdFUixcbiAgICAgIHR5cGU6IEdMLklOVFxuICAgIH07XG4gIH1cbiAgaWYgKGRhdGEgaW5zdGFuY2VvZiBGbG9hdDMyQXJyYXkpIHtcbiAgICByZXR1cm4ge1xuICAgICAgZm9ybWF0OiBHTC5SMzJGLFxuICAgICAgZGF0YUZvcm1hdDogR0wuUkVELFxuICAgICAgdHlwZTogR0wuRkxPQVRcbiAgICB9O1xuICB9XG5cbiAgaWYgKGRhdGEgaW5zdGFuY2VvZiBGbG9hdDY0QXJyYXkpIHtcbiAgICByZXR1cm4ge1xuICAgICAgZm9ybWF0OiBHTC5SMzJGLFxuICAgICAgZGF0YUZvcm1hdDogR0wuUkVELFxuICAgICAgdHlwZTogR0wuRkxPQVRcbiAgICB9O1xuICB9XG5cbiAgLy8gRm9yIGV4aGF1c3RpdmUgY2hlY2sgYWJvdmU7IGZvbGxvd2luZyBzaG91bGQgbmV2ZXIgb2NjdXJcbiAgLy8gaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9hLzU4MDA5OTkyXG4gIGNvbnN0IHVuZXhwZWN0ZWRJbnB1dDogbmV2ZXIgPSBkYXRhO1xuICB0aHJvdyBuZXcgRXJyb3IodW5leHBlY3RlZElucHV0KTtcbn1cblxuLyoqXG4gKiBEaXNjcmV0ZS12YWx1ZWQgY29sb3JtYXBzIChlLmcuIGZyb20gdGhlIG91dHB1dCBvZlxuICogY2xhc3NpZmljYXRpb24gYWxnb3JpdGhtcykgaW4gdGhlIHJhc3RlciBsYXllci4gUHJldmlvdXNseSwgdGhlIHZhbHVlcyBwYXNzZWQgdG9cbiAqIGBURVhUVVJFX01JTl9GSUxURVJgIGFuZCBgVEVYVFVSRV9NQUdfRklMVEVSYCB3ZXJlIGBHTC5MSU5FQVJgLCB3aGljaCBtZWFudCB0aGF0IHRoZSBHUFUgd291bGRcbiAqIGxpbmVhcmx5IGludGVycG9sYXRlIHZhbHVlcyBiZXR3ZWVuIHR3byBuZWlnaGJvcmluZyBjb2xvcm1hcCBwaXhlbCB2YWx1ZXMuIFNldHRpbmcgdGhlc2UgdmFsdWVzXG4gKiB0byBORUFSRVNUIG1lYW5zIHRoYXQgdGhlIEdQVSB3aWxsIGNob29zZSB0aGUgbmVhcmVzdCB2YWx1ZSBvbiB0aGUgdGV4dHVyZTJEIGxvb2t1cCBvcGVyYXRpb24sXG4gKiB3aGljaCBmaXhlcyBwcmVjaXNpb24gaXNzdWVzIGZvciBkaXNjcmV0ZS12YWx1ZWQgY29sb3JtYXBzLiBUaGlzIHNob3VsZCBiZSBvayBmb3IgY29udGludW91c1xuICogY29sb3JtYXBzIGFzIGxvbmcgYXMgdGhlIGNvbG9yIGRpZmZlcmVuY2UgYmV0d2VlbiBlYWNoIHBpeGVsIG9uIHRoZSBjb2xvcm1hcCBpcyBzbWFsbC5cbiAqL1xuZXhwb3J0IGNvbnN0IENPTE9STUFQX1RFWFRVUkVfUEFSQU1FVEVSUyA9IHtcbiAgW0dMLlRFWFRVUkVfTUlOX0ZJTFRFUl06IEdMLk5FQVJFU1QsXG4gIFtHTC5URVhUVVJFX01BR19GSUxURVJdOiBHTC5ORUFSRVNULFxuICBbR0wuVEVYVFVSRV9XUkFQX1NdOiBHTC5DTEFNUF9UT19FREdFLFxuICBbR0wuVEVYVFVSRV9XUkFQX1RdOiBHTC5DTEFNUF9UT19FREdFXG59O1xuXG5jb25zdCBERUZBVUxUXzhCSVRfVEVYVFVSRV9QQVJBTUVURVJTID0ge1xuICBbR0wuVEVYVFVSRV9NSU5fRklMVEVSXTogR0wuTElORUFSX01JUE1BUF9MSU5FQVIsXG4gIFtHTC5URVhUVVJFX01BR19GSUxURVJdOiBHTC5MSU5FQVIsXG4gIFtHTC5URVhUVVJFX1dSQVBfU106IEdMLkNMQU1QX1RPX0VER0UsXG4gIFtHTC5URVhUVVJFX1dSQVBfVF06IEdMLkNMQU1QX1RPX0VER0Vcbn07XG5cbmNvbnN0IERFRkFVTFRfSElHSF9CSVRfVEVYVFVSRV9QQVJBTUVURVJTID0ge1xuICBbR0wuVEVYVFVSRV9NSU5fRklMVEVSXTogR0wuTkVBUkVTVCxcbiAgW0dMLlRFWFRVUkVfTUFHX0ZJTFRFUl06IEdMLk5FQVJFU1QsXG4gIFtHTC5URVhUVVJFX1dSQVBfU106IEdMLkNMQU1QX1RPX0VER0UsXG4gIFtHTC5URVhUVVJFX1dSQVBfVF06IEdMLkNMQU1QX1RPX0VER0Vcbn07XG5cbi8qKlxuICogU2VsZWN0IGNvcnJlY3QgbW9kdWxlIHR5cGUgZm9yIFwiY29tYmluZUJhbmRzXCJcbiAqXG4gKiBjb21iaW5lQmFuZHMgam9pbnMgdXAgdG8gZm91ciAyRCBhcnJheXMgKGNvbnRhaW5lZCBpbiBpbWFnZUJhbmRzKSBpbnRvIGEgc2luZ2xlIFwicmdiYVwiIGltYWdlXG4gKiB0ZXh0dXJlIG9uIHRoZSBHUFUuIFRoYXQgc2hhZGVyIGNvZGUgbmVlZHMgdG8gaGF2ZSB0aGUgc2FtZSBkYXRhIHR5cGUgYXMgdGhlIGFjdHVhbCBpbWFnZSBkYXRhLlxuICogRS5nLiBmb3IgZmxvYXQgZGF0YSB0aGUgdGV4dHVyZSBuZWVkcyB0byBiZSBgc2FtcGxlcjJEYCwgZm9yIHVpbnQgZGF0YSB0aGUgdGV4dHVyZSBuZWVkcyB0byBiZVxuICogYHVzYW1wbGVyMkRgIGFuZCBmb3IgaW50IGRhdGEgdGhlIHRleHR1cmUgbmVlZHMgdG8gYmUgYGlzYW1wbGVyMkRgLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0Q29tYmluZUJhbmRzTW9kdWxlKGltYWdlQmFuZHM6IFRleHR1cmUyRFByb3BzW10pOiBTaGFkZXJNb2R1bGUge1xuICAvLyBFYWNoIGltYWdlIGFycmF5IGlzIGV4cGVjdGVkL3JlcXVpcmVkIHRvIGJlIG9mIHRoZSBzYW1lIGRhdGEgdHlwZVxuICBzd2l0Y2ggKGltYWdlQmFuZHNbMF0uZm9ybWF0KSB7XG4gICAgY2FzZSBHTC5SOFVJOlxuICAgICAgcmV0dXJuIGNvbWJpbmVCYW5kc1VpbnQ7XG4gICAgY2FzZSBHTC5SMTZVSTpcbiAgICAgIHJldHVybiBjb21iaW5lQmFuZHNVaW50O1xuICAgIGNhc2UgR0wuUjMyVUk6XG4gICAgICByZXR1cm4gY29tYmluZUJhbmRzVWludDtcbiAgICBjYXNlIEdMLlI4STpcbiAgICAgIHJldHVybiBjb21iaW5lQmFuZHNJbnQ7XG4gICAgY2FzZSBHTC5SMTZJOlxuICAgICAgcmV0dXJuIGNvbWJpbmVCYW5kc0ludDtcbiAgICBjYXNlIEdMLlIzMkk6XG4gICAgICByZXR1cm4gY29tYmluZUJhbmRzSW50O1xuICAgIGNhc2UgR0wuUjMyRjpcbiAgICAgIHJldHVybiBjb21iaW5lQmFuZHNGbG9hdDtcbiAgICBkZWZhdWx0OlxuICAgICAgdGhyb3cgbmV3IEVycm9yKCdiYWQgZm9ybWF0Jyk7XG4gIH1cbn1cblxuLyoqIFNlbGVjdCBjb3JyZWN0IGltYWdlIG1hc2tpbmcgc2hhZGVyIG1vZHVsZSBmb3IgbWFzayBkYXRhIHR5cGVcbiAqIFRoZSBpbWFnZU1hc2sgY291bGQgKGF0IGxlYXN0IGluIHRoZSBmdXR1cmUsIHRoZW9yZXRpY2FsbHkpIGJlIG9mIGEgZGlmZmVyZW50IGRhdGEgZm9ybWF0IHRoYW5cbiAqIHRoZSBpbWFnZUJhbmRzIGRhdGEgaXRzZWxmLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0SW1hZ2VNYXNrTW9kdWxlKGltYWdlTWFzazogVGV4dHVyZTJEUHJvcHMpOiBTaGFkZXJNb2R1bGUge1xuICBzd2l0Y2ggKGltYWdlTWFzay5mb3JtYXQpIHtcbiAgICBjYXNlIEdMLlI4VUk6XG4gICAgICByZXR1cm4gbWFza1VpbnQ7XG4gICAgY2FzZSBHTC5SMTZVSTpcbiAgICAgIHJldHVybiBtYXNrVWludDtcbiAgICBjYXNlIEdMLlIzMlVJOlxuICAgICAgcmV0dXJuIG1hc2tVaW50O1xuICAgIGNhc2UgR0wuUjhJOlxuICAgICAgcmV0dXJuIG1hc2tJbnQ7XG4gICAgY2FzZSBHTC5SMTZJOlxuICAgICAgcmV0dXJuIG1hc2tJbnQ7XG4gICAgY2FzZSBHTC5SMzJJOlxuICAgICAgcmV0dXJuIG1hc2tJbnQ7XG4gICAgY2FzZSBHTC5SMzJGOlxuICAgICAgcmV0dXJuIG1hc2tGbG9hdDtcbiAgICBkZWZhdWx0OlxuICAgICAgdGhyb3cgbmV3IEVycm9yKCdiYWQgZm9ybWF0Jyk7XG4gIH1cbn1cblxuLyoqXG4gKiBMb2FkIGltYWdlIGFuZCB3cmFwIHdpdGggZGVmYXVsdCBXZWJHTCB0ZXh0dXJlIHBhcmFtZXRlcnNcbiAqXG4gKiBAcGFyYW0gdXJsIFVSTCB0byBsb2FkIGltYWdlXG4gKiBAcGFyYW0gdGV4dHVyZVBhcmFtcyBwYXJhbWV0ZXJzIHRvIHBhc3MgdG8gVGV4dHVyZTJEXG4gKlxuICogQHJldHVybiBpbWFnZSBvYmplY3QgdG8gcGFzcyB0byBUZXh0dXJlMkQgY29uc3RydWN0b3JcbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxvYWRJbWFnZShcbiAgdXJsOiBzdHJpbmcsXG4gIHRleHR1cmVQYXJhbXM6IFRleHR1cmUyRFByb3BzID0ge30sXG4gIHJlcXVlc3RPcHRpb25zOiBSZXF1ZXN0SW5pdCA9IHt9XG4pOiBQcm9taXNlPFRleHR1cmUyRFByb3BzPiB7XG4gIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2hGaWxlKHVybCwgcmVxdWVzdE9wdGlvbnMpO1xuICBjb25zdCBpbWFnZSA9IGF3YWl0IHBhcnNlKHJlc3BvbnNlLCBJbWFnZUxvYWRlcik7XG5cbiAgcmV0dXJuIHtcbiAgICBkYXRhOiBpbWFnZSxcbiAgICBwYXJhbWV0ZXJzOiBERUZBVUxUXzhCSVRfVEVYVFVSRV9QQVJBTUVURVJTLFxuICAgIGZvcm1hdDogR0wuUkdCLFxuICAgIC4uLnRleHR1cmVQYXJhbXNcbiAgfTtcbn1cblxudHlwZSBGZXRjaExpa2UgPSAodXJsOiBzdHJpbmcsIG9wdGlvbnM/OiBSZXF1ZXN0SW5pdCkgPT4gUHJvbWlzZTxSZXNwb25zZT47XG50eXBlIExvYWRpbmdPcHRpb25zID0ge1xuICBmZXRjaD86IHR5cGVvZiBmZXRjaCB8IEZldGNoTGlrZTtcbn07XG5cbi8qKlxuICogTG9hZCBOUFkgQXJyYXlcbiAqXG4gKiBUaGUgTlBZIGZvcm1hdCBpcyBkZXNjcmliZWQgaGVyZTogaHR0cHM6Ly9udW1weS5vcmcvZG9jL3N0YWJsZS9yZWZlcmVuY2UvZ2VuZXJhdGVkL251bXB5LmxpYi5mb3JtYXQuaHRtbC5cbiAqIEl0J3MgZGVzaWduZWQgdG8gYmUgYSB2ZXJ5IHNpbXBsZSBmaWxlIGZvcm1hdCB0byBob2xkIGFuIE4tZGltZW5zaW9uYWwgYmxvY2sgb2YgZGF0YS4gVGhlIGhlYWRlciBkZXNjcmliZXMgdGhlIGRhdGEgdHlwZSwgc2hhcGUsIGFuZCBvcmRlciAoZWl0aGVyIEMgb3IgRm9ydHJhbikgb2YgdGhlIGFycmF5LlxuICpcbiAqIEBwYXJhbSB1cmwgVVJMIHRvIGxvYWQgTlBZIEFycmF5XG4gKiBAcGFyYW0gc3BsaXQgV2hldGhlciB0byBzcGxpdCBzaW5nbGUgdHlwZWQgYXJyYXkgcmVwcmVzZW50aW5nIGFuIE4tZGltZW5zaW9uYWwgYXJyYXkgaW50byBhbiBBcnJheSB3aXRoIGVhY2ggZGltZW5zaW9uIGFzIGl0cyBvd24gdHlwZWQgYXJyYXlcbiAqXG4gKiBAcmV0dXJuIGltYWdlIG9iamVjdCB0byBwYXNzIHRvIFRleHR1cmUyRCBjb25zdHJ1Y3RvclxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gbG9hZE5weUFycmF5KFxuICByZXF1ZXN0OiB7dXJsOiBzdHJpbmc7IG9wdGlvbnM6IFJlcXVlc3RJbml0fSxcbiAgc3BsaXQ6IHRydWUsXG4gIG9wdGlvbnM/OiBMb2FkaW5nT3B0aW9uc1xuKTogUHJvbWlzZTxUZXh0dXJlMkRQcm9wc1tdIHwgbnVsbD47XG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gbG9hZE5weUFycmF5KFxuICByZXF1ZXN0OiB7dXJsOiBzdHJpbmc7IG9wdGlvbnM6IFJlcXVlc3RJbml0fSxcbiAgc3BsaXQ6IGZhbHNlLFxuICBvcHRpb25zPzogTG9hZGluZ09wdGlvbnNcbik6IFByb21pc2U8VGV4dHVyZTJEUHJvcHMgfCBudWxsPjtcbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBsb2FkTnB5QXJyYXkoXG4gIHJlcXVlc3Q6IHt1cmw6IHN0cmluZzsgb3B0aW9uczogUmVxdWVzdEluaXR9LFxuICBzcGxpdDogYm9vbGVhbixcbiAgb3B0aW9ucz86IExvYWRpbmdPcHRpb25zXG4pOiBQcm9taXNlPFRleHR1cmUyRFByb3BzIHwgVGV4dHVyZTJEUHJvcHNbXSB8IG51bGw+IHtcbiAgdHJ5IHtcbiAgICBjb25zdCB7bnB5OiBucHlPcHRpb25zfSA9IGdldExvYWRlck9wdGlvbnMoKTtcbiAgICBjb25zdCByZXNwb25zZTogTlBZTG9hZGVyUmVzcG9uc2UgPSBhd2FpdCBsb2FkKHJlcXVlc3QudXJsLCBOUFlMb2FkZXIsIHtcbiAgICAgIG5weTogbnB5T3B0aW9ucyxcbiAgICAgIGZldGNoOiBvcHRpb25zPy5mZXRjaFxuICAgIH0pO1xuXG4gICAgaWYgKCFyZXNwb25zZSB8fCAhcmVzcG9uc2UuZGF0YSB8fCByZXF1ZXN0Lm9wdGlvbnMuc2lnbmFsPy5hYm9ydGVkKSB7XG4gICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG5cbiAgICAvLyBGbG9hdDY0IGRhdGEgbmVlZHMgdG8gYmUgY29lcmNlZCB0byBGbG9hdDMyIGZvciB0aGUgR1BVXG4gICAgaWYgKHJlc3BvbnNlLmRhdGEgaW5zdGFuY2VvZiBGbG9hdDY0QXJyYXkpIHtcbiAgICAgIHJlc3BvbnNlLmRhdGEgPSBGbG9hdDMyQXJyYXkuZnJvbShyZXNwb25zZS5kYXRhKTtcbiAgICB9XG5cbiAgICBjb25zdCB7ZGF0YSwgaGVhZGVyfSA9IHJlc3BvbnNlO1xuICAgIGNvbnN0IHtzaGFwZX0gPSBoZWFkZXI7XG4gICAgY29uc3Qge2Zvcm1hdCwgZGF0YUZvcm1hdCwgdHlwZX0gPSBnZXRXZWJHTDJUZXh0dXJlUGFyYW1ldGVycyhkYXRhKTtcblxuICAgIC8vIFRPRE86IGNoZWNrIGhlaWdodC13aWR0aCBvciB3aWR0aC1oZWlnaHRcbiAgICAvLyBSZWdhcmRsZXNzLCBpbWFnZXMgdXN1YWxseSBzcXVhcmVcbiAgICAvLyBUT0RPOiBoYW5kbGUgY2FzZXMgb2YgMjU2eDI1NngxIGluc3RlYWQgb2YgMXgyNTZ4MjU2XG4gICAgY29uc3QgW3osIGhlaWdodCwgd2lkdGhdID0gc2hhcGU7XG5cbiAgICAvLyBTaW5jZSB3ZSBub3cgdXNlIFdlYkdMMiBkYXRhIHR5cGVzIGZvciA4LWJpdCB0ZXh0dXJlcywgd2Ugc2V0IHRoZSBmb2xsb3dpbmcgZm9yIGFsbCB0ZXh0dXJlc1xuICAgIGNvbnN0IG1pcG1hcHMgPSBmYWxzZTtcbiAgICBjb25zdCBwYXJhbWV0ZXJzID0gREVGQVVMVF9ISUdIX0JJVF9URVhUVVJFX1BBUkFNRVRFUlM7XG5cbiAgICBpZiAoIXNwbGl0KSB7XG4gICAgICByZXR1cm4ge1xuICAgICAgICBkYXRhLFxuICAgICAgICB3aWR0aCxcbiAgICAgICAgaGVpZ2h0LFxuICAgICAgICBmb3JtYXQsXG4gICAgICAgIGRhdGFGb3JtYXQsXG4gICAgICAgIHR5cGUsXG4gICAgICAgIHBhcmFtZXRlcnMsXG4gICAgICAgIG1pcG1hcHNcbiAgICAgIH07XG4gICAgfVxuXG4gICAgLy8gU3BsaXQgaW50byBpbmRpdmlkdWFsIGFycmF5c1xuICAgIGNvbnN0IGNoYW5uZWxzOiBUZXh0dXJlMkRQcm9wc1tdID0gW107XG4gICAgY29uc3QgY2hhbm5lbFNpemUgPSBoZWlnaHQgKiB3aWR0aDtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IHo7IGkrKykge1xuICAgICAgY2hhbm5lbHMucHVzaCh7XG4gICAgICAgIGRhdGE6IGRhdGEuc3ViYXJyYXkoaSAqIGNoYW5uZWxTaXplLCAoaSArIDEpICogY2hhbm5lbFNpemUpLFxuICAgICAgICB3aWR0aCxcbiAgICAgICAgaGVpZ2h0LFxuICAgICAgICBmb3JtYXQsXG4gICAgICAgIGRhdGFGb3JtYXQsXG4gICAgICAgIHR5cGUsXG4gICAgICAgIHBhcmFtZXRlcnMsXG4gICAgICAgIG1pcG1hcHNcbiAgICAgIH0pO1xuICAgIH1cbiAgICByZXR1cm4gY2hhbm5lbHM7XG4gIH0gY2F0Y2gge1xuICAgIHJldHVybiBudWxsO1xuICB9XG59XG5cbi8qKlxuICogQ3JlYXRlIHRleHR1cmUgZGF0YSBmb3IgY2F0ZWdvcmljYWwgY29sb3JtYXAgc2NhbGVcbiAqIEBwYXJhbSBjYXRlZ29yaWNhbE9wdGlvbnMgLSBjb2xvciBtYXAgY29uZmlndXJhdGlvbiBhbmQgbWluLW1heCB2YWx1ZXMgb2YgY2F0ZWdvcmljYWwgYmFuZFxuICogQHJldHVybnMgdGV4dHVyZSBkYXRhXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZW5lcmF0ZUNhdGVnb3JpY2FsQ29sb3JtYXBUZXh0dXJlKFxuICBjYXRlZ29yaWNhbE9wdGlvbnM6IENhdGVnb3JpY2FsQ29sb3JtYXBPcHRpb25zXG4pOiBUZXh0dXJlMkRQcm9wcyB7XG4gIGNvbnN0IGRhdGEgPSBnZW5lcmF0ZUNhdGVnb3JpY2FsQml0bWFwQXJyYXkoY2F0ZWdvcmljYWxPcHRpb25zKTtcbiAgcmV0dXJuIHtcbiAgICBkYXRhLFxuICAgIHdpZHRoOiBDQVRFR09SSUNBTF9URVhUVVJFX1dJRFRILFxuICAgIGhlaWdodDogMSxcbiAgICBmb3JtYXQ6IEdMLlJHQkEsXG4gICAgZGF0YUZvcm1hdDogR0wuUkdCQSxcbiAgICB0eXBlOiBHTC5VTlNJR05FRF9CWVRFLFxuICAgIHBhcmFtZXRlcnM6IENPTE9STUFQX1RFWFRVUkVfUEFSQU1FVEVSUyxcbiAgICBtaXBtYXBzOiBmYWxzZVxuICB9O1xufVxuXG4vLyBUT0RPOiB3b3VsZCBwcm9iYWJseSBiZSBzaW1wbGVyIHRvIG9ubHkgcGFzcyBpbiB0aGUgcHJvcHMgYWN0dWFsbHkgdXNlZCBieSB0aGlzIGZ1bmN0aW9uLiBUaGF0XG4vLyB3b3VsZCBtZWFuIGEgc21hbGxlciBvYmplY3QgdGhhbiBSZW5kZXJTdWJMYXllcnNQcm9wc1xuLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG1heC1zdGF0ZW1lbnRzLCBjb21wbGV4aXR5XG5leHBvcnQgZnVuY3Rpb24gZ2V0TW9kdWxlcyh7XG4gIGltYWdlcyxcbiAgcHJvcHNcbn06IHtcbiAgaW1hZ2VzOiBQYXJ0aWFsPEltYWdlRGF0YT47XG4gIHByb3BzPzogUmVuZGVyU3ViTGF5ZXJzUHJvcHM7XG59KToge1xuICBtb2R1bGVzOiBTaGFkZXJNb2R1bGVbXTtcbiAgbW9kdWxlUHJvcHM6IFJlY29yZDxzdHJpbmcsIGFueT47XG59IHtcbiAgY29uc3QgbW9kdWxlUHJvcHM6IFJlY29yZDxzdHJpbmcsIGFueT4gPSB7fTtcbiAgLy8gQXJyYXkgb2YgbHVtYS5nbCBXZWJHTCBtb2R1bGVzIHRvIHBhc3MgdG8gdGhlIFJhc3RlckxheWVyXG4gIGNvbnN0IG1vZHVsZXM6IFNoYWRlck1vZHVsZVtdID0gW107XG5cbiAgLy8gdXNlIHJnYmEgaW1hZ2UgZGlyZWN0bHkuIFVzZWQgZm9yIHJhc3RlciAucG10aWxlcyByZW5kZXJpbmdcbiAgaWYgKGltYWdlcy5pbWFnZVJnYmEpIHtcbiAgICBtb2R1bGVzLnB1c2gocmdiYUltYWdlKTtcblxuICAgIC8vIG5vIHN1cHBvcnQgZm9yIG90aGVyIG1vZHVsZXMgYXRtIGZvciBkaXJlY3QgcmdiYSBtb2RlXG4gICAgcmV0dXJuIHttb2R1bGVzLCBtb2R1bGVQcm9wc307XG4gIH1cblxuICBpZiAoIXByb3BzKSB7XG4gICAgcmV0dXJuIHttb2R1bGVzLCBtb2R1bGVQcm9wc307XG4gIH1cblxuICBjb25zdCB7XG4gICAgcmVuZGVyQmFuZEluZGV4ZXMsXG4gICAgbm9uTGluZWFyUmVzY2FsaW5nLFxuICAgIGxpbmVhclJlc2NhbGluZ0ZhY3RvcixcbiAgICBtaW5QaXhlbFZhbHVlLFxuICAgIG1heFBpeGVsVmFsdWUsXG4gICAgZ2FtbWFDb250cmFzdEZhY3RvcixcbiAgICBzaWdtb2lkYWxDb250cmFzdEZhY3RvcixcbiAgICBzaWdtb2lkYWxCaWFzRmFjdG9yLFxuICAgIHNhdHVyYXRpb25WYWx1ZSxcbiAgICBiYW5kQ29tYmluYXRpb24sXG4gICAgZmlsdGVyRW5hYmxlZCxcbiAgICBmaWx0ZXJSYW5nZSxcbiAgICBkYXRhVHlwZSxcbiAgICBtaW5DYXRlZ29yaWNhbEJhbmRWYWx1ZSxcbiAgICBtYXhDYXRlZ29yaWNhbEJhbmRWYWx1ZSxcbiAgICBoYXNDYXRlZ29yaWNhbENvbG9yTWFwXG4gIH0gPSBwcm9wcztcblxuICBpZiAoQXJyYXkuaXNBcnJheShpbWFnZXMuaW1hZ2VCYW5kcykgJiYgaW1hZ2VzLmltYWdlQmFuZHMubGVuZ3RoID4gMCkge1xuICAgIG1vZHVsZXMucHVzaChnZXRDb21iaW5lQmFuZHNNb2R1bGUoaW1hZ2VzLmltYWdlQmFuZHMpKTtcbiAgfVxuXG4gIGlmIChpbWFnZXMuaW1hZ2VNYXNrKSB7XG4gICAgbW9kdWxlcy5wdXNoKGdldEltYWdlTWFza01vZHVsZShpbWFnZXMuaW1hZ2VNYXNrKSk7XG4gICAgLy8gSW4gZ2VuZXJhbCwgZGF0YSBtYXNrcyBhcmUgMCBmb3Igbm9kYXRhIGFuZCB0aGUgbWF4aW11bSB2YWx1ZSBmb3IgdmFsaWQgZGF0YSwgZS5nLiAyNTUgb3JcbiAgICAvLyA2NTUzNSBmb3IgdWludDggb3IgdWludDE2IGRhdGEsIHJlc3BlY3RpdmVseVxuICAgIG1vZHVsZVByb3BzLm1hc2tLZWVwTWluID0gMTtcbiAgfVxuXG4gIGlmIChBcnJheS5pc0FycmF5KHJlbmRlckJhbmRJbmRleGVzKSkge1xuICAgIG1vZHVsZXMucHVzaChyZW9yZGVyQmFuZHMpO1xuICAgIG1vZHVsZVByb3BzLm9yZGVyaW5nID0gcmVuZGVyQmFuZEluZGV4ZXM7XG4gIH1cblxuICBjb25zdCBnbG9iYWxSYW5nZSA9IG1heFBpeGVsVmFsdWUgLSBtaW5QaXhlbFZhbHVlO1xuICAvLyBGaXggcmVzY2FsaW5nIGlmIHdlIGFyZSBzdXJlIHRoYXQgZGF0YXNldCBpcyBjYXRlZ29yaWNhbFxuICBpZiAoaGFzQ2F0ZWdvcmljYWxDb2xvck1hcCkge1xuICAgIG1vZHVsZXMucHVzaChsaW5lYXJSZXNjYWxlKTtcbiAgICBtb2R1bGVQcm9wcy5saW5lYXJSZXNjYWxlU2NhbGVyID0gMSAvIG1heFBpeGVsVmFsdWU7XG4gICAgbW9kdWxlUHJvcHMubGluZWFyUmVzY2FsZU9mZnNldCA9IDA7XG4gIH0gZWxzZSBpZiAoaXNSZXNjYWxpbmdBbGxvd2VkKGJhbmRDb21iaW5hdGlvbikpIHtcbiAgICBpZiAoIW5vbkxpbmVhclJlc2NhbGluZykge1xuICAgICAgY29uc3QgW21pbiwgbWF4XSA9IGxpbmVhclJlc2NhbGluZ0ZhY3RvcjtcbiAgICAgIGNvbnN0IGxvY2FsUmFuZ2UgPSBtYXggLSBtaW47XG5cbiAgICAgIC8vIEFkZCBsaW5lYXIgcmVzY2FsaW5nIG1vZHVsZVxuICAgICAgbW9kdWxlcy5wdXNoKGxpbmVhclJlc2NhbGUpO1xuXG4gICAgICAvLyBEaXZpZGUgYnkgbG9jYWwgcmFuZ2UgKiBnbG9iYWwgcmFuZ2VcbiAgICAgIG1vZHVsZVByb3BzLmxpbmVhclJlc2NhbGVTY2FsZXIgPSAxIC8gKGxvY2FsUmFuZ2UgKiBnbG9iYWxSYW5nZSk7XG5cbiAgICAgIC8vIFN1YnRyYWN0IG9mZiB0aGUgbG9jYWwgbWluXG4gICAgICBtb2R1bGVQcm9wcy5saW5lYXJSZXNjYWxlT2Zmc2V0ID0gLW1pbjtcblxuICAgICAgLy8gQ2xhbXAgdG8gWzAsIDFdIGRvbmUgYXV0b21hdGljYWxseT9cbiAgICB9IGVsc2Uge1xuICAgICAgbW9kdWxlcy5wdXNoKGxpbmVhclJlc2NhbGUpO1xuICAgICAgbW9kdWxlUHJvcHMubGluZWFyUmVzY2FsZVNjYWxlciA9IDEgLyBtYXhQaXhlbFZhbHVlO1xuICAgICAgbW9kdWxlUHJvcHMubGluZWFyUmVzY2FsZU9mZnNldCA9IDA7XG5cbiAgICAgIG1vZHVsZXMucHVzaChnYW1tYUNvbnRyYXN0KTtcbiAgICAgIG1vZHVsZVByb3BzLmdhbW1hQ29udHJhc3RWYWx1ZSA9IGdhbW1hQ29udHJhc3RGYWN0b3I7XG5cbiAgICAgIG1vZHVsZXMucHVzaChzaWdtb2lkYWxDb250cmFzdCk7XG4gICAgICBtb2R1bGVQcm9wcy5zaWdtb2lkYWxDb250cmFzdCA9IHNpZ21vaWRhbENvbnRyYXN0RmFjdG9yO1xuICAgICAgbW9kdWxlUHJvcHMuc2lnbW9pZGFsQmlhcyA9IHNpZ21vaWRhbEJpYXNGYWN0b3I7XG4gICAgfVxuXG4gICAgaWYgKE51bWJlci5pc0Zpbml0ZShzYXR1cmF0aW9uVmFsdWUpICYmIHNhdHVyYXRpb25WYWx1ZSAhPT0gMSkge1xuICAgICAgbW9kdWxlcy5wdXNoKHNhdHVyYXRpb24pO1xuICAgICAgbW9kdWxlUHJvcHMuc2F0dXJhdGlvblZhbHVlID0gc2F0dXJhdGlvblZhbHVlO1xuICAgIH1cbiAgfVxuXG4gIHN3aXRjaCAoYmFuZENvbWJpbmF0aW9uKSB7XG4gICAgY2FzZSAnbm9ybWFsaXplZERpZmZlcmVuY2UnOlxuICAgICAgbW9kdWxlcy5wdXNoKG5vcm1hbGl6ZWREaWZmZXJlbmNlKTtcbiAgICAgIGJyZWFrO1xuICAgIGNhc2UgJ2VuaGFuY2VkVmVnZXRhdGlvbkluZGV4JzpcbiAgICAgIG1vZHVsZXMucHVzaChlbmhhbmNlZFZlZ2V0YXRpb25JbmRleCk7XG4gICAgICBicmVhaztcbiAgICBjYXNlICdzb2lsQWRqdXN0ZWRWZWdldGF0aW9uSW5kZXgnOlxuICAgICAgbW9kdWxlcy5wdXNoKHNvaWxBZGp1c3RlZFZlZ2V0YXRpb25JbmRleCk7XG4gICAgICBicmVhaztcbiAgICBjYXNlICdtb2RpZmllZFNvaWxBZGp1c3RlZFZlZ2V0YXRpb25JbmRleCc6XG4gICAgICBtb2R1bGVzLnB1c2gobW9kaWZpZWRTb2lsQWRqdXN0ZWRWZWdldGF0aW9uSW5kZXgpO1xuICAgICAgYnJlYWs7XG4gICAgZGVmYXVsdDpcbiAgICAgIGJyZWFrO1xuICB9XG5cbiAgaWYgKGlzRmlsdGVyQWxsb3dlZChiYW5kQ29tYmluYXRpb24pICYmIGZpbHRlckVuYWJsZWQpIHtcbiAgICBtb2R1bGVzLnB1c2goZmlsdGVyKTtcbiAgICBtb2R1bGVQcm9wcy5maWx0ZXJNaW4xID0gZmlsdGVyUmFuZ2VbMF07XG4gICAgbW9kdWxlUHJvcHMuZmlsdGVyTWF4MSA9IGZpbHRlclJhbmdlWzFdO1xuICB9XG5cbiAgLy8gQXBwbHkgY29sb3JtYXBcbiAgaWYgKGlzQ29sb3JtYXBBbGxvd2VkKGJhbmRDb21iaW5hdGlvbikgJiYgaW1hZ2VzLmltYWdlQ29sb3JtYXApIHtcbiAgICBtb2R1bGVzLnB1c2goY29sb3JtYXBNb2R1bGUpO1xuICAgIG1vZHVsZVByb3BzLm1pbkNhdGVnb3JpY2FsQmFuZFZhbHVlID0gbWluQ2F0ZWdvcmljYWxCYW5kVmFsdWU7XG4gICAgbW9kdWxlUHJvcHMubWF4Q2F0ZWdvcmljYWxCYW5kVmFsdWUgPSBtYXhDYXRlZ29yaWNhbEJhbmRWYWx1ZTtcbiAgICBtb2R1bGVQcm9wcy5kYXRhVHlwZU1heFZhbHVlID0gZHR5cGVNYXhWYWx1ZVtkYXRhVHlwZV07XG4gICAgbW9kdWxlUHJvcHMubWF4UGl4ZWxWYWx1ZSA9IG1heFBpeGVsVmFsdWU7XG4gIH1cblxuICByZXR1cm4ge21vZHVsZXMsIG1vZHVsZVByb3BzfTtcbn1cbiJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFPQSxJQUFBQSxLQUFBLEdBQUFDLE9BQUE7QUFDQSxJQUFBQyxPQUFBLEdBQUFELE9BQUE7QUFDQSxJQUFBRSxTQUFBLEdBQUFGLE9BQUE7QUFDQSxJQUFBRyxVQUFBLEdBQUFDLHNCQUFBLENBQUFKLE9BQUE7QUFHQSxJQUFBSyxXQUFBLEdBQUFMLE9BQUE7QUFDQSxJQUFBTSxhQUFBLEdBQUFOLE9BQUE7QUF3QkEsSUFBQU8sZ0JBQUEsR0FBQVAsT0FBQTtBQU82QixTQUFBUSxRQUFBQyxDQUFBLEVBQUFDLENBQUEsUUFBQUMsQ0FBQSxHQUFBQyxNQUFBLENBQUFDLElBQUEsQ0FBQUosQ0FBQSxPQUFBRyxNQUFBLENBQUFFLHFCQUFBLFFBQUFDLENBQUEsR0FBQUgsTUFBQSxDQUFBRSxxQkFBQSxDQUFBTCxDQUFBLEdBQUFDLENBQUEsS0FBQUssQ0FBQSxHQUFBQSxDQUFBLENBQUFDLE1BQUEsV0FBQU4sQ0FBQSxXQUFBRSxNQUFBLENBQUFLLHdCQUFBLENBQUFSLENBQUEsRUFBQUMsQ0FBQSxFQUFBUSxVQUFBLE9BQUFQLENBQUEsQ0FBQVEsSUFBQSxDQUFBQyxLQUFBLENBQUFULENBQUEsRUFBQUksQ0FBQSxZQUFBSixDQUFBO0FBQUEsU0FBQVUsY0FBQVosQ0FBQSxhQUFBQyxDQUFBLE1BQUFBLENBQUEsR0FBQVksU0FBQSxDQUFBQyxNQUFBLEVBQUFiLENBQUEsVUFBQUMsQ0FBQSxXQUFBVyxTQUFBLENBQUFaLENBQUEsSUFBQVksU0FBQSxDQUFBWixDQUFBLFFBQUFBLENBQUEsT0FBQUYsT0FBQSxDQUFBSSxNQUFBLENBQUFELENBQUEsT0FBQWEsT0FBQSxXQUFBZCxDQUFBLFFBQUFlLGdCQUFBLGFBQUFoQixDQUFBLEVBQUFDLENBQUEsRUFBQUMsQ0FBQSxDQUFBRCxDQUFBLFNBQUFFLE1BQUEsQ0FBQWMseUJBQUEsR0FBQWQsTUFBQSxDQUFBZSxnQkFBQSxDQUFBbEIsQ0FBQSxFQUFBRyxNQUFBLENBQUFjLHlCQUFBLENBQUFmLENBQUEsS0FBQUgsT0FBQSxDQUFBSSxNQUFBLENBQUFELENBQUEsR0FBQWEsT0FBQSxXQUFBZCxDQUFBLElBQUFFLE1BQUEsQ0FBQWdCLGNBQUEsQ0FBQW5CLENBQUEsRUFBQUMsQ0FBQSxFQUFBRSxNQUFBLENBQUFLLHdCQUFBLENBQUFOLENBQUEsRUFBQUQsQ0FBQSxpQkFBQUQsQ0FBQSxJQTdDN0I7QUFDQTtBQUVBO0FBQ0E7QUFDQTtBQVlBLElBQ0VvQixpQkFBaUIsR0FrQmZDLHlCQUFXLENBbEJiRCxpQkFBaUI7RUFDakJFLGVBQWUsR0FpQmJELHlCQUFXLENBakJiQyxlQUFlO0VBQ2ZDLGdCQUFnQixHQWdCZEYseUJBQVcsQ0FoQmJFLGdCQUFnQjtFQUNoQkMsU0FBUyxHQWVQSCx5QkFBVyxDQWZiRyxTQUFTO0VBQ1RDLE9BQU8sR0FjTEoseUJBQVcsQ0FkYkksT0FBTztFQUNQQyxRQUFRLEdBYU5MLHlCQUFXLENBYmJLLFFBQVE7RUFDUkMsYUFBYSxHQVlYTix5QkFBVyxDQVpiTSxhQUFhO0VBQ2JDLGFBQWEsR0FXWFAseUJBQVcsQ0FYYk8sYUFBYTtFQUNiQyxpQkFBaUIsR0FVZlIseUJBQVcsQ0FWYlEsaUJBQWlCO0VBQ2pCQyxvQkFBb0IsR0FTbEJULHlCQUFXLENBVGJTLG9CQUFvQjtFQUNwQkMsdUJBQXVCLEdBUXJCVix5QkFBVyxDQVJiVSx1QkFBdUI7RUFDdkJDLDJCQUEyQixHQU96QlgseUJBQVcsQ0FQYlcsMkJBQTJCO0VBQzNCQyxtQ0FBbUMsR0FNakNaLHlCQUFXLENBTmJZLG1DQUFtQztFQUN6QkMsY0FBYyxHQUt0QmIseUJBQVcsQ0FMYmMsUUFBUTtFQUNSNUIsTUFBTSxHQUlKYyx5QkFBVyxDQUpiZCxNQUFNO0VBQ042QixVQUFVLEdBR1JmLHlCQUFXLENBSGJlLFVBQVU7RUFDVkMsWUFBWSxHQUVWaEIseUJBQVcsQ0FGYmdCLFlBQVk7RUFDWkMsU0FBUyxHQUNQakIseUJBQVcsQ0FEYmlCLFNBQVM7QUFtQlg7QUFDQTtBQUNBOztBQU9BO0FBQ0E7QUFDQTtBQUNBLFNBQVNDLDBCQUEwQkEsQ0FBQ0MsSUFBd0IsRUFBOEI7RUFDeEYsSUFBSUEsSUFBSSxZQUFZQyxVQUFVLElBQUlELElBQUksWUFBWUUsaUJBQWlCLEVBQUU7SUFDbkUsT0FBTztNQUNMO01BQ0FDLE1BQU0sRUFBRUMscUJBQUUsQ0FBQ0MsSUFBSTtNQUNmQyxVQUFVLEVBQUVGLHFCQUFFLENBQUNHLFdBQVc7TUFDMUJDLElBQUksRUFBRUoscUJBQUUsQ0FBQ0s7SUFDWCxDQUFDO0VBQ0g7RUFFQSxJQUFJVCxJQUFJLFlBQVlVLFdBQVcsRUFBRTtJQUMvQixPQUFPO01BQ0xQLE1BQU0sRUFBRUMscUJBQUUsQ0FBQ08sS0FBSztNQUNoQkwsVUFBVSxFQUFFRixxQkFBRSxDQUFDRyxXQUFXO01BQzFCQyxJQUFJLEVBQUVKLHFCQUFFLENBQUNRO0lBQ1gsQ0FBQztFQUNIO0VBRUEsSUFBSVosSUFBSSxZQUFZYSxXQUFXLEVBQUU7SUFDL0IsT0FBTztNQUNMVixNQUFNLEVBQUVDLHFCQUFFLENBQUNVLEtBQUs7TUFDaEJSLFVBQVUsRUFBRUYscUJBQUUsQ0FBQ0csV0FBVztNQUMxQkMsSUFBSSxFQUFFSixxQkFBRSxDQUFDVztJQUNYLENBQUM7RUFDSDtFQUVBLElBQUlmLElBQUksWUFBWWdCLFNBQVMsRUFBRTtJQUM3QixPQUFPO01BQ0xiLE1BQU0sRUFBRUMscUJBQUUsQ0FBQ2EsR0FBRztNQUNkWCxVQUFVLEVBQUVGLHFCQUFFLENBQUNHLFdBQVc7TUFDMUJDLElBQUksRUFBRUoscUJBQUUsQ0FBQ2M7SUFDWCxDQUFDO0VBQ0g7RUFFQSxJQUFJbEIsSUFBSSxZQUFZbUIsVUFBVSxFQUFFO0lBQzlCLE9BQU87TUFDTGhCLE1BQU0sRUFBRUMscUJBQUUsQ0FBQ2dCLElBQUk7TUFDZmQsVUFBVSxFQUFFRixxQkFBRSxDQUFDRyxXQUFXO01BQzFCQyxJQUFJLEVBQUVKLHFCQUFFLENBQUNpQjtJQUNYLENBQUM7RUFDSDtFQUNBLElBQUlyQixJQUFJLFlBQVlzQixVQUFVLEVBQUU7SUFDOUIsT0FBTztNQUNMbkIsTUFBTSxFQUFFQyxxQkFBRSxDQUFDbUIsSUFBSTtNQUNmakIsVUFBVSxFQUFFRixxQkFBRSxDQUFDRyxXQUFXO01BQzFCQyxJQUFJLEVBQUVKLHFCQUFFLENBQUNvQjtJQUNYLENBQUM7RUFDSDtFQUNBLElBQUl4QixJQUFJLFlBQVl5QixZQUFZLEVBQUU7SUFDaEMsT0FBTztNQUNMdEIsTUFBTSxFQUFFQyxxQkFBRSxDQUFDc0IsSUFBSTtNQUNmcEIsVUFBVSxFQUFFRixxQkFBRSxDQUFDdUIsR0FBRztNQUNsQm5CLElBQUksRUFBRUoscUJBQUUsQ0FBQ3dCO0lBQ1gsQ0FBQztFQUNIO0VBRUEsSUFBSTVCLElBQUksWUFBWTZCLFlBQVksRUFBRTtJQUNoQyxPQUFPO01BQ0wxQixNQUFNLEVBQUVDLHFCQUFFLENBQUNzQixJQUFJO01BQ2ZwQixVQUFVLEVBQUVGLHFCQUFFLENBQUN1QixHQUFHO01BQ2xCbkIsSUFBSSxFQUFFSixxQkFBRSxDQUFDd0I7SUFDWCxDQUFDO0VBQ0g7O0VBRUE7RUFDQTtFQUNBLElBQU1FLGVBQXNCLEdBQUc5QixJQUFJO0VBQ25DLE1BQU0sSUFBSStCLEtBQUssQ0FBQ0QsZUFBZSxDQUFDO0FBQ2xDOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNPLElBQU1FLDJCQUEyQixHQUFBQyxPQUFBLENBQUFELDJCQUFBLE9BQUF4RCxnQkFBQSxpQkFBQUEsZ0JBQUEsaUJBQUFBLGdCQUFBLGlCQUFBQSxnQkFBQSxpQkFDckM0QixxQkFBRSxDQUFDOEIsa0JBQWtCLEVBQUc5QixxQkFBRSxDQUFDK0IsT0FBTyxHQUNsQy9CLHFCQUFFLENBQUNnQyxrQkFBa0IsRUFBR2hDLHFCQUFFLENBQUMrQixPQUFPLEdBQ2xDL0IscUJBQUUsQ0FBQ2lDLGNBQWMsRUFBR2pDLHFCQUFFLENBQUNrQyxhQUFhLEdBQ3BDbEMscUJBQUUsQ0FBQ21DLGNBQWMsRUFBR25DLHFCQUFFLENBQUNrQyxhQUFhLENBQ3RDO0FBRUQsSUFBTUUsK0JBQStCLE9BQUFoRSxnQkFBQSxpQkFBQUEsZ0JBQUEsaUJBQUFBLGdCQUFBLGlCQUFBQSxnQkFBQSxpQkFDbEM0QixxQkFBRSxDQUFDOEIsa0JBQWtCLEVBQUc5QixxQkFBRSxDQUFDcUMsb0JBQW9CLEdBQy9DckMscUJBQUUsQ0FBQ2dDLGtCQUFrQixFQUFHaEMscUJBQUUsQ0FBQ3NDLE1BQU0sR0FDakN0QyxxQkFBRSxDQUFDaUMsY0FBYyxFQUFHakMscUJBQUUsQ0FBQ2tDLGFBQWEsR0FDcENsQyxxQkFBRSxDQUFDbUMsY0FBYyxFQUFHbkMscUJBQUUsQ0FBQ2tDLGFBQWEsQ0FDdEM7QUFFRCxJQUFNSyxtQ0FBbUMsT0FBQW5FLGdCQUFBLGlCQUFBQSxnQkFBQSxpQkFBQUEsZ0JBQUEsaUJBQUFBLGdCQUFBLGlCQUN0QzRCLHFCQUFFLENBQUM4QixrQkFBa0IsRUFBRzlCLHFCQUFFLENBQUMrQixPQUFPLEdBQ2xDL0IscUJBQUUsQ0FBQ2dDLGtCQUFrQixFQUFHaEMscUJBQUUsQ0FBQytCLE9BQU8sR0FDbEMvQixxQkFBRSxDQUFDaUMsY0FBYyxFQUFHakMscUJBQUUsQ0FBQ2tDLGFBQWEsR0FDcENsQyxxQkFBRSxDQUFDbUMsY0FBYyxFQUFHbkMscUJBQUUsQ0FBQ2tDLGFBQWEsQ0FDdEM7O0FBRUQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNPLFNBQVNNLHFCQUFxQkEsQ0FBQ0MsVUFBNEIsRUFBZ0I7RUFDaEY7RUFDQSxRQUFRQSxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMxQyxNQUFNO0lBQzFCLEtBQUtDLHFCQUFFLENBQUNDLElBQUk7TUFDVixPQUFPdEIsZ0JBQWdCO0lBQ3pCLEtBQUtxQixxQkFBRSxDQUFDTyxLQUFLO01BQ1gsT0FBTzVCLGdCQUFnQjtJQUN6QixLQUFLcUIscUJBQUUsQ0FBQ1UsS0FBSztNQUNYLE9BQU8vQixnQkFBZ0I7SUFDekIsS0FBS3FCLHFCQUFFLENBQUNhLEdBQUc7TUFDVCxPQUFPbkMsZUFBZTtJQUN4QixLQUFLc0IscUJBQUUsQ0FBQ2dCLElBQUk7TUFDVixPQUFPdEMsZUFBZTtJQUN4QixLQUFLc0IscUJBQUUsQ0FBQ21CLElBQUk7TUFDVixPQUFPekMsZUFBZTtJQUN4QixLQUFLc0IscUJBQUUsQ0FBQ3NCLElBQUk7TUFDVixPQUFPOUMsaUJBQWlCO0lBQzFCO01BQ0UsTUFBTSxJQUFJbUQsS0FBSyxDQUFDLFlBQVksQ0FBQztFQUNqQztBQUNGOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ08sU0FBU2Usa0JBQWtCQSxDQUFDQyxTQUF5QixFQUFnQjtFQUMxRSxRQUFR