@tensorflow/tfjs-data
Version:
TensorFlow Data API in JavaScript
660 lines • 72.2 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 * as tf from '@tensorflow/tfjs-core';
import * as seedrandom from 'seedrandom';
import { iteratorFromConcatenated, iteratorFromFunction, iteratorFromItems, iteratorFromZipped, ZipMismatchMode } from './iterators/lazy_iterator';
import { canTensorify, deepMapAndAwaitAll, isIterable } from './util/deep_map';
// TODO(soergel): consider vectorized operations within the pipeline.
/**
* Represents a potentially large list of independent data elements (typically
* 'samples' or 'examples').
*
* A 'data example' may be a primitive, an array, a map from string keys to
* values, or any nested structure of these.
*
* A `Dataset` represents an ordered collection of elements, together with a
* chain of transformations to be performed on those elements. Each
* transformation is a method of `Dataset` that returns another `Dataset`, so
* these may be chained, e.g.
* `const processedDataset = rawDataset.filter(...).map(...).batch(...)`.
*
* Data loading and transformation is done in a lazy, streaming fashion. The
* dataset may be iterated over multiple times; each iteration starts the data
* loading anew and recapitulates the transformations.
*
* A `Dataset` is typically processed as a stream of unbatched examples -- i.e.,
* its transformations are applied one example at a time. Batching produces a
* new `Dataset` where each element is a batch. Batching should usually come
* last in a pipeline, because data transformations are easier to express on a
* per-example basis than on a per-batch basis.
*
* The following code examples are calling `await dataset.forEachAsync(...)` to
* iterate once over the entire dataset in order to print out the data.
*
* @doc {heading: 'Data', subheading: 'Classes', namespace: 'data'}
*/
class Dataset {
constructor() {
this.size = null;
}
// TODO(soergel): Make Datasets report whether repeated iterator() calls
// produce the same result (e.g., reading from a file) or different results
// (e.g., from the webcam). Currently we don't make this distinction but it
// could be important for the user to know.
// abstract isDeterministic(): boolean;
/**
* Groups elements into batches.
*
* It is assumed that each of the incoming dataset elements has the same
* structure -- i.e. the same set of keys at each location in an object
* hierarchy. For each key, the resulting `Dataset` provides a batched
* element collecting all of the incoming values for that key.
*
* * Incoming primitives are grouped into a 1-D Tensor.
* * Incoming Tensors are grouped into a new Tensor where the 0th axis is
* the batch dimension.
* * Incoming arrays are converted to Tensor and then batched.
* * A nested array is interpreted as an n-D Tensor, so the batched result
* has n+1 dimensions.
* * An array that cannot be converted to Tensor produces an error.
*
* If an array should not be batched as a unit, it should first be converted
* to an object with integer keys.
*
* Here are a few examples:
*
* Batch a dataset of numbers:
* ```js
* const a = tf.data.array([1, 2, 3, 4, 5, 6, 7, 8]).batch(4);
* await a.forEachAsync(e => e.print());
* ```
*
* Batch a dataset of arrays:
* ```js
* const b = tf.data.array([[1], [2], [3], [4], [5], [6], [7], [8]]).batch(4);
* await b.forEachAsync(e => e.print());
* ```
*
* Batch a dataset of objects:
* ```js
* const c = tf.data.array([{a: 1, b: 11}, {a: 2, b: 12}, {a: 3, b: 13},
* {a: 4, b: 14}, {a: 5, b: 15}, {a: 6, b: 16}, {a: 7, b: 17},
* {a: 8, b: 18}]).batch(4);
* await c.forEachAsync(e => {
* console.log('{');
* for(var key in e) {
* console.log(key+':');
* e[key].print();
* }
* console.log('}');
* })
* ```
*
* @param batchSize The number of elements desired per batch.
* @param smallLastBatch Whether to emit the final batch when it has fewer
* than batchSize elements. Default true.
* @returns A `Dataset`, from which a stream of batches can be obtained.
*
* @doc {heading: 'Data', subheading: 'Classes'}
*/
batch(batchSize, smallLastBatch = true) {
const base = this;
tf.util.assert(batchSize > 0, () => `batchSize needs to be positive, but it is
${batchSize}`);
let size;
if (this.size === Infinity || this.size == null) {
// If the size of this dataset is infinity or null, the new size keeps the
// same.
size = this.size;
}
else if (smallLastBatch) {
// If the size of this dataset is known and include small last batch, the
// new size is full batch count plus last batch.
size = Math.ceil(this.size / batchSize);
}
else {
// If the size of this dataset is known and not include small last batch,
// the new size is full batch count.
size = Math.floor(this.size / batchSize);
}
return datasetFromIteratorFn(async () => {
return (await base.iterator())
.columnMajorBatch(batchSize, smallLastBatch, deepBatchConcat);
}, size);
}
/**
* Concatenates this `Dataset` with another.
*
* ```js
* const a = tf.data.array([1, 2, 3]);
* const b = tf.data.array([4, 5, 6]);
* const c = a.concatenate(b);
* await c.forEachAsync(e => console.log(e));
* ```
*
* @param dataset A `Dataset` to be concatenated onto this one.
* @returns A `Dataset`.
*
* @doc {heading: 'Data', subheading: 'Classes'}
*/
concatenate(dataset) {
const base = this;
let size;
if (this.size === Infinity || dataset.size === Infinity) {
// If the size of any of these two dataset is infinity, new size is
// infinity.
size = Infinity;
}
else if (this.size != null && dataset.size != null) {
// If the size of both datasets are known and not infinity, new size is
// sum the size of these two datasets.
size = this.size + dataset.size;
}
else {
// If neither of these two datasets has infinite size and any of these two
// datasets' size is null, the new size is null.
size = null;
}
return datasetFromIteratorFn(async () => (await base.iterator()).concatenate(await dataset.iterator()), size);
}
/**
* Filters this dataset according to `predicate`.
*
* ```js
* const a = tf.data.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
* .filter(x => x%2 === 0);
* await a.forEachAsync(e => console.log(e));
* ```
*
* @param predicate A function mapping a dataset element to a boolean or a
* `Promise` for one.
*
* @returns A `Dataset` of elements for which the predicate was true.
*
* @doc {heading: 'Data', subheading: 'Classes'}
*/
filter(predicate) {
const base = this;
let size;
if (this.size === Infinity) {
// If the size of this dataset is infinity, new size is infinity
size = Infinity;
}
else {
// If this dataset has limited elements, new size is null because it might
// exhausted randomly.
size = null;
}
return datasetFromIteratorFn(async () => {
return (await base.iterator()).filter(x => tf.tidy(() => predicate(x)));
}, size);
}
/**
* Apply a function to every element of the dataset.
*
* After the function is applied to a dataset element, any Tensors contained
* within that element are disposed.
*
* ```js
* const a = tf.data.array([1, 2, 3]);
* await a.forEachAsync(e => console.log(e));
* ```
*
* @param f A function to apply to each dataset element.
* @returns A `Promise` that resolves after all elements have been processed.
*
* @doc {heading: 'Data', subheading: 'Classes'}
*/
async forEachAsync(f) {
return (await this.iterator()).forEachAsync(f);
}
/**
* Maps this dataset through a 1-to-1 transform.
*
* ```js
* const a = tf.data.array([1, 2, 3]).map(x => x*x);
* await a.forEachAsync(e => console.log(e));
* ```
*
* @param transform A function mapping a dataset element to a transformed
* dataset element.
*
* @returns A `Dataset` of transformed elements.
*
* @doc {heading: 'Data', subheading: 'Classes'}
*/
map(transform) {
const base = this;
return datasetFromIteratorFn(async () => {
return (await base.iterator()).map(x => tf.tidy(() => transform(x)));
}, this.size);
}
/**
* Maps this dataset through an async 1-to-1 transform.
*
* ```js
* const a =
* tf.data.array([1, 2, 3]).mapAsync(x => new Promise(function(resolve){
* setTimeout(() => {
* resolve(x * x);
* }, Math.random()*1000 + 500);
* }));
* console.log(await a.toArray());
* ```
*
* @param transform A function mapping a dataset element to a `Promise` for a
* transformed dataset element. This transform is responsible for disposing
* any intermediate `Tensor`s, i.e. by wrapping its computation in
* `tf.tidy()`; that cannot be automated here (as it is in the synchronous
* `map()` case).
*
* @returns A `Dataset` of transformed elements.
*
* @doc {heading: 'Data', subheading: 'Classes'}
*/
mapAsync(transform) {
const base = this;
return datasetFromIteratorFn(async () => {
return (await base.iterator()).mapAsync(transform);
}, this.size);
}
/**
* Creates a `Dataset` that prefetches elements from this dataset.
*
* @param bufferSize: An integer specifying the number of elements to be
* prefetched.
* @returns A `Dataset`.
*
* @doc {heading: 'Data', subheading: 'Classes'}
*/
prefetch(bufferSize) {
if (bufferSize == null) {
throw new RangeError('`Dataset.prefetch()` requires bufferSize to be specified.');
}
const base = this;
return datasetFromIteratorFn(async () => (await base.iterator()).prefetch(bufferSize), this.size);
}
/**
* Repeats this dataset `count` times.
*
* NOTE: If this dataset is a function of global state (e.g. a random number
* generator), then different repetitions may produce different elements.
*
* ```js
* const a = tf.data.array([1, 2, 3]).repeat(3);
* await a.forEachAsync(e => console.log(e));
* ```
*
* @param count: (Optional) An integer, representing the number of times
* the dataset should be repeated. The default behavior (if `count` is
* `undefined` or negative) is for the dataset be repeated indefinitely.
* @returns A `Dataset`.
*
* @doc {heading: 'Data', subheading: 'Classes'}
*/
repeat(count) {
const base = this;
let size;
if (this.size != null && count > 0) {
// If this dataset has size and count is positive, new size is current
// size multiply count. This also covers the case that current size is
// infinity.
size = this.size * count;
}
else if (count === 0) {
// If count is 0, new size is 0.
size = 0;
}
else if (this.size != null && (count === undefined || count < 0)) {
// If this dataset has size and count is undefined or negative, the
// dataset will be repeated indefinitely and new size is infinity.
size = Infinity;
}
else {
// If the size of this dataset is null, the new dataset's size is null.
size = null;
}
return datasetFromIteratorFn(async () => {
const iteratorIterator = iteratorFromFunction(async () => ({ value: await base.iterator(), done: false }));
return iteratorFromConcatenated(iteratorIterator.take(count));
}, size);
}
/**
* Creates a `Dataset` that skips `count` initial elements from this dataset.
*
* ```js
* const a = tf.data.array([1, 2, 3, 4, 5, 6]).skip(3);
* await a.forEachAsync(e => console.log(e));
* ```
*
* @param count: The number of elements of this dataset that should be skipped
* to form the new dataset. If `count` is greater than the size of this
* dataset, the new dataset will contain no elements. If `count`
* is `undefined` or negative, skips the entire dataset.
*
* @returns A `Dataset`.
*
* @doc {heading: 'Data', subheading: 'Classes'}
*/
skip(count) {
const base = this;
let size;
if (this.size != null && count >= 0 && this.size >= count) {
// If the size of this dataset is greater than count, the new dataset's
// size is current size minus skipped size.This also covers the case that
// current size is infinity.
size = this.size - count;
}
else if (this.size != null &&
(this.size < count || count === undefined || count < 0)) {
// If the size of this dataset is smaller than count, or count is
// undefined or negative, skips the entire dataset and the new size is 0.
size = 0;
}
else {
// If the size of this dataset is null, the new dataset's size is null.
size = null;
}
return datasetFromIteratorFn(async () => (await base.iterator()).skip(count), size);
}
/**
* Pseudorandomly shuffles the elements of this dataset. This is done in a
* streaming manner, by sampling from a given number of prefetched elements.
*
* ```js
* const a = tf.data.array([1, 2, 3, 4, 5, 6]).shuffle(3);
* await a.forEachAsync(e => console.log(e));
* ```
*
* @param bufferSize: An integer specifying the number of elements from this
* dataset from which the new dataset will sample.
* @param seed: (Optional) An integer specifying the random seed that will
* be used to create the distribution.
* @param reshuffleEachIteration: (Optional) A boolean, which if true
* indicates that the dataset should be pseudorandomly reshuffled each time
* it is iterated over. If false, elements will be returned in the same
* shuffled order on each iteration. (Defaults to `true`.)
* @returns A `Dataset`.
*
* @doc {heading: 'Data', subheading: 'Classes'}
*/
shuffle(bufferSize, seed, reshuffleEachIteration = true) {
if (bufferSize == null || bufferSize < 0) {
if (this.size == null) {
throw new RangeError('`Dataset.shuffle()` requires bufferSize to be specified.');
}
else {
throw new RangeError('`Dataset.shuffle()` requires bufferSize to be specified. ' +
'If your data fits in main memory (for regular JS objects), ' +
'and/or GPU memory (for `tf.Tensor`s), consider setting ' +
`bufferSize to the dataset size (${this.size} elements)`);
}
}
const base = this;
const random = seedrandom.alea(seed || tf.util.now().toString());
return datasetFromIteratorFn(async () => {
let seed2 = random.int32();
if (reshuffleEachIteration) {
seed2 += random.int32();
}
return (await base.iterator()).shuffle(bufferSize, seed2.toString());
}, this.size);
}
/**
* Creates a `Dataset` with at most `count` initial elements from this
* dataset.
*
* ```js
* const a = tf.data.array([1, 2, 3, 4, 5, 6]).take(3);
* await a.forEachAsync(e => console.log(e));
* ```
*
* @param count: The number of elements of this dataset that should be taken
* to form the new dataset. If `count` is `undefined` or negative, or if
* `count` is greater than the size of this dataset, the new dataset will
* contain all elements of this dataset.
* @returns A `Dataset`.
*
* @doc {heading: 'Data', subheading: 'Classes'}
*/
take(count) {
const base = this;
let size;
if (this.size != null && this.size > count) {
// If the size of this dataset is greater than count, the new dataset's
// size is count.
size = count;
}
else if (this.size != null && this.size <= count) {
// If the size of this dataset is equal or smaller than count, the new
// dataset's size is the size of this dataset.
size = this.size;
}
else {
// If the size of this dataset is null, the new dataset's size is null.
size = null;
}
return datasetFromIteratorFn(async () => (await base.iterator()).take(count), size);
}
/**
* Collect all elements of this dataset into an array.
*
* Obviously this will succeed only for small datasets that fit in memory.
* Useful for testing and generally should be avoided if possible.
*
* ```js
* const a = tf.data.array([1, 2, 3, 4, 5, 6]);
* console.log(await a.toArray());
* ```
*
* @returns A Promise for an array of elements, which will resolve
* when a new stream has been obtained and fully consumed.
*
* @doc {heading: 'Data', subheading: 'Classes'}
*/
async toArray() {
if (this.size === Infinity) {
throw new Error('Can not convert infinite data stream to array.');
}
return (await this.iterator()).toArray();
}
/**
* Collect all elements of this dataset into an array with prefetching 100
* elements. This is useful for testing, because the prefetch changes the
* order in which the Promises are resolved along the processing pipeline.
* This may help expose bugs where results are dependent on the order of
* Promise resolution rather than on the logical order of the stream (i.e.,
* due to hidden mutable state).
*
* @returns A Promise for an array of elements, which will resolve
* when a new stream has been obtained and fully consumed.
*/
async toArrayForTest() {
if (this.size === Infinity) {
throw new Error('Can not convert infinite data stream to array.');
}
return (await this.iterator()).toArrayForTest();
}
}
// TODO(soergel): deep sharded shuffle, where supported
Dataset.MAX_BUFFER_SIZE = 10000;
export { Dataset };
/**
* Create a `Dataset` defined by a provided iterator() function.
*
* ```js
* let i = -1;
* const func = () =>
* ++i < 5 ? {value: i, done: false} : {value: null, done: true};
* const iter = tf.data.iteratorFromFunction(func);
* const ds = tf.data.datasetFromIteratorFn(iter);
* await ds.forEachAsync(e => console.log(e));
* ```
*/
export function datasetFromIteratorFn(iteratorFn, size = null) {
return new class extends Dataset {
constructor() {
super(...arguments);
this.size = size;
}
/*
* Provide a new stream of elements. Note this will also start new streams
* from any underlying `Dataset`s.
*/
async iterator() {
return iteratorFn();
}
}();
}
/**
* Create a `Dataset` from an array of elements.
*
* Create a Dataset from an array of objects:
* ```js
* const a = tf.data.array([{'item': 1}, {'item': 2}, {'item': 3}]);
* await a.forEachAsync(e => console.log(e));
* ```
*
* Create a Dataset from an array of numbers:
* ```js
* const a = tf.data.array([4, 5, 6]);
* await a.forEachAsync(e => console.log(e));
* ```
* @param items An array of elements that will be parsed as items in a dataset.
*
* @doc {heading: 'Data', subheading: 'Creation', namespace: 'data'}
*/
export function array(items) {
return datasetFromIteratorFn(async () => iteratorFromItems(items), items.length);
}
/**
* Create a `Dataset` by zipping together an array, dict, or nested
* structure of `Dataset`s (and perhaps additional constants).
* The underlying datasets must provide elements in a consistent order such that
* they correspond.
*
* The number of elements in the resulting dataset is the same as the size of
* the smallest dataset in datasets.
*
* The nested structure of the `datasets` argument determines the
* structure of elements in the resulting iterator.
*
* Note this means that, given an array of two datasets that produce dict
* elements, the result is a dataset that produces elements that are arrays
* of two dicts:
*
* Zip an array of datasets:
* ```js
* console.log('Zip two datasets of objects:');
* const ds1 = tf.data.array([{a: 1}, {a: 2}, {a: 3}]);
* const ds2 = tf.data.array([{b: 4}, {b: 5}, {b: 6}]);
* const ds3 = tf.data.zip([ds1, ds2]);
* await ds3.forEachAsync(e => console.log(JSON.stringify(e)));
*
* // If the goal is to merge the dicts in order to produce elements like
* // {a: ..., b: ...}, this requires a second step such as:
* console.log('Merge the objects:');
* const ds4 = ds3.map(x => {return {a: x[0].a, b: x[1].b}});
* await ds4.forEachAsync(e => console.log(e));
* ```
*
* Zip a dict of datasets:
* ```js
* const a = tf.data.array([{a: 1}, {a: 2}, {a: 3}]);
* const b = tf.data.array([{b: 4}, {b: 5}, {b: 6}]);
* const c = tf.data.zip({c: a, d: b});
* await c.forEachAsync(e => console.log(JSON.stringify(e)));
* ```
*
* @doc {heading: 'Data', subheading: 'Operations', namespace: 'data'}
*/
export function zip(datasets) {
// manually type-check the argument for JS users
if (!isIterable(datasets)) {
throw new Error('The argument to zip() must be an object or array.');
}
let size;
if (Array.isArray(datasets)) {
for (let i = 0; i < datasets.length; i++) {
size = size == null ? datasets[i].size :
Math.min(size, datasets[i].size);
}
}
else if (datasets instanceof Object) {
for (const ds in datasets) {
size = size == null ? datasets[ds].size :
Math.min(size, datasets[ds].size);
}
}
return datasetFromIteratorFn(async () => {
const streams = await deepMapAndAwaitAll(datasets, d => {
if (d instanceof Dataset) {
return { value: d.iterator(), recurse: false };
}
else if (isIterable(d)) {
return { value: null, recurse: true };
}
else {
throw new Error('Leaves of the structure passed to zip() must be Datasets, ' +
'not primitives.');
}
});
return iteratorFromZipped(streams, ZipMismatchMode.SHORTEST);
}, size);
}
/**
* A zip function for use with deepZip, passed via the columnMajorBatch call.
*
* Accepts an array of identically-structured nested elements and either batches
* them (if they are primitives, numeric arrays, or Tensors) or requests
* recursion (if not).
*/
// tslint:disable-next-line:no-any
function deepBatchConcat(rows) {
if (rows === null) {
return null;
}
// use the first item to decide whether to recurse or batch here.
const exampleRow = rows[0];
if (canTensorify(exampleRow)) {
// rows is an array of primitives, Tensors, or arrays. Batch them.
const value = batchConcat(rows);
return { value, recurse: false };
}
// the example row is an object, so recurse into it.
return { value: null, recurse: true };
}
/**
* Assembles a list of same-shaped numbers, number arrays, or Tensors
* into a single new Tensor where axis 0 is the batch dimension.
*/
function batchConcat(arrays) {
if (arrays.length === 0) {
// We can't return an empty Tensor because we don't know the element shape.
throw new Error('Can\'t make a batch of zero elements.');
}
if (arrays[0] instanceof tf.Tensor) {
// Input is an array of Tensors
return tf.stack(arrays);
}
else {
// Input is a possibly-nested array of numbers.
return tf.tensor(arrays);
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGF0YXNldC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3RmanMtZGF0YS9zcmMvZGF0YXNldC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7OztHQWdCRztBQUVILE9BQU8sS0FBSyxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFFNUMsT0FBTyxLQUFLLFVBQVUsTUFBTSxZQUFZLENBQUM7QUFFekMsT0FBTyxFQUFDLHdCQUF3QixFQUFFLG9CQUFvQixFQUFFLGlCQUFpQixFQUFFLGtCQUFrQixFQUFnQixlQUFlLEVBQUMsTUFBTSwyQkFBMkIsQ0FBQztBQUUvSixPQUFPLEVBQUMsWUFBWSxFQUFFLGtCQUFrQixFQUFpQixVQUFVLEVBQUMsTUFBTSxpQkFBaUIsQ0FBQztBQU81RixxRUFBcUU7QUFFckU7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTJCRztBQUNILE1BQXNCLE9BQU87SUFBN0I7UUFXVyxTQUFJLEdBQVcsSUFBSSxDQUFDO0lBMmMvQixDQUFDO0lBemNDLHdFQUF3RTtJQUN4RSwyRUFBMkU7SUFDM0UsNEVBQTRFO0lBQzVFLDJDQUEyQztJQUMzQyx1Q0FBdUM7SUFFdkM7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztPQXNERztJQUNILEtBQUssQ0FBQyxTQUFpQixFQUFFLGNBQWMsR0FBRyxJQUFJO1FBQzVDLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQztRQUNsQixFQUFFLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FDVixTQUFTLEdBQUcsQ0FBQyxFQUFFLEdBQUcsRUFBRSxDQUFDO1FBQ3JCLFNBQVMsRUFBRSxDQUFDLENBQUM7UUFDakIsSUFBSSxJQUFJLENBQUM7UUFDVCxJQUFJLElBQUksQ0FBQyxJQUFJLEtBQUssUUFBUSxJQUFJLElBQUksQ0FBQyxJQUFJLElBQUksSUFBSSxFQUFFO1lBQy9DLDBFQUEwRTtZQUMx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QUFNO1FBQ0wsK0NBQStDO1FBQy9DLE9BQU8sRUFBRSxDQUFDLE1BQU0sQ0FBQyxNQUFvQixDQUFDLENBQUM7S0FDeEM7QUFDSCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBAbGljZW5zZVxuICogQ29weXJpZ2h0IDIwMTggR29vZ2xlIExMQy4gQWxsIFJpZ2h0cyBSZXNlcnZlZC5cbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSBcIkxpY2Vuc2VcIik7XG4gKiB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuXG4gKiBZb3UgbWF5IG9idGFpbiBhIGNvcHkgb2YgdGhlIExpY2Vuc2UgYXRcbiAqXG4gKiBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjBcbiAqXG4gKiBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlXG4gKiBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiBcIkFTIElTXCIgQkFTSVMsXG4gKiBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC5cbiAqIFNlZSB0aGUgTGljZW5zZSBmb3IgdGhlIHNwZWNpZmljIGxhbmd1YWdlIGdvdmVybmluZyBwZXJtaXNzaW9ucyBhbmRcbiAqIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLlxuICpcbiAqID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4gKi9cblxuaW1wb3J0ICogYXMgdGYgZnJvbSAnQHRlbnNvcmZsb3cvdGZqcy1jb3JlJztcbmltcG9ydCB7VGVuc29yQ29udGFpbmVyLCBUZW5zb3JMaWtlfSBmcm9tICdAdGVuc29yZmxvdy90ZmpzLWNvcmUnO1xuaW1wb3J0ICogYXMgc2VlZHJhbmRvbSBmcm9tICdzZWVkcmFuZG9tJztcblxuaW1wb3J0IHtpdGVyYXRvckZyb21Db25jYXRlbmF0ZWQsIGl0ZXJhdG9yRnJvbUZ1bmN0aW9uLCBpdGVyYXRvckZyb21JdGVtcywgaXRlcmF0b3JGcm9tWmlwcGVkLCBMYXp5SXRlcmF0b3IsIFppcE1pc21hdGNoTW9kZX0gZnJvbSAnLi9pdGVyYXRvcnMvbGF6eV9pdGVyYXRvcic7XG5pbXBvcnQge0NvbnRhaW5lcn0gZnJvbSAnLi90eXBlcyc7XG5pbXBvcnQge2NhblRlbnNvcmlmeSwgZGVlcE1hcEFuZEF3YWl0QWxsLCBEZWVwTWFwUmVzdWx0LCBpc0l0ZXJhYmxlfSBmcm9tICcuL3V0aWwvZGVlcF9tYXAnO1xuXG4vKipcbiAqIEEgbmVzdGVkIHN0cnVjdHVyZSBvZiBEYXRhc2V0cywgdXNlZCBhcyB0aGUgaW5wdXQgdG8gemlwKCkuXG4gKi9cbmV4cG9ydCB0eXBlIERhdGFzZXRDb250YWluZXIgPSBDb250YWluZXI8RGF0YXNldDxUZW5zb3JDb250YWluZXI+PjtcblxuLy8gVE9ETyhzb2VyZ2VsKTogY29uc2lkZXIgdmVjdG9yaXplZCBvcGVyYXRpb25zIHdpdGhpbiB0aGUgcGlwZWxpbmUuXG5cbi8qKlxuICogUmVwcmVzZW50cyBhIHBvdGVudGlhbGx5IGxhcmdlIGxpc3Qgb2YgaW5kZXBlbmRlbnQgZGF0YSBlbGVtZW50cyAodHlwaWNhbGx5XG4gKiAnc2FtcGxlcycgb3IgJ2V4YW1wbGVzJykuXG4gKlxuICogQSAnZGF0YSBleGFtcGxlJyBtYXkgYmUgYSBwcmltaXRpdmUsIGFuIGFycmF5LCBhIG1hcCBmcm9tIHN0cmluZyBrZXlzIHRvXG4gKiB2YWx1ZXMsIG9yIGFueSBuZXN0ZWQgc3RydWN0dXJlIG9mIHRoZXNlLlxuICpcbiAqIEEgYERhdGFzZXRgIHJlcHJlc2VudHMgYW4gb3JkZXJlZCBjb2xsZWN0aW9uIG9mIGVsZW1lbnRzLCB0b2dldGhlciB3aXRoIGFcbiAqIGNoYWluIG9mIHRyYW5zZm9ybWF0aW9ucyB0byBiZSBwZXJmb3JtZWQgb24gdGhvc2UgZWxlbWVudHMuIEVhY2hcbiAqIHRyYW5zZm9ybWF0aW9uIGlzIGEgbWV0aG9kIG9mIGBEYXRhc2V0YCB0aGF0IHJldHVybnMgYW5vdGhlciBgRGF0YXNldGAsIHNvXG4gKiB0aGVzZSBtYXkgYmUgY2hhaW5lZCwgZS5nLlxuICogYGNvbnN0IHByb2Nlc3NlZERhdGFzZXQgPSByYXdEYXRhc2V0LmZpbHRlciguLi4pLm1hcCguLi4pLmJhdGNoKC4uLilgLlxuICpcbiAqIERhdGEgbG9hZGluZyBhbmQgdHJhbnNmb3JtYXRpb24gaXMgZG9uZSBpbiBhIGxhenksIHN0cmVhbWluZyBmYXNoaW9uLiAgVGhlXG4gKiBkYXRhc2V0IG1heSBiZSBpdGVyYXRlZCBvdmVyIG11bHRpcGxlIHRpbWVzOyBlYWNoIGl0ZXJhdGlvbiBzdGFydHMgdGhlIGRhdGFcbiAqIGxvYWRpbmcgYW5ldyBhbmQgcmVjYXBpdHVsYXRlcyB0aGUgdHJhbnNmb3JtYXRpb25zLlxuICpcbiAqIEEgYERhdGFzZXRgIGlzIHR5cGljYWxseSBwcm9jZXNzZWQgYXMgYSBzdHJlYW0gb2YgdW5iYXRjaGVkIGV4YW1wbGVzIC0tIGkuZS4sXG4gKiBpdHMgdHJhbnNmb3JtYXRpb25zIGFyZSBhcHBsaWVkIG9uZSBleGFtcGxlIGF0IGEgdGltZS4gQmF0Y2hpbmcgcHJvZHVjZXMgYVxuICogbmV3IGBEYXRhc2V0YCB3aGVyZSBlYWNoIGVsZW1lbnQgaXMgYSBiYXRjaC4gQmF0Y2hpbmcgc2hvdWxkIHVzdWFsbHkgY29tZVxuICogbGFzdCBpbiBhIHBpcGVsaW5lLCBiZWNhdXNlIGRhdGEgdHJhbnNmb3JtYXRpb25zIGFyZSBlYXNpZXIgdG8gZXhwcmVzcyBvbiBhXG4gKiBwZXItZXhhbXBsZSBiYXNpcyB0aGFuIG9uIGEgcGVyLWJhdGNoIGJhc2lzLlxuICpcbiAqIFRoZSBmb2xsb3dpbmcgY29kZSBleGFtcGxlcyBhcmUgY2FsbGluZyBgYXdhaXQgZGF0YXNldC5mb3JFYWNoQXN5bmMoLi4uKWAgdG9cbiAqIGl0ZXJhdGUgb25jZSBvdmVyIHRoZSBlbnRpcmUgZGF0YXNldCBpbiBvcmRlciB0byBwcmludCBvdXQgdGhlIGRhdGEuXG4gKlxuICogQGRvYyB7aGVhZGluZzogJ0RhdGEnLCBzdWJoZWFkaW5nOiAnQ2xhc3NlcycsIG5hbWVzcGFjZTogJ2RhdGEnfVxuICovXG5leHBvcnQgYWJzdHJhY3QgY2xhc3MgRGF0YXNldDxUIGV4dGVuZHMgdGYuVGVuc29yQ29udGFpbmVyPiB7XG4gIC8qXG4gICAqIFByb3ZpZGUgYSBuZXcgc3RyZWFtIG9mIGVsZW1lbnRzLiAgTm90ZSB0aGlzIHdpbGwgYWxzbyBzdGFydCBuZXcgc3RyZWFtc1xuICAgKiBmcm9tIGFueSB1bmRlcmx5aW5nIGBEYXRhc2V0YHMuXG4gICAqXG4gICAqIENBVVRJT046IEFueSBUZW5zb3JzIGNvbnRhaW5lZCB3aXRoaW4gdGhlIGVsZW1lbnRzIHJldHVybmVkIGZyb21cbiAgICogdGhpcyBzdHJlYW0gKm11c3QqIGJlIG1hbnVhbGx5IGRpc3Bvc2VkIHRvIGF2b2lkIGEgR1BVIG1lbW9yeSBsZWFrLlxuICAgKiBUaGUgdGYudGlkeSgpIGFwcHJvYWNoIGNhbm5vdCBiZSB1c2VkIGluIGFuIGFzeW5jaHJvbm91cyBjb250ZXh0LlxuICAgKi9cbiAgYWJzdHJhY3QgaXRlcmF0b3IoKTogUHJvbWlzZTxMYXp5SXRlcmF0b3I8VD4+O1xuXG4gIHJlYWRvbmx5IHNpemU6IG51bWJlciA9IG51bGw7XG5cbiAgLy8gVE9ETyhzb2VyZ2VsKTogTWFrZSBEYXRhc2V0cyByZXBvcnQgd2hldGhlciByZXBlYXRlZCBpdGVyYXRvcigpIGNhbGxzXG4gIC8vIHByb2R1Y2UgdGhlIHNhbWUgcmVzdWx0IChlLmcuLCByZWFkaW5nIGZyb20gYSBmaWxlKSBvciBkaWZmZXJlbnQgcmVzdWx0c1xuICAvLyAoZS5nLiwgZnJvbSB0aGUgd2ViY2FtKS4gIEN1cnJlbnRseSB3ZSBkb24ndCBtYWtlIHRoaXMgZGlzdGluY3Rpb24gYnV0IGl0XG4gIC8vIGNvdWxkIGJlIGltcG9ydGFudCBmb3IgdGhlIHVzZXIgdG8ga25vdy5cbiAgLy8gYWJzdHJhY3QgaXNEZXRlcm1pbmlzdGljKCk6IGJvb2xlYW47XG5cbiAgLyoqXG4gICAqIEdyb3VwcyBlbGVtZW50cyBpbnRvIGJhdGNoZXMuXG4gICAqXG4gICAqIEl0IGlzIGFzc3VtZWQgdGhhdCBlYWNoIG9mIHRoZSBpbmNvbWluZyBkYXRhc2V0IGVsZW1lbnRzIGhhcyB0aGUgc2FtZVxuICAgKiBzdHJ1Y3R1cmUgLS0gaS5lLiB0aGUgc2FtZSBzZXQgb2Yga2V5cyBhdCBlYWNoIGxvY2F0aW9uIGluIGFuIG9iamVjdFxuICAgKiBoaWVyYXJjaHkuICBGb3IgZWFjaCBrZXksIHRoZSByZXN1bHRpbmcgYERhdGFzZXRgIHByb3ZpZGVzIGEgYmF0Y2hlZFxuICAgKiBlbGVtZW50IGNvbGxlY3RpbmcgYWxsIG9mIHRoZSBpbmNvbWluZyB2YWx1ZXMgZm9yIHRoYXQga2V5LlxuICAgKlxuICAgKiAgKiBJbmNvbWluZyBwcmltaXRpdmVzIGFyZSBncm91cGVkIGludG8gYSAxLUQgVGVuc29yLlxuICAgKiAgKiBJbmNvbWluZyBUZW5zb3JzIGFyZSBncm91cGVkIGludG8gYSBuZXcgVGVuc29yIHdoZXJlIHRoZSAwdGggYXhpcyBpc1xuICAgKiAgICB0aGUgYmF0Y2ggZGltZW5zaW9uLlxuICAgKiAgKiBJbmNvbWluZyBhcnJheXMgYXJlIGNvbnZlcnRlZCB0byBUZW5zb3IgYW5kIHRoZW4gYmF0Y2hlZC5cbiAgICogICogQSBuZXN0ZWQgYXJyYXkgaXMgaW50ZXJwcmV0ZWQgYXMgYW4gbi1EIFRlbnNvciwgc28gdGhlIGJhdGNoZWQgcmVzdWx0XG4gICAqICAgIGhhcyBuKzEgZGltZW5zaW9ucy5cbiAgICogICogQW4gYXJyYXkgdGhhdCBjYW5ub3QgYmUgY29udmVydGVkIHRvIFRlbnNvciBwcm9kdWNlcyBhbiBlcnJvci5cbiAgICpcbiAgICogSWYgYW4gYXJyYXkgc2hvdWxkIG5vdCBiZSBiYXRjaGVkIGFzIGEgdW5pdCwgaXQgc2hvdWxkIGZpcnN0IGJlIGNvbnZlcnRlZFxuICAgKiB0byBhbiBvYmplY3Qgd2l0aCBpbnRlZ2VyIGtleXMuXG4gICAqXG4gICAqIEhlcmUgYXJlIGEgZmV3IGV4YW1wbGVzOlxuICAgKlxuICAgKiBCYXRjaCBhIGRhdGFzZXQgb2YgbnVtYmVyczpcbiAgICogYGBganNcbiAgICogY29uc3QgYSA9IHRmLmRhdGEuYXJyYXkoWzEsIDIsIDMsIDQsIDUsIDYsIDcsIDhdKS5iYXRjaCg0KTtcbiAgICogYXdhaXQgYS5mb3JFYWNoQXN5bmMoZSA9PiBlLnByaW50KCkpO1xuICAgKiBgYGBcbiAgICpcbiAgICogQmF0Y2ggYSBkYXRhc2V0IG9mIGFycmF5czpcbiAgICogYGBganNcbiAgICogY29uc3QgYiA9IHRmLmRhdGEuYXJyYXkoW1sxXSwgWzJdLCBbM10sIFs0XSwgWzVdLCBbNl0sIFs3XSwgWzhdXSkuYmF0Y2goNCk7XG4gICAqIGF3YWl0IGIuZm9yRWFjaEFzeW5jKGUgPT4gZS5wcmludCgpKTtcbiAgICogYGBgXG4gICAqXG4gICAqIEJhdGNoIGEgZGF0YXNldCBvZiBvYmplY3RzOlxuICAgKiBgYGBqc1xuICAgKiBjb25zdCBjID0gdGYuZGF0YS5hcnJheShbe2E6IDEsIGI6IDExfSwge2E6IDIsIGI6IDEyfSwge2E6IDMsIGI6IDEzfSxcbiAgICogICB7YTogNCwgYjogMTR9LCB7YTogNSwgYjogMTV9LCB7YTogNiwgYjogMTZ9LCB7YTogNywgYjogMTd9LFxuICAgKiAgIHthOiA4LCBiOiAxOH1dKS5iYXRjaCg0KTtcbiAgICogYXdhaXQgYy5mb3JFYWNoQXN5bmMoZSA9PiB7XG4gICAqICAgY29uc29sZS5sb2coJ3snKTtcbiAgICogICBmb3IodmFyIGtleSBpbiBlKSB7XG4gICAqICAgICBjb25zb2xlLmxvZyhrZXkrJzonKTtcbiAgICogICAgIGVba2V5XS5wcmludCgpO1xuICAgKiAgIH1cbiAgICogICBjb25zb2xlLmxvZygnfScpO1xuICAgKiB9KVxuICAgKiBgYGBcbiAgICpcbiAgICogQHBhcmFtIGJhdGNoU2l6ZSBUaGUgbnVtYmVyIG9mIGVsZW1lbnRzIGRlc2lyZWQgcGVyIGJhdGNoLlxuICAgKiBAcGFyYW0gc21hbGxMYXN0QmF0Y2ggV2hldGhlciB0byBlbWl0IHRoZSBmaW5hbCBiYXRjaCB3aGVuIGl0IGhhcyBmZXdlclxuICAgKiAgIHRoYW4gYmF0Y2hTaXplIGVsZW1lbnRzLiBEZWZhdWx0IHRydWUuXG4gICAqIEByZXR1cm5zIEEgYERhdGFzZXRgLCBmcm9tIHdoaWNoIGEgc3RyZWFtIG9mIGJhdGNoZXMgY2FuIGJlIG9idGFpbmVkLlxuICAgKlxuICAgKiBAZG9jIHtoZWFkaW5nOiAnRGF0YScsIHN1YmhlYWRpbmc6ICdDbGFzc2VzJ31cbiAgICovXG4gIGJhdGNoKGJhdGNoU2l6ZTogbnVtYmVyLCBzbWFsbExhc3RCYXRjaCA9IHRydWUpOiBEYXRhc2V0PHRmLlRlbnNvckNvbnRhaW5lcj4ge1xuICAgIGNvbnN0IGJhc2UgPSB0aGlzO1xuICAgIHRmLnV0aWwuYXNzZXJ0KFxuICAgICAgICBiYXRjaFNpemUgPiAwLCAoKSA9PiBgYmF0Y2hTaXplIG5lZWRzIHRvIGJlIHBvc2l0aXZlLCBidXQgaXQgaXNcbiAgICAgICR7YmF0Y2hTaXplfWApO1xuICAgIGxldCBzaXplO1xuICAgIGlmICh0aGlzLnNpemUgPT09IEluZmluaXR5IHx8IHRoaXMuc2l6ZSA9PSBudWxsKSB7XG4gICAgICAvLyBJZiB0aGUgc2l6ZSBvZiB0aGlzIGRhdGFzZXQgaXMgaW5maW5pdHkgb3IgbnVsbCwgdGhlIG5ldyBzaXplIGtlZXBzIHRoZVxuICAgICAgLy8gc2FtZS5cbiAgICAgIHNpemUgPSB0aGlzLnNpemU7XG4gICAgfSBlbHNlIGlmIChzbWFsbExhc3RCYXRjaCkge1xuICAgICAgLy8gSWYgdGhlIHNpemUgb2YgdGhpcyBkYXRhc2V0IGlzIGtub3duIGFuZCBpbmNsdWRlIHNtYWxsIGxhc3QgYmF0Y2gsIHRoZVxuICAgICAgLy8gbmV3IHNpemUgaXMgZnVsbCBiYXRjaCBjb3VudCBwbHVzIGxhc3QgYmF0Y2guXG4gICAgICBzaXplID0gTWF0aC5jZWlsKHRoaXMuc2l6ZSAvIGJhdGNoU2l6ZSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIC8vIElmIHRoZSBzaXplIG9mIHRoaXMgZGF0YXNldCBpcyBrbm93biBhbmQgbm90IGluY2x1ZGUgc21hbGwgbGFzdCBiYXRjaCxcbiAgICAgIC8vIHRoZSBuZXcgc2l6ZSBpcyBmdWxsIGJhdGNoIGNvdW50LlxuICAgICAgc2l6ZSA9IE1hdGguZmxvb3IodGhpcy5zaXplIC8gYmF0Y2hTaXplKTtcbiAgICB9XG4gICAgcmV0dXJuIGRhdGFzZXRGcm9tSXRlcmF0b3JGbihhc3luYyAoKSA9PiB7XG4gICAgICByZXR1cm4gKGF3YWl0IGJhc2UuaXRlcmF0b3IoKSlcbiAgICAgICAgICAuY29sdW1uTWFqb3JCYXRjaChiYXRjaFNpemUsIHNtYWxsTGFzdEJhdGNoLCBkZWVwQmF0Y2hDb25jYXQpO1xuICAgIH0sIHNpemUpO1xuICB9XG5cbiAgLyoqXG4gICAqIENvbmNhdGVuYXRlcyB0aGlzIGBEYXRhc2V0YCB3aXRoIGFub3RoZXIuXG4gICAqXG4gICAqIGBgYGpzXG4gICAqIGNvbnN0IGEgPSB0Zi5kYXRhLmFycmF5KFsxLCAyLCAzXSk7XG4gICAqIGNvbnN0IGIgPSB0Zi5kYXRhLmFycmF5KFs0LCA1LCA2XSk7XG4gICAqIGNvbnN0IGMgPSBhLmNvbmNhdGVuYXRlKGIpO1xuICAgKiBhd2FpdCBjLmZvckVhY2hBc3luYyhlID0+IGNvbnNvbGUubG9nKGUpKTtcbiAgICogYGBgXG4gICAqXG4gICAqIEBwYXJhbSBkYXRhc2V0IEEgYERhdGFzZXRgIHRvIGJlIGNvbmNhdGVuYXRlZCBvbnRvIHRoaXMgb25lLlxuICAgKiBAcmV0dXJucyBBIGBEYXRhc2V0YC5cbiAgICpcbiAgICogQGRvYyB7aGVhZGluZzogJ0RhdGEnLCBzdWJoZWFkaW5nOiAnQ2xhc3Nlcyd9XG4gICAqL1xuICBjb25jYXRlbmF0ZShkYXRhc2V0OiBEYXRhc2V0PFQ+KTogRGF0YXNldDxUPiB7XG4gICAgY29uc3QgYmFzZSA9IHRoaXM7XG4gICAgbGV0IHNpemU7XG4gICAgaWYgKHRoaXMuc2l6ZSA9PT0gSW5maW5pdHkgfHwgZGF0YXNldC5zaXplID09PSBJbmZpbml0eSkge1xuICAgICAgLy8gSWYgdGhlIHNpemUgb2YgYW55IG9mIHRoZXNlIHR3byBkYXRhc2V0IGlzIGluZmluaXR5LCBuZXcgc2l6ZSBpc1xuICAgICAgLy8gaW5maW5pdHkuXG4gICAgICBzaXplID0gSW5maW5pdHk7XG4gICAgfSBlbHNlIGlmICh0aGlzLnNpemUgIT0gbnVsbCAmJiBkYXRhc2V0LnNpemUgIT0gbnVsbCkge1xuICAgICAgLy8gSWYgdGhlIHNpemUgb2YgYm90aCBkYXRhc2V0cyBhcmUga25vd24gYW5kIG5vdCBpbmZpbml0eSwgbmV3IHNpemUgaXNcbiAgICAgIC8vIHN1bSB0aGUgc2l6ZSBvZiB0aGVzZSB0d28gZGF0YXNldHMuXG4gICAgICBzaXplID0gdGhpcy5zaXplICsgZGF0YXNldC5zaXplO1xuICAgIH0gZWxzZSB7XG4gICAgICAvLyBJZiBuZWl0aGVyIG9mIHRoZXNlIHR3byBkYXRhc2V0cyBoYXMgaW5maW5pdGUgc2l6ZSBhbmQgYW55IG9mIHRoZXNlIHR3b1xuICAgICAgLy8gZGF0YXNldHMnIHNpemUgaXMgbnVsbCwgdGhlIG5ldyBzaXplIGlzIG51bGwuXG4gICAgICBzaXplID0gbnVsbDtcbiAgICB9XG4gICAgcmV0dXJuIGRhdGFzZXRGcm9tSXRlcmF0b3JGbihcbiAgICAgICAgYXN5bmMgKCkgPT5cbiAgICAgICAgICAgIChhd2FpdCBiYXNlLml0ZXJhdG9yKCkpLmNvbmNhdGVuYXRlKGF3YWl0IGRhdGFzZXQuaXRlcmF0b3IoKSksXG4gICAgICAgIHNpemUpO1xuICB9XG5cbiAgLyoqXG4gICAqIEZpbHRlcnMgdGhpcyBkYXRhc2V0IGFjY29yZGluZyB0byBgcHJlZGljYXRlYC5cbiAgICpcbiAgICogYGBganNcbiAgICogY29uc3QgYSA9IHRmLmRhdGEuYXJyYXkoWzEsIDIsIDMsIDQsIDUsIDYsIDcsIDgsIDksIDEwXSlcbiAgICogICAuZmlsdGVyKHggPT4geCUyID09PSAwKTtcbiAgICogYXdhaXQgYS5mb3JFYWNoQXN5bmMoZSA9PiBjb25zb2xlLmxvZyhlKSk7XG4gICAqIGBgYFxuICAgKlxuICAgKiBAcGFyYW0gcHJlZGljYXRlIEEgZnVuY3Rpb24gbWFwcGluZyBhIGRhdGFzZXQgZWxlbWVudCB0byBhIGJvb2xlYW4gb3IgYVxuICAgKiBgUHJvbWlzZWAgZm9yIG9uZS5cbiAgICpcbiAgICogQHJldHVybnMgQSBgRGF0YXNldGAgb2YgZWxlbWVudHMgZm9yIHdoaWNoIHRoZSBwcmVkaWNhdGUgd2FzIHRydWUuXG4gICAqXG4gICAqIEBkb2Mge2hlYWRpbmc6ICdEYXRhJywgc3ViaGVhZGluZzogJ0NsYXNzZXMnfVxuICAgKi9cbiAgZmlsdGVyKHByZWRpY2F0ZTogKHZhbHVlOiBUKSA9PiBib29sZWFuKTogRGF0YXNldDxUPiB7XG4gICAgY29uc3QgYmFzZSA9IHRoaXM7XG4gICAgbGV0IHNpemU7XG4gICAgaWYgKHRoaXMuc2l6ZSA9PT0gSW5maW5pdHkpIHtcbiAgICAgIC8vIElmIHRoZSBzaXplIG9mIHRoaXMgZGF0YXNldCBpcyBpbmZpbml0eSwgbmV3IHNpemUgaXMgaW5maW5pdHlcbiAgICAgIHNpemUgPSBJbmZpbml0eTtcbiAgICB9IGVsc2Uge1xuICAgICAgLy8gSWYgdGhpcyBkYXRhc2V0IGhhcyBsaW1pdGVkIGVsZW1lbnRzLCBuZXcgc2l6ZSBpcyBudWxsIGJlY2F1c2UgaXQgbWlnaHRcbiAgICAgIC8vIGV4aGF1c3RlZCByYW5kb21seS5cbiAgICAgIHNpemUgPSBudWxsO1xuICAgIH1cbiAgICByZXR1cm4gZGF0YXNldEZyb21JdGVyYXRvckZuKGFzeW5jICgpID0+IHtcbiAgICAgIHJldHVybiAoYXdhaXQgYmFzZS5pdGVyYXRvcigpKS5maWx0ZXIoeCA9PiB0Zi50aWR5KCgpID0+IHByZWRpY2F0ZSh4KSkpO1xuICAgIH0sIHNpemUpO1xuICB9XG5cbiAgLyoqXG4gICAqIEFwcGx5IGEgZnVuY3Rpb24gdG8gZXZlcnkgZWxlbWVudCBvZiB0aGUgZGF0YXNldC5cbiAgICpcbiAgICogQWZ0ZXIgdGhlIGZ1bmN0aW9uIGlzIGFwcGxpZWQgdG8gYSBkYXRhc2V0IGVsZW1lbnQsIGFueSBUZW5zb3JzIGNvbnRhaW5lZFxuICAgKiB3aXRoaW4gdGhhdCBlbGVtZW50IGFyZSBkaXNwb3NlZC5cbiAgICpcbiAgICogYGBganNcbiAgICogY29uc3QgYSA9IHRmLmRhdGEuYXJyYXkoWzEsIDIsIDNdKTtcbiAgICogYXdhaXQgYS5mb3JFYWNoQXN5bmMoZSA9PiBjb25zb2xlLmxvZyhlKSk7XG4gICAqIGBgYFxuICAgKlxuICAgKiBAcGFyYW0gZiBBIGZ1bmN0aW9uIHRvIGFwcGx5IHRvIGVhY2ggZGF0YXNldCBlbGVtZW50LlxuICAgKiBAcmV