UNPKG

@bespunky/angular-zen

Version:

The Angular tools you always wished were there.

420 lines 77 kB
import { of, forkJoin, BehaviorSubject, EMPTY, animationFrames, interval } from 'rxjs';
import { delay, finalize, map, mapTo, materialize, switchMap, takeWhile, tap, startWith, filter } from 'rxjs/operators';
import { Directive, TemplateRef, ViewContainerRef } from '@angular/core';
import { Destroyable } from '../../destroyable/destroyable';
import { breakdownTime, durationToMs } from '../utils/time-utils';
import { OnObserverContext } from './types/on-observer-context';
import { ObserverCall } from './types/observer-call';
import { ViewRenderCommitment } from './types/view-render-commitment';
import * as i0 from "@angular/core";
/**
 * The default number of times the countdown will be updated in a rendered view waiting to be auto-destroyed.
 * To change this, the user will have to specify a value for the {@link OnObserverBaseDirective.countdownInterval `countdownInterval`} property.
 **/
const DefaultCountdownUpdateCount = 30;
/**
 * Provides functionality for `*onObserver<state>` directives that render templates according to the state of an observable.
 *
 * Any template assigned with the directive will render when the defined observer calls are intercepted, and destroyed when any other calls are
 * intercepted. For example, if the directive intercepts `next` calls, the view will render on the first value emission, then destroy on
 * `complete` or `error`.
 *
 * ## Features
 *
 * #### View Context
 * Use the microsyntax `as` keyword to assign resolved values to a variable.
 * Use the microsyntax `let` keyword to assign the {@link OnObserverContext full context object} to a variable (e.g. `let context`).
 *
 * #### Delayed rendering
 * Specify a value for {@link OnObserverBaseDirective.showAfter `showAfter`} to delay rendering.
 *
 * #### Auto destroy
 * Specify {@link OnObserverBaseDirective.showFor `showFor`} to automatically destroy the view after a certain duration.
 *
 * #### Countdown updates
 * When {@link OnObserverBaseDirective.showFor `showFor`} is specified, the view context will be updated with the time remaining until the view
 * is destroyed and the time elapsed since it was rendered. This allows giving the user feedback in a progress bar, a spinner, a textual timer
 * or any other UI component.
 *
 * Remaining is provided by the {@link OnObserverContext.remaining `remaining`} property. Elapsed time is provided by the {@link OnObserverContext.elapsed `elapsed`}
 * property. Access it by assigning a variable using `let`, like so:
 * `let remaining = remaining`
 *
 * #### Multi view mode
 * Specify {@link OnObserverBaseDirective.viewMode `viewMode = 'multiple'`} to enable rendering a new view for each intercepted call
 * instead of updating a single rendered view. This allows stacking logs, notification snackbars, or any other aggregation functionality.
 * Combined with {@link OnObserverBaseDirective.showFor `showFor`}, this is great for disappearing messages/notifications.
 *
 * #### View index
 * In multi-view mode, the context will contain the index of the view, which can be used for calculations and styling.
 *
 * #### Multi call interception
 * Create different interception combinations by specifying more than one call name in {@link OnObserverBaseDirective.renderOnCallsTo `renderOnCallsTo`}.
 * This allows, for example, the combination of `'error'` and `'complete'` to create a directive named `*onObserverFinalized`.
 *
 * ## Extending
 * As this base class doesn't know what the properties of the extending class will be, extending classes must:
 * 1. Define their selector in the abstract {@link OnObserverBaseDirective.selector `selector`} property. This will allow the directive to
 * assign the view context with a property which will enable the microsyntax `as` keyword.
 * 2. Define the call(s) to intercept and render the view for in the abstract {@link OnObserverBaseDirective.renderOnCallsTo `renderOnCallsTo`}.
 * 3. Define an `@Input() set <selector>` property which will call `this.input.next(value)`.
 * 4. Define an `@Input() set <selector>ViewMode` property which will set `this.viewMode`.
 * 5. Define an `@Input() set <selector>ShowAfter` property which will set `this.showAfter`.
 * 6. Define an `@Input() set <selector>ShowFor` property which will set `this.showFor`.
 * 7. Define an `@Input() set <selector>CountdownInterval` property which will set `this.countdownInterval`.
 * 8. Define this static context type guard to allow strong typing the template:
 * ```ts
 * static ngTemplateContextGuard<T>(directive: ___DIRECTIVE_NAME___<T>, context: unknown): context is OnObserverContext<T>
 * { return true; }
 * ```
 *
 * @export
 * @abstract
 * @class OnObserverBaseDirective
 * @extends {Destroyable}
 * @implements {OnInit}
 * @template T The type of value the observable will emit.
 */
