UNPKG

@dollhousemcp/mcp-server

Version:

DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.

369 lines 36.5 kB
/** * ElementQueryService - Orchestrator for element querying * * Coordinates pagination, filtering, and sorting services to provide * a unified query interface for element collections. * * DESIGN PRINCIPLES: * 1. Composition over inheritance - orchestrates independent services * 2. Correct operation order: filter → sort → paginate * 3. Complete metadata - returns all query context for debugging * 4. Stateless - safe for concurrent use and DI injection * 5. Validation - validates all inputs before processing * * OPERATION ORDER: * The order of operations is critical for correctness and performance: * 1. Filter: Reduce dataset to matching items only * 2. Sort: Order the filtered results * 3. Paginate: Extract requested page from sorted results * * This ensures that: * - Pagination operates on complete filtered+sorted dataset * - Sort operates on filtered dataset (more efficient) * - Results are deterministic and reproducible * * CONCURRENT MODIFICATION BEHAVIOR: * This service operates on element snapshots passed via the items parameter. * It does not read from disk or track file system changes. If elements are * modified on disk between fetch and query, results may be inconsistent with * the current file system state. This is expected behavior - the service is * stateless and designed for performance. For real-time consistency, callers * should re-fetch elements before each query operation. * * @module ElementQueryService */ import { logger } from '../../utils/logger.js'; import { PaginationService } from './PaginationService.js'; import { FilterService } from './FilterService.js'; import { SortService } from './SortService.js'; /** * ElementQueryService implementation * * Orchestrates three independent services to provide complete query capabilities: * - FilterService: Applies filter criteria to element arrays * - SortService: Sorts elements by specified fields * - PaginationService: Paginates sorted results * * All services are injected via constructor for testability and flexibility. * * @template T - Element type extending IElement * * @example * ```typescript * // Using factory (recommended) * const queryService = createElementQueryService<MyElement>(); * * // Query with all options * const result = queryService.query(elements, { * filters: { tags: ['typescript'], status: 'active' }, * sort: { sortBy: 'modified', sortOrder: 'desc' }, * pagination: { page: 2, pageSize: 25 } * }); * * console.log(result.items); // Paginated items * console.log(result.pagination); // Pagination metadata * console.log(result.sorting); // Applied sorting * console.log(result.filters.applied); // Applied filters * ``` */ export class ElementQueryService { paginationService; filterService; sortService; /** * Create a new ElementQueryService with injected dependencies * * DEPENDENCY INJECTION: * All services are provided via constructor to enable: * - Easy testing with mocks * - Service customization * - Container-based DI integration * * @param paginationService - Service for pagination operations * @param filterService - Service for filtering operations * @param sortService - Service for sorting operations */ constructor(paginationService, filterService, sortService) { this.paginationService = paginationService; this.filterService = filterService; this.sortService = sortService; } /** * Execute a complete query operation with full metadata * * OPERATION ORDER: * 1. Validate all query options * 2. Filter items based on criteria * 3. Sort filtered items * 4. Paginate sorted items * 5. Build complete result with metadata * * The returned QueryResult includes: * - items: The paginated subset of matching items * - pagination: Complete pagination metadata * - sorting: Applied sorting configuration * - filters: Summary of applied filters * * SNAPSHOT SEMANTICS: * This method operates on the items array passed to it at the time of the call. * It does not re-read elements from disk or track file system changes. If elements * are modified on disk between when they are fetched and when this method is called, * the results will reflect the state at fetch time, not the current file system state. * This is expected behavior for a stateless query service. For real-time consistency, * callers should re-fetch elements immediately before calling this method. * * @param items - Array of elements to query (snapshot at time of call) * @param options - Complete query configuration * @returns Query result with items and complete metadata * @throws {Error} If query options are invalid * * @example * ```typescript * const result = queryService.query(allPersonas, { * filters: { * nameContains: 'code', * tags: ['typescript'], * status: 'active' * }, * sort: { * sortBy: 'modified', * sortOrder: 'desc' * }, * pagination: { * page: 1, * pageSize: 10 * } * }); * * // Use result * console.log(`Showing ${result.items.length} of ${result.pagination.totalItems} items`); * console.log(`Sorted by ${result.sorting.sortBy} (${result.sorting.sortOrder})`); * console.log(`Applied ${result.filters.applied.count} filters`); * ``` */ query(items, options) { // Step 1: Validate all options this.validateOptions(options); // Step 2: Apply filters const filteredItems = this.filterService.filter(items, options?.filters); const appliedFilters = this.filterService.summarizeFilters(options?.filters); // Step 3: Sort filtered items const sortedItems = this.sortService.sort(filteredItems, options?.sort); const appliedSorting = { sortBy: options?.sort?.sortBy ?? this.sortService.getDefaultSorting().sortBy, sortOrder: options?.sort?.sortOrder ?? this.sortService.getDefaultSorting().sortOrder, }; // Step 4: Paginate sorted items const paginatedResult = this.paginationService.paginate(sortedItems, options?.pagination); // Step 5: Build complete query result const result = { items: paginatedResult.items, pagination: paginatedResult.pagination, sorting: appliedSorting, filters: { applied: appliedFilters, }, }; return result; } /** * Execute query and return only the items (no metadata) * * Convenience method for cases where you only need the items * and don't care about pagination metadata, applied filters, etc. * * This is equivalent to calling query() and extracting result.items, * but may be more convenient for simple use cases. * * @param items - Array of elements to query * @param options - Complete query configuration * @returns Array of items matching query (paginated) * @throws {Error} If query options are invalid * * @example * ```typescript * // Get just the items, ignore metadata * const items = queryService.queryItems(allPersonas, { * filters: { status: 'active' }, * sort: { sortBy: 'name' } * }); * * // Use items directly * items.forEach(item => console.log(item.metadata.name)); * ``` */ queryItems(items, options) { const result = this.query(items, options); return result.items; } /** * Get the default query options * * Returns a complete QueryOptions object with all defaults filled in. * Useful for: * - Understanding what defaults are used * - Building option objects incrementally * - Testing and documentation * * @returns Default query configuration with all fields specified * * @example * ```typescript * const defaults = queryService.getDefaultOptions(); * console.log(defaults); * // { * // pagination: { page: 1, pageSize: 25 }, * // filters: {}, * // sort: { sortBy: 'name', sortOrder: 'asc' } * // } * * // Use as base for custom options * const customOptions = { * ...defaults, * filters: { status: 'active' } * }; * ``` */ getDefaultOptions() { const defaultSorting = this.sortService.getDefaultSorting(); return { pagination: { page: 1, pageSize: 20, }, filters: {}, sort: { sortBy: defaultSorting.sortBy, sortOrder: defaultSorting.sortOrder, }, aggregate: {}, }; } /** * Validate query options without executing query * * Validates all three option types: * - Pagination options via PaginationService * - Filter criteria via FilterService * - Sort options via SortService * * Use this to validate user input before executing a query, * or to test option validity during development. * * @param options - Query options to validate * @returns True if all options are valid * @throws {Error} If any options are invalid with descriptive message * * @example * ```typescript * try { * queryService.validateOptions(userOptions); * // Options are valid, proceed with query * const result = queryService.query(items, userOptions); * } catch (error) { * // Options are invalid, show error to user * console.error('Invalid query options:', error.message); * } * ``` */ validateOptions(options) { if (!options) { return true; // No options is valid (use defaults) } try { // Validate filter criteria if (options.filters) { this.filterService.validateCriteria(options.filters); } // Validate sort options if (options.sort) { this.sortService.validateOptions(options.sort); } // Validate pagination options // PaginationService validates in paginate(), but we can validate structure here if (options.pagination) { this.validatePaginationStructure(options.pagination); } return true; } catch (error) { logger.error('ElementQueryService.validateOptions failed', { error: error instanceof Error ? error.message : String(error), }); throw error; } } /** * Validate pagination options structure * * Performs basic type and range validation for pagination options. * The PaginationService will perform additional validation during * actual pagination, but this catches obvious errors early. * * @param pagination - Pagination options to validate * @throws {Error} If pagination options are invalid * * @private */ validatePaginationStructure(pagination) { if (pagination.page !== undefined) { if (!Number.isInteger(pagination.page) || pagination.page < 1) { throw new Error('Pagination page must be a positive integer'); } } if (pagination.pageSize !== undefined) { if (!Number.isInteger(pagination.pageSize) || pagination.pageSize < 1) { throw new Error('Pagination pageSize must be a positive integer'); } } } } /** * Factory function to create a new ElementQueryService instance * * Creates a complete ElementQueryService with all dependencies instantiated. * This is the recommended way to create a query service for most use cases. * * For custom dependency injection or testing, you can construct * ElementQueryService directly with mock services. * * @template T - Element type extending IElement * @returns New ElementQueryService instance with all dependencies * * @example * ```typescript * // Create service for a specific element type * const personaQueryService = createElementQueryService<Persona>(); * const skillQueryService = createElementQueryService<Skill>(); * * // Use immediately * const results = personaQueryService.query(personas, { * filters: { status: 'active' }, * sort: { sortBy: 'name' }, * pagination: { page: 1, pageSize: 10 } * }); * ``` */ export function createElementQueryService() { const paginationService = new PaginationService(); const filterService = new FilterService(); const sortService = new SortService(); return new ElementQueryService(paginationService, filterService, sortService); } /** * Singleton instance for convenience * * Provides a ready-to-use query service for cases where DI is not needed * or when sharing a single instance across the application is acceptable. * * Since the service is stateless, sharing is safe for concurrent use. * * @example * ```typescript * import { elementQueryService } from './ElementQueryService.js'; * * const result = elementQueryService.query(items, options); * ``` */ export const elementQueryService = createElementQueryService(); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRWxlbWVudFF1ZXJ5U2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9zZXJ2aWNlcy9xdWVyeS9FbGVtZW50UXVlcnlTZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FpQ0c7QUFHSCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFVL0MsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDM0QsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ25ELE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUUvQzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0E2Qkc7QUFDSCxNQUFNLE9BQU8sbUJBQW1CO0lBZVg7SUFDQTtJQUNBO0lBaEJuQjs7Ozs7Ozs7Ozs7O09BWUc7SUFDSCxZQUNtQixpQkFBd0MsRUFDeEMsYUFBZ0MsRUFDaEMsV0FBNEI7UUFGNUIsc0JBQWlCLEdBQWpCLGlCQUFpQixDQUF1QjtRQUN4QyxrQkFBYSxHQUFiLGFBQWEsQ0FBbUI7UUFDaEMsZ0JBQVcsR0FBWCxXQUFXLENBQWlCO0lBQzVDLENBQUM7SUFFSjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztPQW9ERztJQUNJLEtBQUssQ0FBQyxLQUFVLEVBQUUsT0FBc0I7UUFDN0MsK0JBQStCO1FBQy9CLElBQUksQ0FBQyxlQUFlLENBQUMsT0FBTyxDQUFDLENBQUM7UUFFOUIsd0JBQXdCO1FBQ3hCLE1BQU0sYUFBYSxHQUFHLElBQUksQ0FBQyxhQUFhLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxPQUFPLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDekUsTUFBTSxjQUFjLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxnQkFBZ0Is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Iiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBFbGVtZW50UXVlcnlTZXJ2aWNlIC0gT3JjaGVzdHJhdG9yIGZvciBlbGVtZW50IHF1ZXJ5aW5nXG4gKlxuICogQ29vcmRpbmF0ZXMgcGFnaW5hdGlvbiwgZmlsdGVyaW5nLCBhbmQgc29ydGluZyBzZXJ2aWNlcyB0byBwcm92aWRlXG4gKiBhIHVuaWZpZWQgcXVlcnkgaW50ZXJmYWNlIGZvciBlbGVtZW50IGNvbGxlY3Rpb25zLlxuICpcbiAqIERFU0lHTiBQUklOQ0lQTEVTOlxuICogMS4gQ29tcG9zaXRpb24gb3ZlciBpbmhlcml0YW5jZSAtIG9yY2hlc3RyYXRlcyBpbmRlcGVuZGVudCBzZXJ2aWNlc1xuICogMi4gQ29ycmVjdCBvcGVyYXRpb24gb3JkZXI6IGZpbHRlciDihpIgc29ydCDihpIgcGFnaW5hdGVcbiAqIDMuIENvbXBsZXRlIG1ldGFkYXRhIC0gcmV0dXJucyBhbGwgcXVlcnkgY29udGV4dCBmb3IgZGVidWdnaW5nXG4gKiA0LiBTdGF0ZWxlc3MgLSBzYWZlIGZvciBjb25jdXJyZW50IHVzZSBhbmQgREkgaW5qZWN0aW9uXG4gKiA1LiBWYWxpZGF0aW9uIC0gdmFsaWRhdGVzIGFsbCBpbnB1dHMgYmVmb3JlIHByb2Nlc3NpbmdcbiAqXG4gKiBPUEVSQVRJT04gT1JERVI6XG4gKiBUaGUgb3JkZXIgb2Ygb3BlcmF0aW9ucyBpcyBjcml0aWNhbCBmb3IgY29ycmVjdG5lc3MgYW5kIHBlcmZvcm1hbmNlOlxuICogMS4gRmlsdGVyOiBSZWR1Y2UgZGF0YXNldCB0byBtYXRjaGluZyBpdGVtcyBvbmx5XG4gKiAyLiBTb3J0OiBPcmRlciB0aGUgZmlsdGVyZWQgcmVzdWx0c1xuICogMy4gUGFnaW5hdGU6IEV4dHJhY3QgcmVxdWVzdGVkIHBhZ2UgZnJvbSBzb3J0ZWQgcmVzdWx0c1xuICpcbiAqIFRoaXMgZW5zdXJlcyB0aGF0OlxuICogLSBQYWdpbmF0aW9uIG9wZXJhdGVzIG9uIGNvbXBsZXRlIGZpbHRlcmVkK3NvcnRlZCBkYXRhc2V0XG4gKiAtIFNvcnQgb3BlcmF0ZXMgb24gZmlsdGVyZWQgZGF0YXNldCAobW9yZSBlZmZpY2llbnQpXG4gKiAtIFJlc3VsdHMgYXJlIGRldGVybWluaXN0aWMgYW5kIHJlcHJvZHVjaWJsZVxuICpcbiAqIENPTkNVUlJFTlQgTU9ESUZJQ0FUSU9OIEJFSEFWSU9SOlxuICogVGhpcyBzZXJ2aWNlIG9wZXJhdGVzIG9uIGVsZW1lbnQgc25hcHNob3RzIHBhc3NlZCB2aWEgdGhlIGl0ZW1zIHBhcmFtZXRlci5cbiAqIEl0IGRvZXMgbm90IHJlYWQgZnJvbSBkaXNrIG9yIHRyYWNrIGZpbGUgc3lzdGVtIGNoYW5nZXMuIElmIGVsZW1lbnRzIGFyZVxuICogbW9kaWZpZWQgb24gZGlzayBiZXR3ZWVuIGZldGNoIGFuZCBxdWVyeSwgcmVzdWx0cyBtYXkgYmUgaW5jb25zaXN0ZW50IHdpdGhcbiAqIHRoZSBjdXJyZW50IGZpbGUgc3lzdGVtIHN0YXRlLiBUaGlzIGlzIGV4cGVjdGVkIGJlaGF2aW9yIC0gdGhlIHNlcnZpY2UgaXNcbiAqIHN0YXRlbGVzcyBhbmQgZGVzaWduZWQgZm9yIHBlcmZvcm1hbmNlLiBGb3IgcmVhbC10aW1lIGNvbnNpc3RlbmN5LCBjYWxsZXJzXG4gKiBzaG91bGQgcmUtZmV0Y2ggZWxlbWVudHMgYmVmb3JlIGVhY2ggcXVlcnkgb3BlcmF0aW9uLlxuICpcbiAqIEBtb2R1bGUgRWxlbWVudFF1ZXJ5U2VydmljZVxuICovXG5cbmltcG9ydCB7IElFbGVtZW50IH0gZnJvbSAnLi4vLi4vdHlwZXMvZWxlbWVudHMvSUVsZW1lbnQuanMnO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vLi4vdXRpbHMvbG9nZ2VyLmpzJztcbmltcG9ydCB7XG4gIElFbGVtZW50UXVlcnlTZXJ2aWNlLFxuICBJUGFnaW5hdGlvblNlcnZpY2UsXG4gIElGaWx0ZXJTZXJ2aWNlLFxuICBJU29ydFNlcnZpY2UsXG4gIFF1ZXJ5T3B0aW9ucyxcbiAgUXVlcnlSZXN1bHQsXG4gIFBhZ2luYXRpb25PcHRpb25zLFxufSBmcm9tICcuL3R5cGVzLmpzJztcbmltcG9ydCB7IFBhZ2luYXRpb25TZXJ2aWNlIH0gZnJvbSAnLi9QYWdpbmF0aW9uU2VydmljZS5qcyc7XG5pbXBvcnQgeyBGaWx0ZXJTZXJ2aWNlIH0gZnJvbSAnLi9GaWx0ZXJTZXJ2aWNlLmpzJztcbmltcG9ydCB7IFNvcnRTZXJ2aWNlIH0gZnJvbSAnLi9Tb3J0U2VydmljZS5qcyc7XG5cbi8qKlxuICogRWxlbWVudFF1ZXJ5U2VydmljZSBpbXBsZW1lbnRhdGlvblxuICpcbiAqIE9yY2hlc3RyYXRlcyB0aHJlZSBpbmRlcGVuZGVudCBzZXJ2aWNlcyB0byBwcm92aWRlIGNvbXBsZXRlIHF1ZXJ5IGNhcGFiaWxpdGllczpcbiAqIC0gRmlsdGVyU2VydmljZTogQXBwbGllcyBmaWx0ZXIgY3JpdGVyaWEgdG8gZWxlbWVudCBhcnJheXNcbiAqIC0gU29ydFNlcnZpY2U6IFNvcnRzIGVsZW1lbnRzIGJ5IHNwZWNpZmllZCBmaWVsZHNcbiAqIC0gUGFnaW5hdGlvblNlcnZpY2U6IFBhZ2luYXRlcyBzb3J0ZWQgcmVzdWx0c1xuICpcbiAqIEFsbCBzZXJ2aWNlcyBhcmUgaW5qZWN0ZWQgdmlhIGNvbnN0cnVjdG9yIGZvciB0ZXN0YWJpbGl0eSBhbmQgZmxleGliaWxpdHkuXG4gKlxuICogQHRlbXBsYXRlIFQgLSBFbGVtZW50IHR5cGUgZXh0ZW5kaW5nIElFbGVtZW50XG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIC8vIFVzaW5nIGZhY3RvcnkgKHJlY29tbWVuZGVkKVxuICogY29uc3QgcXVlcnlTZXJ2aWNlID0gY3JlYXRlRWxlbWVudFF1ZXJ5U2VydmljZTxNeUVsZW1lbnQ+KCk7XG4gKlxuICogLy8gUXVlcnkgd2l0aCBhbGwgb3B0aW9uc1xuICogY29uc3QgcmVzdWx0ID0gcXVlcnlTZXJ2aWNlLnF1ZXJ5KGVsZW1lbnRzLCB7XG4gKiAgIGZpbHRlcnM6IHsgdGFnczogWyd0eXBlc2NyaXB0J10sIHN0YXR1czogJ2FjdGl2ZScgfSxcbiAqICAgc29ydDogeyBzb3J0Qnk6ICdtb2RpZmllZCcsIHNvcnRPcmRlcjogJ2Rlc2MnIH0sXG4gKiAgIHBhZ2luYXRpb246IHsgcGFnZTogMiwgcGFnZVNpemU6IDI1IH1cbiAqIH0pO1xuICpcbiAqIGNvbnNvbGUubG9nKHJlc3VsdC5pdGVtcyk7ICAgICAgICAgICAvLyBQYWdpbmF0ZWQgaXRlbXNcbiAqIGNvbnNvbGUubG9nKHJlc3VsdC5wYWdpbmF0aW9uKTsgICAgICAvLyBQYWdpbmF0aW9uIG1ldGFkYXRhXG4gKiBjb25zb2xlLmxvZyhyZXN1bHQuc29ydGluZyk7ICAgICAgICAgLy8gQXBwbGllZCBzb3J0aW5nXG4gKiBjb25zb2xlLmxvZyhyZXN1bHQuZmlsdGVycy5hcHBsaWVkKTsgLy8gQXBwbGllZCBmaWx0ZXJzXG4gKiBgYGBcbiAqL1xuZXhwb3J0IGNsYXNzIEVsZW1lbnRRdWVyeVNlcnZpY2U8VCBleHRlbmRzIElFbGVtZW50ID0gSUVsZW1lbnQ+IGltcGxlbWVudHMgSUVsZW1lbnRRdWVyeVNlcnZpY2U8VD4ge1xuICAvKipcbiAgICogQ3JlYXRlIGEgbmV3IEVsZW1lbnRRdWVyeVNlcnZpY2Ugd2l0aCBpbmplY3RlZCBkZXBlbmRlbmNpZXNcbiAgICpcbiAgICogREVQRU5ERU5DWSBJTkpFQ1RJT046XG4gICAqIEFsbCBzZXJ2aWNlcyBhcmUgcHJvdmlkZWQgdmlhIGNvbnN0cnVjdG9yIHRvIGVuYWJsZTpcbiAgICogLSBFYXN5IHRlc3Rpbmcgd2l0aCBtb2Nrc1xuICAgKiAtIFNlcnZpY2UgY3VzdG9taXphdGlvblxuICAgKiAtIENvbnRhaW5lci1iYXNlZCBESSBpbnRlZ3JhdGlvblxuICAgKlxuICAgKiBAcGFyYW0gcGFnaW5hdGlvblNlcnZpY2UgLSBTZXJ2aWNlIGZvciBwYWdpbmF0aW9uIG9wZXJhdGlvbnNcbiAgICogQHBhcmFtIGZpbHRlclNlcnZpY2UgLSBTZXJ2aWNlIGZvciBmaWx0ZXJpbmcgb3BlcmF0aW9uc1xuICAgKiBAcGFyYW0gc29ydFNlcnZpY2UgLSBTZXJ2aWNlIGZvciBzb3J0aW5nIG9wZXJhdGlvbnNcbiAgICovXG4gIGNvbnN0cnVjdG9yKFxuICAgIHByaXZhdGUgcmVhZG9ubHkgcGFnaW5hdGlvblNlcnZpY2U6IElQYWdpbmF0aW9uU2VydmljZTxUPixcbiAgICBwcml2YXRlIHJlYWRvbmx5IGZpbHRlclNlcnZpY2U6IElGaWx0ZXJTZXJ2aWNlPFQ+LFxuICAgIHByaXZhdGUgcmVhZG9ubHkgc29ydFNlcnZpY2U6IElTb3J0U2VydmljZTxUPlxuICApIHt9XG5cbiAgLyoqXG4gICAqIEV4ZWN1dGUgYSBjb21wbGV0ZSBxdWVyeSBvcGVyYXRpb24gd2l0aCBmdWxsIG1ldGFkYXRhXG4gICAqXG4gICAqIE9QRVJBVElPTiBPUkRFUjpcbiAgICogMS4gVmFsaWRhdGUgYWxsIHF1ZXJ5IG9wdGlvbnNcbiAgICogMi4gRmlsdGVyIGl0ZW1zIGJhc2VkIG9uIGNyaXRlcmlhXG4gICAqIDMuIFNvcnQgZmlsdGVyZWQgaXRlbXNcbiAgICogNC4gUGFnaW5hdGUgc29ydGVkIGl0ZW1zXG4gICAqIDUuIEJ1aWxkIGNvbXBsZXRlIHJlc3VsdCB3aXRoIG1ldGFkYXRhXG4gICAqXG4gICAqIFRoZSByZXR1cm5lZCBRdWVyeVJlc3VsdCBpbmNsdWRlczpcbiAgICogLSBpdGVtczogVGhlIHBhZ2luYXRlZCBzdWJzZXQgb2YgbWF0Y2hpbmcgaXRlbXNcbiAgICogLSBwYWdpbmF0aW9uOiBDb21wbGV0ZSBwYWdpbmF0aW9uIG1ldGFkYXRhXG4gICAqIC0gc29ydGluZzogQXBwbGllZCBzb3J0aW5nIGNvbmZpZ3VyYXRpb25cbiAgICogLSBmaWx0ZXJzOiBTdW1tYXJ5IG9mIGFwcGxpZWQgZmlsdGVyc1xuICAgKlxuICAgKiBTTkFQU0hPVCBTRU1BTlRJQ1M6XG4gICAqIFRoaXMgbWV0aG9kIG9wZXJhdGVzIG9uIHRoZSBpdGVtcyBhcnJheSBwYXNzZWQgdG8gaXQgYXQgdGhlIHRpbWUgb2YgdGhlIGNhbGwuXG4gICAqIEl0IGRvZXMgbm90IHJlLXJlYWQgZWxlbWVudHMgZnJvbSBkaXNrIG9yIHRyYWNrIGZpbGUgc3lzdGVtIGNoYW5nZXMuIElmIGVsZW1lbnRzXG4gICAqIGFyZSBtb2RpZmllZCBvbiBkaXNrIGJldHdlZW4gd2hlbiB0aGV5IGFyZSBmZXRjaGVkIGFuZCB3aGVuIHRoaXMgbWV0aG9kIGlzIGNhbGxlZCxcbiAgICogdGhlIHJlc3VsdHMgd2lsbCByZWZsZWN0IHRoZSBzdGF0ZSBhdCBmZXRjaCB0aW1lLCBub3QgdGhlIGN1cnJlbnQgZmlsZSBzeXN0ZW0gc3RhdGUuXG4gICAqIFRoaXMgaXMgZXhwZWN0ZWQgYmVoYXZpb3IgZm9yIGEgc3RhdGVsZXNzIHF1ZXJ5IHNlcnZpY2UuIEZvciByZWFsLXRpbWUgY29uc2lzdGVuY3ksXG4gICAqIGNhbGxlcnMgc2hvdWxkIHJlLWZldGNoIGVsZW1lbnRzIGltbWVkaWF0ZWx5IGJlZm9yZSBjYWxsaW5nIHRoaXMgbWV0aG9kLlxuICAgKlxuICAgKiBAcGFyYW0gaXRlbXMgLSBBcnJheSBvZiBlbGVtZW50cyB0byBxdWVyeSAoc25hcHNob3QgYXQgdGltZSBvZiBjYWxsKVxuICAgKiBAcGFyYW0gb3B0aW9ucyAtIENvbXBsZXRlIHF1ZXJ5IGNvbmZpZ3VyYXRpb25cbiAgICogQHJldHVybnMgUXVlcnkgcmVzdWx0IHdpdGggaXRlbXMgYW5kIGNvbXBsZXRlIG1ldGFkYXRhXG4gICAqIEB0aHJvd3Mge0Vycm9yfSBJZiBxdWVyeSBvcHRpb25zIGFyZSBpbnZhbGlkXG4gICAqXG4gICAqIEBleGFtcGxlXG4gICAqIGBgYHR5cGVzY3JpcHRcbiAgICogY29uc3QgcmVzdWx0ID0gcXVlcnlTZXJ2aWNlLnF1ZXJ5KGFsbFBlcnNvbmFzLCB7XG4gICAqICAgZmlsdGVyczoge1xuICAgKiAgICAgbmFtZUNvbnRhaW5zOiAnY29kZScsXG4gICAqICAgICB0YWdzOiBbJ3R5cGVzY3JpcHQnXSxcbiAgICogICAgIHN0YXR1czogJ2FjdGl2ZSdcbiAgICogICB9LFxuICAgKiAgIHNvcnQ6IHtcbiAgICogICAgIHNvcnRCeTogJ21vZGlmaWVkJyxcbiAgICogICAgIHNvcnRPcmRlcjogJ2Rlc2MnXG4gICAqICAgfSxcbiAgICogICBwYWdpbmF0aW9uOiB7XG4gICAqICAgICBwYWdlOiAxLFxuICAgKiAgICAgcGFnZVNpemU6IDEwXG4gICAqICAgfVxuICAgKiB9KTtcbiAgICpcbiAgICogLy8gVXNlIHJlc3VsdFxuICAgKiBjb25zb2xlLmxvZyhgU2hvd2luZyAke3Jlc3VsdC5pdGVtcy5sZW5ndGh9IG9mICR7cmVzdWx0LnBhZ2luYXRpb24udG90YWxJdGVtc30gaXRlbXNgKTtcbiAgICogY29uc29sZS5sb2coYFNvcnRlZCBieSAke3Jlc3VsdC5zb3J0aW5nLnNvcnRCeX0gKCR7cmVzdWx0LnNvcnRpbmcuc29ydE9yZGVyfSlgKTtcbiAgICogY29uc29sZS5sb2coYEFwcGxpZWQgJHtyZXN1bHQuZmlsdGVycy5hcHBsaWVkLmNvdW50fSBmaWx0ZXJzYCk7XG4gICAqIGBgYFxuICAgKi9cbiAgcHVibGljIHF1ZXJ5KGl0ZW1zOiBUW10sIG9wdGlvbnM/OiBRdWVyeU9wdGlvbnMpOiBRdWVyeVJlc3VsdDxUPiB7XG4gICAgLy8gU3RlcCAxOiBWYWxpZGF0ZSBhbGwgb3B0aW9uc1xuICAgIHRoaXMudmFsaWRhdGVPcHRpb25zKG9wdGlvbnMpO1xuXG4gICAgLy8gU3RlcCAyOiBBcHBseSBmaWx0ZXJzXG4gICAgY29uc3QgZmlsdGVyZWRJdGVtcyA9IHRoaXMuZmlsdGVyU2VydmljZS5maWx0ZXIoaXRlbXMsIG9wdGlvbnM/LmZpbHRlcnMpO1xuICAgIGNvbnN0IGFwcGxpZWRGaWx0ZXJzID0gdGhpcy5maWx0ZXJTZXJ2aWNlLnN1bW1hcml6ZUZpbHRlcnMob3B0aW9ucz8uZmlsdGVycyk7XG5cbiAgICAvLyBTdGVwIDM6IFNvcnQgZmlsdGVyZWQgaXRlbXNcbiAgICBjb25zdCBzb3J0ZWRJdGVtcyA9IHRoaXMuc29ydFNlcnZpY2Uuc29ydChmaWx0ZXJlZEl0ZW1zLCBvcHRpb25zPy5zb3J0KTtcbiAgICBjb25zdCBhcHBsaWVkU29ydGluZyA9IHtcbiAgICAgIHNvcnRCeTogb3B0aW9ucz8uc29ydD8uc29ydEJ5ID8/IHRoaXMuc29ydFNlcnZpY2UuZ2V0RGVmYXVsdFNvcnRpbmcoKS5zb3J0QnksXG4gICAgICBzb3J0T3JkZXI6IG9wdGlvbnM/LnNvcnQ/LnNvcnRPcmRlciA/PyB0aGlzLnNvcnRTZXJ2aWNlLmdldERlZmF1bHRTb3J0aW5nKCkuc29ydE9yZGVyLFxuICAgIH07XG5cbiAgICAvLyBTdGVwIDQ6IFBhZ2luYXRlIHNvcnRlZCBpdGVtc1xuICAgIGNvbnN0IHBhZ2luYXRlZFJlc3VsdCA9IHRoaXMucGFnaW5hdGlvblNlcnZpY2UucGFnaW5hdGUoc29ydGVkSXRlbXMsIG9wdGlvbnM/LnBhZ2luYXRpb24pO1xuXG4gICAgLy8gU3RlcCA1OiBCdWlsZCBjb21wbGV0ZSBxdWVyeSByZXN1bHRcbiAgICBjb25zdCByZXN1bHQ6IFF1ZXJ5UmVzdWx0PFQ+ID0ge1xuICAgICAgaXRlbXM6IHBhZ2luYXRlZFJlc3VsdC5pdGVtcyxcbiAgICAgIHBhZ2luYXRpb246IHBhZ2luYXRlZFJlc3VsdC5wYWdpbmF0aW9uLFxuICAgICAgc29ydGluZzogYXBwbGllZFNvcnRpbmcsXG4gICAgICBmaWx0ZXJzOiB7XG4gICAgICAgIGFwcGxpZWQ6IGFwcGxpZWRGaWx0ZXJzLFxuICAgICAgfSxcbiAgICB9O1xuXG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuXG4gIC8qKlxuICAgKiBFeGVjdXRlIHF1ZXJ5IGFuZCByZXR1cm4gb25seSB0aGUgaXRlbXMgKG5vIG1ldGFkYXRhKVxuICAgKlxuICAgKiBDb252ZW5pZW5jZSBtZXRob2QgZm9yIGNhc2VzIHdoZXJlIHlvdSBvbmx5IG5lZWQgdGhlIGl0ZW1zXG4gICAqIGFuZCBkb24ndCBjYXJlIGFib3V0IHBhZ2luYXRpb24gbWV0YWRhdGEsIGFwcGxpZWQgZmlsdGVycywgZXRjLlxuICAgKlxuICAgKiBUaGlzIGlzIGVxdWl2YWxlbnQgdG8gY2FsbGluZyBxdWVyeSgpIGFuZCBleHRyYWN0aW5nIHJlc3VsdC5pdGVtcyxcbiAgICogYnV0IG1heSBiZSBtb3JlIGNvbnZlbmllbnQgZm9yIHNpbXBsZSB1c2UgY2FzZXMuXG4gICAqXG4gICAqIEBwYXJhbSBpdGVtcyAtIEFycmF5IG9mIGVsZW1lbnRzIHRvIHF1ZXJ5XG4gICAqIEBwYXJhbSBvcHRpb25zIC0gQ29tcGxldGUgcXVlcnkgY29uZmlndXJhdGlvblxuICAgKiBAcmV0dXJucyBBcnJheSBvZiBpdGVtcyBtYXRjaGluZyBxdWVyeSAocGFnaW5hdGVkKVxuICAgKiBAdGhyb3dzIHtFcnJvcn0gSWYgcXVlcnkgb3B0aW9ucyBhcmUgaW52YWxpZFxuICAgKlxuICAgKiBAZXhhbXBsZVxuICAgKiBgYGB0eXBlc2NyaXB0XG4gICAqIC8vIEdldCBqdXN0IHRoZSBpdGVtcywgaWdub3JlIG1ldGFkYXRhXG4gICAqIGNvbnN0IGl0ZW1zID0gcXVlcnlTZXJ2aWNlLnF1ZXJ5SXRlbXMoYWxsUGVyc29uYXMsIHtcbiAgICogICBmaWx0ZXJzOiB7IHN0YXR1czogJ2FjdGl2ZScgfSxcbiAgICogICBzb3J0OiB7IHNvcnRCeTogJ25hbWUnIH1cbiAgICogfSk7XG4gICAqXG4gICAqIC8vIFVzZSBpdGVtcyBkaXJlY3RseVxuICAgKiBpdGVtcy5mb3JFYWNoKGl0ZW0gPT4gY29uc29sZS5sb2coaXRlbS5tZXRhZGF0YS5uYW1lKSk7XG4gICAqIGBgYFxuICAgKi9cbiAgcHVibGljIHF1ZXJ5SXRlbXMoaXRlbXM6IFRbXSwgb3B0aW9ucz86IFF1ZXJ5T3B0aW9ucyk6IFRbXSB7XG4gICAgY29uc3QgcmVzdWx0ID0gdGhpcy5xdWVyeShpdGVtcywgb3B0aW9ucyk7XG4gICAgcmV0dXJuIHJlc3VsdC5pdGVtcztcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgdGhlIGRlZmF1bHQgcXVlcnkgb3B0aW9uc1xuICAgKlxuICAgKiBSZXR1cm5zIGEgY29tcGxldGUgUXVlcnlPcHRpb25zIG9iamVjdCB3aXRoIGFsbCBkZWZhdWx0cyBmaWxsZWQgaW4uXG4gICAqIFVzZWZ1bCBmb3I6XG4gICAqIC0gVW5kZXJzdGFuZGluZyB3aGF0IGRlZmF1bHRzIGFyZSB1c2VkXG4gICAqIC0gQnVpbGRpbmcgb3B0aW9uIG9iamVjdHMgaW5jcmVtZW50YWxseVxuICAgKiAtIFRlc3RpbmcgYW5kIGRvY3VtZW50YXRpb25cbiAgICpcbiAgICogQHJldHVybnMgRGVmYXVsdCBxdWVyeSBjb25maWd1cmF0aW9uIHdpdGggYWxsIGZpZWxkcyBzcGVjaWZpZWRcbiAgICpcbiAgICogQGV4YW1wbGVcbiAgICogYGBgdHlwZXNjcmlwdFxuICAgKiBjb25zdCBkZWZhdWx0cyA9IHF1ZXJ5U2VydmljZS5nZXREZWZhdWx0T3B0aW9ucygpO1xuICAgKiBjb25zb2xlLmxvZyhkZWZhdWx0cyk7XG4gICAqIC8vIHtcbiAgICogLy8gICBwYWdpbmF0aW9uOiB7IHBhZ2U6IDEsIHBhZ2VTaXplOiAyNSB9LFxuICAgKiAvLyAgIGZpbHRlcnM6IHt9LFxuICAgKiAvLyAgIHNvcnQ6IHsgc29ydEJ5OiAnbmFtZScsIHNvcnRPcmRlcjogJ2FzYycgfVxuICAgKiAvLyB9XG4gICAqXG4gICAqIC8vIFVzZSBhcyBiYXNlIGZvciBjdXN0b20gb3B0aW9uc1xuICAgKiBjb25zdCBjdXN0b21PcHRpb25zID0ge1xuICAgKiAgIC4uLmRlZmF1bHRzLFxuICAgKiAgIGZpbHRlcnM6IHsgc3RhdHVzOiAnYWN0aXZlJyB9XG4gICAqIH07XG4gICAqIGBgYFxuICAgKi9cbiAgcHVibGljIGdldERlZmF1bHRPcHRpb25zKCk6IFJlcXVpcmVkPFF1ZXJ5T3B0aW9ucz4ge1xuICAgIGNvbnN0IGRlZmF1bHRTb3J0aW5nID0gdGhpcy5zb3J0U2VydmljZS5nZXREZWZhdWx0U29ydGluZygpO1xuXG4gICAgcmV0dXJuIHtcbiAgICAgIHBhZ2luYXRpb246IHtcbiAgICAgICAgcGFnZTogMSxcbiAgICAgICAgcGFnZVNpemU6IDIwLFxuICAgICAgfSxcbiAgICAgIGZpbHRlcnM6IHt9LFxuICAgICAgc29ydDoge1xuICAgICAgICBzb3J0Qnk6IGRlZmF1bHRTb3J0aW5nLnNvcnRCeSxcbiAgICAgICAgc29ydE9yZGVyOiBkZWZhdWx0U29ydGluZy5zb3J0T3JkZXIsXG4gICAgICB9LFxuICAgICAgYWdncmVnYXRlOiB7fSxcbiAgICB9O1xuICB9XG5cbiAgLyoqXG4gICAqIFZhbGlkYXRlIHF1ZXJ5IG9wdGlvbnMgd2l0aG91dCBleGVjdXRpbmcgcXVlcnlcbiAgICpcbiAgICogVmFsaWRhdGVzIGFsbCB0aHJlZSBvcHRpb24gdHlwZXM6XG4gICAqIC0gUGFnaW5hdGlvbiBvcHRpb25zIHZpYSBQYWdpbmF0aW9uU2VydmljZVxuICAgKiAtIEZpbHRlciBjcml0ZXJpYSB2aWEgRmlsdGVyU2VydmljZVxuICAgKiAtIFNvcnQgb3B0aW9ucyB2aWEgU29ydFNlcnZpY2VcbiAgICpcbiAgICogVXNlIHRoaXMgdG8gdmFsaWRhdGUgdXNlciBpbnB1dCBiZWZvcmUgZXhlY3V0aW5nIGEgcXVlcnksXG4gICAqIG9yIHRvIHRlc3Qgb3B0aW9uIHZhbGlkaXR5IGR1cmluZyBkZXZlbG9wbWVudC5cbiAgICpcbiAgICogQHBhcmFtIG9wdGlvbnMgLSBRdWVyeSBvcHRpb25zIHRvIHZhbGlkYXRlXG4gICAqIEByZXR1cm5zIFRydWUgaWYgYWxsIG9wdGlvbnMgYXJlIHZhbGlkXG4gICAqIEB0aHJvd3Mge0Vycm9yfSBJZiBhbnkgb3B0aW9ucyBhcmUgaW52YWxpZCB3aXRoIGRlc2NyaXB0aXZlIG1lc3NhZ2VcbiAgICpcbiAgICogQGV4YW1wbGVcbiAgICogYGBgdHlwZXNjcmlwdFxuICAgKiB0cnkge1xuICAgKiAgIHF1ZXJ5U2VydmljZS52YWxpZGF0ZU9wdGlvbnModXNlck9wdGlvbnMpO1xuICAgKiAgIC8vIE9wdGlvbnMgYXJlIHZhbGlkLCBwcm9jZWVkIHdpdGggcXVlcnlcbiAgICogICBjb25zdCByZXN1bHQgPSBxdWVyeVNlcnZpY2UucXVlcnkoaXRlbXMsIHVzZXJPcHRpb25zKTtcbiAgICogfSBjYXRjaCAoZXJyb3IpIHtcbiAgICogICAvLyBPcHRpb25zIGFyZSBpbnZhbGlkLCBzaG93IGVycm9yIHRvIHVzZXJcbiAgICogICBjb25zb2xlLmVycm9yKCdJbnZhbGlkIHF1ZXJ5IG9wdGlvbnM6JywgZXJyb3IubWVzc2FnZSk7XG4gICAqIH1cbiAgICogYGBgXG4gICAqL1xuICBwdWJsaWMgdmFsaWRhdGVPcHRpb25zKG9wdGlvbnM/OiBRdWVyeU9wdGlvbnMpOiBib29sZWFuIHtcbiAgICBpZiAoIW9wdGlvbnMpIHtcbiAgICAgIHJldHVybiB0cnVlOyAvLyBObyBvcHRpb25zIGlzIHZhbGlkICh1c2UgZGVmYXVsdHMpXG4gICAgfVxuXG4gICAgdHJ5IHtcbiAgICAgIC8vIFZhbGlkYXRlIGZpbHRlciBjcml0ZXJpYVxuICAgICAgaWYgKG9wdGlvbnMuZmlsdGVycykge1xuICAgICAgICB0aGlzLmZpbHRlclNlcnZpY2UudmFsaWRhdGVDcml0ZXJpYShvcHRpb25zLmZpbHRlcnMpO1xuICAgICAgfVxuXG4gICAgICAvLyBWYWxpZGF0ZSBzb3J0IG9wdGlvbnNcbiAgICAgIGlmIChvcHRpb25zLnNvcnQpIHtcbiAgICAgICAgdGhpcy5zb3J0U2VydmljZS52YWxpZGF0ZU9wdGlvbnMob3B0aW9ucy5zb3J0KTtcbiAgICAgIH1cblxuICAgICAgLy8gVmFsaWRhdGUgcGFnaW5hdGlvbiBvcHRpb25zXG4gICAgICAvLyBQYWdpbmF0aW9uU2VydmljZSB2YWxpZGF0ZXMgaW4gcGFnaW5hdGUoKSwgYnV0IHdlIGNhbiB2YWxpZGF0ZSBzdHJ1Y3R1cmUgaGVyZVxuICAgICAgaWYgKG9wdGlvbnMucGFnaW5hdGlvbikge1xuICAgICAgICB0aGlzLnZhbGlkYXRlUGFnaW5hdGlvblN0cnVjdHVyZShvcHRpb25zLnBhZ2luYXRpb24pO1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgbG9nZ2VyLmVycm9yKCdFbGVtZW50UXVlcnlTZXJ2aWNlLnZhbGlkYXRlT3B0aW9ucyBmYWlsZWQnLCB7XG4gICAgICAgIGVycm9yOiBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvciksXG4gICAgICB9KTtcbiAgICAgIHRocm93IGVycm9yO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBWYWxpZGF0ZSBwYWdpbmF0aW9uIG9wdGlvbnMgc3RydWN0dXJlXG4gICAqXG4gICAqIFBlcmZvcm1zIGJhc2ljIHR5cGUgYW5kIHJhbmdlIHZhbGlkYXRpb24gZm9yIHBhZ2luYXRpb24gb3B0aW9ucy5cbiAgICogVGhlIFBhZ2luYXRpb25TZXJ2aWNlIHdpbGwgcGVyZm9ybSBhZGRpdGlvbmFsIHZhbGlkYXRpb24gZHVyaW5nXG4gICAqIGFjdHVhbCBwYWdpbmF0aW9uLCBidXQgdGhpcyBjYXRjaGVzIG9idmlvdXMgZXJyb3JzIGVhcmx5LlxuICAgKlxuICAgKiBAcGFyYW0gcGFnaW5hdGlvbiAtIFBhZ2luYXRpb24gb3B0aW9ucyB0byB2YWxpZGF0ZVxuICAgKiBAdGhyb3dzIHtFcnJvcn0gSWYgcGFnaW5hdGlvbiBvcHRpb25zIGFyZSBpbnZhbGlkXG4gICAqXG4gICAqIEBwcml2YXRlXG4gICAqL1xuICBwcml2YXRlIHZhbGlkYXRlUGFnaW5hdGlvblN0cnVjdHVyZShwYWdpbmF0aW9uOiBQYWdpbmF0aW9uT3B0aW9ucyk6IHZvaWQge1xuICAgIGlmIChwYWdpbmF0aW9uLnBhZ2UgIT09IHVuZGVmaW5lZCkge1xuICAgICAgaWYgKCFOdW1iZXIuaXNJbnRlZ2VyKHBhZ2luYXRpb24ucGFnZSkgfHwgcGFnaW5hdGlvbi5wYWdlIDwgMSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1BhZ2luYXRpb24gcGFnZSBtdXN0IGJlIGEgcG9zaXRpdmUgaW50ZWdlcicpO1xuICAgICAgfVxuICAgIH1cblxuICAgIGlmIChwYWdpbmF0aW9uLnBhZ2VTaXplICE9PSB1bmRlZmluZWQpIHtcbiAgICAgIGlmICghTnVtYmVyLmlzSW50ZWdlcihwYWdpbmF0aW9uLnBhZ2VTaXplKSB8fCBwYWdpbmF0aW9uLnBhZ2VTaXplIDwgMSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1BhZ2luYXRpb24gcGFnZVNpemUgbXVzdCBiZSBhIHBvc2l0aXZlIGludGVnZXInKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cblxuLyoqXG4gKiBGYWN0b3J5IGZ1bmN0aW9uIHRvIGNyZWF0ZSBhIG5ldyBFbGVtZW50UXVlcnlTZXJ2aWNlIGluc3RhbmNlXG4gKlxuICogQ3JlYXRlcyBhIGNvbXBsZXRlIEVsZW1lbnRRdWVyeVNlcnZpY2Ugd2l0aCBhbGwgZGVwZW5kZW5jaWVzIGluc3RhbnRpYXRlZC5cbiAqIFRoaXMgaXMgdGhlIHJlY29tbWVuZGVkIHdheSB0byBjcmVhdGUgYSBxdWVyeSBzZXJ2aWNlIGZvciBtb3N0IHVzZSBjYXNlcy5cbiAqXG4gKiBGb3IgY3VzdG9tIGRlcGVuZGVuY3kgaW5qZWN0aW9uIG9yIHRlc3RpbmcsIHlvdSBjYW4gY29uc3RydWN0XG4gKiBFbGVtZW50UXVlcnlTZXJ2aWNlIGRpcmVjdGx5IHdpdGggbW9jayBzZXJ2aWNlcy5cbiAqXG4gKiBAdGVtcGxhdGUgVCAtIEVsZW1lbnQgdHlwZSBleHRlbmRpbmcgSUVsZW1lbnRcbiAqIEByZXR1cm5zIE5ldyBFbGVtZW50UXVlcnlTZXJ2aWNlIGluc3RhbmNlIHdpdGggYWxsIGRlcGVuZGVuY2llc1xuICpcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiAvLyBDcmVhdGUgc2VydmljZSBmb3IgYSBzcGVjaWZpYyBlbGVtZW50IHR5cGVcbiAqIGNvbnN0IHBlcnNvbmFRdWVyeVNlcnZpY2UgPSBjcmVhdGVFbGVtZW50UXVlcnlTZXJ2aWNlPFBlcnNvbmE+KCk7XG4gKiBjb25zdCBza2lsbFF1ZXJ5U2VydmljZSA9IGNyZWF0ZUVsZW1lbnRRdWVyeVNlcnZpY2U8U2tpbGw+KCk7XG4gKlxuICogLy8gVXNlIGltbWVkaWF0ZWx5XG4gKiBjb25zdCByZXN1bHRzID0gcGVyc29uYVF1ZXJ5U2VydmljZS5xdWVyeShwZXJzb25hcywge1xuICogICBmaWx0ZXJzOiB7IHN0YXR1czogJ2FjdGl2ZScgfSxcbiAqICAgc29ydDogeyBzb3J0Qnk6ICduYW1lJyB9LFxuICogICBwYWdpbmF0aW9uOiB7IHBhZ2U6IDEsIHBhZ2VTaXplOiAxMCB9XG4gKiB9KTtcbiAqIGBgYFxuICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlRWxlbWVudFF1ZXJ5U2VydmljZTxUIGV4dGVuZHMgSUVsZW1lbnQ+KCk6IElFbGVtZW50UXVlcnlTZXJ2aWNlPFQ+IHtcbiAgY29uc3QgcGFnaW5hdGlvblNlcnZpY2UgPSBuZXcgUGFnaW5hdGlvblNlcnZpY2U8VD4oKTtcbiAgY29uc3QgZmlsdGVyU2VydmljZSA9IG5ldyBGaWx0ZXJTZXJ2aWNlPFQ+KCk7XG4gIGNvbnN0IHNvcnRTZXJ2aWNlID0gbmV3IFNvcnRTZXJ2aWNlPFQ+KCk7XG5cbiAgcmV0dXJuIG5ldyBFbGVtZW50UXVlcnlTZXJ2aWNlPFQ+KHBhZ2luYXRpb25TZXJ2aWNlLCBmaWx0ZXJTZXJ2aWNlLCBzb3J0U2VydmljZSk7XG59XG5cbi8qKlxuICogU2luZ2xldG9uIGluc3RhbmNlIGZvciBjb252ZW5pZW5jZVxuICpcbiAqIFByb3ZpZGVzIGEgcmVhZHktdG8tdXNlIHF1ZXJ5IHNlcnZpY2UgZm9yIGNhc2VzIHdoZXJlIERJIGlzIG5vdCBuZWVkZWRcbiAqIG9yIHdoZW4gc2hhcmluZyBhIHNpbmdsZSBpbnN0YW5jZSBhY3Jvc3MgdGhlIGFwcGxpY2F0aW9uIGlzIGFjY2VwdGFibGUuXG4gKlxuICogU2luY2UgdGhlIHNlcnZpY2UgaXMgc3RhdGVsZXNzLCBzaGFyaW5nIGlzIHNhZmUgZm9yIGNvbmN1cnJlbnQgdXNlLlxuICpcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiBpbXBvcnQgeyBlbGVtZW50UXVlcnlTZXJ2aWNlIH0gZnJvbSAnLi9FbGVtZW50UXVlcnlTZXJ2aWNlLmpzJztcbiAqXG4gKiBjb25zdCByZXN1bHQgPSBlbGVtZW50UXVlcnlTZXJ2aWNlLnF1ZXJ5KGl0ZW1zLCBvcHRpb25zKTtcbiAqIGBgYFxuICovXG5leHBvcnQgY29uc3QgZWxlbWVudFF1ZXJ5U2VydmljZSA9IGNyZWF0ZUVsZW1lbnRRdWVyeVNlcnZpY2U8SUVsZW1lbnQ+KCk7XG4iXX0=