kepler.gl
Version:
kepler.gl is a webgl based application to visualize large scale location data in the browser
548 lines (527 loc) • 70.1 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _typeof = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Processors = exports.PARSE_FIELD_VALUE_FROM_STRING = exports.DATASET_HANDLERS = exports.CSV_NULLS = void 0;
exports.arrowSchemaToFields = arrowSchemaToFields;
exports.getGeoArrowMetadataFromSchema = getGeoArrowMetadataFromSchema;
exports.parseCsvRowsByFieldType = parseCsvRowsByFieldType;
exports.parseRowsByFields = parseRowsByFields;
exports.processArrowBatches = processArrowBatches;
exports.processArrowTable = processArrowTable;
exports.processCsvData = processCsvData;
exports.processGeojson = processGeojson;
exports.processKeplerglDataset = processKeplerglDataset;
exports.processKeplerglJSON = processKeplerglJSON;
exports.processRowObject = processRowObject;
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var arrow = _interopRequireWildcard(require("apache-arrow"));
var _d3Dsv = require("d3-dsv");
var _typeAnalyzer = require("type-analyzer");
var _geojsonNormalize = _interopRequireDefault(require("@mapbox/geojson-normalize"));
var _core = require("@loaders.gl/core");
var _wkt = require("@loaders.gl/wkt");
var _constants = require("@kepler.gl/constants");
var _utils = require("@kepler.gl/utils");
var _commonUtils = require("@kepler.gl/common-utils");
var _schemas = require("@kepler.gl/schemas");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2["default"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } // SPDX-License-Identifier: MIT
// Copyright contributors to the kepler.gl project
// if any of these value occurs in csv, parse it to null;
// const CSV_NULLS = ['', 'null', 'NULL', 'Null', 'NaN', '/N'];
// matches empty string
var CSV_NULLS = exports.CSV_NULLS = /^(null|NULL|Null|NaN|\/N||)$/;
function tryParseJsonString(str) {
try {
return JSON.parse(str);
} catch (e) {
return null;
}
}
var PARSE_FIELD_VALUE_FROM_STRING = exports.PARSE_FIELD_VALUE_FROM_STRING = (0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])({}, _constants.ALL_FIELD_TYPES["boolean"], {
valid: function valid(d) {
return typeof d === 'boolean';
},
parse: function parse(d) {
return d === 'true' || d === 'True' || d === 'TRUE' || d === '1';
}
}), _constants.ALL_FIELD_TYPES.integer, {
// @ts-ignore
valid: function valid(d) {
return parseInt(d, 10) === d;
},
// @ts-ignore
parse: function parse(d) {
return parseInt(d, 10);
}
}), _constants.ALL_FIELD_TYPES.timestamp, {
valid: function valid(d, field) {
return ['x', 'X'].includes(field.format) ? typeof d === 'number' : typeof d === 'string';
},
parse: function parse(d, field) {
return ['x', 'X'].includes(field.format) ? Number(d) : d;
}
}), _constants.ALL_FIELD_TYPES.real, {
// @ts-ignore
valid: function valid(d) {
return parseFloat(d) === d;
},
// Note this will result in NaN for some string
parse: parseFloat
}), _constants.ALL_FIELD_TYPES.object, {
valid: _utils.isPlainObject,
parse: tryParseJsonString
}), _constants.ALL_FIELD_TYPES.array, {
valid: Array.isArray,
parse: tryParseJsonString
}), _constants.ALL_FIELD_TYPES.h3, {
valid: function valid(d) {
return (0, _commonUtils.h3IsValid)(d);
},
parse: function parse(d) {
return d;
}
});
/**
* Process csv data, output a data object with `{fields: [], rows: []}`.
* The data object can be wrapped in a `dataset` and pass to [`addDataToMap`](../actions/actions.md#adddatatomap)
* @param rawData raw csv string
* @returns data object `{fields: [], rows: []}` can be passed to addDataToMaps
* @public
* @example
* import {processCsvData} from 'kepler.gl/processors';
*
* const testData = `gps_data.utc_timestamp,gps_data.lat,gps_data.lng,gps_data.types,epoch,has_result,id,time,begintrip_ts_utc,begintrip_ts_local,date
* 2016-09-17 00:09:55,29.9900937,31.2590542,driver_analytics,1472688000000,False,1,2016-09-23T00:00:00.000Z,2016-10-01 09:41:39+00:00,2016-10-01 09:41:39+00:00,2016-09-23
* 2016-09-17 00:10:56,29.9927699,31.2461142,driver_analytics,1472688000000,False,2,2016-09-23T00:00:00.000Z,2016-10-01 09:46:37+00:00,2016-10-01 16:46:37+00:00,2016-09-23
* 2016-09-17 00:11:56,29.9907261,31.2312742,driver_analytics,1472688000000,False,3,2016-09-23T00:00:00.000Z,,,2016-09-23
* 2016-09-17 00:12:58,29.9870074,31.2175827,driver_analytics,1472688000000,False,4,2016-09-23T00:00:00.000Z,,,2016-09-23`
*
* const dataset = {
* info: {id: 'test_data', label: 'My Csv'},
* data: processCsvData(testData)
* };
*
* dispatch(addDataToMap({
* datasets: [dataset],
* options: {centerMap: true, readOnly: true}
* }));
*/
function processCsvData(rawData, header) {
var rows;
var headerRow;
if (typeof rawData === 'string') {
var _parsedRows = (0, _d3Dsv.csvParseRows)(rawData);
if (!Array.isArray(_parsedRows) || _parsedRows.length < 2) {
// looks like an empty file, throw error to be catch
throw new Error('process Csv Data Failed: CSV is empty');
}
headerRow = _parsedRows[0];
rows = _parsedRows.slice(1);
} else if (Array.isArray(rawData) && rawData.length) {
rows = rawData;
headerRow = header;
if (!Array.isArray(headerRow)) {
// if data is passed in as array of rows and missing header
// assume first row is header
// @ts-ignore
headerRow = rawData[0];
rows = rawData.slice(1);
}
}
if (!rows || !headerRow) {
throw new Error('invalid input passed to processCsvData');
}
// here we assume the csv file that people uploaded will have first row
// as name of the column
cleanUpFalsyCsvValue(rows);
// No need to run type detection on every data point
// here we get a list of none null values to run analyze on
var sample = (0, _commonUtils.getSampleForTypeAnalyze)({
fields: headerRow,
rows: rows
});
var fields = (0, _commonUtils.getFieldsFromData)(sample, headerRow);
var parsedRows = parseRowsByFields(rows, fields);
return {
fields: fields,
rows: parsedRows
};
}
/**
* Parse rows of csv by analyzed field types. So that `'1'` -> `1`, `'True'` -> `true`
* @param rows
* @param fields
*/
function parseRowsByFields(rows, fields) {
// Edit rows in place
var geojsonFieldIdx = fields.findIndex(function (f) {
return f.name === '_geojson';
});
fields.forEach(parseCsvRowsByFieldType.bind(null, rows, geojsonFieldIdx));
return rows;
}
/**
* Convert falsy value in csv including `'', 'null', 'NULL', 'Null', 'NaN'` to `null`,
* so that type-analyzer won't detect it as string
*
* @param rows
*/
function cleanUpFalsyCsvValue(rows) {
var re = new RegExp(CSV_NULLS, 'g');
for (var i = 0; i < rows.length; i++) {
for (var j = 0; j < rows[i].length; j++) {
// analyzer will set any fields to 'string' if there are empty values
// which will be parsed as '' by d3.csv
// here we parse empty data as null
// TODO: create warning when deltect `CSV_NULLS` in the data
if (typeof rows[i][j] === 'string' && rows[i][j].match(re)) {
rows[i][j] = null;
}
}
}
}
/**
* Process uploaded csv file to parse value by field type
*
* @param rows
* @param geoFieldIdx field index
* @param field
* @param i
*/
function parseCsvRowsByFieldType(rows, geoFieldIdx, field, i) {
var parser = PARSE_FIELD_VALUE_FROM_STRING[field.type];
if (parser) {
// check first not null value of it's already parsed
var first = rows.find(function (r) {
return (0, _commonUtils.notNullorUndefined)(r[i]);
});
if (!first || parser.valid(first[i], field)) {
return;
}
rows.forEach(function (row) {
// parse string value based on field type
if (row[i] !== null) {
row[i] = parser.parse(row[i], field);
if (geoFieldIdx > -1 && (0, _utils.isPlainObject)(row[geoFieldIdx]) &&
// @ts-ignore
(0, _utils.hasOwnProperty)(row[geoFieldIdx], 'properties')) {
// @ts-ignore
row[geoFieldIdx].properties[field.name] = row[i];
}
}
});
}
}
/* eslint-enable complexity */
/**
* Process data where each row is an object, output can be passed to [`addDataToMap`](../actions/actions.md#adddatatomap)
* NOTE: This function may mutate input.
* @param rawData an array of row object, each object should have the same number of keys
* @returns dataset containing `fields` and `rows`
* @public
* @example
* import {addDataToMap} from 'kepler.gl/actions';
* import {processRowObject} from 'kepler.gl/processors';
*
* const data = [
* {lat: 31.27, lng: 127.56, value: 3},
* {lat: 31.22, lng: 126.26, value: 1}
* ];
*
* dispatch(addDataToMap({
* datasets: {
* info: {label: 'My Data', id: 'my_data'},
* data: processRowObject(data)
* }
* }));
*/
function processRowObject(rawData) {
if (!Array.isArray(rawData)) {
return null;
} else if (!rawData.length) {
// data is empty
return {
fields: [],
rows: []
};
}
var keys = Object.keys(rawData[0]); // [lat, lng, value]
var rows = rawData.map(function (d) {
return keys.map(function (key) {
return d[key];
});
}); // [[31.27, 127.56, 3]]
// row object can still contain values like `Null` or `N/A`
cleanUpFalsyCsvValue(rows);
return processCsvData(rows, keys);
}
/**
* Process GeoJSON [`FeatureCollection`](http://wiki.geojson.org/GeoJSON_draft_version_6#FeatureCollection),
* output a data object with `{fields: [], rows: []}`.
* The data object can be wrapped in a `dataset` and passed to [`addDataToMap`](../actions/actions.md#adddatatomap)
* NOTE: This function may mutate input.
*
* @param rawData raw geojson feature collection
* @returns dataset containing `fields` and `rows`
* @public
* @example
* import {addDataToMap} from 'kepler.gl/actions';
* import {processGeojson} from 'kepler.gl/processors';
*
* const geojson = {
* "type" : "FeatureCollection",
* "features" : [{
* "type" : "Feature",
* "properties" : {
* "capacity" : "10",
* "type" : "U-Rack"
* },
* "geometry" : {
* "type" : "Point",
* "coordinates" : [ -71.073283, 42.417500 ]
* }
* }]
* };
*
* dispatch(addDataToMap({
* datasets: {
* info: {
* label: 'Sample Taxi Trips in New York City',
* id: 'test_trip_data'
* },
* data: processGeojson(geojson)
* }
* }));
*/
function processGeojson(rawData) {
var normalizedGeojson = (0, _geojsonNormalize["default"])(rawData);
if (!normalizedGeojson || !Array.isArray(normalizedGeojson.features)) {
throw new Error("Read File Failed: File is not a valid GeoJSON. Read more about [supported file format](".concat(_constants.GUIDES_FILE_FORMAT_DOC, ")"));
}
// getting all feature fields
var allDataRows = [];
for (var i = 0; i < normalizedGeojson.features.length; i++) {
var f = normalizedGeojson.features[i];
if (f.geometry) {
allDataRows.push(_objectSpread({
// add feature to _geojson field
_geojson: f
}, f.properties || {}));
}
}
// get all the field
var fields = allDataRows.reduce(function (accu, curr) {
Object.keys(curr).forEach(function (key) {
if (!accu.includes(key)) {
accu.push(key);
}
});
return accu;
}, []);
// make sure each feature has exact same fields
allDataRows.forEach(function (d) {
fields.forEach(function (f) {
if (!(f in d)) {
d[f] = null;
if (d._geojson.properties) {
d._geojson.properties[f] = null;
}
}
});
});
return processRowObject(allDataRows);
}
/**
* Process saved kepler.gl json to be pass to [`addDataToMap`](../actions/actions.md#adddatatomap).
* The json object should contain `datasets` and `config`.
* @param rawData
* @param schema
* @returns datasets and config `{datasets: {}, config: {}}`
* @public
* @example
* import {addDataToMap} from 'kepler.gl/actions';
* import {processKeplerglJSON} from 'kepler.gl/processors';
*
* dispatch(addDataToMap(processKeplerglJSON(keplerGlJson)));
*/
function processKeplerglJSON(rawData) {
var schema = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _schemas.KeplerGlSchema;
return rawData ? schema.load(rawData.datasets, rawData.config) : null;
}
/**
* Parse a single or an array of datasets saved using kepler.gl schema
* @param rawData
* @param schema
*/
function processKeplerglDataset(rawData) {
var schema = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _schemas.KeplerGlSchema;
if (!rawData) {
return null;
}
var results = schema.parseSavedData((0, _commonUtils.toArray)(rawData));
if (!results) {
return null;
}
return Array.isArray(rawData) ? results : results[0];
}
/**
* Parse arrow table and return a dataset
*
* @param arrowTable ArrowTable to parse, see loaders.gl/schema
* @returns dataset containing `fields` and `rows` or null
*/
function processArrowTable(arrowTable) {
// @ts-ignore - Unknown data type causing build failures
return processArrowBatches(arrowTable.data.batches);
}
/**
* Extracts GeoArrow metadata from an Apache Arrow table schema.
* For geoparquet files geoarrow metadata isn't present in fields, so extract extra info from schema.
* @param table The Apache Arrow table to extract metadata from.
* @returns An object mapping column names to their GeoArrow encoding type.
* @throws Logs an error message if parsing of metadata fails.
*/
function getGeoArrowMetadataFromSchema(table) {
var geoArrowMetadata = {};
try {
var _table$schema$metadat;
var geoString = (_table$schema$metadat = table.schema.metadata) === null || _table$schema$metadat === void 0 ? void 0 : _table$schema$metadat.get('geo');
if (geoString) {
var parsedGeoString = JSON.parse(geoString);
if (parsedGeoString.columns) {
Object.keys(parsedGeoString.columns).forEach(function (columnName) {
var columnData = parsedGeoString.columns[columnName];
if ((columnData === null || columnData === void 0 ? void 0 : columnData.encoding) === 'WKB') {
geoArrowMetadata[columnName] = _constants.GEOARROW_EXTENSIONS.WKB;
}
// TODO potentially there are other types but no datasets to test
});
}
}
} catch (error) {
console.error('An error during arrow table schema metadata parsing');
}
return geoArrowMetadata;
}
/**
* Converts an Apache Arrow table schema into an array of Kepler.gl field objects.
* @param table The Apache Arrow table whose schema needs to be converted.
* @param fieldTypeSuggestions Optional mapping of field names to suggested field types.
* @returns An array of field objects suitable for Kepler.gl.
*/
function arrowSchemaToFields(table) {
var fieldTypeSuggestions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var headerRow = table.schema.fields.map(function (f) {
return f.name;
});
var sample = (0, _commonUtils.getSampleForTypeAnalyzeArrow)(table, headerRow);
var keplerFields = (0, _commonUtils.getFieldsFromData)(sample, headerRow);
var geoArrowMetadata = getGeoArrowMetadataFromSchema(table);
return table.schema.fields.map(function (field, fieldIndex) {
var _field$metadata$get;
var type = (0, _utils.arrowDataTypeToFieldType)(field.type);
var analyzerType = (0, _utils.arrowDataTypeToAnalyzerDataType)(field.type);
var format = '';
// geometry fields produced by DuckDB's st_asgeojson()
if (fieldTypeSuggestions[field.name] === 'JSON') {
type = _constants.ALL_FIELD_TYPES.geojson;
analyzerType = _typeAnalyzer.DATA_TYPES.GEOMETRY_FROM_STRING;
} else if (fieldTypeSuggestions[field.name] === 'GEOMETRY' || (_field$metadata$get = field.metadata.get(_constants.GEOARROW_METADATA_KEY)) !== null && _field$metadata$get !== void 0 && _field$metadata$get.startsWith('geoarrow')) {
type = _constants.ALL_FIELD_TYPES.geoarrow;
analyzerType = _typeAnalyzer.DATA_TYPES.GEOMETRY;
} else if (geoArrowMetadata[field.name]) {
var _field$metadata;
type = _constants.ALL_FIELD_TYPES.geoarrow;
analyzerType = _typeAnalyzer.DATA_TYPES.GEOMETRY;
(_field$metadata = field.metadata) === null || _field$metadata === void 0 || _field$metadata.set(_constants.GEOARROW_METADATA_KEY, geoArrowMetadata[field.name]);
} else if (fieldTypeSuggestions[field.name] === 'BLOB') {
// When arrow wkb column saved to DuckDB as BLOB without any metadata, then queried back
try {
var _table$getChildAt;
var data = (_table$getChildAt = table.getChildAt(fieldIndex)) === null || _table$getChildAt === void 0 ? void 0 : _table$getChildAt.get(0);
if (data) {
var binaryGeo = (0, _core.parseSync)(data, _wkt.WKBLoader);
if (binaryGeo) {
var _field$metadata2;
type = _constants.ALL_FIELD_TYPES.geoarrow;
analyzerType = _typeAnalyzer.DATA_TYPES.GEOMETRY;
(_field$metadata2 = field.metadata) === null || _field$metadata2 === void 0 || _field$metadata2.set(_constants.GEOARROW_METADATA_KEY, _constants.GEOARROW_EXTENSIONS.WKB);
}
}
} catch (error) {
// ignore, not WKB
}
} else {
// TODO should we use Kepler getFieldsFromData instead
// of arrowDataTypeToFieldType for all fields?
var keplerField = keplerFields[fieldIndex];
if (keplerField.type === _constants.ALL_FIELD_TYPES.timestamp) {
type = keplerField.type;
analyzerType = keplerField.analyzerType;
format = keplerField.format;
}
}
return _objectSpread(_objectSpread({}, field), {}, {
name: field.name,
id: field.name,
displayName: field.name,
format: format,
fieldIdx: fieldIndex,
type: type,
analyzerType: analyzerType,
valueAccessor: function valueAccessor(dc) {
return function (d) {
return dc.valueAt(d.index, fieldIndex);
};
},
metadata: field.metadata
});
});
}
/**
* Parse arrow batches returned from parseInBatches()
*
* @param arrowTable the arrow table to parse
* @returns dataset containing `fields` and `rows` or null
*/
function processArrowBatches(arrowBatches) {
if (arrowBatches.length === 0) {
return null;
}
var arrowTable = new arrow.Table(arrowBatches);
var fields = arrowSchemaToFields(arrowTable);
var cols = (0, _toConsumableArray2["default"])(Array(arrowTable.numCols).keys()).map(function (i) {
return arrowTable.getChildAt(i);
});
// return empty rows and use raw arrow table to construct column-wise data container
return {
fields: fields,
rows: [],
cols: cols,
metadata: arrowTable.schema.metadata,
// Save original arrow schema, for better ingestion into DuckDB.
// TODO consider returning arrowTable in cols, not an array of Vectors from arrowTable.
arrowSchema: arrowTable.schema
};
}
var DATASET_HANDLERS = exports.DATASET_HANDLERS = (0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])({}, _constants.DATASET_FORMATS.row, processRowObject), _constants.DATASET_FORMATS.geojson, processGeojson), _constants.DATASET_FORMATS.csv, processCsvData), _constants.DATASET_FORMATS.arrow, processArrowTable), _constants.DATASET_FORMATS.keplergl, processKeplerglDataset);
var Processors = exports.Processors = {
processGeojson: processGeojson,
processCsvData: processCsvData,
processArrowTable: processArrowTable,
processArrowBatches: processArrowBatches,
processRowObject: processRowObject,
processKeplerglJSON: processKeplerglJSON,
processKeplerglDataset: processKeplerglDataset,
analyzerTypeToFieldType: _commonUtils.analyzerTypeToFieldType,
getFieldsFromData: _commonUtils.getFieldsFromData,
parseCsvRowsByFieldType: parseCsvRowsByFieldType
};
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJhcnJvdyIsIl9pbnRlcm9wUmVxdWlyZVdpbGRjYXJkIiwicmVxdWlyZSIsIl9kM0RzdiIsIl90eXBlQW5hbHl6ZXIiLCJfZ2VvanNvbk5vcm1hbGl6ZSIsIl9pbnRlcm9wUmVxdWlyZURlZmF1bHQiLCJfY29yZSIsIl93a3QiLCJfY29uc3RhbnRzIiwiX3V0aWxzIiwiX2NvbW1vblV0aWxzIiwiX3NjaGVtYXMiLCJfZ2V0UmVxdWlyZVdpbGRjYXJkQ2FjaGUiLCJlIiwiV2Vha01hcCIsInIiLCJ0IiwiX19lc01vZHVsZSIsIl90eXBlb2YiLCJoYXMiLCJnZXQiLCJuIiwiX19wcm90b19fIiwiYSIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yIiwidSIsImhhc093blByb3BlcnR5IiwiY2FsbCIsImkiLCJzZXQiLCJvd25LZXlzIiwia2V5cyIsImdldE93blByb3BlcnR5U3ltYm9scyIsIm8iLCJmaWx0ZXIiLCJlbnVtZXJhYmxlIiwicHVzaCIsImFwcGx5IiwiX29iamVjdFNwcmVhZCIsImFyZ3VtZW50cyIsImxlbmd0aCIsImZvckVhY2giLCJfZGVmaW5lUHJvcGVydHkyIiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9ycyIsImRlZmluZVByb3BlcnRpZXMiLCJDU1ZfTlVMTFMiLCJleHBvcnRzIiwidHJ5UGFyc2VKc29uU3RyaW5nIiwic3RyIiwiSlNPTiIsInBhcnNlIiwiUEFSU0VfRklFTERfVkFMVUVfRlJPTV9TVFJJTkciLCJBTExfRklFTERfVFlQRVMiLCJ2YWxpZCIsImQiLCJpbnRlZ2VyIiwicGFyc2VJbnQiLCJ0aW1lc3RhbXAiLCJmaWVsZCIsImluY2x1ZGVzIiwiZm9ybWF0IiwiTnVtYmVyIiwicmVhbCIsInBhcnNlRmxvYXQiLCJvYmplY3QiLCJpc1BsYWluT2JqZWN0IiwiYXJyYXkiLCJBcnJheSIsImlzQXJyYXkiLCJoMyIsImgzSXNWYWxpZCIsInByb2Nlc3NDc3ZEYXRhIiwicmF3RGF0YSIsImhlYWRlciIsInJvd3MiLCJoZWFkZXJSb3ciLCJwYXJzZWRSb3dzIiwiY3N2UGFyc2VSb3dzIiwiRXJyb3IiLCJzbGljZSIsImNsZWFuVXBGYWxzeUNzdlZhbHVlIiwic2FtcGxlIiwiZ2V0U2FtcGxlRm9yVHlwZUFuYWx5emUiLCJmaWVsZHMiLCJnZXRGaWVsZHNGcm9tRGF0YSIsInBhcnNlUm93c0J5RmllbGRzIiwiZ2VvanNvbkZpZWxkSWR4IiwiZmluZEluZGV4IiwiZiIsIm5hbWUiLCJwYXJzZUNzdlJvd3NCeUZpZWxkVHlwZSIsImJpbmQiLCJyZSIsIlJlZ0V4cCIsImoiLCJtYXRjaCIsImdlb0ZpZWxkSWR4IiwicGFyc2VyIiwidHlwZSIsImZpcnN0IiwiZmluZCIsIm5vdE51bGxvclVuZGVmaW5lZCIsInJvdyIsInByb3BlcnRpZXMiLCJwcm9jZXNzUm93T2JqZWN0IiwibWFwIiwia2V5IiwicHJvY2Vzc0dlb2pzb24iLCJub3JtYWxpemVkR2VvanNvbiIsIm5vcm1hbGl6ZSIsImZlYXR1cmVzIiwiY29uY2F0IiwiR1VJREVTX0ZJTEVfRk9STUFUX0RPQyIsImFsbERhdGFSb3dzIiwiZ2VvbWV0cnkiLCJfZ2VvanNvbiIsInJlZHVjZSIsImFjY3UiLCJjdXJyIiwicHJvY2Vzc0tlcGxlcmdsSlNPTiIsInNjaGVtYSIsInVuZGVmaW5lZCIsIktlcGxlckdsU2NoZW1hIiwibG9hZCIsImRhdGFzZXRzIiwiY29uZmlnIiwicHJvY2Vzc0tlcGxlcmdsRGF0YXNldCIsInJlc3VsdHMiLCJwYXJzZVNhdmVkRGF0YSIsInRvQXJyYXkiLCJwcm9jZXNzQXJyb3dUYWJsZSIsImFycm93VGFibGUiLCJwcm9jZXNzQXJyb3dCYXRjaGVzIiwiZGF0YSIsImJhdGNoZXMiLCJnZXRHZW9BcnJvd01ldGFkYXRhRnJvbVNjaGVtYSIsInRhYmxlIiwiZ2VvQXJyb3dNZXRhZGF0YSIsIl90YWJsZSRzY2hlbWEkbWV0YWRhdCIsImdlb1N0cmluZyIsIm1ldGFkYXRhIiwicGFyc2VkR2VvU3RyaW5nIiwiY29sdW1ucyIsImNvbHVtbk5hbWUiLCJjb2x1bW5EYXRhIiwiZW5jb2RpbmciLCJHRU9BUlJPV19FWFRFTlNJT05TIiwiV0tCIiwiZXJyb3IiLCJjb25zb2xlIiwiYXJyb3dTY2hlbWFUb0ZpZWxkcyIsImZpZWxkVHlwZVN1Z2dlc3Rpb25zIiwiZ2V0U2FtcGxlRm9yVHlwZUFuYWx5emVBcnJvdyIsImtlcGxlckZpZWxkcyIsImZpZWxkSW5kZXgiLCJfZmllbGQkbWV0YWRhdGEkZ2V0IiwiYXJyb3dEYXRhVHlwZVRvRmllbGRUeXBlIiwiYW5hbHl6ZXJUeXBlIiwiYXJyb3dEYXRhVHlwZVRvQW5hbHl6ZXJEYXRhVHlwZSIsImdlb2pzb24iLCJBbmFseXplckRBVEFfVFlQRVMiLCJHRU9NRVRSWV9GUk9NX1NUUklORyIsIkdFT0FSUk9XX01FVEFEQVRBX0tFWSIsInN0YXJ0c1dpdGgiLCJnZW9hcnJvdyIsIkdFT01FVFJZIiwiX2ZpZWxkJG1ldGFkYXRhIiwiX3RhYmxlJGdldENoaWxkQXQiLCJnZXRDaGlsZEF0IiwiYmluYXJ5R2VvIiwicGFyc2VTeW5jIiwiV0tCTG9hZGVyIiwiX2ZpZWxkJG1ldGFkYXRhMiIsImtlcGxlckZpZWxkIiwiaWQiLCJkaXNwbGF5TmFtZSIsImZpZWxkSWR4IiwidmFsdWVBY2Nlc3NvciIsImRjIiwidmFsdWVBdCIsImluZGV4IiwiYXJyb3dCYXRjaGVzIiwiVGFibGUiLCJjb2xzIiwiX3RvQ29uc3VtYWJsZUFycmF5MiIsIm51bUNvbHMiLCJhcnJvd1NjaGVtYSIsIkRBVEFTRVRfSEFORExFUlMiLCJEQVRBU0VUX0ZPUk1BVFMiLCJjc3YiLCJrZXBsZXJnbCIsIlByb2Nlc3NvcnMiLCJhbmFseXplclR5cGVUb0ZpZWxkVHlwZSJdLCJzb3VyY2VzIjpbIi4uL3NyYy9kYXRhLXByb2Nlc3Nvci50cyJdLCJzb3VyY2VzQ29udGVudCI6WyIvLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogTUlUXG4vLyBDb3B5cmlnaHQgY29udHJpYnV0b3JzIHRvIHRoZSBrZXBsZXIuZ2wgcHJvamVjdFxuXG5pbXBvcnQgKiBhcyBhcnJvdyBmcm9tICdhcGFjaGUtYXJyb3cnO1xuaW1wb3J0IHtjc3ZQYXJzZVJvd3N9IGZyb20gJ2QzLWRzdic7XG5pbXBvcnQge0RBVEFfVFlQRVMgYXMgQW5hbHl6ZXJEQVRBX1RZUEVTfSBmcm9tICd0eXBlLWFuYWx5emVyJztcbmltcG9ydCBub3JtYWxpemUgZnJvbSAnQG1hcGJveC9nZW9qc29uLW5vcm1hbGl6ZSc7XG5pbXBvcnQge3BhcnNlU3luY30gZnJvbSAnQGxvYWRlcnMuZ2wvY29yZSc7XG5pbXBvcnQge0Fycm93VGFibGV9IGZyb20gJ0Bsb2FkZXJzLmdsL3NjaGVtYSc7XG5pbXBvcnQge1dLQkxvYWRlcn0gZnJvbSAnQGxvYWRlcnMuZ2wvd2t0JztcblxuaW1wb3J0IHtcbiAgQUxMX0ZJRUxEX1RZUEVTLFxuICBEQVRBU0VUX0ZPUk1BVFMsXG4gIEdFT0FSUk9XX0VYVEVOU0lPTlMsXG4gIEdFT0FSUk9XX01FVEFEQVRBX0tFWSxcbiAgR1VJREVTX0ZJTEVfRk9STUFUX0RPQ1xufSBmcm9tICdAa2VwbGVyLmdsL2NvbnN0YW50cyc7XG5pbXBvcnQge1Byb2Nlc3NvclJlc3VsdCwgRmllbGR9IGZyb20gJ0BrZXBsZXIuZ2wvdHlwZXMnO1xuaW1wb3J0IHtcbiAgYXJyb3dEYXRhVHlwZVRvQW5hbHl6ZXJEYXRhVHlwZSxcbiAgYXJyb3dEYXRhVHlwZVRvRmllbGRUeXBlLFxuICBoYXNPd25Qcm9wZXJ0eSxcbiAgaXNQbGFpbk9iamVjdFxufSBmcm9tICdAa2VwbGVyLmdsL3V0aWxzJztcbmltcG9ydCB7XG4gIGFuYWx5emVyVHlwZVRvRmllbGRUeXBlLFxuICBnZXRTYW1wbGVGb3JUeXBlQW5hbHl6ZSxcbiAgZ2V0U2FtcGxlRm9yVHlwZUFuYWx5emVBcnJvdyxcbiAgZ2V0RmllbGRzRnJvbURhdGEsXG4gIGgzSXNWYWxpZCxcbiAgbm90TnVsbG9yVW5kZWZpbmVkLFxuICB0b0FycmF5XG59IGZyb20gJ0BrZXBsZXIuZ2wvY29tbW9uLXV0aWxzJztcbmltcG9ydCB7S2VwbGVyR2xTY2hlbWEsIFBhcnNlZERhdGFzZXQsIFNhdmVkTWFwLCBMb2FkZWRNYXB9IGZyb20gJ0BrZXBsZXIuZ2wvc2NoZW1hcyc7XG5pbXBvcnQge0ZlYXR1cmV9IGZyb20gJ0BuZWJ1bGEuZ2wvZWRpdC1tb2Rlcyc7XG5cbi8vIGlmIGFueSBvZiB0aGVzZSB2YWx1ZSBvY2N1cnMgaW4gY3N2LCBwYXJzZSBpdCB0byBudWxsO1xuLy8gY29uc3QgQ1NWX05VTExTID0gWycnLCAnbnVsbCcsICdOVUxMJywgJ051bGwnLCAnTmFOJywgJy9OJ107XG4vLyBtYXRjaGVzIGVtcHR5IHN0cmluZ1xuZXhwb3J0IGNvbnN0IENTVl9OVUxMUyA9IC9eKG51bGx8TlVMTHxOdWxsfE5hTnxcXC9OfHwpJC87XG5cbmZ1bmN0aW9uIHRyeVBhcnNlSnNvblN0cmluZyhzdHIpIHtcbiAgdHJ5IHtcbiAgICByZXR1cm4gSlNPTi5wYXJzZShzdHIpO1xuICB9IGNhdGNoIChlKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbn1cblxuZXhwb3J0IGNvbnN0IFBBUlNFX0ZJRUxEX1ZBTFVFX0ZST01fU1RSSU5HID0ge1xuICBbQUxMX0ZJRUxEX1RZUEVTLmJvb2xlYW5dOiB7XG4gICAgdmFsaWQ6IChkOiB1bmtub3duKTogYm9vbGVhbiA9PiB0eXBlb2YgZCA9PT0gJ2Jvb2xlYW4nLFxuICAgIHBhcnNlOiAoZDogdW5rbm93bik6IGJvb2xlYW4gPT4gZCA9PT0gJ3RydWUnIHx8IGQgPT09ICdUcnVlJyB8fCBkID09PSAnVFJVRScgfHwgZCA9PT0gJzEnXG4gIH0sXG4gIFtBTExfRklFTERfVFlQRVMuaW50ZWdlcl06IHtcbiAgICAvLyBAdHMtaWdub3JlXG4gICAgdmFsaWQ6IChkOiB1bmtub3duKTogYm9vbGVhbiA9PiBwYXJzZUludChkLCAxMCkgPT09IGQsXG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIHBhcnNlOiAoZDogdW5rbm93bik6IG51bWJlciA9PiBwYXJzZUludChkLCAxMClcbiAgfSxcbiAgW0FMTF9GSUVMRF9UWVBFUy50aW1lc3RhbXBdOiB7XG4gICAgdmFsaWQ6IChkOiB1bmtub3duLCBmaWVsZDogRmllbGQpOiBib29sZWFuID0+XG4gICAgICBbJ3gnLCAnWCddLmluY2x1ZGVzKGZpZWxkLmZvcm1hdCkgPyB0eXBlb2YgZCA9PT0gJ251bWJlcicgOiB0eXBlb2YgZCA9PT0gJ3N0cmluZycsXG4gICAgcGFyc2U6IChkOiBhbnksIGZpZWxkOiBGaWVsZCkgPT4gKFsneCcsICdYJ10uaW5jbHVkZXMoZmllbGQuZm9ybWF0KSA/IE51bWJlcihkKSA6IGQpXG4gIH0sXG4gIFtBTExfRklFTERfVFlQRVMucmVhbF06IHtcbiAgICAvLyBAdHMtaWdub3JlXG4gICAgdmFsaWQ6IChkOiB1bmtub3duKTogYm9vbGVhbiA9PiBwYXJzZUZsb2F0KGQpID09PSBkLFxuICAgIC8vIE5vdGUgdGhpcyB3aWxsIHJlc3VsdCBpbiBOYU4gZm9yIHNvbWUgc3RyaW5nXG4gICAgcGFyc2U6IHBhcnNlRmxvYXRcbiAgfSxcbiAgW0FMTF9GSUVMRF9UWVBFUy5vYmplY3RdOiB7XG4gICAgdmFsaWQ6IGlzUGxhaW5PYmplY3QsXG4gICAgcGFyc2U6IHRyeVBhcnNlSnNvblN0cmluZ1xuICB9LFxuXG4gIFtBTExfRklFTERfVFlQRVMuYXJyYXldOiB7XG4gICAgdmFsaWQ6IEFycmF5LmlzQXJyYXksXG4gICAgcGFyc2U6IHRyeVBhcnNlSnNvblN0cmluZ1xuICB9LFxuXG4gIFtBTExfRklFTERfVFlQRVMuaDNdOiB7XG4gICAgdmFsaWQ6IGQgPT4gaDNJc1ZhbGlkKGQpLFxuICAgIHBhcnNlOiBkID0+IGRcbiAgfVxufTtcblxuLyoqXG4gKiBQcm9jZXNzIGNzdiBkYXRhLCBvdXRwdXQgYSBkYXRhIG9iamVjdCB3aXRoIGB7ZmllbGRzOiBbXSwgcm93czogW119YC5cbiAqIFRoZSBkYXRhIG9iamVjdCBjYW4gYmUgd3JhcHBlZCBpbiBhIGBkYXRhc2V0YCBhbmQgcGFzcyB0byBbYGFkZERhdGFUb01hcGBdKC4uL2FjdGlvbnMvYWN0aW9ucy5tZCNhZGRkYXRhdG9tYXApXG4gKiBAcGFyYW0gcmF3RGF0YSByYXcgY3N2IHN0cmluZ1xuICogQHJldHVybnMgZGF0YSBvYmplY3QgYHtmaWVsZHM6IFtdLCByb3dzOiBbXX1gIGNhbiBiZSBwYXNzZWQgdG8gYWRkRGF0YVRvTWFwc1xuICogQHB1YmxpY1xuICogQGV4YW1wbGVcbiAqIGltcG9ydCB7cHJvY2Vzc0NzdkRhdGF9IGZyb20gJ2tlcGxlci5nbC9wcm9jZXNzb3JzJztcbiAqXG4gKiBjb25zdCB0ZXN0RGF0YSA9IGBncHNfZGF0YS51dGNfdGltZXN0YW1wLGdwc19kYXRhLmxhdCxncHNfZGF0YS5sbmcsZ3BzX2RhdGEudHlwZXMsZXBvY2gsaGFzX3Jlc3VsdCxpZCx0aW1lLGJlZ2ludHJpcF90c191dGMsYmVnaW50cmlwX3RzX2xvY2FsLGRhdGVcbiAqIDIwMTYtMDktMTcgMDA6MDk6NTUsMjkuOTkwMDkzNywzMS4yNTkwNTQyLGRyaXZlcl9hbmFseXRpY3MsMTQ3MjY4ODAwMDAwMCxGYWxzZSwxLDIwMTYtMDktMjNUMDA6MDA6MDAuMDAwWiwyMDE2LTEwLTAxIDA5OjQxOjM5KzAwOjAwLDIwMTYtMTAtMDEgMDk6NDE6MzkrMDA6MDAsMjAxNi0wOS0yM1xuICogMjAxNi0wOS0xNyAwMDoxMDo1NiwyOS45OTI3Njk5LDMxLjI0NjExNDIsZHJpdmVyX2FuYWx5dGljcywxNDcyNjg4MDAwMDAwLEZhbHNlLDIsMjAxNi0wOS0yM1QwMDowMDowMC4wMDBaLDIwMTYtMTAtMDEgMDk6NDY6MzcrMDA6MDAsMjAxNi0xMC0wMSAxNjo0NjozNyswMDowMCwyMDE2LTA5LTIzXG4gKiAyMDE2LTA5LTE3IDAwOjExOjU2LDI5Ljk5MDcyNjEsMzEuMjMxMjc0Mixkcml2ZXJfYW5hbHl0aWNzLDE0NzI2ODgwMDAwMDAsRmFsc2UsMywyMDE2LTA5LTIzVDAwOjAwOjAwLjAwMFosLCwyMDE2LTA5LTIzXG4gKiAyMDE2LTA5LTE3IDAwOjEyOjU4LDI5Ljk4NzAwNzQsMzEuMjE3NTgyNyxkcml2ZXJfYW5hbHl0aWNzLDE0NzI2ODgwMDAwMDAsRmFsc2UsNCwyMDE2LTA5LTIzVDAwOjAwOjAwLjAwMFosLCwyMDE2LTA5LTIzYFxuICpcbiAqIGNvbnN0IGRhdGFzZXQgPSB7XG4gKiAgaW5mbzoge2lkOiAndGVzdF9kYXRhJywgbGFiZWw6ICdNeSBDc3YnfSxcbiAqICBkYXRhOiBwcm9jZXNzQ3N2RGF0YSh0ZXN0RGF0YSlcbiAqIH07XG4gKlxuICogZGlzcGF0Y2goYWRkRGF0YVRvTWFwKHtcbiAqICBkYXRhc2V0czogW2RhdGFzZXRdLFxuICogIG9wdGlvbnM6IHtjZW50ZXJNYXA6IHRydWUsIHJlYWRPbmx5OiB0cnVlfVxuICogfSkpO1xuICovXG5leHBvcnQgZnVuY3Rpb24gcHJvY2Vzc0NzdkRhdGEocmF3RGF0YTogdW5rbm93bltdW10gfCBzdHJpbmcsIGhlYWRlcj86IHN0cmluZ1tdKTogUHJvY2Vzc29yUmVzdWx0IHtcbiAgbGV0IHJvd3M6IHVua25vd25bXVtdIHwgdW5kZWZpbmVkO1xuICBsZXQgaGVhZGVyUm93OiBzdHJpbmdbXSB8IHVuZGVmaW5lZDtcblxuICBpZiAodHlwZW9mIHJhd0RhdGEgPT09ICdzdHJpbmcnKSB7XG4gICAgY29uc3QgcGFyc2VkUm93czogc3RyaW5nW11bXSA9IGNzdlBhcnNlUm93cyhyYXdEYXRhKTtcblxuICAgIGlmICghQXJyYXkuaXNBcnJheShwYXJzZWRSb3dzKSB8fCBwYXJzZWRSb3dzLmxlbmd0aCA8IDIpIHtcbiAgICAgIC8vIGxvb2tzIGxpa2UgYW4gZW1wdHkgZmlsZSwgdGhyb3cgZXJyb3IgdG8gYmUgY2F0Y2hcbiAgICAgIHRocm93IG5ldyBFcnJvcigncHJvY2VzcyBDc3YgRGF0YSBGYWlsZWQ6IENTViBpcyBlbXB0eScpO1xuICAgIH1cbiAgICBoZWFkZXJSb3cgPSBwYXJzZWRSb3dzWzBdO1xuICAgIHJvd3MgPSBwYXJzZWRSb3dzLnNsaWNlKDEpO1xuICB9IGVsc2UgaWYgKEFycmF5LmlzQXJyYXkocmF3RGF0YSkgJiYgcmF3RGF0YS5sZW5ndGgpIHtcbiAgICByb3dzID0gcmF3RGF0YTtcbiAgICBoZWFkZXJSb3cgPSBoZWFkZXI7XG5cbiAgICBpZiAoIUFycmF5LmlzQXJyYXkoaGVhZGVyUm93KSkge1xuICAgICAgLy8gaWYgZGF0YSBpcyBwYXNzZWQgaW4gYXMgYXJyYXkgb2Ygcm93cyBhbmQgbWlzc2luZyBoZWFkZXJcbiAgICAgIC8vIGFzc3VtZSBmaXJzdCByb3cgaXMgaGVhZGVyXG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBoZWFkZXJSb3cgPSByYXdEYXRhWzBdO1xuICAgICAgcm93cyA9IHJhd0RhdGEuc2xpY2UoMSk7XG4gICAgfVxuICB9XG5cbiAgaWYgKCFyb3dzIHx8ICFoZWFkZXJSb3cpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ2ludmFsaWQgaW5wdXQgcGFzc2VkIHRvIHByb2Nlc3NDc3ZEYXRhJyk7XG4gIH1cblxuICAvLyBoZXJlIHdlIGFzc3VtZSB0aGUgY3N2IGZpbGUgdGhhdCBwZW9wbGUgdXBsb2FkZWQgd2lsbCBoYXZlIGZpcnN0IHJvd1xuICAvLyBhcyBuYW1lIG9mIHRoZSBjb2x1bW5cblxuICBjbGVhblVwRmFsc3lDc3ZWYWx1ZShyb3dzKTtcbiAgLy8gTm8gbmVlZCB0byBydW4gdHlwZSBkZXRlY3Rpb24gb24gZXZlcnkgZGF0YSBwb2ludFxuICAvLyBoZXJlIHdlIGdldCBhIGxpc3Qgb2Ygbm9uZSBudWxsIHZhbHVlcyB0byBydW4gYW5hbHl6ZSBvblxuICBjb25zdCBzYW1wbGUgPSBnZXRTYW1wbGVGb3JUeXBlQW5hbHl6ZSh7ZmllbGRzOiBoZWFkZXJSb3csIHJvd3N9KTtcbiAgY29uc3QgZmllbGRzID0gZ2V0RmllbGRzRnJvbURhdGEoc2FtcGxlLCBoZWFkZXJSb3cpO1xuICBjb25zdCBwYXJzZWRSb3dzID0gcGFyc2VSb3dzQnlGaWVsZHMocm93cywgZmllbGRzKTtcblxuICByZXR1cm4ge2ZpZWxkcywgcm93czogcGFyc2VkUm93c307XG59XG5cbi8qKlxuICogUGFyc2Ugcm93cyBvZiBjc3YgYnkgYW5hbHl6ZWQgZmllbGQgdHlwZXMuIFNvIHRoYXQgYCcxJ2AgLT4gYDFgLCBgJ1RydWUnYCAtPiBgdHJ1ZWBcbiAqIEBwYXJhbSByb3dzXG4gKiBAcGFyYW0gZmllbGRzXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwYXJzZVJvd3NCeUZpZWxkcyhyb3dzOiBhbnlbXVtdLCBmaWVsZHM6IEZpZWxkW10pIHtcbiAgLy8gRWRpdCByb3dzIGluIHBsYWNlXG4gIGNvbnN0IGdlb2pzb25GaWVsZElkeCA9IGZpZWxkcy5maW5kSW5kZXgoZiA9PiBmLm5hbWUgPT09ICdfZ2VvanNvbicpO1xuICBmaWVsZHMuZm9yRWFjaChwYXJzZUNzdlJvd3NCeUZpZWxkVHlwZS5iaW5kKG51bGwsIHJvd3MsIGdlb2pzb25GaWVsZElkeCkpO1xuXG4gIHJldHVybiByb3dzO1xufVxuXG4vKipcbiAqIENvbnZlcnQgZmFsc3kgdmFsdWUgaW4gY3N2IGluY2x1ZGluZyBgJycsICdudWxsJywgJ05VTEwnLCAnTnVsbCcsICdOYU4nYCB0byBgbnVsbGAsXG4gKiBzbyB0aGF0IHR5cGUtYW5hbHl6ZXIgd29uJ3QgZGV0ZWN0IGl0IGFzIHN0cmluZ1xuICpcbiAqIEBwYXJhbSByb3dzXG4gKi9cbmZ1bmN0aW9uIGNsZWFuVXBGYWxzeUNzdlZhbHVlKHJvd3M6IHVua25vd25bXVtdKTogdm9pZCB7XG4gIGNvbnN0IHJlID0gbmV3IFJlZ0V4cChDU1ZfTlVMTFMsICdnJyk7XG4gIGZvciAobGV0IGkgPSAwOyBpIDwgcm93cy5sZW5ndGg7IGkrKykge1xuICAgIGZvciAobGV0IGogPSAwOyBqIDwgcm93c1tpXS5sZW5ndGg7IGorKykge1xuICAgICAgLy8gYW5hbHl6ZXIgd2lsbCBzZXQgYW55IGZpZWxkcyB0byAnc3RyaW5nJyBpZiB0aGVyZSBhcmUgZW1wdHkgdmFsdWVzXG4gICAgICAvLyB3aGljaCB3aWxsIGJlIHBhcnNlZCBhcyAnJyBieSBkMy5jc3ZcbiAgICAgIC8vIGhlcmUgd2UgcGFyc2UgZW1wdHkgZGF0YSBhcyBudWxsXG4gICAgICAvLyBUT0RPOiBjcmVhdGUgd2FybmluZyB3aGVuIGRlbHRlY3QgYENTVl9OVUxMU2AgaW4gdGhlIGRhdGFcbiAgICAgIGlmICh0eXBlb2Ygcm93c1tpXVtqXSA9PT0gJ3N0cmluZycgJiYgKHJvd3NbaV1bal0gYXMgc3RyaW5nKS5tYXRjaChyZSkpIHtcbiAgICAgICAgcm93c1tpXVtqXSA9IG51bGw7XG4gICAgICB9XG4gICAgfVxuICB9XG59XG5cbi8qKlxuICogUHJvY2VzcyB1cGxvYWRlZCBjc3YgZmlsZSB0byBwYXJzZSB2YWx1ZSBieSBmaWVsZCB0eXBlXG4gKlxuICogQHBhcmFtIHJvd3NcbiAqIEBwYXJhbSBnZW9GaWVsZElkeCBmaWVsZCBpbmRleFxuICogQHBhcmFtIGZpZWxkXG4gKiBAcGFyYW0gaVxuICovXG5leHBvcnQgZnVuY3Rpb24gcGFyc2VDc3ZSb3dzQnlGaWVsZFR5cGUoXG4gIHJvd3M6IHVua25vd25bXVtdLFxuICBnZW9GaWVsZElkeDogbnVtYmVyLFxuICBmaWVsZDogRmllbGQsXG4gIGk6IG51bWJlclxuKTogdm9pZCB7XG4gIGNvbnN0IHBhcnNlciA9IFBBUlNFX0ZJRUxEX1ZBTFVFX0ZST01fU1RSSU5HW2ZpZWxkLnR5cGVdO1xuICBpZiAocGFyc2VyKSB7XG4gICAgLy8gY2hlY2sgZmlyc3Qgbm90IG51bGwgdmFsdWUgb2YgaXQncyBhbHJlYWR5IHBhcnNlZFxuICAgIGNvbnN0IGZpcnN0ID0gcm93cy5maW5kKHIgPT4gbm90TnVsbG9yVW5kZWZpbmVkKHJbaV0pKTtcbiAgICBpZiAoIWZpcnN0IHx8IHBhcnNlci52YWxpZChmaXJzdFtpXSwgZmllbGQpKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHJvd3MuZm9yRWFjaChyb3cgPT4ge1xuICAgICAgLy8gcGFyc2Ugc3RyaW5nIHZhbHVlIGJhc2VkIG9uIGZpZWxkIHR5cGVcbiAgICAgIGlmIChyb3dbaV0gIT09IG51bGwpIHtcbiAgICAgICAgcm93W2ldID0gcGFyc2VyLnBhcnNlKHJvd1tpXSwgZmllbGQpO1xuICAgICAgICBpZiAoXG4gICAgICAgICAgZ2VvRmllbGRJZHggPiAtMSAmJlxuICAgICAgICAgIGlzUGxhaW5PYmplY3Qocm93W2dlb0ZpZWxkSWR4XSkgJiZcbiAgICAgICAgICAvLyBAdHMtaWdub3JlXG4gICAgICAgICAgaGFzT3duUHJvcGVydHkocm93W2dlb0ZpZWxkSWR4XSwgJ3Byb3BlcnRpZXMnKVxuICAgICAgICApIHtcbiAgICAgICAgICAvLyBAdHMtaWdub3JlXG4gICAgICAgICAgcm93W2dlb0ZpZWxkSWR4XS5wcm9wZXJ0aWVzW2ZpZWxkLm5hbWVdID0gcm93W2ldO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSk7XG4gIH1cbn1cblxuLyogZXNsaW50LWVuYWJsZSBjb21wbGV4aXR5ICovXG5cbi8qKlxuICogUHJvY2VzcyBkYXRhIHdoZXJlIGVhY2ggcm93IGlzIGFuIG9iamVjdCwgb3V0cHV0IGNhbiBiZSBwYXNzZWQgdG8gW2BhZGREYXRhVG9NYXBgXSguLi9hY3Rpb25zL2FjdGlvbnMubWQjYWRkZGF0YXRvbWFwKVxuICogTk9URTogVGhpcyBmdW5jdGlvbiBtYXkgbXV0YXRlIGlucHV0LlxuICogQHBhcmFtIHJhd0RhdGEgYW4gYXJyYXkgb2Ygcm93IG9iamVjdCwgZWFjaCBvYmplY3Qgc2hvdWxkIGhhdmUgdGhlIHNhbWUgbnVtYmVyIG9mIGtleXNcbiAqIEByZXR1cm5zIGRhdGFzZXQgY29udGFpbmluZyBgZmllbGRzYCBhbmQgYHJvd3NgXG4gKiBAcHVibGljXG4gKiBAZXhhbXBsZVxuICogaW1wb3J0IHthZGREYXRhVG9NYXB9IGZyb20gJ2tlcGxlci5nbC9hY3Rpb25zJztcbiAqIGltcG9ydCB7cHJvY2Vzc1Jvd09iamVjdH0gZnJvbSAna2VwbGVyLmdsL3Byb2Nlc3NvcnMnO1xuICpcbiAqIGNvbnN0IGRhdGEgPSBbXG4gKiAge2xhdDogMzEuMjcsIGxuZzogMTI3LjU2LCB2YWx1ZTogM30sXG4gKiAge2xhdDogMzEuMjIsIGxuZzogMTI2LjI2LCB2YWx1ZTogMX1cbiAqIF07XG4gKlxuICogZGlzcGF0Y2goYWRkRGF0YVRvTWFwKHtcbiAqICBkYXRhc2V0czoge1xuICogICAgaW5mbzoge2xhYmVsOiAnTXkgRGF0YScsIGlkOiAnbXlfZGF0YSd9LFxuICogICAgZGF0YTogcHJvY2Vzc1Jvd09iamVjdChkYXRhKVxuICogIH1cbiAqIH0pKTtcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHByb2Nlc3NSb3dPYmplY3QocmF3RGF0YTogdW5rbm93bltdKTogUHJvY2Vzc29yUmVzdWx0IHtcbiAgaWYgKCFBcnJheS5pc0FycmF5KHJhd0RhdGEpKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH0gZWxzZSBpZiAoIXJhd0RhdGEubGVuZ3RoKSB7XG4gICAgLy8gZGF0YSBpcyBlbXB0eVxuICAgIHJldHVybiB7XG4gICAgICBmaWVsZHM6IFtdLFxuICAgICAgcm93czogW11cbiAgICB9O1xuICB9XG5cbiAgY29uc3Qga2V5cyA9IE9iamVjdC5rZXlzKHJhd0RhdGFbMF0pOyAvLyBbbGF0LCBsbmcsIHZhbHVlXVxuICBjb25zdCByb3dzID0gcmF3RGF0YS5tYXAoZCA9PiBrZXlzLm1hcChrZXkgPT4gZFtrZXldKSk7IC8vIFtbMzEuMjcsIDEyNy41NiwgM11dXG5cbiAgLy8gcm93IG9iamVjdCBjYW4gc3RpbGwgY29udGFpbiB2YWx1ZXMgbGlrZSBgTnVsbGAgb3IgYE4vQWBcbiAgY2xlYW5VcEZhbHN5Q3N2VmFsdWUocm93cyk7XG5cbiAgcmV0dXJuIHByb2Nlc3NDc3ZEYXRhKHJvd3MsIGtleXMpO1xufVxuXG4vKipcbiAqIFByb2Nlc3MgR2VvSlNPTiBbYEZlYXR1cmVDb2xsZWN0aW9uYF0oaHR0cDovL3dpa2kuZ2VvanNvbi5vcmcvR2VvSlNPTl9kcmFmdF92ZXJzaW9uXzYjRmVhdHVyZUNvbGxlY3Rpb24pLFxuICogb3V0cHV0IGEgZGF0YSBvYmplY3Qgd2l0aCBge2ZpZWxkczogW10sIHJvd3M6IFtdfWAuXG4gKiBUaGUgZGF0YSBvYmplY3QgY2FuIGJlIHdyYXBwZWQgaW4gYSBgZGF0YXNldGAgYW5kIHBhc3NlZCB0byBbYGFkZERhdGFUb01hcGBdKC4uL2FjdGlvbnMvYWN0aW9ucy5tZCNhZGRkYXRhdG9tYXApXG4gKiBOT1RFOiBUaGlzIGZ1bmN0aW9uIG1heSBtdXRhdGUgaW5wdXQuXG4gKlxuICogQHBhcmFtIHJhd0RhdGEgcmF3IGdlb2pzb24gZmVhdHVyZSBjb2xsZWN0aW9uXG4gKiBAcmV0dXJucyBkYXRhc2V0IGNvbnRhaW5pbmcgYGZpZWxkc2AgYW5kIGByb3dzYFxuICogQHB1YmxpY1xuICogQGV4YW1wbGVcbiAqIGltcG9ydCB7YWRkRGF0YVRvTWFwfSBmcm9tICdrZXBsZXIuZ2wvYWN0aW9ucyc7XG4gKiBpbXBvcnQge3Byb2Nlc3NHZW9qc29ufSBmcm9tICdrZXBsZXIuZ2wvcHJvY2Vzc29ycyc7XG4gKlxuICogY29uc3QgZ2VvanNvbiA9IHtcbiAqIFx0XCJ0eXBlXCIgOiBcIkZlYXR1cmVDb2xsZWN0aW9uXCIsXG4gKiBcdFwiZmVhdHVyZXNcIiA6IFt7XG4gKiBcdFx0XCJ0eXBlXCIgOiBcIkZlYXR1cmVcIixcbiAqIFx0XHRcInByb3BlcnRpZXNcIiA6IHtcbiAqIFx0XHRcdFwiY2FwYWNpdHlcIiA6IFwiMTBcIixcbiAqIFx0XHRcdFwidHlwZVwiIDogXCJVLVJhY2tcIlxuICogXHRcdH0sXG4gKiBcdFx0XCJnZW9tZXRyeVwiIDoge1xuICogXHRcdFx0XCJ0eXBlXCIgOiBcIlBvaW50XCIsXG4gKiBcdFx0XHRcImNvb3JkaW5hdGVzXCIgOiBbIC03MS4wNzMyODMsIDQyLjQxNzUwMCBdXG4gKiBcdFx0fVxuICogXHR9XVxuICogfTtcbiAqXG4gKiBkaXNwYXRjaChhZGREYXRhVG9NYXAoe1xuICogIGRhdGFzZXRzOiB7XG4gKiAgICBpbmZvOiB7XG4gKiAgICAgIGxhYmVsOiAnU2FtcGxlIFRheGkgVHJpcHMgaW4gTmV3IFlvcmsgQ2l0eScsXG4gKiAgICAgIGlkOiAndGVzdF90cmlwX2RhdGEnXG4gKiAgICB9LFxuICogICAgZGF0YTogcHJvY2Vzc0dlb2pzb24oZ2VvanNvbilcbiAqICB9XG4gKiB9KSk7XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwcm9jZXNzR2VvanNvbihyYXdEYXRhOiB1bmtub3duKTogUHJvY2Vzc29yUmVzdWx0IHtcbiAgY29uc3Qgbm9ybWFsaXplZEdlb2pzb24gPSBub3JtYWxpemUocmF3RGF0YSk7XG5cbiAgaWYgKCFub3JtYWxpemVkR2VvanNvbiB8fCAhQXJyYXkuaXNBcnJheShub3JtYWxpemVkR2VvanNvbi5mZWF0dXJlcykpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICBgUmVhZCBGaWxlIEZhaWxlZDogRmlsZSBpcyBub3QgYSB2YWxpZCBHZW9KU09OLiBSZWFkIG1vcmUgYWJvdXQgW3N1cHBvcnRlZCBmaWxlIGZvcm1hdF0oJHtHVUlERVNfRklMRV9GT1JNQVRfRE9DfSlgXG4gICAgKTtcbiAgfVxuXG4gIC8vIGdldHRpbmcgYWxsIGZlYXR1cmUgZmllbGRzXG4gIGNvbnN0IGFsbERhdGFSb3dzOiBBcnJheTx7X2dlb2pzb246IEZlYXR1cmV9ICYga2V5b2YgRmVhdHVyZT4gPSBbXTtcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBub3JtYWxpemVkR2VvanNvbi5mZWF0dXJlcy5sZW5ndGg7IGkrKykge1xuICAgIGNvbnN0IGYgPSBub3JtYWxpemVkR2VvanNvbi5mZWF0dXJlc1tpXTtcbiAgICBpZiAoZi5nZW9tZXRyeSkge1xuICAgICAgYWxsRGF0YVJvd3MucHVzaCh7XG4gICAgICAgIC8vIGFkZCBmZWF0dXJlIHRvIF9nZW9qc29uIGZpZWxkXG4gICAgICAgIF9nZW9qc29uOiBmLFxuICAgICAgICAuLi4oZi5wcm9wZXJ0aWVzIHx8IHt9KVxuICAgICAgfSk7XG4gICAgfVxuICB9XG4gIC8vIGdldCBhbGwgdGhlIGZpZWxkXG4gIGNvbnN0IGZpZWxkcyA9IGFsbERhdGFSb3dzLnJlZHVjZTxzdHJpbmdbXT4oKGFjY3UsIGN1cnIpID0+IHtcbiAgICBPYmplY3Qua2V5cyhjdXJyKS5mb3JFYWNoKGtleSA9PiB7XG4gICAgICBpZiAoIWFjY3UuaW5jbHVkZXMoa2V5KSkge1xuICAgICAgICBhY2N1LnB1c2goa2V5KTtcbiAgICAgIH1cbiAgICB9KTtcbiAgICByZXR1cm4gYWNjdTtcbiAgfSwgW10pO1xuXG4gIC8vIG1ha2Ugc3VyZSBlYWNoIGZlYXR1cmUgaGFzIGV4YWN0IHNhbWUgZmllbGRzXG4gIGFsbERhdGFSb3dzLmZvckVhY2goZCA9PiB7XG4gICAgZmllbGRzLmZvckVhY2goZiA9PiB7XG4gICAgICBpZiAoIShmIGluIGQpKSB7XG4gICAgICAgIGRbZl0gPSBudWxsO1xuICAgICAgICBpZiAoZC5fZ2VvanNvbi5wcm9wZXJ0aWVzKSB7XG4gICAgICAgICAgZC5fZ2VvanNvbi5wcm9wZXJ0aWVzW2ZdID0gbnVsbDtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0pO1xuICB9KTtcblxuICByZXR1cm4gcHJvY2Vzc1Jvd09iamVjdChhbGxEYXRhUm93cyk7XG59XG5cbi8qKlxuICogUHJvY2VzcyBzYXZlZCBrZXBsZXIuZ2wganNvbiB0byBiZSBwYXNzIHRvIFtgYWRkRGF0YVRvTWFwYF0oLi4vYWN0aW9ucy9hY3Rpb25zLm1kI2FkZGRhdGF0b21hcCkuXG4gKiBUaGUganNvbiBvYmplY3Qgc2hvdWxkIGNvbnRhaW4gYGRhdGFzZXRzYCBhbmQgYGNvbmZpZ2AuXG4gKiBAcGFyYW0gcmF3RGF0YVxuICogQHBhcmFtIHNjaGVtYVxuICogQHJldHVybnMgZGF0YXNldHMgYW5kIGNvbmZpZyBge2RhdGFzZXRzOiB7fSwgY29uZmlnOiB7fX1gXG4gKiBAcHVibGljXG4gKiBAZXhhbXBsZVxuICogaW1wb3J0IHthZGREYXRhVG9NYXB9IGZyb20gJ2tlcGxlci5nbC9hY3Rpb25zJztcbiAqIGltcG9ydCB7cHJvY2Vzc0tlcGxlcmdsSlNPTn0gZnJvbSAna2VwbGVyLmdsL3Byb2Nlc3NvcnMnO1xuICpcbiAqIGRpc3BhdGNoKGFkZERhdGFUb01hcChwcm9jZXNzS2VwbGVyZ2xKU09OKGtlcGxlckdsSnNvbikpKTtcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHByb2Nlc3NLZXBsZXJnbEpTT04ocmF3RGF0YTogU2F2ZWRNYXAsIHNjaGVtYSA9IEtlcGxlckdsU2NoZW1hKTogTG9hZGVkTWFwIHwgbnVsbCB7XG4gIHJldHVybiByYXdEYXRhID8gc2NoZW1hLmxvYWQocmF3RGF0YS5kYXRhc2V0cywgcmF3RGF0YS5jb25maWcpIDogbnVsbDtcbn1cblxuLyoqXG4gKiBQYXJzZSBhIHNpbmdsZSBvciBhbiBhcnJheSBvZiBkYXRhc2V0cyBzYXZlZCB1c2luZyBrZXBsZXIuZ2wgc2NoZW1hXG4gKiBAcGFyYW0gcmF3RGF0YVxuICogQHBhcmFtIHNjaGVtYVxuICovXG5leHBvcnQgZnVuY3Rpb24gcHJvY2Vzc0tlcGxlcmdsRGF0YXNldChcbiAgcmF3RGF0YTogb2JqZWN0IHwgb2JqZWN0W10sXG4gIHNjaGVtYSA9IEtlcGxlckdsU2NoZW1hXG4pOiBQYXJzZWREYXRhc2V0IHwgUGFyc2VkRGF0YXNldFtdIHwgbnVsbCB7XG4gIGlmICghcmF3RGF0YSkge1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgY29uc3QgcmVzdWx0cyA9IHNjaGVtYS5wYXJzZVNhdmVkRGF0YSh0b0FycmF5KHJhd0RhdGEpKTtcbiAgaWYgKCFyZXN1bHRzKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbiAgcmV0dXJuIEFycmF5LmlzQXJyYXkocmF3RGF0YSkgPyByZXN1bHRzIDogcmVzdWx0c1swXTtcbn1cblxuLyoqXG4gKiBQYXJzZSBhcnJvdyB0YWJsZSBhbmQgcmV0dXJuIGEgZGF0YXNldFxuICpcbiAqIEBwYXJhbSBhcnJvd1RhYmxlIEFycm93VGFibGUgdG8gcGFyc2UsIHNlZSBsb2FkZXJzLmdsL3NjaGVtYVxuICogQHJldHVybnMgZGF0YXNldCBjb250YWluaW5nIGBmaWVsZHNgIGFuZCBgcm93c2Agb3IgbnVsbFxuICovXG5leHBvcnQgZnVuY3Rpb24gcHJvY2Vzc0Fycm93VGFibGUoYXJyb3dUYWJsZTogQXJyb3dUYWJsZSk6IFByb2Nlc3NvclJlc3VsdCB8IG51bGwge1xuICAvLyBAdHMtaWdub3JlIC0gVW5rbm93biBkYXRhIHR5cGUgY2F1c2luZyBidWlsZCBmYWlsdXJlc1xuICByZXR1cm4gcHJvY2Vzc0Fycm93QmF0Y2hlcyhhcnJvd1RhYmxlLmRhdGEuYmF0Y2hlcyk7XG59XG5cbi8qKlxuICogRXh0cmFjdHMgR2VvQXJyb3cgbWV0YWRhdGEgZnJvbSBhbiBBcGFjaGUgQXJyb3cgdGFibGUgc2NoZW1hLlxuICogRm9yIGdlb3BhcnF1ZXQgZmlsZXMgZ2VvYXJyb3cgbWV0YWRhdGEgaXNuJ3QgcHJlc2VudCBpbiBmaWVsZHMsIHNvIGV4dHJhY3QgZXh0cmEgaW5mbyBmcm9tIHNjaGVtYS5cbiAqIEBwYXJhbSB0YWJsZSBUaGUgQXBhY2hlIEFycm93IHRhYmxlIHRvIGV4dHJhY3QgbWV0YWRhdGEgZnJvbS5cbiAqIEByZXR1cm5zIEFuIG9iamVjdCBtYXBwaW5nIGNvbHVtbiBuYW1lcyB0byB0aGVpciBHZW9BcnJvdyBlbmNvZGluZyB0eXBlLlxuICogQHRocm93cyBMb2dzIGFuIGVycm9yIG1lc3NhZ2UgaWYgcGFyc2luZyBvZiBtZXRhZGF0YSBmYWlscy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldEdlb0Fycm93TWV0YWRhdGFGcm9tU2NoZW1hKHRhYmxlOiBhcnJvdy5UYWJsZSk6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4ge1xuICBjb25zdCBnZW9BcnJvd01ldGFkYXRhOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge307XG4gIHRyeSB7XG4gICAgY29uc3QgZ2VvU3RyaW5nID0gdGFibGUuc2NoZW1hLm1ldGFkYXRhPy5nZXQoJ2dlbycpO1xuICAgIGlmIChnZW9TdHJpbmcpIHtcbiAgICAgIGNvbnN0IHBhcnNlZEdlb1N0cmluZyA9IEpTT04ucGFyc2UoZ2VvU3RyaW5nKTtcbiAgICAgIGlmIChwYXJzZWRHZW9TdHJpbmcuY29sdW1ucykge1xuICAgICAgICBPYmplY3Qua2V5cyhwYXJzZWRHZW9TdHJpbmcuY29sdW1ucykuZm9yRWFjaChjb2x1bW5OYW1lID0+IHtcbiAgICAgICAgICBjb25zdCBjb2x1bW5EYXRhID0gcGFyc2VkR2VvU3RyaW5nLmNvbHVtbnNbY29sdW1uTmFtZV07XG4gICAgICAgICAgaWYgKGNvbHVtbkRhdGE/LmVuY29kaW5nID09PSAnV0tCJykge1xuICAgICAgICAgICAgZ2VvQXJyb3dNZXRhZGF0YVtjb2x1bW5OYW1lXSA9IEdFT0FSUk9XX0VYVEVOU0lPTlMuV0tCO1xuICAgICAgICAgIH1cbiAgICAgICAgICAvLyBUT0RPIHBvdGVudGlhbGx5IHRoZXJlIGFyZSBvdGhlciB0eXBlcyBidXQgbm8gZGF0YXNldHMgdG8gdGVzdFxuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICB9XG4gIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgY29uc29sZS5lcnJvcignQW4gZXJyb3IgZHVyaW5nIGFycm93IHRhYmxlIHNjaGVtYSBtZXRhZGF0YSBwYXJzaW5nJyk7XG4gIH1cbiAgcmV0dXJuIGdlb0Fycm93TWV0YWRhdGE7XG59XG5cbi8qKlxuICogQ29udmVydHMgYW4gQXBhY2hlIEFycm93IHRhYmxlIHNjaGVtYSBpbnRvIGFuIGFycmF5IG9mIEtlcGxlci5nbCBmaWVsZCBvYmplY3RzLlxuICogQHBhcmFtIHRhYmxlIFRoZSBBcGFjaGUgQXJyb3cgdGFibGUgd2hvc2Ugc2NoZW1hIG5lZWRzIHRvIGJlIGNvbnZlcnRlZC5cbiAqIEBwYXJhbSBmaWVsZFR5cGVTdWdnZXN0aW9ucyBPcHRpb25hbCBtYXBwaW5nIG9mIGZpZWxkIG5hbWVzIHRvIHN1Z2dlc3RlZCBmaWVsZCB0eXBlcy5cbiAqIEByZXR1cm5zIEFuIGFycmF5IG9mIGZpZWxkIG9iamVjdHMgc3VpdGFibGUgZm9yIEtlcGxlci5nbC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGFycm93U2NoZW1hVG9GaWVsZHMoXG4gIHRhYmxlOiBhcnJvdy5UYWJsZSxcbiAgZmllbGRUeXBlU3VnZ2VzdGlvbnM6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4gPSB7fVxuKTogRmllbGRbXSB7XG4gIGNvbnN0IGhlYWRlclJvdyA9IHRhYmxlLnNjaGVtYS5maWVsZHMubWFwKGYgPT4gZi5uYW1lKTtcbiAgY29uc3Qgc2FtcGxlID0gZ2V0U2FtcGxlRm9yVHlwZUFuYWx5emVBcnJvdyh0YWJsZSwgaGVhZGVyUm93KTtcbiAgY29uc3Qga2VwbGVyRmllbGRzID0gZ2V0RmllbGRzRnJvbURhdGEoc2FtcGxlLCBoZWFkZXJSb3cpO1xuICBjb25zdCBnZW9BcnJvd01ldGFkYXRhID0gZ2V0R2VvQXJyb3dNZXRhZGF0YUZyb21TY2hlbWEodGFibGUpO1xuXG4gIHJldHVybiB0YWJsZS5zY2hlbWEuZmllbGRzLm1hcCgoZmllbGQ6IGFycm93LkZpZWxkLCBmaWVsZEluZGV4OiBudW1iZXIpID0+IHtcbiAgICBsZXQgdHlwZSA9IGFycm93RGF0YVR5cGVUb0ZpZWxkVHlwZShmaWVsZC50eXBlKTtcbiAgICBsZXQgYW5hbHl6ZXJUeXBlID0gYXJyb3dEYXRhVHlwZVRvQW5hbHl6ZXJEYXRhVHlwZShmaWVsZC50eXBlKTtcbiAgICBsZXQgZm9ybWF0ID0gJyc7XG5cbiAgICAvLyBnZW9tZXRyeSBmaWVsZHMgcHJvZHVjZWQgYnkgRHVja0RCJ3Mgc3RfYXNnZW9qc29uKClcbiAgICBpZiAoZmllbGRUeXBlU3VnZ2VzdGlvbnNbZmllbGQubmFtZV0gPT09ICdKU09OJykge1xuICAgICAgdHlwZSA9IEFMTF9GSUVMRF9UWVBFUy5nZW9qc29uO1xuICAgICAgYW5hbHl6ZXJUeXBlID0gQW5hbHl6ZXJEQVRBX1RZUEVTLkdFT01FVFJZX0ZST01fU1RSSU5HO1xuICAgIH0gZWxzZSBpZiAoXG4gICAgICBmaWVsZFR5cGVTdWdnZXN0aW9uc1tmaWVsZC5uYW1lXSA9PT0gJ0dFT01FVFJZJyB8fFxuICAgICAgZmllbGQubWV0YWRhdGEuZ2V0KEdFT0FSUk9XX01FVEFEQVRBX0tFWSk/LnN0YXJ0c1dpdGgoJ2dlb2Fycm93JylcbiAgICApIHtcbiAgICAgIHR5cGUgPSBBTExfRklFTERfVFlQRVMuZ2VvYXJyb3c7XG4gICAgICBhbmFseXplclR5cGUgPSBBbmFseXplckRBVEFfVFlQRVMuR0VPTUVUUlk7XG4gICAgfSBlbHNlIGlmIChnZW9BcnJvd01ldGFkYXRhW2ZpZWxkLm5hbWVdKSB7XG4gICAgICB0eXBlID0gQUxMX0ZJRUxEX1RZUEVTLmdlb2Fycm93O1xuICAgICAgYW5hbHl6ZXJUeXBlID0gQW5hbHl6ZXJEQVRBX1RZUEVTLkdFT01FVFJZO1xuICAgICAgZmllbGQubWV0YWRhdGE/LnNldChHRU9BUlJPV19NRVRBREFUQV9LRVksIGdlb0Fycm93TWV0YWRhdGFbZmllbGQubmFtZV0pO1xuICAgIH0gZWxzZSBpZiAoZmllbGRUeXBlU3VnZ2VzdGlvbnNbZmllbGQubmFtZV0gPT09ICdCTE9CJykge1xuICAgICAgLy8gV2hlbiBhcnJvdyB3a2IgY29sdW1uIHNhdmVkIHRvIER1Y2tEQiBhcyBCTE9CIHdpdGhvdXQgYW55IG1ldGFkYXRhLCB0aGVuIHF1ZXJpZWQgYmFja1xuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgZGF0YSA9IHRhYmxlLmdldENoaWxkQXQoZmllbGRJbmRleCk/LmdldCgwKTtcbiAgICAgICAgaWYgKGRhdGEpIHtcbiAgICAgICAgICBjb25zdCBiaW5hcnlHZW8gPSBwYXJzZVN5bmMoZGF0YSwgV0tCTG9hZGVyKTtcbiAgICAgICAgICBpZiAoYmluYXJ5R2VvKSB7XG4gICAgICAgICAgICB0eXBlID0gQUxMX0ZJRUxEX1RZUEVTLmdlb2Fycm93O1xuICAgICAgICAgICAgYW5hbHl6ZXJUeXBlID0gQW5hbHl6ZXJEQVRBX1RZUEVTLkdFT01FVFJZO1xuICAgICAgICAgICAgZmllbGQubWV0YWRhdGE/LnNldChHRU9BUlJPV19NRVRBREFUQV9LRVksIEdFT0FSUk9XX0VYVEVOU0lPTlMuV0tCKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIC8vIGlnbm9yZSwgbm90IFdLQlxuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICAvLyBUT0RPIHNob3VsZCB3ZSB1c2UgS2VwbGVyIGdldEZpZWxkc0Zyb21EYXRhIGluc3RlYWRcbiAgICAgIC8vIG9mIGFycm93RGF0YVR5cGVUb0ZpZWxkVHlwZSBmb3IgYWxsIGZpZWxkcz9cbiAgICAgIGNvbnN0IGtlcGxlckZpZWxkID0ga2VwbGVyRmllbGRzW2ZpZWxkSW5kZXhdO1xuICAgICAgaWYgKGtlcGxlckZpZWxkLnR5cGUgPT09IEFMTF9GSUVMRF9UWVBFUy50aW1lc3RhbXApIHtcbiAgICAgICAgdHlwZSA9IGtlcGxlckZpZWxkLnR5cGU7XG4gICAgICAgIGFuYWx5emVyVHlwZSA9IGtlcGxlckZpZWxkLmFuYWx5emVyVHlwZTtcbiAgICAgICAgZm9ybWF0ID0ga2VwbGVyRmllbGQuZm9ybWF0O1xuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiB7XG4gICAgICAuLi5maWVsZCxcbiAgICAgIG5hbWU6IGZpZWxkLm5hbWUsXG4gICAgICBpZDogZmllbGQubmFtZSxcbiAgICAgIGRpc3BsYXlOYW1lOiBmaWVsZC5uYW1lLFxuICAgICAgZm9ybWF0OiBmb3JtYXQsXG4gICAgICBmaWVsZElkeDogZmllbGRJbmRleCxcbiAgICAgIHR5cGUsXG4gICAgICBhbmFseXplclR5cGUsXG4gICAgICB2YWx1ZUFjY2Vzc29yOiAoZGM6IGFueSkgPT4gZCA9PiB7XG4gICAgICAgIHJldHVybiBkYy52YWx1ZUF0KGQuaW5kZXgsIGZpZWxkSW5kZXgpO1xuICAgICAgfSxcbiAgICAgIG1ldGFkYXRhOiBmaWVsZC5tZXRhZGF0YVxuICAgIH07XG4gIH0pO1xufVxuXG4vKipcbiAqIFBhcnNlIGFycm93IGJhdGNoZXMgcmV0dXJuZWQgZnJvbSBwYXJzZUluQmF0Y2hlcygpXG4gKlxuICogQHBhcmFtIGFycm93VGFibGUgdGhlIGFycm93IHRhYmxlIHRvIHBhcnNlXG4gKiBAcmV0dXJucyBkYXRhc2V0IGNvbnRhaW5pbmcgYGZpZWxkc2AgYW5kIGByb3dzYCBvciBudWxsXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwcm9jZXNzQXJyb3dCYXRjaGVzKGFycm93QmF0Y2hlczogYXJyb3cuUmVjb3JkQmF0Y2hbXSk6IFByb2Nlc3NvclJlc3VsdCB8IG51bGwge1xuICBpZiAoYXJyb3dCYXRjaGVzLmxlbmd0aCA9PT0gMCkge1xuICAgIHJldHVybiBudWxsO1xuICB9XG4gIGNvbnN0IGFycm93VGFibGUgPSBuZXcgYXJyb3cuVGFibGUoYXJyb3dCYXRjaGVzKTtcbiAgY29uc3QgZmllbGRzID0gYXJyb3dTY2hlbWFUb0ZpZWxkcyhhcnJvd1RhYmxlKTtcblxuICBjb25zdCBjb2xzID0gWy4uLkFycmF5KGFycm93VGFibGUubnVtQ29scykua2V5cygpXS5tYXAoaSA9PiBhcnJvd1RhYmxlLmdldENoaWxkQXQoaSkpO1xuXG4gIC8vIHJldHVybiBlbXB0eSByb3dzIGFuZCB1c2UgcmF3IGFycm93IHRhYmxlIHRvIGNvbnN0cnVjdCBjb2x1bW4td2lzZSBkYXRhIGNvbnRhaW5lclxuICByZXR1cm4ge1xuICAgIGZpZWxkcyxcbiAgICByb3dzOiBbXSxcbiAgICBjb2xzLFxuICAgIG1ldGFkYXRhOiBhcnJvd1RhYmxlLnNjaGVtYS5tZXRhZGF0YSxcbiAgICAvLyBTYXZlIG9yaWdpbmFsIGFycm93IHNjaGVtYSwgZm9yIGJldHRlciBpbmdlc3Rpb24gaW50byBEdWNrREIuXG4gICAgLy8gVE9ETyBjb25zaWRlciByZXR1cm5pbmcgYXJyb3dUYWJsZSBpbiBjb2xzLCBub3QgYW4gYXJyYXkgb2YgVmVjdG9ycyBmcm9tIGFycm93VGFibGUuXG4gICAgYXJyb3dTY2hlbWE6IGFycm93VGFibGUuc2NoZW1hXG4gIH07XG59XG5cbmV4cG9ydCBjb25zdCBEQVRBU0VUX0hBTkRMRVJTID0ge1xuICBbREFUQVNFVF9GT1JNQVRTLnJvd106IHByb2Nlc3NSb3dPYmplY3QsXG4gIFtEQVRBU0VUX0ZPUk1BVFMuZ2VvanNvbl06IHByb2Nlc3NHZW9qc29uLFxuICBbREFUQVNFVF9GT1JNQVRTLmNzdl06IHByb2Nlc3NDc3ZEYXRhLFxuICBbREFUQVNFVF9GT1JNQVRTLmFycm93XTogcHJvY2Vzc0Fycm93VGFibGUsXG4gIFtEQVRBU0VUX0ZPUk1BVFMua2VwbGVyZ2xdOiBwcm9jZXNzS2VwbGVyZ2xEYXRhc2V0XG59O1xuXG5leHBvcnQgY29uc3QgUHJvY2Vzc29yczoge1xuICBwcm9jZXNzR2VvanNvbjogdHlwZW9mIHByb2Nlc3NHZW9qc29uO1xuICBwcm9jZXNzQ3N2RGF0YTogdHlwZW9mIHByb2Nlc3NDc3ZEYXRhO1xuICBwcm9jZXNzQXJyb3dUYWJsZTogdHlwZW9mIHByb2Nlc3NBcnJvd1RhYmxlO1xuICBwcm9jZXNzQXJyb3dCYXRjaGVzOiB0eXBlb2YgcHJvY2Vzc0Fycm93QmF0Y2hlcztcbiAgcHJvY2Vzc1Jvd09iamVjdDogdHlwZW9mIHByb2Nlc3NSb3dPYmplY3Q7XG4gIHByb2Nlc3NLZXBsZXJnbEpTT046IHR5cGVvZiBwcm9jZXNzS2Vwb