export class OnObserverBaseDirective extends Destroyable {
    constructor(template, viewContainer) {
        super();
        this.template = template;
        this.viewContainer = viewContainer;
        /**
         * A global commitment map holding all commitments to render for which the directive has created commitment observables.
         * Ids are the timestamp of the observed calls and values are the commitments with their rendering parameters.
         *
         * @private
         * @type {RenderCommitmentMap<T>}
         */
        this.commitments = new Map();
        /**
         * (Optional) The view mode the directive will operate in:
         * `'single'` - A single view will be rendered on intercepted calls. If a view has already been rendered when a call is intercepted,
         * the existing view will be updated with data from the new call.
         *
         * `'multiple'` - Every new intercepted call will render a new view with its own context and data encapsulated from the current call.
         *
         * Default is `'single'`.
         *
         * ⚠️ Extending classes should:
         * 1. Declare an `@Input()` setter named `{selector}ViewMode` (e.g. onObserverCompleteViewMode) which will set this value.
         * 2. Provide the above documentation for the setter property.
         *
         * @default 'single'
         * @protected
         * @type {ViewMode}
         */
        this.viewMode = 'single';
        /**
         * (Optional) The duration for which the directive should wait before rendering the view once an intercepted call is made.
         *
         * You can specify a number, which will be treated as milliseconds, or a string with the format of `<number><ms | s | ms>`.
         * Numbers can be either integers or floats.
         * For example:
         * - `3000` - Wait for 3 seconds, then render the view.
         * - `'10s'` - Wait for 10 seconds, then render the view.
         * - `'0.5m'` - Wait for 30 seconds, then render the view.
         * - `'100ms'` - Wait for 100 milliseconds, then render the view.
         *
         * Default is `0`, meaning immediately render the view.
         *
         * TODO: ADD LINK TO TOUR OR FULL WIKI PAGE
         * Read more {@link OnObserverBaseDirective About render flow}.
         *
         * ⚠️ Extending classes should:
         * 1. Declare an `@Input()` setter named `{selector}ShowAfter` (e.g. onObserverCompleteShowAfter) which will set this value.
         * 2. Provide the above documentation for the setter property.
         *
         * @protected
         * @type {DurationAnnotation}
         */
        this.showAfter = 0;
        /**
         * ### Why BehaviorSubject<... | null> and not Subject<...>
         * `input` is set from @Input properties. For some reason, Angular passes-in the first value BEFORE
         * ngOnInit, even though other @Input properties (e.g. showAfter, showFor) are passed AFTER ngOnInit.
         * If subscription occurs in the constructor, `input` will emit the first observable too fast, which
         * might lead to pipes breaking or misbehaving if they rely on properties to be instantiated first.
         *
         * This leads to subscribing in ngOnInit, to allow Angular time to initialize those.
         * BUT, if `input` is a Subject, as the first value was already emitted BEFORE ngOnInit, it will not be
         * captured by our subscription to `input`. Hence the BehaviorSubject - To allow capturing that first observable.
         */
        this.input = new BehaviorSubject(null);
    }
    /**
     * The first commitment in the {@link OnObserverBaseDirective.commitments global commitments map}. Used when working with a single view
     * to retrieve its corresponding single commitment.
     *
     * @readonly
     * @private
     * @type {ViewRenderCommitment<T> | undefined}
     */
    get mainCommitment() { return this.commitments.values().next().value; }
    /**
     * `true` if {@link OnObserverBaseDirective.viewMode viewMode} is `'single'`; otherwise, `false`.
     *
     * @readonly
     * @type {boolean}
     */
    get isSingleView() { return this.viewMode === 'single'; }
    /**
     * `true` if {@link OnObserverBaseDirective.viewMode viewMode} is `'multiple'`; otherwise, `false`.
     *
     * @readonly
     * @type {boolean}
     */
    get isMultiView() { return this.viewMode === 'multiple'; }
    ngOnInit() {
        // See `this.input` documentation for why subscription is done in ngOnInit.
        this.subscribe(this.renderFeed());
    }
    /**
     * Destroys any rendered view.
     *
     * @private
     */
    destroyAll() {
        this.commitments.forEach(({ view }) => view?.destroy());
    }
    /**
     * Creates the main feed the directive will subscribe to. The feed will listen to changed to {@link OnObserverBaseDirective.input `input`},
     * then switch to the newly received observable in order to start observing it.
     * The newly received observable will then be materialized and calls will be aggregated as commitment objects with information about
     * what to render and when. Those commitments will pass through the {@link OnObserverBaseDirective.onCommitmentsChanged onCommitmentsChanged()} method
     * which will update the global commitment and create observables with commitments to render and auto destroy views according to the
     * given commitments.
     *
     * This feed is the single reactive entrypoint, meaning any observable created by the directive will be created inside of this
     * observable or its nested observables. Any time a nested observable is created it will be switched to. This allows the pipeline to
     * completely startover when a new call is made or a new {@link OnObserverBaseDirective.input `input`} observable is provided, thus keeping
     * a consistent stream of data to the {@link OnObserverBaseDirective.commitments global commitments map}.
     *
     * @private
     * @return {Observable<ViewRenderCommitment<T>[]>} An observable as described above.
     */
    renderFeed() {
        return this.input.pipe(
        // Make sure views are reset if a new observable is passed-in to the directive
        tap(() => this.destroyAll()), 
        // Free memory once the directive is destroyed and the subscription closed
        // TODO: Will this actually execute? `this.subscribe()` doesn't complete the observable but unsubscribes on component destruction
        finalize(() => this.destroyAll()), switchMap(input => input ? this.observeInput(input) : EMPTY), map(call => this.shouldRender(call) ? this.aggregateCommitments(call) : this.deaggregateCommitments()), switchMap(commitments => this.onCommitmentsChanged(commitments)));
    }
    /**
     * Materializes the observable and converts notifications to an {@link ObserverCall} object.
     * The returned observable will always start with a `'resolving'` call.
     *
     * @private
     * @param {Observable<T>} input The observable to watch.
     * @return {Observable<ObserverCall<T>>} A materialized observable which describes each observable notification as an {@link ObserverCall} object.
     */
    observeInput(input) {
        return input.pipe(materialize(), map(ObserverCall.fromNotification), startWith(ObserverCall.resolving()));
    }
    /**
     * Checks whether the given observer call should be rendered according to the interception config in {@link OnObserverBaseDirective.renderOnCallsTo renderOnCallsTo}.
     *
     * @private
     * @param {ObserverCall<T>} The call to check.
     * @return {boolean} `true` if the call should be rendered; otherwise `false`.
     */
    shouldRender({ name }) {
        const observeOn = Array.isArray(this.renderOnCallsTo) ? this.renderOnCallsTo : [this.renderOnCallsTo];
        return observeOn.includes(name);
    }
    /**
     * Creates the new commitments map when a new commitment should render.
     *
     * When `viewMode` is `'single'` the map will always contain a single commitment. If the commitment hasn't been rendered yet, a new commitment will be created.
     * Otherwise, the existing commitment will be replaced by a clone with updated parameters (i.e. delay and countdown).
     *
     * When `viewMode` is `'multiple'` a new commitment will always be added to the map.
     *
     * @private
     * @param {ObserverCall<T>} call The new call which should render.
     * @return {RenderCommitmentMap<T>} The new map of commitments to render.
     */
    aggregateCommitments(call) {
        const commitments = this.commitments;
        // In single-view mode, if there's already a commitment, we'll replace it with a new one. Otherwise, we'll create a fresh one.
        const newCommitment = this.isSingleView && this.mainCommitment
            ? ViewRenderCommitment.update(this.mainCommitment, call)
            : ViewRenderCommitment.create(call, durationToMs(this.showAfter), durationToMs(this.showFor || 0));
        return new Map(commitments.set(newCommitment.commitmentId, newCommitment));
    }
    /**
     * Creates the new commitments map when a new commitment shouldn't render.
     *
     * @private
     * @return {RenderCommitmentMap<T>} If `showFor` is specified, meaning views should be auto destroyed after a certain duration,
     * the current commitments will kept alive by returning them as a new map. This will allow recommiting to the same render parameters (i.e. delay and countdown).
     * Otherwise, when views should destroy immediately, an empty map will be returned.
     */
    deaggregateCommitments() {
        return this.showFor ? new Map(this.commitments) : new Map();
    }
    /**
     * Handles the changes to the current commitment of the watched observable and creates and commits to render all commitments.
     *
     * This will update the global commitment map. If an empty map is passed, all previous commitments will be destroyed.
     *
     * @private
     * @param {RenderCommitmentMap<T>} commitments The current commitment map.
     * @return {Observable<ViewRenderCommitment<T>[]>} An observable joining all render commitments.
     */
    onCommitmentsChanged(commitments) {
        // If the commitment map has been reset, destroy any previously rendered view
        if (!commitments.size)
            this.destroyAll();
        // Update the global commitment map
        this.commitments = commitments;
        // Map all commitments to a commitment to render observable
        const runCommitments = Array.from(commitments.keys())
            .map((commitmentId, index) => this.commitToRender(commitments, commitmentId, index));
        return forkJoin(runCommitments);
    }
    /**
     * Creates an observable that initiates the render flow for an emission. Render flow is as follows:
     * 1. Delay render until the time for render (i.e. {@link ViewRenderCommitment.renderAt}) has come.
     * 2. Render the view.
     * 3. Update the {@link OnObserverBaseDirective.commitments global commitments map} with the rendered commitment.
     * 4. Initiate an auto destroy timer. See {@link OnObserverBaseDirective.autoDestroy autoDestroy()}.
     * 5. Remove the destroyed commitment from the {@link OnObserverBaseDirective.commitments global commitments map}.
     *
     * @private
     * @param {RenderCommitmentMap<T>} commitments The current commitment map holding all commitments to render.
     * @param {string} commitmentId The id of the commitment to render.
     * @param {number} index The index of the view to be rendered.
     * @return {Observable<ViewRenderCommitment<T>>} An observable that initiates the render flow for an emission.
     */
    commitToRender(commitments, commitmentId, index) {
        if (!commitments.has(commitmentId))
            throw new Error(`
            *${this.selector} has encountered an inconsistency issue. Tried to commit to rendering commitment with ID ${commitmentId}, but no commitment object exists with that ID.
            Please consider filing an issue and providing a stack trace here: https://github.com/BeSpunky/angular-zen/issues/new?assignees=BeSpunky&labels=%F0%9F%90%9B+Bug&template=bug_report.md&title=%F0%9F%90%9B+
        `);
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        return of(commitments.get(commitmentId)).pipe(switchMap(commitment => this.delayRender(commitment)), 
        // Actually perform rendering (or update the view if already rendered)
        switchMap(commitment => this.renderCommitment(commitment, index)), 
        // The commitment returned from `renderCommitment()` now contains the view, so update the global map
        tap(renderedCommitment => commitments.set(commitmentId, renderedCommitment)), 
        // Initiate the auto-destroy mechanism (will skip if `showFor` wasn't specified)
        switchMap(renderedCommitment => this.autoDestroy(renderedCommitment)), 
        // Commitment has completed successfully. Remove it from the global map.
        tap(renderedCommitment => renderedCommitment.autoDestroys ? commitments.delete(commitmentId) : void 0));
    }
    /**
     * Creates an observable which delays the pipeline until the time to render the view (i.e. {@link ViewRenderCommitment.renderAt}) comes.
     *
     * @private
     * @param {ViewRenderCommitment<T>} commitment The commitment to delay.
     * @return {Observable<ViewRenderCommitment<T>>} An observable which delays the pipeline until the time to render the view (i.e. {@link ViewRenderCommitment.renderAt}) comes.
     */
    delayRender(commitment) {
        return of(commitment).pipe(delay(new Date(commitment.renderAt)));
    }
    /**
     * Creates a new context for the given commitment and renders (or updates) the view.
     *
     * @private
     * @param {ViewRenderCommitment<T>} commitment The commitment for which to create the context and render the view.
     * @param {number} index The index of the view. If `viewMode` is `'single'` this should always be `0` as there is only one view.
     * @return {Observable<ViewRenderCommitment<T>>} An observable which renderes the commitment, then emits a new updated commitment referencing the rendered view.
     */
    renderCommitment(commitment, index) {
        const context = OnObserverContext.fromCommitment(this.selector, index, commitment);
        const renderedCommitment = this.renderOrUpdateView(commitment, context);
        return of(renderedCommitment);
    }
    /**
     * Creates an interval observable which counts down until the time to destroy the view is reached, then destroys the view.
     * While the timer is running, the rendered view's context will be updated in fixed intervals with the time left before destruction.
     *
     * @see {@link OnObserverBaseDirective.defineCountdownInterval defineCountdownInterval()} for more about the fixed countdown interval.
     *
     * If {@link OnObserverBaseDirective.countdownInterval `countdownInterval`} is `'animationFrames'`, the rxjs `animationFrames()` function
     * will be used instead of the interval.
     *
     * @private
     * @param {ViewRenderCommitment<T>} commitment The rendered commitment for which to initiate auto destroy.
     * @return {Observable<ViewRenderCommitment<T>>} A timer observable which counts down until the time to destroy the view is reached, then destroys the view, while
     * updating the context with the time left for destruction. The observable will emit the commitment
     */
    autoDestroy(commitment) {
        const { destroyAt, view } = commitment;
        if (!(destroyAt && view))
            return of(commitment);
        const countdownInterval = this.defineCountdownInterval();
        const countdown = countdownInterval === 'animationFrames' ? animationFrames() : interval(countdownInterval);
        return countdown.pipe(map(() => destroyAt - Date.now()), map(timeLeftMs => [timeLeftMs < 0 ? 0 : timeLeftMs, commitment.showFor - timeLeftMs]), tap(([timeLeftMs, elapsedTimeMs]) => this.updateViewContextCountdown(view, timeLeftMs, elapsedTimeMs)), takeWhile(([timeLeftMs]) => timeLeftMs > 0, true), filter(([timeLeftMs]) => timeLeftMs <= 0), tap(() => view.destroy()), mapTo(commitment));
    }
    /**
     * Makes sure the specified commitment is rendered and its context is updated, then returns an updated commitment with the rendered (or updated) view.
     * If the view has been previously rendered, its context will be updated. Otherwise, the view will be rendered for the first time.
     *
     * The new commitment will be used further down the pipeline to update the internal `commitments` map.
     *
     * @see {@link OnObserverBaseDirective.commitToRender `commitToRender()`}.
     *
     * @private
     * @param {ViewRenderCommitment<T>} commitment The commitment for which the view should be rendered.countdown
     * @param {OnObserverContext<T>} context The context object to feed into the view.
     * @return {ViewRenderCommitment<T>} The new commitment containing the rendered (or updated) view.
     */
    renderOrUpdateView(commitment, context) {
        if (commitment.view) {
            commitment.view.context = context;
            return ViewRenderCommitment.rendered(commitment, commitment.view);
        }
        return ViewRenderCommitment.rendered(commitment, this.viewContainer.createEmbeddedView(this.template, context));
    }
    /**
     * Breaks down the time left before the view is destroyed to its parts and updates the view context so that the user may present
     * a countdown or any other UI component indicating when the view will be destroyed.
     *
     * @private
     * @param {RenderedView<T>} view The view in which to update the countdown.
     * @param {number} timeLeftMs The time left (in milliseconds) for the view before being destroyed.
     * @param {number} timeElapsedMs The time elapsed (in milliseconds) from the moment the view was rendered.
     */
    updateViewContextCountdown(view, timeLeftMs, timeElapsedMs) {
        const remaining = breakdownTime(timeLeftMs);
        const elapsed = breakdownTime(timeElapsedMs);
        const { $implicit, call, index } = view.context;
        // TODO: Remove the `showingFor` argument when launching v6.0.0
        view.context = new OnObserverContext(this.selector, index, call, $implicit, remaining, remaining, elapsed);
    }
    /**
     * Defines the interval (in milliseconds) with which countdown updates should be made to the view's context.
     * If the user has defined a value through {@link OnObserverBaseDirective.countdownInterval `countdownInterval`}, that value will be used.
     * If the user has defined `'animationFrames'` as the value for {@link OnObserverBaseDirective.countdownInterval `countdownInterval`}, this will return `'animationFrames'`.
     * Otherwise, {@link OnObserverBaseDirective.showFor `showFor`} will be divided by a fixed number defined by {@link DefaultCountdownUpdateCount `DefaultCountdownUpdateCount`}, currently 30, meaning the user will get
     * 30 countdown updates with fixed intervals between them before the view is destroyed.
     *
     * @private
     * @return {number} The interval with which countdown updates should be made to the view's context.
     */
    defineCountdownInterval() {
        // If the view should persist, or it should auto-destroy but percision has been manually specified, do nothing
        if (!this.showFor)
            throw new Error(`
            Auto-destroy countdown seems to have been initiated when 'showFor' hasn't been set. This shouldn't have happend.
            Please consider filing an issue and providing a stack trace here: https://github.com/BeSpunky/angular-zen/issues/new?assignees=BeSpunky&labels=%F0%9F%90%9B+Bug&template=bug_report.md&title=%F0%9F%90%9B+
        `);
        if (this.countdownInterval === 'animationFrames')
            return 'animationFrames';
        if (this.countdownInterval)
            return durationToMs(this.countdownInterval);
        const showForMs = durationToMs(this.showFor);
        return showForMs / DefaultCountdownUpdateCount;
    }
}
OnObserverBaseDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverBaseDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Directive });
OnObserverBaseDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: OnObserverBaseDirective, usesInheritance: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: OnObserverBaseDirective, decorators: [{
            type: Directive
        }], ctorParameters: function () { return [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }]; } });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib24tb2JzZXJ2ZXItYmFzZS5kaXJlY3RpdmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9saWJzL2FuZ3VsYXItemVuL2NvcmUvc3JjL3J4anMvb24tb2JzZXJ2ZXIvYWJzdHJhY3Rpb24vb24tb2JzZXJ2ZXItYmFzZS5kaXJlY3RpdmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFjLEVBQUUsRUFBRSxRQUFRLEVBQUUsZUFBZSxFQUFFLEtBQUssRUFBRSxlQUFlLEVBQUUsUUFBUSxFQUFhLE1BQU0sTUFBTSxDQUFDO0FBQzlHLE9BQU8sRUFBRSxLQUFLLEVBQUUsUUFBUSxFQUFFLEdBQUcsRUFBRSxLQUFLLEVBQUUsV0FBVyxFQUFFLFNBQVMsRUFBRSxTQUFTLEVBQUUsR0FBRyxFQUFFLFNBQVMsRUFBRSxNQUFNLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUN4SCxPQUFPLEVBQUUsU0FBUyxFQUFVLFdBQVcsRUFBRSxnQkFBZ0IsRUFBd0MsTUFBTSxlQUFlLENBQUM7QUFFdkgsT0FBTyxFQUFFLFdBQVcsRUFBNkUsTUFBTSwrQkFBK0IsQ0FBQztBQUV2SSxPQUFPLEVBQUUsYUFBYSxFQUFFLFlBQVksRUFBNkQsTUFBTSxxQkFBcUIsQ0FBQztBQUM3SCxPQUFPLEVBQUUsaUJBQWlCLEVBQXVFLE1BQU0sNkJBQTZCLENBQUM7QUFDckksT0FBTyxFQUFFLFlBQVksRUFBNEUsTUFBTSx1QkFBdUIsQ0FBQztBQUMvSCxPQUFPLEVBQUUsb0JBQW9CLEVBQW9FLE1BQU0sZ0NBQWdDLENBQUM7O0FBRXhJOzs7SUFHSTtBQUNKLE1BQU0sMkJBQTJCLEdBQUcsRUFBRSxDQUFDO0FBRXZDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQThERztBQUVILE1BQU0sT0FBZ0IsdUJBQTJCLFNBQVEsV0FBVztJQXVLaEUsWUFBNkIs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LE1BQU0sT0FBTyxHQUFLLGFBQWEsQ0FBQyxhQUFhLENBQUMsQ0FBQztRQUUvQyxNQUFNLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDO1FBRWhELCtEQUErRDtRQUMvRCxJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksaUJBQWlCLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxTQUFTLEVBQUUsU0FBUyxFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQy9HLENBQUM7SUFFRDs7Ozs7Ozs7O09BU0c7SUFDSyx1QkFBdUI7UUFFM0IsOEdBQThHO1FBQzlHLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTztZQUFFLE1BQU0sSUFBSSxLQUFLLENBQUM7OztTQUdsQyxDQUFDLENBQUM7UUFFSCxJQUFJLElBQUksQ0FBQyxpQkFBaUIsS0FBSyxpQkFBaUI7WUFBRSxPQUFPLGlCQUFpQixDQUFDO1FBRTNFLElBQUksSUFBSSxDQUFDLGlCQUFpQjtZQUFFLE9BQU8sWUFBWSxDQUFDLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO1FBRXhFLE1BQU0sU0FBUyxHQUFHLFlBQVksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUM7UUFFN0MsT0FBTyxTQUFTLEdBQUcsMkJBQTJCLENBQUM7SUFDbkQsQ0FBQzs7cUhBN2RpQix1QkFBdUI7eUdBQXZCLHVCQUF1Qjs0RkFBdkIsdUJBQXVCO2tCQUQ1QyxTQUFTIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgT2JzZXJ2YWJsZSwgb2YsIGZvcmtKb2luLCBCZWhhdmlvclN1YmplY3QsIEVNUFRZLCBhbmltYXRpb25GcmFtZXMsIGludGVydmFsICAgICAgICAgICAgfSBmcm9tICdyeGpzJztcbmltcG9ydCB7IGRlbGF5LCBmaW5hbGl6ZSwgbWFwLCBtYXBUbywgbWF0ZXJpYWxpemUsIHN3aXRjaE1hcCwgdGFrZVdoaWxlLCB0YXAsIHN0YXJ0V2l0aCwgZmlsdGVyIH0gZnJvbSAncnhqcy9vcGVyYXRvcnMnO1xuaW1wb3J0IHsgRGlyZWN0aXZlLCBPbkluaXQsIFRlbXBsYXRlUmVmLCBWaWV3Q29udGFpbmVyUmVmICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcblxuaW1wb3J0IHsgRGVzdHJveWFibGUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSBmcm9tICcuLi8uLi9kZXN0cm95YWJsZS9kZXN0cm95YWJsZSc7XG5pbXBvcnQgeyBPYnNlcnZlck5hbWUsIER1cmF0aW9uQW5ub3RhdGlvbiwgUmVuZGVyQ29tbWl0bWVudE1hcCwgVmlld01vZGUsIFJlbmRlcmVkVmlldyAgICAgICAgICB9IGZyb20gJy4uL2Fic3RyYWN0aW9uL3R5cGVzL2dlbmVyYWwnO1xuaW1wb3J0IHsgYnJlYWtkb3duVGltZSwgZHVyYXRpb25Ub01zICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSBmcm9tICcuLi91dGlscy90aW1lLXV0aWxzJztcbmltcG9ydCB7IE9uT2JzZXJ2ZXJDb250ZXh0ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0gZnJvbSAnLi90eXBlcy9vbi1vYnNlcnZlci1jb250ZXh0JztcbmltcG9ydCB7IE9ic2VydmVyQ2FsbCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0gZnJvbSAnLi90eXBlcy9vYnNlcnZlci1jYWxsJztcbmltcG9ydCB7IFZpZXdSZW5kZXJDb21taXRtZW50ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0gZnJvbSAnLi90eXBlcy92aWV3LXJlbmRlci1jb21taXRtZW50JztcblxuLyoqXG4gKiBUaGUgZGVmYXVsdCBudW1iZXIgb2YgdGltZXMgdGhlIGNvdW50ZG93biB3aWxsIGJlIHVwZGF0ZWQgaW4gYSByZW5kZXJlZCB2aWV3IHdhaXRpbmcgdG8gYmUgYXV0by1kZXN0cm95ZWQuXG4gKiBUbyBjaGFuZ2UgdGhpcywgdGhlIHVzZXIgd2lsbCBoYXZlIHRvIHNwZWNpZnkgYSB2YWx1ZSBmb3IgdGhlIHtAbGluayBPbk9ic2VydmVyQmFzZURpcmVjdGl2ZS5jb3VudGRvd25JbnRlcnZhbCBgY291bnRkb3duSW50ZXJ2YWxgfSBwcm9wZXJ0eS5cbiAqKi9cbmNvbnN0IERlZmF1bHRDb3VudGRvd25VcGRhdGVDb3VudCA9IDMwO1xuXG4vKipcbiAqIFByb3ZpZGVzIGZ1bmN0aW9uYWxpdHkgZm9yIGAqb25PYnNlcnZlcjxzdGF0ZT5gIGRpcmVjdGl2ZXMgdGhhdCByZW5kZXIgdGVtcGxhdGVzIGFjY29yZGluZyB0byB0aGUgc3RhdGUgb2YgYW4gb2JzZXJ2YWJsZS5cbiAqIFxuICogQW55IHRlbXBsYXRlIGFzc2lnbmVkIHdpdGggdGhlIGRpcmVjdGl2ZSB3aWxsIHJlbmRlciB3aGVuIHRoZSBkZWZpbmVkIG9ic2VydmVyIGNhbGxzIGFyZSBpbnRlcmNlcHRlZCwgYW5kIGRlc3Ryb3llZCB3aGVuIGFueSBvdGhlciBjYWxscyBhcmVcbiAqIGludGVyY2VwdGVkLiBGb3IgZXhhbXBsZSwgaWYgdGhlIGRpcmVjdGl2ZSBpbnRlcmNlcHRzIGBuZXh0YCBjYWxscywgdGhlIHZpZXcgd2lsbCByZW5kZXIgb24gdGhlIGZpcnN0IHZhbHVlIGVtaXNzaW9uLCB0aGVuIGRlc3Ryb3kgb25cbiAqIGBjb21wbGV0ZWAgb3IgYGVycm9yYC5cbiAqIFxuICogIyMgRmVhdHVyZXNcbiAqIFxuICogIyMjIyBWaWV3IENvbnRleHRcbiAqIFVzZSB0aGUgbWljcm9zeW50YXggYGFzYCBrZXl3b3JkIHRvIGFzc2lnbiByZXNvbHZlZCB2YWx1ZXMgdG8gYSB2YXJpYWJsZS5cbiAqIFVzZSB0aGUgbWljcm9zeW50YXggYGxldGAga2V5d29yZCB0byBhc3NpZ24gdGhlIHtAbGluayBPbk9ic2VydmVyQ29udGV4dCBmdWxsIGNvbnRleHQgb2JqZWN0fSB0byBhIHZhcmlhYmxlIChlLmcuIGBsZXQgY29udGV4dGApLlxuICogIFxuICogIyMjIyBEZWxheWVkIHJlbmRlcmluZ1xuICogU3BlY2lmeSBhIHZhbHVlIGZvciB7QGxpbmsgT25PYnNlcnZlckJhc2VEaXJlY3RpdmUuc2hvd0FmdGVyIGBzaG93QWZ0ZXJgfSB0byBkZWxheSByZW5kZXJpbmcuXG4gKiBcbiAqICMjIyMgQXV0byBkZXN0cm95XG4gKiBTcGVjaWZ5IHtAbGluayBPbk9ic2VydmVyQmFzZURpcmVjdGl2ZS5zaG93Rm9yIGBzaG93Rm9yYH0gdG8gYXV0b21hdGljYWxseSBkZXN0cm95IHRoZSB2aWV3IGFmdGVyIGEgY2VydGFpbiBkdXJhdGlvbi5cbiAqIFxuICogIyMjIyBDb3VudGRvd24gdXBkYXRlc1xuICogV2hlbiB7QGxpbmsgT25PYnNlcnZlckJhc2VEaXJlY3RpdmUuc2hvd0ZvciBgc2hvd0ZvcmB9IGlzIHNwZWNpZmllZCwgdGhlIHZpZXcgY29udGV4dCB3aWxsIGJlIHVwZGF0ZWQgd2l0aCB0aGUgdGltZSByZW1haW5pbmcgdW50aWwgdGhlIHZpZXdcbiAqIGlzIGRlc3Ryb3llZCBhbmQgdGhlIHRpbWUgZWxhcHNlZCBzaW5jZSBpdCB3YXMgcmVuZGVyZWQuIFRoaXMgYWxsb3dzIGdpdmluZyB0aGUgdXNlciBmZWVkYmFjayBpbiBhIHByb2dyZXNzIGJhciwgYSBzcGlubmVyLCBhIHRleHR1YWwgdGltZXJcbiAqIG9yIGFueSBvdGhlciBVSSBjb21wb25lbnQuIFxuICogXG4gKiBSZW1haW5pbmcgaXMgcHJvdmlkZWQgYnkgdGhlIHtAbGluayBPbk9ic2VydmVyQ29udGV4dC5yZW1haW5pbmcgYHJlbWFpbmluZ2B9IHByb3BlcnR5LiBFbGFwc2VkIHRpbWUgaXMgcHJvdmlkZWQgYnkgdGhlIHtAbGluayBPbk9ic2VydmVyQ29udGV4dC5lbGFwc2VkIGBlbGFwc2VkYH1cbiAqIHByb3BlcnR5LiBBY2Nlc3MgaXQgYnkgYXNzaWduaW5nIGEgdmFyaWFibGUgdXNpbmcgYGxldGAsIGxpa2Ugc286ICBcbiAqIGBsZXQgcmVtYWluaW5nID0gcmVtYWluaW5nYFxuICogXG4gKiAjIyMjIE11bHRpIHZpZXcgbW9kZVxuICogU3BlY2lmeSB7QGxpbmsgT25PYnNlcnZlckJhc2VEaXJlY3RpdmUudmlld01vZGUgYHZpZXdNb2RlID0gJ211bHRpcGxlJ2B9IHRvIGVuYWJsZSByZW5kZXJpbmcgYSBuZXcgdmlldyBmb3IgZWFjaCBpbnRlcmNlcHRlZCBjYWxsXG4gKiBpbnN0ZWFkIG9mIHVwZGF0aW5nIGEgc2luZ2xlIHJlbmRlcmVkIHZpZXcuIFRoaXMgYWxsb3dzIHN0YWNraW5nIGxvZ3MsIG5vdGlmaWNhdGlvbiBzbmFja2JhcnMsIG9yIGFueSBvdGhlciBhZ2dyZWdhdGlvbiBmdW5jdGlvbmFsaXR5LlxuICogQ29tYmluZWQgd2l0aCB7QGxpbmsgT25PYnNlcnZlckJhc2VEaXJlY3RpdmUuc2hvd0ZvciBgc2hvd0ZvcmB9LCB0aGlzIGlzIGdyZWF0IGZvciBkaXNhcHBlYXJpbmcgbWVzc2FnZXMvbm90aWZpY2F0aW9ucy5cbiAqIFxuICogIyMjIyBWaWV3IGluZGV4XG4gKiBJbiBtdWx0aS12aWV3IG1vZGUsIHRoZSBjb250ZXh0IHdpbGwgY29udGFpbiB0aGUgaW5kZXggb2YgdGhlIHZpZXcsIHdoaWNoIGNhbiBiZSB1c2VkIGZvciBjYWxjdWxhdGlvbnMgYW5kIHN0eWxpbmcuXG4gKlxuICogIyMjIyBNdWx0aSBjYWxsIGludGVyY2VwdGlvblxuICogQ3JlYXRlIGRpZmZlcmVudCBpbnRlcmNlcHRpb24gY29tYmluYXRpb25zIGJ5IHNwZWNpZnlpbmcgbW9yZSB0aGFuIG9uZSBjYWxsIG5hbWUgaW4ge0BsaW5rIE9uT2JzZXJ2ZXJCYXNlRGlyZWN0aXZlLnJlbmRlck9uQ2FsbHNUbyBgcmVuZGVyT25DYWxsc1RvYH0uXG4gKiBUaGlzIGFsbG93cywgZm9yIGV4YW1wbGUsIHRoZSBjb21iaW5hdGlvbiBvZiBgJ2Vycm9yJ2AgYW5kIGAnY29tcGxldGUnYCB0byBjcmVhdGUgYSBkaXJlY3RpdmUgbmFtZWQgYCpvbk9ic2VydmVyRmluYWxpemVkYC5cbiAqIFxuICogIyMgRXh0ZW5kaW5nXG4gKiBBcyB0aGlzIGJhc2UgY2xhc3MgZG9lc24ndCBrbm93IHdoYXQgdGhlIHByb3BlcnRpZXMgb2YgdGhlIGV4dGVuZGluZyBjbGFzcyB3aWxsIGJlLCBleHRlbmRpbmcgY2xhc3NlcyBtdXN0OlxuICogMS4gRGVmaW5lIHRoZWlyIHNlbGVjdG9yIGluIHRoZSBhYnN0cmFjdCB7QGxpbmsgT25PYnNlcnZlckJhc2VEaXJlY3RpdmUuc2VsZWN0b3IgYHNlbGVjdG9yYH0gcHJvcGVydHkuIFRoaXMgd2lsbCBhbGxvdyB0aGUgZGlyZWN0aXZlIHRvXG4gKiBhc3NpZ24gdGhlIHZpZXcgY29udGV4dCB3aXRoIGEgcHJvcGVydHkgd2hpY2ggd2lsbCBlbmFibGUgdGhlIG1pY3Jvc3ludGF4IGBhc2Aga2V5d29yZC5cbiAqIDIuIERlZmluZSB0aGUgY2FsbChzKSB0byBpbnRlcmNlcHQgYW5kIHJlbmRlciB0aGUgdmlldyBmb3IgaW4gdGhlIGFic3RyYWN0IHtAbGluayBPbk9ic2VydmVyQmFzZURpcmVjdGl2ZS5yZW5kZXJPbkNhbGxzVG8gYHJlbmRlck9uQ2FsbHNUb2B9LlxuICogMy4gRGVmaW5lIGFuIGBASW5wdXQoKSBzZXQgPHNlbGVjdG9yPmAgcHJvcGVydHkgd2hpY2ggd2lsbCBjYWxsIGB0aGlzLmlucHV0Lm5leHQodmFsdWUpYC5cbiAqIDQuIERlZmluZSBhbiBgQElucHV0KCkgc2V0IDxzZWxlY3Rvcj5WaWV3TW9kZWAgcHJvcGVydHkgd2hpY2ggd2lsbCBzZXQgYHRoaXMudmlld01vZGVgLlxuICogNS4gRGVmaW5lIGFuIGBASW5wdXQoKSBzZXQgPHNlbGVjdG9yPlNob3dBZnRlcmAgcHJvcGVydHkgd2hpY2ggd2lsbCBzZXQgYHRoaXMuc2hvd0FmdGVyYC5cbiAqIDYuIERlZmluZSBhbiBgQElucHV0KCkgc2V0IDxzZWxlY3Rvcj5TaG93Rm9yYCBwcm9wZXJ0eSB3aGljaCB3aWxsIHNldCBgdGhpcy5zaG93Rm9yYC5cbiAqIDcuIERlZmluZSBhbiBgQElucHV0KCkgc2V0IDxzZWxlY3Rvcj5Db3VudGRvd25JbnRlcnZhbGAgcHJvcGVydHkgd2hpY2ggd2lsbCBzZXQgYHRoaXMuY291bnRkb3duSW50ZXJ2YWxgLlxuICogOC4gRGVmaW5lIHRoaXMgc3RhdGljIGNvbnRleHQgdHlwZSBndWFyZCB0byBhbGxvdyBzdHJvbmcgdHlwaW5nIHRoZSB0ZW1wbGF0ZTpcbiAqIGBgYHRzXG4gKiBzdGF0aWMgbmdUZW1wbGF0ZUNvbnRleHRHdWFyZDxUPihkaXJlY3RpdmU6IF9fX0RJUkVDVElWRV9OQU1FX19fPFQ+LCBjb250ZXh0OiB1bmtub3duKTogY29udGV4dCBpcyBPbk9ic2VydmVyQ29udGV4dDxUPlxuICogeyByZXR1cm4gdHJ1ZTsgfVxuICogYGBgXG4gKiBcbiAqIEBleHBvcnRcbiAqIEBhYnN0cmFjdFxuICogQGNsYXNzIE9uT2JzZXJ2ZXJCYXNlRGlyZWN0aXZlXG4gKiBAZXh0ZW5kcyB7RGVzdHJveWFibGV9XG4gKiBAaW1wbGVtZW50cyB7T25Jbml0fVxuICogQHRlbXBsYXRlIFQgVGhlIHR5cGUgb2YgdmFsdWUgdGhlIG9ic2VydmFibGUgd2lsbCBlbWl0LlxuICovXG5ARGlyZWN0aXZlKClcbmV4cG9ydCBhYnN0cmFjdCBjbGFzcyBPbk9ic2VydmVyQmFzZURpcmVjdGl2ZTxUPiBleHRlbmRzIERlc3Ryb3lhYmxlIGltcGxlbWVudHMgT25Jbml0XG57XG4gICAgLyoqXG4gICAgICogQSBnbG9iYWwgY29tbWl0bWVudCBtYXAgaG9sZGluZyBhbGwgY29tbWl0bWVudHMgdG8gcmVuZGVyIGZvciB3aGljaCB0aGUgZGlyZWN0aXZlIGhhcyBjcmVhdGVkIGNvbW1pdG1lbnQgb2JzZXJ2YWJsZXMuXG4gICAgICogSWRzIGFyZSB0aGUgdGltZXN0YW1wIG9mIHRoZSBvYnNlcnZlZCBjYWxscyBhbmQgdmFsdWVzIGFyZSB0aGUgY29tbWl0bWVudHMgd2l0aCB0aGVpciByZW5kZXJpbmcgcGFyYW1ldGVycy5cbiAgICAgKlxuICAgICAqIEBwcml2YXRlXG4gICAgICogQHR5cGUge1JlbmRlckNvbW1pdG1lbnRNYXA8VD59XG4gICAgICovXG4gICAgcHJpdmF0ZSBjb21taXRtZW50czogUmVuZGVyQ29tbWl0bWVudE1hcDxUPiA9IG5ldyBNYXAoKTtcblxuICAgIC8qKlxuICAgICAqIFRoZSBmaXJzdCBjb21taXRtZW50IGluIHRoZSB7QGxpbmsgT25PYnNlcnZlckJhc2VEaXJlY3RpdmUuY29tbWl0bWVudHMgZ2xvYmFsIGNvbW1pdG1lbnRzIG1hcH0uIFVzZWQgd2hlbiB3b3JraW5nIHdpdGggYSBzaW5nbGUgdmlld1xuICAgICAqIHRvIHJldHJpZXZlIGl0cyBjb3JyZXNwb25kaW5nIHNpbmdsZSBjb21taXRtZW50LlxuICAgICAqXG4gICAgICogQHJlYWRvbmx5XG4gICAgICogQHByaXZhdGVcbiAgICAgKiBAdHlwZSB7Vmlld1JlbmRlckNvbW1pdG1lbnQ8VD4gfCB1bmRlZmluZWR9XG4gICAgICovXG4gICAgcHJpdmF0ZSBnZXQgbWFpbkNvbW1pdG1lbnQoKTogVmlld1JlbmRlckNvbW1pdG1lbnQ8VD4gfCB1bmRlZmluZWQgeyByZXR1cm4gdGhpcy5jb21taXRtZW50cy52YWx1ZXMoKS5uZXh0KCkudmFsdWU7IH1cblxuICAgIC8qKlxuICAgICAqIFRoZSBzZWxlY3RvciBkZWZpbmVkIGZvciB0aGUgZGlyZWN0aXZlIGV4dGVuZGluZyB0aGlzIGNsYXNzLiBXaWxsIGJlIHVzZWQgdG8gY3JlYXRlIGEgY29ycmVzcG9uZGluZ1xuICAgICAqIHByb3BlcnR5IGluIHRoZSB2aWV3IGNvbnRleHQgaW4gb3JkZXIgdG8gbWFrZSB0aGUgbWljcm8tc3ludGF4IGBhc2Aga2V5d29yZCB3b3JrLlxuICAgICAqXG4gICAgICogQHByb3RlY3RlZFxuICAgICAqIEBhYnN0cmFjdFxuICAgICAqIEB0eXBlIHtzdHJpbmd9XG4gICAgICovXG4gICAgcHJvdGVjdGVkIGFic3RyYWN0IHJlYWRvbmx5IHNlbGVjdG9yOiBzdHJpbmc7XG4gICAgLyoqXG4gICAgICogVGhlIG9ic2VydmVyIG5hbWUocykgZm9yIHdoaWNoIHRvIGludGVyY2VwdCBjYWxscy5cbiAgICAgKlxuICAgICAqIEBwcm90ZWN0ZWRcbiAgICAgKiBAYWJzdHJhY3RcbiAgICAgKiBAdHlwZSB7KE9ic2VydmVyTmFtZSB8IE9ic2VydmVyTmFtZVtdKX1cbiAgICAgKi9cbiAgICBwcm90ZWN0ZWQgYWJzdHJhY3QgcmVuZGVyT25DYWxsc1RvICA6IE9ic2VydmVyTmFtZSB8IE9ic2VydmVyTmFtZVtdO1xuICAgIFxuICAgIC8qKlxuICAgICAqIChPcHRpb25hbCkgVGhlIHZpZXcgbW9kZSB0aGUgZGlyZWN0aXZlIHdpbGwgb3BlcmF0ZSBpbjogIFxuICAgICAqIGAnc2luZ2xlJ2AgLSBBIHNpbmdsZSB2aWV3IHdpbGwgYmUgcmVuZGVyZWQgb24gaW50ZXJjZXB0ZWQgY2FsbHMuIElmIGEgdmlldyBoYXMgYWxyZWFkeSBiZWVuIHJlbmRlcmVkIHdoZW4gYSBjYWxsIGlzIGludGVyY2VwdGVkLFxuICAgICAqIHRoZSBleGlzdGluZyB2aWV3IHdpbGwgYmUgdXBkYXRlZCB3aXRoIGRhdGEgZnJvbSB0aGUgbmV3IGNhbGwuXG4gICAgICogXG4gICAgICogYCdtdWx0aXBsZSdgIC0gRXZlcnkgbmV3IGludGVyY2VwdGVkIGNhbGwgd2lsbCByZW5kZXIgYSBuZXcgdmlldyB3aXRoIGl0cyBvd24gY29udGV4dCBhbmQgZGF0YSBlbmNhcHN1bGF0ZWQgZnJvbSB0aGUgY3VycmVudCBjYWxsLlxuICAgICAqIFxuICAgICAqIERlZmF1bHQgaXMgYCdzaW5nbGUnYC5cbiAgICAgKiBcbiAgICAgKiDimqDvuI8gRXh0ZW5kaW5nIGNsYXNzZXMgc2hvdWxkOlxuICAgICAqIDEuIERlY2xhcmUgYW4gYEBJbnB1dCgpYCBzZXR0ZXIgbmFtZWQgYHtzZWxlY3Rvcn1WaWV3TW9kZWAgKGUuZy4gb25PYnNlcnZlckNvbXBsZXRlVmlld01vZGUpIHdoaWNoIHdpbGwgc2V0IHRoaXMgdmFsdWUuXG4gICAgICogMi4gUHJvdmlkZSB0aGUgYWJvdmUgZG9jdW1lbnRhdGlvbiBmb3IgdGhlIHNldHRlciBwcm9wZXJ0eS5cbiAgICAgKlxuICAgICAqIEBkZWZhdWx0ICdzaW5nbGUnXG4gICAgICogQHByb3RlY3RlZFxuICAgICAqIEB0eXBlIHtWaWV3TW9kZX1cbiAgICAgKi9cbiAgICBwcm90ZWN0ZWQgdmlld01vZGUgICAgICAgICAgOiBWaWV3TW9kZSAgICAgICAgICAgPSAnc2luZ2xlJztcbiAgICAvKipcbiAgICAgKiAoT3B0aW9uYWwpIFRoZSBkdXJhdGlvbiBmb3Igd2hpY2ggdGhlIGRpcmVjdGl2ZSBzaG91bGQgd2FpdCBiZWZvcmUgcmVuZGVyaW5nIHRoZSB2aWV3IG9uY2UgYW4gaW50ZXJjZXB0ZWQgY2FsbCBpcyBtYWRlLlxuICAgICAqIFxuICAgICAqIFlvdSBjYW4gc3BlY2lmeSBhIG51bWJlciwgd2hpY2ggd2lsbCBiZSB0cmVhdGVkIGFzIG1pbGxpc2Vjb25kcywgb3IgYSBzdHJpbmcgd2l0aCB0aGUgZm9ybWF0IG9mIGA8bnVtYmVyPjxtcyB8IHMgfCBtcz5gLlxuICAgICAqIE51bWJlcnMgY2FuIGJlIGVpdGhlciBpbnRlZ2VycyBvciBmbG9hdHMuXG4gICAgICogRm9yIGV4YW1wbGU6XG4gICAgICogLSBgMzAwMGAgLSBXYWl0IGZvciAzIHNlY29uZHMsIHRoZW4gcmVuZGVyIHRoZSB2aWV3LlxuICAgICAqIC0gYCcxMHMnYCAtIFdhaXQgZm9yIDEwIHNlY29uZHMsIHRoZW4gcmVuZGVyIHRoZSB2aWV3LlxuICAgICAqIC0gYCcwLjVtJ2AgLSBXYWl0IGZvciAzMCBzZWNvbmRzLCB0aGVuIHJlbmRlciB0aGUgdmlldy5cbiAgICAgKiAtIGAnMTAwbXMnYCAtIFdhaXQgZm9yIDEwMCBtaWxsaXNlY29uZHMsIHRoZW4gcmVuZGVyIHRoZSB2aWV3LlxuICAgICAqIFxuICAgICAqIERlZmF1bHQgaXMgYDBgLCBtZWFuaW5nIGltbWVkaWF0ZWx5IHJlbmRlciB0aGUgdmlldy5cbiAgICAgKlxuICAgICAqIFRPRE86IEFERCBMSU5LIFRPIFRPVVIgT1IgRlVMTCBXSUtJIFBBR0VcbiAgICAgKiBSZWFkIG1vcmUge0BsaW5rIE9uT2JzZXJ2ZXJCYXNlRGlyZWN0aXZlIEFib3V0IHJlbmRlciBmbG93fS5cbiAgICAgKiBcbiAgICAgKiDimqDvuI8gRXh0ZW5kaW5nIGNsYXNzZXMgc2hvdWxkOlxuICAgICAqIDEuIERlY2xhcmUgYW4gYEBJbnB1dCgpYCBzZXR0ZXIgbmFtZWQgYHtzZWxlY3Rvcn1TaG93QWZ0ZXJgIChlLmcuIG9uT2JzZXJ2ZXJDb21wbGV0ZVNob3dBZnRlcikgd2hpY2ggd2lsbCBzZXQgdGhpcyB2YWx1ZS5cbiAgICAgKiAyLiBQcm92aWRlIHRoZSBhYm92ZSBkb2N1bWVudGF0aW9uIGZvciB0aGUgc2V0dGVyIHByb3BlcnR5LlxuICAgICAqIFxuICAgICAqIEBwcm90ZWN0ZWRcbiAgICAgKiBAdHlwZSB7RHVyYXRpb25Bbm5vdGF0aW9ufVxuICAgICAqL1xuICAgIHByb3RlY3RlZCBzaG93QWZ0ZXIgICAgICAgICA6IER1cmF0aW9uQW5ub3RhdGlvbiA9IDA7XG4gICAgLyoqXG4gICAgICogKE9wdGlvbmFsKSBUaGUgZHVyYXRpb24gZm9yIHdoaWNoIHRoZSB2aWV3IHNob3VsZCBiZSByZW5kZXJlZC4gV2hlbiB0aGUgZHVyYXRpb24gcGFzc2VzLCB0aGUgdmlldyB3aWxsIGJlIGF1dG8gZGVzdHJveWVkLlxuICAgICAqXG4gICAgICogWW91IGNhbiBzcGVjaWZ5IGEgbnVtYmVyLCB3aGljaCB3aWxsIGJlIHRyZWF0ZWQgYXMgbWlsbGlzZWNvbmRzLCBvciBhIHN0cmluZyB3aXRoIHRoZSBmb3JtYXQgb2YgYDxudW1iZXI+PG1zIHwgcyB8IG1zPmAuXG4gICAgICogTnVtYmVycyBjYW4gYmUgZWl0aGVyIGludGVnZXJzIG9yIGZsb2F0cy5cbiAgICAgKiBGb3IgZXhhbXBsZTpcbiAgICAgKiAtIGAzMDAwYCAtIFRoZSB2aWV3IHdpbGwgYmUgZGVzdHJveWVkIGFmdGVyIDMgc2Vjb25kcy5cbiAgICAgKiAtIGAnMTBzJ2AgLSBUaGUgdmlldyB3aWxsIGJlIGRlc3Ryb3llZCBhZnRlciAxMCBzZWNvbmRzLlxuICAgICAqIC0gYCcwLjVtJ2AgLSBUaGUgdmlldyB3aWxsIGJlIGRlc3Ryb3llZCBhZnRlciAzMCBzZWNvbmRzLlxuICAgICAqIC0gYCcxMDBtcydgIC0gVGhlIHZpZXcgd2lsbCBiZSBkZXN0cm95ZWQgYWZ0ZXIgMTAwIG1pbGxpc2Vjb25kcy5cbiAgICAgKiBcbiAgICAgKiBEdXJpbmcgdGhlIHRpbWUgdGhlIHZpZXcgaXMgcmVuZGVyZWQsIHRoZSBjb250ZXh0IHdpbGwgYmUgdXBkYXRlZCB3aXRoIGEgY291bnRkb3duIG9iamVjdCB0byBmYWNpbGl0YXRlIGFueSBVSSBwYXJ0IHVzZWQgdG9cbiAgICAgKiBpbmRpY2F0ZSBjb3VudGRvd24gdG8gdGhlIHVzZXIuIFRoZSBjb3VudGRvd24gd2lsbCBiZSBleHBvc2VkIHRocm91Z2ggdGhlIHtAbGluayBPbk9ic2VydmVyQ29udGV4dC5yZW1haW5pbmcgYHJlbWFpbmluZ2B9XG4gICAgICogcHJvcGVydHkgYW5kIHRoZSBlbGFwc2VkIHRpbWUgdGhyb3VnaCB7QGxpbmsgT25PYnNlcnZlckNvbnRleHQuZWxhcHNlZCBgZWxhcHNlZGB9IHByb3BlcnR5IGluIHRoZSB2aWV3IGNvbnRleHQgYW5kIGNhbiBib3RoXG4gICAgICogYmUgYWNjZXNzZWQgYmUgZGVjbGFyaW5nIGEgYGxldGAgdmFyaWFibGUgKGUuZy4gYGxldCByZW1haW5pbmcgPSByZW1haW5pbmdgKS5cbiAgICAgKiBTZWUge0BsaW5rIE9uT2JzZXJ2ZXJCYXNlRGlyZWN0aXZlLmNvdW50ZG93bkludGVydmFsIGBjb3VudGRvd25JbnRlcnZhbGB9IGZvciBjaGFuZ2luZyB0aGUgdXBkYXRlcyBpbnRlcnZhbC5cbiAgICAgKiBcbiAgICAgKiBXaGVuIHVuc3BlY2lmaWVkLCB0aGUgdmlldyB3aWxsIGJlIGRlc3Ryb3llZCBpbW1lZGlhdGVseSBvbmNlIHRoZSBvYnNlcnZlciBkZXRlY3RzIGEgY2FsbCBkaWZmZXJlbnQgdG8gdGhlIGludGVyY2VwdGVkIG9uZXMuXG4gICAgICogXG4gICAgICogVE9ETzogQUREIExJTksgVE8gVE9VUiBPUiBGVUxMIFdJS0kgUEFHRVxuICAgICAqIFJlYWQgbW9yZSB7QGxpbmsgT25PYnNlcnZlckJhc2VEaXJlY3RpdmUgQWJvdXQgcmVuZGVyIGZsb3d9LlxuICAgICAqIFxuICAgICAqIOKaoO+4jyBFeHRlbmRpbmcgY2xhc3NlcyBzaG91bGQ6XG4gICAgICogMS4gRGVjbGFyZSBhbiBgQElucHV0KClgIHNldHRlciBuYW1lZCBge3NlbGVjdG9yfVNob3dGb3JgIChlLmcuIG9uT2JzZXJ2ZXJDb21wbGV0ZVNob3dGb3IpIHdoaWNoIHdpbGwgc2V0IHRoaXMgdmFsdWUuXG4gICAgICogMi4gUHJvdmlkZSB0aGUgYWJvdmUgZG9jdW1lbnRhdGlvbiBmb3IgdGhlIHNldHRlciBwcm9wZXJ0eS5cbiAgICAgKlxuICAgICAqIEBwcm90ZWN0ZWRcbiAgICAgKiBAdHlwZSB7RHVyYXRpb25Bbm5vdGF0aW9ufVxuICAgICAqL1xuICAgIHByb3RlY3RlZCBzaG93Rm9yPyAgICAgICAgICA6IER1cmF0aW9uQW5ub3RhdGlvbjtcbiAgICAvKipcbiAgICAgKiAjIyMgT25seSB1c2VkIHdoZW4gcGFzc2luZyBhIHZhbHVlIHRvIHtAbGluayBPbk9ic2VydmVyQmFzZURpcmVjdGl2ZS5zaG93Rm9yIGBzaG93Rm9yYH0uXG4gICAgICogXG4gICAgICogKE9wdGlvbmFsKSBUaGUgaW50ZXJ2YWwgd2l0aCB3aGljaCBjb3VudGRvd24gdXBkYXRlcyBzaG91bGQgYmUgbWFkZSB0byB0aGUgdmlldydzIGNvbnRleHQgYmVmb3JlIGl0IGF1dG8gZGVzdHJveXMuXG4gICAgICogVGhlIGxvd2VyIHRoZSB2YWx1ZSwgdGhlIG1vcmUgdXBkYXRlcyB3aWxsIGJlIG1hZGUgdG8gdGhlIGNvbnRleHQsIGJ1dCB0aGUgbW9yZSByZXNvdXJjZXMgeW91ciBkaXJlY3RpdmUgd2lsbCBjb25zdW1lLlxuICAgICAqIFxuICAgICAqIFlvdSBjYW4gc3BlY2lmeSBhIG51bWJlciwgd2hpY2ggd2lsbCBiZSB0cmVhdGVkIGFzIG1pbGxpc2Vjb25kcywgb3IgYSBzdHJpbmcgd2l0aCB0aGUgZm9ybWF0IG9mIGA8bnVtYmVyPjxtcyB8IHMgfCBtcz5gLlxuICAgICAqIE51bWJlcnMgY2FuIGJlIGVpdGhlciBpbnRlZ2VycyBvciBmbG9hdHMuXG4gICAgICogRm9yIGV4YW1wbGU6XG4gICAgICogLSBgMzAwMGAgLSAzIHNlY29uZHMgYmV0d2VlbiBlYWNoIHVwZGF0ZS5cbiAgICAgKiAtIGAnMTBzJ2AgLSAxMCBzZWNvbmRzIGJldHdlZW4gZWFjaCB1cGRhdGUuXG4gICAgICogLSBgJzAuNW0nYCAtIDMwIHNlY29uZHMgYmV0d2VlbiBlYWNoIHVwZGF0ZS5cbiAgICAgKiAtIGAnMTAwbXMnYCAtIDEwMCBtaWxsaXNlY29uZHMgYmV0d2VlbiBlYWNoIHVwZGF0ZS5cbiAgICAgKiBcbiAgICAgKiBZb3UgY2FuIGFsc28gc3BlY2lmeSBgJ2FuaW1hdGlvbkZyYW1lcydgIHNvIHRoZSBjb3VudGRvd24gZ2V0cyB1cGRhdGVkIGVhY2ggdGltZSB0aGUgYnJvd3NlciBpcyB3b3JraW5nIG9uIGFuaW1hdGlvbnMuXG4gICAgICogXG4gICAgICogV2hlbiB1bnNwZWNpZmllZCwgdGhlIHRvdGFsIGR1cmF0aW9uIG9mIHRoZSBjb3VudGRvd24gd2lsbCBiZSBkaXZpZGVkIGJ5IHtAbGluayBEZWZhdWx0Q291bnRkb3duVXBkYXRlQ291bnQgYERlZmF1bHRDb3VudGRvd25VcGRhdGVDb3VudGB9XG4gICAgICogdG8gZ2V0IGEgZml4ZWQgaW50ZXJ2YWwgd2hpY2ggd2lsbCBtYWtlIGZvciB7QGxpbmsgRGVmYXVsdENvdW50ZG93blVwZGF0ZUNvdW50IGBEZWZhdWx0Q291bnRkb3duVXBkYXRlQ291bnRgfSBjb3VudGRvd24gdXBkYXRlcy5cbiAgICAgKiBcbiAgICAgKiDimqDvuI8gRXh0ZW5kaW5nIGNsYXNzZXMgc2hvdWxkOlxuICAgICAqIDEuIERlY2xhcmUgYW4gYEBJbnB1dCgpYCBzZXR0ZXIgbmFtZWQgYHtzZWxlY3Rvcn1Db3VudGRvd25JbnRlcnZhbGAgKGUuZy4gb25PYnNlcnZlckNvbXBsZXRlQ291bnRkb3duSW50ZXJ2YWwpIHdoaWNoIHdpbGwgc2V0IHRoaXMgdmFsdWUuXG4gICAgICogMi4gUHJvdmlkZSB0aGUgYWJvdmUgZG9jdW1lbnRhdGlvbiBmb3IgdGhlIHNldHRlciBwcm9wZXJ0eS5cbiAgICAgKlxuICAgICAqIEBwcm90ZWN0ZWRcbiAgICAgKiBAdHlwZSB7RHVyYXRpb25Bbm5vdGF0aW9ufVxuICAgICAqL1xuICAgIHByb3RlY3RlZCBjb3VudGRvd25JbnRlcnZhbD86IER1cmF0aW9uQW5ub3RhdGlvbiB8ICdhbmltYXRpb25GcmFtZXMnO1xuICAgIFxuICAgIC8qKlxuICAgICAqICMjIyBXaHkgQmVoYXZpb3JTdWJqZWN0PC4uLiB8IG51bGw+IGFuZCBub3QgU3ViamVjdDwuLi4+XG4gICAgICogYGlucHV0YCBpcyBzZXQgZnJvbSBASW5wdXQgcHJvcGVydGllcy4gRm9yIHNvbWUgcmVhc29uLCBBbmd1bGFyIHBhc3Nlcy1pbiB0aGUgZmlyc3QgdmFsdWUgQkVGT1JFXG4gICAgICogbmdPbkluaXQsIGV2ZW4gdGhvdWdoIG90aGVyIEBJbnB1dCBwcm9wZXJ0aWVzIChlLmcuIHNob3dBZnRlciwgc2hvd0ZvcikgYXJlIHBhc3NlZCBBRlRFUiBuZ09uSW5pdC5cbiAgICAgKiBJZiBzdWJzY3JpcHRpb24gb2NjdXJzIGluIHRoZSBjb25zdHJ1Y3RvciwgYGlucHV0YCB3aWxsIGVtaXQgdGhlIGZpcnN0IG9ic2VydmFibGUgdG9vIGZhc3QsIHdoaWNoXG4gICAgICogbWlnaHQgbGVhZCB0byBwaXBlcyBicmVha2luZyBvciBtaXNiZWhhdmluZyBpZiB0aGV5IHJlbHkgb24gcHJvcGVydGllcyB0byBiZSBpbnN0YW50aWF0ZWQgZmlyc3QuXG4gICAgICogXG4gICAgICogVGhpcyBsZWFkcyB0byBzdWJzY3JpYmluZyBpbiBuZ09uSW5pdCwgdG8gYWxsb3cgQW5ndWxhciB0aW1lIHRvIGluaXRpYWxpemUgdGhvc2UuXG4gICAgICogQlVULCBpZiBgaW5wdXRgIGlzIGEgU3ViamVjdCwgYXMgdGhlIGZpcnN0IHZhbHVlIHdhcyBhbHJlYWR5IGVtaXR0ZWQgQkVGT1JFIG5nT25Jbml0LCBpdCB3aWxsIG5vdCBiZVxuICAgICAqIGNhcHR1cmVkIGJ5IG91ciBzdWJzY3JpcHRpb24gdG8gYGlucHV0YC4gSGVuY2UgdGhlIEJlaGF2aW9yU3ViamVjdCAtIFRvIGFsbG93IGNhcHR1cmluZyB0aGF0IGZpcnN0IG9ic2VydmFibGUuXG4gICAgICovXG4gICAgcHJvdGVjdGVkIHJlYWRvbmx5IGlucHV0OiBCZWhhdmlvclN1YmplY3Q8T2JzZXJ2YWJsZTxUPiB8IG51bGw+ID0gbmV3IEJlaGF2aW9yU3ViamVjdChudWxsIGFzIE9ic2VydmFibGU8VD4gfCBudWxsKTtcblxuICAgIC8qKlxuICAgICAqIGB0cnVlYCBpZiB7QGxpbmsgT25PYnNlcnZlckJhc2VEaXJlY3RpdmUudmlld01vZGUgdmlld01vZGV9IGlzIGAnc2luZ2xlJ2A7IG90aGVyd2lzZSwgYGZhbHNlYC5cbiAgICAgKlxuICAgICAqIEByZWFkb25seVxuICAgICAqIEB0eXBlIHtib29sZWFufVxuICAgICAqL1xuICAgIHB1YmxpYyBnZXQgaXNTaW5nbGVWaWV3KCk6IGJvb2xlYW4geyByZXR1cm4gdGhpcy52aWV3TW9kZSA9PT0gJ3NpbmdsZScgIDsgfVxuICAgIC8qKlxuICAgICAqIGB0cnVlYCBpZiB7QGxpbmsgT25PYnNlcnZlckJhc2VEaXJlY3RpdmUudmlld01vZGUgdmlld01vZGV9IGlzIGAnbXVsdGlwbGUnYDsgb3RoZXJ3aXNlLCBgZmFsc2VgLlxuICAgICAqXG4gICAgICogQHJlYWRvbmx5XG4gICAgICogQHR5cGUge2Jvb2xlYW59XG4gICAgICovXG4gICAgcHVibGljIGdldCBpc011bHRpVmlldyAoKTogYm9vbGVhbiB7IHJldHVybiB0aGlzLnZpZXdNb2RlID09PSAnbXVsdGlwbGUnOyB9XG5cbiAgICBjb25zdHJ1Y3Rvcihwcml2YXRlIHJlYWRvbmx5IHRlbXBsYXRlOiBUZW1wbGF0ZVJlZjxPbk9ic2VydmVyQ29udGV4dDxUPj4sIHByaXZhdGUgcmVhZG9ubHkgdmlld0NvbnRhaW5lcjogVmlld0NvbnRhaW5lclJlZilcbiAgICB7XG4gICAgICAgIHN1cGVyKCk7XG4gICAgfVxuICAgIFxuICAgIG5nT25Jbml0KClcbiAgICB7XG4gICAgICAgIC8vIFNlZSBgdGhpcy5pbnB1dGAgZG9jdW1lbnRhdGlvbiBmb3Igd2h5IHN1YnNjcmlwdGlvbiBpcyBkb25lIGluIG5nT25Jbml0LlxuICAgICAgICB0aGlzLnN1YnNjcmliZSh0aGlzLnJlbmRlckZlZWQoKSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogRGVzdHJveXMgYW55IHJlbmRlcmVkIHZpZXcuXG4gICAgICpcbiAgICAgKiBAcHJpdmF0ZVxuICAgICAqL1xuICAgIHByaXZhdGUgZGVzdHJveUFsbCgpOiB2b2lkXG4gICAge1xuICAgICAgICB0aGlzLmNvbW1pdG1lbnRzLmZvckVhY2goKHsgdmlldyB9KSA9PiB2aWV3Py5kZXN0cm95KCkpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgdGhlIG1haW4gZmVlZCB0aGUgZGlyZWN0aXZlIHdpbGwgc3Vic2NyaWJlIHRvLiBUaGUgZmVlZCB3aWxsIGxpc3RlbiB0byBjaGFuZ2VkIHRvIHtAbGluayBPbk9ic2VydmVyQmFzZURpcmVjdGl2ZS5pbnB1dCBgaW5wdXRgfSxcbiAgICAgKiB0aGVuIHN3aXRjaCB0byB0aGUgbmV3bHkgcmVjZWl2ZWQgb2JzZXJ2YWJsZSBpbiBvcmRlciB0byBzdGFydCBvYnNlcnZpbmcgaXQuXG4gICAgICogVGhlIG5ld2x5IHJlY2VpdmVkIG9ic2VydmFibGUgd2lsbCB0aGVuIGJlIG1hdGVyaWFsaXplZCBhbmQgY2FsbHMgd2lsbCBiZSBhZ2dyZWdhdGVkIGFzIGNvbW1pdG1lbnQgb2JqZWN0cyB3aXRoIGluZm9ybWF0aW9uIGFib3V0XG4gICAgICogd2hhdCB0byByZW5kZXIgYW5kIHdoZW4uIFRob3NlIGNvbW1pdG1lbnRzIHdpbGwgcGFzcyB0aHJvdWdoIHRoZSB7QGxpbmsgT25PYnNlcnZlckJhc2VEaXJlY3RpdmUub25Db21taXRtZW50c0NoYW5nZWQgb25Db21taXRtZW50c0NoYW5nZWQoKX0gbWV0aG9kXG4gICAgICogd2hpY2ggd2lsbCB1cGRhdGUgdGhlIGdsb2JhbCBjb21taXRtZW50IGFuZCBjcmVhdGUgb2JzZXJ2YWJsZXMgd2l0aCBjb21taXRtZW50cyB0byByZW5kZXIgYW5kIGF1dG8gZGVzdHJveSB2aWV3cyBhY2NvcmRpbmcgdG8gdGhlXG4gICAgICogZ2l2ZW4gY29tbWl0bWVudHMuXG4gICAgICogXG4gICAgICogVGhpcyBmZWVkIGlzIHRoZSBzaW5nbGUgcmVhY3RpdmUgZW50cnlwb2ludCwgbWVhbmluZyBhbnkgb2JzZXJ2YWJsZSBjcmVhdGVkIGJ5IHRoZSBkaXJlY3RpdmUgd2lsbCBiZSBjcmVhdGVkIGluc2lkZSBvZiB0aGlzXG4gICAgICogb2JzZXJ2YWJsZSBvciBpdHMgbmVzdGVkIG9ic2VydmFibGVzLiBBbnkgdGltZSBhIG5lc3RlZCBvYnNlcnZhYmxlIGlzIGNyZWF0ZWQgaXQgd2lsbCBiZSBzd2l0Y2hlZCB0by4gVGhpcyBhbGxvd3MgdGhlIHBpcGVsaW5lIHRvXG4gICAgICogY29tcGxldGVseSBzdGFydG92ZXIgd2hlbiBhIG5ldyBjYWxsIGlzIG1hZGUgb3IgYSBuZXcge0BsaW5rIE9uT2JzZXJ2ZXJCYXNlRGlyZWN0aXZlLmlucHV0IGBpbnB1dGB9IG9ic2VydmFibGUgaXMgcHJvdmlkZWQsIHRodXMga2VlcGluZ1xuICAgICAqIGEgY29uc2lzdGVudCBzdHJlYW0gb2YgZGF0YSB0byB0aGUge0BsaW5rIE9uT2JzZXJ2ZXJCYXNlRGlyZWN0aXZlLmNvbW1pdG1lbnRzIGdsb2JhbCBjb21taXRtZW50cyBtYXB9LlxuICAgICAqXG4gICAgICogQHByaXZhdGVcbiAgICAgKiBAcmV0dXJuIHtPYnNlcnZhYmxlPFZpZXdSZW5kZXJDb21taXRtZW50PFQ+W10+fSBBbiBvYnNlcnZhYmxlIGFzIGRlc2NyaWJlZCBhYm92ZS5cbiAgICAgKi9cbiAgICBwcml2YXRlIHJlbmRlckZlZWQoKTogT2JzZXJ2YWJsZTxWaWV3UmVuZGVyQ29tbWl0bWVudDxUPltdPlxuICAgIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuaW5wdXQucGlwZShcbiAgICAgICAgICAgIC8vIE1ha2Ugc3VyZSB2aWV3cyBhcmUgcmVzZXQgaWYgYSBuZXcgb2JzZXJ2YWJsZSBpcyBwYXNzZWQtaW4gdG8gdGhlIGRpcmVjdGl2ZVxuICAgICAgICAgICAgdGFwICAgICAgKCgpICAgICAgICAgID0+IHRoaXMuZGVzdHJveUFsbCgpKSxcbiAgICAgICAgICAgIC8vIEZyZWUgbWVtb3J5IG9uY2UgdGhlIGRpcmVjdGl2ZSBpcyBkZXN0cm95ZWQgYW5kIHRoZSBzdWJzY3JpcHRpb24gY2xvc2VkXG4gICAgICAgICAgICAvLyBUT0RPOiBXaWxsIHRoaXMgYWN0dWFsbHkgZXhlY3V0ZT8gYHRoaXMuc3Vic2NyaWJlKClgIGRvZXNuJ3QgY29tcGxldGUgdGhlIG9ic2VydmFibGUgYnV0IHVuc3Vic2NyaWJlcyBvbiBjb21wb25lbnQgZGVzdHJ1Y3Rpb25cbiAgICAgICAgICAgIGZpbmFsaXplICgoKSAgICAgICAgICA9PiB0aGlzLmRlc3Ryb3lBbGwoKSksXG4gICAgICAgICAgICBzd2l0Y2hNYXAoaW5wdXQgICAgICAgPT4gaW5wdXQgPyB0aGlzLm9ic2VydmVJbnB1dChpbnB1dCkgOiBFTVBUWSksXG4gICAgICAgICAgICBtYXAgICAgICAoY2FsbCAgICAgICAgPT4gdGhpcy5zaG91bGRSZW5kZXIoY2FsbCkgPyB0aGlzLmFnZ3JlZ2F0ZUNvbW1pdG1lbnRzKGNhbGwpIDogdGhpcy5kZWFnZ3JlZ2F0ZUNvbW1pdG1lbnRzKCkpLFxuICAgICAgICAgICAgc3dpdGNoTWFwKGNvbW1pdG1lbnRzID0+IHRoaXMub25Db21taXRtZW50c0NoYW5nZWQoY29tbWl0bWVudHMpKSxcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBNYXRlcmlhbGl6ZXMgdGhlIG9ic2VydmFibGUgYW5kIGNvbnZlcnRzIG5vdGlmaWNhdGlvbnMgdG8gYW4ge0BsaW5rIE9ic2VydmVyQ2FsbH0gb2JqZWN0LlxuICAgICAqIFRoZSByZXR1cm5lZCBvYnNlcnZhYmxlIHdpbGwgYWx3YXlzIHN0YXJ0IHdpdGggYSBgJ3Jlc29sdmluZydgIGNhbGwuXG4gICAgICpcbiAgICAgKiBAcHJpdmF0ZVxuICAgICAqIEBwYXJhbSB7T2JzZXJ2YWJsZTxUPn0gaW5wdXQgVGhlIG9ic2VydmFibGUgdG8gd2F0Y2guXG4gICAgICogQHJldHVybiB7T2JzZXJ2YWJsZTxPYnNlcnZlckNhbGw8VD4+fSBBIG1hdGVyaWFsaXplZCBvYnNlcnZhYmxlIHdoaWNoIGRlc2NyaWJlcyBlYWNoIG9ic2VydmFibGUgbm90aWZpY2F0aW9uIGFzIGFuIHtAbGluayBPYnNlcnZlckNhbGx9IG9iamVjdC5cbiAgICAgKi9cbiAgICBwcml2YXRlIG9ic2VydmVJbnB1dChpbnB1dDogT2JzZXJ2YWJsZTxUPik6IE9ic2VydmFibGU8T2JzZXJ2ZXJDYWxsPFQ+PlxuICAgIHtcbiAgICAgICAgcmV0dXJuIGlucHV0LnBpcGUoXG4gICAgICAgICAgICBtYXRlcmlhbGl6ZSgpLFxuICAgICAgICAgICAgbWFwICAgICAgICAoT2JzZXJ2ZXJDYWxsLmZyb21Ob3RpZmljYXRpb24pLFxuICAgICAgICAgICAgc3RhcnRXaXRoICAoT2JzZXJ2ZXJDYWxsLnJlc29sdmluZzxUPigpKVxuICAgICAgICApO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENoZWNrcyB3aGV0aGVyIHRoZSBnaXZlbiBvYnNlcnZlciBjYWxsIHNob3VsZCBiZSByZW5kZXJlZCBhY2NvcmRpbmcgdG8gdGhlIGludGVyY2VwdGlvbiBjb25maWcgaW4ge0BsaW5rIE9uT2JzZXJ2ZXJCYXNlRGlyZWN0aXZlLnJlbmRlck9uQ2FsbHNUbyByZW5kZXJPbkNhbGxzVG99LlxuICAgICAqXG4gICAgICogQHByaXZhdGVcbiAgICAgKiBAcGFyYW0ge09ic2VydmVyQ2FsbDxUPn0gVGhlIGNhbGwgdG8gY2hlY2suXG4gICAgICogQHJldHVybiB7Ym9vbGVhbn0gYHRydWVgIGlmIHRoZSBjYWxsIHNob3VsZCBiZSByZW5kZXJlZDsgb3RoZXJ3aXNlIGBmYWxzZWAuXG4gICAgICovXG4gICAgcHJpdmF0ZSBzaG91bGRSZW5kZXIoeyBuYW1lIH06IE9ic2VydmVyQ2FsbDxUPik6IGJvb2xlYW5cbiAgICB7XG4gICAgICAgIGNvbnN0IG9ic2VydmVPbiA9IEFycmF5LmlzQXJyYXkodGhpcy5yZW5kZXJPbkNhbGxzVG8pID8gdGhpcy5yZW5kZXJPbkNhbGxzVG8gOiBbdGhpcy5yZW5kZXJPbkNhbGxzVG9dO1xuICAgICAgICBcbiAgICAgICAgcmV0dXJuIG9ic2VydmVPbi5pbmNsdWRlcyhuYW1lKTtcbiAgICB9XG4gICAgXG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyB0aGUgbmV3IGNvbW1pdG1lbnRzIG1hcCB3aGVuIGEgbmV3IGNvbW1pdG1lbnQgc2hvdWxkIHJlbmRlci5cbiAgICAgKiBcbiAgICAgKiBXaGVuIGB2aWV3TW9kZWAgaXMgYCdzaW5nbGUnYCB0aGUgbWFwIHdpbGwgYWx3YXlzIGNvbnRhaW4gYSBzaW5nbGUgY29tbWl0bWVudC4gSWYgdGhlIGNvbW1pdG1lbnQgaGFzbid0IGJlZW4gcmVuZGVyZWQgeWV0LCBhIG5ldyBjb21taXRtZW50IHdpbGwgYmUgY3JlYXRlZC5cbiAgICAgKiBPdGhlcndpc2UsIHRoZSBleGlzdGluZyBjb21taXRtZW50IHdpbGwgYmUgcmVwbGFjZWQgYnkgYSBjbG9uZSB3aXRoIHVwZGF0ZWQgcGFyYW1ldGVycyAoaS5lLiBkZWxheSBhbmQgY291bnRkb3duKS5cbiAgICAgKiBcbiAgICAgKiBXaGVuIGB2aWV3TW9kZWAgaXMgYCdtdWx0aXBsZSdgIGEgbmV3IGNvbW1pdG1lbnQgd2lsbCBhbHdheXMgYmUgYWRkZWQgdG8gdGhlIG1hcC5cbiAgICAgKlxuICAgICAqIEBwcml2YXRlXG4gICAgICogQHBhcmFtIHtPYnNlcnZlckNhbGw8VD59IGNhbGwgVGhlIG5ldyBjYWxsIHdoaWNoIHNob3VsZCByZW5kZXIuXG4gICAgICogQHJldHVybiB7UmVuZGVyQ29tbWl0bWVudE1hcDxUPn0gVGhlIG5ldyBtYXAgb2YgY29tbWl0bWVudHMgdG8gcmVuZGVyLlxuICAgICAqL1xuICAgIHByaXZhdGUgYWdncmVnYXRlQ29tbWl0bWVudHMoY2FsbDogT2JzZXJ2ZXJDYWxsPFQ+KTogUmVuZGVyQ29tbWl0bWVudE1hcDxUPlxuICAgIHtcbiAgICAgICAgY29uc3QgY29tbWl0bWVudHMgPSB0aGlzLmNvbW1pdG1lbnRzO1xuICAgICAgICAvLyBJbiBzaW5nbGUtdmlldyBtb2RlLCBpZiB0aGVyZSdzIGFscmVhZHkgYSBjb21taXRtZW50LCB3ZSdsbCByZXBsYWNlIGl0IHdpdGggYSBuZXcgb25lLiBPdGhlcndpc2UsIHdlJ2xsIGNyZWF0ZSBhIGZyZXNoIG9uZS5cbiAgICAgICAgY29uc3QgbmV3Q29tbWl0bWVudCA9IHRoaXMuaXNTaW5nbGVWaWV3ICYmIHRoaXMubWFpbkNvbW1pdG1lbnRcbiAgICAgICAgICAgID8gVmlld1JlbmRlckNvbW1pdG1lbnQudXBkYXRlKHRoaXMubWFpbkNvbW1pdG1lbnQsIGNhbGwpXG4gICAgICAgICAgICA6IFZpZXdSZW5kZXJDb21taXRtZW50LmNyZWF0ZShjYWxsLCBkdXJhdGlvblRvTXModGhpcy5zaG93QWZ0ZXIpLCBkdXJhdGlvblRvTXModGhpcy5zaG93Rm9yIHx8IDApKTtcblxuICAgICAgICByZXR1cm4gbmV3IE1hcChjb21taXRtZW50cy5zZXQobmV3Q29tbWl0bWVudC5jb21taXRtZW50SWQsIG5ld0NvbW1pdG1lbnQpKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIHRoZSBuZXcgY29tbWl0bWVudHMgbWFwIHdoZW4gYSBuZXcgY29tbWl0bWVudCBzaG91bGRuJ3QgcmVuZGVyLlxuICAgICAqXG4gICAgICogQHByaXZhdGVcbiAgICAgKiBAcmV0dXJuIHtSZW5kZXJDb21taXRtZW50TWFwPFQ+fSBJZiBgc2hvd0ZvcmAgaXMgc3BlY2lmaWVkLCBtZWFuaW5nIHZpZXdzIHNob3VsZCBiZSBhdXRvIGRlc3Ryb3llZCBhZnRlciBhIGNlcnRhaW4gZHVyYXRpb24sXG4gICAgICogdGhlIGN1cnJlbnQgY29tbWl0bWVudHMgd2lsbCBrZXB0IGFsaXZlIGJ5IHJldHVybmluZyB0aGVtIGFzIGEgbmV3IG1hcC4gVGhpcyB3aWxsIGFsbG93IHJlY29tbWl0aW5nIHRvIHRoZSBzYW1lIHJlbmRlciBwYXJhbWV0ZXJzIChpLmUuIGRlbGF5IGFuZCBjb3VudGRvd24pLlxuICAgICAqIE90aGVyd2lzZSwgd2hlbiB2aWV3cyBzaG91bGQgZGVzdHJveSBpbW1lZGlhdGVseSwgYW4gZW1wdHkgbWFwIHdpbGwgYmUgcmV0dXJuZWQuXG4gICAgICovXG4gICAgcHJpdmF0ZSBkZWFnZ3JlZ2F0ZUNvbW1pdG1lbnRzKCk6IFJlbmRlckNvbW1pdG1lbnRNYXA8VD5cbiAgICB7XG4gICAgICAgIHJldHVybiB0aGlzLnNob3dGb3IgPyBuZXcgTWFwKHRoaXMuY29tbWl0bWVudHMpIDogbmV3IE1hcCgpO1xuICAgIH1cbiAgICBcbiAgICAvKipcbiAgICAgKiBIYW5kbGVzIHRoZSBjaGFuZ2VzIHRvIHRoZSBjdXJyZW50IGNvbW1pdG1lbnQgb2YgdGhlIHdhdGNoZWQgb2JzZXJ2YWJsZSBhbmQgY3JlYXRlcyBhbmQgY29tbWl0cyB0byByZW5kZXIgYWxsIGNvbW1pdG1lbnRzLlxuICAgICAqIFxuICAgICAqIFRoaXMgd2lsbCB1cGRhdGUgdGhlIGdsb2JhbCBjb21taXRtZW50IG1hcC4gSWYgYW4gZW1wdHkgbWFwIGlzIHBhc3NlZCwgYWxsIHByZXZpb3VzIGNvbW1pdG1lbnRzIHdpbGwgYmUgZGVzdHJveWVkLlxuICAgICAqXG4gICAgICogQHByaXZhdGVcbiAgICAgKiBAcGFyYW0ge1JlbmRlckNvbW1pdG1lbnRNYXA8VD59IGNvbW1pdG1lbnRzIFRoZSBjdXJyZW50IGNvbW1pdG1lbnQgbWFwLlxuICAgICAqIEByZXR1cm4ge09ic2VydmFibGU8Vmlld1JlbmRlckNvbW1pdG1lbnQ8VD5bXT59IEFuIG9ic2VydmFibGUgam9pbmluZyBhbGwgcmVuZGVyIGNvbW1pdG1lbnRzLlxuICAgICAqL1xuICAgIHByaXZhdGUgb25Db21taXRtZW50c0NoYW5nZWQoY29tbWl0bWVudHM6IFJlbmRlckNvbW1pdG1lbnRNYXA8VD4pOiBPYnNlcnZhYmxlPFZpZXdSZW5kZXJDb21taXRtZW50PFQ+W10+XG4gICAge1xuICAgICAgICAvLyBJZiB0aGUgY29tbWl0bWVudCBtYXAgaGFzIGJlZW4gcmVzZXQsIGRlc3Ryb3kgYW55IHByZXZpb3VzbHkgcmVuZGVyZWQgdmlld1xuICAgICAgICBpZiAoIWNvbW1pdG1lbnRzLnNpemUpIHRoaXMuZGVzdHJveUFsbCgpO1xuXG4gICAgICAgIC8vIFVwZGF0ZSB0aGUgZ2xvYmFsIGNvbW1pdG1lbnQgbWFwXG4gICAgICAgIHRoaXMuY29tbWl0bWVudHMgPSBjb21taXRtZW50cztcbiAgICAgICAgLy8gTWFwIGFsbCBjb21taXRtZW50cyB0byBhIGNvbW1pdG1lbnQgdG8gcmVuZGVyIG9ic2VydmFibGVcbiAgICAgICAgY29uc3QgcnVuQ29tbWl0bWVudHMgPSBBcnJheS5mcm9tKGNvbW1pdG1lbnRzLmtleXMoKSlcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5tYXAoKGNvbW1pdG1lbnRJZCwgaW5kZXgpID0+IHRoaXMuY29tbWl0VG9SZW5kZXIoY29tbWl0bWVudHMsIGNvbW1pdG1lbnRJZCwgaW5kZXgpKTtcbiAgICAgICAgICAgIFxuICAgICAgICByZXR1cm4gZm9ya0pvaW4ocnVuQ29tbWl0bWVudHMpO1xuICAgIH1cbiAgICBcbiAgICAvKipcbiAgICAgKiBDcmVhdGVzIGFuIG9ic2VydmFibGUgdGhhdCBpbml0aWF0ZXMgdGhlIHJlbmRlciBmbG93IGZvciBhbiBlbWlzc2lvbi4gUmVuZGVyIGZsb3cgaXMgYXMgZm9sbG93czpcbiAgICAgKiAxLiBEZWxheSByZW5kZXIgdW50aWwgdGhlIHRpbWUgZm9yIHJlbmRlciAoaS5lLiB7QGxpbmsgVmlld1JlbmRlckNvbW1pdG1lbnQucmVuZGVyQXR9KSBoYXMgY29tZS5cbiAgICAgKiAyLiBSZW5kZXIgdGhlIHZpZXcuXG4gICAgICogMy4gVXBkYXRlIHRoZSB7QGxpbmsgT25PYnNlcnZlckJhc2VEaXJlY3RpdmUuY29tbWl0bWVudHMgZ2xvYmFsIGNvbW1pdG1lbnRzIG1hcH0gd2l0aCB0aGUgcmVuZGVyZWQgY29tbWl0bWVudC5cbiAgICAgKiA0LiBJbml0aWF0ZSBhbiBhdXRvIGRlc3Ryb3kgdGltZXIuIFNlZSB7QGxpbmsgT25PYnNlcnZlckJhc2VEaXJlY3RpdmUuYXV0b0Rlc3Ryb3kgYXV0b0Rlc3Ryb3koKX0uXG4gICAgICogNS4gUmVtb3ZlIHRoZSBkZXN0cm95ZWQgY29tbWl0bWVudCBmcm9tIHRoZSB7QGxpbmsgT25PYnNlcnZlckJhc2VEaXJlY3RpdmUuY29tbWl0bWVudHMgZ2xvYmFsIGNvbW1pdG1lbnRzIG1hcH0uXG4gICAgICpcbiAgICAgKiBAcHJpdmF0ZVxuICAgICAqIEBwYXJhbSB7UmVuZGVyQ29tbWl0bWVudE1hcDxUPn0gY29tbWl0bWVudHMgVGhlIGN1cnJlbnQgY29tbWl0bWVudCBtYXAgaG9sZGluZyBhbGwgY29tbWl0bWVudHMgdG8gcmVuZGVyLlxuICAgICAqIEBwYXJhbSB7c3RyaW5nfSBjb21taXRtZW50SWQgVGhlIGlkIG9mIHRoZSBjb21taXRtZW50IHRvIHJlbmRlci5cbiAgICAgKiBAcGFyYW0ge251bWJlcn0gaW5kZXggVGhlIGluZGV4IG9mIHRoZSB2aWV3IHRvIGJlIHJlbmRlcmVkLlxuICAgICAqIEByZXR1cm4ge09ic2VydmFibGU8Vmlld1JlbmRlckNvbW1pdG1lbnQ8VD4+fSBBbiBvYnNlcnZhYmxlIHRoYXQgaW5pdGlhdGVzIHRoZSByZW5kZXIgZmxvdyBmb3IgYW4gZW1pc3Npb24uXG4gICAgICovXG4gICAgcHJpdmF0ZSBjb21taXRUb1JlbmRlcihjb21taXRtZW50czogUmVuZGVyQ29tbWl0bWVudE1hcDxUPiwgY29tbWl0bWVudElkOiBzdHJpbmcsIGluZGV4OiBudW1iZXIpOiBPYnNlcnZhYmxlPFZpZXdSZW5kZXJDb21taXRtZW50PFQ+PlxuICAgIHtcbiAgICAgICAgaWYgKCFjb21taXRtZW50cy5oYXMoY29tbWl0bWVudElkKSkgdGhyb3cgbmV3IEVycm9yKGBcbiAgICAgICAgICAgICokeyB0aGlzLnNlbGVjdG9yIH0gaGFzIGVuY291bnRlcmVkIGFuIGluY29uc2lzdGVuY3kgaXNzdWUuIFRyaWVkIHRvIGNvbW1pdCB0byByZW5kZXJpbmcgY29tbWl0bWVudCB3aXRoIElEICR7IGNvbW1pdG1lbnRJZCB9LCBidXQgbm8gY29tbWl0bWVudCBvYmplY3QgZXhpc3RzIHdpdGggdGhhdCBJRC5cbiAgICAgICAgICAgIFBsZWFzZSBjb25zaWRlciBmaWxpbmcgYW4gaXNzdWUgYW5kIHByb3ZpZGluZyBhIHN0YWNrIHRyYWNlIGhlcmU6IGh0dHBzOi8vZ2l0aHViLmNvbS9CZVNwdW5reS9hbmd1bGFyLXplbi9pc3N1ZXMvbmV3P2Fzc2lnbmVlcz1CZVNwdW5reSZsYWJlbHM9JUYwJTlGJTkwJTlCK0J1ZyZ0ZW1wbGF0ZT1idWdfcmVwb3J0Lm1kJnRpdGxlPSVGMCU5RiU5MCU5QitcbiAgICAgICAgYCk7XG5cbiAgICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1ub24tbnVsbC1hc3NlcnRpb25cbiAgICAgICAgcmV0dXJuIG9mKGNvbW1pdG1lbnRzLmdldChjb21taXRtZW50SWQpISkucGlwZShcbiAgICAgICAgICAgIHN3aXRjaE1hcChjb21taXRtZW50ICAgICAgICAgID0+IHRoaXMuZGVsYXlSZW5kZXIoY29tbWl0bWVudCkpLFxuICAgICAgICAgICAgLy8gQWN0dWFsbHkgcGVyZm9ybSByZW5kZXJpbmcgKG9yIHVwZGF0ZSB0aGUgdmlldyBpZiBhbHJlYWR5IHJlbmRlcmVkKVxuICAgICAgICAgICAgc3dpdGNoTWFwKGNvbW1pdG1lbnQgICAgICAgICAgPT4gdGhpcy5yZW5kZXJDb21taXRtZW50KGNvbW1pdG1lbnQsIGluZGV4KSksXG4gICAgICAgICAgICAvLyBUaGUgY29tbWl0bWVudCByZXR1cm5lZCBmcm9tIGByZW5kZXJDb21taXRtZW50KClgIG5vdyBjb250YWlucyB0aGUgdmlldywgc28gdXBkYXRlIHRoZSBnbG9iYWwgbWFwXG4gICAgICAgICAgICB0YXAgICAgICAocmVuZGVyZWRDb21taXRtZW50ICA9PiBjb21taXRtZW50cy5zZXQoY29tbWl0bWVudElkLCByZW5kZXJlZENvbW1pdG1lbnQpKSxcbiAgICAgICAgICAgIC8vIEluaXRpYXRlIHRoZSBhdXRvLWRlc3Ryb3kgbWVjaGFuaXNtICh3aWxsIHNraXAgaWYgYHNob3dGb3JgIHdhc24ndCBzcGVjaWZpZWQpXG4gICAgICAgICAgICBzd2l0Y2hNYXAocmVuZGVyZWRDb21taXRtZW50ICA9PiB0aGlzLmF1dG9EZXN0cm95KHJlbmRlcmVkQ29tbWl0bWVudCkpLFxuICAgICAgICAgICAgLy8gQ29tbWl0bWVudCBoYXMgY29tcGxldGVkIHN1Y2Nlc3NmdWxseS4gUmVtb3ZlIGl0IGZyb20gdGhlIGdsb2JhbCBtYXAuXG4gICAgICAgICAgICB0YXAgICAgICAocmVuZGVyZWRDb21taXRtZW50ICA9PiByZW5kZXJlZENvbW1pdG1lbnQuYXV0b0Rlc3Ryb3lzID8gY29tbWl0bWVudHMuZGVsZXRlKGNvbW1pdG1lbnRJZCkgOiB2b2lkIDApXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhbiBvYnNlcnZhYmxlIHdoaWNoIGRlbGF5cyB0aGUgcGlwZWxpbmUgdW50aWwgdGhlIHRpbWUgdG8gcmVuZGVyIHRoZSB2aWV3IChpLmUuIHtAbGluayBWaWV3UmVuZGVyQ29tbWl0bWVudC5yZW5kZXJBdH0pIGNvbWVzLlxuICAgICAqXG4gICAgICogQHByaXZhdGVcbiAgICAgKiBAcGFyYW0ge1ZpZXdSZW5kZXJDb21taXRtZW50PFQ+fSBjb21taXRtZW50IFRoZSBjb21taXRtZW50IHRvIGRlbGF5LlxuICAgICAqIEByZXR1cm4ge09ic2VydmFibGU8Vmlld1JlbmRlckNvbW1pdG1lbnQ8VD4+fSBBbiBvYnNlcnZhYmxlIHdoaWNoIGRlbGF5cyB0aGUgcGlwZWxpbmUgdW50aWwgdGhlIHRpbWUgdG8gcmVuZGVyIHRoZSB2aWV3IChpLmUuIHtAbGluayBWaWV3UmVuZGVyQ29tbWl0bWVudC5yZW5kZXJBdH0pIGNvbWVzLlxuICAgICAqL1xuICAgIHByaXZhdGUgZGVsYXlSZW5kZXIoY29tbWl0bWVudDogVmlld1JlbmRlckNvbW1pdG1lbnQ8VD4pOiBPYnNlcnZhYmxlPFZpZXdSZW5kZXJDb21taXRtZW50PFQ+PlxuICAgIHtcbiAgICAgICAgcmV0dXJuIG9mKGNvbW1pdG1lbnQpLnBpcGUoXG4gICAgICAgICAgICBkZWxheShuZXcgRGF0ZShjb21taXRtZW50LnJlbmRlckF0KSksXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogQ3JlYXRlcyBhIG5ldyBjb250ZXh0IGZvciB0aGUgZ2l2ZW4gY29tbWl0bWVudCBhbmQgcmVuZGVycyAob3IgdXBkYXRlcykgdGhlIHZpZXcuXG4gICAgICpcbiAgICAgKiBAcHJpdmF0ZVxuICAgICAqIEBwYXJhbSB7Vmlld1JlbmRlckNvbW1pdG1lbnQ8VD59IGNvbW1pdG1lbnQgVGhlIGNvbW1pdG1lbnQgZm9yIHdoaWNoIHRvIGNyZWF0ZSB0aGUgY29udGV4dCBhbmQgcmVuZGVyIHRoZSB2aWV3LlxuICAgICAqIEBwYXJhbSB7bnVtYmVyfSBpbmRleCBUaGUgaW5kZXggb2YgdGhlIHZpZXcuIElmIGB2aWV3TW9kZWAgaXMgYCdzaW5nbGUnYCB0aGlzIHNob3VsZCBhbHdheXMgYmUgYDBgIGFzIHRoZXJlIGlzIG9ubHkgb25lIHZpZXcuXG4gICAgICogQHJldHVybiB7T2JzZXJ2YWJsZTxWaWV3UmVuZGVyQ29tbWl0bWVudDxUPj59IEFuIG9ic2VydmFibGUgd2hpY2ggcmVuZGVyZXMgdGhlIGNvbW1pdG1lbnQsIHRoZW4gZW1pdHMgYSBuZXcgdXBkYXRlZCBjb21taXRtZW50IHJlZmVyZW5jaW5nIHRoZSByZW5kZXJlZCB2aWV3LlxuICAgICAqL1xuICAgIHByaXZhdGUgcmVuZGVyQ29tbWl0bWVudChjb21taXRtZW50OiBWaWV3UmVuZGVyQ29tbWl0bWVudDxUPiwgaW5kZXg6IG51bWJlcik6IE9ic2VydmFibGU8Vmlld1JlbmRlckNvbW1pdG1lbnQ8VD4+XG4gICAge1xuICAgICAgICBjb25zdCBjb250ZXh0ICAgICAgICAgICAgPSBPbk9ic2VydmVyQ29udGV4dC5mcm9tQ29tbWl0bWVudDxUPih0aGlzLnNlbGVjdG9yLCBpbmRleCwgY29tbWl0bWVudCk7XG4gICAgICAgIGNvbnN0IHJlbmRlcmVkQ29tbWl0bWVudCA9IHRoaXMucmVuZGVyT3JVcGRhdGVWaWV3KGNvbW1pdG1lbnQsIGNvbnRleHQpO1xuXG4gICAgICAgIHJldHVybiBvZihyZW5kZXJlZENvbW1pdG1lbnQpO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYW4gaW50ZXJ2YWwgb2JzZXJ2YWJsZSB3aGljaCBjb3VudHMgZG93biB1bnRpbCB0aGUgdGltZSB0byBkZXN0cm95IHRoZSB2aWV3IGlzIHJlYWNoZWQsIHRoZW4gZGVzdHJveXMgdGhlIHZpZXcuXG4gICAgICogV2hpbGUgdGhlIHRpbWVyIGlzIHJ1bm5pbmcsIHRoZSByZW5kZXJlZCB2aWV3J3MgY29udGV4dCB3aWxsIGJlIHVwZGF0ZWQgaW4gZml4ZWQgaW50ZXJ2YWxzIHdpdGggdGhlIHRpbWUgbGVmdCBiZWZvcmUgZGVzdHJ1Y3Rpb24uXG4gICAgICogXG4gICAgICogQHNlZSB7QGxpbmsgT25PYnNlcnZlckJhc2VEaXJlY3RpdmUuZGVmaW5lQ291bnRkb3duSW50ZXJ2YWwgZGVmaW5lQ291bnRkb3duSW50ZXJ2YWwoKX0gZm9yIG1vcmUgYWJvdXQgdGhlIGZpeGVkIGNvdW50ZG93biBpbnRlcnZhbC5cbiAgICAgKiBcbiAgICAgKiBJZiB7QGxpbmsgT25PYnNlcnZlckJhc2VEaXJlY3RpdmUuY291bnRkb3duSW50ZXJ2YWwgYGNvdW50ZG93bkludGVydmFsYH0gaXMgYCdhbmltYXRpb25GcmFtZXMnYCwgdGhlIHJ4anMgYGFuaW1hdGlvbkZyYW1lcygpYCBmdW5jdGlvblxuICAgICAqIHdpbGwgYmUgdXNlZCBpbnN0ZWFkIG9mIHRoZSBpbnRlcnZhbC5cbiAgICAgKiBcbiAgICAgKiBAcHJpdmF0ZVxuICAgICAqIEBwYXJhbSB7Vmlld1JlbmRlckNvbW1pdG1lbnQ8VD59IGNvbW1pdG1lbnQgVGhlIHJlbmRlcmVkIGNvbW1pdG1lbnQgZm9yIHdoaWNoIHRvIGluaXRpYXRlIGF1dG8gZGVzdHJveS5cbiAgICAgKiBAcmV0dXJuIHtPYnNlcnZhYmxlPFZpZXdSZW5kZXJDb21taXRtZW50PFQ+Pn0gQSB0aW1lciBvYnNlcnZhYmxlIHdoaWNoIGNvdW50cyBkb3duIHVudGlsIHRoZSB0aW1lIHRvIGRlc3Ryb3kgdGhlIHZpZXcgaXMgcmVhY2hlZCwgdGhlbiBkZXN0cm95cyB0aGUgdmlldywgd2hpbGVcbiAgICAgKiB1cGRhdGluZyB0aGUgY29udGV4dCB3aXRoIHRoZSB0aW1lIGxlZnQgZm9yIGRlc3RydWN0aW9uLiBUaGUgb2JzZXJ2YWJsZSB3aWxsIGVtaXQgdGhlIGNvbW1pdG1lbnQgXG4gICAgICovXG4gICAgcHJpdmF0ZSBhdXRvRGVzdHJveShjb21taXRtZW50OiBWaWV3UmVuZGVyQ29tbWl0bWVudDxUPik6IE9ic2VydmFibGU8Vmlld1JlbmRlckNvbW1pdG1lbnQ8VD4+XG4gICAge1xuICAgICAgICBjb25zdCB7IGRlc3Ryb3lBdCwgdmlldyB9ID0gY29tbWl0bWVudDtcblxuICAgICAgICBpZiAoIShkZXN0cm95QXQgJiYgdmlldykpIHJldHVybiBvZihjb21taXRtZW50KTtcblxuICAgICAgICBjb25zdCBjb3VudGRvd25JbnRlcnZhbCA9IHRoaXMuZGVmaW5lQ291bnRkb3duSW50ZXJ2YWwoKTtcbiAgICAgICAgY29uc3QgY291bnRkb3duOiBPYnNlcnZhYmxlPHVua25vd24+ID0gY291bnRkb3duSW50ZXJ2YWwgPT09ICdhbmltYXRpb25GcmFtZXMnID8gYW5pbWF0aW9uRnJhbWVzKCkgOiBpbnRlcnZhbChjb3VudGRvd25JbnRlcnZhbCk7XG5cbiAgICAgICAgcmV0dXJuIGNvdW50ZG93bi5waXBlKFxuICAgICAgICAgICAgbWFwICAgICAgKCgpICAgICAgICAgPT4gZGVzdHJveUF0IC0gRGF0ZS5ub3coKSksXG4gICAgICAgICAgICBtYXAgICAgICAodGltZUxlZnRNcyA9PiBbdGltZUxlZnRNcyA8IDAgPyAwIDogdGltZUxlZnRNcywgY29tbWl0bWVudC5zaG93Rm9yIC0gdGltZUxlZnRNc10pLFxuICAgICAgICAgICAgdGFwICAgICAgKChbdGltZUxlZnRNcywgZWxhcHNlZFRpbWVNc10pID0+IHRoaXMudXBkYXRlVmlld0NvbnRleHRDb3VudGRvd24odmlldywgdGltZUxlZnRNcywgZWxhcHNlZFRpbWVNcykpLFxuICAgICAgICAgICAgdGFrZVdoaWxlKChbdGltZUxlZnRNc10pID0+IHRpbWVMZWZ0TXMgPiAwLCB0cnVlKSxcbiAgICAgICAgICAgIGZpbHRlciAgICgoW3RpbWVMZWZ0TXNdKSA9PiB0aW1lTGVmdE1zIDw9IDApLFxuICAgICAgICAgICAgdGFwICAgICAgKCgpID0+IHZpZXcuZGVzdHJveSgpKSxcbiAgICAgICAgICAgIG1hcFRvICAgIChjb21taXRtZW50KVxuICAgICAgICApO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIE1ha2VzIHN1cmUgdGhlIHNwZWNpZmllZCBjb21taXRtZW50IGlzIHJlbmRlcmVkIGFuZCBpdHMgY29udGV4dCBpcyB1cGRhdGVkLCB0aGVuIHJldHVybnMgYW4gdXBkYXRlZCBjb21taXRtZW50IHdpdGggdGhlIHJlbmRlcmVkIChvciB1cGRhdGVkKSB2aWV3LlxuICAgICAqIElmIHRoZSB2aWV3IGhhcyBiZWVuIHByZXZpb3VzbHkgcmVuZGVyZWQsIGl0cyBjb250ZXh0IHdpbGwgYmUgdXBkYXRlZC4gT3RoZXJ3aXNlLCB0aGUgdmlldyB3aWxsIGJlIHJlbmRlcmVkIGZvciB0aGUgZmlyc3QgdGltZS5cbiAgICAgKiBcbiAgICAgKiBUaGUgbmV3IGNvbW1pdG1lbnQgd2lsbCBiZSB1c2VkIGZ1cnRoZXIgZG93biB0aGUgcGlwZWxpbmUgdG8gdXBkYXRlIHRoZSBpbnRlcm5hbCBgY29tbWl0bWVudHNgIG1hcC5cbiAgICAgKiBcbiAgICAgKiBAc2VlIHtAbGluayBPbk9ic2VydmVyQmFzZURpcmVjdGl2ZS5jb21taXRUb1JlbmRlciBgY29tbWl0VG9SZW5kZXIoKWB9LlxuICAgICAqXG4gICAgICogQHByaXZhdGVcbiAgICAgKiBAcGFyYW0ge1ZpZXdSZW5kZXJDb21taXRtZW50PFQ+fSBjb21taXRtZW50IFRoZSBjb21taXRtZW50IGZvciB3aGljaCB0aGUgdmlldyBzaG91bGQgYmUgcmVuZGVyZWQuY291bnRkb3duXG4gICAgICogQHBhcmFtIHtPbk9ic2VydmVyQ29udGV4dDxUPn0gY29udGV4dCBUaGUgY29udGV4dCBvYmplY3QgdG8gZmVlZCBpbnRvIHRoZSB2aWV3LlxuICAgICAqIEByZXR1cm4ge1ZpZXdSZW5kZXJDb21taXRtZW50PFQ+fSBUaGUgbmV3IGNvbW1pdG1lbnQgY29udGFpbmluZyB0aGUgcmVuZGVyZWQgKG9yIHVwZGF0ZWQpIHZpZXcuXG4gICAgICovXG4gICAgcHJpdmF0ZSByZW5kZXJPclVwZGF0ZVZpZXcoY29tbWl0bWVudDogVmlld1JlbmRlckNvbW1pdG1lbnQ8VD4sIGNvbnRleHQ6IE9uT2JzZXJ2ZXJDb250ZXh0PFQ+KTogVmlld1JlbmRlckNvbW1pdG1lbnQ8VD5cbiAgICB7XG4gICAgICAgIGlmIChjb21taXRtZW50LnZpZXcpXG4gICAgICAgIHtcbiAgICAgICAgICAgIGNvbW1pdG1lbnQudmlldy5jb250ZXh0ID0gY29udGV4dDtcbiAgICAgICAgICAgIFxuICAgICAgICAgICAgcmV0dXJuIFZpZXdSZW5kZXJDb21taXRtZW50LnJlbmRlcmVkKGNvbW1pdG1lbnQsIGNvbW1pdG1lbnQudmlldyk7XG4gICAgICAgIH1cbiAgICAgICAgXG4gICAgICAgIHJldHVybiBWaWV3UmVuZGVyQ29tbWl0bWVudC5yZW5kZXJlZChjb21taXRtZW50LCB0aGlzLnZpZXdDb250YWluZXIuY3JlYXRlRW1iZWRkZWRWaWV3KHRoaXMudGVtcGxhdGUsIGNvbnRleHQpKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBCcmVha3MgZG93biB0aGUgdGltZSBsZWZ0IGJlZm9yZSB0aGUgdmlldyBpcyBkZXN0cm95ZWQgdG8gaXRzIHBhcnRzIGFuZCB1cGRhdGVzIHRoZSB2aWV3IGNvbnRleHQgc28gdGhhdCB0aGUgdXNlciBtYXkgcHJlc2VudFxuICAgICAqIGEgY291bnRkb3duIG9yIGFueSBvdGhlciBVSSBjb21wb25lbnQgaW5kaWNhdGluZyB3aGVuIHRoZSB2aWV3IHdpbGwgYmUgZGVzdHJveWVkLlxuICAgICAqXG4gICAgICogQHByaXZhdGVcbiAgICAgKiBAcGFyYW0ge1JlbmRlcmVkVmlldzxUPn0gdmlldyBUaGUgdmlldyBpbiB3aGljaCB0byB1cGRhdGUgdGhlIGNvdW50ZG93bi5cbiAgICAgKiBAcGFyYW0ge251bWJlcn0gdGltZUxlZnRNcyBUaGUgdGltZSBsZWZ0IChpbiBtaWxsaXNlY29uZHMpIGZvciB0aGUgdmlldyBiZWZvcmUgYmVpbmcgZGVzdHJveWVkLlxuICAgICAqIEBwYXJhbSB7bnVtYmVyfSB0aW1lRWxhcHNlZE1zIFRoZSB0aW1lIGVsYXBzZWQgKGluIG1pbGxpc2Vjb25kcykgZnJvbSB0aGUgbW9tZW50IHRoZSB2aWV3IHdhcyByZW5kZXJlZC5cbiAgICAgKi9cbiAgICBwcml2YXRlIHVwZGF0ZVZpZXdDb250ZXh0Q291bnRkb3duKHZpZXc6IFJlbmRlcmVkVmlldzxUPiwgdGltZUxlZnRNczogbnVtYmVyLCB0aW1lRWxhcHNlZE1zOiBudW1iZXIpOiB2b2lkXG4gICAgeyAgICBcbiAgICAgICAgY29uc3QgcmVtYWluaW5nID0gYnJlYWtkb3duVGltZSh0aW1lTGVmdE1zKTtcbiAgICAgICAgY29uc3QgZWxhcHNlZCAgID0gYnJlYWtkb3duVGltZSh0aW1lRWxhcHNlZE1zKTtcblxuICAgICAgICBjb25zdCB7ICRpbXBsaWNpdCwgY2FsbCwgaW5kZXggfSA9IHZpZXcuY29udGV4dDtcblxuICAgICAgICAvLyBUT0RPOiBSZW1vdmUgdGhlIGBzaG93aW5nRm9yYCBhcmd1bWVudCB3aGVuIGxhdW5jaGluZyB2Ni4wLjBcbiAgICAgICAgdmlldy5jb250ZXh0ID0gbmV3IE9uT2JzZXJ2ZXJDb250ZXh0KHRoaXMuc2VsZWN0b3IsIGluZGV4LCBjYWxsLCAkaW1wbGljaXQsIHJlbWFpbmluZywgcmVtYWluaW5nLCBlbGFwc2VkKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBEZWZpbmVzIHRoZSBpbnRlcnZhbCAoaW4gbWlsbGlzZWNvbmRzKSB3aXRoIHdoaWNoIGNvdW50ZG93biB1cGRhdGVzIHNob3VsZCBiZSBtYWRlIHRvIHRoZSB2aWV3J3MgY29udGV4dC5cbiAgICAgKiBJZiB0aGUgdXNlciBoYXMgZGVmaW5lZCBhIHZhbHVlIHRocm91Z2gge0BsaW5rIE9uT2JzZXJ2ZXJCYXNlRGlyZWN0aXZlLmNvdW50ZG93bkludGVydmFsIGBjb3VudGRvd25JbnRlcnZhbGB9LCB0aGF0IHZhbHVlIHdpbGwgYmUgdXNlZC5cbiAgICAgKiBJZiB0aGUgdXNlciBoYXMgZGVmaW5lZCBgJ2FuaW1hdGlvbkZyYW1lcydgIGFzIHRoZSB2YWx1ZSBmb3Ige0BsaW5rIE9uT2JzZXJ2ZXJCYXNlRGlyZWN0aXZlLmNvdW50ZG93bkludGVydmFsIGBjb3VudGRvd25JbnRlcnZhbGB9LCB0aGlzIHdpbGwgcmV0dXJuIGAnYW5pbWF0aW9uRnJhbWVzJ2AuXG4gICAgICogT3RoZXJ3aXNlLCB7QGxpbmsgT25PYnNlcnZlckJhc2VEaXJlY3RpdmUuc2hvd0ZvciBgc2hvd0ZvcmB9IHdpbGwgYmUgZGl2aWRlZCBieSBhIGZpeGVkIG51bWJlciBkZWZpbmVkIGJ5IHtAbGluayBEZWZhdWx0Q291bnRkb3duVXBkYXRlQ291bnQgYERlZmF1bHRDb3VudGRvd25VcGRhdGVDb3VudGB9LCBjdXJyZW50bHkgMzAsIG1lYW5pbmcgdGhlIHVzZXIgd2lsbCBnZXRcbiAgICAgKiAzMCBjb3VudGRvd24gdXBkYXRlcyB3aXRoIGZpeGVkIGludGVydmFscyBiZXR3ZWVuIHRoZW0gYmVmb3JlIHRoZSB2aWV3IGlzIGRlc3Ryb3llZC5cbiAgICAgKlxuICAgICAqIEBwcml2YXRlXG4gICAgICogQHJldHVybiB7bnVtYmVyfSBUaGUgaW50ZXJ2YWwgd2l0aCB3aGljaCBjb3VudGRvd24gdXBkYXRlcyBzaG91bGQgYmUgbWFkZSB0byB0aGUgdmlldydzIGNvbnRleHQuXG4gICAgICovXG4gICAgcHJpdmF0ZSBkZWZpbmVDb3VudGRvd25JbnRlcnZhbCgpOiBudW1iZXIgfCAnYW5pbWF0aW9uRnJhbWVzJ1xuICAgIHtcbiAgICAgICAgLy8gSWYgdGhlIHZpZXcgc2hvdWxkIHBlcnNpc3QsIG9yIGl0IHNob3VsZCBhdXRvLWRlc3Ryb3kgYnV0IHBlcmNpc2lvbiBoYXMgYmVlbiBtYW51YWxseSBzcGVjaWZpZWQsIGRvIG5vdGhpbmdcbiAgICAgICAgaWYgKCF0aGlzLnNob3dGb3IpIHRocm93IG5ldyBFcnJvcihgXG4gICAgICAgICAgICBBdXRvLWRlc3Ryb3kgY291bnRkb3duIHNlZW1zIHRvIGhhdmUgYmVlbiBpbml0aWF0ZWQgd2hlbiAnc2hvd0ZvcicgaGFzbid0IGJlZW4gc2V0LiBUaGlzIHNob3VsZG4ndCBoYXZlIGhhcHBlbmQuXG4gICAgICAgICAgICBQbGVhc2UgY29uc2lkZXIgZmlsaW5nIGFuIGlzc3VlIGFuZCBwcm92aWRpbmcgYSBzdGFjayB0cmFjZSBoZXJlOiBodHRwczovL2dpdGh1Yi5jb20vQmVTcHVua3kvYW5ndWxhci16ZW4vaXNzdWVzL25ldz9hc3NpZ25lZXM9QmVTcHVua3kmbGFiZWxzPSVGMCU5RiU5MCU5QitCdWcmdGVtcGxhdGU9YnVnX3JlcG9ydC5tZCZ0aXRsZT0lRjAlOUYlOTAlOUIrXG4gICAgICAgIGApO1xuICAgICAgICBcbiAgICAgICAgaWYgKHRoaXMuY291bnRkb3duSW50ZXJ2YWwgPT09ICdhbmltYXRpb25GcmFtZXMnKSByZXR1cm4gJ2FuaW1hdGlvbkZyYW1lcyc7XG5cbiAgICAgICAgaWYgKHRoaXMuY291bnRkb3duSW50ZXJ2YWwpIHJldHVybiBkdXJhdGlvblRvTXModGhpcy5jb3VudGRvd25JbnRlcnZhbCk7XG5cbiAgICAgICAgY29uc3Qgc2hvd0Zvck1zID0gZHVyYXRpb25Ub01zKHRoaXMuc2hvd0Zvcik7XG5cbiAgICAgICAgcmV0dXJuIHNob3dGb3JNcyAvIERlZmF1bHRDb3VudGRvd25VcGRhdGVDb3VudDtcbiAgICB9XG59XG4iXX0=