UNPKG

75 kBJavaScriptView Raw
1"use strict";
2var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4 return new (P || (P = Promise))(function (resolve, reject) {
5 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8 step((generator = generator.apply(thisArg, _arguments || [])).next());
9 });
10};
11Object.defineProperty(exports, "__esModule", { value: true });
12exports.grammarScopeToAutoCompleteSelector = void 0;
13const convert_1 = require("../convert");
14const Utils = require("../utils");
15const zadeh_1 = require("zadeh");
16const languageclient_1 = require("../languageclient");
17const apply_edit_adapter_1 = require("./apply-edit-adapter");
18const atom_1 = require("atom");
19class PossiblyResolvedCompletionItem {
20 constructor(completionItem, isResolved) {
21 this.completionItem = completionItem;
22 this.isResolved = isResolved;
23 }
24}
25/** Public: Adapts the language server protocol "textDocument/completion" to the Atom AutoComplete+ package. */
26class AutocompleteAdapter {
27 constructor() {
28 this._suggestionCache = new WeakMap();
29 this._cancellationTokens = new WeakMap();
30 }
31 static canAdapt(serverCapabilities) {
32 return serverCapabilities.completionProvider != null;
33 }
34 static canResolve(serverCapabilities) {
35 return (serverCapabilities.completionProvider != null && serverCapabilities.completionProvider.resolveProvider === true);
36 }
37 /**
38 * Public: Obtain suggestion list for AutoComplete+ by querying the language server using the `textDocument/completion` request.
39 *
40 * @param server An {ActiveServer} pointing to the language server to query.
41 * @param request The {atom$AutocompleteRequest} to satisfy.
42 * @param onDidConvertCompletionItem An optional function that takes a {CompletionItem}, an
43 * {atom$AutocompleteSuggestion} and a {atom$AutocompleteRequest} allowing you to adjust converted items.
44 * @param shouldReplace The behavior of suggestion acceptance (see {ShouldReplace}).
45 * @returns A {Promise} of an {Array} of {atom$AutocompleteSuggestion}s containing the AutoComplete+ suggestions to display.
46 */
47 getSuggestions(server, request, onDidConvertCompletionItem, minimumWordLength, shouldReplace = false) {
48 return __awaiter(this, void 0, void 0, function* () {
49 const triggerChars = server.capabilities.completionProvider != null
50 ? server.capabilities.completionProvider.triggerCharacters || []
51 : [];
52 // triggerOnly is true if we have just typed in a trigger character, and is false if we
53 // have typed additional characters following a trigger character.
54 const [triggerChar, triggerOnly] = AutocompleteAdapter.getTriggerCharacter(request, triggerChars);
55 if (!this.shouldTrigger(request, triggerChar, minimumWordLength || 0)) {
56 return [];
57 }
58 // Get the suggestions either from the cache or by calling the language server
59 const suggestions = yield this.getOrBuildSuggestions(server, request, triggerChar, triggerOnly, shouldReplace, onDidConvertCompletionItem);
60 // We must update the replacement prefix as characters are added and removed
61 const cache = this._suggestionCache.get(server);
62 const replacementPrefix = request.editor.getTextInBufferRange([
63 [cache.triggerPoint.row, cache.triggerPoint.column + cache.triggerChar.length],
64 request.bufferPosition,
65 ]);
66 for (const suggestion of suggestions) {
67 if (suggestion.customReplacmentPrefix) {
68 // having this property means a custom range was provided
69 const len = replacementPrefix.length;
70 const preReplacementPrefix = suggestion.customReplacmentPrefix +
71 replacementPrefix.substring(len + cache.originalBufferPoint.column - request.bufferPosition.column, len);
72 // we cannot replace text after the cursor with the current autocomplete-plus API
73 // so we will simply ignore it for now
74 suggestion.replacementPrefix = preReplacementPrefix;
75 }
76 else {
77 suggestion.replacementPrefix = replacementPrefix;
78 }
79 }
80 const filtered = !(request.prefix === "" || (triggerChar !== "" && triggerOnly));
81 if (filtered) {
82 // filter the suggestions who have `filterText` property
83 const validSuggestions = suggestions.filter((sgs) => typeof sgs.filterText === "string");
84 // TODO use `ObjectArrayFilterer.setCandidate` in `_suggestionCache` to avoid creating `ObjectArrayFilterer` every time from scratch
85 const objFilterer = new zadeh_1.ObjectArrayFilterer(validSuggestions, "filterText");
86 // zadeh returns an array of the selected `Suggestions`
87 return objFilterer.filter(request.prefix);
88 }
89 else {
90 return suggestions;
91 }
92 });
93 }
94 shouldTrigger(request, triggerChar, minWordLength) {
95 return (request.activatedManually || triggerChar !== "" || minWordLength <= 0 || request.prefix.length >= minWordLength);
96 }
97 getOrBuildSuggestions(server, request, triggerChar, triggerOnly, shouldReplace, onDidConvertCompletionItem) {
98 return __awaiter(this, void 0, void 0, function* () {
99 const cache = this._suggestionCache.get(server);
100 const triggerColumn = triggerChar !== "" && triggerOnly
101 ? request.bufferPosition.column - triggerChar.length
102 : request.bufferPosition.column - request.prefix.length - triggerChar.length;
103 const triggerPoint = new atom_1.Point(request.bufferPosition.row, triggerColumn);
104 // Do we have complete cached suggestions that are still valid for this request?
105 if (cache &&
106 !cache.isIncomplete &&
107 cache.triggerChar === triggerChar &&
108 cache.triggerPoint.isEqual(triggerPoint) &&
109 cache.originalBufferPoint.isLessThanOrEqual(request.bufferPosition)) {
110 return Array.from(cache.suggestionMap.keys());
111 }
112 // Our cached suggestions can't be used so obtain new ones from the language server
113 const completions = yield Utils.doWithCancellationToken(server.connection, this._cancellationTokens, (cancellationToken) => server.connection.completion(AutocompleteAdapter.createCompletionParams(request, triggerChar, triggerOnly), cancellationToken));
114 // spec guarantees all edits are on the same line, so we only need to check the columns
115 const triggerColumns = [triggerPoint.column, request.bufferPosition.column];
116 // Setup the cache for subsequent filtered results
117 const isComplete = completions === null || Array.isArray(completions) || !completions.isIncomplete;
118 const suggestionMap = this.completionItemsToSuggestions(completions, request, triggerColumns, shouldReplace, onDidConvertCompletionItem);
119 this._suggestionCache.set(server, {
120 isIncomplete: !isComplete,
121 triggerChar,
122 triggerPoint,
123 originalBufferPoint: request.bufferPosition,
124 suggestionMap,
125 });
126 return Array.from(suggestionMap.keys());
127 });
128 }
129 /**
130 * Public: Obtain a complete version of a suggestion with additional information the language server can provide by
131 * way of the `completionItem/resolve` request.
132 *
133 * @param server An {ActiveServer} pointing to the language server to query.
134 * @param suggestion An {atom$AutocompleteSuggestion} suggestion that should be resolved.
135 * @param request An {Object} with the AutoComplete+ request to satisfy.
136 * @param onDidConvertCompletionItem An optional function that takes a {CompletionItem}, an
137 * {atom$AutocompleteSuggestion} and a {atom$AutocompleteRequest} allowing you to adjust converted items.
138 * @returns A {Promise} of an {atom$AutocompleteSuggestion} with the resolved AutoComplete+ suggestion.
139 */
140 completeSuggestion(server, suggestion, request, onDidConvertCompletionItem) {
141 return __awaiter(this, void 0, void 0, function* () {
142 const cache = this._suggestionCache.get(server);
143 if (cache) {
144 const possiblyResolvedCompletionItem = cache.suggestionMap.get(suggestion);
145 if (possiblyResolvedCompletionItem != null && !possiblyResolvedCompletionItem.isResolved) {
146 const resolvedCompletionItem = yield server.connection.completionItemResolve(possiblyResolvedCompletionItem.completionItem);
147 if (resolvedCompletionItem != null) {
148 AutocompleteAdapter.resolveSuggestion(resolvedCompletionItem, suggestion, request, onDidConvertCompletionItem);
149 possiblyResolvedCompletionItem.isResolved = true;
150 }
151 }
152 }
153 return suggestion;
154 });
155 }
156 static resolveSuggestion(resolvedCompletionItem, suggestion, request, onDidConvertCompletionItem) {
157 // only the `documentation` and `detail` properties may change when resolving
158 AutocompleteAdapter.applyDetailsToSuggestion(resolvedCompletionItem, suggestion);
159 if (onDidConvertCompletionItem != null) {
160 onDidConvertCompletionItem(resolvedCompletionItem, suggestion, request);
161 }
162 }
163 /**
164 * Public: Get the trigger character that caused the autocomplete (if any). This is required because AutoComplete-plus
165 * does not have trigger characters. Although the terminology is 'character' we treat them as variable length strings
166 * as this will almost certainly change in the future to support '->' etc.
167 *
168 * @param request An {Array} of {atom$AutocompleteSuggestion}s to locate the prefix, editor, bufferPosition etc.
169 * @param triggerChars The {Array} of {string}s that can be trigger characters.
170 * @returns A [{string}, boolean] where the string is the matching trigger character or an empty string if one was not
171 * matched, and the boolean is true if the trigger character is in request.prefix, and false if it is in the word
172 * before request.prefix. The boolean return value has no meaning if the string return value is an empty string.
173 */
174 static getTriggerCharacter(request, triggerChars) {
175 // AutoComplete-Plus considers text after a symbol to be a new trigger. So we should look backward
176 // from the current cursor position to see if one is there and thus simulate it.
177 const buffer = request.editor.getBuffer();
178 const cursor = request.bufferPosition;
179 const prefixStartColumn = cursor.column - request.prefix.length;
180 for (const triggerChar of triggerChars) {
181 if (request.prefix.endsWith(triggerChar)) {
182 return [triggerChar, true];
183 }
184 if (prefixStartColumn >= triggerChar.length) {
185 // Far enough along a line to fit the trigger char
186 const start = new atom_1.Point(cursor.row, prefixStartColumn - triggerChar.length);
187 const possibleTrigger = buffer.getTextInRange([start, [cursor.row, prefixStartColumn]]);
188 if (possibleTrigger === triggerChar) {
189 // The text before our trigger is a trigger char!
190 return [triggerChar, false];
191 }
192 }
193 }
194 // There was no explicit trigger char
195 return ["", false];
196 }
197 /**
198 * Public: Create TextDocumentPositionParams to be sent to the language server based on the editor and position from
199 * the AutoCompleteRequest.
200 *
201 * @param request The {atom$AutocompleteRequest} to obtain the editor from.
202 * @param triggerPoint The {atom$Point} where the trigger started.
203 * @returns A {string} containing the prefix including the trigger character.
204 */
205 static getPrefixWithTrigger(request, triggerPoint) {
206 return request.editor.getBuffer().getTextInRange([[triggerPoint.row, triggerPoint.column], request.bufferPosition]);
207 }
208 /**
209 * Public: Create {CompletionParams} to be sent to the language server based on the editor and position from the
210 * Autocomplete request etc.
211 *
212 * @param request The {atom$AutocompleteRequest} containing the request details.
213 * @param triggerCharacter The {string} containing the trigger character (empty if none).
214 * @param triggerOnly A {boolean} representing whether this completion is triggered right after a trigger character.
215 * @returns A {CompletionParams} with the keys:
216 *
217 * - `textDocument` the language server protocol textDocument identification.
218 * - `position` the position within the text document to display completion request for.
219 * - `context` containing the trigger character and kind.
220 */
221 static createCompletionParams(request, triggerCharacter, triggerOnly) {
222 return {
223 textDocument: convert_1.default.editorToTextDocumentIdentifier(request.editor),
224 position: convert_1.default.pointToPosition(request.bufferPosition),
225 context: AutocompleteAdapter.createCompletionContext(triggerCharacter, triggerOnly),
226 };
227 }
228 /**
229 * Public: Create {CompletionContext} to be sent to the language server based on the trigger character.
230 *
231 * @param triggerCharacter The {string} containing the trigger character or '' if none.
232 * @param triggerOnly A {boolean} representing whether this completion is triggered right after a trigger character.
233 * @returns An {CompletionContext} that specifies the triggerKind and the triggerCharacter if there is one.
234 */
235 static createCompletionContext(triggerCharacter, triggerOnly) {
236 if (triggerCharacter === "") {
237 return { triggerKind: languageclient_1.CompletionTriggerKind.Invoked };
238 }
239 else {
240 return triggerOnly
241 ? { triggerKind: languageclient_1.CompletionTriggerKind.TriggerCharacter, triggerCharacter }
242 : { triggerKind: languageclient_1.CompletionTriggerKind.TriggerForIncompleteCompletions, triggerCharacter };
243 }
244 }
245 /**
246 * Public: Convert a language server protocol CompletionItem array or CompletionList to an array of ordered
247 * AutoComplete+ suggestions.
248 *
249 * @param completionItems An {Array} of {CompletionItem} objects or a {CompletionList} containing completion items to
250 * be converted.
251 * @param request The {atom$AutocompleteRequest} to satisfy.
252 * @param shouldReplace The behavior of suggestion acceptance (see {ShouldReplace}).
253 * @param onDidConvertCompletionItem A function that takes a {CompletionItem}, an {atom$AutocompleteSuggestion} and a
254 * {atom$AutocompleteRequest} allowing you to adjust converted items.
255 * @returns A {Map} of AutoComplete+ suggestions ordered by the CompletionItems sortText.
256 */
257 completionItemsToSuggestions(completionItems, request, triggerColumns, shouldReplace, onDidConvertCompletionItem) {
258 const completionsArray = Array.isArray(completionItems)
259 ? completionItems
260 : (completionItems && completionItems.items) || [];
261 return new Map(completionsArray
262 .sort((a, b) => (a.sortText || a.label).localeCompare(b.sortText || b.label))
263 .map((s) => [
264 AutocompleteAdapter.completionItemToSuggestion(s, {}, request, triggerColumns, shouldReplace, onDidConvertCompletionItem),
265 new PossiblyResolvedCompletionItem(s, false),
266 ]));
267 }
268 /**
269 * Public: Convert a language server protocol CompletionItem to an AutoComplete+ suggestion.
270 *
271 * @param item An {CompletionItem} containing a completion item to be converted.
272 * @param suggestion A {atom$AutocompleteSuggestion} to have the conversion applied to.
273 * @param request The {atom$AutocompleteRequest} to satisfy.
274 * @param shouldReplace The behavior of suggestion acceptance (see {ShouldReplace}).
275 * @param onDidConvertCompletionItem A function that takes a {CompletionItem}, an {atom$AutocompleteSuggestion} and a
276 * {atom$AutocompleteRequest} allowing you to adjust converted items.
277 * @returns The {atom$AutocompleteSuggestion} passed in as suggestion with the conversion applied.
278 */
279 static completionItemToSuggestion(item, suggestion, request, triggerColumns, shouldReplace, onDidConvertCompletionItem) {
280 AutocompleteAdapter.applyCompletionItemToSuggestion(item, suggestion);
281 AutocompleteAdapter.applyTextEditToSuggestion(item.textEdit, request.editor, triggerColumns, request.bufferPosition, suggestion, shouldReplace);
282 AutocompleteAdapter.applySnippetToSuggestion(item, suggestion);
283 if (onDidConvertCompletionItem != null) {
284 onDidConvertCompletionItem(item, suggestion, request);
285 }
286 return suggestion;
287 }
288 /**
289 * Public: Convert the primary parts of a language server protocol CompletionItem to an AutoComplete+ suggestion.
290 *
291 * @param item An {CompletionItem} containing the completion items to be merged into.
292 * @param suggestion The {Suggestion} to merge the conversion into.
293 * @returns The {Suggestion} with details added from the {CompletionItem}.
294 */
295 static applyCompletionItemToSuggestion(item, suggestion) {
296 suggestion.text = item.insertText || item.label;
297 suggestion.filterText = item.filterText || item.label;
298 suggestion.displayText = item.label;
299 suggestion.type = AutocompleteAdapter.completionKindToSuggestionType(item.kind);
300 AutocompleteAdapter.applyDetailsToSuggestion(item, suggestion);
301 suggestion.completionItem = item;
302 }
303 static applyDetailsToSuggestion(item, suggestion) {
304 suggestion.rightLabel = item.detail;
305 // Older format, can't know what it is so assign to both and hope for best
306 if (typeof item.documentation === "string") {
307 suggestion.descriptionMarkdown = item.documentation;
308 suggestion.description = item.documentation;
309 }
310 if (item.documentation != null && typeof item.documentation === "object") {
311 // Newer format specifies the kind of documentation, assign appropriately
312 if (item.documentation.kind === "markdown") {
313 suggestion.descriptionMarkdown = item.documentation.value;
314 }
315 else {
316 suggestion.description = item.documentation.value;
317 }
318 }
319 }
320 /**
321 * Public: Applies the textEdit part of a language server protocol CompletionItem to an AutoComplete+ Suggestion via
322 * the replacementPrefix and text properties.
323 *
324 * @param textEdit A {TextEdit} from a CompletionItem to apply.
325 * @param editor An Atom {TextEditor} used to obtain the necessary text replacement.
326 * @param suggestion An {atom$AutocompleteSuggestion} to set the replacementPrefix and text properties of.
327 * @param shouldReplace The behavior of suggestion acceptance (see {ShouldReplace}).
328 */
329 static applyTextEditToSuggestion(textEdit, editor, triggerColumns, originalBufferPosition, suggestion, shouldReplace) {
330 if (!textEdit) {
331 return;
332 }
333 let range;
334 if ("range" in textEdit) {
335 range = textEdit.range;
336 }
337 else if (shouldReplace) {
338 range = textEdit.replace;
339 }
340 else {
341 range = textEdit.insert;
342 }
343 if (range.start.character !== triggerColumns[0]) {
344 const atomRange = convert_1.default.lsRangeToAtomRange(range);
345 suggestion.customReplacmentPrefix = editor.getTextInBufferRange([atomRange.start, originalBufferPosition]);
346 }
347 suggestion.text = textEdit.newText;
348 }
349 /**
350 * Handle additional text edits after a suggestion insert, e.g. `additionalTextEdits`.
351 *
352 * `additionalTextEdits` are An optional array of additional text edits that are applied when selecting this
353 * completion. Edits must not overlap (including the same insert position) with the main edit nor with themselves.
354 *
355 * Additional text edits should be used to change text unrelated to the current cursor position (for example adding an
356 * import statement at the top of the file if the completion item will insert an unqualified type).
357 */
358 static applyAdditionalTextEdits(event) {
359 var _a;
360 const suggestion = event.suggestion;
361 const additionalEdits = (_a = suggestion.completionItem) === null || _a === void 0 ? void 0 : _a.additionalTextEdits;
362 const buffer = event.editor.getBuffer();
363 apply_edit_adapter_1.default.applyEdits(buffer, convert_1.default.convertLsTextEdits(additionalEdits));
364 buffer.groupLastChanges();
365 }
366 /**
367 * Public: Adds a snippet to the suggestion if the CompletionItem contains snippet-formatted text
368 *
369 * @param item An {CompletionItem} containing the completion items to be merged into.
370 * @param suggestion The {atom$AutocompleteSuggestion} to merge the conversion into.
371 */
372 static applySnippetToSuggestion(item, suggestion) {
373 if (item.insertTextFormat === languageclient_1.InsertTextFormat.Snippet) {
374 suggestion.snippet = item.textEdit != null ? item.textEdit.newText : item.insertText || item.label;
375 }
376 }
377 /**
378 * Public: Obtain the textual suggestion type required by AutoComplete+ that most closely maps to the numeric
379 * completion kind supplies by the language server.
380 *
381 * @param kind A {Number} that represents the suggestion kind to be converted.
382 * @returns A {String} containing the AutoComplete+ suggestion type equivalent to the given completion kind.
383 */
384 static completionKindToSuggestionType(kind) {
385 switch (kind) {
386 case languageclient_1.CompletionItemKind.Constant:
387 return "constant";
388 case languageclient_1.CompletionItemKind.Method:
389 return "method";
390 case languageclient_1.CompletionItemKind.Function:
391 case languageclient_1.CompletionItemKind.Constructor:
392 return "function";
393 case languageclient_1.CompletionItemKind.Field:
394 case languageclient_1.CompletionItemKind.Property:
395 return "property";
396 case languageclient_1.CompletionItemKind.Variable:
397 return "variable";
398 case languageclient_1.CompletionItemKind.Class:
399 return "class";
400 case languageclient_1.CompletionItemKind.Struct:
401 case languageclient_1.CompletionItemKind.TypeParameter:
402 return "type";
403 case languageclient_1.CompletionItemKind.Operator:
404 return "selector";
405 case languageclient_1.CompletionItemKind.Interface:
406 return "mixin";
407 case languageclient_1.CompletionItemKind.Module:
408 return "module";
409 case languageclient_1.CompletionItemKind.Unit:
410 return "builtin";
411 case languageclient_1.CompletionItemKind.Enum:
412 case languageclient_1.CompletionItemKind.EnumMember:
413 return "enum";
414 case languageclient_1.CompletionItemKind.Keyword:
415 return "keyword";
416 case languageclient_1.CompletionItemKind.Snippet:
417 return "snippet";
418 case languageclient_1.CompletionItemKind.File:
419 case languageclient_1.CompletionItemKind.Folder:
420 return "import";
421 case languageclient_1.CompletionItemKind.Reference:
422 return "require";
423 default:
424 return "value";
425 }
426 }
427}
428exports.default = AutocompleteAdapter;
429/**
430 * Normalizes the given grammar scope for autoComplete package so it always starts with `.` Based on
431 * https://github.com/atom/autocomplete-plus/wiki/Autocomplete-Providers
432 *
433 * @param grammarScope Such as 'source.python' or '.source.python'
434 * @returns The normalized grammarScope such as `.source.python`
435 */
436function grammarScopeToAutoCompleteSelector(grammarScope) {
437 return grammarScope.includes(".") && grammarScope[0] !== "." ? `.${grammarScope}` : grammarScope;
438}
439exports.grammarScopeToAutoCompleteSelector = grammarScopeToAutoCompleteSelector;
440//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0b2NvbXBsZXRlLWFkYXB0ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9saWIvYWRhcHRlcnMvYXV0b2NvbXBsZXRlLWFkYXB0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBQUEsd0NBQWdDO0FBQ2hDLGtDQUFpQztBQUdqQyxpQ0FBMkM7QUFDM0Msc0RBYTBCO0FBQzFCLDZEQUFtRDtBQUNuRCwrQkFBd0M7QUFtQ3hDLE1BQU0sOEJBQThCO0lBQ2xDLFlBQW1CLGNBQThCLEVBQVMsVUFBbUI7UUFBMUQsbUJBQWMsR0FBZCxjQUFjLENBQWdCO1FBQVMsZUFBVSxHQUFWLFVBQVUsQ0FBUztJQUFHLENBQUM7Q0FDbEY7QUFFRCwrR0FBK0c7QUFDL0csTUFBcUIsbUJBQW1CO0lBQXhDO1FBV1UscUJBQWdCLEdBQWdELElBQUksT0FBTyxFQUFFLENBQUE7UUFDN0Usd0JBQW1CLEdBQStELElBQUksT0FBTyxFQUFFLENBQUE7SUEwZnpHLENBQUM7SUFyZ0JRLE1BQU0sQ0FBQyxRQUFRLENBQUMsa0JBQXNDO1FBQzNELE9BQU8sa0JBQWtCLENBQUMsa0JBQWtCLElBQUksSUFBSSxDQUFBO0lBQ3RELENBQUM7SUFFTSxNQUFNLENBQUMsVUFBVSxDQUFDLGtCQUFzQztRQUM3RCxPQUFPLENBQ0wsa0JBQWtCLENBQUMsa0JBQWtCLElBQUksSUFBSSxJQUFJLGtCQUFrQixDQUFDLGtCQUFrQixDQUFDLGVBQWUsS0FBSyxJQUFJLENBQ2hILENBQUE7SUFDSCxDQUFDO0lBS0Q7Ozs7Ozs7OztPQVNHO0lBQ1UsY0FBYyxDQUN6QixNQUFvQixFQUNwQixPQUFxQyxFQUNyQywwQkFBbUQsRUFDbkQsaUJBQTBCLEVBQzFCLGdCQUErQixLQUFLOztZQUVwQyxNQUFNLFlBQVksR0FDaEIsTUFBTSxDQUFDLFlBQVksQ0FBQyxrQkFBa0IsSUFBSSxJQUFJO2dCQUM1QyxDQUFDLENBQUMsTUFBTSxDQUFDLFlBQVksQ0FB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QUNsQixLQUFLLG1DQUFrQixDQUFDLE9BQU87Z0JBQzdCLE9BQU8sU0FBUyxDQUFBO1lBQ2xCLEtBQUssbUNBQWtCLENBQUMsSUFBSSxDQUFDO1lBQzdCLEtBQUssbUNBQWtCLENBQUMsTUFBTTtnQkFDNUIsT0FBTyxRQUFRLENBQUE7WUFDakIsS0FBSyxtQ0FBa0IsQ0FBQyxTQUFTO2dCQUMvQixPQUFPLFNBQVMsQ0FBQTtZQUNsQjtnQkFDRSxPQUFPLE9BQU8sQ0FBQTtTQUNqQjtJQUNILENBQUM7Q0FDRjtBQXRnQkQsc0NBc2dCQztBQUVEOzs7Ozs7R0FNRztBQUNILFNBQWdCLGtDQUFrQyxDQUFDLFlBQW9CO0lBQ3JFLE9BQU8sWUFBWSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxZQUFZLENBQUMsQ0FBQyxDQUFDLEtBQUssR0FBRyxDQUFDLENBQUMsQ0FBQyxJQUFJLFlBQVksRUFBRSxDQUFDLENBQUMsQ0FBQyxZQUFZLENBQUE7QUFDbEcsQ0FBQztBQUZELGdGQUVDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IENvbnZlcnQgZnJvbSBcIi4uL2NvbnZlcnRcIlxuaW1wb3J0ICogYXMgVXRpbHMgZnJvbSBcIi4uL3V0aWxzXCJcbmltcG9ydCB7IENhbmNlbGxhdGlvblRva2VuU291cmNlIH0gZnJvbSBcInZzY29kZS1qc29ucnBjXCJcbmltcG9ydCB7IEFjdGl2ZVNlcnZlciB9IGZyb20gXCIuLi9zZXJ2ZXItbWFuYWdlclwiXG5pbXBvcnQgeyBPYmplY3RBcnJheUZpbHRlcmVyIH0gZnJvbSBcInphZGVoXCJcbmltcG9ydCB7XG4gIENvbXBsZXRpb25Db250ZXh0LFxuICBDb21wbGV0aW9uSXRlbSxcbiAgQ29tcGxldGlvbkl0ZW1LaW5kLFxuICBDb21wbGV0aW9uTGlzdCxcbiAgQ29tcGxldGlvblBhcmFtcyxcbiAgQ29tcGxldGlvblRyaWdnZXJLaW5kLFxuICBJbnNlcnRUZXh0Rm9ybWF0LFxuICBJbnNlcnRSZXBsYWNlRWRpdCxcbiAgTGFuZ3VhZ2VDbGllbnRDb25uZWN0aW9uLFxuICBSYW5nZSxcbiAgU2VydmVyQ2FwYWJpbGl0aWVzLFxuICBUZXh0RWRpdCxcbn0gZnJvbSBcIi4uL2xhbmd1YWdlY2xpZW50XCJcbmltcG9ydCBBcHBseUVkaXRBZGFwdGVyIGZyb20gXCIuL2FwcGx5LWVkaXQtYWRhcHRlclwiXG5pbXBvcnQgeyBQb2ludCwgVGV4dEVkaXRvciB9IGZyb20gXCJhdG9tXCJcbmltcG9ydCAqIGFzIGFjIGZyb20gXCJhdG9tL2F1dG9jb21wbGV0ZS1wbHVzXCJcbmltcG9ydCB7IFN1Z2dlc3Rpb24sIFRleHRTdWdnZXN0aW9uLCBTbmlwcGV0U3VnZ2VzdGlvbiwgU3VnZ2VzdGlvbkJhc2UgfSBmcm9tIFwiLi4vdHlwZXMvYXV0b2NvbXBsZXRlLWV4dGVuZGVkXCJcblxuLyoqXG4gKiBEZWZpbmVzIHRoZSBiZWhhdmlvciBvZiBzdWdnZXN0aW9uIGFjY2VwdGFuY2UuIEFzc3VtZSB5b3UgaGF2ZSBcImNvbnN8b2xlXCIgaW4gdGhlIGVkaXRvciAoIGB8YCBpcyB0aGUgY3Vyc29yIHBvc2l0aW9uKVxuICogYW5kIHRoZSBhdXRvY29tcGxldGUgc3VnZ2VzdGlvbiBpcyBgY29uc3RgLlxuICpcbiAqIC0gSWYgYGZhbHNlYCAtPiB0aGUgZWRpdHMgYXJlIGluc2VydGVkIDogY29uc3R8b2xlXG4gKiAtIElmIGB0cnVlYGAgLT4gdGhlIGVkaXRzIGFyZSByZXBsYWNlZDogY29uc3R8XG4gKi9cbnR5cGUgU2hvdWxkUmVwbGFjZSA9IGJvb2xlYW5cblxuLyoqXG4gKiBIb2xkcyBhIGxpc3Qgb2Ygc3VnZ2VzdGlvbnMgZ2VuZXJhdGVkIGZyb20gdGhlIENvbXBsZXRpb25JdGVtW10gbGlzdCBzZW50IGJ5IHRoZSBzZXJ2ZXIsIGFzIHdlbGwgYXMgbWV0YWRhdGEgYWJvdXRcbiAqIHRoZSBjb250ZXh0IGl0IHdhcyBjb2xsZWN0ZWQgaW5cbiAqL1xuaW50ZXJmYWNlIFN1Z2dlc3Rpb25DYWNoZUVudHJ5IHtcbiAgLyoqIElmIGB0cnVlYCwgdGhlIHNlcnZlciB3aWxsIHNlbmQgYSBsaXN0IG9mIHN1Z2dlc3Rpb25zIHRvIHJlcGxhY2UgdGhpcyBvbmUgKi9cbiAgaXNJbmNvbXBsZXRlOiBib29sZWFuXG4gIC8qKiBUaGUgcG9pbnQgbGVmdCBvZiB0aGUgZmlyc3QgY2hhcmFjdGVyIGluIHRoZSBvcmlnaW5hbCBwcmVmaXggc2VudCB0byB0aGUgc2VydmVyICovXG4gIHRyaWdnZXJQb2ludDogUG9pbnRcbiAgLyoqIFRoZSBwb2ludCByaWdodCBvZiB0aGUgbGFzdCBjaGFyYWN0ZXIgaW4gdGhlIG9yaWdpbmFsIHByZWZpeCBzZW50IHRvIHRoZSBzZXJ2ZXIgKi9cbiAgb3JpZ2luYWxCdWZmZXJQb2ludDogUG9pbnRcbiAgLyoqIFRoZSB0cmlnZ2VyIHN0cmluZyB0aGF0IGNhdXNlZCB0aGUgYXV0b2NvbXBsZXRlIChpZiBhbnkpICovXG4gIHRyaWdnZXJDaGFyOiBzdHJpbmdcbiAgc3VnZ2VzdGlvbk1hcDogTWFwPFN1Z2dlc3Rpb24sIFBvc3NpYmx5UmVzb2x2ZWRDb21wbGV0aW9uSXRlbT5cbn1cblxudHlwZSBDb21wbGV0aW9uSXRlbUFkanVzdGVyID0gKFxuICBpdGVtOiBDb21wbGV0aW9uSXRlbSxcbiAgc3VnZ2VzdGlvbjogYWMuQW55U3VnZ2VzdGlvbixcbiAgcmVxdWVzdDogYWMuU3VnZ2VzdGlvbnNSZXF1ZXN0ZWRFdmVudFxuKSA9PiB2b2lkXG5cbmNsYXNzIFBvc3NpYmx5UmVzb2x2ZWRDb21wbGV0aW9uSXRlbSB7XG4gIGNvbnN0cnVjdG9yKHB1YmxpYyBjb21wbGV0aW9uSXRlbTogQ29tcGxldGlvbkl0ZW0sIHB1YmxpYyBpc1Jlc29sdmVkOiBib29sZWFuKSB7fVxufVxuXG4vKiogUHVibGljOiBBZGFwdHMgdGhlIGxhbmd1YWdlIHNlcnZlciBwcm90b2NvbCBcInRleHREb2N1bWVudC9jb21wbGV0aW9uXCIgdG8gdGhlIEF0b20gQXV0b0NvbXBsZXRlKyBwYWNrYWdlLiAqL1xuZXhwb3J0IGRlZmF1bHQgY2xhc3MgQXV0b2NvbXBsZXRlQWRhcHRlciB7XG4gIHB1YmxpYyBzdGF0aWMgY2FuQWRhcHQoc2VydmVyQ2FwYWJpbGl0aWVzOiBTZXJ2ZXJDYXBhYmlsaXRpZXMpOiBib29sZWFuIHtcbiAgICByZXR1cm4gc2VydmVyQ2FwYWJpbGl0aWVzLmNvbXBsZXRpb25Qcm92aWRlciAhPSBudWxsXG4gIH1cblxuICBwdWJsaWMgc3RhdGljIGNhblJlc29sdmUoc2VydmVyQ2FwYWJpbGl0aWVzOiBTZXJ2ZXJDYXBhYmlsaXRpZXMpOiBib29sZWFuIHtcbiAgICByZXR1cm4gKFxuICAgICAgc2VydmVyQ2FwYWJpbGl0aWVzLmNvbXBsZXRpb25Qcm92aWRlciAhPSBudWxsICYmIHNlcnZlckNhcGFiaWxpdGllcy5jb21wbGV0aW9uUHJvdmlkZXIucmVzb2x2ZVByb3ZpZGVyID09PSB0cnVlXG4gICAgKVxuICB9XG5cbiAgcHJpdmF0ZSBfc3VnZ2VzdGlvbkNhY2hlOiBXZWFrTWFwPEFjdGl2ZVNlcnZlciwgU3VnZ2VzdGlvbkNhY2hlRW50cnk+ID0gbmV3IFdlYWtNYXAoKVxuICBwcml2YXRlIF9jYW5jZWxsYXRpb25Ub2tlbnM6IFdlYWtNYXA8TGFuZ3VhZ2VDbGllbnRDb25uZWN0aW9uLCBDYW5jZWxsYXRpb25Ub2tlblNvdXJjZT4gPSBuZXcgV2Vha01hcCgpXG5cbiAgLyoqXG4gICAqIFB1YmxpYzogT2J0YWluIHN1Z2dlc3Rpb24gbGlzdCBmb3IgQXV0b0NvbXBsZXRlKyBieSBxdWVyeWluZyB0aGUgbGFuZ3VhZ2Ugc2VydmVyIHVzaW5nIHRoZSBgdGV4dERvY3VtZW50L2NvbXBsZXRpb25gIHJlcXVlc3QuXG4gICAqXG4gICAqIEBwYXJhbSBzZXJ2ZXIgQW4ge0FjdGl2ZVNlcnZlcn0gcG9pbnRpbmcgdG8gdGhlIGxhbmd1YWdlIHNlcnZlciB0byBxdWVyeS5cbiAgICogQHBhcmFtIHJlcXVlc3QgVGhlIHthdG9tJEF1dG9jb21wbGV0ZVJlcXVlc3R9IHRvIHNhdGlzZnkuXG4gICAqIEBwYXJhbSBvbkRpZENvbnZlcnRDb21wbGV0aW9uSXRlbSBBbiBvcHRpb25hbCBmdW5jdGlvbiB0aGF0IHRha2VzIGEge0NvbXBsZXRpb25JdGVtfSwgYW5cbiAgICogICB7YXRvbSRBdXRvY29tcGxldGVTdWdnZXN0aW9ufSBhbmQgYSB7YXRvbSRBdXRvY29tcGxldGVSZXF1ZXN0fSBhbGxvd2luZyB5b3UgdG8gYWRqdXN0IGNvbnZlcnRlZCBpdGVtcy5cbiAgICogQHBhcmFtIHNob3VsZFJlcGxhY2UgVGhlIGJlaGF2aW9yIG9mIHN1Z2dlc3Rpb24gYWNjZXB0YW5jZSAoc2VlIHtTaG91bGRSZXBsYWNlfSkuXG4gICAqIEByZXR1cm5zIEEge1Byb21pc2V9IG9mIGFuIHtBcnJheX0gb2Yge2F0b20kQXV0b2NvbXBsZXRlU3VnZ2VzdGlvbn1zIGNvbnRhaW5pbmcgdGhlIEF1dG9Db21wbGV0ZSsgc3VnZ2VzdGlvbnMgdG8gZGlzcGxheS5cbiAgICovXG4gIHB1YmxpYyBhc3luYyBnZXRTdWdnZXN0aW9ucyhcbiAgICBzZXJ2ZXI6IEFjdGl2ZVNlcnZlcixcbiAgICByZXF1ZXN0OiBhYy5TdWdnZXN0aW9uc1JlcXVlc3RlZEV2ZW50LFxuICAgIG9uRGlkQ29udmVydENvbXBsZXRpb25JdGVtPzogQ29tcGxldGlvbkl0ZW1BZGp1c3RlcixcbiAgICBtaW5pbXVtV29yZExlbmd0aD86IG51bWJlcixcbiAgICBzaG91bGRSZXBsYWNlOiBTaG91bGRSZXBsYWNlID0gZmFsc2VcbiAgKTogUHJvbWlzZTxhYy5BbnlTdWdnZXN0aW9uW10+IHtcbiAgICBjb25zdCB0cmlnZ2VyQ2hhcnMgPVxuICAgICAgc2VydmVyLmNhcGFiaWxpdGllcy5jb21wbGV0aW9uUHJvdmlkZXIgIT0gbnVsbFxuICAgICAgICA/IHNlcnZlci5jYXBhYmlsaXRpZXMuY29tcGxldGlvblByb3ZpZGVyLnRyaWdnZXJDaGFyYWN0ZXJzIHx8IFtdXG4gICAgICAgIDogW11cblxuICAgIC8vIHRyaWdnZXJPbmx5IGlzIHRydWUgaWYgd2UgaGF2ZSBqdXN0IHR5cGVkIGluIGEgdHJpZ2dlciBjaGFyYWN0ZXIsIGFuZCBpcyBmYWxzZSBpZiB3ZVxuICAgIC8vIGhhdmUgdHlwZWQgYWRkaXRpb25hbCBjaGFyYWN0ZXJzIGZvbGxvd2luZyBhIHRyaWdnZXIgY2hhcmFjdGVyLlxuICAgIGNvbnN0IFt0cmlnZ2VyQ2hhciwgdHJpZ2dlck9ubHldID0gQXV0b2NvbXBsZXRlQWRhcHRlci5nZXRUcmlnZ2VyQ2hhcmFjdGVyKHJlcXVlc3QsIHRyaWdnZXJDaGFycylcblxuICAgIGlmICghdGhpcy5zaG91bGRUcmlnZ2VyKHJlcXVlc3QsIHRyaWdnZXJDaGFyLCBtaW5pbXVtV29yZExlbmd0aCB8fCAwKSkge1xuICAgICAgcmV0dXJuIFtdXG4gICAgfVxuXG4gICAgLy8gR2V0IHRoZSBzdWdnZXN0aW9ucyBlaXRoZXIgZnJvbSB0aGUgY2FjaGUgb3IgYnkgY2FsbGluZyB0aGUgbGFuZ3VhZ2Ugc2VydmVyXG4gICAgY29uc3Qgc3VnZ2VzdGlvbnMgPSBhd2FpdCB0aGlzLmdldE9yQnVpbGRTdWdnZXN0aW9ucyhcbiAgICAgIHNlcnZlcixcbiAgICAgIHJlcXVlc3QsXG4gICAgICB0cmlnZ2VyQ2hhcixcbiAgICAgIHRyaWdnZXJPbmx5LFxuICAgICAgc2hvdWxkUmVwbGFjZSxcbiAgICAgIG9uRGlkQ29udmVydENvbXBsZXRpb25JdGVtXG4gICAgKVxuXG4gICAgLy8gV2UgbXVzdCB1cGRhdGUgdGhlIHJlcGxhY2VtZW50IHByZWZpeCBhcyBjaGFyYWN0ZXJzIGFyZSBhZGRlZCBhbmQgcmVtb3ZlZFxuICAgIGNvbnN0IGNhY2hlID0gdGhpcy5fc3VnZ2VzdGlvbkNhY2hlLmdldChzZXJ2ZXIpIVxuICAgIGNvbnN0IHJlcGxhY2VtZW50UHJlZml4ID0gcmVxdWVzdC5lZGl0b3IuZ2V0VGV4dEluQnVmZmVyUmFuZ2UoW1xuICAgICAgW2NhY2hlLnRyaWdnZXJQb2ludC5yb3csIGNhY2hlLnRyaWdnZXJQb2ludC5jb2x1bW4gKyBjYWNoZS50cmlnZ2VyQ2hhci5sZW5ndGhdLFxuICAgICAgcmVxdWVzdC5idWZmZXJQb3NpdGlvbixcbiAgICBdKVxuICAgIGZvciAoY29uc3Qgc3VnZ2VzdGlvbiBvZiBzdWdnZXN0aW9ucykge1xuICAgICAgaWYgKHN1Z2dlc3Rpb24uY3VzdG9tUmVwbGFjbWVudFByZWZpeCkge1xuICAgICAgICAvLyBoYXZpbmcgdGhpcyBwcm9wZXJ0eSBtZWFucyBhIGN1c3RvbSByYW5nZSB3YXMgcHJvdmlkZWRcbiAgICAgICAgY29uc3QgbGVuID0gcmVwbGFjZW1lbnRQcmVmaXgubGVuZ3RoXG4gICAgICAgIGNvbnN0IHByZVJlcGxhY2VtZW50UHJlZml4ID1cbiAgICAgICAgICBzdWdnZXN0aW9uLmN1c3RvbVJlcGxhY21lbnRQcmVmaXggK1xuICAgICAgICAgIHJlcGxhY2VtZW50UHJlZml4LnN1YnN0cmluZyhsZW4gKyBjYWNoZS5vcmlnaW5hbEJ1ZmZlclBvaW50LmNvbHVtbiAtIHJlcXVlc3QuYnVmZmVyUG9zaXRpb24uY29sdW1uLCBsZW4pXG4gICAgICAgIC8vIHdlIGNhbm5vdCByZXBsYWNlIHRleHQgYWZ0ZXIgdGhlIGN1cnNvciB3aXRoIHRoZSBjdXJyZW50IGF1dG9jb21wbGV0ZS1wbHVzIEFQSVxuICAgICAgICAvLyBzbyB3ZSB3aWxsIHNpbXBseSBpZ25vcmUgaXQgZm9yIG5vd1xuICAgICAgICBzdWdnZXN0aW9uLnJlcGxhY2VtZW50UHJlZml4ID0gcHJlUmVwbGFjZW1lbnRQcmVmaXhcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHN1Z2dlc3Rpb24ucmVwbGFjZW1lbnRQcmVmaXggPSByZXBsYWNlbWVudFByZWZpeFxuICAgICAgfVxuICAgIH1cblxuICAgIGNvbnN0IGZpbHRlcmVkID0gIShyZXF1ZXN0LnByZWZpeCA9PT0gXCJcIiB8fCAodHJpZ2dlckNoYXIgIT09IFwiXCIgJiYgdHJpZ2dlck9ubHkpKVxuICAgIGlmIChmaWx0ZXJlZCkge1xuICAgICAgLy8gZmlsdGVyIHRoZSBzdWdnZXN0aW9ucyB3aG8gaGF2ZSBgZmlsdGVyVGV4dGAgcHJvcGVydHlcbiAgICAgIGNvbnN0IHZhbGlkU3VnZ2VzdGlvbnMgPSBzdWdnZXN0aW9ucy5maWx0ZXIoKHNncykgPT4gdHlwZW9mIHNncy5maWx0ZXJUZXh0ID09PSBcInN0cmluZ1wiKSBhcyBTdWdnZXN0aW9uW10gJlxuICAgICAgICB7IGZpbHRlclRleHQ6IHN0cmluZyB9W11cbiAgICAgIC8vIFRPRE8gdXNlIGBPYmplY3RBcnJheUZpbHRlcmVyLnNldENhbmRpZGF0ZWAgaW4gYF9zdWdnZXN0aW9uQ2FjaGVgIHRvIGF2b2lkIGNyZWF0aW5nIGBPYmplY3RBcnJheUZpbHRlcmVyYCBldmVyeSB0aW1lIGZyb20gc2NyYXRjaFxuICAgICAgY29uc3Qgb2JqRmlsdGVyZXIgPSBuZXcgT2JqZWN0QXJyYXlGaWx0ZXJlcih2YWxpZFN1Z2dlc3Rpb25zLCBcImZpbHRlclRleHRcIilcbiAgICAgIC8vIHphZGVoIHJldHVybnMgYW4gYXJyYXkgb2YgdGhlIHNlbGVjdGVkIGBTdWdnZXN0aW9uc2BcbiAgICAgIHJldHVybiBvYmpGaWx0ZXJlci5maWx0ZXIocmVxdWVzdC5wcmVmaXgpIGFzIGFueSBhcyBTdWdnZXN0aW9uW11cbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIHN1Z2dlc3Rpb25zXG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBzaG91bGRUcmlnZ2VyKHJlcXVlc3Q6IGFjLlN1Z2dlc3Rpb25zUmVxdWVzdGVkRXZlbnQsIHRyaWdnZXJDaGFyOiBzdHJpbmcsIG1pbldvcmRMZW5ndGg6IG51bWJlcik6IGJvb2xlYW4ge1xuICAgIHJldHVybiAoXG4gICAgICByZXF1ZXN0LmFjdGl2YXRlZE1hbnVhbGx5IHx8IHRyaWdnZXJDaGFyICE9PSBcIlwiIHx8IG1pbldvcmRMZW5ndGggPD0gMCB8fCByZXF1ZXN0LnByZWZpeC5sZW5ndGggPj0gbWluV29yZExlbmd0aFxuICAgIClcbiAgfVxuXG4gIHByaXZhdGUgYXN5bmMgZ2V0T3JCdWlsZFN1Z2dlc3Rpb25zKFxuICAgIHNlcnZlcjogQWN0aXZlU2VydmVyLFxuICAgIHJlcXVlc3Q6IGFjLlN1Z2dlc3Rpb25zUmVxdWVzdGVkRXZlbnQsXG4gICAgdHJpZ2dlckNoYXI6IHN0cmluZyxcbiAgICB0cmlnZ2VyT25seTogYm9vbGVhbixcbiAgICBzaG91bGRSZXBsYWNlOiBTaG91bGRSZXBsYWNlLFxuICAgIG9uRGlkQ29udmVydENvbXBsZXRpb25JdGVtPzogQ29tcGxldGlvbkl0ZW1BZGp1c3RlclxuICApOiBQcm9taXNlPFN1Z2dlc3Rpb25bXT4ge1xuICAgIGNvbnN0IGNhY2hlID0gdGhpcy5fc3VnZ2VzdGlvbkNhY2hlLmdldChzZXJ2ZXIpXG5cbiAgICBjb25zdCB0cmlnZ2VyQ29sdW1uID1cbiAgICAgIHRyaWdnZXJDaGFyICE9PSBcIlwiICYmIHRyaWdnZXJPbmx5XG4gICAgICAgID8gcmVxdWVzdC5idWZmZXJQb3NpdGlvbi5jb2x1bW4gLSB0cmlnZ2VyQ2hhci5sZW5ndGhcbiAgICAgICAgOiByZXF1ZXN0LmJ1ZmZlclBvc2l0aW9uLmNvbHVtbiAtIHJlcXVlc3QucHJlZml4Lmxlbmd0aCAtIHRyaWdnZXJDaGFyLmxlbmd0aFxuICAgIGNvbnN0IHRyaWdnZXJQb2ludCA9IG5ldyBQb2ludChyZXF1ZXN0LmJ1ZmZlclBvc2l0aW9uLnJvdywgdHJpZ2dlckNvbHVtbilcblxuICAgIC8vIERvIHdlIGhhdmUgY29tcGxldGUgY2FjaGVkIHN1Z2dlc3Rpb25zIHRoYXQgYXJlIHN0aWxsIHZhbGlkIGZvciB0aGlzIHJlcXVlc3Q/XG4gICAgaWYgKFxuICAgICAgY2FjaGUgJiZcbiAgICAgICFjYWNoZS5pc0luY29tcGxldGUgJiZcbiAgICAgIGNhY2hlLnRyaWdnZXJDaGFyID09PSB0cmlnZ2VyQ2hhciAmJlxuICAgICAgY2FjaGUudHJpZ2dlclBvaW50LmlzRXF1YWwodHJpZ2dlclBvaW50KSAmJlxuICAgICAgY2FjaGUub3JpZ2luYWxCdWZmZXJQb2ludC5pc0xlc3NUaGFuT3JFcXVhbChyZXF1ZXN0LmJ1ZmZlclBvc2l0aW9uKVxuICAgICkge1xuICAgICAgcmV0dXJuIEFycmF5LmZyb20oY2FjaGUuc3VnZ2VzdGlvbk1hcC5rZXlzKCkpXG4gICAgfVxuXG4gICAgLy8gT3VyIGNhY2hlZCBzdWdnZXN0aW9ucyBjYW4ndCBiZSB1c2VkIHNvIG9idGFpbiBuZXcgb25lcyBmcm9tIHRoZSBsYW5ndWFnZSBzZXJ2ZXJcbiAgICBjb25zdCBjb21wbGV0aW9ucyA9IGF3YWl0IFV0aWxzLmRvV2l0aENhbmNlbGxhdGlvblRva2VuKFxuICAgICAgc2VydmVyLmNvbm5lY3Rpb24sXG4gICAgICB0aGlzLl9jYW5jZWxsYXRpb25Ub2tlbnMsXG4gICAgICAoY2FuY2VsbGF0aW9uVG9rZW4pID0+XG4gICAgICAgIHNlcnZlci5jb25uZWN0aW9uLmNvbXBsZXRpb24oXG4gICAgICAgICAgQXV0b2NvbXBsZXRlQWRhcHRlci5jcmVhdGVDb21wbGV0aW9uUGFyYW1zKHJlcXVlc3QsIHRyaWdnZXJDaGFyLCB0cmlnZ2VyT25seSksXG4gICAgICAgICAgY2FuY2VsbGF0aW9uVG9rZW5cbiAgICAgICAgKVxuICAgIClcblxuICAgIC8vIHNwZWMgZ3VhcmFudGVlcyBhbGwgZWRpdHMgYXJlIG9uIHRoZSBzYW1lIGxpbmUsIHNvIHdlIG9ubHkgbmVlZCB0byBjaGVjayB0aGUgY29sdW1uc1xuICAgIGNvbnN0IHRyaWdnZXJDb2x1bW5zOiBbbnVtYmVyLCBudW1iZXJdID0gW3RyaWdnZXJQb2ludC5jb2x1bW4sIHJlcXVlc3QuYnVmZmVyUG9zaXRpb24uY29sdW1uXVxuXG4gICAgLy8gU2V0dXAgdGhlIGNhY2hlIGZvciBzdWJzZXF1ZW50IGZpbHRlcmVkIHJlc3VsdHNcbiAgICBjb25zdCBpc0NvbXBsZXRlID0gY29tcGxldGlvbnMgPT09IG51bGwgfHwgQXJyYXkuaXNBcnJheShjb21wbGV0aW9ucykgfHwgIWNvbXBsZXRpb25zLmlzSW5jb21wbGV0ZVxuICAgIGNvbnN0IHN1Z2dlc3Rpb25NYXAgPSB0aGlzLmNvbXBsZXRpb25JdGVtc1RvU3VnZ2VzdGlvbnMoXG4gICAgICBjb21wbGV0aW9ucyxcbiAgICAgIHJlcXVlc3QsXG4gICAgICB0cmlnZ2VyQ29sdW1ucyxcbiAgICAgIHNob3VsZFJlcGxhY2UsXG4gICAgICBvbkRpZENvbnZlcnRDb21wbGV0aW9uSXRlbVxuICAgIClcbiAgICB0aGlzLl9zdWdnZXN0aW9uQ2FjaGUuc2V0KHNlcnZlciwge1xuICAgICAgaXNJbmNvbXBsZXRlOiAhaXNDb21wbGV0ZSxcbiAgICAgIHRyaWdnZXJDaGFyLFxuICAgICAgdHJpZ2dlclBvaW50LFxuICAgICAgb3JpZ2luYWxCdWZmZXJQb2ludDogcmVxdWVzdC5idWZmZXJQb3NpdGlvbixcbiAgICAgIHN1Z2dlc3Rpb25NYXAsXG4gICAgfSlcblxuICAgIHJldHVybiBBcnJheS5mcm9tKHN1Z2dlc3Rpb25NYXAua2V5cygpKVxuICB9XG5cbiAgLyoqXG4gICAqIFB1YmxpYzogT2J0YWluIGEgY29tcGxldGUgdmVyc2lvbiBvZiBhIHN1Z2dlc3Rpb24gd2l0aCBhZGRpdGlvbmFsIGluZm9ybWF0aW9uIHRoZSBsYW5ndWFnZSBzZXJ2ZXIgY2FuIHByb3ZpZGUgYnlcbiAgICogd2F5IG9mIHRoZSBgY29tcGxldGlvbkl0ZW0vcmVzb2x2ZWAgcmVxdWVzdC5cbiAgICpcbiAgICogQHBhcmFtIHNlcnZlciBBbiB7QWN0aXZlU2VydmVyfSBwb2ludGluZyB0byB0aGUgbGFuZ3VhZ2Ugc2VydmVyIHRvIHF1ZXJ5LlxuICAgKiBAcGFyYW0gc3VnZ2VzdGlvbiBBbiB7YXRvbSRBdXRvY29tcGxldGVTdWdnZXN0aW9ufSBzdWdnZXN0aW9uIHRoYXQgc2hvdWxkIGJlIHJlc29sdmVkLlxuICAgKiBAcGFyYW0gcmVxdWVzdCBBbiB7T2JqZWN0fSB3aXRoIHRoZSBBdXRvQ29tcGxldGUrIHJlcXVlc3QgdG8gc2F0aXNmeS5cbiAgICogQHBhcmFtIG9uRGlkQ29udmVydENvbXBsZXRpb25JdGVtIEFuIG9wdGlvbmFsIGZ1bmN0aW9uIHRoYXQgdGFrZXMgYSB7Q29tcGxldGlvbkl0ZW19LCBhblxuICAgKiAgIHthdG9tJEF1dG9jb21wbGV0ZVN1Z2dlc3Rpb259IGFuZCBhIHthdG9tJEF1dG9jb21wbGV0ZVJlcXVlc3R9IGFsbG93aW5nIHlvdSB0byBhZGp1c3QgY29udmVydGVkIGl0ZW1zLlxuICAgKiBAcmV0dXJucyBBIHtQcm9taXNlfSBvZiBhbiB7YXRvbSRBdXRvY29tcGxldGVTdWdnZXN0aW9ufSB3aXRoIHRoZSByZXNvbHZlZCBBdXRvQ29tcGxldGUrIHN1Z2dlc3Rpb24uXG4gICAqL1xuICBwdWJsaWMgYXN5bmMgY29tcGxldGVTdWdnZXN0aW9uKFxuICAgIHNlcnZlcjogQWN0aXZlU2VydmVyLFxuICAgIHN1Z2dlc3Rpb246IGFjLkFueVN1Z2dlc3Rpb24sXG4gICAgcmVxdWVzdDogYWMuU3VnZ2VzdGlvbnNSZXF1ZXN0ZWRFdmVudCxcbiAgICBvbkRpZENvbnZlcnRDb21wbGV0aW9uSXRlbT86IENvbXBsZXRpb25JdGVtQWRqdXN0ZXJcbiAgKTogUHJvbWlzZTxhYy5BbnlTdWdnZXN0aW9uPiB7XG4gICAgY29uc3QgY2FjaGUgPSB0aGlzLl9zdWdnZXN0aW9uQ2FjaGUuZ2V0KHNlcnZlcilcbiAgICBpZiAoY2FjaGUpIHtcbiAgICAgIGNvbnN0IHBvc3NpYmx5UmVzb2x2ZWRDb21wbGV0aW9uSXRlbSA9IGNhY2hlLnN1Z2dlc3Rpb25NYXAuZ2V0KHN1Z2dlc3Rpb24pXG4gICAgICBpZiAocG9zc2libHlSZXNvbHZlZENvbXBsZXRpb25JdGVtICE9IG51bGwgJiYgIXBvc3NpYmx5UmVzb2x2ZWRDb21wbGV0aW9uSXRlbS5pc1Jlc29sdmVkKSB7XG4gICAgICAgIGNvbnN0IHJlc29sdmVkQ29tcGxldGlvbkl0ZW0gPSBhd2FpdCBzZXJ2ZXIuY29ubmVjdGlvbi5jb21wbGV0aW9uSXRlbVJlc29sdmUoXG4gICAgICAgICAgcG9zc2libHlSZXNvbHZlZENvbXBsZXRpb25JdGVtLmNvbXBsZXRpb25JdGVtXG4gICAgICAgIClcbiAgICAgICAgaWYgKHJlc29sdmVkQ29tcGxldGlvbkl0ZW0gIT0gbnVsbCkge1xuICAgICAgICAgIEF1dG9jb21wbGV0ZUFkYXB0ZXIucmVzb2x2ZVN1Z2dlc3Rpb24ocmVzb2x2ZWRDb21wbGV0aW9uSXRlbSwgc3VnZ2VzdGlvbiwgcmVxdWVzdCwgb25EaWRDb252ZXJ0Q29tcGxldGlvbkl0ZW0pXG4gICAgICAgICAgcG9zc2libHlSZXNvbHZlZENvbXBsZXRpb25JdGVtLmlzUmVzb2x2ZWQgPSB0cnVlXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHN1Z2dlc3Rpb25cbiAgfVxuXG4gIHB1YmxpYyBzdGF0aWMgcmVzb2x2ZVN1Z2dlc3Rpb24oXG4gICAgcmVzb2x2ZWRDb21wbGV0aW9uSXRlbTogQ29tcGxldGlvbkl0ZW0sXG4gICAgc3VnZ2VzdGlvbjogYWMuQW55U3VnZ2VzdGlvbixcbiAgICByZXF1ZXN0OiBhYy5TdWdnZXN0aW9uc1JlcXVlc3RlZEV2ZW50LFxuICAgIG9uRGlkQ29udmVydENvbXBsZXRpb25JdGVtPzogQ29tcGxldGlvbkl0ZW1BZGp1c3RlclxuICApOiB2b2lkIHtcbiAgICAvLyBvbmx5IHRoZSBgZG9jdW1lbnRhdGlvbmAgYW5kIGBkZXRhaWxgIHByb3BlcnRpZXMgbWF5IGNoYW5nZSB3aGVuIHJlc29sdmluZ1xuICAgIEF1dG9jb21wbGV0ZUFkYXB0ZXIuYXBwbHlEZXRhaWxzVG9TdWdnZXN0aW9uKHJlc29sdmVkQ29tcGxldGlvbkl0ZW0sIHN1Z2dlc3Rpb24pXG4gICAgaWYgKG9uRGlkQ29udmVydENvbXBsZXRpb25JdGVtICE9IG51bGwpIHtcbiAgICAgIG9uRGlkQ29udmVydENvbXBsZXRpb25JdGVtKHJlc29sdmVkQ29tcGxldGlvbkl0ZW0sIHN1Z2dlc3Rpb24gYXMgYWMuQW55U3VnZ2VzdGlvbiwgcmVxdWVzdClcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUHVibGljOiBHZXQgdGhlIHRyaWdnZXIgY2hhcmFjdGVyIHRoYXQgY2F1c2VkIHRoZSBhdXRvY29tcGxldGUgKGlmIGFueSkuIFRoaXMgaXMgcmVxdWlyZWQgYmVjYXVzZSBBdXRvQ29tcGxldGUtcGx1c1xuICAgKiBkb2VzIG5vdCBoYXZlIHRyaWdnZXIgY2hhcmFjdGVycy4gQWx0aG91Z2ggdGhlIHRlcm1pbm9sb2d5IGlzICdjaGFyYWN0ZXInIHdlIHRyZWF0IHRoZW0gYXMgdmFyaWFibGUgbGVuZ3RoIHN0cmluZ3NcbiAgICogYXMgdGhpcyB3aWxsIGFsbW9zdCBjZXJ0YWlubHkgY2hhbmdlIGluIHRoZSBmdXR1cmUgdG8gc3VwcG9ydCAnLT4nIGV0Yy5cbiAgICpcbiAgICogQHBhcmFtIHJlcXVlc3QgQW4ge0FycmF5fSBvZiB7YXRvbSRBdXRvY29tcGxldGVTdWdnZXN0aW9ufXMgdG8gbG9jYXRlIHRoZSBwcmVmaXgsIGVkaXRvciwgYnVmZmVyUG9zaXRpb24gZXRjLlxuICAgKiBAcGFyYW0gdHJpZ2dlckNoYXJzIFRoZSB7QXJyYXl9IG9mIHtzdHJpbmd9cyB0aGF0IGNhbiBiZSB0cmlnZ2VyIGNoYXJhY3RlcnMuXG4gICAqIEByZXR1cm5zIEEgW3tzdHJpbmd9LCBib29sZWFuXSB3aGVyZSB0aGUgc3RyaW5nIGlzIHRoZSBtYXRjaGluZyB0cmlnZ2VyIGNoYXJhY3RlciBvciBhbiBlbXB0eSBzdHJpbmcgaWYgb25lIHdhcyBub3RcbiAgICogICBtYXRjaGVkLCBhbmQgdGhlIGJvb2xlYW4gaXMgdHJ1ZSBpZiB0aGUgdHJpZ2dlciBjaGFyYWN0ZXIgaXMgaW4gcmVxdWVzdC5wcmVmaXgsIGFuZCBmYWxzZSBpZiBpdCBpcyBpbiB0aGUgd29yZFxuICAgKiAgIGJlZm9yZSByZXF1ZXN0LnByZWZpeC4gVGhlIGJvb2xlYW4gcmV0dXJuIHZhbHVlIGhhcyBubyBtZWFuaW5nIGlmIHRoZSBzdHJpbmcgcmV0dXJuIHZhbHVlIGlzIGFuIGVtcHR5IHN0cmluZy5cbiAgICovXG4gIHB1YmxpYyBzdGF0aWMgZ2V0VHJpZ2dlckNoYXJhY3RlcihyZXF1ZXN0OiBhYy5TdWdnZXN0aW9uc1JlcXVlc3RlZEV2ZW50LCB0cmlnZ2VyQ2hhcnM6IHN0cmluZ1tdKTogW3N0cmluZywgYm9vbGVhbl0ge1xuICAgIC8vIEF1dG9Db21wbGV0ZS1QbHVzIGNvbnNpZGVycyB0ZXh0IGFmdGVyIGEgc3ltYm9sIHRvIGJlIGEgbmV3IHRyaWdnZXIuIFNvIHdlIHNob3VsZCBsb29rIGJhY2t3YXJkXG4gICAgLy8gZnJvbSB0aGUgY3VycmVudCBjdXJzb3IgcG9zaXRpb24gdG8gc2VlIGlmIG9uZSBpcyB0aGVyZSBhbmQgdGh1cyBzaW11bGF0ZSBpdC5cbiAgICBjb25zdCBidWZmZXIgPSByZXF1ZXN0LmVkaXRvci5nZXRCdWZmZXIoKVxuICAgIGNvbnN0IGN1cnNvciA9IHJlcXVlc3QuYnVmZmVyUG9zaXRpb25cbiAgICBjb25zdCBwcmVmaXhTdGFydENvbHVtbiA9IGN1cnNvci5jb2x1bW4gLSByZXF1ZXN0LnByZWZpeC5sZW5ndGhcbiAgICBmb3IgKGNvbnN0IHRyaWdnZXJDaGFyIG9mIHRyaWdnZXJDaGFycykge1xuICAgICAgaWYgKHJlcXVlc3QucHJlZml4LmVuZHNXaXRoKHRyaWdnZXJDaGFyKSkge1xuICAgICAgICByZXR1cm4gW3RyaWdnZXJDaGFyLCB0cnVlXVxuICAgICAgfVxuICAgICAgaWYgKHByZWZpeFN0YXJ0Q29sdW1uID49IHRyaWdnZXJDaGFyLmxlbmd0aCkge1xuICAgICAgICAvLyBGYXIgZW5vdWdoIGFsb25nIGEgbGluZSB0byBmaXQgdGhlIHRyaWdnZXIgY2hhclxuICAgICAgICBjb25zdCBzdGFydCA9IG5ldyBQb2ludChjdXJzb3Iucm93LCBwcmVmaXhTdGFydENvbHVtbiAtIHRyaWdnZXJDaGFyLmxlbmd0aClcbiAgICAgICAgY29uc3QgcG9zc2libGVUcmlnZ2VyID0gYnVmZmVyLmdldFRleHRJblJhbmdlKFtzdGFydCwgW2N1cnNvci5yb3csIHByZWZpeFN0YXJ0Q29sdW1uXV0pXG4gICAgICAgIGlmIChwb3NzaWJsZVRyaWdnZXIgPT09IHRyaWdnZXJDaGFyKSB7XG4gICAgICAgICAgLy8gVGhlIHRleHQgYmVmb3JlIG91ciB0cmlnZ2VyIGlzIGEgdHJpZ2dlciBjaGFyIVxuICAgICAgICAgIHJldHVybiBbdHJpZ2dlckNoYXIsIGZhbHNlXVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gVGhlcmUgd2FzIG5vIGV4cGxpY2l0IHRyaWdnZXIgY2hhclxuICAgIHJldHVybiBbXCJcIiwgZmFsc2VdXG4gIH1cblxuICAvKipcbiAgICogUHVibGljOiBDcmVhdGUgVGV4dERvY3VtZW50UG9zaXRpb25QYXJhbXMgdG8gYmUgc2VudCB0byB0aGUgbGFuZ3VhZ2Ugc2VydmVyIGJhc2VkIG9uIHRoZSBlZGl0b3IgYW5kIHBvc2l0aW9uIGZyb21cbiAgICogdGhlIEF1dG9Db21wbGV0ZVJlcXVlc3QuXG4gICAqXG4gICAqIEBwYXJhbSByZXF1ZXN0IFRoZSB7YXRvbSRBdXRvY29tcGxldGVSZXF1ZXN0fSB0byBvYnRhaW4gdGhlIGVkaXRvciBmcm9tLlxuICAgKiBAcGFyYW0gdHJpZ2dlclBvaW50IFRoZSB7YXRvbSRQb2ludH0gd2hlcmUgdGhlIHRyaWdnZXIgc3RhcnRlZC5cbiAgICogQHJldHVybnMgQSB7c3RyaW5nfSBjb250YWluaW5nIHRoZSBwcmVmaXggaW5jbHVkaW5nIHRoZSB0cmlnZ2VyIGNoYXJhY3Rlci5cbiAgICovXG4gIHB1YmxpYyBzdGF0aWMgZ2V0UHJlZml4V2l0aFRyaWdnZXIocmVxdWVzdDogYWMuU3VnZ2VzdGlvbnNSZXF1ZXN0ZWRFdmVudCwgdHJpZ2dlclBvaW50OiBQb2ludCk6IHN0cmluZyB7XG4gICAgcmV0dXJuIHJlcXVlc3QuZWRpdG9yLmdldEJ1ZmZlcigpLmdldFRleHRJblJhbmdlKFtbdHJpZ2dlclBvaW50LnJvdywgdHJpZ2dlclBvaW50LmNvbHVtbl0sIHJlcXVlc3QuYnVmZmVyUG9zaXRpb25dKVxuICB9XG5cbiAgLyoqXG4gICAqIFB1YmxpYzogQ3JlYXRlIHtDb21wbGV0aW9uUGFyYW1zfSB0byBiZSBzZW50IHRvIHRoZSBsYW5ndWFnZSBzZXJ2ZXIgYmFzZWQgb24gdGhlIGVkaXRvciBhbmQgcG9zaXRpb24gZnJvbSB0aGVcbiAgICogQXV0b2NvbXBsZXRlIHJlcXVlc3QgZXRjLlxuICAgKlxuICAgKiBAcGFyYW0gcmVxdWVzdCBUaGUge2F0b20kQXV0b2NvbXBsZXRlUmVxdWVzdH0gY29udGFpbmluZyB0aGUgcmVxdWVzdCBkZXRhaWxzLlxuICAgKiBAcGFyYW0gdHJpZ2dlckNoYXJhY3RlciBUaGUge3N0cmluZ30gY29udGFpbmluZyB0aGUgdHJpZ2dlciBjaGFyYWN0ZXIgKGVtcHR5IGlmIG5vbmUpLlxuICAgKiBAcGFyYW0gdHJpZ2dlck9ubHkgQSB7Ym9vbGVhbn0gcmVwcmVzZW50aW5nIHdoZXRoZXIgdGhpcyBjb21wbGV0aW9uIGlzIHRyaWdnZXJlZCByaWdodCBhZnRlciBhIHRyaWdnZXIgY2hhcmFjdGVyLlxuICAgKiBAcmV0dXJucyBBIHtDb21wbGV0aW9uUGFyYW1zfSB3aXRoIHRoZSBrZXlzOlxuICAgKlxuICAgKiAgIC0gYHRleHREb2N1bWVudGAgdGhlIGxhbmd1YWdlIHNlcnZlciBwcm90b2NvbCB0ZXh0RG9jdW1lbnQgaWRlbnRpZmljYXRpb24uXG4gICAqICAgLSBgcG9zaXRpb25gIHRoZSBwb3NpdGlvbiB3aXRoaW4gdGhlIHRleHQgZG9jdW1lbnQgdG8gZGlzcGxheSBjb21wbGV0aW9uIHJlcXVlc3QgZm9yLlxuICAgKiAgIC0gYGNvbnRleHRgIGNvbnRhaW5pbmcgdGhlIHRyaWdnZXIgY2hhcmFjdGVyIGFuZCBraW5kLlxuICAgKi9cbiAgcHVibGljIHN0YXRpYyBjcmVhdGVDb21wbGV0aW9uUGFyYW1zKFxuICAgIHJlcXVlc3Q6IGFjLlN1Z2dlc3Rpb25zUmVxdWVzdGVkRXZlbnQsXG4gICAgdHJpZ2dlckNoYXJhY3Rlcjogc3RyaW5nLFxuICAgIHRyaWdnZXJPbmx5OiBib29sZWFuXG4gICk6IENvbXBsZXRpb25QYXJhbXMge1xuICAgIHJldHVybiB7XG4gICAgICB0ZXh0RG9jdW1lbnQ6IENvbnZlcnQuZWRpdG9yVG9UZXh0RG9jdW1lbnRJZGVudGlmaWVyKHJlcXVlc3QuZWRpdG9yKSxcbiAgICAgIHBvc2l0aW9uOiBDb252ZXJ0LnBvaW50VG9Qb3NpdGlvbihyZXF1ZXN0LmJ1ZmZlclBvc2l0aW9uKSxcbiAgICAgIGNvbnRleHQ6IEF1dG9jb21wbGV0ZUFkYXB0ZXIuY3JlYXRlQ29tcGxldGlvbkNvbnRleHQodHJpZ2dlckNoYXJhY3RlciwgdHJpZ2dlck9ubHkpLFxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBQdWJsaWM6IENyZWF0ZSB7Q29tcGxldGlvbkNvbnRleHR9IHRvIGJlIHNlbnQgdG8gdGhlIGxhbmd1YWdlIHNlcnZlciBiYXNlZCBvbiB0aGUgdHJpZ2dlciBjaGFyYWN0ZXIuXG4gICAqXG4gICAqIEBwYXJhbSB0cmlnZ2VyQ2hhcmFjdGVyIFRoZSB7c3RyaW5nfSBjb250YWluaW5nIHRoZSB0cmlnZ2VyIGNoYXJhY3RlciBvciAnJyBpZiBub25lLlxuICAgKiBAcGFyYW0gdHJpZ2dlck9ubHkgQSB7Ym9vbGVhbn0gcmVwcmVzZW50aW5nIHdoZXRoZXIgdGhpcyBjb21wbGV0aW9uIGlzIHRyaWdnZXJlZCByaWdodCBhZnRlciBhIHRyaWdnZXIgY2hhcmFjdGVyLlxuICAgKiBAcmV0dXJucyBBbiB7Q29tcGxldGlvbkNvbnRleHR9IHRoYXQgc3BlY2lmaWVzIHRoZSB0cmlnZ2VyS2luZCBhbmQgdGhlIHRyaWdnZXJDaGFyYWN0ZXIgaWYgdGhlcmUgaXMgb25lLlxuICAgKi9cbiAgcHVibGljIHN0YXRpYyBjcmVhdGVDb21wbGV0aW9uQ29udGV4dCh0cmlnZ2VyQ2hhcmFjdGVyOiBzdHJpbmcsIHRyaWdnZXJPbmx5OiBib29sZWFuKTogQ29tcGxldGlvbkNvbnRleHQge1xuICAgIGlmICh0cmlnZ2VyQ2hhcmFjdGVyID09PSBcIlwiKSB7XG4gICAgICByZXR1cm4geyB0cmlnZ2VyS2luZDogQ29tcGxldGlvblRyaWdnZXJLaW5kLkludm9rZWQgfVxuICAgIH0gZWxzZSB7XG4gICAgICByZXR1cm4gdHJpZ2dlck9ubHlcbiAgICAgICAgPyB7IHRyaWdnZXJLaW5kOiBDb21wbGV0aW9uVHJpZ2dlcktpbmQuVHJpZ2dlckNoYXJhY3RlciwgdHJpZ2dlckNoYXJhY3RlciB9XG4gICAgICAgIDogeyB0cmlnZ2VyS2luZDogQ29tcGxldGlvblRyaWdnZXJLaW5kLlRyaWdnZXJGb3JJbmNvbXBsZXRlQ29tcGxldGlvbnMsIHRyaWdnZXJDaGFyYWN0ZXIgfVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBQdWJsaWM6IENvbnZlcnQgYSBsYW5ndWFnZSBzZXJ2ZXIgcHJvdG9jb2wgQ29tcGxldGlvbkl0ZW0gYXJyYXkgb3IgQ29tcGxldGlvbkxpc3QgdG8gYW4gYXJyYXkgb2Ygb3JkZXJlZFxuICAgKiBBdXRvQ29tcGxldGUrIHN1Z2dlc3Rpb25zLlxuICAgKlxuICAgKiBAcGFyYW0gY29tcGxldGlvbkl0ZW1zIEFuIHtBcnJheX0gb2Yge0NvbXBsZXRpb25JdGVtfSBvYmplY3RzIG9yIGEge0NvbXBsZXRpb25MaXN0fSBjb250YWluaW5nIGNvbXBsZXRpb24gaXRlbXMgdG9cbiAgICogICBiZSBjb252ZXJ0ZWQuXG4gICAqIEBwYXJhbSByZXF1ZXN0IFRoZSB7YXRvbSRBdXRvY29tcGxldGVSZXF1ZXN0fSB0byBzYXRpc2Z5LlxuICAgKiBAcGFyYW0gc2hvdWxkUmVwbGFjZSBUaGUgYmVoYXZpb3Igb2Ygc3VnZ2VzdGlvbiBhY2NlcHRhbmNlIChzZWUge1Nob3VsZFJlcGxhY2V9KS5cbiAgICogQHBhcmFtIG9uRGlkQ29udmVydENvbXBsZXRpb25JdGVtIEEgZnVuY3Rpb24gdGhhdCB0YWtlcyBhIHtDb21wbGV0aW9uSXRlbX0sIGFuIHthdG9tJEF1dG9jb21wbGV0ZVN1Z2dlc3Rpb259IGFuZCBhXG4gICAqICAge2F0b20kQXV0b2NvbXBsZXRlUmVxdWVzdH0gYWxsb3dpbmcgeW91IHRvIGFkanVzdCBjb252ZXJ0ZWQgaXRlbXMuXG4gICAqIEByZXR1cm5zIEEge01hcH0gb2YgQXV0b0NvbXBsZXRlKyBzdWdnZXN0aW9ucyBvcmRlcmVkIGJ5IHRoZSBDb21wbGV0aW9uSXRlbXMgc29ydFRleHQuXG4gICAqL1xuICBwdWJsaWMgY29tcGxldGlvbkl0ZW1zVG9TdWdnZXN0aW9ucyhcbiAgICBjb21wbGV0aW9uSXRlbXM6IENvbXBsZXRpb25JdGVtW10gfCBDb21wbGV0aW9uTGlzdCB8IG51bGwsXG4gICAgcmVxdWVzdDogYWMuU3VnZ2VzdGlvbnNSZXF1ZXN0ZWRFdmVudCxcbiAgICB0cmlnZ2VyQ29sdW1uczogW251bWJlciwgbnVtYmVyXSxcbiAgICBzaG91bGRSZXBsYWNlOiBTaG91bGRSZXBsYWNlLFxuICAgIG9uRGlkQ29udmVydENvbXBsZXRpb25JdGVtPzogQ29tcGxldGlvbkl0ZW1BZGp1c3RlclxuICApOiBNYXA8U3VnZ2VzdGlvbiwgUG9zc2libHlSZXNvbHZlZENvbXBsZXRpb25JdGVtPiB7XG4gICAgY29uc3QgY29tcGxldGlvbnNBcnJheSA9IEFycmF5LmlzQXJyYXkoY29tcGxldGlvbkl0ZW1zKVxuICAgICAgPyBjb21wbGV0aW9uSXRlbXNcbiAgICAgIDogKGNvbXBsZXRpb25JdGVtcyAmJiBjb21wbGV0aW9uSXRlbXMuaXRlbXMpIHx8IFtdXG4gICAgcmV0dXJuIG5ldyBNYXAoXG4gICAgICBjb21wbGV0aW9uc0FycmF5XG4gICAgICAgIC5zb3J0KChhLCBiKSA9PiAoYS5zb3J0VGV4dCB8fCBhLmxhYmVsKS5sb2NhbGVDb21wYXJlKGIuc29ydFRleHQgfHwgYi5sYWJlbCkpXG4gICAgICAgIC5tYXA8W1N1Z2dlc3Rpb24sIFBvc3NpYmx5UmVzb2x2ZWRDb21wbGV0aW9uSXRlbV0+KChzKSA9PiBbXG4gICAgICAgICAgQXV0b2NvbXBsZXRlQWRhcHRlci5jb21wbGV0aW9uSXRlbVRvU3VnZ2VzdGlvbihcbiAgICAgICAgICAgIHMsXG4gICAgICAgICAgICB7fSBhcyBTdWdnZXN0aW9uLFxuICAgICAgICAgICAgcmVxdWVzdCxcbiAgICAgICAgICAgIHRyaWdnZXJDb2x1bW5zLFxuICAgICAgICAgICAgc2hvdWxkUmVwbGFjZSxcbiAgICAgICAgICAgIG9uRGlkQ29udmVydENvbXBsZXRpb25JdGVtXG4gICAgICAgICAgKSxcbiAgICAgICAgICBuZXcgUG9zc2libHlSZXNvbHZlZENvbXBsZXRpb25JdGVtKHMsIGZhbHNlKSxcbiAgICAgICAgXSlcbiAgICApXG4gIH1cblxuICAvKipcbiAgICogUHVibGljOiBDb252ZXJ0IGEgbGFuZ3VhZ2Ugc2VydmVyIHByb3RvY29sIENvbXBsZXRpb25JdGVtIHRvIGFuIEF1dG9Db21wbGV0ZSsgc3VnZ2VzdGlvbi5cbiAgICpcbiAgICogQHBhcmFtIGl0ZW0gQW4ge0NvbXBsZXRpb25JdGVtfSBjb250YWluaW5nIGEgY29tcGxldGlvbiBpdGVtIHRvIGJlIGNvbnZlcnRlZC5cbiAgICogQHBhcmFtIHN1Z2dlc3Rpb24gQSB7YXRvbSRBdXRvY29tcGxldGVTdWdnZXN0aW9ufSB0byBoYXZlIHRoZSBjb252ZXJzaW9uIGFwcGxpZWQgdG8uXG4gICAqIEBwYXJhbSByZXF1ZXN0IFRoZSB7YXRvbSRBdXRvY29tcGxldGVSZXF1ZXN0fSB0byBzYXRpc2Z5LlxuICAgKiBAcGFyYW0gc2hvdWxkUmVwbGFjZSBUaGUgYmVoYXZpb3Igb2Ygc3VnZ2VzdGlvbiBhY2NlcHRhbmNlIChzZWUge1Nob3VsZFJlcGxhY2V9KS5cbiAgICogQHBhcmFtIG9uRGlkQ29udmVydENvbXBsZXRpb25JdGVtIEEgZnVuY3Rpb24gdGhhdCB0YWtlcyBhIHtDb21wbGV0aW9uSXRlbX0sIGFuIHthdG9tJEF1dG9jb21wbGV0ZVN1Z2dlc3Rpb259IGFuZCBhXG4gICAqICAge2F0b20kQXV0b2NvbXBsZXRlUmVxdWVzdH0gYWxsb3dpbmcgeW91IHRvIGFkanVzdCBjb252ZXJ0ZWQgaXRlbXMuXG4gICAqIEByZXR1cm5zIFRoZSB7YXRvbSRBdXRvY29tcGxldGVTdWdnZXN0aW9ufSBwYXNzZWQgaW4gYXMgc3VnZ2VzdGlvbiB3aXRoIHRoZSBjb252ZXJzaW9uIGFwcGxpZWQuXG4gICAqL1xuICBwdWJsaWMgc3RhdGljIGNvbXBsZXRpb25JdGVtVG9TdWdnZXN0aW9uKFxuICAgIGl0ZW06IENvbXBsZXRpb25JdGVtLFxuICAgIHN1Z2dlc3Rpb246IFN1Z2dlc3Rpb24sXG4gICAgcmVxdWVzdDogYWMuU3VnZ2VzdGlvbnNSZXF1ZXN0ZWRFdmVudCxcbiAgICB0cmlnZ2VyQ29sdW1uczogW251bWJlciwgbnVtYmVyXSxcbiAgICBzaG91bGRSZXBsYWNlOiBTaG91bGRSZXBsYWNlLFxuICAgIG9uRGlkQ29udmVydENvbXBsZXRpb25JdGVtPzogQ29tcGxldGlvbkl0ZW1BZGp1c3RlclxuICApOiBTdWdnZXN0aW9uIHtcbiAgICBBdXRvY29tcGxldGVBZGFwdGVyLmFwcGx5Q29tcGxldGlvbkl0ZW1Ub1N1Z2dlc3Rpb24oaXRlbSwgc3VnZ2VzdGlvbiBhcyBUZXh0U3VnZ2VzdGlvbilcbiAgICBBdXRvY29tcGxldGVBZGFwdGVyLmFwcGx5VGV4dEVkaXRUb1N1Z2dlc3Rpb24oXG4gICAgICBpdGVtLnRleHRFZGl0LFxuICAgICAgcmVxdWVzdC5lZGl0b3IsXG4gICAgICB0cmlnZ2VyQ29sdW1ucyxcbiAgICAgIHJlcXVlc3QuYnVmZmVyUG9zaXRpb24sXG4gICAgICBzdWdnZXN0aW9uIGFzIFRleHRTdWdnZXN0aW9uLFxuICAgICAgc2hvdWxkUmVwbGFjZVxuICAgIClcbiAgICBBdXRvY29tcGxldGVBZGFwdGVyLmFwcGx5U25pcHBldFRvU3VnZ2VzdGlvbihpdGVtLCBzdWdnZXN0aW9uIGFzIFNuaXBwZXRTdWdnZXN0aW9uKVxuICAgIGlmIChvbkRpZENvbnZlcnRDb21wbGV0aW9uSXRlbSAhPSBudWxsKSB7XG4gICAgICBvbkRpZENvbnZlcnRDb21wbGV0aW9uSXRlbShpdGVtLCBzdWdnZXN0aW9uIGFzIGFjLkFueVN1Z2dlc3Rpb24sIHJlcXVlc3QpXG4gICAgfVxuXG4gICAgcmV0dXJuIHN1Z2dlc3Rpb25cbiAgfVxuXG4gIC8qKlxuICAgKiBQdWJsaWM6IENvbnZlcnQgdGhlIHByaW1hcnkgcGFydHMgb2YgYSBsYW5ndWFnZSBzZXJ2ZXIgcHJvdG9jb2wgQ29tcGxldGlvbkl0ZW0gdG8gYW4gQXV0b0NvbXBsZXRlKyBzdWdnZXN0aW9uLlxuICAgKlxuICAgKiBAcGFyYW0gaXRlbSBBbiB7Q29tcGxldGlvbkl0ZW19IGNvbnRhaW5pbmcgdGhlIGNvbXBsZXRpb24gaXRlbXMgdG8gYmUgbWVyZ2VkIGludG8uXG4gICAqIEBwYXJhbSBzdWdnZXN0aW9uIFRoZSB7U3VnZ2VzdGlvbn0gdG8gbWVyZ2UgdGhlIGNvbnZlcnNpb24gaW50by5cbiAgICogQHJldHVybnMgVGhlIHtTdWdnZXN0aW9ufSB3aXRoIGRldGFpbHMgYWRkZWQgZnJvbSB0aGUge0NvbXBsZXRpb25JdGVtfS5cbiAgICovXG4gIHB1YmxpYyBzdGF0aWMgYXBwbHlDb21wbGV0aW9uSXRlbVRvU3VnZ2VzdGlvbihpdGVtOiBDb21wbGV0aW9uSXRlbSwgc3VnZ2VzdGlvbjogVGV4dFN1Z2dlc3Rpb24pOiB2b2lkIHtcbiAgICBzdWdnZXN0aW9uLnRleHQgPSBpdGVtLmluc2VydFRleHQgfHwgaXRlbS5sYWJlbFxuICAgIHN1Z2dlc3Rpb24uZmlsdGVyVGV4dCA9IGl0ZW0uZmlsdGVyVGV4dCB8fCBpdGVtLmxhYmVsXG4gICAgc3VnZ2VzdGlvbi5kaXNwbGF5VGV4dCA9IGl0ZW0ubGFiZWxcbiAgICBzdWdnZXN0aW9uLnR5cGUgPSBBdXRvY29tcGxldGVBZGFwdGVyLmNvbXBsZXRpb25LaW5kVG9TdWdnZXN0aW9uVHlwZShpdGVtLmtpbmQpXG4gICAgQXV0b2NvbXBsZXRlQWRhcHRlci5hcHBseURldGFpbHNUb1N1Z2dlc3Rpb24oaXRlbSwgc3VnZ2VzdGlvbilcbiAgICBzdWdnZXN0aW9uLmNvbXBsZXRpb25JdGVtID0gaXRlbVxuICB9XG5cbiAgcHVibGljIHN0YXRpYyBhcHBseURldGFpbHNUb1N1Z2dlc3Rpb24oaXRlbTogQ29tcGxldGlvbkl0ZW0sIHN1Z2dlc3Rpb246IFN1Z2dlc3Rpb24pOiB2b2lkIHtcbiAgICBzdWdnZXN0aW9uLnJpZ2h0TGFiZWwgPSBpdGVtLmRldGFpbFxuXG4gICAgLy8gT2xkZXIgZm9ybWF0LCBjYW4ndCBrbm93IHdoYXQgaXQgaXMgc28gYXNzaWduIHRvIGJvdGggYW5kIGhvcGUgZm9yIGJlc3RcbiAgICBpZiAodHlwZW9mIGl0ZW0uZG9jdW1lbnRhdGlvbiA9PT0gXCJzdHJpbmdcIikge1xuICAgICAgc3VnZ2VzdGlvbi5kZXNjcmlwdGlvbk1hcmtkb3duID0gaXRlbS5kb2N1bWVudGF0aW9uXG4gICAgICBzdWdnZXN0aW9uLmRlc2NyaXB0aW9uID0gaXRlbS5kb2N1bWVudGF0aW9uXG4gICAgfVxuXG4gICAgaWYgKGl0ZW0uZG9jdW1lbnRhdGlvbiAhPSBudWxsICYmIHR5cGVvZiBpdGVtLmRvY3VtZW50YXRpb24gPT09IFwib2JqZWN0XCIpIHtcbiAgICAgIC8vIE5ld2VyIGZvcm1hdCBzcGVjaWZpZXMgdGhlIGtpbmQgb2YgZG9jdW1lbnRhdGlvbiwgYXNzaWduIGFwcHJvcHJpYXRlbHlcbiAgICAgIGlmIChpdGVtLmRvY3VtZW50YXRpb24ua2luZCA9PT0gXCJtYXJrZG93blwiKSB7XG4gICAgICAgIHN1Z2dlc3Rpb24uZGVzY3JpcHRpb25NYXJrZG93biA9IGl0ZW0uZG9jdW1lbnRhdGlvbi52YWx1ZVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgc3VnZ2VzdGlvbi5kZXNjcmlwdGlvbiA9IGl0ZW0uZG9jdW1lbnRhdGlvbi52YWx1ZVxuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBQdWJsaWM6IEFwcGxpZXMgdGhlIHRleHRFZGl0IHBhcnQgb2YgYSBsYW5ndWFnZSBzZXJ2ZXIgcHJvdG9jb2wgQ29tcGxldGlvbkl0ZW0gdG8gYW4gQXV0b0NvbXBsZXRlKyBTdWdnZXN0aW9uIHZpYVxuICAgKiB0aGUgcmVwbGFjZW1lbnRQcmVmaXggYW5kIHRleHQgcHJvcGVydGllcy5cbiAgICpcbiAgICogQHBhcmFtIHRleHRFZGl0IEEge1RleHRFZGl0fSBmcm9tIGEgQ29tcGxldGlvbkl0ZW0gdG8gYXBwbHkuXG4gICAqIEBwYXJhbSBlZGl0b3IgQW4gQXRvbSB7VGV4dEVkaXRvcn0gdXNlZCB0byBvYnRhaW4gdGhlIG5lY2Vzc2FyeSB0ZXh0IHJlcGxhY2VtZW50LlxuICAgKiBAcGFyYW0gc3VnZ2VzdGlvbiBBbiB7YXRvbSRBdXRvY29tcGxldGVTdWdnZXN0aW9ufSB0byBzZXQgdGhlIHJlcGxhY2VtZW50UHJlZml4IGFuZCB0ZXh0IHByb3BlcnRpZXMgb2YuXG4gICAqIEBwYXJhbSBzaG91bGRSZXBsYWNlIFRoZSBiZWhhdmlvciBvZiBzdWdnZXN0aW9uIGFjY2VwdGFuY2UgKHNlZSB7U2hvdWxkUmVwbGFjZX0pLlxuICAgKi9cbiAgcHVibGljIHN0YXRpYyBhcHBseVRleHRFZGl0VG9TdWdnZXN0aW9uKFxuICAgIHRleHRFZGl0OiBUZXh0RWRpdCB8IEluc2VydFJlcGxhY2VFZGl0IHwgdW5kZWZpbmVkLFxuICAgIGVkaXRvcjogVGV4dEVkaXRvcixcbiAgICB0cmlnZ2VyQ29sdW1uczogW251bWJlciwgbnVtYmVyXSxcbiAgICBvcmlnaW5hbEJ1ZmZlclBvc2l0aW9uOiBQb2ludCxcbiAgICBzdWdnZXN0aW9uOiBUZXh0U3VnZ2VzdGlvbixcbiAgICBzaG91bGRSZXBsYWNlOiBTaG91bGRSZXBsYWNlXG4gICk6IHZvaWQge1xuICAgIGlmICghdGV4dEVkaXQpIHtcbiAgICAgIHJldHVyblxuICAgIH1cbiAgICBsZXQgcmFuZ2U6IFJhbmdlXG4gICAgaWYgKFwicmFuZ2VcIiBpbiB0ZXh0RWRpdCkge1xuICAgICAgcmFuZ2UgPSB0ZXh0RWRpdC5yYW5nZVxuICAgIH0gZWxzZSBpZiAoc2hvdWxkUmVwbGFjZSkge1xuICAgICAgcmFuZ2UgPSB0ZXh0RWRpdC5yZXBsYWNlXG4gICAgfSBlbHNlIHtcbiAgICAgIHJhbmdlID0gdGV4dEVkaXQuaW5zZXJ0XG4gICAgfVxuXG4gICAgaWYgKHJhbmdlLnN0YXJ0LmNoYXJhY3RlciAhPT0gdHJpZ2dlckNvbHVtbnNbMF0pIHtcbiAgICAgIGNvbnN0IGF0b21SYW5nZSA9IENvbnZlcnQubHNSYW5nZVRvQXRvbVJhbmdlKHJhbmdlKVxuICAgICAgc3VnZ2VzdGlvbi5jdXN0b21SZXBsYWNtZW50UHJlZml4ID0gZWRpdG9yLmdldFRleHRJbkJ1ZmZlclJhbmdlKFthdG9tUmFuZ2Uuc3RhcnQsIG9yaWdpbmFsQnVmZmVyUG9zaXRpb25dKVxuICAgIH1cbiAgICBzdWdnZXN0aW9uLnRleHQgPSB0ZXh0RWRpdC5uZXdUZXh0XG4gIH1cblxuICAvKipcbiAgICogSGFuZGxlIGFkZGl0aW9uYWwgdGV4dCBlZGl0cyBhZnRlciBhIHN1Z2dlc3Rpb24gaW5zZXJ0LCBlLmcuIGBhZGRpdGlvbmFsVGV4dEVkaXRzYC5cbiAgICpcbiAgICogYGFkZGl0aW9uYWxUZXh0RWRpdHNgIGFyZSBBbiBvcHRpb25hbCBhcnJheSBvZiBhZGRpdGlvbmFsIHRleHQgZWRpdHMgdGhhdCBhcmUgYXBwbGllZCB3aGVuIHNlbGVjdGluZyB0aGlzXG4gICAqIGNvbXBsZXRpb24uIEVkaXRzIG11c3Qgbm90IG92ZXJsYXAgKGluY2x1ZGluZyB0aGUgc2FtZSBpbnNlcnQgcG9zaXRpb24pIHdpdGggdGhlIG1haW4gZWRpdCBub3Igd2l0aCB0aGVtc2VsdmVzLlxuICAgKlxuICAgKiBBZGRpdGlvbmFsIHRleHQgZWRpdHMgc2hvdWxkIGJlIHVzZWQgdG8gY2hhbmdlIHRleHQgdW5yZWxhdGVkIHRvIHRoZSBjdXJyZW50IGN1cnNvciBwb3NpdGlvbiAoZm9yIGV4YW1wbGUgYWRkaW5nIGFuXG4gICAqIGltcG9ydCBzdGF0ZW1lbnQgYXQgdGhlIHRvcCBvZiB0aGUgZmlsZSBpZiB0aGUgY29tcGxldGlvbiBpdGVtIHdpbGwgaW5zZXJ0IGFuIHVucXVhbGlmaWVkIHR5cGUpLlxuICAgKi9cbiAgcHVibGljIHN0YXRpYyBhcHBseUFkZGl0aW9uYWxUZXh0RWRpdHMoZXZlbnQ6IGFjLlN1Z2dlc3Rpb25JbnNlcnRlZEV2ZW50KTogdm9pZCB7XG4gICAgY29uc3Qgc3VnZ2VzdGlvbiA9IGV2ZW50LnN1Z2dlc3Rpb24gYXMgU3VnZ2VzdGlvbkJhc2VcbiAgICBjb25zdCBhZGRpdGlvbmFsRWRpdHMgPSBzdWdnZXN0aW9uLmNvbXBsZXRpb25JdGVtPy5hZGRpdGlvbmFsVGV4dEVkaXRzXG4gICAgY29uc3QgYnVmZmVyID0gZXZlbnQuZWRpdG9yLmdldEJ1ZmZlcigpXG5cbiAgICBBcHBseUVkaXRBZGFwdGVyLmFwcGx5RWRpdHMoYnVmZmVyLCBDb252ZXJ0LmNvbnZlcnRMc1RleHRFZGl0cyhhZGRpdGlvbmFsRWRpdHMpKVxuICAgIGJ1ZmZlci5ncm91cExhc3RDaGFuZ2VzKClcbiAgfVxuXG4gIC8qKlxuICAgKiBQdWJsaWM6IEFkZHMgYSBzbmlwcGV0IHRvIHRoZSBzdWdnZXN0aW9uIGlmIHRoZSBDb21wbGV0aW9uSXRlbSBjb250YWlucyBzbmlwcGV0LWZvcm1hdHRlZCB0ZXh0XG4gICAqXG4gICAqIEBwYXJhbSBpdGVtIEFuIHtDb21wbGV0aW9uSXRlbX0gY29udGFpbmluZyB0aGUgY29tcGxldGlvbiBpdGVtcyB0byBiZSBtZXJnZWQgaW50by5cbiAgICogQHBhcmFtIHN1Z2dlc3Rpb24gVGhlIHthdG9tJEF1dG9jb21wbGV0ZVN1Z2dlc3Rpb259IHRvIG1lcmdlIHRoZSBjb252ZXJzaW9uIGludG8uXG4gICAqL1xuICBwdWJsaWMgc3RhdGljIGFwcGx5U25pcHBldFRvU3VnZ2VzdGlvbihpdGVtOiBDb21wbGV0aW9uSXRlbSwgc3VnZ2VzdGlvbjogU25pcHBldFN1Z2dlc3Rpb24pOiB2b2lkIHtcbiAgICBpZiAoaXRlbS5pbnNlcnRUZXh0Rm9ybWF0ID09PSBJbnNlcnRUZXh0Rm9ybWF0LlNuaXBwZXQpIHtcbiAgICAgIHN1Z2dlc3Rpb24uc25pcHBldCA9IGl0ZW0udGV4dEVkaXQgIT0gbnVsbCA/IGl0ZW0udGV4dEVkaXQubmV3VGV4dCA6IGl0ZW0uaW5zZXJ0VGV4dCB8fCBpdGVtLmxhYmVsXG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFB1YmxpYzogT2J0YWluIHRoZSB0ZXh0dWFsIHN1Z2dlc3Rpb24gdHlwZSByZXF1aXJlZCBieSBBdXRvQ29tcGxldGUrIHRoYXQgbW9zdCBjbG9zZWx5IG1hcHMgdG8gdGhlIG51bWVyaWNcbiAgICogY29tcGxldGlvbiBraW5kIHN1cHBsaWVzIGJ5IHRoZSBsYW5ndWFnZSBzZXJ2ZXIuXG4gICAqXG4gICAqIEBwYXJhbSBraW5kIEEge051bWJlcn0gdGhhdCByZXByZXNlbnRzIHRoZSBzdWdnZXN0aW9uIGtpbmQgdG8gYmUgY29udmVydGVkLlxuICAgKiBAcmV0dXJucyBBIHtTdHJpbmd9IGNvbnRhaW5pbmcgdGhlIEF1dG9Db21wbGV0ZSsgc3VnZ2VzdGlvbiB0eXBlIGVxdWl2YWxlbnQgdG8gdGhlIGdpdmVuIGNvbXBsZXRpb24ga2luZC5cbiAgICovXG4gIHB1YmxpYyBzdGF0aWMgY29tcGxldGlvbktpbmRUb1N1Z2dlc3Rpb25UeXBlKGtpbmQ6IG51bWJlciB8IHVuZGVmaW5lZCk6IHN0cmluZyB7XG4gICAgc3dpdGNoIChraW5kKSB7XG4gICAgICBjYXNlIENvbXBsZXRpb25JdGVtS2luZC5Db25zdGFudDpcbiAgICAgICAgcmV0dXJuIFwiY29uc3RhbnRcIlxuICAgICAgY2FzZSBDb21wbGV0aW9uSXRlbUtpbmQuTWV0aG9kOlxuICAgICAgICByZXR1cm4gXCJtZXRob2RcIlxuICAgICAgY2FzZSBDb21wbGV0aW9uSXRlbUtpbmQuRnVuY3Rpb246XG4gICAgICBjYXNlIENvbXBsZXRpb25JdGVtS2luZC5Db25zdHJ1Y3RvcjpcbiAgICAgICAgcmV0dXJuIFwiZnVuY3Rpb25cIlxuICAgICAgY2FzZSBDb21wbGV0aW9uSXRlbUtpbmQuRmllbGQ6XG4gICAgICBjYXNlIENvbXBsZXRpb25JdGVtS2luZC5Qcm9wZXJ0eTpcbiAgICAgICAgcmV0dXJuIFwicHJvcGVydHlcIlxuICAgICAgY2FzZSBDb21wbGV0aW9uSXRlbUtpbmQuVmFyaWFibGU6XG4gICAgICAgIHJldHVybiBcInZhcmlhYmxlXCJcbiAgICAgIGNhc2UgQ29tcGxldGlvbkl0ZW1LaW5kLkNsYXNzOlxuICAgICAgICByZXR1cm4gXCJjbGFzc1wiXG4gICAgICBjYXNlIENvbXBsZXRpb25JdGVtS2luZC5TdHJ1Y3Q6XG4gICAgICBjYXNlIENvbXBsZXRpb25JdGVtS2luZC5UeXBlUGFyYW1ldGVyOlxuICAgICAgICByZXR1cm4gXCJ0eXBlXCJcbiAgICAgIGNhc2UgQ29tcGxldGlvbkl0ZW1LaW5kLk9wZXJhdG9yOlxuICAgICAgICByZXR1cm4gXCJzZWxlY3RvclwiXG4gICAgICBjYXNlIENvbXBsZXRpb25JdGVtS2luZC5JbnRlcmZhY2U6XG4gICAgICAgIHJldHVybiBcIm1peGluXCJcbiAgICAgIGNhc2UgQ29tcGxldGlvbkl0ZW1LaW5kLk1vZHVsZTpcbiAgICAgICAgcmV0dXJuIFwibW9kdWxlXCJcbiAgICAgIGNhc2UgQ29tcGxldGlvbkl0ZW1LaW5kLlVuaXQ6XG4gICAgICAgIHJldHVybiBcImJ1aWx0aW5cIlxuICAgICAgY2FzZSBDb21wbGV0aW9uSXRlbUtpbmQuRW51bTpcbiAgICAgIGNhc2UgQ29tcGxldGlvbkl0ZW1LaW5kLkVudW1NZW1iZXI6XG4gICAgICAgIHJldHVybiBcImVudW1cIlxuICAgICAgY2FzZSBDb21wbGV0aW9uSXRlbUtpbmQuS2V5d29yZDpcbiAgICAgICAgcmV0dXJuIFwia2V5d29yZFwiXG4gICAgICBjYXNlIENvbXBsZXRpb25JdGVtS2luZC5TbmlwcGV0OlxuICAgICAgICByZXR1cm4gXCJzbmlwcGV0XCJcbiAgICAgIGNhc2UgQ29tcGxldGlvbkl0ZW1LaW5kLkZpbGU6XG4gICAgICBjYXNlIENvbXBsZXRpb25JdGVtS2luZC5Gb2xkZXI6XG4gICAgICAgIHJldHVybiBcImltcG9ydFwiXG4gICAgICBjYXNlIENvbXBsZXRpb25JdGVtS2luZC5SZWZlcmVuY2U6XG4gICAgICAgIHJldHVybiBcInJlcXVpcmVcIlxuICAgICAgZGVmYXVsdDpcbiAgICAgICAgcmV0dXJuIFwidmFsdWVcIlxuICAgIH1cbiAgfVxufVxuXG4vKipcbiAqIE5vcm1hbGl6ZXMgdGhlIGdpdmVuIGdyYW1tYXIgc2NvcGUgZm9yIGF1dG9Db21wbGV0ZSBwYWNrYWdlIHNvIGl0IGFsd2F5cyBzdGFydHMgd2l0aCBgLmAgQmFzZWQgb25cbiAqIGh0dHBzOi8vZ2l0aHViLmNvbS9hdG9tL2F1dG9jb21wbGV0ZS1wbHVzL3dpa2kvQXV0b2NvbXBsZXRlLVByb3ZpZGVyc1xuICpcbiAqIEBwYXJhbSBncmFtbWFyU2NvcGUgU3VjaCBhcyAnc291cmNlLnB5dGhvbicgb3IgJy5zb3VyY2UucHl0aG9uJ1xuICogQHJldHVybnMgVGhlIG5vcm1hbGl6ZWQgZ3JhbW1hclNjb3BlIHN1Y2ggYXMgYC5zb3VyY2UucHl0aG9uYFxuICovXG5leHBvcnQgZnVuY3Rpb24gZ3JhbW1hclNjb3BlVG9BdXRvQ29tcGxldGVTZWxlY3RvcihncmFtbWFyU2NvcGU6IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiBncmFtbWFyU2NvcGUuaW5jbHVkZXMoXCIuXCIpICYmIGdyYW1tYXJTY29wZVswXSAhPT0gXCIuXCIgPyBgLiR7Z3JhbW1hclNjb3BlfWAgOiBncmFtbWFyU2NvcGVcbn1cbiJdfQ==
\No newline at end of file