UNPKG

kepler.gl

Version:

kepler.gl is a webgl based application to visualize large scale location data in the browser

559 lines (538 loc) 72.5 kB
"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 = ''; var fieldTypeSuggestion = fieldTypeSuggestions[field.name]; var keplerField = keplerFields[fieldIndex]; // geometry fields produced by DuckDB's st_asgeojson() if (fieldTypeSuggestion === 'JSON') { type = _constants.ALL_FIELD_TYPES.geojson; analyzerType = _typeAnalyzer.DATA_TYPES.GEOMETRY_FROM_STRING; } else if (fieldTypeSuggestion === '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 (fieldTypeSuggestion === '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 if (fieldTypeSuggestion === 'VARCHAR' && (keplerField.analyzerType === _typeAnalyzer.DATA_TYPES.GEOMETRY || keplerField.analyzerType === _typeAnalyzer.DATA_TYPES.GEOMETRY_FROM_STRING)) { // When wkb/wkt was saved as varchar in DuckDB type = keplerField.type; analyzerType = keplerField.analyzerType; format = keplerField.format; } else if (fieldTypeSuggestion === 'VARCHAR' && keplerField.type === _constants.ALL_FIELD_TYPES.h3) { // when kepler detected h3 column using getFieldsFromData(), set type to h3 and analyzerType to H3 type = _constants.ALL_FIELD_TYPES.h3; analyzerType = keplerField.analyzerType; } else { // TODO should we use Kepler getFieldsFromData instead // of arrowDataTypeToFieldType for all fields? 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,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJhcnJvdyIsIl9pbnRlcm9wUmVxdWlyZVdpbGRjYXJkIiwicmVxdWlyZSIsIl9kM0RzdiIsIl90eXBlQW5hbHl6ZXIiLCJfZ2VvanNvbk5vcm1hbGl6ZSIsIl9pbnRlcm9wUmVxdWlyZURlZmF1bHQiLCJfY29yZSIsIl93a3QiLCJfY29uc3RhbnRzIiwiX3V0aWxzIiwiX2NvbW1vblV0aWxzIiwiX3NjaGVtYXMiLCJfZ2V0UmVxdWlyZVdpbGRjYXJkQ2FjaGUiLCJlIiwiV2Vha01hcCIsInIiLCJ0IiwiX19lc01vZHVsZSIsIl90eXBlb2YiLCJoYXMiLCJnZXQiLCJuIiwiX19wcm90b19fIiwiYSIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yIiwidSIsImhhc093blByb3BlcnR5IiwiY2FsbCIsImkiLCJzZXQiLCJvd25LZXlzIiwia2V5cyIsImdldE93blByb3BlcnR5U3ltYm9scyIsIm8iLCJmaWx0ZXIiLCJlbnVtZXJhYmxlIiwicHVzaCIsImFwcGx5IiwiX29iamVjdFNwcmVhZCIsImFyZ3VtZW50cyIsImxlbmd0aCIsImZvckVhY2giLCJfZGVmaW5lUHJvcGVydHkyIiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9ycyIsImRlZmluZVByb3BlcnRpZXMiLCJDU1ZfTlVMTFMiLCJleHBvcnRzIiwidHJ5UGFyc2VKc29uU3RyaW5nIiwic3RyIiwiSlNPTiIsInBhcnNlIiwiUEFSU0VfRklFTERfVkFMVUVfRlJPTV9TVFJJTkciLCJBTExfRklFTERfVFlQRVMiLCJ2YWxpZCIsImQiLCJpbnRlZ2VyIiwicGFyc2VJbnQiLCJ0aW1lc3RhbXAiLCJmaWVsZCIsImluY2x1ZGVzIiwiZm9ybWF0IiwiTnVtYmVyIiwicmVhbCIsInBhcnNlRmxvYXQiLCJvYmplY3QiLCJpc1BsYWluT2JqZWN0IiwiYXJyYXkiLCJBcnJheSIsImlzQXJyYXkiLCJoMyIsImgzSXNWYWxpZCIsInByb2Nlc3NDc3ZEYXRhIiwicmF3RGF0YSIsImhlYWRlciIsInJvd3MiLCJoZWFkZXJSb3ciLCJwYXJzZWRSb3dzIiwiY3N2UGFyc2VSb3dzIiwiRXJyb3IiLCJzbGljZSIsImNsZWFuVXBGYWxzeUNzdlZhbHVlIiwic2FtcGxlIiwiZ2V0U2FtcGxlRm9yVHlwZUFuYWx5emUiLCJmaWVsZHMiLCJnZXRGaWVsZHNGcm9tRGF0YSIsInBhcnNlUm93c0J5RmllbGRzIiwiZ2VvanNvbkZpZWxkSWR4IiwiZmluZEluZGV4IiwiZiIsIm5hbWUiLCJwYXJzZUNzdlJvd3NCeUZpZWxkVHlwZSIsImJpbmQiLCJyZSIsIlJlZ0V4cCIsImoiLCJtYXRjaCIsImdlb0ZpZWxkSWR4IiwicGFyc2VyIiwidHlwZSIsImZpcnN0IiwiZmluZCIsIm5vdE51bGxvclVuZGVmaW5lZCIsInJvdyIsInByb3BlcnRpZXMiLCJwcm9jZXNzUm93T2JqZWN0IiwiZmlyc3RSb3ciLCJtYXAiLCJrZXkiLCJwcm9jZXNzR2VvanNvbiIsIm5vcm1hbGl6ZWRHZW9qc29uIiwibm9ybWFsaXplIiwiZmVhdHVyZXMiLCJjb25jYXQiLCJHVUlERVNfRklMRV9GT1JNQVRfRE9DIiwiYWxsRGF0YVJvd3MiLCJnZW9tZXRyeSIsIl9nZW9qc29uIiwicmVkdWNlIiwiYWNjdSIsImN1cnIiLCJwcm9jZXNzS2VwbGVyZ2xKU09OIiwic2NoZW1hIiwidW5kZWZpbmVkIiwiS2VwbGVyR2xTY2hlbWEiLCJsb2FkIiwiZGF0YXNldHMiLCJjb25maWciLCJwcm9jZXNzS2VwbGVyZ2xEYXRhc2V0IiwicmVzdWx0cyIsInBhcnNlU2F2ZWREYXRhIiwidG9BcnJheSIsInByb2Nlc3NBcnJvd1RhYmxlIiwiYXJyb3dUYWJsZSIsInByb2Nlc3NBcnJvd0JhdGNoZXMiLCJkYXRhIiwiYmF0Y2hlcyIsImdldEdlb0Fycm93TWV0YWRhdGFGcm9tU2NoZW1hIiwidGFibGUiLCJnZW9BcnJvd01ldGFkYXRhIiwiX3RhYmxlJHNjaGVtYSRtZXRhZGF0IiwiZ2VvU3RyaW5nIiwibWV0YWRhdGEiLCJwYXJzZWRHZW9TdHJpbmciLCJjb2x1bW5zIiwiY29sdW1uTmFtZSIsImNvbHVtbkRhdGEiLCJlbmNvZGluZyIsIkdFT0FSUk9XX0VYVEVOU0lPTlMiLCJXS0IiLCJlcnJvciIsImNvbnNvbGUiLCJhcnJvd1NjaGVtYVRvRmllbGRzIiwiZmllbGRUeXBlU3VnZ2VzdGlvbnMiLCJnZXRTYW1wbGVGb3JUeXBlQW5hbHl6ZUFycm93Iiwia2VwbGVyRmllbGRzIiwiZmllbGRJbmRleCIsIl9maWVsZCRtZXRhZGF0YSRnZXQiLCJhcnJvd0RhdGFUeXBlVG9GaWVsZFR5cGUiLCJhbmFseXplclR5cGUiLCJhcnJvd0RhdGFUeXBlVG9BbmFseXplckRhdGFUeXBlIiwiZmllbGRUeXBlU3VnZ2VzdGlvbiIsImtlcGxlckZpZWxkIiwiZ2VvanNvbiIsIkFuYWx5emVyREFUQV9UWVBFUyIsIkdFT01FVFJZX0ZST01fU1RSSU5HIiwiR0VPQVJST1dfTUVUQURBVEFfS0VZIiwic3RhcnRzV2l0aCIsImdlb2Fycm93IiwiR0VPTUVUUlkiLCJfZmllbGQkbWV0YWRhdGEiLCJfdGFibGUkZ2V0Q2hpbGRBdCIsImdldENoaWxkQXQiLCJiaW5hcnlHZW8iLCJwYXJzZVN5bmMiLCJXS0JMb2FkZXIiLCJfZmllbGQkbWV0YWRhdGEyIiwiaWQiLCJkaXNwbGF5TmFtZSIsImZpZWxkSWR4IiwidmFsdWVBY2Nlc3NvciIsImRjIiwidmFsdWVBdCIsImluZGV4IiwiYXJyb3dCYXRjaGVzIiwiVGFibGUiLCJjb2xzIiwiX3RvQ29uc3VtYWJsZUFycmF5MiIsIm51bUNvbHMiLCJhcnJvd1NjaGVtYSIsIkRBVEFTRVRfSEFORExFUlMiLCJEQVRBU0VUX0ZPUk1BVFMiLCJjc3YiLCJrZXBsZXJnbCIsIlByb2Nlc3NvcnMiLCJhbmFseXplclR5cGVUb0ZpZWxkVHlwZSJdLCJzb3VyY2VzIjpbIi4uL3NyYy9kYXRhLXByb2Nlc3Nvci50cyJdLCJzb3VyY2VzQ29udGVudCI6WyIvLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogTUlUXG4vLyBDb3B5cmlnaHQgY29udHJpYnV0b3JzIHRvIHRoZSBrZXBsZXIuZ2wgcHJvamVjdFxuXG5pbXBvcnQgKiBhcyBhcnJvdyBmcm9tICdhcGFjaGUtYXJyb3cnO1xuaW1wb3J0IHtjc3ZQYXJzZVJvd3N9IGZyb20gJ2QzLWRzdic7XG5pbXBvcnQge0RBVEFfVFlQRVMgYXMgQW5hbHl6ZXJEQVRBX1RZUEVTfSBmcm9tICd0eXBlLWFuYWx5emVyJztcbmltcG9ydCBub3JtYWxpemUgZnJvbSAnQG1hcGJveC9nZW9qc29uLW5vcm1hbGl6ZSc7XG5pbXBvcnQge3BhcnNlU3luY30gZnJvbSAnQGxvYWRlcnMuZ2wvY29yZSc7XG5pbXBvcnQge0Fycm93VGFibGV9IGZyb20gJ0Bsb2FkZXJzLmdsL3NjaGVtYSc7XG5pbXBvcnQge1dLQkxvYWRlcn0gZnJvbSAnQGxvYWRlcnMuZ2wvd2t0JztcblxuaW1wb3J0IHtcbiAgQUxMX0ZJRUxEX1RZUEVTLFxuICBEQVRBU0VUX0ZPUk1BVFMsXG4gIEdFT0FSUk9XX0VYVEVOU0lPTlMsXG4gIEdFT0FSUk9XX01FVEFEQVRBX0tFWSxcbiAgR1VJREVTX0ZJTEVfRk9STUFUX0RPQ1xufSBmcm9tICdAa2VwbGVyLmdsL2NvbnN0YW50cyc7XG5pbXBvcnQge1Byb2Nlc3NvclJlc3VsdCwgRmllbGR9IGZyb20gJ0BrZXBsZXIuZ2wvdHlwZXMnO1xuaW1wb3J0IHtcbiAgYXJyb3dEYXRhVHlwZVRvQW5hbHl6ZXJEYXRhVHlwZSxcbiAgYXJyb3dEYXRhVHlwZVRvRmllbGRUeXBlLFxuICBoYXNPd25Qcm9wZXJ0eSxcbiAgaXNQbGFpbk9iamVjdFxufSBmcm9tICdAa2VwbGVyLmdsL3V0aWxzJztcbmltcG9ydCB7XG4gIGFuYWx5emVyVHlwZVRvRmllbGRUeXBlLFxuICBnZXRTYW1wbGVGb3JUeXBlQW5hbHl6ZSxcbiAgZ2V0U2FtcGxlRm9yVHlwZUFuYWx5emVBcnJvdyxcbiAgZ2V0RmllbGRzRnJvbURhdGEsXG4gIGgzSXNWYWxpZCxcbiAgbm90TnVsbG9yVW5kZWZpbmVkLFxuICB0b0FycmF5XG59IGZyb20gJ0BrZXBsZXIuZ2wvY29tbW9uLXV0aWxzJztcbmltcG9ydCB7S2VwbGVyR2xTY2hlbWEsIFBhcnNlZERhdGFzZXQsIFNhdmVkTWFwLCBMb2FkZWRNYXB9IGZyb20gJ0BrZXBsZXIuZ2wvc2NoZW1hcyc7XG5pbXBvcnQge0ZlYXR1cmV9IGZyb20gJ0BuZWJ1bGEuZ2wvZWRpdC1tb2Rlcyc7XG5cbi8vIGlmIGFueSBvZiB0aGVzZSB2YWx1ZSBvY2N1cnMgaW4gY3N2LCBwYXJzZSBpdCB0byBudWxsO1xuLy8gY29uc3QgQ1NWX05VTExTID0gWycnLCAnbnVsbCcsICdOVUxMJywgJ051bGwnLCAnTmFOJywgJy9OJ107XG4vLyBtYXRjaGVzIGVtcHR5IHN0cmluZ1xuZXhwb3J0IGNvbnN0IENTVl9OVUxMUyA9IC9eKG51bGx8TlVMTHxOdWxsfE5hTnxcXC9OfHwpJC87XG5cbmZ1bmN0aW9uIHRyeVBhcnNlSnNvblN0cmluZyhzdHIpIHtcbiAgdHJ5IHtcbiAgICByZXR1cm4gSlNPTi5wYXJzZShzdHIpO1xuICB9IGNhdGNoIChlKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbn1cblxuZXhwb3J0IGNvbnN0IFBBUlNFX0ZJRUxEX1ZBTFVFX0ZST01fU1RSSU5HID0ge1xuICBbQUxMX0ZJRUxEX1RZUEVTLmJvb2xlYW5dOiB7XG4gICAgdmFsaWQ6IChkOiB1bmtub3duKTogYm9vbGVhbiA9PiB0eXBlb2YgZCA9PT0gJ2Jvb2xlYW4nLFxuICAgIHBhcnNlOiAoZDogdW5rbm93bik6IGJvb2xlYW4gPT4gZCA9PT0gJ3RydWUnIHx8IGQgPT09ICdUcnVlJyB8fCBkID09PSAnVFJVRScgfHwgZCA9PT0gJzEnXG4gIH0sXG4gIFtBTExfRklFTERfVFlQRVMuaW50ZWdlcl06IHtcbiAgICAvLyBAdHMtaWdub3JlXG4gICAgdmFsaWQ6IChkOiB1bmtub3duKTogYm9vbGVhbiA9PiBwYXJzZUludChkLCAxMCkgPT09IGQsXG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIHBhcnNlOiAoZDogdW5rbm93bik6IG51bWJlciA9PiBwYXJzZUludChkLCAxMClcbiAgfSxcbiAgW0FMTF9GSUVMRF9UWVBFUy50aW1lc3RhbXBdOiB7XG4gICAgdmFsaWQ6IChkOiB1bmtub3duLCBmaWVsZDogRmllbGQpOiBib29sZWFuID0+XG4gICAgICBbJ3gnLCAnWCddLmluY2x1ZGVzKGZpZWxkLmZvcm1hdCkgPyB0eXBlb2YgZCA9PT0gJ251bWJlcicgOiB0eXBlb2YgZCA9PT0gJ3N0cmluZycsXG4gICAgcGFyc2U6IChkOiBhbnksIGZpZWxkOiBGaWVsZCkgPT4gKFsneCcsICdYJ10uaW5jbHVkZXMoZmllbGQuZm9ybWF0KSA/IE51bWJlcihkKSA6IGQpXG4gIH0sXG4gIFtBTExfRklFTERfVFlQRVMucmVhbF06IHtcbiAgICAvLyBAdHMtaWdub3JlXG4gICAgdmFsaWQ6IChkOiB1bmtub3duKTogYm9vbGVhbiA9PiBwYXJzZUZsb2F0KGQpID09PSBkLFxuICAgIC8vIE5vdGUgdGhpcyB3aWxsIHJlc3VsdCBpbiBOYU4gZm9yIHNvbWUgc3RyaW5nXG4gICAgcGFyc2U6IHBhcnNlRmxvYXRcbiAgfSxcbiAgW0FMTF9GSUVMRF9UWVBFUy5vYmplY3RdOiB7XG4gICAgdmFsaWQ6IGlzUGxhaW5PYmplY3QsXG4gICAgcGFyc2U6IHRyeVBhcnNlSnNvblN0cmluZ1xuICB9LFxuXG4gIFtBTExfRklFTERfVFlQRVMuYXJyYXldOiB7XG4gICAgdmFsaWQ6IEFycmF5LmlzQXJyYXksXG4gICAgcGFyc2U6IHRyeVBhcnNlSnNvblN0cmluZ1xuICB9LFxuXG4gIFtBTExfRklFTERfVFlQRVMuaDNdOiB7XG4gICAgdmFsaWQ6IGQgPT4gaDNJc1ZhbGlkKGQpLFxuICAgIHBhcnNlOiBkID0+IGRcbiAgfVxufTtcblxuLyoqXG4gKiBQcm9jZXNzIGNzdiBkYXRhLCBvdXRwdXQgYSBkYXRhIG9iamVjdCB3aXRoIGB7ZmllbGRzOiBbXSwgcm93czogW119YC5cbiAqIFRoZSBkYXRhIG9iamVjdCBjYW4gYmUgd3JhcHBlZCBpbiBhIGBkYXRhc2V0YCBhbmQgcGFzcyB0byBbYGFkZERhdGFUb01hcGBdKC4uL2FjdGlvbnMvYWN0aW9ucy5tZCNhZGRkYXRhdG9tYXApXG4gKiBAcGFyYW0gcmF3RGF0YSByYXcgY3N2IHN0cmluZ1xuICogQHJldHVybnMgZGF0YSBvYmplY3QgYHtmaWVsZHM6IFtdLCByb3dzOiBbXX1gIGNhbiBiZSBwYXNzZWQgdG8gYWRkRGF0YVRvTWFwc1xuICogQHB1YmxpY1xuICogQGV4YW1wbGVcbiAqIGltcG9ydCB7cHJvY2Vzc0NzdkRhdGF9IGZyb20gJ0BrZXBsZXIuZ2wvcHJvY2Vzc29ycyc7XG4gKlxuICogY29uc3QgdGVzdERhdGEgPSBgZ3BzX2RhdGEudXRjX3RpbWVzdGFtcCxncHNfZGF0YS5sYXQsZ3BzX2RhdGEubG5nLGdwc19kYXRhLnR5cGVzLGVwb2NoLGhhc19yZXN1bHQsaWQsdGltZSxiZWdpbnRyaXBfdHNfdXRjLGJlZ2ludHJpcF90c19sb2NhbCxkYXRlXG4gKiAyMDE2LTA5LTE3IDAwOjA5OjU1LDI5Ljk5MDA5MzcsMzEuMjU5MDU0Mixkcml2ZXJfYW5hbHl0aWNzLDE0NzI2ODgwMDAwMDAsRmFsc2UsMSwyMDE2LTA5LTIzVDAwOjAwOjAwLjAwMFosMjAxNi0xMC0wMSAwOTo0MTozOSswMDowMCwyMDE2LTEwLTAxIDA5OjQxOjM5KzAwOjAwLDIwMTYtMDktMjNcbiAqIDIwMTYtMDktMTcgMDA6MTA6NTYsMjkuOTkyNzY5OSwzMS4yNDYxMTQyLGRyaXZlcl9hbmFseXRpY3MsMTQ3MjY4ODAwMDAwMCxGYWxzZSwyLDIwMTYtMDktMjNUMDA6MDA6MDAuMDAwWiwyMDE2LTEwLTAxIDA5OjQ2OjM3KzAwOjAwLDIwMTYtMTAtMDEgMTY6NDY6MzcrMDA6MDAsMjAxNi0wOS0yM1xuICogMjAxNi0wOS0xNyAwMDoxMTo1NiwyOS45OTA3MjYxLDMxLjIzMTI3NDIsZHJpdmVyX2FuYWx5dGljcywxNDcyNjg4MDAwMDAwLEZhbHNlLDMsMjAxNi0wOS0yM1QwMDowMDowMC4wMDBaLCwsMjAxNi0wOS0yM1xuICogMjAxNi0wOS0xNyAwMDoxMjo1OCwyOS45ODcwMDc0LDMxLjIxNzU4MjcsZHJpdmVyX2FuYWx5dGljcywxNDcyNjg4MDAwMDAwLEZhbHNlLDQsMjAxNi0wOS0yM1QwMDowMDowMC4wMDBaLCwsMjAxNi0wOS0yM2BcbiAqXG4gKiBjb25zdCBkYXRhc2V0ID0ge1xuICogIGluZm86IHtpZDogJ3Rlc3RfZGF0YScsIGxhYmVsOiAnTXkgQ3N2J30sXG4gKiAgZGF0YTogcHJvY2Vzc0NzdkRhdGEodGVzdERhdGEpXG4gKiB9O1xuICpcbiAqIGRpc3BhdGNoKGFkZERhdGFUb01hcCh7XG4gKiAgZGF0YXNldHM6IFtkYXRhc2V0XSxcbiAqICBvcHRpb25zOiB7Y2VudGVyTWFwOiB0cnVlLCByZWFkT25seTogdHJ1ZX1cbiAqIH0pKTtcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHByb2Nlc3NDc3ZEYXRhKHJhd0RhdGE6IHVua25vd25bXVtdIHwgc3RyaW5nLCBoZWFkZXI/OiBzdHJpbmdbXSk6IFByb2Nlc3NvclJlc3VsdCB7XG4gIGxldCByb3dzOiB1bmtub3duW11bXSB8IHVuZGVmaW5lZDtcbiAgbGV0IGhlYWRlclJvdzogc3RyaW5nW10gfCB1bmRlZmluZWQ7XG5cbiAgaWYgKHR5cGVvZiByYXdEYXRhID09PSAnc3RyaW5nJykge1xuICAgIGNvbnN0IHBhcnNlZFJvd3M6IHN0cmluZ1tdW10gPSBjc3ZQYXJzZVJvd3MocmF3RGF0YSk7XG5cbiAgICBpZiAoIUFycmF5LmlzQXJyYXkocGFyc2VkUm93cykgfHwgcGFyc2VkUm93cy5sZW5ndGggPCAyKSB7XG4gICAgICAvLyBsb29rcyBsaWtlIGFuIGVtcHR5IGZpbGUsIHRocm93IGVycm9yIHRvIGJlIGNhdGNoXG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ3Byb2Nlc3MgQ3N2IERhdGEgRmFpbGVkOiBDU1YgaXMgZW1wdHknKTtcbiAgICB9XG4gICAgaGVhZGVyUm93ID0gcGFyc2VkUm93c1swXTtcbiAgICByb3dzID0gcGFyc2VkUm93cy5zbGljZSgxKTtcbiAgfSBlbHNlIGlmIChBcnJheS5pc0FycmF5KHJhd0RhdGEpICYmIHJhd0RhdGEubGVuZ3RoKSB7XG4gICAgcm93cyA9IHJhd0RhdGE7XG4gICAgaGVhZGVyUm93ID0gaGVhZGVyO1xuXG4gICAgaWYgKCFBcnJheS5pc0FycmF5KGhlYWRlclJvdykpIHtcbiAgICAgIC8vIGlmIGRhdGEgaXMgcGFzc2VkIGluIGFzIGFycmF5IG9mIHJvd3MgYW5kIG1pc3NpbmcgaGVhZGVyXG4gICAgICAvLyBhc3N1bWUgZmlyc3Qgcm93IGlzIGhlYWRlclxuICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgaGVhZGVyUm93ID0gcmF3RGF0YVswXTtcbiAgICAgIHJvd3MgPSByYXdEYXRhLnNsaWNlKDEpO1xuICAgIH1cbiAgfVxuXG4gIGlmICghcm93cyB8fCAhaGVhZGVyUm93KSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdpbnZhbGlkIGlucHV0IHBhc3NlZCB0byBwcm9jZXNzQ3N2RGF0YScpO1xuICB9XG5cbiAgLy8gaGVyZSB3ZSBhc3N1bWUgdGhlIGNzdiBmaWxlIHRoYXQgcGVvcGxlIHVwbG9hZGVkIHdpbGwgaGF2ZSBmaXJzdCByb3dcbiAgLy8gYXMgbmFtZSBvZiB0aGUgY29sdW1uXG5cbiAgY2xlYW5VcEZhbHN5Q3N2VmFsdWUocm93cyk7XG4gIC8vIE5vIG5lZWQgdG8gcnVuIHR5cGUgZGV0ZWN0aW9uIG9uIGV2ZXJ5IGRhdGEgcG9pbnRcbiAgLy8gaGVyZSB3ZSBnZXQgYSBsaXN0IG9mIG5vbmUgbnVsbCB2YWx1ZXMgdG8gcnVuIGFuYWx5emUgb25cbiAgY29uc3Qgc2FtcGxlID0gZ2V0U2FtcGxlRm9yVHlwZUFuYWx5emUoe2ZpZWxkczogaGVhZGVyUm93LCByb3dzfSk7XG4gIGNvbnN0IGZpZWxkcyA9IGdldEZpZWxkc0Zyb21EYXRhKHNhbXBsZSwgaGVhZGVyUm93KTtcbiAgY29uc3QgcGFyc2VkUm93cyA9IHBhcnNlUm93c0J5RmllbGRzKHJvd3MsIGZpZWxkcyk7XG5cbiAgcmV0dXJuIHtmaWVsZHMsIHJvd3M6IHBhcnNlZFJvd3N9O1xufVxuXG4vKipcbiAqIFBhcnNlIHJvd3Mgb2YgY3N2IGJ5IGFuYWx5emVkIGZpZWxkIHR5cGVzLiBTbyB0aGF0IGAnMSdgIC0+IGAxYCwgYCdUcnVlJ2AgLT4gYHRydWVgXG4gKiBAcGFyYW0gcm93c1xuICogQHBhcmFtIGZpZWxkc1xuICovXG5leHBvcnQgZnVuY3Rpb24gcGFyc2VSb3dzQnlGaWVsZHMocm93czogYW55W11bXSwgZmllbGRzOiBGaWVsZFtdKSB7XG4gIC8vIEVkaXQgcm93cyBpbiBwbGFjZVxuICBjb25zdCBnZW9qc29uRmllbGRJZHggPSBmaWVsZHMuZmluZEluZGV4KGYgPT4gZi5uYW1lID09PSAnX2dlb2pzb24nKTtcbiAgZmllbGRzLmZvckVhY2gocGFyc2VDc3ZSb3dzQnlGaWVsZFR5cGUuYmluZChudWxsLCByb3dzLCBnZW9qc29uRmllbGRJZHgpKTtcblxuICByZXR1cm4gcm93cztcbn1cblxuLyoqXG4gKiBDb252ZXJ0IGZhbHN5IHZhbHVlIGluIGNzdiBpbmNsdWRpbmcgYCcnLCAnbnVsbCcsICdOVUxMJywgJ051bGwnLCAnTmFOJ2AgdG8gYG51bGxgLFxuICogc28gdGhhdCB0eXBlLWFuYWx5emVyIHdvbid0IGRldGVjdCBpdCBhcyBzdHJpbmdcbiAqXG4gKiBAcGFyYW0gcm93c1xuICovXG5mdW5jdGlvbiBjbGVhblVwRmFsc3lDc3ZWYWx1ZShyb3dzOiB1bmtub3duW11bXSk6IHZvaWQge1xuICBjb25zdCByZSA9IG5ldyBSZWdFeHAoQ1NWX05VTExTLCAnZycpO1xuICBmb3IgKGxldCBpID0gMDsgaSA8IHJvd3MubGVuZ3RoOyBpKyspIHtcbiAgICBmb3IgKGxldCBqID0gMDsgaiA8IHJvd3NbaV0ubGVuZ3RoOyBqKyspIHtcbiAgICAgIC8vIGFuYWx5emVyIHdpbGwgc2V0IGFueSBmaWVsZHMgdG8gJ3N0cmluZycgaWYgdGhlcmUgYXJlIGVtcHR5IHZhbHVlc1xuICAgICAgLy8gd2hpY2ggd2lsbCBiZSBwYXJzZWQgYXMgJycgYnkgZDMuY3N2XG4gICAgICAvLyBoZXJlIHdlIHBhcnNlIGVtcHR5IGRhdGEgYXMgbnVsbFxuICAgICAgLy8gVE9ETzogY3JlYXRlIHdhcm5pbmcgd2hlbiBkZWx0ZWN0IGBDU1ZfTlVMTFNgIGluIHRoZSBkYXRhXG4gICAgICBpZiAodHlwZW9mIHJvd3NbaV1bal0gPT09ICdzdHJpbmcnICYmIChyb3dzW2ldW2pdIGFzIHN0cmluZykubWF0Y2gocmUpKSB7XG4gICAgICAgIHJvd3NbaV1bal0gPSBudWxsO1xuICAgICAgfVxuICAgIH1cbiAgfVxufVxuXG4vKipcbiAqIFByb2Nlc3MgdXBsb2FkZWQgY3N2IGZpbGUgdG8gcGFyc2UgdmFsdWUgYnkgZmllbGQgdHlwZVxuICpcbiAqIEBwYXJhbSByb3dzXG4gKiBAcGFyYW0gZ2VvRmllbGRJZHggZmllbGQgaW5kZXhcbiAqIEBwYXJhbSBmaWVsZFxuICogQHBhcmFtIGlcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlQ3N2Um93c0J5RmllbGRUeXBlKFxuICByb3dzOiB1bmtub3duW11bXSxcbiAgZ2VvRmllbGRJZHg6IG51bWJlcixcbiAgZmllbGQ6IEZpZWxkLFxuICBpOiBudW1iZXJcbik6IHZvaWQge1xuICBjb25zdCBwYXJzZXIgPSBQQVJTRV9GSUVMRF9WQUxVRV9GUk9NX1NUUklOR1tmaWVsZC50eXBlXTtcbiAgaWYgKHBhcnNlcikge1xuICAgIC8vIGNoZWNrIGZpcnN0IG5vdCBudWxsIHZhbHVlIG9mIGl0J3MgYWxyZWFkeSBwYXJzZWRcbiAgICBjb25zdCBmaXJzdCA9IHJvd3MuZmluZChyID0+IG5vdE51bGxvclVuZGVmaW5lZChyW2ldKSk7XG4gICAgaWYgKCFmaXJzdCB8fCBwYXJzZXIudmFsaWQoZmlyc3RbaV0sIGZpZWxkKSkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICByb3dzLmZvckVhY2gocm93ID0+IHtcbiAgICAgIC8vIHBhcnNlIHN0cmluZyB2YWx1ZSBiYXNlZCBvbiBmaWVsZCB0eXBlXG4gICAgICBpZiAocm93W2ldICE9PSBudWxsKSB7XG4gICAgICAgIHJvd1tpXSA9IHBhcnNlci5wYXJzZShyb3dbaV0sIGZpZWxkKTtcbiAgICAgICAgaWYgKFxuICAgICAgICAgIGdlb0ZpZWxkSWR4ID4gLTEgJiZcbiAgICAgICAgICBpc1BsYWluT2JqZWN0KHJvd1tnZW9GaWVsZElkeF0pICYmXG4gICAgICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgICAgIGhhc093blByb3BlcnR5KHJvd1tnZW9GaWVsZElkeF0sICdwcm9wZXJ0aWVzJylcbiAgICAgICAgKSB7XG4gICAgICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgICAgIHJvd1tnZW9GaWVsZElkeF0ucHJvcGVydGllc1tmaWVsZC5uYW1lXSA9IHJvd1tpXTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0pO1xuICB9XG59XG5cbi8qIGVzbGludC1lbmFibGUgY29tcGxleGl0eSAqL1xuXG4vKipcbiAqIFByb2Nlc3MgZGF0YSB3aGVyZSBlYWNoIHJvdyBpcyBhbiBvYmplY3QsIG91dHB1dCBjYW4gYmUgcGFzc2VkIHRvIFtgYWRkRGF0YVRvTWFwYF0oLi4vYWN0aW9ucy9hY3Rpb25zLm1kI2FkZGRhdGF0b21hcClcbiAqIE5PVEU6IFRoaXMgZnVuY3Rpb24gbWF5IG11dGF0ZSBpbnB1dC5cbiAqIEBwYXJhbSByYXdEYXRhIGFuIGFycmF5IG9mIHJvdyBvYmplY3QsIGVhY2ggb2JqZWN0IHNob3VsZCBoYXZlIHRoZSBzYW1lIG51bWJlciBvZiBrZXlzXG4gKiBAcmV0dXJucyBkYXRhc2V0IGNvbnRhaW5pbmcgYGZpZWxkc2AgYW5kIGByb3dzYFxuICogQHB1YmxpY1xuICogQGV4YW1wbGVcbiAqIGltcG9ydCB7YWRkRGF0YVRvTWFwfSBmcm9tICdAa2VwbGVyLmdsL2FjdGlvbnMnO1xuICogaW1wb3J0IHtwcm9jZXNzUm93T2JqZWN0fSBmcm9tICdAa2VwbGVyLmdsL3Byb2Nlc3NvcnMnO1xuICpcbiAqIGNvbnN0IGRhdGEgPSBbXG4gKiAge2xhdDogMzEuMjcsIGxuZzogMTI3LjU2LCB2YWx1ZTogM30sXG4gKiAge2xhdDogMzEuMjIsIGxuZzogMTI2LjI2LCB2YWx1ZTogMX1cbiAqIF07XG4gKlxuICogZGlzcGF0Y2goYWRkRGF0YVRvTWFwKHtcbiAqICBkYXRhc2V0czoge1xuICogICAgaW5mbzoge2xhYmVsOiAnTXkgRGF0YScsIGlkOiAnbXlfZGF0YSd9LFxuICogICAgZGF0YTogcHJvY2Vzc1Jvd09iamVjdChkYXRhKVxuICogIH1cbiAqIH0pKTtcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHByb2Nlc3NSb3dPYmplY3QocmF3RGF0YTogdW5rbm93bltdKTogUHJvY2Vzc29yUmVzdWx0IHtcbiAgaWYgKCFBcnJheS5pc0FycmF5KHJhd0RhdGEpKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH0gZWxzZSBpZiAoIXJhd0RhdGEubGVuZ3RoKSB7XG4gICAgLy8gZGF0YSBpcyBlbXB0eVxuICAgIHJldHVybiB7XG4gICAgICBmaWVsZHM6IFtdLFxuICAgICAgcm93czogW11cbiAgICB9O1xuICB9XG5cbiAgY29uc3QgZmlyc3RSb3cgPSByYXdEYXRhWzBdIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+O1xuICBjb25zdCBrZXlzID0gT2JqZWN0LmtleXMoZmlyc3RSb3cpOyAvLyBbbGF0LCBsbmcsIHZhbHVlXVxuICBjb25zdCByb3dzID0gcmF3RGF0YS5tYXAoZCA9PiBrZXlzLm1hcChrZXkgPT4gKGQgYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj4pW2tleV0pKTsgLy8gW1szMS4yNywgMTI3LjU2LCAzXV1cblxuICAvLyByb3cgb2JqZWN0IGNhbiBzdGlsbCBjb250YWluIHZhbHVlcyBsaWtlIGBOdWxsYCBvciBgTi9BYFxuICBjbGVhblVwRmFsc3lDc3ZWYWx1ZShyb3dzKTtcblxuICByZXR1cm4gcHJvY2Vzc0NzdkRhdGEocm93cywga2V5cyk7XG59XG5cbi8qKlxuICogUHJvY2VzcyBHZW9KU09OIFtgRmVhdHVyZUNvbGxlY3Rpb25gXShodHRwOi8vd2lraS5nZW9qc29uLm9yZy9HZW9KU09OX2RyYWZ0X3ZlcnNpb25fNiNGZWF0dXJlQ29sbGVjdGlvbiksXG4gKiBvdXRwdXQgYSBkYXRhIG9iamVjdCB3aXRoIGB7ZmllbGRzOiBbXSwgcm93czogW119YC5cbiAqIFRoZSBkYXRhIG9iamVjdCBjYW4gYmUgd3JhcHBlZCBpbiBhIGBkYXRhc2V0YCBhbmQgcGFzc2VkIHRvIFtgYWRkRGF0YVRvTWFwYF0oLi4vYWN0aW9ucy9hY3Rpb25zLm1kI2FkZGRhdGF0b21hcClcbiAqIE5PVEU6IFRoaXMgZnVuY3Rpb24gbWF5IG11dGF0ZSBpbnB1dC5cbiAqXG4gKiBAcGFyYW0gcmF3RGF0YSByYXcgZ2VvanNvbiBmZWF0dXJlIGNvbGxlY3Rpb25cbiAqIEByZXR1cm5zIGRhdGFzZXQgY29udGFpbmluZyBgZmllbGRzYCBhbmQgYHJvd3NgXG4gKiBAcHVibGljXG4gKiBAZXhhbXBsZVxuICogaW1wb3J0IHthZGREYXRhVG9NYXB9IGZyb20gJ0BrZXBsZXIuZ2wvYWN0aW9ucyc7XG4gKiBpbXBvcnQge3Byb2Nlc3NHZW9qc29ufSBmcm9tICdAa2VwbGVyLmdsL3Byb2Nlc3NvcnMnO1xuICpcbiAqIGNvbnN0IGdlb2pzb24gPSB7XG4gKiBcdFwidHlwZVwiIDogXCJGZWF0dXJlQ29sbGVjdGlvblwiLFxuICogXHRcImZlYXR1cmVzXCIgOiBbe1xuICogXHRcdFwidHlwZVwiIDogXCJGZWF0dXJlXCIsXG4gKiBcdFx0XCJwcm9wZXJ0aWVzXCIgOiB7XG4gKiBcdFx0XHRcImNhcGFjaXR5XCIgOiBcIjEwXCIsXG4gKiBcdFx0XHRcInR5cGVcIiA6IFwiVS1SYWNrXCJcbiAqIFx0XHR9LFxuICogXHRcdFwiZ2VvbWV0cnlcIiA6IHtcbiAqIFx0XHRcdFwidHlwZVwiIDogXCJQb2ludFwiLFxuICogXHRcdFx0XCJjb29yZGluYXRlc1wiIDogWyAtNzEuMDczMjgzLCA0Mi40MTc1MDAgXVxuICogXHRcdH1cbiAqIFx0fV1cbiAqIH07XG4gKlxuICogZGlzcGF0Y2goYWRkRGF0YVRvTWFwKHtcbiAqICBkYXRhc2V0czoge1xuICogICAgaW5mbzoge1xuICogICAgICBsYWJlbDogJ1NhbXBsZSBUYXhpIFRyaXBzIGluIE5ldyBZb3JrIENpdHknLFxuICogICAgICBpZDogJ3Rlc3RfdHJpcF9kYXRhJ1xuICogICAgfSxcbiAqICAgIGRhdGE6IHByb2Nlc3NHZW9qc29uKGdlb2pzb24pXG4gKiAgfVxuICogfSkpO1xuICovXG5leHBvcnQgZnVuY3Rpb24gcHJvY2Vzc0dlb2pzb24ocmF3RGF0YTogdW5rbm93bik6IFByb2Nlc3NvclJlc3VsdCB7XG4gIGNvbnN0IG5vcm1hbGl6ZWRHZW9qc29uID0gbm9ybWFsaXplKHJhd0RhdGEpO1xuXG4gIGlmICghbm9ybWFsaXplZEdlb2pzb24gfHwgIUFycmF5LmlzQXJyYXkobm9ybWFsaXplZEdlb2pzb24uZmVhdHVyZXMpKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgYFJlYWQgRmlsZSBGYWlsZWQ6IEZpbGUgaXMgbm90IGEgdmFsaWQgR2VvSlNPTi4gUmVhZCBtb3JlIGFib3V0IFtzdXBwb3J0ZWQgZmlsZSBmb3JtYXRdKCR7R1VJREVTX0ZJTEVfRk9STUFUX0RPQ30pYFxuICAgICk7XG4gIH1cblxuICAvLyBnZXR0aW5nIGFsbCBmZWF0dXJlIGZpZWxkc1xuICBjb25zdCBhbGxEYXRhUm93czogQXJyYXk8e19nZW9qc29uOiBGZWF0dXJlfSAmIGtleW9mIEZlYXR1cmU+ID0gW107XG4gIGZvciAobGV0IGkgPSAwOyBpIDwgbm9ybWFsaXplZEdlb2pzb24uZmVhdHVyZXMubGVuZ3RoOyBpKyspIHtcbiAgICBjb25zdCBmID0gbm9ybWFsaXplZEdlb2pzb24uZmVhdHVyZXNbaV07XG4gICAgaWYgKGYuZ2VvbWV0cnkpIHtcbiAgICAgIGFsbERhdGFSb3dzLnB1c2goe1xuICAgICAgICAvLyBhZGQgZmVhdHVyZSB0byBfZ2VvanNvbiBmaWVsZFxuICAgICAgICBfZ2VvanNvbjogZixcbiAgICAgICAgLi4uKGYucHJvcGVydGllcyB8fCB7fSlcbiAgICAgIH0pO1xuICAgIH1cbiAgfVxuICAvLyBnZXQgYWxsIHRoZSBmaWVsZFxuICBjb25zdCBmaWVsZHMgPSBhbGxEYXRhUm93cy5yZWR1Y2U8c3RyaW5nW10+KChhY2N1LCBjdXJyKSA9PiB7XG4gICAgT2JqZWN0LmtleXMoY3VycikuZm9yRWFjaChrZXkgPT4ge1xuICAgICAgaWYgKCFhY2N1LmluY2x1ZGVzKGtleSkpIHtcbiAgICAgICAgYWNjdS5wdXNoKGtleSk7XG4gICAgICB9XG4gICAgfSk7XG4gICAgcmV0dXJuIGFjY3U7XG4gIH0sIFtdKTtcblxuICAvLyBtYWtlIHN1cmUgZWFjaCBmZWF0dXJlIGhhcyBleGFjdCBzYW1lIGZpZWxkc1xuICBhbGxEYXRhUm93cy5mb3JFYWNoKGQgPT4ge1xuICAgIGZpZWxkcy5mb3JFYWNoKGYgPT4ge1xuICAgICAgaWYgKCEoZiBpbiBkKSkge1xuICAgICAgICBkW2ZdID0gbnVsbDtcbiAgICAgICAgaWYgKGQuX2dlb2pzb24ucHJvcGVydGllcykge1xuICAgICAgICAgIGQuX2dlb2pzb24ucHJvcGVydGllc1tmXSA9IG51bGw7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9KTtcbiAgfSk7XG5cbiAgcmV0dXJuIHByb2Nlc3NSb3dPYmplY3QoYWxsRGF0YVJvd3MpO1xufVxuXG4vKipcbiAqIFByb2Nlc3Mgc2F2ZWQga2VwbGVyLmdsIGpzb24gdG8gYmUgcGFzcyB0byBbYGFkZERhdGFUb01hcGBdKC4uL2FjdGlvbnMvYWN0aW9ucy5tZCNhZGRkYXRhdG9tYXApLlxuICogVGhlIGpzb24gb2JqZWN0IHNob3VsZCBjb250YWluIGBkYXRhc2V0c2AgYW5kIGBjb25maWdgLlxuICogQHBhcmFtIHJhd0RhdGFcbiAqIEBwYXJhbSBzY2hlbWFcbiAqIEByZXR1cm5zIGRhdGFzZXRzIGFuZCBjb25maWcgYHtkYXRhc2V0czoge30sIGNvbmZpZzoge319YFxuICogQHB1YmxpY1xuICogQGV4YW1wbGVcbiAqIGltcG9ydCB7YWRkRGF0YVRvTWFwfSBmcm9tICdAa2VwbGVyLmdsL2FjdGlvbnMnO1xuICogaW1wb3J0IHtwcm9jZXNzS2VwbGVyZ2xKU09OfSBmcm9tICdAa2VwbGVyLmdsL3Byb2Nlc3NvcnMnO1xuICpcbiAqIGRpc3BhdGNoKGFkZERhdGFUb01hcChwcm9jZXNzS2VwbGVyZ2xKU09OKGtlcGxlckdsSnNvbikpKTtcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHByb2Nlc3NLZXBsZXJnbEpTT04ocmF3RGF0YTogU2F2ZWRNYXAsIHNjaGVtYSA9IEtlcGxlckdsU2NoZW1hKTogTG9hZGVkTWFwIHwgbnVsbCB7XG4gIHJldHVybiByYXdEYXRhID8gc2NoZW1hLmxvYWQocmF3RGF0YS5kYXRhc2V0cywgcmF3RGF0YS5jb25maWcpIDogbnVsbDtcbn1cblxuLyoqXG4gKiBQYXJzZSBhIHNpbmdsZSBvciBhbiBhcnJheSBvZiBkYXRhc2V0cyBzYXZlZCB1c2luZyBrZXBsZXIuZ2wgc2NoZW1hXG4gKiBAcGFyYW0gcmF3RGF0YVxuICogQHBhcmFtIHNjaGVtYVxuICovXG5leHBvcnQgZnVuY3Rpb24gcHJvY2Vzc0tlcGxlcmdsRGF0YXNldChcbiAgcmF3RGF0YTogb2JqZWN0IHwgb2JqZWN0W10sXG4gIHNjaGVtYSA9IEtlcGxlckdsU2NoZW1hXG4pOiBQYXJzZWREYXRhc2V0IHwgUGFyc2VkRGF0YXNldFtdIHwgbnVsbCB7XG4gIGlmICghcmF3RGF0YSkge1xuICAgIHJldHVybiBudWxsO1xuICB9XG5cbiAgY29uc3QgcmVzdWx0cyA9IHNjaGVtYS5wYXJzZVNhdmVkRGF0YSh0b0FycmF5KHJhd0RhdGEpKTtcbiAgaWYgKCFyZXN1bHRzKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbiAgcmV0dXJuIEFycmF5LmlzQXJyYXkocmF3RGF0YSkgPyByZXN1bHRzIDogcmVzdWx0c1swXTtcbn1cblxuLyoqXG4gKiBQYXJzZSBhcnJvdyB0YWJsZSBhbmQgcmV0dXJuIGEgZGF0YXNldFxuICpcbiAqIEBwYXJhbSBhcnJvd1RhYmxlIEFycm93VGFibGUgdG8gcGFyc2UsIHNlZSBsb2FkZXJzLmdsL3NjaGVtYVxuICogQHJldHVybnMgZGF0YXNldCBjb250YWluaW5nIGBmaWVsZHNgIGFuZCBgcm93c2Agb3IgbnVsbFxuICovXG5leHBvcnQgZnVuY3Rpb24gcHJvY2Vzc0Fycm93VGFibGUoYXJyb3dUYWJsZTogQXJyb3dUYWJsZSk6IFByb2Nlc3NvclJlc3VsdCB8IG51bGwge1xuICAvLyBAdHMtaWdub3JlIC0gVW5rbm93biBkYXRhIHR5cGUgY2F1c2luZyBidWlsZCBmYWlsdXJlc1xuICByZXR1cm4gcHJvY2Vzc0Fycm93QmF0Y2hlcyhhcnJvd1RhYmxlLmRhdGEuYmF0Y2hlcyk7XG59XG5cbi8qKlxuICogRXh0cmFjdHMgR2VvQXJyb3cgbWV0YWRhdGEgZnJvbSBhbiBBcGFjaGUgQXJyb3cgdGFibGUgc2NoZW1hLlxuICogRm9yIGdlb3BhcnF1ZXQgZmlsZXMgZ2VvYXJyb3cgbWV0YWRhdGEgaXNuJ3QgcHJlc2VudCBpbiBmaWVsZHMsIHNvIGV4dHJhY3QgZXh0cmEgaW5mbyBmcm9tIHNjaGVtYS5cbiAqIEBwYXJhbSB0YWJsZSBUaGUgQXBhY2hlIEFycm93IHRhYmxlIHRvIGV4dHJhY3QgbWV0YWRhdGEgZnJvbS5cbiAqIEByZXR1cm5zIEFuIG9iamVjdCBtYXBwaW5nIGNvbHVtbiBuYW1lcyB0byB0aGVpciBHZW9BcnJvdyBlbmNvZGluZyB0eXBlLlxuICogQHRocm93cyBMb2dzIGFuIGVycm9yIG1lc3NhZ2UgaWYgcGFyc2luZyBvZiBtZXRhZGF0YSBmYWlscy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldEdlb0Fycm93TWV0YWRhdGFGcm9tU2NoZW1hKHRhYmxlOiBhcnJvdy5UYWJsZSk6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4ge1xuICBjb25zdCBnZW9BcnJvd01ldGFkYXRhOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge307XG4gIHRyeSB7XG4gICAgY29uc3QgZ2VvU3RyaW5nID0gdGFibGUuc2NoZW1hLm1ldGFkYXRhPy5nZXQoJ2dlbycpO1xuICAgIGlmIChnZW9TdHJpbmcpIHtcbiAgICAgIGNvbnN0IHBhcnNlZEdlb1N0cmluZyA9IEpTT04ucGFyc2UoZ2VvU3RyaW5nKTtcbiAgICAgIGlmIChwYXJzZWRHZW9TdHJpbmcuY29sdW1ucykge1xuICAgICAgICBPYmplY3Qua2V5cyhwYXJzZWRHZW9TdHJpbmcuY29sdW1ucykuZm9yRWFjaChjb2x1bW5OYW1lID0+IHtcbiAgICAgICAgICBjb25zdCBjb2x1bW5EYXRhID0gcGFyc2VkR2VvU3RyaW5nLmNvbHVtbnNbY29sdW1uTmFtZV07XG4gICAgICAgICAgaWYgKGNvbHVtbkRhdGE/LmVuY29kaW5nID09PSAnV0tCJykge1xuICAgICAgICAgICAgZ2VvQXJyb3dNZXRhZGF0YVtjb2x1bW5OYW1lXSA9IEdFT0FSUk9XX0VYVEVOU0lPTlMuV0tCO1xuICAgICAgICAgIH1cbiAgICAgICAgICAvLyBUT0RPIHBvdGVudGlhbGx5IHRoZXJlIGFyZSBvdGhlciB0eXBlcyBidXQgbm8gZGF0YXNldHMgdG8gdGVzdFxuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICB9XG4gIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgY29uc29sZS5lcnJvcignQW4gZXJyb3IgZHVyaW5nIGFycm93IHRhYmxlIHNjaGVtYSBtZXRhZGF0YSBwYXJzaW5nJyk7XG4gIH1cbiAgcmV0dXJuIGdlb0Fycm93TWV0YWRhdGE7XG59XG5cbi8qKlxuICogQ29udmVydHMgYW4gQXBhY2hlIEFycm93IHRhYmxlIHNjaGVtYSBpbnRvIGFuIGFycmF5IG9mIEtlcGxlci5nbCBmaWVsZCBvYmplY3RzLlxuICogQHBhcmFtIHRhYmxlIFRoZSBBcGFjaGUgQXJyb3cgdGFibGUgd2hvc2Ugc2NoZW1hIG5lZWRzIHRvIGJlIGNvbnZlcnRlZC5cbiAqIEBwYXJhbSBmaWVsZFR5cGVTdWdnZXN0aW9ucyBPcHRpb25hbCBtYXBwaW5nIG9mIGZpZWxkIG5hbWVzIHRvIHN1Z2dlc3RlZCBmaWVsZCB0eXBlcy5cbiAqIEByZXR1cm5zIEFuIGFycmF5IG9mIGZpZWxkIG9iamVjdHMgc3VpdGFibGUgZm9yIEtlcGxlci5nbC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGFycm93U2NoZW1hVG9GaWVsZHMoXG4gIHRhYmxlOiBhcnJvdy5UYWJsZSxcbiAgZmllbGRUeXBlU3VnZ2VzdGlvbnM6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4gPSB7fVxuKTogRmllbGRbXSB7XG4gIGNvbnN0IGhlYWRlclJvdyA9IHRhYmxlLnNjaGVtYS5maWVsZHMubWFwKGYgPT4gZi5uYW1lKTtcbiAgY29uc3Qgc2FtcGxlID0gZ2V0U2FtcGxlRm9yVHlwZUFuYWx5emVBcnJvdyh0YWJsZSwgaGVhZGVyUm93KTtcbiAgY29uc3Qga2VwbGVyRmllbGRzID0gZ2V0RmllbGRzRnJvbURhdGEoc2FtcGxlLCBoZWFkZXJSb3cpO1xuICBjb25zdCBnZW9BcnJvd01ldGFkYXRhID0gZ2V0R2VvQXJyb3dNZXRhZGF0YUZyb21TY2hlbWEodGFibGUpO1xuXG4gIHJldHVybiB0YWJsZS5zY2hlbWEuZmllbGRzLm1hcCgoZmllbGQ6IGFycm93LkZpZWxkLCBmaWVsZEluZGV4OiBudW1iZXIpID0+IHtcbiAgICBsZXQgdHlwZSA9IGFycm93RGF0YVR5cGVUb0ZpZWxkVHlwZShmaWVsZC50eXBlKTtcbiAgICBsZXQgYW5hbHl6ZXJUeXBlID0gYXJyb3dEYXRhVHlwZVRvQW5hbHl6ZXJEYXRhVHlwZShmaWVsZC50eXBlKTtcbiAgICBsZXQgZm9ybWF0ID0gJyc7XG5cbiAgICBjb25zdCBmaWVsZFR5cGVTdWdnZXN0aW9uID0gZmllbGRUeXBlU3VnZ2VzdGlvbnNbZmllbGQubmFtZV07XG4gICAgY29uc3Qga2VwbGVyRmllbGQgPSBrZXBsZXJGaWVsZHNbZmllbGRJbmRleF07XG5cbiAgICAvLyBnZW9tZXRyeSBmaWVsZHMgcHJvZHVjZWQgYnkgRHVja0RCJ3Mgc3RfYXNnZW9qc29uKClcbiAgICBpZiAoZmllbGRUeXBlU3VnZ2VzdGlvbiA9PT0gJ0pTT04nKSB7XG4gICAgICB0eXBlID0gQUxMX0ZJRUxEX1RZUEVTLmdlb2pzb247XG4gICAgICBhbmFseXplclR5cGUgPSBBbmFseXplckRBVEFfVFlQRVMuR0VPTUVUUllfRlJPTV9TVFJJTkc7XG4gICAgfSBlbHNlIGlmIChcbiAgICAgIGZpZWxkVHlwZVN1Z2dlc3Rpb24gPT09ICdHRU9NRVRSWScgfHxcbiAgICAgIGZpZWxkLm1ldGFkYXRhLmdldChHRU9BUlJPV19NRVRBREFUQV9LRVkpPy5zdGFydHNXaXRoKCdnZW9hcnJvdycpXG4gICAgKSB7XG4gICAgICB0eXBlID0gQUxMX0ZJRUxEX1RZUEVTLmdlb2Fycm93O1xuICAgICAgYW5hbHl6ZXJUeXBlID0gQW5hbHl6ZXJEQVRBX1RZUEVTLkdFT01FVFJZO1xuICAgIH0gZWxzZSBpZiAoZ2VvQXJyb3dNZXRhZGF0YVtmaWVsZC5uYW1lXSkge1xuICAgICAgdHlwZSA9IEFMTF9GSUVMRF9UWVBFUy5nZW9hcnJvdztcbiAgICAgIGFuYWx5emVyVHlwZSA9IEFuYWx5emVyREFUQV9UWVBFUy5HRU9NRVRSWTtcbiAgICAgIGZpZWxkLm1ldGFkYXRhPy5zZXQoR0VPQVJST1dfTUVUQURBVEFfS0VZLCBnZW9BcnJvd01ldGFkYXRhW2ZpZWxkLm5hbWVdKTtcbiAgICB9IGVsc2UgaWYgKGZpZWxkVHlwZVN1Z2dlc3Rpb24gPT09ICdCTE9CJykge1xuICAgICAgLy8gV2hlbiBhcnJvdyB3a2IgY29sdW1uIHNhdmVkIHRvIER1Y2tEQiBhcyBCTE9CIHdpdGhvdXQgYW55IG1ldGFkYXRhLCB0aGVuIHF1ZXJpZWQgYmFja1xuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgZGF0YSA9IHRhYmxlLmdldENoaWxkQXQoZmllbGRJbmRleCk/LmdldCgwKTtcbiAgICAgICAgaWYgKGRhdGEpIHtcbiAgICAgICAgICBjb25zdCBiaW5hcnlHZW8gPSBwYXJzZVN5bmMoZGF0YSwgV0tCTG9hZGVyKTtcbiAgICAgICAgICBpZiAoYmluYXJ5R2VvKSB7XG4gICAgICAgICAgICB0eXBlID0gQUxMX0ZJRUxEX1RZUEVTLmdlb2Fycm93O1xuICAgICAgICAgICAgYW5hbHl6ZXJUeXBlID0gQW5hbHl6ZXJEQVRBX1RZUEVTLkdFT01FVFJZO1xuICAgICAgICAgICAgZmllbGQubWV0YWRhdGE/LnNldChHRU9BUlJPV19NRVRBREFUQV9LRVksIEdFT0FSUk9XX0VYVEVOU0lPTlMuV0tCKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIC8vIGlnbm9yZSwgbm90IFdLQlxuICAgICAgfVxuICAgIH0gZWxzZSBpZiAoXG4gICAgICBmaWVsZFR5cGVTdWdnZXN0aW9uID09PSAnVkFSQ0hBUicgJiZcbiAgICAgIChrZXBsZXJGaWVsZC5hbmFseXplclR5cGUgPT09IEFuYWx5emVyREFUQV9UWVBFUy5HRU9NRVRSWSB8fFxuICAgICAgICBrZXBsZXJGaWVsZC5hbmFseXplclR5cGUgPT09IEFuYWx5emVyREFUQV9UWVBFUy5HRU9NRVRSWV9GUk9NX1NUUklORylcbiAgICApIHtcbiAgICAgIC8vIFdoZW4gd2tiL3drdCB3YXMgc2F2ZWQgYXMgdmFyY2hhciBpbiBEdWNrREJcbiAgICAgIHR5cGUgPSBrZXBsZXJGaWVsZC50eXBlO1xuICAgICAgYW5hbHl6ZXJUeXBlID0ga2VwbGVyRmllbGQuYW5hbHl6ZXJUeXBlO1xuICAgICAgZm9ybWF0ID0ga2VwbGVyRmllbGQuZm9ybWF0O1xuICAgIH0gZWxzZSBpZiAoZmllbGRUeXBlU3VnZ2VzdGlvbiA9PT0gJ1ZBUkNIQVInICYmIGtlcGxlckZpZWxkLnR5cGUgPT09IEFMTF9GSUVMRF9UWVBFUy5oMykge1xuICAgICAgLy8gd2hlbiBrZXBsZXIgZGV0ZWN0ZWQgaDMgY29sdW1uIHVzaW5nIGdldEZpZWxkc0Zyb21EYXRhKCksIHNldCB0eXBlIHRvIGgzIGFuZCBhbmFseXplclR5cGUgdG8gSDNcbiAgICAgIHR5cGUgPSBBTExfRklFTERfVFlQRVMuaDM7XG4gICAgICBhbmFseXplclR5cGUgPSBrZXBsZXJGaWVsZC5hbmFseXplclR5cGU7XG4gICAgfSBlbHNlIHtcbiAgICAgIC8vIFRPRE8gc2hvdWxkIHdlIHVzZSBLZXBsZXIgZ2V0RmllbGRzRnJvbURhdGEgaW5zdGVhZFxuICAgICAgLy8gb2YgYXJyb3dEYXRhVHlwZVRvRmllbGRUeXBlIGZvciBhbGwgZmllbGRzP1xuICAgICAgaWYgKGtlcGxlckZpZWxkLnR5cGUgPT09IEFMTF9GSUVMRF9UWVBFUy50aW1lc3RhbXApIHtcbiAgICAgICAgdHlwZSA9IGtlcGxlckZpZWxkLnR5cGU7XG4gICAgICAgIGFuYWx5emVyVHlwZSA9IGtlcGxlckZpZWxkLmFuYWx5emVyVHlwZTtcbiAgICAgICAgZm9ybWF0ID0ga2VwbGVyRmllbGQuZm9ybWF0O1xuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiB7XG4gICAgICAuLi5maWVsZCxcbiAgICAgIG5hbWU6IGZpZWxkLm5hbWUsXG4gICAgICBpZDogZmllbGQubmFtZSxcbiAgICAgIGRpc3BsYXlOYW1lOiBmaWVsZC5uYW1lLFxuICAgICAgZm9ybWF0OiBmb3JtYXQsXG4gICAgICBmaWVsZElkeDogZmllbGRJbmRleCxcbiAgICAgIHR5cGUsXG4gICAgICBhbmFseXplclR5cGUsXG4gICAgICB2YWx1ZUFjY2Vzc29yOiAoZGM6IGFueSkgPT4gZCA9PiB7XG4gICAgICAgIHJldHVybiBkYy52YWx1ZUF0KGQuaW5kZXgsIGZpZWxkSW5kZXgpO1xuICAgICAgfSxcbiAgICAgIG1ldGFkYXRhOiBmaWVsZC5tZXRhZGF0YVxuICAgIH07XG4gIH0pO1xufVxuXG4vKipcbiAqIFBhcnNlIGFycm93IGJhdGNoZXMgcmV0dXJuZWQgZnJvbSBwYXJzZUluQmF0Y2hlcygpXG4gKlxuICogQHBhcmFtIGFycm93VGFibGUgdGhlIGFycm93IHRhYmxlIHRvIHBhcnNlXG4gKiBAcmV0dXJucyBkYXRhc2