@tensorflow/tfjs-converter
Version:
Tensorflow model converter for javascript
406 lines • 50 kB
JavaScript
/**
* @license
* Copyright 2018 Google LLC. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =============================================================================
*/
import { io, Tensor } from '@tensorflow/tfjs-core';
import { OperationMapper } from '../operations/operation_mapper';
import { GraphExecutor } from './graph_executor';
import { ResourceManager } from './resource_manager';
export const TFHUB_SEARCH_PARAM = '?tfjs-format=file';
export const DEFAULT_MODEL_NAME = 'model.json';
/**
* A `tf.GraphModel` is a directed, acyclic graph built from a
* SavedModel GraphDef and allows inference execution.
*
* A `tf.GraphModel` can only be created by loading from a model converted from
* a [TensorFlow SavedModel](https://www.tensorflow.org/guide/saved_model) using
* the command line converter tool and loaded via `tf.loadGraphModel`.
*
* @doc {heading: 'Models', subheading: 'Classes'}
*/
export class GraphModel {
/**
* @param modelUrl url for the model, or an `io.IOHandler`.
* @param weightManifestUrl url for the weight file generated by
* scripts/convert.py script.
* @param requestOption options for Request, which allows to send credentials
* and custom headers.
* @param onProgress Optional, progress callback function, fired periodically
* before the load is completed.
*/
constructor(modelUrl, loadOptions = {}) {
this.modelUrl = modelUrl;
this.loadOptions = loadOptions;
this.version = 'n/a';
if (loadOptions == null) {
this.loadOptions = {};
}
this.resourceManager = new ResourceManager();
}
// Returns the version information for the tensorflow model GraphDef.
get modelVersion() {
return this.version;
}
get inputNodes() {
return this.executor.inputNodes;
}
get outputNodes() {
return this.executor.outputNodes;
}
get inputs() {
return this.executor.inputs;
}
get outputs() {
return this.executor.outputs;
}
get weights() {
return this.executor.weightMap;
}
get metadata() {
return this.artifacts.userDefinedMetadata;
}
get modelSignature() {
return this.signature;
}
findIOHandler() {
const path = this.modelUrl;
if (path.load != null) {
// Path is an IO Handler.
this.handler = path;
}
else if (this.loadOptions.requestInit != null) {
this.handler = io.browserHTTPRequest(path, this.loadOptions);
}
else {
const handlers = io.getLoadHandlers(path, this.loadOptions);
if (handlers.length === 0) {
// For backward compatibility: if no load handler can be found,
// assume it is a relative http path.
handlers.push(io.browserHTTPRequest(path, this.loadOptions));
}
else if (handlers.length > 1) {
throw new Error(`Found more than one (${handlers.length}) load handlers for ` +
`URL '${[path]}'`);
}
this.handler = handlers[0];
}
}
/**
* Loads the model and weight files, construct the in memory weight map and
* compile the inference graph.
*/
async load() {
this.findIOHandler();
if (this.handler.load == null) {
throw new Error('Cannot proceed with model loading because the IOHandler provided ' +
'does not have the `load` method implemented.');
}
const artifacts = await this.handler.load();
return this.loadSync(artifacts);
}
/**
* Synchronously construct the in memory weight map and
* compile the inference graph. Also initialize hashtable if any.
*
* @doc {heading: 'Models', subheading: 'Classes', ignoreCI: true}
*/
loadSync(artifacts) {
this.artifacts = artifacts;
const graph = this.artifacts.modelTopology;
let signature;
if (this.artifacts.userDefinedMetadata != null &&
this.artifacts.userDefinedMetadata.signature != null) {
signature = // tslint:disable-next-line:no-any
this.artifacts.userDefinedMetadata.signature;
}
else {
signature = this.artifacts.signature;
}
this.signature = signature;
this.version = `${graph.versions.producer}.${graph.versions.minConsumer}`;
const weightMap = io.decodeWeights(this.artifacts.weightData, this.artifacts.weightSpecs);
this.executor = new GraphExecutor(OperationMapper.Instance.transformGraph(graph, this.signature));
this.executor.weightMap = this.convertTensorMapToTensorsMap(weightMap);
// Attach a model-level resourceManager to each executor to share resources,
// such as `HashTable`.
this.executor.resourceManager = this.resourceManager;
if (artifacts.modelInitializer != null &&
artifacts.modelInitializer.node != null) {
const initializer = OperationMapper.Instance.transformGraph(artifacts.modelInitializer);
this.initializer = new GraphExecutor(initializer);
this.initializer.weightMap = this.executor.weightMap;
// Attach a model-level resourceManager to the initializer, the
// hashTables created from when executing the initializer will be stored
// in the resourceManager.
this.initializer.resourceManager = this.resourceManager;
this.initializer.executeAsync({}, []);
}
return true;
}
/**
* Save the configuration and/or weights of the GraphModel.
*
* An `IOHandler` is an object that has a `save` method of the proper
* signature defined. The `save` method manages the storing or
* transmission of serialized data ("artifacts") that represent the
* model's topology and weights onto or via a specific medium, such as
* file downloads, local storage, IndexedDB in the web browser and HTTP
* requests to a server. TensorFlow.js provides `IOHandler`
* implementations for a number of frequently used saving mediums, such as
* `tf.io.browserDownloads` and `tf.io.browserLocalStorage`. See `tf.io`
* for more details.
*
* This method also allows you to refer to certain types of `IOHandler`s
* as URL-like string shortcuts, such as 'localstorage://' and
* 'indexeddb://'.
*
* Example 1: Save `model`'s topology and weights to browser [local
* storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage);
* then load it back.
*
* ```js
* const modelUrl =
* 'https://storage.googleapis.com/tfjs-models/savedmodel/mobilenet_v2_1.0_224/model.json';
* const model = await tf.loadGraphModel(modelUrl);
* const zeros = tf.zeros([1, 224, 224, 3]);
* model.predict(zeros).print();
*
* const saveResults = await model.save('localstorage://my-model-1');
*
* const loadedModel = await tf.loadGraphModel('localstorage://my-model-1');
* console.log('Prediction from loaded model:');
* model.predict(zeros).print();
* ```
*
* @param handlerOrURL An instance of `IOHandler` or a URL-like,
* scheme-based string shortcut for `IOHandler`.
* @param config Options for saving the model.
* @returns A `Promise` of `SaveResult`, which summarizes the result of
* the saving, such as byte sizes of the saved artifacts for the model's
* topology and weight values.
*
* @doc {heading: 'Models', subheading: 'Classes', ignoreCI: true}
*/
async save(handlerOrURL, config) {
if (typeof handlerOrURL === 'string') {
const handlers = io.getSaveHandlers(handlerOrURL);
if (handlers.length === 0) {
throw new Error(`Cannot find any save handlers for URL '${handlerOrURL}'`);
}
else if (handlers.length > 1) {
throw new Error(`Found more than one (${handlers.length}) save handlers for ` +
`URL '${handlerOrURL}'`);
}
handlerOrURL = handlers[0];
}
if (handlerOrURL.save == null) {
throw new Error('GraphModel.save() cannot proceed because the IOHandler ' +
'provided does not have the `save` attribute defined.');
}
return handlerOrURL.save(this.artifacts);
}
/**
* Execute the inference for the input tensors.
*
* @param input The input tensors, when there is single input for the model,
* inputs param should be a `tf.Tensor`. For models with mutliple inputs,
* inputs params should be in either `tf.Tensor`[] if the input order is
* fixed, or otherwise NamedTensorMap format.
*
* For model with multiple inputs, we recommend you use NamedTensorMap as the
* input type, if you use `tf.Tensor`[], the order of the array needs to
* follow the
* order of inputNodes array. @see {@link GraphModel.inputNodes}
*
* You can also feed any intermediate nodes using the NamedTensorMap as the
* input type. For example, given the graph
* InputNode => Intermediate => OutputNode,
* you can execute the subgraph Intermediate => OutputNode by calling
* model.execute('IntermediateNode' : tf.tensor(...));
*
* This is useful for models that uses tf.dynamic_rnn, where the intermediate
* state needs to be fed manually.
*
* For batch inference execution, the tensors for each input need to be
* concatenated together. For example with mobilenet, the required input shape
* is [1, 244, 244, 3], which represents the [batch, height, width, channel].
* If we are provide a batched data of 100 images, the input tensor should be
* in the shape of [100, 244, 244, 3].
*
* @param config Prediction configuration for specifying the batch size and
* output node names. Currently the batch size option is ignored for graph
* model.
*
* @returns Inference result tensors. The output would be single `tf.Tensor`
* if model has single output node, otherwise Tensor[] or NamedTensorMap[]
* will be returned for model with multiple outputs.
*
* @doc {heading: 'Models', subheading: 'Classes'}
*/
predict(inputs, config) {
return this.execute(inputs, this.outputNodes);
}
normalizeInputs(inputs) {
if (!(inputs instanceof Tensor) && !Array.isArray(inputs)) {
// The input is already a NamedTensorMap.
return inputs;
}
inputs = Array.isArray(inputs) ? inputs : [inputs];
if (inputs.length !== this.inputNodes.length) {
throw new Error('Input tensor count mismatch,' +
`the graph model has ${this.inputNodes.length} placeholders, ` +
`while there are ${inputs.length} input tensors.`);
}
return this.inputNodes.reduce((map, inputName, i) => {
map[inputName] = inputs[i];
return map;
}, {});
}
normalizeOutputs(outputs) {
outputs = outputs || this.outputNodes;
return !Array.isArray(outputs) ? [outputs] : outputs;
}
/**
* Executes inference for the model for given input tensors.
* @param inputs tensor, tensor array or tensor map of the inputs for the
* model, keyed by the input node names.
* @param outputs output node name from the Tensorflow model, if no
* outputs are specified, the default outputs of the model would be used.
* You can inspect intermediate nodes of the model by adding them to the
* outputs array.
*
* @returns A single tensor if provided with a single output or no outputs
* are provided and there is only one default output, otherwise return a
* tensor array. The order of the tensor array is the same as the outputs
* if provided, otherwise the order of outputNodes attribute of the model.
*
* @doc {heading: 'Models', subheading: 'Classes'}
*/
execute(inputs, outputs) {
inputs = this.normalizeInputs(inputs);
outputs = this.normalizeOutputs(outputs);
const result = this.executor.execute(inputs, outputs);
return result.length > 1 ? result : result[0];
}
/**
* Executes inference for the model for given input tensors in async
* fashion, use this method when your model contains control flow ops.
* @param inputs tensor, tensor array or tensor map of the inputs for the
* model, keyed by the input node names.
* @param outputs output node name from the Tensorflow model, if no outputs
* are specified, the default outputs of the model would be used. You can
* inspect intermediate nodes of the model by adding them to the outputs
* array.
*
* @returns A Promise of single tensor if provided with a single output or
* no outputs are provided and there is only one default output, otherwise
* return a tensor map.
*
* @doc {heading: 'Models', subheading: 'Classes'}
*/
async executeAsync(inputs, outputs) {
inputs = this.normalizeInputs(inputs);
outputs = this.normalizeOutputs(outputs);
const result = await this.executor.executeAsync(inputs, outputs);
return result.length > 1 ? result : result[0];
}
/**
* Get intermediate tensors for model debugging mode (flag
* KEEP_INTERMEDIATE_TENSORS is true).
*
* @doc {heading: 'Models', subheading: 'Classes'}
*/
getIntermediateTensors() {
return this.executor.getIntermediateTensors();
}
/**
* Dispose intermediate tensors for model debugging mode (flag
* KEEP_INTERMEDIATE_TENSORS is true).
*
* @doc {heading: 'Models', subheading: 'Classes'}
*/
disposeIntermediateTensors() {
this.executor.disposeIntermediateTensors();
}
convertTensorMapToTensorsMap(map) {
return Object.keys(map).reduce((newMap, key) => {
newMap[key] = [map[key]];
return newMap;
}, {});
}
/**
* Releases the memory used by the weight tensors and resourceManager.
*
* @doc {heading: 'Models', subheading: 'Classes'}
*/
dispose() {
this.executor.dispose();
if (this.initializer) {
this.initializer.dispose();
}
this.resourceManager.dispose();
}
}
/**
* Load a graph model given a URL to the model definition.
*
* Example of loading MobileNetV2 from a URL and making a prediction with a
* zeros input:
*
* ```js
* const modelUrl =
* 'https://storage.googleapis.com/tfjs-models/savedmodel/mobilenet_v2_1.0_224/model.json';
* const model = await tf.loadGraphModel(modelUrl);
* const zeros = tf.zeros([1, 224, 224, 3]);
* model.predict(zeros).print();
* ```
*
* Example of loading MobileNetV2 from a TF Hub URL and making a prediction with
* a zeros input:
*
* ```js
* const modelUrl =
* 'https://tfhub.dev/google/imagenet/mobilenet_v2_140_224/classification/2';
* const model = await tf.loadGraphModel(modelUrl, {fromTFHub: true});
* const zeros = tf.zeros([1, 224, 224, 3]);
* model.predict(zeros).print();
* ```
* @param modelUrl The url or an `io.IOHandler` that loads the model.
* @param options Options for the HTTP request, which allows to send credentials
* and custom headers.
*
* @doc {heading: 'Models', subheading: 'Loading'}
*/
export async function loadGraphModel(modelUrl, options = {}) {
if (modelUrl == null) {
throw new Error('modelUrl in loadGraphModel() cannot be null. Please provide a url ' +
'or an IOHandler that loads the model');
}
if (options == null) {
options = {};
}
if (options.fromTFHub) {
if (modelUrl.load == null) {
if (!modelUrl.endsWith('/')) {
modelUrl = modelUrl + '/';
}
modelUrl = `${modelUrl}${DEFAULT_MODEL_NAME}${TFHUB_SEARCH_PARAM}`;
}
}
const model = new GraphModel(modelUrl, options);
await model.load();
return model;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ3JhcGhfbW9kZWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi90ZmpzLWNvbnZlcnRlci9zcmMvZXhlY3V0b3IvZ3JhcGhfbW9kZWwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7OztHQWVHO0FBRUgsT0FBTyxFQUFpQixFQUFFLEVBQXNDLE1BQU0sRUFBQyxNQUFNLHVCQUF1QixDQUFDO0FBSXJHLE9BQU8sRUFBQyxlQUFlLEVBQUMsTUFBTSxnQ0FBZ0MsQ0FBQztBQUUvRCxPQUFPLEVBQUMsYUFBYSxFQUFDLE1BQU0sa0JBQWtCLENBQUM7QUFDL0MsT0FBTyxFQUFDLGVBQWUsRUFBQyxNQUFNLG9CQUFvQixDQUFDO0FBRW5ELE1BQU0sQ0FBQyxNQUFNLGtCQUFrQixHQUFHLG1CQUFtQixDQUFDO0FBQ3RELE1BQU0sQ0FBQyxNQUFNLGtCQUFrQixHQUFHLFlBQVksQ0FBQztBQUMvQzs7Ozs7Ozs7O0dBU0c7QUFDSCxNQUFNLE9BQU8sVUFBVTtJQTBDckI7Ozs7Ozs7O09BUUc7SUFDSCxZQUNZLFFBQTZCLEVBQzdCLGNBQThCLEVBQUU7UUFEaEMsYUFBUSxHQUFSLFFBQVEsQ0FBcUI7UUFDN0IsZ0JBQVcsR0FBWCxXQUFXLENBQXFCO1FBbkRwQyxZQUFPLEdBQUcsS0FBSyxDQUFDO1FBb0R0QixJQUFJLFdBQVcsSUFBSSxJQUFJLEVBQUU7WUFDdkIsSUFBSSxDQUFDLFdBQVcsR0FBRyxFQUFFLENBQUM7U0FDdkI7UUFDRCxJQUFJLENBQUMsZUFBZSxHQUFHLElBQUksZUFBZSxFQUFFLENBQUM7SUFDL0MsQ0FBQztJQWpERCxxRUFBcUU7SUFDckUsSUFBSSxZQUFZO1FBQ2QsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDO0lBQ3RCLENBQUM7SUFFRCxJQUFJLFVBQVU7UUFDWixPQUFPLElBQUksQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDO0lBQ2xDLENBQUM7SUFFRCxJQUFJLFdBQVc7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Q2YsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGxpY2Vuc2VcbiAqIENvcHlyaWdodCAyMDE4IEdvb2dsZSBMTEMuIEFsbCBSaWdodHMgUmVzZXJ2ZWQuXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgXCJMaWNlbnNlXCIpO1xuICogeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLlxuICogWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0XG4gKlxuICogaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wXG4gKlxuICogVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZVxuICogZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gXCJBUyBJU1wiIEJBU0lTLFxuICogV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuXG4gKiBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kXG4gKiBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS5cbiAqID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4gKi9cblxuaW1wb3J0IHtJbmZlcmVuY2VNb2RlbCwgaW8sIE1vZGVsUHJlZGljdENvbmZpZywgTmFtZWRUZW5zb3JNYXAsIFRlbnNvcn0gZnJvbSAnQHRlbnNvcmZsb3cvdGZqcy1jb3JlJztcblxuaW1wb3J0ICogYXMgdGVuc29yZmxvdyBmcm9tICcuLi9kYXRhL2NvbXBpbGVkX2FwaSc7XG5pbXBvcnQge05hbWVkVGVuc29yc01hcCwgVGVuc29ySW5mb30gZnJvbSAnLi4vZGF0YS90eXBlcyc7XG5pbXBvcnQge09wZXJhdGlvbk1hcHBlcn0gZnJvbSAnLi4vb3BlcmF0aW9ucy9vcGVyYXRpb25fbWFwcGVyJztcblxuaW1wb3J0IHtHcmFwaEV4ZWN1dG9yfSBmcm9tICcuL2dyYXBoX2V4ZWN1dG9yJztcbmltcG9ydCB7UmVzb3VyY2VNYW5hZ2VyfSBmcm9tICcuL3Jlc291cmNlX21hbmFnZXInO1xuXG5leHBvcnQgY29uc3QgVEZIVUJfU0VBUkNIX1BBUkFNID0gJz90ZmpzLWZvcm1hdD1maWxlJztcbmV4cG9ydCBjb25zdCBERUZBVUxUX01PREVMX05BTUUgPSAnbW9kZWwuanNvbic7XG4vKipcbiAqIEEgYHRmLkdyYXBoTW9kZWxgIGlzIGEgZGlyZWN0ZWQsIGFjeWNsaWMgZ3JhcGggYnVpbHQgZnJvbSBhXG4gKiBTYXZlZE1vZGVsIEdyYXBoRGVmIGFuZCBhbGxvd3MgaW5mZXJlbmNlIGV4ZWN1dGlvbi5cbiAqXG4gKiBBIGB0Zi5HcmFwaE1vZGVsYCBjYW4gb25seSBiZSBjcmVhdGVkIGJ5IGxvYWRpbmcgZnJvbSBhIG1vZGVsIGNvbnZlcnRlZCBmcm9tXG4gKiBhIFtUZW5zb3JGbG93IFNhdmVkTW9kZWxdKGh0dHBzOi8vd3d3LnRlbnNvcmZsb3cub3JnL2d1aWRlL3NhdmVkX21vZGVsKSB1c2luZ1xuICogdGhlIGNvbW1hbmQgbGluZSBjb252ZXJ0ZXIgdG9vbCBhbmQgbG9hZGVkIHZpYSBgdGYubG9hZEdyYXBoTW9kZWxgLlxuICpcbiAqIEBkb2Mge2hlYWRpbmc6ICdNb2RlbHMnLCBzdWJoZWFkaW5nOiAnQ2xhc3Nlcyd9XG4gKi9cbmV4cG9ydCBjbGFzcyBHcmFwaE1vZGVsIGltcGxlbWVudHMgSW5mZXJlbmNlTW9kZWwge1xuICBwcml2YXRlIGV4ZWN1dG9yOiBHcmFwaEV4ZWN1dG9yO1xuICBwcml2YXRlIHZlcnNpb24gPSAnbi9hJztcbiAgcHJpdmF0ZSBoYW5kbGVyOiBpby5JT0hhbmRsZXI7XG4gIHByaXZhdGUgYXJ0aWZhY3RzOiBpby5Nb2RlbEFydGlmYWN0cztcbiAgcHJpdmF0ZSBpbml0aWFsaXplcjogR3JhcGhFeGVjdXRvcjtcbiAgcHJpdmF0ZSByZXNvdXJjZU1hbmFnZXI6IFJlc291cmNlTWFuYWdlcjtcbiAgcHJpdmF0ZSBzaWduYXR1cmU6IHRlbnNvcmZsb3cuSVNpZ25hdHVyZURlZjtcblxuICAvLyBSZXR1cm5zIHRoZSB2ZXJzaW9uIGluZm9ybWF0aW9uIGZvciB0aGUgdGVuc29yZmxvdyBtb2RlbCBHcmFwaERlZi5cbiAgZ2V0IG1vZGVsVmVyc2lvbigpOiBzdHJpbmcge1xuICAgIHJldHVybiB0aGlzLnZlcnNpb247XG4gIH1cblxuICBnZXQgaW5wdXROb2RlcygpOiBzdHJpbmdbXSB7XG4gICAgcmV0dXJuIHRoaXMuZXhlY3V0b3IuaW5wdXROb2RlcztcbiAgfVxuXG4gIGdldCBvdXRwdXROb2RlcygpOiBzdHJpbmdbXSB7XG4gICAgcmV0dXJuIHRoaXMuZXhlY3V0b3Iub3V0cHV0Tm9kZXM7XG4gIH1cblxuICBnZXQgaW5wdXRzKCk6IFRlbnNvckluZm9bXSB7XG4gICAgcmV0dXJuIHRoaXMuZXhlY3V0b3IuaW5wdXRzO1xuICB9XG5cbiAgZ2V0IG91dHB1dHMoKTogVGVuc29ySW5mb1tdIHtcbiAgICByZXR1cm4gdGhpcy5leGVjdXRvci5vdXRwdXRzO1xuICB9XG5cbiAgZ2V0IHdlaWdodHMoKTogTmFtZWRUZW5zb3JzTWFwIHtcbiAgICByZXR1cm4gdGhpcy5leGVjdXRvci53ZWlnaHRNYXA7XG4gIH1cblxuICBnZXQgbWV0YWRhdGEoKToge30ge1xuICAgIHJldHVybiB0aGlzLmFydGlmYWN0cy51c2VyRGVmaW5lZE1ldGFkYXRhO1xuICB9XG5cbiAgZ2V0IG1vZGVsU2lnbmF0dXJlKCk6IHt9IHtcbiAgICByZXR1cm4gdGhpcy5zaWduYXR1cmU7XG4gIH1cblxuICAvKipcbiAgICogQHBhcmFtIG1vZGVsVXJsIHVybCBmb3IgdGhlIG1vZGVsLCBvciBhbiBgaW8uSU9IYW5kbGVyYC5cbiAgICogQHBhcmFtIHdlaWdodE1hbmlmZXN0VXJsIHVybCBmb3IgdGhlIHdlaWdodCBmaWxlIGdlbmVyYXRlZCBieVxuICAgKiBzY3JpcHRzL2NvbnZlcnQucHkgc2NyaXB0LlxuICAgKiBAcGFyYW0gcmVxdWVzdE9wdGlvbiBvcHRpb25zIGZvciBSZXF1ZXN0LCB3aGljaCBhbGxvd3MgdG8gc2VuZCBjcmVkZW50aWFsc1xuICAgKiBhbmQgY3VzdG9tIGhlYWRlcnMuXG4gICAqIEBwYXJhbSBvblByb2dyZXNzIE9wdGlvbmFsLCBwcm9ncmVzcyBjYWxsYmFjayBmdW5jdGlvbiwgZmlyZWQgcGVyaW9kaWNhbGx5XG4gICAqIGJlZm9yZSB0aGUgbG9hZCBpcyBjb21wbGV0ZWQuXG4gICAqL1xuICBjb25zdHJ1Y3RvcihcbiAgICAgIHByaXZhdGUgbW9kZWxVcmw6IHN0cmluZ3xpby5JT0hhbmRsZXIsXG4gICAgICBwcml2YXRlIGxvYWRPcHRpb25zOiBpby5Mb2FkT3B0aW9ucyA9IHt9KSB7XG4gICAgaWYgKGxvYWRPcHRpb25zID09IG51bGwpIHtcbiAgICAgIHRoaXMubG9hZE9wdGlvbnMgPSB7fTtcbiAgICB9XG4gICAgdGhpcy5yZXNvdXJjZU1hbmFnZXIgPSBuZXcgUmVzb3VyY2VNYW5hZ2VyKCk7XG4gIH1cblxuICBwcml2YXRlIGZpbmRJT0hhbmRsZXIoKSB7XG4gICAgY29uc3QgcGF0aCA9IHRoaXMubW9kZWxVcmw7XG4gICAgaWYgKChwYXRoIGFzIGlvLklPSGFuZGxlcikubG9hZCAhPSBudWxsKSB7XG4gICAgICAvLyBQYXRoIGlzIGFuIElPIEhhbmRsZXIuXG4gICAgICB0aGlzLmhhbmRsZXIgPSBwYXRoIGFzIGlvLklPSGFuZGxlcjtcbiAgICB9IGVsc2UgaWYgKHRoaXMubG9hZE9wdGlvbnMucmVxdWVzdEluaXQgIT0gbnVsbCkge1xuICAgICAgdGhpcy5oYW5kbGVyID0gaW8uYnJvd3NlckhUVFBSZXF1ZXN0KHBhdGggYXMgc3RyaW5nLCB0aGlzLmxvYWRPcHRpb25zKTtcbiAgICB9IGVsc2Uge1xuICAgICAgY29uc3QgaGFuZGxlcnMgPSBpby5nZXRMb2FkSGFuZGxlcnMocGF0aCBhcyBzdHJpbmcsIHRoaXMubG9hZE9wdGlvbnMpO1xuICAgICAgaWYgKGhhbmRsZXJzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAvLyBGb3IgYmFja3dhcmQgY29tcGF0aWJpbGl0eTogaWYgbm8gbG9hZCBoYW5kbGVyIGNhbiBiZSBmb3VuZCxcbiAgICAgICAgLy8gYXNzdW1lIGl0IGlzIGEgcmVsYXRpdmUgaHR0cCBwYXRoLlxuICAgICAgICBoYW5kbGVycy5wdXNoKGlvLmJyb3dzZXJIVFRQUmVxdWVzdChwYXRoIGFzIHN0cmluZywgdGhpcy5sb2FkT3B0aW9ucykpO1xuICAgICAgfSBlbHNlIGlmIChoYW5kbGVycy5sZW5ndGggPiAxKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgIGBGb3VuZCBtb3JlIHRoYW4gb25lICgke2hhbmRsZXJzLmxlbmd0aH0pIGxvYWQgaGFuZGxlcnMgZm9yIGAgK1xuICAgICAgICAgICAgYFVSTCAnJHtbcGF0aF19J2ApO1xuICAgICAgfVxuICAgICAgdGhpcy5oYW5kbGVyID0gaGFuZGxlcnNbMF07XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIExvYWRzIHRoZSBtb2RlbCBhbmQgd2VpZ2h0IGZpbGVzLCBjb25zdHJ1Y3QgdGhlIGluIG1lbW9yeSB3ZWlnaHQgbWFwIGFuZFxuICAgKiBjb21waWxlIHRoZSBpbmZlcmVuY2UgZ3JhcGguXG4gICAqL1xuICBhc3luYyBsb2FkKCk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICAgIHRoaXMuZmluZElPSGFuZGxlcigpO1xuICAgIGlmICh0aGlzLmhhbmRsZXIubG9hZCA9PSBudWxsKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgJ0Nhbm5vdCBwcm9jZWVkIHdpdGggbW9kZWwgbG9hZGluZyBiZWNhdXNlIHRoZSBJT0hhbmRsZXIgcHJvdmlkZWQgJyArXG4gICAgICAgICAgJ2RvZXMgbm90IGhhdmUgdGhlIGBsb2FkYCBtZXRob2QgaW1wbGVtZW50ZWQuJyk7XG4gICAgfVxuICAgIGNvbnN0IGFydGlmYWN0cyA9IGF3YWl0IHRoaXMuaGFuZGxlci5sb2FkKCk7XG5cbiAgICByZXR1cm4gdGhpcy5sb2FkU3luYyhhcnRpZmFjdHMpO1xuICB9XG5cbiAgLyoqXG4gICAqIFN5bmNocm9ub3VzbHkgY29uc3RydWN0IHRoZSBpbiBtZW1vcnkgd2VpZ2h0IG1hcCBhbmRcbiAgICogY29tcGlsZSB0aGUgaW5mZXJlbmNlIGdyYXBoLiBBbHNvIGluaXRpYWxpemUgaGFzaHRhYmxlIGlmIGFueS5cbiAgICpcbiAgICogQGRvYyB7aGVhZGluZzogJ01vZGVscycsIHN1YmhlYWRpbmc6ICdDbGFzc2VzJywgaWdub3JlQ0k6IHRydWV9XG4gICAqL1xuICBsb2FkU3luYyhhcnRpZmFjdHM6IGlvLk1vZGVsQXJ0aWZhY3RzKSB7XG4gICAgdGhpcy5hcnRpZmFjdHMgPSBhcnRpZmFjdHM7XG4gICAgY29uc3QgZ3JhcGggPSB0aGlzLmFydGlmYWN0cy5tb2RlbFRvcG9sb2d5IGFzIHRlbnNvcmZsb3cuSUdyYXBoRGVmO1xuXG4gICAgbGV0IHNpZ25hdHVyZTtcbiAgICBpZiAodGhpcy5hcnRpZmFjdHMudXNlckRlZmluZWRNZXRhZGF0YSAhPSBudWxsICYmXG4gICAgICAgIHRoaXMuYXJ0aWZhY3RzLnVzZXJEZWZpbmVkTWV0YWRhdGEuc2lnbmF0dXJlICE9IG51bGwpIHtcbiAgICAgIHNpZ25hdHVyZSA9ICAvLyB0c2xpbnQ6ZGlzYWJsZS1uZXh0LWxpbmU6bm8tYW55XG4gICAgICAgICAgKHRoaXMuYXJ0aWZhY3RzLnVzZXJEZWZpbmVkTWV0YWRhdGEgYXMgYW55KS5zaWduYXR1cmUgYXNcbiAgICAgICAgICB0ZW5zb3JmbG93LklTaWduYXR1cmVEZWY7XG4gICAgfSBlbHNlIHtcbiAgICAgIHNpZ25hdHVyZSA9IHRoaXMuYXJ0aWZhY3RzLnNpZ25hdHVyZTtcbiAgICB9XG4gICAgdGhpcy5zaWduYXR1cmUgPSBzaWduYXR1cmU7XG5cbiAgICB0aGlzLnZlcnNpb24gPSBgJHtncmFwaC52ZXJzaW9ucy5wcm9kdWNlcn0uJHtncmFwaC52ZXJzaW9ucy5taW5Db25zdW1lcn1gO1xuICAgIGNvbnN0IHdlaWdodE1hcCA9XG4gICAgICAgIGlvLmRlY29kZVdlaWdodHModGhpcy5hcnRpZmFjdHMud2VpZ2h0RGF0YSwgdGhpcy5hcnRpZmFjdHMud2VpZ2h0U3BlY3MpO1xuICAgIHRoaXMuZXhlY3V0b3IgPSBuZXcgR3JhcGhFeGVjdXRvcihcbiAgICAgICAgT3BlcmF0aW9uTWFwcGVyLkluc3RhbmNlLnRyYW5zZm9ybUdyYXBoKGdyYXBoLCB0aGlzLnNpZ25hdHVyZSkpO1xuICAgIHRoaXMuZXhlY3V0b3Iud2VpZ2h0TWFwID0gdGhpcy5jb252ZXJ0VGVuc29yTWFwVG9UZW5zb3JzTWFwKHdlaWdodE1hcCk7XG4gICAgLy8gQXR0YWNoIGEgbW9kZWwtbGV2ZWwgcmVzb3VyY2VNYW5hZ2VyIHRvIGVhY2ggZXhlY3V0b3IgdG8gc2hhcmUgcmVzb3VyY2VzLFxuICAgIC8vIHN1Y2ggYXMgYEhhc2hUYWJsZWAuXG4gICAgdGhpcy5leGVjdXRvci5yZXNvdXJjZU1hbmFnZXIgPSB0aGlzLnJlc291cmNlTWFuYWdlcjtcblxuICAgIGlmIChhcnRpZmFjdHMubW9kZWxJbml0aWFsaXplciAhPSBudWxsICYmXG4gICAgICAgIChhcnRpZmFjdHMubW9kZWxJbml0aWFsaXplciBhcyB0ZW5zb3JmbG93LklHcmFwaERlZikubm9kZSAhPSBudWxsKSB7XG4gICAgICBjb25zdCBpbml0aWFsaXplciA9XG4gICAgICAgICAgT3BlcmF0aW9uTWFwcGVyLkluc3RhbmNlLnRyYW5zZm9ybUdyYXBoKGFydGlmYWN0cy5tb2RlbEluaXRpYWxpemVyKTtcbiAgICAgIHRoaXMuaW5pdGlhbGl6ZXIgPSBuZXcgR3JhcGhFeGVjdXRvcihpbml0aWFsaXplcik7XG4gICAgICB0aGlzLmluaXRpYWxpemVyLndlaWdodE1hcCA9IHRoaXMuZXhlY3V0b3Iud2VpZ2h0TWFwO1xuICAgICAgLy8gQXR0YWNoIGEgbW9kZWwtbGV2ZWwgcmVzb3VyY2VNYW5hZ2VyIHRvIHRoZSBpbml0aWFsaXplciwgdGhlXG4gICAgICAvLyBoYXNoVGFibGVzIGNyZWF0ZWQgZnJvbSB3aGVuIGV4ZWN1dGluZyB0aGUgaW5pdGlhbGl6ZXIgd2lsbCBiZSBzdG9yZWRcbiAgICAgIC8vIGluIHRoZSByZXNvdXJjZU1hbmFnZXIuXG4gICAgICB0aGlzLmluaXRpYWxpemVyLnJlc291cmNlTWFuYWdlciA9IHRoaXMucmVzb3VyY2VNYW5hZ2VyO1xuICAgICAgdGhpcy5pbml0aWFsaXplci5leGVjdXRlQXN5bmMoe30sIFtdKTtcbiAgICB9XG5cbiAgICByZXR1cm4gdHJ1ZTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTYXZlIHRoZSBjb25maWd1cmF0aW9uIGFuZC9vciB3ZWlnaHRzIG9mIHRoZSBHcmFwaE1vZGVsLlxuICAgKlxuICAgKiBBbiBgSU9IYW5kbGVyYCBpcyBhbiBvYmplY3QgdGhhdCBoYXMgYSBgc2F2ZWAgbWV0aG9kIG9mIHRoZSBwcm9wZXJcbiAgICogc2lnbmF0dXJlIGRlZmluZWQuIFRoZSBgc2F2ZWAgbWV0aG9kIG1hbmFnZXMgdGhlIHN0b3Jpbmcgb3JcbiAgICogdHJhbnNtaXNzaW9uIG9mIHNlcmlhbGl6ZWQgZGF0YSAoXCJhcnRpZmFjdHNcIikgdGhhdCByZXByZXNlbnQgdGhlXG4gICAqIG1vZGVsJ3MgdG9wb2xvZ3kgYW5kIHdlaWdodHMgb250byBvciB2aWEgYSBzcGVjaWZpYyBtZWRpdW0sIHN1Y2ggYXNcbiAgICogZmlsZSBkb3dubG9hZHMsIGxvY2FsIHN0b3JhZ2UsIEluZGV4ZWREQiBpbiB0aGUgd2ViIGJyb3dzZXIgYW5kIEhUVFBcbiAgICogcmVxdWVzdHMgdG8gYSBzZXJ2ZXIuIFRlbnNvckZsb3cuanMgcHJvdmlkZXMgYElPSGFuZGxlcmBcbiAgICogaW1wbGVtZW50YXRpb25zIGZvciBhIG51bWJlciBvZiBmcmVxdWVudGx5IHVzZWQgc2F2aW5nIG1lZGl1bXMsIHN1Y2ggYXNcbiAgICogYHRmLmlvLmJyb3dzZXJEb3dubG9hZHNgIGFuZCBgdGYuaW8uYnJvd3NlckxvY2FsU3RvcmFnZWAuIFNlZSBgdGYuaW9gXG4gICAqIGZvciBtb3JlIGRldGFpbHMuXG4gICAqXG4gICAqIFRoaXMgbWV0aG9kIGFsc28gYWxsb3dzIHlvdSB0byByZWZlciB0byBjZXJ0YWluIHR5cGVzIG9mIGBJT0hhbmRsZXJgc1xuICAgKiBhcyBVUkwtbGlrZSBzdHJpbmcgc2hvcnRjdXRzLCBzdWNoIGFzICdsb2NhbHN0b3JhZ2U6Ly8nIGFuZFxuICAgKiAnaW5kZXhlZGRiOi8vJy5cbiAgICpcbiAgICogRXhhbXBsZSAxOiBTYXZlIGBtb2RlbGAncyB0b3BvbG9neSBhbmQgd2VpZ2h0cyB0byBicm93c2VyIFtsb2NhbFxuICAgKiBzdG9yYWdlXShodHRwczovL2RldmVsb3Blci5tb3ppbGxhLm9yZy9lbi1VUy9kb2NzL1dlYi9BUEkvV2luZG93L2xvY2FsU3RvcmFnZSk7XG4gICAqIHRoZW4gbG9hZCBpdCBiYWNrLlxuICAgKlxuICAgKiBgYGBqc1xuICAgKiBjb25zdCBtb2RlbFVybCA9XG4gICAqICAgICdodHRwczovL3N0b3JhZ2UuZ29vZ2xlYXBpcy5jb20vdGZqcy1tb2RlbHMvc2F2ZWRtb2RlbC9tb2JpbGVuZXRfdjJfMS4wXzIyNC9tb2RlbC5qc29uJztcbiAgICogY29uc3QgbW9kZWwgPSBhd2FpdCB0Zi5sb2FkR3JhcGhNb2RlbChtb2RlbFVybCk7XG4gICAqIGNvbnN0IHplcm9zID0gdGYuemVyb3MoWzEsIDIyNCwgMjI0LCAzXSk7XG4gICAqIG1vZGVsLnByZWRpY3QoemVyb3MpLnByaW50KCk7XG4gICAqXG4gICAqIGNvbnN0IHNhdmVSZXN1bHRzID0gYXdhaXQgbW9kZWwuc2F2ZSgnbG9jYWxzdG9yYWdlOi8vbXktbW9kZWwtMScpO1xuICAgKlxuICAgKiBjb25zdCBsb2FkZWRNb2RlbCA9IGF3YWl0IHRmLmxvYWRHcmFwaE1vZGVsKCdsb2NhbHN0b3JhZ2U6Ly9teS1tb2RlbC0xJyk7XG4gICAqIGNvbnNvbGUubG9nKCdQcmVkaWN0aW9uIGZyb20gbG9hZGVkIG1vZGVsOicpO1xuICAgKiBtb2RlbC5wcmVkaWN0KHplcm9zKS5wcmludCgpO1xuICAgKiBgYGBcbiAgICpcbiAgICogQHBhcmFtIGhhbmRsZXJPclVSTCBBbiBpbnN0YW5jZSBvZiBgSU9IYW5kbGVyYCBvciBhIFVSTC1saWtlLFxuICAgKiBzY2hlbWUtYmFzZWQgc3RyaW5nIHNob3J0Y3V0IGZvciBgSU9IYW5kbGVyYC5cbiAgICogQHBhcmFtIGNvbmZpZyBPcHRpb25zIGZvciBzYXZpbmcgdGhlIG1vZGVsLlxuICAgKiBAcmV0dXJucyBBIGBQcm9taXNlYCBvZiBgU2F2ZVJlc3VsdGAsIHdoaWNoIHN1bW1hcml6ZXMgdGhlIHJlc3VsdCBvZlxuICAgKiB0aGUgc2F2aW5nLCBzdWNoIGFzIGJ5dGUgc2l6ZXMgb2YgdGhlIHNhdmVkIGFydGlmYWN0cyBmb3IgdGhlIG1vZGVsJ3NcbiAgICogICB0b3BvbG9neSBhbmQgd2VpZ2h0IHZhbHVlcy5cbiAgICpcbiAgICogQGRvYyB7aGVhZGluZzogJ01vZGVscycsIHN1YmhlYWRpbmc6ICdDbGFzc2VzJywgaWdub3JlQ0k6IHRydWV9XG4gICAqL1xuICBhc3luYyBzYXZlKGhhbmRsZXJPclVSTDogaW8uSU9IYW5kbGVyfHN0cmluZywgY29uZmlnPzogaW8uU2F2ZUNvbmZpZyk6XG4gICAgICBQcm9taXNlPGlvLlNhdmVSZXN1bHQ+IHtcbiAgICBpZiAodHlwZW9mIGhhbmRsZXJPclVSTCA9PT0gJ3N0cmluZycpIHtcbiAgICAgIGNvbnN0IGhhbmRsZXJzID0gaW8uZ2V0U2F2ZUhhbmRsZXJzKGhhbmRsZXJPclVSTCk7XG4gICAgICBpZiAoaGFuZGxlcnMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgIGBDYW5ub3QgZmluZCBhbnkgc2F2ZSBoYW5kbGVycyBmb3IgVVJMICcke2hhbmRsZXJPclVSTH0nYCk7XG4gICAgICB9IGVsc2UgaWYgKGhhbmRsZXJzLmxlbmd0aCA+IDEpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgYEZvdW5kIG1vcmUgdGhhbiBvbmUgKCR7aGFuZGxlcnMubGVuZ3RofSkgc2F2ZSBoYW5kbGVycyBmb3IgYCArXG4gICAgICAgICAgICBgVVJMICcke2hhbmRsZXJPclVSTH0nYCk7XG4gICAgICB9XG4gICAgICBoYW5kbGVyT3JVUkwgPSBoYW5kbGVyc1swXTtcbiAgICB9XG4gICAgaWYgKGhhbmRsZXJPclVSTC5zYXZlID09IG51bGwpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAnR3JhcGhNb2RlbC5zYXZlKCkgY2Fubm90IHByb2NlZWQgYmVjYXVzZSB0aGUgSU9IYW5kbGVyICcgK1xuICAgICAgICAgICdwcm92aWRlZCBkb2VzIG5vdCBoYXZlIHRoZSBgc2F2ZWAgYXR0cmlidXRlIGRlZmluZWQuJyk7XG4gICAgfVxuXG4gICAgcmV0dXJuIGhhbmRsZXJPclVSTC5zYXZlKHRoaXMuYXJ0aWZhY3RzKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBFeGVjdXRlIHRoZSBpbmZlcmVuY2UgZm9yIHRoZSBpbnB1dCB0ZW5zb3JzLlxuICAgKlxuICAgKiBAcGFyYW0gaW5wdXQgVGhlIGlucHV0IHRlbnNvcnMsIHdoZW4gdGhlcmUgaXMgc2luZ2xlIGlucHV0IGZvciB0aGUgbW9kZWwsXG4gICAqIGlucHV0cyBwYXJhbSBzaG91bGQgYmUgYSBgdGYuVGVuc29yYC4gRm9yIG1vZGVscyB3aXRoIG11dGxpcGxlIGlucHV0cyxcbiAgICogaW5wdXRzIHBhcmFtcyBzaG91bGQgYmUgaW4gZWl0aGVyIGB0Zi5UZW5zb3JgW10gaWYgdGhlIGlucHV0IG9yZGVyIGlzXG4gICAqIGZpeGVkLCBvciBvdGhlcndpc2UgTmFtZWRUZW5zb3JNYXAgZm9ybWF0LlxuICAgKlxuICAgKiBGb3IgbW9kZWwgd2l0aCBtdWx0aXBsZSBpbnB1dHMsIHdlIHJlY29tbWVuZCB5b3UgdXNlIE5hbWVkVGVuc29yTWFwIGFzIHRoZVxuICAgKiBpbnB1dCB0eXBlLCBpZiB5b3UgdXNlIGB0Zi5UZW5zb3JgW10sIHRoZSBvcmRlciBvZiB0aGUgYXJyYXkgbmVlZHMgdG9cbiAgICogZm9sbG93IHRoZVxuICAgKiBvcmRlciBvZiBpbnB1dE5vZGVzIGFycmF5LiBAc2VlIHtAbGluayBHcmFwaE1vZGVsLmlucHV0Tm9kZXN9XG4gICAqXG4gICAqIFlvdSBjYW4gYWxzbyBmZWVkIGFueSBpbnRlcm1lZGlhdGUgbm9kZXMgdXNpbmcgdGhlIE5hbWVkVGVuc29yTWFwIGFzIHRoZVxuICAgKiBpbnB1dCB0eXBlLiBGb3IgZXhhbXBsZSwgZ2l2ZW4gdGhlIGdyYXBoXG4gICAqICAgIElucHV0Tm9kZSA9PiBJbnRlcm1lZGlhdGUgPT4gT3V0cHV0Tm9kZSxcbiAgICogeW91IGNhbiBleGVjdXRlIHRoZSBzdWJncmFwaCBJbnRlcm1lZGlhdGUgPT4gT3V0cHV0Tm9kZSBieSBjYWxsaW5nXG4gICAqICAgIG1vZGVsLmV4ZWN1dGUoJ0ludGVybWVkaWF0ZU5vZGUnIDogdGYudGVuc29yKC4uLikpO1xuICAgKlxuICAgKiBUaGlzIGlzIHVzZWZ1bCBmb3IgbW9kZWxzIHRoYXQgdXNlcyB0Zi5keW5hbWljX3Jubiwgd2hlcmUgdGhlIGludGVybWVkaWF0ZVxuICAgKiBzdGF0ZSBuZWVkcyB0byBiZSBmZWQgbWFudWFsbHkuXG4gICAqXG4gICAqIEZvciBiYXRjaCBpbmZlcmVuY2UgZXhlY3V0aW9uLCB0aGUgdGVuc29ycyBmb3IgZWFjaCBpbnB1dCBuZWVkIHRvIGJlXG4gICAqIGNvbmNhdGVuYXRlZCB0b2dldGhlci4gRm9yIGV4YW1wbGUgd2l0aCBtb2JpbGVuZXQsIHRoZSByZXF1aXJlZCBpbnB1dCBzaGFwZVxuICAgKiBpcyBbMSwgMjQ0LCAyNDQsIDNdLCB3aGljaCByZXByZXNlbnRzIHRoZSBbYmF0Y2gsIGhlaWdodCwgd2lkdGgsIGNoYW5uZWxdLlxuICAgKiBJZiB3ZSBhcmUgcHJvdmlkZSBhIGJhdGNoZWQgZGF0YSBvZiAxMDAgaW1hZ2VzLCB0aGUgaW5wdXQgdGVuc29yIHNob3VsZCBiZVxuICAgKiBpbiB0aGUgc2hhcGUgb2YgWzEwMCwgMjQ0LCAyNDQsIDNdLlxuICAgKlxuICAgKiBAcGFyYW0gY29uZmlnIFByZWRpY3Rpb24gY29uZmlndXJhdGlvbiBmb3Igc3BlY2lmeWluZyB0aGUgYmF0Y2ggc2l6ZSBhbmRcbiAgICogb3V0cHV0IG5vZGUgbmFtZXMuIEN1cnJlbnRseSB0aGUgYmF0Y2ggc2l6ZSBvcHRpb24gaXMgaWdub3JlZCBmb3IgZ3JhcGhcbiAgICogbW9kZWwuXG4gICAqXG4gICAqIEByZXR1cm5zIEluZmVyZW5jZSByZXN1bHQgdGVuc29ycy4gVGhlIG91dHB1dCB3b3VsZCBiZSBzaW5nbGUgYHRmLlRlbnNvcmBcbiAgICogaWYgbW9kZWwgaGFzIHNpbmdsZSBvdXRwdXQgbm9kZSwgb3RoZXJ3aXNlIFRlbnNvcltdIG9yIE5hbWVkVGVuc29yTWFwW11cbiAgICogd2lsbCBiZSByZXR1cm5lZCBmb3IgbW9kZWwgd2l0aCBtdWx0aXBsZSBvdXRwdXRzLlxuICAgKlxuICAgKiBAZG9jIHtoZWFkaW5nOiAnTW9kZWxzJywgc3ViaGVhZGluZzogJ0NsYXNzZXMnfVxuICAgKi9cbiAgcHJlZGljdChpbnB1dHM6IFRlbnNvcnxUZW5zb3JbXXxOYW1lZFRlbnNvck1hcCwgY29uZmlnPzogTW9kZWxQcmVkaWN0Q29uZmlnKTpcbiAgICAgIFRlbnNvcnxUZW5zb3JbXXxOYW1lZFRlbnNvck1hcCB7XG4gICAgcmV0dXJuIHRoaXMuZXhlY3V0ZShpbnB1dHMsIHRoaXMub3V0cHV0Tm9kZXMpO1xuICB9XG5cbiAgcHJpdmF0ZSBub3JtYWxpemVJbnB1dHMoaW5wdXRzOiBUZW5zb3J8VGVuc29yW118XG4gICAgICAgICAgICAgICAgICAgICAgICAgIE5hbWVkVGVuc29yTWFwKTogTmFtZWRUZW5zb3JNYXAge1xuICAgIGlmICghKGlucHV0cyBpbnN0YW5jZW9mIFRlbnNvcikgJiYgIUFycmF5LmlzQXJyYXkoaW5wdXRzKSkge1xuICAgICAgLy8gVGhlIGlucHV0IGlzIGFscmVhZHkgYSBOYW1lZFRlbnNvck1hcC5cbiAgICAgIHJldHVybiBpbnB1dHM7XG4gICAgfVxuICAgIGlucHV0cyA9IEFycmF5LmlzQXJyYXkoaW5wdXRzKSA/IGlucHV0cyA6IFtpbnB1dHNdO1xuICAgIGlmIChpbnB1dHMubGVuZ3RoICE9PSB0aGlzLmlucHV0Tm9kZXMubGVuZ3RoKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgJ0lucHV0IHRlbnNvciBjb3VudCBtaXNtYXRjaCwnICtcbiAgICAgICAgICBgdGhlIGdyYXBoIG1vZGVsIGhhcyAke3RoaXMuaW5wdXROb2Rlcy5sZW5ndGh9IHBsYWNlaG9sZGVycywgYCArXG4gICAgICAgICAgYHdoaWxlIHRoZXJlIGFyZSAke2lucHV0cy5sZW5ndGh9IGlucHV0IHRlbnNvcnMuYCk7XG4gICAgfVxuICAgIHJldHVybiB0aGlzLmlucHV0Tm9kZXMucmVkdWNlKChtYXAsIGlucHV0TmFtZSwgaSkgPT4ge1xuICAgICAgbWFwW2lucHV0TmFtZV0gPSAoaW5wdXRzIGFzIFRlbnNvcltdKVtpXTtcbiAgICAgIHJldHVybiBtYXA7XG4gICAgfSwge30gYXMgTmFtZWRUZW5zb3JNYXApO1xuICB9XG5cbiAgcHJpdmF0ZSBub3JtYWxpemVPdXRwdXRzKG91dHB1dHM6IHN0cmluZ3xzdHJpbmdbXSk6IHN0cmluZ1tdIHtcbiAgICBvdXRwdXRzID0gb3V0cHV0cyB8fCB0aGlzLm91dHB1dE5vZGVzO1xuICAgIHJldHVybiAhQXJyYXkuaXNBcnJheShvdXRwdXRzKSA/IFtvdXRwdXRzXSA6IG91dHB1dHM7XG4gIH1cblxuICAvKipcbiAgICogRXhlY3V0ZXMgaW5mZXJlbmNlIGZvciB0aGUgbW9kZWwgZm9yIGdpdmVuIGlucHV0IHRlbnNvcnMuXG4gICAqIEBwYXJhbSBpbnB1dHMgdGVuc29yLCB0ZW5zb3IgYXJyYXkgb3IgdGVuc29yIG1hcCBvZiB0aGUgaW5wdXRzIGZvciB0aGVcbiAgICogbW9kZWwsIGtleWVkIGJ5IHRoZSBpbnB1dCBub2RlIG5hbWVzLlxuICAgKiBAcGFyYW0gb3V0cHV0cyBvdXRwdXQgbm9kZSBuYW1lIGZyb20gdGhlIFRlbnNvcmZsb3cgbW9kZWwsIGlmIG5vXG4gICAqIG91dHB1dHMgYXJlIHNwZWNpZmllZCwgdGhlIGRlZmF1bHQgb3V0cHV0cyBvZiB0aGUgbW9kZWwgd291bGQgYmUgdXNlZC5cbiAgICogWW91IGNhbiBpbnNwZWN0IGludGVybWVkaWF0ZSBub2RlcyBvZiB0aGUgbW9kZWwgYnkgYWRkaW5nIHRoZW0gdG8gdGhlXG4gICAqIG91dHB1dHMgYXJyYXkuXG4gICAqXG4gICAqIEByZXR1cm5zIEEgc2luZ2xlIHRlbnNvciBpZiBwcm92aWRlZCB3aXRoIGEgc2luZ2xlIG91dHB1dCBvciBubyBvdXRwdXRzXG4gICAqIGFyZSBwcm92aWRlZCBhbmQgdGhlcmUgaXMgb25seSBvbmUgZGVmYXVsdCBvdXRwdXQsIG90aGVyd2lzZSByZXR1cm4gYVxuICAgKiB0ZW5zb3IgYXJyYXkuIFRoZSBvcmRlciBvZiB0aGUgdGVuc29yIGFycmF5IGlzIHRoZSBzYW1lIGFzIHRoZSBvdXRwdXRzXG4gICAqIGlmIHByb3ZpZGVkLCBvdGhlcndpc2UgdGhlIG9yZGVyIG9mIG91dHB1dE5vZGVzIGF0dHJpYnV0ZSBvZiB0aGUgbW9kZWwuXG4gICAqXG4gICAqIEBkb2Mge2hlYWRpbmc6ICdNb2RlbHMnLCBzdWJoZWFkaW5nOiAnQ2xhc3Nlcyd9XG4gICAqL1xuICBleGVjdXRlKGlucHV0czogVGVuc29yfFRlbnNvcltdfE5hbWVkVGVuc29yTWFwLCBvdXRwdXRzPzogc3RyaW5nfHN0cmluZ1tdKTpcbiAgICAgIFRlbnNvcnxUZW5zb3JbXSB7XG4gICAgaW5wdXRzID0gdGhpcy5ub3JtYWxpemVJbnB1dHMoaW5wdXRzKTtcbiAgICBvdXRwdXRzID0gdGhpcy5ub3JtYWxpemVPdXRwdXRzKG91dHB1dHMpO1xuICAgIGNvbnN0IHJlc3VsdCA9IHRoaXMuZXhlY3V0b3IuZXhlY3V0ZShpbnB1dHMsIG91dHB1dHMpO1xuICAgIHJldHVybiByZXN1bHQubGVuZ3RoID4gMSA/IHJlc3VsdCA6IHJlc3VsdFswXTtcbiAgfVxuICAvKipcbiAgICogRXhlY3V0ZXMgaW5mZXJlbmNlIGZvciB0aGUgbW9kZWwgZm9yIGdpdmVuIGlucHV0IHRlbnNvcnMgaW4gYXN5bmNcbiAgICogZmFzaGlvbiwgdXNlIHRoaXMgbWV0aG9kIHdoZW4geW91ciBtb2RlbCBjb250YWlucyBjb250cm9sIGZsb3cgb3BzLlxuICAgKiBAcGFyYW0gaW5wdXRzIHRlbnNvciwgdGVuc29yIGFycmF5IG9yIHRlbnNvciBtYXAgb2YgdGhlIGlucHV0cyBmb3IgdGhlXG4gICAqIG1vZGVsLCBrZXllZCBieSB0aGUgaW5wdXQgbm9kZSBuYW1lcy5cbiAgICogQHBhcmFtIG91dHB1dHMgb3V0cHV0IG5vZGUgbmFtZSBmcm9tIHRoZSBUZW5zb3JmbG93IG1vZGVsLCBpZiBubyBvdXRwdXRzXG4gICAqIGFyZSBzcGVjaWZpZWQsIHRoZSBkZWZhdWx0IG91dHB1dHMgb2YgdGhlIG1vZGVsIHdvdWxkIGJlIHVzZWQuIFlvdSBjYW5cbiAgICogaW5zcGVjdCBpbnRlcm1lZGlhdGUgbm9kZXMgb2YgdGhlIG1vZGVsIGJ5IGFkZGluZyB0aGVtIHRvIHRoZSBvdXRwdXRzXG4gICAqIGFycmF5LlxuICAgKlxuICAgKiBAcmV0dXJucyBBIFByb21pc2Ugb2Ygc2luZ2xlIHRlbnNvciBpZiBwcm92aWRlZCB3aXRoIGEgc2luZ2xlIG91dHB1dCBvclxuICAgKiBubyBvdXRwdXRzIGFyZSBwcm92aWRlZCBhbmQgdGhlcmUgaXMgb25seSBvbmUgZGVmYXVsdCBvdXRwdXQsIG90aGVyd2lzZVxuICAgKiByZXR1cm4gYSB0ZW5zb3IgbWFwLlxuICAgKlxuICAgKiBAZG9jIHtoZWFkaW5nOiAnTW9kZWxzJywgc3ViaGVhZGluZzogJ0NsYXNzZXMnfVxuICAgKi9cbiAgYXN5bmMgZXhlY3V0ZUFzeW5jKFxuICAgICAgaW5wdXRzOiBUZW5zb3J8VGVuc29yW118TmFtZWRUZW5zb3JNYXAsXG4gICAgICBvdXRwdXRzPzogc3RyaW5nfHN0cmluZ1tdKTogUHJvbWlzZTxUZW5zb3J8VGVuc29yW10+IHtcbiAgICBpbnB1dHMgPSB0aGlzLm5vcm1hbGl6ZUlucHV0cyhpbnB1dHMpO1xuICAgIG91dHB1dHMgPSB0aGlzLm5vcm1hbGl6ZU91dHB1dHMob3V0cHV0cyk7XG4gICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdGhpcy5leGVjdXRvci5leGVjdXRlQXN5bmMoaW5wdXRzLCBvdXRwdXRzKTtcbiAgICByZXR1cm4gcmVzdWx0Lmxlbmd0aCA+IDEgPyByZXN1bHQgOiByZXN1bHRbMF07XG4gIH1cblxuICAvKipcbiAgICogR2V0IGludGVybWVkaWF0ZSB0ZW5zb3JzIGZvciBtb2RlbCBkZWJ1Z2dpbmcgbW9kZSAoZmxhZ1xuICAgKiBLRUVQX0lOVEVSTUVESUFURV9URU5TT1JTIGlzIHRydWUpLlxuICAgKlxuICAgKiBAZG9jIHtoZWFkaW5nOiAnTW9kZWxzJywgc3ViaGVhZGluZzogJ0NsYXNzZXMnfVxuICAgKi9cbiAgZ2V0SW50ZXJtZWRpYXRlVGVuc29ycygpOiBOYW1lZFRlbnNvcnNNYXAge1xuICAgIHJldHVybiB0aGlzLmV4ZWN1dG9yLmdldEludGVybWVkaWF0ZVRlbnNvcnMoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBEaXNwb3NlIGludGVybWVkaWF0ZSB0ZW5zb3JzIGZvciBtb2RlbCBkZWJ1Z2dpbmcgbW9kZSAoZmxhZ1xuICAgKiBLRUVQX0lOVEVSTUVESUFURV9URU5TT1JTIGlzIHRydWUpLlxuICAgKlxuICAgKiBAZG9jIHtoZWFkaW5nOiAnTW9kZWxzJywgc3ViaGVhZGluZzogJ0NsYXNzZXMnfVxuICAgKi9cbiAgZGlzcG9zZUludGVybWVkaWF0ZVRlbnNvcnMoKSB7XG4gICAgdGhpcy5leGVjdXRvci5kaXNwb3NlSW50ZXJtZWRpYXRlVGVuc29ycygpO1xuICB9XG5cbiAgcHJpdmF0ZSBjb252ZXJ0VGVuc29yTWFwVG9UZW5zb3JzTWFwKG1hcDogTmFtZWRUZW5zb3JNYXApOiBOYW1lZFRlbnNvcnNNYXAge1xuICAgIHJldHVybiBPYmplY3Qua2V5cyhtYXApLnJlZHVjZSgobmV3TWFwOiBOYW1lZFRlbnNvcnNNYXAsIGtleSkgPT4ge1xuICAgICAgbmV3TWFwW2tleV0gPSBbbWFwW2tleV1dO1xuICAgICAgcmV0dXJuIG5ld01hcDtcbiAgICB9LCB7fSk7XG4gIH1cblxuICAvKipcbiAgICogUmVsZWFzZXMgdGhlIG1lbW9yeSB1c2VkIGJ5IHRoZSB3ZWlnaHQgdGVuc29ycyBhbmQgcmVzb3VyY2VNYW5hZ2VyLlxuICAgKlxuICAgKiBAZG9jIHtoZWFkaW5nOiAnTW9kZWxzJywgc3ViaGVhZGluZzogJ0NsYXNzZXMnfVxuICAgKi9cbiAgZGlzcG9zZSgpIHtcbiAgICB0aGlzLmV4ZWN1dG9yLmRpc3Bvc2UoKTtcblxuICAgIGlmICh0aGlzLmluaXRpYWxpemVyKSB7XG4gICAgICB0aGlzLmluaXRpYWxpemVyLmRpc3Bvc2UoKTtcbiAgICB9XG5cbiAgICB0aGlzLnJlc291cmNlTWFuYWdlci5kaXNwb3NlKCk7XG4gIH1cbn1cblxuLyoqXG4gKiBMb2FkIGEgZ3JhcGggbW9kZWwgZ2l2ZW4gYSBVUkwgdG8gdGhlIG1vZGVsIGRlZmluaXRpb24uXG4gKlxuICogRXhhbXBsZSBvZiBsb2FkaW5nIE1vYmlsZU5ldFYyIGZyb20gYSBVUkwgYW5kIG1ha2luZyBhIHByZWRpY3Rpb24gd2l0aCBhXG4gKiB6ZXJvcyBpbnB1dDpcbiAqXG4gKiBgYGBqc1xuICogY29uc3QgbW9kZWxVcmwgPVxuICogICAgJ2h0dHBzOi8vc3RvcmFnZS5nb29nbGVhcGlzLmNvbS90ZmpzLW1vZGVscy9zYXZlZG1vZGVsL21vYmlsZW5ldF92Ml8xLjBfMjI0L21vZGVsLmpzb24nO1xuICogY29uc3QgbW9kZWwgPSBhd2FpdCB0Zi5sb2FkR3JhcGhNb2RlbChtb2RlbFVybCk7XG4gKiBjb25zdCB6ZXJvcyA9IHRmLnplcm9zKFsxLCAyMjQsIDIyNCwgM10pO1xuICogbW9kZWwucHJlZGljdCh6ZXJvcykucHJpbnQoKTtcbiAqIGBgYFxuICpcbiAqIEV4YW1wbGUgb2YgbG9hZGluZyBNb2JpbGVOZXRWMiBmcm9tIGEgVEYgSHViIFVSTCBhbmQgbWFraW5nIGEgcHJlZGljdGlvbiB3aXRoXG4gKiBhIHplcm9zIGlucHV0OlxuICpcbiAqIGBgYGpzXG4gKiBjb25zdCBtb2RlbFVybCA9XG4gKiAgICAnaHR0cHM6Ly90Zmh1Yi5kZXYvZ29vZ2xlL2ltYWdlbmV0L21vYmlsZW5ldF92Ml8xNDBfMjI0L2NsYXNzaWZpY2F0aW9uLzInO1xuICogY29uc3QgbW9kZWwgPSBhd2FpdCB0Zi5sb2FkR3JhcGhNb2RlbChtb2RlbFVybCwge2Zyb21URkh1YjogdHJ1ZX0pO1xuICogY29uc3QgemVyb3MgPSB0Zi56ZXJvcyhbMSwgMjI0LCAyMjQsIDNdKTtcbiAqIG1vZGVsLnByZWRpY3QoemVyb3MpLnByaW50KCk7XG4gKiBgYGBcbiAqIEBwYXJhbSBtb2RlbFVybCBUaGUgdXJsIG9yIGFuIGBpby5JT0hhbmRsZXJgIHRoYXQgbG9hZHMgdGhlIG1vZGVsLlxuICogQHBhcmFtIG9wdGlvbnMgT3B0aW9ucyBmb3IgdGhlIEhUVFAgcmVxdWVzdCwgd2hpY2ggYWxsb3dzIHRvIHNlbmQgY3JlZGVudGlhbHNcbiAqICAgIGFuZCBjdXN0b20gaGVhZGVycy5cbiAqXG4gKiBAZG9jIHtoZWFkaW5nOiAnTW9kZWxzJywgc3ViaGVhZGluZzogJ0xvYWRpbmcnfVxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gbG9hZEdyYXBoTW9kZWwoXG4gICAgbW9kZWxVcmw6IHN0cmluZ3xpby5JT0hhbmRsZXIsXG4gICAgb3B0aW9uczogaW8uTG9hZE9wdGlvbnMgPSB7fSk6IFByb21pc2U8R3JhcGhNb2RlbD4ge1xuICBpZiAobW9kZWxVcmwgPT0gbnVsbCkge1xuICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgJ21vZGVsVXJsIGluIGxvYWRHcmFwaE1vZGVsKCkgY2Fubm90IGJlIG51bGwuIFBsZWFzZSBwcm92aWRlIGEgdXJsICcgK1xuICAgICAgICAnb3IgYW4gSU9IYW5kbGVyIHRoYXQgbG9hZHMgdGhlIG1vZGVsJyk7XG4gIH1cbiAgaWYgKG9wdGlvbnMgPT0gbnVsbCkge1xuICAgIG9wdGlvbnMgPSB7fTtcbiAgfVxuXG4gIGlmIChvcHRpb25zLmZyb21URkh1Yikge1xuICAgIGlmICgobW9kZWxVcmwgYXMgaW8uSU9IYW5kbGVyKS5sb2FkID09IG51bGwpIHtcbiAgICAgIGlmICghKG1vZGVsVXJsIGFzIHN0cmluZykuZW5kc1dpdGgoJy8nKSkge1xuICAgICAgICBtb2RlbFVybCA9IChtb2RlbFVybCBhcyBzdHJpbmcpICsgJy8nO1xuICAgICAgfVxuICAgICAgbW9kZWxVcmwgPSBgJHttb2RlbFVybH0ke0RFRkFVTFRfTU9ERUxfTkFNRX0ke1RGSFVCX1NFQVJDSF9QQVJBTX1gO1xuICAgIH1cbiAgfVxuICBjb25zdCBtb2RlbCA9IG5ldyBHcmFwaE1vZGVsKG1vZGVsVXJsLCBvcHRpb25zKTtcbiAgYXdhaXQgbW9kZWwubG9hZCgpO1xuICByZXR1cm4gbW9kZWw7XG59XG4iXX0=