@tensorflow/tfjs-converter
Version:
Tensorflow model converter for javascript
584 lines • 75.6 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 { dispose, io, Tensor, util } 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 {
// 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;
}
get modelStructuredOutputKeys() {
return this.structuredOutputKeys;
}
/**
* @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 = {}, tfio = io) {
this.modelUrl = modelUrl;
this.loadOptions = loadOptions;
this.version = 'n/a';
this.io = tfio;
if (loadOptions == null) {
this.loadOptions = {};
}
this.resourceManager = new ResourceManager();
}
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 = this.io.browserHTTPRequest(path, this.loadOptions);
}
else {
const handlers = this.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(this.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.
*/
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 loadResult = this.handler.load();
if (util.isPromise(loadResult)) {
return loadResult.then(artifacts => this.loadSync(artifacts));
}
return this.loadSync(loadResult);
}
/**
* Synchronously construct the in memory weight map and
* compile the inference graph.
*
* @doc {heading: 'Models', subheading: 'Classes', ignoreCI: true}
*/
loadSync(artifacts) {
this.artifacts = artifacts;
const graph = this.artifacts.modelTopology;
let signature = this.artifacts.signature;
if (this.artifacts.userDefinedMetadata != null) {
const metadata = this.artifacts.userDefinedMetadata;
if (metadata.signature != null) {
signature = metadata.signature;
}
if (metadata.structuredOutputKeys != null) {
this.structuredOutputKeys = metadata.structuredOutputKeys;
}
}
this.signature = signature;
this.version = `${graph.versions.producer}.${graph.versions.minConsumer}`;
const weightMap = this.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.initializerSignature = artifacts.initializerSignature;
}
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 = this.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);
}
addStructuredOutputNames(outputTensors) {
if (this.structuredOutputKeys) {
const outputTensorsArray = outputTensors instanceof Tensor ? [outputTensors] : outputTensors;
const outputTensorMap = {};
outputTensorsArray.forEach((outputTensor, i) => outputTensorMap[this.structuredOutputKeys[i]] =
outputTensor);
return outputTensorMap;
}
return outputTensors;
}
/**
* 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.
* Currently the batch size option is ignored for graph model.
*
* @returns Inference result tensors. If the model is converted and it
* originally had structured_outputs in tensorflow, then a NamedTensorMap
* will be returned matching the structured_outputs. If no structured_outputs
* are present, the output will be single `tf.Tensor` if the model has single
* output node, otherwise Tensor[].
*
* @doc {heading: 'Models', subheading: 'Classes'}
*/
predict(inputs, config) {
const outputTensors = this.execute(inputs, this.outputNodes);
return this.addStructuredOutputNames(outputTensors);
}
/**
* Execute the inference for the input tensors in async fashion, use this
* method when your model contains control flow ops.
*
* @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.
* Currently the batch size option is ignored for graph model.
*
* @returns A Promise of inference result tensors. If the model is converted
* and it originally had structured_outputs in tensorflow, then a
* NamedTensorMap will be returned matching the structured_outputs. If no
* structured_outputs are present, the output will be single `tf.Tensor` if
* the model has single output node, otherwise Tensor[].
*
* @doc {heading: 'Models', subheading: 'Classes'}
*/
async predictAsync(inputs, config) {
const outputTensors = await this.executeAsync(inputs, this.outputNodes);
return this.addStructuredOutputNames(outputTensors);
}
normalizeInputs(inputs) {
var _a;
if (!(inputs instanceof Tensor) && !Array.isArray(inputs)) {
// The input is already a NamedTensorMap.
const signatureInputs = (_a = this.signature) === null || _a === void 0 ? void 0 : _a.inputs;
if (signatureInputs != null) {
for (const input in signatureInputs) {
const tensor = signatureInputs[input];
if (tensor.resourceId != null) {
inputs[input] = this.resourceIdToCapturedInput[tensor.resourceId];
}
}
}
return inputs;
}
inputs = Array.isArray(inputs) ? inputs : [inputs];
const numCapturedInputs = Object.keys(this.resourceIdToCapturedInput).length;
if (inputs.length + numCapturedInputs !== this.inputNodes.length) {
throw new Error(`Input tensor count mismatch, the graph model has ${this.inputNodes.length -
numCapturedInputs} non-resource placeholders, while there are ${inputs.length} input tensors provided.`);
}
let inputIndex = 0;
return this.inputNodes.reduce((map, inputName) => {
var _a, _b, _c;
const resourceId = (_c = (_b = (_a = this.signature) === null || _a === void 0 ? void 0 : _a.inputs) === null || _b === void 0 ? void 0 : _b[inputName]) === null || _c === void 0 ? void 0 : _c.resourceId;
if (resourceId != null) {
map[inputName] = this.resourceIdToCapturedInput[resourceId];
}
else {
map[inputName] = inputs[inputIndex++];
}
return map;
}, {});
}
normalizeOutputs(outputs) {
outputs = outputs || this.outputNodes;
return !Array.isArray(outputs) ? [outputs] : outputs;
}
executeInitializerGraph() {
if (this.initializer == null) {
return [];
}
if (this.initializerSignature == null) {
return this.initializer.execute({}, []);
}
else {
return this.initializer.execute({}, Object.keys(this.initializerSignature.outputs));
}
}
async executeInitializerGraphAsync() {
if (this.initializer == null) {
return [];
}
if (this.initializerSignature == null) {
return this.initializer.executeAsync({}, []);
}
else {
return this.initializer.executeAsync({}, Object.keys(this.initializerSignature.outputs));
}
}
setResourceIdToCapturedInput(outputs) {
this.resourceIdToCapturedInput = {};
if (this.initializerSignature) {
const signatureOutputs = this.initializerSignature.outputs;
const outputNames = Object.keys(signatureOutputs);
for (let i = 0; i < outputNames.length; i++) {
const outputName = outputNames[i];
const tensorInfo = signatureOutputs[outputName];
this.resourceIdToCapturedInput[tensorInfo.resourceId] = outputs[i];
}
}
}
/**
* 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) {
if (this.resourceIdToCapturedInput == null) {
this.setResourceIdToCapturedInput(this.executeInitializerGraph());
}
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) {
if (this.resourceIdToCapturedInput == null) {
this.setResourceIdToCapturedInput(await this.executeInitializerGraphAsync());
}
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();
if (this.resourceIdToCapturedInput) {
dispose(this.resourceIdToCapturedInput);
}
}
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 = {}, tfio = io) {
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 && typeof modelUrl === 'string') {
modelUrl = getTFHubUrl(modelUrl);
}
const model = new GraphModel(modelUrl, options, tfio);
await model.load();
return model;
}
/**
* Load a graph model given a synchronous IO handler with a 'load' method.
*
* @param modelSource The `io.IOHandlerSync` that loads the model, or the
* `io.ModelArtifacts` that encode the model, or a tuple of
* `[io.ModelJSON, ArrayBuffer]` of which the first element encodes the
* model and the second contains the weights.
*
* @doc {heading: 'Models', subheading: 'Loading'}
*/
export function loadGraphModelSync(modelSource) {
if (modelSource == null) {
throw new Error('modelUrl in loadGraphModelSync() cannot be null. Please provide ' +
'model artifacts or an IOHandler that loads the model');
}
let ioHandler;
if (modelSource instanceof Array) {
const [modelJSON, weights] = modelSource;
if (!modelJSON) {
throw new Error('modelJSON must be the first element of the array');
}
if (!weights || !(weights instanceof ArrayBuffer)) {
throw new Error('An ArrayBuffer of weights must be the second element of' +
' the array');
}
if (!('modelTopology' in modelJSON)) {
throw new Error('Model JSON is missing \'modelTopology\'');
}
if (!('weightsManifest' in modelJSON)) {
throw new Error('Model JSON is missing \'weightsManifest\'');
}
const weightSpecs = io.getWeightSpecs(modelJSON.weightsManifest);
const modelArtifacts = io.getModelArtifactsForJSONSync(modelJSON, weightSpecs, weights);
ioHandler = io.fromMemorySync(modelArtifacts);
}
else if ('load' in modelSource) {
// Then modelSource is already an IOHandlerSync.
ioHandler = modelSource;
}
else if ('modelTopology' in modelSource && 'weightSpecs' in modelSource &&
'weightData' in modelSource) {
// modelSource is of type ModelArtifacts.
ioHandler = io.fromMemorySync(modelSource);
}
else {
throw new Error('Unknown model format');
}
const model = new GraphModel(ioHandler);
model.load();
return model;
}
function getTFHubUrl(modelUrl) {
if (!modelUrl.endsWith('/')) {
modelUrl = (modelUrl) + '/';
}
return `${modelUrl}${DEFAULT_MODEL_NAME}${TFHUB_SEARCH_PARAM}`;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ3JhcGhfbW9kZWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi90ZmpzLWNvbnZlcnRlci9zcmMvZXhlY3V0b3IvZ3JhcGhfbW9kZWwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7OztHQWVHO0FBRUgsT0FBTyxFQUFDLE9BQU8sRUFBa0IsRUFBRSxFQUFzQyxNQUFNLEVBQUUsSUFBSSxFQUFDLE1BQU0sdUJBQXVCLENBQUM7QUFJcEgsT0FBTyxFQUFDLGVBQWUsRUFBQyxNQUFNLGdDQUFnQyxDQUFDO0FBRS9ELE9BQU8sRUFBQyxhQUFhLEVBQUMsTUFBTSxrQkFBa0IsQ0FBQztBQUMvQyxPQUFPLEVBQUMsZUFBZSxFQUFDLE1BQU0sb0JBQW9CLENBQUM7QUFFbkQsTUFBTSxDQUFDLE1BQU0sa0JBQWtCLEdBQUcsbUJBQW1CLENBQUM7QUFDdEQsTUFBTSxDQUFDLE1BQU0sa0JBQWtCLEdBQUcsWUFBWSxDQUFDO0FBSS9DOzs7Ozs7Ozs7R0FTRztBQUNILE1BQU0sT0FBTyxVQUFVO0lBY3JCLHFFQUFxRTtJQUNyRSxJQUFJLFlBQVk7UUFDZCxPQUFPLElBQUksQ0FBQyxPQUFPLENBQUM7SUFDdEIsQ0FBQztJQUVELElBQUksVUFBVTtRQUNaLE9BQU8sSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUM7SUFDbEMsQ0FBQztJQUVELElBQUksV0FBVztRQUNiLE9BQU8sSUFBSSxDQUFDLFFBQVEsQ0FBQyxXQUFXLENBQUM7SUFDbkMsQ0FBQztJQUVELElBQUksTUFBTTtRQUNSLE9BQU8sSUFBSSxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUM7SUFDOUIsQ0FBQztJQUVELElBQUksT0FBTztRQUNULE9BQU8sSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUM7SUFDL0IsQ0FBQztJQUVELElBQUksT0FBTztRQUNULE9BQU8sSUFBSSxDQUFDLFFBQVEsQ0FBQyxTQUFTLENBQUM7SUFDakMs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TEMuIEFsbCBSaWdodHMgUmVzZXJ2ZWQuXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgXCJMaWNlbnNlXCIpO1xuICogeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLlxuICogWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0XG4gKlxuICogaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wXG4gKlxuICogVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZVxuICogZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gXCJBUyBJU1wiIEJBU0lTLFxuICogV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuXG4gKiBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kXG4gKiBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS5cbiAqID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4gKi9cblxuaW1wb3J0IHtkaXNwb3NlLCBJbmZlcmVuY2VNb2RlbCwgaW8sIE1vZGVsUHJlZGljdENvbmZpZywgTmFtZWRUZW5zb3JNYXAsIFRlbnNvciwgdXRpbH0gZnJvbSAnQHRlbnNvcmZsb3cvdGZqcy1jb3JlJztcblxuaW1wb3J0ICogYXMgdGVuc29yZmxvdyBmcm9tICcuLi9kYXRhL2NvbXBpbGVkX2FwaSc7XG5pbXBvcnQge05hbWVkVGVuc29yc01hcCwgVGVuc29ySW5mb30gZnJvbSAnLi4vZGF0YS90eXBlcyc7XG5pbXBvcnQge09wZXJhdGlvbk1hcHBlcn0gZnJvbSAnLi4vb3BlcmF0aW9ucy9vcGVyYXRpb25fbWFwcGVyJztcblxuaW1wb3J0IHtHcmFwaEV4ZWN1dG9yfSBmcm9tICcuL2dyYXBoX2V4ZWN1dG9yJztcbmltcG9ydCB7UmVzb3VyY2VNYW5hZ2VyfSBmcm9tICcuL3Jlc291cmNlX21hbmFnZXInO1xuXG5leHBvcnQgY29uc3QgVEZIVUJfU0VBUkNIX1BBUkFNID0gJz90ZmpzLWZvcm1hdD1maWxlJztcbmV4cG9ydCBjb25zdCBERUZBVUxUX01PREVMX05BTUUgPSAnbW9kZWwuanNvbic7XG50eXBlIFVybCA9IHN0cmluZ3xpby5JT0hhbmRsZXJ8aW8uSU9IYW5kbGVyU3luYztcbnR5cGUgVXJsSU9IYW5kbGVyPFQgZXh0ZW5kcyBVcmw+ID0gVCBleHRlbmRzIHN0cmluZyA/IGlvLklPSGFuZGxlciA6IFQ7XG5cbi8qKlxuICogQSBgdGYuR3JhcGhNb2RlbGAgaXMgYSBkaXJlY3RlZCwgYWN5Y2xpYyBncmFwaCBidWlsdCBmcm9tIGFcbiAqIFNhdmVkTW9kZWwgR3JhcGhEZWYgYW5kIGFsbG93cyBpbmZlcmVuY2UgZXhlY3V0aW9uLlxuICpcbiAqIEEgYHRmLkdyYXBoTW9kZWxgIGNhbiBvbmx5IGJlIGNyZWF0ZWQgYnkgbG9hZGluZyBmcm9tIGEgbW9kZWwgY29udmVydGVkIGZyb21cbiAqIGEgW1RlbnNvckZsb3cgU2F2ZWRNb2RlbF0oaHR0cHM6Ly93d3cudGVuc29yZmxvdy5vcmcvZ3VpZGUvc2F2ZWRfbW9kZWwpIHVzaW5nXG4gKiB0aGUgY29tbWFuZCBsaW5lIGNvbnZlcnRlciB0b29sIGFuZCBsb2FkZWQgdmlhIGB0Zi5sb2FkR3JhcGhNb2RlbGAuXG4gKlxuICogQGRvYyB7aGVhZGluZzogJ01vZGVscycsIHN1YmhlYWRpbmc6ICdDbGFzc2VzJ31cbiAqL1xuZXhwb3J0IGNsYXNzIEdyYXBoTW9kZWw8TW9kZWxVUkwgZXh0ZW5kcyBVcmwgPSBzdHJpbmcgfCBpby5JT0hhbmRsZXI+IGltcGxlbWVudHNcbiAgICBJbmZlcmVuY2VNb2RlbCB7XG4gIHByaXZhdGUgZXhlY3V0b3I6IEdyYXBoRXhlY3V0b3I7XG4gIHByaXZhdGUgdmVyc2lvbiA9ICduL2EnO1xuICBwcml2YXRlIGhhbmRsZXI6IFVybElPSGFuZGxlcjxNb2RlbFVSTD47XG4gIHByaXZhdGUgYXJ0aWZhY3RzOiBpby5Nb2RlbEFydGlmYWN0cztcbiAgcHJpdmF0ZSBpbml0aWFsaXplcjogR3JhcGhFeGVjdXRvcjtcbiAgcHJpdmF0ZSByZXNvdXJjZUlkVG9DYXB0dXJlZElucHV0OiB7W2tleTogbnVtYmVyXTogVGVuc29yfTtcbiAgcHJpdmF0ZSByZXNvdXJjZU1hbmFnZXI6IFJlc291cmNlTWFuYWdlcjtcbiAgcHJpdmF0ZSBzaWduYXR1cmU6IHRlbnNvcmZsb3cuSVNpZ25hdHVyZURlZjtcbiAgcHJpdmF0ZSBpbml0aWFsaXplclNpZ25hdHVyZTogdGVuc29yZmxvdy5JU2lnbmF0dXJlRGVmO1xuICBwcml2YXRlIHN0cnVjdHVyZWRPdXRwdXRLZXlzOiBzdHJpbmdbXTtcbiAgcHJpdmF0ZSByZWFkb25seSBpbzogdHlwZW9mIGlvO1xuXG4gIC8vIFJldHVybnMgdGhlIHZlcnNpb24gaW5mb3JtYXRpb24gZm9yIHRoZSB0ZW5zb3JmbG93IG1vZGVsIEdyYXBoRGVmLlxuICBnZXQgbW9kZWxWZXJzaW9uKCk6IHN0cmluZyB7XG4gICAgcmV0dXJuIHRoaXMudmVyc2lvbjtcbiAgfVxuXG4gIGdldCBpbnB1dE5vZGVzKCk6IHN0cmluZ1tdIHtcbiAgICByZXR1cm4gdGhpcy5leGVjdXRvci5pbnB1dE5vZGVzO1xuICB9XG5cbiAgZ2V0IG91dHB1dE5vZGVzKCk6IHN0cmluZ1tdIHtcbiAgICByZXR1cm4gdGhpcy5leGVjdXRvci5vdXRwdXROb2RlcztcbiAgfVxuXG4gIGdldCBpbnB1dHMoKTogVGVuc29ySW5mb1tdIHtcbiAgICByZXR1cm4gdGhpcy5leGVjdXRvci5pbnB1dHM7XG4gIH1cblxuICBnZXQgb3V0cHV0cygpOiBUZW5zb3JJbmZvW10ge1xuICAgIHJldHVybiB0aGlzLmV4ZWN1dG9yLm91dHB1dHM7XG4gIH1cblxuICBnZXQgd2VpZ2h0cygpOiBOYW1lZFRlbnNvcnNNYXAge1xuICAgIHJldHVybiB0aGlzLmV4ZWN1dG9yLndlaWdodE1hcDtcbiAgfVxuXG4gIGdldCBtZXRhZGF0YSgpOiB7fSB7XG4gICAgcmV0dXJuIHRoaXMuYXJ0aWZhY3RzLnVzZXJEZWZpbmVkTWV0YWRhdGE7XG4gIH1cblxuICBnZXQgbW9kZWxTaWduYXR1cmUoKToge30ge1xuICAgIHJldHVybiB0aGlzLnNpZ25hdHVyZTtcbiAgfVxuXG4gIGdldCBtb2RlbFN0cnVjdHVyZWRPdXRwdXRLZXlzKCk6IHt9IHtcbiAgICByZXR1cm4gdGhpcy5zdHJ1Y3R1cmVkT3V0cHV0S2V5cztcbiAgfVxuXG4gIC8qKlxuICAgKiBAcGFyYW0gbW9kZWxVcmwgdXJsIGZvciB0aGUgbW9kZWwsIG9yIGFuIGBpby5JT0hhbmRsZXJgLlxuICAgKiBAcGFyYW0gd2VpZ2h0TWFuaWZlc3RVcmwgdXJsIGZvciB0aGUgd2VpZ2h0IGZpbGUgZ2VuZXJhdGVkIGJ5XG4gICAqIHNjcmlwdHMvY29udmVydC5weSBzY3JpcHQuXG4gICAqIEBwYXJhbSByZXF1ZXN0T3B0aW9uIG9wdGlvbnMgZm9yIFJlcXVlc3QsIHdoaWNoIGFsbG93cyB0byBzZW5kIGNyZWRlbnRpYWxzXG4gICAqIGFuZCBjdXN0b20gaGVhZGVycy5cbiAgICogQHBhcmFtIG9uUHJvZ3Jlc3MgT3B0aW9uYWwsIHByb2dyZXNzIGNhbGxiYWNrIGZ1bmN0aW9uLCBmaXJlZCBwZXJpb2RpY2FsbHlcbiAgICogYmVmb3JlIHRoZSBsb2FkIGlzIGNvbXBsZXRlZC5cbiAgICovXG4gIGNvbnN0cnVjdG9yKFxuICAgICAgcHJpdmF0ZSBtb2RlbFVybDogTW9kZWxVUkwsIHByaXZhdGUgbG9hZE9wdGlvbnM6IGlvLkxvYWRPcHRpb25zID0ge30sXG4gICAgICB0ZmlvID0gaW8pIHtcbiAgICB0aGlzLmlvID0gdGZpbztcbiAgICBpZiAobG9hZE9wdGlvbnMgPT0gbnVsbCkge1xuICAgICAgdGhpcy5sb2FkT3B0aW9ucyA9IHt9O1xuICAgIH1cbiAgICB0aGlzLnJlc291cmNlTWFuYWdlciA9IG5ldyBSZXNvdXJjZU1hbmFnZXIoKTtcbiAgfVxuXG4gIHByaXZhdGUgZmluZElPSGFuZGxlcigpIHtcbiAgICB0eXBlIElPSGFuZGxlciA9IFVybElPSGFuZGxlcjxNb2RlbFVSTD47XG4gICAgY29uc3QgcGF0aCA9IHRoaXMubW9kZWxVcmw7XG4gICAgaWYgKChwYXRoIGFzIGlvLklPSGFuZGxlcikubG9hZCAhPSBudWxsKSB7XG4gICAgICAvLyBQYXRoIGlzIGFuIElPIEhhbmRsZXIuXG4gICAgICB0aGlzLmhhbmRsZXIgPSBwYXRoIGFzIElPSGFuZGxlcjtcbiAgICB9IGVsc2UgaWYgKHRoaXMubG9hZE9wdGlvbnMucmVxdWVzdEluaXQgIT0gbnVsbCkge1xuICAgICAgdGhpcy5oYW5kbGVyID0gdGhpcy5pby5icm93c2VySFRUUFJlcXVlc3QoXG4gICAgICAgICAgICAgICAgICAgICAgICAgcGF0aCBhcyBzdHJpbmcsIHRoaXMubG9hZE9wdGlvbnMpIGFzIElPSGFuZGxlcjtcbiAgICB9IGVsc2Uge1xuICAgICAgY29uc3QgaGFuZGxlcnMgPVxuICAgICAgICAgIHRoaXMuaW8uZ2V0TG9hZEhhbmRsZXJzKHBhdGggYXMgc3RyaW5nLCB0aGlzLmxvYWRPcHRpb25zKTtcbiAgICAgIGlmIChoYW5kbGVycy5sZW5ndGggPT09IDApIHtcbiAgICAgICAgLy8gRm9yIGJhY2t3YXJkIGNvbXBhdGliaWxpdHk6IGlmIG5vIGxvYWQgaGFuZGxlciBjYW4gYmUgZm91bmQsXG4gICAgICAgIC8vIGFzc3VtZSBpdCBpcyBhIHJlbGF0aXZlIGh0dHAgcGF0aC5cbiAgICAgICAgaGFuZGxlcnMucHVzaChcbiAgICAgICAgICAgIHRoaXMuaW8uYnJvd3NlckhUVFBSZXF1ZXN0KHBhdGggYXMgc3RyaW5nLCB0aGlzLmxvYWRPcHRpb25zKSk7XG4gICAgICB9IGVsc2UgaWYgKGhhbmRsZXJzLmxlbmd0aCA+IDEpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgYEZvdW5kIG1vcmUgdGhhbiBvbmUgKCR7aGFuZGxlcnMubGVuZ3RofSkgbG9hZCBoYW5kbGVycyBmb3IgYCArXG4gICAgICAgICAgICBgVVJMICcke1twYXRoXX0nYCk7XG4gICAgICB9XG4gICAgICB0aGlzLmhhbmRsZXIgPSBoYW5kbGVyc1swXSBhcyBJT0hhbmRsZXI7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIExvYWRzIHRoZSBtb2RlbCBhbmQgd2VpZ2h0IGZpbGVzLCBjb25zdHJ1Y3QgdGhlIGluIG1lbW9yeSB3ZWlnaHQgbWFwIGFuZFxuICAgKiBjb21waWxlIHRoZSBpbmZlcmVuY2UgZ3JhcGguXG4gICAqL1xuICBsb2FkKCk6IFVybElPSGFuZGxlcjxNb2RlbFVSTD4gZXh0ZW5kcyBpby5JT0hhbmRsZXJTeW5jPyBib29sZWFuOlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUHJvbWlzZTxib29sZWFuPiB7XG4gICAgdHlwZSBJT0hhbmRsZXIgPSBVcmxJT0hhbmRsZXI8TW9kZWxVUkw+O1xuICAgIHRoaXMuZmluZElPSGFuZGxlcigpO1xuICAgIGlmICh0aGlzLmhhbmRsZXIubG9hZCA9PSBudWxsKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgJ0Nhbm5vdCBwcm9jZWVkIHdpdGggbW9kZWwgbG9hZGluZyBiZWNhdXNlIHRoZSBJT0hhbmRsZXIgcHJvdmlkZWQgJyArXG4gICAgICAgICAgJ2RvZXMgbm90IGhhdmUgdGhlIGBsb2FkYCBtZXRob2QgaW1wbGVtZW50ZWQuJyk7XG4gICAgfVxuXG4gICAgdHlwZSBSZXN1bHQgPVxuICAgICAgICBJT0hhbmRsZXIgZXh0ZW5kcyBpby5JT0hhbmRsZXJTeW5jID8gYm9vbGVhbiA6IFByb21pc2U8Ym9vbGVhbj47XG5cbiAgICBjb25zdCBsb2FkUmVzdWx0ID0gdGhpcy5oYW5kbGVyLmxvYWQoKSBhcyBSZXR1cm5UeXBlPElPSGFuZGxlclsnbG9hZCddPjtcbiAgICBpZiAodXRpbC5pc1Byb21pc2UobG9hZFJlc3VsdCkpIHtcbiAgICAgIHJldHVybiBsb2FkUmVzdWx0LnRoZW4oYXJ0aWZhY3RzID0+IHRoaXMubG9hZFN5bmMoYXJ0aWZhY3RzKSkgYXMgUmVzdWx0O1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzLmxvYWRTeW5jKGxvYWRSZXN1bHQpIGFzIFJlc3VsdDtcbiAgfVxuXG4gIC8qKlxuICAgKiBTeW5jaHJvbm91c2x5IGNvbnN0cnVjdCB0aGUgaW4gbWVtb3J5IHdlaWdodCBtYXAgYW5kXG4gICAqIGNvbXBpbGUgdGhlIGluZmVyZW5jZSBncmFwaC5cbiAgICpcbiAgICogQGRvYyB7aGVhZGluZzogJ01vZGVscycsIHN1YmhlYWRpbmc6ICdDbGFzc2VzJywgaWdub3JlQ0k6IHRydWV9XG4gICAqL1xuICBsb2FkU3luYyhhcnRpZmFjdHM6IGlvLk1vZGVsQXJ0aWZhY3RzKSB7XG4gICAgdGhpcy5hcnRpZmFjdHMgPSBhcnRpZmFjdHM7XG4gICAgY29uc3QgZ3JhcGggPSB0aGlzLmFydGlmYWN0cy5tb2RlbFRvcG9sb2d5IGFzIHRlbnNvcmZsb3cuSUdyYXBoRGVmO1xuXG4gICAgbGV0IHNpZ25hdHVyZSA9IHRoaXMuYXJ0aWZhY3RzLnNpZ25hdHVyZTtcbiAgICBpZiAodGhpcy5hcnRpZmFjdHMudXNlckRlZmluZWRNZXRhZGF0YSAhPSBudWxsKSB7XG4gICAgICBjb25zdCBtZXRhZGF0YSA9IHRoaXMuYXJ0aWZhY3RzLnVzZXJEZWZpbmVkTWV0YWRhdGE7XG4gICAgICBpZiAobWV0YWRhdGEuc2lnbmF0dXJlICE9IG51bGwpIHtcbiAgICAgICAgc2lnbmF0dXJlID0gbWV0YWRhdGEuc2lnbmF0dXJlO1xuICAgICAgfVxuXG4gICAgICBpZiAobWV0YWRhdGEuc3RydWN0dXJlZE91dHB1dEtleXMgIT0gbnVsbCkge1xuICAgICAgICB0aGlzLnN0cnVjdHVyZWRPdXRwdXRLZXlzID0gbWV0YWRhdGEuc3RydWN0dXJlZE91dHB1dEtleXMgYXMgc3RyaW5nW107XG4gICAgICB9XG4gICAgfVxuICAgIHRoaXMuc2lnbmF0dXJlID0gc2lnbmF0dXJlO1xuXG4gICAgdGhpcy52ZXJzaW9uID0gYCR7Z3JhcGgudmVyc2lvbnMucHJvZHVjZXJ9LiR7Z3JhcGgudmVyc2lvbnMubWluQ29uc3VtZXJ9YDtcbiAgICBjb25zdCB3ZWlnaHRNYXAgPSB0aGlzLmlvLmRlY29kZVdlaWdodHMoXG4gICAgICAgIHRoaXMuYXJ0aWZhY3RzLndlaWdodERhdGEsIHRoaXMuYXJ0aWZhY3RzLndlaWdodFNwZWNzKTtcbiAgICB0aGlzLmV4ZWN1dG9yID0gbmV3IEdyYXBoRXhlY3V0b3IoXG4gICAgICAgIE9wZXJhdGlvbk1hcHBlci5JbnN0YW5jZS50cmFuc2Zvcm1HcmFwaChncmFwaCwgdGhpcy5zaWduYXR1cmUpKTtcbiAgICB0aGlzLmV4ZWN1dG9yLndlaWdodE1hcCA9IHRoaXMuY29udmVydFRlbnNvck1hcFRvVGVuc29yc01hcCh3ZWlnaHRNYXApO1xuICAgIC8vIEF0dGFjaCBhIG1vZGVsLWxldmVsIHJlc291cmNlTWFuYWdlciB0byBlYWNoIGV4ZWN1dG9yIHRvIHNoYXJlIHJlc291cmNlcyxcbiAgICAvLyBzdWNoIGFzIGBIYXNoVGFibGVgLlxuICAgIHRoaXMuZXhlY3V0b3IucmVzb3VyY2VNYW5hZ2VyID0gdGhpcy5yZXNvdXJjZU1hbmFnZXI7XG5cbiAgICBpZiAoYXJ0aWZhY3RzLm1vZGVsSW5pdGlhbGl6ZXIgIT0gbnVsbCAmJlxuICAgICAgICAoYXJ0aWZhY3RzLm1vZGVsSW5pdGlhbGl6Z