1 | /**
|
2 | * @license
|
3 | * Copyright 2018 Google LLC
|
4 | *
|
5 | * Use of this source code is governed by an MIT-style
|
6 | * license that can be found in the LICENSE file or at
|
7 | * https://opensource.org/licenses/MIT.
|
8 | * =============================================================================
|
9 | */
|
10 | /* Original Source: engine/training.py */
|
11 | import * as tfc from '@tensorflow/tfjs-core';
|
12 | import { io, Optimizer, scalar, serialization, Tensor, tensor1d, util } from '@tensorflow/tfjs-core';
|
13 | import * as K from '../backend/tfjs_backend';
|
14 | import { nameScope } from '../common';
|
15 | import { NotImplementedError, RuntimeError, ValueError } from '../errors';
|
16 | import { deserialize } from '../layers/serialization';
|
17 | import * as losses from '../losses';
|
18 | import * as Metrics from '../metrics';
|
19 | import * as optimizers from '../optimizers';
|
20 | import { checkUserDefinedMetadata } from '../user_defined_metadata';
|
21 | import { count, pyListRepeat, singletonOrArray, toCamelCase, toSnakeCase, unique } from '../utils/generic_utils';
|
22 | import { printSummary } from '../utils/layer_utils';
|
23 | import { range } from '../utils/math_utils';
|
24 | import { convertPythonicToTs } from '../utils/serialization_utils';
|
25 | import { version } from '../version';
|
26 | import { Container } from './container';
|
27 | import { execute, FeedDict } from './executor';
|
28 | import { evaluateDataset, fitDataset } from './training_dataset';
|
29 | import { checkBatchSize, disposeNewTensors, ensureTensorsRank2OrHigher, fitTensors, makeBatches, sliceArrays, sliceArraysByIndices } from './training_tensors';
|
30 | import { computeWeightedLoss, standardizeClassWeights, standardizeWeights } from './training_utils';
|
31 | /**
|
32 | * Helper function for polymorphic input data: 1. singleton Tensor.
|
33 | */
|
34 | export function isDataTensor(x) {
|
35 | return x instanceof Tensor;
|
36 | }
|
37 | /**
|
38 | * Helper function for polymorphic input data: 2. Array of Tensor.
|
39 | */
|
40 | export function isDataArray(x) {
|
41 | return Array.isArray(x);
|
42 | }
|
43 | /**
|
44 | * Helper function for polymorphic input data: 3. "dict" of Tensor.
|
45 | */
|
46 | export function isDataDict(x) {
|
47 | return !isDataTensor(x) && !isDataArray(x);
|
48 | }
|
49 | /**
|
50 | * Normalizes inputs and targets provided by users.
|
51 | * @param data User-provided input data (polymorphic).
|
52 | * @param names An Array of expected Tensor names.
|
53 | * @param shapes Optional Array of expected Tensor shapes.
|
54 | * @param checkBatchAxis Whether to check that the batch axis of the arrays
|
55 | * match the expected value found in `shapes`.
|
56 | * @param exceptionPrefix String prefix used for exception formatting.
|
57 | * @returns List of standardized input Tensors (one Tensor per model input).
|
58 | * @throws ValueError: in case of improperly formatted user data.
|
59 | */
|
60 | export function standardizeInputData(data, names, shapes, checkBatchAxis = true, exceptionPrefix = '') {
|
61 | if (names == null || names.length === 0) {
|
62 | // Check for the case where the model expected no data, but some data got
|
63 | // sent.
|
64 | if (data != null) {
|
65 | let gotUnexpectedData = false;
|
66 | if (isDataArray(data) && data.length > 0) {
|
67 | gotUnexpectedData = true;
|
68 | }
|
69 | else if (isDataDict(data)) {
|
70 | for (const key in data) {
|
71 | if (data.hasOwnProperty(key)) {
|
72 | gotUnexpectedData = true;
|
73 | break;
|
74 | }
|
75 | }
|
76 | }
|
77 | else {
|
78 | // `data` is a singleton Tensor in this case.
|
79 | gotUnexpectedData = true;
|
80 | }
|
81 | if (gotUnexpectedData) {
|
82 | throw new ValueError(`Error when checking model ${exceptionPrefix} expected no data, ` +
|
83 | `but got ${data}`);
|
84 | }
|
85 | }
|
86 | return [];
|
87 | }
|
88 | if (data == null) {
|
89 | return names.map(name => null);
|
90 | }
|
91 | let arrays;
|
92 | if (isDataDict(data)) {
|
93 | data = data;
|
94 | arrays = [];
|
95 | for (const name of names) {
|
96 | if (data[name] == null) {
|
97 | throw new ValueError(`No data provided for "${name}". Need data for each key in: ` +
|
98 | `${names}`);
|
99 | }
|
100 | arrays.push(data[name]);
|
101 | }
|
102 | }
|
103 | else if (isDataArray(data)) {
|
104 | data = data;
|
105 | if (data.length !== names.length) {
|
106 | throw new ValueError(`Error when checking model ${exceptionPrefix}: the Array of ` +
|
107 | `Tensors that you are passing to your model is not the size the ` +
|
108 | `model expected. Expected to see ${names.length} Tensor(s), but ` +
|
109 | `instead got the following list of Tensor(s): ${data}`);
|
110 | }
|
111 | arrays = data;
|
112 | }
|
113 | else {
|
114 | data = data;
|
115 | if (names.length > 1) {
|
116 | throw new ValueError(`The model ${exceptionPrefix} expects ${names.length} Tensor(s), ` +
|
117 | `but only received one Tensor. Found: Tensor with shape ${data.shape}`);
|
118 | }
|
119 | arrays = [data];
|
120 | }
|
121 | arrays = ensureTensorsRank2OrHigher(arrays);
|
122 | // Check shape compatibility.
|
123 | if (shapes != null) {
|
124 | for (let i = 0; i < names.length; ++i) {
|
125 | if (shapes[i] == null) {
|
126 | continue;
|
127 | }
|
128 | const array = arrays[i];
|
129 | if (array.shape.length !== shapes[i].length) {
|
130 | throw new ValueError(`Error when checking ${exceptionPrefix}: expected ${names[i]} ` +
|
131 | `to have ${shapes[i].length} dimension(s). but got array with ` +
|
132 | `shape ${array.shape}`);
|
133 | }
|
134 | for (let j = 0; j < shapes[i].length; ++j) {
|
135 | if (j === 0 && !checkBatchAxis) {
|
136 | // Skip the first (batch) axis.
|
137 | continue;
|
138 | }
|
139 | const dim = array.shape[j];
|
140 | const refDim = shapes[i][j];
|
141 | if (refDim != null && refDim >= 0 && dim !== refDim) {
|
142 | throw new ValueError(`${exceptionPrefix} expected a batch of elements where each ` +
|
143 | `example has shape [${shapes[i].slice(1, shapes[i].length)}] ` +
|
144 | `(i.e.,tensor shape [*,${shapes[i].slice(1, shapes[i].length)}])` +
|
145 | ` but the ${exceptionPrefix} received an input with ${array.shape[0]}` +
|
146 | ` examples, each with shape [${array.shape.slice(1, array.shape.length)}]` +
|
147 | ` (tensor shape [${array.shape}])`);
|
148 | }
|
149 | }
|
150 | }
|
151 | }
|
152 | return arrays;
|
153 | }
|
154 | /**
|
155 | * User input validation for Tensors.
|
156 | * @param inputs `Array` of `tf.Tensor`s for inputs.
|
157 | * @param targets `Array` of `tf.Tensor`s for targets.
|
158 | * @param weights Optional `Array` of `tf.Tensor`s for sample weights.
|
159 | * @throws ValueError: in case of incorrectly formatted data.
|
160 | */
|
161 | export function checkArrayLengths(inputs, targets, weights) {
|
162 | const setX = unique(inputs.map(input => input.shape[0]));
|
163 | setX.sort();
|
164 | const setY = unique(targets.map(target => target.shape[0]));
|
165 | setY.sort();
|
166 | // TODO(cais): Check `weights` as well.
|
167 | if (setX.length > 1) {
|
168 | throw new ValueError(`All input Tensors (x) should have the same number of samples. ` +
|
169 | `Got array shapes: ` +
|
170 | `${JSON.stringify(inputs.map(input => input.shape))}`);
|
171 | }
|
172 | if (setY.length > 1) {
|
173 | throw new ValueError(`All target Tensors (y) should have the same number of samples. ` +
|
174 | `Got array shapes: ` +
|
175 | `${JSON.stringify(targets.map(target => target.shape))}`);
|
176 | }
|
177 | if (setX.length > 0 && setY.length > 0 && !util.arraysEqual(setX, setY)) {
|
178 | throw new ValueError(`Input Tensors should have the same number of samples as target ` +
|
179 | `Tensors. Found ${setX[0]} input sample(s) and ${setY[0]} target ` +
|
180 | `sample(s).`);
|
181 | }
|
182 | }
|
183 | /**
|
184 | * Validation on the compatibility of targes and loss functions.
|
185 | *
|
186 | * This helps prevent users from using loss functions incorrectly.
|
187 | *
|
188 | * @param targets `Array` of `tf.Tensor`s of targets.
|
189 | * @param lossFns `Array` of loss functions.
|
190 | * @param outputShapes `Array` of shapes of model outputs.
|
191 | */
|
192 | function checkLossAndTargetCompatibility(targets, lossFns, outputShapes) {
|
193 | // TODO(cais): Dedicated test coverage?
|
194 | const keyLosses = [
|
195 | losses.meanSquaredError, losses.binaryCrossentropy,
|
196 | losses.categoricalCrossentropy
|
197 | ];
|
198 | for (let i = 0; i < targets.length; ++i) {
|
199 | const y = targets[i];
|
200 | const loss = lossFns[i];
|
201 | const shape = outputShapes[i];
|
202 | if (loss == null) {
|
203 | continue;
|
204 | }
|
205 | if (loss === losses.categoricalCrossentropy) {
|
206 | if (y.shape[y.shape.length - 1] === 1) {
|
207 | throw new ValueError(`You are passing a target array of shape ${y.shape} while using ` +
|
208 | `a loss 'categorical_crossentropy'. 'categorical_crossentropy'` +
|
209 | `expects targets to be binary matrices (1s and 0s) of shape ` +
|
210 | `[samples, classes].`);
|
211 | // TODO(cais): Example code in error message.
|
212 | }
|
213 | }
|
214 | if (keyLosses.indexOf(loss) !== -1) {
|
215 | const slicedYShape = y.shape.slice(1);
|
216 | const slicedShape = shape.slice(1);
|
217 | for (let j = 0; j < slicedYShape.length; ++j) {
|
218 | const targetDim = slicedYShape[j];
|
219 | const outDim = slicedShape[j];
|
220 | if (outDim != null && targetDim !== outDim) {
|
221 | throw new ValueError(`A target Tensor with shape ${y.shape} was passed for an ` +
|
222 | `output of shape ${shape}, while using a loss function that ` +
|
223 | `expects targets to have the same shape as the output.`);
|
224 | }
|
225 | }
|
226 | }
|
227 | }
|
228 | }
|
229 | /**
|
230 | * Check inputs provided by the user.
|
231 | *
|
232 | * Porting Note: This corresponds to _standardize_input_data() in Python
|
233 | * Keras. Because of the strong typing in TF.js, we do not need to convert
|
234 | * the data. Specifically:
|
235 | * 1) in PyKeras, `data` can be `DataFrame` instances from pandas, for
|
236 | * example. We don't need to worry about that here because there is no
|
237 | * widely popular javascript/typesdcript equivalent of pandas (so far).
|
238 | * If one becomes available in the future, we can add support.
|
239 | * 2) in PyKeras, inputs can be Python dict. But here we are stipulating
|
240 | * that the data is either a single `tf.Tensor` or an Array of `tf.Tensor`s. We
|
241 | * may add support for `Object` data inputs in the future when the need
|
242 | * arises.
|
243 | *
|
244 | * Instead, we perform basic checks for number of parameters and shapes.
|
245 | *
|
246 | * @param data: The input data.
|
247 | * @param names: Name for the inputs, from the model.
|
248 | * @param shapes: Expected shapes for the input data, from the model.
|
249 | * @param checkBatchAxis: Whether the size along the batch axis (i.e., the
|
250 | * first dimension) will be checked for matching.
|
251 | * @param exceptionPrefix: Execption prefix message, used in generating error
|
252 | * messages.
|
253 | * @throws ValueError: on incorrect number of inputs or mismatches in shapes.
|
254 | */
|
255 | function checkInputData(data, names, shapes, checkBatchAxis = true, exceptionPrefix = '') {
|
256 | let arrays;
|
257 | if (Array.isArray(data)) {
|
258 | if (data.length !== names.length) {
|
259 | throw new ValueError(`Error when checking model ${exceptionPrefix}: the Array of ` +
|
260 | `Tensors that you are passing to your model is not the size the ` +
|
261 | `the model expected. Expected to see ${names.length} Tensor(s),` +
|
262 | ` but instead got ${data.length} Tensors(s).`);
|
263 | }
|
264 | arrays = data;
|
265 | }
|
266 | else {
|
267 | if (names.length > 1) {
|
268 | throw new ValueError(`The model expects ${names.length} ${exceptionPrefix} Tensors, ` +
|
269 | `but only received one Tensor. Found: array with shape ` +
|
270 | `${JSON.stringify(data.shape)}.`);
|
271 | }
|
272 | arrays = [data];
|
273 | }
|
274 | if (shapes != null) {
|
275 | for (let i = 0; i < names.length; ++i) {
|
276 | if (shapes[i] == null) {
|
277 | continue;
|
278 | }
|
279 | const array = arrays[i];
|
280 | if (array.shape.length !== shapes[i].length) {
|
281 | throw new ValueError(`Error when checking ${exceptionPrefix}: expected ${names[i]} ` +
|
282 | `to have ${shapes[i].length} dimension(s), but got array with ` +
|
283 | `shape ${JSON.stringify(array.shape)}`);
|
284 | }
|
285 | for (let j = 0; j < shapes[i].length; ++j) {
|
286 | if (j === 0 && !checkBatchAxis) {
|
287 | continue;
|
288 | }
|
289 | const dim = array.shape[j];
|
290 | const refDim = shapes[i][j];
|
291 | if (refDim != null) {
|
292 | if (refDim !== dim) {
|
293 | throw new ValueError(`Error when checking ${exceptionPrefix}: expected ` +
|
294 | `${names[i]} to have shape ${JSON.stringify(shapes[i])} but ` +
|
295 | `got array with shape ${JSON.stringify(array.shape)}.`);
|
296 | }
|
297 | }
|
298 | }
|
299 | }
|
300 | }
|
301 | }
|
302 | /**
|
303 | * Maps metric functions to model outputs.
|
304 | * @param metrics An shortcut strings name, metric function, `Array` or dict
|
305 | * (`Object`) of metric functions.
|
306 | * @param outputNames An `Array` of the names of model outputs.
|
307 | * @returns An `Array` (one entry per model output) of `Array` of metric
|
308 | * functions. For instance, if the model has 2 outputs, and for the first
|
309 | * output we want to compute `binaryAccuracy` and `binaryCrossentropy`,
|
310 | * and just `binaryAccuracy` for the second output, the `Array` would look
|
311 | * like:
|
312 | * `[[binaryAccuracy, binaryCrossentropy], [binaryAccuracy]]`
|
313 | * @throws TypeError: incompatible metrics format.
|
314 | */
|
315 | export function collectMetrics(metrics, outputNames) {
|
316 | if (metrics == null || Array.isArray(metrics) && metrics.length === 0) {
|
317 | return outputNames.map(name => []);
|
318 | }
|
319 | let wrappedMetrics;
|
320 | if (typeof metrics === 'string' || typeof metrics === 'function') {
|
321 | wrappedMetrics = [metrics];
|
322 | }
|
323 | else if (Array.isArray(metrics) || typeof metrics === 'object') {
|
324 | wrappedMetrics = metrics;
|
325 | }
|
326 | else {
|
327 | throw new TypeError('Type of metrics argument not understood. Expected an string,' +
|
328 | `function, Array, or Object, found: ${metrics}`);
|
329 | }
|
330 | if (Array.isArray(wrappedMetrics)) {
|
331 | // We then apply all metrics to all outputs.
|
332 | return outputNames.map(name => wrappedMetrics);
|
333 | }
|
334 | else {
|
335 | // In this case, metrics is a dict.
|
336 | const nestedMetrics = [];
|
337 | for (const name of outputNames) {
|
338 | let outputMetrics = wrappedMetrics.hasOwnProperty(name) ? wrappedMetrics[name] : [];
|
339 | if (!Array.isArray(outputMetrics)) {
|
340 | outputMetrics = [outputMetrics];
|
341 | }
|
342 | nestedMetrics.push(outputMetrics);
|
343 | }
|
344 | return nestedMetrics;
|
345 | }
|
346 | }
|
347 | const LAYERS_MODEL_FORMAT_NAME = 'layers-model';
|
348 | /**
|
349 | * A `tf.LayersModel` is a directed, acyclic graph of `tf.Layer`s plus methods
|
350 | * for training, evaluation, prediction and saving.
|
351 | *
|
352 | * `tf.LayersModel` is the basic unit of training, inference and evaluation in
|
353 | * TensorFlow.js. To create a `tf.LayersModel`, use `tf.LayersModel`.
|
354 | *
|
355 | * See also:
|
356 | * `tf.Sequential`, `tf.loadLayersModel`.
|
357 | *
|
358 | * @doc {heading: 'Models', subheading: 'Classes'}
|
359 | */
|
360 | export class LayersModel extends Container {
|
361 | constructor(args) {
|
362 | super(args);
|
363 | this.isTraining = false;
|
364 | }
|
365 | /**
|
366 | * Print a text summary of the model's layers.
|
367 | *
|
368 | * The summary includes
|
369 | * - Name and type of all layers that comprise the model.
|
370 | * - Output shape(s) of the layers
|
371 | * - Number of weight parameters of each layer
|
372 | * - If the model has non-sequential-like topology, the inputs each layer
|
373 | * receives
|
374 | * - The total number of trainable and non-trainable parameters of the model.
|
375 | *
|
376 | * ```js
|
377 | * const input1 = tf.input({shape: [10]});
|
378 | * const input2 = tf.input({shape: [20]});
|
379 | * const dense1 = tf.layers.dense({units: 4}).apply(input1);
|
380 | * const dense2 = tf.layers.dense({units: 8}).apply(input2);
|
381 | * const concat = tf.layers.concatenate().apply([dense1, dense2]);
|
382 | * const output =
|
383 | * tf.layers.dense({units: 3, activation: 'softmax'}).apply(concat);
|
384 | *
|
385 | * const model = tf.model({inputs: [input1, input2], outputs: output});
|
386 | * model.summary();
|
387 | * ```
|
388 | *
|
389 | * @param lineLength Custom line length, in number of characters.
|
390 | * @param positions Custom widths of each of the columns, as either
|
391 | * fractions of `lineLength` (e.g., `[0.5, 0.75, 1]`) or absolute number
|
392 | * of characters (e.g., `[30, 50, 65]`). Each number corresponds to
|
393 | * right-most (i.e., ending) position of a column.
|
394 | * @param printFn Custom print function. Can be used to replace the default
|
395 | * `console.log`. For example, you can use `x => {}` to mute the printed
|
396 | * messages in the console.
|
397 | *
|
398 | * @doc {heading: 'Models', subheading: 'Classes'}
|
399 | */
|
400 | summary(lineLength, positions, printFn = console.log) {
|
401 | if (!this.built) {
|
402 | throw new ValueError(`This model has never been called, thus its weights have not been ` +
|
403 | `created yet. So no summary can be displayed. Build the model ` +
|
404 | `first (e.g., by calling it on some test data).`);
|
405 | }
|
406 | printSummary(this, lineLength, positions, printFn);
|
407 | }
|
408 | /**
|
409 | * Configures and prepares the model for training and evaluation. Compiling
|
410 | * outfits the model with an optimizer, loss, and/or metrics. Calling `fit`
|
411 | * or `evaluate` on an un-compiled model will throw an error.
|
412 | *
|
413 | * @param args a `ModelCompileArgs` specifying the loss, optimizer, and
|
414 | * metrics to be used for fitting and evaluating this model.
|
415 | *
|
416 | * @doc {heading: 'Models', subheading: 'Classes'}
|
417 | */
|
418 | compile(args) {
|
419 | if (args.loss == null) {
|
420 | args.loss = [];
|
421 | }
|
422 | this.loss = args.loss;
|
423 | if (typeof args.optimizer === 'string') {
|
424 | this.optimizer_ = optimizers.getOptimizer(args.optimizer);
|
425 | this.isOptimizerOwned = true;
|
426 | }
|
427 | else {
|
428 | if (!(args.optimizer instanceof Optimizer)) {
|
429 | throw new ValueError(`User-defined optimizer must be an instance of tf.Optimizer.`);
|
430 | }
|
431 | this.optimizer_ = args.optimizer;
|
432 | this.isOptimizerOwned = false;
|
433 | }
|
434 | // TODO(cais): Add lossWeights.
|
435 | // TODO(cais): Add sampleWeightMode.
|
436 | // Prepare loss functions.
|
437 | let lossFunctions = [];
|
438 | if (!Array.isArray(args.loss) && typeof args.loss !== 'string' &&
|
439 | typeof args.loss !== 'function') {
|
440 | args.loss = args.loss;
|
441 | for (const name in args.loss) {
|
442 | if (this.outputNames.indexOf(name) === -1) {
|
443 | throw new ValueError(`Unknown entry in loss dictionary: "${name}". ` +
|
444 | `Only expected the following keys: ${this.outputNames}`);
|
445 | }
|
446 | }
|
447 | for (const name of this.outputNames) {
|
448 | if (args.loss[name] == null) {
|
449 | console.warn(`Output "${name}" is missing from loss dictionary. We assume ` +
|
450 | `this was done on purpose, and we will not be expecting data ` +
|
451 | `to be passed to ${name} during training`);
|
452 | }
|
453 | lossFunctions.push(losses.get(args.loss[name]));
|
454 | }
|
455 | }
|
456 | else if (Array.isArray(args.loss)) {
|
457 | if (args.loss.length !== this.outputs.length) {
|
458 | throw new ValueError(`When passing an Array as loss, it should have one entry per ` +
|
459 | `model output. The model has ${this.outputs.length} output(s), ` +
|
460 | `but you passed loss=${args.loss}.`);
|
461 | }
|
462 | const theLosses = args.loss;
|
463 | lossFunctions = theLosses.map(l => losses.get(l));
|
464 | }
|
465 | else {
|
466 | const lossFunction = losses.get(args.loss);
|
467 | this.outputs.forEach(_ => {
|
468 | lossFunctions.push(lossFunction);
|
469 | });
|
470 | }
|
471 | this.lossFunctions = lossFunctions;
|
472 | this.feedOutputNames = [];
|
473 | this.feedOutputShapes = [];
|
474 | this.feedLossFns = [];
|
475 | for (let i = 0; i < this.outputs.length; ++i) {
|
476 | // TODO(cais): Logic for skipping target(s).
|
477 | const shape = this.internalOutputShapes[i];
|
478 | const name = this.outputNames[i];
|
479 | this.feedOutputNames.push(name);
|
480 | this.feedOutputShapes.push(shape);
|
481 | this.feedLossFns.push(this.lossFunctions[i]);
|
482 | }
|
483 | // TODO(cais): Add logic for output masks.
|
484 | // TODO(cais): Add logic for sample weights.
|
485 | const skipTargetIndices = [];
|
486 | // Prepare metrics.
|
487 | this.metrics = args.metrics;
|
488 | // TODO(cais): Add weightedMetrics.
|
489 | this.metricsNames = ['loss'];
|
490 | this.metricsTensors = [];
|
491 | // Compute total loss.
|
492 | // Porting Note: In PyKeras, metrics_tensors are symbolic tensor objects.
|
493 | // Here, metricsTensors are TypeScript functions. This difference is due
|
494 | // to the difference in symbolic/imperative property of the backends.
|
495 | nameScope('loss', () => {
|
496 | for (let i = 0; i < this.outputs.length; ++i) {
|
497 | if (skipTargetIndices.indexOf(i) !== -1) {
|
498 | continue;
|
499 | }
|
500 | // TODO(cais): Add weightedLoss, sampleWeight and mask.
|
501 | // The following line should be weightedLoss
|
502 | const weightedLoss = this.lossFunctions[i];
|
503 | if (this.outputs.length > 1) {
|
504 | this.metricsTensors.push([weightedLoss, i]);
|
505 | this.metricsNames.push(this.outputNames[i] + '_loss');
|
506 | }
|
507 | }
|
508 | // Porting Note: Due to the imperative nature of the backend, we calculate
|
509 | // the regularizer penalties in the totalLossFunction, instead of here.
|
510 | });
|
511 | const nestedMetrics = collectMetrics(args.metrics, this.outputNames);
|
512 | // TODO(cais): Add nestedWeightedMetrics.
|
513 | /**
|
514 | * Helper function used in loop below.
|
515 | */
|
516 | const appendMetric = (outputIndex, metricName, metricTensor) => {
|
517 | if (this.outputNames.length > 1) {
|
518 | metricName = this.outputNames[outputIndex] + '_' + metricName;
|
519 | }
|
520 | this.metricsNames.push(metricName);
|
521 | this.metricsTensors.push([metricTensor, outputIndex]);
|
522 | };
|
523 | nameScope('metric', () => {
|
524 | for (let i = 0; i < this.outputs.length; ++i) {
|
525 | if (skipTargetIndices.indexOf(i) !== -1) {
|
526 | continue;
|
527 | }
|
528 | const outputMetrics = nestedMetrics[i];
|
529 | // TODO(cais): Add weights and outputWeightedMetrics.
|
530 | // TODO(cais): Add optional arg `weights` to the following function.
|
531 | const handleMetrics = (metrics) => {
|
532 | const metricNamePrefix = '';
|
533 | let metricName;
|
534 | let accFn;
|
535 | let weightedMetricFn;
|
536 | // TODO(cais): Use 'weights_' for weighted metrics.
|
537 | for (const metric of metrics) {
|
538 | if (typeof metric === 'string' &&
|
539 | ['accuracy', 'acc', 'crossentropy', 'ce'].indexOf(metric) !==
|
540 | -1) {
|
541 | const outputShape = this.internalOutputShapes[i];
|
542 | if (outputShape[outputShape.length - 1] === 1 ||
|
543 | this.lossFunctions[i] === losses.binaryCrossentropy) {
|
544 | // case: binary accuracy/crossentropy.
|
545 | if (['accuracy', 'acc'].indexOf(metric) !== -1) {
|
546 | accFn = Metrics.binaryAccuracy;
|
547 | }
|
548 | else if (['crossentropy', 'ce'].indexOf(metric) !== -1) {
|
549 | accFn = Metrics.binaryCrossentropy;
|
550 | }
|
551 | }
|
552 | else if (this.lossFunctions[i] ===
|
553 | losses.sparseCategoricalCrossentropy) {
|
554 | // case: categorical accuracy / crossentropy with sparse
|
555 | // targets.
|
556 | if (['accuracy', 'acc'].indexOf(metric) !== -1) {
|
557 | accFn = Metrics.sparseCategoricalAccuracy;
|
558 | }
|
559 | else if (['crossentropy', 'ce'].indexOf(metric) !== -1) {
|
560 | accFn = Metrics.sparseCategoricalCrossentropy;
|
561 | }
|
562 | }
|
563 | else {
|
564 | // case: categorical accuracy / crossentropy.
|
565 | if (['accuracy', 'acc'].indexOf(metric) !== -1) {
|
566 | accFn = Metrics.categoricalAccuracy;
|
567 | }
|
568 | else if (['crossentropy', 'ce'].indexOf(metric) !== -1) {
|
569 | accFn = Metrics.categoricalCrossentropy;
|
570 | }
|
571 | }
|
572 | let suffix;
|
573 | if (['accuracy', 'acc'].indexOf(metric) !== -1) {
|
574 | suffix = 'acc';
|
575 | }
|
576 | else if (['crossentropy', 'ce'].indexOf(metric) !== -1) {
|
577 | suffix = 'ce';
|
578 | }
|
579 | // TODO(cais): Add weighting actually.
|
580 | weightedMetricFn = accFn;
|
581 | metricName = metricNamePrefix + suffix;
|
582 | }
|
583 | else {
|
584 | const metricFn = Metrics.get(metric);
|
585 | // TODO(cais): Add weighting actually.
|
586 | weightedMetricFn = metricFn;
|
587 | metricName =
|
588 | metricNamePrefix + Metrics.getLossOrMetricName(metric);
|
589 | }
|
590 | // TODO(cais): Add weighting and masking to metricResult.
|
591 | let metricResult;
|
592 | nameScope(metricName, () => {
|
593 | metricResult = weightedMetricFn;
|
594 | });
|
595 | appendMetric(i, metricName, metricResult);
|
596 | }
|
597 | };
|
598 | handleMetrics(outputMetrics);
|
599 | // TODO(cais): Call handleMetrics with weights.
|
600 | }
|
601 | });
|
602 | // Porting Notes: Given the imperative backend of tfjs-core,
|
603 | // there is no need for constructing the symbolic graph and placeholders.
|
604 | this.collectedTrainableWeights = this.trainableWeights;
|
605 | }
|
606 | /**
|
607 | * Check trainable weights count consistency.
|
608 | *
|
609 | * This will raise a warning if `this.trainableWeights` and
|
610 | * `this.collectedTrainableWeights` are inconsistent (i.e., have different
|
611 | * numbers of parameters).
|
612 | * Inconsistency will typically arise when one modifies `model.trainable`
|
613 | * without calling `model.compile()` again.
|
614 | */
|
615 | checkTrainableWeightsConsistency() {
|
616 | if (this.collectedTrainableWeights == null) {
|
617 | return;
|
618 | }
|
619 | if (this.trainableWeights.length !==
|
620 | this.collectedTrainableWeights.length) {
|
621 | console.warn('Discrepancy between trainableweights and collected trainable ' +
|
622 | 'weights. Did you set `model.trainable` without calling ' +
|
623 | '`model.compile()` afterwards?');
|
624 | }
|
625 | }
|
626 | /**
|
627 | * Returns the loss value & metrics values for the model in test mode.
|
628 | *
|
629 | * Loss and metrics are specified during `compile()`, which needs to happen
|
630 | * before calls to `evaluate()`.
|
631 | *
|
632 | * Computation is done in batches.
|
633 | *
|
634 | * ```js
|
635 | * const model = tf.sequential({
|
636 | * layers: [tf.layers.dense({units: 1, inputShape: [10]})]
|
637 | * });
|
638 | * model.compile({optimizer: 'sgd', loss: 'meanSquaredError'});
|
639 | * const result = model.evaluate(
|
640 | * tf.ones([8, 10]), tf.ones([8, 1]), {batchSize: 4});
|
641 | * result.print();
|
642 | * ```
|
643 | *
|
644 | * @param x `tf.Tensor` of test data, or an `Array` of `tf.Tensor`s if the
|
645 | * model has multiple inputs.
|
646 | * @param y `tf.Tensor` of target data, or an `Array` of `tf.Tensor`s if the
|
647 | * model has multiple outputs.
|
648 | * @param args A `ModelEvaluateArgs`, containing optional fields.
|
649 | *
|
650 | * @return `Scalar` test loss (if the model has a single output and no
|
651 | * metrics) or `Array` of `Scalar`s (if the model has multiple outputs
|
652 | * and/or metrics). The attribute `model.metricsNames`
|
653 | * will give you the display labels for the scalar outputs.
|
654 | *
|
655 | * @doc {heading: 'Models', subheading: 'Classes'}
|
656 | */
|
657 | evaluate(x, y, args = {}) {
|
658 | const batchSize = args.batchSize == null ? 32 : args.batchSize;
|
659 | checkBatchSize(batchSize);
|
660 | // TODO(cais): Standardize `config.sampleWeights` as well.
|
661 | // Validate user data.
|
662 | const checkBatchAxis = true;
|
663 | const standardizedOuts = this.standardizeUserDataXY(x, y, checkBatchAxis, batchSize);
|
664 | try {
|
665 | // TODO(cais): If uses `useLearningPhase`, set the corresponding element
|
666 | // of the input to 0.
|
667 | const ins = standardizedOuts[0].concat(standardizedOuts[1]);
|
668 | this.makeTestFunction();
|
669 | const f = this.testFunction;
|
670 | const testOuts = this.testLoop(f, ins, batchSize, args.verbose, args.steps);
|
671 | return singletonOrArray(testOuts);
|
672 | }
|
673 | finally {
|
674 | disposeNewTensors(standardizedOuts[0], x);
|
675 | disposeNewTensors(standardizedOuts[1], y);
|
676 | }
|
677 | }
|
678 | // TODO(cais): Add code snippet below once real dataset objects are
|
679 | // available.
|
680 | /**
|
681 | * Evaluate model using a dataset object.
|
682 | *
|
683 | * Note: Unlike `evaluate()`, this method is asynchronous (`async`);
|
684 | *
|
685 | * @param dataset A dataset object. Its `iterator()` method is expected
|
686 | * to generate a dataset iterator object, the `next()` method of which
|
687 | * is expected to produce data batches for evaluation. The return value
|
688 | * of the `next()` call ought to contain a boolean `done` field and a
|
689 | * `value` field. The `value` field is expected to be an array of two
|
690 | * `tf.Tensor`s or an array of two nested `tf.Tensor` structures. The former
|
691 | * case is for models with exactly one input and one output (e.g..
|
692 | * a sequential model). The latter case is for models with multiple
|
693 | * inputs and/or multiple outputs. Of the two items in the array, the
|
694 | * first is the input feature(s) and the second is the output target(s).
|
695 | * @param args A configuration object for the dataset-based evaluation.
|
696 | * @returns Loss and metric values as an Array of `Scalar` objects.
|
697 | *
|
698 | * @doc {heading: 'Models', subheading: 'Classes'}
|
699 | */
|
700 | async evaluateDataset(dataset, args) {
|
701 | this.makeTestFunction();
|
702 | return evaluateDataset(this, dataset, args);
|
703 | }
|
704 | /**
|
705 | * Get number of samples provided for training, evaluation or prediction.
|
706 | *
|
707 | * @param ins Input `tf.Tensor`.
|
708 | * @param batchSize Integer batch size, optional.
|
709 | * @param steps Total number of steps (batches of samples) before
|
710 | * declaring loop finished. Optional.
|
711 | * @param stepsName The public API's parameter name for `steps`.
|
712 | * @returns Number of samples provided.
|
713 | */
|
714 | checkNumSamples(ins, batchSize, steps, stepsName = 'steps') {
|
715 | let numSamples;
|
716 | if (steps != null) {
|
717 | numSamples = null;
|
718 | if (batchSize != null) {
|
719 | throw new ValueError(`If ${stepsName} is set, batchSize must be null or undefined.` +
|
720 | `Got batchSize = ${batchSize}`);
|
721 | }
|
722 | }
|
723 | else if (ins != null) {
|
724 | if (Array.isArray(ins)) {
|
725 | numSamples = ins[0].shape[0];
|
726 | }
|
727 | else {
|
728 | numSamples = ins.shape[0];
|
729 | }
|
730 | }
|
731 | else {
|
732 | throw new ValueError(`Either the input data should have a defined shape, or ` +
|
733 | `${stepsName} shoud be specified.`);
|
734 | }
|
735 | return numSamples;
|
736 | }
|
737 | /**
|
738 | * Execute internal tensors of the model with input data feed.
|
739 | * @param inputs Input data feed. Must match the inputs of the model.
|
740 | * @param outputs Names of the output tensors to be fetched. Must match
|
741 | * names of the SymbolicTensors that belong to the graph.
|
742 | * @returns Fetched values for `outputs`.
|
743 | */
|
744 | execute(inputs, outputs) {
|
745 | if (Array.isArray(outputs) && outputs.length === 0) {
|
746 | throw new ValueError('`outputs` is an empty Array, which is not allowed.');
|
747 | }
|
748 | const outputsIsArray = Array.isArray(outputs);
|
749 | const outputNames = (outputsIsArray ? outputs : [outputs]);
|
750 | const outputSymbolicTensors = this.retrieveSymbolicTensors(outputNames);
|
751 | // Format the input into a FeedDict.
|
752 | const feedDict = new FeedDict();
|
753 | if (inputs instanceof Tensor) {
|
754 | inputs = [inputs];
|
755 | }
|
756 | if (Array.isArray(inputs)) {
|
757 | if (inputs.length !== this.inputs.length) {
|
758 | throw new ValueError(`The number of inputs provided (${inputs.length}) ` +
|
759 | `does not match the number of inputs of this model ` +
|
760 | `(${this.inputs.length}).`);
|
761 | }
|
762 | for (let i = 0; i < this.inputs.length; ++i) {
|
763 | feedDict.add(this.inputs[i], inputs[i]);
|
764 | }
|
765 | }
|
766 | else {
|
767 | for (const input of this.inputs) {
|
768 | const tensorValue = inputs[input.name];
|
769 | if (tensorValue == null) {
|
770 | throw new ValueError(`No value is provided for the model's input ${input.name}`);
|
771 | }
|
772 | feedDict.add(input, tensorValue);
|
773 | }
|
774 | }
|
775 | // Run execution.
|
776 | const executeOutputs = execute(outputSymbolicTensors, feedDict);
|
777 | return outputsIsArray ? executeOutputs : executeOutputs[0];
|
778 | }
|
779 | /**
|
780 | * Retrieve the model's internal symbolic tensors from symbolic-tensor names.
|
781 | */
|
782 | retrieveSymbolicTensors(symbolicTensorNames) {
|
783 | const outputSymbolicTensors = pyListRepeat(null, symbolicTensorNames.length);
|
784 | let outputsRemaining = symbolicTensorNames.length;
|
785 | for (const layer of this.layers) {
|
786 | const layerOutputs = Array.isArray(layer.output) ? layer.output : [layer.output];
|
787 | const layerOutputNames = layerOutputs.map(output => output.name);
|
788 | for (let i = 0; i < symbolicTensorNames.length; ++i) {
|
789 | const index = layerOutputNames.indexOf(symbolicTensorNames[i]);
|
790 | if (index !== -1) {
|
791 | outputSymbolicTensors[i] = layerOutputs[index];
|
792 | outputsRemaining--;
|
793 | }
|
794 | if (outputsRemaining === 0) {
|
795 | break;
|
796 | }
|
797 | }
|
798 | if (outputsRemaining === 0) {
|
799 | break;
|
800 | }
|
801 | }
|
802 | if (outputsRemaining > 0) {
|
803 | const remainingNames = [];
|
804 | outputSymbolicTensors.forEach((tensor, i) => {
|
805 | if (tensor == null) {
|
806 | remainingNames.push(symbolicTensorNames[i]);
|
807 | }
|
808 | });
|
809 | throw new ValueError(`Cannot find SymbolicTensors for output name(s): ` +
|
810 | `${JSON.stringify(remainingNames)}`);
|
811 | }
|
812 | return outputSymbolicTensors;
|
813 | }
|
814 | /**
|
815 | * Helper method to loop over some data in batches.
|
816 | *
|
817 | * Porting Note: Not using the functional approach in the Python equivalent
|
818 | * due to the imperative backend.
|
819 | * Porting Note: Does not support step mode currently.
|
820 | *
|
821 | * @param ins: input data
|
822 | * @param batchSize: integer batch size.
|
823 | * @param verbose: verbosity model
|
824 | * @returns: Predictions as `tf.Tensor` (if a single output) or an `Array` of
|
825 | * `tf.Tensor` (if multipe outputs).
|
826 | */
|
827 | predictLoop(ins, batchSize = 32, verbose = false) {
|
828 | return tfc.tidy(() => {
|
829 | const numSamples = this.checkNumSamples(ins);
|
830 | if (verbose) {
|
831 | throw new NotImplementedError('Verbose predictLoop() is not implemented yet.');
|
832 | }
|
833 | // Sample-based predictions.
|
834 | // Porting Note: Tensor currently does not support sliced assignments as
|
835 | // in numpy, e.g., x[1:3] = y. Therefore we use concatenation while
|
836 | // iterating over the batches.
|
837 | const batches = makeBatches(numSamples, batchSize);
|
838 | const outsBatches = this.outputs.map(output => []);
|
839 | // TODO(cais): Can the scope() be pushed down inside the for loop?
|
840 | for (let batchIndex = 0; batchIndex < batches.length; ++batchIndex) {
|
841 | const batchOuts = tfc.tidy(() => {
|
842 | const batchStart = batches[batchIndex][0];
|
843 | const batchEnd = batches[batchIndex][1];
|
844 | // TODO(cais): Take care of the case of the last element is a flag for
|
845 | // training/test.
|
846 | const insBatch = sliceArrays(ins, batchStart, batchEnd);
|
847 | // Construct the feeds for execute();
|
848 | const feeds = [];
|
849 | if (Array.isArray(insBatch)) {
|
850 | for (let i = 0; i < insBatch.length; ++i) {
|
851 | feeds.push({ key: this.inputs[i], value: insBatch[i] });
|
852 | }
|
853 | }
|
854 | else {
|
855 | feeds.push({ key: this.inputs[0], value: insBatch });
|
856 | }
|
857 | const feedDict = new FeedDict(feeds);
|
858 | return execute(this.outputs, feedDict);
|
859 | });
|
860 | batchOuts.forEach((batchOut, i) => outsBatches[i].push(batchOut));
|
861 | }
|
862 | return singletonOrArray(outsBatches.map(batches => tfc.concat(batches, 0)));
|
863 | });
|
864 | }
|
865 | /**
|
866 | * Generates output predictions for the input samples.
|
867 | *
|
868 | * Computation is done in batches.
|
869 | *
|
870 | * Note: the "step" mode of predict() is currently not supported.
|
871 | * This is because the TensorFlow.js core backend is imperative only.
|
872 | *
|
873 | * ```js
|
874 | * const model = tf.sequential({
|
875 | * layers: [tf.layers.dense({units: 1, inputShape: [10]})]
|
876 | * });
|
877 | * model.predict(tf.ones([8, 10]), {batchSize: 4}).print();
|
878 | * ```
|
879 | *
|
880 | * @param x The input data, as a Tensor, or an `Array` of `tf.Tensor`s if
|
881 | * the model has multiple inputs.
|
882 | * @param args A `ModelPredictArgs` object containing optional fields.
|
883 | *
|
884 | * @return Prediction results as a `tf.Tensor`(s).
|
885 | *
|
886 | * @exception ValueError In case of mismatch between the provided input data
|
887 | * and the model's expectations, or in case a stateful model receives a
|
888 | * number of samples that is not a multiple of the batch size.
|
889 | *
|
890 | * @doc {heading: 'Models', subheading: 'Classes'}
|
891 | */
|
892 | predict(x, args = {}) {
|
893 | const xsRank2OrHigher = ensureTensorsRank2OrHigher(x);
|
894 | checkInputData(xsRank2OrHigher, this.inputNames, this.feedInputShapes, false);
|
895 | try {
|
896 | // TODO(cais): Take care of stateful models.
|
897 | // if (this.stateful) ...
|
898 | // TODO(cais): Take care of the learning_phase boolean flag.
|
899 | // if (this.useLearningPhase) ...
|
900 | const batchSize = args.batchSize == null ? 32 : args.batchSize;
|
901 | checkBatchSize(batchSize);
|
902 | return this.predictLoop(xsRank2OrHigher, batchSize);
|
903 | }
|
904 | finally {
|
905 | disposeNewTensors(xsRank2OrHigher, x);
|
906 | }
|
907 | }
|
908 | /**
|
909 | * Returns predictions for a single batch of samples.
|
910 | *
|
911 | * ```js
|
912 | * const model = tf.sequential({
|
913 | * layers: [tf.layers.dense({units: 1, inputShape: [10]})]
|
914 | * });
|
915 | * model.predictOnBatch(tf.ones([8, 10])).print();
|
916 | * ```
|
917 | * @param x: Input samples, as a Tensor (for models with exactly one
|
918 | * input) or an array of Tensors (for models with more than one input).
|
919 | * @return Tensor(s) of predictions
|
920 | *
|
921 | * @doc {heading: 'Models', subheading: 'Classes'}
|
922 | */
|
923 | predictOnBatch(x) {
|
924 | checkInputData(x, this.inputNames, this.feedInputShapes, true);
|
925 | // TODO(cais): Take care of the learning_phase boolean flag.
|
926 | // if (this.useLearningPhase) ...
|
927 | const batchSize = (Array.isArray(x) ? x[0] : x).shape[0];
|
928 | return this.predictLoop(x, batchSize);
|
929 | }
|
930 | standardizeUserDataXY(x, y, checkBatchAxis = true, batchSize) {
|
931 | // TODO(cais): Add sampleWeight, classWeight
|
932 | if (this.optimizer_ == null) {
|
933 | throw new RuntimeError('You must compile a model before training/testing. Use ' +
|
934 | 'LayersModel.compile(modelCompileArgs).');
|
935 | }
|
936 | const outputShapes = [];
|
937 | for (let i = 0; i < this.feedOutputShapes.length; ++i) {
|
938 | const outputShape = this.feedOutputShapes[i];
|
939 | const lossFn = this.feedLossFns[i];
|
940 | if (lossFn === losses.sparseCategoricalCrossentropy) {
|
941 | outputShapes.push(outputShape.slice(0, outputShape.length - 1).concat([1]));
|
942 | }
|
943 | else {
|
944 | // Porting Note: Because of strong typing `lossFn` must be a function.
|
945 | outputShapes.push(outputShape);
|
946 | }
|
947 | }
|
948 | x = standardizeInputData(x, this.feedInputNames, this.feedInputShapes, false, 'input');
|
949 | y = standardizeInputData(y, this.feedOutputNames, outputShapes, false, 'target');
|
950 | // TODO(cais): Standardize sampleWeights & classWeights.
|
951 | checkArrayLengths(x, y, null);
|
952 | // TODO(cais): Check sampleWeights as well.
|
953 | checkLossAndTargetCompatibility(y, this.feedLossFns, this.feedOutputShapes);
|
954 | if (this.stateful && batchSize != null && batchSize > 0) {
|
955 | if (x[0].shape[0] % batchSize !== 0) {
|
956 | throw new ValueError(`In a stateful network, you should only pass inputs with a ` +
|
957 | `number of samples that is divisible by the batch size ` +
|
958 | `${batchSize}. Found: ${x[0].shape[0]} sample(s).`);
|
959 | }
|
960 | }
|
961 | return [x, y];
|
962 | }
|
963 | async standardizeUserData(x, y, sampleWeight, classWeight, checkBatchAxis = true, batchSize) {
|
964 | const [standardXs, standardYs] = this.standardizeUserDataXY(x, y, checkBatchAxis, batchSize);
|
965 | // TODO(cais): Handle sampleWeights.
|
966 | if (sampleWeight != null) {
|
967 | throw new Error('sample weight is not supported yet.');
|
968 | }
|
969 | let standardSampleWeights = null;
|
970 | if (classWeight != null) {
|
971 | const classWeights = standardizeClassWeights(classWeight, this.outputNames);
|
972 | standardSampleWeights = [];
|
973 | for (let i = 0; i < classWeights.length; ++i) {
|
974 | standardSampleWeights.push(await standardizeWeights(standardYs[i], null, classWeights[i]));
|
975 | }
|
976 | }
|
977 | // TODO(cais): Deal with the case of model.stateful == true.
|
978 | return [standardXs, standardYs, standardSampleWeights];
|
979 | }
|
980 | /**
|
981 | * Loop over some test data in batches.
|
982 | * @param f A Function returning a list of tensors.
|
983 | * @param ins Array of tensors to be fed to `f`.
|
984 | * @param batchSize Integer batch size or `null` / `undefined`.
|
985 | * @param verbose verbosity mode.
|
986 | * @param steps Total number of steps (batches of samples) before
|
987 | * declaring test finished. Ignored with the default value of `null` /
|
988 | * `undefined`.
|
989 | * @returns Array of Scalars.
|
990 | */
|
991 | testLoop(f, ins, batchSize, verbose = 0, steps) {
|
992 | return tfc.tidy(() => {
|
993 | const numSamples = this.checkNumSamples(ins, batchSize, steps, 'steps');
|
994 | const outs = [];
|
995 | if (verbose > 0) {
|
996 | throw new NotImplementedError('Verbose mode is not implemented yet.');
|
997 | }
|
998 | // TODO(cais): Use `indicesForConversionToDense' to prevent slow down.
|
999 | if (steps != null) {
|
1000 | throw new NotImplementedError('steps mode in testLoop() is not implemented yet');
|
1001 | }
|
1002 | else {
|
1003 | const batches = makeBatches(numSamples, batchSize);
|
1004 | const indexArray = tensor1d(range(0, numSamples));
|
1005 | for (let batchIndex = 0; batchIndex < batches.length; ++batchIndex) {
|
1006 | const batchStart = batches[batchIndex][0];
|
1007 | const batchEnd = batches[batchIndex][1];
|
1008 | const batchIds = K.sliceAlongFirstAxis(indexArray, batchStart, batchEnd - batchStart);
|
1009 | // TODO(cais): In ins, train flag can be a number, instead of an
|
1010 | // Tensor? Do we need to handle this in tfjs-layers?
|
1011 | const insBatch = sliceArraysByIndices(ins, batchIds);
|
1012 | const batchOuts = f(insBatch);
|
1013 | if (batchIndex === 0) {
|
1014 | for (let i = 0; i < batchOuts.length; ++i) {
|
1015 | outs.push(scalar(0));
|
1016 | }
|
1017 | }
|
1018 | for (let i = 0; i < batchOuts.length; ++i) {
|
1019 | const batchOut = batchOuts[i];
|
1020 | outs[i] =
|
1021 | tfc.add(outs[i], tfc.mul(batchEnd - batchStart, batchOut));
|
1022 | }
|
1023 | }
|
1024 | for (let i = 0; i < outs.length; ++i) {
|
1025 | outs[i] = tfc.div(outs[i], numSamples);
|
1026 | }
|
1027 | }
|
1028 | return outs;
|
1029 | });
|
1030 | }
|
1031 | getDedupedMetricsNames() {
|
1032 | const outLabels = this.metricsNames;
|
1033 | // Rename duplicated metrics names (can happen with an output layer
|
1034 | // shared among multiple dataflows).
|
1035 | const dedupedOutLabels = [];
|
1036 | for (let i = 0; i < outLabels.length; ++i) {
|
1037 | const label = outLabels[i];
|
1038 | let newLabel = label;
|
1039 | if (count(outLabels, label) > 1) {
|
1040 | const dupIndex = count(outLabels.slice(0, i), label);
|
1041 | newLabel += `_${dupIndex}`;
|
1042 | }
|
1043 | dedupedOutLabels.push(newLabel);
|
1044 | }
|
1045 | return dedupedOutLabels;
|
1046 | }
|
1047 | /**
|
1048 | * Creates a function that performs the following actions:
|
1049 | *
|
1050 | * 1. computes the losses
|
1051 | * 2. sums them to get the total loss
|
1052 | * 3. call the optimizer computes the gradients of the LayersModel's
|
1053 | * trainable weights w.r.t. the total loss and update the variables
|
1054 | * 4. calculates the metrics
|
1055 | * 5. returns the values of the losses and metrics.
|
1056 | */
|
1057 | makeTrainFunction() {
|
1058 | return (data) => {
|
1059 | const lossValues = [];
|
1060 | const inputs = data.slice(0, this.inputs.length);
|
1061 | const targets = data.slice(this.inputs.length, this.inputs.length + this.outputs.length);
|
1062 | const sampleWeights = data.slice(this.inputs.length + this.outputs.length, this.inputs.length + this.outputs.length * 2);
|
1063 | const metricsValues = [];
|
1064 | // Create a function that computes the total loss based on the
|
1065 | // inputs. This function is used for obtaining gradients through
|
1066 | // backprop.
|
1067 | const totalLossFunction = () => {
|
1068 | const feeds = [];
|
1069 | for (let i = 0; i < this.inputs.length; ++i) {
|
1070 | feeds.push({ key: this.inputs[i], value: inputs[i] });
|
1071 | }
|
1072 | const feedDict = new FeedDict(feeds);
|
1073 | const outputs = execute(this.outputs, feedDict, { 'training': true });
|
1074 | // TODO(cais): Take care of the case of multiple outputs from a
|
1075 | // single layer?
|
1076 | let totalLoss;
|
1077 | for (let i = 0; i < this.lossFunctions.length; ++i) {
|
1078 | const lossFunction = this.lossFunctions[i];
|
1079 | let loss = lossFunction(targets[i], outputs[i]);
|
1080 | if (sampleWeights[i] != null) {
|
1081 | loss = computeWeightedLoss(loss, sampleWeights[i]);
|
1082 | }
|
1083 | // TODO(cais): push Scalar instead.
|
1084 | const meanLoss = tfc.mean(loss);
|
1085 | // TODO(cais): Use a scope() instead, to avoid ownership.
|
1086 | lossValues.push(meanLoss);
|
1087 | if (i === 0) {
|
1088 | totalLoss = loss;
|
1089 | }
|
1090 | else {
|
1091 | totalLoss = tfc.add(totalLoss, loss);
|
1092 | }
|
1093 | }
|
1094 | // Compute the metrics.
|
1095 | // TODO(cais): These should probably be calculated outside
|
1096 | // totalLossFunction to benefit speed?
|
1097 | for (let i = 0; i < this.metricsTensors.length; ++i) {
|
1098 | let weightedMetric;
|
1099 | if (this.outputs.length > 1 && i < this.outputs.length) {
|
1100 | weightedMetric = lossValues[i];
|
1101 | }
|
1102 | else {
|
1103 | const metric = this.metricsTensors[i][0];
|
1104 | const outputIndex = this.metricsTensors[i][1];
|
1105 | weightedMetric =
|
1106 | tfc.mean(metric(targets[outputIndex], outputs[outputIndex]));
|
1107 | }
|
1108 | tfc.keep(weightedMetric);
|
1109 | // TODO(cais): Use a scope() instead, to avoid ownership.
|
1110 | metricsValues.push(weightedMetric);
|
1111 | }
|
1112 | totalLoss = tfc.mean(totalLoss);
|
1113 | // Add regularizer penalties.
|
1114 | this.calculateLosses().forEach(regularizerLoss => {
|
1115 | totalLoss = tfc.add(totalLoss, regularizerLoss);
|
1116 | });
|
1117 | return totalLoss;
|
1118 | };
|
1119 | const variables = this.collectedTrainableWeights.map(param => param.read());
|
1120 | const returnCost = true;
|
1121 | const totalLossValue = this.optimizer_.minimize(totalLossFunction, returnCost, variables);
|
1122 | return [totalLossValue].concat(metricsValues);
|
1123 | };
|
1124 | }
|
1125 | /**
|
1126 | * Create a function which, when invoked with an array of `tf.Tensor`s as a
|
1127 | * batch of inputs, returns the prespecified loss and metrics of the model
|
1128 | * under the batch of input data.
|
1129 | */
|
1130 | makeTestFunction() {
|
1131 | this.testFunction = (data) => {
|
1132 | return tfc.tidy(() => {
|
1133 | const valOutputs = [];
|
1134 | let totalLoss;
|
1135 | const inputs = data.slice(0, this.inputs.length);
|
1136 | const targets = data.slice(this.inputs.length, this.inputs.length + this.outputs.length);
|
1137 | const feeds = [];
|
1138 | for (let i = 0; i < this.inputs.length; ++i) {
|
1139 | feeds.push({ key: this.inputs[i], value: inputs[i] });
|
1140 | }
|
1141 | const feedDict = new FeedDict(feeds);
|
1142 | const outputs = execute(this.outputs, feedDict);
|
1143 | // Compute total loss.
|
1144 | for (let i = 0; i < this.lossFunctions.length; ++i) {
|
1145 | const lossFunction = this.lossFunctions[i];
|
1146 | // TODO(cais): Add sample weighting and replace the simple
|
1147 | // averaging.
|
1148 | const loss = tfc.mean(lossFunction(targets[i], outputs[i]));
|
1149 | if (i === 0) {
|
1150 | totalLoss = loss;
|
1151 | }
|
1152 | else {
|
1153 | totalLoss = tfc.add(totalLoss, loss);
|
1154 | }
|
1155 | valOutputs.push(totalLoss);
|
1156 | }
|
1157 | // Compute the metrics.
|
1158 | for (let i = 0; i < this.metricsTensors.length; ++i) {
|
1159 | const metric = this.metricsTensors[i][0];
|
1160 | const outputIndex = this.metricsTensors[i][1];
|
1161 | // TODO(cais): Replace K.mean() with a proper weighting function.
|
1162 | const meanMetric = tfc.mean(metric(targets[outputIndex], outputs[outputIndex]));
|
1163 | valOutputs.push(meanMetric);
|
1164 | }
|
1165 | return valOutputs;
|
1166 | });
|
1167 | };
|
1168 | }
|
1169 | /**
|
1170 | * Trains the model for a fixed number of epochs (iterations on a
|
1171 | * dataset).
|
1172 | *
|
1173 | * ```js
|
1174 | * const model = tf.sequential({
|
1175 | * layers: [tf.layers.dense({units: 1, inputShape: [10]})]
|
1176 | * });
|
1177 | * model.compile({optimizer: 'sgd', loss: 'meanSquaredError'});
|
1178 | * for (let i = 1; i < 5 ; ++i) {
|
1179 | * const h = await model.fit(tf.ones([8, 10]), tf.ones([8, 1]), {
|
1180 | * batchSize: 4,
|
1181 | * epochs: 3
|
1182 | * });
|
1183 | * console.log("Loss after Epoch " + i + " : " + h.history.loss[0]);
|
1184 | * }
|
1185 | * ```
|
1186 | *
|
1187 | * @param x `tf.Tensor` of training data, or an array of `tf.Tensor`s if the
|
1188 | * model has multiple inputs. If all inputs in the model are named, you
|
1189 | * can also pass a dictionary mapping input names to `tf.Tensor`s.
|
1190 | * @param y `tf.Tensor` of target (label) data, or an array of `tf.Tensor`s if
|
1191 | * the model has multiple outputs. If all outputs in the model are named,
|
1192 | * you can also pass a dictionary mapping output names to `tf.Tensor`s.
|
1193 | * @param args A `ModelFitArgs`, containing optional fields.
|
1194 | *
|
1195 | * @return A `History` instance. Its `history` attribute contains all
|
1196 | * information collected during training.
|
1197 | *
|
1198 | * @exception ValueError In case of mismatch between the provided input
|
1199 | * data and what the model expects.
|
1200 | *
|
1201 | * @doc {heading: 'Models', subheading: 'Classes'}
|
1202 | */
|
1203 | async fit(x, y, args = {}) {
|
1204 | return fitTensors(this, x, y, args);
|
1205 | }
|
1206 | // TODO(cais): Add code snippet below when it's possible to instantiate
|
1207 | // actual dataset objects.
|
1208 | /**
|
1209 | * Trains the model using a dataset object.
|
1210 | *
|
1211 | * @param dataset A dataset object. Its `iterator()` method is expected
|
1212 | * to generate a dataset iterator object, the `next()` method of which
|
1213 | * is expected to produce data batches for training. The return value
|
1214 | * of the `next()` call ought to contain a boolean `done` field and a
|
1215 | * `value` field. The `value` field is expected to be an array of two
|
1216 | * `tf.Tensor`s or an array of two nested `tf.Tensor` structures. The former
|
1217 | * case is for models with exactly one input and one output (e.g..
|
1218 | * a sequential model). The latter case is for models with multiple
|
1219 | * inputs and/or multiple outputs.
|
1220 | * Of the two items in the array, the first is the input feature(s) and
|
1221 | * the second is the output target(s).
|
1222 | * @param args A `ModelFitDatasetArgs`, containing optional fields.
|
1223 | *
|
1224 | * @return A `History` instance. Its `history` attribute contains all
|
1225 | * information collected during training.
|
1226 | *
|
1227 | * @doc {heading: 'Models', subheading: 'Classes'}
|
1228 | */
|
1229 | async fitDataset(dataset, args) {
|
1230 | return fitDataset(this, dataset, args);
|
1231 | }
|
1232 | /**
|
1233 | * Runs a single gradient update on a single batch of data.
|
1234 | *
|
1235 | * This method differs from `fit()` and `fitDataset()` in the following
|
1236 | * regards:
|
1237 | * - It operates on exactly one batch of data.
|
1238 | * - It returns only the loss and matric values, instead of
|
1239 | * returning the batch-by-batch loss and metric values.
|
1240 | * - It doesn't support fine-grained options such as verbosity and
|
1241 | * callbacks.
|
1242 | *
|
1243 | * @param x Input data. It could be one of the following:
|
1244 | * - A `tf.Tensor`, or an Array of `tf.Tensor`s (in case the model has
|
1245 | * multiple inputs).
|
1246 | * - An Object mapping input names to corresponding `tf.Tensor` (if the
|
1247 | * model has named inputs).
|
1248 | * @param y Target darta. It could be either a `tf.Tensor` a multiple
|
1249 | * `tf.Tensor`s. It should be consistent with `x`.
|
1250 | * @returns Training loss or losses (in case the model has
|
1251 | * multiple outputs), along with metrics (if any), as numbers.
|
1252 | *
|
1253 | * @doc {heading: 'Models', subheading: 'Classes'}
|
1254 | */
|
1255 | async trainOnBatch(x, y) {
|
1256 | // TODO(cais): Support sampleWeight and classWeight.
|
1257 | // TODO(cais): Support Dataset objects.
|
1258 | const standardizeOut = await this.standardizeUserData(x, y);
|
1259 | const inputs = standardizeOut[0];
|
1260 | const targets = standardizeOut[1];
|
1261 | const trainFunction = this.makeTrainFunction();
|
1262 | const losses = trainFunction(inputs.concat(targets));
|
1263 | const lossValues = [];
|
1264 | for (const loss of losses) {
|
1265 | const v = await loss.data();
|
1266 | lossValues.push(v[0]);
|
1267 | }
|
1268 | tfc.dispose(losses);
|
1269 | disposeNewTensors(standardizeOut[0], x);
|
1270 | disposeNewTensors(standardizeOut[1], y);
|
1271 | return singletonOrArray(lossValues);
|
1272 | }
|
1273 | /**
|
1274 | * Extract weight values of the model.
|
1275 | *
|
1276 | * @param config: An instance of `io.SaveConfig`, which specifies
|
1277 | * model-saving options such as whether only trainable weights are to be
|
1278 | * saved.
|
1279 | * @returns A `NamedTensorMap` mapping original weight names (i.e.,
|
1280 | * non-uniqueified weight names) to their values.
|
1281 | */
|
1282 | getNamedWeights(config) {
|
1283 | const namedWeights = [];
|
1284 | const trainableOnly = config != null && config.trainableOnly;
|
1285 | const weights = trainableOnly ? this.trainableWeights : this.weights;
|
1286 | const weightValues = this.getWeights(trainableOnly);
|
1287 | for (let i = 0; i < weights.length; ++i) {
|
1288 | if (trainableOnly && !weights[i].trainable) {
|
1289 | // Optionally skip non-trainable weights.
|
1290 | continue;
|
1291 | }
|
1292 | namedWeights.push({ name: weights[i].originalName, tensor: weightValues[i] });
|
1293 | }
|
1294 | return namedWeights;
|
1295 | }
|
1296 | /**
|
1297 | * Setter used for force stopping of LayersModel.fit() (i.e., training).
|
1298 | *
|
1299 | * Example:
|
1300 | *
|
1301 | * ```js
|
1302 | * const input = tf.input({shape: [10]});
|
1303 | * const output = tf.layers.dense({units: 1}).apply(input);
|
1304 | * const model = tf.model({inputs: [input], outputs: [output]});
|
1305 | * model.compile({loss: 'meanSquaredError', optimizer: 'sgd'});
|
1306 | * const xs = tf.ones([8, 10]);
|
1307 | * const ys = tf.zeros([8, 1]);
|
1308 | *
|
1309 | * const history = await model.fit(xs, ys, {
|
1310 | * epochs: 10,
|
1311 | * callbacks: {
|
1312 | * onEpochEnd: async (epoch, logs) => {
|
1313 | * if (epoch === 2) {
|
1314 | * model.stopTraining = true;
|
1315 | * }
|
1316 | * }
|
1317 | * }
|
1318 | * });
|
1319 | *
|
1320 | * // There should be only 3 values in the loss array, instead of 10
|
1321 | * values,
|
1322 | * // due to the stopping after 3 epochs.
|
1323 | * console.log(history.history.loss);
|
1324 | * ```
|
1325 | */
|
1326 | set stopTraining(stop) {
|
1327 | this.stopTraining_ = stop;
|
1328 | }
|
1329 | get stopTraining() {
|
1330 | return this.stopTraining_;
|
1331 | }
|
1332 | get optimizer() {
|
1333 | return this.optimizer_;
|
1334 | }
|
1335 | set optimizer(optimizer) {
|
1336 | if (this.optimizer_ !== optimizer) {
|
1337 | this.optimizer_ = optimizer;
|
1338 | this.isOptimizerOwned = false;
|
1339 | }
|
1340 | }
|
1341 | dispose() {
|
1342 | const result = super.dispose();
|
1343 | if (result.refCountAfterDispose === 0 && this.optimizer != null &&
|
1344 | this.isOptimizerOwned) {
|
1345 | const numTensorsBeforeOptmizerDisposal = tfc.memory().numTensors;
|
1346 | this.optimizer_.dispose();
|
1347 | result.numDisposedVariables +=
|
1348 | numTensorsBeforeOptmizerDisposal - tfc.memory().numTensors;
|
1349 | }
|
1350 | return result;
|
1351 | }
|
1352 | getLossIdentifiers() {
|
1353 | let lossNames;
|
1354 | if (typeof this.loss === 'string') {
|
1355 | lossNames = toSnakeCase(this.loss);
|
1356 | }
|
1357 | else if (Array.isArray(this.loss)) {
|
1358 | for (const loss of this.loss) {
|
1359 | if (typeof loss !== 'string') {
|
1360 | throw new Error('Serialization of non-string loss is not supported.');
|
1361 | }
|
1362 | }
|
1363 | lossNames = this.loss.map(name => toSnakeCase(name));
|
1364 | }
|
1365 | else {
|
1366 | const outputNames = Object.keys(this.loss);
|
1367 | lossNames = {};
|
1368 | const losses = this.loss;
|
1369 | for (const outputName of outputNames) {
|
1370 | if (typeof losses[outputName] === 'string') {
|
1371 | lossNames[outputName] =
|
1372 | toSnakeCase(losses[outputName]);
|
1373 | }
|
1374 | else {
|
1375 | throw new Error('Serialization of non-string loss is not supported.');
|
1376 | }
|
1377 | }
|
1378 | }
|
1379 | return lossNames;
|
1380 | }
|
1381 | getMetricIdentifiers() {
|
1382 | if (typeof this.metrics === 'string' ||
|
1383 | typeof this.metrics === 'function') {
|
1384 | return [toSnakeCase(Metrics.getLossOrMetricName(this.metrics))];
|
1385 | }
|
1386 | else if (Array.isArray(this.metrics)) {
|
1387 | return this.metrics.map(metric => toSnakeCase(Metrics.getLossOrMetricName(metric)));
|
1388 | }
|
1389 | else {
|
1390 | const metricsIdentifiers = {};
|
1391 | for (const key in this.metrics) {
|
1392 | metricsIdentifiers[key] =
|
1393 | toSnakeCase(Metrics.getLossOrMetricName(this.metrics[key]));
|
1394 | }
|
1395 | return metricsIdentifiers;
|
1396 | }
|
1397 | }
|
1398 | getTrainingConfig() {
|
1399 | return {
|
1400 | loss: this.getLossIdentifiers(),
|
1401 | metrics: this.getMetricIdentifiers(),
|
1402 | optimizer_config: {
|
1403 | class_name: this.optimizer.getClassName(),
|
1404 | config: this.optimizer.getConfig()
|
1405 | }
|
1406 | };
|
1407 | // TODO(cais): Add weight_metrics when they are supported.
|
1408 | // TODO(cais): Add sample_weight_mode when it's supported.
|
1409 | // TODO(cais): Add loss_weights when it's supported.
|
1410 | }
|
1411 | loadTrainingConfig(trainingConfig) {
|
1412 | if (trainingConfig.weighted_metrics != null) {
|
1413 | throw new Error('Loading weight_metrics is not supported yet.');
|
1414 | }
|
1415 | if (trainingConfig.loss_weights != null) {
|
1416 | throw new Error('Loading loss_weights is not supported yet.');
|
1417 | }
|
1418 | if (trainingConfig.sample_weight_mode != null) {
|
1419 | throw new Error('Loading sample_weight_mode is not supported yet.');
|
1420 | }
|
1421 | const tsConfig = convertPythonicToTs(trainingConfig.optimizer_config);
|
1422 | const optimizer = deserialize(tsConfig);
|
1423 | let loss;
|
1424 | if (typeof trainingConfig.loss === 'string') {
|
1425 | loss = toCamelCase(trainingConfig.loss);
|
1426 | }
|
1427 | else if (Array.isArray(trainingConfig.loss)) {
|
1428 | loss = trainingConfig.loss.map(lossEntry => toCamelCase(lossEntry));
|
1429 | }
|
1430 | else if (trainingConfig.loss != null) {
|
1431 | loss = {};
|
1432 | for (const key in trainingConfig.loss) {
|
1433 | loss[key] = toCamelCase(trainingConfig.loss[key]);
|
1434 | }
|
1435 | }
|
1436 | let metrics;
|
1437 | if (Array.isArray(trainingConfig.metrics)) {
|
1438 | metrics = trainingConfig.metrics.map(metric => toCamelCase(metric));
|
1439 | }
|
1440 | else if (trainingConfig.metrics != null) {
|
1441 | metrics = {};
|
1442 | for (const key in trainingConfig.metrics) {
|
1443 | metrics[key] = toCamelCase(trainingConfig.metrics[key]);
|
1444 | }
|
1445 | }
|
1446 | this.compile({ loss, metrics, optimizer });
|
1447 | }
|
1448 | /**
|
1449 | * Save the configuration and/or weights of the LayersModel.
|
1450 | *
|
1451 | * An `IOHandler` is an object that has a `save` method of the proper
|
1452 | * signature defined. The `save` method manages the storing or
|
1453 | * transmission of serialized data ("artifacts") that represent the
|
1454 | * model's topology and weights onto or via a specific medium, such as
|
1455 | * file downloads, local storage, IndexedDB in the web browser and HTTP
|
1456 | * requests to a server. TensorFlow.js provides `IOHandler`
|
1457 | * implementations for a number of frequently used saving mediums, such as
|
1458 | * `tf.io.browserDownloads` and `tf.io.browserLocalStorage`. See `tf.io`
|
1459 | * for more details.
|
1460 | *
|
1461 | * This method also allows you to refer to certain types of `IOHandler`s
|
1462 | * as URL-like string shortcuts, such as 'localstorage://' and
|
1463 | * 'indexeddb://'.
|
1464 | *
|
1465 | * Example 1: Save `model`'s topology and weights to browser [local
|
1466 | * storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage);
|
1467 | * then load it back.
|
1468 | *
|
1469 | * ```js
|
1470 | * const model = tf.sequential(
|
1471 | * {layers: [tf.layers.dense({units: 1, inputShape: [3]})]});
|
1472 | * console.log('Prediction from original model:');
|
1473 | * model.predict(tf.ones([1, 3])).print();
|
1474 | *
|
1475 | * const saveResults = await model.save('localstorage://my-model-1');
|
1476 | *
|
1477 | * const loadedModel = await tf.loadLayersModel('localstorage://my-model-1');
|
1478 | * console.log('Prediction from loaded model:');
|
1479 | * loadedModel.predict(tf.ones([1, 3])).print();
|
1480 | * ```
|
1481 | *
|
1482 | * Example 2. Saving `model`'s topology and weights to browser
|
1483 | * [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API);
|
1484 | * then load it back.
|
1485 | *
|
1486 | * ```js
|
1487 | * const model = tf.sequential(
|
1488 | * {layers: [tf.layers.dense({units: 1, inputShape: [3]})]});
|
1489 | * console.log('Prediction from original model:');
|
1490 | * model.predict(tf.ones([1, 3])).print();
|
1491 | *
|
1492 | * const saveResults = await model.save('indexeddb://my-model-1');
|
1493 | *
|
1494 | * const loadedModel = await tf.loadLayersModel('indexeddb://my-model-1');
|
1495 | * console.log('Prediction from loaded model:');
|
1496 | * loadedModel.predict(tf.ones([1, 3])).print();
|
1497 | * ```
|
1498 | *
|
1499 | * Example 3. Saving `model`'s topology and weights as two files
|
1500 | * (`my-model-1.json` and `my-model-1.weights.bin`) downloaded from
|
1501 | * browser.
|
1502 | *
|
1503 | * ```js
|
1504 | * const model = tf.sequential(
|
1505 | * {layers: [tf.layers.dense({units: 1, inputShape: [3]})]});
|
1506 | * const saveResults = await model.save('downloads://my-model-1');
|
1507 | * ```
|
1508 | *
|
1509 | * Example 4. Send `model`'s topology and weights to an HTTP server.
|
1510 | * See the documentation of `tf.io.http` for more details
|
1511 | * including specifying request parameters and implementation of the
|
1512 | * server.
|
1513 | *
|
1514 | * ```js
|
1515 | * const model = tf.sequential(
|
1516 | * {layers: [tf.layers.dense({units: 1, inputShape: [3]})]});
|
1517 | * const saveResults = await model.save('http://my-server/model/upload');
|
1518 | * ```
|
1519 | *
|
1520 | * @param handlerOrURL An instance of `IOHandler` or a URL-like,
|
1521 | * scheme-based string shortcut for `IOHandler`.
|
1522 | * @param config Options for saving the model.
|
1523 | * @returns A `Promise` of `SaveResult`, which summarizes the result of
|
1524 | * the saving, such as byte sizes of the saved artifacts for the model's
|
1525 | * topology and weight values.
|
1526 | *
|
1527 | * @doc {heading: 'Models', subheading: 'Classes', ignoreCI: true}
|
1528 | */
|
1529 | async save(handlerOrURL, config) {
|
1530 | if (typeof handlerOrURL === 'string') {
|
1531 | const handlers = io.getSaveHandlers(handlerOrURL);
|
1532 | if (handlers.length === 0) {
|
1533 | throw new ValueError(`Cannot find any save handlers for URL '${handlerOrURL}'`);
|
1534 | }
|
1535 | else if (handlers.length > 1) {
|
1536 | throw new ValueError(`Found more than one (${handlers.length}) save handlers for ` +
|
1537 | `URL '${handlerOrURL}'`);
|
1538 | }
|
1539 | handlerOrURL = handlers[0];
|
1540 | }
|
1541 | if (handlerOrURL.save == null) {
|
1542 | throw new ValueError('LayersModel.save() cannot proceed because the IOHandler ' +
|
1543 | 'provided does not have the `save` attribute defined.');
|
1544 | }
|
1545 | const weightDataAndSpecs = await io.encodeWeights(this.getNamedWeights(config));
|
1546 | const returnString = false;
|
1547 | const unusedArg = null;
|
1548 | const modelConfig = this.toJSON(unusedArg, returnString);
|
1549 | const modelArtifacts = {
|
1550 | modelTopology: modelConfig,
|
1551 | format: LAYERS_MODEL_FORMAT_NAME,
|
1552 | generatedBy: `TensorFlow.js tfjs-layers v${version}`,
|
1553 | convertedBy: null,
|
1554 | };
|
1555 | const includeOptimizer = config == null ? false : config.includeOptimizer;
|
1556 | if (includeOptimizer && this.optimizer != null) {
|
1557 | modelArtifacts.trainingConfig = this.getTrainingConfig();
|
1558 | const weightType = 'optimizer';
|
1559 | const { data: optimizerWeightData, specs: optimizerWeightSpecs } = await io.encodeWeights(await this.optimizer.getWeights(), weightType);
|
1560 | weightDataAndSpecs.specs.push(...optimizerWeightSpecs);
|
1561 | weightDataAndSpecs.data = io.concatenateArrayBuffers([weightDataAndSpecs.data, optimizerWeightData]);
|
1562 | }
|
1563 | if (this.userDefinedMetadata != null) {
|
1564 | // Check serialized size of user-defined metadata.
|
1565 | const checkSize = true;
|
1566 | checkUserDefinedMetadata(this.userDefinedMetadata, this.name, checkSize);
|
1567 | modelArtifacts.userDefinedMetadata = this.userDefinedMetadata;
|
1568 | }
|
1569 | modelArtifacts.weightData = weightDataAndSpecs.data;
|
1570 | modelArtifacts.weightSpecs = weightDataAndSpecs.specs;
|
1571 | return handlerOrURL.save(modelArtifacts);
|
1572 | }
|
1573 | /**
|
1574 | * Set user-defined metadata.
|
1575 | *
|
1576 | * The set metadata will be serialized together with the topology
|
1577 | * and weights of the model during `save()` calls.
|
1578 | *
|
1579 | * @param setUserDefinedMetadata
|
1580 | */
|
1581 | setUserDefinedMetadata(userDefinedMetadata) {
|
1582 | checkUserDefinedMetadata(userDefinedMetadata, this.name);
|
1583 | this.userDefinedMetadata = userDefinedMetadata;
|
1584 | }
|
1585 | /**
|
1586 | * Get user-defined metadata.
|
1587 | *
|
1588 | * The metadata is supplied via one of the two routes:
|
1589 | * 1. By calling `setUserDefinedMetadata()`.
|
1590 | * 2. Loaded during model loading (if the model is constructed
|
1591 | * via `tf.loadLayersModel()`.)
|
1592 | *
|
1593 | * If no user-defined metadata is available from either of the
|
1594 | * two routes, this function will return `undefined`.
|
1595 | */
|
1596 | getUserDefinedMetadata() {
|
1597 | return this.userDefinedMetadata;
|
1598 | }
|
1599 | }
|
1600 | // The class name is 'Model' rather than 'LayersModel' for backwards
|
1601 | // compatibility since this class name shows up in the serialization format.
|
1602 | /** @nocollapse */
|
1603 | LayersModel.className = 'Model';
|
1604 | serialization.registerClass(LayersModel);
|
1605 | /**
|
1606 | * A `tf.Functional` is an alias to `tf.LayersModel`.
|
1607 | *
|
1608 | * See also:
|
1609 | * `tf.LayersModel`, `tf.Sequential`, `tf.loadLayersModel`.
|
1610 | */
|
1611 | /** @doc {heading: 'Models', subheading: 'Classes'} */
|
1612 | export class Functional extends LayersModel {
|
1613 | }
|
1614 | Functional.className = 'Functional';
|
1615 | serialization.registerClass(Functional);
|
1616 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHJhaW5pbmcuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi90ZmpzLWxheWVycy9zcmMvZW5naW5lL3RyYWluaW5nLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7OztHQVFHO0FBRUgseUNBQXlDO0FBRXpDLE9BQU8sS0FBSyxHQUFHLE1BQU0sdUJBQXVCLENBQUM7QUFDN0MsT0FBTyxFQUFDLEVBQUUsRUFBMEQsU0FBUyxFQUFVLE1BQU0sRUFBRSxhQUFhLEVBQUUsTUFBTSxFQUFZLFFBQVEsRUFBRSxJQUFJLEVBQUMsTUFBTSx1QkFBdUIsQ0FBQztBQUU3SyxPQUFPLEtBQUssQ0FBQyxNQUFNLHlCQUF5QixDQUFDO0FBRTdDLE9BQU8sRUFBQyxTQUFTLEVBQUMsTUFBTSxXQUFXLENBQUM7QUFDcEMsT0FBTyxFQUFDLG1CQUFtQixFQUFFLFlBQVksRUFBRSxVQUFVLEVBQUMsTUFBTSxXQUFXLENBQUM7QUFLeEUsT0FBTyxFQUFDLFdBQVcsRUFBQyxNQUFNLHlCQUF5QixDQUFDO0FBQ3BELE9BQU8sS0FBSyxNQUFNLE1BQU0sV0FBVyxDQUFDO0FBQ3BDLE9BQU8sS0FBSyxPQUFPLE1BQU0sWUFBWSxDQUFDO0FBQ3RDLE9BQU8sS0FBSyxVQUFVLE1BQU0sZUFBZSxDQUFDO0FBRTVDLE9BQU8sRUFBQyx3QkFBd0IsRUFBQyxNQUFNLDBCQUEwQixDQUFDO0FBQ2xFLE9BQU8sRUFBQyxLQUFLLEVBQUUsWUFBWSxFQUFFLGdCQUFnQixFQUFFLFdBQVcsRUFBRSxXQUFXLEVBQUUsTUFBTSxFQUFDLE1BQU0sd0JBQXdCLENBQUM7QUFDL0csT0FBTyxFQUFDLFlBQVksRUFBQyxNQUFNLHNCQUFzQixDQUFDO0FBQ2xELE9BQU8sRUFBQyxLQUFLLEVBQUMsTUFBTSxxQkFBcUIsQ0FBQztBQUMxQyxPQUFPLEVBQUMsbUJBQW1CLEVBQUMsTUFBTSw4QkFBOEIsQ0FBQztBQUVqRSxPQUFPLEVBQUMsT0FBTyxFQUFD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bUIsR0FBRyxJQUFJLENBQUMsbUJBQW1CLENBQUM7U0FDL0Q7UUFFRCxjQUFjLENBQUMsVUFBVSxHQUFHLGtCQUFrQixDQUFDLElBQUksQ0FBQztRQUNwRCxjQUFjLENBQUMsV0FBVyxHQUFHLGtCQUFrQixDQUFDLEtBQUssQ0FBQztRQUN0RCxPQUFPLFlBQVksQ0FBQyxJQUFJLENBQUMsY0FBYyxDQUFDLENBQUM7SUFDM0MsQ0FBQztJQUVEOzs7Ozs7O09BT0c7SUFDSCxzQkFBc0IsQ0FBQyxtQkFBdUI7UUFDNUMsd0JBQXdCLENBQUMsbUJBQW1CLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3pELElBQUksQ0FBQyxtQkFBbUIsR0FBRyxtQkFBbUIsQ0FBQztJQUNqRCxDQUFDO0lBRUQ7Ozs7Ozs7Ozs7T0FVRztJQUNILHNCQUFzQjtRQUNwQixPQUFPLElBQUksQ0FBQyxtQkFBbUIsQ0FBQztJQUNsQyxDQUFDOztBQTc0Q0Qsb0VBQW9FO0FBQ3BFLDRFQUE0RTtBQUM1RSxrQkFBa0I7QUFDWCxxQkFBUyxHQUFHLE9BQU8sQ0FBQztBQTQ0QzdCLGFBQWEsQ0FBQyxhQUFhLENBQUMsV0FBVyxDQUFDLENBQUM7QUFFekM7Ozs7O0dBS0c7QUFDSCxzREFBc0Q7QUFDdEQsTUFBTSxPQUFPLFVBQVcsU0FBUSxXQUFXOztBQUNsQyxvQkFBUyxHQUFHLFlBQVksQ0FBQztBQUVsQyxhQUFhLENBQUMsYUFBYSxDQUFDLFVBQVUsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBAbGljZW5zZVxuICogQ29weXJpZ2h0IDIwMTggR29vZ2xlIExMQ1xuICpcbiAqIFVzZSBvZiB0aGlzIHNvdXJjZSBjb2RlIGlzIGdvdmVybmVkIGJ5IGFuIE1JVC1zdHlsZVxuICogbGljZW5zZSB0aGF0IGNhbiBiZSBmb3VuZCBpbiB0aGUgTElDRU5TRSBmaWxlIG9yIGF0XG4gKiBodHRwczovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL01JVC5cbiAqID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4gKi9cblxuLyogT3JpZ2luYWwgU291cmNlOiBlbmdpbmUvdHJhaW5pbmcucHkgKi9cblxuaW1wb3J0ICogYXMgdGZjIGZyb20gJ0B0ZW5zb3JmbG93L3RmanMtY29yZSc7XG5pbXBvcnQge2lvLCBNb2RlbFByZWRpY3RDb25maWcgYXMgTW9kZWxQcmVkaWN0QXJncywgTmFtZWRUZW5zb3JNYXAsIE9wdGltaXplciwgU2NhbGFyLCBzY2FsYXIsIHNlcmlhbGl6YXRpb24sIFRlbnNvciwgVGVuc29yMUQsIHRlbnNvcjFkLCB1dGlsfSBmcm9tICdAdGVuc29yZmxvdy90ZmpzLWNvcmUnO1xuXG5pbXBvcnQgKiBhcyBLIGZyb20gJy4uL2JhY2tlbmQvdGZqc19iYWNrZW5kJztcbmltcG9ydCB7SGlzdG9yeSwgTW9kZWxMb2dnaW5nVmVyYm9zaXR5fSBmcm9tICcuLi9iYXNlX2NhbGxiYWNrcyc7XG5pbXBvcnQge25hbWVTY29wZX0gZnJvbSAnLi4vY29tbW9uJztcbmltcG9ydCB7Tm90SW1wbGVtZW50ZWRFcnJvciwgUnVudGltZUVycm9yLCBWYWx1ZUVycm9yfSBmcm9tICcuLi9lcnJvcnMnO1xuaW1wb3J0IHtTaGFwZX0gZnJvbSAnLi4va2VyYXNfZm9ybWF0L2NvbW1vbic7XG5pbXBvcnQge0xvc3NJZGVudGlmaWVyfSBmcm9tICcuLi9rZXJhc19mb3JtYXQvbG9zc19jb25maWcnO1xuaW1wb3J0IHtPcHRpbWl6ZXJTZXJpYWxpemF0aW9ufSBmcm9tICcuLi9rZXJhc19mb3JtYXQvb3B0aW1pemVyX2NvbmZpZyc7XG5pbXBvcnQge01ldHJpY3NJZGVudGlmaWVyLCBUcmFpbmluZ0NvbmZpZ30gZnJvbSAnLi4va2VyYXNfZm9ybWF0L3RyYWluaW5nX2NvbmZpZyc7XG5pbXBvcnQge2Rlc2VyaWFsaXplfSBmcm9tICcuLi9sYXllcnMvc2VyaWFsaXphdGlvbic7XG5pbXBvcnQgKiBhcyBsb3NzZXMgZnJvbSAnLi4vbG9zc2VzJztcbmltcG9ydCAqIGFzIE1ldHJpY3MgZnJvbSAnLi4vbWV0cmljcyc7XG5pbXBvcnQgKiBhcyBvcHRpbWl6ZXJzIGZyb20gJy4uL29wdGltaXplcnMnO1xuaW1wb3J0IHtMb3NzT3JNZXRyaWNGbiwgTmFtZWRUZW5zb3J9IGZyb20gJy4uL3R5cGVzJztcbmltcG9ydCB7Y2hlY2tVc2VyRGVmaW5lZE1ldGFkYXRhfSBmcm9tICcuLi91c2VyX2RlZmluZWRfbWV0YWRhdGEnO1xuaW1wb3J0IHtjb3VudCwgcHlMaXN0UmVwZWF0LCBzaW5nbGV0b25PckFycmF5LCB0b0NhbWVsQ2FzZSwgdG9TbmFrZUNhc2UsIHVuaXF1ZX0gZnJvbSAnLi4vdXRpbHMvZ2VuZXJpY191dGlscyc7XG5pbXBvcnQge3ByaW50U3VtbWFyeX0gZnJvbSAnLi4vdXRpbHMvbGF5ZXJfdXRpbHMnO1xuaW1wb3J0IHtyYW5nZX0gZnJvbSAnLi4vdXRpbHMvbWF0aF91dGlscyc7XG5pbXBvcnQge2NvbnZlcnRQeXRob25pY1RvVHN9IGZyb20gJy4uL3V0aWxzL3NlcmlhbGl6YXRpb25fdXRpbHMnO1xuaW1wb3J0IHtMYXllclZhcmlhYmxlfSBmcm9tICcuLi92YXJpYWJsZXMnO1xuaW1wb3J0IHt2ZXJzaW9ufSBmcm9tICcuLi92ZXJzaW9uJztcblxuaW1wb3J0IHtDb250YWluZXIsIENvbnRhaW5lckFyZ3N9IGZyb20gJy4vY29udGFpbmVyJztcbmltcG9ydCB7RGF0YXNldH0gZnJvbSAnLi9kYXRhc2V0X3N0dWInO1xuaW1wb3J0IHtleGVjdXRlLCBGZWVkRGljdH0gZnJvbSAnLi9leGVjdXRvcic7XG5pbXBvcnQge0Rpc3Bvc2VSZXN1bHQsIFN5bWJvbGljVGVuc29yfSBmcm9tICcuL3RvcG9sb2d5JztcbmltcG9ydCB7ZXZhbHVhdGVEYXRhc2V0LCBmaXREYXRhc2V0LCBNb2RlbEV2YWx1YXRlRGF0YXNldEFyZ3MsIE1vZGVsRml0RGF0YXNldEFyZ3N9IGZyb20gJy4vdHJhaW5pbmdfZGF0YXNldCc7XG5pbXBvcnQge2NoZWNrQmF0Y2hTaXplLCBkaXNwb3NlTmV3VGVuc29ycywgZW5zdXJlVGVuc29yc1JhbmsyT3JIaWdoZXIsIGZpdFRlbnNvcnMsIG1ha2VCYXRjaGVzLCBNb2RlbEZpdEFyZ3MsIHNsaWNlQXJyYXlzLCBzbGljZUFycmF5c0J5SW5kaWNlc30gZnJvbSAnLi90cmFpbmluZ190ZW5zb3JzJztcbmltcG9ydCB7Q2xhc3NXZWlnaHQsIENsYXNzV2VpZ2h0TWFwLCBjb21wdXRlV2VpZ2h0ZWRMb3NzLCBzdGFuZGFyZGl6ZUNsYXNzV2VpZ2h0cywgc3RhbmRhcmRpemVXZWlnaHRzfSBmcm9tICcuL3RyYWluaW5nX3V0aWxzJztcblxuLyoqXG4gKiBIZWxwZXIgZnVuY3Rpb24gZm9yIHBvbHltb3JwaGljIGlucHV0IGRhdGE6IDEuIHNpbmdsZXRvbiBUZW5zb3IuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpc0RhdGFUZW5zb3IoeDogVGVuc29yfFRlbnNvcltdfHtbaW5wdXROYW1lOiBzdHJpbmddOiBUZW5zb3J9fFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7W2lucHV0TmFtZTogc3RyaW5nXTogVGVuc29yW119KTogYm9vbGVhbiB7XG4gIHJldHVybiB4IGluc3RhbmNlb2YgVGVuc29yO1xufVxuXG4vKipcbiAqIEhlbHBlciBmdW5jdGlvbiBmb3IgcG9seW1vcnBoaWMgaW5wdXQgZGF0YTogMi4gQXJyYXkgb2YgVGVuc29yLlxuICovXG5leHBvcnQgZnVuY3Rpb24gaXNEYXRhQXJyYXkoeDogVGVuc29yfFRlbnNvcltdfFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHtbaW5wdXROYW1lOiBzdHJpbmddOiBUZW5zb3J9KTogYm9vbGVhbiB7XG4gIHJldHVybiBBcnJheS5pc0FycmF5KHgpO1xufVxuXG4vKipcbiAqIEhlbHBlciBmdW5jdGlvbiBmb3IgcG9seW1vcnBoaWMgaW5wdXQgZGF0YTogMy4gXCJkaWN0XCIgb2YgVGVuc29yLlxuICovXG5leHBvcnQgZnVuY3Rpb24gaXNEYXRhRGljdCh4OiBUZW5zb3J8VGVuc29yW118XG4gICAgICAgICAgICAgICAgICAgICAgICAgICB7W2lucHV0TmFtZTogc3RyaW5nXTogVGVuc29yfSk6IGJvb2xlYW4ge1xuICByZXR1cm4gIWlzRGF0YVRlbnNvcih4KSAmJiAhaXNEYXRhQXJyYXkoeCk7XG59XG5cbi8qKlxuICogTm9ybWFsaXplcyBpbnB1dHMgYW5kIHRhcmdldHMgcHJvdmlkZWQgYnkgdXNlcnMuXG4gKiBAcGFyYW0gZGF0YSBVc2VyLXByb3ZpZGVkIGlucHV0IGRhdGEgKHBvbHltb3JwaGljKS5cbiAqIEBwYXJhbSBuYW1lcyBBbiBBcnJheSBvZiBleHBlY3RlZCBUZW5zb3IgbmFtZXMuXG4gKiBAcGFyYW0gc2hhcGVzIE9wdGlvbmFsIEFycmF5IG9mIGV4cGVjdGVkIFRlbnNvciBzaGFwZXMuXG4gKiBAcGFyYW0gY2hlY2tCYXRjaEF4aXMgV2hldGhlciB0byBjaGVjayB0aGF0IHRoZSBiYXRjaCBheGlzIG9mIHRoZSBhcnJheXNcbiAqICAgbWF0Y2ggIHRoZSBleHBlY3RlZCB2YWx1ZSBmb3VuZCBpbiBgc2hhcGVzYC5cbiAqIEBwYXJhbSBleGNlcHRpb25QcmVmaXggU3RyaW5nIHByZWZpeCB1c2VkIGZvciBleGNlcHRpb24gZm9ybWF0dGluZy5cbiAqIEByZXR1cm5zIExpc3Qgb2Ygc3RhbmRhcmRpemVkIGlucHV0IFRlbnNvcnMgKG9uZSBUZW5zb3IgcGVyIG1vZGVsIGlucHV0KS5cbiAqIEB0aHJvd3MgVmFsdWVFcnJvcjogaW4gY2FzZSBvZiBpbXByb3Blcmx5IGZvcm1hdHRlZCB1c2VyIGRhdGEuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzdGFuZGFyZGl6ZUlucHV0RGF0YShcbiAgICBkYXRhOiBUZW5zb3J8VGVuc29yW118e1tpbnB1dE5hbWU6IHN0cmluZ106IFRlbnNvcn0sIG5hbWVzOiBzdHJpbmdbXSxcbiAgICBzaGFwZXM/OiBTaGFwZVtdLCBjaGVja0JhdGNoQXhpcyA9IHRydWUsIGV4Y2VwdGlvblByZWZpeCA9ICcnKTogVGVuc29yW10ge1xuICBpZiAobmFtZXMgPT0gbnVsbCB8fCBuYW1lcy5sZW5ndGggPT09IDApIHtcbiAgICAvLyBDaGVjayBmb3IgdGhlIGNhc2Ugd2hlcmUgdGhlIG1vZGVsIGV4cGVjdGVkIG5vIGRhdGEsIGJ1dCBzb21lIGRhdGEgZ290XG4gICAgLy8gc2VudC5cbiAgICBpZiAoZGF0YSAhPSBudWxsKSB7XG4gICAgICBsZXQgZ290VW5leHBlY3RlZERhdGEgPSBmYWxzZTtcbiAgICAgIGlmIChpc0RhdGFBcnJheShkYXRhKSAmJiAoZGF0YSBhcyBUZW5zb3JbXSkubGVuZ3RoID4gMCkge1xuICAgICAgICBnb3RVbmV4cGVjdGVkRGF0YSA9IHRydWU7XG4gICAgICB9IGVsc2UgaWYgKGlzRGF0YURpY3QoZGF0YSkpIHtcbiAgICAgICAgZm9yIChjb25zdCBrZXkgaW4gZGF0YSkge1xuICAgICAgICAgIGlmIChkYXRhLmhhc093blByb3BlcnR5KGtleSkpIHtcbiAgICAgICAgICAgIGdvdFVuZXhwZWN0ZWREYXRhID0gdHJ1ZTtcbiAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgLy8gYGRhdGFgIGlzIGEgc2luZ2xldG9uIFRlbnNvciBpbiB0aGlzIGNhc2UuXG4gICAgICAgIGdvdFVuZXhwZWN0ZWREYXRhID0gdHJ1ZTtcbiAgICAgIH1cbiAgICAgIGlmIChnb3RVbmV4cGVjdGVkRGF0YSkge1xuICAgICAgICB0aHJvdyBuZXcgVmFsdWVFcnJvcihcbiAgICAgICAgICAgIGBFcnJvciB3aGVuIGNoZWNraW5nIG1vZGVsICR7ZXhjZXB0aW9uUHJlZml4fSBleHBlY3RlZCBubyBkYXRhLCBgICtcbiAgICAgICAgICAgIGBidXQgZ290ICR7ZGF0YX1gKTtcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIFtdO1xuICB9XG4gIGlmIChkYXRhID09IG51bGwpIHtcbiAgICByZXR1cm4gbmFtZXMubWFwKG5hbWUgPT4gbnVsbCk7XG4gIH1cblxuICBsZXQgYXJyYXlzOiBUZW5zb3JbXTtcbiAgaWYgKGlzRGF0YURpY3QoZGF0YSkpIHtcbiAgICBkYXRhID0gZGF0YSBhcyB7W2lucHV0TmFtZTogc3RyaW5nXTogVGVuc29yfTtcbiAgICBhcnJheXMgPSBbXTtcbiAgICBmb3IgKGNvbnN0IG5hbWUgb2YgbmFtZXMpIHtcbiAgICAgIGlmIChkYXRhW25hbWVdID09IG51bGwpIHtcbiAgICAgICAgdGhyb3cgbmV3IFZhbHVlRXJyb3IoXG4gICAgICAgICAgICBgTm8gZGF0YSBwcm92aWRlZCBmb3IgXCIke25hbWV9XCIuIE5lZWQgZGF0YSBmb3IgZWFjaCBrZXkgaW46IGAgK1xuICAgICAgICAgICAgYCR7bmFtZXN9YCk7XG4gICAgICB9XG4gICAgICBhcnJheXMucHVzaChkYXRhW25hbWVdKTtcbiAgICB9XG4gIH0gZWxzZSBpZiAoaXNEYXRhQXJyYXkoZGF0YSkpIHtcbiAgICBkYXRhID0gZGF0YSBhcyBUZW5zb3JbXTtcbiAgICBpZiAoZGF0YS5sZW5ndGggIT09IG5hbWVzLmxlbmd0aCkge1xuICAgICAgdGhyb3cgbmV3IFZhbHVlRXJyb3IoXG4gICAgICAgICAgYEVycm9yIHdoZW4gY2hlY2tpbmcgbW9kZWwgJHtleGNlcHRpb25QcmVmaXh9OiB0aGUgQXJyYXkgb2YgYCArXG4gICAgICAgICAgYFRlbnNvcnMgdGhhdCB5b3UgYXJlIHBhc3NpbmcgdG8geW91ciBtb2RlbCBpcyBub3QgdGhlIHNpemUgdGhlIGAgK1xuICAgICAgICAgIGBtb2RlbCBleHBlY3RlZC4gRXhwZWN0ZWQgdG8gc2VlICR7bmFtZXMubGVuZ3RofSBUZW5zb3IocyksIGJ1dCBgICtcbiAgICAgICAgICBgaW5zdGVhZCBnb3QgdGhlIGZvbGxvd2luZyBsaXN0IG9mIFRlbnNvcihzKTogJHtkYXRhfWApO1xuICAgIH1cbiAgICBhcnJheXMgPSBkYXRhO1xuICB9IGVsc2Uge1xuICAgIGRhdGEgPSBkYXRhIGFzIFRlbnNvcjtcbiAgICBpZiAobmFtZXMubGVuZ3RoID4gMSkge1xuICAgICAgdGhyb3cgbmV3IFZhbHVlRXJyb3IoXG4gICAgICAgICAgYFRoZSBtb2RlbCAke2V4Y2VwdGlvblByZWZpeH0gZXhwZWN0cyAke25hbWVzLmxlbmd0aH0gVGVuc29yKHMpLCBgICtcbiAgICAgICAgICBgYnV0IG9ubHkgcmVjZWl2ZWQgb25lIFRlbnNvci4gRm91bmQ6IFRlbnNvciB3aXRoIHNoYXBlICR7XG4gICAgICAgICAgICAgIGRhdGEuc2hhcGV9YCk7XG4gICAgfVxuICAgIGFycmF5cyA9IFtkYXRhXTtcbiAgfVxuXG4gIGFycmF5cyA9IGVuc3VyZVRlbnNvcnNSYW5rMk9ySGlnaGVyKGFycmF5cyk7XG5cbiAgLy8gQ2hlY2sgc2hhcGUgY29tcGF0aWJpbGl0eS5cbiAgaWYgKHNoYXBlcyAhPSBudWxsKSB7XG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBuYW1lcy5sZW5ndGg7ICsraSkge1xuICAgICAgaWYgKHNoYXBlc1tpXSA9PSBudWxsKSB7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuICAgICAgY29uc3QgYXJyYXkgPSBhcnJheXNbaV07XG4gICAgICBpZiAoYXJyYXkuc2hhcGUubGVuZ3RoICE9PSBzaGFwZXNbaV0ubGVuZ3RoKSB7XG4gICAgICAgIHRocm93IG5ldyBWYWx1ZUVycm9yKFxuICAgICAgICAgICAgYEVycm9yIHdoZW4gY2hlY2tpbmcgJHtleGNlcHRpb25QcmVmaXh9OiBleHBlY3RlZCAke25hbWVzW2ldfSBgICtcbiAgICAgICAgICAgIGB0byBoYXZlICR7c2hhcGVzW2ldLmxlbmd0aH0gZGltZW5zaW9uKHMpLiBidXQgZ290IGFycmF5IHdpdGggYCArXG4gICAgICAgICAgICBgc2hhcGUgJHthcnJheS5zaGFwZX1gKTtcbiAgICAgIH1cbiAgICAgIGZvciAobGV0IGogPSAwOyBqIDwgc2hhcGVzW2ldLmxlbmd0aDsgKytqKSB7XG4gICAgICAgIGlmIChqID09PSAwICYmICFjaGVja0JhdGNoQXhpcykge1xuICAgICAgICAgIC8vIFNraXAgdGhlIGZpcnN0IChiYXRjaCkgYXhpcy5cbiAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBkaW0gPSBhcnJheS5zaGFwZVtqXTtcbiAgICAgICAgY29uc3QgcmVmRGltID0gc2hhcGVzW2ldW2pdO1xuICAgICAgICBpZiAocmVmRGltICE9IG51bGwgJiYgcmVmRGltID49IDAgJiYgZGltICE9PSByZWZEaW0pIHtcbiAgICAgICAgICB0aHJvdyBuZXcgVmFsdWVFcnJvcihcbiAgICAgICAgICAgICAgYCR7ZXhjZXB0aW9uUHJlZml4fSBleHBlY3RlZCBhIGJhdGNoIG9mIGVsZW1lbnRzIHdoZXJlIGVhY2ggYCArXG4gICAgICAgICAgICAgIGBleGFtcGxlIGhhcyBzaGFwZSBbJHtzaGFwZXNbaV0uc2xpY2UoMSwgc2hhcGVzW2ldLmxlbmd0aCl9XSBgICtcbiAgICAgICAgICAgICAgYChpLmUuLHRlbnNvciBzaGFwZSBbKiwke1xuICAgICAgICAgICAgICAgICAgc2hhcGVzW2ldLnNsaWNlKDEsIHNoYXBlc1tpXS5sZW5ndGgpfV0pYCArXG4gICAgICAgICAgICAgIGAgYnV0IHRoZSAke2V4Y2VwdGlvblByZWZpeH0gcmVjZWl2ZWQgYW4gaW5wdXQgd2l0aCAke1xuICAgICAgICAgICAgICAgICAgYXJyYXkuc2hhcGVbMF19YCArXG4gICAgICAgICAgICAgIGAgZXhhbXBsZXMsIGVhY2ggd2l0aCBzaGFwZSBbJHtcbiAgICAgICAgICAgICAgICAgIGFycmF5LnNoYXBlLnNsaWNlKDEsIGFycmF5LnNoYXBlLmxlbmd0aCl9XWAgK1xuICAgICAgICAgICAgICBgICh0ZW5zb3Igc2hhcGUgWyR7YXJyYXkuc2hhcGV9XSlgKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfVxuICByZXR1cm4gYXJyYXlzO1xufVxuXG4vKipcbiAqIFVzZXIgaW5wdXQgdmFsaWRhdGlvbiBmb3IgVGVuc29ycy5cbiAqIEBwYXJhbSBpbnB1dHMgYEFycmF5YCBvZiBgdGYuVGVuc29yYHMgZm9yIGlucHV0cy5cbiAqIEBwYXJhbSB0YXJnZXRzIGBBcnJheWAgb2YgYHRmLlRlbnNvcmBzIGZvciB0YXJnZXRzLlxuICogQHBhcmFtIHdlaWdodHMgT3B0aW9uYWwgYEFycmF5YCBvZiBgdGYuVGVuc29yYHMgZm9yIHNhbXBsZSB3ZWlnaHRzLlxuICogQHRocm93cyBWYWx1ZUVycm9yOiBpbiBjYXNlIG9mIGluY29ycmVjdGx5IGZvcm1hdHRlZCBkYXRhLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY2hlY2tBcnJheUxlbmd0aHMoXG4gICAgaW5wdXRzOiBUZW5zb3JbXSwgdGFyZ2V0czogVGVuc29yW10sIHdlaWdodHM/OiBUZW5zb3JbXSkge1xuICBjb25zdCBzZXRYID0gdW5pcXVlKGlucHV0cy5tYXAoaW5wdXQgPT4gaW5wdXQuc2hhcGVbMF0pKTtcbiAgc2V0WC5zb3J0KCk7XG4gIGNvbnN0IHNldFkgPSB1bmlxdWUodGFyZ2V0cy5tYXAodGFyZ2V0ID0+IHRhcmdldC5zaGFwZVswXSkpO1xuICBzZXRZLnNvcnQoKTtcbiAgLy8gVE9ETyhjYWlzKTogQ2hlY2sgYHdlaWdodHNgIGFzIHdlbGwuXG4gIGlmIChzZXRYLmxlbmd0aCA+IDEpIHtcbiAgICB0aHJvdyBuZXcgVmFsdWVFcnJvcihcbiAgICAgICAgYEFsbCBpbnB1dCBUZW5zb3JzICh4KSBzaG91bGQgaGF2ZSB0aGUgc2FtZSBudW1iZXIgb2Ygc2FtcGxlcy4gYCArXG4gICAgICAgIGBHb3QgYXJyYXkgc2hhcGVzOiBgICtcbiAgICAgICAgYCR7SlNPTi5zdHJpbmdpZnkoaW5wdXRzLm1hcChpbnB1dCA9PiBpbnB1dC5zaGFwZSkpfWApO1xuICB9XG4gIGlmIChzZXRZLmxlbmd0aCA+IDEpIHtcbiAgICB0aHJvdyBuZXcgVmFsdWVFcnJvcihcbiAgICAgICAgYEFsbCB0YXJnZXQgVGVuc29ycyAoeSkgc2hvdWxkIGhhdmUgdGhlIHNhbWUgbnVtYmVyIG9mIHNhbXBsZXMuIGAgK1xuICAgICAgICBgR290IGFycmF5IHNoYXBlczogYCArXG4gICAgICAgIGAke0pTT04uc3RyaW5naWZ5KHRhcmdldHMubWFwKHRhcmdldCA9PiB0YXJnZXQuc2hhcGUpKX1gKTtcbiAgfVxuICBpZiAoc2V0WC5sZW5ndGggPiAwICYmIHNldFkubGVuZ3RoID4gMCAmJiAhdXRpbC5hcnJheXNFcXVhbChzZXRYLCBzZXRZKSkge1xuICAgIHRocm93IG5ldyBWYWx1ZUVycm9yKFxuICAgICAgICBgSW5wdXQgVGVuc29ycyBzaG91bGQgaGF2ZSB0aGUgc2FtZSBudW1iZXIgb2Ygc2FtcGxlcyBhcyB0YXJnZXQgYCArXG4gICAgICAgIGBUZW5zb3JzLiBGb3VuZCAke3NldFhbMF19IGlucHV0IHNhbXBsZShzKSBhbmQgJHtzZXRZWzBdfSB0YXJnZXQgYCArXG4gICAgICAgIGBzYW1wbGUocykuYCk7XG4gIH1cbn1cblxuLyoqXG4gKiBWYWxpZGF0aW9uIG9uIHRoZSBjb21wYXRpYmlsaXR5IG9mIHRhcmdlcyBhbmQgbG9zcyBmdW5jdGlvbnMuXG4gKlxuICogVGhpcyBoZWxwcyBwcmV2ZW50IHVzZXJzIGZyb20gdXNpbmcgbG9zcyBmdW5jdGlvbnMgaW5jb3JyZWN0bHkuXG4gKlxuICogQHBhcmFtIHRhcmdldHMgYEFycmF5YCBvZiBgdGYuVGVuc29yYHMgb2YgdGFyZ2V0cy5cbiAqIEBwYXJhbSBsb3NzRm5zIGBBcnJheWAgb2YgbG9zcyBmdW5jdGlvbnMuXG4gKiBAcGFyYW0gb3V0cHV0U2hhcGVzIGBBcnJheWAgb2Ygc2hhcGVzIG9mIG1vZGVsIG91dHB1dHMuXG4gKi9cbmZ1bmN0aW9uIGNoZWNrTG9zc0FuZFRhcmdldENvbXBhdGliaWxpdHkoXG4gICAgdGFyZ2V0czogVGVuc29yW10sIGxvc3NGbnM6IExvc3NPck1ldHJpY0ZuW10sIG91dHB1dFNoYXBlczogU2hhcGVbXSkge1xuICAvLyBUT0RPKGNhaXMpOiBEZWRpY2F0ZWQgdGVzdCBjb3ZlcmFnZT9cbiAgY29uc3Qga2V5TG9zc2VzID0gW1xuICAgIGxvc3Nlcy5tZWFuU3F1YXJlZEVycm9yLCBsb3NzZXMuYmluYXJ5Q3Jvc3NlbnRyb3B5LFxuICAgIGxvc3Nlcy5jYXRlZ29yaWNhbENyb3NzZW50cm9weVxuICBdO1xuICBmb3IgKGxldCBpID0gMDsgaSA8IHRhcmdldHMubGVuZ3RoOyArK2kpIHtcbiAgICBjb25zdCB5ID0gdGFyZ2V0c1tpXTtcbiAgICBjb25zdCBsb3NzID0gbG9zc0Zuc1tpXTtcbiAgICBjb25zdCBzaGFwZSA9IG91dHB1dFNoYXBlc1tpXTtcbiAgICBpZiAobG9zcyA9PSBudWxsKSB7XG4gICAgICBjb250aW51ZTtcbiAgICB9XG4gICAgaWYgKGxvc3MgPT09IGxvc3Nlcy5jYXRlZ29yaWNhbENyb3NzZW50cm9weSkge1xuICAgICAgaWYgKHkuc2hhcGVbeS5zaGFwZS5sZW5ndGggLSAxXSA9PT0gMSkge1xuICAgICAgICB0aHJvdyBuZXcgVmFsdWVFcnJvcihcbiAgICAgICAgICAgIGBZb3UgYXJlIHBhc3NpbmcgYSB0YXJnZXQgYXJyYXkgb2Ygc2hhcGUgJHt5LnNoYXBlfSB3aGlsZSB1c2luZyBgICtcbiAgICAgICAgICAgIGBhIGxvc3MgJ2NhdGVnb3JpY2FsX2Nyb3NzZW50cm9weScuICdjYXRlZ29yaWNhbF9jcm9zc2VudHJvcHknYCArXG4gICAgICAgICAgICBgZXhwZWN0cyB0YXJnZXRzIHRvIGJlIGJpbmFyeSBtYXRyaWNlcyAoMXMgYW5kIDBzKSBvZiBzaGFwZSBgICtcbiAgICAgICAgICAgIGBbc2FtcGxlcywgY2xhc3Nlc10uYCk7XG4gICAgICAgIC8vIFRPRE8oY2Fpcyk6IEV4YW1wbGUgY29kZSBpbiBlcnJvciBtZXNzYWdlLlxuICAgICAgfVxuICAgIH1cbiAgICBpZiAoa2V5TG9zc2VzLmluZGV4T2YobG9zcykgIT09IC0xKSB7XG4gICAgICBjb25zdCBzbGljZWRZU2hhcGUgPSB5LnNoYXBlLnNsaWNlKDEpO1xuICAgICAgY29uc3Qgc2xpY2VkU2hhcGUgPSBzaGFwZS5zbGljZSgxKTtcbiAgICAgIGZvciAobGV0IGogPSAwOyBqIDwgc2xpY2VkWVNoYXBlLmxlbmd0aDsgKytqKSB7XG4gICAgICAgIGNvbnN0IHRhcmdldERpbSA9IHNsaWNlZFlTaGFwZVtqXTtcbiAgICAgICAgY29uc3Qgb3V0RGltID0gc2xpY2VkU2hhcGVbal07XG4gICAgICAgIGlmIChvdXREaW0gIT0gbnVsbCAmJiB0YXJnZXREaW0gIT09IG91dERpbSkge1xuICAgICAgICAgIHRocm93IG5ldyBWYWx1ZUVycm9yKFxuICAgICAgICAgICAgICBgQSB0YXJnZXQgVGVuc29yIHdpdGggc2hhcGUgJHt5LnNoYXBlfSB3YXMgcGFzc2VkIGZvciBhbiBgICtcbiAgICAgICAgICAgICAgYG91dHB1dCBvZiBzaGFwZSAke3NoYXBlfSwgd2hpbGUgdXNpbmcgYSBsb3NzIGZ1bmN0aW9uIHRoYXQgYCArXG4gICAgICAgICAgICAgIGBleHBlY3RzIHRhcmdldHMgdG8gaGF2ZSB0aGUgc2FtZSBzaGFwZSBhcyB0aGUgb3V0cHV0LmApO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG59XG5cbi8qKlxuICogQ2hlY2sgaW5wdXRzIHByb3ZpZGVkIGJ5IHRoZSB1c2VyLlxuICpcbiAqIFBvcnRpbmcgTm90ZTogVGhpcyBjb3JyZXNwb25kcyB0byBfc3RhbmRhcmRpemVfaW5wdXRfZGF0YSgpIGluIFB5dGhvblxuICogICBLZXJhcy4gQmVjYXVzZSBvZiB0aGUgc3Ryb25nIHR5cGluZyBpbiBURi5qcywgd2UgZG8gbm90IG5lZWQgdG8gY29udmVydFxuICogICB0aGUgZGF0YS4gU3BlY2lmaWNhbGx5OlxuICogICAxKSBpbiBQeUtlcmFzLCBgZGF0YWAgY2FuIGJlIGBEYXRhRnJhbWVgIGluc3RhbmNlcyBmcm9tIHBhbmRhcywgZm9yXG4gKiAgICAgIGV4YW1wbGUuIFdlIGRvbid0IG5lZWQgdG8gd29ycnkgYWJvdXQgdGhhdCBoZXJlIGJlY2F1c2UgdGhlcmUgaXMgbm9cbiAqICAgICAgd2lkZWx5IHBvcHVsYXIgamF2YXNjcmlwdC90eXBlc2RjcmlwdCBlcXVpdmFsZW50IG9mIHBhbmRhcyAoc28gZmFyKS5cbiAqICAgICAgSWYgb25lIGJlY29tZXMgYXZhaWxhYmxlIGluIHRoZSBmdXR1cmUsIHdlIGNhbiBhZGQgc3VwcG9ydC5cbiAqICAgMikgaW4gUHlLZXJhcywgaW5wdXRzIGNhbiBiZSBQeXRob24gZGljdC4gQnV0IGhlcmUgd2UgYXJlIHN0aXB1bGF0aW5nXG4gKiB0aGF0IHRoZSBkYXRhIGlzIGVpdGhlciBhIHNpbmdsZSBgdGYuVGVuc29yYCBvciBhbiBBcnJheSBvZiBgdGYuVGVuc29yYHMuIFdlXG4gKiBtYXkgYWRkIHN1cHBvcnQgZm9yIGBPYmplY3RgIGRhdGEgaW5wdXRzIGluIHRoZSBmdXR1cmUgd2hlbiB0aGUgbmVlZFxuICogYXJpc2VzLlxuICpcbiAqIEluc3RlYWQsIHdlIHBlcmZvcm0gYmFzaWMgY2hlY2tzIGZvciBudW1iZXIgb2YgcGFyYW1ldGVycyBhbmQgc2hhcGVzLlxuICpcbiAqIEBwYXJhbSBkYXRhOiBUaGUgaW5wdXQgZGF0YS5cbiAqIEBwYXJhbSBuYW1lczogTmFtZSBmb3IgdGhlIGlucHV0cywgZnJvbSB0aGUgbW9kZWwuXG4gKiBAcGFyYW0gc2hhcGVzOiBFeHBlY3RlZCBzaGFwZXMgZm9yIHRoZSBpbnB1dCBkYXRhLCBmcm9tIHRoZSBtb2RlbC5cbiAqIEBwYXJhbSBjaGVja0JhdGNoQXhpczogV2hldGhlciB0aGUgc2l6ZSBhbG9uZyB0aGUgYmF0Y2ggYXhpcyAoaS5lLiwgdGhlXG4gKiAgIGZpcnN0IGRpbWVuc2lvbikgd2lsbCBiZSBjaGVja2VkIGZvciBtYXRjaGluZy5cbiAqIEBwYXJhbSBleGNlcHRpb25QcmVmaXg6IEV4ZWNwdGlvbiBwcmVmaXggbWVzc2FnZSwgdXNlZCBpbiBnZW5lcmF0aW5nIGVycm9yXG4gKiAgIG1lc3NhZ2VzLlxuICogQHRocm93cyBWYWx1ZUVycm9yOiBvbiBpbmNvcnJlY3QgbnVtYmVyIG9mIGlucHV0cyBvciBtaXNtYXRjaGVzIGluIHNoYXBlcy5cbiAqL1xuZnVuY3Rpb24gY2hlY2tJbnB1dERhdGEoXG4gICAgZGF0YTogVGVuc29yfFRlbnNvcltdLCBuYW1lczogc3RyaW5nW10sIHNoYXBlcz86IFNoYXBlW10sXG4gICAgY2hlY2tCYXRjaEF4aXMgPSB0cnVlLCBleGNlcHRpb25QcmVmaXggPSAnJykge1xuICBsZXQgYXJyYXlzOiBUZW5zb3JbXTtcbiAgaWYgKEFycmF5LmlzQXJyYXkoZGF0YSkpIHtcbiAgICBpZiAoZGF0YS5sZW5ndGggIT09IG5hbWVzLmxlbmd0aCkge1xuICAgICAgdGhyb3cgbmV3IFZhbHVlRXJyb3IoXG4gICAgICAgICAgYEVycm9yIHdoZW4gY2hlY2tpbmcgbW9kZWwgJHtleGNlcHRpb25QcmVmaXh9OiB0aGUgQXJyYXkgb2YgYCArXG4gICAgICAgICAgYFRlbnNvcnMgdGhhdCB5b3UgYXJlIHBhc3NpbmcgdG8geW91ciBtb2RlbCBpcyBub3QgdGhlIHNpemUgdGhlIGAgK1xuICAgICAgICAgIGB0aGUgbW9kZWwgZXhwZWN0ZWQuIEV4cGVjdGVkIHRvIHNlZSAke25hbWVzLmxlbmd0aH0gVGVuc29yKHMpLGAgK1xuICAgICAgICAgIGAgYnV0IGluc3RlYWQgZ290ICR7ZGF0YS5sZW5ndGh9IFRlbnNvcnMocykuYCk7XG4gICAgfVxuICAgIGFycmF5cyA9IGRhdGE7XG4gIH0gZWxzZSB7XG4gICAgaWYgKG5hbWVzLmxlbmd0aCA+IDEpIHtcbiAgICAgIHRocm93IG5ldyBWYWx1ZUVycm9yKFxuICAgICAgICAgIGBUaGUgbW9kZWwgZXhwZWN0cyAke25hbWVzLmxlbmd0aH0gJHtleGNlcHRpb25QcmVmaXh9IFRlbnNvcnMsIGAgK1xuICAgICAgICAgIGBidXQgb25seSByZWNlaXZlZCBvbmUgVGVuc29yLiBGb3VuZDogYXJyYXkgd2l0aCBzaGFwZSBgICtcbiAgICAgICAgICBgJHtKU09OLnN0cmluZ2lmeShkYXRhLnNoYXBlKX0uYCk7XG4gICAgfVxuICAgIGFycmF5cyA9IFtkYXRhXTtcbiAgfVxuXG4gIGlmIChzaGFwZXMgIT0gbnVsbCkge1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgbmFtZXMubGVuZ3RoOyArK2kpIHtcbiAgICAgIGlmIChzaGFwZXNbaV0gPT0gbnVsbCkge1xuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cbiAgICAgIGNvbnN0IGFycmF5ID0gYXJyYXlzW2ldO1xuICAgICAgaWYgKGFycmF5LnNoYXBlLmxlbmd0aCAhPT0gc2hhcGVzW2ldLmxlbmd0aCkge1xuICAgICAgICB0aHJvdyBuZXcgVmFsdWVFcnJvcihcbiAgICAgICAgICAgIGBFcnJvciB3aGVuIGNoZWNraW5nICR7ZXhjZXB0aW9uUHJlZml4fTogZXhwZWN0ZWQgJHtuYW1lc1tpXX0gYCArXG4gICAgICAgICAgICBgdG8gaGF2ZSAke3NoYXBlc1tpXS5sZW5ndGh9IGRpbWVuc2lvbihzKSwgYnV0IGdvdCBhcnJheSB3aXRoIGAgK1xuICAgICAgICAgICAgYHNoYXBlICR7SlNPTi5zdHJpbmdpZnkoYXJyYXkuc2hhcGUpfWApO1xuICAgICAgfVxuICAgICAgZm9yIChsZXQgaiA9IDA7IGogPCBzaGFwZXNbaV0ubGVuZ3RoOyArK2opIHtcbiAgICAgICAgaWYgKGogPT09IDAgJiYgIWNoZWNrQmF0Y2hBeGlzKSB7XG4gICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgZGltID0gYXJyYXkuc2hhcGVbal07XG4gICAgICAgIGNvbnN0IHJlZkRpbSA9IHNoYXBlc1tpXVtqXTtcbiAgICAgICAgaWYgKHJlZkRpbSAhPSBudWxsKSB7XG4gICAgICAgICAgaWYgKHJlZkRpbSAhPT0gZGltKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgVmFsdWVFcnJvcihcbiAgICAgICAgICAgICAgICBgRXJyb3Igd2hlbiBjaGVja2luZyAke2V4Y2VwdGlvblByZWZpeH06IGV4cGVjdGVkIGAgK1xuICAgICAgICAgICAgICAgIGAke25hbWVzW2ldfSB0byBoYXZlIHNoYXBlICR7SlNPTi5zdHJpbmdpZnkoc2hhcGVzW2ldKX0gYnV0IGAgK1xuICAgICAgICAgICAgICAgIGBnb3QgYXJyYXkgd2l0aCBzaGFwZSAke0pTT04uc3RyaW5naWZ5KGFycmF5LnNoYXBlKX0uYCk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG59XG5cbi8qKlxuICogTWFwcyBtZXRyaWMgZnVuY3Rpb25zIHRvIG1vZGVsIG91dHB1dHMuXG4gKiBAcGFyYW0gbWV0cmljcyBBbiBzaG9ydGN1dCBzdHJpbmdzIG5hbWUsIG1ldHJpYyBmdW5jdGlvbiwgYEFycmF5YCBvciBkaWN0XG4gKiAgIChgT2JqZWN0YCkgb2YgbWV0cmljIGZ1bmN0aW9ucy5cbiAqIEBwYXJhbSBvdXRwdXROYW1lcyBBbiBgQXJyYXlgIG9mIHRoZSBuYW1lcyBvZiBtb2RlbCBvdXRwdXRzLlxuICogQHJldHVybnMgQW4gYEFycmF5YCAob25lIGVudHJ5IHBlciBtb2RlbCBvdXRwdXQpIG9mIGBBcnJheWAgb2YgbWV0cmljXG4gKiAgIGZ1bmN0aW9ucy4gRm9yIGluc3RhbmNlLCBpZiB0aGUgbW9kZWwgaGFzIDIgb3V0cHV0cywgYW5kIGZvciB0aGUgZmlyc3RcbiAqICAgb3V0cHV0IHdlIHdhbnQgdG8gY29tcHV0ZSBgYmluYXJ5QWNjdXJhY3lgIGFuZCBgYmluYXJ5Q3Jvc3NlbnRyb3B5YCxcbiAqICAgYW5kIGp1c3QgYGJpbmFyeUFjY3VyYWN5YCBmb3IgdGhlIHNlY29uZCBvdXRwdXQsIHRoZSBgQXJyYXlgIHdvdWxkIGxvb2tcbiAqICAgbGlrZTpcbiAqICAgICBgW1tiaW5hcnlBY2N1cmFjeSwgYmluYXJ5Q3Jvc3NlbnRyb3B5XSwgIFtiaW5hcnlBY2N1cmFjeV1dYFxuICogQHRocm93cyBUeXBlRXJyb3I6IGluY29tcGF0aWJsZSBtZXRyaWNzIGZvcm1hdC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNvbGxlY3RNZXRyaWNzKFxuICAgIG1ldHJpY3M6IHN0cmluZ3xMb3NzT3JNZXRyaWNGbnxBcnJheTxzdHJpbmd8TG9zc09yTWV0cmljRm4+fFxuICAgIHtbb3V0cHV0TmFtZTogc3RyaW5nXTogc3RyaW5nIHwgTG9zc09yTWV0cmljRm59LFxuICAgIG91dHB1dE5hbWVzOiBzdHJpbmdbXSk6IEFycmF5PEFycmF5PHN0cmluZ3xMb3NzT3JNZXRyaWNGbj4+IHtcbiAgaWYgKG1ldHJpY3MgPT0gbnVsbCB8fCBBcnJheS5pc0FycmF5KG1ldHJpY3MpICYmIG1ldHJpY3MubGVuZ3RoID09PSAwKSB7XG4gICAgcmV0dXJuIG91dHB1dE5hbWVzLm1hcChuYW1lID0+IFtdKTtcbiAgfVxuXG4gIGxldCB3cmFwcGVkTWV0cmljczogQXJyYXk8c3RyaW5nfExvc3NPck1ldHJpY0ZuPnxcbiAgICAgIHtbb3V0cHV0TmFtZTogc3RyaW5nXTogc3RyaW5nIHwgTG9zc09yTWV0cmljRm59O1xuICBpZiAodHlwZW9mIG1ldHJpY3MgPT09ICdzdHJpbmcnIHx8IHR5cGVvZiBtZXRyaWNzID09PSAnZnVuY3Rpb24nKSB7XG4gICAgd3JhcHBlZE1ldHJpY3MgPSBbbWV0cmljc107XG4gIH0gZWxzZSBpZiAoQXJyYXkuaXNBcnJheShtZXRyaWNzKSB8fCB0eXBlb2YgbWV0cmljcyA9PT0gJ29iamVjdCcpIHtcbiAgICB3cmFwcGVkTWV0cmljcyA9IG1ldHJpY3MgYXMgQXJyYXk8c3RyaW5nfExvc3NPck1ldHJpY0ZuPnxcbiAgICAgICAge1tvdXRwdXROYW1lOiBzdHJpbmddOiBzdHJpbmd9IHwge1tvdXRwdXROYW1lOiBzdHJpbmddOiBMb3NzT3JNZXRyaWNGbn07XG4gIH0gZWxzZSB7XG4gICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcbiAgICAgICAgJ1R5cGUgb2YgbWV0cmljcyBhcmd1bWVudCBub3QgdW5kZXJzdG9vZC4gRXhwZWN0ZWQgYW4gc3RyaW5nLCcgK1xuICAgICAgICBgZnVuY3Rpb24sIEFycmF5LCBvciBPYmplY3QsIGZvdW5kOiAke21ldHJpY3N9YCk7XG4gIH1cblxuICBpZiAoQXJyYXkuaXNBcnJheSh3cmFwcGVkTWV0cmljcykpIHtcbiAgICAvLyBXZSB0aGVuIGFwcGx5IGFsbCBtZXRyaWNzIHRvIGFsbCBvdXRwdXRzLlxuICAgIHJldHVybiBvdXRwdXROYW1lcy5tYXAoXG4gICAgICAgIG5hbWUgPT4gd3JhcHBlZE1ldHJpY3MgYXMgQXJyYXk8c3RyaW5nfExvc3NPck1ldHJpY0ZuPik7XG4gIH0gZWxzZSB7XG4gICAgLy8gSW4gdGhpcyBjYXNlLCBtZXRyaWNzIGlzIGEgZGljdC5cbiAgICBjb25zdCBuZXN0ZWRNZXRyaWNzOiBBcnJheTxBcnJheTxzdHJpbmd8TG9zc09yTWV0cmljRm4+PiA9IFtdO1xuICAgIGZvciAoY29uc3QgbmFtZSBvZiBvdXRwdXROYW1lcykge1xuICAgICAgbGV0IG91dHB1dE1ldHJpY3M6IHN0cmluZ3xMb3NzT3JNZXRyaWNGbnxBcnJheTxzdHJpbmd8TG9zc09yTWV0cmljRm4+ID1cbiAgICAgICAgICB3cmFwcGVkTWV0cmljcy5oYXNPd25Qcm9wZXJ0eShuYW1lKSA/IHdyYXBwZWRNZXRyaWNzW25hbWVdIDogW107XG4gICAgICBpZiAoIUFycmF5LmlzQXJyYXkob3V0cHV0TWV0cmljcykpIHtcbiAgICAgICAgb3V0cHV0TWV0cmljcyA9IFtvdXRwdXRNZXRyaWNzXTtcbiAgICAgIH1cbiAgICAgIG5lc3RlZE1ldHJpY3MucHVzaChvdXRwdXRNZXRyaWNzKTtcbiAgICB9XG4gICAgcmV0dXJuIG5lc3RlZE1ldHJpY3M7XG4gIH1cbn1cblxuZXhwb3J0IGludGVyZmFjZSBNb2RlbEV2YWx1YXRlQXJncyB7XG4gIC8qKlxuICAgKiBCYXRjaCBzaXplIChJbnRlZ2VyKS4gSWYgdW5zcGVjaWZpZWQsIGl0IHdpbGwgZGVmYXVsdCB0byAzMi5cbiAgICovXG4gIGJhdGNoU2l6ZT86IG51bWJlcjtcblxuICAvKipcbiAgICogVmVyYm9zaXR5IG1vZGUuXG4gICAqL1xuICB2ZXJib3NlPzogTW9kZWxMb2dnaW5nVmVyYm9zaXR5O1xuXG4gIC8qKlxuICAgKiBUZW5zb3Igb2Ygd2VpZ2h0cyB0byB3ZWlnaHQgdGhlIGNvbnRyaWJ1dGlvbiBvZiBkaWZmZXJlbnQgc2FtcGxlcyB0byB0aGVcbiAgICogbG9zcyBhbmQgbWV0cmljcy5cbiAgICovXG4gIHNhbXBsZVdlaWdodD86IFRlbnNvcjtcblxuICAvKipcbiAgICogaW50ZWdlcjogdG90YWwgbnVtYmVyIG9mIHN0ZXBzIChiYXRjaGVzIG9mIHNhbXBsZXMpXG4gICAqIGJlZm9yZSBkZWNsYXJpbmcgdGhlIGV2YWx1YXRpb24gcm91bmQgZmluaXNoZWQuIElnbm9yZWQgd2l0aCB0aGUgZGVmYXVsdFxuICAgKiB2YWx1ZSBvZiBgdW5kZWZpbmVkYC5cbiAgICovXG4gIHN0ZXBzPzogbnVtYmVyO1xufVxuXG4vKipcbiAqIENvbmZpZ3VyYXRpb24gZm9yIGNhbGxzIHRvIGBMYXllcnNNb2RlbC5jb21waWxlKClgLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIE1vZGVsQ29tcGlsZUFyZ3Mge1xuICAvKipcbiAgICogQW4gaW5zdGFuY2Ugb2YgYHRmLnRyYWluLk9wdGltaXplcmAgb3IgYSBzdHJpbmcgbmFtZSBmb3IgYW4gT3B0aW1pemVyLlxuICAgKi9cbiAgb3B0aW1pemVyOiBzdHJpbmd8T3B0aW1pemVyO1xuXG4gIC8qKlxuICAgKiBPYmplY3QgZnVuY3Rpb24ocykgb3IgbmFtZShzKSBvZiBvYmplY3QgZnVuY3Rpb24ocykuXG4gICAqIElmIHRoZSBtb2RlbCBoYXMgbXVsdGlwbGUgb3V0cHV0cywgeW91IGNhbiB1c2UgYSBkaWZmZXJlbnQgbG9zc1xuICAgKiBvbiBlYWNoIG91dHB1dCBieSBwYXNzaW5nIGEgZGljdGlvbmFyeSBvciBhbiBBcnJheSBvZiBsb3NzZXMuXG4gICAqIFRoZSBsb3NzIHZhbHVlIHRoYXQgd2lsbCBiZSBtaW5pbWl6ZWQgYnkgdGhlIG1vZGVsIHdpbGwgdGhlbiBiZSB0aGUgc3VtXG4gICAqIG9mIGFsbCBpbmRpdmlkdWFsIGxvc3Nlcy5cbiAgICovXG4gIGxvc3M6IHN0cmluZ3xzdHJpbmdbXXx7W291dHB1dE5hbWU6IHN0cmluZ106IHN0cmluZ318TG9zc09yTWV0cmljRm58XG4gICAgICBMb3NzT3JNZXRyaWNGbltdfHtbb3V0cHV0TmFtZTogc3RyaW5nXTogTG9zc09yTWV0cmljRm59O1xuXG4gIC8qKlxuICAgKiBMaXN0IG9mIG1ldHJpY3MgdG8gYmUgZXZhbHVhdGVkIGJ5IHRoZSBtb2RlbCBkdXJpbmcgdHJhaW5pbmcgYW5kIHRlc3RpbmcuXG4gICAqIFR5cGljYWxseSB5b3Ugd2lsbCB1c2UgYG1ldHJpY3M9WydhY2N1cmFjeSddYC5cbiAgICogVG8gc3BlY2lmeSBkaWZmZXJlbnQgbWV0cmljcyBmb3IgZGlmZmVyZW50IG91dHB1dHMgb2YgYSBtdWx0aS1vdXRwdXRcbiAgICogbW9kZWwsIHlvdSBjb3VsZCBhbHNvIHBhc3MgYSBkaWN0aW9uYXJ5LlxuICAgKi9cbiAgbWV0cmljcz86IHN0cmluZ3xMb3NzT3JNZXRyaWNGbnxBcnJheTxzdHJpbmd8TG9zc09yTWV0cmljRm4+fFxuICAgICAge1tvdXRwdXROYW1lOiBzdHJpbmddOiBzdHJpbmcgfCBMb3NzT3JNZXRyaWNGbn07XG5cbiAgLy8gVE9ETyhjYWlzKTogQWRkIGxvc3NXZWlnaHRzLCBzYW1wbGVXZWlnaHRNb2RlLCB3ZWlnaHRlZE1ldHJpY3MsIGFuZFxuICAvLyAgIHRhcmdldFRlbnNvcnMuXG59XG5cbmNvbnN0IExBWUVSU19NT0RFTF9GT1JNQVRfTkFNRSA9ICdsYXllcnMtbW9kZWwnO1xuXG4vKipcbiAqIEEgYHRmLkxheWVyc01vZGVsYCBpcyBhIGRpcmVjdGVkLCBhY3ljbGljIGdyYXBoIG9mIGB0Zi5MYXllcmBzIHBsdXMgbWV0aG9kc1xuICogZm9yIHRyYWluaW5nLCBldmFsdWF0aW9uLCBwcmVkaWN0aW9uIGFuZCBzYXZpbmcuXG4gKlxuICogYHRmLkxheWVyc01vZGVsYCBpcyB0aGUgYmFzaWMgdW5pdCBvZiB0cmFpbmluZywgaW5mZXJlbmNlIGFuZCBldmFsdWF0aW9uIGluXG4gKiBUZW5zb3JGbG93LmpzLiBUbyBjcmVhdGUgYSBgdGYuTGF5ZXJzTW9kZWxgLCB1c2UgYHRmLkxheWVyc01vZGVsYC5cbiAqXG4gKiBTZWUgYWxzbzpcbiAqICAgYHRmLlNlcXVlbnRpYWxgLCBgdGYubG9hZExheWVyc01vZGVsYC5cbiAqXG4gKiBAZG9jIHtoZWFkaW5nOiAnTW9kZWxzJywgc3ViaGVhZGluZzogJ0NsYXNzZXMnfVxuICovXG5leHBvcnQgY2xhc3MgTGF5ZXJzTW9kZWwgZXh0ZW5kcyBDb250YWluZXIgaW1wbGVtZW50cyB0ZmMuSW5mZXJlbmNlTW9kZWwge1xuICAvLyBUaGUgY2xhc3MgbmFtZSBpcyAnTW9kZWwnIHJhdGhlciB0aGFuICdMYXllcnNNb2RlbCcgZm9yIGJhY2t3YXJkc1xuICAvLyBjb21wYXRpYmlsaXR5IHNpbmNlIHRoaXMgY2xhc3MgbmFtZSBzaG93cyB1cCBpbiB0aGUgc2VyaWFsaXphdGlvbiBmb3JtYXQuXG4gIC8qKiBAbm9jb2xsYXBzZSAqL1xuICBzdGF0aWMgY2xhc3NOYW1lID0gJ01vZGVsJztcbiAgcHJvdGVjdGVkIG9wdGltaXplcl86IE9wdGltaXplcjtcbiAgLy8gV2hldGhlciB0aGUgbW9kZWwgaW5zdGFuY2Ugb3ducyB0aGUgb3B0aW1pemVyOiBgdHJ1ZWAgaWYgYW5kIG9ubHkgaWZcbiAgLy8gYG9wdGltaXplcmAgaXMgY3JlYXRlZCBmcm9tIGEgc3RyaW5nIHBhcmFtZXRlciBkdXJpbmcgYGNvbXBpbGUoKWAgY2FsbC5cbiAgcHJvdGVjdGVkIGlzT3B0aW1pemVyT3duZWQ6IGJvb2xlYW47XG5cbiAgbG9zczogc3RyaW5nfHN0cmluZ1tdfHtbb3V0cHV0TmFtZTogc3RyaW5nXTogc3RyaW5nfXxMb3NzT3JNZXRyaWNGbnxcbiAgICAgIExvc3NPck1ldHJpY0ZuW118e1tvdXRwdXROYW1lOiBzdHJpbmddOiBMb3NzT3JNZXRyaWNGbn07XG4gIGxvc3NGdW5jdGlvbnM6IExvc3NPck1ldHJpY0ZuW107XG5cbiAgLy8gVE9ETyhjYWlzKTogVGhlc2UgcHJpdmF0ZSB2YXJpYWJsZXMgc2hvdWxkIHByb2JhYmx5IG5vdCBoYXZlIHRoZSBzdHJpbmdcbiAgLy8gICAnZmVlZCcgaW4gdGhlaXIgbmFtZXMsIGJlY2F1c2Ugd2UgYXJlIG5vdCBkZWFsaW5nIHdpdGggYSBzeW1ib2xpY1xuICAvLyAgIGJhY2tlbmQuXG4gIHByaXZhdGUgZmVlZE91dHB1dFNoYXBlczogU2hhcGVbXTtcbiAgcHJpdmF0ZSBmZWVkTG9zc0ZuczogTG9zc09yTWV0cmljRm5bXTtcbiAgcHJpdmF0ZSBjb2xsZWN0ZWRUcmFpbmFibGVXZWlnaHRzOiBMYXllclZhcmlhYmxlW107XG4gIHByaXZhdGUgdGVzdEZ1bmN0aW9uOiAoZGF0YTogVGVuc29yW10pID0+IFNjYWxhcltdO1xuICBoaXN0b3J5OiBIaXN0b3J5O1xuXG4gIC8vIEEgcHVibGljIHByb3BlcnR5IHRoYXQgY2FuIGJlIHNldCBieSBDYWxsYmFja3MgdG8gb3JkZXIgZWFybHkgc3RvcHBpbmdcbiAgLy8gZHVyaW5nIGBmaXQoKWAgY2FsbHMuXG4gIHByb3RlY3RlZCBzdG9wVHJhaW5pbmdfOiBib29sZWFuO1xuICBwcm90ZWN0ZWQgaXNUcmFpbmluZzogYm9vbGVhbjtcblxuICBtZXRyaWNzOiBzdHJpbmd8TG9zc09yTWV0cmljRm58QXJyYXk8c3RyaW5nfExvc3NPck1ldHJpY0ZuPnxcbiAgICAgIHtbb3V0cHV0TmFtZTogc3RyaW5nXTogc3RyaW5nIHwgTG9zc09yTWV0cmljRm59O1xuICBtZXRyaWNzTmFtZXM6IHN0cmluZ1tdO1xuICAvLyBQb3J0aW5nIE5vdGU6IGBtZXRyaWNzX3RlbnNvcnNgIGluIFB5S2VyYXMgaXMgYSBzeW1ib2xpYyB0ZW5zb3IuIEJ1dCBnaXZlblxuICAvLyAgIHRoZSBpbXBlcmF0aXZlIG5hdHVyZSBvZiB0ZmpzLWNvcmUsIGBtZXRyaWNzVGVuc29yc2AgaXMgYVxuICAvLyAgIFR5cGVTY3JpcHQgZnVuY3Rpb24gaGVyZS5cbiAgLy8gICBBbHNvIG5vdGUgdGhhdCBkdWUgdG8gdGhlIGltcGVyYXRpdmUgbmF0dXJlIG9mIHRmanMtY29yZSwgYG1ldHJpY3NUZW5zb3JgXG4gIC8vICAgaGVyZSBuZWVkcyBhbiBvdXRwdXQgaW5kZXggdG8ga2VlcCB0cmFjayBvZiB3aGljaCBvdXRwdXQgb2YgdGhlXG4gIC8vICAgTGF5ZXJzTW9kZWwgYSBtZXRyaWMgYmVsb25ncyB0by4gVGhpcyBpcyB1bmxpa2UgYG1ldHJpY3NfdGVuc29yc2AgaW5cbiAgLy8gICBQeUtlcmFzLCB3aGljaCBpcyBhIGBsaXN0YCBvZiBzeW1ib2xpYyB0ZW5zb3JzLCBlYWNoIG9mIHdoaWNoIGhhc1xuICAvLyAgIGltcGxpY2l0IFwia25vd2xlZGdlXCIgb2YgdGhlIG91dHB1dHMgaXQgZGVwZW5kcyBvbi5cbiAgbWV0cmljc1RlbnNvcnM6IEFycmF5PFtMb3NzT3JNZXRyaWNGbiwgbnVtYmVyXT47XG5cbiAgLy8gVXNlciBkZWZpbmQgbWV0YWRhdGEgKGlmIGFueSkuXG4gIHByaXZhdGUgdXNlckRlZmluZWRNZXRhZGF0YToge307XG5cbiAgY29uc3RydWN0b3IoYXJnczogQ29udGFpbmVyQXJncykge1xuICAgIHN1cGVyKGFyZ3MpO1xuICAgIHRoaXMuaXNUcmFpbmluZyA9IGZhbHNlO1xuICB9XG5cbiAgLyoqXG4gICAqIFByaW50IGEgdGV4dCBzdW1tYXJ5IG9mIHRoZSBtb2RlbCdzIGxheWVycy5cbiAgICpcbiAgICogVGhlIHN1bW1hcnkgaW5jbHVkZXNcbiAgICogLSBOYW1lIGFuZCB0eXBlIG9mIGFsbCBsYXllcnMgdGhhdCBjb21wcmlzZSB0aGUgbW9kZWwuXG4gICAqIC0gT3V0cHV0IHNoYXBlKHMpIG9mIHRoZSBsYXllcnNcbiAgICogLSBOdW1iZXIgb2Ygd2VpZ2h0IHBhcmFtZXRlcnMgb2YgZWFjaCBsYXllclxuICAgKiAtIElmIHRoZSBtb2RlbCBoYXMgbm9uLXNlcXVlbnRpYWwtbGlrZSB0b3BvbG9neSwgdGhlIGlucHV0cyBlYWNoIGxheWVyXG4gICAqICAgcmVjZWl2ZXNcbiAgICogLSBUaGUgdG90YWwgbnVtYmVyIG9mIHRyYWluYWJsZSBhbmQgbm9uLXRyYWluYWJsZSBwYXJhbWV0ZXJzIG9mIHRoZSBtb2RlbC5cbiAgICpcbiAgICogYGBganNcbiAgICogY29uc3QgaW5wdXQxID0gdGYuaW5wdXQoe3NoYXBlOiBbMTBdfSk7XG4gICAqIGNvbnN0IGlucHV0MiA9IHRmLmlucHV0KHtzaGFwZTogWzIwXX0pO1xuICAgKiBjb25zdCBkZW5zZTEgPSB0Zi5sYXllcnMuZGVuc2Uoe3VuaXRzOiA0fSkuYXBwbHkoaW5wdXQxKTtcbiAgICogY29uc3QgZGVuc2UyID0gdGYubGF5ZXJzLmRlbnNlKHt1bml0czogOH0pLmFwcGx5KGlucHV0Mik7XG4gICAqIGNvbnN0IGNvbmNhdCA9IHRmLmxheWVycy5jb25jYXRlbmF0ZSgpLmFwcGx5KFtkZW5zZTEsIGRlbnNlMl0pO1xuICAgKiBjb25zdCBvdXRwdXQgPVxuICAgKiAgICAgdGYubGF5ZXJzLmRlbnNlKHt1bml0czogMywgYWN0aXZhdGlvbjogJ3NvZnRtYXgnfSkuYXBwbHkoY29uY2F0KTtcbiAgICpcbiAgICogY29uc3QgbW9kZWwgPSB0Zi5tb2RlbCh7aW5wdXRzOiBbaW5wdXQxLCBpbnB1dDJdLCBvdXRwdXRzOiBvdXRwdXR9KTtcbiAgICogbW9kZWwuc3VtbWFyeSgpO1xuICAgKiBgYGBcbiAgICpcbiAgICogQHBhcmFtIGxpbmVMZW5ndGggQ3VzdG9tIGxpbmUgbGVuZ3RoLCBpbiBudW1iZXIgb2YgY2hhcmFjdGVycy5cbiAgICogQHBhcmFtIHBvc2l0aW9ucyBDdXN0b20gd2lkdGhzIG9mIGVhY2ggb2YgdGhlIGNvbHVtbnMsIGFzIGVpdGhlclxuICAgKiAgIGZyYWN0aW9ucyBvZiBgbGluZUxlbmd0aGAgKGUuZy4sIGBbMC41LCAwLjc1LCAxXWApIG9yIGFic29sdXRlIG51bWJlclxuICAgKiAgIG9mIGNoYXJhY3RlcnMgKGUuZy4sIGBbMzAsIDUwLCA2NV1gKS4gRWFjaCBudW1iZXIgY29ycmVzcG9uZHMgdG9cbiAgICogICByaWdodC1tb3N0IChpLmUuLCBlbmRpbmcpIHBvc2l0aW9uIG9mIGEgY29sdW1uLlxuICAgKiBAcGFyYW0gcHJpbnRGbiBDdXN0b20gcHJpbnQgZnVuY3Rpb24uIENhbiBiZSB1c2VkIHRvIHJlcGxhY2UgdGhlIGRlZmF1bHRcbiAgICogICBgY29uc29sZS5sb2dgLiBGb3IgZXhhbXBsZSwgeW91IGNhbiB1c2UgYHggPT4ge31gIHRvIG11dGUgdGhlIHByaW50ZWRcbiAgICogICBtZXNzYWdlcyBpbiB0aGUgY29uc29sZS5cbiAgICpcbiAgICogQGRvYyB7aGVhZGluZzogJ01vZGVscycsIHN1YmhlYWRpbmc6ICdDbGFzc2VzJ31cbiAgICovXG4gIHN1bW1hcnkoXG4gICAgICBsaW5lTGVuZ3RoPzogbnVtYmVyLCBwb3NpdGlvbnM/OiBudW1iZXJbXSxcbiAgICAgIHByaW50Rm46XG4gICAgICAgICAgLy8gdHNsaW50OmRpc2FibGUtbmV4dC1saW5lOm5vLWFueVxuICAgICAgKG1lc3NhZ2U/OiBhbnksIC4uLm9wdGlvbmFsUGFyYW1zOiBhbnlbXSkgPT4gdm9pZCA9IGNvbnNvbGUubG9nKSB7XG4gICAgaWYgKCF0aGlzLmJ1aWx0KSB7XG4gICAgICB0aHJvdyBuZXcgVmFsdWVFcnJvcihcbiAgICAgICAgICBgVGhpcyBtb2RlbCBoYXMgbmV2ZXIgYmVlbiBjYWxsZWQsIHRodXMgaXRzIHdlaWdodHMgaGF2ZSBub3QgYmVlbiBgICtcbiAgICAgICAgICBgY3JlYXRlZCB5ZXQuIFNvIG5vIHN1bW1hcnkgY2FuIGJlIGRpc3BsYXllZC4gQnVpbGQgdGhlIG1vZGVsIGAgK1xuICAgICAgICAgIGBmaXJzdCAoZS5nLiwgYnkgY2FsbGluZyBpdCBvbiBzb21lIHRlc3QgZGF0YSkuYCk7XG4gICAgfVxuICAgIHByaW50U3VtbWFyeSh0aGlzLCBsaW5lTGVuZ3RoLCBwb3NpdGlvbnMsIHByaW50Rm4pO1xuICB9XG5cbiAgLyoqXG4gICAqIENvbmZpZ3VyZXMgYW5kIHByZXBhcmVzIHRoZSBtb2RlbCBmb3IgdHJhaW5pbmcgYW5kIGV2YWx1YXRpb24uICBDb21waWxpbmdcbiAgICogb3V0Zml0cyB0aGUgbW9kZWwgd2l0aCBhbiBvcHRpbWl6ZXIsIGxvc3MsIGFuZC9vciBtZXRyaWNzLiAgQ2FsbGluZyBgZml0YFxuICAgKiBvciBgZXZhbHVhdGVgIG9uIGFuIHVuLWNvbXBpbGVkIG1vZGVsIHdpbGwgdGhyb3cgYW4gZXJyb3IuXG4gICAqXG4gICAqIEBwYXJhbSBhcmdzIGEgYE1vZGVsQ29tcGlsZUFyZ3NgIHNwZWNpZnlpbmcgdGhlIGxvc3MsIG9wdGltaXplciwgYW5kXG4gICAqIG1ldHJpY3MgdG8gYmUgdXNlZCBmb3IgZml0dGluZyBhbmQgZXZhbHVhdGluZyB0aGlzIG1vZGVsLlxuICAgKlxuICAgKiBAZG9jIHtoZWFkaW5nOiAnTW9kZWxzJywgc3ViaGVhZGluZzogJ0NsYXNzZXMnfVxuICAgKi9cbiAgY29tcGlsZShhcmdzOiBNb2RlbENvbXBpbGVBcmdzKTogdm9pZCB7XG4gICAgaWYgKGFyZ3MubG9zcyA9PSBudWxsKSB7XG4gICAgICBhcmdzLmxvc3MgPSBbXTtcbiAgICB9XG4gICAgdGhpcy5sb3NzID0gYXJncy5sb3NzO1xuXG4gICAgaWYgKHR5cGVvZiBhcmdzLm9wdGltaXplciA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHRoaXMub3B0aW1pemVyXyA9IG9wdGltaXplcnMuZ2V0T3B0aW1pemVyKGFyZ3Mub3B0aW1pemVyKTtcbiAgICAgIHRoaXMuaXNPcHRpbWl6ZXJPd25lZCA9IHRydWU7XG4gICAgfSBlbHNlIHtcbiAgICAgIGlmICghKGFyZ3Mub3B0aW1pemVyIGluc3RhbmNlb2YgT3B0aW1pemVyKSkge1xuICAgICAgICB0aHJvdyBuZXcgVmFsdWVFcnJvcihcbiAgICAgICAgICAgIGBVc2VyLWRlZmluZWQgb3B0aW1pemVyIG11c3QgYmUgYW4gaW5zdGFuY2Ugb2YgdGYuT3B0aW1pemVyLmApO1xuICAgICAgfVxuICAgICAgdGhpcy5vcHRpbWl6ZXJfID0gYXJncy5vcHRpbWl6ZXI7XG4gICAgICB0aGlzLmlzT3B0aW1pemVyT3duZWQgPSBmYWxzZTtcbiAgICB9XG5cbiAgICAvLyBUT0RPKGNhaXMpOiBBZGQgbG9zc1dlaWdodHMuXG4gICAgLy8gVE9ETyhjYWlzKTogQWRkIHNhbXBsZVdlaWdodE1vZGUuXG5cbiAgICAvLyBQcmVwYXJlIGxvc3MgZnVuY3Rpb25zLlxuICAgIGxldCBsb3NzRnVuY3Rpb25zOiBMb3NzT3JNZXRyaWNGbltdID0gW107XG4gICAgaWYgKCFBcnJheS5pc0FycmF5KGFyZ3MubG9zcykgJiYgdHlwZW9mIGFyZ3MubG9zcyAhPT0gJ3N0cmluZycgJiZcbiAgICAgICAgdHlwZW9mIGFyZ3MubG9zcyAhPT0gJ2Z1bmN0aW9uJykge1xuICAgICAgYXJncy5sb3NzID0gYXJncy5sb3NzIGFzIHtbb3V0cHV0TmFtZTogc3RyaW5nXTogc3RyaW5nfTtcbiAgICAgIGZvciAoY29uc3QgbmFtZSBpbiBhcmdzLmxvc3MpIHtcbiAgICAgICAgaWYgKHRoaXMub3V0cHV0TmFtZXMuaW5kZXhPZihuYW1lKSA9PT0gLTEpIHtcbiAgICAgICAgICB0aHJvdyBuZXcgVmFsdWVFcnJvcihcbiAgICAgICAgICAgICAgYFVua25vd24gZW50cnkgaW4gbG9zcyBkaWN0aW9uYXJ5OiBcIiR7bmFtZX1cIi4gYCArXG4gICAgICAgICAgICAgIGBPbmx5IGV4cGVjdGVkIHRoZSBmb2xsb3dpbmcga2V5czogJHt0aGlzLm91dHB1dE5hbWVzfWApO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBmb3IgKGNvbnN0IG5hbWUgb2YgdGhpcy5vdXRwdXROYW1lcykge1xuICAgICAgICBpZiAoYXJncy5sb3NzW25hbWVdID09IG51bGwpIHtcbiAgICAgICAgICBjb25zb2xlLndhcm4oXG4gICAgICAgICAgICAgIGBPdXRwdXQgXCIke25hbWV9XCIgaXMgbWlzc2luZyBmcm9tIGxvc3MgZGljdGlvbmFyeS4gV2UgYXNzdW1lIGAgK1xuICAgICAgICAgICAgICBgdGhpcyB3YXMgZG9uZSBvbiBwdXJwb3NlLCBhbmQgd2Ugd2lsbCBub3QgYmUgZXhwZWN0aW5nIGRhdGEgYCArXG4gICAgICAgICAgICAgIGB0byBiZSBwYXNzZWQgdG8gJHtuYW1lfSBkdXJpbmcgdHJhaW5pbmdgKTtcbiAgICAgICAgfVxuICAgICAgICBsb3NzRnVuY3Rpb25zLnB1c2gobG9zc2VzLmdldChhcmdzLmxvc3NbbmFtZV0pKTtcbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKEFycmF5LmlzQXJyYXkoYXJncy5sb3NzKSkge1xuICAgICAgaWYgKGFyZ3MubG9zcy5sZW5ndGggIT09IHRoaXMub3V0cHV0cy5sZW5ndGgpIHtcbiAgICAgICAgdGhyb3cgbmV3IFZhbHVlRXJyb3IoXG4gICAgICAgICAgICBgV2hlbiBwYXNzaW5nIGFuIEFycmF5IGFzIGxvc3MsIGl0IHNob3VsZCBoYXZlIG9uZSBlbnRyeSBwZXIgYCArXG4gICAgICAgICAgICBgbW9kZWwgb3V0cHV0LiBUaGUgbW9kZWwgaGFzICR7dGhpcy5vdXRwdXRzLmxlbmd0aH0gb3V0cHV0KHMpLCBgICtcbiAgICAgICAgICAgIGBidXQgeW91IHBhc3NlZCBsb3NzPSR7YXJncy5sb3NzfS5gKTtcbiAgICAgIH1cbiAgICAgIGNvbnN0IHRoZUxvc3NlcyA9IGFyZ3MubG9zcyBhcyBBcnJheTxzdHJpbmd8TG9zc09yTWV0cmljRm4+O1xuICAgICAgbG9zc0Z1bmN0aW9ucyA9IHRoZUxvc3Nlcy5tYXAobCA9PiBsb3NzZXMuZ2V0KGwpKTtcbiAgICB9IGVsc2Uge1xuICAgICAgY29uc3QgbG9zc0Z1bmN0aW9uID0gbG9zc2VzLmdldChhcmdzLmxvc3MpO1xuICAgICAgdGhpcy5vdXRwdXRzLmZvckVhY2goXyA9PiB7XG4gICAgICAgIGxvc3NGdW5jdGlvbnMucHVzaChsb3NzRnVuY3Rpb24pO1xuICAgICAgfSk7XG4gICAgfVxuXG4gICAgdGhpcy5sb3NzRnVuY3Rpb25zID0gbG9zc0Z1bmN0aW9ucztcblxuICAgIHRoaXMuZmVlZE91dHB1dE5hbWVzID0gW107XG4gICAgdGhpcy5mZWVkT3V0cHV0U2hhcGVzID0gW107XG4gICAgdGhpcy5mZWVkTG9zc0ZucyA9IFtdO1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgdGhpcy5vdXRwdXRzLmxlbmd0aDsgKytpKSB7XG4gICAgICAvLyBUT0RPKGNhaXMpOiBMb2dpYyBmb3Igc2tpcHBpbmcgdGFyZ2V0KHMpLlxuICAgICAgY29uc3Qgc2hhcGUgPSB0aGlzLmludGVybmFsT3V0cHV0U2hhcGVzW2ldO1xuICAgICAgY29uc3QgbmFtZSA9IHRoaXMub3V0cHV0TmFtZXNbaV07XG4gICAgICB0aGlzLmZlZWRPdXRwdXROYW1lcy5wdXNoKG5hbWUpO1xuICAgICAgdGhpcy5mZWVkT3V0cHV0U2hhcGVzLnB1c2goc2hhcGUpO1xuICAgICAgdGhpcy5mZWVkTG9zc0Zucy5wdXNoKHRoaXMubG9zc0Z1bmN0aW9uc1tpXSk7XG4gICAgfVxuXG4gICAgLy8gVE9ETyhjYWlzKTogQWRkIGxvZ2ljIGZvciBvdXRwdXQgbWFza3MuXG4gICAgLy8gVE9ETyhjYWlzKTogQWRkIGxvZ2ljIGZvciBzYW1wbGUgd2VpZ2h0cy5cbiAgICBjb25zdCBza2lwVGFyZ2V0SW5kaWNlczogbnVtYmVyW10gPSBbXTtcblxuICAgIC8vIFByZXBhcmUgbWV0cmljcy5cbiAgICB0aGlzLm1ldHJpY3MgPSBhcmdzLm1ldHJpY3M7XG4gICAgLy8gVE9ETyhjYWlzKTogQWRkIHdlaWdodGVkTWV0cmljcy5cbiAgICB0aGlzLm1ldHJpY3NOYW1lcyA9IFsnbG9zcyddO1xuICAgIHRoaXMubWV0cmljc1RlbnNvcnMgPSBbXTtcblxuICAgIC8vIENvbXB1dGUgdG90YWwgbG9zcy5cbiAgICAvLyBQb3J0aW5nIE5vdGU6IEluIFB5S2VyYXMsIG1ldHJpY3NfdGVuc29ycyBhcmUgc3ltYm9saWMgdGVuc29yIG9iamVjdHMuXG4gICAgLy8gICBIZXJlLCBtZXRyaWNzVGVuc29ycyBhcmUgVHlwZVNjcmlwdCBmdW5jdGlvbnMuIFRoaXMgZGlmZmVyZW5jZSBpcyBkdWVcbiAgICAvLyAgIHRvIHRoZSBkaWZmZXJlbmNlIGluIHN5bWJvbGljL2ltcGVyYXRpdmUgcHJvcGVydHkgb2YgdGhlIGJhY2tlbmRzLlxuICAgIG5hbWVTY29wZSgnbG9zcycsICgpID0+IHtcbiAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgdGhpcy5vdXRwdXRzLmxlbmd0aDsgKytpKSB7XG4gICAgICAgIGlmIChza2lwVGFyZ2V0SW5kaWNlcy5pbmRleE9mKGkpICE9PSAtMSkge1xuICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9XG4gICAgICAgIC8vIFRPRE8oY2Fpcyk6IEFkZCB3ZWlnaHRlZExvc3MsIHNhbXBsZVdlaWdodCBhbmQgbWFzay5cbiAgICAgICAgLy8gICBUaGUgZm9sbG93aW5nIGxpbmUgc2hvdWxkIGJlIHdlaWdodGVkTG9zc1xuICAgICAgICBjb25zdCB3ZWlnaHRlZExvc3MgPSB0aGlzLmxvc3NGdW5jdGlvbnNbaV07XG4gICAgICAgIGlmICh0aGlzLm91dHB1dHMubGVuZ3RoID4gMSkge1xuICAgICAgICAgIHRoaXMubWV0cmljc1RlbnNvcnMucHVzaChbd2VpZ2h0ZWRMb3NzLCBpXSk7XG4gICAgICAgICAgdGhpcy5tZXRyaWNzTmFtZXMucHVzaCh0aGlzLm91dHB1dE5hbWVzW2ldICsgJ19sb3NzJyk7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgLy8gUG9ydGluZyBOb3RlOiBEdWUgdG8gdGhlIGltcGVyYXRpdmUgbmF0dXJlIG9mIHRoZSBiYWNrZW5kLCB3ZSBjYWxjdWxhdGVcbiAgICAgIC8vICAgdGhlIHJlZ3VsYXJpemVyIHBlbmFsdGllcyBpbiB0aGUgdG90YWxMb3NzRnVuY3Rpb24sIGluc3RlYWQgb2YgaGVyZS5cbiAgICB9KTtcblxuICAgIGNvbnN0IG5lc3RlZE1ldHJpY3MgPSBjb2xsZWN0TWV0cmljcyhhcmdzLm1ldHJpY3MsIHRoaXMub3V0cHV0TmFtZXMpO1xuICAgIC8vIFRPRE8oY2Fpcyk6IEFkZCBuZXN0ZWRXZWlnaHRlZE1ldHJpY3MuXG5cbiAgICAvKipcbiAgICAgKiBIZWxwZXIgZnVuY3Rpb24gdXNlZCBpbiBsb29wIGJlbG93LlxuICAgICAqL1xuICAgIGNvbnN0IGFwcGVuZE1ldHJpYyA9XG4gICAgICAgIChvdXRwdXRJbmRleDogbnVtYmVyLCBtZXRyaWNOYW1lOiBzdHJpbmcsXG4gICAgICAgICBtZXRyaWNUZW5zb3I6IExvc3NPck1ldHJpY0ZuKSA9PiB7XG4gICAgICAgICAgaWYgKHRoaXMub3V0cHV0TmFtZXMubGVuZ3RoID4gMSkge1xuICAgICAgICAgICAgbWV0cmljTmFtZSA9IHRoaXMub3V0cHV0TmFtZXNbb3V0cHV0SW5kZXhdICsgJ18nICsgbWV0cmljTmFtZTtcbiAgICAgICAgICB9XG4gICAgICAgICAgdGhpcy5tZXRyaWNzTmFtZXMucHVzaChtZXRyaWNOYW1lKTtcbiAgICAgICAgICB0aGlzLm1ldHJpY3NUZW5zb3JzLnB1c2goW21ldHJpY1RlbnNvciwgb3V0cHV0SW5kZXhdKTtcbiAgICAgICAgfTtcblxuICAgIG5hbWVTY29wZSgnbWV0cmljJywgKCkgPT4ge1xuICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCB0aGlzLm91dHB1dHMubGVuZ3RoOyArK2kpIHtcbiAgICAgICAgaWYgKHNraXBUYXJnZXRJbmRpY2VzLmluZGV4T2YoaSkgIT09IC0xKSB7XG4gICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3Qgb3V0cHV0TWV0cmljcyA9IG5lc3RlZE1ldHJpY3NbaV07XG4gICAgICAgIC8vIFRPRE8oY2Fpcyk6IEFkZCB3ZWlnaHRzIGFuZCBvdXRwdXRXZWlnaHRlZE1ldHJpY3MuXG5cbiAgICAgICAgLy8gVE9ETyhjYWlzKTogQWRkIG9wdGlvbmFsIGFyZyBgd2VpZ2h0c2AgdG8gdGhlIGZvbGxvd2luZyBmdW5jdGlvbi5cbiAgICAgICAgY29uc3QgaGFuZGxlTWV0cmljcyA9IChtZXRyaWNzOiBBcnJheTxzdHJpbmd8TG9zc09yTWV0cmljRm4+KSA9PiB7XG4gICAgICAgICAgY29uc3QgbWV0cmljTmFtZVByZWZpeCA9ICcnO1xuICAgICAgICAgIGxldCBtZXRyaWNOYW1lOiBzdHJpbmc7XG4gICAgICAgICAgbGV0IGFjY0ZuOiBMb3NzT3JNZXRyaWNGbjtcbiAgICAgICAgICBsZXQgd2VpZ2h0ZWRNZXRyaWNGbjogTG9zc09yTWV0cmljRm47XG4gICAgICAgICAgLy8gIFRPRE8oY2Fpcyk6IFVzZSAnd2VpZ2h0c18nIGZvciB3ZWlnaHRlZCBtZXRyaWNzLlxuXG4gICAgICAgICAgZm9yIChjb25zdCBtZXRyaWMgb2YgbWV0cmljcykge1xuICAgICAgICAgICAgaWYgKHR5cGVvZiBtZXRyaWMgPT09ICdzdHJpbmcnICYmXG4gICAgICAgICAgICAgICAgWydhY2N1cmFjeScsICdhY2MnLCAnY3Jvc3NlbnRyb3B5JywgJ2NlJ10uaW5kZXhPZihtZXRyaWMpICE9PVxuICAgICAgICAgICAgICAgICAgICAtMSkge1xuICAgICAgICAgICAgICBjb25zdCBvdXRwdXRTaGFwZSA9IHRoaXMuaW50ZXJuYWxPdXRwdXRTaGFwZXNbaV07XG5cbiAgICAgICAgICAgICAgaWYgKG91dHB1dFNoYXBlW291dHB1dFNoYXBlLmxlbmd0aCAtIDFdID09PSAxIHx8XG4gICAgICAgICAgICAgICAgICB0aGlzLmxvc3NGdW5jdGlvbnNbaV0gPT09IGxvc3Nlcy5iaW5hcnlDcm9zc2VudHJvcHkpIHtcbiAgICAgICAgICAgICAgICAvLyBjYXNlOiBiaW5hcnkgYWNjdXJhY3kvY3Jvc3NlbnRyb3B5LlxuICAgICAgICAgICAgICAgIGlmIChbJ2FjY3VyYWN5JywgJ2FjYyddLmluZGV4T2YobWV0cmljKSAhPT0gLTEpIHtcbiAgICAgICAgICAgICAgICAgIGFjY0ZuID0gTWV0cmljcy5iaW5hcnlBY2N1cmFjeTtcbiAgICAgICAgICAgICAgICB9IGVsc2UgaWYgKFsnY3Jvc3NlbnRyb3B5JywgJ2NlJ10uaW5kZXhPZihtZXRyaWMpICE9PSAtMSkge1xuICAgICAgICAgICAgICAgICAgYWNjRm4gPSBNZXRyaWNzLmJpbmFyeUNyb3NzZW50cm9weTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIH0gZWxzZSBpZiAoXG4gICAgICAgICAgICAgICAgICB0aGlzLmxvc3NGdW5jdGlvbnNbaV0gPT09XG4gICAgICAgICAgICAgICAgICBsb3NzZXMuc3BhcnNlQ2F0ZWdvcmljYWxDcm9zc2VudHJvcHkpIHtcbiAgICAgICAgICAgICAgICAvLyBjYXNlOiBjYXRlZ29yaWNhbCBhY2N1cmFjeSAvIGNyb3NzZW50cm9weSB3aXRoIHNwYXJzZVxuICAgICAgICAgICAgICAgIC8vIHRhcmdldHMuXG4gICAgICAgICAgICAgICAgaWYgKFsnYWNjdXJhY3knLCAnYWNjJ10uaW5kZXhPZihtZXRyaWMpICE9PSAtMSkge1xuICAgICAgICAgICAgICAgICAgYWNjRm4gPSBNZXRyaWNzLnNwYXJzZUNhdGVnb3JpY2FsQWNjdXJhY3k7XG4gICAgICAgICAgICAgICAgfSBlbHNlIGlmIChbJ2Nyb3NzZW50cm9weScsICdjZSddLmluZGV4T2YobWV0cmljKSAhPT0gLTEpIHtcbiAgICAgICAgICAgICAgICAgIGFjY0ZuID0gTWV0cmljcy5zcGFyc2VDYXRlZ29yaWNhbENyb3NzZW50cm9weTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgLy8gY2FzZTogY2F0ZWdvcmljYWwgYWNjdXJhY3kgLyBjcm9zc2VudHJvcHkuXG4gICAgICAgICAgICAgICAgaWYgKFsnYWNjdXJhY3knLCAnYWNjJ10uaW5kZXhPZihtZXRyaWMpICE9PSAtMSkge1xuICAgICAgICAgICAgICAgICAgYWNjRm4gPSBNZXRyaWNzLmNhdGVnb3JpY2FsQWNjdXJhY3k7XG4gICAgICAgICAgICAgICAgfSBlbHNlIGlmIChbJ2Nyb3NzZW50cm9weScsICdjZSddLmluZGV4T2YobWV0cmljKSAhPT0gLTEpIHtcbiAgICAgICAgICAgICAgICAgIGFjY0ZuID0gTWV0cmljcy5jYXRlZ29yaWNhbENyb3NzZW50cm9weTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgbGV0IHN1ZmZpeDogc3RyaW5nO1xuICAgICAgICAgICAgICBpZiAoWydhY2N1cmFjeScsICdhY2MnXS5pbmRleE9mKG1ldHJpYykgIT09IC0xKSB7XG4gICAgICAgICAgICAgICAgc3VmZml4ID0gJ2FjYyc7XG4gICAgICAgICAgICAgIH0gZWxzZSBpZiAoWydjcm9zc2VudHJvcHknLCAnY2UnXS5pbmRleE9mKG1ldHJpYykgIT09IC0xKSB7XG4gICAgICAgICAgICAgICAgc3VmZml4ID0gJ2NlJztcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAvLyBUT0RPKGNhaXMpOiBBZGQgd2VpZ2h0aW5nIGFjdHVhbGx5LlxuICAgICAgICAgICAgICB3ZWlnaHRlZE1ldHJpY0ZuID0gYWNjRm47XG4gICAgICAgICAgICAgIG1ldHJpY05hbWUgPSBtZXRyaWNOYW1lUHJlZml4ICsgc3VmZml4O1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgY29uc3QgbWV0cmljRm4gPSBNZXRyaWNzLmdldChtZXRyaWMpO1xuICAgICAgICAgICAgICAvLyBUT0RPKGNhaXMpOiBBZGQgd2VpZ2h0aW5nIGFjdHVhbGx5LlxuICAgICAgICAgICAgICB3ZWlnaHRlZE1ldHJpY0ZuID0gbWV0cmljRm47XG4gICAgICAgICAgICAgIG1ldHJpY05hbWUgPVxuICAgICAgICAgICAgICAgICAgbWV0cmljTmFtZVByZWZpeCArIE1ldHJpY3MuZ2V0TG9zc09yTWV0cmljTmFtZShtZXRyaWMpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBUT0RPKGNhaXMpOiBBZGQgd2VpZ2h0aW5nIGFuZCBtYXNraW5nIHRvIG1ldHJpY1Jlc3VsdC5cbiAgICAgICAgICAgIGxldCBtZXRyaWNSZXN1bHQ6IExvc3NPck1ldHJpY0ZuO1xuICAgICAgICAgICAgbmFtZVNjb3BlKG1ldHJpY05hbWUsICgpID0+IHtcbiAgICAgICAgICAgICAgbWV0cmljUmVzdWx0ID0gd2VpZ2h0ZWRNZXRyaWNGbjtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgYXBwZW5kTWV0cmljKGksIG1ldHJpY05hbWUsIG1ldHJpY1Jlc3VsdCk7XG4gICAgICAgICAgfVxuICAgICAgICB9O1xuXG4gICAgICAgIGhhbmRsZU1ldHJpY3Mob3V0cHV0TWV0cmljcyk7XG4gICAgICAgIC8vIFRPRE8oY2Fpcyk6IENhbGwgaGFuZGxlTWV0cmljcyB3aXRoIHdlaWdodHMuXG4gICAgICB9XG4gICAgfSk7XG5cbiAgICAvLyBQb3J0aW5nIE5vdGVzOiBHaXZlbiB0aGUgaW1wZXJhdGl2ZSBiYWNrZW5kIG9mIHRmanMtY29yZSxcbiAgICAvLyAgIHRoZXJlIGlzIG5vIG5lZWQgZm9yIGNvbnN0cnVjdGluZyB0aGUgc3ltYm9saWMgZ3JhcGggYW5kIHBsYWNlaG9sZGVycy5cbiAgICB0aGlzLmNvbGxlY3RlZFRyYWluYWJsZVdlaWdodHMgPSB0aGlzLnRyYWluYWJsZVdlaWdodHM7XG4gIH1cblxuICAvKipcbiAgICogQ2hlY2sgdHJhaW5hYmxlIHdlaWdodHMgY291bnQgY29uc2lzdGVuY3kuXG4gICAqXG4gICAqIFRoaXMgd2lsbCByYWlzZSBhIHdhcm5pbmcgaWYgYHRoaXMudHJhaW5hYmxlV2VpZ2h0c2AgYW5kXG4gICAqIGB0aGlzLmNvbGxlY3RlZFRyYWluYWJsZVdlaWdodHNgIGFyZSBpbmNvbnNpc3RlbnQgKGkuZS4sIGhhdmUgZGlmZmVyZW50XG4gICAqIG51bWJlcnMgb2YgcGFyYW1ldGVycykuXG4gICAqIEluY29uc2lzdGVuY3kgd2lsbCB0eXBpY2FsbHkgYXJpc2Ugd2hlbiBvbmUgbW9kaWZpZXMgYG1vZGVsLnRyYWluYWJsZWBcbiAgICogd2l0aG91dCBjYWxsaW5nIGBtb2RlbC5jb21waWxlKClgIGFnYWluLlxuICAgKi9cbiAgcHJvdGVjdGVkIGNoZWNrVHJhaW5hYmxlV2VpZ2h0c0NvbnNpc3RlbmN5KCk6IHZvaWQge1xuICAgIGlmICh0aGlzLmNvbGxlY3RlZFRyYWluYWJsZVdlaWdodHMgPT0gbnVsbCkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBpZiAodGhpcy50cmFpbmFibGVXZWlnaHRzLmxlbmd0aCAhPT1cbiAgICAgICAgdGhpcy5jb2xsZWN0ZWRUcmFpbmFibGVXZWlnaHRzLmxlbmd0aCkge1xuICAgICAgY29uc29sZS53YXJuKFxuICAgICAgICAgICdEaXNjcmVwYW5jeSBiZXR3ZWVuIHRyYWluYWJsZXdlaWdodHMgYW5kIGNvbGxlY3RlZCB0cmFpbmFibGUgJyArXG4gICAgICAgICAgJ3dlaWdodHMuIERpZCB5b3Ugc2V0IGBtb2RlbC50cmFpbmFibGVgIHdpdGhvdXQgY2FsbGluZyAnICtcbiAgICAgICAgICAnYG1vZGVsLmNvbXBpbGUoKWAgYWZ0ZXJ3YXJkcz8nKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgbG9zcyB2YWx1ZSAmIG1ldHJpY3MgdmFsdWVzIGZvciB0aGUgbW9kZWwgaW4gdGVzdCBtb2RlLlxuICAgKlxuICAgKiBMb3NzIGFuZCBtZXRyaWNzIGFyZSBzcGVjaWZpZWQgZHVyaW5nIGBjb21waWxlKClgLCB3aGljaCBuZWVkcyB0byBoYXBwZW5cbiAgICogYmVmb3JlIGNhbGxzIHRvIGBldmFsdWF0ZSgpYC5cbiAgICpcbiAgICogQ29tcHV0YXRpb24gaXMgZG9uZSBpbiBiYXRjaGVzLlxuICAgKlxuICAgKiBgYGBqc1xuICAgKiBjb25zdCBtb2RlbCA9IHRmLnNlcXVlbnRpYWwoe1xuICAgKiAgIGxheWVyczogW3RmLmxheWVycy5kZW5zZSh7dW5pdHM6IDEsIGlucHV0U2hhcGU6IFsxMF19KV1cbiAgICogfSk7XG4gICAqIG1vZGVsLmNvbXBpbGUoe29wdGltaXplcjogJ3NnZCcsIGxvc3M6ICdtZWFuU3F1YXJlZEVycm9yJ30pO1xuICAgKiBjb25zdCByZXN1bHQgPSBtb2RlbC5ldmFsdWF0ZShcbiAgICogICAgIHRmLm9uZXMoWzgsIDEwXSksIHRmLm9uZXMoWzgsIDFdKSwge2JhdGNoU2l6ZTogNH0pO1xuICAgKiByZXN1bHQucHJpbnQoKTtcbiAgICogYGBgXG4gICAqXG4gICAqIEBwYXJhbSB4IGB0Zi5UZW5zb3JgIG9mIHRlc3QgZGF0YSwgb3IgYW4gYEFycmF5YCBvZiBgdGYuVGVuc29yYHMgaWYgdGhlXG4gICAqIG1vZGVsIGhhcyBtdWx0aXBsZSBpbnB1dHMuXG4gICAqIEBwYXJhbSB5IGB0Zi5UZW5zb3JgIG9mIHRhcmdldCBkYXRhLCBvciBhbiBgQXJyYXlgIG9mIGB0Zi5UZW5zb3JgcyBpZiB0aGVcbiAgICogbW9kZWwgaGFzIG11bHRpcGxlIG91dHB1dHMuXG4gICAqIEBwYXJhbSBhcmdzIEEgYE1vZGVsRXZhbHVhdGVBcmdzYCwgY29udGFpbmluZyBvcHRpb25hbCBmaWVsZHMuXG4gICAqXG4gICAqIEByZXR1cm4gYFNjYWxhcmAgdGVzdCBsb3NzIChpZiB0aGUgbW9kZWwgaGFzIGEgc2luZ2xlIG91dHB1dCBhbmQgbm9cbiAgICogICBtZXRyaWNzKSBvciBgQXJyYXlgIG9mIGBTY2FsYXJgcyAoaWYgdGhlIG1vZGVsIGhhcyBtdWx0aXBsZSBvdXRwdXRzXG4gICAqICAgYW5kL29yIG1ldHJpY3MpLiBUaGUgYXR0cmlidXRlIGBtb2RlbC5tZXRyaWNzTmFtZXNgXG4gICAqICAgd2lsbCBnaXZlIHlvdSB0aGUgZGlzcGxheSBsYWJlbHMgZm9yIHRoZSBzY2FsYXIgb3V0cHV0cy5cbiAgICpcbiAgICogQGRvYyB7aGVhZGluZzogJ01vZGVscycsIHN1YmhlYWRpbmc6ICdDbGFzc2VzJ31cbiAgICovXG4gIGV2YWx1YXRlKFxuICAgICAgeDogVGVuc29yfFRlbnNvcltdLCB5OiBUZW5zb3J8VGVuc29yW10sXG4gICAgICBhcmdzOiBNb2RlbEV2YWx1YXRlQXJncyA9IHt9KTogU2NhbGFyfFNjYWxhcltdIHtcbiAgICBjb25zdCBiYXRjaFNpemUgPSBhcmdzLmJhdGNoU2l6ZSA9PSBudWxsID8gMzIgOiBhcmdzLmJhdGNoU2l6ZTtcbiAgICBjaGVja0JhdGNoU2l6ZShiYXRjaFNpemUpO1xuXG4gICAgLy8gVE9ETyhjYWlzKTogU3RhbmRhcmRpemUgYGNvbmZpZy5zYW1wbGVXZWlnaHRzYCBhcyB3ZWxsLlxuICAgIC8vIFZhbGlkYXRlIHVzZXIgZGF0YS5cbiAgICBjb25zdCBjaGVja0JhdGNoQXhpcyA9IHRydWU7XG4gICAgY29uc3Qgc3RhbmRhcmRpemVkT3V0cyA9XG4gICAgICAgIHRoaXMuc3RhbmRhcmRpemVVc2VyRGF0YVhZKHgsIHksIGNoZWNrQmF0Y2hBeGlzLCBiYXRjaFNpemUpO1xuICAgIHRyeSB7XG4gICAgICAvLyBUT0RPKGNhaXMpOiBJZiB1c2VzIGB1c2VMZWFybmluZ1BoYXNlYCwgc2V0IHRoZSBjb3JyZXNwb25kaW5nIGVsZW1lbnRcbiAgICAgIC8vIG9mIHRoZSBpbnB1dCB0byAwLlxuICAgICAgY29uc3QgaW5zID0gc3RhbmRhcmRpemVkT3V0c1swXS5jb25jYXQoc3RhbmRhcmRpemVkT3V0c1sxXSk7XG4gICAgICB0aGlzLm1ha2VUZXN0RnVuY3Rpb24oKTtcbiAgICAgIGNvbnN0IGYgPSB0aGlzLnRlc3RGdW5jdGlvbjtcbiAgICAgIGNvbnN0IHRlc3RPdXRzID1cbiAgICAgICAgICB0aGlzLnRlc3RMb29wKGYsIGlucywgYmF0Y2hTaXplLCBhcmdzLnZlcmJvc2UsIGFyZ3Muc3RlcHMpO1xuICAgICAgcmV0dXJuIHNpbmdsZXRvbk9yQXJyYXkodGVzdE91dHMpO1xuICAgIH0gZmluYWxseSB7XG4gICAgICBkaXNwb3NlTmV3VGVuc29ycyhzdGFuZGFyZGl6ZWRPdXRzWzBdLCB4KTtcbiAgICAgIGRpc3Bvc2VOZXdUZW5zb3JzKHN0YW5kYXJkaXplZE91dHNbMV0sIHkpO1xuICAgIH1cbiAgfVxuXG4gIC8vIFRPRE8oY2Fpcyk6IEFkZCBjb2RlIHNuaXBwZXQgYmVsb3cgb25jZSByZWFsIGRhdGFzZXQgb2JqZWN0cyBhcmVcbiAgLy8gICBhdmFpbGFibGUuXG4gIC8qKlxuICAgKiBFdmFsdWF0ZSBtb2RlbCB1c2luZyBhIGRhdGFzZXQgb2JqZWN0LlxuICAgKlxuICAgKiBOb3RlOiBVbmxpa2UgYGV2YWx1YXRlKClgLCB0aGlzIG1ldGhvZCBpcyBhc3luY2hyb25vdXMgKGBhc3luY2ApO1xuICAgKlxuICAgKiBAcGFyYW0gZGF0YXNldCBBIGRhdGFzZXQgb2JqZWN0LiBJdHMgYGl0ZXJhdG9yKClgIG1ldGhvZCBpcyBleHBlY3RlZFxuICAgKiAgIHRvIGdlbmVyYXRlIGEgZGF0YXNldCBpdGVyYXRvciBvYmplY3QsIHRoZSBgbmV4dCgpYCBtZXRob2Qgb2Ygd2hpY2hcbiAgICogICBpcyBleHBlY3RlZCB0byBwcm9kdWNlIGRhdGEgYmF0Y2hlcyBmb3IgZXZhbHVhdGlvbi4gVGhlIHJldHVybiB2YWx1ZVxuICAgKiAgIG9mIHRoZSBgbmV4dCgpYCBjYWxsIG91Z2h0IHRvIGNvbnRhaW4gYSBib29sZWFuIGBkb25lYCBmaWVsZCBhbmQgYVxuICAgKiAgIGB2YWx1ZWAgZmllbGQuIFRoZSBgdmFsdWVgIGZpZWxkIGlzIGV4cGVjdGVkIHRvIGJlIGFuIGFycmF5IG9mIHR3b1xuICAgKiAgIGB0Zi5UZW5zb3JgcyBvciBhbiBhcnJheSBvZiB0d28gbmVzdGVkIGB0Zi5UZW5zb3JgIHN0cnVjdHVyZXMuIFRoZSBmb3JtZXJcbiAgICogICBjYXNlIGlzIGZvciBtb2RlbHMgd2l0aCBleGFjdGx5IG9uZSBpbnB1dCBhbmQgb25lIG91dHB1dCAoZS5nLi5cbiAgICogICBhIHNlcXVlbnRpYWwgbW9kZWwpLiBUaGUgbGF0dGVyIGNhc2UgaXMgZm9yIG1vZGVscyB3aXRoIG11bHRpcGxlXG4gICAqICAgaW5wdXRzIGFuZC9vciBtdWx0aXBsZSBvdXRwdXRzLiBPZiB0aGUgdHdvIGl0ZW1zIGluIHRoZSBhcnJheSwgdGhlXG4gICAqICAgZmlyc3QgaXMgdGhlIGlucHV0IGZlYXR1cmUocykgYW5kIHRoZSBzZWNvbmQgaXMgdGhlIG91dHB1dCB0YXJnZXQocykuXG4gICAqIEBwYXJhbSBhcmdzIEEgY29uZmlndXJhdGlvbiBvYmplY3QgZm9yIHRoZSBkYXRhc2V0LWJhc2VkIGV2YWx1YXRpb24uXG4gICAqIEByZXR1cm5zIExvc3MgYW5kIG1ldHJpYyB2YWx1ZXMgYXMgYW4gQXJyYXkgb2YgYFNjYWxhcmAgb2JqZWN0cy5cbiAgICpcbiAgICogQGRvYyB7aGVhZGluZzogJ01vZGVscycsIHN1YmhlYWRpbmc6ICdDbGFzc2VzJ31cbiAgICovXG4gIGFzeW5jIGV2YWx1YXRlRGF0YXNldChkYXRhc2V0OiBEYXRhc2V0PHt9PiwgYXJncz86IE1vZGVsRXZhbHVhdGVEYXRhc2V0QXJncyk6XG4gICAgICBQcm9taXNlPFNjYWxhcnxTY2FsYXJbXT4ge1xuICAgIHRoaXMubWFrZVRlc3RGdW5jdGlvbigpO1xuICAgIHJldHVybiBldmFsdWF0ZURhdGFzZXQodGhpcywgZGF0YXNldCwgYXJncyk7XG4gIH1cblxuICAvKipcbiAgICogR2V0IG51bWJlciBvZiBzYW1wbGVzIHByb3ZpZGVkIGZvciB0cmFpbmluZywgZXZhbHVhdGlvbiBvciBwcmVkaWN0aW9uLlxuICAgKlxuICAgKiBAcGFyYW0gaW5zIElucHV0IGB0Zi5UZW5zb3JgLlxuICAgKiBAcGFyYW0gYmF0Y2hTaXplIEludGVnZXIgYmF0Y2ggc2l6ZSwgb3B0aW9uYWwuXG4gICAqIEBwYXJhbSBzdGVwcyBUb3RhbCBudW1iZXIgb2Ygc3RlcHMgKGJhdGNoZXMgb2Ygc2FtcGxlcykgYmVmb3JlXG4gICAqIGRlY2xhcmluZyBsb29wIGZpbmlzaGVkLiBPcHRpb25hbC5cbiAgICogQHBhcmFtIHN0ZXBzTmFtZSBUaGUgcHVibGljIEFQSSdzIHBhcmFtZXRlciBuYW1lIGZvciBgc3RlcHNgLlxuICAgKiBAcmV0dXJucyBOdW1iZXIgb2Ygc2FtcGxlcyBwcm92aWRlZC5cbiAgICovXG4gIHByaXZhdGUgY2hlY2tOdW1TYW1wbGVzKFxuICAgICAgaW5zOiBUZW5zb3J8VGVuc29yW10sIGJhdGNoU2l6ZT86IG51bWJlciwgc3RlcHM/OiBudW1iZXIsXG4gICAgICBzdGVwc05hbWUgPSAnc3RlcHMnKTogbnVtYmVyIHtcbiAgICBsZXQgbnVtU2FtcGxlczogbnVtYmVyO1xuICAgIGlmIChzdGVwcyAhPSBudWxsKSB7XG4gICAgICBudW1TYW1wbGVzID0gbnVsbDtcbiAgICAgIGlmIChiYXRjaFNpemUgIT0gbnVsbCkge1xuICAgICAgICB0aHJvdyBuZXcgVmFsdWVFcnJvcihcbiAgICAgICAgICAgIGBJZiAke3N0ZXBzTmFtZX0gaXMgc2V0LCBiYXRjaFNpemUgbXVzdCBiZSBudWxsIG9yIHVuZGVmaW5lZC5gICtcbiAgICAgICAgICAgIGBHb3QgYmF0Y2hTaXplID0gJHtiYXRjaFNpemV9YCk7XG4gICAgICB9XG4gICAgfSBlbHNlIGlmIChpbnMgIT0gbnVsbCkge1xuICAgICAgaWYgKEFycmF5LmlzQXJyYXkoaW5zKSkge1xuICAgICAgICBudW1TYW1wbGVzID0gaW5zWzBdLnNoYXBlWzBdO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgbnVtU2FtcGxlcyA9IGlucy5zaGFwZVswXTtcbiAgICAgIH1cbiAgICB9IGVsc2Uge1xuICAgICAgdGhyb3cgbmV3IFZhbHVlRXJyb3IoXG4gICAgICAgICAgYEVpdGhlciB0aGUgaW5wdXQgZGF0YSBzaG91bGQgaGF2ZSBhIGRlZmluZWQgc2hhcGUsIG9yIGAgK1xuICAgICAgICAgIGAke3N0ZXBzTmFtZX0gc2hvdWQgYmUgc3BlY2lmaWVkLmApO1xuICAgIH1cbiAgICByZXR1cm4gbnVtU2FtcGxlcztcbiAgfVxuXG4gIC8qKlxuICAgKiBFeGVjdXRlIGludGVybmFsIHRlbnNvcnMgb2YgdGhlIG1vZGVsIHdpdGggaW5wdXQgZGF0YSBmZWVkLlxuICAgKiBAcGFyYW0gaW5wdXRzIElucHV0IGRhdGEgZmVlZC4gTXVzdCBtYXRjaCB0aGUgaW5wdXRzIG9mIHRoZSBtb2RlbC5cbiAgICogQHBhcmFtIG91dHB1dHMgTmFtZXMgb2YgdGhlIG91dHB1dCB0ZW5zb3JzIHRvIGJlIGZldGNoZWQuIE11c3QgbWF0Y2hcbiAgICogICBuYW1lcyBvZiB0aGUgU3ltYm9saWNUZW5zb3JzIHRoYXQgYmVsb25nIHRvIHRoZSBncmFwaC5cbiAgICogQHJldHVybnMgRmV0Y2hlZCB2YWx1ZXMgZm9yIGBvdXRwdXRzYC5cbiAgICovXG4gIGV4ZWN1dGUoaW5wdXRzOiBUZW5zb3J8VGVuc29yW118TmFtZWRUZW5zb3JNYXAsIG91dHB1dHM6IHN0cmluZ3xzdHJpbmdbXSk6XG4gICAgICBUZW5zb3J8VGVuc29yW10ge1xuICAgIGlmIChBcnJheS5pc0FycmF5KG91dHB1dHMpICYmIG91dHB1dHMubGVuZ3RoID09PSAwKSB7XG4gICAgICB0aHJvdyBuZXcgVmFsdWVFcnJvcihcbiAgICAgICAgICAnYG91dHB1dHNgIGlzIGFuIGVtcHR5IEFycmF5LCB3aGljaCBpcyBub3QgYWxsb3dlZC4nKTtcbiAgICB9XG5cbiAgICBjb25zdCBvdXRwdXRzSXNBcnJheSA9IEFycmF5LmlzQXJyYXkob3V0cHV0cyk7XG4gICAgY29uc3Qgb3V0cHV0TmFtZXMgPVxuICAgICAgICAob3V0cHV0c0lzQXJyYXkgPyBvdXRwdXRzIGFzIHN0cmluZ1tdIDogW291dHB1dHMgYXMgc3RyaW5nXSk7XG4gICAgY29uc3Qgb3V0cHV0U3ltYm9saWNUZW5zb3JzID0gdGhpcy5yZXRyaWV2ZVN5bWJvbGljVGVuc29ycyhvdXRwdXROYW1lcyk7XG5cbiAgICAvLyBGb3JtYXQgdGhlIGlucHV0IGludG8gYSBGZWVkRGljdC5cbiAgICBjb25zdCBmZWVkRGljdCA9IG5ldyBGZWVkRGljdCgpO1xuICAgIGlmIChpbnB1dHMgaW5zdGFuY2VvZiBUZW5zb3IpIHtcbiAgICAgIGlucHV0cyA9IFtpbnB1dHNdO1xuICAgIH1cbiAgICBpZiAoQXJyYXkuaXNBcnJheShpbnB1dHMpKSB7XG4gICAgICBpZiAoaW5wdXRzLmxlbmd0aCAhPT0gdGhpcy5pbnB1dHMubGVuZ3RoKSB7XG4gICAgICAgIHRocm93IG5ldyBWYWx1ZUVycm9yKFxuICAgICAgICAgICAgYFRoZSBudW1iZXIgb2YgaW5wdXRzIHByb3ZpZGVkICgke2lucHV0cy5sZW5ndGh9KSBgICtcbiAgICAgICAgICAgIGBkb2VzIG5vdCBtYXRjaCB0aGUgbnVtYmVyIG9mIGlucHV0cyBvZiB0aGlzIG1vZGVsIGAgK1xuICAgICAgICAgICAgYCgke3RoaXMuaW5wdXRzLmxlbmd0aH0pLmApO1xuICAgICAgfVxuICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCB0aGlzLmlucHV0cy5sZW5ndGg7ICsraSkge1xuICAgICAgICBmZWVkRGljdC5hZGQodGhpcy5pbnB1dHNbaV0sIGlucHV0c1tpXSk7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIGZvciAoY29uc3QgaW5wdXQgb2YgdGhpcy5pbnB1dHMpIHtcbiAgICAgICAgY29uc3QgdGVuc29yVmFsdWUgPSBpbnB1dHNbaW5wdXQubmFtZV07XG4gICAgICAgIGlmICh0ZW5zb3JWYWx1ZSA9PSBudWxsKSB7XG4gICAgICAgICAgdGhyb3cgbmV3IFZhbHVlRXJyb3IoXG4gICAgICAgICAgICAgIGBObyB2YWx1ZSBpcyBwcm92aWRlZCBmb3IgdGhlIG1vZGVsJ3MgaW5wdXQgJHtpbnB1dC5uYW1lfWApO1xuICAgICAgICB9XG4gICAgICAgIGZlZWREaWN0LmFkZChpbnB1dCwgdGVuc29yVmFsdWUpO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIFJ1biBleGVjdXRpb24uXG4gICAgY29uc3QgZXhlY3V0ZU91dHB1dHMgPSBleGVjdXRlKG91dHB1dFN5bWJvbGljVGVuc29ycywgZmVlZERpY3QpIGFzIFRlbnNvcltdO1xuICAgIHJldHVybiBvdXRwdXRzSXNBcnJheSA/IGV4ZWN1dGVPdXRwdXRzIDogZXhlY3V0ZU91dHB1dHNbMF07XG4gIH1cblxuICAvKipcbiAgICogUmV0cmlldmUgdGhlIG1vZGVsJ3MgaW50ZXJuYWwgc3ltYm9saWMgdGVuc29ycyBmcm9tIHN5bWJvbGljLXRlbnNvciBuYW1lcy5cbiAgICovXG4gIHByaXZhdGUgcmV0cmlldmVTeW1ib2xpY1RlbnNvcnMoc3ltYm9saWNUZW5zb3JOYW1lczogc3RyaW5nW10pOlxuICAgICAgU3ltYm9saWNUZW5zb3JbXSB7XG4gICAgY29uc3Qgb3V0cHV0U3ltYm9saWNUZW5zb3JzOiBTeW1ib2xpY1RlbnNvcltdID1cbiAgICAgICAgcHlMaXN0UmVwZWF0KG51bGwsIHN5bWJvbGljVGVuc29yTmFtZXMubGVuZ3RoKTtcbiAgICBsZXQgb3V0cHV0c1JlbWFpbmluZyA9IHN5bWJvbGljVGVuc29yTmFtZXMubGVuZ3RoO1xuICAgIGZvciAoY29uc3QgbGF5ZXIgb2YgdGhpcy5sYXllcnMpIHtcbiAgICAgIGNvbnN0IGxheWVyT3V0cHV0czogU3ltYm9saWNUZW5zb3JbXSA9XG4gICAgICAgICAgQXJyYXkuaXNBcnJheShsYXllci5vdXRwdXQpID8gbGF5ZXIub3V0cHV0IDogW2xheWVyLm91dHB1dF07XG4gICAgICBjb25zdCBsYXllck91dHB1dE5hbWVzID0gbGF5ZXJPdXRwdXRzLm1hcChvdXRwdXQgPT4gb3V0cHV0Lm5hbWUpO1xuICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBzeW1ib2xpY1RlbnNvck5hbWVzLmxlbmd0aDsgKytpKSB7XG4gICAgICAgIGNvbnN0IGluZGV4ID0gbGF5ZXJPdXRwdXROYW1lcy5pbmRleE9mKHN5bWJvbGljVGVuc29yTmFtZXNbaV0pO1xuICAgICAgICBpZiAoaW5kZXggIT09IC0xKSB7XG4gICAgICAgICAgb3V0cHV0U3ltYm9saWNUZW5zb3JzW2ldID0gbGF5ZXJPdXRwdXRzW2luZGV4XTtcbiAgICAgICAgICBvdXRwdXRzUmVtYWluaW5nLS07XG4gICAgICAgIH1cbiAgICAgICAgaWYgKG91dHB1dHNSZW1haW5pbmcgPT09IDApIHtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgaWYgKG91dHB1dHNSZW1haW5pbmcgPT09IDApIHtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKG91dHB1dHNSZW1haW5pbmcgPiAwKSB7XG4gICAgICBjb25zdCByZW1haW5pbmdOYW1lczogc3RyaW5nW10gPSBbXTtcbiAgICAgIG91dHB1dFN5bWJvbGljVGVuc29ycy5mb3JFYWNoKCh0ZW5zb3IsIGkpID0+IHtcbiAgICAgICAgaWYgKHRlbnNvciA9PSBudWxsKSB7XG4gICAgICAgICAgcmVtYWluaW5nTmFtZXMucHVzaChzeW1ib2xpY1RlbnNvck5hbWVzW2ldKTtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgICB0aHJvdyBuZXcgVmFsdWVFcnJvcihcbiAgICAgICAgICBgQ2Fubm90IGZpbmQgU3ltYm9saWNUZW5zb3JzIGZvciBvdXRwdXQgbmFtZShzKTogYCArXG4gICAgICAgICAgYCR7SlNPTi5zdHJpbmdpZnkocmVtYWluaW5nTmFtZXMpfWApO1xuICAgIH1cbiAgICByZXR1cm4gb3V0cHV0U3ltYm9saWNUZW5zb3JzO1xuICB9XG5cbiAgLyoqXG4gICAqIEhlbHBlciBtZXRob2QgdG8gbG9vcCBvdmVyIHNvbWUgZGF0YSBpbiBiYXRjaGVzLlxuICAgKlxuICAgKiBQb3J0aW5nIE5vdGU6IE5vdCB1c2luZyB0aGUgZnVuY3Rpb25hbCBhcHByb2FjaCBpbiB0aGUgUHl0aG9uIGVxdWl2YWxlbnRcbiAgICogICBkdWUgdG8gdGhlIGltcGVyYXRpdmUgYmFja2VuZC5cbiAgICogUG9ydGluZyBOb3RlOiBEb2VzIG5vdCBzdXBwb3J0IHN0ZXAgbW9kZSBjdXJyZW50bHkuXG4gICAqXG4gICAqIEBwYXJhbSBpbnM6IGlucHV0IGRhdGFcbiAgICogQHBhcmFtIGJhdGNoU2l6ZTogaW50ZWdlciBiYXRjaCBzaXplLlxuICAgKiBAcGFyYW0gdmVyYm9zZTogdmVyYm9zaXR5IG1vZGVsXG4gICAqIEByZXR1cm5zOiBQcmVkaWN0aW9ucyBhcyBgdGYuVGVuc29yYCAoaWYgYSBzaW5nbGUgb3V0cHV0KSBvciBhbiBgQXJyYXlgIG9mXG4gICAqICAgYHRmLlRlbnNvcmAgKGlmIG11bHRpcGUgb3V0cHV0cykuXG4gICAqL1xuICBwcml2YXRlIHByZWRpY3RMb29wKGluczogVGVuc29yfFRlbnNvcltdLCBiYXRjaFNpemUgPSAzMiwgdmVyYm9zZSA9IGZhbHNlKTpcbiAgICAgIFRlbnNvcnxUZW5zb3JbXSB7XG4gICAgcmV0dXJuIHRmYy50aWR5KCgpID0+IHtcbiAgICAgIGNvbnN0IG51bVNhbXBsZXMgPSB0aGlzLmNoZWNrTnVtU2FtcGxlcyhpbnMpO1xuICAgICAgaWYgKHZlcmJvc2UpIHtcbiAgICAgICAgdGhyb3cgbmV3IE5vdEltcGxlbWVudGVkRXJyb3IoXG4gICAgICAgICAgICAnVmVyYm9zZSBwcmVkaWN0TG9vcCgpIGlzIG5vdCBpbXBsZW1lbnRlZCB5ZXQuJyk7XG4gICAgICB9XG5cbiAgICAgIC8vIFNhbXBsZS1iYXNlZCBwcmVkaWN0aW9ucy5cbiAgICAgIC8vIFBvcnRpbmcgTm90ZTogVGVuc29yIGN1cnJlbnRseSBkb2VzIG5vdCBzdXBwb3J0IHNsaWNlZCBhc3NpZ25tZW50cyBhc1xuICAgICAgLy8gICBpbiBudW1weSwgZS5nLiwgeFsxOjNdID0geS4gVGhlcmVmb3JlIHdlIHVzZSBjb25jYXRlbmF0aW9uIHdoaWxlXG4gICAgICAvLyAgIGl0ZXJhdGluZyBvdmVyIHRoZSBiYXRjaGVzLlxuXG4gICAgICBjb25zdCBiYXRjaGVzID0gbWFrZUJhdGNoZXMobnVtU2FtcGxlcywgYmF0Y2hTaXplKTtcbiAgICAgIGNvbnN0IG91dHNCYXRjaGVzOiBUZW5zb3JbXVtdID0gdGhpcy5vdXRwdXRzLm1hcChvdXRwdXQgPT4gW10pO1xuXG4gICAgICAvLyBUT0RPKGNhaXMpOiBDYW4gdGhlIHNjb3BlKCkgYmUgcHVzaGVkIGRvd24gaW5zaWRlIHRoZSBmb3IgbG9vcD9cbiAgICAgIGZvciAobGV0IGJhdGNoSW5kZXggPSAwOyBiYXRjaEluZGV4IDwgYmF0Y2hlcy5sZW5ndGg7ICsrYmF0Y2hJbmRleCkge1xuICAgICAgICBjb25zdCBiYXRjaE91dHMgPSB0ZmMudGlkeSgoKSA9PiB7XG4gICAgICAgICAgY29uc3QgYmF0Y2hTdGFydCA9IGJhdGNoZXNbYmF0Y2hJbmRleF1bMF07XG4gICAgICAgICAgY29uc3QgYmF0Y2hFbmQgPSBiYXRjaGVzW2JhdGNoSW5kZXhdWzFdO1xuICAgICAgICAgIC8vIFRPRE8oY2Fpcyk6IFRha2UgY2FyZSBvZiB0aGUgY2FzZSBvZiB0aGUgbGFzdCBlbGVtZW50IGlzIGEgZmxhZyBmb3JcbiAgICAgICAgICAvLyAgIHRyYWluaW5nL3Rlc3QuXG4gICAgICAgICAgY29uc3QgaW5zQmF0Y2ggPSBzbGljZUFycmF5cyhpbnMsIGJhdGNoU3RhcnQsIGJhdGNoRW5kKTtcblxuICAgICAgICAgIC8vIENvbnN0cnVjdCB0aGUgZmVlZHMgZm9yIGV4ZWN1dGUoKTtcbiAgICAgICAgICBjb25zdCBmZWVkcyA9IFtdO1xuICAgICAgICAgIGlmIChBcnJheS5pc0FycmF5KGluc0JhdGNoKSkge1xuICAgICAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBpbnNCYXRjaC5sZW5ndGg7ICsraSkge1xuICAgICAgICAgICAgICBmZWVkcy5wdXNoKHtrZXk6IHRoaXMuaW5wdXRzW2ldLCB2YWx1ZTogaW5zQmF0Y2hbaV19KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgZmVlZHMucHVzaCh7a2V5OiB0aGlzLmlucHV0c1swXSwgdmFsdWU6IGluc0JhdGNofSk7XG4gICAgICAgICAgfVxuICAgICAgICAgIGNvbnN0IGZlZWREaWN0ID0gbmV3IEZlZWREaWN0KGZlZWRzKTtcbiAgICAgICAgICByZXR1cm4gZXhlY3V0ZSh0aGlzLm91dHB1dHMsIGZlZWREaWN0KSBhcyBUZW5zb3JbXTtcbiAgICAgICAgfSk7XG4gICAgICAgIGJhdGNoT3V0cy5mb3JFYWNoKChiYXRjaE91dCwgaSkgPT4gb3V0c0JhdGNoZXNbaV0ucHVzaChiYXRjaE91dCkpO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHNpbmdsZXRvbk9yQXJyYXkoXG4gICAgICAgICAgb3V0c0JhdGNoZXMubWFwKGJhdGNoZXMgPT4gdGZjLmNvbmNhdChiYXRjaGVzLCAwKSkpO1xuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIEdlbmVyYXRlcyBvdXRwdXQgcHJlZGljdGlvbnMgZm9yIHRoZSBpbnB1dCBzYW1wbGVzLlxuICAgKlxuICAgKiBDb21wdXRhdGlvbiBpcyBkb25lIGluIGJhdGNoZXMuXG4gICAqXG4gICAqIE5vdGU6IHRoZSBcInN0ZXBcIiBtb2RlIG9mIHByZWRpY3QoKSBpcyBjdXJyZW50bHkgbm90IHN1cHBvcnRlZC5cbiAgICogICBUaGlzIGlzIGJlY2F1c2UgdGhlIFRlbnNvckZsb3cuanMgY29yZSBiYWNrZW5kIGlzIGltcGVyYXRpdmUgb25seS5cbiAgICpcbiAgICogYGBganNcbiAgICogY29uc3QgbW9kZWwgPSB0Zi5zZXF1ZW50aWFsKHtcbiAgICogICBsYXllcnM6IFt0Zi5sYXllcnMuZGVuc2Uoe3VuaXRzOiAxLCBpbnB1dFNoYXBlOiBbMTBdfSldXG4gICAqIH0pO1xuICAgKiBtb2RlbC5wcmVkaWN0KHRmLm9uZXMoWzgsIDEwXSksIHtiYXRjaFNpemU6IDR9KS5wcmludCgpO1xuICAgKiBgYGBcbiAgICpcbiAgICogQHBhcmFtIHggVGhlIGlucHV0IGRhdGEsIGFzIGEgVGVuc29yLCBvciBhbiBgQXJyYXlgIG9mIGB0Zi5UZW5zb3JgcyBpZlxuICAgKiAgIHRoZSBtb2RlbCBoYXMgbXVsdGlwbGUgaW5wdXRzLlxuICAgKiBAcGFyYW0gYXJncyBBIGBNb2RlbFByZWRpY3RBcmdzYCBvYmplY3QgY29udGFpbmluZyBvcHRpb25hbCBmaWVsZHMuXG4gICAqXG4gICAqIEByZXR1cm4gUHJlZGljdGlvbiByZXN1bHRzIGFzIGEgYHRmLlRlbnNvcmAocykuXG4gICAqXG4gICAqIEBleGNlcHRpb24gVmFsdWVFcnJvciBJbiBjYXNlIG9mIG1pc21hdGNoIGJldHdlZW4gdGhlIHByb3ZpZGVkIGlucHV0IGRhdGFcbiAgICogICBhbmQgdGhlIG1vZGVsJ3MgZXhwZWN0YXRpb25zLCBvciBpbiBjYXNlIGEgc3RhdGVmdWwgbW9kZWwgcmVjZWl2ZXMgYVxuICAgKiAgIG51bWJlciBvZiBzYW1wbGVzIHRoYXQgaXMgbm90IGEgbXVsdGlwbGUgb2YgdGhlIGJhdGNoIHNpemUuXG4gICAqXG4gICAqIEBkb2Mge2hlYWRpbmc6ICdNb2RlbHMnLCBzdWJoZWFkaW5nOiAnQ2xhc3Nlcyd9XG4gICAqL1xuICBwcmVkaWN0KHg6IFRlbnNvcnxUZW5zb3JbXSwgYXJnczogTW9kZWxQcmVkaWN0QXJncyA9IHt9KTogVGVuc29yfFRlbnNvcltdIHtcbiAgICBjb25zdCB4c1JhbmsyT3JIaWdoZXIgPSBlbnN1cmVUZW5zb3JzUmFuazJPckhpZ2hlcih4KTtcbiAgICBjaGVja0lucHV0RGF0YShcbiAgICAgICAgeHNSYW5rMk9ySGlnaGVyLCB0aGlzLmlucHV0TmFtZXMsIHRoaXMuZmVlZElucHV0U2hhcGVzLCBmYWxzZSk7XG4gICAgdHJ5IHtcbiAgICAgIC8vIFRPRE8oY2Fpcyk6IFRha2UgY2FyZSBvZiBzdGF0ZWZ1bCBtb2RlbHMuXG4gICAgICAvLyAgIGlmICh0aGlzLnN0YXRlZnVsKSAuLi5cbiAgICAgIC8vIFRPRE8oY2Fpcyk6IFRha2UgY2FyZSBvZiB0aGUgbGVhcm5pbmdfcGhhc2UgYm9vbGVhbiBmbGFnLlxuICAgICAgLy8gICBpZiAodGhpcy51c2VMZWFybmluZ1BoYXNlKSAuLi5cbiAgICAgIGNvbnN0IGJhdGNoU2l6ZSA9IGFyZ3MuYmF0Y2hTaXplID09IG51bGwgPyAzMiA6IGFyZ3MuYmF0Y2hTaXplO1xuICAgICAgY2hlY2tCYXRjaFNpemUoYmF0Y2hTaXplKTtcbiAgICAgIHJldHVybiB0aGlzLnByZWRpY3RMb29wKHhzUmFuazJPckhpZ2hlciwgYmF0Y2hTaXplKTtcbiAgICB9IGZpbmFsbHkge1xuICAgICAgZGlzcG9zZU5ld1RlbnNvcnMoeHNSYW5rMk9ySGlnaGVyLCB4KTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBwcmVkaWN0aW9ucyBmb3IgYSBzaW5nbGUgYmF0Y2ggb2Ygc2FtcGxlcy5cbiAgICpcbiAgICogYGBganNcbiAgICogY29uc3QgbW9kZWwgPSB0Zi5zZXF1ZW50aWFsKHtcbiAgICogICBsYXllcnM6IFt0Zi5sYXllcnMuZGVuc2Uoe3VuaXRzOiAxLCBpbnB1dFNoYXBlOiBbMTBdfSldXG4gICAqIH0pO1xuICAgKiBtb2RlbC5wcmVkaWN0T25CYXRjaCh0Zi5vbmVzKFs4LCAxMF0pKS5wcmludCgpO1xuICAgKiBgYGBcbiAgICogQHBhcmFtIHg6IElucHV0IHNhbXBsZXMsIGFzIGEgVGVuc29yIChmb3IgbW9kZWxzIHdpdGggZXhhY3RseSBvbmVcbiAgICogICBpbnB1dCkgb3IgYW4gYXJyYXkgb2YgVGVuc29ycyAoZm9yIG1vZGVscyB3aXRoIG1vcmUgdGhhbiBvbmUgaW5wdXQpLlxuICAgKiBAcmV0dXJuIFRlbnNvcihzKSBvZiBwcmVkaWN0aW9uc1xuICAgKlxuICAgKiBAZG9jIHtoZWFkaW5nOiAnTW9kZWxzJywgc3ViaGVhZGluZzogJ0NsYXNzZXMnfVxuICAgKi9cbiAgcHJlZGljdE9uQmF0Y2goeDogVGVuc29yfFRlbnNvcltdKTogVGVuc29yfFRlbnNvcltdIHtcbiAgICBjaGVja0lucHV0RGF0YSh4LCB0aGlzLmlucHV0TmFtZXMsIHRoaXMuZmVlZElucHV0U2hhcGVzLCB0cnVlKTtcbiAgICAvLyBUT0RPKGNhaXMpOiBUYWtlIGNhcmUgb2YgdGhlIGxlYXJuaW5nX3BoYXNlIGJvb2xlYW4gZmxhZy5cbiAgICAvLyAgIGlmICh0aGlzLnVzZUxlYXJuaW5nUGhhc2UpIC4uLlxuICAgIGNvbnN0IGJhdGNoU2l6ZSA9IChBcnJheS5pc0FycmF5KHgpID8geFswXSA6IHgpLnNoYXBlWzBdO1xuICAgIHJldHVybiB0aGlzLnByZWRpY3RMb29wKHgsIGJhdGNoU2l6ZSk7XG4gIH1cblxuICBwcm90ZWN0ZWQgc3RhbmRhcmRpemVVc2VyRGF0YVhZKFxuICAgICAgeDogVGVuc29yfFRlbnNvcltdfHtbaW5wdXROYW1lOiBzdHJpbmddOiBUZW5zb3J9LFxuICAgICAgeTogVGVuc29yfFRlbnNvcltdfHtbaW5wdXROYW1lOiBzdHJpbmddOiBUZW5zb3J9LCBjaGVja0JhdGNoQXhpcyA9IHRydWUsXG4gICAgICBiYXRjaFNpemU/OiBudW1iZXIpOiBbVGVuc29yW10sIFRlbnNvcltdXSB7XG4gICAgLy8gVE9ETyhjYWlzKTogQWRkIHNhbXBsZVdlaWdodCwgY2xhc3NXZWlnaHRcbiAgICBpZiAodGhpcy5vcHRpbWl6ZXJfID09IG51bGwpIHtcbiAgICAgIHRocm93IG5ldyBSdW50aW1lRXJyb3IoXG4gICAgICAgICAgJ1lvdSBtdXN0IGNvbXBpbGUgYSBtb2RlbCBiZWZvcmUgdHJhaW5pbmcvdGVzdGluZy4gVXNlICcgK1xuICAgICAgICAgICdMYXllcnNNb2RlbC5jb21waWxlKG1vZGVsQ29tcGlsZUFyZ3MpLicpO1xuICAgIH1cbiAgICBjb25zdCBvdXRwdXRTaGFwZXM6IFNoYXBlW10gPSBbXTtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IHRoaXMuZmVlZE91dHB1dFNoYXBlcy5sZW5ndGg7ICsraSkge1xuICAgICAgY29uc3Qgb3V0cHV0U2hhcGUgPSB0aGlzLmZlZWRPdXRwdXRTaGFwZXNbaV07XG4gICAgICBjb25zdCBsb3NzRm4gPSB0aGlzLmZlZWRMb3NzRm5zW2ldO1xuICAgICAgaWYgKGxvc3NGbiA9PT0gbG9zc2VzLnNwYXJzZUNhdGVnb3JpY2FsQ3Jvc3NlbnRyb3B5KSB7XG4gICAgICAgIG91dHB1dFNoYXBlcy5wdXNoKFxuICAgICAgICAgICAgb3V0cHV0U2hhcGUuc2xpY2UoMCwgb3V0cHV0U2hhcGUubGVuZ3RoIC0gMSkuY29uY2F0KFsxXSkpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgLy8gUG9ydGluZyBOb3RlOiBCZWNhdXNlIG9mIHN0cm9uZyB0eXBpbmcgYGxvc3NGbmAgbXVzdCBiZSBhIGZ1bmN0aW9uLlxuICAgICAgICBvdXRwdXRTaGFwZXMucHVzaChvdXRwdXRTaGFwZSk7XG4gICAgICB9XG4gICAgfVxuICAgIHggPSBzdGFuZGFyZGl6ZUlucHV0RGF0YShcbiAgICAgICAgeCwgdGhpcy5mZWVkSW5wdXROYW1lcywgdGhpcy5mZWVkSW5wdXRTaGFwZXMsIGZhbHNlLCAnaW5wdXQnKTtcbiAgICB5ID0gc3RhbmRhcmRpemVJbnB1dERhdGEoXG4gICAgICAgIHksIHRoaXMuZmVlZE91dHB1dE5hbWVzLCBvdXRwdXRTaGFwZXMsIGZhbHNlLCAndGFyZ2V0Jyk7XG4gICAgLy8gVE9ETyhjYWlzKTogU3RhbmRhcmRpemUgc2FtcGxlV2VpZ2h0cyAmIGNsYXNzV2VpZ2h0cy5cbiAgICBjaGVja0FycmF5TGVuZ3Rocyh4LCB5LCBudWxsKTtcbiAgICAvLyBUT0RPKGNhaXMpOiBDaGVjayBzYW1wbGVXZWlnaHRzIGFzIHdlbGwuXG4gICAgY2hlY2tMb3NzQW5kVGFyZ2V0Q29tcGF0aWJpbGl0eSh5LCB0aGlzLmZlZWRMb3NzRm5zLCB0aGlzLmZlZWRPdXRwdXRTaGFwZXMpO1xuICAgIGlmICh0aGlzLnN0YXRlZnVsICYmIGJhdGNoU2l6ZSAhPSBudWxsICYmIGJhdGNoU2l6ZSA+IDApIHtcbiAgICAgIGlmICh4WzBdLnNoYXBlWzBdICUgYmF0Y2hTaXplICE9PSAwKSB7XG4gICAgICAgIHRocm93IG5ldyBWYWx1ZUVycm9yKFxuICAgICAgICAgICAgYEluIGEgc3RhdGVmdWwgbmV0d29yaywgeW91IHNob3VsZCBvbmx5IHBhc3MgaW5wdXRzIHdpdGggYSBgICtcbiAgICAgICAgICAgIGBudW1iZXIgb2Ygc2FtcGxlcyB0aGF0IGlzIGRpdmlzaWJsZSBieSB0aGUgYmF0Y2ggc2l6ZSBgICtcbiAgICAgICAgICAgIGAke2JhdGNoU2l6ZX0uIEZvdW5kOiAke3hbMF0uc2hhcGVbMF19IHNhbXBsZShzKS5gKTtcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIFt4LCB5XTtcbiAgfVxuXG4gIHByb3RlY3RlZCBhc3luYyBzdGFuZGFyZGl6ZVVzZXJEYXRhKFxuICAgICAgeDogVGVuc29yfFRlbnNvcltdfHtbaW5wdXROYW1lOiBzdHJpbmddOiBUZW5zb3J9LFxuICAgICAgeTogVGVuc29yfFRlbnNvcltdfHtbaW5wdXROYW1lOiBzdHJpbmddOiBUZW5zb3J9LFxuICAgICAgc2FtcGxlV2VpZ2h0PzogVGVuc29yfFRlbnNvcltdfHtbb3V0cHV0TmFtZTogc3RyaW5nXTogVGVuc29yfSxcbiAgICAgIGNsYXNzV2VpZ2h0PzogQ2xhc3NXZWlnaHR8Q2xhc3NXZWlnaHRbXXxDbGFzc1dlaWdodE1hcCxcbiAgICAgIGNoZWNrQmF0Y2hBeGlzID0gdHJ1ZSxcbiAgICAgIGJhdGNoU2l6ZT86IG51bWJlcik6IFByb21pc2U8W1RlbnNvcltdLCBUZW5zb3JbXSwgVGVuc29yW11dPiB7XG4gICAgY29uc3QgW3N0YW5kYXJkWHMsIHN0YW5kYXJkWXNdID1cbiAgICAgICAgdGhpcy5zdGFuZGFyZGl6ZVVzZXJEYXRhWFkoeCwgeSwgY2hlY2tCYXRjaEF4aXMsIGJhdGNoU2l6ZSk7XG4gICAgLy8gVE9ETyhjYWlzKTogSGFuZGxlIHNhbXBsZVdlaWdodHMuXG4gICAgaWYgKHNhbXBsZVdlaWdodCAhPSBudWxsKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ3NhbXBsZSB3ZWlnaHQgaXMgbm90IHN1cHBvcnRlZCB5ZXQuJyk7XG4gICAgfVxuXG4gICAgbGV0IHN0YW5kYXJkU2FtcGxlV2VpZ2h0czogVGVuc29yW10gPSBudWxsO1xuICAgIGlmIChjbGFzc1dlaWdodCAhPSBudWxsKSB7XG4gICAgICBjb25zdCBjbGFzc1dlaWdodHMgPVxuICAgICAgICAgIHN0YW5kYXJkaXplQ2xhc3NXZWlnaHRzKGNsYXNzV2VpZ2h0LCB0aGlzLm91dHB1dE5hbWVzKTtcbiAgICAgIHN0YW5kYXJkU2FtcGxlV2VpZ2h0cyA9IFtdO1xuICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBjbGFzc1dlaWdodHMubGVuZ3RoOyArK2kpIHtcbiAgICAgICAgc3RhbmRhcmRTYW1wbGVXZWlnaHRzLnB1c2goXG4gICAgICAgICAgICBhd2FpdCBzdGFuZGFyZGl6ZVdlaWdodHMoc3RhbmRhcmRZc1tpXSwgbnVsbCwgY2xhc3NXZWlnaHRzW2ldKSk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gVE9ETyhjYWlzKTogRGVhbCB3aXRoIHRoZSBjYXNlIG9mIG1vZGVsLnN0YXRlZnVsID09IHRydWUuXG4gICAgcmV0dXJuIFtzdGFuZGFyZFhzLCBzdGFuZGFyZFlzLCBzdGFuZGFyZFNhbXBsZVdlaWdodHNdO1xuICB9XG5cbiAgLyoqXG4gICAqIExvb3Agb3ZlciBzb21lIHRlc3QgZGF0YSBpbiBiYXRjaGVzLlxuICAgKiBAcGFyYW0gZiBBIEZ1bmN0aW9uIHJldHVybmluZyBhIGxpc3Qgb2YgdGVuc29ycy5cbiAgICogQHBhcmFtIGlucyBBcnJheSBvZiB0ZW5zb3JzIHRvIGJlIGZlZCB0byBgZmAuXG4gICAqIEBwYXJhbSBiYXRjaFNpemUgSW50ZWdlciBiYXRjaCBzaXplIG9yIGBudWxsYCAvIGB1bmRlZmluZWRgLlxuICAgKiBAcGFyYW0gdmVyYm9zZSB2ZXJib3NpdHkgbW9kZS5cbiAgICogQHBhcmFtIHN0ZXBzIFRvdGFsIG51bWJlciBvZiBzdGVwcyAoYmF0Y2hlcyBvZiBzYW1wbGVzKSBiZWZvcmVcbiAgICogZGVjbGFyaW5nIHRlc3QgZmluaXNoZWQuIElnbm9yZWQgd2l0aCB0aGUgZGVmYXVsdCB2YWx1ZSBvZiBgbnVsbGAgL1xuICAgKiBgdW5kZWZpbmVkYC5cbiAgICogQHJldHVybnMgQXJyYXkgb2YgU2NhbGFycy5cbiAgICovXG4gIHByaXZhdGUgdGVzdExvb3AoXG4gICAgICBmOiAoZGF0YTogVGVuc29yW10pID0+IFNjYWxhcltdLCBpbnM6IFRlbnNvcltdLCBiYXRjaFNpemU/OiBudW1iZXIsXG4gICAgICB2ZXJib3NlID0gMCwgc3RlcHM/OiBudW1iZXIpOiBTY2FsYXJbXSB7XG4gICAgcmV0dXJuIHRmYy50aWR5KCgpID0+IHtcbiAgICAgIGNvbnN0IG51bVNhbXBsZXMgPSB0aGlzLmNoZWNrTnVtU2FtcGxlcyhpbnMsIGJhdGNoU2l6ZSwgc3RlcHMsICdzdGVwcycpO1xuICAgICAgY29uc3Qgb3V0czogU2NhbGFyW10gPSBbXTtcbiAgICAgIGlmICh2ZXJib3NlID4gMCkge1xuICAgICAgICB0aHJvdyBuZXcgTm90SW1wbGVtZW50ZWRFcnJvcignVmVyYm9zZSBtb2RlIGlzIG5vdCBpbXBsZW1lbnRlZCB5ZXQuJyk7XG4gICAgICB9XG4gICAgICAvLyBUT0RPKGNhaXMpOiBVc2UgYGluZGljZXNGb3JDb252ZXJzaW9uVG9EZW5zZScgdG8gcHJldmVudCBzbG93IGRvd24uXG4gICAgICBpZiAoc3RlcHMgIT0gbnVsbCkge1xuICAgICAgICB0aHJvdyBuZXcgTm90SW1wbGVtZW50ZWRFcnJvcihcbiAgICAgICAgICAgICdzdGVwcyBtb2RlIGluIHRlc3RMb29wKCkgaXMgbm90IGltcGxlbWVudGVkIHlldCcpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgY29uc3QgYmF0Y2hlcyA9IG1ha2VCYXRjaGVzKG51bVNhbXBsZXMsIGJhdGNoU2l6ZSk7XG4gICAgICAgIGNvbnN0IGluZGV4QXJyYXkgPSB0ZW5zb3IxZChyYW5nZSgwLCBudW1TYW1wbGVzKSk7XG4gICAgICAgIGZvciAobGV0IGJhdGNoSW5kZXggPSAwOyBiYXRjaEluZGV4IDwgYmF0Y2hlcy5sZW5ndGg7ICsrYmF0Y2hJbmRleCkge1xuICAgICAgICAgIGNvbnN0IGJhdGNoU3RhcnQgPSBiYXRjaGVzW2JhdGNoSW5kZXhdWzBdO1xuICAgICAgICAgIGNvbnN0IGJhdGNoRW5kID0gYmF0Y2hlc1tiYXRjaEluZGV4XVsxXTtcbiAgICAgICAgICBjb25zdCBiYXRjaElkcyA9XG4gICAgICAgICAgICAgIEsuc2xpY2VBbG9uZ0ZpcnN0QXhpcyhcbiAgICAgICAgICAgICAgICAgIGluZGV4QXJyYXksIGJhdGNoU3RhcnQsIGJhdGNoRW5kIC0gYmF0Y2hTdGFydCkgYXMgVGVuc29yMUQ7XG4gICAgICAgICAgLy8gVE9ETyhjYWlzKTogSW4gaW5zLCB0cmFpbiBmbGFnIGNhbiBiZSBhIG51bWJlciwgaW5zdGVhZCBvZiBhblxuICAgICAgICAgIC8vICAgVGVuc29yPyBEbyB3ZSBuZWVkIHRvIGhhbmRsZSB0aGlzIGluIHRmanMtbGF5ZXJzP1xuICAgICAgICAgIGNvbnN0IGluc0JhdGNoID0gc2xpY2VBcnJheXNCeUluZGljZXMoaW5zLCBiYXRjaElkcykgYXMgU2NhbGFyW107XG4gICAgICAgICAgY29uc3QgYmF0Y2hPdXRzID0gZihpbnNCYXRjaCk7XG4gICAgICAgICAgaWYgKGJhdGNoSW5kZXggPT09IDApIHtcbiAgICAgICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgYmF0Y2hPdXRzLmxlbmd0aDsgKytpKSB7XG4gICAgICAgICAgICAgIG91dHMucHVzaChzY2FsYXIoMCkpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IGJhdGNoT3V0cy5sZW5ndGg7ICsraSkge1xuICAgICAgICAgICAgY29uc3QgYmF0Y2hPdXQgPSBiYXRjaE91dHNbaV07XG4gICAgICAgICAgICBvdXRzW2ldID1cbiAgICAgICAgICAgICAgICB0ZmMuYWRkKG91dHNbaV0sIHRmYy5tdWwoYmF0Y2hFbmQgLSBiYXRjaFN0YXJ0LCBiYXRjaE91dCkpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IG91dHMubGVuZ3RoOyArK2kpIHtcbiAgICAgICAgICBvdXRzW2ldID0gdGZjLmRpdihvdXRzW2ldLCBudW1TYW1wbGVzKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgcmV0dXJuIG91dHM7XG4gICAgfSk7XG4gIH1cblxuICBwcm90ZWN0ZWQgZ2V0RGVkdXBlZE1ldHJpY3NOYW1lcygpOiBzdHJpbmdbXSB7XG4gICAgY29uc3Qgb3V0TGFiZWxzID0gdGhpcy5tZXRyaWNzTmFtZXM7XG4gICAgLy8gUmVuYW1lIGR1cGxpY2F0ZWQgbWV0cmljcyBuYW1lcyAoY2FuIGhhcHBlbiB3aXRoIGFuIG91dHB1dCBsYXllclxuICAgIC8vIHNoYXJlZCBhbW9uZyBtdWx0aXBsZSBkYXRhZmxvd3MpLlxuICAgIGNvbnN0IGRlZHVwZWRPdXRMYWJlbHMgPSBbXTtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IG91dExhYmVscy5sZW5ndGg7ICsraSkge1xuICAgICAgY29uc3QgbGFiZWwgPSBvdXRMYWJlbHNbaV07XG4gICAgICBsZXQgbmV3TGFiZWwgPSBsYWJlbDtcbiAgICAgIGlmIChjb3VudChvdXRMYWJlbHMsIGxhYmVsKSA+IDEpIHtcbiAgICAgICAgY29uc3QgZHVwSW5kZXggPSBjb3VudChvdXRMYWJlbHMuc2xpY2UoMCwgaSksIGxhYmVsKTtcbiAgICAgICAgbmV3TGFiZWwgKz0gYF8ke2R1cEluZGV4fWA7XG4gICAgICB9XG4gICAgICBkZWR1cGVkT3V0TGFiZWxzLnB1c2gobmV3TGFiZWwpO1xuICAgIH1cbiAgICByZXR1cm4gZGVkdXBlZE91dExhYmVscztcbiAgfVxuXG4gIC8qKlxuICAgKiBDcmVhdGVzIGEgZnVuY3Rpb24gdGhhdCBwZXJmb3JtcyB0aGUgZm9sbG93aW5nIGFjdGlvbnM6XG4gICAqXG4gICAqIDEuIGNvbXB1dGVzIHRoZSBsb3NzZXNcbiAgICogMi4gc3VtcyB0aGVtIHRvIGdldCB0aGUgdG90YWwgbG9zc1xuICAgKiAzLiBjYWxsIHRoZSBvcHRpbWl6ZXIgY29tcHV0ZXMgdGhlIGdyYWRpZW50cyBvZiB0aGUgTGF5ZXJzTW9kZWwnc1xuICAgKiAgICB0cmFpbmFibGUgd2VpZ2h0cyB3LnIudC4gdGhlIHRvdGFsIGxvc3MgYW5kIHVwZGF0ZSB0aGUgdmFyaWFibGVzXG4gICAqIDQuIGNhbGN1bGF0ZXMgdGhlIG1ldHJpY3NcbiAgICogNS4gcmV0dXJucyB0aGUgdmFsdWVzIG9mIHRoZSBsb3NzZXMgYW5kIG1ldHJpY3MuXG4gICAqL1xuICBwcm90ZWN0ZWQgbWFrZVRyYWluRnVuY3Rpb24oKTogKGRhdGE6IFRlbnNvcltdKSA9PiBTY2FsYXJbXSB7XG4gICAgcmV0dXJuIChkYXRhOiBUZW5zb3JbXSkgPT4ge1xuICAgICAgY29uc3QgbG9zc1ZhbHVlczogU2NhbGFyW10gPSBbXTtcblxuICAgICAgY29uc3QgaW5wdXRzID0gZGF0YS5zbGljZSgwLCB0aGlzLmlucHV0cy5sZW5ndGgpO1xuICAgICAgY29uc3QgdGFyZ2V0cyA9IGRhdGEuc2xpY2UoXG4gICAgICAgICAgdGhpcy5pbnB1dHMubGVuZ3RoLCB0aGlzLmlucHV0cy5sZW5ndGggKyB0aGlzLm91dHB1dHMubGVuZ3RoKTtcbiAgICAgIGNvbnN0IHNhbXBsZVdlaWdodHMgPSBkYXRhLnNsaWNlKFxuICAgICAgICAgIHRoaXMuaW5wdXRzLmxlbmd0aCArIHRoaXMub3V0cHV0cy5sZW5ndGgsXG4gICAgICAgICAgdGhpcy5pbnB1dHMubGVuZ3RoICsgdGhpcy5vdXRwdXRzLmxlbmd0aCAqIDIpO1xuXG4gICAgICBjb25zdCBtZXRyaWNzVmFsdWVzOiBTY2FsYXJbXSA9IFtdO1xuXG4gICAgICAvLyBDcmVhdGUgYSBmdW5jdGlvbiB0aGF0IGNvbXB1dGVzIHRoZSB0b3RhbCBsb3NzIGJhc2VkIG9uIHRoZVxuICAgICAgLy8gaW5wdXRzLiBUaGlzIGZ1bmN0aW9uIGlzIHVzZWQgZm9yIG9idGFpbmluZyBncmFkaWVudHMgdGhyb3VnaFxuICAgICAgLy8gYmFja3Byb3AuXG4gICAgICBjb25zdCB0b3RhbExvc3NGdW5jdGlvbiA9ICgpID0+IHtcbiAgICAgICAgY29uc3QgZmVlZHMgPSBbXTtcbiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCB0aGlzLmlucHV0cy5sZW5ndGg7ICsraSkge1xuICAgICAgICAgIGZlZWRzLnB1c2goe2tleTogdGhpcy5pbnB1dHNbaV0sIHZhbHVlOiBpbnB1dHNbaV19KTtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBmZWVkRGljdCA9IG5ldyBGZWVkRGljdChmZWVkcyk7XG4gICAgICAgIGNvbnN0IG91dHB1dHMgPVxuICAgICAgICAgICAgZXhlY3V0ZSh0aGlzLm91dHB1dHMsIGZlZWREaWN0LCB7J3RyYWluaW5nJzogdHJ1ZX0pIGFzIFRlbnNvcltdO1xuICAgICAgICAvLyBUT0RPKGNhaXMpOiBUYWtlIGNhcmUgb2YgdGhlIGNhc2Ugb2YgbXVsdGlwbGUgb3V0cHV0cyBmcm9tIGFcbiAgICAgICAgLy8gICBzaW5nbGUgbGF5ZXI/XG5cbiAgICAgICAgbGV0IHRvdGFsTG9zczogVGVuc29yO1xuICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IHRoaXMubG9zc0Z1bmN0aW9ucy5sZW5ndGg7ICsraSkge1xuICAgICAgICAgIGNvbnN0IGxvc3NGdW5jdGlvbiA9IHRoaXMubG9zc0Z1bmN0aW9uc1tpXTtcbiAgICAgICAgICBsZXQgbG9zcyA9IGxvc3NGdW5jdGlvbih0YXJnZXRzW2ldLCBvdXRwdXRzW2ldKTtcbiAgICAgICAgICBpZiAoc2FtcGxlV2VpZ2h0c1tpXSAhPSBudWxsKSB7XG4gICAgICAgICAgICBsb3NzID0gY29tcHV0ZVdlaWdodGVkTG9zcyhsb3NzLCBzYW1wbGVXZWlnaHRzW2ldKTtcbiAgICAgICAgICB9XG5cbiAgICAgICAgICAvLyBUT0RPKGNhaXMpOiBwdXNoIFNjYWxhciBpbnN0ZWFkLlxuICAgICAgICAgIGNvbnN0IG1lYW5Mb3NzOiBTY2FsYXIgPSB0ZmMubWVhbihsb3NzKTtcbiAgICAgICAgICAvLyBUT0RPKGNhaXMpOiBVc2UgYSBzY29wZSgpIGluc3RlYWQsIHRvIGF2b2lkIG93bmVyc2hpcC5cbiAgICAgICAgICBsb3NzVmFsdWVzLnB1c2gobWVhbkxvc3MpO1xuICAgICAgICAgIGlmIChpID09PSAwKSB7XG4gICAgICAgICAgICB0b3RhbExvc3MgPSBsb3NzO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICB0b3RhbExvc3MgPSB0ZmMuYWRkKHRvdGFsTG9zcywgbG9zcyk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gQ29tcHV0ZSB0aGUgbWV0cmljcy5cbiAgICAgICAgLy8gVE9ETyhjYWlzKTogVGhlc2Ugc2hvdWxkIHByb2JhYmx5IGJlIGNhbGN1bGF0ZWQgb3V0c2lkZVxuICAgICAgICAvLyAgIHRvdGFsTG9zc0Z1bmN0aW9uIHRvIGJlbmVmaXQgc3BlZWQ/XG4gICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgdGhpcy5tZXRyaWNzVGVuc29ycy5sZW5ndGg7ICsraSkge1xuICAgICAgICAgIGxldCB3ZWlnaHRlZE1ldHJpYzogU2NhbGFyO1xuXG4gICAgICAgICAgaWYgKHRoaXMub3V0cHV0cy5sZW5ndGggPiAxICYmIGkgPCB0aGlzLm91dHB1dHMubGVuZ3RoKSB7XG4gICAgICAgICAgICB3ZWlnaHRlZE1ldHJpYyA9IGxvc3NWYWx1ZXNbaV07XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGNvbnN0IG1ldHJpYyA9IHRoaXMubWV0cmljc1RlbnNvcnNbaV1bMF07XG4gICAgICAgICAgICBjb25zdCBvdXRwdXRJbmRleCA9IHRoaXMubWV0cmljc1RlbnNvcnNbaV1bMV07XG4gICAgICAgICAgICB3ZWlnaHRlZE1ldHJpYyA9XG4gICAgICAgICAgICAgICAgdGZjLm1lYW4obWV0cmljKHRhcmdldHNbb3V0cHV0SW5kZXhdLCBvdXRwdXRzW291dHB1dEluZGV4XSkpO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIHRmYy5rZWVwKHdlaWdodGVkTWV0cmljKTtcbiAgICAgICAgICAvLyBUT0RPKGNhaXMpOiBVc2UgYSBzY29wZSgpIGluc3RlYWQsIHRvIGF2b2lkIG93bmVyc2hpcC5cbiAgICAgICAgICBtZXRyaWNzVmFsdWVzLnB1c2god2VpZ2h0ZWRNZXRyaWMpO1xuICAgICAgICB9XG5cbiAgICAgICAgdG90YWxMb3NzID0gdGZjLm1lYW4odG90YWxMb3NzKTtcblxuICAgICAgICAvLyBBZGQgcmVndWxhcml6ZXIgcGVuYWx0aWVzLlxuICAgICAgICB0aGlzLmNhbGN1bGF0ZUxvc3NlcygpLmZvckVhY2gocmVndWxhcml6ZXJMb3NzID0+IHtcbiAgICAgICAgICB0b3RhbExvc3MgPSB0ZmMuYWRkKHRvdGFsTG9zcywgcmVndWxhcml6ZXJMb3NzKTtcbiAgICAgICAgfSk7XG5cbiAgICAgICAgcmV0dXJuIHRvdGFsTG9zcyBhcyBTY2FsYXI7XG4gICAgICB9O1xuXG4gICAgICBjb25zdCB2YXJpYWJsZXMgPSB0aGlzLmNvbGxlY3RlZFRyYWluYWJsZVdlaWdodHMubWFwKFxuICAgICAgICAgIHBhcmFtID0+IHBhcmFtLnJlYWQoKSBhcyB0ZmMuVmFyaWFibGUpO1xuICAgICAgY29uc3QgcmV0dXJuQ29zdCA9IHRydWU7XG4gICAgICBjb25zdCB0b3RhbExvc3NWYWx1ZSA9XG4gICAgICAgICAgdGhpcy5vcHRpbWl6ZXJfLm1pbmltaXplKHRvdGFsTG9zc0Z1bmN0aW9uLCByZXR1cm5Db3N0LCB2YXJpYWJsZXMpO1xuXG4gICAgICByZXR1cm4gW3RvdGFsTG9zc1ZhbHVlXS5jb25jYXQobWV0cmljc1ZhbHVlcyk7XG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDcmVhdGUgYSBmdW5jdGlvbiB3aGljaCwgd2hlbiBpbnZva2VkIHdpdGggYW4gYXJyYXkgb2YgYHRmLlRlbnNvcmBzIGFzIGFcbiAgICogYmF0Y2ggb2YgaW5wdXRzLCByZXR1cm5zIHRoZSBwcmVzcGVjaWZpZWQgbG9zcyBhbmQgbWV0cmljcyBvZiB0aGUgbW9kZWxcbiAgICogdW5kZXIgdGhlIGJhdGNoIG9mIGlucHV0IGRhdGEuXG4gICAqL1xuICBwcml2YXRlIG1ha2VUZXN0RnVuY3Rpb24oKSB7XG4gICAgdGhpcy50ZXN0RnVuY3Rpb24gPSAoZGF0YTogVGVuc29yW10pID0+IHtcbiAgICAgIHJldHVybiB0ZmMudGlkeSgoKSA9PiB7XG4gICAgICAgIGNvbnN0IHZhbE91dHB1dHM6IFNjYWxhcltdID0gW107XG4gICAgICAgIGxldCB0b3RhbExvc3M6IFNjYWxhcjtcbiAgICAgICAgY29uc3QgaW5wdXRzID0gZGF0YS5zbGljZSgwLCB0aGlzLmlucHV0cy5sZW5ndGgpO1xuICAgICAgICBjb25zdCB0YXJnZXRzID0gZGF0YS5zbGljZShcbiAgICAgICAgICAgIHRoaXMuaW5wdXRzLmxlbmd0aCwgdGhpcy5pbnB1dHMubGVuZ3RoICsgdGhpcy5vdXRwdXRzLmxlbmd0aCk7XG4gICAgICAgIGNvbnN0IGZlZWRzID0gW107XG4gICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgdGhpcy5pbnB1dHMubGVuZ3RoOyArK2kpIHtcbiAgICAgICAgICBmZWVkcy5wdXNoKHtrZXk6IHRoaXMuaW5wdXRzW2ldLCB2YWx1ZTogaW5wdXRzW2ldfSk7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgZmVlZERpY3QgPSBuZXcgRmVlZERpY3QoZmVlZHMpO1xuICAgICAgICBjb25zdCBvdXRwdXRzID0gZXhlY3V0ZSh0aGlzLm91dHB1dHMsIGZlZWREaWN0KSBhcyBUZW5zb3JbXTtcbiAgICAgICAgLy8gQ29tcHV0ZSB0b3RhbCBsb3NzLlxuICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IHRoaXMubG9zc0Z1bmN0aW9ucy5sZW5ndGg7ICsraSkge1xuICAgICAgICAgIGNvbnN0IGxvc3NGdW5jdGlvbiA9IHRoaXMubG9zc0Z1bmN0aW9uc1tpXTtcbiAgICAgICAgICAvLyBUT0RPKGNhaXMpOiBBZGQgc2FtcGxlIHdlaWdodGluZyBhbmQgcmVwbGFjZSB0aGUgc2ltcGxlXG4gICAgICAgICAgLy8gYXZlcmFnaW5nLlxuICAgICAgICAgIGNvbnN0IGxvc3M6IFNjYWxhciA9IHRmYy5tZWFuKGxvc3NGdW5jdGlvbih0YXJnZXRzW2ldLCBvdXRwdXRzW2ldKSk7XG4gICAgICAgICAgaWYgKGkgPT09IDApIHtcbiAgICAgICAgICAgIHRvdGFsTG9zcyA9IGxvc3M7XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHRvdGFsTG9zcyA9IHRmYy5hZGQodG90YWxMb3NzLCBsb3NzKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgdmFsT3V0cHV0cy5wdXNoKHRvdGFsTG9zcyk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gQ29tcHV0ZSB0aGUgbWV0cmljcy5cbiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCB0aGlzLm1ldHJpY3NUZW5zb3JzLmxlbmd0aDsgKytpKSB7XG4gICAgICAgICAgY29uc3QgbWV0cmljID0gdGhpcy5tZXRyaWNzVGVuc29yc1tpXVswXTtcbiAgICAgICAgICBjb25zdCBvdXRwdXRJbmRleCA9IHRoaXMubWV0cmljc1RlbnNvcnNbaV1bMV07XG4gICAgICAgICAgLy8gVE9ETyhjYWlzKTogUmVwbGFjZSBLLm1lYW4oKSB3aXRoIGEgcHJvcGVyIHdlaWdodGluZyBmdW5jdGlvbi5cbiAgICAgICAgICBjb25zdCBtZWFuTWV0cmljID1cbiAgICAgICAgICAgICAgdGZjLm1lYW4obWV0cmljKHRhcmdldHNbb3V0cHV0SW5kZXhdLCBvdXRwdXRzW291dHB1dEluZGV4XSkpO1xuICAgICAgICAgIHZhbE91dHB1dHMucHVzaChtZWFuTWV0cmljIGFzIFNjYWxhcik7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHZhbE91dHB1dHM7XG4gICAgICB9KTtcbiAgICB9O1xuICB9XG5cbiAgLyoqXG4gICAqIFRyYWlucyB0aGUgbW9kZWwgZm9yIGEgZml4ZWQgbnVtYmVyIG9mIGVwb2NocyAoaXRlcmF0aW9ucyBvbiBhXG4gICAqIGRhdGFzZXQpLlxuICAgKlxuICAgKiBgYGBqc1xuICAgKiBjb25zdCBtb2RlbCA9IHRmLnNlcXVlbnRpYWwoe1xuICAgKiAgICAgbGF5ZXJzOiBbdGYubGF5ZXJzLmRlbnNlKHt1bml0czogMSwgaW5wdXRTaGFwZTogWzEwXX0pXVxuICAgKiB9KTtcbiAgICogbW9kZWwuY29tcGlsZSh7b3B0aW1pemVyOiAnc2dkJywgbG9zczogJ21lYW5TcXVhcmVkRXJyb3InfSk7XG4gICAqIGZvciAobGV0IGkgPSAxOyBpIDwgNSA7ICsraSkge1xuICAgKiAgIGNvbnN0IGggPSBhd2FpdCBtb2RlbC5maXQodGYub25lcyhbOCwgMTBdKSwgdGYub25lcyhbOCwgMV0pLCB7XG4gICAqICAgICAgIGJhdGNoU2l6ZTogNCxcbiAgICogICAgICAgZXBvY2hzOiAzXG4gICAqICAgfSk7XG4gICAqICAgY29uc29sZS5sb2coXCJMb3NzIGFmdGVyIEVwb2NoIFwiICsgaSArIFwiIDogXCIgKyBoLmhpc3RvcnkubG9zc1swXSk7XG4gICAqIH1cbiAgICogYGBgXG4gICAqXG4gICAqIEBwYXJhbSB4IGB0Zi5UZW5zb3JgIG9mIHRyYWluaW5nIGRhdGEsIG9yIGFuIGFycmF5IG9mIGB0Zi5UZW5zb3JgcyBpZiB0aGVcbiAgICogbW9kZWwgaGFzIG11bHRpcGxlIGlucHV0cy4gSWYgYWxsIGlucHV0cyBpbiB0aGUgbW9kZWwgYXJlIG5hbWVkLCB5b3VcbiAgICogY2FuIGFsc28gcGFzcyBhIGRpY3Rpb25hcnkgbWFwcGluZyBpbnB1dCBuYW1lcyB0byBgdGYuVGVuc29yYHMuXG4gICAqIEBwYXJhbSB5IGB0Zi5UZW5zb3JgIG9mIHRhcmdldCAobGFiZWwpIGRhdGEsIG9yIGFuIGFycmF5IG9mIGB0Zi5UZW5zb3JgcyBpZlxuICAgKiB0aGUgbW9kZWwgaGFzIG11bHRpcGxlIG91dHB1dHMuIElmIGFsbCBvdXRwdXRzIGluIHRoZSBtb2RlbCBhcmUgbmFtZWQsXG4gICAqIHlvdSBjYW4gYWxzbyBwYXNzIGEgZGljdGlvbmFyeSBtYXBwaW5nIG91dHB1dCBuYW1lcyB0byBgdGYuVGVuc29yYHMuXG4gICAqIEBwYXJhbSBhcmdzIEEgYE1vZGVsRml0QXJnc2AsIGNvbnRhaW5pbmcgb3B0aW9uYWwgZmllbGRzLlxuICAgKlxuICAgKiBAcmV0dXJuIEEgYEhpc3RvcnlgIGluc3RhbmNlLiBJdHMgYGhpc3RvcnlgIGF0dHJpYnV0ZSBjb250YWlucyBhbGxcbiAgICogICBpbmZvcm1hdGlvbiBjb2xsZWN0ZWQgZHVyaW5nIHRyYWluaW5nLlxuICAgKlxuICAgKiBAZXhjZXB0aW9uIFZhbHVlRXJyb3IgSW4gY2FzZSBvZiBtaXNtYXRjaCBiZXR3ZWVuIHRoZSBwcm92aWRlZCBpbnB1dFxuICAgKiBkYXRhIGFuZCB3aGF0IHRoZSBtb2RlbCBleHBlY3RzLlxuICAgKlxuICAgKiBAZG9jIHtoZWFkaW5nOiAnTW9kZWxzJywgc3ViaGVhZGluZzogJ0NsYXNzZXMnfVxuICAgKi9cbiAgYXN5bmMgZml0KFxuICAgICAgeDogVGVuc29yfFRlbnNvcltdfHtbaW5wdXROYW1lOiBzdHJpbmddOiBUZW5zb3J9LFxuICAgICAgeTogVGVuc29yfFRlbnNvcltdfHtbaW5wdXROYW1lOiBzdHJpbmddOiBUZW5zb3J9LFxuICAgICAgYXJnczogTW9kZWxGaXRBcmdzID0ge30pOiBQcm9taXNlPEhpc3Rvcnk+IHtcbiAgICByZXR1cm4gZml0VGVuc29ycyh0aGlzLCB4LCB5LCBhcmdzKTtcbiAgfVxuXG4gIC8vIFRPRE8oY2Fpcyk6IEFkZCBjb2RlIHNuaXBwZXQgYmVsb3cgd2hlbiBpdCdzIHBvc3NpYmxlIHRvIGluc3RhbnRpYXRlXG4gIC8vICAgYWN0dWFsIGRhdGFzZXQgb2JqZWN0cy5cbiAgLyoqXG4gICAqIFRyYWlucyB0aGUgbW9kZWwgdXNpbmcgYSBkYXRhc2V0IG9iamVjdC5cbiAgICpcbiAgICogQHBhcmFtIGRhdGFzZXQgQSBkYXRhc2V0IG9iamVjdC4gSXRzIGBpdGVyYXRvcigpYCBtZXRob2QgaXMgZXhwZWN0ZWRcbiAgICogICB0byBnZW5lcmF0ZSBhIGRhdGFzZXQgaXRlcmF0b3Igb2JqZWN0LCB0aGUgYG5leHQoKWAgbWV0aG9kIG9mIHdoaWNoXG4gICAqICAgaXMgZXhwZWN0ZWQgdG8gcHJvZHVjZSBkYXRhIGJhdGNoZXMgZm9yIHRyYWluaW5nLiBUaGUgcmV0dXJuIHZhbHVlXG4gICAqICAgb2YgdGhlIGBuZXh0KClgIGNhbGwgb3VnaHQgdG8gY29udGFpbiBhIGJvb2xlYW4gYGRvbmVgIGZpZWxkIGFuZCBhXG4gICAqICAgYHZhbHVlYCBmaWVsZC4gVGhlIGB2YWx1ZWAgZmllbGQgaXMgZXhwZWN0ZWQgdG8gYmUgYW4gYXJyYXkgb2YgdHdvXG4gICAqICAgYHRmLlRlbnNvcmBzIG9yIGFuIGFycmF5IG9mIHR3byBuZXN0ZWQgYHRmLlRlbnNvcmAgc3RydWN0dXJlcy4gVGhlIGZvcm1lclxuICAgKiAgIGNhc2UgaXMgZm9yIG1vZGVscyB3aXRoIGV4YWN0bHkgb25lIGlucHV0IGFuZCBvbmUgb3V0cHV0IChlLmcuLlxuICAgKiAgIGEgc2VxdWVudGlhbCBtb2RlbCkuIFRoZSBsYXR0ZXIgY2FzZSBpcyBmb3IgbW9kZWxzIHdpdGggbXVsdGlwbGVcbiAgICogICBpbnB1dHMgYW5kL29yIG11bHRpcGxlIG91dHB1dHMuXG4gICAqICAgT2YgdGhlIHR3byBpdGVtcyBpbiB0aGUgYXJyYXksIHRoZSBmaXJzdCBpcyB0aGUgaW5wdXQgZmVhdHVyZShzKSBhbmRcbiAgICogICB0aGUgc2Vjb25kIGlzIHRoZSBvdXRwdXQgdGFyZ2V0KHMpLlxuICAgKiBAcGFyYW0gYXJncyBBIGBNb2RlbEZpdERhdGFzZXRBcmdzYCwgY29udGFpbmluZyBvcHRpb25hbCBmaWVsZHMuXG4gICAqXG4gICAqIEByZXR1cm4gQSBgSGlzdG9yeWAgaW5zdGFuY2UuIEl0cyBgaGlzdG9yeWAgYXR0cmlidXRlIGNvbnRhaW5zIGFsbFxuICAgKiAgIGluZm9ybWF0aW9uIGNvbGxlY3RlZCBkdXJpbmcgdHJhaW5pbmcuXG4gICAqXG4gICAqIEBkb2Mge2hlYWRpbmc6ICdNb2RlbHMnLCBzdWJoZWFkaW5nOiAnQ2xhc3Nlcyd9XG4gICAqL1xuICBhc3luYyBmaXREYXRhc2V0PFQ+KGRhdGFzZXQ6IERhdGFzZXQ8VD4sIGFyZ3M6IE1vZGVsRml0RGF0YXNldEFyZ3M8VD4pOlxuICAgICAgUHJvbWlzZTxIaXN0b3J5PiB7XG4gICAgcmV0dXJuIGZpdERhdGFzZXQodGhpcywgZGF0YXNldCwgYXJncyk7XG4gIH1cblxuICAvKipcbiAgICogUnVucyBhIHNpbmdsZSBncmFkaWVudCB1cGRhdGUgb24gYSBzaW5nbGUgYmF0Y2ggb2YgZGF0YS5cbiAgICpcbiAgICogVGhpcyBtZXRob2QgZGlmZmVycyBmcm9tIGBmaXQoKWAgYW5kIGBmaXREYXRhc2V0KClgIGluIHRoZSBmb2xsb3dpbmdcbiAgICogcmVnYXJkczpcbiAgICogICAtIEl0IG9wZXJhdGVzIG9uIGV4YWN0bHkgb25lIGJhdGNoIG9mIGRhdGEuXG4gICAqICAgLSBJdCByZXR1cm5zIG9ubHkgdGhlIGxvc3MgYW5kIG1hdHJpYyB2YWx1ZXMsIGluc3RlYWQgb2ZcbiAgICogICAgIHJldHVybmluZyB0aGUgYmF0Y2gtYnktYmF0Y2ggbG9zcyBhbmQgbWV0cmljIHZhbHVlcy5cbiAgICogICAtIEl0IGRvZXNuJ3Qgc3VwcG9ydCBmaW5lLWdyYWluZWQgb3B0aW9ucyBzdWNoIGFzIHZlcmJvc2l0eSBhbmRcbiAgICogICAgIGNhbGxiYWNrcy5cbiAgICpcbiAgICogQHBhcmFtIHggSW5wdXQgZGF0YS4gSXQgY291bGQgYmUgb25lIG9mIHRoZSBmb2xsb3dpbmc6XG4gICAqICAgLSBBIGB0Zi5UZW5zb3JgLCBvciBhbiBBcnJheSBvZiBgdGYuVGVuc29yYHMgKGluIGNhc2UgdGhlIG1vZGVsIGhhc1xuICAgKiAgICAgbXVsdGlwbGUgaW5wdXRzKS5cbiAgICogICAtIEFuIE9iamVjdCBtYXBwaW5nIGlucHV0IG5hbWVzIHRvIGNvcnJlc3BvbmRpbmcgYHRmLlRlbnNvcmAgKGlmIHRoZVxuICAgKiAgICAgbW9kZWwgaGFzIG5hbWVkIGlucHV0cykuXG4gICAqIEBwYXJhbSB5IFRhcmdldCBkYXJ0YS4gSXQgY291bGQgYmUgZWl0aGVyIGEgYHRmLlRlbnNvcmAgYSBtdWx0aXBsZVxuICAgKiAgIGB0Zi5UZW5zb3Jgcy4gSXQgc2hvdWxkIGJlIGNvbnNpc3RlbnQgd2l0aCBgeGAuXG4gICAqIEByZXR1cm5zIFRyYWluaW5nIGxvc3Mgb3IgbG9zc2VzIChpbiBjYXNlIHRoZSBtb2RlbCBoYXNcbiAgICogICBtdWx0aXBsZSBvdXRwdXRzKSwgYWxvbmcgd2l0aCBtZXRyaWNzIChpZiBhbnkpLCBhcyBudW1iZXJzLlxuICAgKlxuICAgKiBAZG9jIHtoZWFkaW5nOiAnTW9kZWxzJywgc3ViaGVhZGluZzogJ0NsYXNzZXMnfVxuICAgKi9cbiAgYXN5bmMgdHJhaW5PbkJhdGNoKFxuICAgICAgeDogVGVuc29yfFRlbnNvcltdfHtbaW5wdXROYW1lOiBzdHJpbmddOiBUZW5zb3J9LFxuICAgICAgeTogVGVuc29yfFRlbnNvcltdfFxuICAgICAge1tpbnB1dE5hbWU6IHN0cmluZ106IFRlbnNvcn0pOiBQcm9taXNlPG51bWJlcnxudW1iZXJbXT4ge1xuICAgIC8vIFRPRE8oY2Fpcyk6IFN1cHBvcnQgc2FtcGxlV2VpZ2h0IGFuZCBjbGFzc1dlaWdodC5cbiAgICAvLyBUT0RPKGNhaXMpOiBTdXBwb3J0IERhdGFzZXQgb2JqZWN0cy5cbiAgICBjb25zdCBzdGFuZGFyZGl6ZU91dCA9IGF3YWl0IHRoaXMuc3RhbmRhcmRpemVVc2VyRGF0YSh4LCB5KTtcbiAgICBjb25zdCBpbnB1dHMgPSBzdGFuZGFyZGl6ZU91dFswXTtcbiAgICBjb25zdCB0YXJnZXRzID0gc3RhbmRhcmRpemVPdXRbMV07XG4gICAgY29uc3QgdHJhaW5GdW5jdGlvbiA9IHRoaXMubWFrZVRyYWluRnVuY3Rpb24oKTtcbiAgICBjb25zdCBsb3NzZXMgPSB0cmFpbkZ1bmN0aW9uKGlucHV0cy5jb25jYXQodGFyZ2V0cykpO1xuICAgIGNvbnN0IGxvc3NWYWx1ZXM6IG51bWJlcltdID0gW107XG4gICAgZm9yIChjb25zdCBsb3NzIG9mIGxvc3Nlcykge1xuICAgICAgY29uc3QgdiA9IGF3YWl0IGxvc3MuZGF0YSgpO1xuICAgICAgbG9zc1ZhbHVlcy5wdXNoKHZbMF0pO1xuICAgIH1cbiAgICB0ZmMuZGlzcG9zZShsb3NzZXMpO1xuICAgIGRpc3Bvc2VOZXdUZW5zb3JzKHN0YW5kYXJkaXplT3V0WzBdLCB4KTtcbiAgICBkaXNwb3NlTmV3VGVuc29ycyhzdGFuZGFyZGl6ZU91dFsxXSwgeSk7XG4gICAgcmV0dXJuIHNpbmdsZXRvbk9yQXJyYXkobG9zc1ZhbHVlcyk7XG4gIH1cblxuICAvKipcbiAgICogRXh0cmFjdCB3ZWlnaHQgdmFsdWVzIG9mIHRoZSBtb2RlbC5cbiAgICpcbiAgICogQHBhcmFtIGNvbmZpZzogQW4gaW5zdGFuY2Ugb2YgYGlvLlNhdmVDb25maWdgLCB3aGljaCBzcGVjaWZpZXNcbiAgICogbW9kZWwtc2F2aW5nIG9wdGlvbnMgc3VjaCBhcyB3aGV0aGVyIG9ubHkgdHJhaW5hYmxlIHdlaWdodHMgYXJlIHRvIGJlXG4gICAqIHNhdmVkLlxuICAgKiBAcmV0dXJucyBBIGBOYW1lZFRlbnNvck1hcGAgbWFwcGluZyBvcmlnaW5hbCB3ZWlnaHQgbmFtZXMgKGkuZS4sXG4gICAqICAgbm9uLXVuaXF1ZWlmaWVkIHdlaWdodCBuYW1lcykgdG8gdGhlaXIgdmFsdWVzLlxuICAgKi9cbiAgcHJvdGVjdGVkIGdldE5hbWVkV2VpZ2h0cyhjb25maWc/OiBpby5TYXZlQ29uZmlnKTogTmFtZWRUZW5zb3JbXSB7XG4gICAgY29uc3QgbmFtZWRXZWlnaHRzOiBOYW1lZFRlbnNvcltdID0gW107XG5cbiAgICBjb25zdCB0cmFpbmFibGVPbmx5ID0gY29uZmlnICE9IG51bGwgJiYgY29uZmlnLnRyYWluYWJsZU9ubHk7XG4gICAgY29uc3Qgd2VpZ2h0cyA9IHRyYWluYWJsZU9ubHkgPyB0aGlzLnRyYWluYWJsZVdlaWdodHMgOiB0aGlzLndlaWdodHM7XG4gICAgY29uc3Qgd2VpZ2h0VmFsdWVzID0gdGhpcy5nZXRXZWlnaHRzKHRyYWluYWJsZU9ubHkpO1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgd2VpZ2h0cy5sZW5ndGg7ICsraSkge1xuICAgICAgaWYgKHRyYWluYWJsZU9ubHkgJiYgIXdlaWdodHNbaV0udHJhaW5hYmxlKSB7XG4gICAgICAgIC8vIE9wdGlvbmFsbHkgc2tpcCBub24tdHJhaW5hYmxlIHdlaWdodHMuXG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuICAgICAgbmFtZWRXZWlnaHRzLnB1c2goXG4gICAgICAgICAge25hbWU6IHdlaWdodHNbaV0ub3JpZ2luYWxOYW1lLCB0ZW5zb3I6IHdlaWdodFZhbHVlc1tpXX0pO1xuICAgIH1cbiAgICByZXR1cm4gbmFtZWRXZWlnaHRzO1xuICB9XG5cbiAgLyoqXG4gICAqIFNldHRlciB1c2VkIGZvciBmb3JjZSBzdG9wcGluZyBvZiBMYXllcnNNb2RlbC5maXQoKSAoaS5lLiwgdHJhaW5pbmcpLlxuICAgKlxuICAgKiBFeGFtcGxlOlxuICAgKlxuICAgKiBgYGBqc1xuICAgKiBjb25zdCBpbnB1dCA9IHRmLmlucHV0KHtzaGFwZTogWzEwXX0pO1xuICAgKiBjb25zdCBvdXRwdXQgPSB0Zi5sYXllcnMuZGVuc2Uoe3VuaXRzOiAxfSkuYXBwbHkoaW5wdXQpO1xuICAgKiBjb25zdCBtb2RlbCA9IHRmLm1vZGVsKHtpbnB1dHM6IFtpbnB1dF0sIG91dHB1dHM6IFtvdXRwdXRdfSk7XG4gICAqIG1vZGVsLmNvbXBpbGUoe2xvc3M6ICdtZWFuU3F1YXJlZEVycm9yJywgb3B0aW1pemVyOiAnc2dkJ30pO1xuICAgKiBjb25zdCB4cyA9IHRmLm9uZXMoWzgsIDEwXSk7XG4gICAqIGNvbnN0IHlzID0gdGYuemVyb3MoWzgsIDFdKTtcbiAgICpcbiAgICogY29uc3QgaGlzdG9yeSA9IGF3YWl0IG1vZGVsLmZpdCh4cywgeXMsIHtcbiAgICogICBlcG9jaHM6IDEwLFxuICAgKiAgIGNhbGxiYWNrczoge1xuICAgKiAgICAgb25FcG9jaEVuZDogYXN5bmMgKGVwb2NoLCBsb2dzKSA9PiB7XG4gICAqICAgICAgIGlmIChlcG9jaCA9PT0gMikge1xuICAgKiAgICAgICAgIG1vZGVsLnN0b3BUcmFpbmluZyA9IHRydWU7XG4gICAqICAgICAgIH1cbiAgICogICAgIH1cbiAgICogICB9XG4gICAqIH0pO1xuICAgKlxuICAgKiAvLyBUaGVyZSBzaG91bGQgYmUgb25seSAzIHZhbHVlcyBpbiB0aGUgbG9zcyBhcnJheSwgaW5zdGVhZCBvZiAxMFxuICAgKiB2YWx1ZXMsXG4gICAqIC8vIGR1ZSB0byB0aGUgc3RvcHBpbmcgYWZ0ZXIgMyBlcG9jaHMuXG4gICAqIGNvbnNvbGUubG9nKGhpc3RvcnkuaGlzdG9yeS5sb3NzKTtcbiAgICogYGBgXG4gICAqL1xuICBzZXQgc3RvcFRyYWluaW5nKHN0b3A6IGJvb2xlYW4pIHtcbiAgICB0aGlzLnN0b3BUcmFpbmluZ18gPSBzdG9wO1xuICB9XG5cbiAgZ2V0IHN0b3BUcmFpbmluZygpOiBib29sZWFuIHtcbiAgICByZXR1cm4gdGhpcy5zdG9wVHJhaW5pbmdfO1xuICB9XG5cbiAgZ2V0IG9wdGltaXplcigpOiBPcHRpbWl6ZXIge1xuICAgIHJldHVybiB0aGlzLm9wdGltaXplcl87XG4gIH1cblxuICBzZXQgb3B0aW1pemVyKG9wdGltaXplcjogT3B0aW1pemVyKSB7XG4gICAgaWYgKHRoaXMub3B0aW1pemVyXyAhPT0gb3B0aW1pemVyKSB7XG4gICAgICB0aGlzLm9wdGltaXplcl8gPSBvcHRpbWl6ZXI7XG4gICAgICB0aGlzLmlzT3B0aW1pemVyT3duZWQgPSBmYWxzZTtcbiAgICB9XG4gIH1cblxuICBkaXNwb3NlKCk6IERpc3Bvc2VSZXN1bHQge1xuICAgIGNvbnN0IHJlc3VsdCA9IHN1cGVyLmRpc3Bvc2UoKTtcbiAgICBpZiAocmVzdWx0LnJlZkNvdW50QWZ0ZXJEaXNwb3NlID09PSAwICYmIHRoaXMub3B0aW1pemVyICE9IG51bGwgJiZcbiAgICAgICAgdGhpcy5pc09wdGltaXplck93bmVkKSB7XG4gICAgICBjb25zdCBudW1UZW5zb3JzQmVmb3JlT3B0bWl6ZXJEaXNwb3NhbCA9IHRmYy5tZW1vcnkoKS5udW1UZW5zb3JzO1xuICAgICAgdGhpcy5vcHRpbWl6ZXJfLmRpc3Bvc2UoKTtcbiAgICAgIHJlc3VsdC5udW1EaXNwb3NlZFZhcmlhYmxlcyArPVxuICAgICAgICAgIG51bVRlbnNvcnNCZWZvcmVPcHRtaXplckRpc3Bvc2FsIC0gdGZjLm1lbW9yeSgpLm51bVRlbnNvcnM7XG4gICAgfVxuICAgIHJldHVybiByZXN1bHQ7XG4gIH1cblxuICBwcml2YXRlIGdldExvc3NJZGVudGlmaWVycygpOiBMb3NzSWRlbnRpZmllcnxMb3NzSWRlbnRpZmllcltdfFxuICAgICAge1tvdXRwdXROYW1lOiBzdHJpbmddOiBMb3NzSWRlbnRpZmllcn0ge1xuICAgIGxldCBsb3NzTmFtZXM6IExvc3NJZGVudGlmaWVyfExvc3NJZGVudGlmaWVyW118XG4gICAgICAgIHtbb3V0cHV0TmFtZTogc3RyaW5nXTogTG9zc0lkZW50aWZpZXJ9O1xuICAgIGlmICh0eXBlb2YgdGhpcy5sb3NzID09PSAnc3RyaW5nJykge1xuICAgICAgbG9zc05hbWVzID0gdG9TbmFrZUNhc2UodGhpcy5sb3NzKSBhcyBMb3NzSWRlbnRpZmllcjtcbiAgICB9IGVsc2UgaWYgKEFycmF5LmlzQXJyYXkodGhpcy5sb3NzKSkge1xuICAgICAgZm9yIChjb25zdCBsb3NzIG9mIHRoaXMubG9zcykge1xuICAgICAgICBpZiAodHlwZW9mIGxvc3MgIT09ICdzdHJpbmcnKSB7XG4gICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdTZXJpYWxpemF0aW9uIG9mIG5vbi1zdHJpbmcgbG9zcyBpcyBub3Qgc3VwcG9ydGVkLicpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBsb3NzTmFtZXMgPSAodGhpcy5sb3NzIGFzIHN0cmluZ1tdKS5tYXAobmFtZSA9PiB0b1NuYWtlQ2FzZShuYW1lKSkgYXNcbiAgICAgICAgICBMb3NzSWRlbnRpZmllcltdO1xuICAgIH0gZWxzZSB7XG4gICAgICBjb25zdCBvdXRwdXROYW1lcyA9IE9iamVjdC5rZXlzKHRoaXMubG9zcyk7XG4gICAgICBsb3NzTmFtZXMgPSB7fSBhcyB7W291dHB1dE5hbWU6IHN0cmluZ106IExvc3NJZGVudGlmaWVyfTtcbiAgICAgIGNvbnN0IGxvc3NlcyA9XG4gICAgICAgICAgdGhpcy5sb3NzIGFzIHtbb3V0cHV0TmFtZTogc3RyaW5nXTogTG9zc09yTWV0cmljRm4gfCBzdHJpbmd9O1xuICAgICAgZm9yIChjb25zdCBvdXRwdXROYW1lIG9mIG91dHB1dE5hbWVzKSB7XG4gICAgICAgIGlmICh0eXBlb2YgbG9zc2VzW291dHB1dE5hbWVdID09PSAnc3RyaW5nJykge1xuICAgICAgICAgIGxvc3NOYW1lc1tvdXRwdXROYW1lXSA9XG4gICAgICAgICAgICAgIHRvU25ha2VDYXNlKGxvc3Nlc1tvdXRwdXROYW1lXSBhcyBzdHJpbmcpIGFzIExvc3NJZGVudGlmaWVyO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHRocm93IG5ldyBFcnJvcignU2VyaWFsaXphdGlvbiBvZiBub24tc3RyaW5nIGxvc3MgaXMgbm90IHN1cHBvcnRlZC4nKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gbG9zc05hbWVzO1xuICB9XG5cbiAgcHJpdmF0ZSBnZXRNZXRyaWNJZGVudGlmaWVycygpOiBNZXRyaWNzSWRlbnRpZmllcltdfFxuICAgICAge1trZXk6IHN0cmluZ106IE1ldHJpY3NJZGVudGlmaWVyfSB7XG4gICAgaWYgKHR5cGVvZiB0aGlzLm1ldHJpY3MgPT09ICdzdHJpbmcnIHx8XG4gICAgICAgIHR5cGVvZiB0aGlzLm1ldHJpY3MgPT09ICdmdW5jdGlvbicpIHtcbiAgICAgIHJldHVybiBbdG9TbmFrZUNhc2UoTWV0cmljcy5nZXRMb3NzT3JNZXRyaWNOYW1lKHRoaXMubWV0cmljcykpXTtcbiAgICB9IGVsc2UgaWYgKEFycmF5LmlzQXJyYXkodGhpcy5tZXRyaWNzKSkge1xuICAgICAgcmV0dXJuIHRoaXMubWV0cmljcy5tYXAoXG4gICAgICAgICAgbWV0cmljID0+IHRvU25ha2VDYXNlKE1ldHJpY3MuZ2V0TG9zc09yTWV0cmljTmFtZShtZXRyaWMpKSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIGNvbnN0IG1ldHJpY3NJZGVudGlmaWVyczoge1trZXk6IHN0cmluZ106IE1ldHJpY3NJZGVudGlmaWVyfSA9IHt9O1xuICAgICAgZm9yIChjb25zdCBrZXkgaW4gdGhpcy5tZXRyaWNzKSB7XG4gICAgICAgIG1ldHJpY3NJZGVudGlmaWVyc1trZXldID1cbiAgICAgICAgICAgIHRvU25ha2VDYXNlKE1ldHJpY3MuZ2V0TG9zc09yTWV0cmljTmFtZSh0aGlzLm1ldHJpY3Nba2V5XSkpO1xuICAgICAgfVxuICAgICAgcmV0dXJuIG1ldHJpY3NJZGVudGlmaWVycztcbiAgICB9XG4gIH1cblxuICBwcm90ZWN0ZWQgZ2V0VHJhaW5pbmdDb25maWcoKTogVHJhaW5pbmdDb25maWcge1xuICAgIHJldHVybiB7XG4gICAgICBsb3NzOiB0aGlzLmdldExvc3NJZGVudGlmaWVycygpLFxuICAgICAgbWV0cmljczogdGhpcy5nZXRNZXRyaWNJZGVudGlmaWVycygpLFxuICAgICAgb3B0aW1pemVyX2NvbmZpZzoge1xuICAgICAgICBjbGFzc19uYW1lOiB0aGlzLm9wdGltaXplci5nZXRDbGFzc05hbWUoKSxcbiAgICAgICAgY29uZmlnOiB0aGlzLm9wdGltaXplci5nZXRDb25maWcoKVxuICAgICAgfSBhcyBPcHRpbWl6ZXJTZXJpYWxpemF0aW9uXG4gICAgfTtcbiAgICAvLyBUT0RPKGNhaXMpOiBBZGQgd2VpZ2h0X21ldHJpY3Mgd2hlbiB0aGV5IGFyZSBzdXBwb3J0ZWQuXG4gICAgLy8gVE9ETyhjYWlzKTogQWRkIHNhbXBsZV93ZWlnaHRfbW9kZSB3aGVuIGl0J3Mgc3VwcG9ydGVkLlxuICAgIC8vIFRPRE8oY2Fpcyk6IEFkZCBsb3NzX3dlaWdodHMgd2hlbiBpdCdzIHN1cHBvcnRlZC5cbiAgfVxuXG4gIGxvYWRUcmFpbmluZ0NvbmZpZyh0cmFpbmluZ0NvbmZpZzogVHJhaW5pbmdDb25maWcpIHtcbiAgICBpZiAodHJhaW5pbmdDb25maWcud2VpZ2h0ZWRfbWV0cmljcyAhPSBudWxsKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ0xvYWRpbmcgd2VpZ2h0X21ldHJpY3MgaXMgbm90IHN1cHBvcnRlZCB5ZXQuJyk7XG4gICAgfVxuICAgIGlmICh0cmFpbmluZ0NvbmZpZy5sb3NzX3dlaWdodHMgIT0gbnVsbCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdMb2FkaW5nIGxvc3Nfd2VpZ2h0cyBpcyBub3Qgc3VwcG9ydGVkIHlldC4nKTtcbiAgICB9XG4gICAgaWYgKHRyYWluaW5nQ29uZmlnLnNhbXBsZV93ZWlnaHRfbW9kZSAhPSBudWxsKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ0xvYWRpbmcgc2FtcGxlX3dlaWdodF9tb2RlIGlzIG5vdCBzdXBwb3J0ZWQgeWV0LicpO1xuICAgIH1cblxuICAgIGNvbnN0IHRzQ29uZmlnID0gY29udmVydFB5dGhvbmljVG9Ucyh0cmFpbmluZ0NvbmZpZy5vcHRpbWl6ZXJfY29uZmlnKSBhc1xuICAgICAgICBzZXJpYWxpemF0aW9uLkNvbmZpZ0RpY3Q7XG4gICAgY29uc3Qgb3B0aW1pemVyID0gZGVzZXJpYWxpemUodHNDb25maWcpIGFzIE9wdGltaXplcjtcblxuICAgIGxldCBsb3NzO1xuICAgIGlmICh0eXBlb2YgdHJhaW5pbmdDb25maWcubG9zcyA9PT0gJ3N0cmluZycpIHtcbiAgICAgIGxvc3MgPSB0b0NhbWVsQ2FzZSh0cmFpbmluZ0NvbmZpZy5sb3NzKTtcbiAgICB9IGVsc2UgaWYgKEFycmF5LmlzQXJyYXkodHJhaW5pbmdDb25maWcubG9zcykpIHtcbiAgICAgIGxvc3MgPSB0cmFpbmluZ0NvbmZpZy5sb3NzLm1hcChsb3NzRW50cnkgPT4gdG9DYW1lbENhc2UobG9zc0VudHJ5KSk7XG4gICAgfSBlbHNlIGlmICh0cmFpbmluZ0NvbmZpZy5sb3NzICE9IG51bGwpIHtcbiAgICAgIGxvc3MgPSB7fSBhcyB7W291dHB1dE5hbWU6IHN0cmluZ106IExvc3NJZGVudGlmaWVyfTtcbiAgICAgIGZvciAoY29uc3Qga2V5IGluIHRyYWluaW5nQ29uZmlnLmxvc3MpIHtcbiAgICAgICAgbG9zc1trZXldID0gdG9DYW1lbENhc2UodHJhaW5pbmdDb25maWcubG9zc1trZXldKSBhcyBMb3NzSWRlbnRpZmllcjtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBsZXQgbWV0cmljcztcbiAgICBpZiAoQXJyYXkuaXNBcnJheSh0cmFpbmluZ0NvbmZpZy5tZXRyaWNzKSkge1xuICAgICAgbWV0cmljcyA9IHRyYWluaW5nQ29uZmlnLm1ldHJpY3MubWFwKG1ldHJpYyA9PiB0b0NhbWVsQ2FzZShtZXRyaWMpKTtcbiAgICB9IGVsc2UgaWYgKHRyYWluaW5nQ29uZmlnLm1ldHJpY3MgIT0gbnVsbCkge1xuICAgICAgbWV0cmljcyA9IHt9IGFzIHtbb3V0cHV0TmFtZTogc3RyaW5nXTogTWV0cmljc0lkZW50aWZpZXJ9O1xuICAgICAgZm9yIChjb25zdCBrZXkgaW4gdHJhaW5pbmdDb25maWcubWV0cmljcykge1xuICAgICAgICBtZXRyaWNzW2tleV0gPSB0b0NhbWVsQ2FzZSh0cmFpbmluZ0NvbmZpZy5tZXRyaWNzW2tleV0pO1xuICAgICAgfVxuICAgIH1cblxuICAgIHRoaXMuY29tcGlsZSh7bG9zcywgbWV0cmljcywgb3B0aW1pemVyfSk7XG4gIH1cblxuICAvKipcbiAgICogU2F2ZSB0aGUgY29uZmlndXJhdGlvbiBhbmQvb3Igd2VpZ2h0cyBvZiB0aGUgTGF5ZXJzTW9kZWwuXG4gICAqXG4gICAqIEFuIGBJT0hhbmRsZXJgIGlzIGFuIG9iamVjdCB0aGF0IGhhcyBhIGBzYXZlYCBtZXRob2Qgb2YgdGhlIHByb3BlclxuICAgKiBzaWduYXR1cmUgZGVmaW5lZC4gVGhlIGBzYXZlYCBtZXRob2QgbWFuYWdlcyB0aGUgc3RvcmluZyBvclxuICAgKiB0cmFuc21pc3Npb24gb2Ygc2VyaWFsaXplZCBkYXRhIChcImFydGlmYWN0c1wiKSB0aGF0IHJlcHJlc2VudCB0aGVcbiAgICogbW9kZWwncyB0b3BvbG9neSBhbmQgd2VpZ2h0cyBvbnRvIG9yIHZpYSBhIHNwZWNpZmljIG1lZGl1bSwgc3VjaCBhc1xuICAgKiBmaWxlIGRvd25sb2FkcywgbG9jYWwgc3RvcmFnZSwgSW5kZXhlZERCIGluIHRoZSB3ZWIgYnJvd3NlciBhbmQgSFRUUFxuICAgKiByZXF1ZXN0cyB0byBhIHNlcnZlci4gVGVuc29yRmxvdy5qcyBwcm92aWRlcyBgSU9IYW5kbGVyYFxuICAgKiBpbXBsZW1lbnRhdGlvbnMgZm9yIGEgbnVtYmVyIG9mIGZyZXF1ZW50bHkgdXNlZCBzYXZpbmcgbWVkaXVtcywgc3VjaCBhc1xuICAgKiBgdGYuaW8uYnJvd3NlckRvd25sb2Fkc2AgYW5kIGB0Zi5pby5icm93c2VyTG9jYWxTdG9yYWdlYC4gU2VlIGB0Zi5pb2BcbiAgICogZm9yIG1vcmUgZGV0YWlscy5cbiAgICpcbiAgICogVGhpcyBtZXRob2QgYWxzbyBhbGxvd3MgeW91IHRvIHJlZmVyIHRvIGNlcnRhaW4gdHlwZXMgb2YgYElPSGFuZGxlcmBzXG4gICAqIGFzIFVSTC1saWtlIHN0cmluZyBzaG9ydGN1dHMsIHN1Y2ggYXMgJ2xvY2Fsc3RvcmFnZTovLycgYW5kXG4gICAqICdpbmRleGVkZGI6Ly8nLlxuICAgKlxuICAgKiBFeGFtcGxlIDE6IFNhdmUgYG1vZGVsYCdzIHRvcG9sb2d5IGFuZCB3ZWlnaHRzIHRvIGJyb3dzZXIgW2xvY2FsXG4gICAqIHN0b3JhZ2VdKGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0FQSS9XaW5kb3cvbG9jYWxTdG9yYWdlKTtcbiAgICogdGhlbiBsb2FkIGl0IGJhY2suXG4gICAqXG4gICAqIGBgYGpzXG4gICAqIGNvbnN0IG1vZGVsID0gdGYuc2VxdWVudGlhbChcbiAgICogICAgIHtsYXllcnM6IFt0Zi5sYXllcnMuZGVuc2Uoe3VuaXRzOiAxLCBpbnB1dFNoYXBlOiBbM119KV19KTtcbiAgICogY29uc29sZS5sb2coJ1ByZWRpY3Rpb24gZnJvbSBvcmlnaW5hbCBtb2RlbDonKTtcbiAgICogbW9kZWwucHJlZGljdCh0Zi5vbmVzKFsxLCAzXSkpLnByaW50KCk7XG4gICAqXG4gICAqIGNvbnN0IHNhdmVSZXN1bHRzID0gYXdhaXQgbW9kZWwuc2F2ZSgnbG9jYWxzdG9yYWdlOi8vbXktbW9kZWwtMScpO1xuICAgKlxuICAgKiBjb25zdCBsb2FkZWRNb2RlbCA9IGF3YWl0IHRmLmxvYWRMYXllcnNNb2RlbCgnbG9jYWxzdG9yYWdlOi8vbXktbW9kZWwtMScpO1xuICAgKiBjb25zb2xlLmxvZygnUHJlZGljdGlvbiBmcm9tIGxvYWRlZCBtb2RlbDonKTtcbiAgICogbG9hZGVkTW9kZWwucHJlZGljdCh0Zi5vbmVzKFsxLCAzXSkpLnByaW50KCk7XG4gICAqIGBgYFxuICAgKlxuICAgKiBFeGFtcGxlIDIuIFNhdmluZyBgbW9kZWxgJ3MgdG9wb2xvZ3kgYW5kIHdlaWdodHMgdG8gYnJvd3NlclxuICAgKiBbSW5kZXhlZERCXShodHRwczovL2RldmVsb3Blci5tb3ppbGxhLm9yZy9lbi1VUy9kb2NzL1dlYi9BUEkvSW5kZXhlZERCX0FQSSk7XG4gICAqIHRoZW4gbG9hZCBpdCBiYWNrLlxuICAgKlxuICAgKiBgYGBqc1xuICAgKiBjb25zdCBtb2RlbCA9IHRmLnNlcXVlbnRpYWwoXG4gICAqICAgICB7bGF5ZXJzOiBbdGYubGF5ZXJzLmRlbnNlKHt1bml0czogMSwgaW5wdXRTaGFwZTogWzNdfSldfSk7XG4gICAqIGNvbnNvbGUubG9nKCdQcmVkaWN0aW9uIGZyb20gb3JpZ2luYWwgbW9kZWw6Jyk7XG4gICAqIG1vZGVsLnByZWRpY3QodGYub25lcyhbMSwgM10pKS5wcmludCgpO1xuICAgKlxuICAgKiBjb25zdCBzYXZlUmVzdWx0cyA9IGF3YWl0IG1vZGVsLnNhdmUoJ2luZGV4ZWRkYjovL215LW1vZGVsLTEnKTtcbiAgICpcbiAgICogY29uc3QgbG9hZGVkTW9kZWwgPSBhd2FpdCB0Zi5sb2FkTGF5ZXJzTW9kZWwoJ2luZGV4ZWRkYjovL215LW1vZGVsLTEnKTtcbiAgICogY29uc29sZS5sb2coJ1ByZWRpY3Rpb24gZnJvbSBsb2FkZWQgbW9kZWw6Jyk7XG4gICAqIGxvYWRlZE1vZGVsLnByZWRpY3QodGYub25lcyhbMSwgM10pKS5wcmludCgpO1xuICAgKiBgYGBcbiAgICpcbiAgICogRXhhbXBsZSAzLiBTYXZpbmcgYG1vZGVsYCdzIHRvcG9sb2d5IGFuZCB3ZWlnaHRzIGFzIHR3byBmaWxlc1xuICAgKiAoYG15LW1vZGVsLTEuanNvbmAgYW5kIGBteS1tb2RlbC0xLndlaWdodHMuYmluYCkgZG93bmxvYWRlZCBmcm9tXG4gICAqIGJyb3dzZXIuXG4gICAqXG4gICAqIGBgYGpzXG4gICAqIGNvbnN0IG1vZGVsID0gdGYuc2VxdWVudGlhbChcbiAgICogICAgIHtsYXllcnM6IFt0Zi5sYXllcnMuZGVuc2Uoe3VuaXRzOiAxLCBpbnB1dFNoYXBlOiBbM119KV19KTtcbiAgICogY29uc3Qgc2F2ZVJlc3VsdHMgPSBhd2FpdCBtb2RlbC5zYXZlKCdkb3dubG9hZHM6Ly9teS1tb2RlbC0xJyk7XG4gICAqIGBgYFxuICAgKlxuICAgKiBFeGFtcGxlIDQuIFNlbmQgIGBtb2RlbGAncyB0b3BvbG9neSBhbmQgd2VpZ2h0cyB0byBhbiBIVFRQIHNlcnZlci5cbiAgICogU2VlIHRoZSBkb2N1bWVudGF0aW9uIG9mIGB0Zi5pby5odHRwYCBmb3IgbW9yZSBkZXRhaWxzXG4gICAqIGluY2x1ZGluZyBzcGVjaWZ5aW5nIHJlcXVlc3QgcGFyYW1ldGVycyBhbmQgaW1wbGVtZW50YXRpb24gb2YgdGhlXG4gICAqIHNlcnZlci5cbiAgICpcbiAgICogYGBganNcbiAgICogY29uc3QgbW9kZWwgPSB0Zi5zZXF1ZW50aWFsKFxuICAgKiAgICAge2xheWVyczogW3RmLmxheWVycy5kZW5zZSh7dW5pdHM6IDEsIGlucHV0U2hhcGU6IFszXX0pXX0pO1xuICAgKiBjb25zdCBzYXZlUmVzdWx0cyA9IGF3YWl0IG1vZGVsLnNhdmUoJ2h0dHA6Ly9teS1zZXJ2ZXIvbW9kZWwvdXBsb2FkJyk7XG4gICAqIGBgYFxuICAgKlxuICAgKiBAcGFyYW0gaGFuZGxlck9yVVJMIEFuIGluc3RhbmNlIG9mIGBJT0hhbmRsZXJgIG9yIGEgVVJMLWxpa2UsXG4gICAqIHNjaGVtZS1iYXNlZCBzdHJpbmcgc2hvcnRjdXQgZm9yIGBJT0hhbmRsZXJgLlxuICAgKiBAcGFyYW0gY29uZmlnIE9wdGlvbnMgZm9yIHNhdmluZyB0aGUgbW9kZWwuXG4gICAqIEByZXR1cm5zIEEgYFByb21pc2VgIG9mIGBTYXZlUmVzdWx0YCwgd2hpY2ggc3VtbWFyaXplcyB0aGUgcmVzdWx0IG9mXG4gICAqIHRoZSBzYXZpbmcsIHN1Y2ggYXMgYnl0ZSBzaXplcyBvZiB0aGUgc2F2ZWQgYXJ0aWZhY3RzIGZvciB0aGUgbW9kZWwnc1xuICAgKiAgIHRvcG9sb2d5IGFuZCB3ZWlnaHQgdmFsdWVzLlxuICAgKlxuICAgKiBAZG9jIHtoZWFkaW5nOiAnTW9kZWxzJywgc3ViaGVhZGluZzogJ0NsYXNzZXMnLCBpZ25vcmVDSTogdHJ1ZX1cbiAgICovXG4gIGFzeW5jIHNhdmUoaGFuZGxlck9yVVJMOiBpby5JT0hhbmRsZXJ8c3RyaW5nLCBjb25maWc/OiBpby5TYXZlQ29uZmlnKTpcbiAgICAgIFByb21pc2U8aW8uU2F2ZVJlc3VsdD4ge1xuICAgIGlmICh0eXBlb2YgaGFuZGxlck9yVVJMID09PSAnc3RyaW5nJykge1xuICAgICAgY29uc3QgaGFuZGxlcnMgPSBpby5nZXRTYXZlSGFuZGxlcnMoaGFuZGxlck9yVVJMKTtcbiAgICAgIGlmIChoYW5kbGVycy5sZW5ndGggPT09IDApIHtcbiAgICAgICAgdGhyb3cgbmV3IFZhbHVlRXJyb3IoXG4gICAgICAgICAgICBgQ2Fubm90IGZpbmQgYW55IHNhdmUgaGFuZGxlcnMgZm9yIFVSTCAnJHtoYW5kbGVyT3JVUkx9J2ApO1xuICAgICAgfSBlbHNlIGlmIChoYW5kbGVycy5sZW5ndGggPiAxKSB7XG4gICAgICAgIHRocm93IG5ldyBWYWx1ZUVycm9yKFxuICAgICAgICAgICAgYEZvdW5kIG1vcmUgdGhhbiBvbmUgKCR7aGFuZGxlcnMubGVuZ3RofSkgc2F2ZSBoYW5kbGVycyBmb3IgYCArXG4gICAgICAgICAgICBgVVJMICcke2hhbmRsZXJPclVSTH0nYCk7XG4gICAgICB9XG4gICAgICBoYW5kbGVyT3JVUkwgPSBoYW5kbGVyc1swXTtcbiAgICB9XG4gICAgaWYgKGhhbmRsZXJPclVSTC5zYXZlID09IG51bGwpIHtcbiAgICAgIHRocm93IG5ldyBWYWx1ZUVycm9yKFxuICAgICAgICAgICdMYXllcnNNb2RlbC5zYXZlKCkgY2Fubm90IHByb2NlZWQgYmVjYXVzZSB0aGUgSU9IYW5kbGVyICcgK1xuICAgICAgICAgICdwcm92aWRlZCBkb2VzIG5vdCBoYXZlIHRoZSBgc2F2ZWAgYXR0cmlidXRlIGRlZmluZWQuJyk7XG4gICAgfVxuXG4gICAgY29uc3Qgd2VpZ2h0RGF0YUFuZFNwZWNzID1cbiAgICAgICAgYXdhaXQgaW8uZW5jb2RlV2VpZ2h0cyh0aGlzLmdldE5hbWVkV2VpZ2h0cyhjb25maWcpKTtcblxuICAgIGNvbnN0IHJldHVyblN0cmluZyA9IGZhbHNlO1xuICAgIGNvbnN0IHVudXNlZEFyZzoge30gPSBudWxsO1xuICAgIGNvbnN0IG1vZGVsQ29uZmlnID0gdGhpcy50b0pTT04odW51c2VkQXJnLCByZXR1cm5TdHJpbmcpO1xuICAgIGNvbnN0IG1vZGVsQXJ0aWZhY3RzOiBpby5Nb2RlbEFydGlmYWN0cyA9IHtcbiAgICAgIG1vZGVsVG9wb2xvZ3k6IG1vZGVsQ29uZmlnLFxuICAgICAgZm9ybWF0OiBMQVlFUlNfTU9ERUxfRk9STUFUX05BTUUsXG4gICAgICBnZW5lcmF0ZWRCeTogYFRlbnNvckZsb3cuanMgdGZqcy1sYXllcnMgdiR7dmVyc2lvbn1gLFxuICAgICAgY29udmVydGVkQnk6IG51bGwsXG4gICAgfTtcblxuICAgIGNvbnN0IGluY2x1ZGVPcHRpbWl6ZXIgPSBjb25maWcgPT0gbnVsbCA/IGZhbHNlIDogY29uZmlnLmluY2x1ZGVPcHRpbWl6ZXI7XG4gICAgaWYgKGluY2x1ZGVPcHRpbWl6ZXIgJiYgdGhpcy5vcHRpbWl6ZXIgIT0gbnVsbCkge1xuICAgICAgbW9kZWxBcnRpZmFjdHMudHJhaW5pbmdDb25maWcgPSB0aGlzLmdldFRyYWluaW5nQ29uZmlnKCk7XG4gICAgICBjb25zdCB3ZWlnaHRUeXBlID0gJ29wdGltaXplcic7XG4gICAgICBjb25zdCB7ZGF0YTogb3B0aW1pemVyV2VpZ2h0RGF0YSwgc3BlY3M6IG9wdGltaXplcldlaWdodFNwZWNzfSA9XG4gICAgICAgICAgYXdhaXQgaW8uZW5jb2RlV2VpZ2h0cyhhd2FpdCB0aGlzLm9wdGltaXplci5nZXRXZWlnaHRzKCksIHdlaWdodFR5cGUpO1xuICAgICAgd2VpZ2h0RGF0YUFuZFNwZWNzLnNwZWNzLnB1c2goLi4ub3B0aW1pemVyV2VpZ2h0U3BlY3MpO1xuICAgICAgd2VpZ2h0RGF0YUFuZFNwZWNzLmRhdGEgPSBpby5jb25jYXRlbmF0ZUFycmF5QnVmZmVycyhcbiAgICAgICAgICBbd2VpZ2h0RGF0YUFuZFNwZWNzLmRhdGEsIG9wdGltaXplcldlaWdodERhdGFdKTtcbiAgICB9XG5cbiAgICBpZiAodGhpcy51c2VyRGVmaW5lZE1ldGFkYXRhICE9IG51bGwpIHtcbiAgICAgIC8vIENoZWNrIHNlcmlhbGl6ZWQgc2l6ZSBvZiB1c2VyLWRlZmluZWQgbWV0YWRhdGEuXG4gICAgICBjb25zdCBjaGVja1NpemUgPSB0cnVlO1xuICAgICAgY2hlY2tVc2VyRGVmaW5lZE1ldGFkYXRhKHRoaXMudXNlckRlZmluZWRNZXRhZGF0YSwgdGhpcy5uYW1lLCBjaGVja1NpemUpO1xuICAgICAgbW9kZWxBcnRpZmFjdHMudXNlckRlZmluZWRNZXRhZGF0YSA9IHRoaXMudXNlckRlZmluZWRNZXRhZGF0YTtcbiAgICB9XG5cbiAgICBtb2RlbEFydGlmYWN0cy53ZWlnaHREYXRhID0gd2VpZ2h0RGF0YUFuZFNwZWNzLmRhdGE7XG4gICAgbW9kZWxBcnRpZmFjdHMud2VpZ2h0U3BlY3MgPSB3ZWlnaHREYXRhQW5kU3BlY3Muc3BlY3M7XG4gICAgcmV0dXJuIGhhbmRsZXJPclVSTC5zYXZlKG1vZGVsQXJ0aWZhY3RzKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTZXQgdXNlci1kZWZpbmVkIG1ldGFkYXRhLlxuICAgKlxuICAgKiBUaGUgc2V0IG1ldGFkYXRhIHdpbGwgYmUgc2VyaWFsaXplZCB0b2dldGhlciB3aXRoIHRoZSB0b3BvbG9neVxuICAgKiBhbmQgd2VpZ2h0cyBvZiB0aGUgbW9kZWwgZHVyaW5nIGBzYXZlKClgIGNhbGxzLlxuICAgKlxuICAgKiBAcGFyYW0gc2V0VXNlckRlZmluZWRNZXRhZGF0YVxuICAgKi9cbiAgc2V0VXNlckRlZmluZWRNZXRhZGF0YSh1c2VyRGVmaW5lZE1ldGFkYXRhOiB7fSk6IHZvaWQge1xuICAgIGNoZWNrVXNlckRlZmluZWRNZXRhZGF0YSh1c2VyRGVmaW5lZE1ldGFkYXRhLCB0aGlzLm5hbWUpO1xuICAgIHRoaXMudXNlckRlZmluZWRNZXRhZGF0YSA9IHVzZXJEZWZpbmVkTWV0YWRhdGE7XG4gIH1cblxuICAvKipcbiAgICogR2V0IHVzZXItZGVmaW5lZCBtZXRhZGF0YS5cbiAgICpcbiAgICogVGhlIG1ldGFkYXRhIGlzIHN1cHBsaWVkIHZpYSBvbmUgb2YgdGhlIHR3byByb3V0ZXM6XG4gICAqICAgMS4gQnkgY2FsbGluZyBgc2V0VXNlckRlZmluZWRNZXRhZGF0YSgpYC5cbiAgICogICAyLiBMb2FkZWQgZHVyaW5nIG1vZGVsIGxvYWRpbmcgKGlmIHRoZSBtb2RlbCBpcyBjb25zdHJ1Y3RlZFxuICAgKiAgICAgIHZpYSBgdGYubG9hZExheWVyc01vZGVsKClgLilcbiAgICpcbiAgICogSWYgbm8gdXNlci1kZWZpbmVkIG1ldGFkYXRhIGlzIGF2YWlsYWJsZSBmcm9tIGVpdGhlciBvZiB0aGVcbiAgICogdHdvIHJvdXRlcywgdGhpcyBmdW5jdGlvbiB3aWxsIHJldHVybiBgdW5kZWZpbmVkYC5cbiAgICovXG4gIGdldFVzZXJEZWZpbmVkTWV0YWRhdGEoKToge30ge1xuICAgIHJldHVybiB0aGlzLnVzZXJEZWZpbmVkTWV0YWRhdGE7XG4gIH1cbn1cbnNlcmlhbGl6YXRpb24ucmVnaXN0ZXJDbGFzcyhMYXllcnNNb2RlbCk7XG5cbi8qKlxuICogQSBgdGYuRnVuY3Rpb25hbGAgaXMgYW4gYWxpYXMgdG8gYHRmLkxheWVyc01vZGVsYC5cbiAqXG4gKiBTZWUgYWxzbzpcbiAqICAgYHRmLkxheWVyc01vZGVsYCwgYHRmLlNlcXVlbnRpYWxgLCBgdGYubG9hZExheWVyc01vZGVsYC5cbiAqL1xuLyoqIEBkb2Mge2hlYWRpbmc6ICdNb2RlbHMnLCBzdWJoZWFkaW5nOiAnQ2xhc3Nlcyd9ICovXG5leHBvcnQgY2xhc3MgRnVuY3Rpb25hbCBleHRlbmRzIExheWVyc01vZGVsIHtcbiAgc3RhdGljIGNsYXNzTmFtZSA9ICdGdW5jdGlvbmFsJztcbn1cbnNlcmlhbGl6YXRpb24ucmVnaXN0ZXJDbGFzcyhGdW5jdGlvbmFsKTtcbiJdfQ== |
\ | No newline at end of file |