UNPKG

@activejs/core

Version:

Pragmatic, Reactive State Management for JavaScript Apps

659 lines 80.3 kB
import { Observable } from 'rxjs'; import { Configuration } from './configuration'; import { Base } from './abstract-base'; import { UnitBase } from './abstract-unit-base'; import { NonPrimitiveUnitBase } from './abstract-non-primitive-unit-base'; import { EventListUnitCopyWithin, EventListUnitDelete, EventListUnitFill, EventListUnitPop, EventListUnitPush, EventListUnitRemove, EventListUnitReverse, EventListUnitSet, EventListUnitShift, EventListUnitSort, EventListUnitSplice, EventListUnitUnshift, } from '../models'; import { isFunction, isObject, isValidIndex, IteratorSymbol, makeNonEnumerable, normalizeIndex, sanitizeIndices, } from '../utils/funcs'; /** * ListUnit is a reactive storage Unit that emulates `array`. * * ListUnit only accepts `array` data type as its value. * It ensures that at any point of time the value would always be an `array`. * * ListUnit is based on `array`, it implements all the `Array.prototype` methods that are available * in the environment/browser its running, including polyfills. * e.g.: `find`, `filter`, `includes`, etc. * * Learn more about Units [here](https://docs.activejs.dev/fundamentals/units). \ * Learn more about ListUnit [here](https://docs.activejs.dev/fundamentals/units/listunit). * * Just like every other Non-Primitive ActiveJS Unit: * - ListUnit extends {@link NonPrimitiveUnitBase} * - Which further extends {@link UnitBase}, {@link Base} and `Observable` * * @category 1. Units */ export class ListUnit extends NonPrimitiveUnitBase { constructor(config) { super(Object.assign(Object.assign({}, Configuration.LIST_UNIT), config)); makeNonEnumerable(this); } /** * The length of the list-{@link value}. */ get length() { return this.rawValue().length; } /** * Indicates whether the length of the list-{@link value} is `0` or not. */ get isEmpty() { return this.length === 0; } /** * @internal please do not use. */ defaultValue() { return []; } /** * Extends {@link UnitBase.wouldDispatch} and adds additional check for type `array` {@see {@link Array.isArray}}, \ * which cannot be bypassed even by using {@link force}. * * @param value The value to be dispatched. * @param force Whether dispatch-checks should be bypassed or not. * @returns A boolean indicating whether the param `value` would pass the dispatch-checks if dispatched. * * @category Common Units */ wouldDispatch(value, force) { return this.isValidValue(value) && super.wouldDispatch(value, force); } /** * Sets the item on the given index in the list-{@link value}. \ * Dispatches a new copy of the value, without mutating the current-value. * * It only works if the Unit is not frozen, and the index is valid-numeric. * * @param index The index of the item. \ * A negative index is normalized as following: \ * - If the index is less than negative of list-{@link length} it's treated as 0. * - Otherwise, it's subtracted from the list-{@link length}. * eg: If list length is `5`, negative index `-5` will be normalized to `0`, \ * negative index `-6` will be normalized to `0`, and so on. * @param item The item. * * @triggers {@link EventListUnitSet} * @category Custom ListUnit */ set(index, item) { var _a; if (this.isFrozen || !isValidIndex(index)) { return; } this.checkSerializabilityMaybe(item); const listShallowCopy = [...this.rawValue()]; listShallowCopy[normalizeIndex(index, this.length)] = this.deepCopyMaybe(item); this.updateValueAndCache(listShallowCopy); if (((_a = this.eventsSubject) === null || _a === void 0 ? void 0 : _a.observers.length) && !this.isMuted) { this.eventsSubject.next(new EventListUnitSet(index, item)); } } /** * Inserts items to the list-{@link value}. \ * Dispatches a new copy of the value, without mutating the current-value. * * It only works if the Unit is not frozen and there's at least 1 item to be inserted. * * @param start The zero-based index in the list from which to start adding items. \ * A negative index is normalized as following: \ * - If the index is less than negative of list-{@link length} it's treated as 0. * - Otherwise, it's subtracted from the list-{@link length}. * eg: If list length is `5`, negative index `-5` will be normalized to `0`, \ * negative index `-6` will be normalized to `0`, and so on. * @param items The items to be insert. * @returns The new length of the list. * * @triggers {@link EventListUnitSplice} * @category Custom ListUnit */ insert(start, ...items) { if (this.isFrozen || !items.length) { return this.length; } this.checkSerializabilityMaybe(items); this.splice(start, 0, ...items); // splice also takes care of empty spaces return this.length; } /** * Removes items at given indices from the list-{@link value}. \ * Dispatches a new copy of the value, without mutating the current-value. \ * It modifies the length of the list as these indices are not deleted, * rather removed using `Array.prototype.splice`. * * It only works if the Unit is not frozen, and * the list is not empty, and at least 1 valid-numeric index is provided. * * Notes: * - The indices are removed in descending order. * - The indices are deduplicated. * - Negative indices are normalized before starting the removal. \ * A negative index is normalized as following: \ * - If the index is less than negative of list-{@link length}, it's treated as 0. * - Otherwise, it's subtracted from the list-{@link length}. * eg: If list length is `5`, negative index `-5` will be normalized to `0`, \ * negative index `-6` will be normalized to `0`, and so on. * * @param indices The indices of the items to be removed. * @returns The removed items or an empty `array`. * * @triggers {@link EventListUnitRemove} * @category Custom ListUnit */ remove(...indices) { var _a; const listLength = this.length; indices = sanitizeIndices(indices, listLength).filter(i => this.hasOwnProperty(i)); if (this.isFrozen || !indices.length || this.isEmpty) { return []; } indices.sort().reverse(); const removedItems = []; const listShallowCopy = [...this.rawValue()]; indices.forEach(index => { removedItems.push(...this.deepCopyMaybe(listShallowCopy.splice(index, 1))); }); this.updateValueAndCache(listShallowCopy); removedItems.reverse(); if (((_a = this.eventsSubject) === null || _a === void 0 ? void 0 : _a.observers.length) && !this.isMuted) { this.eventsSubject.next(new EventListUnitRemove(indices, removedItems)); } return removedItems; } /** * Removes items from the list-{@link value} where the predicate returns `truthy` value. \ * Dispatches a new copy of the value, without mutating the current-value. \ * It modifies the length of the list as these indices are not deleted, * rather removed using `Array.prototype.splice`. * * It only works if the Unit is not frozen, and * the predicate is a function, and the list is not empty. \ * It uses {@link remove} internally, for actual removal. * * @param predicate A callback function that gets called for every item in the list with the item and index as arguments. * @returns The removed items or an empty `array`. * * @triggers {@link EventListUnitRemove} * @category Custom ListUnit */ removeIf(predicate) { if (this.isFrozen || typeof predicate !== 'function' || this.isEmpty) { return []; } const indicesToBeRemoved = []; this.value().forEach((item, index) => { if (predicate(item, index)) { indicesToBeRemoved.push(index); } }); return this.remove(...indicesToBeRemoved); } /** * Deletes items at given indices in the list-{@link value}. \ * Dispatches a new copy of the value, without mutating the current-value. \ * It doesn't modify the length of the list as these indices are only deleted, not removed. * * It only works if the Unit is not frozen, and * the list is not empty, and at least 1 valid-numeric index is provided. * * @param indices The indices of the items to be deleted. \ * A negative index is normalized as following: \ * - If the index is less than negative of list-{@link length} it's treated as 0. * - Otherwise, it's subtracted from the list-{@link length}. * eg: If list length is `5`, negative index `-5` will be normalized to `0`, \ * negative index `-6` will be normalized to `0`, and so on. * @returns The removed items or an empty `array`. * * @triggers {@link EventListUnitDelete} * @category Custom ListUnit */ delete(...indices) { var _a; indices = sanitizeIndices(indices, this.length).filter(i => this.hasOwnProperty(i)); if (this.isFrozen || indices.length === 0 || this.isEmpty) { return []; } const deletedItems = []; const listShallowCopy = [...this.rawValue()]; indices.forEach(index => { deletedItems.push(this.deepCopyMaybe(listShallowCopy[index])); delete listShallowCopy[index]; }); this.updateValueAndCache(listShallowCopy); if (((_a = this.eventsSubject) === null || _a === void 0 ? void 0 : _a.observers.length) && !this.isMuted) { this.eventsSubject.next(new EventListUnitDelete(indices, deletedItems)); } return deletedItems; } /** * Deletes items from the list-{@link value} where the predicate returns `true`. \ * Dispatches a new copy of the value, without mutating the current-value. \ * It doesn't modify the length of the list as these indices are only deleted, not removed. * * It only works if the Unit is not frozen, and * the predicate is a function, and the list is not empty. * * @param predicate A callback function that gets called for every item in the list with the item and index as arguments. * @returns The removed items or an empty `array`. * * @triggers {@link EventListUnitDelete} * @category Custom ListUnit */ deleteIf(predicate) { if (this.isFrozen || typeof predicate !== 'function' || this.isEmpty) { return []; } const indicesToBeDeleted = []; this.value().forEach((item, index) => { if (predicate(item, index)) { indicesToBeDeleted.push(index); } }); return this.delete(...indicesToBeDeleted); } /** * Finds items of the list that have a direct child property * that matches with `key` and `value` passed as params. * * @example * ```ts * const initialValue = [{b: 1}, {b: 1, b2: 2}, {c: 1}, {b: null}]; * const list = new ListUnit({initialValue}); * const itemsFound = list.findByProp('b', 1); * console.log(itemsFound); // logs [[0, {b: 1}], [1, {b: 1, b2: 2}]] * ``` * * @param key The key of the property whose value needs to be matched against param `value`. * @param value A value that will be matched against every item's prop-value using equality operator. * @param strictEquality A flag governing whether the value be matched using `===` or `==` operator. * Default is `true`, ie: strict equality `===`. Pass `false` to use `==` instead. * @returns An `array` of key/values of the matched properties, an empty `array` otherwise. * * @category Custom ListUnit */ findByProp(key, value, strictEquality = true) { if (this.isEmpty) { return []; } return this.rawValue().reduce((res, item, index) => { const aMatch = isObject(item) && (strictEquality === false ? // tslint:disable-next-line:triple-equals item[key] == value : item[key] === value); if (aMatch) { res.push([index, this.deepCopyMaybe(item)]); } return res; }, []); } /** * Returns the item at the given index in the list-{@link value}. \ * Negative index returns items from the end of the list. * * @param index The index of the item. \ * A negative index is normalized as following: \ * - If the index is less than negative of list-{@link length} it's treated as 0. * - Otherwise, it's subtracted from the list-{@link length}. * eg: If list length is `5`, negative index `-5` will be normalized to `0`, \ * negative index `-6` will be normalized to `0`, and so on. * @returns The item at the given index. * * @category Custom ListUnit */ get(index) { index = normalizeIndex(index, this.length); return this.hasOwnProperty(index) ? this.deepCopyMaybe(this.rawValue()[index]) : undefined; } /** * Returns the first item in the list-{@link value}. * * @returns The first item in the list. * * @category Custom ListUnit */ first() { return this.get(0); } /** * Returns the last item in the list-{@link value}. * * @returns The last item in the list. * * @category Custom ListUnit */ last() { return this.get(-1); } /** * Adds all the items of the list separated by the specified separator string, and * all the items are converted into string first using JSON.stringify * * @param separator A string used to separate one item of the list from the next in the resulting String. * If omitted, the list items are separated with a comma. * @returns Concatenated `JSON.stringify`d list items. * * @category Custom ListUnit */ jsonJoin(separator) { return this.rawValue() .map(item => JSON.stringify(item)) .join(separator); } /** * Appends new items to the list-{@link value}. \ * Dispatches a new copy of the value, without mutating the current-value. * * It only works if the Unit is not frozen, and at least 1 item is provided. * * @param items The items to be appended to the list. * @returns The new length of the list. * * @triggers {@link EventListUnitPush} * @category Customised Array.prototype */ push(...items) { var _a; if (this.isFrozen || !items.length) { return this.length; } this.checkSerializabilityMaybe(items); const listShallowCopy = [...this.rawValue()]; const newLength = listShallowCopy.push(...this.deepCopyMaybe(items)); this.updateValueAndCache(listShallowCopy); if (((_a = this.eventsSubject) === null || _a === void 0 ? void 0 : _a.observers.length) && !this.isMuted) { this.eventsSubject.next(new EventListUnitPush(items)); } return newLength; } /** * Removes the last item from the list-{@link value} and returns it. \ * Dispatches a new copy of the value, without mutating the current-value. * * It only works if the Unit is not frozen, and the list is not empty {@link isEmpty}. * * @returns The popped item. * * @triggers {@link EventListUnitPop} * @category Customised Array.prototype */ pop() { var _a; if (this.isFrozen || this.isEmpty) { return; } const listShallowCopy = [...this.rawValue()]; const poppedItem = this.deepCopyMaybe(listShallowCopy.pop()); this.updateValueAndCache(listShallowCopy); if (((_a = this.eventsSubject) === null || _a === void 0 ? void 0 : _a.observers.length) && !this.isMuted) { this.eventsSubject.next(new EventListUnitPop(poppedItem)); } return poppedItem; } /** * Removes the first item from the list-{@link value} and returns it. \ * Dispatches a new copy of the value, without mutating the current-value. * * It only works if the Unit is not frozen, and the list is not empty {@link isEmpty}. * * @returns The shifted item. * * @triggers {@link EventListUnitShift} * @category Customised Array.prototype */ shift() { var _a; if (this.isFrozen || this.isEmpty) { return; } const listShallowCopy = [...this.rawValue()]; const shiftedItem = this.deepCopyMaybe(listShallowCopy.shift()); this.updateValueAndCache(listShallowCopy); if (((_a = this.eventsSubject) === null || _a === void 0 ? void 0 : _a.observers.length) && !this.isMuted) { this.eventsSubject.next(new EventListUnitShift(shiftedItem)); } return shiftedItem; } /** * Inserts new items at the start of the list-{@link value}. \ * Dispatches a new copy of the value, without mutating the current-value. * * It only works if the Unit is not frozen, and at least 1 item is provided. * * @param items The items to be insert at the start of the list * @returns The new length of the list. * * @triggers {@link EventListUnitUnshift} * @category Customised Array.prototype */ unshift(...items) { var _a; if (this.isFrozen || !items.length) { return this.length; } this.checkSerializabilityMaybe(items); const listShallowCopy = [...this.rawValue()]; const newLength = listShallowCopy.unshift(...this.deepCopyMaybe(items)); this.updateValueAndCache(listShallowCopy); if (((_a = this.eventsSubject) === null || _a === void 0 ? void 0 : _a.observers.length) && !this.isMuted) { this.eventsSubject.next(new EventListUnitUnshift(items)); } return newLength; } /** * Removes items from the list-{@link value} and, if necessary, * inserts new items in their place, returning the deleted items. \ * Dispatches a new copy of the value, without mutating the current-value. * * It only works if the Unit is not frozen, and \ * either deleteCount is not 0 (loose comparison) and the list is not empty {@link isEmpty}, or \ * there's at least 1 item to be added. * * @param start The zero-based location in the list from which to start removing items. * @param deleteCount The number of items to remove. * @param items Items to insert into the list in place of the deleted items. * @returns The deleted Items or an empty `array`. * * @triggers {@link EventListUnitSplice} * @category Customised Array.prototype */ splice(start, deleteCount, ...items) { var _a; // tslint:disable-next-line:triple-equals if (this.isFrozen || ((deleteCount == 0 || this.isEmpty) && !items.length)) { return []; } this.checkSerializabilityMaybe(items); const listShallowCopy = [...this.rawValue()]; const removedItems = this.deepCopyMaybe(listShallowCopy.splice(start, deleteCount, ...items.map(item => this.deepCopyMaybe(item)))); this.updateValueAndCache(listShallowCopy); if (((_a = this.eventsSubject) === null || _a === void 0 ? void 0 : _a.observers.length) && !this.isMuted) { this.eventsSubject.next(new EventListUnitSplice(start, deleteCount, removedItems, items)); } return removedItems; } /** * Fills a section of the list-{@link value} with provided param `item`. \ * Dispatches a new copy of the value, without mutating the current-value. * * It only works if the Unit is not frozen, and the list is not empty {@link isEmpty}. * * @param item The item to fill the section of ListUnit's value with. * @param start The index to start filling the list at. If start is negative, it is treated as * length+start where length is the length of the list. * @param end The index to stop filling the list at. If end is negative, it is treated as * length+end. * @returns Nothing, unlike `Array.prototype.fill` * * @triggers {@link EventListUnitFill} * @category Customised Array.prototype */ fill(item, start, end) { var _a; if (this.isFrozen || this.isEmpty) { return; } this.checkSerializabilityMaybe(item); const listShallowCopy = [...this.rawValue()]; listShallowCopy.fill(this.deepCopyMaybe(item), start, end); this.updateValueAndCache(listShallowCopy); if (((_a = this.eventsSubject) === null || _a === void 0 ? void 0 : _a.observers.length) && !this.isMuted) { this.eventsSubject.next(new EventListUnitFill(item, start, end)); } } /** * Copies a section of the current-value identified by param `start` and param `end` * to the ListUnit's value starting at position {@link target}. \ * Dispatches a new copy of the value, without mutating the current-value. * * It only works if the Unit is not frozen, and the list is not empty {@link isEmpty}. * * @param target If target is negative, it is treated as length+target where length is the * length of the list. * @param start If start is negative, it is treated as length+start. If end is negative, it * is treated as length+end. * @param end If end is not specified, length of the list is used as its default value. * @returns Nothing, unlike `Array.prototype.copyWithin` * * @triggers {@link EventListUnitCopyWithin} * @category Customised Array.prototype */ copyWithin(target, start, end) { var _a; if (this.isFrozen || this.isEmpty) { return; } const listShallowCopy = [...this.rawValue()]; listShallowCopy.copyWithin(target, start, end); this.updateValueAndCache(listShallowCopy); if (((_a = this.eventsSubject) === null || _a === void 0 ? void 0 : _a.observers.length) && !this.isMuted) { this.eventsSubject.next(new EventListUnitCopyWithin(target, start, end)); } } /** * Reverses the items in the list-{@link value}. \ * Dispatches a new copy of the value, without mutating the current-value. * * It only works if the Unit is not frozen, and the list is not empty {@link isEmpty}. * * @returns Nothing, unlike `Array.prototype.reverse` * * @triggers {@link EventListUnitReverse} * @category Customised Array.prototype */ reverse() { var _a; if (this.isFrozen || this.isEmpty) { return; } const listShallowCopyReversed = [...this.rawValue()].reverse(); this.updateValueAndCache(listShallowCopyReversed); if (((_a = this.eventsSubject) === null || _a === void 0 ? void 0 : _a.observers.length) && !this.isMuted) { this.eventsSubject.next(new EventListUnitReverse()); } } /** * Sorts the list-{@link value}. \ * Dispatches a new copy of the value, without mutating the current-value. * * It only works if the Unit is not frozen, and the list is not empty {@link isEmpty}. * * @param compareFn The name of the function used to determine the order of the items. * If omitted, the items are sorted in ascending, ASCII character order. * @returns Nothing, unlike `Array.prototype.sort` * * @triggers {@link EventListUnitSort} * @category Customised Array.prototype */ sort(compareFn) { var _a; if (this.isFrozen || this.isEmpty) { return; } const listShallowCopySorted = typeof compareFn === 'function' ? [...this.value()].sort(compareFn) : [...this.rawValue()].sort(); this.updateValueAndCache(listShallowCopySorted); if (((_a = this.eventsSubject) === null || _a === void 0 ? void 0 : _a.observers.length) && !this.isMuted) { this.eventsSubject.next(new EventListUnitSort()); } } /** * Returns a section of the list-{@link value}. * * @param start The beginning of the specified portion of the list. * @param end The end of the specified portion of the list. * @returns A section of the list. * * @category Customised Array.prototype */ slice(start, end) { return this.deepCopyMaybe(this.rawValue().slice(start, end)); } /** * Performs the specified action for each item in the list-{@link value}. \ * It's a drop-in replacement for the `Array.prototype.forEach` method. * * @param callbackFn A function that accepts up to three arguments. * forEvery calls the callbackFn function one time for each element in the list. * @param thisArg An object to which this keyword can refer in the callbackFn function. * If thisArg is omitted, undefined is used as this value. * * @category Customised Array.prototype */ forEvery(callbackFn, thisArg) { Array.prototype.forEach.apply(this.value(), [callbackFn, thisArg]); } /** * @internal please do not use. */ [IteratorSymbol]() { return Array.prototype[IteratorSymbol].call(this.value()); } /** * @internal please do not use. */ isValidValue(value) { return Array.isArray(value); } } /** * @internal please do not use. */ const MethodsWithNoRiskOfMutation = [ 'includes', 'indexOf', 'lastIndexOf', 'join', 'keys', 'toLocaleString', ]; MethodsWithNoRiskOfMutation.forEach(methodName => { Object.defineProperty(ListUnit.prototype, methodName, { value(...args) { return Array.prototype[methodName].apply(this.rawValue(), args); }, }); }); /** * @internal please do not use. */ const MethodsNotToImplement = [ ...Object.getOwnPropertyNames(Observable.prototype), ...Object.getOwnPropertyNames(Base.prototype), ...Object.getOwnPropertyNames(UnitBase.prototype), ...Object.getOwnPropertyNames(ListUnit.prototype), ...MethodsWithNoRiskOfMutation, ]; Object.getOwnPropertyNames(Array.prototype).forEach(method => { if (!isFunction(Array.prototype[method]) || MethodsNotToImplement.includes(method)) { return; } Object.defineProperty(ListUnit.prototype, method, { value(...args) { return Array.prototype[method].apply(this.value(), args); }, }); }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGlzdC11bml0LmpzIiwic291cmNlUm9vdCI6Ii9ob21lL3J1bm5lci93b3JrL2FjdGl2ZWpzL2FjdGl2ZWpzL3BhY2thZ2VzL2NvcmUvc3JjLyIsInNvdXJjZXMiOlsibGliL2xpc3QtdW5pdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUMsVUFBVSxFQUFVLE1BQU0sTUFBTSxDQUFDO0FBQ3pDLE9BQU8sRUFBQyxhQUFhLEVBQUMsTUFBTSxpQkFBaUIsQ0FBQztBQUM5QyxPQUFPLEVBQUMsSUFBSSxFQUFDLE1BQU0saUJBQWlCLENBQUM7QUFDckMsT0FBTyxFQUFDLFFBQVEsRUFBQyxNQUFNLHNCQUFzQixDQUFDO0FBQzlDLE9BQU8sRUFBQyxvQkFBb0IsRUFBQyxNQUFNLG9DQUFvQyxDQUFDO0FBQ3hFLE9BQU8sRUFDTCx1QkFBdUIsRUFDdkIsbUJBQW1CLEVBQ25CLGlCQUFpQixFQUNqQixnQkFBZ0IsRUFDaEIsaUJBQWlCLEVBQ2pCLG1CQUFtQixFQUNuQixvQkFBb0IsRUFDcEIsZ0JBQWdCLEVBQ2hCLGtCQUFrQixFQUNsQixpQkFBaUIsRUFDakIsbUJBQW1CLEVBQ25CLG9CQUFvQixHQUlyQixNQUFNLFdBQVcsQ0FBQztBQUNuQixPQUFPLEVBQ0wsVUFBVSxFQUNWLFFBQVEsRUFDUixZQUFZLEVBQ1osY0FBYyxFQUNkLGlCQUFpQixFQUNqQixjQUFjLEVBQ2QsZUFBZSxHQUNoQixNQUFNLGdCQUFnQixDQUFDO0FBS3hCOzs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FrQkc7QUFDSCxNQUFNLE9BQU8sUUFBZSxTQUFRLG9CQUE0QjtJQStCOUQsWUFBWSxNQUEyQjtRQUNyQyxLQUFLLGlDQUNBLGFBQWEsQ0FBQyxTQUFTLEdBQ3ZCLE1BQU0sRUFDVCxDQUFDO1FBRUgsaUJBQWlCLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDMUIsQ0FBQztJQTVCRDs7T0FFRztJQUNILElBQUksTUFBTTtRQUNSLE9BQU8sSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDLE1BQU0sQ0FBQztJQUNoQyxDQUFDO0lBRUQ7O09BRUc7SUFDSCxJQUFJLE9BQU87UUFDVCxPQUFPLElBQUksQ0FBQyxNQUFNLEtBQUssQ0FBQyxDQUFDO0lBQzNCLENBQUM7SUFFRDs7T0FFRztJQUNPLFlBQVk7UUFDcEIsT0FBTyxFQUFFLENBQUM7SUFDWixDQUFDO0lBV0Q7Ozs7Ozs7OztPQVNHO0lBQ0gsYUFBYSxDQUFDLEtBQWEsRUFBRSxLQUFlO1FBQzFDLE9BQU8sSUFBSSxDQUFDLFlBQVksQ0FBQyxLQUFLLENBQUMsSUFBSSxLQUFLLENBQUMsYUFBYSxDQUFDLEtBQUssRUFBRSxLQUFLLENBQUMsQ0FBQztJQUN2RSxDQUFDO0lBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7T0FnQkc7SUFDSCxHQUFHLENBQUMsS0FBYSxFQUFFLElBQVU7O1FBQzNCLElBQUksSUFBSSxDQUFDLFFBQVEsSUFBSSxDQUFDLFlBQVksQ0FBQyxLQUFLLENBQUMsRUFBRTtZQUN6QyxPQUFPO1NBQ1I7UUFDRCxJQUFJLENBQUMseUJBQXlCLENBQUMsSUFBSSxDQUFDLENBQUM7UUFFckMsTUFBTSxlQUFlLEdBQUcsQ0FBQyxHQUFHLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDO1FBQzdDLGVBQWUsQ0FBQyxjQUFjLENBQUMsS0FBSyxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxHQUFHLElBQUksQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDL0UsSUFBSSxDQUFDLG1CQUFtQixDQUFDLGVBQWUsQ0FBQyxDQUFDO1FBRTFDLElBQUksT0FBQSxJQUFJLENBQUMsYUFBYSwwQ0FBRSxTQUFTLENBQUMsTUFBTSxLQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRTtZQUN6RCxJQUFJLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxJQUFJLGdCQUFnQixDQUFDLEtBQUssRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDO1NBQzVEO0lBQ0gsQ0FBQztJQUVEOzs7Ozs7Ozs7Ozs7Ozs7OztPQWlCRztJQUNILE1BQU0sQ0FBQyxLQUFhLEVBQUUsR0FBRyxLQUFhO1FBQ3BDLElBQUksSUFBSSxDQUFDLFFBQVEsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLEVBQUU7WUFDbEMsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDO1NBQ3BCO1FBQ0QsSUFBSSxDQUFDLHlCQUF5QixDQUFDLEtBQUssQ0FBQyxDQUFDO1FBRXRDLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxFQUFFLENBQUMsRUFBRSxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUMseUNBQXlDO1FBRTFFLE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQztJQUNyQixDQUFDO0lBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztPQXdCRztJQUNILE1BQU0sQ0FBQyxHQUFHLE9BQWlCOztRQUN6QixNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDO1FBQy9CLE9BQU8sR0FBRyxlQUFlLENBQUMsT0FBTyxFQUFFLFVBQVUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUVuRixJQUFJLElBQUksQ0FBQyxRQUFRLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxJQUFJLElBQUksQ0FBQyxPQUFPLEVBQUU7WUFDcEQsT0FBTyxFQUFFLENBQUM7U0FDWDtRQUVELE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUN6QixNQUFNLFlBQVksR0FBRyxFQUFFLENBQUM7UUFDeEIsTUFBTSxlQUFlLEdBQUcsQ0FBQyxHQUFHLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDO1FBRTdDLE9BQU8sQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLEVBQUU7WUFDdEIsWUFBWSxDQUFDLElBQUksQ0FBQyxHQUFHLElBQUksQ0FBQyxhQUFhLENBQUMsZUFBZSxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQzdFLENBQUMsQ0FBQyxDQUFDO1FBQ0gsSUFBSSxDQUFDLG1CQUFtQixDQUFDLGVBQWUsQ0FBQyxDQUFDO1FBQzFDLFlBQVksQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUV2QixJQUFJLE9BQUEsSUFBSSxDQUFDLGFBQWEsMENBQUUsU0FBUyxDQUFDLE1BQU0sS0FBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUU7WUFDekQsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsSUFBSSxtQkFBbUIsQ0FBQyxPQUFPLEVBQUUsWUFBWSxDQUFDLENBQUMsQ0FBQztTQUN6RTtRQUNELE9BQU8sWUFBWSxDQUFDO0lBQ3RCLENBQUM7SUFFRDs7Ozs7Ozs7Ozs7Ozs7O09BZUc7SUFDSCxRQUFRLENBQUMsU0FBaUQ7UUFDeEQsSUFBSSxJQUFJLENBQUMsUUFBUSxJQUFJLE9BQU8sU0FBUyxLQUFLLFVBQVUsSUFBSSxJQUFJLENBQUMsT0FBTyxFQUFFO1lBQ3BFLE9BQU8sRUFBRSxDQUFDO1NBQ1g7UUFFRCxNQUFNLGtCQUFrQixHQUFHLEVBQUUsQ0FBQztRQUM5QixJQUFJLENBQUMsS0FBSyxFQUFFLENBQUMsT0FBTyxDQUFDLENBQUMsSUFBSSxFQUFFLEtBQUssRUFBRSxFQUFFO1lBQ25DLElBQUksU0FBUyxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsRUFBRTtnQkFDMUIsa0JBQWtCLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO2FBQ2hDO1FBQ0gsQ0FBQyxDQUFDLENBQUM7UUFDSCxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBSSxrQkFBNEMsQ0FBQyxDQUFDO0lBQ3ZFLENBQUM7SUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7O09Ba0JHO0lBQ0gsTUFBTSxDQUFDLEdBQUcsT0FBaUI7O1FBQ3pCLE9BQU8sR0FBRyxlQUFlLENBQUMsT0FBTyxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsY0FBYyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFFcEYsSUFBSSxJQUFJLENBQUMsUUFBUSxJQUFJLE9BQU8sQ0FBQyxNQUFNLEtBQUssQ0FBQyxJQUFJLElBQUksQ0FBQyxPQUFPLEVBQUU7WUFDekQsT0FBTyxFQUFFLENBQUM7U0FDWDtRQUVELE1BQU0sWUFBWSxHQUFHLEVBQUUsQ0FBQztRQUN4QixNQUFNLGVBQWUsR0FBRyxDQUFDLEdBQUcsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDN0MsT0FBTyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsRUFBRTtZQUN0QixZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsZUFBZSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUM5RCxPQUFPLGVBQWUsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUNoQyxDQUFDLENBQUMsQ0FBQztRQUNILElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxlQUFlLENBQUMsQ0FBQztRQUUxQyxJQUFJLE9BQUEsSUFBSSxDQUFDLGFBQWEsMENBQUUsU0FBUyxDQUFDLE1BQU0sS0FBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUU7WUFDekQsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsSUFBSSxtQkFBbUIsQ0FBQyxPQUFPLEVBQUUsWUFBWSxDQUFDLENBQUMsQ0FBQztTQUN6RTtRQUNELE9BQU8sWUFBWSxDQUFDO0lBQ3RCLENBQUM7SUFFRDs7Ozs7Ozs7Ozs7OztPQWFHO0lBQ0gsUUFBUSxDQUFDLFNBQWlEO1FBQ3hELElBQUksSUFBSSxDQUFDLFFBQVEsSUFBSSxPQUFPLFNBQVMsS0FBSyxVQUFVLElBQUksSUFBSSxDQUFDLE9BQU8sRUFBRTtZQUNwRSxPQUFPLEVBQUUsQ0FBQztTQUNYO1FBRUQsTUFBTSxrQkFBa0IsR0FBRyxFQUFFLENBQUM7UUFDOUIsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDLE9BQU8sQ0FBQyxDQUFDLElBQUksRUFBRSxLQUFLLEVBQUUsRUFBRTtZQUNuQyxJQUFJLFNBQVMsQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLEVBQUU7Z0JBQzFCLGtCQUFrQixDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQzthQUNoQztRQUNILENBQUMsQ0FBQyxDQUFDO1FBQ0gsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUksa0JBQTRDLENBQUMsQ0FBQztJQUN2RSxDQUFDO0lBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7T0FtQkc7SUFDSCxVQUFVLENBQ1IsR0FBTSxFQUNOLEtBQWdDLEVBQ2hDLGNBQWMsR0FBRyxJQUFJO1FBRXJCLElBQUksSUFBSSxDQUFDLE9BQU8sRUFBRTtZQUNoQixPQUFPLEVBQUUsQ0FBQztTQUNYO1FBRUQsT0FBTyxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUMsTUFBTSxDQUFDLENBQUMsR0FBcUIsRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLEVBQUU7WUFDbkUsTUFBTSxNQUFNLEdBQ1YsUUFBUSxDQUFDLElBQUksQ0FBQztnQkFDZCxDQUFDLGNBQWMsS0FBSyxLQUFLO29CQUN2QixDQUFDLENBQUMseUNBQXlDO3dCQUN6QyxJQUFJLENBQUMsR0FBc0IsQ0FBQyxJQUFJLEtBQUs7b0JBQ3ZDLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBc0IsQ0FBQyxLQUFLLEtBQUssQ0FBQyxDQUFDO1lBRTlDLElBQUksTUFBTSxFQUFFO2dCQUNWLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQyxLQUFLLEVBQUUsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7YUFDN0M7WUFDRCxPQUFPLEdBQUcsQ0FBQztRQUNiLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQztJQUNULENBQUM7SUFFRDs7Ozs7Ozs7Ozs7OztPQWFHO0lBQ0gsR0FBRyxDQUFDLEtBQWE7UUFDZixLQUFLLEdBQUcsY0FBYyxDQUFDLEtBQUssRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDM0MsT0FBTyxJQUFJLENBQUMsY0FBYyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7SUFDN0YsQ0FBQztJQUVEOzs7Ozs7T0FNRztJQUNILEtBQUs7UUFDSCxPQUFPLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDckIsQ0FBQztJQUVEOzs7Ozs7T0FNRztJQUNILElBQUk7UUFDRixPQUFPLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUN0QixDQUFDO0lBRUQ7Ozs7Ozs7OztPQVNHO0lBQ0gsUUFBUSxDQUFDLFNBQWtCO1FBQ3pCLE9BQU8sSUFBSSxDQUFDLFFBQVEsRUFBRTthQUNuQixHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO2FBQ2pDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUNyQixDQUFDO0lBRUQ7Ozs7Ozs7Ozs7O09BV0c7SUFDSCxJQUFJLENBQUMsR0FBRyxLQUFhOztRQUNuQixJQUFJLElBQUksQ0FBQyxRQUFRLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxFQUFFO1lBQ2xDLE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQztTQUNwQjtRQUNELElBQUksQ0FBQyx5QkFBeUIsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUV0QyxNQUFNLGVBQWUsR0FBRyxDQUFDLEdBQUcsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDN0MsTUFBTSxTQUFTLEdBQUcsZUFBZSxDQUFDLElBQUksQ0FBQyxHQUFHLElBQUksQ0FBQyxhQUFhLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztRQUNyRSxJQUFJLENBQUMsbUJBQW1CLENBQUMsZUFBZSxDQUFDLENBQUM7UUFFMUMsSUFBSSxPQUFBLElBQUksQ0FBQyxhQUFhLDBDQUFFLFNBQVMsQ0FBQyxNQUFNLEtBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFO1lBQ3pELElBQUksQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLElBQUksaUJBQWlCLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztTQUN2RDtRQUNELE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7SUFFRDs7Ozs7Ozs7OztPQVVHO0lBQ0gsR0FBRzs7UUFDRCxJQUFJLElBQUksQ0FBQyxRQUFRLElBQUksSUFBSSxDQUFDLE9BQU8sRUFBRTtZQUNqQyxPQUFPO1NBQ1I7UUFDRCxNQUFNLGVBQWUsR0FBRyxDQUFDLEdBQUcsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDN0MsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxlQUFlLENBQUMsR0FBRyxFQUFFLENBQUMsQ0FBQztRQUM3RCxJQUFJLENBQUMsbUJBQW1CLENBQUMsZUFBZSxDQUFDLENBQUM7UUFFMUMsSUFBSSxPQUFBLElBQUksQ0FBQyxhQUFhLDBDQUFFLFNBQVMsQ0FBQyxNQUFNLEtBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFO1lBQ3pELElBQUksQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLElBQUksZ0JBQWdCLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQztTQUMzRDtRQUNELE9BQU8sVUFBVSxDQUFDO0lBQ3BCLENBQUM7SUFFRDs7Ozs7Ozs7OztPQVVHO0lBQ0gsS0FBSzs7UUFDSCxJQUFJLElBQUksQ0FBQyxRQUFRLElBQUksSUFBSSxDQUFDLE9BQU8sRUFBRTtZQUNqQyxPQUFPO1NBQ1I7UUFDRCxNQUFNLGVBQWUsR0FBRyxDQUFDLEdBQUcsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDN0MsTUFBTSxXQUFXLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxlQUFlLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBQztRQUNoRSxJQUFJLENBQUMsbUJBQW1CLENBQUMsZUFBZSxDQUFDLENBQUM7UUFFMUMsSUFBSSxPQUFBLElBQUksQ0FBQyxhQUFhLDBDQUFFLFNBQVMsQ0FBQyxNQUFNLEtBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFO1lBQ3pELElBQUksQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLElBQUksa0JBQWtCLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQztTQUM5RDtRQUNELE9BQU8sV0FBVyxDQUFDO0lBQ3JCLENBQUM7SUFFRDs7Ozs7Ozs7Ozs7T0FXRztJQUNILE9BQU8sQ0FBQyxHQUFHLEtBQWE7O1FBQ3RCLElBQUksSUFBSSxDQUFDLFFBQVEsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLEVBQUU7WUFDbEMsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDO1NBQ3BCO1FBQ0QsSUFBSSxDQUFDLHlCQUF5QixDQUFDLEtBQUssQ0FBQyxDQUFDO1FBRXRDLE1BQU0sZUFBZSxHQUFHLENBQUMsR0FBRyxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztRQUM3QyxNQUFNLFNBQVMsR0FBRyxlQUFlLENBQUMsT0FBTyxDQUFDLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO1FBQ3hFLElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxlQUFlLENBQUMsQ0FBQztRQUUxQyxJQUFJLE9BQUEsSUFBSSxDQUFDLGFBQWEsMENBQUUsU0FBUyxDQUFDLE1BQU0sS0FBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUU7WUFDekQsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsSUFBSSxvQkFBb0IsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO1NBQzFEO1FBQ0QsT0FBTyxTQUFTLENBQUM7SUFDbkIsQ0FBQztJQUVEOzs7Ozs7Ozs7Ozs7Ozs7O09BZ0JHO0lBQ0gsTUFBTSxDQUFDLEtBQWEsRUFBRSxXQUFtQixFQUFFLEdBQUcsS0FBYTs7UUFDekQseUNBQXlDO1FBQ3pDLElBQUksSUFBSSxDQUFDLFFBQVEsSUFBSSxDQUFDLENBQUMsV0FBVyxJQUFJLENBQUMsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLEVBQUU7WUFDMUUsT0FBTyxFQUFFLENBQUM7U0FDWDtRQUNELElBQUksQ0FBQyx5QkFBeUIsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUV0QyxNQUFNLGVBQWUsR0FBRyxDQUFDLEdBQUcsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDN0MsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FDckMsZUFBZSxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsV0FBVyxFQUFFLEdBQUcsS0FBSyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUMzRixDQUFDO1FBQ0YsSUFBSSxDQUFDLG1CQUFtQixDQUFDLGVBQWUsQ0FBQyxDQUFDO1FBRTFDLElBQUksT0FBQSxJQUFJLENBQUMsYUFBYSwwQ0FBRSxTQUFTLENBQUMsTUFBTSxLQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRTtZQUN6RCxJQUFJLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxJQUFJLG1CQUFtQixDQUFDLEtBQUssRUFBRSxXQUFXLEVBQUUsWUFBWSxFQUFFLEtBQUssQ0FBQyxDQUFDLENBQUM7U0FDM0Y7UUFDRCxPQUFPLFlBQVksQ0FBQztJQUN0QixDQUFDO0lBRUQ7Ozs7Ozs7Ozs7Ozs7OztPQWVHO0lBQ0gsSUFBSSxDQUFDLElBQVUsRUFBRSxLQUFjLEVBQUUsR0FBWTs7UUFDM0MsSUFBSSxJQUFJLENBQUMsUUFBUSxJQUFJLElBQUksQ0FBQyxPQUFPLEVBQUU7WUFDakMsT0FBTztTQUNSO1FBQ0QsSUFBSSxDQUFDLHlCQUF5QixDQUFDLElBQUksQ0FBQyxDQUFDO1FBRXJDLE1BQU0sZUFBZSxHQUFHLENBQUMsR0FBRyxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztRQUM3QyxlQUFlLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLEVBQUUsS0FBSyxFQUFFLEdBQUcsQ0FBQyxDQUFDO1FBQzNELElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxlQUFlLENBQUMsQ0FBQztRQUUxQyxJQUFJLE9BQUEsSUFBSSxDQUFDLGFBQWEsMENBQUUsU0FBUyxDQUFDLE1BQU0sS0FBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUU7WUFDekQsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsSUFBSSxpQkFBaUIsQ0FBQyxJQUFJLEVBQUUsS0FBSyxFQUFFLEdBQUcsQ0FBQyxDQUFDLENBQUM7U0FDbEU7SUFDSCxDQUFDO0lBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7T0FnQkc7SUFDSCxVQUFVLENBQUMsTUFBYyxFQUFFLEtBQWEsRUFBRSxHQUFZOztRQUNwRCxJQUFJLElBQUksQ0FBQyxRQUFRLElBQUksSUFBSSxDQUFDLE9BQU8sRUFBRTtZQUNqQyxPQUFPO1NBQ1I7UUFDRCxNQUFNLGVBQWUsR0FBRyxDQUFDLEdBQUcsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDN0MsZUFBZSxDQUFDLFVBQVUsQ0FBQyxNQUFNLEVBQUUsS0FBSyxFQUFFLEdBQUcsQ0FBQyxDQUFDO1FBQy9DLElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxlQUFlLENBQUMsQ0FBQztRQUUxQyxJQUFJLE9BQUEsSUFBSSxDQUFDLGFBQWEsMENBQUUsU0FBUyxDQUFDLE1BQU0sS0FBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUU7WUFDekQsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsSUFBSSx1QkFBdUIsQ0FBQyxNQUFNLEVBQUUsS0FBSyxFQUFFLEdBQUcsQ0FBQyxDQUFDLENBQUM7U0FDMUU7SUFDSCxDQUFDO0lBRUQ7Ozs7Ozs7Ozs7T0FVRztJQUNILE9BQU87O1FBQ0wsSUFBSSxJQUFJLENBQUMsUUFBUSxJQUFJLElBQUksQ0FBQyxPQUFPLEVBQUU7WUFDakMsT0FBTztTQUNSO1FBQ0QsTUFBTSx1QkFBdUIsR0FBRyxDQUFDLEdBQUcsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7UUFDL0QsSUFBSSxDQUFDLG1CQUFtQixDQUFDLHVCQUF1QixDQUFDLENBQUM7UUFFbEQsSUFBSSxPQUFBLElBQUksQ0FBQyxhQUFhLDBDQUFFLFNBQVMsQ0FBQyxNQUFNLEtBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFO1lBQ3pELElBQUksQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLElBQUksb0JBQW9CLEVBQUUsQ0FBQyxDQUFDO1NBQ3JEO0lBQ0gsQ0FBQztJQUVEOzs7Ozs7Ozs7Ozs7T0FZRztJQUNILElBQUksQ0FBQyxTQUF3Qzs7UUFDM0MsSUFBSSxJQUFJLENBQUMsUUFBUSxJQUFJLElBQUksQ0FBQyxPQUFPLEVBQUU7WUFDakMsT0FBTztTQUNSO1FBQ0QsTUFBTSxxQkFBcUIsR0FDekIsT0FBTyxTQUFTLEtBQUssVUFBVTtZQUM3QixDQUFDLENBQUMsQ0FBQyxHQUFHLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUM7WUFDbkMsQ0FBQyxDQUFDLENBQUMsR0FBRyxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUNsQyxJQUFJLENBQUMsbUJBQW1CLENBQUMscUJBQXFCLENBQUMsQ0FBQztRQUVoRCxJQUFJLE9BQUEsSUFBSSxDQUFDLGFBQWEsMENBQUUsU0FBUyxDQUFDLE1BQU0sS0FBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUU7WUFDekQsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsSUFBSSxpQkFBaUIsRUFBRSxDQUFDLENBQUM7U0FDbEQ7SUFDSCxDQUFDO0lBRUQ7Ozs7Ozs7O09BUUc7SUFDSCxLQUFLLENBQUMsS0FBYyxFQUFFLEdBQVk7UUFDaEMsT0FBTyxJQUFJLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQyxLQUFLLENBQUMsS0FBSyxFQUFFLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDL0QsQ0FBQztJQUVEOzs7Ozs7Ozs7O09BVUc7SUFDSCxRQUFRLENBQUMsVUFBOEQsRUFBRSxPQUFhO1FBQ3BGLEtBQUssQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxVQUFVLEVBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQztJQUNyRSxDQUFDO0lBRUQ7O09BRUc7SUFDSCxDQUFDLGNBQWMsQ0FBQztRQUNkLE9BQU8sS0FBSyxDQUFDLFNBQVMsQ0FBQyxjQUFjLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDLENBQUM7SUFDNUQsQ0FBQztJQUVEOztPQUVHO0lBQ08sWUFBWSxDQUFDLEtBQVU7UUFDL0IsT0FBTyxLQUFLLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQzlCLENBQUM7Q0E4QkY7QUFFRDs7R0FFRztBQUNILE1BQU0sMkJBQTJCLEdBQUc7SUFDbEMsVUFBVTtJQUNWLFNBQVM7SUFDVCxhQUFhO0lBQ2IsTUFBTTtJQUNOLE1BQU07SUFDTixnQkFBZ0I7Q0FDakIsQ0FBQztBQUNGLDJCQUEyQixDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUMsRUFBRTtJQUMvQyxNQUFNLENBQUMsY0FBYyxDQUFDLFFBQVEsQ0FBQyxTQUFTLEVBQUUsVUFBVSxFQUFFO1FBQ3BELEtBQUssQ0FBQyxHQUFHLElBQUk7WUFDWCxPQUFPLEtBQUssQ0FBQyxTQUFTLENBQUMsVUFBVSxDQUFDLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsRUFBRSxJQUFJLENBQUMsQ0FBQztRQUNsRSxDQUFDO0tBQ0YsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDLENBQUM7QUFFSDs7R0FFRztBQUNILE1BQU0scUJBQXFCLEdBQUc7SUFDNUIsR0FBRyxNQUFNLENBQUMsbUJBQW1CLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQztJQUNuRCxHQUFHLE1BQU0sQ0FBQyxtQkFBbUIsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDO0lBQzdDLEdBQUcsTUFBTSxDQUFDLG1CQUFtQixDQUFDLFFBQVEsQ0FBQyxTQUFTLENBQUM7SUFDakQsR0FBRyxNQUFNLENBQUMsbUJBQW1CLENBQUMsUUFBUSxDQUFDLFNBQVMsQ0FBQztJQUNqRCxHQUFHLDJCQUEyQjtDQUMvQixDQUFDO0FBQ0YsTUFBTSxDQUFDLG1CQUFtQixDQUFDLEtBQUssQ0FBQyxTQUFTLENBQUMsQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLEVBQUU7SUFDM0QsSUFBSSxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxDQUFDLElBQUkscUJBQXFCLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxFQUFFO1FBQ2xGLE9BQU87S0FDUjtJQUVELE1BQU0sQ0FBQyxjQUFjLENBQUMsUUFBUSxDQUFDLFNBQVMsRUFBRSxNQUFNLEVBQUU7UUFDaEQsS0FBSyxDQUFDLEdBQUcsSUFBSTtZQUNYLE9BQU8sS0FBSyxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLEtBQUssRUFBRSxFQUFFLElBQUksQ0FBQyxDQUFDO1FBQzNELENBQUM7S0FDRixDQUFDLENBQUM7QUFDTCxDQUFDLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7T2JzZXJ2YWJsZSwgU3ViamVjdH0gZnJvbSAncnhqcyc7XG5pbXBvcnQge0NvbmZpZ3VyYXRpb259IGZyb20gJy4vY29uZmlndXJhdGlvbic7XG5pbXBvcnQge0Jhc2V9IGZyb20gJy4vYWJzdHJhY3QtYmFzZSc7XG5pbXBvcnQge1VuaXRCYXNlfSBmcm9tICcuL2Fic3RyYWN0LXVuaXQtYmFzZSc7XG5pbXBvcnQge05vblByaW1pdGl2ZVVuaXRCYXNlfSBmcm9tICcuL2Fic3RyYWN0LW5vbi1wcmltaXRpdmUtdW5pdC1iYXNlJztcbmltcG9ydCB7XG4gIEV2ZW50TGlzdFVuaXRDb3B5V2l0aGluLFxuICBFdmVudExpc3RVbml0RGVsZXRlLFxuICBFdmVudExpc3RVbml0RmlsbCxcbiAgRXZlbnRMaXN0VW5pdFBvcCxcbiAgRXZlbnRMaXN0VW5pdFB1c2gsXG4gIEV2ZW50TGlzdFVuaXRSZW1vdmUsXG4gIEV2ZW50TGlzdFVuaXRSZXZlcnNlLFxuICBFdmVudExpc3RVbml0U2V0LFxuICBFdmVudExpc3RVbml0U2hpZnQsXG4gIEV2ZW50TGlzdFVuaXRTb3J0LFxuICBFdmVudExpc3RVbml0U3BsaWNlLFxuICBFdmVudExpc3RVbml0VW5zaGlmdCxcbiAgS09mLFxuICBMaXN0VW5pdEV2ZW50cyxcbiAgVW5pdENvbmZpZyxcbn0gZnJvbSAnLi4vbW9kZWxzJztcbmltcG9ydCB7XG4gIGlzRnVuY3Rpb24sXG4gIGlzT2JqZWN0LFxuICBpc1ZhbGlkSW5kZXgsXG4gIEl0ZXJhdG9yU3ltYm9sLFxuICBtYWtlTm9uRW51bWVyYWJsZSxcbiAgbm9ybWFsaXplSW5kZXgsXG4gIHNhbml0aXplSW5kaWNlcyxcbn0gZnJvbSAnLi4vdXRpbHMvZnVuY3MnO1xuXG4vLyB0c2xpbnQ6ZGlzYWJsZS1uZXh0LWxpbmU6bm8tZW1wdHktaW50ZXJmYWNlXG5leHBvcnQgaW50ZXJmYWNlIExpc3RVbml0PEl0ZW0+IGV4dGVuZHMgQXJyYXk8SXRlbT4ge31cblxuLyoqXG4gKiBMaXN0VW5pdCBpcyBhIHJlYWN0aXZlIHN0b3JhZ2UgVW5pdCB0aGF0IGVtdWxhdGVzIGBhcnJheWAuXG4gKlxuICogTGlzdFVuaXQgb25seSBhY2NlcHRzIGBhcnJheWAgZGF0YSB0eXBlIGFzIGl0cyB2YWx1ZS5cbiAqIEl0IGVuc3VyZXMgdGhhdCBhdCBhbnkgcG9pbnQgb2YgdGltZSB0aGUgdmFsdWUgd291bGQgYWx3YXlzIGJlIGFuIGBhcnJheWAuXG4gKlxuICogTGlzdFVuaXQgaXMgYmFzZWQgb24gYGFycmF5YCwgaXQgaW1wbGVtZW50cyBhbGwgdGhlIGBBcnJheS5wcm90b3R5cGVgIG1ldGhvZHMgdGhhdCBhcmUgYXZhaWxhYmxlXG4gKiBpbiB0aGUgZW52aXJvbm1lbnQvYnJvd3NlciBpdHMgcnVubmluZywgaW5jbHVkaW5nIHBvbHlmaWxscy5cbiAqIGUuZy46IGBmaW5kYCwgYGZpbHRlcmAsIGBpbmNsdWRlc2AsIGV0Yy5cbiAqXG4gKiBMZWFybiBtb3JlIGFib3V0IFVuaXRzIFtoZXJlXShodHRwczovL2RvY3MuYWN0aXZlanMuZGV2L2Z1bmRhbWVudGFscy91bml0cykuIFxcXG4gKiBMZWFybiBtb3JlIGFib3V0IExpc3RVbml0IFtoZXJlXShodHRwczovL2RvY3MuYWN0aXZlanMuZGV2L2Z1bmRhbWVudGFscy91bml0cy9saXN0dW5pdCkuXG4gKlxuICogSnVzdCBsaWtlIGV2ZXJ5IG90aGVyIE5vbi1QcmltaXRpdmUgQWN0aXZlSlMgVW5pdDpcbiAqIC0gTGlzdFVuaXQgZXh0ZW5kcyB7QGxpbmsgTm9uUHJpbWl0aXZlVW5pdEJhc2V9XG4gKiAtIFdoaWNoIGZ1cnRoZXIgZXh0ZW5kcyB7QGxpbmsgVW5pdEJhc2V9LCB7QGxpbmsgQmFzZX0gYW5kIGBPYnNlcnZhYmxlYFxuICpcbiAqIEBjYXRlZ29yeSAxLiBVbml0c1xuICovXG5leHBvcnQgY2xhc3MgTGlzdFVuaXQ8SXRlbT4gZXh0ZW5kcyBOb25QcmltaXRpdmVVbml0QmFzZTxJdGVtW10+IHtcbiAgLyoqXG4gICAqIEBpbnRlcm5hbCBwbGVhc2UgZG8gbm90IHVzZS5cbiAgICovXG4gIHByb3RlY3RlZCByZWFkb25seSBldmVudHNTdWJqZWN0OiBTdWJqZWN0PExpc3RVbml0RXZlbnRzPEl0ZW0+PjtcbiAgLyoqXG4gICAqIE9uLWRlbWFuZCBvYnNlcnZhYmxlIGV2ZW50cy5cbiAgICovXG4gIHJlYWRvbmx5IGV2ZW50cyQ6IE9ic2VydmFibGU8TGlzdFVuaXRFdmVudHM8SXRlbT4+O1xuXG4gIC8qKlxuICAgKiBUaGUgbGVuZ3RoIG9mIHRoZSBsaXN0LXtAbGluayB2YWx1ZX0uXG4gICAqL1xuICBnZXQgbGVuZ3RoKCk6IG51bWJlciB7XG4gICAgcmV0dXJuIHRoaXMucmF3VmFsdWUoKS5sZW5ndGg7XG4gIH1cblxuICAvKipcbiAgICogSW5kaWNhdGVzIHdoZXRoZXIgdGhlIGxlbmd0aCBvZiB0aGUgbGlzdC17QGxpbmsgdmFsdWV9IGlzIGAwYCBvciBub3QuXG4gICAqL1xuICBnZXQgaXNFbXB0eSgpOiBib29sZWFuIHtcbiAgICByZXR1cm4gdGhpcy5sZW5ndGggPT09IDA7XG4gIH1cblxuICAvKipcbiAgICogQGludGVybmFsIHBsZWFzZSBkbyBub3QgdXNlLlxuICAgKi9cbiAgcHJvdGVjdGVkIGRlZmF1bHRWYWx1ZSgpOiBJdGVtW10ge1xuICAgIHJldHVybiBbXTtcbiAgfVxuXG4gIGNvbnN0cnVjdG9yKGNvbmZpZz86IFVuaXRDb25maWc8SXRlbVtdPikge1xuICAgIHN1cGVyKHtcbiAgICAgIC4uLkNvbmZpZ3VyYXRpb24uTElTVF9VTklULFxuICAgICAgLi4uY29uZmlnLFxuICAgIH0pO1xuXG4gICAgbWFrZU5vbkVudW1lcmFibGUodGhpcyk7XG4gIH1cblxuICAvKipcbiAgICogRXh0ZW5kcyB7QGxpbmsgVW5pdEJhc2Uud291bGREaXNwYXRjaH0gYW5kIGFkZHMgYWRkaXRpb25hbCBjaGVjayBmb3IgdHlwZSBgYXJyYXlgIHtAc2VlIHtAbGluayBBcnJheS5pc0FycmF5fX0sIFxcXG4gICAqIHdoaWNoIGNhbm5vdCBiZSBieXBhc3NlZCBldmVuIGJ5IHVzaW5nIHtAbGluayBmb3JjZX0uXG4gICAqXG4gICAqIEBwYXJhbSB2YWx1ZSBUaGUgdmFsdWUgdG8gYmUgZGlzcGF0Y2hlZC5cbiAgICogQHBhcmFtIGZvcmNlIFdoZXRoZXIgZGlzcGF0Y2gtY2hlY2tzIHNob3VsZCBiZSBieXBhc3NlZCBvciBub3QuXG4gICAqIEByZXR1cm5zIEEgYm9vbGVhbiBpbmRpY2F0aW5nIHdoZXRoZXIgdGhlIHBhcmFtIGB2YWx1ZWAgd291bGQgcGFzcyB0aGUgZGlzcGF0Y2gtY2hlY2tzIGlmIGRpc3BhdGNoZWQuXG4gICAqXG4gICAqIEBjYXRlZ29yeSBDb21tb24gVW5pdHNcbiAgICovXG4gIHdvdWxkRGlzcGF0Y2godmFsdWU6IEl0ZW1bXSwgZm9yY2U/OiBib29sZWFuKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIHRoaXMuaXNWYWxpZFZhbHVlKHZhbHVlKSAmJiBzdXBlci53b3VsZERpc3BhdGNoKHZhbHVlLCBmb3JjZSk7XG4gIH1cblxuICAvKipcbiAgICogU2V0cyB0aGUgaXRlbSBvbiB0aGUgZ2l2ZW4gaW5kZXggaW4gdGhlIGxpc3Qte0BsaW5rIHZhbHVlfS4gXFxcbiAgICogRGlzcGF0Y2hlcyBhIG5ldyBjb3B5IG9mIHRoZSB2YWx1ZSwgd2l0aG91dCBtdXRhdGluZyB0aGUgY3VycmVudC12YWx1ZS5cbiAgICpcbiAgICogSXQgb25seSB3b3JrcyBpZiB0aGUgVW5pdCBpcyBub3QgZnJvemVuLCBhbmQgdGhlIGluZGV4IGlzIHZhbGlkLW51bWVyaWMuXG4gICAqXG4gICAqIEBwYXJhbSBpbmRleCBUaGUgaW5kZXggb2YgdGhlIGl0ZW0uIFxcXG4gICAqIEEgbmVnYXRpdmUgaW5kZXggaXMgbm9ybWFsaXplZCBhcyBmb2xsb3dpbmc6IFxcXG4gICAqIC0gSWYgdGhlIGluZGV4IGlzIGxlc3MgdGhhbiBuZWdhdGl2ZSBvZiBsaXN0LXtAbGluayBsZW5ndGh9IGl0J3MgdHJlYXRlZCBhcyAwLlxuICAgKiAtIE90aGVyd2lzZSwgaXQncyBzdWJ0cmFjdGVkIGZyb20gdGhlIGxpc3Qte0BsaW5rIGxlbmd0aH0uXG4gICAqIGVnOiBJZiBsaXN0IGxlbmd0aCBpcyBgNWAsIG5lZ2F0aXZlIGluZGV4IGAtNWAgd2lsbCBiZSBub3JtYWxpemVkIHRvIGAwYCwgXFxcbiAgICogbmVnYXRpdmUgaW5kZXggYC02YCB3aWxsIGJlIG5vcm1hbGl6ZWQgdG8gYDBgLCBhbmQgc28gb24uXG4gICAqIEBwYXJhbSBpdGVtIFRoZSBpdGVtLlxuICAgKlxuICAgKiBAdHJpZ2dlcnMge0BsaW5rIEV2ZW50TGlzdFVuaXRTZXR9XG4gICAqIEBjYXRlZ29yeSBDdXN0b20gTGlzdFVuaXRcbiAgICovXG4gIHNldChpbmRleDogbnVtYmVyLCBpdGVtOiBJdGVtKTogdm9pZCB7XG4gICAgaWYgKHRoaXMuaXNGcm96ZW4gfHwgIWlzVmFsaWRJbmRleChpbmRleCkpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgdGhpcy5jaGVja1NlcmlhbGl6YWJpbGl0eU1heWJlKGl0ZW0pO1xuXG4gICAgY29uc3QgbGlzdFNoYWxsb3dDb3B5ID0gWy4uLnRoaXMucmF3VmFsdWUoKV07XG4gICAgbGlzdFNoYWxsb3dDb3B5W25vcm1hbGl6ZUluZGV4KGluZGV4LCB0aGlzLmxlbmd0aCldID0gdGhpcy5kZWVwQ29weU1heWJlKGl0ZW0pO1xuICAgIHRoaXMudXBkYXRlVmFsdWVBbmRDYWNoZShsaXN0U2hhbGxvd0NvcHkpO1xuXG4gICAgaWYgKHRoaXMuZXZlbnRzU3ViamVjdD8ub2JzZXJ2ZXJzLmxlbmd0aCAmJiAhdGhpcy5pc011dGVkKSB7XG4gICAgICB0aGlzLmV2ZW50c1N1YmplY3QubmV4dChuZXcgRXZlbnRMaXN0VW5pdFNldChpbmRleCwgaXRlbSkpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBJbnNlcnRzIGl0ZW1zIHRvIHRoZSBsaXN0LXtAbGluayB2YWx1ZX0uIFxcXG4gICAqIERpc3BhdGNoZXMgYSBuZXcgY29weSBvZiB0aGUgdmFsdWUsIHdpdGhvdXQgbXV0YXRpbmcgdGhlIGN1cnJlbnQtdmFsdWUuXG4gICAqXG4gICAqIEl0IG9ubHkgd29ya3MgaWYgdGhlIFVuaXQgaXMgbm90IGZyb3plbiBhbmQgdGhlcmUncyBhdCBsZWFzdCAxIGl0ZW0gdG8gYmUgaW5zZXJ0ZWQuXG4gICAqXG4gICAqIEBwYXJhbSBzdGFydCBUaGUgemVyby1iYXNlZCBpbmRleCBpbiB0aGUgbGlzdCBmcm9tIHdoaWNoIHRvIHN0YXJ0IGFkZGluZyBpdGVtcy4gXFxcbiAgICogQSBuZWdhdGl2ZSBpbmRleCBpcyBub3JtYWxpemVkIGFzIGZvbGxvd2luZzogXFxcbiAgICogLSBJZiB0aGUgaW5kZXggaXMgbGVzcyB0aGFuIG5lZ2F0aXZlIG9mIGxpc3Qte0BsaW5rIGxlbmd0aH0gaXQncyB0cmVhdGVkIGFzIDAuXG4gICAqIC0gT3RoZXJ3aXNlLCBpdCdzIHN1YnRyYWN0ZWQgZnJvbSB0aGUgbGlzdC17QGxpbmsgbGVuZ3Rof