kepler.gl
Version:
kepler.gl is a webgl based application to visualize large scale location data in the browser
549 lines (528 loc) • 70.3 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 firstRow = rawData[0];
var keys = Object.keys(firstRow); // [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,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJhcnJvdyIsIl9pbnRlcm9wUmVxdWlyZVdpbGRjYXJkIiwicmVxdWlyZSIsIl9kM0RzdiIsIl90eXBlQW5hbHl6ZXIiLCJfZ2VvanNvbk5vcm1hbGl6ZSIsIl9pbnRlcm9wUmVxdWlyZURlZmF1bHQiLCJfY29yZSIsIl93a3QiLCJfY29uc3RhbnRzIiwiX3V0aWxzIiwiX2NvbW1vblV0aWxzIiwiX3NjaGVtYXMiLCJfZ2V0UmVxdWlyZVdpbGRjYXJkQ2FjaGUiLCJlIiwiV2Vha01hcCIsInIiLCJ0IiwiX19lc01vZHVsZSIsIl90eXBlb2YiLCJoYXMiLCJnZXQiLCJuIiwiX19wcm90b19fIiwiYSIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yIiwidSIsImhhc093blByb3BlcnR5IiwiY2FsbCIsImkiLCJzZXQiLCJvd25LZXlzIiwia2V5cyIsImdldE93blByb3BlcnR5U3ltYm9scyIsIm8iLCJmaWx0ZXIiLCJlbnVtZXJhYmxlIiwicHVzaCIsImFwcGx5IiwiX29iamVjdFNwcmVhZCIsImFyZ3VtZW50cyIsImxlbmd0aCIsImZvckVhY2giLCJfZGVmaW5lUHJvcGVydHkyIiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9ycyIsImRlZmluZVByb3BlcnRpZXMiLCJDU1ZfTlVMTFMiLCJleHBvcnRzIiwidHJ5UGFyc2VKc29uU3RyaW5nIiwic3RyIiwiSlNPTiIsInBhcnNlIiwiUEFSU0VfRklFTERfVkFMVUVfRlJPTV9TVFJJTkciLCJBTExfRklFTERfVFlQRVMiLCJ2YWxpZCIsImQiLCJpbnRlZ2VyIiwicGFyc2VJbnQiLCJ0aW1lc3RhbXAiLCJmaWVsZCIsImluY2x1ZGVzIiwiZm9ybWF0IiwiTnVtYmVyIiwicmVhbCIsInBhcnNlRmxvYXQiLCJvYmplY3QiLCJpc1BsYWluT2JqZWN0IiwiYXJyYXkiLCJBcnJheSIsImlzQXJyYXkiLCJoMyIsImgzSXNWYWxpZCIsInByb2Nlc3NDc3ZEYXRhIiwicmF3RGF0YSIsImhlYWRlciIsInJvd3MiLCJoZWFkZXJSb3ciLCJwYXJzZWRSb3dzIiwiY3N2UGFyc2VSb3dzIiwiRXJyb3IiLCJzbGljZSIsImNsZWFuVXBGYWxzeUNzdlZhbHVlIiwic2FtcGxlIiwiZ2V0U2FtcGxlRm9yVHlwZUFuYWx5emUiLCJmaWVsZHMiLCJnZXRGaWVsZHNGcm9tRGF0YSIsInBhcnNlUm93c0J5RmllbGRzIiwiZ2VvanNvbkZpZWxkSWR4IiwiZmluZEluZGV4IiwiZiIsIm5hbWUiLCJwYXJzZUNzdlJvd3NCeUZpZWxkVHlwZSIsImJpbmQiLCJyZSIsIlJlZ0V4cCIsImoiLCJtYXRjaCIsImdlb0ZpZWxkSWR4IiwicGFyc2VyIiwidHlwZSIsImZpcnN0IiwiZmluZCIsIm5vdE51bGxvclVuZGVmaW5lZCIsInJvdyIsInByb3BlcnRpZXMiLCJwcm9jZXNzUm93T2JqZWN0IiwiZmlyc3RSb3ciLCJtYXAiLCJrZXkiLCJwcm9jZXNzR2VvanNvbiIsIm5vcm1hbGl6ZWRHZW9qc29uIiwibm9ybWFsaXplIiwiZmVhdHVyZXMiLCJjb25jYXQiLCJHVUlERVNfRklMRV9GT1JNQVRfRE9DIiwiYWxsRGF0YVJvd3MiLCJnZW9tZXRyeSIsIl9nZW9qc29uIiwicmVkdWNlIiwiYWNjdSIsImN1cnIiLCJwcm9jZXNzS2VwbGVyZ2xKU09OIiwic2NoZW1hIiwidW5kZWZpbmVkIiwiS2VwbGVyR2xTY2hlbWEiLCJsb2FkIiwiZGF0YXNldHMiLCJjb25maWciLCJwcm9jZXNzS2VwbGVyZ2xEYXRhc2V0IiwicmVzdWx0cyIsInBhcnNlU2F2ZWREYXRhIiwidG9BcnJheSIsInByb2Nlc3NBcnJvd1RhYmxlIiwiYXJyb3dUYWJsZSIsInByb2Nlc3NBcnJvd0JhdGNoZXMiLCJkYXRhIiwiYmF0Y2hlcyIsImdldEdlb0Fycm93TWV0YWRhdGFGcm9tU2NoZW1hIiwidGFibGUiLCJnZW9BcnJvd01ldGFkYXRhIiwiX3RhYmxlJHNjaGVtYSRtZXRhZGF0IiwiZ2VvU3RyaW5nIiwibWV0YWRhdGEiLCJwYXJzZWRHZW9TdHJpbmciLCJjb2x1bW5zIiwiY29sdW1uTmFtZSIsImNvbHVtbkRhdGEiLCJlbmNvZGluZyIsIkdFT0FSUk9XX0VYVEVOU0lPTlMiLCJXS0IiLCJlcnJvciIsImNvbnNvbGUiLCJhcnJvd1NjaGVtYVRvRmllbGRzIiwiZmllbGRUeXBlU3VnZ2VzdGlvbnMiLCJnZXRTYW1wbGVGb3JUeXBlQW5hbHl6ZUFycm93Iiwia2VwbGVyRmllbGRzIiwiZmllbGRJbmRleCIsIl9maWVsZCRtZXRhZGF0YSRnZXQiLCJhcnJvd0RhdGFUeXBlVG9GaWVsZFR5cGUiLCJhbmFseXplclR5cGUiLCJhcnJvd0RhdGFUeXBlVG9BbmFseXplckRhdGFUeXBlIiwiZ2VvanNvbiIsIkFuYWx5emVyREFUQV9UWVBFUyIsIkdFT01FVFJZX0ZST01fU1RSSU5HIiwiR0VPQVJST1dfTUVUQURBVEFfS0VZIiwic3RhcnRzV2l0aCIsImdlb2Fycm93IiwiR0VPTUVUUlkiLCJfZmllbGQkbWV0YWRhdGEiLCJfdGFibGUkZ2V0Q2hpbGRBdCIsImdldENoaWxkQXQiLCJiaW5hcnlHZW8iLCJwYXJzZVN5bmMiLCJXS0JMb2FkZXIiLCJfZmllbGQkbWV0YWRhdGEyIiwia2VwbGVyRmllbGQiLCJpZCIsImRpc3BsYXlOYW1lIiwiZmllbGRJZHgiLCJ2YWx1ZUFjY2Vzc29yIiwiZGMiLCJ2YWx1ZUF0IiwiaW5kZXgiLCJhcnJvd0JhdGNoZXMiLCJUYWJsZSIsImNvbHMiLCJfdG9Db25zdW1hYmxlQXJyYXkyIiwibnVtQ29scyIsImFycm93U2NoZW1hIiwiREFUQVNFVF9IQU5ETEVSUyIsIkRBVEFTRVRfRk9STUFUUyIsImNzdiIsImtlcGxlcmdsIiwiUHJvY2Vzc29ycyIsImFuYWx5emVyVHlwZVRvRmllbGRUeXBlIl0sInNvdXJjZXMiOlsiLi4vc3JjL2RhdGEtcHJvY2Vzc29yLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBNSVRcbi8vIENvcHlyaWdodCBjb250cmlidXRvcnMgdG8gdGhlIGtlcGxlci5nbCBwcm9qZWN0XG5cbmltcG9ydCAqIGFzIGFycm93IGZyb20gJ2FwYWNoZS1hcnJvdyc7XG5pbXBvcnQge2NzdlBhcnNlUm93c30gZnJvbSAnZDMtZHN2JztcbmltcG9ydCB7REFUQV9UWVBFUyBhcyBBbmFseXplckRBVEFfVFlQRVN9IGZyb20gJ3R5cGUtYW5hbHl6ZXInO1xuaW1wb3J0IG5vcm1hbGl6ZSBmcm9tICdAbWFwYm94L2dlb2pzb24tbm9ybWFsaXplJztcbmltcG9ydCB7cGFyc2VTeW5jfSBmcm9tICdAbG9hZGVycy5nbC9jb3JlJztcbmltcG9ydCB7QXJyb3dUYWJsZX0gZnJvbSAnQGxvYWRlcnMuZ2wvc2NoZW1hJztcbmltcG9ydCB7V0tCTG9hZGVyfSBmcm9tICdAbG9hZGVycy5nbC93a3QnO1xuXG5pbXBvcnQge1xuICBBTExfRklFTERfVFlQRVMsXG4gIERBVEFTRVRfRk9STUFUUyxcbiAgR0VPQVJST1dfRVhURU5TSU9OUyxcbiAgR0VPQVJST1dfTUVUQURBVEFfS0VZLFxuICBHVUlERVNfRklMRV9GT1JNQVRfRE9DXG59IGZyb20gJ0BrZXBsZXIuZ2wvY29uc3RhbnRzJztcbmltcG9ydCB7UHJvY2Vzc29yUmVzdWx0LCBGaWVsZH0gZnJvbSAnQGtlcGxlci5nbC90eXBlcyc7XG5pbXBvcnQge1xuICBhcnJvd0RhdGFUeXBlVG9BbmFseXplckRhdGFUeXBlLFxuICBhcnJvd0RhdGFUeXBlVG9GaWVsZFR5cGUsXG4gIGhhc093blByb3BlcnR5LFxuICBpc1BsYWluT2JqZWN0XG59IGZyb20gJ0BrZXBsZXIuZ2wvdXRpbHMnO1xuaW1wb3J0IHtcbiAgYW5hbHl6ZXJUeXBlVG9GaWVsZFR5cGUsXG4gIGdldFNhbXBsZUZvclR5cGVBbmFseXplLFxuICBnZXRTYW1wbGVGb3JUeXBlQW5hbHl6ZUFycm93LFxuICBnZXRGaWVsZHNGcm9tRGF0YSxcbiAgaDNJc1ZhbGlkLFxuICBub3ROdWxsb3JVbmRlZmluZWQsXG4gIHRvQXJyYXlcbn0gZnJvbSAnQGtlcGxlci5nbC9jb21tb24tdXRpbHMnO1xuaW1wb3J0IHtLZXBsZXJHbFNjaGVtYSwgUGFyc2VkRGF0YXNldCwgU2F2ZWRNYXAsIExvYWRlZE1hcH0gZnJvbSAnQGtlcGxlci5nbC9zY2hlbWFzJztcbmltcG9ydCB7RmVhdHVyZX0gZnJvbSAnQG5lYnVsYS5nbC9lZGl0LW1vZGVzJztcblxuLy8gaWYgYW55IG9mIHRoZXNlIHZhbHVlIG9jY3VycyBpbiBjc3YsIHBhcnNlIGl0IHRvIG51bGw7XG4vLyBjb25zdCBDU1ZfTlVMTFMgPSBbJycsICdudWxsJywgJ05VTEwnLCAnTnVsbCcsICdOYU4nLCAnL04nXTtcbi8vIG1hdGNoZXMgZW1wdHkgc3RyaW5nXG5leHBvcnQgY29uc3QgQ1NWX05VTExTID0gL14obnVsbHxOVUxMfE51bGx8TmFOfFxcL058fCkkLztcblxuZnVuY3Rpb24gdHJ5UGFyc2VKc29uU3RyaW5nKHN0cikge1xuICB0cnkge1xuICAgIHJldHVybiBKU09OLnBhcnNlKHN0cik7XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxufVxuXG5leHBvcnQgY29uc3QgUEFSU0VfRklFTERfVkFMVUVfRlJPTV9TVFJJTkcgPSB7XG4gIFtBTExfRklFTERfVFlQRVMuYm9vbGVhbl06IHtcbiAgICB2YWxpZDogKGQ6IHVua25vd24pOiBib29sZWFuID0+IHR5cGVvZiBkID09PSAnYm9vbGVhbicsXG4gICAgcGFyc2U6IChkOiB1bmtub3duKTogYm9vbGVhbiA9PiBkID09PSAndHJ1ZScgfHwgZCA9PT0gJ1RydWUnIHx8IGQgPT09ICdUUlVFJyB8fCBkID09PSAnMSdcbiAgfSxcbiAgW0FMTF9GSUVMRF9UWVBFUy5pbnRlZ2VyXToge1xuICAgIC8vIEB0cy1pZ25vcmVcbiAgICB2YWxpZDogKGQ6IHVua25vd24pOiBib29sZWFuID0+IHBhcnNlSW50KGQsIDEwKSA9PT0gZCxcbiAgICAvLyBAdHMtaWdub3JlXG4gICAgcGFyc2U6IChkOiB1bmtub3duKTogbnVtYmVyID0+IHBhcnNlSW50KGQsIDEwKVxuICB9LFxuICBbQUxMX0ZJRUxEX1RZUEVTLnRpbWVzdGFtcF06IHtcbiAgICB2YWxpZDogKGQ6IHVua25vd24sIGZpZWxkOiBGaWVsZCk6IGJvb2xlYW4gPT5cbiAgICAgIFsneCcsICdYJ10uaW5jbHVkZXMoZmllbGQuZm9ybWF0KSA/IHR5cGVvZiBkID09PSAnbnVtYmVyJyA6IHR5cGVvZiBkID09PSAnc3RyaW5nJyxcbiAgICBwYXJzZTogKGQ6IGFueSwgZmllbGQ6IEZpZWxkKSA9PiAoWyd4JywgJ1gnXS5pbmNsdWRlcyhmaWVsZC5mb3JtYXQpID8gTnVtYmVyKGQpIDogZClcbiAgfSxcbiAgW0FMTF9GSUVMRF9UWVBFUy5yZWFsXToge1xuICAgIC8vIEB0cy1pZ25vcmVcbiAgICB2YWxpZDogKGQ6IHVua25vd24pOiBib29sZWFuID0+IHBhcnNlRmxvYXQoZCkgPT09IGQsXG4gICAgLy8gTm90ZSB0aGlzIHdpbGwgcmVzdWx0IGluIE5hTiBmb3Igc29tZSBzdHJpbmdcbiAgICBwYXJzZTogcGFyc2VGbG9hdFxuICB9LFxuICBbQUxMX0ZJRUxEX1RZUEVTLm9iamVjdF06IHtcbiAgICB2YWxpZDogaXNQbGFpbk9iamVjdCxcbiAgICBwYXJzZTogdHJ5UGFyc2VKc29uU3RyaW5nXG4gIH0sXG5cbiAgW0FMTF9GSUVMRF9UWVBFUy5hcnJheV06IHtcbiAgICB2YWxpZDogQXJyYXkuaXNBcnJheSxcbiAgICBwYXJzZTogdHJ5UGFyc2VKc29uU3RyaW5nXG4gIH0sXG5cbiAgW0FMTF9GSUVMRF9UWVBFUy5oM106IHtcbiAgICB2YWxpZDogZCA9PiBoM0lzVmFsaWQoZCksXG4gICAgcGFyc2U6IGQgPT4gZFxuICB9XG59O1xuXG4vKipcbiAqIFByb2Nlc3MgY3N2IGRhdGEsIG91dHB1dCBhIGRhdGEgb2JqZWN0IHdpdGggYHtmaWVsZHM6IFtdLCByb3dzOiBbXX1gLlxuICogVGhlIGRhdGEgb2JqZWN0IGNhbiBiZSB3cmFwcGVkIGluIGEgYGRhdGFzZXRgIGFuZCBwYXNzIHRvIFtgYWRkRGF0YVRvTWFwYF0oLi4vYWN0aW9ucy9hY3Rpb25zLm1kI2FkZGRhdGF0b21hcClcbiAqIEBwYXJhbSByYXdEYXRhIHJhdyBjc3Ygc3RyaW5nXG4gKiBAcmV0dXJucyBkYXRhIG9iamVjdCBge2ZpZWxkczogW10sIHJvd3M6IFtdfWAgY2FuIGJlIHBhc3NlZCB0byBhZGREYXRhVG9NYXBzXG4gKiBAcHVibGljXG4gKiBAZXhhbXBsZVxuICogaW1wb3J0IHtwcm9jZXNzQ3N2RGF0YX0gZnJvbSAna2VwbGVyLmdsL3Byb2Nlc3NvcnMnO1xuICpcbiAqIGNvbnN0IHRlc3REYXRhID0gYGdwc19kYXRhLnV0Y190aW1lc3RhbXAsZ3BzX2RhdGEubGF0LGdwc19kYXRhLmxuZyxncHNfZGF0YS50eXBlcyxlcG9jaCxoYXNfcmVzdWx0LGlkLHRpbWUsYmVnaW50cmlwX3RzX3V0YyxiZWdpbnRyaXBfdHNfbG9jYWwsZGF0ZVxuICogMjAxNi0wOS0xNyAwMDowOTo1NSwyOS45OTAwOTM3LDMxLjI1OTA1NDIsZHJpdmVyX2FuYWx5dGljcywxNDcyNjg4MDAwMDAwLEZhbHNlLDEsMjAxNi0wOS0yM1QwMDowMDowMC4wMDBaLDIwMTYtMTAtMDEgMDk6NDE6MzkrMDA6MDAsMjAxNi0xMC0wMSAwOTo0MTozOSswMDowMCwyMDE2LTA5LTIzXG4gKiAyMDE2LTA5LTE3IDAwOjEwOjU2LDI5Ljk5Mjc2OTksMzEuMjQ2MTE0Mixkcml2ZXJfYW5hbHl0aWNzLDE0NzI2ODgwMDAwMDAsRmFsc2UsMiwyMDE2LTA5LTIzVDAwOjAwOjAwLjAwMFosMjAxNi0xMC0wMSAwOTo0NjozNyswMDowMCwyMDE2LTEwLTAxIDE2OjQ2OjM3KzAwOjAwLDIwMTYtMDktMjNcbiAqIDIwMTYtMDktMTcgMDA6MTE6NTYsMjkuOTkwNzI2MSwzMS4yMzEyNzQyLGRyaXZlcl9hbmFseXRpY3MsMTQ3MjY4ODAwMDAwMCxGYWxzZSwzLDIwMTYtMDktMjNUMDA6MDA6MDAuMDAwWiwsLDIwMTYtMDktMjNcbiAqIDIwMTYtMDktMTcgMDA6MTI6NTgsMjkuOTg3MDA3NCwzMS4yMTc1ODI3LGRyaXZlcl9hbmFseXRpY3MsMTQ3MjY4ODAwMDAwMCxGYWxzZSw0LDIwMTYtMDktMjNUMDA6MDA6MDAuMDAwWiwsLDIwMTYtMDktMjNgXG4gKlxuICogY29uc3QgZGF0YXNldCA9IHtcbiAqICBpbmZvOiB7aWQ6ICd0ZXN0X2RhdGEnLCBsYWJlbDogJ015IENzdid9LFxuICogIGRhdGE6IHByb2Nlc3NDc3ZEYXRhKHRlc3REYXRhKVxuICogfTtcbiAqXG4gKiBkaXNwYXRjaChhZGREYXRhVG9NYXAoe1xuICogIGRhdGFzZXRzOiBbZGF0YXNldF0sXG4gKiAgb3B0aW9uczoge2NlbnRlck1hcDogdHJ1ZSwgcmVhZE9ubHk6IHRydWV9XG4gKiB9KSk7XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwcm9jZXNzQ3N2RGF0YShyYXdEYXRhOiB1bmtub3duW11bXSB8IHN0cmluZywgaGVhZGVyPzogc3RyaW5nW10pOiBQcm9jZXNzb3JSZXN1bHQge1xuICBsZXQgcm93czogdW5rbm93bltdW10gfCB1bmRlZmluZWQ7XG4gIGxldCBoZWFkZXJSb3c6IHN0cmluZ1tdIHwgdW5kZWZpbmVkO1xuXG4gIGlmICh0eXBlb2YgcmF3RGF0YSA9PT0gJ3N0cmluZycpIHtcbiAgICBjb25zdCBwYXJzZWRSb3dzOiBzdHJpbmdbXVtdID0gY3N2UGFyc2VSb3dzKHJhd0RhdGEpO1xuXG4gICAgaWYgKCFBcnJheS5pc0FycmF5KHBhcnNlZFJvd3MpIHx8IHBhcnNlZFJvd3MubGVuZ3RoIDwgMikge1xuICAgICAgLy8gbG9va3MgbGlrZSBhbiBlbXB0eSBmaWxlLCB0aHJvdyBlcnJvciB0byBiZSBjYXRjaFxuICAgICAgdGhyb3cgbmV3IEVycm9yKCdwcm9jZXNzIENzdiBEYXRhIEZhaWxlZDogQ1NWIGlzIGVtcHR5Jyk7XG4gICAgfVxuICAgIGhlYWRlclJvdyA9IHBhcnNlZFJvd3NbMF07XG4gICAgcm93cyA9IHBhcnNlZFJvd3Muc2xpY2UoMSk7XG4gIH0gZWxzZSBpZiAoQXJyYXkuaXNBcnJheShyYXdEYXRhKSAmJiByYXdEYXRhLmxlbmd0aCkge1xuICAgIHJvd3MgPSByYXdEYXRhO1xuICAgIGhlYWRlclJvdyA9IGhlYWRlcjtcblxuICAgIGlmICghQXJyYXkuaXNBcnJheShoZWFkZXJSb3cpKSB7XG4gICAgICAvLyBpZiBkYXRhIGlzIHBhc3NlZCBpbiBhcyBhcnJheSBvZiByb3dzIGFuZCBtaXNzaW5nIGhlYWRlclxuICAgICAgLy8gYXNzdW1lIGZpcnN0IHJvdyBpcyBoZWFkZXJcbiAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgIGhlYWRlclJvdyA9IHJhd0RhdGFbMF07XG4gICAgICByb3dzID0gcmF3RGF0YS5zbGljZSgxKTtcbiAgICB9XG4gIH1cblxuICBpZiAoIXJvd3MgfHwgIWhlYWRlclJvdykge1xuICAgIHRocm93IG5ldyBFcnJvcignaW52YWxpZCBpbnB1dCBwYXNzZWQgdG8gcHJvY2Vzc0NzdkRhdGEnKTtcbiAgfVxuXG4gIC8vIGhlcmUgd2UgYXNzdW1lIHRoZSBjc3YgZmlsZSB0aGF0IHBlb3BsZSB1cGxvYWRlZCB3aWxsIGhhdmUgZmlyc3Qgcm93XG4gIC8vIGFzIG5hbWUgb2YgdGhlIGNvbHVtblxuXG4gIGNsZWFuVXBGYWxzeUNzdlZhbHVlKHJvd3MpO1xuICAvLyBObyBuZWVkIHRvIHJ1biB0eXBlIGRldGVjdGlvbiBvbiBldmVyeSBkYXRhIHBvaW50XG4gIC8vIGhlcmUgd2UgZ2V0IGEgbGlzdCBvZiBub25lIG51bGwgdmFsdWVzIHRvIHJ1biBhbmFseXplIG9uXG4gIGNvbnN0IHNhbXBsZSA9IGdldFNhbXBsZUZvclR5cGVBbmFseXplKHtmaWVsZHM6IGhlYWRlclJvdywgcm93c30pO1xuICBjb25zdCBmaWVsZHMgPSBnZXRGaWVsZHNGcm9tRGF0YShzYW1wbGUsIGhlYWRlclJvdyk7XG4gIGNvbnN0IHBhcnNlZFJvd3MgPSBwYXJzZVJvd3NCeUZpZWxkcyhyb3dzLCBmaWVsZHMpO1xuXG4gIHJldHVybiB7ZmllbGRzLCByb3dzOiBwYXJzZWRSb3dzfTtcbn1cblxuLyoqXG4gKiBQYXJzZSByb3dzIG9mIGNzdiBieSBhbmFseXplZCBmaWVsZCB0eXBlcy4gU28gdGhhdCBgJzEnYCAtPiBgMWAsIGAnVHJ1ZSdgIC0+IGB0cnVlYFxuICogQHBhcmFtIHJvd3NcbiAqIEBwYXJhbSBmaWVsZHNcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlUm93c0J5RmllbGRzKHJvd3M6IGFueVtdW10sIGZpZWxkczogRmllbGRbXSkge1xuICAvLyBFZGl0IHJvd3MgaW4gcGxhY2VcbiAgY29uc3QgZ2VvanNvbkZpZWxkSWR4ID0gZmllbGRzLmZpbmRJbmRleChmID0+IGYubmFtZSA9PT0gJ19nZW9qc29uJyk7XG4gIGZpZWxkcy5mb3JFYWNoKHBhcnNlQ3N2Um93c0J5RmllbGRUeXBlLmJpbmQobnVsbCwgcm93cywgZ2VvanNvbkZpZWxkSWR4KSk7XG5cbiAgcmV0dXJuIHJvd3M7XG59XG5cbi8qKlxuICogQ29udmVydCBmYWxzeSB2YWx1ZSBpbiBjc3YgaW5jbHVkaW5nIGAnJywgJ251bGwnLCAnTlVMTCcsICdOdWxsJywgJ05hTidgIHRvIGBudWxsYCxcbiAqIHNvIHRoYXQgdHlwZS1hbmFseXplciB3b24ndCBkZXRlY3QgaXQgYXMgc3RyaW5nXG4gKlxuICogQHBhcmFtIHJvd3NcbiAqL1xuZnVuY3Rpb24gY2xlYW5VcEZhbHN5Q3N2VmFsdWUocm93czogdW5rbm93bltdW10pOiB2b2lkIHtcbiAgY29uc3QgcmUgPSBuZXcgUmVnRXhwKENTVl9OVUxMUywgJ2cnKTtcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCByb3dzLmxlbmd0aDsgaSsrKSB7XG4gICAgZm9yIChsZXQgaiA9IDA7IGogPCByb3dzW2ldLmxlbmd0aDsgaisrKSB7XG4gICAgICAvLyBhbmFseXplciB3aWxsIHNldCBhbnkgZmllbGRzIHRvICdzdHJpbmcnIGlmIHRoZXJlIGFyZSBlbXB0eSB2YWx1ZXNcbiAgICAgIC8vIHdoaWNoIHdpbGwgYmUgcGFyc2VkIGFzICcnIGJ5IGQzLmNzdlxuICAgICAgLy8gaGVyZSB3ZSBwYXJzZSBlbXB0eSBkYXRhIGFzIG51bGxcbiAgICAgIC8vIFRPRE86IGNyZWF0ZSB3YXJuaW5nIHdoZW4gZGVsdGVjdCBgQ1NWX05VTExTYCBpbiB0aGUgZGF0YVxuICAgICAgaWYgKHR5cGVvZiByb3dzW2ldW2pdID09PSAnc3RyaW5nJyAmJiAocm93c1tpXVtqXSBhcyBzdHJpbmcpLm1hdGNoKHJlKSkge1xuICAgICAgICByb3dzW2ldW2pdID0gbnVsbDtcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cblxuLyoqXG4gKiBQcm9jZXNzIHVwbG9hZGVkIGNzdiBmaWxlIHRvIHBhcnNlIHZhbHVlIGJ5IGZpZWxkIHR5cGVcbiAqXG4gKiBAcGFyYW0gcm93c1xuICogQHBhcmFtIGdlb0ZpZWxkSWR4IGZpZWxkIGluZGV4XG4gKiBAcGFyYW0gZmllbGRcbiAqIEBwYXJhbSBpXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwYXJzZUNzdlJvd3NCeUZpZWxkVHlwZShcbiAgcm93czogdW5rbm93bltdW10sXG4gIGdlb0ZpZWxkSWR4OiBudW1iZXIsXG4gIGZpZWxkOiBGaWVsZCxcbiAgaTogbnVtYmVyXG4pOiB2b2lkIHtcbiAgY29uc3QgcGFyc2VyID0gUEFSU0VfRklFTERfVkFMVUVfRlJPTV9TVFJJTkdbZmllbGQudHlwZV07XG4gIGlmIChwYXJzZXIpIHtcbiAgICAvLyBjaGVjayBmaXJzdCBub3QgbnVsbCB2YWx1ZSBvZiBpdCdzIGFscmVhZHkgcGFyc2VkXG4gICAgY29uc3QgZmlyc3QgPSByb3dzLmZpbmQociA9PiBub3ROdWxsb3JVbmRlZmluZWQocltpXSkpO1xuICAgIGlmICghZmlyc3QgfHwgcGFyc2VyLnZhbGlkKGZpcnN0W2ldLCBmaWVsZCkpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgcm93cy5mb3JFYWNoKHJvdyA9PiB7XG4gICAgICAvLyBwYXJzZSBzdHJpbmcgdmFsdWUgYmFzZWQgb24gZmllbGQgdHlwZVxuICAgICAgaWYgKHJvd1tpXSAhPT0gbnVsbCkge1xuICAgICAgICByb3dbaV0gPSBwYXJzZXIucGFyc2Uocm93W2ldLCBmaWVsZCk7XG4gICAgICAgIGlmIChcbiAgICAgICAgICBnZW9GaWVsZElkeCA+IC0xICYmXG4gICAgICAgICAgaXNQbGFpbk9iamVjdChyb3dbZ2VvRmllbGRJZHhdKSAmJlxuICAgICAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgICAgICBoYXNPd25Qcm9wZXJ0eShyb3dbZ2VvRmllbGRJZHhdLCAncHJvcGVydGllcycpXG4gICAgICAgICkge1xuICAgICAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgICAgICByb3dbZ2VvRmllbGRJZHhdLnByb3BlcnRpZXNbZmllbGQubmFtZV0gPSByb3dbaV07XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9KTtcbiAgfVxufVxuXG4vKiBlc2xpbnQtZW5hYmxlIGNvbXBsZXhpdHkgKi9cblxuLyoqXG4gKiBQcm9jZXNzIGRhdGEgd2hlcmUgZWFjaCByb3cgaXMgYW4gb2JqZWN0LCBvdXRwdXQgY2FuIGJlIHBhc3NlZCB0byBbYGFkZERhdGFUb01hcGBdKC4uL2FjdGlvbnMvYWN0aW9ucy5tZCNhZGRkYXRhdG9tYXApXG4gKiBOT1RFOiBUaGlzIGZ1bmN0aW9uIG1heSBtdXRhdGUgaW5wdXQuXG4gKiBAcGFyYW0gcmF3RGF0YSBhbiBhcnJheSBvZiByb3cgb2JqZWN0LCBlYWNoIG9iamVjdCBzaG91bGQgaGF2ZSB0aGUgc2FtZSBudW1iZXIgb2Yga2V5c1xuICogQHJldHVybnMgZGF0YXNldCBjb250YWluaW5nIGBmaWVsZHNgIGFuZCBgcm93c2BcbiAqIEBwdWJsaWNcbiAqIEBleGFtcGxlXG4gKiBpbXBvcnQge2FkZERhdGFUb01hcH0gZnJvbSAna2VwbGVyLmdsL2FjdGlvbnMnO1xuICogaW1wb3J0IHtwcm9jZXNzUm93T2JqZWN0fSBmcm9tICdrZXBsZXIuZ2wvcHJvY2Vzc29ycyc7XG4gKlxuICogY29uc3QgZGF0YSA9IFtcbiAqICB7bGF0OiAzMS4yNywgbG5nOiAxMjcuNTYsIHZhbHVlOiAzfSxcbiAqICB7bGF0OiAzMS4yMiwgbG5nOiAxMjYuMjYsIHZhbHVlOiAxfVxuICogXTtcbiAqXG4gKiBkaXNwYXRjaChhZGREYXRhVG9NYXAoe1xuICogIGRhdGFzZXRzOiB7XG4gKiAgICBpbmZvOiB7bGFiZWw6ICdNeSBEYXRhJywgaWQ6ICdteV9kYXRhJ30sXG4gKiAgICBkYXRhOiBwcm9jZXNzUm93T2JqZWN0KGRhdGEpXG4gKiAgfVxuICogfSkpO1xuICovXG5leHBvcnQgZnVuY3Rpb24gcHJvY2Vzc1Jvd09iamVjdChyYXdEYXRhOiB1bmtub3duW10pOiBQcm9jZXNzb3JSZXN1bHQge1xuICBpZiAoIUFycmF5LmlzQXJyYXkocmF3RGF0YSkpIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfSBlbHNlIGlmICghcmF3RGF0YS5sZW5ndGgpIHtcbiAgICAvLyBkYXRhIGlzIGVtcHR5XG4gICAgcmV0dXJuIHtcbiAgICAgIGZpZWxkczogW10sXG4gICAgICByb3dzOiBbXVxuICAgIH07XG4gIH1cblxuICBjb25zdCBmaXJzdFJvdyA9IHJhd0RhdGFbMF0gYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj47XG4gIGNvbnN0IGtleXMgPSBPYmplY3Qua2V5cyhmaXJzdFJvdyk7IC8vIFtsYXQsIGxuZywgdmFsdWVdXG4gIGNvbnN0IHJvd3MgPSByYXdEYXRhLm1hcChkID0+IGtleXMubWFwKGtleSA9PiAoZCBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPilba2V5XSkpOyAvLyBbWzMxLjI3LCAxMjcuNTYsIDNdXVxuXG4gIC8vIHJvdyBvYmplY3QgY2FuIHN0aWxsIGNvbnRhaW4gdmFsdWVzIGxpa2UgYE51bGxgIG9yIGBOL0FgXG4gIGNsZWFuVXBGYWxzeUNzdlZhbHVlKHJvd3MpO1xuXG4gIHJldHVybiBwcm9jZXNzQ3N2RGF0YShyb3dzLCBrZXlzKTtcbn1cblxuLyoqXG4gKiBQcm9jZXNzIEdlb0pTT04gW2BGZWF0dXJlQ29sbGVjdGlvbmBdKGh0dHA6Ly93aWtpLmdlb2pzb24ub3JnL0dlb0pTT05fZHJhZnRfdmVyc2lvbl82I0ZlYXR1cmVDb2xsZWN0aW9uKSxcbiAqIG91dHB1dCBhIGRhdGEgb2JqZWN0IHdpdGggYHtmaWVsZHM6IFtdLCByb3dzOiBbXX1gLlxuICogVGhlIGRhdGEgb2JqZWN0IGNhbiBiZSB3cmFwcGVkIGluIGEgYGRhdGFzZXRgIGFuZCBwYXNzZWQgdG8gW2BhZGREYXRhVG9NYXBgXSguLi9hY3Rpb25zL2FjdGlvbnMubWQjYWRkZGF0YXRvbWFwKVxuICogTk9URTogVGhpcyBmdW5jdGlvbiBtYXkgbXV0YXRlIGlucHV0LlxuICpcbiAqIEBwYXJhbSByYXdEYXRhIHJhdyBnZW9qc29uIGZlYXR1cmUgY29sbGVjdGlvblxuICogQHJldHVybnMgZGF0YXNldCBjb250YWluaW5nIGBmaWVsZHNgIGFuZCBgcm93c2BcbiAqIEBwdWJsaWNcbiAqIEBleGFtcGxlXG4gKiBpbXBvcnQge2FkZERhdGFUb01hcH0gZnJvbSAna2VwbGVyLmdsL2FjdGlvbnMnO1xuICogaW1wb3J0IHtwcm9jZXNzR2VvanNvbn0gZnJvbSAna2VwbGVyLmdsL3Byb2Nlc3NvcnMnO1xuICpcbiAqIGNvbnN0IGdlb2pzb24gPSB7XG4gKiBcdFwidHlwZVwiIDogXCJGZWF0dXJlQ29sbGVjdGlvblwiLFxuICogXHRcImZlYXR1cmVzXCIgOiBbe1xuICogXHRcdFwidHlwZVwiIDogXCJGZWF0dXJlXCIsXG4gKiBcdFx0XCJwcm9wZXJ0aWVzXCIgOiB7XG4gKiBcdFx0XHRcImNhcGFjaXR5XCIgOiBcIjEwXCIsXG4gKiBcdFx0XHRcInR5cGVcIiA6IFwiVS1SYWNrXCJcbiAqIFx0XHR9LFxuICogXHRcdFwiZ2VvbWV0cnlcIiA6IHtcbiAqIFx0XHRcdFwidHlwZVwiIDogXCJQb2ludFwiLFxuICogXHRcdFx0XCJjb29yZGluYXRlc1wiIDogWyAtNzEuMDczMjgzLCA0Mi40MTc1MDAgXVxuICogXHRcdH1cbiAqIFx0fV1cbiAqIH07XG4gKlxuICogZGlzcGF0Y2goYWRkRGF0YVRvTWFwKHtcbiAqICBkYXRhc2V0czoge1xuICogICAgaW5mbzoge1xuICogICAgICBsYWJlbDogJ1NhbXBsZSBUYXhpIFRyaXBzIGluIE5ldyBZb3JrIENpdHknLFxuICogICAgICBpZDogJ3Rlc3RfdHJpcF9kYXRhJ1xuICogICAgfSxcbiAqICAgIGRhdGE6IHByb2Nlc3NHZW9qc29uKGdlb2pzb24pXG4gKiAgfVxuICogfSkpO1xuICovXG5leHBvcnQgZnVuY3Rpb24gcHJvY2Vzc0dlb2pzb24ocmF3RGF0YTogdW5rbm93bik6IFByb2Nlc3NvclJlc3VsdCB7XG4gIGNvbnN0IG5vcm1hbGl6ZWRHZW9qc29uID0gbm9ybWFsaXplKHJhd0RhdGEpO1xuXG4gIGlmICghbm9ybWFsaXplZEdlb2pzb24gfHwgIUFycmF5LmlzQXJyYXkobm9ybWFsaXplZEdlb2pzb24uZmVhdHVyZXMpKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgYFJlYWQgRmlsZSBGYWlsZWQ6IEZpbGUgaXMgbm90IGEgdmFsaWQgR2VvSlNPTi4gUmVhZCBtb3JlIGFib3V0IFtzdXBwb3J0ZWQgZmlsZSBmb3JtYXRdKCR7R1VJREVTX0ZJTEVfRk9STUFUX0RPQ30pYFxuICAgICk7XG4gIH1cblxuICAvLyBnZXR0aW5nIGFsbCBmZWF0dXJlIGZpZWxkc1xuICBjb25zdCBhbGxEYXRhUm93czogQXJyYXk8e19nZW9qc29uOiBGZWF0dXJlfSAmIGtleW9mIEZlYXR1cmU+ID0gW107XG4gIGZvciAobGV0IGkgPSAwOyBpIDwgbm9ybWFsaXplZEdlb2pzb24uZmVhdHVyZXMubGVuZ3RoOyBpKyspIHtcbiAgICBjb25zdCBmID0gbm9ybWFsaXplZEdlb2pzb24uZmVhdHVyZXNbaV07XG4gICAgaWYgKGYuZ2VvbWV0cnkpIHtcbiAgICAgIGFsbERhdGFSb3dzLnB1c2goe1xuICAgICAgICAvLyBhZGQgZmVhdHVyZSB0byBfZ2VvanNvbiBmaWVsZFxuICAgICAgICBfZ2VvanNvbjogZixcbiAgICAgICAgLi4uKGYucHJvcGVydGllcyB8fCB7fSlcbiAgICAgIH0pO1xuICAgIH1cbiAgfVxuICAvLyBnZXQgYWxsIHRoZSBmaWVsZFxuICBjb25zdCBmaWVsZHMgPSBhbGxEYXRhUm93cy5yZWR1Y2U8c3RyaW5nW10+KChhY2N1LCBjdXJyKSA9PiB7XG4gICAgT2JqZWN0LmtleXMoY3VycikuZm9yRWFjaChrZXkgPT4ge1xuICAgICAgaWYgKCFhY2N1LmluY2x1ZGVzKGtleSkpIHtcbiAgICAgICAgYWNjdS5wdXNoKGtleSk7XG4gICAgICB9XG4gICAgfSk7XG4gICAgcmV0dXJuIGFjY3U7XG4gIH0sIFtdKTtcblxuICAvLyBtYWtlIHN1cmUgZWFjaCBmZWF0dXJlIGhhcyBleGFjdCBzYW1lIGZpZWxkc1xuICBhbGxEYXRhUm93cy5mb3JFYWNoKGQgPT4ge1xuICAgIGZpZWxkcy5mb3JFYWNoKGYgPT4ge1xuICAgICAgaWYgKCEoZiBpbiBkKSkge1xuICAgICAgICBkW2ZdID0gbnVsbDtcbiAgICAgICAgaWYgKGQuX2dlb2pzb24ucHJvcGVydGllcykge1xuICAgICAgICAgIGQuX2dlb2pzb24ucHJvcGVydGllc1tmXSA9IG51bGw7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9KTtcbiAgfSk7XG5cbiAgcmV0dXJuIHByb2Nlc3NSb3dPYmplY3QoYWxsRGF0YVJvd3MpO1xufVxuXG4vKipcbiAqIFByb2Nlc3Mgc2F2ZWQga2VwbGVyLmdsIGpzb24gdG8gYmUgcGFzcyB0byBbYGFkZERhdGFUb01hcGBdKC4uL2FjdGlvbnMvYWN0aW9ucy5tZCNhZGRkYXRhdG9tYXApLlxuICogVGhlIGpzb24gb2JqZWN0IHNob3VsZCBjb250YWluIGBkYXRhc2V0c2AgYW5kIGBjb25maWdgLlxuICogQHBhcmFtIHJhd0RhdGFcbiAqIEBwYXJhbSBzY2hlbWFcbiAqIEByZXR1cm5zIGRhdGFzZXRzIGFuZCBjb25maWcgYHtkYXRhc2V0czoge30sIGNvbmZpZzoge319YFxuICogQHB1YmxpY1xuICogQGV4YW1wbGVcbiAqIGltcG9ydCB7YWRkRGF0YVRvTWFwfSBmcm9tICdrZXBsZXIuZ2wvYWN0aW9ucyc7XG4gKiBpbXBvcnQge3Byb2Nlc3NLZXBsZXJnbEpTT059IGZyb20gJ2tlcGxlci5nbC9wcm9jZXNzb3JzJztcbiAqXG4gKiBkaXNwYXRjaChhZGREYXRhVG9NYXAocHJvY2Vzc0tlcGxlcmdsSlNPTihrZXBsZXJHbEpzb24pKSk7XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwcm9jZXNzS2VwbGVyZ2xKU09OKHJhd0RhdGE6IFNhdmVkTWFwLCBzY2hlbWEgPSBLZXBsZXJHbFNjaGVtYSk6IExvYWRlZE1hcCB8IG51bGwge1xuICByZXR1cm4gcmF3RGF0YSA/IHNjaGVtYS5sb2FkKHJhd0RhdGEuZGF0YXNldHMsIHJhd0RhdGEuY29uZmlnKSA6IG51bGw7XG59XG5cbi8qKlxuICogUGFyc2UgYSBzaW5nbGUgb3IgYW4gYXJyYXkgb2YgZGF0YXNldHMgc2F2ZWQgdXNpbmcga2VwbGVyLmdsIHNjaGVtYVxuICogQHBhcmFtIHJhd0RhdGFcbiAqIEBwYXJhbSBzY2hlbWFcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHByb2Nlc3NLZXBsZXJnbERhdGFzZXQoXG4gIHJhd0RhdGE6IG9iamVjdCB8IG9iamVjdFtdLFxuICBzY2hlbWEgPSBLZXBsZXJHbFNjaGVtYVxuKTogUGFyc2VkRGF0YXNldCB8IFBhcnNlZERhdGFzZXRbXSB8IG51bGwge1xuICBpZiAoIXJhd0RhdGEpIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIGNvbnN0IHJlc3VsdHMgPSBzY2hlbWEucGFyc2VTYXZlZERhdGEodG9BcnJheShyYXdEYXRhKSk7XG4gIGlmICghcmVzdWx0cykge1xuICAgIHJldHVybiBudWxsO1xuICB9XG4gIHJldHVybiBBcnJheS5pc0FycmF5KHJhd0RhdGEpID8gcmVzdWx0cyA6IHJlc3VsdHNbMF07XG59XG5cbi8qKlxuICogUGFyc2UgYXJyb3cgdGFibGUgYW5kIHJldHVybiBhIGRhdGFzZXRcbiAqXG4gKiBAcGFyYW0gYXJyb3dUYWJsZSBBcnJvd1RhYmxlIHRvIHBhcnNlLCBzZWUgbG9hZGVycy5nbC9zY2hlbWFcbiAqIEByZXR1cm5zIGRhdGFzZXQgY29udGFpbmluZyBgZmllbGRzYCBhbmQgYHJvd3NgIG9yIG51bGxcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHByb2Nlc3NBcnJvd1RhYmxlKGFycm93VGFibGU6IEFycm93VGFibGUpOiBQcm9jZXNzb3JSZXN1bHQgfCBudWxsIHtcbiAgLy8gQHRzLWlnbm9yZSAtIFVua25vd24gZGF0YSB0eXBlIGNhdXNpbmcgYnVpbGQgZmFpbHVyZXNcbiAgcmV0dXJuIHByb2Nlc3NBcnJvd0JhdGNoZXMoYXJyb3dUYWJsZS5kYXRhLmJhdGNoZXMpO1xufVxuXG4vKipcbiAqIEV4dHJhY3RzIEdlb0Fycm93IG1ldGFkYXRhIGZyb20gYW4gQXBhY2hlIEFycm93IHRhYmxlIHNjaGVtYS5cbiAqIEZvciBnZW9wYXJxdWV0IGZpbGVzIGdlb2Fycm93IG1ldGFkYXRhIGlzbid0IHByZXNlbnQgaW4gZmllbGRzLCBzbyBleHRyYWN0IGV4dHJhIGluZm8gZnJvbSBzY2hlbWEuXG4gKiBAcGFyYW0gdGFibGUgVGhlIEFwYWNoZSBBcnJvdyB0YWJsZSB0byBleHRyYWN0IG1ldGFkYXRhIGZyb20uXG4gKiBAcmV0dXJucyBBbiBvYmplY3QgbWFwcGluZyBjb2x1bW4gbmFtZXMgdG8gdGhlaXIgR2VvQXJyb3cgZW5jb2RpbmcgdHlwZS5cbiAqIEB0aHJvd3MgTG9ncyBhbiBlcnJvciBtZXNzYWdlIGlmIHBhcnNpbmcgb2YgbWV0YWRhdGEgZmFpbHMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRHZW9BcnJvd01ldGFkYXRhRnJvbVNjaGVtYSh0YWJsZTogYXJyb3cuVGFibGUpOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+IHtcbiAgY29uc3QgZ2VvQXJyb3dNZXRhZGF0YTogUmVjb3JkPHN0cmluZywgc3RyaW5nPiA9IHt9O1xuICB0cnkge1xuICAgIGNvbnN0IGdlb1N0cmluZyA9IHRhYmxlLnNjaGVtYS5tZXRhZGF0YT8uZ2V0KCdnZW8nKTtcbiAgICBpZiAoZ2VvU3RyaW5nKSB7XG4gICAgICBjb25zdCBwYXJzZWRHZW9TdHJpbmcgPSBKU09OLnBhcnNlKGdlb1N0cmluZyk7XG4gICAgICBpZiAocGFyc2VkR2VvU3RyaW5nLmNvbHVtbnMpIHtcbiAgICAgICAgT2JqZWN0LmtleXMocGFyc2VkR2VvU3RyaW5nLmNvbHVtbnMpLmZvckVhY2goY29sdW1uTmFtZSA9PiB7XG4gICAgICAgICAgY29uc3QgY29sdW1uRGF0YSA9IHBhcnNlZEdlb1N0cmluZy5jb2x1bW5zW2NvbHVtbk5hbWVdO1xuICAgICAgICAgIGlmIChjb2x1bW5EYXRhPy5lbmNvZGluZyA9PT0gJ1dLQicpIHtcbiAgICAgICAgICAgIGdlb0Fycm93TWV0YWRhdGFbY29sdW1uTmFtZV0gPSBHRU9BUlJPV19FWFRFTlNJT05TLldLQjtcbiAgICAgICAgICB9XG4gICAgICAgICAgLy8gVE9ETyBwb3RlbnRpYWxseSB0aGVyZSBhcmUgb3RoZXIgdHlwZXMgYnV0IG5vIGRhdGFzZXRzIHRvIHRlc3RcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfVxuICB9IGNhdGNoIChlcnJvcikge1xuICAgIGNvbnNvbGUuZXJyb3IoJ0FuIGVycm9yIGR1cmluZyBhcnJvdyB0YWJsZSBzY2hlbWEgbWV0YWRhdGEgcGFyc2luZycpO1xuICB9XG4gIHJldHVybiBnZW9BcnJvd01ldGFkYXRhO1xufVxuXG4vKipcbiAqIENvbnZlcnRzIGFuIEFwYWNoZSBBcnJvdyB0YWJsZSBzY2hlbWEgaW50byBhbiBhcnJheSBvZiBLZXBsZXIuZ2wgZmllbGQgb2JqZWN0cy5cbiAqIEBwYXJhbSB0YWJsZSBUaGUgQXBhY2hlIEFycm93IHRhYmxlIHdob3NlIHNjaGVtYSBuZWVkcyB0byBiZSBjb252ZXJ0ZWQuXG4gKiBAcGFyYW0gZmllbGRUeXBlU3VnZ2VzdGlvbnMgT3B0aW9uYWwgbWFwcGluZyBvZiBmaWVsZCBuYW1lcyB0byBzdWdnZXN0ZWQgZmllbGQgdHlwZXMuXG4gKiBAcmV0dXJucyBBbiBhcnJheSBvZiBmaWVsZCBvYmplY3RzIHN1aXRhYmxlIGZvciBLZXBsZXIuZ2wuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBhcnJvd1NjaGVtYVRvRmllbGRzKFxuICB0YWJsZTogYXJyb3cuVGFibGUsXG4gIGZpZWxkVHlwZVN1Z2dlc3Rpb25zOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge31cbik6IEZpZWxkW10ge1xuICBjb25zdCBoZWFkZXJSb3cgPSB0YWJsZS5zY2hlbWEuZmllbGRzLm1hcChmID0+IGYubmFtZSk7XG4gIGNvbnN0IHNhbXBsZSA9IGdldFNhbXBsZUZvclR5cGVBbmFseXplQXJyb3codGFibGUsIGhlYWRlclJvdyk7XG4gIGNvbnN0IGtlcGxlckZpZWxkcyA9IGdldEZpZWxkc0Zyb21EYXRhKHNhbXBsZSwgaGVhZGVyUm93KTtcbiAgY29uc3QgZ2VvQXJyb3dNZXRhZGF0YSA9IGdldEdlb0Fycm93TWV0YWRhdGFGcm9tU2NoZW1hKHRhYmxlKTtcblxuICByZXR1cm4gdGFibGUuc2NoZW1hLmZpZWxkcy5tYXAoKGZpZWxkOiBhcnJvdy5GaWVsZCwgZmllbGRJbmRleDogbnVtYmVyKSA9PiB7XG4gICAgbGV0IHR5cGUgPSBhcnJvd0RhdGFUeXBlVG9GaWVsZFR5cGUoZmllbGQudHlwZSk7XG4gICAgbGV0IGFuYWx5emVyVHlwZSA9IGFycm93RGF0YVR5cGVUb0FuYWx5emVyRGF0YVR5cGUoZmllbGQudHlwZSk7XG4gICAgbGV0IGZvcm1hdCA9ICcnO1xuXG4gICAgLy8gZ2VvbWV0cnkgZmllbGRzIHByb2R1Y2VkIGJ5IER1Y2tEQidzIHN0X2FzZ2VvanNvbigpXG4gICAgaWYgKGZpZWxkVHlwZVN1Z2dlc3Rpb25zW2ZpZWxkLm5hbWVdID09PSAnSlNPTicpIHtcbiAgICAgIHR5cGUgPSBBTExfRklFTERfVFlQRVMuZ2VvanNvbjtcbiAgICAgIGFuYWx5emVyVHlwZSA9IEFuYWx5emVyREFUQV9UWVBFUy5HRU9NRVRSWV9GUk9NX1NUUklORztcbiAgICB9IGVsc2UgaWYgKFxuICAgICAgZmllbGRUeXBlU3VnZ2VzdGlvbnNbZmllbGQubmFtZV0gPT09ICdHRU9NRVRSWScgfHxcbiAgICAgIGZpZWxkLm1ldGFkYXRhLmdldChHRU9BUlJPV19NRVRBREFUQV9LRVkpPy5zdGFydHNXaXRoKCdnZW9hcnJvdycpXG4gICAgKSB7XG4gICAgICB0eXBlID0gQUxMX0ZJRUxEX1RZUEVTLmdlb2Fycm93O1xuICAgICAgYW5hbHl6ZXJUeXBlID0gQW5hbHl6ZXJEQVRBX1RZUEVTLkdFT01FVFJZO1xuICAgIH0gZWxzZSBpZiAoZ2VvQXJyb3dNZXRhZGF0YVtmaWVsZC5uYW1lXSkge1xuICAgICAgdHlwZSA9IEFMTF9GSUVMRF9UWVBFUy5nZW9hcnJvdztcbiAgICAgIGFuYWx5emVyVHlwZSA9IEFuYWx5emVyREFUQV9UWVBFUy5HRU9NRVRSWTtcbiAgICAgIGZpZWxkLm1ldGFkYXRhPy5zZXQoR0VPQVJST1dfTUVUQURBVEFfS0VZLCBnZW9BcnJvd01ldGFkYXRhW2ZpZWxkLm5hbWVdKTtcbiAgICB9IGVsc2UgaWYgKGZpZWxkVHlwZVN1Z2dlc3Rpb25zW2ZpZWxkLm5hbWVdID09PSAnQkxPQicpIHtcbiAgICAgIC8vIFdoZW4gYXJyb3cgd2tiIGNvbHVtbiBzYXZlZCB0byBEdWNrREIgYXMgQkxPQiB3aXRob3V0IGFueSBtZXRhZGF0YSwgdGhlbiBxdWVyaWVkIGJhY2tcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IGRhdGEgPSB0YWJsZS5nZXRDaGlsZEF0KGZpZWxkSW5kZXgpPy5nZXQoMCk7XG4gICAgICAgIGlmIChkYXRhKSB7XG4gICAgICAgICAgY29uc3QgYmluYXJ5R2VvID0gcGFyc2VTeW5jKGRhdGEsIFdLQkxvYWRlcik7XG4gICAgICAgICAgaWYgKGJpbmFyeUdlbykge1xuICAgICAgICAgICAgdHlwZSA9IEFMTF9GSUVMRF9UWVBFUy5nZW9hcnJvdztcbiAgICAgICAgICAgIGFuYWx5emVyVHlwZSA9IEFuYWx5emVyREFUQV9UWVBFUy5HRU9NRVRSWTtcbiAgICAgICAgICAgIGZpZWxkLm1ldGFkYXRhPy5zZXQoR0VPQVJST1dfTUVUQURBVEFfS0VZLCBHRU9BUlJPV19FWFRFTlNJT05TLldLQik7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICAvLyBpZ25vcmUsIG5vdCBXS0JcbiAgICAgIH1cbiAgICB9IGVsc2Uge1xuICAgICAgLy8gVE9ETyBzaG91bGQgd2UgdXNlIEtlcGxlciBnZXRGaWVsZHNGcm9tRGF0YSBpbnN0ZWFkXG4gICAgICAvLyBvZiBhcnJvd0RhdGFUeXBlVG9GaWVsZFR5cGUgZm9yIGFsbCBmaWVsZHM/XG4gICAgICBjb25zdCBrZXBsZXJGaWVsZCA9IGtlcGxlckZpZWxkc1tmaWVsZEluZGV4XTtcbiAgICAgIGlmIChrZXBsZXJGaWVsZC50eXBlID09PSBBTExfRklFTERfVFlQRVMudGltZXN0YW1wKSB7XG4gICAgICAgIHR5cGUgPSBrZXBsZXJGaWVsZC50eXBlO1xuICAgICAgICBhbmFseXplclR5cGUgPSBrZXBsZXJGaWVsZC5hbmFseXplclR5cGU7XG4gICAgICAgIGZvcm1hdCA9IGtlcGxlckZpZWxkLmZvcm1hdDtcbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4ge1xuICAgICAgLi4uZmllbGQsXG4gICAgICBuYW1lOiBmaWVsZC5uYW1lLFxuICAgICAgaWQ6IGZpZWxkLm5hbWUsXG4gICAgICBkaXNwbGF5TmFtZTogZmllbGQubmFtZSxcbiAgICAgIGZvcm1hdDogZm9ybWF0LFxuICAgICAgZmllbGRJZHg6IGZpZWxkSW5kZXgsXG4gICAgICB0eXBlLFxuICAgICAgYW5hbHl6ZXJUeXBlLFxuICAgICAgdmFsdWVBY2Nlc3NvcjogKGRjOiBhbnkpID0+IGQgPT4ge1xuICAgICAgICByZXR1cm4gZGMudmFsdWVBdChkLmluZGV4LCBmaWVsZEluZGV4KTtcbiAgICAgIH0sXG4gICAgICBtZXRhZGF0YTogZmllbGQubWV0YWRhdGFcbiAgICB9O1xuICB9KTtcbn1cblxuLyoqXG4gKiBQYXJzZSBhcnJvdyBiYXRjaGVzIHJldHVybmVkIGZyb20gcGFyc2VJbkJhdGNoZXMoKVxuICpcbiAqIEBwYXJhbSBhcnJvd1RhYmxlIHRoZSBhcnJvdyB0YWJsZSB0byBwYXJzZVxuICogQHJldHVybnMgZGF0YXNldCBjb250YWluaW5nIGBmaWVsZHNgIGFuZCBgcm93c2Agb3IgbnVsbFxuICovXG5leHBvcnQgZnVuY3Rpb24gcHJvY2Vzc0Fycm93QmF0Y2hlcyhhcnJvd0JhdGNoZXM6IGFycm93LlJlY29yZEJhdGNoW10pOiBQcm9jZXNzb3JSZXN1bHQgfCBudWxsIHtcbiAgaWYgKGFycm93QmF0Y2hlcy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuICBjb25zdCBhcnJvd1RhYmxlID0gbmV3IGFycm93LlRhYmxlKGFycm93QmF0Y2hlcyk7XG4gIGNvbnN0IGZpZWxkcyA9IGFycm93U2NoZW1hVG9GaWVsZHMoYXJyb3dUYWJsZSk7XG5cbiAgY29uc3QgY29scyA9IFsuLi5BcnJheShhcnJvd1RhYmxlLm51bUNvbHMpLmtleXMoKV0ubWFwKGkgPT4gYXJyb3dUYWJsZS5nZXRDaGlsZEF0KGkpKTtcblxuICAvLyByZXR1cm4gZW1wdHkgcm93cyBhbmQgdXNlIHJhdyBhcnJvdyB0YWJsZSB0byBjb25zdHJ1Y3QgY29sdW1uLXdpc2UgZGF0YSBjb250YWluZXJcbiAgcmV0dXJuIHtcbiAgICBmaWVsZHMsXG4gICAgcm93czogW10sXG4gICAgY29scyxcbiAgICBtZXRhZGF0YTogYXJyb3dUYWJsZS5zY2hlbWEubWV0YWRhdGEsXG4gICAgLy8gU2F2ZSBvcmlnaW5hbCBhcnJvdyBzY2hlbWEsIGZvciBiZXR0ZXIgaW5nZXN0aW9uIGludG8gRHVja0RCLlxuICAgIC8vIFRPRE8gY29uc2lkZXIgcmV0dXJuaW5nIGFycm93VGFibGUgaW4gY29scywgbm90IGFuIGFycmF5IG9mIFZlY3RvcnMgZnJvbSBhcnJvd1RhYmxlLlxuICAgIGFycm93U2NoZW1hOiBhcnJvd1RhYmxlLnNjaGVtYVxuICB9O1xufVxuXG5leHBvcnQgY29uc3QgREFUQVNFVF9IQU5ETEVSUyA9IHtcbiAgW0RBVEFTRVRfRk9STUFUUy5yb3ddOiBwcm9jZXNzUm93T2JqZWN0LFxuICBbREFUQVNFVF9GT1JNQVRTLmdlb2pzb25dOiBwcm9jZXNzR2VvanNvbixcbiAgW0RBVEFTRVRfRk9STUFUUy5jc3ZdOiBwcm9jZXNzQ3N2RGF0YSxcbiAgW0RBVEFTRVRfRk9STUFUUy5hcnJvd106IHByb2Nlc3NBcnJvd1RhYmxlLFxuICBbREFUQVNFVF9GT1JNQVRTLmtlcGxlcmdsXTogcHJvY2Vzc0tlcGxlcmdsRGF0YXNldFxufTtcblxuZXhwb3J0IGNvbnN0IFByb2Nlc3NvcnM6IHtcbiAgcHJvY2Vzc0dlb2pzb246IHR5cGVvZiBwcm9jZXNzR2VvanNvbjtcbiAgcHJvY2Vzc0NzdkRhdGE6IHR5cGVvZiBwcm9jZXNzQ3N2RGF0YTtcbiAgcHJvY2Vzc0Fycm93VGFibGU6IHR5cGVvZiBwcm9jZXNzQXJyb3dUYWJsZTtcbiAgcHJvY2Vzc0Fycm93QmF0Y2hlcz