Merge branch 'bug/JAL-1644_Finder-logic-improvement' into develop
authorCharles Ofoegbu <tcnofoegbu@dundee.ac.uk>
Mon, 2 Feb 2015 10:32:14 +0000 (10:32 +0000)
committerCharles Ofoegbu <tcnofoegbu@dundee.ac.uk>
Mon, 2 Feb 2015 10:32:14 +0000 (10:32 +0000)
172 files changed:
.classpath
.gitignore
.settings/org.eclipse.jdt.core.prefs
examples/example_biojs.html [new file with mode: 0644]
examples/testdata/uniref50_seqref.jva [new file with mode: 0644]
help/help.jhm
help/helpTOC.xml
help/html/calculations/AnnotationColumnSelectionWithSM.gif [new file with mode: 0644]
help/html/calculations/AnnotationColumnSelectionWithoutSM.gif [new file with mode: 0644]
help/html/calculations/columnFilterByAnnotation.html [new file with mode: 0644]
help/html/features/annotationsFormat.html
lib/jsoup-1.8.1.jar [new file with mode: 0644]
resources/lang/Messages.properties
resources/templates/BioJSTemplate.txt [new file with mode: 0644]
src/MCview/AppletPDBCanvas.java
src/MCview/PDBCanvas.java
src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java
src/ext/edu/ucsf/rbvi/strucviz2/port/ListenerThreads.java
src/jalview/analysis/AlignSeq.java
src/jalview/analysis/Conservation.java
src/jalview/analysis/Finder.java
src/jalview/analysis/NJTree.java
src/jalview/analysis/SequenceIdMatcher.java
src/jalview/analysis/scoremodels/FeatureScoreModel.java [new file with mode: 0644]
src/jalview/api/AlignViewControllerGuiI.java
src/jalview/api/AlignViewControllerI.java
src/jalview/api/AlignViewportI.java
src/jalview/api/AlignmentViewPanel.java
src/jalview/api/FeatureRenderer.java
src/jalview/api/FeatureSettingsControllerI.java [new file with mode: 0644]
src/jalview/api/FeatureSettingsModelI.java [new file with mode: 0644]
src/jalview/api/FeaturesDisplayedI.java [new file with mode: 0644]
src/jalview/api/SequenceRenderer.java
src/jalview/api/SequenceStructureBinding.java
src/jalview/api/analysis/ViewBasedAnalysisI.java [new file with mode: 0644]
src/jalview/api/structures/JalviewStructureDisplayI.java
src/jalview/appletgui/APopupMenu.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/AlignViewport.java
src/jalview/appletgui/AlignmentPanel.java
src/jalview/appletgui/AnnotationColourChooser.java
src/jalview/appletgui/AnnotationColumnChooser.java [new file with mode: 0644]
src/jalview/appletgui/AnnotationLabels.java
src/jalview/appletgui/AnnotationRowFilter.java [new file with mode: 0644]
src/jalview/appletgui/AppletJmol.java
src/jalview/appletgui/AppletJmolBinding.java
src/jalview/appletgui/CutAndPasteTransfer.java
src/jalview/appletgui/ExtJmol.java
src/jalview/appletgui/FeatureColourChooser.java
src/jalview/appletgui/FeatureRenderer.java
src/jalview/appletgui/FeatureSettings.java
src/jalview/appletgui/Finder.java
src/jalview/appletgui/IdCanvas.java
src/jalview/appletgui/OverviewPanel.java
src/jalview/appletgui/ScalePanel.java
src/jalview/appletgui/SeqCanvas.java
src/jalview/appletgui/SeqPanel.java
src/jalview/appletgui/SequenceRenderer.java
src/jalview/appletgui/TitledPanel.java [new file with mode: 0644]
src/jalview/appletgui/TreePanel.java
src/jalview/bin/Jalview.java
src/jalview/bin/JalviewLite.java
src/jalview/controller/AlignViewController.java
src/jalview/controller/FeatureSettingsController.java [new file with mode: 0644]
src/jalview/controller/FeatureSettingsControllerGuiI.java [new file with mode: 0644]
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/CigarArray.java
src/jalview/datamodel/ColumnSelection.java
src/jalview/datamodel/PDBEntry.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceCollectionI.java
src/jalview/datamodel/SequenceI.java
src/jalview/datamodel/StructureViewerModel.java [new file with mode: 0644]
src/jalview/exceptions/JalviewException.java [new file with mode: 0644]
src/jalview/exceptions/NoFileSelectedException.java [new file with mode: 0644]
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/ext/rbvi/chimera/ChimeraCommands.java
src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
src/jalview/ext/varna/JalviewVarnaBinding.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AnnotationColourChooser.java
src/jalview/gui/AnnotationColumnChooser.java [new file with mode: 0644]
src/jalview/gui/AnnotationExporter.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/AnnotationPanel.java
src/jalview/gui/AnnotationRowFilter.java [new file with mode: 0644]
src/jalview/gui/AppJmol.java
src/jalview/gui/AppJmolBinding.java
src/jalview/gui/ChimeraViewFrame.java
src/jalview/gui/Desktop.java
src/jalview/gui/FeatureColourChooser.java
src/jalview/gui/FeatureRenderer.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/Finder.java
src/jalview/gui/FontChooser.java
src/jalview/gui/HTMLOptions.java [new file with mode: 0644]
src/jalview/gui/IdCanvas.java
src/jalview/gui/IdPanel.java
src/jalview/gui/IdwidthAdjuster.java
src/jalview/gui/Jalview2XML.java
src/jalview/gui/Jalview2XML_V1.java
src/jalview/gui/JalviewChimeraBindingModel.java
src/jalview/gui/JvSwingUtils.java
src/jalview/gui/OverviewPanel.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/Preferences.java
src/jalview/gui/RedundancyPanel.java
src/jalview/gui/ScalePanel.java
src/jalview/gui/ScriptWindow.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SeqPanel.java
src/jalview/gui/SequenceRenderer.java
src/jalview/gui/SliderPanel.java
src/jalview/gui/SplashScreen.java
src/jalview/gui/StructureViewer.java
src/jalview/gui/StructureViewerBase.java [new file with mode: 0644]
src/jalview/gui/TreePanel.java
src/jalview/io/AnnotationFile.java
src/jalview/io/AppletFormatAdapter.java
src/jalview/io/BioJsHTMLOutput.java [new file with mode: 0644]
src/jalview/io/FeaturesFile.java
src/jalview/io/FileLoader.java
src/jalview/io/FileParse.java
src/jalview/io/FormatAdapter.java
src/jalview/io/HTMLOutput.java
src/jalview/io/HtmlFile.java [new file with mode: 0644]
src/jalview/io/HtmlSvgOutput.java [new file with mode: 0644]
src/jalview/io/IdentifyFile.java
src/jalview/io/SequenceAnnotationReport.java
src/jalview/io/VamsasAppDatastore.java
src/jalview/io/packed/ParsePackedSet.java
src/jalview/javascript/MouseOverStructureListener.java
src/jalview/jbgui/GAlignFrame.java
src/jalview/jbgui/GPreferences.java
src/jalview/jbgui/GStructureViewer.java
src/jalview/json/binding/v1/BioJsAlignmentPojo.java [new file with mode: 0644]
src/jalview/json/binding/v1/BioJsFeaturePojo.java [new file with mode: 0644]
src/jalview/json/binding/v1/BioJsSeqPojo.java [new file with mode: 0644]
src/jalview/math/AlignmentDimension.java [new file with mode: 0644]
src/jalview/renderer/seqfeatures/FeatureRenderer.java [new file with mode: 0644]
src/jalview/schemes/AnnotationColourGradient.java
src/jalview/schemes/Blosum62ColourScheme.java
src/jalview/schemes/ResidueProperties.java
src/jalview/structures/models/AAStructureBindingModel.java [new file with mode: 0644]
src/jalview/structures/models/SequenceStructureBindingModel.java
src/jalview/util/ColorUtils.java
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/viewmodel/annotationfilter/AnnotationFilterParameter.java [new file with mode: 0644]
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java [new file with mode: 0644]
src/jalview/viewmodel/seqfeatures/FeatureRendererSettings.java [new file with mode: 0644]
src/jalview/viewmodel/seqfeatures/FeatureSettingsModel.java [new file with mode: 0644]
src/jalview/viewmodel/seqfeatures/FeaturesDisplayed.java [new file with mode: 0644]
src/jalview/ws/AWSThread.java
src/jalview/ws/HttpClientUtils.java
src/jalview/ws/jws2/AADisorderClient.java
src/jalview/ws/rest/params/AnnotationFile.java
test/jalview/analysis/scoremodels/FeatureScoreModelTest.java [new file with mode: 0644]
test/jalview/datamodel/SequenceTest.java
test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java [new file with mode: 0644]
test/jalview/ext/rbvi/chimera/JalviewChimeraView.java
test/jalview/gui/SequenceRendererTest.java [new file with mode: 0644]
test/jalview/io/AnnotationFileIOTest.java
test/jalview/io/BioJsHTMLOutputTest.java [new file with mode: 0644]
test/jalview/io/HtmlFileTest.java [new file with mode: 0644]
test/jalview/structure/Mapping.java
test/jalview/util/ColorUtilsTest.java
test/jalview/ws/jabaws/DisorderAnnotExportImport.java
test/jalview/ws/jabaws/JpredJabaStructExportImport.java
test/jalview/ws/jabaws/RNAStructExportImport.java
utils/InstallAnywhere/Jalview.iap_xml

index f87dd26..59772ae 100644 (file)
        <classpathentry kind="lib" path="lib/min-jabaws-client-2.1.0.jar" sourcepath="/clustengine"/>
        <classpathentry kind="lib" path="lib/json_simple-1.1.jar" sourcepath="/Users/jimp/Downloads/json_simple-1.1-all.zip"/>
        <classpathentry kind="lib" path="lib/slf4j-api-1.7.7.jar"/>
+       <classpathentry kind="lib" path="lib/jsoup-1.8.1.jar"/>
        <classpathentry kind="lib" path="lib/log4j-to-slf4j-2.0-rc2.jar"/>
        <classpathentry kind="lib" path="lib/slf4j-log4j12-1.7.7.jar"/>
        <classpathentry kind="lib" path="lib/VARNAv3-91.jar"/>
-
-       <classpathentry kind="lib" path="lib/jfreesvg-2.1.jar"/>
-       <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/plugin.jar"/>
        <classpathentry kind="lib" path="lib/xml-apis.jar"/>
        <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
        <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-       <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/Plugin.jar"/>
+       <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/plugin"/>
+       <classpathentry kind="lib" path="lib/jfreesvg-2.1.jar"/>
        <classpathentry kind="output" path="classes"/>
 </classpath>
index c47ff62..0c12fb0 100644 (file)
@@ -5,3 +5,6 @@
 /.DS_Store
 .DS_Store
 /.com.apple.timemachine.supported
+.gitattributes
+
+
index 884491a..f72955b 100644 (file)
@@ -1,4 +1,15 @@
 eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.7
 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
diff --git a/examples/example_biojs.html b/examples/example_biojs.html
new file mode 100644 (file)
index 0000000..b6f7bec
--- /dev/null
@@ -0,0 +1,9031 @@
+<html>
+<header><title>BioJS viewer</title></header>
+
+<body>
+
+<!-- include MSA js + css -->
+<!-- <script src="https://s3-eu-west-1.amazonaws.com/biojs/msa/latest/msa.js"></script> -->
+<!-- <link type=text/css rel=stylesheet href=https://s3-eu-west-1.amazonaws.com/biojs/msa/latest/msa.css /> -->
+ <img src="http://www.jalview.org/help/html/Jalview_Logo.png" alt="Jalview Logo" title="This html page was generated from Jalview, to import the data back to Jalview, please drag the generated html file and drop it unto the Jalview workbench.
+ Alternatively, you could copy the url from the address bar and use Jalview's url importer (main menu-> File-> Input Alignment-> from URL) to import back the alignment jalview." >
+
+</br>
+</br>
+
+<input type="button" name="divToggleButton" id="divToggleButton" onclick="javascipt:toggleMenuVisibility();" value="Show Menu"></input>
+<button onclick="javascipt:openJalviewUsingCurrentUrl();">Launch in Jalview</button>
+
+</br>
+</br> 
+  
+<div id="yourDiv">press "Run with JS"</div>
+<input type='hidden' id='seqData' name='seqData' value='{"globalColorScheme":"zappo","seqs":[{"id":"1","start":1,"name":"FER_CAPAA/1-97","seq":"----------------------------------------------------------ASYKVKLITPDGPIEFDCPDDVYILDQAEEAGHDLPYSCRAGSCSSCAGKIAGGAVDQTDGNFLDDDQLEEGWVLTCVAYPQSDVTIETHKEAELVG-","features":[],"end":97},{"id":"2","start":1,"name":"FER_CAPAN/1-144","seq":"------MASVSATMISTSFMPRKPAVTSLKPIP-NVG-EALFGLKS---ANGGKVTCMASYKVKLITPDGPIEFDCPDNVYILDQAEEAGHDLPYSCRAGSCSSCAGKIAGGAVDQTDGNFLDDDQLEEGWVLTCVAYPQSDVTIETHKEAELVG-","features":[{"text":"feature_1","xEnd":46,"fillColor":"#8c25cd","xStart":16}],"end":144},{"id":"3","start":1,"name":"FER1_SOLLC/1-144","seq":"------MASISGTMISTSFLPRKPAVTSLKAIS-NVG-EALFGLKS---GRNGRITCMASYKVKLITPEGPIEFECPDDVYILDQAEEEGHDLPYSCRAGSCSSCAGKVTAGSVDQSDGNFLDEDQEAAGFVLTCVAYPKGDVTIETHKEEELTA-","features":[],"end":144},{"id":"4","start":1,"name":"Q93XJ9_SOLTU/1-144","seq":"------MASISGTMISTSFLPRKPVVTSLKAIS-NVG-EALFGLKS---GRNGRITCMASYKVKLITPDGPIEFECPDDVYILDQAEEEGHDLPYSCRAGSCSSCAGKVTAGTVDQSDGKFLDDDQEAAGFVLTCVAYPKCDVTIETHKEEELTA-","features":[],"end":144},{"id":"5","start":1,"name":"FER1_PEA/1-149","seq":"---MATTPALYGTAVSTSFLRTQPMPMSVTTTKAFSN--GFLGLKT-SLKRGDLAVAMASYKVKLVTPDGTQEFECPSDVYILDHAEEVGIDLPYSCRAGSCSSCAGKVVGGEVDQSDGSFLDDEQIEAGFVLTCVAYPTSDVVIETHKEEDLTA-","features":[],"end":149},{"id":"6","start":1,"name":"Q7XA98_TRIPR/1-152","seq":"---MATTPALYGTAVSTSFMRRQPVPMSVATTTTTKAFPSGFGLKSVSTKRGDLAVAMATYKVKLITPEGPQEFDCPDDVYILDHAEEVGIELPYSCRAGSCSSCAGKVVNGNVNQEDGSFLDDEQIEGGWVLTCVAFPTSDVTIETHKEEELTA-","features":[{"text":"feature_2","xEnd":24,"fillColor":"#0000cc","xStart":8}],"end":152},{"id":"7","start":1,"name":"FER1_MESCR/1-148","seq":"--MAATTAALSGATMSTAFAPKT--PPMTAALPTNVG-RALFGLKS--SASRGRVTAMAAYKVTLVTPEGKQELECPDDVYILDAAEEAGIDLPYSCRAGSCSSCAGKVTSGSVNQDDGSFLDDDQIKEGWVLTCVAYPTGDVTIETHKEEELTA-","features":[],"end":148},{"id":"8","start":1,"name":"FER1_SPIOL/1-147","seq":"----MAATTTTMMGMATTFVPKPQAPPMMAALPSNTG-RSLFGLKT--GSRGGRMT-MAAYKVTLVTPTGNVEFQCPDDVYILDAAEEEGIDLPYSCRAGSCSSCAGKLKTGSLNQDDQSFLDDDQIDEGWVLTCAAYPVSDVTIETHKEEELTA-","features":[],"end":147},{"id":"9","start":1,"name":"FER3_RAPSA/1-96","seq":"----------------------------------------------------------ATYKVKFITPEGEQEVECDDDVYVLDAAEEAGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQSFLDDDQIAEGFVLTCAAYPTSDVTIETHREEDMV--","features":[],"end":96},{"id":"10","start":1,"name":"FER1_ARATH/1-148","seq":"----MASTALSSAIVGTSFIRRSPAPISLRSLPSANT-QSLFGLKS-GTARGGRVTAMATYKVKFITPEGELEVECDDDVYVLDAAEEAGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQSFLDDEQIGEGFVLTCAAYPTSDVTIETHKEEDIV--","features":[],"end":148},{"id":"11","start":1,"name":"FER_BRANA/1-96","seq":"----------------------------------------------------------ATYKVKFITPEGEQEVECDDDVYVLDAAEEAGIDLPYSCRAGSCSSCAGKVVSGFVDQSDESFLDDDQIAEGFVLTCAAYPTSDVTIETHKEEELV--","features":[],"end":96},{"id":"12","start":1,"name":"FER2_ARATH/1-148","seq":"----MASTALSSAIVSTSFLRRQQTPISLRSLPFANT-QSLFGLKS-STARGGRVTAMATYKVKFITPEGEQEVECEEDVYVLDAAEEAGLDLPYSCRAGSCSSCAGKVVSGSIDQSDQSFLDDEQMSEGYVLTCVAYPTSDVVIETHKEEAIM--","features":[{"text":"feature_3","xEnd":32,"fillColor":"#ffff00","xStart":4}],"end":148},{"id":"13","start":1,"name":"Q93Z60_ARATH/1-118","seq":"----MASTALSSAIVSTSFLRRQQTPISLRSLPFANT-QSLFGLKS-STARGGRVTAMATYKVKFITPEGEQEVECEEDVYVLDAAEEAGLDLPYSCRAGSCSSCAGKVVSGSIDQSDQSFLDD--------------------------------","features":[],"end":118},{"id":"14","start":1,"name":"FER1_MAIZE/1-150","seq":"MATVLGSPRAPAFFFSSSSLRAAPAPTAVALPAAKVG---IMGRSA---SSRRRLRAQATYNVKLITPEGEVELQVPDDVYILDQAEEDGIDLPYSCRAGSCSSCAGKVVSGSVDQSDQSYLDDGQIADGWVLTCHAYPTSDVVIETHKEEELTGA","features":[],"end":150},{"id":"15","start":1,"name":"O80429_MAIZE/1-140","seq":"---------MAATALSMSILRAPP-PCFSSPLRLRVAVAKPLAAPM----RRQLLRAQATYNVKLITPEGEVELQVPDDVYILDFAEEEGIDLPFSCRAGSCSSCAGKVVSGSVDQSDQSFLNDNQVADGWVLTCAAYPTSDVVIETHKEDDLL--","features":[],"end":140}],"webStartUrl":"http://www.jalview.org/services/launchApp","jalviewVersion":"Test"}'/>
+
+</body>
+</html>
+
+
+
+<script>
+
+function toggleMenuVisibility(){
+       var menu = document.getElementsByClassName("biojs_msa_menubar");
+       var divToggleButton = document.getElementById("divToggleButton");
+       if(menu[0].style.display == 'block'){
+          menu[0].style.display = 'none';
+          divToggleButton.value="Show Menu";
+       }else{
+          menu[0].style.display = 'block'; 
+          divToggleButton.value="Hide Menu";
+          }
+}
+
+
+function openJalviewUsingCurrentUrl(){
+       var jalviewData = JSON.parse(document.getElementById("seqData").value)
+    var jalviewVersion = jalviewData['jalviewVersion'];
+    var url = jalviewData['webStartUrl'];
+       var myForm = document.createElement("form");
+       myForm.action = url;
+       
+    var heap = document.createElement("input") ;
+    heap.setAttribute("name", "jvm-max-heap") ;
+    heap.setAttribute("value", "2G");
+    myForm.appendChild(heap) ;
+    
+    var target = document.createElement("input") ;
+    target.setAttribute("name", "open") ;
+    target.setAttribute("value", document.URL);
+    myForm.appendChild(target) ;
+    
+    var jvVersion = document.createElement("input") ;
+    jvVersion.setAttribute("name", "version") ;
+    jvVersion.setAttribute("value", jalviewVersion);
+    myForm.appendChild(jvVersion) ;
+    
+       document.body.appendChild(myForm) ;
+       myForm.submit() ;
+       document.body.removeChild(myForm) ;
+}
+
+
+require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+var css = ".biojs_msa_stage {\n  cursor: default;\n  line-height: normal; }\n\n.biojs_msa_labels {\n  color: black;\n  display: inline-block;\n  white-space: nowrap;\n  cursor: pointer;\n  vertical-align: top; }\n\n.biojs_msa_seqblock {\n  cursor: move; }\n\n.biojs_msa_layer {\n  display: block;\n  white-space: nowrap; }\n\n.biojs_msa_labelblock::-webkit-scrollbar, .biojs_msa_header::-webkit-scrollbar {\n  -webkit-appearance: none;\n  width: 7px;\n  height: 7px; }\n\n.biojs_msa_labelblock::-webkit-scrollbar-thumb, .biojs_msa_header::-webkit-scrollbar-thumb {\n  border-radius: 4px;\n  background-color: rgba(0, 0, 0, 0.5);\n  box-shadow: 0 0 1px rgba(255, 255, 255, 0.5); }\n\n.biojs_msa_marker {\n  color: grey;\n  white-space: nowrap;\n  cursor: pointer; }\n\n.biojs_msa_marker span {\n  text-align: center; }\n\n.biojs_msa_menubar .biojs_msa_menubar_alink {\n  background: #3498db;\n  background-image: -webkit-linear-gradient(top, #3498db, #2980b9);\n  background-image: -moz-linear-gradient(top, #3498db, #2980b9);\n  background-image: -ms-linear-gradient(top, #3498db, #2980b9);\n  background-image: -o-linear-gradient(top, #3498db, #2980b9);\n  background-image: linear-gradient(to bottom, #3498db, #2980b9);\n  -webkit-border-radius: 28;\n  -moz-border-radius: 28;\n  border-radius: 28px;\n  font-family: Arial;\n  color: #ffffff;\n  padding: 3px 10px 3px 10px;\n  margin-left: 10px;\n  text-decoration: none; }\n\n.biojs_msa_menubar .biojs_msa_menubar_alink:hover {\n  cursor: pointer; }\n\n/* jquery dropdown CSS */\n.dropdown {\n  position: absolute;\n  z-index: 9999999;\n  display: none; }\n\n.dropdown .dropdown-menu,\n.dropdown .dropdown-panel {\n  min-width: 160px;\n  max-width: 360px;\n  list-style: none;\n  background: #FFF;\n  border: solid 1px #DDD;\n  border: solid 1px rgba(0, 0, 0, 0.2);\n  border-radius: 6px;\n  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n  overflow: visible;\n  padding: 4px 0;\n  margin: 0; }\n\n.dropdown .dropdown-panel {\n  padding: 10px; }\n\n.dropdown.dropdown-scroll .dropdown-menu,\n.dropdown.dropdown-scroll .dropdown-panel {\n  max-height: 358px;\n  overflow: auto; }\n\n.dropdown .dropdown-menu LI {\n  list-style: none;\n  padding: 0 0;\n  margin: 0;\n  line-height: 18px; }\n\n.dropdown .dropdown-menu LI,\n.dropdown .dropdown-menu LABEL {\n  display: block;\n  color: #555;\n  text-decoration: none;\n  line-height: 18px;\n  padding: 3px 15px;\n  white-space: nowrap; }\n\n.dropdown .dropdown-menu LI:hover,\n.dropdown .dropdown-menu LABEL:hover {\n  background-color: #08C;\n  color: #FFF;\n  cursor: pointer; }\n\n.dropdown .dropdown-menu .dropdown-divider {\n  font-size: 1px;\n  border-top: solid 1px #E5E5E5;\n  padding: 0;\n  margin: 5px 0; }\n"; (require("/home/travis/build/greenify/biojs-vis-msa/node_modules/cssify"))(css); module.exports = css;
+},{"/home/travis/build/greenify/biojs-vis-msa/node_modules/cssify":48}],2:[function(require,module,exports){
+module.exports = require("./src/index");
+
+},{"./src/index":72}],3:[function(require,module,exports){
+var _ = require('underscore');
+var viewType = require("backbone-viewj");
+var pluginator;
+
+module.exports = pluginator = viewType.extend({
+  renderSubviews: function() {
+    var oldEl = this.el;
+    var el = document.createElement("div");
+    this.setElement(el);
+    var frag = document.createDocumentFragment();
+    if (oldEl.parentNode != null) {
+      oldEl.parentNode.replaceChild(this.el, oldEl);
+    }
+    var views = this._views();
+    var viewsSorted = _.sortBy(views, function(el) {
+      return el.ordering;
+    });
+    var view, node;
+    for (var i = 0; i <  viewsSorted.length; i++) {
+      view = viewsSorted[i];
+      view.render();
+      node = view.el;
+      if (node != null) {
+        frag.appendChild(node);
+      }
+    }
+    el.appendChild(frag);
+    return el;
+  },
+  addView: function(key, view) {
+    var views = this._views();
+    if (view == null) {
+      throw "Invalid plugin. ";
+    }
+    if (view.ordering == null) {
+      view.ordering = key;
+    }
+    return views[key] = view;
+  },
+  removeViews: function() {
+    var el, key;
+    var views = this._views();
+    for (key in views) {
+      el = views[key];
+      el.undelegateEvents();
+      el.unbind();
+      if (el.removeViews != null) {
+        el.removeViews();
+      }
+      el.remove();
+    }
+    return this.views = {};
+  },
+  removeView: function(key) {
+    var views = this._views();
+    views[key].remove();
+    return delete views[key];
+  },
+  getView: function(key) {
+    var views = this._views();
+    return views[key];
+  },
+  remove: function() {
+    this.removeViews();
+    return viewType.prototype.remove.apply(this);
+  },
+  _views: function() {
+    if (this.views == null) {
+      this.views = {};
+    }
+    return this.views;
+  }
+});
+
+},{"backbone-viewj":10,"underscore":59}],4:[function(require,module,exports){
+//     Backbone.js 1.1.2
+
+//     (c) 2010-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+//     Backbone may be freely distributed under the MIT license.
+//     For all details and documentation:
+//     http://backbonejs.org
+
+var Events = require("backbone-events-standalone");
+var extend = require("backbone-extend-standalone");
+var _ = require("underscore");
+var Model = require("./model");
+
+// Create local references to array methods we'll want to use later.
+var array = [];
+var slice = array.slice;
+
+// Backbone.Collection
+// -------------------
+
+// If models tend to represent a single row of data, a Backbone Collection is
+// more analogous to a table full of data ... or a small slice or page of that
+// table, or a collection of rows that belong together for a particular reason
+// -- all of the messages in this particular folder, all of the documents
+// belonging to this particular author, and so on. Collections maintain
+// indexes of their models, both in order, and for lookup by `id`.
+
+// Create a new **Collection**, perhaps to contain a specific type of `model`.
+// If a `comparator` is specified, the Collection will maintain
+// its models in sort order, as they're added and removed.
+var Collection = function(models, options) {
+  options || (options = {});
+  if (options.model) this.model = options.model;
+  if (options.comparator !== void 0) this.comparator = options.comparator;
+  this._reset();
+  this.initialize.apply(this, arguments);
+  if (models) this.reset(models, _.extend({silent: true}, options));
+};
+
+// Default options for `Collection#set`.
+var setOptions = {add: true, remove: true, merge: true};
+var addOptions = {add: true, remove: false};
+
+// Define the Collection's inheritable methods.
+_.extend(Collection.prototype, Events, {
+
+  // The default model for a collection is just a **Backbone.Model**.
+  // This should be overridden in most cases.
+  model: Model,
+
+  // Initialize is an empty function by default. Override it with your own
+  // initialization logic.
+  initialize: function(){},
+
+    // The JSON representation of a Collection is an array of the
+    // models' attributes.
+  toJSON: function(options) {
+    return this.map(function(model){ return model.toJSON(options); });
+  },
+
+    // Proxy `Backbone.sync` by default.
+  sync: function() {
+    return Backbone.sync.apply(this, arguments);
+  },
+
+    // Add a model, or list of models to the set.
+  add: function(models, options) {
+    return this.set(models, _.extend({merge: false}, options, addOptions));
+  },
+
+    // Remove a model, or a list of models from the set.
+  remove: function(models, options) {
+    var singular = !_.isArray(models);
+    models = singular ? [models] : _.clone(models);
+    options || (options = {});
+    for (var i = 0, length = models.length; i < length; i++) {
+      var model = models[i] = this.get(models[i]);
+      if (!model) continue;
+      var id = this.modelId(model.attributes);
+      if (id != null) delete this._byId[id];
+      delete this._byId[model.cid];
+      var index = this.indexOf(model);
+      this.models.splice(index, 1);
+      this.length--;
+      if (!options.silent) {
+        options.index = index;
+        model.trigger('remove', model, this, options);
+      }
+      this._removeReference(model, options);
+    }
+    return singular ? models[0] : models;
+  },
+
+    // Update a collection by `set`-ing a new list of models, adding new ones,
+    // removing models that are no longer present, and merging models that
+    // already exist in the collection, as necessary. Similar to **Model#set**,
+    // the core operation for updating the data contained by the collection.
+  set: function(models, options) {
+    options = _.defaults({}, options, setOptions);
+    if (options.parse) models = this.parse(models, options);
+    var singular = !_.isArray(models);
+    models = singular ? (models ? [models] : []) : models.slice();
+    var id, model, attrs, existing, sort;
+    var at = options.at;
+    var sortable = this.comparator && (at == null) && options.sort !== false;
+    var sortAttr = _.isString(this.comparator) ? this.comparator : null;
+    var toAdd = [], toRemove = [], modelMap = {};
+    var add = options.add, merge = options.merge, remove = options.remove;
+    var order = !sortable && add && remove ? [] : false;
+
+    // Turn bare objects into model references, and prevent invalid models
+    // from being added.
+    for (var i = 0, length = models.length; i < length; i++) {
+      attrs = models[i];
+
+      // If a duplicate is found, prevent it from being added and
+      // optionally merge it into the existing model.
+      if (existing = this.get(attrs)) {
+        if (remove) modelMap[existing.cid] = true;
+        if (merge && attrs !== existing) {
+          attrs = this._isModel(attrs) ? attrs.attributes : attrs;
+          if (options.parse) attrs = existing.parse(attrs, options);
+          existing.set(attrs, options);
+          if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
+        }
+        models[i] = existing;
+
+        // If this is a new, valid model, push it to the `toAdd` list.
+      } else if (add) {
+        model = models[i] = this._prepareModel(attrs, options);
+        if (!model) continue;
+        toAdd.push(model);
+        this._addReference(model, options);
+      }
+
+      // Do not add multiple models with the same `id`.
+      model = existing || model;
+      if (!model) continue;
+      id = this.modelId(model.attributes);
+      if (order && (model.isNew() || !modelMap[id])) order.push(model);
+      modelMap[id] = true;
+    }
+
+    // Remove nonexistent models if appropriate.
+    if (remove) {
+      for (var i = 0, length = this.length; i < length; i++) {
+        if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
+      }
+      if (toRemove.length) this.remove(toRemove, options);
+    }
+
+    // See if sorting is needed, update `length` and splice in new models.
+    if (toAdd.length || (order && order.length)) {
+      if (sortable) sort = true;
+      this.length += toAdd.length;
+      if (at != null) {
+        for (var i = 0, length = toAdd.length; i < length; i++) {
+          this.models.splice(at + i, 0, toAdd[i]);
+        }
+      } else {
+        if (order) this.models.length = 0;
+        var orderedModels = order || toAdd;
+        for (var i = 0, length = orderedModels.length; i < length; i++) {
+          this.models.push(orderedModels[i]);
+        }
+      }
+    }
+
+    // Silently sort the collection if appropriate.
+    if (sort) this.sort({silent: true});
+
+    // Unless silenced, it's time to fire all appropriate add/sort events.
+    if (!options.silent) {
+      var addOpts = at != null ? _.clone(options) : options;
+      for (var i = 0, length = toAdd.length; i < length; i++) {
+        if (at != null) addOpts.index = at + i;
+        (model = toAdd[i]).trigger('add', model, this, addOpts);
+      }
+      if (sort || (order && order.length)) this.trigger('sort', this, options);
+    }
+
+    // Return the added (or merged) model (or models).
+    return singular ? models[0] : models;
+  },
+
+    // When you have more items than you want to add or remove individually,
+    // you can reset the entire set with a new list of models, without firing
+    // any granular `add` or `remove` events. Fires `reset` when finished.
+    // Useful for bulk operations and optimizations.
+  reset: function(models, options) {
+    options || (options = {});
+    for (var i = 0, length = this.models.length; i < length; i++) {
+      this._removeReference(this.models[i], options);
+    }
+    options.previousModels = this.models;
+    this._reset();
+    models = this.add(models, _.extend({silent: true}, options));
+    if (!options.silent) this.trigger('reset', this, options);
+    return models;
+  },
+
+    // Add a model to the end of the collection.
+  push: function(model, options) {
+    return this.add(model, _.extend({at: this.length}, options));
+  },
+
+    // Remove a model from the end of the collection.
+  pop: function(options) {
+    var model = this.at(this.length - 1);
+    this.remove(model, options);
+    return model;
+  },
+
+    // Add a model to the beginning of the collection.
+  unshift: function(model, options) {
+    return this.add(model, _.extend({at: 0}, options));
+  },
+
+    // Remove a model from the beginning of the collection.
+  shift: function(options) {
+    var model = this.at(0);
+    this.remove(model, options);
+    return model;
+  },
+
+    // Slice out a sub-array of models from the collection.
+  slice: function() {
+    return slice.apply(this.models, arguments);
+  },
+
+    // Get a model from the set by id.
+  get: function(obj) {
+    if (obj == null) return void 0;
+    var id = this.modelId(this._isModel(obj) ? obj.attributes : obj);
+    return this._byId[obj] || this._byId[id] || this._byId[obj.cid];
+  },
+
+    // Get the model at the given index.
+  at: function(index) {
+    if (index < 0) index += this.length;
+    return this.models[index];
+  },
+
+    // Return models with matching attributes. Useful for simple cases of
+    // `filter`.
+  where: function(attrs, first) {
+    if (_.isEmpty(attrs)) return first ? void 0 : [];
+    return this[first ? 'find' : 'filter'](function(model) {
+      for (var key in attrs) {
+        if (attrs[key] !== model.get(key)) return false;
+      }
+      return true;
+    });
+  },
+
+    // Return the first model with matching attributes. Useful for simple cases
+    // of `find`.
+  findWhere: function(attrs) {
+    return this.where(attrs, true);
+  },
+
+    // Force the collection to re-sort itself. You don't need to call this under
+    // normal circumstances, as the set will maintain sort order as each item
+    // is added.
+  sort: function(options) {
+    if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
+    options || (options = {});
+
+    // Run sort based on type of `comparator`.
+    if (_.isString(this.comparator) || this.comparator.length === 1) {
+      this.models = this.sortBy(this.comparator, this);
+    } else {
+      this.models.sort(_.bind(this.comparator, this));
+    }
+
+    if (!options.silent) this.trigger('sort', this, options);
+    return this;
+  },
+
+    // Pluck an attribute from each model in the collection.
+  pluck: function(attr) {
+    return _.invoke(this.models, 'get', attr);
+  },
+
+    // Fetch the default set of models for this collection, resetting the
+    // collection when they arrive. If `reset: true` is passed, the response
+    // data will be passed through the `reset` method instead of `set`.
+  fetch: function(options) {
+    options = options ? _.clone(options) : {};
+    if (options.parse === void 0) options.parse = true;
+    var success = options.success;
+    var collection = this;
+    options.success = function(resp) {
+      var method = options.reset ? 'reset' : 'set';
+      collection[method](resp, options);
+      if (success) success(collection, resp, options);
+      collection.trigger('sync', collection, resp, options);
+    };
+    wrapError(this, options);
+    return this.sync('read', this, options);
+  },
+
+    // Create a new instance of a model in this collection. Add the model to the
+    // collection immediately, unless `wait: true` is passed, in which case we
+    // wait for the server to agree.
+  create: function(model, options) {
+    options = options ? _.clone(options) : {};
+    if (!(model = this._prepareModel(model, options))) return false;
+    if (!options.wait) this.add(model, options);
+    var collection = this;
+    var success = options.success;
+    options.success = function(model, resp) {
+      if (options.wait) collection.add(model, options);
+      if (success) success(model, resp, options);
+    };
+    model.save(null, options);
+    return model;
+  },
+
+    // **parse** converts a response into a list of models to be added to the
+    // collection. The default implementation is just to pass it through.
+  parse: function(resp, options) {
+    return resp;
+  },
+
+    // Create a new collection with an identical list of models as this one.
+  clone: function() {
+    return new this.constructor(this.models, {
+      model: this.model,
+      comparator: this.comparator
+    });
+  },
+
+    // Define how to uniquely identify models in the collection.
+  modelId: function (attrs) {
+    return attrs[this.model.prototype.idAttribute || 'id'];
+  },
+
+    // Private method to reset all internal state. Called when the collection
+    // is first initialized or reset.
+  _reset: function() {
+    this.length = 0;
+    this.models = [];
+    this._byId  = {};
+  },
+
+    // Prepare a hash of attributes (or other model) to be added to this
+    // collection.
+  _prepareModel: function(attrs, options) {
+    if (this._isModel(attrs)) {
+      if (!attrs.collection) attrs.collection = this;
+      return attrs;
+    }
+    options = options ? _.clone(options) : {};
+    options.collection = this;
+    var model = new this.model(attrs, options);
+    if (!model.validationError) return model;
+    this.trigger('invalid', this, model.validationError, options);
+    return false;
+  },
+
+    // Method for checking whether an object should be considered a model for
+    // the purposes of adding to the collection.
+  _isModel: function (model) {
+    return model instanceof Model;
+  },
+
+    // Internal method to create a model's ties to a collection.
+  _addReference: function(model, options) {
+    this._byId[model.cid] = model;
+    var id = this.modelId(model.attributes);
+    if (id != null) this._byId[id] = model;
+    model.on('all', this._onModelEvent, this);
+  },
+
+    // Internal method to sever a model's ties to a collection.
+  _removeReference: function(model, options) {
+    if (this === model.collection) delete model.collection;
+    model.off('all', this._onModelEvent, this);
+  },
+
+    // Internal method called every time a model in the set fires an event.
+    // Sets need to update their indexes when models change ids. All other
+    // events simply proxy through. "add" and "remove" events that originate
+    // in other collections are ignored.
+  _onModelEvent: function(event, model, collection, options) {
+    if ((event === 'add' || event === 'remove') && collection !== this) return;
+    if (event === 'destroy') this.remove(model, options);
+    if (event === 'change') {
+      var prevId = this.modelId(model.previousAttributes());
+      var id = this.modelId(model.attributes);
+      if (prevId !== id) {
+        if (prevId != null) delete this._byId[prevId];
+        if (id != null) this._byId[id] = model;
+      }
+    }
+    this.trigger.apply(this, arguments);
+  }
+
+});
+
+// Underscore methods that we want to implement on the Collection.
+// 90% of the core usefulness of Backbone Collections is actually implemented
+// right here:
+var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
+    'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
+    'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
+    'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
+    'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle',
+    'lastIndexOf', 'isEmpty', 'chain', 'sample', 'partition'];
+
+// Mix in each Underscore method as a proxy to `Collection#models`.
+_.each(methods, function(method) {
+  if (!_[method]) return;
+  Collection.prototype[method] = function() {
+    var args = slice.call(arguments);
+    args.unshift(this.models);
+    return _[method].apply(_, args);
+  };
+});
+
+// Underscore methods that take a property name as an argument.
+var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy'];
+
+// Use attributes instead of properties.
+_.each(attributeMethods, function(method) {
+  if (!_[method]) return;
+  Collection.prototype[method] = function(value, context) {
+    var iterator = _.isFunction(value) ? value : function(model) {
+      return model.get(value);
+    };
+    return _[method](this.models, iterator, context);
+  };
+});
+
+// setup inheritance
+Collection.extend = extend;
+module.exports = Collection;
+
+},{"./model":6,"backbone-events-standalone":8,"backbone-extend-standalone":9,"underscore":59}],5:[function(require,module,exports){
+module.exports.Model = require("./model");
+module.exports.Collection = require("./collection");
+module.exports.Events = require("backbone-events-standalone");
+module.exports.extend = require("backbone-extend-standalone");
+
+},{"./collection":4,"./model":6,"backbone-events-standalone":8,"backbone-extend-standalone":9}],6:[function(require,module,exports){
+//     Backbone.js 1.1.2
+
+//     (c) 2010-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+//     Backbone may be freely distributed under the MIT license.
+//     For all details and documentation:
+//     http://backbonejs.org
+
+var Events = require("backbone-events-standalone");
+var extend = require("backbone-extend-standalone");
+var _ = require("underscore");
+
+// Backbone.Model
+// --------------
+
+// Backbone **Models** are the basic data object in the framework --
+// frequently representing a row in a table in a database on your server.
+// A discrete chunk of data and a bunch of useful, related methods for
+// performing computations and transformations on that data.
+
+// Create a new model with the specified attributes. A client id (`cid`)
+// is automatically generated and assigned for you.
+var Model = function(attributes, options) {
+  var attrs = attributes || {};
+  options || (options = {});
+  this.cid = _.uniqueId('c');
+  this.attributes = {};
+  if (options.collection) this.collection = options.collection;
+  if (options.parse) attrs = this.parse(attrs, options) || {};
+  attrs = _.defaults({}, attrs, _.result(this, 'defaults'));
+  this.set(attrs, options);
+  this.changed = {};
+  this.initialize.apply(this, arguments);
+};
+
+// Attach all inheritable methods to the Model prototype.
+_.extend(Model.prototype, Events, {
+
+  // A hash of attributes whose current and previous value differ.
+  changed: null,
+
+  // The value returned during the last failed validation.
+  validationError: null,
+
+    // The default name for the JSON `id` attribute is `"id"`. MongoDB and
+    // CouchDB users may want to set this to `"_id"`.
+  idAttribute: 'id',
+
+    // Initialize is an empty function by default. Override it with your own
+    // initialization logic.
+  initialize: function(){},
+
+    // Return a copy of the model's `attributes` object.
+  toJSON: function(options) {
+    return _.clone(this.attributes);
+  },
+
+    // Proxy `Backbone.sync` by default -- but override this if you need
+    // custom syncing semantics for *this* particular model.
+  sync: function() {
+    return Backbone.sync.apply(this, arguments);
+  },
+
+    // Get the value of an attribute.
+  get: function(attr) {
+    return this.attributes[attr];
+  },
+
+    // Get the HTML-escaped value of an attribute.
+  escape: function(attr) {
+    return _.escape(this.get(attr));
+  },
+
+    // Returns `true` if the attribute contains a value that is not null
+    // or undefined.
+  has: function(attr) {
+    return this.get(attr) != null;
+  },
+
+    // Set a hash of model attributes on the object, firing `"change"`. This is
+    // the core primitive operation of a model, updating the data and notifying
+    // anyone who needs to know about the change in state. The heart of the beast.
+  set: function(key, val, options) {
+    var attr, attrs, unset, changes, silent, changing, prev, current;
+    if (key == null) return this;
+
+    // Handle both `"key", value` and `{key: value}` -style arguments.
+    if (typeof key === 'object') {
+      attrs = key;
+      options = val;
+    } else {
+      (attrs = {})[key] = val;
+    }
+
+    options || (options = {});
+
+    // Run validation.
+    if (!this._validate(attrs, options)) return false;
+
+    // Extract attributes and options.
+    unset           = options.unset;
+    silent          = options.silent;
+    changes         = [];
+    changing        = this._changing;
+    this._changing  = true;
+
+    if (!changing) {
+      this._previousAttributes = _.clone(this.attributes);
+      this.changed = {};
+    }
+    current = this.attributes, prev = this._previousAttributes;
+
+    // Check for changes of `id`.
+    if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
+
+    // For each `set` attribute, update or delete the current value.
+    for (attr in attrs) {
+      val = attrs[attr];
+      if (!_.isEqual(current[attr], val)) changes.push(attr);
+      if (!_.isEqual(prev[attr], val)) {
+        this.changed[attr] = val;
+      } else {
+        delete this.changed[attr];
+      }
+      unset ? delete current[attr] : current[attr] = val;
+    }
+
+    // Trigger all relevant attribute changes.
+    if (!silent) {
+      if (changes.length) this._pending = options;
+      for (var i = 0, length = changes.length; i < length; i++) {
+        this.trigger('change:' + changes[i], this, current[changes[i]], options);
+      }
+    }
+
+    // You might be wondering why there's a `while` loop here. Changes can
+    // be recursively nested within `"change"` events.
+    if (changing) return this;
+    if (!silent) {
+      while (this._pending) {
+        options = this._pending;
+        this._pending = false;
+        this.trigger('change', this, options);
+      }
+    }
+    this._pending = false;
+    this._changing = false;
+    return this;
+  },
+
+    // Remove an attribute from the model, firing `"change"`. `unset` is a noop
+    // if the attribute doesn't exist.
+  unset: function(attr, options) {
+    return this.set(attr, void 0, _.extend({}, options, {unset: true}));
+  },
+
+    // Clear all attributes on the model, firing `"change"`.
+  clear: function(options) {
+    var attrs = {};
+    for (var key in this.attributes) attrs[key] = void 0;
+    return this.set(attrs, _.extend({}, options, {unset: true}));
+  },
+
+    // Determine if the model has changed since the last `"change"` event.
+    // If you specify an attribute name, determine if that attribute has changed.
+  hasChanged: function(attr) {
+    if (attr == null) return !_.isEmpty(this.changed);
+    return _.has(this.changed, attr);
+  },
+
+    // Return an object containing all the attributes that have changed, or
+    // false if there are no changed attributes. Useful for determining what
+    // parts of a view need to be updated and/or what attributes need to be
+    // persisted to the server. Unset attributes will be set to undefined.
+    // You can also pass an attributes object to diff against the model,
+    // determining if there *would be* a change.
+  changedAttributes: function(diff) {
+    if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
+    var val, changed = false;
+    var old = this._changing ? this._previousAttributes : this.attributes;
+    for (var attr in diff) {
+      if (_.isEqual(old[attr], (val = diff[attr]))) continue;
+      (changed || (changed = {}))[attr] = val;
+    }
+    return changed;
+  },
+
+    // Get the previous value of an attribute, recorded at the time the last
+    // `"change"` event was fired.
+  previous: function(attr) {
+    if (attr == null || !this._previousAttributes) return null;
+    return this._previousAttributes[attr];
+  },
+
+    // Get all of the attributes of the model at the time of the previous
+    // `"change"` event.
+  previousAttributes: function() {
+    return _.clone(this._previousAttributes);
+  },
+
+    // Fetch the model from the server. If the server's representation of the
+    // model differs from its current attributes, they will be overridden,
+    // triggering a `"change"` event.
+  fetch: function(options) {
+    options = options ? _.clone(options) : {};
+    if (options.parse === void 0) options.parse = true;
+    var model = this;
+    var success = options.success;
+    options.success = function(resp) {
+      if (!model.set(model.parse(resp, options), options)) return false;
+      if (success) success(model, resp, options);
+      model.trigger('sync', model, resp, options);
+    };
+    wrapError(this, options);
+    return this.sync('read', this, options);
+  },
+
+    // Set a hash of model attributes, and sync the model to the server.
+    // If the server returns an attributes hash that differs, the model's
+    // state will be `set` again.
+  save: function(key, val, options) {
+    var attrs, method, xhr, attributes = this.attributes;
+
+    // Handle both `"key", value` and `{key: value}` -style arguments.
+    if (key == null || typeof key === 'object') {
+      attrs = key;
+      options = val;
+    } else {
+      (attrs = {})[key] = val;
+    }
+
+    options = _.extend({validate: true}, options);
+
+    // If we're not waiting and attributes exist, save acts as
+    // `set(attr).save(null, opts)` with validation. Otherwise, check if
+    // the model will be valid when the attributes, if any, are set.
+    if (attrs && !options.wait) {
+      if (!this.set(attrs, options)) return false;
+    } else {
+      if (!this._validate(attrs, options)) return false;
+    }
+
+    // Set temporary attributes if `{wait: true}`.
+    if (attrs && options.wait) {
+      this.attributes = _.extend({}, attributes, attrs);
+    }
+
+    // After a successful server-side save, the client is (optionally)
+    // updated with the server-side state.
+    if (options.parse === void 0) options.parse = true;
+    var model = this;
+    var success = options.success;
+    options.success = function(resp) {
+      // Ensure attributes are restored during synchronous saves.
+      model.attributes = attributes;
+      var serverAttrs = model.parse(resp, options);
+      if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
+      if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
+        return false;
+      }
+      if (success) success(model, resp, options);
+      model.trigger('sync', model, resp, options);
+    };
+    wrapError(this, options);
+
+    method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
+    if (method === 'patch' && !options.attrs) options.attrs = attrs;
+    xhr = this.sync(method, this, options);
+
+    // Restore attributes.
+    if (attrs && options.wait) this.attributes = attributes;
+
+    return xhr;
+  },
+
+    // Destroy this model on the server if it was already persisted.
+    // Optimistically removes the model from its collection, if it has one.
+    // If `wait: true` is passed, waits for the server to respond before removal.
+  destroy: function(options) {
+    options = options ? _.clone(options) : {};
+    var model = this;
+    var success = options.success;
+
+    var destroy = function() {
+      model.stopListening();
+      model.trigger('destroy', model, model.collection, options);
+    };
+
+    options.success = function(resp) {
+      if (options.wait || model.isNew()) destroy();
+      if (success) success(model, resp, options);
+      if (!model.isNew()) model.trigger('sync', model, resp, options);
+    };
+
+    if (this.isNew()) {
+      options.success();
+      return false;
+    }
+    wrapError(this, options);
+
+    var xhr = this.sync('delete', this, options);
+    if (!options.wait) destroy();
+    return xhr;
+  },
+
+    // Default URL for the model's representation on the server -- if you're
+    // using Backbone's restful methods, override this to change the endpoint
+    // that will be called.
+  url: function() {
+    var base =
+      _.result(this, 'urlRoot') ||
+      _.result(this.collection, 'url') ||
+      urlError();
+    if (this.isNew()) return base;
+    return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id);
+  },
+
+    // **parse** converts a response into the hash of attributes to be `set` on
+    // the model. The default implementation is just to pass the response along.
+  parse: function(resp, options) {
+    return resp;
+  },
+
+    // Create a new model with identical attributes to this one.
+  clone: function() {
+    return new this.constructor(this.attributes);
+  },
+
+    // A model is new if it has never been saved to the server, and lacks an id.
+  isNew: function() {
+    return !this.has(this.idAttribute);
+  },
+
+    // Check if the model is currently in a valid state.
+  isValid: function(options) {
+    return this._validate({}, _.extend(options || {}, { validate: true }));
+  },
+
+    // Run validation against the next complete set of model attributes,
+    // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
+  _validate: function(attrs, options) {
+    if (!options.validate || !this.validate) return true;
+    attrs = _.extend({}, this.attributes, attrs);
+    var error = this.validationError = this.validate(attrs, options) || null;
+    if (!error) return true;
+    this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
+    return false;
+  }
+
+});
+
+// Underscore methods that we want to implement on the Model.
+var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit', 'chain', 'isEmpty'];
+
+// Mix in each Underscore method as a proxy to `Model#attributes`.
+_.each(modelMethods, function(method) {
+  if (!_[method]) return;
+  Model.prototype[method] = function() {
+    var args = slice.call(arguments);
+    args.unshift(this.attributes);
+    return _[method].apply(_, args);
+  };
+});
+
+// setup inheritance
+Model.extend = extend;
+module.exports = Model;
+
+},{"backbone-events-standalone":8,"backbone-extend-standalone":9,"underscore":59}],7:[function(require,module,exports){
+/**
+ * Standalone extraction of Backbone.Events, no external dependency required.
+ * Degrades nicely when Backone/underscore are already available in the current
+ * global context.
+ *
+ * Note that docs suggest to use underscore's `_.extend()` method to add Events
+ * support to some given object. A `mixin()` method has been added to the Events
+ * prototype to avoid using underscore for that sole purpose:
+ *
+ *     var myEventEmitter = BackboneEvents.mixin({});
+ *
+ * Or for a function constructor:
+ *
+ *     function MyConstructor(){}
+ *     MyConstructor.prototype.foo = function(){}
+ *     BackboneEvents.mixin(MyConstructor.prototype);
+ *
+ * (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc.
+ * (c) 2013 Nicolas Perriault
+ */
+/* global exports:true, define, module */
+(function() {
+  var root = this,
+      breaker = {},
+      nativeForEach = Array.prototype.forEach,
+      hasOwnProperty = Object.prototype.hasOwnProperty,
+      slice = Array.prototype.slice,
+      idCounter = 0;
+
+  // Returns a partial implementation matching the minimal API subset required
+  // by Backbone.Events
+  function miniscore() {
+    return {
+      keys: Object.keys || function (obj) {
+        if (typeof obj !== "object" && typeof obj !== "function" || obj === null) {
+          throw new TypeError("keys() called on a non-object");
+        }
+        var key, keys = [];
+        for (key in obj) {
+          if (obj.hasOwnProperty(key)) {
+            keys[keys.length] = key;
+          }
+        }
+        return keys;
+      },
+
+      uniqueId: function(prefix) {
+        var id = ++idCounter + '';
+        return prefix ? prefix + id : id;
+      },
+
+      has: function(obj, key) {
+        return hasOwnProperty.call(obj, key);
+      },
+
+      each: function(obj, iterator, context) {
+        if (obj == null) return;
+        if (nativeForEach && obj.forEach === nativeForEach) {
+          obj.forEach(iterator, context);
+        } else if (obj.length === +obj.length) {
+          for (var i = 0, l = obj.length; i < l; i++) {
+            if (iterator.call(context, obj[i], i, obj) === breaker) return;
+          }
+        } else {
+          for (var key in obj) {
+            if (this.has(obj, key)) {
+              if (iterator.call(context, obj[key], key, obj) === breaker) return;
+            }
+          }
+        }
+      },
+
+      once: function(func) {
+        var ran = false, memo;
+        return function() {
+          if (ran) return memo;
+          ran = true;
+          memo = func.apply(this, arguments);
+          func = null;
+          return memo;
+        };
+      }
+    };
+  }
+
+  var _ = miniscore(), Events;
+
+  // Backbone.Events
+  // ---------------
+
+  // A module that can be mixed in to *any object* in order to provide it with
+  // custom events. You may bind with `on` or remove with `off` callback
+  // functions to an event; `trigger`-ing an event fires all callbacks in
+  // succession.
+  //
+  //     var object = {};
+  //     _.extend(object, Backbone.Events);
+  //     object.on('expand', function(){ alert('expanded'); });
+  //     object.trigger('expand');
+  //
+  Events = {
+
+    // Bind an event to a `callback` function. Passing `"all"` will bind
+    // the callback to all events fired.
+    on: function(name, callback, context) {
+      if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
+      this._events || (this._events = {});
+      var events = this._events[name] || (this._events[name] = []);
+      events.push({callback: callback, context: context, ctx: context || this});
+      return this;
+    },
+
+    // Bind an event to only be triggered a single time. After the first time
+    // the callback is invoked, it will be removed.
+    once: function(name, callback, context) {
+      if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
+      var self = this;
+      var once = _.once(function() {
+        self.off(name, once);
+        callback.apply(this, arguments);
+      });
+      once._callback = callback;
+      return this.on(name, once, context);
+    },
+
+    // Remove one or many callbacks. If `context` is null, removes all
+    // callbacks with that function. If `callback` is null, removes all
+    // callbacks for the event. If `name` is null, removes all bound
+    // callbacks for all events.
+    off: function(name, callback, context) {
+      var retain, ev, events, names, i, l, j, k;
+      if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
+      if (!name && !callback && !context) {
+        this._events = {};
+        return this;
+      }
+
+      names = name ? [name] : _.keys(this._events);
+      for (i = 0, l = names.length; i < l; i++) {
+        name = names[i];
+        if (events = this._events[name]) {
+          this._events[name] = retain = [];
+          if (callback || context) {
+            for (j = 0, k = events.length; j < k; j++) {
+              ev = events[j];
+              if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
+                  (context && context !== ev.context)) {
+                retain.push(ev);
+              }
+            }
+          }
+          if (!retain.length) delete this._events[name];
+        }
+      }
+
+      return this;
+    },
+
+    // Trigger one or many events, firing all bound callbacks. Callbacks are
+    // passed the same arguments as `trigger` is, apart from the event name
+    // (unless you're listening on `"all"`, which will cause your callback to
+    // receive the true name of the event as the first argument).
+    trigger: function(name) {
+      if (!this._events) return this;
+      var args = slice.call(arguments, 1);
+      if (!eventsApi(this, 'trigger', name, args)) return this;
+      var events = this._events[name];
+      var allEvents = this._events.all;
+      if (events) triggerEvents(events, args);
+      if (allEvents) triggerEvents(allEvents, arguments);
+      return this;
+    },
+
+    // Tell this object to stop listening to either specific events ... or
+    // to every object it's currently listening to.
+    stopListening: function(obj, name, callback) {
+      var listeners = this._listeners;
+      if (!listeners) return this;
+      var deleteListener = !name && !callback;
+      if (typeof name === 'object') callback = this;
+      if (obj) (listeners = {})[obj._listenerId] = obj;
+      for (var id in listeners) {
+        listeners[id].off(name, callback, this);
+        if (deleteListener) delete this._listeners[id];
+      }
+      return this;
+    }
+
+  };
+
+  // Regular expression used to split event strings.
+  var eventSplitter = /\s+/;
+
+  // Implement fancy features of the Events API such as multiple event
+  // names `"change blur"` and jQuery-style event maps `{change: action}`
+  // in terms of the existing API.
+  var eventsApi = function(obj, action, name, rest) {
+    if (!name) return true;
+
+    // Handle event maps.
+    if (typeof name === 'object') {
+      for (var key in name) {
+        obj[action].apply(obj, [key, name[key]].concat(rest));
+      }
+      return false;
+    }
+
+    // Handle space separated event names.
+    if (eventSplitter.test(name)) {
+      var names = name.split(eventSplitter);
+      for (var i = 0, l = names.length; i < l; i++) {
+        obj[action].apply(obj, [names[i]].concat(rest));
+      }
+      return false;
+    }
+
+    return true;
+  };
+
+  // A difficult-to-believe, but optimized internal dispatch function for
+  // triggering events. Tries to keep the usual cases speedy (most internal
+  // Backbone events have 3 arguments).
+  var triggerEvents = function(events, args) {
+    var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
+    switch (args.length) {
+      case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
+      case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
+      case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
+      case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
+      default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
+    }
+  };
+
+  var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
+
+  // Inversion-of-control versions of `on` and `once`. Tell *this* object to
+  // listen to an event in another object ... keeping track of what it's
+  // listening to.
+  _.each(listenMethods, function(implementation, method) {
+    Events[method] = function(obj, name, callback) {
+      var listeners = this._listeners || (this._listeners = {});
+      var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
+      listeners[id] = obj;
+      if (typeof name === 'object') callback = this;
+      obj[implementation](name, callback, this);
+      return this;
+    };
+  });
+
+  // Aliases for backwards compatibility.
+  Events.bind   = Events.on;
+  Events.unbind = Events.off;
+
+  // Mixin utility
+  Events.mixin = function(proto) {
+    var exports = ['on', 'once', 'off', 'trigger', 'stopListening', 'listenTo',
+                   'listenToOnce', 'bind', 'unbind'];
+    _.each(exports, function(name) {
+      proto[name] = this[name];
+    }, this);
+    return proto;
+  };
+
+  // Export Events as BackboneEvents depending on current context
+  if (typeof define === "function") {
+    define(function() {
+      return Events;
+    });
+  } else if (typeof exports !== 'undefined') {
+    if (typeof module !== 'undefined' && module.exports) {
+      exports = module.exports = Events;
+    }
+    exports.BackboneEvents = Events;
+  } else {
+    root.BackboneEvents = Events;
+  }
+})(this);
+
+},{}],8:[function(require,module,exports){
+module.exports = require('./backbone-events-standalone');
+
+},{"./backbone-events-standalone":7}],9:[function(require,module,exports){
+(function (definition) {
+  if (typeof exports === "object") {
+    module.exports = definition();
+  }
+  else if (typeof define === 'function' && define.amd) {
+    define(definition);
+  }
+  else {
+    window.BackboneExtend = definition();
+  }
+})(function () {
+  "use strict";
+  
+  // mini-underscore
+  var _ = {
+    has: function (obj, key) {
+      return Object.prototype.hasOwnProperty.call(obj, key);
+    },
+  
+    extend: function(obj) {
+      for (var i=1; i<arguments.length; ++i) {
+        var source = arguments[i];
+        if (source) {
+          for (var prop in source) {
+            obj[prop] = source[prop];
+          }
+        }
+      }
+      return obj;
+    }
+  };
+
+  /// Following code is pasted from Backbone.js ///
+
+  // Helper function to correctly set up the prototype chain, for subclasses.
+  // Similar to `goog.inherits`, but uses a hash of prototype properties and
+  // class properties to be extended.
+  var extend = function(protoProps, staticProps) {
+    var parent = this;
+    var child;
+
+    // The constructor function for the new subclass is either defined by you
+    // (the "constructor" property in your `extend` definition), or defaulted
+    // by us to simply call the parent's constructor.
+    if (protoProps && _.has(protoProps, 'constructor')) {
+      child = protoProps.constructor;
+    } else {
+      child = function(){ return parent.apply(this, arguments); };
+    }
+
+    // Add static properties to the constructor function, if supplied.
+    _.extend(child, parent, staticProps);
+
+    // Set the prototype chain to inherit from `parent`, without calling
+    // `parent`'s constructor function.
+    var Surrogate = function(){ this.constructor = child; };
+    Surrogate.prototype = parent.prototype;
+    child.prototype = new Surrogate();
+
+    // Add prototype properties (instance properties) to the subclass,
+    // if supplied.
+    if (protoProps) _.extend(child.prototype, protoProps);
+
+    // Set a convenience property in case the parent's prototype is needed
+    // later.
+    child.__super__ = parent.prototype;
+
+    return child;
+  };
+
+  // Expose the extend function
+  return extend;
+});
+
+},{}],10:[function(require,module,exports){
+// this is the extracted view model from backbone
+// note that we inject jbone as jquery replacment
+// (and underscore directly)
+//
+// Views are almost more convention than they are actual code.
+//  MVC pattern
+// Backbone.View
+// -------------
+
+var _ = require("underscore");
+var Events = require("backbone-events-standalone");
+var extend = require("backbone-extend-standalone");
+var $ = require('jbone');
+
+// Backbone Views are almost more convention than they are actual code. A View
+// is simply a JavaScript object that represents a logical chunk of UI in the
+// DOM. This might be a single item, an entire list, a sidebar or panel, or
+// even the surrounding frame which wraps your whole app. Defining a chunk of
+// UI as a **View** allows you to define your DOM events declaratively, without
+// having to worry about render order ... and makes it easy for the view to
+// react to specific changes in the state of your models.
+
+// Creating a Backbone.View creates its initial element outside of the DOM,
+// if an existing element is not provided...
+var View =  function(options) {
+  this.cid = _.uniqueId('view');
+  options || (options = {});
+  _.extend(this, _.pick(options, viewOptions));
+  this._ensureElement();
+  this.initialize.apply(this, arguments);
+};
+
+// Cached regex to split keys for `delegate`.
+var delegateEventSplitter = /^(\S+)\s*(.*)$/;
+
+// List of view options to be merged as properties.
+var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
+
+// Set up all inheritable **Backbone.View** properties and methods.
+_.extend(View.prototype, Events, {
+
+  // The default `tagName` of a View's element is `"div"`.
+  tagName: 'div',
+
+  // jQuery delegate for element lookup, scoped to DOM elements within the
+  // current view. This should be preferred to global lookups where possible.
+  $: function(selector) {
+    return this.$el.find(selector);
+  },
+
+    // Initialize is an empty function by default. Override it with your own
+    // initialization logic.
+  initialize: function(){},
+
+    // **render** is the core function that your view should override, in order
+    // to populate its element (`this.el`), with the appropriate HTML. The
+    // convention is for **render** to always return `this`.
+  render: function() {
+    return this;
+  },
+
+    // Remove this view by taking the element out of the DOM, and removing any
+    // applicable Backbone.Events listeners.
+  remove: function() {
+    this._removeElement();
+    this.stopListening();
+    return this;
+  },
+
+    // Remove this view's element from the document and all event listeners
+    // attached to it. Exposed for subclasses using an alternative DOM
+    // manipulation API.
+  _removeElement: function() {
+    this.$el.remove();
+  },
+
+    // Change the view's element (`this.el` property) and re-delegate the
+    // view's events on the new element.
+  setElement: function(element) {
+    this.undelegateEvents();
+    this._setElement(element);
+    this.delegateEvents();
+    return this;
+  },
+
+    // Creates the `this.el` and `this.$el` references for this view using the
+    // given `el`. `el` can be a CSS selector or an HTML string, a jQuery
+    // context or an element. Subclasses can override this to utilize an
+    // alternative DOM manipulation API and are only required to set the
+    // `this.el` property.
+  _setElement: function(el) {
+    this.$el = el instanceof $ ? el : $(el);
+    this.el = this.$el[0];
+  },
+
+    // Set callbacks, where `this.events` is a hash of
+    //
+    // *{"event selector": "callback"}*
+    //
+    //     {
+    //       'mousedown .title':  'edit',
+    //       'click .button':     'save',
+    //       'click .open':       function(e) { ... }
+    //     }
+    //
+    // pairs. Callbacks will be bound to the view, with `this` set properly.
+    // Uses event delegation for efficiency.
+    // Omitting the selector binds the event to `this.el`.
+  delegateEvents: function(events) {
+    if (!(events || (events = _.result(this, 'events')))) return this;
+    this.undelegateEvents();
+    for (var key in events) {
+      var method = events[key];
+      if (!_.isFunction(method)) method = this[events[key]];
+      if (!method) continue;
+      var match = key.match(delegateEventSplitter);
+      this.delegate(match[1], match[2], _.bind(method, this));
+    }
+    return this;
+  },
+
+    // Add a single event listener to the view's element (or a child element
+    // using `selector`). This only works for delegate-able events: not `focus`,
+    // `blur`, and not `change`, `submit`, and `reset` in Internet Explorer.
+  delegate: function(eventName, selector, listener) {
+    this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
+  },
+
+    // Clears all callbacks previously bound to the view by `delegateEvents`.
+    // You usually don't need to use this, but may wish to if you have multiple
+    // Backbone views attached to the same DOM element.
+  undelegateEvents: function() {
+    if (this.$el) this.$el.off('.delegateEvents' + this.cid);
+    return this;
+  },
+
+    // A finer-grained `undelegateEvents` for removing a single delegated event.
+    // `selector` and `listener` are both optional.
+  undelegate: function(eventName, selector, listener) {
+    this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener);
+  },
+
+    // Produces a DOM element to be assigned to your view. Exposed for
+    // subclasses using an alternative DOM manipulation API.
+  _createElement: function(tagName) {
+    return document.createElement(tagName);
+  },
+
+    // Ensure that the View has a DOM element to render into.
+    // If `this.el` is a string, pass it through `$()`, take the first
+    // matching element, and re-assign it to `el`. Otherwise, create
+    // an element from the `id`, `className` and `tagName` properties.
+  _ensureElement: function() {
+    if (!this.el) {
+      var attrs = _.extend({}, _.result(this, 'attributes'));
+      if (this.id) attrs.id = _.result(this, 'id');
+      if (this.className) attrs['class'] = _.result(this, 'className');
+      this.setElement(this._createElement(_.result(this, 'tagName')));
+      this._setAttributes(attrs);
+    } else {
+      this.setElement(_.result(this, 'el'));
+    }
+  },
+
+    // Set attributes from a hash on this view's element.  Exposed for
+    // subclasses using an alternative DOM manipulation API.
+  _setAttributes: function(attributes) {
+    this.$el.attr(attributes);
+  }
+
+});
+
+// setup inheritance
+View.extend = extend;
+module.exports = View;
+
+},{"backbone-events-standalone":12,"backbone-extend-standalone":13,"jbone":50,"underscore":59}],11:[function(require,module,exports){
+module.exports=require(7)
+},{"/home/travis/build/greenify/biojs-vis-msa/node_modules/backbone-thin/node_modules/backbone-events-standalone/backbone-events-standalone.js":7}],12:[function(require,module,exports){
+module.exports=require(8)
+},{"./backbone-events-standalone":11,"/home/travis/build/greenify/biojs-vis-msa/node_modules/backbone-thin/node_modules/backbone-events-standalone/index.js":8}],13:[function(require,module,exports){
+module.exports=require(9)
+},{"/home/travis/build/greenify/biojs-vis-msa/node_modules/backbone-thin/node_modules/backbone-extend-standalone/backbone-extend-standalone.js":9}],14:[function(require,module,exports){
+var events = require("backbone-events-standalone");
+
+events.onAll = function(callback,context){
+  this.on("all", callback,context);
+  return this;
+};
+
+// Mixin utility
+events.oldMixin = events.mixin;
+events.mixin = function(proto) {
+  events.oldMixin(proto);
+  // add custom onAll
+  var exports = ['onAll'];
+  for(var i=0; i < exports.length;i++){
+    var name = exports[i];
+    proto[name] = this[name];
+  }
+  return proto;
+};
+
+module.exports = events;
+
+},{"backbone-events-standalone":16}],15:[function(require,module,exports){
+module.exports=require(7)
+},{"/home/travis/build/greenify/biojs-vis-msa/node_modules/backbone-thin/node_modules/backbone-events-standalone/backbone-events-standalone.js":7}],16:[function(require,module,exports){
+module.exports=require(8)
+},{"./backbone-events-standalone":15,"/home/travis/build/greenify/biojs-vis-msa/node_modules/backbone-thin/node_modules/backbone-events-standalone/index.js":8}],17:[function(require,module,exports){
+// Generated by CoffeeScript 1.8.0
+var GenericReader, xhr;
+
+xhr = require('nets');
+
+module.exports = GenericReader = (function() {
+  function GenericReader() {}
+
+  GenericReader.read = function(url, callback) {
+    var onret;
+    onret = (function(_this) {
+      return function(err, response, text) {
+        return _this._onRetrieval(text, callback);
+      };
+    })(this);
+    return xhr(url, onret);
+  };
+
+  GenericReader._onRetrieval = function(text, callback) {
+    var rText;
+    rText = this.parse(text);
+    return callback(rText);
+  };
+
+  return GenericReader;
+
+})();
+
+},{"nets":undefined}],18:[function(require,module,exports){
+// Generated by CoffeeScript 1.8.0
+var Seq;
+
+module.exports = Seq = (function() {
+  function Seq(seq, name, id) {
+    var meta;
+    this.seq = seq;
+    this.name = name;
+    this.id = id;
+    meta = {};
+  }
+
+  return Seq;
+
+})();
+
+},{}],19:[function(require,module,exports){
+// Generated by CoffeeScript 1.8.0
+var strings;
+
+strings = {
+  contains: function(text, search) {
+    return ''.indexOf.call(text, search, 0) !== -1;
+  }
+};
+
+module.exports = strings;
+
+},{}],20:[function(require,module,exports){
+module.exports=require(17)
+},{"/home/travis/build/greenify/biojs-vis-msa/node_modules/biojs-io-clustal/lib/generic_reader.js":17,"nets":undefined}],21:[function(require,module,exports){
+// Generated by CoffeeScript 1.8.0
+var Fasta, GenericReader, Seq, Str,
+  __hasProp = {}.hasOwnProperty,
+  __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+Str = require("./strings");
+
+GenericReader = require("./generic_reader");
+
+Seq = require("biojs-model").seq;
+
+module.exports = Fasta = (function(_super) {
+  __extends(Fasta, _super);
+
+  function Fasta() {
+    return Fasta.__super__.constructor.apply(this, arguments);
+  }
+
+  Fasta.parse = function(text) {
+    var currentSeq, database, databaseID, identifiers, k, label, line, seqs, _i, _len;
+    seqs = [];
+    if (Object.prototype.toString.call(text) !== '[object Array]') {
+      text = text.split("\n");
+    }
+    for (_i = 0, _len = text.length; _i < _len; _i++) {
+      line = text[_i];
+      if (line[0] === ">" || line[0] === ";") {
+        label = line.slice(1);
+        currentSeq = new Seq("", label, seqs.length);
+        seqs.push(currentSeq);
+        if (Str.contains("|", line)) {
+          identifiers = label.split("|");
+          k = 1;
+          while (k < identifiers.length) {
+            database = identifiers[k];
+            databaseID = identifiers[k + 1];
+            currentSeq.meta[database] = databaseID;
+            k += 2;
+          }
+          currentSeq.name = identifiers[identifiers.length - 1];
+        }
+      } else {
+        currentSeq.seq += line;
+      }
+    }
+    return seqs;
+  };
+
+  return Fasta;
+
+})(GenericReader);
+
+},{"./generic_reader":20,"./strings":22,"biojs-model":25}],22:[function(require,module,exports){
+module.exports=require(19)
+},{"/home/travis/build/greenify/biojs-vis-msa/node_modules/biojs-io-clustal/lib/strings.js":19}],23:[function(require,module,exports){
+// Generated by CoffeeScript 1.8.0
+var Utils;
+
+Utils = {};
+
+Utils.splitNChars = function(txt, num) {
+  var i, result, _i, _ref;
+  result = [];
+  for (i = _i = 0, _ref = txt.length - 1; num > 0 ? _i <= _ref : _i >= _ref; i = _i += num) {
+    result.push(txt.substr(i, num));
+  }
+  return result;
+};
+
+module.exports = Utils;
+
+},{}],24:[function(require,module,exports){
+// Generated by CoffeeScript 1.8.0
+var FastaExporter, Utils;
+
+Utils = require("./utils");
+
+module.exports = FastaExporter = (function() {
+  function FastaExporter() {}
+
+  FastaExporter["export"] = function(seqs, access) {
+    var seq, text, _i, _len;
+    text = "";
+    for (_i = 0, _len = seqs.length; _i < _len; _i++) {
+      seq = seqs[_i];
+      if (access != null) {
+        seq = access(seq);
+      }
+      text += ">" + seq.name + "\n";
+      text += (Utils.splitNChars(seq.seq, 80)).join("\n");
+      text += "\n";
+    }
+    return text;
+  };
+
+  return FastaExporter;
+
+})();
+
+},{"./utils":23}],25:[function(require,module,exports){
+module.exports.seq = require("./seq");
+
+},{"./seq":26}],26:[function(require,module,exports){
+module.exports = function(seq, name, id) {
+    this.seq = seq;
+    this.name = name;
+    this.id = id;
+    this.meta = {};
+};
+
+},{}],27:[function(require,module,exports){
+module.exports=require(25)
+},{"./seq":28,"/home/travis/build/greenify/biojs-vis-msa/node_modules/biojs-io-fasta/node_modules/biojs-model/src/index.js":25}],28:[function(require,module,exports){
+module.exports=require(26)
+},{"/home/travis/build/greenify/biojs-vis-msa/node_modules/biojs-io-fasta/node_modules/biojs-model/src/seq.js":26}],29:[function(require,module,exports){
+module.exports = require('./src/index.js')
+
+},{"./src/index.js":36}],30:[function(require,module,exports){
+module.exports = {
+  A: "#00a35c",
+  R: "#00fc03",
+  N: "#00eb14",
+  D: "#00eb14",
+  C: "#0000ff",
+  Q: "#00f10e",
+  E: "#00f10e",
+  G: "#009d62",
+  H: "#00d52a",
+  I: "#0054ab",
+  L: "#007b84",
+  K: "#00ff00",
+  M: "#009768",
+  F: "#008778",
+  P: "#00e01f",
+  S: "#00d52a",
+  T: "#00db24",
+  W: "#00a857",
+  Y: "#00e619",
+  V: "#005fa0",
+  B: "#00eb14",
+  X: "#00b649",
+  Z: "#00f10e"
+};
+
+},{}],31:[function(require,module,exports){
+module.exports = {
+  A: "#BBBBBB",
+  B: "grey",
+  C: "yellow",
+  D: "red",
+  E: "red",
+  F: "magenta",
+  G: "brown",
+  H: "#00FFFF",
+  I: "#BBBBBB",
+  J: "#fff",
+  K: "#00FFFF",
+  L: "#BBBBBB",
+  M: "#BBBBBB",
+  N: "green",
+  O: "#fff",
+  P: "brown",
+  Q: "green",
+  R: "#00FFFF",
+  S: "green",
+  T: "green",
+  U: "#fff",
+  V: "#BBBBBB",
+  W: "magenta",
+  X: "grey",
+  Y: "magenta",
+  Z: "grey",
+  Gap: "grey"
+};
+
+},{}],32:[function(require,module,exports){
+module.exports = {
+  A: "orange",
+  B: "#fff",
+  C: "green",
+  D: "red",
+  E: "red",
+  F: "blue",
+  G: "orange",
+  H: "red",
+  I: "green",
+  J: "#fff",
+  K: "red",
+  L: "green",
+  M: "green",
+  N: "#fff",
+  O: "#fff",
+  P: "orange",
+  Q: "#fff",
+  R: "red",
+  S: "orange",
+  T: "orange",
+  U: "#fff",
+  V: "green",
+  W: "blue",
+  X: "#fff",
+  Y: "blue",
+  Z: "#fff",
+  Gap: "#fff"
+};
+
+},{}],33:[function(require,module,exports){
+module.exports = {
+  A: "#80a0f0",
+  R: "#f01505",
+  N: "#00ff00",
+  D: "#c048c0",
+  C: "#f08080",
+  Q: "#00ff00",
+  E: "#c048c0",
+  G: "#f09048",
+  H: "#15a4a4",
+  I: "#80a0f0",
+  L: "#80a0f0",
+  K: "#f01505",
+  M: "#80a0f0",
+  F: "#80a0f0",
+  P: "#ffff00",
+  S: "#00ff00",
+  T: "#00ff00",
+  W: "#80a0f0",
+  Y: "#15a4a4",
+  V: "#80a0f0",
+  B: "#fff",
+  X: "#fff",
+  Z: "#fff"
+};
+
+},{}],34:[function(require,module,exports){
+module.exports = {
+  A: "#e718e7",
+  R: "#6f906f",
+  N: "#1be41b",
+  D: "#778877",
+  C: "#23dc23",
+  Q: "#926d92",
+  E: "#ff00ff",
+  G: "#00ff00",
+  H: "#758a75",
+  I: "#8a758a",
+  L: "#ae51ae",
+  K: "#a05fa0",
+  M: "#ef10ef",
+  F: "#986798",
+  P: "#00ff00",
+  S: "#36c936",
+  T: "#47b847",
+  W: "#8a758a",
+  Y: "#21de21",
+  V: "#857a85",
+  B: "#49b649",
+  X: "#758a75",
+  Z: "#c936c9"
+};
+
+},{}],35:[function(require,module,exports){
+module.exports = {
+  A: "#ad0052",
+  B: "#0c00f3",
+  C: "#c2003d",
+  D: "#0c00f3",
+  E: "#0c00f3",
+  F: "#cb0034",
+  G: "#6a0095",
+  H: "#1500ea",
+  I: "#ff0000",
+  J: "#fff",
+  K: "#0000ff",
+  L: "#ea0015",
+  M: "#b0004f",
+  N: "#0c00f3",
+  O: "#fff",
+  P: "#4600b9",
+  Q: "#0c00f3",
+  R: "#0000ff",
+  S: "#5e00a1",
+  T: "#61009e",
+  U: "#fff",
+  V: "#f60009",
+  W: "#5b00a4",
+  X: "#680097",
+  Y: "#4f00b0",
+  Z: "#0c00f3"
+};
+
+},{}],36:[function(require,module,exports){
+module.exports.selector = require("./selector");
+
+// basics
+module.exports.taylor = require("./taylor");
+module.exports.zappo= require("./zappo");
+module.exports.hydro= require("./hydrophobicity");
+
+module.exports.clustal = require("./clustal");
+module.exports.clustal2 = require("./clustal2");
+
+module.exports.curied = require("./buried");
+module.exports.cinema = require("./cinema");
+module.exports.nucleotide  = require("./nucleotide");
+module.exports.helix  = require("./helix");
+module.exports.lesk  = require("./lesk");
+module.exports.mae = require("./mae");
+module.exports.purine = require("./purine");
+module.exports.strand = require("./strand");
+module.exports.turn = require("./turn");
+
+},{"./buried":30,"./cinema":31,"./clustal":32,"./clustal2":33,"./helix":34,"./hydrophobicity":35,"./lesk":37,"./mae":38,"./nucleotide":39,"./purine":40,"./selector":41,"./strand":42,"./taylor":43,"./turn":44,"./zappo":45}],37:[function(require,module,exports){
+module.exports = {
+  A: " orange",
+  B: " #fff",
+  C: " green",
+  D: " red",
+  E: " red",
+  F: " green",
+  G: " orange",
+  H: " magenta",
+  I: " green",
+  J: " #fff",
+  K: " red",
+  L: " green",
+  M: " green",
+  N: " magenta",
+  O: " #fff",
+  P: " green",
+  Q: " magenta",
+  R: " red",
+  S: " orange",
+  T: " orange",
+  U: " #fff",
+  V: " green",
+  W: " green",
+  X: " #fff",
+  Y: " green",
+  Z: " #fff",
+  Gap: " #fff"
+};
+
+},{}],38:[function(require,module,exports){
+module.exports = {
+  A: " #77dd88",
+  B: " #fff",
+  C: " #99ee66",
+  D: " #55bb33",
+  E: " #55bb33",
+  F: " #9999ff",
+  G: " #77dd88",
+  H: " #5555ff",
+  I: " #66bbff",
+  J: " #fff",
+  K: " #ffcc77",
+  L: " #66bbff",
+  M: " #66bbff",
+  N: " #55bb33",
+  O: " #fff",
+  P: " #eeaaaa",
+  Q: " #55bb33",
+  R: " #ffcc77",
+  S: " #ff4455",
+  T: " #ff4455",
+  U: " #fff",
+  V: " #66bbff",
+  W: " #9999ff",
+  X: " #fff",
+  Y: " #9999ff",
+  Z: " #fff",
+  Gap: " #fff"
+};
+
+},{}],39:[function(require,module,exports){
+module.exports = {
+  A: " #64F73F",
+  C: " #FFB340",
+  G: " #EB413C",
+  T: " #3C88EE",
+  U: " #3C88EE"
+};
+
+},{}],40:[function(require,module,exports){
+module.exports = {
+  A: " #FF83FA",
+  C: " #40E0D0",
+  G: " #FF83FA",
+  R: " #FF83FA",
+  T: " #40E0D0",
+  U: " #40E0D0",
+  Y: " #40E0D0"
+};
+
+},{}],41:[function(require,module,exports){
+var Buried = require("./buried");
+var Cinema = require("./cinema");
+var Clustal = require("./clustal");
+var Clustal2 = require("./clustal2");
+var Helix = require("./helix");
+var Hydro = require("./hydrophobicity");
+var Lesk = require("./lesk");
+var Mae = require("./mae");
+var Nucleotide = require("./nucleotide");
+var Purine = require("./purine");
+var Strand = require("./strand");
+var Taylor = require("./taylor");
+var Turn = require("./turn");
+var Zappo = require("./zappo");
+
+module.exports = Colors = {
+  mapping: {
+    buried: Buried,
+    buried_index: Buried,
+    cinema: Cinema,
+    clustal2: Clustal2,
+    clustal: Clustal,
+    helix: Helix,
+    helix_propensity: Helix,
+    hydro: Hydro,
+    lesk: Lesk,
+    mae: Mae,
+    nucleotide: Nucleotide,
+    purine: Purine,
+    purine_pyrimidine: Purine,
+    strand: Strand,
+    strand_propensity: Strand,
+    taylor: Taylor,
+    turn: Turn,
+    turn_propensity: Turn,
+    zappo: Zappo,
+  },
+  getColor: function(scheme) {
+    var color = Colors.mapping[scheme];
+    if (color === undefined) {
+      color = {};
+    }
+    return color;
+  }
+};
+
+},{"./buried":30,"./cinema":31,"./clustal":32,"./clustal2":33,"./helix":34,"./hydrophobicity":35,"./lesk":37,"./mae":38,"./nucleotide":39,"./purine":40,"./strand":42,"./taylor":43,"./turn":44,"./zappo":45}],42:[function(require,module,exports){
+module.exports = {
+  A: "#5858a7",
+  R: "#6b6b94",
+  N: "#64649b",
+  D: "#2121de",
+  C: "#9d9d62",
+  Q: "#8c8c73",
+  E: "#0000ff",
+  G: "#4949b6",
+  H: "#60609f",
+  I: "#ecec13",
+  L: "#b2b24d",
+  K: "#4747b8",
+  M: "#82827d",
+  F: "#c2c23d",
+  P: "#2323dc",
+  S: "#4949b6",
+  T: "#9d9d62",
+  W: "#c0c03f",
+  Y: "#d3d32c",
+  V: "#ffff00",
+  B: "#4343bc",
+  X: "#797986",
+  Z: "#4747b8"
+};
+
+},{}],43:[function(require,module,exports){
+module.exports = {
+  A: "#ccff00",
+  R: "#0000ff",
+  N: "#cc00ff",
+  D: "#ff0000",
+  C: "#ffff00",
+  Q: "#ff00cc",
+  E: "#ff0066",
+  G: "#ff9900",
+  H: "#0066ff",
+  I: "#66ff00",
+  L: "#33ff00",
+  K: "#6600ff",
+  M: "#00ff00",
+  F: "#00ff66",
+  P: "#ffcc00",
+  S: "#ff3300",
+  T: "#ff6600",
+  W: "#00ccff",
+  Y: "#00ffcc",
+  V: "#99ff00",
+  B: "#fff",
+  X: "#fff",
+  Z: "#fff"
+};
+
+},{}],44:[function(require,module,exports){
+module.exports = {
+  A: "#2cd3d3",
+  R: "#708f8f",
+  N: "#ff0000",
+  D: "#e81717",
+  C: "#a85757",
+  Q: "#3fc0c0",
+  E: "#778888",
+  G: "#ff0000",
+  H: "#708f8f",
+  I: "#00ffff",
+  L: "#1ce3e3",
+  K: "#7e8181",
+  M: "#1ee1e1",
+  F: "#1ee1e1",
+  P: "#f60909",
+  S: "#e11e1e",
+  T: "#738c8c",
+  W: "#738c8c",
+  Y: "#9d6262",
+  V: "#07f8f8",
+  B: "#f30c0c",
+  X: "#7c8383",
+  Z: "#5ba4a4"
+};
+
+},{}],45:[function(require,module,exports){
+module.exports = {
+  A: "#ffafaf",
+  R: "#6464ff",
+  N: "#00ff00",
+  D: "#ff0000",
+  C: "#ffff00",
+  Q: "#00ff00",
+  E: "#ff0000",
+  G: "#ff00ff",
+  H: "#6464ff",
+  I: "#ffafaf",
+  L: "#ffafaf",
+  K: "#6464ff",
+  M: "#ffafaf",
+  F: "#ffc800",
+  P: "#ff00ff",
+  S: "#00ff00",
+  T: "#00ff00",
+  W: "#ffc800",
+  Y: "#ffc800",
+  V: "#ffafaf",
+  B: "#fff",
+  X: "#fff",
+  Z: "#fff"
+};
+
+},{}],46:[function(require,module,exports){
+/*
+ * JavaScript Canvas to Blob 2.0.5
+ * https://github.com/blueimp/JavaScript-Canvas-to-Blob
+ *
+ * Copyright 2012, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ *
+ * Based on stackoverflow user Stoive's code snippet:
+ * http://stackoverflow.com/q/4998908
+ */
+var CanvasPrototype = window.HTMLCanvasElement &&
+window.HTMLCanvasElement.prototype,
+  hasBlobConstructor = window.Blob && (function () {
+    try {
+      return Boolean(new Blob());
+    } catch (e) {
+      return false;
+    }
+  }()),
+  hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array &&
+  (function () {
+    try {
+      return new Blob([new Uint8Array(100)]).size === 100;
+    } catch (e) {
+      return false;
+    }
+  }()),
+  BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
+  window.MozBlobBuilder || window.MSBlobBuilder,
+  dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob &&
+  window.ArrayBuffer && window.Uint8Array && function (dataURI) {
+    var byteString,
+    arrayBuffer,
+    intArray,
+      i,
+      mimeString,
+        bb;
+    if (dataURI.split(',')[0].indexOf('base64') >= 0) {
+      // Convert base64 to raw binary data held in a string:
+      byteString = atob(dataURI.split(',')[1]);
+    } else {
+      // Convert base64/URLEncoded data component to raw binary data:
+      byteString = decodeURIComponent(dataURI.split(',')[1]);
+    }
+    // Write the bytes of the string to an ArrayBuffer:
+    arrayBuffer = new ArrayBuffer(byteString.length);
+    intArray = new Uint8Array(arrayBuffer);
+    for (i = 0; i < byteString.length; i += 1) {
+      intArray[i] = byteString.charCodeAt(i);
+    }
+    // Separate out the mime component:
+    mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
+    // Write the ArrayBuffer (or ArrayBufferView) to a blob:
+    if (hasBlobConstructor) {
+      return new Blob(
+          [hasArrayBufferViewSupport ? intArray : arrayBuffer],
+          {type: mimeString}
+          );
+    }
+    bb = new BlobBuilder();
+    bb.append(arrayBuffer);
+    return bb.getBlob(mimeString);
+  };
+if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {
+  if (CanvasPrototype.mozGetAsFile) {
+    CanvasPrototype.toBlob = function (callback, type, quality) {
+      if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {
+        callback(dataURLtoBlob(this.toDataURL(type, quality)));
+      } else {
+        callback(this.mozGetAsFile('blob', type));
+      }
+    };
+  } else if (CanvasPrototype.toDataURL && dataURLtoBlob) {
+    CanvasPrototype.toBlob = function (callback, type, quality) {
+      callback(dataURLtoBlob(this.toDataURL(type, quality)));
+    };
+  }
+}
+
+module.exports = dataURLtoBlob;
+
+},{}],47:[function(require,module,exports){
+/* FileSaver.js
+ *  A saveAs() FileSaver implementation.
+ *  2014-05-27
+ *
+ *  By Eli Grey, http://eligrey.com
+ *  License: X11/MIT
+ *    See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
+ */
+
+/*global self */
+/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
+
+/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
+
+var saveAs = saveAs
+  // IE 10+ (native saveAs)
+  || (typeof navigator !== "undefined" &&
+      navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator))
+  // Everyone else
+  || (function(view) {
+       "use strict";
+       // IE <10 is explicitly unsupported
+       if (typeof navigator !== "undefined" &&
+           /MSIE [1-9]\./.test(navigator.userAgent)) {
+               return;
+       }
+       var
+                 doc = view.document
+                 // only get URL when necessary in case Blob.js hasn't overridden it yet
+               , get_URL = function() {
+                       return view.URL || view.webkitURL || view;
+               }
+               , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
+               , can_use_save_link = !view.externalHost && "download" in save_link
+               , click = function(node) {
+                       var event = doc.createEvent("MouseEvents");
+                       event.initMouseEvent(
+                               "click", true, false, view, 0, 0, 0, 0, 0
+                               , false, false, false, false, 0, null
+                       );
+                       node.dispatchEvent(event);
+               }
+               , webkit_req_fs = view.webkitRequestFileSystem
+               , req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
+               , throw_outside = function(ex) {
+                       (view.setImmediate || view.setTimeout)(function() {
+                               throw ex;
+                       }, 0);
+               }
+               , force_saveable_type = "application/octet-stream"
+               , fs_min_size = 0
+               , deletion_queue = []
+               , process_deletion_queue = function() {
+                       var i = deletion_queue.length;
+                       while (i--) {
+                               var file = deletion_queue[i];
+                               if (typeof file === "string") { // file is an object URL
+                                       get_URL().revokeObjectURL(file);
+                               } else { // file is a File
+                                       file.remove();
+                               }
+                       }
+                       deletion_queue.length = 0; // clear queue
+               }
+               , dispatch = function(filesaver, event_types, event) {
+                       event_types = [].concat(event_types);
+                       var i = event_types.length;
+                       while (i--) {
+                               var listener = filesaver["on" + event_types[i]];
+                               if (typeof listener === "function") {
+                                       try {
+                                               listener.call(filesaver, event || filesaver);
+                                       } catch (ex) {
+                                               throw_outside(ex);
+                                       }
+                               }
+                       }
+               }
+               , FileSaver = function(blob, name) {
+                       // First try a.download, then web filesystem, then object URLs
+                       var
+                                 filesaver = this
+                               , type = blob.type
+                               , blob_changed = false
+                               , object_url
+                               , target_view
+                               , get_object_url = function() {
+                                       var object_url = get_URL().createObjectURL(blob);
+                                       deletion_queue.push(object_url);
+                                       return object_url;
+                               }
+                               , dispatch_all = function() {
+                                       dispatch(filesaver, "writestart progress write writeend".split(" "));
+                               }
+                               // on any filesys errors revert to saving with object URLs
+                               , fs_error = function() {
+                                       // don't create more object URLs than needed
+                                       if (blob_changed || !object_url) {
+                                               object_url = get_object_url(blob);
+                                       }
+                                       if (target_view) {
+                                               target_view.location.href = object_url;
+                                       } else {
+                                               window.open(object_url, "_blank");
+                                       }
+                                       filesaver.readyState = filesaver.DONE;
+                                       dispatch_all();
+                               }
+                               , abortable = function(func) {
+                                       return function() {
+                                               if (filesaver.readyState !== filesaver.DONE) {
+                                                       return func.apply(this, arguments);
+                                               }
+                                       };
+                               }
+                               , create_if_not_found = {create: true, exclusive: false}
+                               , slice
+                       ;
+                       filesaver.readyState = filesaver.INIT;
+                       if (!name) {
+                               name = "download";
+                       }
+                       if (can_use_save_link) {
+                               object_url = get_object_url(blob);
+                               save_link.href = object_url;
+                               save_link.download = name;
+                               click(save_link);
+                               filesaver.readyState = filesaver.DONE;
+                               dispatch_all();
+                               return;
+                       }
+                       // Object and web filesystem URLs have a problem saving in Google Chrome when
+                       // viewed in a tab, so I force save with application/octet-stream
+                       // http://code.google.com/p/chromium/issues/detail?id=91158
+                       if (view.chrome && type && type !== force_saveable_type) {
+                               slice = blob.slice || blob.webkitSlice;
+                               blob = slice.call(blob, 0, blob.size, force_saveable_type);
+                               blob_changed = true;
+                       }
+                       // Since I can't be sure that the guessed media type will trigger a download
+                       // in WebKit, I append .download to the filename.
+                       // https://bugs.webkit.org/show_bug.cgi?id=65440
+                       if (webkit_req_fs && name !== "download") {
+                               name += ".download";
+                       }
+                       if (type === force_saveable_type || webkit_req_fs) {
+                               target_view = view;
+                       }
+                       if (!req_fs) {
+                               fs_error();
+                               return;
+                       }
+                       fs_min_size += blob.size;
+                       req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
+                               fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
+                                       var save = function() {
+                                               dir.getFile(name, create_if_not_found, abortable(function(file) {
+                                                       file.createWriter(abortable(function(writer) {
+                                                               writer.onwriteend = function(event) {
+                                                                       target_view.location.href = file.toURL();
+                                                                       deletion_queue.push(file);
+                                                                       filesaver.readyState = filesaver.DONE;
+                                                                       dispatch(filesaver, "writeend", event);
+                                                               };
+                                                               writer.onerror = function() {
+                                                                       var error = writer.error;
+                                                                       if (error.code !== error.ABORT_ERR) {
+                                                                               fs_error();
+                                                                       }
+                                                               };
+                                                               "writestart progress write abort".split(" ").forEach(function(event) {
+                                                                       writer["on" + event] = filesaver["on" + event];
+                                                               });
+                                                               writer.write(blob);
+                                                               filesaver.abort = function() {
+                                                                       writer.abort();
+                                                                       filesaver.readyState = filesaver.DONE;
+                                                               };
+                                                               filesaver.readyState = filesaver.WRITING;
+                                                       }), fs_error);
+                                               }), fs_error);
+                                       };
+                                       dir.getFile(name, {create: false}, abortable(function(file) {
+                                               // delete file if it already exists
+                                               file.remove();
+                                               save();
+                                       }), abortable(function(ex) {
+                                               if (ex.code === ex.NOT_FOUND_ERR) {
+                                                       save();
+                                               } else {
+                                                       fs_error();
+                                               }
+                                       }));
+                               }), fs_error);
+                       }), fs_error);
+               }
+               , FS_proto = FileSaver.prototype
+               , saveAs = function(blob, name) {
+                       return new FileSaver(blob, name);
+               }
+       ;
+       FS_proto.abort = function() {
+               var filesaver = this;
+               filesaver.readyState = filesaver.DONE;
+               dispatch(filesaver, "abort");
+       };
+       FS_proto.readyState = FS_proto.INIT = 0;
+       FS_proto.WRITING = 1;
+       FS_proto.DONE = 2;
+
+       FS_proto.error =
+       FS_proto.onwritestart =
+       FS_proto.onprogress =
+       FS_proto.onwrite =
+       FS_proto.onabort =
+       FS_proto.onerror =
+       FS_proto.onwriteend =
+               null;
+
+       view.addEventListener("unload", process_deletion_queue, false);
+       saveAs.unload = function() {
+               process_deletion_queue();
+               view.removeEventListener("unload", process_deletion_queue, false);
+       };
+       return saveAs;
+}(
+          typeof self !== "undefined" && self
+       || typeof window !== "undefined" && window
+       || this.content
+));
+// `self` is undefined in Firefox for Android content script context
+// while `this` is nsIContentFrameMessageManager
+// with an attribute `content` that corresponds to the window
+
+amdDefine = window.define;
+if( typeof amdDefine === "undefined" && (typeof window.almond !== "undefined" 
+    && "define" in window.almond )){
+  amdDefine = window.almond.define;
+}
+
+if (typeof module !== "undefined" && module !== null) {
+  module.exports = saveAs;
+} else if ((typeof amdDefine !== "undefined" && amdDefine !== null) && (amdDefine.amd != null)) {
+  amdDefine("saveAs",[], function() {
+    return saveAs;
+  });
+}
+
+},{}],48:[function(require,module,exports){
+module.exports = function (css, customDocument) {
+  var doc = customDocument || document;
+  if (doc.createStyleSheet) {
+    var sheet = doc.createStyleSheet()
+    sheet.cssText = css;
+    return sheet.ownerNode;
+  } else {
+    var head = doc.getElementsByTagName('head')[0],
+        style = doc.createElement('style');
+
+    style.type = 'text/css';
+
+    if (style.styleSheet) {
+      style.styleSheet.cssText = css;
+    } else {
+      style.appendChild(doc.createTextNode(css));
+    }
+
+    head.appendChild(style);
+    return style;
+  }
+};
+
+module.exports.byUrl = function(url) {
+  if (document.createStyleSheet) {
+    return document.createStyleSheet(url).ownerNode;
+  } else {
+    var head = document.getElementsByTagName('head')[0],
+        link = document.createElement('link');
+
+    link.rel = 'stylesheet';
+    link.href = url;
+
+    head.appendChild(link);
+    return link;
+  }
+};
+
+},{}],49:[function(require,module,exports){
+var Utils = {};
+
+
+/*
+Remove an element and provide a function that inserts it into its original position
+https://developers.google.com/speed/articles/javascript-dom
+@param element {Element} The element to be temporarily removed
+@return {Function} A function that inserts the element into its original position
+ */
+
+Utils.removeToInsertLater = function(element) {
+  var nextSibling, parentNode;
+  parentNode = element.parentNode;
+  nextSibling = element.nextSibling;
+  parentNode.removeChild(element);
+  return function() {
+    if (nextSibling) {
+      parentNode.insertBefore(element, nextSibling);
+    } else {
+      parentNode.appendChild(element);
+    }
+  };
+};
+
+
+/*
+fastest possible way to destroy all sub nodes (aka childs)
+http://jsperf.com/innerhtml-vs-removechild/15
+@param element {Element} The element for which all childs should be removed
+ */
+
+Utils.removeAllChilds = function(element) {
+  var count;
+  count = 0;
+  while (element.firstChild) {
+    count++;
+    element.removeChild(element.firstChild);
+  }
+};
+
+module.exports = Utils;
+
+},{}],50:[function(require,module,exports){
+/*!
+ * jBone v1.0.19 - 2014-10-12 - Library for DOM manipulation
+ *
+ * https://github.com/kupriyanenko/jbone
+ *
+ * Copyright 2014 Alexey Kupriyanenko
+ * Released under the MIT license.
+ */
+
+(function (win) {
+
+var
+// cache previous versions
+_$ = win.$,
+_jBone = win.jBone,
+
+// Quick match a standalone tag
+rquickSingleTag = /^<(\w+)\s*\/?>$/,
+
+// A simple way to check for HTML strings
+// Prioritize #id over <tag> to avoid XSS via location.hash
+rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+// Alias for function
+slice = [].slice,
+splice = [].splice,
+keys = Object.keys,
+
+// Alias for global variables
+doc = document,
+
+isString = function(el) {
+    return typeof el === "string";
+},
+isObject = function(el) {
+    return el instanceof Object;
+},
+isFunction = function(el) {
+    var getType = {};
+    return el && getType.toString.call(el) === "[object Function]";
+},
+isArray = function(el) {
+    return Array.isArray(el);
+},
+jBone = function(element, data) {
+    return new fn.init(element, data);
+},
+fn;
+
+// set previous values and return the instance upon calling the no-conflict mode
+jBone.noConflict = function() {
+    win.$ = _$;
+    win.jBone = _jBone;
+
+    return jBone;
+};
+
+fn = jBone.fn = jBone.prototype = {
+    init: function(element, data) {
+        var elements, tag, wraper, fragment;
+
+        if (!element) {
+            return this;
+        }
+        if (isString(element)) {
+            // Create single DOM element
+            if (tag = rquickSingleTag.exec(element)) {
+                this[0] = doc.createElement(tag[1]);
+                this.length = 1;
+
+                if (isObject(data)) {
+                    this.attr(data);
+                }
+
+                return this;
+            }
+            // Create DOM collection
+            if ((tag = rquickExpr.exec(element)) && tag[1]) {
+                fragment = doc.createDocumentFragment();
+                wraper = doc.createElement("div");
+                wraper.innerHTML = element;
+                while (wraper.lastChild) {
+                    fragment.appendChild(wraper.firstChild);
+                }
+                elements = slice.call(fragment.childNodes);
+
+                return jBone.merge(this, elements);
+            }
+            // Find DOM elements with querySelectorAll
+            if (jBone.isElement(data)) {
+                return jBone(data).find(element);
+            }
+
+            try {
+                elements = doc.querySelectorAll(element);
+
+                return jBone.merge(this, elements);
+            } catch (e) {
+                return this;
+            }
+        }
+        // Wrap DOMElement
+        if (element.nodeType) {
+            this[0] = element;
+            this.length = 1;
+
+            return this;
+        }
+        // Run function
+        if (isFunction(element)) {
+            return element();
+        }
+        // Return jBone element as is
+        if (element instanceof jBone) {
+            return element;
+        }
+
+        // Return element wrapped by jBone
+        return jBone.makeArray(element, this);
+    },
+
+    pop: [].pop,
+    push: [].push,
+    reverse: [].reverse,
+    shift: [].shift,
+    sort: [].sort,
+    splice: [].splice,
+    slice: [].slice,
+    indexOf: [].indexOf,
+    forEach: [].forEach,
+    unshift: [].unshift,
+    concat: [].concat,
+    join: [].join,
+    every: [].every,
+    some: [].some,
+    filter: [].filter,
+    map: [].map,
+    reduce: [].reduce,
+    reduceRight: [].reduceRight,
+    length: 0
+};
+
+fn.constructor = jBone;
+
+fn.init.prototype = fn;
+
+jBone.setId = function(el) {
+    var jid = el.jid;
+
+    if (el === win) {
+        jid = "window";
+    } else if (el.jid === undefined) {
+        el.jid = jid = ++jBone._cache.jid;
+    }
+
+    if (!jBone._cache.events[jid]) {
+        jBone._cache.events[jid] = {};
+    }
+};
+
+jBone.getData = function(el) {
+    el = el instanceof jBone ? el[0] : el;
+
+    var jid = el === win ? "window" : el.jid;
+
+    return {
+        jid: jid,
+        events: jBone._cache.events[jid]
+    };
+};
+
+jBone.isElement = function(el) {
+    return el && el instanceof jBone || el instanceof HTMLElement || isString(el);
+};
+
+jBone._cache = {
+    events: {},
+    jid: 0
+};
+
+function isArraylike(obj) {
+    var length = obj.length,
+        type = typeof obj;
+
+    if (isFunction(type) || obj === win) {
+        return false;
+    }
+
+    if (obj.nodeType === 1 && length) {
+        return true;
+    }
+
+    return isArray(type) || length === 0 ||
+        typeof length === "number" && length > 0 && (length - 1) in obj;
+}
+
+jBone.merge = function(first, second) {
+    var l = second.length,
+        i = first.length,
+        j = 0;
+
+    while (j < l) {
+        first[i++] = second[j++];
+    }
+
+    first.length = i;
+
+    return first;
+};
+
+jBone.contains = function(container, contained) {
+    var result;
+
+    container.reverse().some(function(el) {
+        if (el.contains(contained)) {
+            return result = el;
+        }
+    });
+
+    return result;
+};
+
+jBone.extend = function(target) {
+    var k, kl, i, tg;
+
+    splice.call(arguments, 1).forEach(function(object) {
+        if (!object) {
+            return;
+        }
+
+        k = keys(object);
+        kl = k.length;
+        i = 0;
+        tg = target; //caching target for perf improvement
+
+        for (; i < kl; i++) {
+            tg[k[i]] = object[k[i]];
+        }
+    });
+
+    return target;
+};
+
+jBone.makeArray = function(arr, results) {
+    var ret = results || [];
+
+    if (arr !== null) {
+        if (isArraylike(arr)) {
+            jBone.merge(ret, isString(arr) ? [arr] : arr);
+        } else {
+            ret.push(arr);
+        }
+    }
+
+    return ret;
+};
+
+function BoneEvent(e, data) {
+    var key, setter;
+
+    this.originalEvent = e;
+
+    setter = function(key, e) {
+        if (key === "preventDefault") {
+            this[key] = function() {
+                this.defaultPrevented = true;
+                return e[key]();
+            };
+        } else if (isFunction(e[key])) {
+            this[key] = function() {
+                return e[key]();
+            };
+        } else {
+            this[key] = e[key];
+        }
+    };
+
+    for (key in e) {
+        if (e[key] || typeof e[key] === "function") {
+            setter.call(this, key, e);
+        }
+    }
+
+    jBone.extend(this, data);
+}
+
+jBone.Event = function(event, data) {
+    var namespace, eventType;
+
+    if (event.type && !data) {
+        data = event;
+        event = event.type;
+    }
+
+    namespace = event.split(".").splice(1).join(".");
+    eventType = event.split(".")[0];
+
+    event = doc.createEvent("Event");
+    event.initEvent(eventType, true, true);
+
+    return jBone.extend(event, {
+        namespace: namespace,
+        isDefaultPrevented: function() {
+            return event.defaultPrevented;
+        }
+    }, data);
+};
+
+fn.on = function(event) {
+    var args = arguments,
+        length = this.length,
+        i = 0,
+        callback, target, namespace, fn, events, eventType, expectedTarget, addListener;
+
+    if (args.length === 2) {
+        callback = args[1];
+    } else {
+        target = args[1];
+        callback = args[2];
+    }
+
+    addListener = function(el) {
+        jBone.setId(el);
+        events = jBone.getData(el).events;
+        event.split(" ").forEach(function(event) {
+            eventType = event.split(".")[0];
+            namespace = event.split(".").splice(1).join(".");
+            events[eventType] = events[eventType] || [];
+
+            fn = function(e) {
+                if (e.namespace && e.namespace !== namespace) {
+                    return;
+                }
+
+                expectedTarget = null;
+                if (!target) {
+                    callback.call(el, e);
+                } else if (~jBone(el).find(target).indexOf(e.target) || (expectedTarget = jBone.contains(jBone(el).find(target), e.target))) {
+                    expectedTarget = expectedTarget || e.target;
+                    e = new BoneEvent(e, {
+                        currentTarget: expectedTarget
+                    });
+
+                    callback.call(expectedTarget, e);
+                }
+            };
+
+            events[eventType].push({
+                namespace: namespace,
+                fn: fn,
+                originfn: callback
+            });
+
+            el.addEventListener && el.addEventListener(eventType, fn, false);
+        });
+    };
+
+    for (; i < length; i++) {
+        addListener(this[i]);
+    }
+
+    return this;
+};
+
+fn.one = function(event) {
+    var args = arguments,
+        i = 0,
+        length = this.length,
+        callback, target, addListener;
+
+    if (args.length === 2) {
+        callback = args[1];
+    } else {
+        target = args[1], callback = args[2];
+    }
+
+    addListener = function(el) {
+        event.split(" ").forEach(function(event) {
+            var fn = function(e) {
+                jBone(el).off(event, fn);
+                callback.call(el, e);
+            };
+
+            if (!target) {
+                jBone(el).on(event, fn);
+            } else {
+                jBone(el).on(event, target, fn);
+            }
+        });
+    };
+
+    for (; i < length; i++) {
+        addListener(this[i]);
+    }
+
+    return this;
+};
+
+fn.trigger = function(event) {
+    var events = [],
+        i = 0,
+        length = this.length,
+        dispatchEvents;
+
+    if (!event) {
+        return this;
+    }
+
+    if (isString(event)) {
+        events = event.split(" ").map(function(event) {
+            return jBone.Event(event);
+        });
+    } else {
+        event = event instanceof Event ? event : jBone.Event(event);
+        events = [event];
+    }
+
+    dispatchEvents = function(el) {
+        events.forEach(function(event) {
+            if (!event.type) {
+                return;
+            }
+
+            el.dispatchEvent && el.dispatchEvent(event);
+        });
+    };
+
+    for (; i < length; i++) {
+        dispatchEvents(this[i]);
+    }
+
+    return this;
+};
+
+fn.off = function(event, fn) {
+    var i = 0,
+        length = this.length,
+        removeListener = function(events, eventType, index, el, e) {
+            var callback;
+
+            // get callback
+            if ((fn && e.originfn === fn) || !fn) {
+                callback = e.fn;
+            }
+
+            if (events[eventType][index].fn === callback) {
+                el.removeEventListener(eventType, callback);
+
+                // remove handler from cache
+                jBone._cache.events[jBone.getData(el).jid][eventType].splice(index, 1);
+            }
+        },
+        events, namespace, removeListeners, eventType;
+
+    removeListeners = function(el) {
+        var l, eventsByType, e;
+
+        events = jBone.getData(el).events;
+
+        if (!events) {
+            return;
+        }
+
+        // remove all events
+        if (!event && events) {
+            return keys(events).forEach(function(eventType) {
+                eventsByType = events[eventType];
+                l = eventsByType.length;
+
+                while(l--) {
+                    removeListener(events, eventType, l, el, eventsByType[l]);
+                }
+            });
+        }
+
+        event.split(" ").forEach(function(event) {
+            eventType = event.split(".")[0];
+            namespace = event.split(".").splice(1).join(".");
+
+            // remove named events
+            if (events[eventType]) {
+                eventsByType = events[eventType];
+                l = eventsByType.length;
+
+                while(l--) {
+                    e = eventsByType[l];
+                    if (!namespace || (namespace && e.namespace === namespace)) {
+                        removeListener(events, eventType, l, el, e);
+                    }
+                }
+            }
+            // remove all namespaced events
+            else if (namespace) {
+                keys(events).forEach(function(eventType) {
+                    eventsByType = events[eventType];
+                    l = eventsByType.length;
+
+                    while(l--) {
+                        e = eventsByType[l];
+                        if (e.namespace.split(".")[0] === namespace.split(".")[0]) {
+                            removeListener(events, eventType, l, el, e);
+                        }
+                    }
+                });
+            }
+        });
+    };
+
+    for (; i < length; i++) {
+        removeListeners(this[i]);
+    }
+
+    return this;
+};
+
+fn.find = function(selector) {
+    var results = [],
+        i = 0,
+        length = this.length,
+        finder = function(el) {
+            if (isFunction(el.querySelectorAll)) {
+                [].forEach.call(el.querySelectorAll(selector), function(found) {
+                    results.push(found);
+                });
+            }
+        };
+
+    for (; i < length; i++) {
+        finder(this[i]);
+    }
+
+    return jBone(results);
+};
+
+fn.get = function(index) {
+    return this[index];
+};
+
+fn.eq = function(index) {
+    return jBone(this[index]);
+};
+
+fn.parent = function() {
+    var results = [],
+        parent,
+        i = 0,
+        length = this.length;
+
+    for (; i < length; i++) {
+        if (!~results.indexOf(parent = this[i].parentElement) && parent) {
+            results.push(parent);
+        }
+    }
+
+    return jBone(results);
+};
+
+fn.toArray = function() {
+    return slice.call(this);
+};
+
+fn.is = function() {
+    var args = arguments;
+
+    return this.some(function(el) {
+        return el.tagName.toLowerCase() === args[0];
+    });
+};
+
+fn.has = function() {
+    var args = arguments;
+
+    return this.some(function(el) {
+        return el.querySelectorAll(args[0]).length;
+    });
+};
+
+fn.attr = function(key, value) {
+    var args = arguments,
+        i = 0,
+        length = this.length,
+        setter;
+
+    if (isString(key) && args.length === 1) {
+        return this[0] && this[0].getAttribute(key);
+    }
+
+    if (args.length === 2) {
+        setter = function(el) {
+            el.setAttribute(key, value);
+        };
+    } else if (isObject(key)) {
+        setter = function(el) {
+            keys(key).forEach(function(name) {
+                el.setAttribute(name, key[name]);
+            });
+        };
+    }
+
+    for (; i < length; i++) {
+        setter(this[i]);
+    }
+
+    return this;
+};
+
+fn.removeAttr = function(key) {
+    var i = 0,
+        length = this.length;
+
+    for (; i < length; i++) {
+        this[i].removeAttribute(key);
+    }
+
+    return this;
+};
+
+fn.val = function(value) {
+    var i = 0,
+        length = this.length;
+
+    if (arguments.length === 0) {
+        return this[0] && this[0].value;
+    }
+
+    for (; i < length; i++) {
+        this[i].value = value;
+    }
+
+    return this;
+};
+
+fn.css = function(key, value) {
+    var args = arguments,
+        i = 0,
+        length = this.length,
+        setter;
+
+    // Get attribute
+    if (isString(key) && args.length === 1) {
+        return this[0] && win.getComputedStyle(this[0])[key];
+    }
+
+    // Set attributes
+    if (args.length === 2) {
+        setter = function(el) {
+            el.style[key] = value;
+        };
+    } else if (isObject(key)) {
+        setter = function(el) {
+            keys(key).forEach(function(name) {
+                el.style[name] = key[name];
+            });
+        };
+    }
+
+    for (; i < length; i++) {
+        setter(this[i]);
+    }
+
+    return this;
+};
+
+fn.data = function(key, value) {
+    var args = arguments, data = {},
+        i = 0,
+        length = this.length,
+        setter,
+        setValue = function(el, key, value) {
+            if (isObject(value)) {
+                el.jdata = el.jdata || {};
+                el.jdata[key] = value;
+            } else {
+                el.dataset[key] = value;
+            }
+        },
+        getValue = function(value) {
+            if (value === "true") {
+                return true;
+            } else if (value === "false") {
+                return false;
+            } else {
+                return value;
+            }
+        };
+
+    // Get all data
+    if (args.length === 0) {
+        this[0].jdata && (data = this[0].jdata);
+
+        keys(this[0].dataset).forEach(function(key) {
+            data[key] = getValue(this[0].dataset[key]);
+        }, this);
+
+        return data;
+    }
+    // Get data by name
+    if (args.length === 1 && isString(key)) {
+        return this[0] && getValue(this[0].dataset[key] || this[0].jdata && this[0].jdata[key]);
+    }
+
+    // Set data
+    if (args.length === 1 && isObject(key)) {
+        setter = function(el) {
+            keys(key).forEach(function(name) {
+                setValue(el, name, key[name]);
+            });
+        };
+    } else if (args.length === 2) {
+        setter = function(el) {
+            setValue(el, key, value);
+        };
+    }
+
+    for (; i < length; i++) {
+        setter(this[i]);
+    }
+
+    return this;
+};
+
+fn.removeData = function(key) {
+    var i = 0,
+        length = this.length,
+        jdata, dataset;
+
+    for (; i < length; i++) {
+        jdata = this[i].jdata;
+        dataset = this[i].dataset;
+
+        if (key) {
+            jdata && jdata[key] && delete jdata[key];
+            delete dataset[key];
+        } else {
+            for (key in jdata) {
+                delete jdata[key];
+            }
+
+            for (key in dataset) {
+                delete dataset[key];
+            }
+        }
+    }
+
+    return this;
+};
+
+fn.html = function(value) {
+    var args = arguments,
+        el;
+
+    // add HTML into elements
+    if (args.length === 1 && value !== undefined) {
+        return this.empty().append(value);
+    }
+    // get HTML from element
+    else if (args.length === 0 && (el = this[0])) {
+        return el.innerHTML;
+    }
+
+    return this;
+};
+
+fn.append = function(appended) {
+    var i = 0,
+        length = this.length,
+        setter;
+
+    // create jBone object and then append
+    if (isString(appended) && rquickExpr.exec(appended)) {
+        appended = jBone(appended);
+    }
+    // create text node for inserting
+    else if (!isObject(appended)) {
+        appended = document.createTextNode(appended);
+    }
+
+    appended = appended instanceof jBone ? appended : jBone(appended);
+
+    setter = function(el, i) {
+        appended.forEach(function(node) {
+            if (i) {
+                el.appendChild(node.cloneNode());
+            } else {
+                el.appendChild(node);
+            }
+        });
+    };
+
+    for (; i < length; i++) {
+        setter(this[i], i);
+    }
+
+    return this;
+};
+
+fn.appendTo = function(to) {
+    jBone(to).append(this);
+
+    return this;
+};
+
+fn.empty = function() {
+    var i = 0,
+        length = this.length,
+        el;
+
+    for (; i < length; i++) {
+        el = this[i];
+
+        while (el.lastChild) {
+            el.removeChild(el.lastChild);
+        }
+    }
+
+    return this;
+};
+
+fn.remove = function() {
+    var i = 0,
+        length = this.length,
+        el;
+
+    // remove all listners
+    this.off();
+
+    for (; i < length; i++) {
+        el = this[i];
+
+        // remove data and nodes
+        delete el.jdata;
+        el.parentNode && el.parentNode.removeChild(el);
+    }
+
+    return this;
+};
+
+if (typeof module === "object" && module && typeof module.exports === "object") {
+    // Expose jBone as module.exports in loaders that implement the Node
+    // module pattern (including browserify). Do not create the global, since
+    // the user will be storing it themselves locally, and globals are frowned
+    // upon in the Node module world.
+    module.exports = jBone;
+}
+// Register as a AMD module
+else if (typeof define === "function" && define.amd) {
+    define(function() {
+        return jBone;
+    });
+
+    win.jBone = win.$ = jBone;
+} else if (typeof win === "object" && typeof win.document === "object") {
+    win.jBone = win.$ = jBone;
+}
+
+}(window));
+
+},{}],51:[function(require,module,exports){
+var Mouse;
+
+module.exports = Mouse = {
+  rel: function(e) {
+    var mouseX, mouseY, rect, target;
+    mouseX = e.offsetX;
+    mouseY = e.offsetY;
+    if (mouseX == null) {
+      rect = target.getBoundingClientRect();
+      target = e.target || e.srcElement;
+      if (mouseX == null) {
+        mouseX = e.clientX - rect.left;
+        mouseY = e.clientY - rect.top;
+      }
+      if (mouseX == null) {
+        mouseX = e.pageX - target.offsetLeft;
+        mouseY = e.pageY - target.offsetTop;
+      }
+      if (mouseX == null) {
+        console.log(e, "no mouse event defined. your browser sucks");
+        return;
+      }
+    }
+    return [mouseX, mouseY];
+  },
+  abs: function(e) {
+    var mouseX, mouseY;
+    mouseX = e.pageX;
+    mouseY = e.pageY;
+    if (mouseX == null) {
+      mouseX = e.layerX;
+      mouseY = e.layerY;
+    }
+    if (mouseX == null) {
+      mouseX = e.clientX;
+      mouseY = e.clientY;
+    }
+    if (mouseX == null) {
+      mouseX = e.x;
+      mouseY = e.y;
+    }
+    return [mouseX, mouseY];
+  },
+  wheelDelta: function(e) {
+    var delta, dir;
+    delta = [e.deltaX, e.deltaY];
+    if (delta[0] == null) {
+      dir = Math.floor(e.detail / 3);
+      delta = [0, e.mozMovementX * dir];
+    }
+    return delta;
+  }
+};
+
+},{}],52:[function(require,module,exports){
+var window = require("global/window")
+var once = require("once")
+var parseHeaders = require('parse-headers')
+
+var messages = {
+    "0": "Internal XMLHttpRequest Error",
+    "4": "4xx Client Error",
+    "5": "5xx Server Error"
+}
+
+var XHR = window.XMLHttpRequest || noop
+var XDR = "withCredentials" in (new XHR()) ? XHR : window.XDomainRequest
+
+module.exports = createXHR
+
+function createXHR(options, callback) {
+    if (typeof options === "string") {
+        options = { uri: options }
+    }
+
+    options = options || {}
+    callback = once(callback)
+
+    var xhr = options.xhr || null
+
+    if (!xhr) {
+        if (options.cors || options.useXDR) {
+            xhr = new XDR()
+        }else{
+            xhr = new XHR()
+        }
+    }
+
+    var uri = xhr.url = options.uri || options.url
+    var method = xhr.method = options.method || "GET"
+    var body = options.body || options.data
+    var headers = xhr.headers = options.headers || {}
+    var sync = !!options.sync
+    var isJson = false
+    var key
+    var load = options.response ? loadResponse : loadXhr
+
+    if ("json" in options) {
+        isJson = true
+        headers["Accept"] = "application/json"
+        if (method !== "GET" && method !== "HEAD") {
+            headers["Content-Type"] = "application/json"
+            body = JSON.stringify(options.json)
+        }
+    }
+
+    xhr.onreadystatechange = readystatechange
+    xhr.onload = load
+    xhr.onerror = error
+    // IE9 must have onprogress be set to a unique function.
+    xhr.onprogress = function () {
+        // IE must die
+    }
+    // hate IE
+    xhr.ontimeout = noop
+    xhr.open(method, uri, !sync)
+                                    //backward compatibility
+    if (options.withCredentials || (options.cors && options.withCredentials !== false)) {
+        xhr.withCredentials = true
+    }
+
+    // Cannot set timeout with sync request
+    if (!sync) {
+        xhr.timeout = "timeout" in options ? options.timeout : 5000
+    }
+
+    if (xhr.setRequestHeader) {
+        for(key in headers){
+            if(headers.hasOwnProperty(key)){
+                xhr.setRequestHeader(key, headers[key])
+            }
+        }
+    } else if (options.headers) {
+        throw new Error("Headers cannot be set on an XDomainRequest object")
+    }
+
+    if ("responseType" in options) {
+        xhr.responseType = options.responseType
+    }
+    
+    if ("beforeSend" in options && 
+        typeof options.beforeSend === "function"
+    ) {
+        options.beforeSend(xhr)
+    }
+
+    xhr.send(body)
+
+    return xhr
+
+    function readystatechange() {
+        if (xhr.readyState === 4) {
+            load()
+        }
+    }
+
+    function getBody() {
+        // Chrome with requestType=blob throws errors arround when even testing access to responseText
+        var body = null
+
+        if (xhr.response) {
+            body = xhr.response
+        } else if (xhr.responseType === 'text' || !xhr.responseType) {
+            body = xhr.responseText || xhr.responseXML
+        }
+
+        if (isJson) {
+            try {
+                body = JSON.parse(body)
+            } catch (e) {}
+        }
+
+        return body
+    }
+
+    function getStatusCode() {
+        return xhr.status === 1223 ? 204 : xhr.status
+    }
+
+    // if we're getting a none-ok statusCode, build & return an error
+    function errorFromStatusCode(status) {
+        var error = null
+        if (status === 0 || (status >= 400 && status < 600)) {
+            var message = (typeof body === "string" ? body : false) ||
+                messages[String(status).charAt(0)]
+            error = new Error(message)
+            error.statusCode = status
+        }
+
+        return error
+    }
+
+    // will load the data & process the response in a special response object
+    function loadResponse() {
+        var status = getStatusCode()
+        var error = errorFromStatusCode(status)
+        var response = {
+            body: getBody(),
+            statusCode: status,
+            statusText: xhr.statusText,
+            raw: xhr
+        }
+        if(xhr.getAllResponseHeaders){ //remember xhr can in fact be XDR for CORS in IE
+            response.headers = parseHeaders(xhr.getAllResponseHeaders())
+        } else {
+            response.headers = {}
+        }
+
+        callback(error, response, response.body)
+    }
+
+    // will load the data and add some response properties to the source xhr
+    // and then respond with that
+    function loadXhr() {
+        var status = getStatusCode()
+        var error = errorFromStatusCode(status)
+
+        xhr.status = xhr.statusCode = status
+        xhr.body = getBody()
+        xhr.headers = parseHeaders(xhr.getAllResponseHeaders())
+
+        callback(error, xhr, xhr.body)
+    }
+
+    function error(evt) {
+        callback(evt, xhr)
+    }
+}
+
+
+function noop() {}
+
+},{"global/window":53,"once":54,"parse-headers":58}],53:[function(require,module,exports){
+(function (global){
+if (typeof window !== "undefined") {
+    module.exports = window;
+} else if (typeof global !== "undefined") {
+    module.exports = global;
+} else if (typeof self !== "undefined"){
+    module.exports = self;
+} else {
+    module.exports = {};
+}
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],54:[function(require,module,exports){
+module.exports = once
+
+once.proto = once(function () {
+  Object.defineProperty(Function.prototype, 'once', {
+    value: function () {
+      return once(this)
+    },
+    configurable: true
+  })
+})
+
+function once (fn) {
+  var called = false
+  return function () {
+    if (called) return
+    called = true
+    return fn.apply(this, arguments)
+  }
+}
+
+},{}],55:[function(require,module,exports){
+var isFunction = require('is-function')
+
+module.exports = forEach
+
+var toString = Object.prototype.toString
+var hasOwnProperty = Object.prototype.hasOwnProperty
+
+function forEach(list, iterator, context) {
+    if (!isFunction(iterator)) {
+        throw new TypeError('iterator must be a function')
+    }
+
+    if (arguments.length < 3) {
+        context = this
+    }
+    
+    if (toString.call(list) === '[object Array]')
+        forEachArray(list, iterator, context)
+    else if (typeof list === 'string')
+        forEachString(list, iterator, context)
+    else
+        forEachObject(list, iterator, context)
+}
+
+function forEachArray(array, iterator, context) {
+    for (var i = 0, len = array.length; i < len; i++) {
+        if (hasOwnProperty.call(array, i)) {
+            iterator.call(context, array[i], i, array)
+        }
+    }
+}
+
+function forEachString(string, iterator, context) {
+    for (var i = 0, len = string.length; i < len; i++) {
+        // no such thing as a sparse string.
+        iterator.call(context, string.charAt(i), i, string)
+    }
+}
+
+function forEachObject(object, iterator, context) {
+    for (var k in object) {
+        if (hasOwnProperty.call(object, k)) {
+            iterator.call(context, object[k], k, object)
+        }
+    }
+}
+
+},{"is-function":56}],56:[function(require,module,exports){
+module.exports = isFunction
+
+var toString = Object.prototype.toString
+
+function isFunction (fn) {
+  var string = toString.call(fn)
+  return string === '[object Function]' ||
+    (typeof fn === 'function' && string !== '[object RegExp]') ||
+    (typeof window !== 'undefined' &&
+     // IE8 and below
+     (fn === window.setTimeout ||
+      fn === window.alert ||
+      fn === window.confirm ||
+      fn === window.prompt))
+};
+
+},{}],57:[function(require,module,exports){
+
+exports = module.exports = trim;
+
+function trim(str){
+  return str.replace(/^\s*|\s*$/g, '');
+}
+
+exports.left = function(str){
+  return str.replace(/^\s*/, '');
+};
+
+exports.right = function(str){
+  return str.replace(/\s*$/, '');
+};
+
+},{}],58:[function(require,module,exports){
+var trim = require('trim')
+  , forEach = require('for-each')
+  , isArray = function(arg) {
+      return Object.prototype.toString.call(arg) === '[object Array]';
+    }
+
+module.exports = function (headers) {
+  if (!headers)
+    return {}
+
+  var result = {}
+
+  forEach(
+      trim(headers).split('\n')
+    , function (row) {
+        var index = row.indexOf(':')
+          , key = trim(row.slice(0, index)).toLowerCase()
+          , value = trim(row.slice(index + 1))
+
+        if (typeof(result[key]) === 'undefined') {
+          result[key] = value
+        } else if (isArray(result[key])) {
+          result[key].push(value)
+        } else {
+          result[key] = [ result[key], value ]
+        }
+      }
+  )
+
+  return result
+}
+},{"for-each":55,"trim":57}],59:[function(require,module,exports){
+//     Underscore.js 1.7.0
+//     http://underscorejs.org
+//     (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+//     Underscore may be freely distributed under the MIT license.
+
+(function() {
+
+  // Baseline setup
+  // --------------
+
+  // Establish the root object, `window` in the browser, or `exports` on the server.
+  var root = this;
+
+  // Save the previous value of the `_` variable.
+  var previousUnderscore = root._;
+
+  // Save bytes in the minified (but not gzipped) version:
+  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
+
+  // Create quick reference variables for speed access to core prototypes.
+  var
+    push             = ArrayProto.push,
+    slice            = ArrayProto.slice,
+    concat           = ArrayProto.concat,
+    toString         = ObjProto.toString,
+    hasOwnProperty   = ObjProto.hasOwnProperty;
+
+  // All **ECMAScript 5** native function implementations that we hope to use
+  // are declared here.
+  var
+    nativeIsArray      = Array.isArray,
+    nativeKeys         = Object.keys,
+    nativeBind         = FuncProto.bind;
+
+  // Create a safe reference to the Underscore object for use below.
+  var _ = function(obj) {
+    if (obj instanceof _) return obj;
+    if (!(this instanceof _)) return new _(obj);
+    this._wrapped = obj;
+  };
+
+  // Export the Underscore object for **Node.js**, with
+  // backwards-compatibility for the old `require()` API. If we're in
+  // the browser, add `_` as a global object.
+  if (typeof exports !== 'undefined') {
+    if (typeof module !== 'undefined' && module.exports) {
+      exports = module.exports = _;
+    }
+    exports._ = _;
+  } else {
+    root._ = _;
+  }
+
+  // Current version.
+  _.VERSION = '1.7.0';
+
+  // Internal function that returns an efficient (for current engines) version
+  // of the passed-in callback, to be repeatedly applied in other Underscore
+  // functions.
+  var createCallback = function(func, context, argCount) {
+    if (context === void 0) return func;
+    switch (argCount == null ? 3 : argCount) {
+      case 1: return function(value) {
+        return func.call(context, value);
+      };
+      case 2: return function(value, other) {
+        return func.call(context, value, other);
+      };
+      case 3: return function(value, index, collection) {
+        return func.call(context, value, index, collection);
+      };
+      case 4: return function(accumulator, value, index, collection) {
+        return func.call(context, accumulator, value, index, collection);
+      };
+    }
+    return function() {
+      return func.apply(context, arguments);
+    };
+  };
+
+  // A mostly-internal function to generate callbacks that can be applied
+  // to each element in a collection, returning the desired result â€” either
+  // identity, an arbitrary callback, a property matcher, or a property accessor.
+  _.iteratee = function(value, context, argCount) {
+    if (value == null) return _.identity;
+    if (_.isFunction(value)) return createCallback(value, context, argCount);
+    if (_.isObject(value)) return _.matches(value);
+    return _.property(value);
+  };
+
+  // Collection Functions
+  // --------------------
+
+  // The cornerstone, an `each` implementation, aka `forEach`.
+  // Handles raw objects in addition to array-likes. Treats all
+  // sparse array-likes as if they were dense.
+  _.each = _.forEach = function(obj, iteratee, context) {
+    if (obj == null) return obj;
+    iteratee = createCallback(iteratee, context);
+    var i, length = obj.length;
+    if (length === +length) {
+      for (i = 0; i < length; i++) {
+        iteratee(obj[i], i, obj);
+      }
+    } else {
+      var keys = _.keys(obj);
+      for (i = 0, length = keys.length; i < length; i++) {
+        iteratee(obj[keys[i]], keys[i], obj);
+      }
+    }
+    return obj;
+  };
+
+  // Return the results of applying the iteratee to each element.
+  _.map = _.collect = function(obj, iteratee, context) {
+    if (obj == null) return [];
+    iteratee = _.iteratee(iteratee, context);
+    var keys = obj.length !== +obj.length && _.keys(obj),
+        length = (keys || obj).length,
+        results = Array(length),
+        currentKey;
+    for (var index = 0; index < length; index++) {
+      currentKey = keys ? keys[index] : index;
+      results[index] = iteratee(obj[currentKey], currentKey, obj);
+    }
+    return results;
+  };
+
+  var reduceError = 'Reduce of empty array with no initial value';
+
+  // **Reduce** builds up a single result from a list of values, aka `inject`,
+  // or `foldl`.
+  _.reduce = _.foldl = _.inject = function(obj, iteratee, memo, context) {
+    if (obj == null) obj = [];
+    iteratee = createCallback(iteratee, context, 4);
+    var keys = obj.length !== +obj.length && _.keys(obj),
+        length = (keys || obj).length,
+        index = 0, currentKey;
+    if (arguments.length < 3) {
+      if (!length) throw new TypeError(reduceError);
+      memo = obj[keys ? keys[index++] : index++];
+    }
+    for (; index < length; index++) {
+      currentKey = keys ? keys[index] : index;
+      memo = iteratee(memo, obj[currentKey], currentKey, obj);
+    }
+    return memo;
+  };
+
+  // The right-associative version of reduce, also known as `foldr`.
+  _.reduceRight = _.foldr = function(obj, iteratee, memo, context) {
+    if (obj == null) obj = [];
+    iteratee = createCallback(iteratee, context, 4);
+    var keys = obj.length !== + obj.length && _.keys(obj),
+        index = (keys || obj).length,
+        currentKey;
+    if (arguments.length < 3) {
+      if (!index) throw new TypeError(reduceError);
+      memo = obj[keys ? keys[--index] : --index];
+    }
+    while (index--) {
+      currentKey = keys ? keys[index] : index;
+      memo = iteratee(memo, obj[currentKey], currentKey, obj);
+    }
+    return memo;
+  };
+
+  // Return the first value which passes a truth test. Aliased as `detect`.
+  _.find = _.detect = function(obj, predicate, context) {
+    var result;
+    predicate = _.iteratee(predicate, context);
+    _.some(obj, function(value, index, list) {
+      if (predicate(value, index, list)) {
+        result = value;
+        return true;
+      }
+    });
+    return result;
+  };
+
+  // Return all the elements that pass a truth test.
+  // Aliased as `select`.
+  _.filter = _.select = function(obj, predicate, context) {
+    var results = [];
+    if (obj == null) return results;
+    predicate = _.iteratee(predicate, context);
+    _.each(obj, function(value, index, list) {
+      if (predicate(value, index, list)) results.push(value);
+    });
+    return results;
+  };
+
+  // Return all the elements for which a truth test fails.
+  _.reject = function(obj, predicate, context) {
+    return _.filter(obj, _.negate(_.iteratee(predicate)), context);
+  };
+
+  // Determine whether all of the elements match a truth test.
+  // Aliased as `all`.
+  _.every = _.all = function(obj, predicate, context) {
+    if (obj == null) return true;
+    predicate = _.iteratee(predicate, context);
+    var keys = obj.length !== +obj.length && _.keys(obj),
+        length = (keys || obj).length,
+        index, currentKey;
+    for (index = 0; index < length; index++) {
+      currentKey = keys ? keys[index] : index;
+      if (!predicate(obj[currentKey], currentKey, obj)) return false;
+    }
+    return true;
+  };
+
+  // Determine if at least one element in the object matches a truth test.
+  // Aliased as `any`.
+  _.some = _.any = function(obj, predicate, context) {
+    if (obj == null) return false;
+    predicate = _.iteratee(predicate, context);
+    var keys = obj.length !== +obj.length && _.keys(obj),
+        length = (keys || obj).length,
+        index, currentKey;
+    for (index = 0; index < length; index++) {
+      currentKey = keys ? keys[index] : index;
+      if (predicate(obj[currentKey], currentKey, obj)) return true;
+    }
+    return false;
+  };
+
+  // Determine if the array or object contains a given value (using `===`).
+  // Aliased as `include`.
+  _.contains = _.include = function(obj, target) {
+    if (obj == null) return false;
+    if (obj.length !== +obj.length) obj = _.values(obj);
+    return _.indexOf(obj, target) >= 0;
+  };
+
+  // Invoke a method (with arguments) on every item in a collection.
+  _.invoke = function(obj, method) {
+    var args = slice.call(arguments, 2);
+    var isFunc = _.isFunction(method);
+    return _.map(obj, function(value) {
+      return (isFunc ? method : value[method]).apply(value, args);
+    });
+  };
+
+  // Convenience version of a common use case of `map`: fetching a property.
+  _.pluck = function(obj, key) {
+    return _.map(obj, _.property(key));
+  };
+
+  // Convenience version of a common use case of `filter`: selecting only objects
+  // containing specific `key:value` pairs.
+  _.where = function(obj, attrs) {
+    return _.filter(obj, _.matches(attrs));
+  };
+
+  // Convenience version of a common use case of `find`: getting the first object
+  // containing specific `key:value` pairs.
+  _.findWhere = function(obj, attrs) {
+    return _.find(obj, _.matches(attrs));
+  };
+
+  // Return the maximum element (or element-based computation).
+  _.max = function(obj, iteratee, context) {
+    var result = -Infinity, lastComputed = -Infinity,
+        value, computed;
+    if (iteratee == null && obj != null) {
+      obj = obj.length === +obj.length ? obj : _.values(obj);
+      for (var i = 0, length = obj.length; i < length; i++) {
+        value = obj[i];
+        if (value > result) {
+          result = value;
+        }
+      }
+    } else {
+      iteratee = _.iteratee(iteratee, context);
+      _.each(obj, function(value, index, list) {
+        computed = iteratee(value, index, list);
+        if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
+          result = value;
+          lastComputed = computed;
+        }
+      });
+    }
+    return result;
+  };
+
+  // Return the minimum element (or element-based computation).
+  _.min = function(obj, iteratee, context) {
+    var result = Infinity, lastComputed = Infinity,
+        value, computed;
+    if (iteratee == null && obj != null) {
+      obj = obj.length === +obj.length ? obj : _.values(obj);
+      for (var i = 0, length = obj.length; i < length; i++) {
+        value = obj[i];
+        if (value < result) {
+          result = value;
+        }
+      }
+    } else {
+      iteratee = _.iteratee(iteratee, context);
+      _.each(obj, function(value, index, list) {
+        computed = iteratee(value, index, list);
+        if (computed < lastComputed || computed === Infinity && result === Infinity) {
+          result = value;
+          lastComputed = computed;
+        }
+      });
+    }
+    return result;
+  };
+
+  // Shuffle a collection, using the modern version of the
+  // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
+  _.shuffle = function(obj) {
+    var set = obj && obj.length === +obj.length ? obj : _.values(obj);
+    var length = set.length;
+    var shuffled = Array(length);
+    for (var index = 0, rand; index < length; index++) {
+      rand = _.random(0, index);
+      if (rand !== index) shuffled[index] = shuffled[rand];
+      shuffled[rand] = set[index];
+    }
+    return shuffled;
+  };
+
+  // Sample **n** random values from a collection.
+  // If **n** is not specified, returns a single random element.
+  // The internal `guard` argument allows it to work with `map`.
+  _.sample = function(obj, n, guard) {
+    if (n == null || guard) {
+      if (obj.length !== +obj.length) obj = _.values(obj);
+      return obj[_.random(obj.length - 1)];
+    }
+    return _.shuffle(obj).slice(0, Math.max(0, n));
+  };
+
+  // Sort the object's values by a criterion produced by an iteratee.
+  _.sortBy = function(obj, iteratee, context) {
+    iteratee = _.iteratee(iteratee, context);
+    return _.pluck(_.map(obj, function(value, index, list) {
+      return {
+        value: value,
+        index: index,
+        criteria: iteratee(value, index, list)
+      };
+    }).sort(function(left, right) {
+      var a = left.criteria;
+      var b = right.criteria;
+      if (a !== b) {
+        if (a > b || a === void 0) return 1;
+        if (a < b || b === void 0) return -1;
+      }
+      return left.index - right.index;
+    }), 'value');
+  };
+
+  // An internal function used for aggregate "group by" operations.
+  var group = function(behavior) {
+    return function(obj, iteratee, context) {
+      var result = {};
+      iteratee = _.iteratee(iteratee, context);
+      _.each(obj, function(value, index) {
+        var key = iteratee(value, index, obj);
+        behavior(result, value, key);
+      });
+      return result;
+    };
+  };
+
+  // Groups the object's values by a criterion. Pass either a string attribute
+  // to group by, or a function that returns the criterion.
+  _.groupBy = group(function(result, value, key) {
+    if (_.has(result, key)) result[key].push(value); else result[key] = [value];
+  });
+
+  // Indexes the object's values by a criterion, similar to `groupBy`, but for
+  // when you know that your index values will be unique.
+  _.indexBy = group(function(result, value, key) {
+    result[key] = value;
+  });
+
+  // Counts instances of an object that group by a certain criterion. Pass
+  // either a string attribute to count by, or a function that returns the
+  // criterion.
+  _.countBy = group(function(result, value, key) {
+    if (_.has(result, key)) result[key]++; else result[key] = 1;
+  });
+
+  // Use a comparator function to figure out the smallest index at which
+  // an object should be inserted so as to maintain order. Uses binary search.
+  _.sortedIndex = function(array, obj, iteratee, context) {
+    iteratee = _.iteratee(iteratee, context, 1);
+    var value = iteratee(obj);
+    var low = 0, high = array.length;
+    while (low < high) {
+      var mid = low + high >>> 1;
+      if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
+    }
+    return low;
+  };
+
+  // Safely create a real, live array from anything iterable.
+  _.toArray = function(obj) {
+    if (!obj) return [];
+    if (_.isArray(obj)) return slice.call(obj);
+    if (obj.length === +obj.length) return _.map(obj, _.identity);
+    return _.values(obj);
+  };
+
+  // Return the number of elements in an object.
+  _.size = function(obj) {
+    if (obj == null) return 0;
+    return obj.length === +obj.length ? obj.length : _.keys(obj).length;
+  };
+
+  // Split a collection into two arrays: one whose elements all satisfy the given
+  // predicate, and one whose elements all do not satisfy the predicate.
+  _.partition = function(obj, predicate, context) {
+    predicate = _.iteratee(predicate, context);
+    var pass = [], fail = [];
+    _.each(obj, function(value, key, obj) {
+      (predicate(value, key, obj) ? pass : fail).push(value);
+    });
+    return [pass, fail];
+  };
+
+  // Array Functions
+  // ---------------
+
+  // Get the first element of an array. Passing **n** will return the first N
+  // values in the array. Aliased as `head` and `take`. The **guard** check
+  // allows it to work with `_.map`.
+  _.first = _.head = _.take = function(array, n, guard) {
+    if (array == null) return void 0;
+    if (n == null || guard) return array[0];
+    if (n < 0) return [];
+    return slice.call(array, 0, n);
+  };
+
+  // Returns everything but the last entry of the array. Especially useful on
+  // the arguments object. Passing **n** will return all the values in
+  // the array, excluding the last N. The **guard** check allows it to work with
+  // `_.map`.
+  _.initial = function(array, n, guard) {
+    return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
+  };
+
+  // Get the last element of an array. Passing **n** will return the last N
+  // values in the array. The **guard** check allows it to work with `_.map`.
+  _.last = function(array, n, guard) {
+    if (array == null) return void 0;
+    if (n == null || guard) return array[array.length - 1];
+    return slice.call(array, Math.max(array.length - n, 0));
+  };
+
+  // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
+  // Especially useful on the arguments object. Passing an **n** will return
+  // the rest N values in the array. The **guard**
+  // check allows it to work with `_.map`.
+  _.rest = _.tail = _.drop = function(array, n, guard) {
+    return slice.call(array, n == null || guard ? 1 : n);
+  };
+
+  // Trim out all falsy values from an array.
+  _.compact = function(array) {
+    return _.filter(array, _.identity);
+  };
+
+  // Internal implementation of a recursive `flatten` function.
+  var flatten = function(input, shallow, strict, output) {
+    if (shallow && _.every(input, _.isArray)) {
+      return concat.apply(output, input);
+    }
+    for (var i = 0, length = input.length; i < length; i++) {
+      var value = input[i];
+      if (!_.isArray(value) && !_.isArguments(value)) {
+        if (!strict) output.push(value);
+      } else if (shallow) {
+        push.apply(output, value);
+      } else {
+        flatten(value, shallow, strict, output);
+      }
+    }
+    return output;
+  };
+
+  // Flatten out an array, either recursively (by default), or just one level.
+  _.flatten = function(array, shallow) {
+    return flatten(array, shallow, false, []);
+  };
+
+  // Return a version of the array that does not contain the specified value(s).
+  _.without = function(array) {
+    return _.difference(array, slice.call(arguments, 1));
+  };
+
+  // Produce a duplicate-free version of the array. If the array has already
+  // been sorted, you have the option of using a faster algorithm.
+  // Aliased as `unique`.
+  _.uniq = _.unique = function(array, isSorted, iteratee, context) {
+    if (array == null) return [];
+    if (!_.isBoolean(isSorted)) {
+      context = iteratee;
+      iteratee = isSorted;
+      isSorted = false;
+    }
+    if (iteratee != null) iteratee = _.iteratee(iteratee, context);
+    var result = [];
+    var seen = [];
+    for (var i = 0, length = array.length; i < length; i++) {
+      var value = array[i];
+      if (isSorted) {
+        if (!i || seen !== value) result.push(value);
+        seen = value;
+      } else if (iteratee) {
+        var computed = iteratee(value, i, array);
+        if (_.indexOf(seen, computed) < 0) {
+          seen.push(computed);
+          result.push(value);
+        }
+      } else if (_.indexOf(result, value) < 0) {
+        result.push(value);
+      }
+    }
+    return result;
+  };
+
+  // Produce an array that contains the union: each distinct element from all of
+  // the passed-in arrays.
+  _.union = function() {
+    return _.uniq(flatten(arguments, true, true, []));
+  };
+
+  // Produce an array that contains every item shared between all the
+  // passed-in arrays.
+  _.intersection = function(array) {
+    if (array == null) return [];
+    var result = [];
+    var argsLength = arguments.length;
+    for (var i = 0, length = array.length; i < length; i++) {
+      var item = array[i];
+      if (_.contains(result, item)) continue;
+      for (var j = 1; j < argsLength; j++) {
+        if (!_.contains(arguments[j], item)) break;
+      }
+      if (j === argsLength) result.push(item);
+    }
+    return result;
+  };
+
+  // Take the difference between one array and a number of other arrays.
+  // Only the elements present in just the first array will remain.
+  _.difference = function(array) {
+    var rest = flatten(slice.call(arguments, 1), true, true, []);
+    return _.filter(array, function(value){
+      return !_.contains(rest, value);
+    });
+  };
+
+  // Zip together multiple lists into a single array -- elements that share
+  // an index go together.
+  _.zip = function(array) {
+    if (array == null) return [];
+    var length = _.max(arguments, 'length').length;
+    var results = Array(length);
+    for (var i = 0; i < length; i++) {
+      results[i] = _.pluck(arguments, i);
+    }
+    return results;
+  };
+
+  // Converts lists into objects. Pass either a single array of `[key, value]`
+  // pairs, or two parallel arrays of the same length -- one of keys, and one of
+  // the corresponding values.
+  _.object = function(list, values) {
+    if (list == null) return {};
+    var result = {};
+    for (var i = 0, length = list.length; i < length; i++) {
+      if (values) {
+        result[list[i]] = values[i];
+      } else {
+        result[list[i][0]] = list[i][1];
+      }
+    }
+    return result;
+  };
+
+  // Return the position of the first occurrence of an item in an array,
+  // or -1 if the item is not included in the array.
+  // If the array is large and already in sort order, pass `true`
+  // for **isSorted** to use binary search.
+  _.indexOf = function(array, item, isSorted) {
+    if (array == null) return -1;
+    var i = 0, length = array.length;
+    if (isSorted) {
+      if (typeof isSorted == 'number') {
+        i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted;
+      } else {
+        i = _.sortedIndex(array, item);
+        return array[i] === item ? i : -1;
+      }
+    }
+    for (; i < length; i++) if (array[i] === item) return i;
+    return -1;
+  };
+
+  _.lastIndexOf = function(array, item, from) {
+    if (array == null) return -1;
+    var idx = array.length;
+    if (typeof from == 'number') {
+      idx = from < 0 ? idx + from + 1 : Math.min(idx, from + 1);
+    }
+    while (--idx >= 0) if (array[idx] === item) return idx;
+    return -1;
+  };
+
+  // Generate an integer Array containing an arithmetic progression. A port of
+  // the native Python `range()` function. See
+  // [the Python documentation](http://docs.python.org/library/functions.html#range).
+  _.range = function(start, stop, step) {
+    if (arguments.length <= 1) {
+      stop = start || 0;
+      start = 0;
+    }
+    step = step || 1;
+
+    var length = Math.max(Math.ceil((stop - start) / step), 0);
+    var range = Array(length);
+
+    for (var idx = 0; idx < length; idx++, start += step) {
+      range[idx] = start;
+    }
+
+    return range;
+  };
+
+  // Function (ahem) Functions
+  // ------------------
+
+  // Reusable constructor function for prototype setting.
+  var Ctor = function(){};
+
+  // Create a function bound to a given object (assigning `this`, and arguments,
+  // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
+  // available.
+  _.bind = function(func, context) {
+    var args, bound;
+    if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
+    if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
+    args = slice.call(arguments, 2);
+    bound = function() {
+      if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
+      Ctor.prototype = func.prototype;
+      var self = new Ctor;
+      Ctor.prototype = null;
+      var result = func.apply(self, args.concat(slice.call(arguments)));
+      if (_.isObject(result)) return result;
+      return self;
+    };
+    return bound;
+  };
+
+  // Partially apply a function by creating a version that has had some of its
+  // arguments pre-filled, without changing its dynamic `this` context. _ acts
+  // as a placeholder, allowing any combination of arguments to be pre-filled.
+  _.partial = function(func) {
+    var boundArgs = slice.call(arguments, 1);
+    return function() {
+      var position = 0;
+      var args = boundArgs.slice();
+      for (var i = 0, length = args.length; i < length; i++) {
+        if (args[i] === _) args[i] = arguments[position++];
+      }
+      while (position < arguments.length) args.push(arguments[position++]);
+      return func.apply(this, args);
+    };
+  };
+
+  // Bind a number of an object's methods to that object. Remaining arguments
+  // are the method names to be bound. Useful for ensuring that all callbacks
+  // defined on an object belong to it.
+  _.bindAll = function(obj) {
+    var i, length = arguments.length, key;
+    if (length <= 1) throw new Error('bindAll must be passed function names');
+    for (i = 1; i < length; i++) {
+      key = arguments[i];
+      obj[key] = _.bind(obj[key], obj);
+    }
+    return obj;
+  };
+
+  // Memoize an expensive function by storing its results.
+  _.memoize = function(func, hasher) {
+    var memoize = function(key) {
+      var cache = memoize.cache;
+      var address = hasher ? hasher.apply(this, arguments) : key;
+      if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
+      return cache[address];
+    };
+    memoize.cache = {};
+    return memoize;
+  };
+
+  // Delays a function for the given number of milliseconds, and then calls
+  // it with the arguments supplied.
+  _.delay = function(func, wait) {
+    var args = slice.call(arguments, 2);
+    return setTimeout(function(){
+      return func.apply(null, args);
+    }, wait);
+  };
+
+  // Defers a function, scheduling it to run after the current call stack has
+  // cleared.
+  _.defer = function(func) {
+    return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
+  };
+
+  // Returns a function, that, when invoked, will only be triggered at most once
+  // during a given window of time. Normally, the throttled function will run
+  // as much as it can, without ever going more than once per `wait` duration;
+  // but if you'd like to disable the execution on the leading edge, pass
+  // `{leading: false}`. To disable execution on the trailing edge, ditto.
+  _.throttle = function(func, wait, options) {
+    var context, args, result;
+    var timeout = null;
+    var previous = 0;
+    if (!options) options = {};
+    var later = function() {
+      previous = options.leading === false ? 0 : _.now();
+      timeout = null;
+      result = func.apply(context, args);
+      if (!timeout) context = args = null;
+    };
+    return function() {
+      var now = _.now();
+      if (!previous && options.leading === false) previous = now;
+      var remaining = wait - (now - previous);
+      context = this;
+      args = arguments;
+      if (remaining <= 0 || remaining > wait) {
+        clearTimeout(timeout);
+        timeout = null;
+        previous = now;
+        result = func.apply(context, args);
+        if (!timeout) context = args = null;
+      } else if (!timeout && options.trailing !== false) {
+        timeout = setTimeout(later, remaining);
+      }
+      return result;
+    };
+  };
+
+  // Returns a function, that, as long as it continues to be invoked, will not
+  // be triggered. The function will be called after it stops being called for
+  // N milliseconds. If `immediate` is passed, trigger the function on the
+  // leading edge, instead of the trailing.
+  _.debounce = function(func, wait, immediate) {
+    var timeout, args, context, timestamp, result;
+
+    var later = function() {
+      var last = _.now() - timestamp;
+
+      if (last < wait && last > 0) {
+        timeout = setTimeout(later, wait - last);
+      } else {
+        timeout = null;
+        if (!immediate) {
+          result = func.apply(context, args);
+          if (!timeout) context = args = null;
+        }
+      }
+    };
+
+    return function() {
+      context = this;
+      args = arguments;
+      timestamp = _.now();
+      var callNow = immediate && !timeout;
+      if (!timeout) timeout = setTimeout(later, wait);
+      if (callNow) {
+        result = func.apply(context, args);
+        context = args = null;
+      }
+
+      return result;
+    };
+  };
+
+  // Returns the first function passed as an argument to the second,
+  // allowing you to adjust arguments, run code before and after, and
+  // conditionally execute the original function.
+  _.wrap = function(func, wrapper) {
+    return _.partial(wrapper, func);
+  };
+
+  // Returns a negated version of the passed-in predicate.
+  _.negate = function(predicate) {
+    return function() {
+      return !predicate.apply(this, arguments);
+    };
+  };
+
+  // Returns a function that is the composition of a list of functions, each
+  // consuming the return value of the function that follows.
+  _.compose = function() {
+    var args = arguments;
+    var start = args.length - 1;
+    return function() {
+      var i = start;
+      var result = args[start].apply(this, arguments);
+      while (i--) result = args[i].call(this, result);
+      return result;
+    };
+  };
+
+  // Returns a function that will only be executed after being called N times.
+  _.after = function(times, func) {
+    return function() {
+      if (--times < 1) {
+        return func.apply(this, arguments);
+      }
+    };
+  };
+
+  // Returns a function that will only be executed before being called N times.
+  _.before = function(times, func) {
+    var memo;
+    return function() {
+      if (--times > 0) {
+        memo = func.apply(this, arguments);
+      } else {
+        func = null;
+      }
+      return memo;
+    };
+  };
+
+  // Returns a function that will be executed at most one time, no matter how
+  // often you call it. Useful for lazy initialization.
+  _.once = _.partial(_.before, 2);
+
+  // Object Functions
+  // ----------------
+
+  // Retrieve the names of an object's properties.
+  // Delegates to **ECMAScript 5**'s native `Object.keys`
+  _.keys = function(obj) {
+    if (!_.isObject(obj)) return [];
+    if (nativeKeys) return nativeKeys(obj);
+    var keys = [];
+    for (var key in obj) if (_.has(obj, key)) keys.push(key);
+    return keys;
+  };
+
+  // Retrieve the values of an object's properties.
+  _.values = function(obj) {
+    var keys = _.keys(obj);
+    var length = keys.length;
+    var values = Array(length);
+    for (var i = 0; i < length; i++) {
+      values[i] = obj[keys[i]];
+    }
+    return values;
+  };
+
+  // Convert an object into a list of `[key, value]` pairs.
+  _.pairs = function(obj) {
+    var keys = _.keys(obj);
+    var length = keys.length;
+    var pairs = Array(length);
+    for (var i = 0; i < length; i++) {
+      pairs[i] = [keys[i], obj[keys[i]]];
+    }
+    return pairs;
+  };
+
+  // Invert the keys and values of an object. The values must be serializable.
+  _.invert = function(obj) {
+    var result = {};
+    var keys = _.keys(obj);
+    for (var i = 0, length = keys.length; i < length; i++) {
+      result[obj[keys[i]]] = keys[i];
+    }
+    return result;
+  };
+
+  // Return a sorted list of the function names available on the object.
+  // Aliased as `methods`
+  _.functions = _.methods = function(obj) {
+    var names = [];
+    for (var key in obj) {
+      if (_.isFunction(obj[key])) names.push(key);
+    }
+    return names.sort();
+  };
+
+  // Extend a given object with all the properties in passed-in object(s).
+  _.extend = function(obj) {
+    if (!_.isObject(obj)) return obj;
+    var source, prop;
+    for (var i = 1, length = arguments.length; i < length; i++) {
+      source = arguments[i];
+      for (prop in source) {
+        if (hasOwnProperty.call(source, prop)) {
+            obj[prop] = source[prop];
+        }
+      }
+    }
+    return obj;
+  };
+
+  // Return a copy of the object only containing the whitelisted properties.
+  _.pick = function(obj, iteratee, context) {
+    var result = {}, key;
+    if (obj == null) return result;
+    if (_.isFunction(iteratee)) {
+      iteratee = createCallback(iteratee, context);
+      for (key in obj) {
+        var value = obj[key];
+        if (iteratee(value, key, obj)) result[key] = value;
+      }
+    } else {
+      var keys = concat.apply([], slice.call(arguments, 1));
+      obj = new Object(obj);
+      for (var i = 0, length = keys.length; i < length; i++) {
+        key = keys[i];
+        if (key in obj) result[key] = obj[key];
+      }
+    }
+    return result;
+  };
+
+   // Return a copy of the object without the blacklisted properties.
+  _.omit = function(obj, iteratee, context) {
+    if (_.isFunction(iteratee)) {
+      iteratee = _.negate(iteratee);
+    } else {
+      var keys = _.map(concat.apply([], slice.call(arguments, 1)), String);
+      iteratee = function(value, key) {
+        return !_.contains(keys, key);
+      };
+    }
+    return _.pick(obj, iteratee, context);
+  };
+
+  // Fill in a given object with default properties.
+  _.defaults = function(obj) {
+    if (!_.isObject(obj)) return obj;
+    for (var i = 1, length = arguments.length; i < length; i++) {
+      var source = arguments[i];
+      for (var prop in source) {
+        if (obj[prop] === void 0) obj[prop] = source[prop];
+      }
+    }
+    return obj;
+  };
+
+  // Create a (shallow-cloned) duplicate of an object.
+  _.clone = function(obj) {
+    if (!_.isObject(obj)) return obj;
+    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
+  };
+
+  // Invokes interceptor with the obj, and then returns obj.
+  // The primary purpose of this method is to "tap into" a method chain, in
+  // order to perform operations on intermediate results within the chain.
+  _.tap = function(obj, interceptor) {
+    interceptor(obj);
+    return obj;
+  };
+
+  // Internal recursive comparison function for `isEqual`.
+  var eq = function(a, b, aStack, bStack) {
+    // Identical objects are equal. `0 === -0`, but they aren't identical.
+    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
+    if (a === b) return a !== 0 || 1 / a === 1 / b;
+    // A strict comparison is necessary because `null == undefined`.
+    if (a == null || b == null) return a === b;
+    // Unwrap any wrapped objects.
+    if (a instanceof _) a = a._wrapped;
+    if (b instanceof _) b = b._wrapped;
+    // Compare `[[Class]]` names.
+    var className = toString.call(a);
+    if (className !== toString.call(b)) return false;
+    switch (className) {
+      // Strings, numbers, regular expressions, dates, and booleans are compared by value.
+      case '[object RegExp]':
+      // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
+      case '[object String]':
+        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+        // equivalent to `new String("5")`.
+        return '' + a === '' + b;
+      case '[object Number]':
+        // `NaN`s are equivalent, but non-reflexive.
+        // Object(NaN) is equivalent to NaN
+        if (+a !== +a) return +b !== +b;
+        // An `egal` comparison is performed for other numeric values.
+        return +a === 0 ? 1 / +a === 1 / b : +a === +b;
+      case '[object Date]':
+      case '[object Boolean]':
+        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+        // millisecond representations. Note that invalid dates with millisecond representations
+        // of `NaN` are not equivalent.
+        return +a === +b;
+    }
+    if (typeof a != 'object' || typeof b != 'object') return false;
+    // Assume equality for cyclic structures. The algorithm for detecting cyclic
+    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+    var length = aStack.length;
+    while (length--) {
+      // Linear search. Performance is inversely proportional to the number of
+      // unique nested structures.
+      if (aStack[length] === a) return bStack[length] === b;
+    }
+    // Objects with different constructors are not equivalent, but `Object`s
+    // from different frames are.
+    var aCtor = a.constructor, bCtor = b.constructor;
+    if (
+      aCtor !== bCtor &&
+      // Handle Object.create(x) cases
+      'constructor' in a && 'constructor' in b &&
+      !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
+        _.isFunction(bCtor) && bCtor instanceof bCtor)
+    ) {
+      return false;
+    }
+    // Add the first object to the stack of traversed objects.
+    aStack.push(a);
+    bStack.push(b);
+    var size, result;
+    // Recursively compare objects and arrays.
+    if (className === '[object Array]') {
+      // Compare array lengths to determine if a deep comparison is necessary.
+      size = a.length;
+      result = size === b.length;
+      if (result) {
+        // Deep compare the contents, ignoring non-numeric properties.
+        while (size--) {
+          if (!(result = eq(a[size], b[size], aStack, bStack))) break;
+        }
+      }
+    } else {
+      // Deep compare objects.
+      var keys = _.keys(a), key;
+      size = keys.length;
+      // Ensure that both objects contain the same number of properties before comparing deep equality.
+      result = _.keys(b).length === size;
+      if (result) {
+        while (size--) {
+          // Deep compare each member
+          key = keys[size];
+          if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
+        }
+      }
+    }
+    // Remove the first object from the stack of traversed objects.
+    aStack.pop();
+    bStack.pop();
+    return result;
+  };
+
+  // Perform a deep comparison to check if two objects are equal.
+  _.isEqual = function(a, b) {
+    return eq(a, b, [], []);
+  };
+
+  // Is a given array, string, or object empty?
+  // An "empty" object has no enumerable own-properties.
+  _.isEmpty = function(obj) {
+    if (obj == null) return true;
+    if (_.isArray(obj) || _.isString(obj) || _.isArguments(obj)) return obj.length === 0;
+    for (var key in obj) if (_.has(obj, key)) return false;
+    return true;
+  };
+
+  // Is a given value a DOM element?
+  _.isElement = function(obj) {
+    return !!(obj && obj.nodeType === 1);
+  };
+
+  // Is a given value an array?
+  // Delegates to ECMA5's native Array.isArray
+  _.isArray = nativeIsArray || function(obj) {
+    return toString.call(obj) === '[object Array]';
+  };
+
+  // Is a given variable an object?
+  _.isObject = function(obj) {
+    var type = typeof obj;
+    return type === 'function' || type === 'object' && !!obj;
+  };
+
+  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
+  _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
+    _['is' + name] = function(obj) {
+      return toString.call(obj) === '[object ' + name + ']';
+    };
+  });
+
+  // Define a fallback version of the method in browsers (ahem, IE), where
+  // there isn't any inspectable "Arguments" type.
+  if (!_.isArguments(arguments)) {
+    _.isArguments = function(obj) {
+      return _.has(obj, 'callee');
+    };
+  }
+
+  // Optimize `isFunction` if appropriate. Work around an IE 11 bug.
+  if (typeof /./ !== 'function') {
+    _.isFunction = function(obj) {
+      return typeof obj == 'function' || false;
+    };
+  }
+
+  // Is a given object a finite number?
+  _.isFinite = function(obj) {
+    return isFinite(obj) && !isNaN(parseFloat(obj));
+  };
+
+  // Is the given value `NaN`? (NaN is the only number which does not equal itself).
+  _.isNaN = function(obj) {
+    return _.isNumber(obj) && obj !== +obj;
+  };
+
+  // Is a given value a boolean?
+  _.isBoolean = function(obj) {
+    return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
+  };
+
+  // Is a given value equal to null?
+  _.isNull = function(obj) {
+    return obj === null;
+  };
+
+  // Is a given variable undefined?
+  _.isUndefined = function(obj) {
+    return obj === void 0;
+  };
+
+  // Shortcut function for checking if an object has a given property directly
+  // on itself (in other words, not on a prototype).
+  _.has = function(obj, key) {
+    return obj != null && hasOwnProperty.call(obj, key);
+  };
+
+  // Utility Functions
+  // -----------------
+
+  // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
+  // previous owner. Returns a reference to the Underscore object.
+  _.noConflict = function() {
+    root._ = previousUnderscore;
+    return this;
+  };
+
+  // Keep the identity function around for default iteratees.
+  _.identity = function(value) {
+    return value;
+  };
+
+  _.constant = function(value) {
+    return function() {
+      return value;
+    };
+  };
+
+  _.noop = function(){};
+
+  _.property = function(key) {
+    return function(obj) {
+      return obj[key];
+    };
+  };
+
+  // Returns a predicate for checking whether an object has a given set of `key:value` pairs.
+  _.matches = function(attrs) {
+    var pairs = _.pairs(attrs), length = pairs.length;
+    return function(obj) {
+      if (obj == null) return !length;
+      obj = new Object(obj);
+      for (var i = 0; i < length; i++) {
+        var pair = pairs[i], key = pair[0];
+        if (pair[1] !== obj[key] || !(key in obj)) return false;
+      }
+      return true;
+    };
+  };
+
+  // Run a function **n** times.
+  _.times = function(n, iteratee, context) {
+    var accum = Array(Math.max(0, n));
+    iteratee = createCallback(iteratee, context, 1);
+    for (var i = 0; i < n; i++) accum[i] = iteratee(i);
+    return accum;
+  };
+
+  // Return a random integer between min and max (inclusive).
+  _.random = function(min, max) {
+    if (max == null) {
+      max = min;
+      min = 0;
+    }
+    return min + Math.floor(Math.random() * (max - min + 1));
+  };
+
+  // A (possibly faster) way to get the current timestamp as an integer.
+  _.now = Date.now || function() {
+    return new Date().getTime();
+  };
+
+   // List of HTML entities for escaping.
+  var escapeMap = {
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;',
+    '"': '&quot;',
+    "'": '&#x27;',
+    '`': '&#x60;'
+  };
+  var unescapeMap = _.invert(escapeMap);
+
+  // Functions for escaping and unescaping strings to/from HTML interpolation.
+  var createEscaper = function(map) {
+    var escaper = function(match) {
+      return map[match];
+    };
+    // Regexes for identifying a key that needs to be escaped
+    var source = '(?:' + _.keys(map).join('|') + ')';
+    var testRegexp = RegExp(source);
+    var replaceRegexp = RegExp(source, 'g');
+    return function(string) {
+      string = string == null ? '' : '' + string;
+      return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
+    };
+  };
+  _.escape = createEscaper(escapeMap);
+  _.unescape = createEscaper(unescapeMap);
+
+  // If the value of the named `property` is a function then invoke it with the
+  // `object` as context; otherwise, return it.
+  _.result = function(object, property) {
+    if (object == null) return void 0;
+    var value = object[property];
+    return _.isFunction(value) ? object[property]() : value;
+  };
+
+  // Generate a unique integer id (unique within the entire client session).
+  // Useful for temporary DOM ids.
+  var idCounter = 0;
+  _.uniqueId = function(prefix) {
+    var id = ++idCounter + '';
+    return prefix ? prefix + id : id;
+  };
+
+  // By default, Underscore uses ERB-style template delimiters, change the
+  // following template settings to use alternative delimiters.
+  _.templateSettings = {
+    evaluate    : /<%([\s\S]+?)%>/g,
+    interpolate : /<%=([\s\S]+?)%>/g,
+    escape      : /<%-([\s\S]+?)%>/g
+  };
+
+  // When customizing `templateSettings`, if you don't want to define an
+  // interpolation, evaluation or escaping regex, we need one that is
+  // guaranteed not to match.
+  var noMatch = /(.)^/;
+
+  // Certain characters need to be escaped so that they can be put into a
+  // string literal.
+  var escapes = {
+    "'":      "'",
+    '\\':     '\\',
+    '\r':     'r',
+    '\n':     'n',
+    '\u2028': 'u2028',
+    '\u2029': 'u2029'
+  };
+
+  var escaper = /\\|'|\r|\n|\u2028|\u2029/g;
+
+  var escapeChar = function(match) {
+    return '\\' + escapes[match];
+  };
+
+  // JavaScript micro-templating, similar to John Resig's implementation.
+  // Underscore templating handles arbitrary delimiters, preserves whitespace,
+  // and correctly escapes quotes within interpolated code.
+  // NB: `oldSettings` only exists for backwards compatibility.
+  _.template = function(text, settings, oldSettings) {
+    if (!settings && oldSettings) settings = oldSettings;
+    settings = _.defaults({}, settings, _.templateSettings);
+
+    // Combine delimiters into one regular expression via alternation.
+    var matcher = RegExp([
+      (settings.escape || noMatch).source,
+      (settings.interpolate || noMatch).source,
+      (settings.evaluate || noMatch).source
+    ].join('|') + '|$', 'g');
+
+    // Compile the template source, escaping string literals appropriately.
+    var index = 0;
+    var source = "__p+='";
+    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
+      source += text.slice(index, offset).replace(escaper, escapeChar);
+      index = offset + match.length;
+
+      if (escape) {
+        source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
+      } else if (interpolate) {
+        source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
+      } else if (evaluate) {
+        source += "';\n" + evaluate + "\n__p+='";
+      }
+
+      // Adobe VMs need the match returned to produce the correct offest.
+      return match;
+    });
+    source += "';\n";
+
+    // If a variable is not specified, place data values in local scope.
+    if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
+
+    source = "var __t,__p='',__j=Array.prototype.join," +
+      "print=function(){__p+=__j.call(arguments,'');};\n" +
+      source + 'return __p;\n';
+
+    try {
+      var render = new Function(settings.variable || 'obj', '_', source);
+    } catch (e) {
+      e.source = source;
+      throw e;
+    }
+
+    var template = function(data) {
+      return render.call(this, data, _);
+    };
+
+    // Provide the compiled source as a convenience for precompilation.
+    var argument = settings.variable || 'obj';
+    template.source = 'function(' + argument + '){\n' + source + '}';
+
+    return template;
+  };
+
+  // Add a "chain" function. Start chaining a wrapped Underscore object.
+  _.chain = function(obj) {
+    var instance = _(obj);
+    instance._chain = true;
+    return instance;
+  };
+
+  // OOP
+  // ---------------
+  // If Underscore is called as a function, it returns a wrapped object that
+  // can be used OO-style. This wrapper holds altered versions of all the
+  // underscore functions. Wrapped objects may be chained.
+
+  // Helper function to continue chaining intermediate results.
+  var result = function(obj) {
+    return this._chain ? _(obj).chain() : obj;
+  };
+
+  // Add your own custom functions to the Underscore object.
+  _.mixin = function(obj) {
+    _.each(_.functions(obj), function(name) {
+      var func = _[name] = obj[name];
+      _.prototype[name] = function() {
+        var args = [this._wrapped];
+        push.apply(args, arguments);
+        return result.call(this, func.apply(_, args));
+      };
+    });
+  };
+
+  // Add all of the Underscore functions to the wrapper object.
+  _.mixin(_);
+
+  // Add all mutator Array functions to the wrapper.
+  _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
+    var method = ArrayProto[name];
+    _.prototype[name] = function() {
+      var obj = this._wrapped;
+      method.apply(obj, arguments);
+      if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
+      return result.call(this, obj);
+    };
+  });
+
+  // Add all accessor Array functions to the wrapper.
+  _.each(['concat', 'join', 'slice'], function(name) {
+    var method = ArrayProto[name];
+    _.prototype[name] = function() {
+      return result.call(this, method.apply(this._wrapped, arguments));
+    };
+  });
+
+  // Extracts the result from a wrapped and chained object.
+  _.prototype.value = function() {
+    return this._wrapped;
+  };
+
+  // AMD registration happens at the end for compatibility with AMD loaders
+  // that may not enforce next-turn semantics on modules. Even though general
+  // practice for AMD registration is to be anonymous, underscore registers
+  // as a named module because, like jQuery, it is a base library that is
+  // popular enough to be bundled in a third party lib, but not be part of
+  // an AMD load request. Those cases could generate an error when an
+  // anonymous define() is called outside of a loader request.
+  if (typeof define === 'function' && define.amd) {
+    define('underscore', [], function() {
+      return _;
+    });
+  }
+}.call(this));
+
+},{}],60:[function(require,module,exports){
+var _;
+
+_ = require("underscore");
+
+module.exports = function(seqs) {
+  var occs;
+  seqs = seqs.map(function(el) {
+    return el.get("seq");
+  });
+  occs = new Array(seqs.length);
+  _.each(seqs, function(el, i) {
+    return _.each(el, function(char, pos) {
+      if (occs[pos] == null) {
+        occs[pos] = {};
+      }
+      if (occs[pos][char] == null) {
+        occs[pos][char] = 0;
+      }
+      return occs[pos][char]++;
+    });
+  });
+  return _.reduce(occs, function(memo, occ) {
+    var keys;
+    keys = _.keys(occ);
+    return memo += _.max(keys, function(key) {
+      return occ[key];
+    });
+  }, "");
+};
+
+
+
+},{"underscore":59}],61:[function(require,module,exports){
+var identitiyCalc;
+
+module.exports = identitiyCalc = function(seqs, consensus) {
+  if (consensus === void 0) {
+    console.warn("bug on consenus calc");
+    return;
+  }
+  return seqs.each(function(seqObj) {
+    var i, matches, seq, total, _i, _ref;
+    seq = seqObj.get("seq");
+    matches = 0;
+    total = 0;
+    for (i = _i = 0, _ref = seq.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
+      if (seq[i] !== "-" && consensus[i] !== "-") {
+        total++;
+        if (seq[i] === consensus[i]) {
+          matches++;
+        }
+      }
+    }
+    return seqObj.set("identity", matches / total);
+  });
+};
+
+
+
+},{}],62:[function(require,module,exports){
+module.exports.consensus = require("./ConsensusCalc");
+
+
+
+},{"./ConsensusCalc":60}],63:[function(require,module,exports){
+var Colorator, Model;
+
+Model = require("backbone-thin").Model;
+
+module.exports = Colorator = Model.extend({
+  defaults: {
+    scheme: "taylor",
+    colorBackground: true,
+    showLowerCase: true,
+    opacity: 0.6
+  }
+});
+
+
+
+},{"backbone-thin":5}],64:[function(require,module,exports){
+var Columns, Model, consenus, _;
+
+Model = require("backbone-thin").Model;
+
+consenus = require("../algo/ConsensusCalc");
+
+_ = require("underscore");
+
+module.exports = Columns = Model.extend({
+  defaults: {
+    scaling: "lin"
+  },
+  initialize: function() {
+    if (this.get("hidden") == null) {
+      return this.set("hidden", []);
+    }
+  },
+  calcHiddenColumns: function(n) {
+    var hidden, i, newX, _i, _len;
+    hidden = this.get("hidden");
+    newX = n;
+    for (_i = 0, _len = hidden.length; _i < _len; _i++) {
+      i = hidden[_i];
+      if (i <= newX) {
+        newX++;
+      }
+    }
+    return newX - n;
+  },
+  _calcConservationPre: function(seqs) {
+    var cons, matches, nMax, total;
+    console.log(seqs.length);
+    if (seqs.length > 1000) {
+      return;
+    }
+    cons = consenus(seqs);
+    seqs = seqs.map(function(el) {
+      return el.get("seq");
+    });
+    nMax = (_.max(seqs, function(el) {
+      return el.length;
+    })).length;
+    total = new Array(nMax);
+    matches = new Array(nMax);
+    _.each(seqs, function(el, i) {
+      return _.each(el, function(char, pos) {
+        total[pos] = total[pos] + 1 || 1;
+        if (cons[pos] === char) {
+          return matches[pos] = matches[pos] + 1 || 1;
+        }
+      });
+    });
+    return [matches, total, nMax];
+  },
+  calcConservation: function(seqs) {
+    if (this.attributes.scaling === "exp") {
+      return this.calcConservationExp(seqs);
+    } else if (this.attributes.scaling === "log") {
+      return this.calcConservationLog(seqs);
+    } else if (this.attributes.scaling === "lin") {
+      return this.calcConservationLin(seqs);
+    }
+  },
+  calcConservationLin: function(seqs) {
+    var i, matches, nMax, total, _i, _ref, _ref1;
+    _ref = this._calcConservationPre(seqs), matches = _ref[0], total = _ref[1], nMax = _ref[2];
+    for (i = _i = 0, _ref1 = nMax - 1; 0 <= _ref1 ? _i <= _ref1 : _i >= _ref1; i = 0 <= _ref1 ? ++_i : --_i) {
+      matches[i] = matches[i] / total[i];
+    }
+    this.set("conserv", matches);
+    return matches;
+  },
+  calcConservationLog: function(seqs) {
+    var i, matches, nMax, total, _i, _ref, _ref1;
+    _ref = this._calcConservationPre(seqs), matches = _ref[0], total = _ref[1], nMax = _ref[2];
+    for (i = _i = 0, _ref1 = nMax - 1; 0 <= _ref1 ? _i <= _ref1 : _i >= _ref1; i = 0 <= _ref1 ? ++_i : --_i) {
+      matches[i] = Math.log(matches[i] + 1) / Math.log(total[i] + 1);
+    }
+    this.set("conserv", matches);
+    return matches;
+  },
+  calcConservationExp: function(seqs) {
+    var i, matches, nMax, total, _i, _ref, _ref1;
+    _ref = this._calcConservationPre(seqs), matches = _ref[0], total = _ref[1], nMax = _ref[2];
+    for (i = _i = 0, _ref1 = nMax - 1; 0 <= _ref1 ? _i <= _ref1 : _i >= _ref1; i = 0 <= _ref1 ? ++_i : --_i) {
+      matches[i] = Math.exp(matches[i] + 1) / Math.exp(total[i] + 1);
+    }
+    this.set("conserv", matches);
+    return matches;
+  }
+});
+
+
+
+},{"../algo/ConsensusCalc":60,"backbone-thin":5,"underscore":59}],65:[function(require,module,exports){
+var Config, Model;
+
+Model = require("backbone-thin").Model;
+
+module.exports = Config = Model.extend({
+  defaults: {
+    registerMouseHover: false,
+    registerMouseClicks: true,
+    importProxy: "https://cors-anywhere.herokuapp.com/",
+    eventBus: true
+  }
+});
+
+
+
+},{"backbone-thin":5}],66:[function(require,module,exports){
+var Consenus, Model, consenusCalc;
+
+Model = require("backbone-thin").Model;
+
+consenusCalc = require("../algo/ConsensusCalc");
+
+module.exports = Consenus = Model.extend({
+  defaults: {
+    consenus: ""
+  },
+  getConsensus: function(seqs) {
+    var cons;
+    if (seqs.length > 1000) {
+      return;
+    }
+    cons = consenusCalc(seqs);
+    this.set("consenus", cons);
+    return cons;
+  }
+});
+
+
+
+},{"../algo/ConsensusCalc":60,"backbone-thin":5}],67:[function(require,module,exports){
+var ColumnSelection, Model, PosSelection, RowSelection, Selection, _;
+
+_ = require("underscore");
+
+Model = require("backbone-thin").Model;
+
+Selection = Model.extend({
+  defaults: {
+    type: "super"
+  }
+});
+
+RowSelection = Selection.extend({
+  defaults: _.extend({}, Selection.prototype.defaults, {
+    type: "row",
+    seqId: ""
+  }),
+  inRow: function(seqId) {
+    return seqId === this.get("seqId");
+  },
+  inColumn: function(rowPos) {
+    return true;
+  },
+  getLength: function() {
+    return 1;
+  }
+});
+
+ColumnSelection = Selection.extend({
+  defaults: _.extend({}, Selection.prototype.defaults, {
+    type: "column",
+    xStart: -1,
+    xEnd: -1
+  }),
+  inRow: function() {
+    return true;
+  },
+  inColumn: function(rowPos) {
+    return xStart <= rowPos && rowPos <= xEnd;
+  },
+  getLength: function() {
+    return xEnd - xStart;
+  }
+});
+
+PosSelection = RowSelection.extend(_.extend({}, _.pick(ColumnSelection, "inColumn"), _.pick(ColumnSelection, "getLength"), {
+  defaults: _.extend({}, ColumnSelection.prototype.defaults, RowSelection.prototype.defaults, {
+    type: "pos"
+  })
+}));
+
+module.exports.sel = Selection;
+
+module.exports.possel = PosSelection;
+
+module.exports.rowsel = RowSelection;
+
+module.exports.columnsel = ColumnSelection;
+
+
+
+},{"backbone-thin":5,"underscore":59}],68:[function(require,module,exports){
+var Collection, SelectionManager, sel, _;
+
+sel = require("./Selection");
+
+_ = require("underscore");
+
+Collection = require("backbone-thin").Collection;
+
+module.exports = SelectionManager = Collection.extend({
+  model: sel.sel,
+  initialize: function(data, opts) {
+    this.g = opts.g;
+    this.listenTo(this.g, "residue:click", function(e) {
+      return this._handleE(e.evt, new sel.possel({
+        xStart: e.rowPos,
+        xEnd: e.rowPos,
+        seqId: e.seqId
+      }));
+    });
+    this.listenTo(this.g, "row:click", function(e) {
+      return this._handleE(e.evt, new sel.rowsel({
+        xStart: e.rowPos,
+        xEnd: e.rowPos,
+        seqId: e.seqId
+      }));
+    });
+    return this.listenTo(this.g, "column:click", function(e) {
+      return this._handleE(e.evt, new sel.columnsel({
+        xStart: e.rowPos,
+        xEnd: e.rowPos + e.stepSize - 1
+      }));
+    });
+  },
+  getSelForRow: function(seqId) {
+    return this.filter(function(el) {
+      return el.inRow(seqId);
+    });
+  },
+  getSelForColumns: function(rowPos) {
+    return this.filter(function(el) {
+      return el.inColumn(rowPos);
+    });
+  },
+  getBlocksForRow: function(seqId, maxLen) {
+    var blocks, seli, selis, _i, _j, _k, _len, _ref, _ref1, _results, _results1;
+    selis = this.filter(function(el) {
+      return el.inRow(seqId);
+    });
+    blocks = [];
+    for (_i = 0, _len = selis.length; _i < _len; _i++) {
+      seli = selis[_i];
+      if (seli.attributes.type === "row") {
+        blocks = (function() {
+          _results = [];
+          for (var _j = 0; 0 <= maxLen ? _j <= maxLen : _j >= maxLen; 0 <= maxLen ? _j++ : _j--){ _results.push(_j); }
+          return _results;
+        }).apply(this);
+        break;
+      } else {
+        blocks = blocks.concat((function() {
+          _results1 = [];
+          for (var _k = _ref = seli.attributes.xStart, _ref1 = seli.attributes.xEnd; _ref <= _ref1 ? _k <= _ref1 : _k >= _ref1; _ref <= _ref1 ? _k++ : _k--){ _results1.push(_k); }
+          return _results1;
+        }).apply(this));
+      }
+    }
+    return blocks;
+  },
+  getAllColumnBlocks: function(conf) {
+    var blocks, filtered, maxLen, seli, withPos, _i, _j, _len, _ref, _ref1, _results;
+    maxLen = conf.maxLen;
+    withPos = conf.withPos;
+    blocks = [];
+    if (conf.withPos) {
+      filtered = this.filter(function(el) {
+        return el.get('xStart') != null;
+      });
+    } else {
+      filtered = this.filter(function(el) {
+        return el.get('type') === "column";
+      });
+    }
+    for (_i = 0, _len = filtered.length; _i < _len; _i++) {
+      seli = filtered[_i];
+      blocks = blocks.concat((function() {
+        _results = [];
+        for (var _j = _ref = seli.attributes.xStart, _ref1 = seli.attributes.xEnd; _ref <= _ref1 ? _j <= _ref1 : _j >= _ref1; _ref <= _ref1 ? _j++ : _j--){ _results.push(_j); }
+        return _results;
+      }).apply(this));
+    }
+    blocks = _.uniq(blocks);
+    return blocks;
+  },
+  invertRow: function(rows) {
+    var el, inverted, s, selRows, _i, _len;
+    selRows = this.where({
+      type: "row"
+    });
+    selRows = _.map(selRows, function(el) {
+      return el.attributes.seqId;
+    });
+    inverted = _.filter(rows, function(el) {
+      if (selRows.indexOf(el) >= 0) {
+        return false;
+      }
+      return true;
+    });
+    s = [];
+    for (_i = 0, _len = inverted.length; _i < _len; _i++) {
+      el = inverted[_i];
+      s.push(new sel.rowsel({
+        seqId: el
+      }));
+    }
+    console.log(s);
+    return this.reset(s);
+  },
+  invertCol: function(columns) {
+    var el, inverted, s, selColumns, xEnd, xStart, _i, _len;
+    selColumns = this.where({
+      type: "column"
+    });
+    selColumns = _.reduce(selColumns, function(memo, el) {
+      var _i, _ref, _ref1, _results;
+      return memo.concat((function() {
+        _results = [];
+        for (var _i = _ref = el.attributes.xStart, _ref1 = el.attributes.xEnd; _ref <= _ref1 ? _i <= _ref1 : _i >= _ref1; _ref <= _ref1 ? _i++ : _i--){ _results.push(_i); }
+        return _results;
+      }).apply(this));
+    }, []);
+    inverted = _.filter(columns, function(el) {
+      if (selColumns.indexOf(el) >= 0) {
+        return false;
+      }
+      return true;
+    });
+    if (inverted.length === 0) {
+      return;
+    }
+    s = [];
+    console.log(inverted);
+    xStart = xEnd = inverted[0];
+    for (_i = 0, _len = inverted.length; _i < _len; _i++) {
+      el = inverted[_i];
+      if (xEnd + 1 === el) {
+        xEnd = el;
+      } else {
+        s.push(new sel.columnsel({
+          xStart: xStart,
+          xEnd: xEnd
+        }));
+        xStart = xEnd = el;
+      }
+    }
+    if (xStart !== xEnd) {
+      s.push(new sel.columnsel({
+        xStart: xStart,
+        xEnd: inverted[inverted.length - 1]
+      }));
+    }
+    return this.reset(s);
+  },
+  _handleE: function(e, selection) {
+    if (e.ctrlKey || e.metaKey) {
+      return this.add(selection);
+    } else {
+      return this.reset([selection]);
+    }
+  },
+  _reduceColumns: function() {
+    return this.each(function(el, index, arr) {
+      var cols, left, lefts, right, rights, xEnd, xStart, _i, _j, _len, _len1;
+      cols = _.filter(arr, function(el) {
+        return el.get('type') === 'column';
+      });
+      xStart = el.get('xStart');
+      xEnd = el.get('xEnd');
+      lefts = _.filter(cols, function(el) {
+        return el.get('xEnd') === (xStart - 1);
+      });
+      for (_i = 0, _len = lefts.length; _i < _len; _i++) {
+        left = lefts[_i];
+        left.set('xEnd', xStart);
+      }
+      rights = _.filter(cols, function(el) {
+        return el.get('xStart') === (xEnd + 1);
+      });
+      for (_j = 0, _len1 = rights.length; _j < _len1; _j++) {
+        right = rights[_j];
+        right.set('xStart', xEnd);
+      }
+      if (lefts.length > 0 || rights.length > 0) {
+        console.log("removed el");
+        return el.collection.remove(el);
+      }
+    });
+  }
+});
+
+
+
+},{"./Selection":67,"backbone-thin":5,"underscore":59}],69:[function(require,module,exports){
+var Model, Visibility;
+
+Model = require("backbone-thin").Model;
+
+module.exports = Visibility = Model.extend({
+  defaults: {
+    overviewBox: 30,
+    headerBox: -1,
+    alignmentBody: 0
+  }
+});
+
+
+
+},{"backbone-thin":5}],70:[function(require,module,exports){
+var Model, Visibility;
+
+Model = require("backbone-thin").Model;
+
+module.exports = Visibility = Model.extend({
+  defaults: {
+    sequences: true,
+    markers: true,
+    metacell: false,
+    conserv: true,
+    overviewbox: false,
+    labels: true,
+    labelName: true,
+    labelId: true,
+    labelPartition: false,
+    labelCheckbox: false
+  }
+});
+
+
+
+},{"backbone-thin":5}],71:[function(require,module,exports){
+var Model, Zoomer;
+
+Model = require("backbone-thin").Model;
+
+module.exports = Zoomer = Model.extend({
+  constructor: function(attributes, options) {
+    Model.apply(this, arguments);
+    this.g = options.g;
+    return this;
+  },
+  defaults: {
+    alignmentWidth: "auto",
+    alignmentHeight: 195,
+    columnWidth: 15,
+    rowHeight: 15,
+    labelWidth: 100,
+    metaWidth: 100,
+    textVisible: true,
+    labelIdLength: 30,
+    labelFontsize: "13px",
+    labelLineHeight: "13px",
+    markerFontsize: "10px",
+    stepSize: 1,
+    markerStepSize: 2,
+    residueFont: "13px mono",
+    canvasEventScale: 1,
+    boxRectHeight: 5,
+    boxRectWidth: 5,
+    menuFontsize: "20px",
+    menuItemFontsize: "18px",
+    menuItemLineHeight: "18px",
+    menuMarginLeft: "5px",
+    menuPadding: "3px 5px 3px 5px",
+    _alignmentScrollLeft: 0,
+    _alignmentScrollTop: 0
+  },
+  getAlignmentWidth: function(n) {
+    if (this.get("alignmentWidth") === "auto") {
+      return this.get("columnWidth") * n;
+    } else {
+      return this.get("alignmentWidth");
+    }
+  },
+  setLeftOffset: function(n) {
+    var val;
+    val = (n - 1) * this.get('columnWidth');
+    val = Math.max(0, val);
+    return this.set("_alignmentScrollLeft", val);
+  },
+  setTopOffset: function(n) {
+    var val;
+    val = (n - 1) * this.get('rowHeight');
+    val = Math.max(0, val);
+    return this.set("_alignmentScrollTop", val);
+  },
+  getLabelWidth: function() {
+    var paddingLeft;
+    paddingLeft = 0;
+    if (this.g.vis.get("labels")) {
+      paddingLeft += this.get("labelWidth");
+    }
+    if (this.g.vis.get("metacell")) {
+      paddingLeft += this.get("metaWidth");
+    }
+    return paddingLeft;
+  },
+  _adjustWidth: function(el, model) {
+    var calcWidth, maxWidth, parentWidth, val;
+    if ((el.parentNode != null) && el.parentNode.offsetWidth !== 0) {
+      parentWidth = el.parentNode.offsetWidth;
+    } else {
+      parentWidth = document.body.clientWidth - 35;
+    }
+    maxWidth = parentWidth - this.getLabelWidth();
+    calcWidth = this.getAlignmentWidth(model.getMaxLength() - this.g.columns.get('hidden').length);
+    val = Math.min(maxWidth, calcWidth);
+    val = Math.floor(val / this.get("columnWidth")) * this.get("columnWidth");
+    return this.set("alignmentWidth", val);
+  },
+  _checkScrolling: function(scrollObj, opts) {
+    var xScroll, yScroll;
+    xScroll = scrollObj[0];
+    yScroll = scrollObj[1];
+    this.set("_alignmentScrollLeft", xScroll, opts);
+    return this.set("_alignmentScrollTop", yScroll, opts);
+  }
+});
+
+
+
+},{"backbone-thin":5}],72:[function(require,module,exports){
+module.exports.msa = require("./msa");
+
+module.exports.model = require("./model");
+
+module.exports.algo = require("./algo");
+
+module.exports.menu = require("./menu");
+
+module.exports.utils = require("./utils");
+
+module.exports.selection = require("./g/selection/Selection");
+
+module.exports.view = require("backbone-viewj");
+
+module.exports.boneView = require("backbone-childs");
+
+module.exports._ = require('underscore');
+
+module.exports.$ = require('jbone');
+
+module.exports.version = "0.1.0";
+
+
+
+},{"./algo":62,"./g/selection/Selection":67,"./menu":74,"./model":89,"./msa":90,"./utils":92,"backbone-childs":3,"backbone-viewj":10,"jbone":50,"underscore":59}],73:[function(require,module,exports){
+var ColorMenu, ExportMenu, ExtraMenu, FilterMenu, HelpMenu, ImportMenu, MenuView, OrderingMenu, SelectionMenu, VisMenu, boneView;
+
+boneView = require("backbone-childs");
+
+ImportMenu = require("./views/ImportMenu");
+
+FilterMenu = require("./views/FilterMenu");
+
+SelectionMenu = require("./views/SelectionMenu");
+
+VisMenu = require("./views/VisMenu");
+
+ColorMenu = require("./views/ColorMenu");
+
+OrderingMenu = require("./views/OrderingMenu");
+
+ExtraMenu = require("./views/ExtraMenu");
+
+ExportMenu = require("./views/ExportMenu");
+
+HelpMenu = require("./views/HelpMenu");
+
+module.exports = MenuView = boneView.extend({
+  initialize: function(data) {
+    this.msa = data.msa;
+    this.addView("10_import", new ImportMenu({
+      model: this.msa.seqs,
+      g: this.msa.g
+    }));
+    this.addView("20_filter", new FilterMenu({
+      model: this.msa.seqs,
+      g: this.msa.g
+    }));
+    this.addView("30_selection", new SelectionMenu({
+      model: this.msa.seqs,
+      g: this.msa.g
+    }));
+    this.addView("40_vis", new VisMenu({
+      model: this.msa.seqs,
+      g: this.msa.g
+    }));
+    this.addView("50_color", new ColorMenu({
+      model: this.msa.seqs,
+      g: this.msa.g
+    }));
+    this.addView("60_ordering", new OrderingMenu({
+      model: this.msa.seqs,
+      g: this.msa.g
+    }));
+    this.addView("70_extra", new ExtraMenu({
+      model: this.msa.seqs,
+      g: this.msa.g
+    }));
+    this.addView("80_export", new ExportMenu({
+      model: this.msa.seqs,
+      g: this.msa.g,
+      msa: this.msa
+    }));
+    return this.addView("90_help", new HelpMenu({
+      g: this.msa.g
+    }));
+  },
+  render: function() {
+    this.renderSubviews();
+    this.el.setAttribute("class", "biojs_msa_menubar");
+    return this.el.appendChild(document.createElement("p"));
+  }
+});
+
+
+
+},{"./views/ColorMenu":76,"./views/ExportMenu":77,"./views/ExtraMenu":78,"./views/FilterMenu":79,"./views/HelpMenu":80,"./views/ImportMenu":81,"./views/OrderingMenu":82,"./views/SelectionMenu":83,"./views/VisMenu":84,"backbone-childs":3}],74:[function(require,module,exports){
+module.exports.defaultmenu = require("./defaultmenu");
+
+module.exports.menubuilder = require("./menubuilder");
+
+
+
+},{"./defaultmenu":73,"./menubuilder":75}],75:[function(require,module,exports){
+var BMath, MenuBuilder, jbone, view;
+
+BMath = require("../utils/bmath");
+
+jbone = require("jbone");
+
+view = require("backbone-viewj");
+
+module.exports = MenuBuilder = view.extend({
+  setName: function(name) {
+    this.name = name;
+    return this._nodes = [];
+  },
+  addNode: function(label, callback, data) {
+    var style;
+    if (data != null) {
+      style = data.style;
+    }
+    if (this._nodes == null) {
+      this._nodes = [];
+    }
+    return this._nodes.push({
+      label: label,
+      callback: callback,
+      style: style
+    });
+  },
+  buildDOM: function() {
+    return this._buildM({
+      nodes: this._nodes,
+      name: this.name
+    });
+  },
+  _buildM: function(data) {
+    var displayedButton, frag, key, li, menu, menuUl, name, node, nodes, style, _i, _len, _ref;
+    nodes = data.nodes;
+    name = data.name;
+    menu = document.createElement("div");
+    menu.className = "dropdown dropdown-tip";
+    menu.id = "adrop-" + BMath.uniqueId();
+    menu.style.display = "none";
+    menuUl = document.createElement("ul");
+    menuUl.className = "dropdown-menu";
+    for (_i = 0, _len = nodes.length; _i < _len; _i++) {
+      node = nodes[_i];
+      li = document.createElement("li");
+      li.textContent = node.label;
+      _ref = node.style;
+      for (key in _ref) {
+        style = _ref[key];
+        li.style[key] = style;
+      }
+      li.addEventListener("click", node.callback);
+      if (this.g != null) {
+        li.style.lineHeight = this.g.zoomer.get("menuItemLineHeight");
+      }
+      menuUl.appendChild(li);
+    }
+    menu.appendChild(menuUl);
+    frag = document.createDocumentFragment();
+    displayedButton = document.createElement("a");
+    displayedButton.textContent = name;
+    displayedButton.className = "biojs_msa_menubar_alink";
+    if (this.g != null) {
+      menuUl.style.fontSize = this.g.zoomer.get("menuItemFontsize");
+      displayedButton.style.fontSize = this.g.zoomer.get("menuFontsize");
+      displayedButton.style.marginLeft = this.g.zoomer.get("menuMarginLeft");
+      displayedButton.style.padding = this.g.zoomer.get("menuPadding");
+    }
+    jbone(displayedButton).on("click", (function(_this) {
+      return function(e) {
+        _this._showMenu(e, menu, displayedButton);
+        return window.setTimeout(function() {
+          return jbone(document.body).one("click", function(e) {
+            console.log("next click");
+            return menu.style.display = "none";
+          });
+        }, 5);
+      };
+    })(this));
+    frag.appendChild(menu);
+    frag.appendChild(displayedButton);
+    return frag;
+  },
+  _showMenu: function(e, menu, target) {
+    var rect;
+    menu.style.display = "block";
+    menu.style.position = "absolute";
+    rect = target.getBoundingClientRect();
+    menu.style.left = rect.left + "px";
+    return menu.style.top = (rect.top + target.offsetHeight) + "px";
+  }
+});
+
+
+
+},{"../utils/bmath":91,"backbone-viewj":10,"jbone":50}],76:[function(require,module,exports){
+var ColorMenu, MenuBuilder, dom, _;
+
+MenuBuilder = require("../menubuilder");
+
+_ = require("underscore");
+
+dom = require("dom-helper");
+
+module.exports = ColorMenu = MenuBuilder.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    this.el.style.display = "inline-block";
+    return this.listenTo(this.g.colorscheme, "change", function() {
+      return this.render();
+    });
+  },
+  render: function() {
+    var colorschemes, menuColor, scheme, text, _i, _len;
+    menuColor = this.setName("Color scheme");
+    colorschemes = this.getColorschemes();
+    for (_i = 0, _len = colorschemes.length; _i < _len; _i++) {
+      scheme = colorschemes[_i];
+      this.addScheme(menuColor, scheme);
+    }
+    text = "Background";
+    if (this.g.colorscheme.get("colorBackground")) {
+      text = "Hide " + text;
+    } else {
+      text = "Show " + text;
+    }
+    this.addNode(text, (function(_this) {
+      return function() {
+        return _this.g.colorscheme.set("colorBackground", !_this.g.colorscheme.get("colorBackground"));
+      };
+    })(this));
+    this.grey(menuColor);
+    dom.removeAllChilds(this.el);
+    this.el.appendChild(this.buildDOM());
+    return this;
+  },
+  addScheme: function(menuColor, scheme) {
+    var current, style;
+    style = {};
+    current = this.g.colorscheme.get("scheme");
+    if (current === scheme.id) {
+      style.backgroundColor = "#77ED80";
+    }
+    return this.addNode(scheme.name, (function(_this) {
+      return function() {
+        return _this.g.colorscheme.set("scheme", scheme.id);
+      };
+    })(this), {
+      style: style
+    });
+  },
+  getColorschemes: function() {
+    var schemes;
+    schemes = [];
+    schemes.push({
+      name: "Zappo",
+      id: "zappo"
+    });
+    schemes.push({
+      name: "Taylor",
+      id: "taylor"
+    });
+    schemes.push({
+      name: "Hydrophobicity",
+      id: "hydro"
+    });
+    schemes.push({
+      name: "Lesk",
+      id: "lesk"
+    });
+    schemes.push({
+      name: "Cinema",
+      id: "cinema"
+    });
+    schemes.push({
+      name: "MAE",
+      id: "mae"
+    });
+    schemes.push({
+      name: "Clustal",
+      id: "clustal"
+    });
+    schemes.push({
+      name: "Clustal2",
+      id: "clustal2"
+    });
+    schemes.push({
+      name: "Turn",
+      id: "turn"
+    });
+    schemes.push({
+      name: "Strand",
+      id: "strand"
+    });
+    schemes.push({
+      name: "Buried",
+      id: "buried"
+    });
+    schemes.push({
+      name: "Helix",
+      id: "helix"
+    });
+    schemes.push({
+      name: "Nucleotide",
+      id: "nucleotide"
+    });
+    schemes.push({
+      name: "Purine",
+      id: "purine"
+    });
+    schemes.push({
+      name: "PID",
+      id: "pid"
+    });
+    schemes.push({
+      name: "No color",
+      id: "foo"
+    });
+    return schemes;
+  },
+  grey: function(menuColor) {
+    this.addNode("Grey", (function(_this) {
+      return function() {
+        _this.g.colorscheme.set("showLowerCase", false);
+        return _this.model.each(function(seq) {
+          var grey, residues;
+          residues = seq.get("seq");
+          grey = [];
+          _.each(residues, function(el, index) {
+            if (el === el.toLowerCase()) {
+              return grey.push(index);
+            }
+          });
+          return seq.set("grey", grey);
+        });
+      };
+    })(this));
+    this.addNode("Grey by threshold", (function(_this) {
+      return function() {
+        var conserv, grey, i, maxLen, threshold, _i, _ref;
+        threshold = prompt("Enter threshold (in percent)", 20);
+        threshold = threshold / 100;
+        maxLen = _this.model.getMaxLength();
+        conserv = _this.g.columns.get("conserv");
+        grey = [];
+        for (i = _i = 0, _ref = maxLen - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
+          console.log(conserv[i]);
+          if (conserv[i] < threshold) {
+            grey.push(i);
+          }
+        }
+        return _this.model.each(function(seq) {
+          return seq.set("grey", grey);
+        });
+      };
+    })(this));
+    this.addNode("Grey selection", (function(_this) {
+      return function() {
+        var maxLen;
+        maxLen = _this.model.getMaxLength();
+        return _this.model.each(function(seq) {
+          var blocks;
+          blocks = _this.g.selcol.getBlocksForRow(seq.get("id"), maxLen);
+          return seq.set("grey", blocks);
+        });
+      };
+    })(this));
+    return this.addNode("Reset grey", (function(_this) {
+      return function() {
+        _this.g.colorscheme.set("showLowerCase", true);
+        return _this.model.each(function(seq) {
+          return seq.set("grey", []);
+        });
+      };
+    })(this));
+  }
+});
+
+
+
+},{"../menubuilder":75,"dom-helper":49,"underscore":59}],77:[function(require,module,exports){
+var ExportMenu, FastaExporter, MenuBuilder, blobURL, saveAs, _;
+
+MenuBuilder = require("../menubuilder");
+
+saveAs = require("browser-saveas");
+
+FastaExporter = require("biojs-io-fasta").writer;
+
+_ = require("underscore");
+
+blobURL = require("blueimp_canvastoblob");
+
+module.exports = ExportMenu = MenuBuilder.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    this.msa = data.msa;
+    return this.el.style.display = "inline-block";
+  },
+  render: function() {
+    this.setName("Export");
+    this.addNode("Export sequences", (function(_this) {
+      return function() {
+        var blob, text;
+        text = FastaExporter["export"](_this.model.toJSON());
+        blob = new Blob([text], {
+          type: 'text/plain'
+        });
+        return saveAs(blob, "all.fasta");
+      };
+    })(this));
+    this.addNode("Export selection", (function(_this) {
+      return function() {
+        var blob, i, selection, text, _i, _ref;
+        selection = _this.g.selcol.pluck("seqId");
+        if (selection != null) {
+          selection = _this.model.filter(function(el) {
+            return _.contains(selection, el.get("id"));
+          });
+          for (i = _i = 0, _ref = selection.length - 1; _i <= _ref; i = _i += 1) {
+            selection[i] = selection[i].toJSON();
+          }
+        } else {
+          selection = _this.model.toJSON();
+          console.log("no selection found");
+        }
+        text = FastaExporter["export"](selection);
+        blob = new Blob([text], {
+          type: 'text/plain'
+        });
+        return saveAs(blob, "selection.fasta");
+      };
+    })(this));
+    this.addNode("Export image", (function(_this) {
+      return function() {
+        var canvas, url;
+        canvas = _this.msa.getView('stage').getView('body').getView('seqblock').el;
+        if (canvas != null) {
+          url = canvas.toDataURL('image/png');
+          return saveAs(blobURL(url), "biojs-msa.png", "image/png");
+        }
+      };
+    })(this));
+    this.el.appendChild(this.buildDOM());
+    return this;
+  }
+});
+
+
+
+},{"../menubuilder":75,"biojs-io-fasta":undefined,"blueimp_canvastoblob":46,"browser-saveas":47,"underscore":59}],78:[function(require,module,exports){
+var ExtraMenu, MenuBuilder, Seq, consenus;
+
+MenuBuilder = require("../menubuilder");
+
+consenus = require("../../algo/ConsensusCalc");
+
+Seq = require("../../model/Sequence");
+
+module.exports = ExtraMenu = MenuBuilder.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    return this.el.style.display = "inline-block";
+  },
+  render: function() {
+    this.setName("Extras");
+    this.addNode("Add consensus seq", (function(_this) {
+      return function() {
+        var con, seq;
+        con = consenus(_this.model);
+        console.log(con);
+        seq = new Seq({
+          seq: con,
+          id: "0c",
+          name: "consenus"
+        });
+        _this.model.add(seq);
+        _this.model.comparator = function(seq) {
+          return seq.get("id");
+        };
+        return _this.model.sort();
+      };
+    })(this));
+    this.addNode("Increase font size", (function(_this) {
+      return function() {
+        _this.g.zoomer.set("columnWidth", _this.g.zoomer.get("columnWidth") + 2);
+        _this.g.zoomer.set("labelWidth", _this.g.zoomer.get("columnWidth") + 5);
+        _this.g.zoomer.set("rowHeight", _this.g.zoomer.get("rowHeight") + 2);
+        return _this.g.zoomer.set("labelFontSize", _this.g.zoomer.get("labelFontSize") + 2);
+      };
+    })(this));
+    this.addNode("Decrease font size", (function(_this) {
+      return function() {
+        _this.g.zoomer.set("columnWidth", _this.g.zoomer.get("columnWidth") - 2);
+        _this.g.zoomer.set("rowHeight", _this.g.zoomer.get("rowHeight") - 2);
+        _this.g.zoomer.set("labelFontSize", _this.g.zoomer.get("labelFontSize") - 2);
+        if (_this.g.zoomer.get("columnWidth") < 8) {
+          return _this.g.zoomer.set("textVisible", false);
+        }
+      };
+    })(this));
+    this.addNode("Bar chart exp scaling", (function(_this) {
+      return function() {
+        return _this.g.columns.set("scaling", "exp");
+      };
+    })(this));
+    this.addNode("Bar chart linear scaling", (function(_this) {
+      return function() {
+        return _this.g.columns.set("scaling", "lin");
+      };
+    })(this));
+    this.addNode("Bar chart log scaling", (function(_this) {
+      return function() {
+        return _this.g.columns.set("scaling", "log");
+      };
+    })(this));
+    this.addNode("Minimized width", (function(_this) {
+      return function() {
+        return _this.g.zoomer.set("alignmentWidth", 600);
+      };
+    })(this));
+    this.addNode("Minimized height", (function(_this) {
+      return function() {
+        return _this.g.zoomer.set("alignmentHeight", 120);
+      };
+    })(this));
+    this.addNode("Jump to a column", (function(_this) {
+      return function() {
+        var offset;
+        offset = prompt("Column", "20");
+        if (offset < 0 || offset > _this.model.getMaxLength() || isNaN(offset)) {
+          alert("invalid column");
+          return;
+        }
+        return _this.g.zoomer.setLeftOffset(offset);
+      };
+    })(this));
+    this.el.appendChild(this.buildDOM());
+    return this;
+  }
+});
+
+
+
+},{"../../algo/ConsensusCalc":60,"../../model/Sequence":88,"../menubuilder":75}],79:[function(require,module,exports){
+var FilterMenu, MenuBuilder, _;
+
+MenuBuilder = require("../menubuilder");
+
+_ = require("underscore");
+
+module.exports = FilterMenu = MenuBuilder.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    return this.el.style.display = "inline-block";
+  },
+  render: function() {
+    this.setName("Filter");
+    this.addNode("Hide columns by threshold", (function(_this) {
+      return function(e) {
+        var conserv, hidden, i, maxLen, threshold, _i, _ref;
+        threshold = prompt("Enter threshold (in percent)", 20);
+        threshold = threshold / 100;
+        maxLen = _this.model.getMaxLength();
+        hidden = [];
+        conserv = _this.g.columns.get("conserv");
+        for (i = _i = 0, _ref = maxLen - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
+          if (conserv[i] < threshold) {
+            hidden.push(i);
+          }
+        }
+        return _this.g.columns.set("hidden", hidden);
+      };
+    })(this));
+    this.addNode("Hide columns by selection", (function(_this) {
+      return function() {
+        var hidden, hiddenOld;
+        hiddenOld = _this.g.columns.get("hidden");
+        hidden = hiddenOld.concat(_this.g.selcol.getAllColumnBlocks({
+          maxLen: _this.model.getMaxLength(),
+          withPos: true
+        }));
+        _this.g.selcol.reset([]);
+        return _this.g.columns.set("hidden", hidden);
+      };
+    })(this));
+    this.addNode("Hide columns by gaps", (function(_this) {
+      return function() {
+        var gapContent, gaps, hidden, i, maxLen, threshold, total, _i, _ref;
+        threshold = prompt("Enter threshold (in percent)", 20);
+        threshold = threshold / 100;
+        maxLen = _this.model.getMaxLength();
+        hidden = [];
+        for (i = _i = 0, _ref = maxLen - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
+          gaps = 0;
+          total = 0;
+          _this.model.each(function(el) {
+            if (el.get('seq')[i] === "-") {
+              gaps++;
+            }
+            return total++;
+          });
+          gapContent = gaps / total;
+          if (gapContent > threshold) {
+            hidden.push(i);
+          }
+        }
+        return _this.g.columns.set("hidden", hidden);
+      };
+    })(this));
+    this.addNode("Hide seqs by identity", (function(_this) {
+      return function() {
+        var threshold;
+        threshold = prompt("Enter threshold (in percent)", 20);
+        threshold = threshold / 100;
+        return _this.model.each(function(el) {
+          if (el.get('identity') < threshold) {
+            return el.set('hidden', true);
+          }
+        });
+      };
+    })(this));
+    this.addNode("Hide seqs by selection", (function(_this) {
+      return function() {
+        var hidden, ids;
+        hidden = _this.g.selcol.where({
+          type: "row"
+        });
+        ids = _.map(hidden, function(el) {
+          return el.get('seqId');
+        });
+        _this.g.selcol.reset([]);
+        return _this.model.each(function(el) {
+          if (ids.indexOf(el.get('id')) >= 0) {
+            return el.set('hidden', true);
+          }
+        });
+      };
+    })(this));
+    this.addNode("Hide seqs by gaps", (function(_this) {
+      return function() {
+        var threshold;
+        threshold = prompt("Enter threshold (in percent)", 40);
+        return _this.model.each(function(el, i) {
+          var gaps, seq;
+          seq = el.get('seq');
+          gaps = _.reduce(seq, (function(memo, c) {
+            if (c === '-') {
+              memo++;
+            }
+            return memo;
+          }), 0);
+          console.log(gaps);
+          if (gaps > threshold) {
+            return el.set('hidden', true);
+          }
+        });
+      };
+    })(this));
+    this.addNode("Reset", (function(_this) {
+      return function() {
+        _this.g.columns.set("hidden", []);
+        return _this.model.each(function(el) {
+          if (el.get('hidden')) {
+            return el.set('hidden', false);
+          }
+        });
+      };
+    })(this));
+    this.el.appendChild(this.buildDOM());
+    return this;
+  }
+});
+
+
+
+},{"../menubuilder":75,"underscore":59}],80:[function(require,module,exports){
+var HelpMenu, MenuBuilder;
+
+MenuBuilder = require("../menubuilder");
+
+module.exports = HelpMenu = MenuBuilder.extend({
+  initialize: function(data) {
+    return this.g = data.g;
+  },
+  render: function() {
+    this.setName("Help");
+    this.addNode("About the project", (function(_this) {
+      return function() {
+        return window.open("https://github.com/greenify/biojs-vis-msa");
+      };
+    })(this));
+    this.addNode("Report issues", (function(_this) {
+      return function() {
+        return window.open("https://github.com/greenify/biojs-vis-msa/issues");
+      };
+    })(this));
+    this.addNode("User manual", (function(_this) {
+      return function() {
+        return window.open("https://github.com/greenify/biojs-vis-msa/wiki");
+      };
+    })(this));
+    this.el.style.display = "inline-block";
+    this.el.appendChild(this.buildDOM());
+    return this;
+  }
+});
+
+
+
+},{"../menubuilder":75}],81:[function(require,module,exports){
+var Clustal, FastaReader, ImportMenu, MenuBuilder, corsURL;
+
+Clustal = require("biojs-io-clustal");
+
+FastaReader = require("biojs-io-fasta").parse;
+
+MenuBuilder = require("../menubuilder");
+
+corsURL = require("../../utils/proxy").corsURL;
+
+module.exports = ImportMenu = MenuBuilder.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    return this.el.style.display = "inline-block";
+  },
+  render: function() {
+    this.setName("Import");
+    this.addNode("FASTA", (function(_this) {
+      return function(e) {
+        var url;
+        url = prompt("URL", "/test/dummy/samples/p53.clustalo.fasta");
+        url = corsURL(url, _this.g);
+        return FastaReader.read(url, function(seqs) {
+          var zoomer;
+          zoomer = _this.g.zoomer.toJSON();
+          zoomer.labelWidth = 200;
+          zoomer.boxRectHeight = 2;
+          zoomer.boxRectWidth = 2;
+          _this.model.reset([]);
+          _this.g.zoomer.set(zoomer);
+          _this.model.reset(seqs);
+          return _this.g.columns.calcConservation(_this.model);
+        });
+      };
+    })(this));
+    this.addNode("CLUSTAL", (function(_this) {
+      return function() {
+        var url;
+        url = prompt("URL", "/test/dummy/samples/p53.clustalo.clustal");
+        url = corsURL(url, _this.g);
+        return Clustal.read(url, function(seqs) {
+          var zoomer;
+          zoomer = _this.g.zoomer.toJSON();
+          zoomer.labelWidth = 200;
+          zoomer.boxRectHeight = 2;
+          zoomer.boxRectWidth = 2;
+          _this.model.reset([]);
+          _this.g.zoomer.set(zoomer);
+          _this.model.reset(seqs);
+          return _this.g.columns.calcConservation(_this.model);
+        });
+      };
+    })(this));
+    this.addNode("add your own Parser", (function(_this) {
+      return function() {
+        return window.open("https://github.com/biojs/biojs2");
+      };
+    })(this));
+    this.el.appendChild(this.buildDOM());
+    return this;
+  }
+});
+
+
+
+},{"../../utils/proxy":93,"../menubuilder":75,"biojs-io-clustal":undefined,"biojs-io-fasta":undefined}],82:[function(require,module,exports){
+var MenuBuilder, OrderingMenu, dom, _;
+
+MenuBuilder = require("../menubuilder");
+
+dom = require("dom-helper");
+
+_ = require('underscore');
+
+module.exports = OrderingMenu = MenuBuilder.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    this.order = "ID";
+    return this.el.style.display = "inline-block";
+  },
+  setOrder: function(order) {
+    this.order = order;
+    return this.render();
+  },
+  render: function() {
+    var comps, el, m, _i, _len;
+    this.setName("Ordering");
+    comps = this.getComparators();
+    for (_i = 0, _len = comps.length; _i < _len; _i++) {
+      m = comps[_i];
+      this._addNode(m);
+    }
+    el = this.buildDOM();
+    dom.removeAllChilds(this.el);
+    this.el.appendChild(el);
+    return this;
+  },
+  _addNode: function(m) {
+    var style, text;
+    text = m.text;
+    style = {};
+    if (text === this.order) {
+      style.backgroundColor = "#77ED80";
+    }
+    return this.addNode(text, (function(_this) {
+      return function() {
+        if (m.precode != null) {
+          m.precode();
+        }
+        _this.model.comparator = m.comparator;
+        _this.model.sort();
+        return _this.setOrder(m.text);
+      };
+    })(this), {
+      style: style
+    });
+  },
+  getComparators: function() {
+    var models;
+    models = [];
+    models.push({
+      text: "ID",
+      comparator: "id"
+    });
+    models.push({
+      text: "ID Desc",
+      comparator: function(a, b) {
+        return -a.get("id").localeCompare(b.get("id"));
+      }
+    });
+    models.push({
+      text: "Label",
+      comparator: "name"
+    });
+    models.push({
+      text: "Label Desc",
+      comparator: function(a, b) {
+        return -a.get("name").localeCompare(b.get("name"));
+      }
+    });
+    models.push({
+      text: "Seq",
+      comparator: "seq"
+    });
+    models.push({
+      text: "Seq Desc",
+      comparator: function(a, b) {
+        return -a.get("seq").localeCompare(b.get("seq"));
+      }
+    });
+    models.push({
+      text: "Identity",
+      comparator: "identity"
+    });
+    models.push({
+      text: "Identity Desc",
+      comparator: function(seq) {
+        return -seq.get("identity");
+      }
+    });
+    models.push({
+      text: "Partition codes",
+      comparator: "partition",
+      precode: (function(_this) {
+        return function() {
+          _this.g.vis.set('labelPartition', true);
+          return _this.model.each(function(el) {
+            return el.set('partition', _.random(1, 3));
+          });
+        };
+      })(this)
+    });
+    return models;
+  }
+});
+
+
+
+},{"../menubuilder":75,"dom-helper":49,"underscore":59}],83:[function(require,module,exports){
+var MenuBuilder, SelectionMenu, sel;
+
+sel = require("../../g/selection/Selection");
+
+MenuBuilder = require("../menubuilder");
+
+module.exports = SelectionMenu = MenuBuilder.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    return this.el.style.display = "inline-block";
+  },
+  render: function() {
+    this.setName("Selection");
+    this.addNode("Find Motif (supports RegEx)", (function(_this) {
+      return function() {
+        var leftestIndex, newSeli, origIndex, search, selcol;
+        search = prompt("your search", "D");
+        search = new RegExp(search, "gi");
+        selcol = _this.g.selcol;
+        newSeli = [];
+        leftestIndex = origIndex = 100042;
+        _this.model.each(function(seq) {
+          var args, index, match, strSeq, _results;
+          strSeq = seq.get("seq");
+          _results = [];
+          while (match = search.exec(strSeq)) {
+            index = match.index;
+            args = {
+              xStart: index,
+              xEnd: index + match[0].length - 1,
+              seqId: seq.get("id")
+            };
+            newSeli.push(new sel.possel(args));
+            _results.push(leftestIndex = Math.min(index, leftestIndex));
+          }
+          return _results;
+        });
+        if (newSeli.length === 0) {
+          alert("no selection found");
+        }
+        selcol.reset(newSeli);
+        if (leftestIndex === origIndex) {
+          leftestIndex = 0;
+        }
+        return _this.g.zoomer.setLeftOffset(leftestIndex);
+      };
+    })(this));
+    this.addNode("Invert columns", (function(_this) {
+      return function() {
+        var _i, _ref, _results;
+        return _this.g.selcol.invertCol((function() {
+          _results = [];
+          for (var _i = 0, _ref = _this.model.getMaxLength(); 0 <= _ref ? _i <= _ref : _i >= _ref; 0 <= _ref ? _i++ : _i--){ _results.push(_i); }
+          return _results;
+        }).apply(this));
+      };
+    })(this));
+    this.addNode("Invert rows", (function(_this) {
+      return function() {
+        return _this.g.selcol.invertRow(_this.model.pluck("id"));
+      };
+    })(this));
+    this.addNode("Reset", (function(_this) {
+      return function() {
+        return _this.g.selcol.reset();
+      };
+    })(this));
+    this.el.appendChild(this.buildDOM());
+    return this;
+  }
+});
+
+
+
+},{"../../g/selection/Selection":67,"../menubuilder":75}],84:[function(require,module,exports){
+var ImportMenu, MenuBuilder, dom;
+
+MenuBuilder = require("../menubuilder");
+
+dom = require("dom-helper");
+
+module.exports = ImportMenu = MenuBuilder.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    this.el.style.display = "inline-block";
+    return this.listenTo(this.g.vis, "change", this.render);
+  },
+  render: function() {
+    var visEl, visElements, _i, _len;
+    this.setName("Vis. elements");
+    visElements = this.getVisElements();
+    for (_i = 0, _len = visElements.length; _i < _len; _i++) {
+      visEl = visElements[_i];
+      this._addVisEl(visEl);
+    }
+    this.addNode("Reset", (function(_this) {
+      return function() {
+        _this.g.vis.set("labels", true);
+        _this.g.vis.set("sequences", true);
+        _this.g.vis.set("metacell", true);
+        _this.g.vis.set("conserv", true);
+        _this.g.vis.set("labelId", true);
+        _this.g.vis.set("labelName", true);
+        return _this.g.vis.set("labelCheckbox", false);
+      };
+    })(this));
+    this.addNode("Toggle mouseover events", (function(_this) {
+      return function() {
+        return _this.g.config.set("registerMouseHover", !_this.g.config.get("registerMouseHover"));
+      };
+    })(this));
+    dom.removeAllChilds(this.el);
+    this.el.appendChild(this.buildDOM());
+    return this;
+  },
+  _addVisEl: function(visEl) {
+    var pre, style;
+    style = {};
+    if (this.g.vis.get(visEl.id)) {
+      pre = "Hide ";
+      style.color = "red";
+    } else {
+      pre = "Show ";
+      style.color = "green";
+    }
+    return this.addNode(pre + visEl.name, (function(_this) {
+      return function() {
+        return _this.g.vis.set(visEl.id, !_this.g.vis.get(visEl.id));
+      };
+    })(this), {
+      style: style
+    });
+  },
+  getVisElements: function() {
+    var vis;
+    vis = [];
+    vis.push({
+      name: "Markers",
+      id: "markers"
+    });
+    vis.push({
+      name: "Labels",
+      id: "labels"
+    });
+    vis.push({
+      name: "Sequences",
+      id: "sequences"
+    });
+    vis.push({
+      name: "Meta info",
+      id: "metacell"
+    });
+    vis.push({
+      name: "Overviewbox",
+      id: "overviewbox"
+    });
+    vis.push({
+      name: "conserv",
+      id: "conserv"
+    });
+    vis.push({
+      name: "LabelName",
+      id: "labelName"
+    });
+    vis.push({
+      name: "LabelId",
+      id: "labelId"
+    });
+    vis.push({
+      name: "LabelCheckbox",
+      id: "labelCheckbox"
+    });
+    return vis;
+  }
+});
+
+
+
+},{"../menubuilder":75,"dom-helper":49}],85:[function(require,module,exports){
+var Feature, Model;
+
+Feature = require("./Feature");
+
+Model = require("backbone-thin").Model;
+
+module.exports = Feature = Model.extend({
+  defaults: {
+    xStart: -1,
+    xEnd: -1,
+    height: -1,
+    text: "",
+    fillColor: "red",
+    fillOpacity: 0.5,
+    type: "rectangle",
+    borderSize: 1,
+    borderColor: "black",
+    borderOpacity: 0.5,
+    validate: true
+  },
+  validate: function() {
+    if (isNaN(this.attributes.xStart || isNaN(this.attributes.xEnd))) {
+      return "features need integer start and end.";
+    }
+  },
+  contains: function(index) {
+    return this.attributes.xStart <= index && index <= this.attributes.xEnd;
+  }
+});
+
+
+
+},{"./Feature":85,"backbone-thin":5}],86:[function(require,module,exports){
+var Collection, Feature, FeatureCol, _;
+
+Feature = require("./Feature");
+
+Collection = require("backbone-thin").Collection;
+
+_ = require("underscore");
+
+module.exports = FeatureCol = Collection.extend({
+  model: Feature,
+  constructor: function() {
+    this.startOnCache = [];
+    this.on("all", function() {
+      return this.startOnCache = [];
+    }, this);
+    return Collection.apply(this, arguments);
+  },
+  startOn: function(index) {
+    if (this.startOnCache[index] == null) {
+      this.startOnCache[index] = this.where({
+        xStart: index
+      });
+    }
+    return this.startOnCache[index];
+  },
+  contains: function(index) {
+    return this.reduce(function(el, memo) {
+      return memo || el.contains(index);
+    }, false);
+  },
+  getMinRows: function() {
+    var len, rows, x;
+    len = this.max(function(el) {
+      return el.get("xEnd");
+    });
+    rows = (function() {
+      var _i, _results;
+      _results = [];
+      for (x = _i = 1; 1 <= len ? _i <= len : _i >= len; x = 1 <= len ? ++_i : --_i) {
+        _results.push(0);
+      }
+      return _results;
+    })();
+    this.each(function(el) {
+      var _i, _ref, _ref1, _results;
+      _results = [];
+      for (x = _i = _ref = el.get("xStart"), _ref1 = feature.get("xEnd"); _i <= _ref1; x = _i += 1) {
+        _results.push(rows[x]++);
+      }
+      return _results;
+    });
+    return _.max(rows);
+  }
+});
+
+
+
+},{"./Feature":85,"backbone-thin":5,"underscore":59}],87:[function(require,module,exports){
+var Collection, SeqManager, Sequence;
+
+Sequence = require("./Sequence");
+
+Collection = require("backbone-thin").Collection;
+
+module.exports = SeqManager = Collection.extend({
+  model: Sequence,
+  constructor: function() {
+    Collection.apply(this, arguments);
+    this.on("all", function() {
+      return this.lengthCache = null;
+    }, this);
+    this.lengthCache = null;
+    return this;
+  },
+  getMaxLength: function() {
+    if (this.models.length === 0) {
+      return 0;
+    }
+    if (this.lengthCache === null) {
+      this.lengthCache = this.max(function(seq) {
+        return seq.get("seq").length;
+      }).get("seq").length;
+    }
+    return this.lengthCache;
+  },
+  prev: function(model, endless) {
+    var index;
+    index = this.indexOf(model) - 1;
+    if (index < 0 && endless) {
+      index = this.length - 1;
+    }
+    return this.at(index);
+  },
+  next: function(model, endless) {
+    var index;
+    index = this.indexOf(model) + 1;
+    if (index === this.length && endless) {
+      index = 0;
+    }
+    return this.at(index);
+  },
+  calcHiddenSeqs: function(n) {
+    var i, nNew, _i;
+    nNew = n;
+    for (i = _i = 0; 0 <= nNew ? _i <= nNew : _i >= nNew; i = 0 <= nNew ? ++_i : --_i) {
+      if (this.at(i).get("hidden")) {
+        nNew++;
+      }
+    }
+    return nNew - n;
+  }
+});
+
+
+
+},{"./Sequence":88,"backbone-thin":5}],88:[function(require,module,exports){
+var FeatureCol, Model, Sequence;
+
+Model = require("backbone-thin").Model;
+
+FeatureCol = require("./FeatureCol");
+
+module.exports = Sequence = Model.extend({
+  defaults: {
+    name: "",
+    id: "",
+    seq: ""
+  },
+  initialize: function() {
+    this.set("grey", []);
+    return this.set("features", new FeatureCol());
+  }
+});
+
+
+
+},{"./FeatureCol":86,"backbone-thin":5}],89:[function(require,module,exports){
+module.exports.seq = require("./Sequence");
+
+module.exports.seqcol = require("./SeqCollection");
+
+module.exports.feature = require("./Feature");
+
+module.exports.featurecol = require("./FeatureCol");
+
+
+
+},{"./Feature":85,"./FeatureCol":86,"./SeqCollection":87,"./Sequence":88}],90:[function(require,module,exports){
+var Colorator, Columns, Config, Consensus, Eventhandler, SelCol, SeqCollection, Stage, VisOrdering, Visibility, Zoomer, boneView;
+
+SeqCollection = require("./model/SeqCollection");
+
+Colorator = require("./g/colorator");
+
+Consensus = require("./g/consensus");
+
+Columns = require("./g/columns");
+
+Config = require("./g/config");
+
+SelCol = require("./g/selection/SelectionCol");
+
+Visibility = require("./g/visibility");
+
+VisOrdering = require("./g/visOrdering");
+
+Zoomer = require("./g/zoomer");
+
+boneView = require("backbone-childs");
+
+Eventhandler = require("biojs-events");
+
+Stage = require("./views/Stage");
+
+module.exports = boneView.extend({
+  initialize: function(data) {
+    var _ref;
+    if (data.columns == null) {
+      data.columns = {};
+    }
+    if (data.conf == null) {
+      data.conf = {};
+    }
+    if (data.vis == null) {
+      data.vis = {};
+    }
+    if (data.zoomer == null) {
+      if (!((_ref = data.visorder) != null ? _ref : data.zoomer = {})) {
+        data.visorder = {};
+      }
+    }
+    this.g = Eventhandler.mixin({});
+    if (data.seqs === void 0 || data.seqs.length === 0) {
+      console.log("warning. empty seqs.");
+    }
+    this.seqs = new SeqCollection(data.seqs);
+    this.g.config = new Config(data.conf);
+    this.g.consensus = new Consensus();
+    this.g.columns = new Columns(data.columns);
+    this.g.colorscheme = new Colorator();
+    this.g.selcol = new SelCol([], {
+      g: this.g
+    });
+    this.g.vis = new Visibility(data.vis);
+    this.g.visorder = new VisOrdering(data.visorder);
+    this.g.zoomer = new Zoomer(data.zoomer, {
+      g: this.g
+    });
+    this.addView("stage", new Stage({
+      model: this.seqs,
+      g: this.g
+    }));
+    this.el.setAttribute("class", "biojs_msa_div");
+    if (this.g.config.get("eventBus") === true) {
+      return this.startEventBus();
+    }
+  },
+  startEventBus: function() {
+    var busObjs, key, _i, _len, _results;
+    busObjs = ["config", "consensus", "columns", "colorscheme", "selcol", "vis", "visorder", "zoomer"];
+    _results = [];
+    for (_i = 0, _len = busObjs.length; _i < _len; _i++) {
+      key = busObjs[_i];
+      _results.push(this._proxyToG(key));
+    }
+    return _results;
+  },
+  _proxyToG: function(key) {
+    return this.listenTo(this.g[key], "all", function(name, prev, now) {
+      if (name === "change") {
+        return;
+      }
+      return this.g.trigger(key + ":" + name, now);
+    });
+  },
+  render: function() {
+    this.renderSubviews();
+    this.g.vis.set("loaded", true);
+    return this;
+  }
+});
+
+
+
+},{"./g/colorator":63,"./g/columns":64,"./g/config":65,"./g/consensus":66,"./g/selection/SelectionCol":68,"./g/visOrdering":69,"./g/visibility":70,"./g/zoomer":71,"./model/SeqCollection":87,"./views/Stage":100,"backbone-childs":3,"biojs-events":14}],91:[function(require,module,exports){
+var BMath;
+
+module.exports = BMath = (function() {
+  function BMath() {}
+
+  BMath.randomInt = function(lower, upper) {
+    var _ref, _ref1;
+    if (upper == null) {
+      _ref = [0, lower], lower = _ref[0], upper = _ref[1];
+    }
+    if (lower > upper) {
+      _ref1 = [upper, lower], lower = _ref1[0], upper = _ref1[1];
+    }
+    return Math.floor(Math.random() * (upper - lower + 1) + lower);
+  };
+
+  BMath.uniqueId = function(length) {
+    var id;
+    if (length == null) {
+      length = 8;
+    }
+    id = "";
+    while (id.length < length) {
+      id += Math.random().toString(36).substr(2);
+    }
+    return id.substr(0, length);
+  };
+
+  BMath.getRandomInt = function(min, max) {
+    return Math.floor(Math.random() * (max - min + 1)) + min;
+  };
+
+  return BMath;
+
+})();
+
+
+
+},{}],92:[function(require,module,exports){
+module.exports.bmath = require("./bmath");
+
+module.exports.proxy = require("./proxy");
+
+module.exports.seqgen = require("./seqgen");
+
+
+
+},{"./bmath":91,"./proxy":93,"./seqgen":94}],93:[function(require,module,exports){
+var proxy;
+
+module.exports = proxy = {
+  corsURL: (function(_this) {
+    return function(url, g) {
+      _this.g = g;
+      if (document.URL.indexOf('localhost') >= 0 && url[0] === "/") {
+        return url;
+      }
+      url = url.replace("www\.", "");
+      url = url.replace("http://", "");
+      url = _this.g.config.get('importProxy') + url;
+      return url;
+    };
+  })(this)
+};
+
+
+
+},{}],94:[function(require,module,exports){
+var BMath, Sequence, seqgen;
+
+Sequence = require("biojs-model").seq;
+
+BMath = require("./bmath");
+
+seqgen = module.exports = {
+  _generateSequence: function(len) {
+    var i, possible, text, _i, _ref;
+    text = "";
+    possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+    for (i = _i = 0, _ref = len - 1; _i <= _ref; i = _i += 1) {
+      text += possible.charAt(Math.floor(Math.random() * possible.length));
+    }
+    return text;
+  },
+  getDummySequences: function(len, seqLen) {
+    var i, seqs, _i;
+    seqs = [];
+    if (len == null) {
+      len = BMath.getRandomInt(3, 5);
+    }
+    if (seqLen == null) {
+      seqLen = BMath.getRandomInt(50, 200);
+    }
+    for (i = _i = 1; _i <= len; i = _i += 1) {
+      seqs.push(new Sequence(seqgen._generateSequence(seqLen), "seq" + i, "r" + i));
+    }
+    return seqs;
+  }
+};
+
+
+
+},{"./bmath":91,"biojs-model":27}],95:[function(require,module,exports){
+var Base, Line, Polygon, Rect, setAttr, svgns;
+
+svgns = "http://www.w3.org/2000/svg";
+
+setAttr = function(obj, opts) {
+  var name, value;
+  for (name in opts) {
+    value = opts[name];
+    obj.setAttributeNS(null, name, value);
+  }
+  return obj;
+};
+
+Base = function(opts) {
+  var svg;
+  svg = document.createElementNS(svgns, 'svg');
+  svg.setAttribute("width", opts.width);
+  svg.setAttribute("height", opts.height);
+  return svg;
+};
+
+Rect = function(opts) {
+  var rect;
+  rect = document.createElementNS(svgns, 'rect');
+  return setAttr(rect, opts);
+};
+
+Line = function(opts) {
+  var line;
+  line = document.createElementNS(svgns, 'line');
+  return setAttr(line, opts);
+};
+
+Polygon = function(opts) {
+  var line;
+  line = document.createElementNS(svgns, 'polygon');
+  return setAttr(line, opts);
+};
+
+module.exports.rect = Rect;
+
+module.exports.line = Line;
+
+module.exports.polygon = Polygon;
+
+module.exports.base = Base;
+
+
+
+},{}],96:[function(require,module,exports){
+var LabelBlock, SeqBlock, boneView;
+
+boneView = require("backbone-childs");
+
+SeqBlock = require("./CanvasSeqBlock");
+
+LabelBlock = require("./labels/LabelBlock");
+
+module.exports = boneView.extend({
+  initialize: function(data) {
+    var labelblock, seqblock;
+    this.g = data.g;
+    if (true) {
+      labelblock = new LabelBlock({
+        model: this.model,
+        g: this.g
+      });
+      labelblock.ordering = -1;
+      this.addView("labelblock", labelblock);
+    }
+    if (this.g.vis.get("sequences")) {
+      seqblock = new SeqBlock({
+        model: this.model,
+        g: this.g
+      });
+      seqblock.ordering = 0;
+      this.addView("seqblock", seqblock);
+    }
+    this.listenTo(this.g.zoomer, "change:alignmentHeight", this.adjustHeight);
+    return this.listenTo(this.g.columns, "change:hidden", this.adjustHeight);
+  },
+  render: function() {
+    this.renderSubviews();
+    this.el.className = "biojs_msa_albody";
+    this.el.style.whiteSpace = "nowrap";
+    this.adjustHeight();
+    return this;
+  },
+  adjustHeight: function() {
+    if (this.g.zoomer.get("alignmentHeight") === "auto") {
+      this.el.style.height = (this.g.zoomer.get("rowHeight") * this.model.length) + 5;
+    } else {
+      this.el.style.height = this.g.zoomer.get("alignmentHeight");
+    }
+    return this.el.style.width = this.getWidth() + 15;
+  },
+  getWidth: function() {
+    var width;
+    width = 0;
+    if (this.g.vis.get("labels")) {
+      width += this.g.zoomer.get("labelWidth");
+    }
+    if (this.g.vis.get("metacell")) {
+      width += this.g.zoomer.get("metaWidth");
+    }
+    if (this.g.vis.get("sequences")) {
+      width += this.g.zoomer.get("alignmentWidth");
+    }
+    return width;
+  }
+});
+
+
+
+},{"./CanvasSeqBlock":98,"./labels/LabelBlock":104,"backbone-childs":3}],97:[function(require,module,exports){
+var CanvasCharCache, Events;
+
+Events = require("biojs-events");
+
+module.exports = CanvasCharCache = (function() {
+  function CanvasCharCache(g) {
+    this.g = g;
+    this.cache = {};
+    this.cacheHeight = 0;
+    this.cacheWidth = 0;
+  }
+
+  CanvasCharCache.prototype.getFontTile = function(letter, width, height) {
+    if (width !== this.cacheWidth || height !== this.cacheHeight) {
+      this.cacheHeight = height;
+      this.cacheWidth = width;
+      this.cache = {};
+    }
+    if (this.cache[letter] === void 0) {
+      this.createTile(letter, width, height);
+    }
+    return this.cache[letter];
+  };
+
+  CanvasCharCache.prototype.createTile = function(letter, width, height) {
+    var canvas;
+    canvas = this.cache[letter] = document.createElement("canvas");
+    canvas.width = width;
+    canvas.height = height;
+    this.ctx = canvas.getContext('2d');
+    this.ctx.font = this.g.zoomer.get("residueFont");
+    this.ctx.textBaseline = 'middle';
+    this.ctx.textAlign = "center";
+    return this.ctx.fillText(letter, width / 2, height / 2, width);
+  };
+
+  return CanvasCharCache;
+
+})();
+
+
+
+},{"biojs-events":14}],98:[function(require,module,exports){
+var CharCache, boneView, colorSelector, jbone, mouse, _;
+
+boneView = require("backbone-childs");
+
+mouse = require("mouse-pos");
+
+colorSelector = require("biojs-util-colorschemes").selector;
+
+_ = require("underscore");
+
+jbone = require("jbone");
+
+CharCache = require("./CanvasCharCache");
+
+module.exports = boneView.extend({
+  tagName: "canvas",
+  initialize: function(data) {
+    this.g = data.g;
+    this.listenTo(this.g.zoomer, "change:_alignmentScrollLeft change:_alignmentScrollTop", function(model, value, options) {
+      if (((options != null ? options.origin : void 0) == null) || options.origin !== "canvasseq") {
+        return this.render();
+      }
+    });
+    this.listenTo(this.g.columns, "change:hidden", this.render);
+    this.listenTo(this.g.zoomer, "change:alignmentWidth", this.render);
+    this.listenTo(this.g.colorscheme, "change", this.render);
+    this.listenTo(this.g.selcol, "reset add", this.render);
+    this.el.style.display = "inline-block";
+    this.el.style.overflowX = "hidden";
+    this.el.style.overflowY = "hidden";
+    this.el.className = "biojs_msa_seqblock";
+    this.ctx = this.el.getContext('2d');
+    this.cache = new CharCache(this.g);
+    this.throttleTime = 0;
+    this.throttleCounts = 0;
+    if (document.documentElement.style.webkitAppearance != null) {
+      this.throttledDraw = function() {
+        var start, tTime;
+        start = +new Date();
+        this.draw();
+        this.throttleTime += +new Date() - start;
+        this.throttleCounts++;
+        if (this.throttleCounts > 15) {
+          tTime = Math.ceil(this.throttleTime / this.throttleCounts);
+          console.log("avgDrawTime/WebKit", tTime);
+          return this.throttledDraw = this.draw;
+        }
+      };
+    } else {
+      this.throttledDraw = _.throttle(this.throttledDraw, 30);
+    }
+    return this.manageEvents();
+  },
+  throttledDraw: function() {
+    var start, tTime;
+    start = +new Date();
+    this.draw();
+    this.throttleTime += +new Date() - start;
+    this.throttleCounts++;
+    if (this.throttleCounts > 15) {
+      tTime = Math.ceil(this.throttleTime / this.throttleCounts);
+      console.log("avgDrawTime", tTime);
+      tTime *= 1.2;
+      tTime = Math.max(20, tTime);
+      return this.throttledDraw = _.throttle(this.draw, tTime);
+    }
+  },
+  manageEvents: function() {
+    var events;
+    events = {};
+    events.mousedown = "_onmousedown";
+    events.touchstart = "_ontouchstart";
+    if (this.g.config.get("registerMouseClicks")) {
+      events.dblclick = "_onclick";
+    }
+    if (this.g.config.get("registerMouseHover")) {
+      events.mousein = "_onmousein";
+      events.mouseout = "_onmouseout";
+    }
+    events.mousewheel = "_onmousewheel";
+    events.DOMMouseScroll = "_onmousewheel";
+    this.delegateEvents(events);
+    this.listenTo(this.g.config, "change:registerMouseHover", this.manageEvents);
+    this.listenTo(this.g.config, "change:registerMouseClick", this.manageEvents);
+    return this.dragStart = [];
+  },
+  draw: function() {
+    var rectHeight;
+    this.el.width = this.el.width;
+    rectHeight = this.g.zoomer.get("rowHeight");
+    this.ctx.globalAlpha = this.g.colorscheme.get("opacity");
+    this.drawSeqs(function(data) {
+      return this.drawSeq(data, this._drawRect);
+    });
+    this.ctx.globalAlpha = 1;
+    this.drawSeqs(function(data) {
+      return this.drawSeq(data, this._drawLetter);
+    });
+    return this.drawSeqs(this.drawSeqExtended);
+  },
+  drawSeqs: function(callback) {
+    var hidden, i, rectHeight, start, y, _i, _ref, _results;
+    rectHeight = this.g.zoomer.get("rowHeight");
+    hidden = this.g.columns.get("hidden");
+    start = Math.max(0, Math.abs(Math.ceil(-this.g.zoomer.get('_alignmentScrollTop') / rectHeight)));
+    y = -Math.abs(-this.g.zoomer.get('_alignmentScrollTop') % rectHeight);
+    _results = [];
+    for (i = _i = start, _ref = this.model.length - 1; _i <= _ref; i = _i += 1) {
+      if (this.model.at(i).get('hidden')) {
+        continue;
+      }
+      callback.call(this, {
+        model: this.model.at(i),
+        y: y,
+        hidden: hidden
+      });
+      y = y + rectHeight;
+      if (y > this.el.height) {
+        break;
+      } else {
+        _results.push(void 0);
+      }
+    }
+    return _results;
+  },
+  drawSeq: function(data, callback) {
+    var c, elWidth, j, rectHeight, rectWidth, res, seq, start, x, y, _i, _ref, _results;
+    seq = data.model.get("seq");
+    y = data.y;
+    rectWidth = this.g.zoomer.get("columnWidth");
+    rectHeight = this.g.zoomer.get("rowHeight");
+    start = Math.max(0, Math.abs(Math.ceil(-this.g.zoomer.get('_alignmentScrollLeft') / rectWidth)));
+    x = -Math.abs(-this.g.zoomer.get('_alignmentScrollLeft') % rectWidth);
+    res = {
+      rectWidth: rectWidth,
+      rectHeight: rectHeight,
+      y: y
+    };
+    elWidth = this.el.width;
+    _results = [];
+    for (j = _i = start, _ref = seq.length - 1; _i <= _ref; j = _i += 1) {
+      c = seq[j];
+      c = c.toUpperCase();
+      res.x = x;
+      res.c = c;
+      if (data.hidden.indexOf(j) < 0) {
+        callback(this, res);
+      } else {
+        continue;
+      }
+      x = x + rectWidth;
+      if (x > elWidth) {
+        break;
+      } else {
+        _results.push(void 0);
+      }
+    }
+    return _results;
+  },
+  _drawRect: function(that, data) {
+    var color;
+    color = that.color[data.c];
+    if (color != null) {
+      that.ctx.fillStyle = color;
+      return that.ctx.fillRect(data.x, data.y, data.rectWidth, data.rectHeight);
+    }
+  },
+  _drawLetter: function(that, data) {
+    return that.ctx.drawImage(that.cache.getFontTile(data.c, data.rectWidth, data.rectHeight), data.x, data.y, data.rectWidth, data.rectHeight);
+  },
+  drawSeqExtended: function(data) {
+    var f, features, j, mNextSel, mPrevSel, rectHeight, rectWidth, selection, seq, start, starts, x, xZero, yZero, _i, _j, _len, _ref, _ref1;
+    seq = data.model.get("seq");
+    rectWidth = this.g.zoomer.get("columnWidth");
+    rectHeight = this.g.zoomer.get("rowHeight");
+    start = Math.max(0, Math.abs(Math.ceil(-this.g.zoomer.get('_alignmentScrollLeft') / rectWidth)));
+    x = -Math.abs(-this.g.zoomer.get('_alignmentScrollLeft') % rectWidth);
+    xZero = x - start * rectWidth;
+    selection = this._getSelection(data.model);
+    _ref = this._getPrevNextSelection(data.model), mPrevSel = _ref[0], mNextSel = _ref[1];
+    features = data.model.get("features");
+    yZero = data.y;
+    for (j = _i = start, _ref1 = seq.length - 1; _i <= _ref1; j = _i += 1) {
+      starts = features.startOn(j);
+      if (data.hidden.indexOf(j) >= 0) {
+        continue;
+      }
+      if (starts.length > 0) {
+        for (_j = 0, _len = starts.length; _j < _len; _j++) {
+          f = starts[_j];
+          this.appendFeature({
+            f: f,
+            xZero: x,
+            yZero: yZero
+          });
+        }
+      }
+      x = x + rectWidth;
+      if (x > this.el.width) {
+        break;
+      }
+    }
+    return this._appendSelection({
+      model: data.model,
+      xZero: xZero,
+      yZero: yZero,
+      hidden: data.hidden
+    });
+  },
+  render: function() {
+    this.el.setAttribute('height', this.g.zoomer.get("alignmentHeight"));
+    this.el.setAttribute('width', this.g.zoomer.get("alignmentWidth"));
+    this.g.zoomer._adjustWidth(this.el, this.model);
+    this.g.zoomer._checkScrolling(this._checkScrolling([this.g.zoomer.get('_alignmentScrollLeft'), this.g.zoomer.get('_alignmentScrollTop')]), {
+      header: "canvasseq"
+    });
+    this.color = colorSelector.getColor(this.g.colorscheme.get("scheme"));
+    this.throttledDraw();
+    return this;
+  },
+  _onmousemove: function(e, reversed) {
+    var dragEnd, i, relDist, relEnd, scaleFactor, scrollCorrected, _i, _j, _k;
+    if (this.dragStart.length === 0) {
+      return;
+    }
+    dragEnd = mouse.abs(e);
+    relEnd = [dragEnd[0] - this.dragStart[0], dragEnd[1] - this.dragStart[1]];
+    scaleFactor = this.g.zoomer.get("canvasEventScale");
+    if (reversed) {
+      scaleFactor = 3;
+    }
+    for (i = _i = 0; _i <= 1; i = _i += 1) {
+      relEnd[i] = relEnd[i] * scaleFactor;
+    }
+    relDist = [this.dragStartScroll[0] - relEnd[0], this.dragStartScroll[1] - relEnd[1]];
+    for (i = _j = 0; _j <= 1; i = _j += 1) {
+      relDist[i] = Math.round(relDist[i]);
+    }
+    scrollCorrected = this._checkScrolling(relDist);
+    this.g.zoomer._checkScrolling(scrollCorrected, {
+      origin: "canvasseq"
+    });
+    for (i = _k = 0; _k <= 1; i = _k += 1) {
+      if (scrollCorrected[i] !== relDist[i]) {
+        if (scrollCorrected[i] === 0) {
+          this.dragStart[i] = dragEnd[i];
+          this.dragStartScroll[i] = 0;
+        } else {
+          this.dragStart[i] = dragEnd[i] - scrollCorrected[i];
+        }
+      }
+    }
+    this.throttledDraw();
+    if (e.preventDefault != null) {
+      e.preventDefault();
+      return e.stopPropagation();
+    }
+  },
+  _ontouchmove: function(e) {
+    this._onmousemove(e.changedTouches[0], true);
+    e.preventDefault();
+    return e.stopPropagation();
+  },
+  _onmousedown: function(e) {
+    this.dragStart = mouse.abs(e);
+    this.dragStartScroll = [this.g.zoomer.get('_alignmentScrollLeft'), this.g.zoomer.get('_alignmentScrollTop')];
+    jbone(document.body).on('mousemove.overmove', (function(_this) {
+      return function(e) {
+        return _this._onmousemove(e);
+      };
+    })(this));
+    jbone(document.body).on('mouseup.overup', (function(_this) {
+      return function() {
+        return _this._cleanup();
+      };
+    })(this));
+    return e.preventDefault();
+  },
+  _ontouchstart: function(e) {
+    this.dragStart = mouse.abs(e.changedTouches[0]);
+    this.dragStartScroll = [this.g.zoomer.get('_alignmentScrollLeft'), this.g.zoomer.get('_alignmentScrollTop')];
+    jbone(document.body).on('touchmove.overtmove', (function(_this) {
+      return function(e) {
+        return _this._ontouchmove(e);
+      };
+    })(this));
+    return jbone(document.body).on('touchend.overtend touchleave.overtleave touchcancel.overtcanel', (function(_this) {
+      return function(e) {
+        return _this._touchCleanup(e);
+      };
+    })(this));
+  },
+  _onmousewinout: function(e) {
+    if (e.toElement === document.body.parentNode) {
+      return this._cleanup();
+    }
+  },
+  _cleanup: function() {
+    this.dragStart = [];
+    jbone(document.body).off('.overmove');
+    jbone(document.body).off('.overup');
+    return jbone(document.body).off('.overout');
+  },
+  _touchCleanup: function(e) {
+    if (e.changedTouches.length > 0) {
+      this._onmousemove(e.changedTouches[0], true);
+    }
+    this.dragStart = [];
+    jbone(document.body).off('.overtmove');
+    jbone(document.body).off('.overtend');
+    jbone(document.body).off('.overtleave');
+    return jbone(document.body).off('.overtcancel');
+  },
+  _onmousewheel: function(e) {
+    var delta;
+    delta = mouse.wheelDelta(e);
+    this.g.zoomer.set('_alignmentScrollLeft', this.g.zoomer.get('_alignmentScrollLeft') + delta[0]);
+    this.g.zoomer.set('_alignmentScrollTop', this.g.zoomer.get('_alignmentScrollTop') + delta[1]);
+    return e.preventDefault();
+  },
+  _onclick: function(e) {
+    this.g.trigger("residue:click", this._getClickPos(e));
+    return this.throttledDraw();
+  },
+  _onmousein: function(e) {
+    this.g.trigger("residue:click", this._getClickPos(e));
+    return this.throttledDraw();
+  },
+  _onmouseout: function(e) {
+    this.g.trigger("residue:click", this._getClickPos(e));
+    return this.throttledDraw();
+  },
+  _getClickPos: function(e) {
+    var coords, seqId, x, y;
+    coords = mouse.rel(e);
+    coords[0] += this.g.zoomer.get("_alignmentScrollLeft");
+    coords[1] += this.g.zoomer.get("_alignmentScrollTop");
+    x = Math.floor(coords[0] / this.g.zoomer.get("columnWidth"));
+    y = Math.floor(coords[1] / this.g.zoomer.get("rowHeight"));
+    x += this.g.columns.calcHiddenColumns(x);
+    y += this.model.calcHiddenSeqs(y);
+    x = Math.max(0, x);
+    y = Math.max(0, y);
+    seqId = this.model.at(y).get("id");
+    return {
+      seqId: seqId,
+      rowPos: x,
+      evt: e
+    };
+  },
+  _checkScrolling: function(scrollObj) {
+    var i, max, _i;
+    max = [this.model.getMaxLength() * this.g.zoomer.get("columnWidth") - this.g.zoomer.get('alignmentWidth'), this.model.length * this.g.zoomer.get("rowHeight") - this.g.zoomer.get('alignmentHeight')];
+    for (i = _i = 0; _i <= 1; i = _i += 1) {
+      if (scrollObj[i] > max[i]) {
+        scrollObj[i] = max[i];
+      }
+      if (scrollObj[i] < 0) {
+        scrollObj[i] = 0;
+      }
+    }
+    return scrollObj;
+  },
+  _getSelection: function(model) {
+    var maxLen, n, rows, sel, selection, sels, _i, _j, _k, _len, _ref, _ref1, _ref2;
+    maxLen = model.get("seq").length;
+    selection = [];
+    sels = this.g.selcol.getSelForRow(model.get("id"));
+    rows = _.find(sels, function(el) {
+      return el.get("type") === "row";
+    });
+    if (rows != null) {
+      for (n = _i = 0, _ref = maxLen - 1; _i <= _ref; n = _i += 1) {
+        selection.push(n);
+      }
+    } else if (sels.length > 0) {
+      for (_j = 0, _len = sels.length; _j < _len; _j++) {
+        sel = sels[_j];
+        for (n = _k = _ref1 = sel.get("xStart"), _ref2 = sel.get("xEnd"); _k <= _ref2; n = _k += 1) {
+          selection.push(n);
+        }
+      }
+    }
+    return selection;
+  },
+  appendFeature: function(data) {
+    var beforeStyle, beforeWidth, boxHeight, boxWidth, f, width;
+    f = data.f;
+    boxWidth = this.g.zoomer.get("columnWidth");
+    boxHeight = this.g.zoomer.get("rowHeight");
+    width = (f.get("xEnd") - f.get("xStart")) * boxWidth;
+    beforeWidth = this.ctx.lineWidth;
+    this.ctx.lineWidth = 3;
+    beforeStyle = this.ctx.strokeStyle;
+    this.ctx.strokeStyle = f.get("fillColor");
+    this.ctx.strokeRect(data.xZero, data.yZero, width, boxHeight);
+    this.ctx.strokeStyle = beforeStyle;
+    return this.ctx.lineWidth = beforeWidth;
+  },
+  _appendSelection: function(data) {
+    var boxHeight, boxWidth, hiddenOffset, k, mNextSel, mPrevSel, n, selection, seq, _i, _ref, _ref1, _results;
+    seq = data.model.get("seq");
+    selection = this._getSelection(data.model);
+    _ref = this._getPrevNextSelection(data.model), mPrevSel = _ref[0], mNextSel = _ref[1];
+    boxWidth = this.g.zoomer.get("columnWidth");
+    boxHeight = this.g.zoomer.get("rowHeight");
+    if (selection.length === 0) {
+      return;
+    }
+    hiddenOffset = 0;
+    _results = [];
+    for (n = _i = 0, _ref1 = seq.length - 1; _i <= _ref1; n = _i += 1) {
+      if (data.hidden.indexOf(n) >= 0) {
+        _results.push(hiddenOffset++);
+      } else {
+        k = n - hiddenOffset;
+        if (selection.indexOf(n) >= 0 && (k === 0 || selection.indexOf(n - 1) < 0)) {
+          _results.push(this._renderSelection({
+            n: n,
+            k: k,
+            selection: selection,
+            mPrevSel: mPrevSel,
+            mNextSel: mNextSel,
+            xZero: data.xZero,
+            yZero: data.yZero,
+            model: data.model
+          }));
+        } else {
+          _results.push(void 0);
+        }
+      }
+    }
+    return _results;
+  },
+  _renderSelection: function(data) {
+    var beforeStyle, beforeWidth, boxHeight, boxWidth, hidden, i, k, mNextSel, mPrevSel, n, selection, selectionLength, totalWidth, xPart, xPos, xZero, yZero, _i, _j, _ref, _ref1;
+    xZero = data.xZero;
+    yZero = data.yZero;
+    n = data.n;
+    k = data.k;
+    selection = data.selection;
+    mPrevSel = data.mPrevSel;
+    mNextSel = data.mNextSel;
+    selectionLength = 0;
+    for (i = _i = n, _ref = data.model.get("seq").length - 1; _i <= _ref; i = _i += 1) {
+      if (selection.indexOf(i) >= 0) {
+        selectionLength++;
+      } else {
+        break;
+      }
+    }
+    boxWidth = this.g.zoomer.get("columnWidth");
+    boxHeight = this.g.zoomer.get("rowHeight");
+    totalWidth = (boxWidth * selectionLength) + 1;
+    hidden = this.g.columns.get('hidden');
+    this.ctx.beginPath();
+    beforeWidth = this.ctx.lineWidth;
+    this.ctx.lineWidth = 3;
+    beforeStyle = this.ctx.strokeStyle;
+    this.ctx.strokeStyle = "#FF0000";
+    xZero += k * boxWidth;
+    xPart = 0;
+    for (i = _j = 0, _ref1 = selectionLength - 1; 0 <= _ref1 ? _j <= _ref1 : _j >= _ref1; i = 0 <= _ref1 ? ++_j : --_j) {
+      xPos = n + i;
+      if (hidden.indexOf(xPos) >= 0) {
+        continue;
+      }
+      if (!((mPrevSel != null) && mPrevSel.indexOf(xPos) >= 0)) {
+        this.ctx.moveTo(xZero + xPart, yZero);
+        this.ctx.lineTo(xPart + boxWidth + xZero, yZero);
+      }
+      if (!((mNextSel != null) && mNextSel.indexOf(xPos) >= 0)) {
+        this.ctx.moveTo(xPart + xZero, boxHeight + yZero);
+        this.ctx.lineTo(xPart + boxWidth + xZero, boxHeight + yZero);
+      }
+      xPart += boxWidth;
+    }
+    this.ctx.moveTo(xZero, yZero);
+    this.ctx.lineTo(xZero, boxHeight + yZero);
+    this.ctx.moveTo(xZero + totalWidth, yZero);
+    this.ctx.lineTo(xZero + totalWidth, boxHeight + yZero);
+    this.ctx.stroke();
+    this.ctx.strokeStyle = beforeStyle;
+    return this.ctx.lineWidth = beforeWidth;
+  },
+  _getPrevNextSelection: function(model) {
+    var mNextSel, mPrevSel, modelNext, modelPrev;
+    modelPrev = model.collection.prev(model);
+    modelNext = model.collection.next(model);
+    if (modelPrev != null) {
+      mPrevSel = this._getSelection(modelPrev);
+    }
+    if (modelNext != null) {
+      mNextSel = this._getSelection(modelNext);
+    }
+    return [mPrevSel, mNextSel];
+  }
+});
+
+
+
+},{"./CanvasCharCache":97,"backbone-childs":3,"biojs-util-colorschemes":29,"jbone":50,"mouse-pos":51,"underscore":59}],99:[function(require,module,exports){
+var OverviewBox, colorSelector, jbone, mouse, selection, view, _;
+
+view = require("backbone-viewj");
+
+mouse = require("mouse-pos");
+
+selection = require("../g/selection/Selection");
+
+colorSelector = require("biojs-util-colorschemes").selector;
+
+jbone = require("jbone");
+
+_ = require("underscore");
+
+module.exports = OverviewBox = view.extend({
+  className: "biojs_msa_overviewbox",
+  tagName: "canvas",
+  initialize: function(data) {
+    this.g = data.g;
+    this.listenTo(this.g.zoomer, "change:boxRectWidth change:boxRectHeight", this.render);
+    this.listenTo(this.g.selcol, "add reset change", this.render);
+    this.listenTo(this.g.columns, "change:hidden", this.render);
+    this.listenTo(this.g.colorscheme, "change:showLowerCase", this.render);
+    this.listenTo(this.model, "change", _.debounce(this.render, 5));
+    this.color = colorSelector.getColor(this.g.colorscheme.get("scheme"));
+    this.listenTo(this.g.colorscheme, "change:scheme", function() {
+      this.color = colorSelector.getColor(this.g.colorscheme.get("scheme"));
+      return this.render();
+    });
+    return this.dragStart = [];
+  },
+  events: {
+    click: "_onclick",
+    mousedown: "_onmousedown"
+  },
+  render: function() {
+    var c, color, hidden, i, j, rectHeight, rectWidth, seq, showLowerCase, x, y, _i, _j, _ref, _ref1;
+    this._createCanvas();
+    this.el.textContent = "overview";
+    this.ctx.fillStyle = "#999999";
+    this.ctx.fillRect(0, 0, this.el.width, this.el.height);
+    rectWidth = this.g.zoomer.get("boxRectWidth");
+    rectHeight = this.g.zoomer.get("boxRectHeight");
+    hidden = this.g.columns.get("hidden");
+    showLowerCase = this.g.colorscheme.get("showLowerCase");
+    y = -rectHeight;
+    for (i = _i = 0, _ref = this.model.length - 1; _i <= _ref; i = _i += 1) {
+      seq = this.model.at(i).get("seq");
+      x = 0;
+      y = y + rectHeight;
+      if (this.model.at(i).get("hidden")) {
+        console.log(this.model.at(i).get("hidden"));
+        this.ctx.fillStyle = "grey";
+        this.ctx.fillRect(0, y, seq.length * rectWidth, rectHeight);
+        continue;
+      }
+      for (j = _j = 0, _ref1 = seq.length - 1; _j <= _ref1; j = _j += 1) {
+        c = seq[j];
+        if (showLowerCase) {
+          c = c.toUpperCase();
+        }
+        color = this.color[c];
+        if (hidden.indexOf(j) >= 0) {
+          color = "grey";
+        }
+        if (color != null) {
+          this.ctx.fillStyle = color;
+          this.ctx.fillRect(x, y, rectWidth, rectHeight);
+        }
+        x = x + rectWidth;
+      }
+    }
+    return this._drawSelection();
+  },
+  _drawSelection: function() {
+    var i, maxHeight, pos, rectHeight, rectWidth, sel, seq, _i, _ref;
+    if (this.dragStart.length > 0 && !this.prolongSelection) {
+      return;
+    }
+    rectWidth = this.g.zoomer.get("boxRectWidth");
+    rectHeight = this.g.zoomer.get("boxRectHeight");
+    maxHeight = rectHeight * this.model.length;
+    this.ctx.fillStyle = "#ffff00";
+    this.ctx.globalAlpha = 0.9;
+    for (i = _i = 0, _ref = this.g.selcol.length - 1; _i <= _ref; i = _i += 1) {
+      sel = this.g.selcol.at(i);
+      if (sel.get('type') === 'column') {
+        this.ctx.fillRect(rectWidth * sel.get('xStart'), 0, rectWidth * (sel.get('xEnd') - sel.get('xStart') + 1), maxHeight);
+      } else if (sel.get('type') === 'row') {
+        seq = (this.model.filter(function(el) {
+          return el.get('id') === sel.get('seqId');
+        }))[0];
+        pos = this.model.indexOf(seq);
+        this.ctx.fillRect(0, rectHeight * pos, rectWidth * seq.get('seq').length, rectHeight);
+      } else if (sel.get('type') === 'pos') {
+        seq = (this.model.filter(function(el) {
+          return el.get('id') === sel.get('seqId');
+        }))[0];
+        pos = this.model.indexOf(seq);
+        this.ctx.fillRect(rectWidth * sel.get('xStart'), rectHeight * pos, rectWidth * (sel.get('xEnd') - sel.get('xStart') + 1), rectHeight);
+      }
+    }
+    return this.ctx.globalAlpha = 1;
+  },
+  _onclick: function(evt) {
+    return this.g.trigger("meta:click", {
+      seqId: this.model.get("id", {
+        evt: evt
+      })
+    });
+  },
+  _onmousemove: function(e) {
+    var rect;
+    if (this.dragStart.length === 0) {
+      return;
+    }
+    this.render();
+    this.ctx.fillStyle = "#ffff00";
+    this.ctx.globalAlpha = 0.9;
+    rect = this._calcSelection(mouse.abs(e));
+    this.ctx.fillRect(rect[0][0], rect[1][0], rect[0][1] - rect[0][0], rect[1][1] - rect[1][0]);
+    e.preventDefault();
+    return e.stopPropagation();
+  },
+  _onmousedown: function(e) {
+    this.dragStart = mouse.abs(e);
+    this.dragStartRel = mouse.rel(e);
+    if (e.ctrlKey || e.metaKey) {
+      this.prolongSelection = true;
+    } else {
+      this.prolongSelection = false;
+    }
+    jbone(document.body).on('mousemove.overmove', (function(_this) {
+      return function(e) {
+        return _this._onmousemove(e);
+      };
+    })(this));
+    jbone(document.body).on('mouseup.overup', (function(_this) {
+      return function(e) {
+        return _this._onmouseup(e);
+      };
+    })(this));
+    return this.dragStart;
+  },
+  _calcSelection: function(dragMove) {
+    var dragRel, i, rect, _i, _j;
+    dragRel = [dragMove[0] - this.dragStart[0], dragMove[1] - this.dragStart[1]];
+    for (i = _i = 0; _i <= 1; i = _i += 1) {
+      dragRel[i] = this.dragStartRel[i] + dragRel[i];
+    }
+    rect = [[this.dragStartRel[0], dragRel[0]], [this.dragStartRel[1], dragRel[1]]];
+    for (i = _j = 0; _j <= 1; i = _j += 1) {
+      if (rect[i][1] < rect[i][0]) {
+        rect[i] = [rect[i][1], rect[i][0]];
+      }
+      rect[i][0] = Math.max(rect[i][0], 0);
+    }
+    return rect;
+  },
+  _endSelection: function(dragEnd) {
+    var args, i, j, rect, selis, _i, _j, _k, _ref, _ref1;
+    jbone(document.body).off('.overmove');
+    jbone(document.body).off('.overup');
+    if (this.dragStart.length === 0) {
+      return;
+    }
+    rect = this._calcSelection(dragEnd);
+    for (i = _i = 0; _i <= 1; i = ++_i) {
+      rect[0][i] = Math.floor(rect[0][i] / this.g.zoomer.get("boxRectWidth"));
+    }
+    for (i = _j = 0; _j <= 1; i = ++_j) {
+      rect[1][i] = Math.floor(rect[1][i] / this.g.zoomer.get("boxRectHeight"));
+    }
+    rect[0][1] = Math.min(this.model.getMaxLength() - 1, rect[0][1]);
+    rect[1][1] = Math.min(this.model.length - 1, rect[1][1]);
+    selis = [];
+    for (j = _k = _ref = rect[1][0], _ref1 = rect[1][1]; _k <= _ref1; j = _k += 1) {
+      args = {
+        seqId: this.model.at(j).get('id'),
+        xStart: rect[0][0],
+        xEnd: rect[0][1]
+      };
+      selis.push(new selection.possel(args));
+    }
+    this.dragStart = [];
+    if (this.prolongSelection) {
+      this.g.selcol.add(selis);
+    } else {
+      this.g.selcol.reset(selis);
+    }
+    this.g.zoomer.setLeftOffset(rect[0][0]);
+    return this.g.zoomer.setTopOffset(rect[1][0]);
+  },
+  _onmouseup: function(e) {
+    return this._endSelection(mouse.abs(e));
+  },
+  _onmouseout: function(e) {
+    return this._endSelection(mouse.abs(e));
+  },
+  _createCanvas: function() {
+    var rectHeight, rectWidth;
+    rectWidth = this.g.zoomer.get("boxRectWidth");
+    rectHeight = this.g.zoomer.get("boxRectHeight");
+    this.el.height = this.model.length * rectHeight;
+    this.el.width = this.model.getMaxLength() * rectWidth;
+    this.ctx = this.el.getContext("2d");
+    this.el.style.overflow = "scroll";
+    return this.el.style.cursor = "crosshair";
+  }
+});
+
+
+
+},{"../g/selection/Selection":67,"backbone-viewj":10,"biojs-util-colorschemes":29,"jbone":50,"mouse-pos":51,"underscore":59}],100:[function(require,module,exports){
+var AlignmentBody, HeaderBlock, OverviewBox, boneView, identityCalc, _;
+
+boneView = require("backbone-childs");
+
+AlignmentBody = require("./AlignmentBody");
+
+HeaderBlock = require("./header/HeaderBlock");
+
+OverviewBox = require("./OverviewBox");
+
+identityCalc = require("../algo/identityCalc");
+
+_ = require('underscore');
+
+module.exports = boneView.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    this.draw();
+    this.listenTo(this.model, "reset", function() {
+      this.isNotDirty = false;
+      return this.rerender();
+    });
+    this.listenTo(this.model, "change:hidden", _.debounce(this.rerender, 10));
+    this.listenTo(this.model, "sort", this.rerender);
+    this.listenTo(this.model, "add", function() {
+      return console.log("seq add");
+    });
+    this.listenTo(this.g.vis, "change:sequences", this.rerender);
+    this.listenTo(this.g.vis, "change:overviewbox", this.rerender);
+    return this.listenTo(this.g.visorder, "change", this.rerender);
+  },
+  draw: function() {
+    var body, consensus, headerblock, overviewbox;
+    this.removeViews();
+    if (!this.isNotDirty) {
+      consensus = this.g.consensus.getConsensus(this.model);
+      identityCalc(this.model, consensus);
+      this.isNotDirty = true;
+    }
+    if (this.g.vis.get("overviewbox")) {
+      overviewbox = new OverviewBox({
+        model: this.model,
+        g: this.g
+      });
+      overviewbox.ordering = this.g.visorder.get('overviewBox');
+      this.addView("overviewbox", overviewbox);
+    }
+    if (true) {
+      headerblock = new HeaderBlock({
+        model: this.model,
+        g: this.g
+      });
+      headerblock.ordering = this.g.visorder.get('headerBox');
+      this.addView("headerblock", headerblock);
+    }
+    body = new AlignmentBody({
+      model: this.model,
+      g: this.g
+    });
+    body.ordering = this.g.visorder.get('alignmentBody');
+    return this.addView("body", body);
+  },
+  render: function() {
+    this.renderSubviews();
+    this.el.className = "biojs_msa_stage";
+    return this;
+  },
+  rerender: function() {
+    this.draw();
+    return this.render();
+  }
+});
+
+
+
+},{"../algo/identityCalc":61,"./AlignmentBody":96,"./OverviewBox":99,"./header/HeaderBlock":102,"backbone-childs":3,"underscore":59}],101:[function(require,module,exports){
+var ConservationView, dom, svg, view;
+
+view = require("backbone-viewj");
+
+dom = require("dom-helper");
+
+svg = require("../../utils/svg");
+
+ConservationView = view.extend({
+  className: "biojs_msa_conserv",
+  initialize: function(data) {
+    this.g = data.g;
+    this.listenTo(this.g.zoomer, "change:stepSize change:labelWidth change:columnWidth", this.render);
+    this.listenTo(this.g.vis, "change:labels change:metacell", this.render);
+    this.listenTo(this.g.columns, "change:scaling", this.render);
+    this.listenTo(this.model, "reset", this.render);
+    return this.manageEvents();
+  },
+  render: function() {
+    var avgHeight, cellWidth, height, hidden, i, maxHeight, n, nMax, rect, s, stepSize, width, x, _i, _ref;
+    this.g.columns.calcConservation(this.model);
+    dom.removeAllChilds(this.el);
+    nMax = this.model.getMaxLength();
+    cellWidth = this.g.zoomer.get("columnWidth");
+    maxHeight = 20;
+    width = cellWidth * (nMax - this.g.columns.get('hidden').length);
+    console.log(this.g.columns.get('hidden'));
+    s = svg.base({
+      height: maxHeight,
+      width: width
+    });
+    s.style.display = "inline-block";
+    s.style.cursor = "pointer";
+    stepSize = this.g.zoomer.get("stepSize");
+    hidden = this.g.columns.get("hidden");
+    x = 0;
+    n = 0;
+    while (n < nMax) {
+      if (hidden.indexOf(n) >= 0) {
+        n += stepSize;
+        continue;
+      }
+      width = cellWidth * stepSize;
+      avgHeight = 0;
+      for (i = _i = 0, _ref = stepSize - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
+        avgHeight += this.g.columns.get("conserv")[n];
+      }
+      height = maxHeight * (avgHeight / stepSize);
+      rect = svg.rect({
+        x: x,
+        y: maxHeight - height,
+        width: width - cellWidth / 4,
+        height: height,
+        style: "stroke:red;stroke-width:1;"
+      });
+      rect.rowPos = n;
+      s.appendChild(rect);
+      x += width;
+      n += stepSize;
+    }
+    this.el.appendChild(s);
+    return this;
+  },
+  _onclick: function(evt) {
+    var i, rowPos, stepSize, _i, _ref, _results;
+    rowPos = evt.target.rowPos;
+    stepSize = this.g.zoomer.get("stepSize");
+    _results = [];
+    for (i = _i = 0, _ref = stepSize - 1; _i <= _ref; i = _i += 1) {
+      _results.push(this.g.trigger("bar:click", {
+        rowPos: rowPos + i,
+        evt: evt
+      }));
+    }
+    return _results;
+  },
+  manageEvents: function() {
+    var events;
+    events = {};
+    if (this.g.config.get("registerMouseClicks")) {
+      events.click = "_onclick";
+    }
+    if (this.g.config.get("registerMouseHover")) {
+      events.mousein = "_onmousein";
+      events.mouseout = "_onmouseout";
+    }
+    this.delegateEvents(events);
+    this.listenTo(this.g.config, "change:registerMouseHover", this.manageEvents);
+    return this.listenTo(this.g.config, "change:registerMouseClick", this.manageEvents);
+  },
+  _onmousein: function(evt) {
+    var rowPos;
+    rowPos = this.g.zoomer.get("stepSize" * evt.rowPos);
+    return this.g.trigger("bar:mousein", {
+      rowPos: rowPos,
+      evt: evt
+    });
+  },
+  _onmouseout: function(evt) {
+    var rowPos;
+    rowPos = this.g.zoomer.get("stepSize" * evt.rowPos);
+    return this.g.trigger("bar:mouseout", {
+      rowPos: rowPos,
+      evt: evt
+    });
+  }
+});
+
+module.exports = ConservationView;
+
+
+
+},{"../../utils/svg":95,"backbone-viewj":10,"dom-helper":49}],102:[function(require,module,exports){
+var ConservationView, MarkerView, boneView, identityCalc, _;
+
+MarkerView = require("./MarkerView");
+
+ConservationView = require("./ConservationView");
+
+identityCalc = require("../../algo/identityCalc");
+
+boneView = require("backbone-childs");
+
+_ = require('underscore');
+
+module.exports = boneView.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    this.blockEvents = false;
+    this.listenTo(this.g.vis, "change:markers change:conserv", function() {
+      this.draw();
+      return this.render();
+    });
+    this.listenTo(this.g.vis, "change", this._setSpacer);
+    this.listenTo(this.g.zoomer, "change:alignmentWidth", function() {
+      return this._adjustWidth();
+    });
+    this.listenTo(this.g.zoomer, "change:_alignmentScrollLeft", this._adjustScrollingLeft);
+    this.listenTo(this.g.columns, "change:hidden", function() {
+      this.draw();
+      return this.render();
+    });
+    this.draw();
+    this._onscroll = this._sendScrollEvent;
+    return this.g.vis.once('change:loaded', this._adjustScrollingLeft, this);
+  },
+  events: {
+    "scroll": "_onscroll"
+  },
+  draw: function() {
+    var consensus, conserv, marker;
+    this.removeViews();
+    if (!this.isNotDirty) {
+      consensus = this.g.consensus.getConsensus(this.model);
+      identityCalc(this.model, consensus);
+      this.isNotDirty = true;
+    }
+    if (this.g.vis.get("conserv")) {
+      conserv = new ConservationView({
+        model: this.model,
+        g: this.g
+      });
+      conserv.ordering = -20;
+      this.addView("conserv", conserv);
+    }
+    if (this.g.vis.get("markers")) {
+      marker = new MarkerView({
+        model: this.model,
+        g: this.g
+      });
+      marker.ordering = -10;
+      return this.addView("marker", marker);
+    }
+  },
+  render: function() {
+    this.renderSubviews();
+    this._setSpacer();
+    this.el.className = "biojs_msa_header";
+    this.el.style.overflowX = "auto";
+    this._adjustWidth();
+    this._adjustScrollingLeft();
+    return this;
+  },
+  _sendScrollEvent: function() {
+    if (!this.blockEvents) {
+      this.g.zoomer.set("_alignmentScrollLeft", this.el.scrollLeft, {
+        origin: "header"
+      });
+    }
+    return this.blockEvents = false;
+  },
+  _adjustScrollingLeft: function(model, value, options) {
+    var scrollLeft;
+    if (((options != null ? options.origin : void 0) == null) || options.origin !== "header") {
+      scrollLeft = this.g.zoomer.get("_alignmentScrollLeft");
+      this.blockEvents = true;
+      return this.el.scrollLeft = scrollLeft;
+    }
+  },
+  _setSpacer: function() {
+    return this.el.style.marginLeft = this._getLabelWidth() + "px";
+  },
+  _getLabelWidth: function() {
+    var paddingLeft;
+    paddingLeft = 0;
+    if (this.g.vis.get("labels")) {
+      paddingLeft += this.g.zoomer.get("labelWidth");
+    }
+    if (this.g.vis.get("metacell")) {
+      paddingLeft += this.g.zoomer.get("metaWidth");
+    }
+    return paddingLeft;
+  },
+  _adjustWidth: function() {
+    return this.el.style.width = this.g.zoomer.get("alignmentWidth") + "px";
+  }
+});
+
+
+
+},{"../../algo/identityCalc":61,"./ConservationView":101,"./MarkerView":103,"backbone-childs":3,"underscore":59}],103:[function(require,module,exports){
+var HeaderView, dom, jbone, svg, view;
+
+view = require("backbone-viewj");
+
+dom = require("dom-helper");
+
+svg = require("../../utils/svg");
+
+jbone = require("jbone");
+
+HeaderView = view.extend({
+  className: "biojs_msa_marker",
+  initialize: function(data) {
+    this.g = data.g;
+    this.listenTo(this.g.zoomer, "change:stepSize change:labelWidth change:columnWidth change:markerStepSize change:markerFontsize", this.render);
+    this.listenTo(this.g.vis, "change:labels change:metacell", this.render);
+    return this.manageEvents();
+  },
+  render: function() {
+    var cellWidth, container, hidden, n, nMax, span, stepSize;
+    dom.removeAllChilds(this.el);
+    this.el.style.fontSize = this.g.zoomer.get("markerFontsize");
+    container = document.createElement("span");
+    n = 0;
+    cellWidth = this.g.zoomer.get("columnWidth");
+    nMax = this.model.getMaxLength();
+    stepSize = this.g.zoomer.get("stepSize");
+    hidden = this.g.columns.get("hidden");
+    while (n < nMax) {
+      if (hidden.indexOf(n) >= 0) {
+        this.markerHidden(span, n, stepSize);
+        n += stepSize;
+        continue;
+      }
+      span = document.createElement("span");
+      span.style.width = (cellWidth * stepSize) + "px";
+      span.style.display = "inline-block";
+      if ((n + 1) % this.g.zoomer.get('markerStepSize') === 0) {
+        span.textContent = n + 1;
+      } else {
+        span.textContent = ".";
+      }
+      span.rowPos = n;
+      n += stepSize;
+      container.appendChild(span);
+    }
+    this.el.appendChild(container);
+    return this;
+  },
+  markerHidden: function(span, n, stepSize) {
+    var hidden, index, j, length, min, nMax, prevHidden, s, triangle, _i, _j;
+    hidden = this.g.columns.get("hidden").slice(0);
+    min = Math.max(0, n - stepSize);
+    prevHidden = true;
+    for (j = _i = min; _i <= n; j = _i += 1) {
+      prevHidden &= hidden.indexOf(j) >= 0;
+    }
+    if (prevHidden) {
+      return;
+    }
+    nMax = this.model.getMaxLength();
+    length = 0;
+    index = -1;
+    for (n = _j = n; _j <= nMax; n = _j += 1) {
+      if (!(index >= 0)) {
+        index = hidden.indexOf(n);
+      }
+      if (hidden.indexOf(n) >= 0) {
+        length++;
+      } else {
+        break;
+      }
+    }
+    s = svg.base({
+      height: 10,
+      width: 10
+    });
+    s.style.position = "relative";
+    triangle = svg.polygon({
+      points: "0,0 5,5 10,0",
+      style: "fill:lime;stroke:purple;stroke-width:1"
+    });
+    jbone(triangle).on("click", (function(_this) {
+      return function(evt) {
+        hidden.splice(index, length);
+        return _this.g.columns.set("hidden", hidden);
+      };
+    })(this));
+    s.appendChild(triangle);
+    span.appendChild(s);
+    return s;
+  },
+  manageEvents: function() {
+    var events;
+    events = {};
+    if (this.g.config.get("registerMouseClicks")) {
+      events.click = "_onclick";
+    }
+    if (this.g.config.get("registerMouseHover")) {
+      events.mousein = "_onmousein";
+      events.mouseout = "_onmouseout";
+    }
+    this.delegateEvents(events);
+    this.listenTo(this.g.config, "change:registerMouseHover", this.manageEvents);
+    return this.listenTo(this.g.config, "change:registerMouseClick", this.manageEvents);
+  },
+  _onclick: function(evt) {
+    var rowPos, stepSize;
+    rowPos = evt.target.rowPos;
+    stepSize = this.g.zoomer.get("stepSize");
+    return this.g.trigger("column:click", {
+      rowPos: rowPos,
+      stepSize: stepSize,
+      evt: evt
+    });
+  },
+  _onmousein: function(evt) {
+    var rowPos, stepSize;
+    rowPos = this.g.zoomer.get("stepSize" * evt.rowPos);
+    stepSize = this.g.zoomer.get("stepSize");
+    return this.g.trigger("column:mousein", {
+      rowPos: rowPos,
+      stepSize: stepSize,
+      evt: evt
+    });
+  },
+  _onmouseout: function(evt) {
+    var rowPos, stepSize;
+    rowPos = this.g.zoomer.get("stepSize" * evt.rowPos);
+    stepSize = this.g.zoomer.get("stepSize");
+    return this.g.trigger("column:mouseout", {
+      rowPos: rowPos,
+      stepSize: stepSize,
+      evt: evt
+    });
+  }
+});
+
+module.exports = HeaderView;
+
+
+
+},{"../../utils/svg":95,"backbone-viewj":10,"dom-helper":49,"jbone":50}],104:[function(require,module,exports){
+var LabelRowView, boneView;
+
+LabelRowView = require("./LabelRowView");
+
+boneView = require("backbone-childs");
+
+module.exports = boneView.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    this.draw();
+    this.listenTo(this.g.zoomer, "change:_alignmentScrollTop", this._adjustScrollingTop);
+    return this.g.vis.once('change:loaded', this._adjustScrollingTop, this);
+  },
+  draw: function() {
+    var i, view, _i, _ref, _results;
+    this.removeViews();
+    _results = [];
+    for (i = _i = 0, _ref = this.model.length - 1; _i <= _ref; i = _i += 1) {
+      if (this.model.at(i).get('hidden')) {
+        continue;
+      }
+      view = new LabelRowView({
+        model: this.model.at(i),
+        g: this.g
+      });
+      view.ordering = i;
+      _results.push(this.addView("row_" + i, view));
+    }
+    return _results;
+  },
+  events: {
+    "scroll": "_sendScrollEvent"
+  },
+  _sendScrollEvent: function() {
+    return this.g.zoomer.set("_alignmentScrollTop", this.el.scrollTop, {
+      origin: "label"
+    });
+  },
+  _adjustScrollingTop: function() {
+    return this.el.scrollTop = this.g.zoomer.get("_alignmentScrollTop");
+  },
+  render: function() {
+    this.renderSubviews();
+    this.el.className = "biojs_msa_labelblock";
+    this.el.style.display = "inline-block";
+    this.el.style.verticalAlign = "top";
+    this.el.style.height = this.g.zoomer.get("alignmentHeight") + "px";
+    this.el.style.overflowY = "auto";
+    this.el.style.overflowX = "hidden";
+    this.el.style.fontSize = "" + (this.g.zoomer.get("labelFontsize"));
+    this.el.style.lineHeight = "" + (this.g.zoomer.get("labelLineHeight"));
+    return this;
+  }
+});
+
+
+
+},{"./LabelRowView":105,"backbone-childs":3}],105:[function(require,module,exports){
+var LabelView, MetaView, boneView;
+
+boneView = require("backbone-childs");
+
+LabelView = require("./LabelView");
+
+MetaView = require("./MetaView");
+
+module.exports = boneView.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    this.draw();
+    this.listenTo(this.g.vis, "change:labels", this.drawR);
+    return this.listenTo(this.g.vis, "change:metacell", this.drawR);
+  },
+  draw: function() {
+    this.removeViews();
+    if (this.g.vis.get("labels")) {
+      this.addView("labels", new LabelView({
+        model: this.model,
+        g: this.g
+      }));
+    }
+    if (this.g.vis.get("metacell")) {
+      return this.addView("metacell", new MetaView({
+        model: this.model,
+        g: this.g
+      }));
+    }
+  },
+  drawR: function() {
+    this.draw();
+    return this.render();
+  },
+  render: function() {
+    this.renderSubviews();
+    this.el.setAttribute("class", "biojs_msa_labelrow");
+    this.el.style.height = this.g.zoomer.get("rowHeight");
+    return this;
+  }
+});
+
+
+
+},{"./LabelView":106,"./MetaView":107,"backbone-childs":3}],106:[function(require,module,exports){
+var LabelView, dom, view;
+
+view = require("backbone-viewj");
+
+dom = require("dom-helper");
+
+LabelView = view.extend({
+  initialize: function(data) {
+    this.seq = data.seq;
+    this.g = data.g;
+    return this.manageEvents();
+  },
+  manageEvents: function() {
+    var events;
+    events = {};
+    if (this.g.config.get("registerMouseClicks")) {
+      events.click = "_onclick";
+    }
+    if (this.g.config.get("registerMouseHover")) {
+      events.mousein = "_onmousein";
+      events.mouseout = "_onmouseout";
+    }
+    this.delegateEvents(events);
+    this.listenTo(this.g.config, "change:registerMouseHover", this.manageEvents);
+    this.listenTo(this.g.config, "change:registerMouseClick", this.manageEvents);
+    this.listenTo(this.g.vis, "change:labelName", this.render);
+    this.listenTo(this.g.vis, "change:labelId", this.render);
+    this.listenTo(this.g.vis, "change:labelPartition", this.render);
+    return this.listenTo(this.g.vis, "change:labelCheckbox", this.render);
+  },
+  render: function() {
+    var checkBox, id, name, part;
+    dom.removeAllChilds(this.el);
+    this.el.style.width = "" + (this.g.zoomer.get("labelWidth")) + "px";
+    this.el.style.height = "" + (this.g.zoomer.get("rowHeight")) + "px";
+    this.el.setAttribute("class", "biojs_msa_labels");
+    if (this.g.vis.get("labelCheckbox")) {
+      checkBox = document.createElement("input");
+      checkBox.setAttribute("type", "checkbox");
+      checkBox.value = this.model.get('id');
+      checkBox.name = "seq";
+      this.el.appendChild(checkBox);
+    }
+    if (this.g.vis.get("labelId")) {
+      id = document.createElement("span");
+      id.textContent = this.model.get("id");
+      id.style.width = this.g.zoomer.get("labelIdLength");
+      id.style.display = "inline-block";
+      this.el.appendChild(id);
+    }
+    if (this.g.vis.get("labelPartition")) {
+      part = document.createElement("span");
+      part.style.width = 15;
+      part.textContent = this.model.get("partition");
+      part.style.display = "inline-block";
+      this.el.appendChild(id);
+      this.el.appendChild(part);
+    }
+    if (this.g.vis.get("labelName")) {
+      name = document.createElement("span");
+      name.textContent = this.model.get("name");
+      this.el.appendChild(name);
+    }
+    this.el.style.overflow = scroll;
+    return this;
+  },
+  _onclick: function(evt) {
+    var seqId;
+    seqId = this.model.get("id");
+    return this.g.trigger("row:click", {
+      seqId: seqId,
+      evt: evt
+    });
+  },
+  _onmousein: function(evt) {
+    var seqId;
+    seqId = this.model.get("id");
+    return this.g.trigger("row:mouseout", {
+      seqId: seqId,
+      evt: evt
+    });
+  },
+  _onmouseout: function(evt) {
+    var seqId;
+    seqId = this.model.get("id");
+    return this.g.trigger("row:mouseout", {
+      seqId: seqId,
+      evt: evt
+    });
+  }
+});
+
+module.exports = LabelView;
+
+
+
+},{"backbone-viewj":10,"dom-helper":49}],107:[function(require,module,exports){
+var MenuBuilder, MetaView, dom, view, _;
+
+view = require("backbone-viewj");
+
+MenuBuilder = require("../../menu/menubuilder");
+
+_ = require('underscore');
+
+dom = require("dom-helper");
+
+module.exports = MetaView = view.extend({
+  className: "biojs_msa_metaview",
+  initialize: function(data) {
+    return this.g = data.g;
+  },
+  events: {
+    click: "_onclick",
+    mousein: "_onmousein",
+    mouseout: "_onmouseout"
+  },
+  render: function() {
+    var gapSpan, gaps, ident, identSpan, menu, seq, width;
+    dom.removeAllChilds(this.el);
+    this.el.style.display = "inline-block";
+    width = this.g.zoomer.get("metaWidth");
+    this.el.style.width = width - 5;
+    this.el.style.paddingRight = 5;
+    seq = this.model.get('seq');
+    gaps = _.reduce(seq, (function(memo, c) {
+      if (c === '-') {
+        memo++;
+      }
+      return memo;
+    }), 0);
+    gaps = (gaps / seq.length).toFixed(1);
+    gapSpan = document.createElement('span');
+    gapSpan.textContent = gaps;
+    gapSpan.style.display = "inline-block";
+    gapSpan.style.width = 35;
+    this.el.appendChild(gapSpan);
+    ident = this.model.get('identity');
+    identSpan = document.createElement('span');
+    identSpan.textContent = ident.toFixed(2);
+    identSpan.style.display = "inline-block";
+    identSpan.style.width = 40;
+    this.el.appendChild(identSpan);
+    menu = new MenuBuilder("↗");
+    menu.addNode("Uniprot", (function(_this) {
+      return function(e) {
+        return window.open("http://beta.uniprot.org/uniprot/Q7T2N8");
+      };
+    })(this));
+    this.el.appendChild(menu.buildDOM());
+    this.el.width = 10;
+    this.el.style.height = "" + (this.g.zoomer.get("rowHeight")) + "px";
+    return this.el.style.cursor = "pointer";
+  },
+  _onclick: function(evt) {
+    return this.g.trigger("meta:click", {
+      seqId: this.model.get("id", {
+        evt: evt
+      })
+    });
+  },
+  _onmousein: function(evt) {
+    return this.g.trigger("meta:mousein", {
+      seqId: this.model.get("id", {
+        evt: evt
+      })
+    });
+  },
+  _onmouseout: function(evt) {
+    return this.g.trigger("meta:mouseout", {
+      seqId: this.model.get("id", {
+        evt: evt
+      })
+    });
+  }
+});
+
+
+
+},{"../../menu/menubuilder":75,"backbone-viewj":10,"dom-helper":49,"underscore":59}],"biojs-io-clustal":[function(require,module,exports){
+// Generated by CoffeeScript 1.8.0
+var Clustal, GenericReader, Seq, Str,
+  __hasProp = {}.hasOwnProperty,
+  __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+Str = require("./strings");
+
+GenericReader = require("./generic_reader");
+
+Seq = require("./seq");
+
+module.exports = Clustal = (function(_super) {
+  __extends(Clustal, _super);
+
+  function Clustal() {
+    return Clustal.__super__.constructor.apply(this, arguments);
+  }
+
+  Clustal.parse = function(text) {
+    var blockstate, k, label, line, lines, match, regex, seqCounter, seqs, sequence;
+    seqs = [];
+    if (Object.prototype.toString.call(text) === '[object Array]') {
+      lines = text;
+    } else {
+      lines = text.split("\n");
+    }
+    if (lines[0].slice(0, 6) === !"CLUSTAL") {
+      throw new Error("Invalid CLUSTAL Header");
+    }
+    k = 0;
+    blockstate = 1;
+    seqCounter = 0;
+    while (k < lines.length) {
+      k++;
+      line = lines[k];
+      if ((line == null) || line.length === 0) {
+        blockstate = 1;
+        continue;
+      }
+      if (line.trim().length === 0) {
+        blockstate = 1;
+        continue;
+      } else {
+        if (Str.contains(line, "*")) {
+          continue;
+        }
+        if (blockstate === 1) {
+          seqCounter = 0;
+          blockstate = 0;
+        }
+        regex = /^(?:\s*)(\S+)(?:\s+)(\S+)(?:\s*)(\d*)(?:\s*|$)/g;
+        match = regex.exec(line);
+        if (match != null) {
+          label = match[1];
+          sequence = match[2];
+          if (seqCounter >= seqs.length) {
+            seqs.push(new Seq(sequence, label, seqCounter));
+          } else {
+            seqs[seqCounter].seq += sequence;
+          }
+          seqCounter++;
+        } else {
+          console.log(line);
+        }
+      }
+    }
+    return seqs;
+  };
+
+  return Clustal;
+
+})(GenericReader);
+
+},{"./generic_reader":17,"./seq":18,"./strings":19}],"biojs-io-fasta":[function(require,module,exports){
+// Generated by CoffeeScript 1.8.0
+module.exports.parse = require("./parser");
+
+module.exports.writer = require("./writer");
+
+},{"./parser":21,"./writer":24}],"biojs-vis-msa":[function(require,module,exports){
+if (typeof biojs === 'undefined') {
+  biojs = {};
+}
+if (typeof biojs.vis === 'undefined') {
+  biojs.vis = {};
+}
+// use two namespaces
+window.msa = biojs.vis.msa = module.exports = require('./index');
+
+// TODO: how should this be bundled
+
+if (typeof biojs.io === 'undefined') {
+  biojs.io = {};
+}
+// just bundle the two parsers
+window.biojs.io.fasta = require("biojs-io-fasta");
+window.biojs.io.clustal = require("biojs-io-clustal");
+window.biojs.xhr = require("nets");
+
+// simulate standalone flag
+window.biojsVisMsa = window.msa;
+
+require('./build/msa.css');
+
+},{"./build/msa.css":1,"./index":2,"biojs-io-clustal":undefined,"biojs-io-fasta":undefined,"nets":undefined}],"nets":[function(require,module,exports){
+var req = require('request')
+
+module.exports = Nets
+
+function Nets(uri, opts, cb) {
+  req(uri, opts, cb)
+}
+},{"request":52}]},{},["biojs-vis-msa"])
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9idWlsZC9tc2EuY3NzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2EvaW5kZXguanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmFja2JvbmUtY2hpbGRzL2luZGV4LmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2JhY2tib25lLXRoaW4vY29sbGVjdGlvbi5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9iYWNrYm9uZS10aGluL2luZGV4LmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2JhY2tib25lLXRoaW4vbW9kZWwuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmFja2JvbmUtdGhpbi9ub2RlX21vZHVsZXMvYmFja2JvbmUtZXZlbnRzLXN0YW5kYWxvbmUvYmFja2JvbmUtZXZlbnRzLXN0YW5kYWxvbmUuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmFja2JvbmUtdGhpbi9ub2RlX21vZHVsZXMvYmFja2JvbmUtZXZlbnRzLXN0YW5kYWxvbmUvaW5kZXguanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmFja2JvbmUtdGhpbi9ub2RlX21vZHVsZXMvYmFja2JvbmUtZXh0ZW5kLXN0YW5kYWxvbmUvYmFja2JvbmUtZXh0ZW5kLXN0YW5kYWxvbmUuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmFja2JvbmUtdmlld2ovaW5kZXguanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmlvanMtZXZlbnRzL2luZGV4LmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLWlvLWNsdXN0YWwvbGliL2dlbmVyaWNfcmVhZGVyLmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLWlvLWNsdXN0YWwvbGliL3NlcS5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9iaW9qcy1pby1jbHVzdGFsL2xpYi9zdHJpbmdzLmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLWlvLWZhc3RhL2xpYi9wYXJzZXIuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmlvanMtaW8tZmFzdGEvbGliL3V0aWxzLmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLWlvLWZhc3RhL2xpYi93cml0ZXIuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmlvanMtaW8tZmFzdGEvbm9kZV9tb2R1bGVzL2Jpb2pzLW1vZGVsL3NyYy9pbmRleC5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9iaW9qcy1pby1mYXN0YS9ub2RlX21vZHVsZXMvYmlvanMtbW9kZWwvc3JjL3NlcS5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9iaW9qcy11dGlsLWNvbG9yc2NoZW1lcy9pbmRleC5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9iaW9qcy11dGlsLWNvbG9yc2NoZW1lcy9zcmMvYnVyaWVkLmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLXV0aWwtY29sb3JzY2hlbWVzL3NyYy9jaW5lbWEuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmlvanMtdXRpbC1jb2xvcnNjaGVtZXMvc3JjL2NsdXN0YWwuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmlvanMtdXRpbC1jb2xvcnNjaGVtZXMvc3JjL2NsdXN0YWwyLmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLXV0aWwtY29sb3JzY2hlbWVzL3NyYy9oZWxpeC5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9iaW9qcy11dGlsLWNvbG9yc2NoZW1lcy9zcmMvaHlkcm9waG9iaWNpdHkuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmlvanMtdXRpbC1jb2xvcnNjaGVtZXMvc3JjL2luZGV4LmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLXV0aWwtY29sb3JzY2hlbWVzL3NyYy9sZXNrLmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLXV0aWwtY29sb3JzY2hlbWVzL3NyYy9tYWUuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmlvanMtdXRpbC1jb2xvcnNjaGVtZXMvc3JjL251Y2xlb3RpZGUuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmlvanMtdXRpbC1jb2xvcnNjaGVtZXMvc3JjL3B1cmluZS5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9iaW9qcy11dGlsLWNvbG9yc2NoZW1lcy9zcmMvc2VsZWN0b3IuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmlvanMtdXRpbC1jb2xvcnNjaGVtZXMvc3JjL3N0cmFuZC5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9iaW9qcy11dGlsLWNvbG9yc2NoZW1lcy9zcmMvdGF5bG9yLmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLXV0aWwtY29sb3JzY2hlbWVzL3NyYy90dXJuLmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLXV0aWwtY29sb3JzY2hlbWVzL3NyYy96YXBwby5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9ibHVlaW1wX2NhbnZhc3RvYmxvYi9pbmRleC5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9icm93c2VyLXNhdmVhcy9pbmRleC5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9jc3NpZnkvYnJvd3Nlci5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9kb20taGVscGVyL2luZGV4LmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2pib25lL2Rpc3QvamJvbmUuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvbW91c2UtcG9zL2luZGV4LmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL25ldHMvbm9kZV9tb2R1bGVzL3hoci9pbmRleC5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9uZXRzL25vZGVfbW9kdWxlcy94aHIvbm9kZV9tb2R1bGVzL2dsb2JhbC93aW5kb3cuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvbmV0cy9ub2RlX21vZHVsZXMveGhyL25vZGVfbW9kdWxlcy9vbmNlL29uY2UuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvbmV0cy9ub2RlX21vZHVsZXMveGhyL25vZGVfbW9kdWxlcy9wYXJzZS1oZWFkZXJzL25vZGVfbW9kdWxlcy9mb3ItZWFjaC9pbmRleC5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9uZXRzL25vZGVfbW9kdWxlcy94aHIvbm9kZV9tb2R1bGVzL3BhcnNlLWhlYWRlcnMvbm9kZV9tb2R1bGVzL2Zvci1lYWNoL25vZGVfbW9kdWxlcy9pcy1mdW5jdGlvbi9pbmRleC5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9uZXRzL25vZGVfbW9kdWxlcy94aHIvbm9kZV9tb2R1bGVzL3BhcnNlLWhlYWRlcnMvbm9kZV9tb2R1bGVzL3RyaW0vaW5kZXguanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvbmV0cy9ub2RlX21vZHVsZXMveGhyL25vZGVfbW9kdWxlcy9wYXJzZS1oZWFkZXJzL3BhcnNlLWhlYWRlcnMuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvdW5kZXJzY29yZS91bmRlcnNjb3JlLmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL2FsZ28vQ29uc2Vuc3VzQ2FsYy5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvYWxnby9pZGVudGl0eUNhbGMuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL2FsZ28vaW5kZXguY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL2cvY29sb3JhdG9yLmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9nL2NvbHVtbnMuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL2cvY29uZmlnLmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9nL2NvbnNlbnN1cy5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvZy9zZWxlY3Rpb24vU2VsZWN0aW9uLmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9nL3NlbGVjdGlvbi9TZWxlY3Rpb25Db2wuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL2cvdmlzT3JkZXJpbmcuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL2cvdmlzaWJpbGl0eS5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvZy96b29tZXIuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL2luZGV4LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9tZW51L2RlZmF1bHRtZW51LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9tZW51L2luZGV4LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9tZW51L21lbnVidWlsZGVyLmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9tZW51L3ZpZXdzL0NvbG9yTWVudS5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvbWVudS92aWV3cy9FeHBvcnRNZW51LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9tZW51L3ZpZXdzL0V4dHJhTWVudS5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvbWVudS92aWV3cy9GaWx0ZXJNZW51LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9tZW51L3ZpZXdzL0hlbHBNZW51LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9tZW51L3ZpZXdzL0ltcG9ydE1lbnUuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL21lbnUvdmlld3MvT3JkZXJpbmdNZW51LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9tZW51L3ZpZXdzL1NlbGVjdGlvbk1lbnUuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL21lbnUvdmlld3MvVmlzTWVudS5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvbW9kZWwvRmVhdHVyZS5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvbW9kZWwvRmVhdHVyZUNvbC5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvbW9kZWwvU2VxQ29sbGVjdGlvbi5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvbW9kZWwvU2VxdWVuY2UuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL21vZGVsL2luZGV4LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9tc2EuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL3V0aWxzL2JtYXRoLmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy91dGlscy9pbmRleC5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvdXRpbHMvcHJveHkuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL3V0aWxzL3NlcWdlbi5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvdXRpbHMvc3ZnLmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy92aWV3cy9BbGlnbm1lbnRCb2R5LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy92aWV3cy9DYW52YXNDaGFyQ2FjaGUuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL3ZpZXdzL0NhbnZhc1NlcUJsb2NrLmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy92aWV3cy9PdmVydmlld0JveC5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvdmlld3MvU3RhZ2UuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL3ZpZXdzL2hlYWRlci9Db25zZXJ2YXRpb25WaWV3LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy92aWV3cy9oZWFkZXIvSGVhZGVyQmxvY2suY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL3ZpZXdzL2hlYWRlci9NYXJrZXJWaWV3LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy92aWV3cy9sYWJlbHMvTGFiZWxCbG9jay5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvdmlld3MvbGFiZWxzL0xhYmVsUm93Vmlldy5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvdmlld3MvbGFiZWxzL0xhYmVsVmlldy5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvdmlld3MvbGFiZWxzL01ldGFWaWV3LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9iaW9qcy1pby1jbHVzdGFsL2xpYi9jbHVzdGFsLmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLWlvLWZhc3RhL2xpYi9pbmRleC5qcyIsIi4vYnJvd3NlciIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9uZXRzL2luZGV4LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FDQUE7O0FDQUE7QUFDQTs7QUNEQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDcmJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDSkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDOVdBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDclJBO0FBQ0E7O0FDREE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN6RUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7Ozs7Ozs7QUMvS0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7Ozs7OztBQ3JCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUMzQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDZkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7OztBQ1ZBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7O0FDbkRBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2ZBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUMxQkE7QUFDQTs7QUNEQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7Ozs7O0FDTkE7QUFDQTs7QUNEQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDN0JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM3QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN6QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN6QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM1QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNuQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzdCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDN0JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDUEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDVEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDN0NBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDekJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDekJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDekJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDekJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNuRkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN2UEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNyQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3gxQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3JEQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDaExBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNYQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ25CQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzlDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNmQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDZEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDOUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN2NENBLElBQUEsQ0FBQTs7QUFBQSxDQUFBLEdBQUksT0FBQSxDQUFRLFlBQVIsQ0FBSixDQUFBOztBQUFBLE1BSU0sQ0FBQyxPQUFQLEdBQWlCLFNBQUMsSUFBRCxHQUFBO0FBRWYsTUFBQSxJQUFBO0FBQUEs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Q0FyRFo7QUFBQSxFQXdEQSxXQUFBLEVBQWEsU0FBQyxHQUFELEdBQUE7V0FDWCxJQUFDLENBQUEsQ0FBQyxDQUFDLE9BQUgsQ0FBVyxlQUFYLEVBQTRCO0FBQUEsTUFBQyxLQUFBLEVBQU8sSUFBQyxDQUFBLEtBQUssQ0FBQyxHQUFQLENBQVcsSUFBWCxFQUFpQjtBQUFBLFFBQUEsR0FBQSxFQUFJLEdBQUo7T0FBakIsQ0FBUjtLQUE1QixFQURXO0VBQUEsQ0F4RGI7Q0FGMEIsQ0FMNUIsQ0FBQTs7Ozs7QUNBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN4RUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNKQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDdkJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsInZhciBjc3MgPSBcIi5iaW9qc19tc2Ffc3RhZ2Uge1xcbiAgY3Vyc29yOiBkZWZhdWx0O1xcbiAgbGluZS1oZWlnaHQ6IG5vcm1hbDsgfVxcblxcbi5iaW9qc19tc2FfbGFiZWxzIHtcXG4gIGNvbG9yOiBibGFjaztcXG4gIGRpc3BsYXk6IGlubGluZS1ibG9jaztcXG4gIHdoaXRlLXNwYWNlOiBub3dyYXA7XFxuICBjdXJzb3I6IHBvaW50ZXI7XFxuICB2ZXJ0aWNhbC1hbGlnbjogdG9wOyB9XFxuXFxuLmJpb2pzX21zYV9zZXFibG9jayB7XFxuICBjdXJzb3I6IG1vdmU7IH1cXG5cXG4uYmlvanNfbXNhX2xheWVyIHtcXG4gIGRpc3BsYXk6IGJsb2NrO1xcbiAgd2hpdGUtc3BhY2U6IG5vd3JhcDsgfVxcblxcbi5iaW9qc19tc2FfbGFiZWxibG9jazo6LXdlYmtpdC1zY3JvbGxiYXIsIC5iaW9qc19tc2FfaGVhZGVyOjotd2Via2l0LXNjcm9sbGJhciB7XFxuICAtd2Via2l0LWFwcGVhcmFuY2U6IG5vbmU7XFxuICB3aWR0aDogN3B4O1xcbiAgaGVpZ2h0OiA3cHg7IH1cXG5cXG4uYmlvanNfbXNhX2xhYmVsYmxvY2s6Oi13ZWJraXQtc2Nyb2xsYmFyLXRodW1iLCAuYmlvanNfbXNhX2hlYWRlcjo6LXdlYmtpdC1zY3JvbGxiYXItdGh1bWIge1xcbiAgYm9yZGVyLXJhZGl1czogNHB4O1xcbiAgYmFja2dyb3VuZC1jb2xvcjogcmdiYSgwLCAwLCAwLCAwLjUpO1xcbiAgYm94LXNoYWRvdzogMCAwIDFweCByZ2JhKDI1NSwgMjU1LCAyNTUsIDAuNSk7IH1cXG5cXG4uYmlvanNfbXNhX21hcmtlciB7XFxuICBjb2xvcjogZ3JleTtcXG4gIHdoaXRlLXNwYWNlOiBub3dyYXA7XFxuICBjdXJzb3I6IHBvaW50ZXI7IH1cXG5cXG4uYmlvanNfbXNhX21hcmtlciBzcGFuIHtcXG4gIHRleHQtYWxpZ246IGNlbnRlcjsgfVxcblxcbi5iaW9qc19tc2FfbWVudWJhciAuYmlvanNfbXNhX21lbnViYXJfYWxpbmsge1xcbiAgYmFja2dyb3VuZDogIzM0OThkYjtcXG4gIGJhY2tncm91bmQtaW1hZ2U6IC13ZWJraXQtbGluZWFyLWdyYWRpZW50KHRvcCwgIzM0OThkYiwgIzI5ODBiOSk7XFxuICBiYWNrZ3JvdW5kLWltYWdlOiAtbW96LWxpbmVhci1ncmFkaWVudCh0b3AsICMzNDk4ZGIsICMyOTgwYjkpO1xcbiAgYmFja2dyb3VuZC1pbWFnZTogLW1zLWxpbmVhci1ncmFkaWVudCh0b3AsICMzNDk4ZGIsICMyOTgwYjkpO1xcbiAgYmFja2dyb3VuZC1pbWFnZTogLW8tbGluZWFyLWdyYWRpZW50KHRvcCwgIzM0OThkYiwgIzI5ODBiOSk7XFxuICBiYWNrZ3JvdW5kLWltYWdlOiBsaW5lYXItZ3JhZGllbnQodG8gYm90dG9tLCAjMzQ5OGRiLCAjMjk4MGI5KTtcXG4gIC13ZWJraXQtYm9yZGVyLXJhZGl1czogMjg7XFxuICAtbW96LWJvcmRlci1yYWRpdXM6IDI4O1xcbiAgYm9yZGVyLXJhZGl1czogMjhweDtcXG4gIGZvbnQtZmFtaWx5OiBBcmlhbDtcXG4gIGNvbG9yOiAjZmZmZmZmO1xcbiAgcGFkZGluZzogM3B4IDEwcHggM3B4IDEwcHg7XFxuICBtYXJnaW4tbGVmdDogMTBweDtcXG4gIHRleHQtZGVjb3JhdGlvbjogbm9uZTsgfVxcblxcbi5iaW9qc19tc2FfbWVudWJhciAuYmlvanNfbXNhX21lbnViYXJfYWxpbms6aG92ZXIge1xcbiAgY3Vyc29yOiBwb2ludGVyOyB9XFxuXFxuLyoganF1ZXJ5IGRyb3Bkb3duIENTUyAqL1xcbi5kcm9wZG93biB7XFxuICBwb3NpdGlvbjogYWJzb2x1dGU7XFxuICB6LWluZGV4OiA5OTk5OTk5O1xcbiAgZGlzcGxheTogbm9uZTsgfVxcblxcbi5kcm9wZG93biAuZHJvcGRvd24tbWVudSxcXG4uZHJvcGRvd24gLmRyb3Bkb3duLXBhbmVsIHtcXG4gIG1pbi13aWR0aDogMTYwcHg7XFxuICBtYXgtd2lkdGg6IDM2MHB4O1xcbiAgbGlzdC1zdHlsZTogbm9uZTtcXG4gIGJhY2tncm91bmQ6ICNGRkY7XFxuICBib3JkZXI6IHNvbGlkIDFweCAjREREO1xcbiAgYm9yZGVyOiBzb2xpZCAxcHggcmdiYSgwLCAwLCAwLCAwLjIpO1xcbiAgYm9yZGVyLXJhZGl1czogNnB4O1xcbiAgYm94LXNoYWRvdzogMCA1cHggMTBweCByZ2JhKDAsIDAsIDAsIDAuMik7XFxuICBvdmVyZmxvdzogdmlzaWJsZTtcXG4gIHBhZGRpbmc6IDRweCAwO1xcbiAgbWFyZ2luOiAwOyB9XFxuXFxuLmRyb3Bkb3duIC5kcm9wZG93bi1wYW5lbCB7XFxuICBwYWRkaW5nOiAxMHB4OyB9XFxuXFxuLmRyb3Bkb3duLmRyb3Bkb3duLXNjcm9sbCAuZHJvcGRvd24tbWVudSxcXG4uZHJvcGRvd24uZHJvcGRvd24tc2Nyb2xsIC5kcm9wZG93bi1wYW5lbCB7XFxuICBtYXgtaGVpZ2h0OiAzNThweDtcXG4gIG92ZXJmbG93OiBhdXRvOyB9XFxuXFxuLmRyb3Bkb3duIC5kcm9wZG93bi1tZW51IExJIHtcXG4gIGxpc3Qtc3R5bGU6IG5vbmU7XFxuICBwYWRkaW5nOiAwIDA7XFxuICBtYXJnaW46IDA7XFxuICBsaW5lLWhlaWdodDogMThweDsgfVxcblxcbi5kcm9wZG93biAuZHJvcGRvd24tbWVudSBMSSxcXG4uZHJvcGRvd24gLmRyb3Bkb3duLW1lbnUgTEFCRUwge1xcbiAgZGlzcGxheTogYmxvY2s7XFxuICBjb2xvcjogIzU1NTtcXG4gIHRleHQtZGVjb3JhdGlvbjogbm9uZTtcXG4gIGxpbmUtaGVpZ2h0OiAxOHB4O1xcbiAgcGFkZGluZzogM3B4IDE1cHg7XFxuICB3aGl0ZS1zcGFjZTogbm93cmFwOyB9XFxuXFxuLmRyb3Bkb3duIC5kcm9wZG93bi1tZW51IExJOmhvdmVyLFxcbi5kcm9wZG93biAuZHJvcGRvd24tbWVudSBMQUJFTDpob3ZlciB7XFxuICBiYWNrZ3JvdW5kLWNvbG9yOiAjMDhDO1xcbiAgY29sb3I6ICNGRkY7XFxuICBjdXJzb3I6IHBvaW50ZXI7IH1cXG5cXG4uZHJvcGRvd24gLmRyb3Bkb3duLW1lbnUgLmRyb3Bkb3duLWRpdmlkZXIge1xcbiAgZm9udC1zaXplOiAxcHg7XFxuICBib3JkZXItdG9wOiBzb2xpZCAxcHggI0U1RTVFNTtcXG4gIHBhZGRpbmc6IDA7XFxuICBtYXJnaW46IDVweCAwOyB9XFxuXCI7IChyZXF1aXJlKFwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Nzc2lmeVwiKSkoY3NzKTsgbW9kdWxlLmV4cG9ydHMgPSBjc3M7IiwibW9kdWxlLmV4cG9ydHMgPSByZXF1aXJlKFwiLi9zcmMvaW5kZXhcIik7XG4iLCJ2YXIgXyA9IHJlcXVpcmUoJ3VuZGVyc2NvcmUnKTtcbnZhciB2aWV3VHlwZSA9IHJlcXVpcmUoXCJiYWNrYm9uZS12aWV3alwiKTtcbnZhciBwbHVnaW5hdG9yO1xuXG5tb2R1bGUuZXhwb3J0cyA9IHBsdWdpbmF0b3IgPSB2aWV3VHlwZS5leHRlbmQoe1xuICByZW5kZXJTdWJ2aWV3czogZnVuY3Rpb24oKSB7XG4gICAgdmFyIG9sZEVsID0gdGhpcy5lbDtcbiAgICB2YXIgZWwgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KFwiZGl2XCIpO1xuICAgIHRoaXMuc2V0RWxlbWVudChlbCk7XG4gICAgdmFyIGZyYWcgPSBkb2N1bWVudC5jcmVhdGVEb2N1bWVudEZyYWdtZW50KCk7XG4gICAgaWYgKG9sZEVsLnBhcmVudE5vZGUgIT0gbnVsbCkge1xuICAgICAgb2xkRWwucGFyZW50Tm9kZS5yZXBsYWNlQ2hpbGQodGhpcy5lbCwgb2xkRWwpO1xuICAgIH1cbiAgICB2YXIgdmlld3MgPSB0aGlzLl92aWV3cygpO1xuICAgIHZhciB2aWV3c1NvcnRlZCA9IF8uc29ydEJ5KHZpZXdzLCBmdW5jdGlvbihlbCkge1xuICAgICAgcmV0dXJuIGVsLm9yZGVyaW5nO1xuICAgIH0pO1xuICAgIHZhciB2aWV3LCBub2RlO1xuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgIHZpZXdzU29ydGVkLmxlbmd0aDsgaSsrKSB7XG4gICAgICB2aWV3ID0gdmlld3NTb3J0ZWRbaV07XG4gICAgICB2aWV3LnJlbmRlcigpO1xuICAgICAgbm9kZSA9IHZpZXcuZWw7XG4gICAgICBpZiAobm9kZSAhPSBudWxsKSB7XG4gICAgICAgIGZyYWcuYXBwZW5kQ2hpbGQobm9kZSk7XG4gICAgICB9XG4gICAgfVxuICAgIGVsLmFwcGVuZENoaWxkKGZyYWcpO1xuICAgIHJldHVybiBlbDtcbiAgfSxcbiAgYWRkVmlldzogZnVuY3Rpb24oa2V5LCB2aWV3KSB7XG4gICAgdmFyIHZpZXdzID0gdGhpcy5fdmlld3MoKTtcbiAgICBpZiAodmlldyA9PSBudWxsKSB7XG4gICAgICB0aHJvdyBcIkludmFsaWQgcGx1Z2luLiBcIjtcbiAgICB9XG4gICAgaWYgKHZpZXcub3JkZXJpbmcgPT0gbnVsbCkge1xuICAgICAgdmlldy5vcmRlcmluZyA9IGtleTtcbiAgICB9XG4gICAgcmV0dXJuIHZpZXdzW2tleV0gPSB2aWV3O1xuICB9LFxuICByZW1vdmVWaWV3czogZnVuY3Rpb24oKSB7XG4gICAgdmFyIGVsLCBrZXk7XG4gICAgdmFyIHZpZXdzID0gdGhpcy5fdmlld3MoKTtcbiAgICBmb3IgKGtleSBpbiB2aWV3cykge1xuICAgICAgZWwgPSB2aWV3c1trZXldO1xuICAgICAgZWwudW5kZWxlZ2F0ZUV2ZW50cygpO1xuICAgICAgZWwudW5iaW5kKCk7XG4gICAgICBpZiAoZWwucmVtb3ZlVmlld3MgIT0gbnVsbCkge1xuICAgICAgICBlbC5yZW1vdmVWaWV3cygpO1xuICAgICAgfVxuICAgICAgZWwucmVtb3ZlKCk7XG4gICAgfVxuICAgIHJldHVybiB0aGlzLnZpZXdzID0ge307XG4gIH0sXG4gIHJlbW92ZVZpZXc6IGZ1bmN0aW9uKGtleSkge1xuICAgIHZhciB2aWV3cyA9IHRoaXMuX3ZpZXdzKCk7XG4gICAgdmlld3Nba2V5XS5yZW1vdmUoKTtcbiAgICByZXR1cm4gZGVsZXRlIHZpZXdzW2tleV07XG4gIH0sXG4gIGdldFZpZXc6IGZ1bmN0aW9uKGtleSkge1xuICAgIHZhciB2aWV3cyA9IHRoaXMuX3ZpZXdzKCk7XG4gICAgcmV0dXJuIHZpZXdzW2tleV07XG4gIH0sXG4gIHJlbW92ZTogZnVuY3Rpb24oKSB7XG4gICAgdGhpcy5yZW1vdmVWaWV3cygpO1xuICAgIHJldHVybiB2aWV3VHlwZS5wcm90b3R5cGUucmVtb3ZlLmFwcGx5KHRoaXMpO1xuICB9LFxuICBfdmlld3M6IGZ1bmN0aW9uKCkge1xuICAgIGlmICh0aGlzLnZpZXdzID09IG51bGwpIHtcbiAgICAgIHRoaXMudmlld3MgPSB7fTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXMudmlld3M7XG4gIH1cbn0pO1xuIiwiLy8gICAgIEJhY2tib25lLmpzIDEuMS4yXG5cbi8vICAgICAoYykgMjAxMC0yMDE0IEplcmVteSBBc2hrZW5hcywgRG9jdW1lbnRDbG91ZCBhbmQgSW52ZXN0aWdhdGl2ZSBSZXBvcnRlcnMgJiBFZGl0b3JzXG4vLyAgICAgQmFja2JvbmUgbWF5IGJlIGZyZWVseSBkaXN0cmlidXRlZCB1bmRlciB0aGUgTUlUIGxpY2Vuc2UuXG4vLyAgICAgRm9yIGFsbCBkZXRhaWxzIGFuZCBkb2N1bWVudGF0aW9uOlxuLy8gICAgIGh0dHA6Ly9iYWNrYm9uZWpzLm9yZ1xuXG52YXIgRXZlbnRzID0gcmVxdWlyZShcImJhY2tib25lLWV2ZW50cy1zdGFuZGFsb25lXCIpO1xudmFyIGV4dGVuZCA9IHJlcXVpcmUoXCJiYWNrYm9uZS1leHRlbmQtc3RhbmRhbG9uZVwiKTtcbnZhciBfID0gcmVxdWlyZShcInVuZGVyc2NvcmVcIik7XG52YXIgTW9kZWwgPSByZXF1aXJlKFwiLi9tb2RlbFwiKTtcblxuLy8gQ3JlYXRlIGxvY2FsIHJlZmVyZW5jZXMgdG8gYXJyYXkgbWV0aG9kcyB3ZSdsbCB3YW50IHRvIHVzZSBsYXRlci5cbnZhciBhcnJheSA9IFtdO1xudmFyIHNsaWNlID0gYXJyYXkuc2xpY2U7XG5cbi8vIEJhY2tib25lLkNvbGxlY3Rpb25cbi8vIC0tLS0tLS0tLS0tLS0tLS0tLS1cblxuLy8gSWYgbW9kZWxzIHRlbmQgdG8gcmVwcmVzZW50IGEgc2luZ2xlIHJvdyBvZiBkYXRhLCBhIEJhY2tib25lIENvbGxlY3Rpb24gaXNcbi8vIG1vcmUgYW5hbG9nb3VzIHRvIGEgdGFibGUgZnVsbCBvZiBkYXRhIC4uLiBvciBhIHNtYWxsIHNsaWNlIG9yIHBhZ2Ugb2YgdGhhdFxuLy8gdGFibGUsIG9yIGEgY29sbGVjdGlvbiBvZiByb3dzIHRoYXQgYmVsb25nIHRvZ2V0aGVyIGZvciBhIHBhcnRpY3VsYXIgcmVhc29uXG4vLyAtLSBhbGwgb2YgdGhlIG1lc3NhZ2VzIGluIHRoaXMgcGFydGljdWxhciBmb2xkZXIsIGFsbCBvZiB0aGUgZG9jdW1lbnRzXG4vLyBiZWxvbmdpbmcgdG8gdGhpcyBwYXJ0aWN1bGFyIGF1dGhvciwgYW5kIHNvIG9uLiBDb2xsZWN0aW9ucyBtYWludGFpblxuLy8gaW5kZXhlcyBvZiB0aGVpciBtb2RlbHMsIGJvdGggaW4gb3JkZXIsIGFuZCBmb3IgbG9va3VwIGJ5IGBpZGAuXG5cbi8vIENyZWF0ZSBhIG5ldyAqKkNvbGxlY3Rpb24qKiwgcGVyaGFwcyB0byBjb250YWluIGEgc3BlY2lmaWMgdHlwZSBvZiBgbW9kZWxgLlxuLy8gSWYgYSBgY29tcGFyYXRvcmAgaXMgc3BlY2lmaWVkLCB0aGUgQ29sbGVjdGlvbiB3aWxsIG1haW50YWluXG4vLyBpdHMgbW9kZWxzIGluIHNvcnQgb3JkZXIsIGFzIHRoZXkncmUgYWRkZWQgYW5kIHJlbW92ZWQuXG52YXIgQ29sbGVjdGlvbiA9IGZ1bmN0aW9uKG1vZGVscywgb3B0aW9ucykge1xuICBvcHRpb25zIHx8IChvcHRpb25zID0ge30pO1xuICBpZiAob3B0aW9ucy5tb2RlbCkgdGhpcy5tb2RlbCA9IG9wdGlvbnMubW9kZWw7XG4gIGlmIChvcHRpb25zLmNvbXBhcmF0b3IgIT09IHZvaWQgMCkgdGhpcy5jb21wYXJhdG9yID0gb3B0aW9ucy5jb21wYXJhdG9yO1xuICB0aGlzLl9yZXNldCgpO1xuICB0aGlzLmluaXRpYWxpemUuYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgaWYgKG1vZGVscykgdGhpcy5yZXNldChtb2RlbHMsIF8uZXh0ZW5kKHtzaWxlbnQ6IHRydWV9LCBvcHRpb25zKSk7XG59O1xuXG4vLyBEZWZhdWx0IG9wdGlvbnMgZm9yIGBDb2xsZWN0aW9uI3NldGAuXG52YXIgc2V0T3B0aW9ucyA9IHthZGQ6IHRydWUsIHJlbW92ZTogdHJ1ZSwgbWVyZ2U6IHRydWV9O1xudmFyIGFkZE9wdGlvbnMgPSB7YWRkOiB0cnVlLCByZW1vdmU6IGZhbHNlfTtcblxuLy8gRGVmaW5lIHRoZSBDb2xsZWN0aW9uJ3MgaW5oZXJpdGFibGUgbWV0aG9kcy5cbl8uZXh0ZW5kKENvbGxlY3Rpb24ucHJvdG90eXBlLCBFdmVudHMsIHtcblxuICAvLyBUaGUgZGVmYXVsdCBtb2RlbCBmb3IgYSBjb2xsZWN0aW9uIGlzIGp1c3QgYSAqKkJhY2tib25lLk1vZGVsKiouXG4gIC8vIFRoaXMgc2hvdWxkIGJlIG92ZXJyaWRkZW4gaW4gbW9zdCBjYXNlcy5cbiAgbW9kZWw6IE1vZGVsLFxuXG4gIC8vIEluaXRpYWxpemUgaXMgYW4gZW1wdHkgZnVuY3Rpb24gYnkgZGVmYXVsdC4gT3ZlcnJpZGUgaXQgd2l0aCB5b3VyIG93blxuICAvLyBpbml0aWFsaXphdGlvbiBsb2dpYy5cbiAgaW5pdGlhbGl6ZTogZnVuY3Rpb24oKXt9LFxuXG4gICAgLy8gVGhlIEpTT04gcmVwcmVzZW50YXRpb24gb2YgYSBDb2xsZWN0aW9uIGlzIGFuIGFycmF5IG9mIHRoZVxuICAgIC8vIG1vZGVscycgYXR0cmlidXRlcy5cbiAgdG9KU09OOiBmdW5jdGlvbihvcHRpb25zKSB7XG4gICAgcmV0dXJuIHRoaXMubWFwKGZ1bmN0aW9uKG1vZGVsKXsgcmV0dXJuIG1vZGVsLnRvSlNPTihvcHRpb25zKTsgfSk7XG4gIH0sXG5cbiAgICAvLyBQcm94eSBgQmFja2JvbmUuc3luY2AgYnkgZGVmYXVsdC5cbiAgc3luYzogZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIEJhY2tib25lLnN5bmMuYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgfSxcblxuICAgIC8vIEFkZCBhIG1vZGVsLCBvciBsaXN0IG9mIG1vZGVscyB0byB0aGUgc2V0LlxuICBhZGQ6IGZ1bmN0aW9uKG1vZGVscywgb3B0aW9ucykge1xuICAgIHJldHVybiB0aGlzLnNldChtb2RlbHMsIF8uZXh0ZW5kKHttZXJnZTogZmFsc2V9LCBvcHRpb25zLCBhZGRPcHRpb25zKSk7XG4gIH0sXG5cbiAgICAvLyBSZW1vdmUgYSBtb2RlbCwgb3IgYSBsaXN0IG9mIG1vZGVscyBmcm9tIHRoZSBzZXQuXG4gIHJlbW92ZTogZnVuY3Rpb24obW9kZWxzLCBvcHRpb25zKSB7XG4gICAgdmFyIHNpbmd1bGFyID0gIV8uaXNBcnJheShtb2RlbHMpO1xuICAgIG1vZGVscyA9IHNpbmd1bGFyID8gW21vZGVsc10gOiBfLmNsb25lKG1vZGVscyk7XG4gICAgb3B0aW9ucyB8fCAob3B0aW9ucyA9IHt9KTtcbiAgICBmb3IgKHZhciBpID0gMCwgbGVuZ3RoID0gbW9kZWxzLmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICB2YXIgbW9kZWwgPSBtb2RlbHNbaV0gPSB0aGlzLmdldChtb2RlbHNbaV0pO1xuICAgICAgaWYgKCFtb2RlbCkgY29udGludWU7XG4gICAgICB2YXIgaWQgPSB0aGlzLm1vZGVsSWQobW9kZWwuYXR0cmlidXRlcyk7XG4gICAgICBpZiAoaWQgIT0gbnVsbCkgZGVsZXRlIHRoaXMuX2J5SWRbaWRdO1xuICAgICAgZGVsZXRlIHRoaXMuX2J5SWRbbW9kZWwuY2lkXTtcbiAgICAgIHZhciBpbmRleCA9IHRoaXMuaW5kZXhPZihtb2RlbCk7XG4gICAgICB0aGlzLm1vZGVscy5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgdGhpcy5sZW5ndGgtLTtcbiAgICAgIGlmICghb3B0aW9ucy5zaWxlbnQpIHtcbiAgICAgICAgb3B0aW9ucy5pbmRleCA9IGluZGV4O1xuICAgICAgICBtb2RlbC50cmlnZ2VyKCdyZW1vdmUnLCBtb2RlbCwgdGhpcywgb3B0aW9ucyk7XG4gICAgICB9XG4gICAgICB0aGlzLl9yZW1vdmVSZWZlcmVuY2UobW9kZWwsIG9wdGlvbnMpO1xuICAgIH1cbiAgICByZXR1cm4gc2luZ3VsYXIgPyBtb2RlbHNbMF0gOiBtb2RlbHM7XG4gIH0sXG5cbiAgICAvLyBVcGRhdGUgYSBjb2xsZWN0aW9uIGJ5IGBzZXRgLWluZyBhIG5ldyBsaXN0IG9mIG1vZGVscywgYWRkaW5nIG5ldyBvbmVzLFxuICAgIC8vIHJlbW92aW5nIG1vZGVscyB0aGF0IGFyZSBubyBsb25nZXIgcHJlc2VudCwgYW5kIG1lcmdpbmcgbW9kZWxzIHRoYXRcbiAgICAvLyBhbHJlYWR5IGV4aXN0IGluIHRoZSBjb2xsZWN0aW9uLCBhcyBuZWNlc3NhcnkuIFNpbWlsYXIgdG8gKipNb2RlbCNzZXQqKixcbiAgICAvLyB0aGUgY29yZSBvcGVyYXRpb24gZm9yIHVwZGF0aW5nIHRoZSBkYXRhIGNvbnRhaW5lZCBieSB0aGUgY29sbGVjdGlvbi5cbiAgc2V0OiBmdW5jdGlvbihtb2RlbHMsIG9wdGlvbnMpIHtcbiAgICBvcHRpb25zID0gXy5kZWZhdWx0cyh7fSwgb3B0aW9ucywgc2V0T3B0aW9ucyk7XG4gICAgaWYgKG9wdGlvbnMucGFyc2UpIG1vZGVscyA9IHRoaXMucGFyc2UobW9kZWxzLCBvcHRpb25zKTtcbiAgICB2YXIgc2luZ3VsYXIgPSAhXy5pc0FycmF5KG1vZGVscyk7XG4gICAgbW9kZWxzID0gc2luZ3VsYXIgPyAobW9kZWxzID8gW21vZGVsc10gOiBbXSkgOiBtb2RlbHMuc2xpY2UoKTtcbiAgICB2YXIgaWQsIG1vZGVsLCBhdHRycywgZXhpc3RpbmcsIHNvcnQ7XG4gICAgdmFyIGF0ID0gb3B0aW9ucy5hdDtcbiAgICB2YXIgc29ydGFibGUgPSB0aGlzLmNvbXBhcmF0b3IgJiYgKGF0ID09IG51bGwpICYmIG9wdGlvbnMuc29ydCAhPT0gZmFsc2U7XG4gICAgdmFyIHNvcnRBdHRyID0gXy5pc1N0cmluZyh0aGlzLmNvbXBhcmF0b3IpID8gdGhpcy5jb21wYXJhdG9yIDogbnVsbDtcbiAgICB2YXIgdG9BZGQgPSBbXSwgdG9SZW1vdmUgPSBbXSwgbW9kZWxNYXAgPSB7fTtcbiAgICB2YXIgYWRkID0gb3B0aW9ucy5hZGQsIG1lcmdlID0gb3B0aW9ucy5tZXJnZSwgcmVtb3ZlID0gb3B0aW9ucy5yZW1vdmU7XG4gICAgdmFyIG9yZGVyID0gIXNvcnRhYmxlICYmIGFkZCAmJiByZW1vdmUgPyBbXSA6IGZhbHNlO1xuXG4gICAgLy8gVHVybiBiYXJlIG9iamVjdHMgaW50byBtb2RlbCByZWZlcmVuY2VzLCBhbmQgcHJldmVudCBpbnZhbGlkIG1vZGVsc1xuICAgIC8vIGZyb20gYmVpbmcgYWRkZWQuXG4gICAgZm9yICh2YXIgaSA9IDAsIGxlbmd0aCA9IG1vZGVscy5sZW5ndGg7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgICAgYXR0cnMgPSBtb2RlbHNbaV07XG5cbiAgICAgIC8vIElmIGEgZHVwbGljYXRlIGlzIGZvdW5kLCBwcmV2ZW50IGl0IGZyb20gYmVpbmcgYWRkZWQgYW5kXG4gICAgICAvLyBvcHRpb25hbGx5IG1lcmdlIGl0IGludG8gdGhlIGV4aXN0aW5nIG1vZGVsLlxuICAgICAgaWYgKGV4aXN0aW5nID0gdGhpcy5nZXQoYXR0cnMpKSB7XG4gICAgICAgIGlmIChyZW1vdmUpIG1vZGVsTWFwW2V4aXN0aW5nLmNpZF0gPSB0cnVlO1xuICAgICAgICBpZiAobWVyZ2UgJiYgYXR0cnMgIT09IGV4aXN0aW5nKSB7XG4gICAgICAgICAgYXR0cnMgPSB0aGlzLl9pc01vZGVsKGF0dHJzKSA/IGF0dHJzLmF0dHJpYnV0ZXMgOiBhdHRycztcbiAgICAgICAgICBpZiAob3B0aW9ucy5wYXJzZSkgYXR0cnMgPSBleGlzdGluZy5wYXJzZShhdHRycywgb3B0aW9ucyk7XG4gICAgICAgICAgZXhpc3Rpbmcuc2V0KGF0dHJzLCBvcHRpb25zKTtcbiAgICAgICAgICBpZiAoc29ydGFibGUgJiYgIXNvcnQgJiYgZXhpc3RpbmcuaGFzQ2hhbmdlZChzb3J0QXR0cikpIHNvcnQgPSB0cnVlO1xuICAgICAgICB9XG4gICAgICAgIG1vZGVsc1tpXSA9IGV4aXN0aW5nO1xuXG4gICAgICAgIC8vIElmIHRoaXMgaXMgYSBuZXcsIHZhbGlkIG1vZGVsLCBwdXNoIGl0IHRvIHRoZSBgdG9BZGRgIGxpc3QuXG4gICAgICB9IGVsc2UgaWYgKGFkZCkge1xuICAgICAgICBtb2RlbCA9IG1vZGVsc1tpXSA9IHRoaXMuX3ByZXBhcmVNb2RlbChhdHRycywgb3B0aW9ucyk7XG4gICAgICAgIGlmICghbW9kZWwpIGNvbnRpbnVlO1xuICAgICAgICB0b0FkZC5wdXNoKG1vZGVsKTtcbiAgICAgICAgdGhpcy5fYWRkUmVmZXJlbmNlKG1vZGVsLCBvcHRpb25zKTtcbiAgICAgIH1cblxuICAgICAgLy8gRG8gbm90IGFkZCBtdWx0aXBsZSBtb2RlbHMgd2l0aCB0aGUgc2FtZSBgaWRgLlxuICAgICAgbW9kZWwgPSBleGlzdGluZyB8fCBtb2RlbDtcbiAgICAgIGlmICghbW9kZWwpIGNvbnRpbnVlO1xuICAgICAgaWQgPSB0aGlzLm1vZGVsSWQobW9kZWwuYXR0cmlidXRlcyk7XG4gICAgICBpZiAob3JkZXIgJiYgKG1vZGVsLmlzTmV3KCkgfHwgIW1vZGVsTWFwW2lkXSkpIG9yZGVyLnB1c2gobW9kZWwpO1xuICAgICAgbW9kZWxNYXBbaWRdID0gdHJ1ZTtcbiAgICB9XG5cbiAgICAvLyBSZW1vdmUgbm9uZXhpc3RlbnQgbW9kZWxzIGlmIGFwcHJvcHJpYXRlLlxuICAgIGlmIChyZW1vdmUpIHtcbiAgICAgIGZvciAodmFyIGkgPSAwLCBsZW5ndGggPSB0aGlzLmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGlmICghbW9kZWxNYXBbKG1vZGVsID0gdGhpcy5tb2RlbHNbaV0pLmNpZF0pIHRvUmVtb3ZlLnB1c2gobW9kZWwpO1xuICAgICAgfVxuICAgICAgaWYgKHRvUmVtb3ZlLmxlbmd0aCkgdGhpcy5yZW1vdmUodG9SZW1vdmUsIG9wdGlvbnMpO1xuICAgIH1cblxuICAgIC8vIFNlZSBpZiBzb3J0aW5nIGlzIG5lZWRlZCwgdXBkYXRlIGBsZW5ndGhgIGFuZCBzcGxpY2UgaW4gbmV3IG1vZGVscy5cbiAgICBpZiAodG9BZGQubGVuZ3RoIHx8IChvcmRlciAmJiBvcmRlci5sZW5ndGgpKSB7XG4gICAgICBpZiAoc29ydGFibGUpIHNvcnQgPSB0cnVlO1xuICAgICAgdGhpcy5sZW5ndGggKz0gdG9BZGQubGVuZ3RoO1xuICAgICAgaWYgKGF0ICE9IG51bGwpIHtcbiAgICAgICAgZm9yICh2YXIgaSA9IDAsIGxlbmd0aCA9IHRvQWRkLmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgdGhpcy5tb2RlbHMuc3BsaWNlKGF0ICsgaSwgMCwgdG9BZGRbaV0pO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBpZiAob3JkZXIpIHRoaXMubW9kZWxzLmxlbmd0aCA9IDA7XG4gICAgICAgIHZhciBvcmRlcmVkTW9kZWxzID0gb3JkZXIgfHwgdG9BZGQ7XG4gICAgICAgIGZvciAodmFyIGkgPSAwLCBsZW5ndGggPSBvcmRlcmVkTW9kZWxzLmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgdGhpcy5tb2RlbHMucHVzaChvcmRlcmVkTW9kZWxzW2ldKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIC8vIFNpbGVudGx5IHNvcnQgdGhlIGNvbGxlY3Rpb24gaWYgYXBwcm9wcmlhdGUuXG4gICAgaWYgKHNvcnQpIHRoaXMuc29ydCh7c2lsZW50OiB0cnVlfSk7XG5cbiAgICAvLyBVbmxlc3Mgc2lsZW5jZWQsIGl0J3MgdGltZSB0byBmaXJlIGFsbCBhcHByb3ByaWF0ZSBhZGQvc29ydCBldmVudHMuXG4gICAgaWYgKCFvcHRpb25zLnNpbGVudCkge1xuICAgICAgdmFyIGFkZE9wdHMgPSBhdCAhPSBudWxsID8gXy5jbG9uZShvcHRpb25zKSA6IG9wdGlvbnM7XG4gICAgICBmb3IgKHZhciBpID0gMCwgbGVuZ3RoID0gdG9BZGQubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgaWYgKGF0ICE9IG51bGwpIGFkZE9wdHMuaW5kZXggPSBhdCArIGk7XG4gICAgICAgIChtb2RlbCA9IHRvQWRkW2ldKS50cmlnZ2VyKCdhZGQnLCBtb2RlbCwgdGhpcywgYWRkT3B0cyk7XG4gICAgICB9XG4gICAgICBpZiAoc29ydCB8fCAob3JkZXIgJiYgb3JkZXIubGVuZ3RoKSkgdGhpcy50cmlnZ2VyKCdzb3J0JywgdGhpcywgb3B0aW9ucyk7XG4gICAgfVxuXG4gICAgLy8gUmV0dXJuIHRoZSBhZGRlZCAob3IgbWVyZ2VkKSBtb2RlbCAob3IgbW9kZWxzKS5cbiAgICByZXR1cm4gc2luZ3VsYXIgPyBtb2RlbHNbMF0gOiBtb2RlbHM7XG4gIH0sXG5cbiAgICAvLyBXaGVuIHlvdSBoYXZlIG1vcmUgaXRlbXMgdGhhbiB5b3Ugd2FudCB0byBhZGQgb3IgcmVtb3ZlIGluZGl2aWR1YWxseSxcbiAgICAvLyB5b3UgY2FuIHJlc2V0IHRoZSBlbnRpcmUgc2V0IHdpdGggYSBuZXcgbGlzdCBvZiBtb2RlbHMsIHdpdGhvdXQgZmlyaW5nXG4gICAgLy8gYW55IGdyYW51bGFyIGBhZGRgIG9yIGByZW1vdmVgIGV2ZW50cy4gRmlyZXMgYHJlc2V0YCB3aGVuIGZpbmlzaGVkLlxuICAgIC8vIFVzZWZ1bCBmb3IgYnVsayBvcGVyYXRpb25zIGFuZCBvcHRpbWl6YXRpb25zLlxuICByZXNldDogZnVuY3Rpb24obW9kZWxzLCBvcHRpb25zKSB7XG4gICAgb3B0aW9ucyB8fCAob3B0aW9ucyA9IHt9KTtcbiAgICBmb3IgKHZhciBpID0gMCwgbGVuZ3RoID0gdGhpcy5tb2RlbHMubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgIHRoaXMuX3JlbW92ZVJlZmVyZW5jZSh0aGlzLm1vZGVsc1tpXSwgb3B0aW9ucyk7XG4gICAgfVxuICAgIG9wdGlvbnMucHJldmlvdXNNb2RlbHMgPSB0aGlzLm1vZGVscztcbiAgICB0aGlzLl9yZXNldCgpO1xuICAgIG1vZGVscyA9IHRoaXMuYWRkKG1vZGVscywgXy5leHRlbmQoe3NpbGVudDogdHJ1ZX0sIG9wdGlvbnMpKTtcbiAgICBpZiAoIW9wdGlvbnMuc2lsZW50KSB0aGlzLnRyaWdnZXIoJ3Jlc2V0JywgdGhpcywgb3B0aW9ucyk7XG4gICAgcmV0dXJuIG1vZGVscztcbiAgfSxcblxuICAgIC8vIEFkZCBhIG1vZGVsIHRvIHRoZSBlbmQgb2YgdGhlIGNvbGxlY3Rpb24uXG4gIHB1c2g6IGZ1bmN0aW9uKG1vZGVsLCBvcHRpb25zKSB7XG4gICAgcmV0dXJuIHRoaXMuYWRkKG1vZGVsLCBfLmV4dGVuZCh7YXQ6IHRoaXMubGVuZ3RofSwgb3B0aW9ucykpO1xuICB9LFxuXG4gICAgLy8gUmVtb3ZlIGEgbW9kZWwgZnJvbSB0aGUgZW5kIG9mIHRoZSBjb2xsZWN0aW9uLlxuICBwb3A6IGZ1bmN0aW9uKG9wdGlvbnMpIHtcbiAgICB2YXIgbW9kZWwgPSB0aGlzLmF0KHRoaXMubGVuZ3RoIC0gMSk7XG4gICAgdGhpcy5yZW1vdmUobW9kZWwsIG9wdGlvbnMpO1xuICAgIHJldHVybiBtb2RlbDtcbiAgfSxcblxuICAgIC8vIEFkZCBhIG1vZGVsIHRvIHRoZSBiZWdpbm5pbmcgb2YgdGhlIGNvbGxlY3Rpb24uXG4gIHVuc2hpZnQ6IGZ1bmN0aW9uKG1vZGVsLCBvcHRpb25zKSB7XG4gICAgcmV0dXJuIHRoaXMuYWRkKG1vZGVsLCBfLmV4dGVuZCh7YXQ6IDB9LCBvcHRpb25zKSk7XG4gIH0sXG5cbiAgICAvLyBSZW1vdmUgYSBtb2RlbCBmcm9tIHRoZSBiZWdpbm5pbmcgb2YgdGhlIGNvbGxlY3Rpb24uXG4gIHNoaWZ0OiBmdW5jdGlvbihvcHRpb25zKSB7XG4gICAgdmFyIG1vZGVsID0gdGhpcy5hdCgwKTtcbiAgICB0aGlzLnJlbW92ZShtb2RlbCwgb3B0aW9ucyk7XG4gICAgcmV0dXJuIG1vZGVsO1xuICB9LFxuXG4gICAgLy8gU2xpY2Ugb3V0IGEgc3ViLWFycmF5IG9mIG1vZGVscyBmcm9tIHRoZSBjb2xsZWN0aW9uLlxuICBzbGljZTogZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIHNsaWNlLmFwcGx5KHRoaXMubW9kZWxzLCBhcmd1bWVudHMpO1xuICB9LFxuXG4gICAgLy8gR2V0IGEgbW9kZWwgZnJvbSB0aGUgc2V0IGJ5IGlkLlxuICBnZXQ6IGZ1bmN0aW9uKG9iaikge1xuICAgIGlmIChvYmogPT0gbnVsbCkgcmV0dXJuIHZvaWQgMDtcbiAgICB2YXIgaWQgPSB0aGlzLm1vZGVsSWQodGhpcy5faXNNb2RlbChvYmopID8gb2JqLmF0dHJpYnV0ZXMgOiBvYmopO1xuICAgIHJldHVybiB0aGlzLl9ieUlkW29ial0gfHwgdGhpcy5fYnlJZFtpZF0gfHwgdGhpcy5fYnlJZFtvYmouY2lkXTtcbiAgfSxcblxuICAgIC8vIEdldCB0aGUgbW9kZWwgYXQgdGhlIGdpdmVuIGluZGV4LlxuICBhdDogZnVuY3Rpb24oaW5kZXgpIHtcbiAgICBpZiAoaW5kZXggPCAwKSBpbmRleCArPSB0aGlzLmxlbmd0aDtcbiAgICByZXR1cm4gdGhpcy5tb2RlbHNbaW5kZXhdO1xuICB9LFxuXG4gICAgLy8gUmV0dXJuIG1vZGVscyB3aXRoIG1hdGNoaW5nIGF0dHJpYnV0ZXMuIFVzZWZ1bCBmb3Igc2ltcGxlIGNhc2VzIG9mXG4gICAgLy8gYGZpbHRlcmAuXG4gIHdoZXJlOiBmdW5jdGlvbihhdHRycywgZmlyc3QpIHtcbiAgICBpZiAoXy5pc0VtcHR5KGF0dHJzKSkgcmV0dXJuIGZpcnN0ID8gdm9pZCAwIDogW107XG4gICAgcmV0dXJuIHRoaXNbZmlyc3QgPyAnZmluZCcgOiAnZmlsdGVyJ10oZnVuY3Rpb24obW9kZWwpIHtcbiAgICAgIGZvciAodmFyIGtleSBpbiBhdHRycykge1xuICAgICAgICBpZiAoYXR0cnNba2V5XSAhPT0gbW9kZWwuZ2V0KGtleSkpIHJldHVybiBmYWxzZTtcbiAgICAgIH1cbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH0pO1xuICB9LFxuXG4gICAgLy8gUmV0dXJuIHRoZSBmaXJzdCBtb2RlbCB3aXRoIG1hdGNoaW5nIGF0dHJpYnV0ZXMuIFVzZWZ1bCBmb3Igc2ltcGxlIGNhc2VzXG4gICAgLy8gb2YgYGZpbmRgLlxuICBmaW5kV2hlcmU6IGZ1bmN0aW9uKGF0dHJzKSB7XG4gICAgcmV0dXJuIHRoaXMud2hlcmUoYXR0cnMsIHRydWUpO1xuICB9LFxuXG4gICAgLy8gRm9yY2UgdGhlIGNvbGxlY3Rpb24gdG8gcmUtc29ydCBpdHNlbGYuIFlvdSBkb24ndCBuZWVkIHRvIGNhbGwgdGhpcyB1bmRlclxuICAgIC8vIG5vcm1hbCBjaXJjdW1zdGFuY2VzLCBhcyB0aGUgc2V0IHdpbGwgbWFpbnRhaW4gc29ydCBvcmRlciBhcyBlYWNoIGl0ZW1cbiAgICAvLyBpcyBhZGRlZC5cbiAgc29ydDogZnVuY3Rpb24ob3B0aW9ucykge1xuICAgIGlmICghdGhpcy5jb21wYXJhdG9yKSB0aHJvdyBuZXcgRXJyb3IoJ0Nhbm5vdCBzb3J0IGEgc2V0IHdpdGhvdXQgYSBjb21wYXJhdG9yJyk7XG4gICAgb3B0aW9ucyB8fCAob3B0aW9ucyA9IHt9KTtcblxuICAgIC8vIFJ1biBzb3J0IGJhc2VkIG9uIHR5cGUgb2YgYGNvbXBhcmF0b3JgLlxuICAgIGlmIChfLmlzU3RyaW5nKHRoaXMuY29tcGFyYXRvcikgfHwgdGhpcy5jb21wYXJhdG9yLmxlbmd0aCA9PT0gMSkge1xuICAgICAgdGhpcy5tb2RlbHMgPSB0aGlzLnNvcnRCeSh0aGlzLmNvbXBhcmF0b3IsIHRoaXMpO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLm1vZGVscy5zb3J0KF8uYmluZCh0aGlzLmNvbXBhcmF0b3IsIHRoaXMpKTtcbiAgICB9XG5cbiAgICBpZiAoIW9wdGlvbnMuc2lsZW50KSB0aGlzLnRyaWdnZXIoJ3NvcnQnLCB0aGlzLCBvcHRpb25zKTtcbiAgICByZXR1cm4gdGhpcztcbiAgfSxcblxuICAgIC8vIFBsdWNrIGFuIGF0dHJpYnV0ZSBmcm9tIGVhY2ggbW9kZWwgaW4gdGhlIGNvbGxlY3Rpb24uXG4gIHBsdWNrOiBmdW5jdGlvbihhdHRyKSB7XG4gICAgcmV0dXJuIF8uaW52b2tlKHRoaXMubW9kZWxzLCAnZ2V0JywgYXR0cik7XG4gIH0sXG5cbiAgICAvLyBGZXRjaCB0aGUgZGVmYXVsdCBzZXQgb2YgbW9kZWxzIGZvciB0aGlzIGNvbGxlY3Rpb24sIHJlc2V0dGluZyB0aGVcbiAgICAvLyBjb2xsZWN0aW9uIHdoZW4gdGhleSBhcnJpdmUuIElmIGByZXNldDogdHJ1ZWAgaXMgcGFzc2VkLCB0aGUgcmVzcG9uc2VcbiAgICAvLyBkYXRhIHdpbGwgYmUgcGFzc2VkIHRocm91Z2ggdGhlIGByZXNldGAgbWV0aG9kIGluc3RlYWQgb2YgYHNldGAuXG4gIGZldGNoOiBmdW5jdGlvbihvcHRpb25zKSB7XG4gICAgb3B0aW9ucyA9IG9wdGlvbnMgPyBfLmNsb25lKG9wdGlvbnMpIDoge307XG4gICAgaWYgKG9wdGlvbnMucGFyc2UgPT09IHZvaWQgMCkgb3B0aW9ucy5wYXJzZSA9IHRydWU7XG4gICAgdmFyIHN1Y2Nlc3MgPSBvcHRpb25zLnN1Y2Nlc3M7XG4gICAgdmFyIGNvbGxlY3Rpb24gPSB0aGlzO1xuICAgIG9wdGlvbnMuc3VjY2VzcyA9IGZ1bmN0aW9uKHJlc3ApIHtcbiAgICAgIHZhciBtZXRob2QgPSBvcHRpb25zLnJlc2V0ID8gJ3Jlc2V0JyA6ICdzZXQnO1xuICAgICAgY29sbGVjdGlvblttZXRob2RdKHJlc3AsIG9wdGlvbnMpO1xuICAgICAgaWYgKHN1Y2Nlc3MpIHN1Y2Nlc3MoY29sbGVjdGlvbiwgcmVzcCwgb3B0aW9ucyk7XG4gICAgICBjb2xsZWN0aW9uLnRyaWdnZXIoJ3N5bmMnLCBjb2xsZWN0aW9uLCByZXNwLCBvcHRpb25zKTtcbiAgICB9O1xuICAgIHdyYXBFcnJvcih0aGlzLCBvcHRpb25zKTtcbiAgICByZXR1cm4gdGhpcy5zeW5jKCdyZWFkJywgdGhpcywgb3B0aW9ucyk7XG4gIH0sXG5cbiAgICAvLyBDcmVhdGUgYSBuZXcgaW5zdGFuY2Ugb2YgYSBtb2RlbCBpbiB0aGlzIGNvbGxlY3Rpb24uIEFkZCB0aGUgbW9kZWwgdG8gdGhlXG4gICAgLy8gY29sbGVjdGlvbiBpbW1lZGlhdGVseSwgdW5sZXNzIGB3YWl0OiB0cnVlYCBpcyBwYXNzZWQsIGluIHdoaWNoIGNhc2Ugd2VcbiAgICAvLyB3YWl0IGZvciB0aGUgc2VydmVyIHRvIGFncmVlLlxuICBjcmVhdGU6IGZ1bmN0aW9uKG1vZGVsLCBvcHRpb25zKSB7XG4gICAgb3B0aW9ucyA9IG9wdGlvbnMgPyBfLmNsb25lKG9wdGlvbnMpIDoge307XG4gICAgaWYgKCEobW9kZWwgPSB0aGlzLl9wcmVwYXJlTW9kZWwobW9kZWwsIG9wdGlvbnMpKSkgcmV0dXJuIGZhbHNlO1xuICAgIGlmICghb3B0aW9ucy53YWl0KSB0aGlzLmFkZChtb2RlbCwgb3B0aW9ucyk7XG4gICAgdmFyIGNvbGxlY3Rpb24gPSB0aGlzO1xuICAgIHZhciBzdWNjZXNzID0gb3B0aW9ucy5zdWNjZXNzO1xuICAgIG9wdGlvbnMuc3VjY2VzcyA9IGZ1bmN0aW9uKG1vZGVsLCByZXNwKSB7XG4gICAgICBpZiAob3B0aW9ucy53YWl0KSBjb2xsZWN0aW9uLmFkZChtb2RlbCwgb3B0aW9ucyk7XG4gICAgICBpZiAoc3VjY2Vzcykgc3VjY2Vzcyhtb2RlbCwgcmVzcCwgb3B0aW9ucyk7XG4gICAgfTtcbiAgICBtb2RlbC5zYXZlKG51bGwsIG9wdGlvbnMpO1xuICAgIHJldHVybiBtb2RlbDtcbiAgfSxcblxuICAgIC8vICoqcGFyc2UqKiBjb252ZXJ0cyBhIHJlc3BvbnNlIGludG8gYSBsaXN0IG9mIG1vZGVscyB0byBiZSBhZGRlZCB0byB0aGVcbiAgICAvLyBjb2xsZWN0aW9uLiBUaGUgZGVmYXVsdCBpbXBsZW1lbnRhdGlvbiBpcyBqdXN0IHRvIHBhc3MgaXQgdGhyb3VnaC5cbiAgcGFyc2U6IGZ1bmN0aW9uKHJlc3AsIG9wdGlvbnMpIHtcbiAgICByZXR1cm4gcmVzcDtcbiAgfSxcblxuICAgIC8vIENyZWF0ZSBhIG5ldyBjb2xsZWN0aW9uIHdpdGggYW4gaWRlbnRpY2FsIGxpc3Qgb2YgbW9kZWxzIGFzIHRoaXMgb25lLlxuICBjbG9uZTogZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKHRoaXMubW9kZWxzLCB7XG4gICAgICBtb2RlbDogdGhpcy5tb2RlbCxcbiAgICAgIGNvbXBhcmF0b3I6IHRoaXMuY29tcGFyYXRvclxuICAgIH0pO1xuICB9LFxuXG4gICAgLy8gRGVmaW5lIGhvdyB0byB1bmlxdWVseSBpZGVudGlmeSBtb2RlbHMgaW4gdGhlIGNvbGxlY3Rpb24uXG4gIG1vZGVsSWQ6IGZ1bmN0aW9uIChhdHRycykge1xuICAgIHJldHVybiBhdHRyc1t0aGlzLm1vZGVsLnByb3RvdHlwZS5pZEF0dHJpYnV0ZSB8fCAnaWQnXTtcbiAgfSxcblxuICAgIC8vIFByaXZhdGUgbWV0aG9kIHRvIHJlc2V0IGFsbCBpbnRlcm5hbCBzdGF0ZS4gQ2FsbGVkIHdoZW4gdGhlIGNvbGxlY3Rpb25cbiAgICAvLyBpcyBmaXJzdCBpbml0aWFsaXplZCBvciByZXNldC5cbiAgX3Jlc2V0OiBmdW5jdGlvbigpIHtcbiAgICB0aGlzLmxlbmd0aCA9IDA7XG4gICAgdGhpcy5tb2RlbHMgPSBbXTtcbiAgICB0aGlzLl9ieUlkICA9IHt9O1xuICB9LFxuXG4gICAgLy8gUHJlcGFyZSBhIGhhc2ggb2YgYXR0cmlidXRlcyAob3Igb3RoZXIgbW9kZWwpIHRvIGJlIGFkZGVkIHRvIHRoaXNcbiAgICAvLyBjb2xsZWN0aW9uLlxuICBfcHJlcGFyZU1vZGVsOiBmdW5jdGlvbihhdHRycywgb3B0aW9ucykge1xuICAgIGlmICh0aGlzLl9pc01vZGVsKGF0dHJzKSkge1xuICAgICAgaWYgKCFhdHRycy5jb2xsZWN0aW9uKSBhdHRycy5jb2xsZWN0aW9uID0gdGhpcztcbiAgICAgIHJldHVybiBhdHRycztcbiAgICB9XG4gICAgb3B0aW9ucyA9IG9wdGlvbnMgPyBfLmNsb25lKG9wdGlvbnMpIDoge307XG4gICAgb3B0aW9ucy5jb2xsZWN0aW9uID0gdGhpcztcbiAgICB2YXIgbW9kZWwgPSBuZXcgdGhpcy5tb2RlbChhdHRycywgb3B0aW9ucyk7XG4gICAgaWYgKCFtb2RlbC52YWxpZGF0aW9uRXJyb3IpIHJldHVybiBtb2RlbDtcbiAgICB0aGlzLnRyaWdnZXIoJ2ludmFsaWQnLCB0aGlzLCBtb2RlbC52YWxpZGF0aW9uRXJyb3IsIG9wdGlvbnMpO1xuICAgIHJldHVybiBmYWxzZTtcbiAgfSxcblxuICAgIC8vIE1ldGhvZCBmb3IgY2hlY2tpbmcgd2hldGhlciBhbiBvYmplY3Qgc2hvdWxkIGJlIGNvbnNpZGVyZWQgYSBtb2RlbCBmb3JcbiAgICAvLyB0aGUgcHVycG9zZXMgb2YgYWRkaW5nIHRvIHRoZSBjb2xsZWN0aW9uLlxuICBfaXNNb2RlbDogZnVuY3Rpb24gKG1vZGVsKSB7XG4gICAgcmV0dXJuIG1vZGVsIGluc3RhbmNlb2YgTW9kZWw7XG4gIH0sXG5cbiAgICAvLyBJbnRlcm5hbCBtZXRob2QgdG8gY3JlYXRlIGEgbW9kZWwncyB0aWVzIHRvIGEgY29sbGVjdGlvbi5cbiAgX2FkZFJlZmVyZW5jZTogZnVuY3Rpb24obW9kZWwsIG9wdGlvbnMpIHtcbiAgICB0aGlzLl9ieUlkW21vZGVsLmNpZF0gPSBtb2RlbDtcbiAgICB2YXIgaWQgPSB0aGlzLm1vZGVsSWQobW9kZWwuYXR0cmlidXRlcyk7XG4gICAgaWYgKGlkICE9IG51bGwpIHRoaXMuX2J5SWRbaWRdID0gbW9kZWw7XG4gICAgbW9kZWwub24oJ2FsbCcsIHRoaXMuX29uTW9kZWxFdmVudCwgdGhpcyk7XG4gIH0sXG5cbiAgICAvLyBJbnRlcm5hbCBtZXRob2QgdG8gc2V2ZXIgYSBtb2RlbCdzIHRpZXMgdG8gYSBjb2xsZWN0aW9uLlxuICBfcmVtb3ZlUmVmZXJlbmNlOiBmdW5jdGlvbihtb2RlbCwgb3B0aW9ucykge1xuICAgIGlmICh0aGlzID09PSBtb2RlbC5jb2xsZWN0aW9uKSBkZWxldGUgbW9kZWwuY29sbGVjdGlvbjtcbiAgICBtb2RlbC5vZmYoJ2FsbCcsIHRoaXMuX29uTW9kZWxFdmVudCwgdGhpcyk7XG4gIH0sXG5cbiAgICAvLyBJbnRlcm5hbCBtZXRob2QgY2FsbGVkIGV2ZXJ5IHRpbWUgYSBtb2RlbCBpbiB0aGUgc2V0IGZpcmVzIGFuIGV2ZW50LlxuICAgIC8vIFNldHMgbmVlZCB0byB1cGRhdGUgdGhlaXIgaW5kZXhlcyB3aGVuIG1vZGVscyBjaGFuZ2UgaWRzLiBBbGwgb3RoZXJcbiAgICAvLyBldmVudHMgc2ltcGx5IHByb3h5IHRocm91Z2guIFwiYWRkXCIgYW5kIFwicmVtb3ZlXCIgZXZlbnRzIHRoYXQgb3JpZ2luYXRlXG4gICAgLy8gaW4gb3RoZXIgY29sbGVjdGlvbnMgYXJlIGlnbm9yZWQuXG4gIF9vbk1vZGVsRXZlbnQ6IGZ1bmN0aW9uKGV2ZW50LCBtb2RlbCwgY29sbGVjdGlvbiwgb3B0aW9ucykge1xuICAgIGlmICgoZXZlbnQgPT09ICdhZGQnIHx8IGV2ZW50ID09PSAncmVtb3ZlJykgJiYgY29sbGVjdGlvbiAhPT0gdGhpcykgcmV0dXJuO1xuICAgIGlmIChldmVudCA9PT0gJ2Rlc3Ryb3knKSB0aGlzLnJlbW92ZShtb2RlbCwgb3B0aW9ucyk7XG4gICAgaWYgKGV2ZW50ID09PSAnY2hhbmdlJykge1xuICAgICAgdmFyIHByZXZJZCA9IHRoaXMubW9kZWxJZChtb2RlbC5wcmV2aW91c0F0dHJpYnV0ZXMoKSk7XG4gICAgICB2YXIgaWQgPSB0aGlzLm1vZGVsSWQobW9kZWwuYXR0cmlidXRlcyk7XG4gICAgICBpZiAocHJldklkICE9PSBpZCkge1xuICAgICAgICBpZiAocHJldklkICE9IG51bGwpIGRlbGV0ZSB0aGlzLl9ieUlkW3ByZXZJZF07XG4gICAgICAgIGlmIChpZCAhPSBudWxsKSB0aGlzLl9ieUlkW2lkXSA9IG1vZGVsO1xuICAgICAgfVxuICAgIH1cbiAgICB0aGlzLnRyaWdnZXIuYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgfVxuXG59KTtcblxuLy8gVW5kZXJzY29yZSBtZXRob2RzIHRoYXQgd2Ugd2FudCB0byBpbXBsZW1lbnQgb24gdGhlIENvbGxlY3Rpb24uXG4vLyA5MCUgb2YgdGhlIGNvcmUgdXNlZnVsbmVzcyBvZiBCYWNrYm9uZSBDb2xsZWN0aW9ucyBpcyBhY3R1YWxseSBpbXBsZW1lbnRlZFxuLy8gcmlnaHQgaGVyZTpcbnZhciBtZXRob2RzID0gWydmb3JFYWNoJywgJ2VhY2gnLCAnbWFwJywgJ2NvbGxlY3QnLCAncmVkdWNlJywgJ2ZvbGRsJyxcbiAgICAnaW5qZWN0JywgJ3JlZHVjZVJpZ2h0JywgJ2ZvbGRyJywgJ2ZpbmQnLCAnZGV0ZWN0JywgJ2ZpbHRlcicsICdzZWxlY3QnLFxuICAgICdyZWplY3QnLCAnZXZlcnknLCAnYWxsJywgJ3NvbWUnLCAnYW55JywgJ2luY2x1ZGUnLCAnY29udGFpbnMnLCAnaW52b2tlJyxcbiAgICAnbWF4JywgJ21pbicsICd0b0FycmF5JywgJ3NpemUnLCAnZmlyc3QnLCAnaGVhZCcsICd0YWtlJywgJ2luaXRpYWwnLCAncmVzdCcsXG4gICAgJ3RhaWwnLCAnZHJvcCcsICdsYXN0JywgJ3dpdGhvdXQnLCAnZGlmZmVyZW5jZScsICdpbmRleE9mJywgJ3NodWZmbGUnLFxuICAgICdsYXN0SW5kZXhPZicsICdpc0VtcHR5JywgJ2NoYWluJywgJ3NhbXBsZScsICdwYXJ0aXRpb24nXTtcblxuLy8gTWl4IGluIGVhY2ggVW5kZXJzY29yZSBtZXRob2QgYXMgYSBwcm94eSB0byBgQ29sbGVjdGlvbiNtb2RlbHNgLlxuXy5lYWNoKG1ldGhvZHMsIGZ1bmN0aW9uKG1ldGhvZCkge1xuICBpZiAoIV9bbWV0aG9kXSkgcmV0dXJuO1xuICBDb2xsZWN0aW9uLnByb3RvdHlwZVttZXRob2RdID0gZnVuY3Rpb24oKSB7XG4gICAgdmFyIGFyZ3MgPSBzbGljZS5jYWxsKGFyZ3VtZW50cyk7XG4gICAgYXJncy51bnNoaWZ0KHRoaXMubW9kZWxzKTtcbiAgICByZXR1cm4gX1ttZXRob2RdLmFwcGx5KF8sIGFyZ3MpO1xuICB9O1xufSk7XG5cbi8vIFVuZGVyc2NvcmUgbWV0aG9kcyB0aGF0IHRha2UgYSBwcm9wZXJ0eSBuYW1lIGFzIGFuIGFyZ3VtZW50LlxudmFyIGF0dHJpYnV0ZU1ldGhvZHMgPSBbJ2dyb3VwQnknLCAnY291bnRCeScsICdzb3J0QnknLCAnaW5kZXhCeSddO1xuXG4vLyBVc2UgYXR0cmlidXRlcyBpbnN0ZWFkIG9mIHByb3BlcnRpZXMuXG5fLmVhY2goYXR0cmlidXRlTWV0aG9kcywgZnVuY3Rpb24obWV0aG9kKSB7XG4gIGlmICghX1ttZXRob2RdKSByZXR1cm47XG4gIENvbGxlY3Rpb24ucHJvdG90eXBlW21ldGhvZF0gPSBmdW5jdGlvbih2YWx1ZSwgY29udGV4dCkge1xuICAgIHZhciBpdGVyYXRvciA9IF8uaXNGdW5jdGlvbih2YWx1ZSkgPyB2YWx1ZSA6IGZ1bmN0aW9uKG1vZGVsKSB7XG4gICAgICByZXR1cm4gbW9kZWwuZ2V0KHZhbHVlKTtcbiAgICB9O1xuICAgIHJldHVybiBfW21ldGhvZF0odGhpcy5tb2RlbHMsIGl0ZXJhdG9yLCBjb250ZXh0KTtcbiAgfTtcbn0pO1xuXG4vLyBzZXR1cCBpbmhlcml0YW5jZVxuQ29sbGVjdGlvbi5leHRlbmQgPSBleHRlbmQ7XG5tb2R1bGUuZXhwb3J0cyA9IENvbGxlY3Rpb247XG4iLCJtb2R1bGUuZXhwb3J0cy5Nb2RlbCA9IHJlcXVpcmUoXCIuL21vZGVsXCIpO1xubW9kdWxlLmV4cG9ydHMuQ29sbGVjdGlvbiA9IHJlcXVpcmUoXCIuL2NvbGxlY3Rpb25cIik7XG5tb2R1bGUuZXhwb3J0cy5FdmVudHMgPSByZXF1aXJlKFwiYmFja2JvbmUtZXZlbnRzLXN0YW5kYWxvbmVcIik7XG5tb2R1bGUuZXhwb3J0cy5leHRlbmQgPSByZXF1aXJlKFwiYmFja2JvbmUtZXh0ZW5kLXN0YW5kYWxvbmVcIik7XG4iLCIvLyAgICAgQmFja2JvbmUuanMgMS4xLjJcblxuLy8gICAgIChjKSAyMDEwLTIwMTQgSmVyZW15IEFzaGtlbmFzLCBEb2N1bWVudENsb3VkIGFuZCBJbnZlc3RpZ2F0aXZlIFJlcG9ydGVycyAmIEVkaXRvcnNcbi8vICAgICBCYWNrYm9uZSBtYXkgYmUgZnJlZWx5IGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBNSVQgbGljZW5zZS5cbi8vICAgICBGb3IgYWxsIGRldGFpbHMgYW5kIGRvY3VtZW50YXRpb246XG4vLyAgICAgaHR0cDovL2JhY2tib25lanMub3JnXG5cbnZhciBFdmVudHMgPSByZXF1aXJlKFwiYmFja2JvbmUtZXZlbnRzLXN0YW5kYWxvbmVcIik7XG52YXIgZXh0ZW5kID0gcmVxdWlyZShcImJhY2tib25lLWV4dGVuZC1zdGFuZGFsb25lXCIpO1xudmFyIF8gPSByZXF1aXJlKFwidW5kZXJzY29yZVwiKTtcblxuLy8gQmFja2JvbmUuTW9kZWxcbi8vIC0tLS0tLS0tLS0tLS0tXG5cbi8vIEJhY2tib25lICoqTW9kZWxzKiogYXJlIHRoZSBiYXNpYyBkYXRhIG9iamVjdCBpbiB0aGUgZnJhbWV3b3JrIC0tXG4vLyBmcmVxdWVudGx5IHJlcHJlc2VudGluZyBhIHJvdyBpbiBhIHRhYmxlIGluIGEgZGF0YWJhc2Ugb24geW91ciBzZXJ2ZXIuXG4vLyBBIGRpc2NyZXRlIGNodW5rIG9mIGRhdGEgYW5kIGEgYnVuY2ggb2YgdXNlZnVsLCByZWxhdGVkIG1ldGhvZHMgZm9yXG4vLyBwZXJmb3JtaW5nIGNvbXB1dGF0aW9ucyBhbmQgdHJhbnNmb3JtYXRpb25zIG9uIHRoYXQgZGF0YS5cblxuLy8gQ3JlYXRlIGEgbmV3IG1vZGVsIHdpdGggdGhlIHNwZWNpZmllZCBhdHRyaWJ1dGVzLiBBIGNsaWVudCBpZCAoYGNpZGApXG4vLyBpcyBhdXRvbWF0aWNhbGx5IGdlbmVyYXRlZCBhbmQgYXNzaWduZWQgZm9yIHlvdS5cbnZhciBNb2RlbCA9IGZ1bmN0aW9uKGF0dHJpYnV0ZXMsIG9wdGlvbnMpIHtcbiAgdmFyIGF0dHJzID0gYXR0cmlidXRlcyB8fCB7fTtcbiAgb3B0aW9ucyB8fCAob3B0aW9ucyA9IHt9KTtcbiAgdGhpcy5jaWQgPSBfLnVuaXF1ZUlkKCdjJyk7XG4gIHRoaXMuYXR0cmlidXRlcyA9IHt9O1xuICBpZiAob3B0aW9ucy5jb2xsZWN0aW9uKSB0aGlzLmNvbGxlY3Rpb24gPSBvcHRpb25zLmNvbGxlY3Rpb247XG4gIGlmIChvcHRpb25zLnBhcnNlKSBhdHRycyA9IHRoaXMucGFyc2UoYXR0cnMsIG9wdGlvbnMpIHx8IHt9O1xuICBhdHRycyA9IF8uZGVmYXVsdHMoe30sIGF0dHJzLCBfLnJlc3VsdCh0aGlzLCAnZGVmYXVsdHMnKSk7XG4gIHRoaXMuc2V0KGF0dHJzLCBvcHRpb25zKTtcbiAgdGhpcy5jaGFuZ2VkID0ge307XG4gIHRoaXMuaW5pdGlhbGl6ZS5hcHBseSh0aGlzLCBhcmd1bWVudHMpO1xufTtcblxuLy8gQXR0YWNoIGFsbCBpbmhlcml0YWJsZSBtZXRob2RzIHRvIHRoZSBNb2RlbCBwcm90b3R5cGUuXG5fLmV4dGVuZChNb2RlbC5wcm90b3R5cGUsIEV2ZW50cywge1xuXG4gIC8vIEEgaGFzaCBvZiBhdHRyaWJ1dGVzIHdob3NlIGN1cnJlbnQgYW5kIHByZXZpb3VzIHZhbHVlIGRpZmZlci5cbiAgY2hhbmdlZDogbnVsbCxcblxuICAvLyBUaGUgdmFsdWUgcmV0dXJuZWQgZHVyaW5nIHRoZSBsYXN0IGZhaWxlZCB2YWxpZGF0aW9uLlxuICB2YWxpZGF0aW9uRXJyb3I6IG51bGwsXG5cbiAgICAvLyBUaGUgZGVmYXVsdCBuYW1lIGZvciB0aGUgSlNPTiBgaWRgIGF0dHJpYnV0ZSBpcyBgXCJpZFwiYC4gTW9uZ29EQiBhbmRcbiAgICAvLyBDb3VjaERCIHVzZXJzIG1heSB3YW50IHRvIHNldCB0aGlzIHRvIGBcIl9pZFwiYC5cbiAgaWRBdHRyaWJ1dGU6ICdpZCcsXG5cbiAgICAvLyBJbml0aWFsaXplIGlzIGFuIGVtcHR5IGZ1bmN0aW9uIGJ5IGRlZmF1bHQuIE92ZXJyaWRlIGl0IHdpdGggeW91ciBvd25cbiAgICAvLyBpbml0aWFsaXphdGlvbiBsb2dpYy5cbiAgaW5pdGlhbGl6ZTogZnVuY3Rpb24oKXt9LFxuXG4gICAgLy8gUmV0dXJuIGEgY29weSBvZiB0aGUgbW9kZWwncyBgYXR0cmlidXRlc2Agb2JqZWN0LlxuICB0b0pTT046IGZ1bmN0aW9uKG9wdGlvbnMpIHtcbiAgICByZXR1cm4gXy5jbG9uZSh0aGlzLmF0dHJpYnV0ZXMpO1xuICB9LFxuXG4gICAgLy8gUHJveHkgYEJhY2tib25lLnN5bmNgIGJ5IGRlZmF1bHQgLS0gYnV0IG92ZXJyaWRlIHRoaXMgaWYgeW91IG5lZWRcbiAgICAvLyBjdXN0b20gc3luY2luZyBzZW1hbnRpY3MgZm9yICp0aGlzKiBwYXJ0aWN1bGFyIG1vZGVsLlxuICBzeW5jOiBmdW5jdGlvbigpIHtcbiAgICByZXR1cm4gQmFja2JvbmUuc3luYy5hcHBseSh0aGlzLCBhcmd1bWVudHMpO1xuICB9LFxuXG4gICAgLy8gR2V0IHRoZSB2YWx1ZSBvZiBhbiBhdHRyaWJ1dGUuXG4gIGdldDogZnVuY3Rpb24oYXR0cikge1xuICAgIHJldHVybiB0aGlzLmF0dHJpYnV0ZXNbYXR0cl07XG4gIH0sXG5cbiAgICAvLyBHZXQgdGhlIEhUTUwtZXNjYXBlZCB2YWx1ZSBvZiBhbiBhdHRyaWJ1dGUuXG4gIGVzY2FwZTogZnVuY3Rpb24oYXR0cikge1xuICAgIHJldHVybiBfLmVzY2FwZSh0aGlzLmdldChhdHRyKSk7XG4gIH0sXG5cbiAgICAvLyBSZXR1cm5zIGB0cnVlYCBpZiB0aGUgYXR0cmlidXRlIGNvbnRhaW5zIGEgdmFsdWUgdGhhdCBpcyBub3QgbnVsbFxuICAgIC8vIG9yIHVuZGVmaW5lZC5cbiAgaGFzOiBmdW5jdGlvbihhdHRyKSB7XG4gICAgcmV0dXJuIHRoaXMuZ2V0KGF0dHIpICE9IG51bGw7XG4gIH0sXG5cbiAgICAvLyBTZXQgYSBoYXNoIG9mIG1vZGVsIGF0dHJpYnV0ZXMgb24gdGhlIG9iamVjdCwgZmlyaW5nIGBcImNoYW5nZVwiYC4gVGhpcyBpc1xuICAgIC8vIHRoZSBjb3JlIHByaW1pdGl2ZSBvcGVyYXRpb24gb2YgYSBtb2RlbCwgdXBkYXRpbmcgdGhlIGRhdGEgYW5kIG5vdGlmeWluZ1xuICAgIC8vIGFueW9uZSB3aG8gbmVlZHMgdG8ga25vdyBhYm91dCB0aGUgY2hhbmdlIGluIHN0YXRlLiBUaGUgaGVhcnQgb2YgdGhlIGJlYXN0LlxuICBzZXQ6IGZ1bmN0aW9uKGtleSwgdmFsLCBvcHRpb25zKSB7XG4gICAgdmFyIGF0dHIsIGF0dHJzLCB1bnNldCwgY2hhbmdlcywgc2lsZW50LCBjaGFuZ2luZywgcHJldiwgY3VycmVudDtcbiAgICBpZiAoa2V5ID09IG51bGwpIHJldHVybiB0aGlzO1xuXG4gICAgLy8gSGFuZGxlIGJvdGggYFwia2V5XCIsIHZhbHVlYCBhbmQgYHtrZXk6IHZhbHVlfWAgLXN0eWxlIGFyZ3VtZW50cy5cbiAgICBpZiAodHlwZW9mIGtleSA9PT0gJ29iamVjdCcpIHtcbiAgICAgIGF0dHJzID0ga2V5O1xuICAgICAgb3B0aW9ucyA9IHZhbDtcbiAgICB9IGVsc2Uge1xuICAgICAgKGF0dHJzID0ge30pW2tleV0gPSB2YWw7XG4gICAgfVxuXG4gICAgb3B0aW9ucyB8fCAob3B0aW9ucyA9IHt9KTtcblxuICAgIC8vIFJ1biB2YWxpZGF0aW9uLlxuICAgIGlmICghdGhpcy5fdmFsaWRhdGUoYXR0cnMsIG9wdGlvbnMpKSByZXR1cm4gZmFsc2U7XG5cbiAgICAvLyBFeHRyYWN0IGF0dHJpYnV0ZXMgYW5kIG9wdGlvbnMuXG4gICAgdW5zZXQgICAgICAgICAgID0gb3B0aW9ucy51bnNldDtcbiAgICBzaWxlbnQgICAgICAgICAgPSBvcHRpb25zLnNpbGVudDtcbiAgICBjaGFuZ2VzICAgICAgICAgPSBbXTtcbiAgICBjaGFuZ2luZyAgICAgICAgPSB0aGlzLl9jaGFuZ2luZztcbiAgICB0aGlzLl9jaGFuZ2luZyAgPSB0cnVlO1xuXG4gICAgaWYgKCFjaGFuZ2luZykge1xuICAgICAgdGhpcy5fcHJldmlvdXNBdHRyaWJ1dGVzID0gXy5jbG9uZSh0aGlzLmF0dHJpYnV0ZXMpO1xuICAgICAgdGhpcy5jaGFuZ2VkID0ge307XG4gICAgfVxuICAgIGN1cnJlbnQgPSB0aGlzLmF0dHJpYnV0ZXMsIHByZXYgPSB0aGlzLl9wcmV2aW91c0F0dHJpYnV0ZXM7XG5cbiAgICAvLyBDaGVjayBmb3IgY2hhbmdlcyBvZiBgaWRgLlxuICAgIGlmICh0aGlzLmlkQXR0cmlidXRlIGluIGF0dHJzKSB0aGlzLmlkID0gYXR0cnNbdGhpcy5pZEF0dHJpYnV0ZV07XG5cbiAgICAvLyBGb3IgZWFjaCBgc2V0YCBhdHRyaWJ1dGUsIHVwZGF0ZSBvciBkZWxldGUgdGhlIGN1cnJlbnQgdmFsdWUuXG4gICAgZm9yIChhdHRyIGluIGF0dHJzKSB7XG4gICAgICB2YWwgPSBhdHRyc1thdHRyXTtcbiAgICAgIGlmICghXy5pc0VxdWFsKGN1cnJlbnRbYXR0cl0sIHZhbCkpIGNoYW5nZXMucHVzaChhdHRyKTtcbiAgICAgIGlmICghXy5pc0VxdWFsKHByZXZbYXR0cl0sIHZhbCkpIHtcbiAgICAgICAgdGhpcy5jaGFuZ2VkW2F0dHJdID0gdmFsO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgZGVsZXRlIHRoaXMuY2hhbmdlZFthdHRyXTtcbiAgICAgIH1cbiAgICAgIHVuc2V0ID8gZGVsZXRlIGN1cnJlbnRbYXR0cl0gOiBjdXJyZW50W2F0dHJdID0gdmFsO1xuICAgIH1cblxuICAgIC8vIFRyaWdnZXIgYWxsIHJlbGV2YW50IGF0dHJpYnV0ZSBjaGFuZ2VzLlxuICAgIGlmICghc2lsZW50KSB7XG4gICAgICBpZiAoY2hhbmdlcy5sZW5ndGgpIHRoaXMuX3BlbmRpbmcgPSBvcHRpb25zO1xuICAgICAgZm9yICh2YXIgaSA9IDAsIGxlbmd0aCA9IGNoYW5nZXMubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgdGhpcy50cmlnZ2VyKCdjaGFuZ2U6JyArIGNoYW5nZXNbaV0sIHRoaXMsIGN1cnJlbnRbY2hhbmdlc1tpXV0sIG9wdGlvbnMpO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIFlvdSBtaWdodCBiZSB3b25kZXJpbmcgd2h5IHRoZXJlJ3MgYSBgd2hpbGVgIGxvb3AgaGVyZS4gQ2hhbmdlcyBjYW5cbiAgICAvLyBiZSByZWN1cnNpdmVseSBuZXN0ZWQgd2l0aGluIGBcImNoYW5nZVwiYCBldmVudHMuXG4gICAgaWYgKGNoYW5naW5nKSByZXR1cm4gdGhpcztcbiAgICBpZiAoIXNpbGVudCkge1xuICAgICAgd2hpbGUgKHRoaXMuX3BlbmRpbmcpIHtcbiAgICAgICAgb3B0aW9ucyA9IHRoaXMuX3BlbmRpbmc7XG4gICAgICAgIHRoaXMuX3BlbmRpbmcgPSBmYWxzZTtcbiAgICAgICAgdGhpcy50cmlnZ2VyKCdjaGFuZ2UnLCB0aGlzLCBvcHRpb25zKTtcbiAgICAgIH1cbiAgICB9XG4gICAgdGhpcy5fcGVuZGluZyA9IGZhbHNlO1xuICAgIHRoaXMuX2NoYW5naW5nID0gZmFsc2U7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH0sXG5cbiAgICAvLyBSZW1vdmUgYW4gYXR0cmlidXRlIGZyb20gdGhlIG1vZGVsLCBmaXJpbmcgYFwiY2hhbmdlXCJgLiBgdW5zZXRgIGlzIGEgbm9vcFxuICAgIC8vIGlmIHRoZSBhdHRyaWJ1dGUgZG9lc24ndCBleGlzdC5cbiAgdW5zZXQ6IGZ1bmN0aW9uKGF0dHIsIG9wdGlvbnMpIHtcbiAgICByZXR1cm4gdGhpcy5zZXQoYXR0ciwgdm9pZCAwLCBfLmV4dGVuZCh7fSwgb3B0aW9ucywge3Vuc2V0OiB0cnVlfSkpO1xuICB9LFxuXG4gICAgLy8gQ2xlYXIgYWxsIGF0dHJpYnV0ZXMgb24gdGhlIG1vZGVsLCBmaXJpbmcgYFwiY2hhbmdlXCJgLlxuICBjbGVhcjogZnVuY3Rpb24ob3B0aW9ucykge1xuICAgIHZhciBhdHRycyA9IHt9O1xuICAgIGZvciAodmFyIGtleSBpbiB0aGlzLmF0dHJpYnV0ZXMpIGF0dHJzW2tleV0gPSB2b2lkIDA7XG4gICAgcmV0dXJuIHRoaXMuc2V0KGF0dHJzLCBfLmV4dGVuZCh7fSwgb3B0aW9ucywge3Vuc2V0OiB0cnVlfSkpO1xuICB9LFxuXG4gICAgLy8gRGV0ZXJtaW5lIGlmIHRoZSBtb2RlbCBoYXMgY2hhbmdlZCBzaW5jZSB0aGUgbGFzdCBgXCJjaGFuZ2VcImAgZXZlbnQuXG4gICAgLy8gSWYgeW91IHNwZWNpZnkgYW4gYXR0cmlidXRlIG5hbWUsIGRldGVybWluZSBpZiB0aGF0IGF0dHJpYnV0ZSBoYXMgY2hhbmdlZC5cbiAgaGFzQ2hhbmdlZDogZnVuY3Rpb24oYXR0cikge1xuICAgIGlmIChhdHRyID09IG51bGwpIHJldHVybiAhXy5pc0VtcHR5KHRoaXMuY2hhbmdlZCk7XG4gICAgcmV0dXJuIF8uaGFzKHRoaXMuY2hhbmdlZCwgYXR0cik7XG4gIH0sXG5cbiAgICAvLyBSZXR1cm4gYW4gb2JqZWN0IGNvbnRhaW5pbmcgYWxsIHRoZSBhdHRyaWJ1dGVzIHRoYXQgaGF2ZSBjaGFuZ2VkLCBvclxuICAgIC8vIGZhbHNlIGlmIHRoZXJlIGFyZSBubyBjaGFuZ2VkIGF0dHJpYnV0ZXMuIFVzZWZ1bCBmb3IgZGV0ZXJtaW5pbmcgd2hhdFxuICAgIC8vIHBhcnRzIG9mIGEgdmlldyBuZWVkIHRvIGJlIHVwZGF0ZWQgYW5kL29yIHdoYXQgYXR0cmlidXRlcyBuZWVkIHRvIGJlXG4gICAgLy8gcGVyc2lzdGVkIHRvIHRoZSBzZXJ2ZXIuIFVuc2V0IGF0dHJpYnV0ZXMgd2lsbCBiZSBzZXQgdG8gdW5kZWZpbmVkLlxuICAgIC8vIFlvdSBjYW4gYWxzbyBwYXNzIGFuIGF0dHJpYnV0ZXMgb2JqZWN0IHRvIGRpZmYgYWdhaW5zdCB0aGUgbW9kZWwsXG4gICAgLy8gZGV0ZXJtaW5pbmcgaWYgdGhlcmUgKndvdWxkIGJlKiBhIGNoYW5nZS5cbiAgY2hhbmdlZEF0dHJpYnV0ZXM6IGZ1bmN0aW9uKGRpZmYpIHtcbiAgICBpZiAoIWRpZmYpIHJldHVybiB0aGlzLmhhc0NoYW5nZWQoKSA/IF8uY2xvbmUodGhpcy5jaGFuZ2VkKSA6IGZhbHNlO1xuICAgIHZhciB2YWwsIGNoYW5nZWQgPSBmYWxzZTtcbiAgICB2YXIgb2xkID0gdGhpcy5fY2hhbmdpbmcgPyB0aGlzLl9wcmV2aW91c0F0dHJpYnV0ZXMgOiB0aGlzLmF0dHJpYnV0ZXM7XG4gICAgZm9yICh2YXIgYXR0ciBpbiBkaWZmKSB7XG4gICAgICBpZiAoXy5pc0VxdWFsKG9sZFthdHRyXSwgKHZhbCA9IGRpZmZbYXR0cl0pKSkgY29udGludWU7XG4gICAgICAoY2hhbmdlZCB8fCAoY2hhbmdlZCA9IHt9KSlbYXR0cl0gPSB2YWw7XG4gICAgfVxuICAgIHJldHVybiBjaGFuZ2VkO1xuICB9LFxuXG4gICAgLy8gR2V0IHRoZSBwcmV2aW91cyB2YWx1ZSBvZiBhbiBhdHRyaWJ1dGUsIHJlY29yZGVkIGF0IHRoZSB0aW1lIHRoZSBsYXN0XG4gICAgLy8gYFwiY2hhbmdlXCJgIGV2ZW50IHdhcyBmaXJlZC5cbiAgcHJldmlvdXM6IGZ1bmN0aW9uKGF0dHIpIHtcbiAgICBpZiAoYXR0ciA9PSBudWxsIHx8ICF0aGlzLl9wcmV2aW91c0F0dHJpYnV0ZXMpIHJldHVybiBudWxsO1xuICAgIHJldHVybiB0aGlzLl9wcmV2aW91c0F0dHJpYnV0ZXNbYXR0cl07XG4gIH0sXG5cbiAgICAvLyBHZXQgYWxsIG9mIHRoZSBhdHRyaWJ1dGVzIG9mIHRoZSBtb2RlbCBhdCB0aGUgdGltZSBvZiB0aGUgcHJldmlvdXNcbiAgICAvLyBgXCJjaGFuZ2VcImAgZXZlbnQuXG4gIHByZXZpb3VzQXR0cmlidXRlczogZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIF8uY2xvbmUodGhpcy5fcHJldmlvdXNBdHRyaWJ1dGVzKTtcbiAgfSxcblxuICAgIC8vIEZldGNoIHRoZSBtb2RlbCBmcm9tIHRoZSBzZXJ2ZXIuIElmIHRoZSBzZXJ2ZXIncyByZXByZXNlbnRhdGlvbiBvZiB0aGVcbiAgICAvLyBtb2RlbCBkaWZmZXJzIGZyb20gaXRzIGN1cnJlbnQgYXR0cmlidXRlcywgdGhleSB3aWxsIGJlIG92ZXJyaWRkZW4sXG4gICAgLy8gdHJpZ2dlcmluZyBhIGBcImNoYW5nZVwiYCBldmVudC5cbiAgZmV0Y2g6IGZ1bmN0aW9uKG9wdGlvbnMpIHtcbiAgICBvcHRpb25zID0gb3B0aW9ucyA/IF8uY2xvbmUob3B0aW9ucykgOiB7fTtcbiAgICBpZiAob3B0aW9ucy5wYXJzZSA9PT0gdm9pZCAwKSBvcHRpb25zLnBhcnNlID0gdHJ1ZTtcbiAgICB2YXIgbW9kZWwgPSB0aGlzO1xuICAgIHZhciBzdWNjZXNzID0gb3B0aW9ucy5zdWNjZXNzO1xuICAgIG9wdGlvbnMuc3VjY2VzcyA9IGZ1bmN0aW9uKHJlc3ApIHtcbiAgICAgIGlmICghbW9kZWwuc2V0KG1vZGVsLnBhcnNlKHJlc3AsIG9wdGlvbnMpLCBvcHRpb25zKSkgcmV0dXJuIGZhbHNlO1xuICAgICAgaWYgKHN1Y2Nlc3MpIHN1Y2Nlc3MobW9kZWwsIHJlc3AsIG9wdGlvbnMpO1xuICAgICAgbW9kZWwudHJpZ2dlcignc3luYycsIG1vZGVsLCByZXNwLCBvcHRpb25zKTtcbiAgICB9O1xuICAgIHdyYXBFcnJvcih0aGlzLCBvcHRpb25zKTtcbiAgICByZXR1cm4gdGhpcy5zeW5jKCdyZWFkJywgdGhpcywgb3B0aW9ucyk7XG4gIH0sXG5cbiAgICAvLyBTZXQgYSBoYXNoIG9mIG1vZGVsIGF0dHJpYnV0ZXMsIGFuZCBzeW5jIHRoZSBtb2RlbCB0byB0aGUgc2VydmVyLlxuICAgIC8vIElmIHRoZSBzZXJ2ZXIgcmV0dXJucyBhbiBhdHRyaWJ1dGVzIGhhc2ggdGhhdCBkaWZmZXJzLCB0aGUgbW9kZWwnc1xuICAgIC8vIHN0YXRlIHdpbGwgYmUgYHNldGAgYWdhaW4uXG4gIHNhdmU6IGZ1bmN0aW9uKGtleSwgdmFsLCBvcHRpb25zKSB7XG4gICAgdmFyIGF0dHJzLCBtZXRob2QsIHhociwgYXR0cmlidXRlcyA9IHRoaXMuYXR0cmlidXRlcztcblxuICAgIC8vIEhhbmRsZSBib3RoIGBcImtleVwiLCB2YWx1ZWAgYW5kIGB7a2V5OiB2YWx1ZX1gIC1zdHlsZSBhcmd1bWVudHMuXG4gICAgaWYgKGtleSA9PSBudWxsIHx8IHR5cGVvZiBrZXkgPT09ICdvYmplY3QnKSB7XG4gICAgICBhdHRycyA9IGtleTtcbiAgICAgIG9wdGlvbnMgPSB2YWw7XG4gICAgfSBlbHNlIHtcbiAgICAgIChhdHRycyA9IHt9KVtrZXldID0gdmFsO1xuICAgIH1cblxuICAgIG9wdGlvbnMgPSBfLmV4dGVuZCh7dmFsaWRhdGU6IHRydWV9LCBvcHRpb25zKTtcblxuICAgIC8vIElmIHdlJ3JlIG5vdCB3YWl0aW5nIGFuZCBhdHRyaWJ1dGVzIGV4aXN0LCBzYXZlIGFjdHMgYXNcbiAgICAvLyBgc2V0KGF0dHIpLnNhdmUobnVsbCwgb3B0cylgIHdpdGggdmFsaWRhdGlvbi4gT3RoZXJ3aXNlLCBjaGVjayBpZlxuICAgIC8vIHRoZSBtb2RlbCB3aWxsIGJlIHZhbGlkIHdoZW4gdGhlIGF0dHJpYnV0ZXMsIGlmIGFueSwgYXJlIHNldC5cbiAgICBpZiAoYXR0cnMgJiYgIW9wdGlvbnMud2FpdCkge1xuICAgICAgaWYgKCF0aGlzLnNldChhdHRycywgb3B0aW9ucykpIHJldHVybiBmYWxzZTtcbiAgICB9IGVsc2Uge1xuICAgICAgaWYgKCF0aGlzLl92YWxpZGF0ZShhdHRycywgb3B0aW9ucykpIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICAvLyBTZXQgdGVtcG9yYXJ5IGF0dHJpYnV0ZXMgaWYgYHt3YWl0OiB0cnVlfWAuXG4gICAgaWYgKGF0dHJzICYmIG9wdGlvbnMud2FpdCkge1xuICAgICAgdGhpcy5hdHRyaWJ1dGVzID0gXy5leHRlbmQoe30sIGF0dHJpYnV0ZXMsIGF0dHJzKTtcbiAgICB9XG5cbiAgICAvLyBBZnRlciBhIHN1Y2Nlc3NmdWwgc2VydmVyLXNpZGUgc2F2ZSwgdGhlIGNsaWVudCBpcyAob3B0aW9uYWxseSlcbiAgICAvLyB1cGRhdGVkIHdpdGggdGhlIHNlcnZlci1zaWRlIHN0YXRlLlxuICAgIGlmIChvcHRpb25zLnBhcnNlID09PSB2b2lkIDApIG9wdGlvbnMucGFyc2UgPSB0cnVlO1xuICAgIHZhciBtb2RlbCA9IHRoaXM7XG4gICAgdmFyIHN1Y2Nlc3MgPSBvcHRpb25zLnN1Y2Nlc3M7XG4gICAgb3B0aW9ucy5zdWNjZXNzID0gZnVuY3Rpb24ocmVzcCkge1xuICAgICAgLy8gRW5zdXJlIGF0dHJpYnV0ZXMgYXJlIHJlc3RvcmVkIGR1cmluZyBzeW5jaHJvbm91cyBzYXZlcy5cbiAgICAgIG1vZGVsLmF0dHJpYnV0ZXMgPSBhdHRyaWJ1dGVzO1xuICAgICAgdmFyIHNlcnZlckF0dHJzID0gbW9kZWwucGFyc2UocmVzcCwgb3B0aW9ucyk7XG4gICAgICBpZiAob3B0aW9ucy53YWl0KSBzZXJ2ZXJBdHRycyA9IF8uZXh0ZW5kKGF0dHJzIHx8IHt9LCBzZXJ2ZXJBdHRycyk7XG4gICAgICBpZiAoXy5pc09iamVjdChzZXJ2ZXJBdHRycykgJiYgIW1vZGVsLnNldChzZXJ2ZXJBdHRycywgb3B0aW9ucykpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuICAgICAgaWYgKHN1Y2Nlc3MpIHN1Y2Nlc3MobW9kZWwsIHJlc3AsIG9wdGlvbnMpO1xuICAgICAgbW9kZWwudHJpZ2dlcignc3luYycsIG1vZGVsLCByZXNwLCBvcHRpb25zKTtcbiAgICB9O1xuICAgIHdyYXBFcnJvcih0aGlzLCBvcHRpb25zKTtcblxuICAgIG1ldGhvZCA9IHRoaXMuaXNOZXcoKSA/ICdjcmVhdGUnIDogKG9wdGlvbnMucGF0Y2ggPyAncGF0Y2gnIDogJ3VwZGF0ZScpO1xuICAgIGlmIChtZXRob2QgPT09ICdwYXRjaCcgJiYgIW9wdGlvbnMuYXR0cnMpIG9wdGlvbnMuYXR0cnMgPSBhdHRycztcbiAgICB4aHIgPSB0aGlzLnN5bmMobWV0aG9kLCB0aGlzLCBvcHRpb25zKTtcblxuICAgIC8vIFJlc3RvcmUgYXR0cmlidXRlcy5cbiAgICBpZiAoYXR0cnMgJiYgb3B0aW9ucy53YWl0KSB0aGlzLmF0dHJpYnV0ZXMgPSBhdHRyaWJ1dGVzO1xuXG4gICAgcmV0dXJuIHhocjtcbiAgfSxcblxuICAgIC8vIERlc3Ryb3kgdGhpcyBtb2RlbCBvbiB0aGUgc2VydmVyIGlmIGl0IHdhcyBhbHJlYWR5IHBlcnNpc3RlZC5cbiAgICAvLyBPcHRpbWlzdGljYWxseSByZW1vdmVzIHRoZSBtb2RlbCBmcm9tIGl0cyBjb2xsZWN0aW9uLCBpZiBpdCBoYXMgb25lLlxuICAgIC8vIElmIGB3YWl0OiB0cnVlYCBpcyBwYXNzZWQsIHdhaXRzIGZvciB0aGUgc2VydmVyIHRvIHJlc3BvbmQgYmVmb3JlIHJlbW92YWwuXG4gIGRlc3Ryb3k6IGZ1bmN0aW9uKG9wdGlvbnMpIHtcbiAgICBvcHRpb25zID0gb3B0aW9ucyA/IF8uY2xvbmUob3B0aW9ucykgOiB7fTtcbiAgICB2YXIgbW9kZWwgPSB0aGlzO1xuICAgIHZhciBzdWNjZXNzID0gb3B0aW9ucy5zdWNjZXNzO1xuXG4gICAgdmFyIGRlc3Ryb3kgPSBmdW5jdGlvbigpIHtcbiAgICAgIG1vZGVsLnN0b3BMaXN0ZW5pbmcoKTtcbiAgICAgIG1vZGVsLnRyaWdnZXIoJ2Rlc3Ryb3knLCBtb2RlbCwgbW9kZWwuY29sbGVjdGlvbiwgb3B0aW9ucyk7XG4gICAgfTtcblxuICAgIG9wdGlvbnMuc3VjY2VzcyA9IGZ1bmN0aW9uKHJlc3ApIHtcbiAgICAgIGlmIChvcHRpb25zLndhaXQgfHwgbW9kZWwuaXNOZXcoKSkgZGVzdHJveSgpO1xuICAgICAgaWYgKHN1Y2Nlc3MpIHN1Y2Nlc3MobW9kZWwsIHJlc3AsIG9wdGlvbnMpO1xuICAgICAgaWYgKCFtb2RlbC5pc05ldygpKSBtb2RlbC50cmlnZ2VyKCdzeW5jJywgbW9kZWwsIHJlc3AsIG9wdGlvbnMpO1xuICAgIH07XG5cbiAgICBpZiAodGhpcy5pc05ldygpKSB7XG4gICAgICBvcHRpb25zLnN1Y2Nlc3MoKTtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgd3JhcEVycm9yKHRoaXMsIG9wdGlvbnMpO1xuXG4gICAgdmFyIHhociA9IHRoaXMuc3luYygnZGVsZXRlJywgdGhpcywgb3B0aW9ucyk7XG4gICAgaWYgKCFvcHRpb25zLndhaXQpIGRlc3Ryb3koKTtcbiAgICByZXR1cm4geGhyO1xuICB9LFxuXG4gICAgLy8gRGVmYXVsdCBVUkwgZm9yIHRoZSBtb2RlbCdzIHJlcHJlc2VudGF0aW9uIG9uIHRoZSBzZXJ2ZXIgLS0gaWYgeW91J3JlXG4gICAgLy8gdXNpbmcgQmFja2JvbmUncyByZXN0ZnVsIG1ldGhvZHMsIG92ZXJyaWRlIHRoaXMgdG8gY2hhbmdlIHRoZSBlbmRwb2ludFxuICAgIC8vIHRoYXQgd2lsbCBiZSBjYWxsZWQuXG4gIHVybDogZnVuY3Rpb24oKSB7XG4gICAgdmFyIGJhc2UgPVxuICAgICAgXy5yZXN1bHQodGhpcywgJ3VybFJvb3QnKSB8fFxuICAgICAgXy5yZXN1bHQodGhpcy5jb2xsZWN0aW9uLCAndXJsJykgfHxcbiAgICAgIHVybEVycm9yKCk7XG4gICAgaWYgKHRoaXMuaXNOZXcoKSkgcmV0dXJuIGJhc2U7XG4gICAgcmV0dXJuIGJhc2UucmVwbGFjZSgvKFteXFwvXSkkLywgJyQxLycpICsgZW5jb2RlVVJJQ29tcG9uZW50KHRoaXMuaWQpO1xuICB9LFxuXG4gICAgLy8gKipwYXJzZSoqIGNvbnZlcnRzIGEgcmVzcG9uc2UgaW50byB0aGUgaGFzaCBvZiBhdHRyaWJ1dGVzIHRvIGJlIGBzZXRgIG9uXG4gICAgLy8gdGhlIG1vZGVsLiBUaGUgZGVmYXVsdCBpbXBsZW1lbnRhdGlvbiBpcyBqdXN0IHRvIHBhc3MgdGhlIHJlc3BvbnNlIGFsb25nLlxuICBwYXJzZTogZnVuY3Rpb24ocmVzcCwgb3B0aW9ucykge1xuICAgIHJldHVybiByZXNwO1xuICB9LFxuXG4gICAgLy8gQ3JlYXRlIGEgbmV3IG1vZGVsIHdpdGggaWRlbnRpY2FsIGF0dHJpYnV0ZXMgdG8gdGhpcyBvbmUuXG4gIGNsb25lOiBmdW5jdGlvbigpIHtcbiAgICByZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IodGhpcy5hdHRyaWJ1dGVzKTtcbiAgfSxcblxuICAgIC8vIEEgbW9kZWwgaXMgbmV3IGlmIGl0IGhhcyBuZXZlciBiZWVuIHNhdmVkIHRvIHRoZSBzZXJ2ZXIsIGFuZCBsYWNrcyBhbiBpZC5cbiAgaXNOZXc6IGZ1bmN0aW9uKCkge1xuICAgIHJldHVybiAhdGhpcy5oYXModGhpcy5pZEF0dHJpYnV0ZSk7XG4gIH0sXG5cbiAgICAvLyBDaGVjayBpZiB0aGUgbW9kZWwgaXMgY3VycmVudGx5IGluIGEgdmFsaWQgc3RhdGUuXG4gIGlzVmFsaWQ6IGZ1bmN0aW9uKG9wdGlvbnMpIHtcbiAgICByZXR1cm4gdGhpcy5fdmFsaWRhdGUoe30sIF8uZXh0ZW5kKG9wdGlvbnMgfHwge30sIHsgdmFsaWRhdGU6IHRydWUgfSkpO1xuICB9LFxuXG4gICAgLy8gUnVuIHZhbGlkYXRpb24gYWdhaW5zdCB0aGUgbmV4dCBjb21wbGV0ZSBzZXQgb2YgbW9kZWwgYXR0cmlidXRlcyxcbiAgICAvLyByZXR1cm5pbmcgYHRydWVgIGlmIGFsbCBpcyB3ZWxsLiBPdGhlcndpc2UsIGZpcmUgYW4gYFwiaW52YWxpZFwiYCBldmVudC5cbiAgX3ZhbGlkYXRlOiBmdW5jdGlvbihhdHRycywgb3B0aW9ucykge1xuICAgIGlmICghb3B0aW9ucy52YWxpZGF0ZSB8fCAhdGhpcy52YWxpZGF0ZSkgcmV0dXJuIHRydWU7XG4gICAgYXR0cnMgPSBfLmV4dGVuZCh7fSwgdGhpcy5hdHRyaWJ1dGVzLCBhdHRycyk7XG4gICAgdmFyIGVycm9yID0gdGhpcy52YWxpZGF0aW9uRXJyb3IgPSB0aGlzLnZhbGlkYXRlKGF0dHJzLCBvcHRpb25zKSB8fCBudWxsO1xuICAgIGlmICghZXJyb3IpIHJldHVybiB0cnVlO1xuICAgIHRoaXMudHJpZ2dlcignaW52YWxpZCcsIHRoaXMsIGVycm9yLCBfLmV4dGVuZChvcHRpb25zLCB7dmFsaWRhdGlvbkVycm9yOiBlcnJvcn0pKTtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxufSk7XG5cbi8vIFVuZGVyc2NvcmUgbWV0aG9kcyB0aGF0IHdlIHdhbnQgdG8gaW1wbGVtZW50IG9uIHRoZSBNb2RlbC5cbnZhciBtb2RlbE1ldGhvZHMgPSBbJ2tleXMnLCAndmFsdWVzJywgJ3BhaXJzJywgJ2ludmVydCcsICdwaWNrJywgJ29taXQnLCAnY2hhaW4nLCAnaXNFbXB0eSddO1xuXG4vLyBNaXggaW4gZWFjaCBVbmRlcnNjb3JlIG1ldGhvZCBhcyBhIHByb3h5IHRvIGBNb2RlbCNhdHRyaWJ1dGVzYC5cbl8uZWFjaChtb2RlbE1ldGhvZHMsIGZ1bmN0aW9uKG1ldGhvZCkge1xuICBpZiAoIV9bbWV0aG9kXSkgcmV0dXJuO1xuICBNb2RlbC5wcm90b3R5cGVbbWV0aG9kXSA9IGZ1bmN0aW9uKCkge1xuICAgIHZhciBhcmdzID0gc2xpY2UuY2FsbChhcmd1bWVudHMpO1xuICAgIGFyZ3MudW5zaGlmdCh0aGlzLmF0dHJpYnV0ZXMpO1xuICAgIHJldHVybiBfW21ldGhvZF0uYXBwbHkoXywgYXJncyk7XG4gIH07XG59KTtcblxuLy8gc2V0dXAgaW5oZXJpdGFuY2Vcbk1vZGVsLmV4dGVuZCA9IGV4dGVuZDtcbm1vZHVsZS5leHBvcnRzID0gTW9kZWw7XG4iLCIvKipcbiAqIFN0YW5kYWxvbmUgZXh0cmFjdGlvbiBvZiBCYWNrYm9uZS5FdmVudHMsIG5vIGV4dGVybmFsIGRlcGVuZGVuY3kgcmVxdWlyZWQuXG4gKiBEZWdyYWRlcyBuaWNlbHkgd2hlbiBCYWNrb25lL3VuZGVyc2NvcmUgYXJlIGFscmVhZHkgYXZhaWxhYmxlIGluIHRoZSBjdXJyZW50XG4gKiBnbG9iYWwgY29udGV4dC5cbiAqXG4gKiBOb3RlIHRoYXQgZG9jcyBzdWdnZXN0IHRvIHVzZSB1bmRlcnNjb3JlJ3MgYF8uZXh0ZW5kKClgIG1ldGhvZCB0byBhZGQgRXZlbnRzXG4gKiBzdXBwb3J0IHRvIHNvbWUgZ2l2ZW4gb2JqZWN0LiBBIGBtaXhpbigpYCBtZXRob2QgaGFzIGJlZW4gYWRkZWQgdG8gdGhlIEV2ZW50c1xuICogcHJvdG90eXBlIHRvIGF2b2lkIHVzaW5nIHVuZGVyc2NvcmUgZm9yIHRoYXQgc29sZSBwdXJwb3NlOlxuICpcbiAqICAgICB2YXIgbXlFdmVudEVtaXR0ZXIgPSBCYWNrYm9uZUV2ZW50cy5taXhpbih7fSk7XG4gKlxuICogT3IgZm9yIGEgZnVuY3Rpb24gY29uc3RydWN0b3I6XG4gKlxuICogICAgIGZ1bmN0aW9uIE15Q29uc3RydWN0b3IoKXt9XG4gKiAgICAgTXlDb25zdHJ1Y3Rvci5wcm90b3R5cGUuZm9vID0gZnVuY3Rpb24oKXt9XG4gKiAgICAgQmFja2JvbmVFdmVudHMubWl4aW4oTXlDb25zdHJ1Y3Rvci5wcm90b3R5cGUpO1xuICpcbiAqIChjKSAyMDA5LTIwMTMgSmVyZW15IEFzaGtlbmFzLCBEb2N1bWVudENsb3VkIEluYy5cbiAqIChjKSAyMDEzIE5pY29sYXMgUGVycmlhdWx0XG4gKi9cbi8qIGdsb2JhbCBleHBvcnRzOnRydWUsIGRlZmluZSwgbW9kdWxlICovXG4oZnVuY3Rpb24oKSB7XG4gIHZhciByb290ID0gdGhpcyxcbiAgICAgIGJyZWFrZXIgPSB7fSxcbiAgICAgIG5hdGl2ZUZvckVhY2ggPSBBcnJheS5wcm90b3R5cGUuZm9yRWFjaCxcbiAgICAgIGhhc093blByb3BlcnR5ID0gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eSxcbiAgICAgIHNsaWNlID0gQXJyYXkucHJvdG90eXBlLnNsaWNlLFxuICAgICAgaWRDb3VudGVyID0gMDtcblxuICAvLyBSZXR1cm5zIGEgcGFydGlhbCBpbXBsZW1lbnRhdGlvbiBtYXRjaGluZyB0aGUgbWluaW1hbCBBUEkgc3Vic2V0IHJlcXVpcmVkXG4gIC8vIGJ5IEJhY2tib25lLkV2ZW50c1xuICBmdW5jdGlvbiBtaW5pc2NvcmUoKSB7XG4gICAgcmV0dXJuIHtcbiAgICAgIGtleXM6IE9iamVjdC5rZXlzIHx8IGZ1bmN0aW9uIChvYmopIHtcbiAgICAgICAgaWYgKHR5cGVvZiBvYmogIT09IFwib2JqZWN0XCIgJiYgdHlwZW9mIG9iaiAhPT0gXCJmdW5jdGlvblwiIHx8IG9iaiA9PT0gbnVsbCkge1xuICAgICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXCJrZXlzKCkgY2FsbGVkIG9uIGEgbm9uLW9iamVjdFwiKTtcbiAgICAgICAgfVxuICAgICAgICB2YXIga2V5LCBrZXlzID0gW107XG4gICAgICAgIGZvciAoa2V5IGluIG9iaikge1xuICAgICAgICAgIGlmIChvYmouaGFzT3duUHJvcGVydHkoa2V5KSkge1xuICAgICAgICAgICAga2V5c1trZXlzLmxlbmd0aF0gPSBrZXk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiBrZXlzO1xuICAgICAgfSxcblxuICAgICAgdW5pcXVlSWQ6IGZ1bmN0aW9uKHByZWZpeCkge1xuICAgICAgICB2YXIgaWQgPSArK2lkQ291bnRlciArICcnO1xuICAgICAgICByZXR1cm4gcHJlZml4ID8gcHJlZml4ICsgaWQgOiBpZDtcbiAgICAgIH0sXG5cbiAgICAgIGhhczogZnVuY3Rpb24ob2JqLCBrZXkpIHtcbiAgICAgICAgcmV0dXJuIGhhc093blByb3BlcnR5LmNhbGwob2JqLCBrZXkpO1xuICAgICAgfSxcblxuICAgICAgZWFjaDogZnVuY3Rpb24ob2JqLCBpdGVyYXRvciwgY29udGV4dCkge1xuICAgICAgICBpZiAob2JqID09IG51bGwpIHJldHVybjtcbiAgICAgICAgaWYgKG5hdGl2ZUZvckVhY2ggJiYgb2JqLmZvckVhY2ggPT09IG5hdGl2ZUZvckVhY2gpIHtcbiAgICAgICAgICBvYmouZm9yRWFjaChpdGVyYXRvciwgY29udGV4dCk7XG4gICAgICAgIH0gZWxzZSBpZiAob2JqLmxlbmd0aCA9PT0gK29iai5sZW5ndGgpIHtcbiAgICAgICAgICBmb3IgKHZhciBpID0gMCwgbCA9IG9iai5sZW5ndGg7IGkgPCBsOyBpKyspIHtcbiAgICAgICAgICAgIGlmIChpdGVyYXRvci5jYWxsKGNvbnRleHQsIG9ialtpXSwgaSwgb2JqKSA9PT0gYnJlYWtlcikgcmV0dXJuO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBmb3IgKHZhciBrZXkgaW4gb2JqKSB7XG4gICAgICAgICAgICBpZiAodGhpcy5oYXMob2JqLCBrZXkpKSB7XG4gICAgICAgICAgICAgIGlmIChpdGVyYXRvci5jYWxsKGNvbnRleHQsIG9ialtrZXldLCBrZXksIG9iaikgPT09IGJyZWFrZXIpIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH0sXG5cbiAgICAgIG9uY2U6IGZ1bmN0aW9uKGZ1bmMpIHtcbiAgICAgICAgdmFyIHJhbiA9IGZhbHNlLCBtZW1vO1xuICAgICAgICByZXR1cm4gZnVuY3Rpb24oKSB7XG4gICAgICAgICAgaWYgKHJhbikgcmV0dXJuIG1lbW87XG4gICAgICAgICAgcmFuID0gdHJ1ZTtcbiAgICAgICAgICBtZW1vID0gZnVuYy5hcHBseSh0aGlzLCBhcmd1bWVudHMpO1xuICAgICAgICAgIGZ1bmMgPSBudWxsO1xuICAgICAgICAgIHJldHVybiBtZW1vO1xuICAgICAgICB9O1xuICAgICAgfVxuICAgIH07XG4gIH1cblxuICB2YXIgXyA9IG1pbmlzY29yZSgpLCBFdmVudHM7XG5cbiAgLy8gQmFja2JvbmUuRXZlbnRzXG4gIC8vIC0tLS0tLS0tLS0tLS0tLVxuXG4gIC8vIEEgbW9kdWxlIHRoYXQgY2FuIGJlIG1peGVkIGluIHRvICphbnkgb2JqZWN0KiBpbiBvcmRlciB0byBwcm92aWRlIGl0IHdpdGhcbiAgLy8gY3VzdG9tIGV2ZW50cy4gWW91IG1heSBiaW5kIHdpdGggYG9uYCBvciByZW1vdmUgd2l0aCBgb2ZmYCBjYWxsYmFja1xuICAvLyBmdW5jdGlvbnMgdG8gYW4gZXZlbnQ7IGB0cmlnZ2VyYC1pbmcgYW4gZXZlbnQgZmlyZXMgYWxsIGNhbGxiYWNrcyBpblxuICAvLyBzdWNjZXNzaW9uLlxuICAvL1xuICAvLyAgICAgdmFyIG9iamVjdCA9IHt9O1xuICAvLyAgICAgXy5leHRlbmQob2JqZWN0LCBCYWNrYm9uZS5FdmVudHMpO1xuICAvLyAgICAgb2JqZWN0Lm9uKCdleHBhbmQnLCBmdW5jdGlvbigpeyBhbGVydCgnZXhwYW5kZWQnKTsgfSk7XG4gIC8vICAgICBvYmplY3QudHJpZ2dlcignZXhwYW5kJyk7XG4gIC8vXG4gIEV2ZW50cyA9IHtcblxuICAgIC8vIEJpbmQgYW4gZXZlbnQgdG8gYSBgY2FsbGJhY2tgIGZ1bmN0aW9uLiBQYXNzaW5nIGBcImFsbFwiYCB3aWxsIGJpbmRcbiAgICAvLyB0aGUgY2FsbGJhY2sgdG8gYWxsIGV2ZW50cyBmaXJlZC5cbiAgICBvbjogZnVuY3Rpb24obmFtZSwgY2FsbGJhY2ssIGNvbnRleHQpIHtcbiAgICAgIGlmICghZXZlbnRzQXBpKHRoaXMsICdvbicsIG5hbWUsIFtjYWxsYmFjaywgY29udGV4dF0pIHx8ICFjYWxsYmFjaykgcmV0dXJuIHRoaXM7XG4gICAgICB0aGlzLl9ldmVudHMgfHwgKHRoaXMuX2V2ZW50cyA9IHt9KTtcbiAgICAgIHZhciBldmVudHMgPSB0aGlzLl9ldmVudHNbbmFtZV0gfHwgKHRoaXMuX2V2ZW50c1tuYW1lXSA9IFtdKTtcbiAgICAgIGV2ZW50cy5wdXNoKHtjYWxsYmFjazogY2FsbGJhY2ssIGNvbnRleHQ6IGNvbnRleHQsIGN0eDogY29udGV4dCB8fCB0aGlzfSk7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9LFxuXG4gICAgLy8gQmluZCBhbiBldmVudCB0byBvbmx5IGJlIHRyaWdnZXJlZCBhIHNpbmdsZSB0aW1lLiBBZnRlciB0aGUgZmlyc3QgdGltZVxuICAgIC8vIHRoZSBjYWxsYmFjayBpcyBpbnZva2VkLCBpdCB3aWxsIGJlIHJlbW92ZWQuXG4gICAgb25jZTogZnVuY3Rpb24obmFtZSwgY2FsbGJhY2ssIGNvbnRleHQpIHtcbiAgICAgIGlmICghZXZlbnRzQXBpKHRoaXMsICdvbmNlJywgbmFtZSwgW2NhbGxiYWNrLCBjb250ZXh0XSkgfHwgIWNhbGxiYWNrKSByZXR1cm4gdGhpcztcbiAgICAgIHZhciBzZWxmID0gdGhpcztcbiAgICAgIHZhciBvbmNlID0gXy5vbmNlKGZ1bmN0aW9uKCkge1xuICAgICAgICBzZWxmLm9mZihuYW1lLCBvbmNlKTtcbiAgICAgICAgY2FsbGJhY2suYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgICAgIH0pO1xuICAgICAgb25jZS5fY2FsbGJhY2sgPSBjYWxsYmFjaztcbiAgICAgIHJldHVybiB0aGlzLm9uKG5hbWUsIG9uY2UsIGNvbnRleHQpO1xuICAgIH0sXG5cbiAgICAvLyBSZW1vdmUgb25lIG9yIG1hbnkgY2FsbGJhY2tzLiBJZiBgY29udGV4dGAgaXMgbnVsbCwgcmVtb3ZlcyBhbGxcbiAgICAvLyBjYWxsYmFja3Mgd2l0aCB0aGF0IGZ1bmN0aW9uLiBJZiBgY2FsbGJhY2tgIGlzIG51bGwsIHJlbW92ZXMgYWxsXG4gICAgLy8gY2FsbGJhY2tzIGZvciB0aGUgZXZlbnQuIElmIGBuYW1lYCBpcyBudWxsLCByZW1vdmVzIGFsbCBib3VuZFxuICAgIC8vIGNhbGxiYWNrcyBmb3IgYWxsIGV2ZW50cy5cbiAgICBvZmY6IGZ1bmN0aW9uKG5hbWUsIGNhbGxiYWNrLCBjb250ZXh0KSB7XG4gICAgICB2YXIgcmV0YWluLCBldiwgZXZlbnRzLCBuYW1lcywgaSwgbCwgaiwgaztcbiAgICAgIGlmICghdGhpcy5fZXZlbnRzIHx8ICFldmVudHNBcGkodGhpcywgJ29mZicsIG5hbWUsIFtjYWxsYmFjaywgY29udGV4dF0pKSByZXR1cm4gdGhpcztcbiAgICAgIGlmICghbmFtZSAmJiAhY2FsbGJhY2sgJiYgIWNvbnRleHQpIHtcbiAgICAgICAgdGhpcy5fZXZlbnRzID0ge307XG4gICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgfVxuXG4gICAgICBuYW1lcyA9IG5hbWUgPyBbbmFtZV0gOiBfLmtleXModGhpcy5fZXZlbnRzKTtcbiAgICAgIGZvciAoaSA9IDAsIGwgPSBuYW1lcy5sZW5ndGg7IGkgPCBsOyBpKyspIHtcbiAgICAgICAgbmFtZSA9IG5hbWVzW2ldO1xuICAgICAgICBpZiAoZXZlbnRzID0gdGhpcy5fZXZlbnRzW25hbWVdKSB7XG4gICAgICAgICAgdGhpcy5fZXZlbnRzW25hbWVdID0gcmV0YWluID0gW107XG4gICAgICAgICAgaWYgKGNhbGxiYWNrIHx8IGNvbnRleHQpIHtcbiAgICAgICAgICAgIGZvciAoaiA9IDAsIGsgPSBldmVudHMubGVuZ3RoOyBqIDwgazsgaisrKSB7XG4gICAgICAgICAgICAgIGV2ID0gZXZlbnRzW2pdO1xuICAgICAgICAgICAgICBpZiAoKGNhbGxiYWNrICYmIGNhbGxiYWNrICE9PSBldi5jYWxsYmFjayAmJiBjYWxsYmFjayAhPT0gZXYuY2FsbGJhY2suX2NhbGxiYWNrKSB8fFxuICAgICAgICAgICAgICAgICAgKGNvbnRleHQgJiYgY29udGV4dCAhPT0gZXYuY29udGV4dCkpIHtcbiAgICAgICAgICAgICAgICByZXRhaW4ucHVzaChldik7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgICAgaWYgKCFyZXRhaW4ubGVuZ3RoKSBkZWxldGUgdGhpcy5fZXZlbnRzW25hbWVdO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHJldHVybiB0aGlzO1xuICAgIH0sXG5cbiAgICAvLyBUcmlnZ2VyIG9uZSBvciBtYW55IGV2ZW50cywgZmlyaW5nIGFsbCBib3VuZCBjYWxsYmFja3MuIENhbGxiYWNrcyBhcmVcbiAgICAvLyBwYXNzZWQgdGhlIHNhbWUgYXJndW1lbnRzIGFzIGB0cmlnZ2VyYCBpcywgYXBhcnQgZnJvbSB0aGUgZXZlbnQgbmFtZVxuICAgIC8vICh1bmxlc3MgeW91J3JlIGxpc3RlbmluZyBvbiBgXCJhbGxcImAsIHdoaWNoIHdpbGwgY2F1c2UgeW91ciBjYWxsYmFjayB0b1xuICAgIC8vIHJlY2VpdmUgdGhlIHRydWUgbmFtZSBvZiB0aGUgZXZlbnQgYXMgdGhlIGZpcnN0IGFyZ3VtZW50KS5cbiAgICB0cmlnZ2VyOiBmdW5jdGlvbihuYW1lKSB7XG4gICAgICBpZiAoIXRoaXMuX2V2ZW50cykgcmV0dXJuIHRoaXM7XG4gICAgICB2YXIgYXJncyA9IHNsaWNlLmNhbGwoYXJndW1lbnRzLCAxKTtcbiAgICAgIGlmICghZXZlbnRzQXBpKHRoaXMsICd0cmlnZ2VyJywgbmFtZSwgYXJncykpIHJldHVybiB0aGlzO1xuICAgICAgdmFyIGV2ZW50cyA9IHRoaXMuX2V2ZW50c1tuYW1lXTtcbiAgICAgIHZhciBhbGxFdmVudHMgPSB0aGlzLl9ldmVudHMuYWxsO1xuICAgICAgaWYgKGV2ZW50cykgdHJpZ2dlckV2ZW50cyhldmVudHMsIGFyZ3MpO1xuICAgICAgaWYgKGFsbEV2ZW50cykgdHJpZ2dlckV2ZW50cyhhbGxFdmVudHMsIGFyZ3VtZW50cyk7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9LFxuXG4gICAgLy8gVGVsbCB0aGlzIG9iamVjdCB0byBzdG9wIGxpc3RlbmluZyB0byBlaXRoZXIgc3BlY2lmaWMgZXZlbnRzIC4uLiBvclxuICAgIC8vIHRvIGV2ZXJ5IG9iamVjdCBpdCdzIGN1cnJlbnRseSBsaXN0ZW5pbmcgdG8uXG4gICAgc3RvcExpc3RlbmluZzogZnVuY3Rpb24ob2JqLCBuYW1lLCBjYWxsYmFjaykge1xuICAgICAgdmFyIGxpc3RlbmVycyA9IHRoaXMuX2xpc3RlbmVycztcbiAgICAgIGlmICghbGlzdGVuZXJzKSByZXR1cm4gdGhpcztcbiAgICAgIHZhciBkZWxldGVMaXN0ZW5lciA9ICFuYW1lICYmICFjYWxsYmFjaztcbiAgICAgIGlmICh0eXBlb2YgbmFtZSA9PT0gJ29iamVjdCcpIGNhbGxiYWNrID0gdGhpcztcbiAgICAgIGlmIChvYmopIChsaXN0ZW5lcnMgPSB7fSlbb2JqLl9saXN0ZW5lcklkXSA9IG9iajtcbiAgICAgIGZvciAodmFyIGlkIGluIGxpc3RlbmVycykge1xuICAgICAgICBsaXN0ZW5lcnNbaWRdLm9mZihuYW1lLCBjYWxsYmFjaywgdGhpcyk7XG4gICAgICAgIGlmIChkZWxldGVMaXN0ZW5lcikgZGVsZXRlIHRoaXMuX2xpc3RlbmVyc1tpZF07XG4gICAgICB9XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9XG5cbiAgfTtcblxuICAvLyBSZWd1bGFyIGV4cHJlc3Npb24gdXNlZCB0byBzcGxpdCBldmVudCBzdHJpbmdzLlxuICB2YXIgZXZlbnRTcGxpdHRlciA9IC9cXHMrLztcblxuICAvLyBJbXBsZW1lbnQgZmFuY3kgZmVhdHVyZXMgb2YgdGhlIEV2ZW50cyBBUEkgc3VjaCBhcyBtdWx0aXBsZSBldmVudFxuICAvLyBuYW1lcyBgXCJjaGFuZ2UgYmx1clwiYCBhbmQgalF1ZXJ5LXN0eWxlIGV2ZW50IG1hcHMgYHtjaGFuZ2U6IGFjdGlvbn1gXG4gIC8vIGluIHRlcm1zIG9mIHRoZSBleGlzdGluZyBBUEkuXG4gIHZhciBldmVudHNBcGkgPSBmdW5jdGlvbihvYmosIGFjdGlvbiwgbmFtZSwgcmVzdCkge1xuICAgIGlmICghbmFtZSkgcmV0dXJuIHRydWU7XG5cbiAgICAvLyBIYW5kbGUgZXZlbnQgbWFwcy5cbiAgICBpZiAodHlwZW9mIG5hbWUgPT09ICdvYmplY3QnKSB7XG4gICAgICBmb3IgKHZhciBrZXkgaW4gbmFtZSkge1xuICAgICAgICBvYmpbYWN0aW9uXS5hcHBseShvYmosIFtrZXksIG5hbWVba2V5XV0uY29uY2F0KHJlc3QpKTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICAvLyBIYW5kbGUgc3BhY2Ugc2VwYXJhdGVkIGV2ZW50IG5hbWVzLlxuICAgIGlmIChldmVudFNwbGl0dGVyLnRlc3QobmFtZSkpIHtcbiAgICAgIHZhciBuYW1lcyA9IG5hbWUuc3BsaXQoZXZlbnRTcGxpdHRlcik7XG4gICAgICBmb3IgKHZhciBpID0gMCwgbCA9IG5hbWVzLmxlbmd0aDsgaSA8IGw7IGkrKykge1xuICAgICAgICBvYmpbYWN0aW9uXS5hcHBseShvYmosIFtuYW1lc1tpXV0uY29uY2F0KHJlc3QpKTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICByZXR1cm4gdHJ1ZTtcbiAgfTtcblxuICAvLyBBIGRpZmZpY3VsdC10by1iZWxpZXZlLCBidXQgb3B0aW1pemVkIGludGVybmFsIGRpc3BhdGNoIGZ1bmN0aW9uIGZvclxuICAvLyB0cmlnZ2VyaW5nIGV2ZW50cy4gVHJpZXMgdG8ga2VlcCB0aGUgdXN1YWwgY2FzZXMgc3BlZWR5IChtb3N0IGludGVybmFsXG4gIC8vIEJhY2tib25lIGV2ZW50cyBoYXZlIDMgYXJndW1lbnRzKS5cbiAgdmFyIHRyaWdnZXJFdmVudHMgPSBmdW5jdGlvbihldmVudHMsIGFyZ3MpIHtcbiAgICB2YXIgZXYsIGkgPSAtMSwgbCA9IGV2ZW50cy5sZW5ndGgsIGExID0gYXJnc1swXSwgYTIgPSBhcmdzWzFdLCBhMyA9IGFyZ3NbMl07XG4gICAgc3dpdGNoIChhcmdzLmxlbmd0aCkge1xuICAgICAgY2FzZSAwOiB3aGlsZSAoKytpIDwgbCkgKGV2ID0gZXZlbnRzW2ldKS5jYWxsYmFjay5jYWxsKGV2LmN0eCk7IHJldHVybjtcbiAgICAgIGNhc2UgMTogd2hpbGUgKCsraSA8IGwpIChldiA9IGV2ZW50c1tpXSkuY2FsbGJhY2suY2FsbChldi5jdHgsIGExKTsgcmV0dXJuO1xuICAgICAgY2FzZSAyOiB3aGlsZSAoKytpIDwgbCkgKGV2ID0gZXZlbnRzW2ldKS5jYWxsYmFjay5jYWxsKGV2LmN0eCwgYTEsIGEyKTsgcmV0dXJuO1xuICAgICAgY2FzZSAzOiB3aGlsZSAoKytpIDwgbCkgKGV2ID0gZXZlbnRzW2ldKS5jYWxsYmFjay5jYWxsKGV2LmN0eCwgYTEsIGEyLCBhMyk7IHJldHVybjtcbiAgICAgIGRlZmF1bHQ6IHdoaWxlICgrK2kgPCBsKSAoZXYgPSBldmVudHNbaV0pLmNhbGxiYWNrLmFwcGx5KGV2LmN0eCwgYXJncyk7XG4gICAgfVxuICB9O1xuXG4gIHZhciBsaXN0ZW5NZXRob2RzID0ge2xpc3RlblRvOiAnb24nLCBsaXN0ZW5Ub09uY2U6ICdvbmNlJ307XG5cbiAgLy8gSW52ZXJzaW9uLW9mLWNvbnRyb2wgdmVyc2lvbnMgb2YgYG9uYCBhbmQgYG9uY2VgLiBUZWxsICp0aGlzKiBvYmplY3QgdG9cbiAgLy8gbGlzdGVuIHRvIGFuIGV2ZW50IGluIGFub3RoZXIgb2JqZWN0IC4uLiBrZWVwaW5nIHRyYWNrIG9mIHdoYXQgaXQnc1xuICAvLyBsaXN0ZW5pbmcgdG8uXG4gIF8uZWFjaChsaXN0ZW5NZXRob2RzLCBmdW5jdGlvbihpbXBsZW1lbnRhdGlvbiwgbWV0aG9kKSB7XG4gICAgRXZlbnRzW21ldGhvZF0gPSBmdW5jdGlvbihvYmosIG5hbWUsIGNhbGxiYWNrKSB7XG4gICAgICB2YXIgbGlzdGVuZXJzID0gdGhpcy5fbGlzdGVuZXJzIHx8ICh0aGlzLl9saXN0ZW5lcnMgPSB7fSk7XG4gICAgICB2YXIgaWQgPSBvYmouX2xpc3RlbmVySWQgfHwgKG9iai5fbGlzdGVuZXJJZCA9IF8udW5pcXVlSWQoJ2wnKSk7XG4gICAgICBsaXN0ZW5lcnNbaWRdID0gb2JqO1xuICAgICAgaWYgKHR5cGVvZiBuYW1lID09PSAnb2JqZWN0JykgY2FsbGJhY2sgPSB0aGlzO1xuICAgICAgb2JqW2ltcGxlbWVudGF0aW9uXShuYW1lLCBjYWxsYmFjaywgdGhpcyk7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9O1xuICB9KTtcblxuICAvLyBBbGlhc2VzIGZvciBiYWNrd2FyZHMgY29tcGF0aWJpbGl0eS5cbiAgRXZlbnRzLmJpbmQgICA9IEV2ZW50cy5vbjtcbiAgRXZlbnRzLnVuYmluZCA9IEV2ZW50cy5vZmY7XG5cbiAgLy8gTWl4aW4gdXRpbGl0eVxuICBFdmVudHMubWl4aW4gPSBmdW5jdGlvbihwcm90bykge1xuICAgIHZhciBleHBvcnRzID0gWydvbicsICdvbmNlJywgJ29mZicsICd0cmlnZ2VyJywgJ3N0b3BMaXN0ZW5pbmcnLCAnbGlzdGVuVG8nLFxuICAgICAgICAgICAgICAgICAgICdsaXN0ZW5Ub09uY2UnLCAnYmluZCcsICd1bmJpbmQnXTtcbiAgICBfLmVhY2goZXhwb3J0cywgZnVuY3Rpb24obmFtZSkge1xuICAgICAgcHJvdG9bbmFtZV0gPSB0aGlzW25hbWVdO1xuICAgIH0sIHRoaXMpO1xuICAgIHJldHVybiBwcm90bztcbiAgfTtcblxuICAvLyBFeHBvcnQgRXZlbnRzIGFzIEJhY2tib25lRXZlbnRzIGRlcGVuZGluZyBvbiBjdXJyZW50IGNvbnRleHRcbiAgaWYgKHR5cGVvZiBkZWZpbmUgPT09IFwiZnVuY3Rpb25cIikge1xuICAgIGRlZmluZShmdW5jdGlvbigpIHtcbiAgICAgIHJldHVybiBFdmVudHM7XG4gICAgfSk7XG4gIH0gZWxzZSBpZiAodHlwZW9mIGV4cG9ydHMgIT09ICd1bmRlZmluZWQnKSB7XG4gICAgaWYgKHR5cGVvZiBtb2R1bGUgIT09ICd1bmRlZmluZWQnICYmIG1vZHVsZS5leHBvcnRzKSB7XG4gICAgICBleHBvcnRzID0gbW9kdWxlLmV4cG9ydHMgPSBFdmVudHM7XG4gICAgfVxuICAgIGV4cG9ydHMuQmFja2JvbmVFdmVudHMgPSBFdmVudHM7XG4gIH0gZWxzZSB7XG4gICAgcm9vdC5CYWNrYm9uZUV2ZW50cyA9IEV2ZW50cztcbiAgfVxufSkodGhpcyk7XG4iLCJtb2R1bGUuZXhwb3J0cyA9IHJlcXVpcmUoJy4vYmFja2JvbmUtZXZlbnRzLXN0YW5kYWxvbmUnKTtcbiIsIihmdW5jdGlvbiAoZGVmaW5pdGlvbikge1xuICBpZiAodHlwZW9mIGV4cG9ydHMgPT09IFwib2JqZWN0XCIpIHtcbiAgICBtb2R1bGUuZXhwb3J0cyA9IGRlZmluaXRpb24oKTtcbiAgfVxuICBlbHNlIGlmICh0eXBlb2YgZGVmaW5lID09PSAnZnVuY3Rpb24nICYmIGRlZmluZS5hbWQpIHtcbiAgICBkZWZpbmUoZGVmaW5pdGlvbik7XG4gIH1cbiAgZWxzZSB7XG4gICAgd2luZG93LkJhY2tib25lRXh0ZW5kID0gZGVmaW5pdGlvbigpO1xuICB9XG59KShmdW5jdGlvbiAoKSB7XG4gIFwidXNlIHN0cmljdFwiO1xuICBcbiAgLy8gbWluaS11bmRlcnNjb3JlXG4gIHZhciBfID0ge1xuICAgIGhhczogZnVuY3Rpb24gKG9iaiwga2V5KSB7XG4gICAgICByZXR1cm4gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKG9iaiwga2V5KTtcbiAgICB9LFxuICBcbiAgICBleHRlbmQ6IGZ1bmN0aW9uKG9iaikge1xuICAgICAgZm9yICh2YXIgaT0xOyBpPGFyZ3VtZW50cy5sZW5ndGg7ICsraSkge1xuICAgICAgICB2YXIgc291cmNlID0gYXJndW1lbnRzW2ldO1xuICAgICAgICBpZiAoc291cmNlKSB7XG4gICAgICAgICAgZm9yICh2YXIgcHJvcCBpbiBzb3VyY2UpIHtcbiAgICAgICAgICAgIG9ialtwcm9wXSA9IHNvdXJjZVtwcm9wXTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHJldHVybiBvYmo7XG4gICAgfVxuICB9O1xuXG4gIC8vLyBGb2xsb3dpbmcgY29kZSBpcyBwYXN0ZWQgZnJvbSBCYWNrYm9uZS5qcyAvLy9cblxuICAvLyBIZWxwZXIgZnVuY3Rpb24gdG8gY29ycmVjdGx5IHNldCB1cCB0aGUgcHJvdG90eXBlIGNoYWluLCBmb3Igc3ViY2xhc3Nlcy5cbiAgLy8gU2ltaWxhciB0byBgZ29vZy5pbmhlcml0c2AsIGJ1dCB1c2VzIGEgaGFzaCBvZiBwcm90b3R5cGUgcHJvcGVydGllcyBhbmRcbiAgLy8gY2xhc3MgcHJvcGVydGllcyB0byBiZSBleHRlbmRlZC5cbiAgdmFyIGV4dGVuZCA9IGZ1bmN0aW9uKHByb3RvUHJvcHMsIHN0YXRpY1Byb3BzKSB7XG4gICAgdmFyIHBhcmVudCA9IHRoaXM7XG4gICAgdmFyIGNoaWxkO1xuXG4gICAgLy8gVGhlIGNvbnN0cnVjdG9yIGZ1bmN0aW9uIGZvciB0aGUgbmV3IHN1YmNsYXNzIGlzIGVpdGhlciBkZWZpbmVkIGJ5IHlvdVxuICAgIC8vICh0aGUgXCJjb25zdHJ1Y3RvclwiIHByb3BlcnR5IGluIHlvdXIgYGV4dGVuZGAgZGVmaW5pdGlvbiksIG9yIGRlZmF1bHRlZFxuICAgIC8vIGJ5IHVzIHRvIHNpbXBseSBjYWxsIHRoZSBwYXJlbnQncyBjb25zdHJ1Y3Rvci5cbiAgICBpZiAocHJvdG9Qcm9wcyAmJiBfLmhhcyhwcm90b1Byb3BzLCAnY29uc3RydWN0b3InKSkge1xuICAgICAgY2hpbGQgPSBwcm90b1Byb3BzLmNvbnN0cnVjdG9yO1xuICAgIH0gZWxzZSB7XG4gICAgICBjaGlsZCA9IGZ1bmN0aW9uKCl7IHJldHVybiBwYXJlbnQuYXBwbHkodGhpcywgYXJndW1lbnRzKTsgfTtcbiAgICB9XG5cbiAgICAvLyBBZGQgc3RhdGljIHByb3BlcnRpZXMgdG8gdGhlIGNvbnN0cnVjdG9yIGZ1bmN0aW9uLCBpZiBzdXBwbGllZC5cbiAgICBfLmV4dGVuZChjaGlsZCwgcGFyZW50LCBzdGF0aWNQcm9wcyk7XG5cbiAgICAvLyBTZXQgdGhlIHByb3RvdHlwZSBjaGFpbiB0byBpbmhlcml0IGZyb20gYHBhcmVudGAsIHdpdGhvdXQgY2FsbGluZ1xuICAgIC8vIGBwYXJlbnRgJ3MgY29uc3RydWN0b3IgZnVuY3Rpb24uXG4gICAgdmFyIFN1cnJvZ2F0ZSA9IGZ1bmN0aW9uKCl7IHRoaXMuY29uc3RydWN0b3IgPSBjaGlsZDsgfTtcbiAgICBTdXJyb2dhdGUucHJvdG90eXBlID0gcGFyZW50LnByb3RvdHlwZTtcbiAgICBjaGlsZC5wcm90b3R5cGUgPSBuZXcgU3Vycm9nYXRlKCk7XG5cbiAgICAvLyBBZGQgcHJvdG90eXBlIHByb3BlcnRpZXMgKGluc3RhbmNlIHByb3BlcnRpZXMpIHRvIHRoZSBzdWJjbGFzcyxcbiAgICAvLyBpZiBzdXBwbGllZC5cbiAgICBpZiAocHJvdG9Qcm9wcykgXy5leHRlbmQoY2hpbGQucHJvdG90eXBlLCBwcm90b1Byb3BzKTtcblxuICAgIC8vIFNldCBhIGNvbnZlbmllbmNlIHByb3BlcnR5IGluIGNhc2UgdGhlIHBhcmVudCdzIHByb3RvdHlwZSBpcyBuZWVkZWRcbiAgICAvLyBsYXRlci5cbiAgICBjaGlsZC5fX3N1cGVyX18gPSBwYXJlbnQucHJvdG90eXBlO1xuXG4gICAgcmV0dXJuIGNoaWxkO1xuICB9O1xuXG4gIC8vIEV4cG9zZSB0aGUgZXh0ZW5kIGZ1bmN0aW9uXG4gIHJldHVybiBleHRlbmQ7XG59KTtcbiIsIi8vIHRoaXMgaXMgdGhlIGV4dHJhY3RlZCB2aWV3IG1vZGVsIGZyb20gYmFja2JvbmVcbi8vIG5vdGUgdGhhdCB3ZSBpbmplY3QgamJvbmUgYXMganF1ZXJ5IHJlcGxhY21lbnRcbi8vIChhbmQgdW5kZXJzY29yZSBkaXJlY3RseSlcbi8vXG4vLyBWaWV3cyBhcmUgYWxtb3N0IG1vcmUgY29udmVudGlvbiB0aGFuIHRoZXkgYXJlIGFjdHVhbCBjb2RlLlxuLy8gIE1WQyBwYXR0ZXJuXG4vLyBCYWNrYm9uZS5WaWV3XG4vLyAtLS0tLS0tLS0tLS0tXG5cbnZhciBfID0gcmVxdWlyZShcInVuZGVyc2NvcmVcIik7XG52YXIgRXZlbnRzID0gcmVxdWlyZShcImJhY2tib25lLWV2ZW50cy1zdGFuZGFsb25lXCIpO1xudmFyIGV4dGVuZCA9IHJlcXVpcmUoXCJiYWNrYm9uZS1leHRlbmQtc3RhbmRhbG9uZVwiKTtcbnZhciAkID0gcmVxdWlyZSgnamJvbmUnKTtcblxuLy8gQmFja2JvbmUgVmlld3MgYXJlIGFsbW9zdCBtb3JlIGNvbnZlbnRpb24gdGhhbiB0aGV5IGFyZSBhY3R1YWwgY29kZS4gQSBWaWV3XG4vLyBpcyBzaW1wbHkgYSBKYXZhU2NyaXB0IG9iamVjdCB0aGF0IHJlcHJlc2VudHMgYSBsb2dpY2FsIGNodW5rIG9mIFVJIGluIHRoZVxuLy8gRE9NLiBUaGlzIG1pZ2h0IGJlIGEgc2luZ2xlIGl0ZW0sIGFuIGVudGlyZSBsaXN0LCBhIHNpZGViYXIgb3IgcGFuZWwsIG9yXG4vLyBldmVuIHRoZSBzdXJyb3VuZGluZyBmcmFtZSB3aGljaCB3cmFwcyB5b3VyIHdob2xlIGFwcC4gRGVmaW5pbmcgYSBjaHVuayBvZlxuLy8gVUkgYXMgYSAqKlZpZXcqKiBhbGxvd3MgeW91IHRvIGRlZmluZSB5b3VyIERPTSBldmVudHMgZGVjbGFyYXRpdmVseSwgd2l0aG91dFxuLy8gaGF2aW5nIHRvIHdvcnJ5IGFib3V0IHJlbmRlciBvcmRlciAuLi4gYW5kIG1ha2VzIGl0IGVhc3kgZm9yIHRoZSB2aWV3IHRvXG4vLyByZWFjdCB0byBzcGVjaWZpYyBjaGFuZ2VzIGluIHRoZSBzdGF0ZSBvZiB5b3VyIG1vZGVscy5cblxuLy8gQ3JlYXRpbmcgYSBCYWNrYm9uZS5WaWV3IGNyZWF0ZXMgaXRzIGluaXRpYWwgZWxlbWVudCBvdXRzaWRlIG9mIHRoZSBET00sXG4vLyBpZiBhbiBleGlzdGluZyBlbGVtZW50IGlzIG5vdCBwcm92aWRlZC4uLlxudmFyIFZpZXcgPSAgZnVuY3Rpb24ob3B0aW9ucykge1xuICB0aGlzLmNpZCA9IF8udW5pcXVlSWQoJ3ZpZXcnKTtcbiAgb3B0aW9ucyB8fCAob3B0aW9ucyA9IHt9KTtcbiAgXy5leHRlbmQodGhpcywgXy5waWNrKG9wdGlvbnMsIHZpZXdPcHRpb25zKSk7XG4gIHRoaXMuX2Vuc3VyZUVsZW1lbnQoKTtcbiAgdGhpcy5pbml0aWFsaXplLmFwcGx5KHRoaXMsIGFyZ3VtZW50cyk7XG59O1xuXG4vLyBDYWNoZWQgcmVnZXggdG8gc3BsaXQga2V5cyBmb3IgYGRlbGVnYXRlYC5cbnZhciBkZWxlZ2F0ZUV2ZW50U3BsaXR0ZXIgPSAvXihcXFMrKVxccyooLiopJC87XG5cbi8vIExpc3Qgb2YgdmlldyBvcHRpb25zIHRvIGJlIG1lcmdlZCBhcyBwcm9wZXJ0aWVzLlxudmFyIHZpZXdPcHRpb25zID0gWydtb2RlbCcsICdjb2xsZWN0aW9uJywgJ2VsJywgJ2lkJywgJ2F0dHJpYnV0ZXMnLCAnY2xhc3NOYW1lJywgJ3RhZ05hbWUnLCAnZXZlbnRzJ107XG5cbi8vIFNldCB1cCBhbGwgaW5oZXJpdGFibGUgKipCYWNrYm9uZS5WaWV3KiogcHJvcGVydGllcyBhbmQgbWV0aG9kcy5cbl8uZXh0ZW5kKFZpZXcucHJvdG90eXBlLCBFdmVudHMsIHtcblxuICAvLyBUaGUgZGVmYXVsdCBgdGFnTmFtZWAgb2YgYSBWaWV3J3MgZWxlbWVudCBpcyBgXCJkaXZcImAuXG4gIHRhZ05hbWU6ICdkaXYnLFxuXG4gIC8vIGpRdWVyeSBkZWxlZ2F0ZSBmb3IgZWxlbWVudCBsb29rdXAsIHNjb3BlZCB0byBET00gZWxlbWVudHMgd2l0aGluIHRoZVxuICAvLyBjdXJyZW50IHZpZXcuIFRoaXMgc2hvdWxkIGJlIHByZWZlcnJlZCB0byBnbG9iYWwgbG9va3VwcyB3aGVyZSBwb3NzaWJsZS5cbiAgJDogZnVuY3Rpb24oc2VsZWN0b3IpIHtcbiAgICByZXR1cm4gdGhpcy4kZWwuZmluZChzZWxlY3Rvcik7XG4gIH0sXG5cbiAgICAvLyBJbml0aWFsaXplIGlzIGFuIGVtcHR5IGZ1bmN0aW9uIGJ5IGRlZmF1bHQuIE92ZXJyaWRlIGl0IHdpdGggeW91ciBvd25cbiAgICAvLyBpbml0aWFsaXphdGlvbiBsb2dpYy5cbiAgaW5pdGlhbGl6ZTogZnVuY3Rpb24oKXt9LFxuXG4gICAgLy8gKipyZW5kZXIqKiBpcyB0aGUgY29yZSBmdW5jdGlvbiB0aGF0IHlvdXIgdmlldyBzaG91bGQgb3ZlcnJpZGUsIGluIG9yZGVyXG4gICAgLy8gdG8gcG9wdWxhdGUgaXRzIGVsZW1lbnQgKGB0aGlzLmVsYCksIHdpdGggdGhlIGFwcHJvcHJpYXRlIEhUTUwuIFRoZVxuICAgIC8vIGNvbnZlbnRpb24gaXMgZm9yICoqcmVuZGVyKiogdG8gYWx3YXlzIHJldHVybiBgdGhpc2AuXG4gIHJlbmRlcjogZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH0sXG5cbiAgICAvLyBSZW1vdmUgdGhpcyB2aWV3IGJ5IHRha2luZyB0aGUgZWxlbWVudCBvdXQgb2YgdGhlIERPTSwgYW5kIHJlbW92aW5nIGFueVxuICAgIC8vIGFwcGxpY2FibGUgQmFja2JvbmUuRXZlbnRzIGxpc3RlbmVycy5cbiAgcmVtb3ZlOiBmdW5jdGlvbigpIHtcbiAgICB0aGlzLl9yZW1vdmVFbGVtZW50KCk7XG4gICAgdGhpcy5zdG9wTGlzdGVuaW5nKCk7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH0sXG5cbiAgICAvLyBSZW1vdmUgdGhpcyB2aWV3J3MgZWxlbWVudCBmcm9tIHRoZSBkb2N1bWVudCBhbmQgYWxsIGV2ZW50IGxpc3RlbmVyc1xuICAgIC8vIGF0dGFjaGVkIHRvIGl0LiBFeHBvc2VkIGZvciBzdWJjbGFzc2VzIHVzaW5nIGFuIGFsdGVybmF0aXZlIERPTVxuICAgIC8vIG1hbmlwdWxhdGlvbiBBUEkuXG4gIF9yZW1vdmVFbGVtZW50OiBmdW5jdGlvbigpIHtcbiAgICB0aGlzLiRlbC5yZW1vdmUoKTtcbiAgfSxcblxuICAgIC8vIENoYW5nZSB0aGUgdmlldydzIGVsZW1lbnQgKGB0aGlzLmVsYCBwcm9wZXJ0eSkgYW5kIHJlLWRlbGVnYXRlIHRoZVxuICAgIC8vIHZpZXcncyBldmVudHMgb24gdGhlIG5ldyBlbGVtZW50LlxuICBzZXRFbGVtZW50OiBmdW5jdGlvbihlbGVtZW50KSB7XG4gICAgdGhpcy51bmRlbGVnYXRlRXZlbnRzKCk7XG4gICAgdGhpcy5fc2V0RWxlbWVudChlbGVtZW50KTtcbiAgICB0aGlzLmRlbGVnYXRlRXZlbnRzKCk7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH0sXG5cbiAgICAvLyBDcmVhdGVzIHRoZSBgdGhpcy5lbGAgYW5kIGB0aGlzLiRlbGAgcmVmZXJlbmNlcyBmb3IgdGhpcyB2aWV3IHVzaW5nIHRoZVxuICAgIC8vIGdpdmVuIGBlbGAuIGBlbGAgY2FuIGJlIGEgQ1NTIHNlbGVjdG9yIG9yIGFuIEhUTUwgc3RyaW5nLCBhIGpRdWVyeVxuICAgIC8vIGNvbnRleHQgb3IgYW4gZWxlbWVudC4gU3ViY2xhc3NlcyBjYW4gb3ZlcnJpZGUgdGhpcyB0byB1dGlsaXplIGFuXG4gICAgLy8gYWx0ZXJuYXRpdmUgRE9NIG1hbmlwdWxhdGlvbiBBUEkgYW5kIGFyZSBvbmx5IHJlcXVpcmVkIHRvIHNldCB0aGVcbiAgICAvLyBgdGhpcy5lbGAgcHJvcGVydHkuXG4gIF9zZXRFbGVtZW50OiBmdW5jdGlvbihlbCkge1xuICAgIHRoaXMuJGVsID0gZWwgaW5zdGFuY2VvZiAkID8gZWwgOiAkKGVsKTtcbiAgICB0aGlzLmVsID0gdGhpcy4kZWxbMF07XG4gIH0sXG5cbiAgICAvLyBTZXQgY2FsbGJhY2tzLCB3aGVyZSBgdGhpcy5ldmVudHNgIGlzIGEgaGFzaCBvZlxuICAgIC8vXG4gICAgLy8gKntcImV2ZW50IHNlbGVjdG9yXCI6IFwiY2FsbGJhY2tcIn0qXG4gICAgLy9cbiAgICAvLyAgICAge1xuICAgIC8vICAgICAgICdtb3VzZWRvd24gLnRpdGxlJzogICdlZGl0JyxcbiAgICAvLyAgICAgICAnY2xpY2sgLmJ1dHRvbic6ICAgICAnc2F2ZScsXG4gICAgLy8gICAgICAgJ2NsaWNrIC5vcGVuJzogICAgICAgZnVuY3Rpb24oZSkgeyAuLi4gfVxuICAgIC8vICAgICB9XG4gICAgLy9cbiAgICAvLyBwYWlycy4gQ2FsbGJhY2tzIHdpbGwgYmUgYm91bmQgdG8gdGhlIHZpZXcsIHdpdGggYHRoaXNgIHNldCBwcm9wZXJseS5cbiAgICAvLyBVc2VzIGV2ZW50IGRlbGVnYXRpb24gZm9yIGVmZmljaWVuY3kuXG4gICAgLy8gT21pdHRpbmcgdGhlIHNlbGVjdG9yIGJpbmRzIHRoZSBldmVudCB0byBgdGhpcy5lbGAuXG4gIGRlbGVnYXRlRXZlbnRzOiBmdW5jdGlvbihldmVudHMpIHtcbiAgICBpZiAoIShldmVudHMgfHwgKGV2ZW50cyA9IF8ucmVzdWx0KHRoaXMsICdldmVudHMnKSkpKSByZXR1cm4gdGhpcztcbiAgICB0aGlzLnVuZGVsZWdhdGVFdmVudHMoKTtcbiAgICBmb3IgKHZhciBrZXkgaW4gZXZlbnRzKSB7XG4gICAgICB2YXIgbWV0aG9kID0gZXZlbnRzW2tleV07XG4gICAgICBpZiAoIV8uaXNGdW5jdGlvbihtZXRob2QpKSBtZXRob2QgPSB0aGlzW2V2ZW50c1trZXldXTtcbiAgICAgIGlmICghbWV0aG9kKSBjb250aW51ZTtcbiAgICAgIHZhciBtYXRjaCA9IGtleS5tYXRjaChkZWxlZ2F0ZUV2ZW50U3BsaXR0ZXIpO1xuICAgICAgdGhpcy5kZWxlZ2F0ZShtYXRjaFsxXSwgbWF0Y2hbMl0sIF8uYmluZChtZXRob2QsIHRoaXMpKTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXM7XG4gIH0sXG5cbiAgICAvLyBBZGQgYSBzaW5nbGUgZXZlbnQgbGlzdGVuZXIgdG8gdGhlIHZpZXcncyBlbGVtZW50IChvciBhIGNoaWxkIGVsZW1lbnRcbiAgICAvLyB1c2luZyBgc2VsZWN0b3JgKS4gVGhpcyBvbmx5IHdvcmtzIGZvciBkZWxlZ2F0ZS1hYmxlIGV2ZW50czogbm90IGBmb2N1c2AsXG4gICAgLy8gYGJsdXJgLCBhbmQgbm90IGBjaGFuZ2VgLCBgc3VibWl0YCwgYW5kIGByZXNldGAgaW4gSW50ZXJuZXQgRXhwbG9yZXIuXG4gIGRlbGVnYXRlOiBmdW5jdGlvbihldmVudE5hbWUsIHNlbGVjdG9yLCBsaXN0ZW5lcikge1xuICAgIHRoaXMuJGVsLm9uKGV2ZW50TmFtZSArICcuZGVsZWdhdGVFdmVudHMnICsgdGhpcy5jaWQsIHNlbGVjdG9yLCBsaXN0ZW5lcik7XG4gIH0sXG5cbiAgICAvLyBDbGVhcnMgYWxsIGNhbGxiYWNrcyBwcmV2aW91c2x5IGJvdW5kIHRvIHRoZSB2aWV3IGJ5IGBkZWxlZ2F0ZUV2ZW50c2AuXG4gICAgLy8gWW91IHVzdWFsbHkgZG9uJ3QgbmVlZCB0byB1c2UgdGhpcywgYnV0IG1heSB3aXNoIHRvIGlmIHlvdSBoYXZlIG11bHRpcGxlXG4gICAgLy8gQmFja2JvbmUgdmlld3MgYXR0YWNoZWQgdG8gdGhlIHNhbWUgRE9NIGVsZW1lbnQuXG4gIHVuZGVsZWdhdGVFdmVudHM6IGZ1bmN0aW9uKCkge1xuICAgIGlmICh0aGlzLiRlbCkgdGhpcy4kZWwub2ZmKCcuZGVsZWdhdGVFdmVudHMnICsgdGhpcy5jaWQpO1xuICAgIHJldHVybiB0aGlzO1xuICB9LFxuXG4gICAgLy8gQSBmaW5lci1ncmFpbmVkIGB1bmRlbGVnYXRlRXZlbnRzYCBmb3IgcmVtb3ZpbmcgYSBzaW5nbGUgZGVsZWdhdGVkIGV2ZW50LlxuICAgIC8vIGBzZWxlY3RvcmAgYW5kIGBsaXN0ZW5lcmAgYXJlIGJvdGggb3B0aW9uYWwuXG4gIHVuZGVsZWdhdGU6IGZ1bmN0aW9uKGV2ZW50TmFtZSwgc2VsZWN0b3IsIGxpc3RlbmVyKSB7XG4gICAgdGhpcy4kZWwub2ZmKGV2ZW50TmFtZSArICcuZGVsZWdhdGVFdmVudHMnICsgdGhpcy5jaWQsIHNlbGVjdG9yLCBsaXN0ZW5lcik7XG4gIH0sXG5cbiAgICAvLyBQcm9kdWNlcyBhIERPTSBlbGVtZW50IHRvIGJlIGFzc2lnbmVkIHRvIHlvdXIgdmlldy4gRXhwb3NlZCBmb3JcbiAgICAvLyBzdWJjbGFzc2VzIHVzaW5nIGFuIGFsdGVybmF0aXZlIERPTSBtYW5pcHVsYXRpb24gQVBJLlxuICBfY3JlYXRlRWxlbWVudDogZnVuY3Rpb24odGFnTmFtZSkge1xuICAgIHJldHVybiBkb2N1bWVudC5jcmVhdGVFbGVtZW50KHRhZ05hbWUpO1xuICB9LFxuXG4gICAgLy8gRW5zdXJlIHRoYXQgdGhlIFZpZXcgaGFzIGEgRE9NIGVsZW1lbnQgdG8gcmVuZGVyIGludG8uXG4gICAgLy8gSWYgYHRoaXMuZWxgIGlzIGEgc3RyaW5nLCBwYXNzIGl0IHRocm91Z2ggYCQoKWAsIHRha2UgdGhlIGZpcnN0XG4gICAgLy8gbWF0Y2hpbmcgZWxlbWVudCwgYW5kIHJlLWFzc2lnbiBpdCB0byBgZWxgLiBPdGhlcndpc2UsIGNyZWF0ZVxuICAgIC8vIGFuIGVsZW1lbnQgZnJvbSB0aGUgYGlkYCwgYGNsYXNzTmFtZWAgYW5kIGB0YWdOYW1lYCBwcm9wZXJ0aWVzLlxuICBfZW5zdXJlRWxlbWVudDogZnVuY3Rpb24oKSB7XG4gICAgaWYgKCF0aGlzLmVsKSB7XG4gICAgICB2YXIgYXR0cnMgPSBfLmV4dGVuZCh7fSwgXy5yZXN1bHQodGhpcywgJ2F0dHJpYnV0ZXMnKSk7XG4gICAgICBpZiAodGhpcy5pZCkgYXR0cnMuaWQgPSBfLnJlc3VsdCh0aGlzLCAnaWQnKTtcbiAgICAgIGlmICh0aGlzLmNsYXNzTmFtZSkgYXR0cnNbJ2NsYXNzJ10gPSBfLnJlc3VsdCh0aGlzLCAnY2xhc3NOYW1lJyk7XG4gICAgICB0aGlzLnNldEVsZW1lbnQodGhpcy5fY3JlYXRlRWxlbWVudChfLnJlc3VsdCh0aGlzLCAndGFnTmFtZScpKSk7XG4gICAgICB0aGlzLl9zZXRBdHRyaWJ1dGVzKGF0dHJzKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5zZXRFbGVtZW50KF8ucmVzdWx0KHRoaXMsICdlbCcpKTtcbiAgICB9XG4gIH0sXG5cbiAgICAvLyBTZXQgYXR0cmlidXRlcyBmcm9tIGEgaGFzaCBvbiB0aGlzIHZpZXcncyBlbGVtZW50LiAgRXhwb3NlZCBmb3JcbiAgICAvLyBzdWJjbGFzc2VzIHVzaW5nIGFuIGFsdGVybmF0aXZlIERPTSBtYW5pcHVsYXRpb24gQVBJLlxuICBfc2V0QXR0cmlidXRlczogZnVuY3Rpb24oYXR0cmlidXRlcykge1xuICAgIHRoaXMuJGVsLmF0dHIoYXR0cmlidXRlcyk7XG4gIH1cblxufSk7XG5cbi8vIHNldHVwIGluaGVyaXRhbmNlXG5WaWV3LmV4dGVuZCA9IGV4dGVuZDtcbm1vZHVsZS5leHBvcnRzID0gVmlldztcbiIsInZhciBldmVudHMgPSByZXF1aXJlKFwiYmFja2JvbmUtZXZlbnRzLXN0YW5kYWxvbmVcIik7XG5cbmV2ZW50cy5vbkFsbCA9IGZ1bmN0aW9uKGNhbGxiYWNrLGNvbnRleHQpe1xuICB0aGlzLm9uKFwiYWxsXCIsIGNhbGxiYWNrLGNvbnRleHQpO1xuICByZXR1cm4gdGhpcztcbn07XG5cbi8vIE1peGluIHV0aWxpdHlcbmV2ZW50cy5vbGRNaXhpbiA9IGV2ZW50cy5taXhpbjtcbmV2ZW50cy5taXhpbiA9IGZ1bmN0aW9uKHByb3RvKSB7XG4gIGV2ZW50cy5vbGRNaXhpbihwcm90byk7XG4gIC8vIGFkZCBjdXN0b20gb25BbGxcbiAgdmFyIGV4cG9ydHMgPSBbJ29uQWxsJ107XG4gIGZvcih2YXIgaT0wOyBpIDwgZXhwb3J0cy5sZW5ndGg7aSsrKXtcbiAgICB2YXIgbmFtZSA9IGV4cG9ydHNbaV07XG4gICAgcHJvdG9bbmFtZV0gPSB0aGlzW25hbWVdO1xuICB9XG4gIHJldHVybiBwcm90bztcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gZXZlbnRzO1xuIiwiLy8gR2VuZXJhdGVkIGJ5IENvZmZlZVNjcmlwdCAxLjguMFxudmFyIEdlbmVyaWNSZWFkZXIsIHhocjtcblxueGhyID0gcmVxdWlyZSgnbmV0cycpO1xuXG5tb2R1bGUuZXhwb3J0cyA9IEdlbmVyaWNSZWFkZXIgPSAoZnVuY3Rpb24oKSB7XG4gIGZ1bmN0aW9uIEdlbmVyaWNSZWFkZXIoKSB7fVxuXG4gIEdlbmVyaWNSZWFkZXIucmVhZCA9IGZ1bmN0aW9uKHVybCwgY2FsbGJhY2spIHtcbiAgICB2YXIgb25yZXQ7XG4gICAgb25yZXQgPSAoZnVuY3Rpb24oX3RoaXMpIHtcbiAgICAgIHJldHVybiBmdW5jdGlvbihlcnIsIHJlc3BvbnNlLCB0ZXh0KSB7XG4gICAgICAgIHJldHVybiBfdGhpcy5fb25SZXRyaWV2YWwodGV4dCwgY2FsbGJhY2spO1xuICAgICAgfTtcbiAgICB9KSh0aGlzKTtcbiAgICByZXR1cm4geGhyKHVybCwgb25yZXQpO1xuICB9O1xuXG4gIEdlbmVyaWNSZWFkZXIuX29uUmV0cmlldmFsID0gZnVuY3Rpb24odGV4dCwgY2FsbGJhY2spIHtcbiAgICB2YXIgclRleHQ7XG4gICAgclRleHQgPSB0aGlzLnBhcnNlKHRleHQpO1xuICAgIHJldHVybiBjYWxsYmFjayhyVGV4dCk7XG4gIH07XG5cbiAgcmV0dXJuIEdlbmVyaWNSZWFkZXI7XG5cbn0pKCk7XG4iLCIvLyBHZW5lcmF0ZWQgYnkgQ29mZmVlU2NyaXB0IDEuOC4wXG52YXIgU2VxO1xuXG5tb2R1bGUuZXhwb3J0cyA9IFNlcSA9IChmdW5jdGlvbigpIHtcbiAgZnVuY3Rpb24gU2VxKHNlcSwgbmFtZSwgaWQpIHtcbiAgICB2YXIgbWV0YTtcbiAgICB0aGlzLnNlcSA9IHNlcTtcbiAgICB0aGlzLm5hbWUgPSBuYW1lO1xuICAgIHRoaXMuaWQgPSBpZDtcbiAgICBtZXRhID0ge307XG4gIH1cblxuICByZXR1cm4gU2VxO1xuXG59KSgpO1xuIiwiLy8gR2VuZXJhdGVkIGJ5IENvZmZlZVNjcmlwdCAxLjguMFxudmFyIHN0cmluZ3M7XG5cbnN0cmluZ3MgPSB7XG4gIGNvbnRhaW5zOiBmdW5jdGlvbih0ZXh0LCBzZWFyY2gpIHtcbiAgICByZXR1cm4gJycuaW5kZXhPZi5jYWxsKHRleHQsIHNlYXJjaCwgMCkgIT09IC0xO1xuICB9XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IHN0cmluZ3M7XG4iLCIvLyBHZW5lcmF0ZWQgYnkgQ29mZmVlU2NyaXB0IDEuOC4wXG52YXIgRmFzdGEsIEdlbmVyaWNSZWFkZXIsIFNlcSwgU3RyLFxuICBfX2hhc1Byb3AgPSB7fS5oYXNPd25Qcm9wZXJ0eSxcbiAgX19leHRlbmRzID0gZnVuY3Rpb24oY2hpbGQsIHBhcmVudCkgeyBmb3IgKHZhciBrZXkgaW4gcGFyZW50KSB7IGlmIChfX2hhc1Byb3AuY2FsbChwYXJlbnQsIGtleSkpIGNoaWxkW2tleV0gPSBwYXJlbnRba2V5XTsgfSBmdW5jdGlvbiBjdG9yKCkgeyB0aGlzLmNvbnN0cnVjdG9yID0gY2hpbGQ7IH0gY3Rvci5wcm90b3R5cGUgPSBwYXJlbnQucHJvdG90eXBlOyBjaGlsZC5wcm90b3R5cGUgPSBuZXcgY3RvcigpOyBjaGlsZC5fX3N1cGVyX18gPSBwYXJlbnQucHJvdG90eXBlOyByZXR1cm4gY2hpbGQ7IH07XG5cblN0ciA9IHJlcXVpcmUoXCIuL3N0cmluZ3NcIik7XG5cbkdlbmVyaWNSZWFkZXIgPSByZXF1aXJlKFwiLi9nZW5lcmljX3JlYWRlclwiKTtcblxuU2VxID0gcmVxdWlyZShcImJpb2pzLW1vZGVsXCIpLnNlcTtcblxubW9kdWxlLmV4cG9ydHMgPSBGYXN0YSA9IChmdW5jdGlvbihfc3VwZXIpIHtcbiAgX19leHRlbmRzKEZhc3RhLCBfc3VwZXIpO1xuXG4gIGZ1bmN0aW9uIEZhc3RhKCkge1xuICAgIHJldHVybiBGYXN0YS5fX3N1cGVyX18uY29uc3RydWN0b3IuYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgfVxuXG4gIEZhc3RhLnBhcnNlID0gZnVuY3Rpb24odGV4dCkge1xuICAgIHZhciBjdXJyZW50U2VxLCBkYXRhYmFzZSwgZGF0YWJhc2VJRCwgaWRlbnRpZmllcnMsIGssIGxhYmVsLCBsaW5lLCBzZXFzLCBfaSwgX2xlbjtcbiAgICBzZXFzID0gW107XG4gICAgaWYgKE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcuY2FsbCh0ZXh0KSAhPT0gJ1tvYmplY3QgQXJyYXldJykge1xuICAgICAgdGV4dCA9IHRleHQuc3BsaXQoXCJcXG5cIik7XG4gICAgfVxuICAgIGZvciAoX2kgPSAwLCBfbGVuID0gdGV4dC5sZW5ndGg7IF9pIDwgX2xlbjsgX2krKykge1xuICAgICAgbGluZSA9IHRleHRbX2ldO1xuICAgICAgaWYgKGxpbmVbMF0gPT09IFwiPlwiIHx8IGxpbmVbMF0gPT09IFwiO1wiKSB7XG4gICAgICAgIGxhYmVsID0gbGluZS5zbGljZSgxKTtcbiAgICAgICAgY3VycmVudFNlcSA9IG5ldyBTZXEoXCJcIiwgbGFiZWwsIHNlcXMubGVuZ3RoKTtcbiAgICAgICAgc2Vxcy5wdXNoKGN1cnJlbnRTZXEpO1xuICAgICAgICBpZiAoU3RyLmNvbnRhaW5zKFwifFwiLCBsaW5lKSkge1xuICAgICAgICAgIGlkZW50aWZpZXJzID0gbGFiZWwuc3BsaXQoXCJ8XCIpO1xuICAgICAgICAgIGsgPSAxO1xuICAgICAgICAgIHdoaWxlIChrIDwgaWRlbnRpZmllcnMubGVuZ3RoKSB7XG4gICAgICAgICAgICBkYXRhYmFzZSA9IGlkZW50aWZpZXJzW2tdO1xuICAgICAgICAgICAgZGF0YWJhc2VJRCA9IGlkZW50aWZpZXJzW2sgKyAxXTtcbiAgICAgICAgICAgIGN1cnJlbnRTZXEubWV0YVtkYXRhYmFzZV0gPSBkYXRhYmFzZUlEO1xuICAgICAgICAgICAgayArPSAyO1xuICAgICAgICAgIH1cbiAgICAgICAgICBjdXJyZW50U2VxLm5hbWUgPSBpZGVudGlmaWVyc1tpZGVudGlmaWVycy5sZW5ndGggLSAxXTtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgY3VycmVudFNlcS5zZXEgKz0gbGluZTtcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHNlcXM7XG4gIH07XG5cbiAgcmV0dXJuIEZhc3RhO1xuXG59KShHZW5lcmljUmVhZGVyKTtcbiIsIi8vIEdlbmVyYXRlZCBieSBDb2ZmZWVTY3JpcHQgMS44LjBcbnZhciBVdGlscztcblxuVXRpbHMgPSB7fTtcblxuVXRpbHMuc3BsaXROQ2hhcnMgPSBmdW5jdGlvbih0eHQsIG51bSkge1xuICB2YXIgaSwgcmVzdWx0LCBfaSwgX3JlZjtcbiAgcmVzdWx0ID0gW107XG4gIGZvciAoaSA9IF9pID0gMCwgX3JlZiA9IHR4dC5sZW5ndGggLSAxOyBudW0gPiAwID8gX2kgPD0gX3JlZiA6IF9pID49IF9yZWY7IGkgPSBfaSArPSBudW0pIHtcbiAgICByZXN1bHQucHVzaCh0eHQuc3Vic3RyKGksIG51bSkpO1xuICB9XG4gIHJldHVybiByZXN1bHQ7XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IFV0aWxzO1xuIiwiLy8gR2VuZXJhdGVkIGJ5IENvZmZlZVNjcmlwdCAxLjguMFxudmFyIEZhc3RhRXhwb3J0ZXIsIFV0aWxzO1xuXG5VdGlscyA9IHJlcXVpcmUoXCIuL3V0aWxzXCIpO1xuXG5tb2R1bGUuZXhwb3J0cyA9IEZhc3RhRXhwb3J0ZXIgPSAoZnVuY3Rpb24oKSB7XG4gIGZ1bmN0aW9uIEZhc3RhRXhwb3J0ZXIoKSB7fVxuXG4gIEZhc3RhRXhwb3J0ZXJbXCJleHBvcnRcIl0gPSBmdW5jdGlvbihzZXFzLCBhY2Nlc3MpIHtcbiAgICB2YXIgc2VxLCB0ZXh0LCBfaSwgX2xlbjtcbiAgICB0ZXh0ID0gXCJcIjtcbiAgICBmb3IgKF9pID0gMCwgX2xlbiA9IHNlcXMubGVuZ3RoOyBfaSA8IF9sZW47IF9pKyspIHtcbiAgICAgIHNlcSA9IHNlcXNbX2ldO1xuICAgICAgaWYgKGFjY2VzcyAhPSBudWxsKSB7XG4gICAgICAgIHNlcSA9IGFjY2VzcyhzZXEpO1xuICAgICAgfVxuICAgICAgdGV4dCArPSBcIj5cIiArIHNlcS5uYW1lICsgXCJcXG5cIjtcbiAgICAgIHRleHQgKz0gKFV0aWxzLnNwbGl0TkNoYXJzKHNlcS5zZXEsIDgwKSkuam9pbihcIlxcblwiKTtcbiAgICAgIHRleHQgKz0gXCJcXG5cIjtcbiAgICB9XG4gICAgcmV0dXJuIHRleHQ7XG4gIH07XG5cbiAgcmV0dXJuIEZhc3RhRXhwb3J0ZXI7XG5cbn0pKCk7XG4iLCJtb2R1bGUuZXhwb3J0cy5zZXEgPSByZXF1aXJlKFwiLi9zZXFcIik7XG4iLCJtb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uKHNlcSwgbmFtZSwgaWQpIHtcbiAgICB0aGlzLnNlcSA9IHNlcTtcbiAgICB0aGlzLm5hbWUgPSBuYW1lO1xuICAgIHRoaXMuaWQgPSBpZDtcbiAgICB0aGlzLm1ldGEgPSB7fTtcbn07XG4iLCJtb2R1bGUuZXhwb3J0cyA9IHJlcXVpcmUoJy4vc3JjL2luZGV4LmpzJylcbiIsIm1vZHVsZS5leHBvcnRzID0ge1xuICBBOiBcIiMwMGEzNWNcIixcbiAgUjogXCIjMDBmYzAzXCIsXG4gIE46IFwiIzAwZWIxNFwiLFxuICBEOiBcIiMwMGViMTRcIixcbiAgQzogXCIjMDAwMGZmXCIsXG4gIFE6IFwiIzAwZjEwZVwiLFxuICBFOiBcIiMwMGYxMGVcIixcbiAgRzogXCIjMDA5ZDYyXCIsXG4gIEg6IFwiIzAwZDUyYVwiLFxuICBJOiBcIiMwMDU0YWJcIixcbiAgTDogXCIjMDA3Yjg0XCIsXG4gIEs6IFwiIzAwZmYwMFwiLFxuICBNOiBcIiMwMDk3NjhcIixcbiAgRjogXCIjMDA4Nzc4XCIsXG4gIFA6IFwiIzAwZTAxZlwiLFxuICBTOiBcIiMwMGQ1MmFcIixcbiAgVDogXCIjMDBkYjI0XCIsXG4gIFc6IFwiIzAwYTg1N1wiLFxuICBZOiBcIiMwMGU2MTlcIixcbiAgVjogXCIjMDA1ZmEwXCIsXG4gIEI6IFwiIzAwZWIxNFwiLFxuICBYOiBcIiMwMGI2NDlcIixcbiAgWjogXCIjMDBmMTBlXCJcbn07XG4iLCJtb2R1bGUuZXhwb3J0cyA9IHtcbiAgQTogXCIjQkJCQkJCXCIsXG4gIEI6IFwiZ3JleVwiLFxuICBDOiBcInllbGxvd1wiLFxuICBEOiBcInJlZFwiLFxuICBFOiBcInJlZFwiLFxuICBGOiBcIm1hZ2VudGFcIixcbiAgRzogXCJicm93blwiLFxuICBIOiBcIiMwMEZGRkZcIixcbiAgSTogXCIjQkJCQkJCXCIsXG4gIEo6IFwiI2ZmZlwiLFxuICBLOiBcIiMwMEZGRkZcIixcbiAgTDogXCIjQkJCQkJCXCIsXG4gIE06IFwiI0JCQkJCQlwiLFxuICBOOiBcImdyZWVuXCIsXG4gIE86IFwiI2ZmZlwiLFxuICBQOiBcImJyb3duXCIsXG4gIFE6IFwiZ3JlZW5cIixcbiAgUjogXCIjMDBGRkZGXCIsXG4gIFM6IFwiZ3JlZW5cIixcbiAgVDogXCJncmVlblwiLFxuICBVOiBcIiNmZmZcIixcbiAgVjogXCIjQkJCQkJCXCIsXG4gIFc6IFwibWFnZW50YVwiLFxuICBYOiBcImdyZXlcIixcbiAgWTogXCJtYWdlbnRhXCIsXG4gIFo6IFwiZ3JleVwiLFxuICBHYXA6IFwiZ3JleVwiXG59O1xuIiwibW9kdWxlLmV4cG9ydHMgPSB7XG4gIEE6IFwib3JhbmdlXCIsXG4gIEI6IFwiI2ZmZlwiLFxuICBDOiBcImdyZWVuXCIsXG4gIEQ6IFwicmVkXCIsXG4gIEU6IFwicmVkXCIsXG4gIEY6IFwiYmx1ZVwiLFxuICBHOiBcIm9yYW5nZVwiLFxuICBIOiBcInJlZFwiLFxuICBJOiBcImdyZWVuXCIsXG4gIEo6IFwiI2ZmZlwiLFxuICBLOiBcInJlZFwiLFxuICBMOiBcImdyZWVuXCIsXG4gIE06IFwiZ3JlZW5cIixcbiAgTjogXCIjZmZmXCIsXG4gIE86IFwiI2ZmZlwiLFxuICBQOiBcIm9yYW5nZVwiLFxuICBROiBcIiNmZmZcIixcbiAgUjogXCJyZWRcIixcbiAgUzogXCJvcmFuZ2VcIixcbiAgVDogXCJvcmFuZ2VcIixcbiAgVTogXCIjZmZmXCIsXG4gIFY6IFwiZ3JlZW5cIixcbiAgVzogXCJibHVlXCIsXG4gIFg6IFwiI2ZmZlwiLFxuICBZOiBcImJsdWVcIixcbiAgWjogXCIjZmZmXCIsXG4gIEdhcDogXCIjZmZmXCJcbn07XG4iLCJtb2R1bGUuZXhwb3J0cyA9IHtcbiAgQTogXCIjODBhMGYwXCIsXG4gIFI6IFwiI2YwMTUwNVwiLFxuICBOOiBcIiMwMGZmMDBcIixcbiAgRDogXCIjYzA0OGMwXCIsXG4gIEM6IFwiI2YwODA4MFwiLFxuICBROiBcIiMwMGZmMDBcIixcbiAgRTogXCIjYzA0OGMwXCIsXG4gIEc6IFwiI2YwOTA0OFwiLFxuICBIOiBcIiMxNWE0YTRcIixcbiAgSTogXCIjODBhMGYwXCIsXG4gIEw6IFwiIzgwYTBmMFwiLFxuICBLOiBcIiNmMDE1MDVcIixcbiAgTTogXCIjODBhMGYwXCIsXG4gIEY6IFwiIzgwYTBmMFwiLFxuICBQOiBcIiNmZmZmMDBcIixcbiAgUzogXCIjMDBmZjAwXCIsXG4gIFQ6IFwiIzAwZmYwMFwiLFxuICBXOiBcIiM4MGEwZjBcIixcbiAgWTogXCIjMTVhNGE0XCIsXG4gIFY6IFwiIzgwYTBmMFwiLFxuICBCOiBcIiNmZmZcIixcbiAgWDogXCIjZmZmXCIsXG4gIFo6IFwiI2ZmZlwiXG59O1xuIiwibW9kdWxlLmV4cG9ydHMgPSB7XG4gIEE6IFwiI2U3MThlN1wiLFxuICBSOiBcIiM2ZjkwNmZcIixcbiAgTjogXCIjMWJlNDFiXCIsXG4gIEQ6IFwiIzc3ODg3N1wiLFxuICBDOiBcIiMyM2RjMjNcIixcbiAgUTogXCIjOTI2ZDkyXCIsXG4gIEU6IFwiI2ZmMDBmZlwiLFxuICBHOiBcIiMwMGZmMDBcIixcbiAgSDogXCIjNzU4YTc1XCIsXG4gIEk6IFwiIzhhNzU4YVwiLFxuICBMOiBcIiNhZTUxYWVcIixcbiAgSzogXCIjYTA1ZmEwXCIsXG4gIE06IFwiI2VmMTBlZlwiLFxuICBGOiBcIiM5ODY3OThcIixcbiAgUDogXCIjMDBmZjAwXCIsXG4gIFM6IFwiIzM2YzkzNlwiLFxuICBUOiBcIiM0N2I4NDdcIixcbiAgVzogXCIjOGE3NThhXCIsXG4gIFk6IFwiIzIxZGUyMVwiLFxuICBWOiBcIiM4NTdhODVcIixcbiAgQjogXCIjNDliNjQ5XCIsXG4gIFg6IFwiIzc1OGE3NVwiLFxuICBaOiBcIiNjOTM2YzlcIlxufTtcbiIsIm1vZHVsZS5leHBvcnRzID0ge1xuICBBOiBcIiNhZDAwNTJcIixcbiAgQjogXCIjMGMwMGYzXCIsXG4gIEM6IFwiI2MyMDAzZFwiLFxuICBEOiBcIiMwYzAwZjNcIixcbiAgRTogXCIjMGMwMGYzXCIsXG4gIEY6IFwiI2NiMDAzNFwiLFxuICBHOiBcIiM2YTAwOTVcIixcbiAgSDogXCIjMTUwMGVhXCIsXG4gIEk6IFwiI2ZmMDAwMFwiLFxuICBKOiBcIiNmZmZcIixcbiAgSzogXCIjMDAwMGZmXCIsXG4gIEw6IFwiI2VhMDAxNVwiLFxuICBNOiBcIiNiMDAwNGZcIixcbiAgTjogXCIjMGMwMGYzXCIsXG4gIE86IFwiI2ZmZlwiLFxuICBQOiBcIiM0NjAwYjlcIixcbiAgUTogXCIjMGMwMGYzXCIsXG4gIFI6IFwiIzAwMDBmZlwiLFxuICBTOiBcIiM1ZTAwYTFcIixcbiAgVDogXCIjNjEwMDllXCIsXG4gIFU6IFwiI2ZmZlwiLFxuICBWOiBcIiNmNjAwMDlcIixcbiAgVzogXCIjNWIwMGE0XCIsXG4gIFg6IFwiIzY4MDA5N1wiLFxuICBZOiBcIiM0ZjAwYjBcIixcbiAgWjogXCIjMGMwMGYzXCJcbn07XG4iLCJtb2R1bGUuZXhwb3J0cy5zZWxlY3RvciA9IHJlcXVpcmUoXCIuL3NlbGVjdG9yXCIpO1xuXG4vLyBiYXNpY3Ncbm1vZHVsZS5leHBvcnRzLnRheWxvciA9IHJlcXVpcmUoXCIuL3RheWxvclwiKTtcbm1vZHVsZS5leHBvcnRzLnphcHBvPSByZXF1aXJlKFwiLi96YXBwb1wiKTtcbm1vZHVsZS5leHBvcnRzLmh5ZHJvPSByZXF1aXJlKFwiLi9oeWRyb3Bob2JpY2l0eVwiKTtcblxubW9kdWxlLmV4cG9ydHMuY2x1c3RhbCA9IHJlcXVpcmUoXCIuL2NsdXN0YWxcIik7XG5tb2R1bGUuZXhwb3J0cy5jbHVzdGFsMiA9IHJlcXVpcmUoXCIuL2NsdXN0YWwyXCIpO1xuXG5tb2R1bGUuZXhwb3J0cy5jdXJpZWQgPSByZXF1aXJlKFwiLi9idXJpZWRcIik7XG5tb2R1bGUuZXhwb3J0cy5jaW5lbWEgPSByZXF1aXJlKFwiLi9jaW5lbWFcIik7XG5tb2R1bGUuZXhwb3J0cy5udWNsZW90aWRlICA9IHJlcXVpcmUoXCIuL251Y2xlb3RpZGVcIik7XG5tb2R1bGUuZXhwb3J0cy5oZWxpeCAgPSByZXF1aXJlKFwiLi9oZWxpeFwiKTtcbm1vZHVsZS5leHBvcnRzLmxlc2sgID0gcmVxdWlyZShcIi4vbGVza1wiKTtcbm1vZHVsZS5leHBvcnRzLm1hZSA9IHJlcXVpcmUoXCIuL21hZVwiKTtcbm1vZHVsZS5leHBvcnRzLnB1cmluZSA9IHJlcXVpcmUoXCIuL3B1cmluZVwiKTtcbm1vZHVsZS5leHBvcnRzLnN0cmFuZCA9IHJlcXVpcmUoXCIuL3N0cmFuZFwiKTtcbm1vZHVsZS5leHBvcnRzLnR1cm4gPSByZXF1aXJlKFwiLi90dXJuXCIpO1xuIiwibW9kdWxlLmV4cG9ydHMgPSB7XG4gIEE6IFwiIG9yYW5nZVwiLFxuICBCOiBcIiAjZmZmXCIsXG4gIEM6IFwiIGdyZWVuXCIsXG4gIEQ6IFwiIHJlZFwiLFxuICBFOiBcIiByZWRcIixcbiAgRjogXCIgZ3JlZW5cIixcbiAgRzogXCIgb3JhbmdlXCIsXG4gIEg6IFwiIG1hZ2VudGFcIixcbiAgSTogXCIgZ3JlZW5cIixcbiAgSjogXCIgI2ZmZlwiLFxuICBLOiBcIiByZWRcIixcbiAgTDogXCIgZ3JlZW5cIixcbiAgTTogXCIgZ3JlZW5cIixcbiAgTjogXCIgbWFnZW50YVwiLFxuICBPOiBcIiAjZmZmXCIsXG4gIFA6IFwiIGdyZWVuXCIsXG4gIFE6IFwiIG1hZ2VudGFcIixcbiAgUjogXCIgcmVkXCIsXG4gIFM6IFwiIG9yYW5nZVwiLFxuICBUOiBcIiBvcmFuZ2VcIixcbiAgVTogXCIgI2ZmZlwiLFxuICBWOiBcIiBncmVlblwiLFxuICBXOiBcIiBncmVlblwiLFxuICBYOiBcIiAjZmZmXCIsXG4gIFk6IFwiIGdyZWVuXCIsXG4gIFo6IFwiICNmZmZcIixcbiAgR2FwOiBcIiAjZmZmXCJcbn07XG4iLCJtb2R1bGUuZXhwb3J0cyA9IHtcbiAgQTogXCIgIzc3ZGQ4OFwiLFxuICBCOiBcIiAjZmZmXCIsXG4gIEM6IFwiICM5OWVlNjZcIixcbiAgRDogXCIgIzU1YmIzM1wiLFxuICBFOiBcIiAjNTViYjMzXCIsXG4gIEY6IFwiICM5OTk5ZmZcIixcbiAgRzogXCIgIzc3ZGQ4OFwiLFxuICBIOiBcIiAjNTU1NWZmXCIsXG4gIEk6IFwiICM2NmJiZmZcIixcbiAgSjogXCIgI2ZmZlwiLFxuICBLOiBcIiAjZmZjYzc3XCIsXG4gIEw6IFwiICM2NmJiZmZcIixcbiAgTTogXCIgIzY2YmJmZlwiLFxuICBOOiBcIiAjNTViYjMzXCIsXG4gIE86IFwiICNmZmZcIixcbiAgUDogXCIgI2VlYWFhYVwiLFxuICBROiBcIiAjNTViYjMzXCIsXG4gIFI6IFwiICNmZmNjNzdcIixcbiAgUzogXCIgI2ZmNDQ1NVwiLFxuICBUOiBcIiAjZmY0NDU1XCIsXG4gIFU6IFwiICNmZmZcIixcbiAgVjogXCIgIzY2YmJmZlwiLFxuICBXOiBcIiAjOTk5OWZmXCIsXG4gIFg6IFwiICNmZmZcIixcbiAgWTogXCIgIzk5OTlmZlwiLFxuICBaOiBcIiAjZmZmXCIsXG4gIEdhcDogXCIgI2ZmZlwiXG59O1xuIiwibW9kdWxlLmV4cG9ydHMgPSB7XG4gIEE6IFwiICM2NEY3M0ZcIixcbiAgQzogXCIgI0ZGQjM0MFwiLFxuICBHOiBcIiAjRUI0MTNDXCIsXG4gIFQ6IFwiICMzQzg4RUVcIixcbiAgVTogXCIgIzNDODhFRVwiXG59O1xuIiwibW9kdWxlLmV4cG9ydHMgPSB7XG4gIEE6IFwiICNGRjgzRkFcIixcbiAgQzogXCIgIzQwRTBEMFwiLFxuICBHOiBcIiAjRkY4M0ZBXCIsXG4gIFI6IFwiICNGRjgzRkFcIixcbiAgVDogXCIgIzQwRTBEMFwiLFxuICBVOiBcIiAjNDBFMEQwXCIsXG4gIFk6IFwiICM0MEUwRDBcIlxufTtcbiIsInZhciBCdXJpZWQgPSByZXF1aXJlKFwiLi9idXJpZWRcIik7XG52YXIgQ2luZW1hID0gcmVxdWlyZShcIi4vY2luZW1hXCIpO1xudmFyIENsdXN0YWwgPSByZXF1aXJlKFwiLi9jbHVzdGFsXCIpO1xudmFyIENsdXN0YWwyID0gcmVxdWlyZShcIi4vY2x1c3RhbDJcIik7XG52YXIgSGVsaXggPSByZXF1aXJlKFwiLi9oZWxpeFwiKTtcbnZhciBIeWRybyA9IHJlcXVpcmUoXCIuL2h5ZHJvcGhvYmljaXR5XCIpO1xudmFyIExlc2sgPSByZXF1aXJlKFwiLi9sZXNrXCIpO1xudmFyIE1hZSA9IHJlcXVpcmUoXCIuL21hZVwiKTtcbnZhciBOdWNsZW90aWRlID0gcmVxdWlyZShcIi4vbnVjbGVvdGlkZVwiKTtcbnZhciBQdXJpbmUgPSByZXF1aXJlKFwiLi9wdXJpbmVcIik7XG52YXIgU3RyYW5kID0gcmVxdWlyZShcIi4vc3RyYW5kXCIpO1xudmFyIFRheWxvciA9IHJlcXVpcmUoXCIuL3RheWxvclwiKTtcbnZhciBUdXJuID0gcmVxdWlyZShcIi4vdHVyblwiKTtcbnZhciBaYXBwbyA9IHJlcXVpcmUoXCIuL3phcHBvXCIpO1xuXG5tb2R1bGUuZXhwb3J0cyA9IENvbG9ycyA9IHtcbiAgbWFwcGluZzoge1xuICAgIGJ1cmllZDogQnVyaWVkLFxuICAgIGJ1cmllZF9pbmRleDogQnVyaWVkLFxuICAgIGNpbmVtYTogQ2luZW1hLFxuICAgIGNsdXN0YWwyOiBDbHVzdGFsMixcbiAgICBjbHVzdGFsOiBDbHVzdGFsLFxuICAgIGhlbGl4OiBIZWxpeCxcbiAgICBoZWxpeF9wcm9wZW5zaXR5OiBIZWxpeCxcbiAgICBoeWRybzogSHlkcm8sXG4gICAgbGVzazogTGVzayxcbiAgICBtYWU6IE1hZSxcbiAgICBudWNsZW90aWRlOiBOdWNsZW90aWRlLFxuICAgIHB1cmluZTogUHVyaW5lLFxuICAgIHB1cmluZV9weXJpbWlkaW5lOiBQdXJpbmUsXG4gICAgc3RyYW5kOiBTdHJhbmQsXG4gICAgc3RyYW5kX3Byb3BlbnNpdHk6IFN0cmFuZCxcbiAgICB0YXlsb3I6IFRheWxvcixcbiAgICB0dXJuOiBUdXJuLFxuICAgIHR1cm5fcHJvcGVuc2l0eTogVHVybixcbiAgICB6YXBwbzogWmFwcG8sXG4gIH0sXG4gIGdldENvbG9yOiBmdW5jdGlvbihzY2hlbWUpIHtcbiAgICB2YXIgY29sb3IgPSBDb2xvcnMubWFwcGluZ1tzY2hlbWVdO1xuICAgIGlmIChjb2xvciA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICBjb2xvciA9IHt9O1xuICAgIH1cbiAgICByZXR1cm4gY29sb3I7XG4gIH1cbn07XG4iLCJtb2R1bGUuZXhwb3J0cyA9IHtcbiAgQTogXCIjNTg1OGE3XCIsXG4gIFI6IFwiIzZiNmI5NFwiLFxuICBOOiBcIiM2NDY0OWJcIixcbiAgRDogXCIjMjEyMWRlXCIsXG4gIEM6IFwiIzlkOWQ2MlwiLFxuICBROiBcIiM4YzhjNzNcIixcbiAgRTogXCIjMDAwMGZmXCIsXG4gIEc6IFwiIzQ5NDliNlwiLFxuICBIOiBcIiM2MDYwOWZcIixcbiAgSTogXCIjZWNlYzEzXCIsXG4gIEw6IFwiI2IyYjI0ZFwiLFxuICBLOiBcIiM0NzQ3YjhcIixcbiAgTTogXCIjODI4MjdkXCIsXG4gIEY6IFwiI2MyYzIzZFwiLFxuICBQOiBcIiMyMzIzZGNcIixcbiAgUzogXCIjNDk0OWI2XCIsXG4gIFQ6IFwiIzlkOWQ2MlwiLFxuICBXOiBcIiNjMGMwM2ZcIixcbiAgWTogXCIjZDNkMzJjXCIsXG4gIFY6IFwiI2ZmZmYwMFwiLFxuICBCOiBcIiM0MzQzYmNcIixcbiAgWDogXCIjNzk3OTg2XCIsXG4gIFo6IFwiIzQ3NDdiOFwiXG59O1xuIiwibW9kdWxlLmV4cG9ydHMgPSB7XG4gIEE6IFwiI2NjZmYwMFwiLFxuICBSOiBcIiMwMDAwZmZcIixcbiAgTjogXCIjY2MwMGZmXCIsXG4gIEQ6IFwiI2ZmMDAwMFwiLFxuICBDOiBcIiNmZmZmMDBcIixcbiAgUTogXCIjZmYwMGNjXCIsXG4gIEU6IFwiI2ZmMDA2NlwiLFxuICBHOiBcIiNmZjk5MDBcIixcbiAgSDogXCIjMDA2NmZmXCIsXG4gIEk6IFwiIzY2ZmYwMFwiLFxuICBMOiBcIiMzM2ZmMDBcIixcbiAgSzogXCIjNjYwMGZmXCIsXG4gIE06IFwiIzAwZmYwMFwiLFxuICBGOiBcIiMwMGZmNjZcIixcbiAgUDogXCIjZmZjYzAwXCIsXG4gIFM6IFwiI2ZmMzMwMFwiLFxuICBUOiBcIiNmZjY2MDBcIixcbiAgVzogXCIjMDBjY2ZmXCIsXG4gIFk6IFwiIzAwZmZjY1wiLFxuICBWOiBcIiM5OWZmMDBcIixcbiAgQjogXCIjZmZmXCIsXG4gIFg6IFwiI2ZmZlwiLFxuICBaOiBcIiNmZmZcIlxufTtcbiIsIm1vZHVsZS5leHBvcnRzID0ge1xuICBBOiBcIiMyY2QzZDNcIixcbiAgUjogXCIjNzA4ZjhmXCIsXG4gIE46IFwiI2ZmMDAwMFwiLFxuICBEOiBcIiNlODE3MTdcIixcbiAgQzogXCIjYTg1NzU3XCIsXG4gIFE6IFwiIzNmYzBjMFwiLFxuICBFOiBcIiM3Nzg4ODhcIixcbiAgRzogXCIjZmYwMDAwXCIsXG4gIEg6IFwiIzcwOGY4ZlwiLFxuICBJOiBcIiMwMGZmZmZcIixcbiAgTDogXCIjMWNlM2UzXCIsXG4gIEs6IFwiIzdlODE4MVwiLFxuICBNOiBcIiMxZWUxZTFcIixcbiAgRjogXCIjMWVlMWUxXCIsXG4gIFA6IFwiI2Y2MDkwOVwiLFxuICBTOiBcIiNlMTFlMWVcIixcbiAgVDogXCIjNzM4YzhjXCIsXG4gIFc6IFwiIzczOGM4Y1wiLFxuICBZOiBcIiM5ZDYyNjJcIixcbiAgVjogXCIjMDdmOGY4XCIsXG4gIEI6IFwiI2YzMGMwY1wiLFxuICBYOiBcIiM3YzgzODNcIixcbiAgWjogXCIjNWJhNGE0XCJcbn07XG4iLCJtb2R1bGUuZXhwb3J0cyA9IHtcbiAgQTogXCIjZmZhZmFmXCIsXG4gIFI6IFwiIzY0NjRmZlwiLFxuICBOOiBcIiMwMGZmMDBcIixcbiAgRDogXCIjZmYwMDAwXCIsXG4gIEM6IFwiI2ZmZmYwMFwiLFxuICBROiBcIiMwMGZmMDBcIixcbiAgRTogXCIjZmYwMDAwXCIsXG4gIEc6IFwiI2ZmMDBmZlwiLFxuICBIOiBcIiM2NDY0ZmZcIixcbiAgSTogXCIjZmZhZmFmXCIsXG4gIEw6IFwiI2ZmYWZhZlwiLFxuICBLOiBcIiM2NDY0ZmZcIixcbiAgTTogXCIjZmZhZmFmXCIsXG4gIEY6IFwiI2ZmYzgwMFwiLFxuICBQOiBcIiNmZjAwZmZcIixcbiAgUzogXCIjMDBmZjAwXCIsXG4gIFQ6IFwiIzAwZmYwMFwiLFxuICBXOiBcIiNmZmM4MDBcIixcbiAgWTogXCIjZmZjODAwXCIsXG4gIFY6IFwiI2ZmYWZhZlwiLFxuICBCOiBcIiNmZmZcIixcbiAgWDogXCIjZmZmXCIsXG4gIFo6IFwiI2ZmZlwiXG59O1xuIiwiLypcbiAqIEphdmFTY3JpcHQgQ2FudmFzIHRvIEJsb2IgMi4wLjVcbiAqIGh0dHBzOi8vZ2l0aHViLmNvbS9ibHVlaW1wL0phdmFTY3JpcHQtQ2FudmFzLXRvLUJsb2JcbiAqXG4gKiBDb3B5cmlnaHQgMjAxMiwgU2ViYXN0aWFuIFRzY2hhblxuICogaHR0cHM6Ly9ibHVlaW1wLm5ldFxuICpcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgbGljZW5zZTpcbiAqIGh0dHA6Ly93d3cub3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvTUlUXG4gKlxuICogQmFzZWQgb24gc3RhY2tvdmVyZmxvdyB1c2VyIFN0b2l2ZSdzIGNvZGUgc25pcHBldDpcbiAqIGh0dHA6Ly9zdGFja292ZXJmbG93LmNvbS9xLzQ5OTg5MDhcbiAqL1xudmFyIENhbnZhc1Byb3RvdHlwZSA9IHdpbmRvdy5IVE1MQ2FudmFzRWxlbWVudCAmJlxud2luZG93LkhUTUxDYW52YXNFbGVtZW50LnByb3RvdHlwZSxcbiAgaGFzQmxvYkNvbnN0cnVjdG9yID0gd2luZG93LkJsb2IgJiYgKGZ1bmN0aW9uICgpIHtcbiAgICB0cnkge1xuICAgICAgcmV0dXJuIEJvb2xlYW4obmV3IEJsb2IoKSk7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgfSgpKSxcbiAgaGFzQXJyYXlCdWZmZXJWaWV3U3VwcG9ydCA9IGhhc0Jsb2JDb25zdHJ1Y3RvciAmJiB3aW5kb3cuVWludDhBcnJheSAmJlxuICAoZnVuY3Rpb24gKCkge1xuICAgIHRyeSB7XG4gICAgICByZXR1cm4gbmV3IEJsb2IoW25ldyBVaW50OEFycmF5KDEwMCldKS5zaXplID09PSAxMDA7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgfSgpKSxcbiAgQmxvYkJ1aWxkZXIgPSB3aW5kb3cuQmxvYkJ1aWxkZXIgfHwgd2luZG93LldlYktpdEJsb2JCdWlsZGVyIHx8XG4gIHdpbmRvdy5Nb3pCbG9iQnVpbGRlciB8fCB3aW5kb3cuTVNCbG9iQnVpbGRlcixcbiAgZGF0YVVSTHRvQmxvYiA9IChoYXNCbG9iQ29uc3RydWN0b3IgfHwgQmxvYkJ1aWxkZXIpICYmIHdpbmRvdy5hdG9iICYmXG4gIHdpbmRvdy5BcnJheUJ1ZmZlciAmJiB3aW5kb3cuVWludDhBcnJheSAmJiBmdW5jdGlvbiAoZGF0YVVSSSkge1xuICAgIHZhciBieXRlU3RyaW5nLFxuICAgIGFycmF5QnVmZmVyLFxuICAgIGludEFycmF5LFxuICAgICAgaSxcbiAgICAgIG1pbWVTdHJpbmcsXG4gICAgICAgIGJiO1xuICAgIGlmIChkYXRhVVJJLnNwbGl0KCcsJylbMF0uaW5kZXhPZignYmFzZTY0JykgPj0gMCkge1xuICAgICAgLy8gQ29udmVydCBiYXNlNjQgdG8gcmF3IGJpbmFyeSBkYXRhIGhlbGQgaW4gYSBzdHJpbmc6XG4gICAgICBieXRlU3RyaW5nID0gYXRvYihkYXRhVVJJLnNwbGl0KCcsJylbMV0pO1xuICAgIH0gZWxzZSB7XG4gICAgICAvLyBDb252ZXJ0IGJhc2U2NC9VUkxFbmNvZGVkIGRhdGEgY29tcG9uZW50IHRvIHJhdyBiaW5hcnkgZGF0YTpcbiAgICAgIGJ5dGVTdHJpbmcgPSBkZWNvZGVVUklDb21wb25lbnQoZGF0YVVSSS5zcGxpdCgnLCcpWzFdKTtcbiAgICB9XG4gICAgLy8gV3JpdGUgdGhlIGJ5dGVzIG9mIHRoZSBzdHJpbmcgdG8gYW4gQXJyYXlCdWZmZXI6XG4gICAgYXJyYXlCdWZmZXIgPSBuZXcgQXJyYXlCdWZmZXIoYnl0ZVN0cmluZy5sZW5ndGgpO1xuICAgIGludEFycmF5ID0gbmV3IFVpbnQ4QXJyYXkoYXJyYXlCdWZmZXIpO1xuICAgIGZvciAoaSA9IDA7IGkgPCBieXRlU3RyaW5nLmxlbmd0aDsgaSArPSAxKSB7XG4gICAgICBpbnRBcnJheVtpXSA9IGJ5dGVTdHJpbmcuY2hhckNvZGVBdChpKTtcbiAgICB9XG4gICAgLy8gU2VwYXJhdGUgb3V0IHRoZSBtaW1lIGNvbXBvbmVudDpcbiAgICBtaW1lU3RyaW5nID0gZGF0YVVSSS5zcGxpdCgnLCcpWzBdLnNwbGl0KCc6JylbMV0uc3BsaXQoJzsnKVswXTtcbiAgICAvLyBXcml0ZSB0aGUgQXJyYXlCdWZmZXIgKG9yIEFycmF5QnVmZmVyVmlldykgdG8gYSBibG9iOlxuICAgIGlmIChoYXNCbG9iQ29uc3RydWN0b3IpIHtcbiAgICAgIHJldHVybiBuZXcgQmxvYihcbiAgICAgICAgICBbaGFzQXJyYXlCdWZmZXJWaWV3U3VwcG9ydCA/IGludEFycmF5IDogYXJyYXlCdWZmZXJdLFxuICAgICAgICAgIHt0eXBlOiBtaW1lU3RyaW5nfVxuICAgICAgICAgICk7XG4gICAgfVxuICAgIGJiID0gbmV3IEJsb2JCdWlsZGVyKCk7XG4gICAgYmIuYXBwZW5kKGFycmF5QnVmZmVyKTtcbiAgICByZXR1cm4gYmIuZ2V0QmxvYihtaW1lU3RyaW5nKTtcbiAgfTtcbmlmICh3aW5kb3cuSFRNTENhbnZhc0VsZW1lbnQgJiYgIUNhbnZhc1Byb3RvdHlwZS50b0Jsb2IpIHtcbiAgaWYgKENhbnZhc1Byb3RvdHlwZS5tb3pHZXRBc0ZpbGUpIHtcbiAgICBDYW52YXNQcm90b3R5cGUudG9CbG9iID0gZnVuY3Rpb24gKGNhbGxiYWNrLCB0eXBlLCBxdWFsaXR5KSB7XG4gICAgICBpZiAocXVhbGl0eSAmJiBDYW52YXNQcm90b3R5cGUudG9EYXRhVVJMICYmIGRhdGFVUkx0b0Jsb2IpIHtcbiAgICAgICAgY2FsbGJhY2soZGF0YVVSTHRvQmxvYih0aGlzLnRvRGF0YVVSTCh0eXBlLCBxdWFsaXR5KSkpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgY2FsbGJhY2sodGhpcy5tb3pHZXRBc0ZpbGUoJ2Jsb2InLCB0eXBlKSk7XG4gICAgICB9XG4gICAgfTtcbiAgfSBlbHNlIGlmIChDYW52YXNQcm90b3R5cGUudG9EYXRhVVJMICYmIGRhdGFVUkx0b0Jsb2IpIHtcbiAgICBDYW52YXNQcm90b3R5cGUudG9CbG9iID0gZnVuY3Rpb24gKGNhbGxiYWNrLCB0eXBlLCBxdWFsaXR5KSB7XG4gICAgICBjYWxsYmFjayhkYXRhVVJMdG9CbG9iKHRoaXMudG9EYXRhVVJMKHR5cGUsIHF1YWxpdHkpKSk7XG4gICAgfTtcbiAgfVxufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGRhdGFVUkx0b0Jsb2I7XG4iLCIvKiBGaWxlU2F2ZXIuanNcbiAqICBBIHNhdmVBcygpIEZpbGVTYXZlciBpbXBsZW1lbnRhdGlvbi5cbiAqICAyMDE0LTA1LTI3XG4gKlxuICogIEJ5IEVsaSBHcmV5LCBodHRwOi8vZWxpZ3JleS5jb21cbiAqICBMaWNlbnNlOiBYMTEvTUlUXG4gKiAgICBTZWUgaHR0cHM6Ly9naXRodWIuY29tL2VsaWdyZXkvRmlsZVNhdmVyLmpzL2Jsb2IvbWFzdGVyL0xJQ0VOU0UubWRcbiAqL1xuXG4vKmdsb2JhbCBzZWxmICovXG4vKmpzbGludCBiaXR3aXNlOiB0cnVlLCBpbmRlbnQ6IDQsIGxheGJyZWFrOiB0cnVlLCBsYXhjb21tYTogdHJ1ZSwgc21hcnR0YWJzOiB0cnVlLCBwbHVzcGx1czogdHJ1ZSAqL1xuXG4vKiEgQHNvdXJjZSBodHRwOi8vcHVybC5lbGlncmV5LmNvbS9naXRodWIvRmlsZVNhdmVyLmpzL2Jsb2IvbWFzdGVyL0ZpbGVTYXZlci5qcyAqL1xuXG52YXIgc2F2ZUFzID0gc2F2ZUFzXG4gIC8vIElFIDEwKyAobmF0aXZlIHNhdmVBcylcbiAgfHwgKHR5cGVvZiBuYXZpZ2F0b3IgIT09IFwidW5kZWZpbmVkXCIgJiZcbiAgICAgIG5hdmlnYXRvci5tc1NhdmVPck9wZW5CbG9iICYmIG5hdmlnYXRvci5tc1NhdmVPck9wZW5CbG9iLmJpbmQobmF2aWdhdG9yKSlcbiAgLy8gRXZlcnlvbmUgZWxzZVxuICB8fCAoZnVuY3Rpb24odmlldykge1xuXHRcInVzZSBzdHJpY3RcIjtcblx0Ly8gSUUgPDEwIGlzIGV4cGxpY2l0bHkgdW5zdXBwb3J0ZWRcblx0aWYgKHR5cGVvZiBuYXZpZ2F0b3IgIT09IFwidW5kZWZpbmVkXCIgJiZcblx0ICAgIC9NU0lFIFsxLTldXFwuLy50ZXN0KG5hdmlnYXRvci51c2VyQWdlbnQpKSB7XG5cdFx0cmV0dXJuO1xuXHR9XG5cdHZhclxuXHRcdCAgZG9jID0gdmlldy5kb2N1bWVudFxuXHRcdCAgLy8gb25seSBnZXQgVVJMIHdoZW4gbmVjZXNzYXJ5IGluIGNhc2UgQmxvYi5qcyBoYXNuJ3Qgb3ZlcnJpZGRlbiBpdCB5ZXRcblx0XHQsIGdldF9VUkwgPSBmdW5jdGlvbigpIHtcblx0XHRcdHJldHVybiB2aWV3LlVSTCB8fCB2aWV3LndlYmtpdFVSTCB8fCB2aWV3O1xuXHRcdH1cblx0XHQsIHNhdmVfbGluayA9IGRvYy5jcmVhdGVFbGVtZW50TlMoXCJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sXCIsIFwiYVwiKVxuXHRcdCwgY2FuX3VzZV9zYXZlX2xpbmsgPSAhdmlldy5leHRlcm5hbEhvc3QgJiYgXCJkb3dubG9hZFwiIGluIHNhdmVfbGlua1xuXHRcdCwgY2xpY2sgPSBmdW5jdGlvbihub2RlKSB7XG5cdFx0XHR2YXIgZXZlbnQgPSBkb2MuY3JlYXRlRXZlbnQoXCJNb3VzZUV2ZW50c1wiKTtcblx0XHRcdGV2ZW50LmluaXRNb3VzZUV2ZW50KFxuXHRcdFx0XHRcImNsaWNrXCIsIHRydWUsIGZhbHNlLCB2aWV3LCAwLCAwLCAwLCAwLCAwXG5cdFx0XHRcdCwgZmFsc2UsIGZhbHNlLCBmYWxzZSwgZmFsc2UsIDAsIG51bGxcblx0XHRcdCk7XG5cdFx0XHRub2RlLmRpc3BhdGNoRXZlbnQoZXZlbnQpO1xuXHRcdH1cblx0XHQsIHdlYmtpdF9yZXFfZnMgPSB2aWV3LndlYmtpdFJlcXVlc3RGaWxlU3lzdGVtXG5cdFx0LCByZXFfZnMgPSB2aWV3LnJlcXVlc3RGaWxlU3lzdGVtIHx8IHdlYmtpdF9yZXFfZnMgfHwgdmlldy5tb3pSZXF1ZXN0RmlsZVN5c3RlbVxuXHRcdCwgdGhyb3dfb3V0c2lkZSA9IGZ1bmN0aW9uKGV4KSB7XG5cdFx0XHQodmlldy5zZXRJbW1lZGlhdGUgfHwgdmlldy5zZXRUaW1lb3V0KShmdW5jdGlvbigpIHtcblx0XHRcdFx0dGhyb3cgZXg7XG5cdFx0XHR9LCAwKTtcblx0XHR9XG5cdFx0LCBmb3JjZV9zYXZlYWJsZV90eXBlID0gXCJhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW1cIlxuXHRcdCwgZnNfbWluX3NpemUgPSAwXG5cdFx0LCBkZWxldGlvbl9xdWV1ZSA9IFtdXG5cdFx0LCBwcm9jZXNzX2RlbGV0aW9uX3F1ZXVlID0gZnVuY3Rpb24oKSB7XG5cdFx0XHR2YXIgaSA9IGRlbGV0aW9uX3F1ZXVlLmxlbmd0aDtcblx0XHRcdHdoaWxlIChpLS0pIHtcblx0XHRcdFx0dmFyIGZpbGUgPSBkZWxldGlvbl9xdWV1ZVtpXTtcblx0XHRcdFx0aWYgKHR5cGVvZiBmaWxlID09PSBcInN0cmluZ1wiKSB7IC8vIGZpbGUgaXMgYW4gb2JqZWN0IFVSTFxuXHRcdFx0XHRcdGdldF9VUkwoKS5yZXZva2VPYmplY3RVUkwoZmlsZSk7XG5cdFx0XHRcdH0gZWxzZSB7IC8vIGZpbGUgaXMgYSBGaWxlXG5cdFx0XHRcdFx0ZmlsZS5yZW1vdmUoKTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdFx0ZGVsZXRpb25fcXVldWUubGVuZ3RoID0gMDsgLy8gY2xlYXIgcXVldWVcblx0XHR9XG5cdFx0LCBkaXNwYXRjaCA9IGZ1bmN0aW9uKGZpbGVzYXZlciwgZXZlbnRfdHlwZXMsIGV2ZW50KSB7XG5cdFx0XHRldmVudF90eXBlcyA9IFtdLmNvbmNhdChldmVudF90eXBlcyk7XG5cdFx0XHR2YXIgaSA9IGV2ZW50X3R5cGVzLmxlbmd0aDtcblx0XHRcdHdoaWxlIChpLS0pIHtcblx0XHRcdFx0dmFyIGxpc3RlbmVyID0gZmlsZXNhdmVyW1wib25cIiArIGV2ZW50X3R5cGVzW2ldXTtcblx0XHRcdFx0aWYgKHR5cGVvZiBsaXN0ZW5lciA9PT0gXCJmdW5jdGlvblwiKSB7XG5cdFx0XHRcdFx0dHJ5IHtcblx0XHRcdFx0XHRcdGxpc3RlbmVyLmNhbGwoZmlsZXNhdmVyLCBldmVudCB8fCBmaWxlc2F2ZXIpO1xuXHRcdFx0XHRcdH0gY2F0Y2ggKGV4KSB7XG5cdFx0XHRcdFx0XHR0aHJvd19vdXRzaWRlKGV4KTtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHR9XG5cdFx0LCBGaWxlU2F2ZXIgPSBmdW5jdGlvbihibG9iLCBuYW1lKSB7XG5cdFx0XHQvLyBGaXJzdCB0cnkgYS5kb3dubG9hZCwgdGhlbiB3ZWIgZmlsZXN5c3RlbSwgdGhlbiBvYmplY3QgVVJMc1xuXHRcdFx0dmFyXG5cdFx0XHRcdCAgZmlsZXNhdmVyID0gdGhpc1xuXHRcdFx0XHQsIHR5cGUgPSBibG9iLnR5cGVcblx0XHRcdFx0LCBibG9iX2NoYW5nZWQgPSBmYWxzZVxuXHRcdFx0XHQsIG9iamVjdF91cmxcblx0XHRcdFx0LCB0YXJnZXRfdmlld1xuXHRcdFx0XHQsIGdldF9vYmplY3RfdXJsID0gZnVuY3Rpb24oKSB7XG5cdFx0XHRcdFx0dmFyIG9iamVjdF91cmwgPSBnZXRfVVJMKCkuY3JlYXRlT2JqZWN0VVJMKGJsb2IpO1xuXHRcdFx0XHRcdGRlbGV0aW9uX3F1ZXVlLnB1c2gob2JqZWN0X3VybCk7XG5cdFx0XHRcdFx0cmV0dXJuIG9iamVjdF91cmw7XG5cdFx0XHRcdH1cblx0XHRcdFx0LCBkaXNwYXRjaF9hbGwgPSBmdW5jdGlvbigpIHtcblx0XHRcdFx0XHRkaXNwYXRjaChmaWxlc2F2ZXIsIFwid3JpdGVzdGFydCBwcm9ncmVzcyB3cml0ZSB3cml0ZWVuZFwiLnNwbGl0KFwiIFwiKSk7XG5cdFx0XHRcdH1cblx0XHRcdFx0Ly8gb24gYW55IGZpbGVzeXMgZXJyb3JzIHJldmVydCB0byBzYXZpbmcgd2l0aCBvYmplY3QgVVJMc1xuXHRcdFx0XHQsIGZzX2Vycm9yID0gZnVuY3Rpb24oKSB7XG5cdFx0XHRcdFx0Ly8gZG9uJ3QgY3JlYXRlIG1vcmUgb2JqZWN0IFVSTHMgdGhhbiBuZWVkZWRcblx0XHRcdFx0XHRpZiAoYmxvYl9jaGFuZ2VkIHx8ICFvYmplY3RfdXJsKSB7XG5cdFx0XHRcdFx0XHRvYmplY3RfdXJsID0gZ2V0X29iamVjdF91cmwoYmxvYik7XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHRcdGlmICh0YXJnZXRfdmlldykge1xuXHRcdFx0XHRcdFx0dGFyZ2V0X3ZpZXcubG9jYXRpb24uaHJlZiA9IG9iamVjdF91cmw7XG5cdFx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHRcdHdpbmRvdy5vcGVuKG9iamVjdF91cmwsIFwiX2JsYW5rXCIpO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0XHRmaWxlc2F2ZXIucmVhZHlTdGF0ZSA9IGZpbGVzYXZlci5ET05FO1xuXHRcdFx0XHRcdGRpc3BhdGNoX2FsbCgpO1xuXHRcdFx0XHR9XG5cdFx0XHRcdCwgYWJvcnRhYmxlID0gZnVuY3Rpb24oZnVuYykge1xuXHRcdFx0XHRcdHJldHVybiBmdW5jdGlvbigpIHtcblx0XHRcdFx0XHRcdGlmIChmaWxlc2F2ZXIucmVhZHlTdGF0ZSAhPT0gZmlsZXNhdmVyLkRPTkUpIHtcblx0XHRcdFx0XHRcdFx0cmV0dXJuIGZ1bmMuYXBwbHkodGhpcywgYXJndW1lbnRzKTtcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9O1xuXHRcdFx0XHR9XG5cdFx0XHRcdCwgY3JlYXRlX2lmX25vdF9mb3VuZCA9IHtjcmVhdGU6IHRydWUsIGV4Y2x1c2l2ZTogZmFsc2V9XG5cdFx0XHRcdCwgc2xpY2Vcblx0XHRcdDtcblx0XHRcdGZpbGVzYXZlci5yZWFkeVN0YXRlID0gZmlsZXNhdmVyLklOSVQ7XG5cdFx0XHRpZiAoIW5hbWUpIHtcblx0XHRcdFx0bmFtZSA9IFwiZG93bmxvYWRcIjtcblx0XHRcdH1cblx0XHRcdGlmIChjYW5fdXNlX3NhdmVfbGluaykge1xuXHRcdFx0XHRvYmplY3RfdXJsID0gZ2V0X29iamVjdF91cmwoYmxvYik7XG5cdFx0XHRcdHNhdmVfbGluay5ocmVmID0gb2JqZWN0X3VybDtcblx0XHRcdFx0c2F2ZV9saW5rLmRvd25sb2FkID0gbmFtZTtcblx0XHRcdFx0Y2xpY2soc2F2ZV9saW5rKTtcblx0XHRcdFx0ZmlsZXNhdmVyLnJlYWR5U3RhdGUgPSBmaWxlc2F2ZXIuRE9ORTtcblx0XHRcdFx0ZGlzcGF0Y2hfYWxsKCk7XG5cdFx0XHRcdHJldHVybjtcblx0XHRcdH1cblx0XHRcdC8vIE9iamVjdCBhbmQgd2ViIGZpbGVzeXN0ZW0gVVJMcyBoYXZlIGEgcHJvYmxlbSBzYXZpbmcgaW4gR29vZ2xlIENocm9tZSB3aGVuXG5cdFx0XHQvLyB2aWV3ZWQgaW4gYSB0YWIsIHNvIEkgZm9yY2Ugc2F2ZSB3aXRoIGFwcGxpY2F0aW9uL29jdGV0LXN0cmVhbVxuXHRcdFx0Ly8gaHR0cDovL2NvZGUuZ29vZ2xlLmNvbS9wL2Nocm9taXVtL2lzc3Vlcy9kZXRhaWw/aWQ9OTExNThcblx0XHRcdGlmICh2aWV3LmNocm9tZSAmJiB0eXBlICYmIHR5cGUgIT09IGZvcmNlX3NhdmVhYmxlX3R5cGUpIHtcblx0XHRcdFx0c2xpY2UgPSBibG9iLnNsaWNlIHx8IGJsb2Iud2Via2l0U2xpY2U7XG5cdFx0XHRcdGJsb2IgPSBzbGljZS5jYWxsKGJsb2IsIDAsIGJsb2Iuc2l6ZSwgZm9yY2Vfc2F2ZWFibGVfdHlwZSk7XG5cdFx0XHRcdGJsb2JfY2hhbmdlZCA9IHRydWU7XG5cdFx0XHR9XG5cdFx0XHQvLyBTaW5jZSBJIGNhbid0IGJlIHN1cmUgdGhhdCB0aGUgZ3Vlc3NlZCBtZWRpYSB0eXBlIHdpbGwgdHJpZ2dlciBhIGRvd25sb2FkXG5cdFx0XHQvLyBpbiBXZWJLaXQsIEkgYXBwZW5kIC5kb3dubG9hZCB0byB0aGUgZmlsZW5hbWUuXG5cdFx0XHQvLyBodHRwczovL2J1Z3Mud2Via2l0Lm9yZy9zaG93X2J1Zy5jZ2k/aWQ9NjU0NDBcblx0XHRcdGlmICh3ZWJraXRfcmVxX2ZzICYmIG5hbWUgIT09IFwiZG93bmxvYWRcIikge1xuXHRcdFx0XHRuYW1lICs9IFwiLmRvd25sb2FkXCI7XG5cdFx0XHR9XG5cdFx0XHRpZiAodHlwZSA9PT0gZm9yY2Vfc2F2ZWFibGVfdHlwZSB8fCB3ZWJraXRfcmVxX2ZzKSB7XG5cdFx0XHRcdHRhcmdldF92aWV3ID0gdmlldztcblx0XHRcdH1cblx0XHRcdGlmICghcmVxX2ZzKSB7XG5cdFx0XHRcdGZzX2Vycm9yKCk7XG5cdFx0XHRcdHJldHVybjtcblx0XHRcdH1cblx0XHRcdGZzX21pbl9zaXplICs9IGJsb2Iuc2l6ZTtcblx0XHRcdHJlcV9mcyh2aWV3LlRFTVBPUkFSWSwgZnNfbWluX3NpemUsIGFib3J0YWJsZShmdW5jdGlvbihmcykge1xuXHRcdFx0XHRmcy5yb290LmdldERpcmVjdG9yeShcInNhdmVkXCIsIGNyZWF0ZV9pZl9ub3RfZm91bmQsIGFib3J0YWJsZShmdW5jdGlvbihkaXIpIHtcblx0XHRcdFx0XHR2YXIgc2F2ZSA9IGZ1bmN0aW9uKCkge1xuXHRcdFx0XHRcdFx0ZGlyLmdldEZpbGUobmFtZSwgY3JlYXRlX2lmX25vdF9mb3VuZCwgYWJvcnRhYmxlKGZ1bmN0aW9uKGZpbGUpIHtcblx0XHRcdFx0XHRcdFx0ZmlsZS5jcmVhdGVXcml0ZXIoYWJvcnRhYmxlKGZ1bmN0aW9uKHdyaXRlcikge1xuXHRcdFx0XHRcdFx0XHRcdHdyaXRlci5vbndyaXRlZW5kID0gZnVuY3Rpb24oZXZlbnQpIHtcblx0XHRcdFx0XHRcdFx0XHRcdHRhcmdldF92aWV3LmxvY2F0aW9uLmhyZWYgPSBmaWxlLnRvVVJMKCk7XG5cdFx0XHRcdFx0XHRcdFx0XHRkZWxldGlvbl9xdWV1ZS5wdXNoKGZpbGUpO1xuXHRcdFx0XHRcdFx0XHRcdFx0ZmlsZXNhdmVyLnJlYWR5U3RhdGUgPSBmaWxlc2F2ZXIuRE9ORTtcblx0XHRcdFx0XHRcdFx0XHRcdGRpc3BhdGNoKGZpbGVzYXZlciwgXCJ3cml0ZWVuZFwiLCBldmVudCk7XG5cdFx0XHRcdFx0XHRcdFx0fTtcblx0XHRcdFx0XHRcdFx0XHR3cml0ZXIub25lcnJvciA9IGZ1bmN0aW9uKCkge1xuXHRcdFx0XHRcdFx0XHRcdFx0dmFyIGVycm9yID0gd3JpdGVyLmVycm9yO1xuXHRcdFx0XHRcdFx0XHRcdFx0aWYgKGVycm9yLmNvZGUgIT09IGVycm9yLkFCT1JUX0VSUikge1xuXHRcdFx0XHRcdFx0XHRcdFx0XHRmc19lcnJvcigpO1xuXHRcdFx0XHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0XHRcdH07XG5cdFx0XHRcdFx0XHRcdFx0XCJ3cml0ZXN0YXJ0IHByb2dyZXNzIHdyaXRlIGFib3J0XCIuc3BsaXQoXCIgXCIpLmZvckVhY2goZnVuY3Rpb24oZXZlbnQpIHtcblx0XHRcdFx0XHRcdFx0XHRcdHdyaXRlcltcIm9uXCIgKyBldmVudF0gPSBmaWxlc2F2ZXJbXCJvblwiICsgZXZlbnRdO1xuXHRcdFx0XHRcdFx0XHRcdH0pO1xuXHRcdFx0XHRcdFx0XHRcdHdyaXRlci53cml0ZShibG9iKTtcblx0XHRcdFx0XHRcdFx0XHRmaWxlc2F2ZXIuYWJvcnQgPSBmdW5jdGlvbigpIHtcblx0XHRcdFx0XHRcdFx0XHRcdHdyaXRlci5hYm9ydCgpO1xuXHRcdFx0XHRcdFx0XHRcdFx0ZmlsZXNhdmVyLnJlYWR5U3RhdGUgPSBmaWxlc2F2ZXIuRE9ORTtcblx0XHRcdFx0XHRcdFx0XHR9O1xuXHRcdFx0XHRcdFx0XHRcdGZpbGVzYXZlci5yZWFkeVN0YXRlID0gZmlsZXNhdmVyLldSSVRJTkc7XG5cdFx0XHRcdFx0XHRcdH0pLCBmc19lcnJvcik7XG5cdFx0XHRcdFx0XHR9KSwgZnNfZXJyb3IpO1xuXHRcdFx0XHRcdH07XG5cdFx0XHRcdFx0ZGlyLmdldEZpbGUobmFtZSwge2NyZWF0ZTogZmFsc2V9LCBhYm9ydGFibGUoZnVuY3Rpb24oZmlsZSkge1xuXHRcdFx0XHRcdFx0Ly8gZGVsZXRlIGZpbGUgaWYgaXQgYWxyZWFkeSBleGlzdHNcblx0XHRcdFx0XHRcdGZpbGUucmVtb3ZlKCk7XG5cdFx0XHRcdFx0XHRzYXZlKCk7XG5cdFx0XHRcdFx0fSksIGFib3J0YWJsZShmdW5jdGlvbihleCkge1xuXHRcdFx0XHRcdFx0aWYgKGV4LmNvZGUgPT09IGV4Lk5PVF9GT1VORF9FUlIpIHtcblx0XHRcdFx0XHRcdFx0c2F2ZSgpO1xuXHRcdFx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHRcdFx0ZnNfZXJyb3IoKTtcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9KSk7XG5cdFx0XHRcdH0pLCBmc19lcnJvcik7XG5cdFx0XHR9KSwgZnNfZXJyb3IpO1xuXHRcdH1cblx0XHQsIEZTX3Byb3RvID0gRmlsZVNhdmVyLnByb3RvdHlwZVxuXHRcdCwgc2F2ZUFzID0gZnVuY3Rpb24oYmxvYiwgbmFtZSkge1xuXHRcdFx0cmV0dXJuIG5ldyBGaWxlU2F2ZXIoYmxvYiwgbmFtZSk7XG5cdFx0fVxuXHQ7XG5cdEZTX3Byb3RvLmFib3J0ID0gZnVuY3Rpb24oKSB7XG5cdFx0dmFyIGZpbGVzYXZlciA9IHRoaXM7XG5cdFx0ZmlsZXNhdmVyLnJlYWR5U3RhdGUgPSBmaWxlc2F2ZXIuRE9ORTtcblx0XHRkaXNwYXRjaChmaWxlc2F2ZXIsIFwiYWJvcnRcIik7XG5cdH07XG5cdEZTX3Byb3RvLnJlYWR5U3RhdGUgPSBGU19wcm90by5JTklUID0gMDtcblx0RlNfcHJvdG8uV1JJVElORyA9IDE7XG5cdEZTX3Byb3RvLkRPTkUgPSAyO1xuXG5cdEZTX3Byb3RvLmVycm9yID1cblx0RlNfcHJvdG8ub253cml0ZXN0YXJ0ID1cblx0RlNfcHJvdG8ub25wcm9ncmVzcyA9XG5cdEZTX3Byb3RvLm9ud3JpdGUgPVxuXHRGU19wcm90by5vbmFib3J0ID1cblx0RlNfcHJvdG8ub25lcnJvciA9XG5cdEZTX3Byb3RvLm9ud3JpdGVlbmQgPVxuXHRcdG51bGw7XG5cblx0dmlldy5hZGRFdmVudExpc3RlbmVyKFwidW5sb2FkXCIsIHByb2Nlc3NfZGVsZXRpb25fcXVldWUsIGZhbHNlKTtcblx0c2F2ZUFzLnVubG9hZCA9IGZ1bmN0aW9uKCkge1xuXHRcdHByb2Nlc3NfZGVsZXRpb25fcXVldWUoKTtcblx0XHR2aWV3LnJlbW92ZUV2ZW50TGlzdGVuZXIoXCJ1bmxvYWRcIiwgcHJvY2Vzc19kZWxldGlvbl9xdWV1ZSwgZmFsc2UpO1xuXHR9O1xuXHRyZXR1cm4gc2F2ZUFzO1xufShcblx0ICAgdHlwZW9mIHNlbGYgIT09IFwidW5kZWZpbmVkXCIgJiYgc2VsZlxuXHR8fCB0eXBlb2Ygd2luZG93ICE9PSBcInVuZGVmaW5lZFwiICYmIHdpbmRvd1xuXHR8fCB0aGlzLmNvbnRlbnRcbikpO1xuLy8gYHNlbGZgIGlzIHVuZGVmaW5lZCBpbiBGaXJlZm94IGZvciBBbmRyb2lkIGNvbnRlbnQgc2NyaXB0IGNvbnRleHRcbi8vIHdoaWxlIGB0aGlzYCBpcyBuc0lDb250ZW50RnJhbWVNZXNzYWdlTWFuYWdlclxuLy8gd2l0aCBhbiBhdHRyaWJ1dGUgYGNvbnRlbnRgIHRoYXQgY29ycmVzcG9uZHMgdG8gdGhlIHdpbmRvd1xuXG5hbWREZWZpbmUgPSB3aW5kb3cuZGVmaW5lO1xuaWYoIHR5cGVvZiBhbWREZWZpbmUgPT09IFwidW5kZWZpbmVkXCIgJiYgKHR5cGVvZiB3aW5kb3cuYWxtb25kICE9PSBcInVuZGVmaW5lZFwiIFxuICAgICYmIFwiZGVmaW5lXCIgaW4gd2luZG93LmFsbW9uZCApKXtcbiAgYW1kRGVmaW5lID0gd2luZG93LmFsbW9uZC5kZWZpbmU7XG59XG5cbmlmICh0eXBlb2YgbW9kdWxlICE9PSBcInVuZGVmaW5lZFwiICYmIG1vZHVsZSAhPT0gbnVsbCkge1xuICBtb2R1bGUuZXhwb3J0cyA9IHNhdmVBcztcbn0gZWxzZSBpZiAoKHR5cGVvZiBhbWREZWZpbmUgIT09IFwidW5kZWZpbmVkXCIgJiYgYW1kRGVmaW5lICE9PSBudWxsKSAmJiAoYW1kRGVmaW5lLmFtZCAhPSBudWxsKSkge1xuICBhbWREZWZpbmUoXCJzYXZlQXNcIixbXSwgZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIHNhdmVBcztcbiAgfSk7XG59XG4iLCJtb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uIChjc3MsIGN1c3RvbURvY3VtZW50KSB7XG4gIHZhciBkb2MgPSBjdXN0b21Eb2N1bWVudCB8fCBkb2N1bWVudDtcbiAgaWYgKGRvYy5jcmVhdGVTdHlsZVNoZWV0KSB7XG4gICAgdmFyIHNoZWV0ID0gZG9jLmNyZWF0ZVN0eWxlU2hlZXQoKVxuICAgIHNoZWV0LmNzc1RleHQgPSBjc3M7XG4gICAgcmV0dXJuIHNoZWV0Lm93bmVyTm9kZTtcbiAgfSBlbHNlIHtcbiAgICB2YXIgaGVhZCA9IGRvYy5nZXRFbGVtZW50c0J5VGFnTmFtZSgnaGVhZCcpWzBdLFxuICAgICAgICBzdHlsZSA9IGRvYy5jcmVhdGVFbGVtZW50KCdzdHlsZScpO1xuXG4gICAgc3R5bGUudHlwZSA9ICd0ZXh0L2Nzcyc7XG5cbiAgICBpZiAoc3R5bGUuc3R5bGVTaGVldCkge1xuICAgICAgc3R5bGUuc3R5bGVTaGVldC5jc3NUZXh0ID0gY3NzO1xuICAgIH0gZWxzZSB7XG4gICAgICBzdHlsZS5hcHBlbmRDaGlsZChkb2MuY3JlYXRlVGV4dE5vZGUoY3NzKSk7XG4gICAgfVxuXG4gICAgaGVhZC5hcHBlbmRDaGlsZChzdHlsZSk7XG4gICAgcmV0dXJuIHN0eWxlO1xuICB9XG59O1xuXG5tb2R1bGUuZXhwb3J0cy5ieVVybCA9IGZ1bmN0aW9uKHVybCkge1xuICBpZiAoZG9jdW1lbnQuY3JlYXRlU3R5bGVTaGVldCkge1xuICAgIHJldHVybiBkb2N1bWVudC5jcmVhdGVTdHlsZVNoZWV0KHVybCkub3duZXJOb2RlO1xuICB9IGVsc2Uge1xuICAgIHZhciBoZWFkID0gZG9jdW1lbnQuZ2V0RWxlbWVudHNCeVRhZ05hbWUoJ2hlYWQnKVswXSxcbiAgICAgICAgbGluayA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2xpbmsnKTtcblxuICAgIGxpbmsucmVsID0gJ3N0eWxlc2hlZXQnO1xuICAgIGxpbmsuaHJlZiA9IHVybDtcblxuICAgIGhlYWQuYXBwZW5kQ2hpbGQobGluayk7XG4gICAgcmV0dXJuIGxpbms7XG4gIH1cbn07XG4iLCJ2YXIgVXRpbHMgPSB7fTtcblxuXG4vKlxuUmVtb3ZlIGFuIGVsZW1lbnQgYW5kIHByb3ZpZGUgYSBmdW5jdGlvbiB0aGF0IGluc2VydHMgaXQgaW50byBpdHMgb3JpZ2luYWwgcG9zaXRpb25cbmh0dHBzOi8vZGV2ZWxvcGVycy5nb29nbGUuY29tL3NwZWVkL2FydGljbGVzL2phdmFzY3JpcHQtZG9tXG5AcGFyYW0gZWxlbWVudCB7RWxlbWVudH0gVGhlIGVsZW1lbnQgdG8gYmUgdGVtcG9yYXJpbHkgcmVtb3ZlZFxuQHJldHVybiB7RnVuY3Rpb259IEEgZnVuY3Rpb24gdGhhdCBpbnNlcnRzIHRoZSBlbGVtZW50IGludG8gaXRzIG9yaWdpbmFsIHBvc2l0aW9uXG4gKi9cblxuVXRpbHMucmVtb3ZlVG9JbnNlcnRMYXRlciA9IGZ1bmN0aW9uKGVsZW1lbnQpIHtcbiAgdmFyIG5leHRTaWJsaW5nLCBwYXJlbnROb2RlO1xuICBwYXJlbnROb2RlID0gZWxlbWVudC5wYXJlbnROb2RlO1xuICBuZXh0U2libGluZyA9IGVsZW1lbnQubmV4dFNpYmxpbmc7XG4gIHBhcmVudE5vZGUucmVtb3ZlQ2hpbGQoZWxlbWVudCk7XG4gIHJldHVybiBmdW5jdGlvbigpIHtcbiAgICBpZiAobmV4dFNpYmxpbmcpIHtcbiAgICAgIHBhcmVudE5vZGUuaW5zZXJ0QmVmb3JlKGVsZW1lbnQsIG5leHRTaWJsaW5nKTtcbiAgICB9IGVsc2Uge1xuICAgICAgcGFyZW50Tm9kZS5hcHBlbmRDaGlsZChlbGVtZW50KTtcbiAgICB9XG4gIH07XG59O1xuXG5cbi8qXG5mYXN0ZXN0IHBvc3NpYmxlIHdheSB0byBkZXN0cm95IGFsbCBzdWIgbm9kZXMgKGFrYSBjaGlsZHMpXG5odHRwOi8vanNwZXJmLmNvbS9pbm5lcmh0bWwtdnMtcmVtb3ZlY2hpbGQvMTVcbkBwYXJhbSBlbGVtZW50IHtFbGVtZW50fSBUaGUgZWxlbWVudCBmb3Igd2hpY2ggYWxsIGNoaWxkcyBzaG91bGQgYmUgcmVtb3ZlZFxuICovXG5cblV0aWxzLnJlbW92ZUFsbENoaWxkcyA9IGZ1bmN0aW9uKGVsZW1lbnQpIHtcbiAgdmFyIGNvdW50O1xuICBjb3VudCA9IDA7XG4gIHdoaWxlIChlbGVtZW50LmZpcnN0Q2hpbGQpIHtcbiAgICBjb3VudCsrO1xuICAgIGVsZW1lbnQucmVtb3ZlQ2hpbGQoZWxlbWVudC5maXJzdENoaWxkKTtcbiAgfVxufTtcblxubW9kdWxlLmV4cG9ydHMgPSBVdGlscztcbiIsIi8qIVxuICogakJvbmUgdjEuMC4xOSAtIDIwMTQtMTAtMTIgLSBMaWJyYXJ5IGZvciBET00gbWFuaXB1bGF0aW9uXG4gKlxuICogaHR0cHM6Ly9naXRodWIuY29tL2t1cHJpeWFuZW5rby9qYm9uZVxuICpcbiAqIENvcHlyaWdodCAyMDE0IEFsZXhleSBLdXByaXlhbmVua29cbiAqIFJlbGVhc2VkIHVuZGVyIHRoZSBNSVQgbGljZW5zZS5cbiAqL1xuXG4oZnVuY3Rpb24gKHdpbikge1xuXG52YXJcbi8vIGNhY2hlIHByZXZpb3VzIHZlcnNpb25zXG5fJCA9IHdpbi4kLFxuX2pCb25lID0gd2luLmpCb25lLFxuXG4vLyBRdWljayBtYXRjaCBhIHN0YW5kYWxvbmUgdGFnXG5ycXVpY2tTaW5nbGVUYWcgPSAvXjwoXFx3KylcXHMqXFwvPz4kLyxcblxuLy8gQSBzaW1wbGUgd2F5IHRvIGNoZWNrIGZvciBIVE1MIHN0cmluZ3Ncbi8vIFByaW9yaXRpemUgI2lkIG92ZXIgPHRhZz4gdG8gYXZvaWQgWFNTIHZpYSBsb2NhdGlvbi5oYXNoXG5ycXVpY2tFeHByID0gL14oPzpbXiM8XSooPFtcXHdcXFddKz4pW14+XSokfCMoW1xcd1xcLV0qKSQpLyxcblxuLy8gQWxpYXMgZm9yIGZ1bmN0aW9uXG5zbGljZSA9IFtdLnNsaWNlLFxuc3BsaWNlID0gW10uc3BsaWNlLFxua2V5cyA9IE9iamVjdC5rZXlzLFxuXG4vLyBBbGlhcyBmb3IgZ2xvYmFsIHZhcmlhYmxlc1xuZG9jID0gZG9jdW1lbnQsXG5cbmlzU3RyaW5nID0gZnVuY3Rpb24oZWwpIHtcbiAgICByZXR1cm4gdHlwZW9mIGVsID09PSBcInN0cmluZ1wiO1xufSxcbmlzT2JqZWN0ID0gZnVuY3Rpb24oZWwpIHtcbiAgICByZXR1cm4gZWwgaW5zdGFuY2VvZiBPYmplY3Q7XG59LFxuaXNGdW5jdGlvbiA9IGZ1bmN0aW9uKGVsKSB7XG4gICAgdmFyIGdldFR5cGUgPSB7fTtcbiAgICByZXR1cm4gZWwgJiYgZ2V0VHlwZS50b1N0cmluZy5jYWxsKGVsKSA9PT0gXCJbb2JqZWN0IEZ1bmN0aW9uXVwiO1xufSxcbmlzQXJyYXkgPSBmdW5jdGlvbihlbCkge1xuICAgIHJldHVybiBBcnJheS5pc0FycmF5KGVsKTtcbn0sXG5qQm9uZSA9IGZ1bmN0aW9uKGVsZW1lbnQsIGRhdGEpIHtcbiAgICByZXR1cm4gbmV3IGZuLmluaXQoZWxlbWVudCwgZGF0YSk7XG59LFxuZm47XG5cbi8vIHNldCBwcmV2aW91cyB2YWx1ZXMgYW5kIHJldHVybiB0aGUgaW5zdGFuY2UgdXBvbiBjYWxsaW5nIHRoZSBuby1jb25mbGljdCBtb2RlXG5qQm9uZS5ub0NvbmZsaWN0ID0gZnVuY3Rpb24oKSB7XG4gICAgd2luLiQgPSBfJDtcbiAgICB3aW4uakJvbmUgPSBfakJvbmU7XG5cbiAgICByZXR1cm4gakJvbmU7XG59O1xuXG5mbiA9IGpCb25lLmZuID0gakJvbmUucHJvdG90eXBlID0ge1xuICAgIGluaXQ6IGZ1bmN0aW9uKGVsZW1lbnQsIGRhdGEpIHtcbiAgICAgICAgdmFyIGVsZW1lbnRzLCB0YWcsIHdyYXBlciwgZnJhZ21lbnQ7XG5cbiAgICAgICAgaWYgKCFlbGVtZW50KSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuICAgICAgICBpZiAoaXNTdHJpbmcoZWxlbWVudCkpIHtcbiAgICAgICAgICAgIC8vIENyZWF0ZSBzaW5nbGUgRE9NIGVsZW1lbnRcbiAgICAgICAgICAgIGlmICh0YWcgPSBycXVpY2tTaW5nbGVUYWcuZXhlYyhlbGVtZW50KSkge1xuICAgICAgICAgICAgICAgIHRoaXNbMF0gPSBkb2MuY3JlYXRlRWxlbWVudCh0YWdbMV0pO1xuICAgICAgICAgICAgICAgIHRoaXMubGVuZ3RoID0gMTtcblxuICAgICAgICAgICAgICAgIGlmIChpc09iamVjdChkYXRhKSkge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLmF0dHIoZGF0YSk7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBDcmVhdGUgRE9NIGNvbGxlY3Rpb25cbiAgICAgICAgICAgIGlmICgodGFnID0gcnF1aWNrRXhwci5leGVjKGVsZW1lbnQpKSAmJiB0YWdbMV0pIHtcbiAgICAgICAgICAgICAgICBmcmFnbWVudCA9IGRvYy5jcmVhdGVEb2N1bWVudEZyYWdtZW50KCk7XG4gICAgICAgICAgICAgICAgd3JhcGVyID0gZG9jLmNyZWF0ZUVsZW1lbnQoXCJkaXZcIik7XG4gICAgICAgICAgICAgICAgd3JhcGVyLmlubmVySFRNTCA9IGVsZW1lbnQ7XG4gICAgICAgICAgICAgICAgd2hpbGUgKHdyYXBlci5sYXN0Q2hpbGQpIHtcbiAgICAgICAgICAgICAgICAgICAgZnJhZ21lbnQuYXBwZW5kQ2hpbGQod3JhcGVyLmZpcnN0Q2hpbGQpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbGVtZW50cyA9IHNsaWNlLmNhbGwoZnJhZ21lbnQuY2hpbGROb2Rlcyk7XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gakJvbmUubWVyZ2UodGhpcywgZWxlbWVudHMpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gRmluZCBET00gZWxlbWVudHMgd2l0aCBxdWVyeVNlbGVjdG9yQWxsXG4gICAgICAgICAgICBpZiAoakJvbmUuaXNFbGVtZW50KGRhdGEpKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGpCb25lKGRhdGEpLmZpbmQoZWxlbWVudCk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgZWxlbWVudHMgPSBkb2MucXVlcnlTZWxlY3RvckFsbChlbGVtZW50KTtcblxuICAgICAgICAgICAgICAgIHJldHVybiBqQm9uZS5tZXJnZSh0aGlzLCBlbGVtZW50cyk7XG4gICAgICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgLy8gV3JhcCBET01FbGVtZW50XG4gICAgICAgIGlmIChlbGVtZW50Lm5vZGVUeXBlKSB7XG4gICAgICAgICAgICB0aGlzWzBdID0gZWxlbWVudDtcbiAgICAgICAgICAgIHRoaXMubGVuZ3RoID0gMTtcblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cbiAgICAgICAgLy8gUnVuIGZ1bmN0aW9uXG4gICAgICAgIGlmIChpc0Z1bmN0aW9uKGVsZW1lbnQpKSB7XG4gICAgICAgICAgICByZXR1cm4gZWxlbWVudCgpO1xuICAgICAgICB9XG4gICAgICAgIC8vIFJldHVybiBqQm9uZSBlbGVtZW50IGFzIGlzXG4gICAgICAgIGlmIChlbGVtZW50IGluc3RhbmNlb2YgakJvbmUpIHtcbiAgICAgICAgICAgIHJldHVybiBlbGVtZW50O1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gUmV0dXJuIGVsZW1lbnQgd3JhcHBlZCBieSBqQm9uZVxuICAgICAgICByZXR1cm4gakJvbmUubWFrZUFycmF5KGVsZW1lbnQsIHRoaXMpO1xuICAgIH0sXG5cbiAgICBwb3A6IFtdLnBvcCxcbiAgICBwdXNoOiBbXS5wdXNoLFxuICAgIHJldmVyc2U6IFtdLnJldmVyc2UsXG4gICAgc2hpZnQ6IFtdLnNoaWZ0LFxuICAgIHNvcnQ6IFtdLnNvcnQsXG4gICAgc3BsaWNlOiBbXS5zcGxpY2UsXG4gICAgc2xpY2U6IFtdLnNsaWNlLFxuICAgIGluZGV4T2Y6IFtdLmluZGV4T2YsXG4gICAgZm9yRWFjaDogW10uZm9yRWFjaCxcbiAgICB1bnNoaWZ0OiBbXS51bnNoaWZ0LFxuICAgIGNvbmNhdDogW10uY29uY2F0LFxuICAgIGpvaW46IFtdLmpvaW4sXG4gICAgZXZlcnk6IFtdLmV2ZXJ5LFxuICAgIHNvbWU6IFtdLnNvbWUsXG4gICAgZmlsdGVyOiBbXS5maWx0ZXIsXG4gICAgbWFwOiBbXS5tYXAsXG4gICAgcmVkdWNlOiBbXS5yZWR1Y2UsXG4gICAgcmVkdWNlUmlnaHQ6IFtdLnJlZHVjZVJpZ2h0LFxuICAgIGxlbmd0aDogMFxufTtcblxuZm4uY29uc3RydWN0b3IgPSBqQm9uZTtcblxuZm4uaW5pdC5wcm90b3R5cGUgPSBmbjtcblxuakJvbmUuc2V0SWQgPSBmdW5jdGlvbihlbCkge1xuICAgIHZhciBqaWQgPSBlbC5qaWQ7XG5cbiAgICBpZiAoZWwgPT09IHdpbikge1xuICAgICAgICBqaWQgPSBcIndpbmRvd1wiO1xuICAgIH0gZWxzZSBpZiAoZWwuamlkID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgZWwuamlkID0gamlkID0gKytqQm9uZS5fY2FjaGUuamlkO1xuICAgIH1cblxuICAgIGlmICghakJvbmUuX2NhY2hlLmV2ZW50c1tqaWRdKSB7XG4gICAgICAgIGpCb25lLl9jYWNoZS5ldmVudHNbamlkXSA9IHt9O1xuICAgIH1cbn07XG5cbmpCb25lLmdldERhdGEgPSBmdW5jdGlvbihlbCkge1xuICAgIGVsID0gZWwgaW5zdGFuY2VvZiBqQm9uZSA/IGVsWzBdIDogZWw7XG5cbiAgICB2YXIgamlkID0gZWwgPT09IHdpbiA/IFwid2luZG93XCIgOiBlbC5qaWQ7XG5cbiAgICByZXR1cm4ge1xuICAgICAgICBqaWQ6IGppZCxcbiAgICAgICAgZXZlbnRzOiBqQm9uZS5fY2FjaGUuZXZlbnRzW2ppZF1cbiAgICB9O1xufTtcblxuakJvbmUuaXNFbGVtZW50ID0gZnVuY3Rpb24oZWwpIHtcbiAgICByZXR1cm4gZWwgJiYgZWwgaW5zdGFuY2VvZiBqQm9uZSB8fCBlbCBpbnN0YW5jZW9mIEhUTUxFbGVtZW50IHx8IGlzU3RyaW5nKGVsKTtcbn07XG5cbmpCb25lLl9jYWNoZSA9IHtcbiAgICBldmVudHM6IHt9LFxuICAgIGppZDogMFxufTtcblxuZnVuY3Rpb24gaXNBcnJheWxpa2Uob2JqKSB7XG4gICAgdmFyIGxlbmd0aCA9IG9iai5sZW5ndGgsXG4gICAgICAgIHR5cGUgPSB0eXBlb2Ygb2JqO1xuXG4gICAgaWYgKGlzRnVuY3Rpb24odHlwZSkgfHwgb2JqID09PSB3aW4pIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cblxuICAgIGlmIChvYmoubm9kZVR5cGUgPT09IDEgJiYgbGVuZ3RoKSB7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cblxuICAgIHJldHVybiBpc0FycmF5KHR5cGUpIHx8IGxlbmd0aCA9PT0gMCB8fFxuICAgICAgICB0eXBlb2YgbGVuZ3RoID09PSBcIm51bWJlclwiICYmIGxlbmd0aCA+IDAgJiYgKGxlbmd0aCAtIDEpIGluIG9iajtcbn1cblxuakJvbmUubWVyZ2UgPSBmdW5jdGlvbihmaXJzdCwgc2Vjb25kKSB7XG4gICAgdmFyIGwgPSBzZWNvbmQubGVuZ3RoLFxuICAgICAgICBpID0gZmlyc3QubGVuZ3RoLFxuICAgICAgICBqID0gMDtcblxuICAgIHdoaWxlIChqIDwgbCkge1xuICAgICAgICBmaXJzdFtpKytdID0gc2Vjb25kW2orK107XG4gICAgfVxuXG4gICAgZmlyc3QubGVuZ3RoID0gaTtcblxuICAgIHJldHVybiBmaXJzdDtcbn07XG5cbmpCb25lLmNvbnRhaW5zID0gZnVuY3Rpb24oY29udGFpbmVyLCBjb250YWluZWQpIHtcbiAgICB2YXIgcmVzdWx0O1xuXG4gICAgY29udGFpbmVyLnJldmVyc2UoKS5zb21lKGZ1bmN0aW9uKGVsKSB7XG4gICAgICAgIGlmIChlbC5jb250YWlucyhjb250YWluZWQpKSB7XG4gICAgICAgICAgICByZXR1cm4gcmVzdWx0ID0gZWw7XG4gICAgICAgIH1cbiAgICB9KTtcblxuICAgIHJldHVybiByZXN1bHQ7XG59O1xuXG5qQm9uZS5leHRlbmQgPSBmdW5jdGlvbih0YXJnZXQpIHtcbiAgICB2YXIgaywga2wsIGksIHRnO1xuXG4gICAgc3BsaWNlLmNhbGwoYXJndW1lbnRzLCAxKS5mb3JFYWNoKGZ1bmN0aW9uKG9iamVjdCkge1xuICAgICAgICBpZiAoIW9iamVjdCkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgayA9IGtleXMob2JqZWN0KTtcbiAgICAgICAga2wgPSBrLmxlbmd0aDtcbiAgICAgICAgaSA9IDA7XG4gICAgICAgIHRnID0gdGFyZ2V0OyAvL2NhY2hpbmcgdGFyZ2V0IGZvciBwZXJmIGltcHJvdmVtZW50XG5cbiAgICAgICAgZm9yICg7IGkgPCBrbDsgaSsrKSB7XG4gICAgICAgICAgICB0Z1trW2ldXSA9IG9iamVjdFtrW2ldXTtcbiAgICAgICAgfVxuICAgIH0pO1xuXG4gICAgcmV0dXJuIHRhcmdldDtcbn07XG5cbmpCb25lLm1ha2VBcnJheSA9IGZ1bmN0aW9uKGFyciwgcmVzdWx0cykge1xuICAgIHZhciByZXQgPSByZXN1bHRzIHx8IFtdO1xuXG4gICAgaWYgKGFyciAhPT0gbnVsbCkge1xuICAgICAgICBpZiAoaXNBcnJheWxpa2UoYXJyKSkge1xuICAgICAgICAgICAgakJvbmUubWVyZ2UocmV0LCBpc1N0cmluZyhhcnIpID8gW2Fycl0gOiBhcnIpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgcmV0LnB1c2goYXJyKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiByZXQ7XG59O1xuXG5mdW5jdGlvbiBCb25lRXZlbnQoZSwgZGF0YSkge1xuICAgIHZhciBrZXksIHNldHRlcjtcblxuICAgIHRoaXMub3JpZ2luYWxFdmVudCA9IGU7XG5cbiAgICBzZXR0ZXIgPSBmdW5jdGlvbihrZXksIGUpIHtcbiAgICAgICAgaWYgKGtleSA9PT0gXCJwcmV2ZW50RGVmYXVsdFwiKSB7XG4gICAgICAgICAgICB0aGlzW2tleV0gPSBmdW5jdGlvbigpIHtcbiAgICAgICAgICAgICAgICB0aGlzLmRlZmF1bHRQcmV2ZW50ZWQgPSB0cnVlO1xuICAgICAgICAgICAgICAgIHJldHVybiBlW2tleV0oKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0gZWxzZSBpZiAoaXNGdW5jdGlvbihlW2tleV0pKSB7XG4gICAgICAgICAgICB0aGlzW2tleV0gPSBmdW5jdGlvbigpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gZVtrZXldKCk7XG4gICAgICAgICAgICB9O1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgdGhpc1trZXldID0gZVtrZXldO1xuICAgICAgICB9XG4gICAgfTtcblxuICAgIGZvciAoa2V5IGluIGUpIHtcbiAgICAgICAgaWYgKGVba2V5XSB8fCB0eXBlb2YgZVtrZXldID09PSBcImZ1bmN0aW9uXCIpIHtcbiAgICAgICAgICAgIHNldHRlci5jYWxsKHRoaXMsIGtleSwgZSk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBqQm9uZS5leHRlbmQodGhpcywgZGF0YSk7XG59XG5cbmpCb25lLkV2ZW50ID0gZnVuY3Rpb24oZXZlbnQsIGRhdGEpIHtcbiAgICB2YXIgbmFtZXNwYWNlLCBldmVudFR5cGU7XG5cbiAgICBpZiAoZXZlbnQudHlwZSAmJiAhZGF0YSkge1xuICAgICAgICBkYXRhID0gZXZlbnQ7XG4gICAgICAgIGV2ZW50ID0gZXZlbnQudHlwZTtcbiAgICB9XG5cbiAgICBuYW1lc3BhY2UgPSBldmVudC5zcGxpdChcIi5cIikuc3BsaWNlKDEpLmpvaW4oXCIuXCIpO1xuICAgIGV2ZW50VHlwZSA9IGV2ZW50LnNwbGl0KFwiLlwiKVswXTtcblxuICAgIGV2ZW50ID0gZG9jLmNyZWF0ZUV2ZW50KFwiRXZlbnRcIik7XG4gICAgZXZlbnQuaW5pdEV2ZW50KGV2ZW50VHlwZSwgdHJ1ZSwgdHJ1ZSk7XG5cbiAgICByZXR1cm4gakJvbmUuZXh0ZW5kKGV2ZW50LCB7XG4gICAgICAgIG5hbWVzcGFjZTogbmFtZXNwYWNlLFxuICAgICAgICBpc0RlZmF1bHRQcmV2ZW50ZWQ6IGZ1bmN0aW9uKCkge1xuICAgICAgICAgICAgcmV0dXJuIGV2ZW50LmRlZmF1bHRQcmV2ZW50ZWQ7XG4gICAgICAgIH1cbiAgICB9LCBkYXRhKTtcbn07XG5cbmZuLm9uID0gZnVuY3Rpb24oZXZlbnQpIHtcbiAgICB2YXIgYXJncyA9IGFyZ3VtZW50cyxcbiAgICAgICAgbGVuZ3RoID0gdGhpcy5sZW5ndGgsXG4gICAgICAgIGkgPSAwLFxuICAgICAgICBjYWxsYmFjaywgdGFyZ2V0LCBuYW1lc3BhY2UsIGZuLCBldmVudHMsIGV2ZW50VHlwZSwgZXhwZWN0ZWRUYXJnZXQsIGFkZExpc3RlbmVyO1xuXG4gICAgaWYgKGFyZ3MubGVuZ3RoID09PSAyKSB7XG4gICAgICAgIGNhbGxiYWNrID0gYXJnc1sxXTtcbiAgICB9IGVsc2Uge1xuICAgICAgICB0YXJnZXQgPSBhcmdzWzFdO1xuICAgICAgICBjYWxsYmFjayA9IGFyZ3NbMl07XG4gICAgfVxuXG4gICAgYWRkTGlzdGVuZXIgPSBmdW5jdGlvbihlbCkge1xuICAgICAgICBqQm9uZS5zZXRJZChlbCk7XG4gICAgICAgIGV2ZW50cyA9IGpCb25lLmdldERhdGEoZWwpLmV2ZW50cztcbiAgICAgICAgZXZlbnQuc3BsaXQoXCIgXCIpLmZvckVhY2goZnVuY3Rpb24oZXZlbnQpIHtcbiAgICAgICAgICAgIGV2ZW50VHlwZSA9IGV2ZW50LnNwbGl0KFwiLlwiKVswXTtcbiAgICAgICAgICAgIG5hbWVzcGFjZSA9IGV2ZW50LnNwbGl0KFwiLlwiKS5zcGxpY2UoMSkuam9pbihcIi5cIik7XG4gICAgICAgICAgICBldmVudHNbZXZlbnRUeXBlXSA9IGV2ZW50c1tldmVudFR5cGVdIHx8IFtdO1xuXG4gICAgICAgICAgICBmbiA9IGZ1bmN0aW9uKGUpIHtcbiAgICAgICAgICAgICAgICBpZiAoZS5uYW1lc3BhY2UgJiYgZS5uYW1lc3BhY2UgIT09IG5hbWVzcGFjZSkge1xuICAgICAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgZXhwZWN0ZWRUYXJnZXQgPSBudWxsO1xuICAgICAgICAgICAgICAgIGlmICghdGFyZ2V0KSB7XG4gICAgICAgICAgICAgICAgICAgIGNhbGxiYWNrLmNhbGwoZWwsIGUpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSBpZiAofmpCb25lKGVsKS5maW5kKHRhcmdldCkuaW5kZXhPZihlLnRhcmdldCkgfHwgKGV4cGVjdGVkVGFyZ2V0ID0gakJvbmUuY29udGFpbnMoakJvbmUoZWwpLmZpbmQodGFyZ2V0KSwgZS50YXJnZXQpKSkge1xuICAgICAgICAgICAgICAgICAgICBleHBlY3RlZFRhcmdldCA9IGV4cGVjdGVkVGFyZ2V0IHx8IGUudGFyZ2V0O1xuICAgICAgICAgICAgICAgICAgICBlID0gbmV3IEJvbmVFdmVudChlLCB7XG4gICAgICAgICAgICAgICAgICAgICAgICBjdXJyZW50VGFyZ2V0OiBleHBlY3RlZFRhcmdldFxuICAgICAgICAgICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgICAgICAgICBjYWxsYmFjay5jYWxsKGV4cGVjdGVkVGFyZ2V0LCBlKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9O1xuXG4gICAgICAgICAgICBldmVudHNbZXZlbnRUeXBlXS5wdXNoKHtcbiAgICAgICAgICAgICAgICBuYW1lc3BhY2U6IG5hbWVzcGFjZSxcbiAgICAgICAgICAgICAgICBmbjogZm4sXG4gICAgICAgICAgICAgICAgb3JpZ2luZm46IGNhbGxiYWNrXG4gICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgZWwuYWRkRXZlbnRMaXN0ZW5lciAmJiBlbC5hZGRFdmVudExpc3RlbmVyKGV2ZW50VHlwZSwgZm4sIGZhbHNlKTtcbiAgICAgICAgfSk7XG4gICAgfTtcblxuICAgIGZvciAoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgYWRkTGlzdGVuZXIodGhpc1tpXSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXM7XG59O1xuXG5mbi5vbmUgPSBmdW5jdGlvbihldmVudCkge1xuICAgIHZhciBhcmdzID0gYXJndW1lbnRzLFxuICAgICAgICBpID0gMCxcbiAgICAgICAgbGVuZ3RoID0gdGhpcy5sZW5ndGgsXG4gICAgICAgIGNhbGxiYWNrLCB0YXJnZXQsIGFkZExpc3RlbmVyO1xuXG4gICAgaWYgKGFyZ3MubGVuZ3RoID09PSAyKSB7XG4gICAgICAgIGNhbGxiYWNrID0gYXJnc1sxXTtcbiAgICB9IGVsc2Uge1xuICAgICAgICB0YXJnZXQgPSBhcmdzWzFdLCBjYWxsYmFjayA9IGFyZ3NbMl07XG4gICAgfVxuXG4gICAgYWRkTGlzdGVuZXIgPSBmdW5jdGlvbihlbCkge1xuICAgICAgICBldmVudC5zcGxpdChcIiBcIikuZm9yRWFjaChmdW5jdGlvbihldmVudCkge1xuICAgICAgICAgICAgdmFyIGZuID0gZnVuY3Rpb24oZSkge1xuICAgICAgICAgICAgICAgIGpCb25lKGVsKS5vZmYoZXZlbnQsIGZuKTtcbiAgICAgICAgICAgICAgICBjYWxsYmFjay5jYWxsKGVsLCBlKTtcbiAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgIGlmICghdGFyZ2V0KSB7XG4gICAgICAgICAgICAgICAgakJvbmUoZWwpLm9uKGV2ZW50LCBmbik7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIGpCb25lKGVsKS5vbihldmVudCwgdGFyZ2V0LCBmbik7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH07XG5cbiAgICBmb3IgKDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGFkZExpc3RlbmVyKHRoaXNbaV0pO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzO1xufTtcblxuZm4udHJpZ2dlciA9IGZ1bmN0aW9uKGV2ZW50KSB7XG4gICAgdmFyIGV2ZW50cyA9IFtdLFxuICAgICAgICBpID0gMCxcbiAgICAgICAgbGVuZ3RoID0gdGhpcy5sZW5ndGgsXG4gICAgICAgIGRpc3BhdGNoRXZlbnRzO1xuXG4gICAgaWYgKCFldmVudCkge1xuICAgICAgICByZXR1cm4gdGhpcztcbiAgICB9XG5cbiAgICBpZiAoaXNTdHJpbmcoZXZlbnQpKSB7XG4gICAgICAgIGV2ZW50cyA9IGV2ZW50LnNwbGl0KFwiIFwiKS5tYXAoZnVuY3Rpb24oZXZlbnQpIHtcbiAgICAgICAgICAgIHJldHVybiBqQm9uZS5FdmVudChldmVudCk7XG4gICAgICAgIH0pO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIGV2ZW50ID0gZXZlbnQgaW5zdGFuY2VvZiBFdmVudCA/IGV2ZW50IDogakJvbmUuRXZlbnQoZXZlbnQpO1xuICAgICAgICBldmVudHMgPSBbZXZlbnRdO1xuICAgIH1cblxuICAgIGRpc3BhdGNoRXZlbnRzID0gZnVuY3Rpb24oZWwpIHtcbiAgICAgICAgZXZlbnRzLmZvckVhY2goZnVuY3Rpb24oZXZlbnQpIHtcbiAgICAgICAgICAgIGlmICghZXZlbnQudHlwZSkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgZWwuZGlzcGF0Y2hFdmVudCAmJiBlbC5kaXNwYXRjaEV2ZW50KGV2ZW50KTtcbiAgICAgICAgfSk7XG4gICAgfTtcblxuICAgIGZvciAoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgZGlzcGF0Y2hFdmVudHModGhpc1tpXSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXM7XG59O1xuXG5mbi5vZmYgPSBmdW5jdGlvbihldmVudCwgZm4pIHtcbiAgICB2YXIgaSA9IDAsXG4gICAgICAgIGxlbmd0aCA9IHRoaXMubGVuZ3RoLFxuICAgICAgICByZW1vdmVMaXN0ZW5lciA9IGZ1bmN0aW9uKGV2ZW50cywgZXZlbnRUeXBlLCBpbmRleCwgZWwsIGUpIHtcbiAgICAgICAgICAgIHZhciBjYWxsYmFjaztcblxuICAgICAgICAgICAgLy8gZ2V0IGNhbGxiYWNrXG4gICAgICAgICAgICBpZiAoKGZuICYmIGUub3JpZ2luZm4gPT09IGZuKSB8fCAhZm4pIHtcbiAgICAgICAgICAgICAgICBjYWxsYmFjayA9IGUuZm47XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChldmVudHNbZXZlbnRUeXBlXVtpbmRleF0uZm4gPT09IGNhbGxiYWNrKSB7XG4gICAgICAgICAgICAgICAgZWwucmVtb3ZlRXZlbnRMaXN0ZW5lcihldmVudFR5cGUsIGNhbGxiYWNrKTtcblxuICAgICAgICAgICAgICAgIC8vIHJlbW92ZSBoYW5kbGVyIGZyb20gY2FjaGVcbiAgICAgICAgICAgICAgICBqQm9uZS5fY2FjaGUuZXZlbnRzW2pCb25lLmdldERhdGEoZWwpLmppZF1bZXZlbnRUeXBlXS5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICBldmVudHMsIG5hbWVzcGFjZSwgcmVtb3ZlTGlzdGVuZXJzLCBldmVudFR5cGU7XG5cbiAgICByZW1vdmVMaXN0ZW5lcnMgPSBmdW5jdGlvbihlbCkge1xuICAgICAgICB2YXIgbCwgZXZlbnRzQnlUeXBlLCBlO1xuXG4gICAgICAgIGV2ZW50cyA9IGpCb25lLmdldERhdGEoZWwpLmV2ZW50cztcblxuICAgICAgICBpZiAoIWV2ZW50cykge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gcmVtb3ZlIGFsbCBldmVudHNcbiAgICAgICAgaWYgKCFldmVudCAmJiBldmVudHMpIHtcbiAgICAgICAgICAgIHJldHVybiBrZXlzKGV2ZW50cykuZm9yRWFjaChmdW5jdGlvbihldmVudFR5cGUpIHtcbiAgICAgICAgICAgICAgICBldmVudHNCeVR5cGUgPSBldmVudHNbZXZlbnRUeXBlXTtcbiAgICAgICAgICAgICAgICBsID0gZXZlbnRzQnlUeXBlLmxlbmd0aDtcblxuICAgICAgICAgICAgICAgIHdoaWxlKGwtLSkge1xuICAgICAgICAgICAgICAgICAgICByZW1vdmVMaXN0ZW5lcihldmVudHMsIGV2ZW50VHlwZSwgbCwgZWwsIGV2ZW50c0J5VHlwZVtsXSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cblxuICAgICAgICBldmVudC5zcGxpdChcIiBcIikuZm9yRWFjaChmdW5jdGlvbihldmVudCkge1xuICAgICAgICAgICAgZXZlbnRUeXBlID0gZXZlbnQuc3BsaXQoXCIuXCIpWzBdO1xuICAgICAgICAgICAgbmFtZXNwYWNlID0gZXZlbnQuc3BsaXQoXCIuXCIpLnNwbGljZSgxKS5qb2luKFwiLlwiKTtcblxuICAgICAgICAgICAgLy8gcmVtb3ZlIG5hbWVkIGV2ZW50c1xuICAgICAgICAgICAgaWYgKGV2ZW50c1tldmVudFR5cGVdKSB7XG4gICAgICAgICAgICAgICAgZXZlbnRzQnlUeXBlID0gZXZlbnRzW2V2ZW50VHlwZV07XG4gICAgICAgICAgICAgICAgbCA9IGV2ZW50c0J5VHlwZS5sZW5ndGg7XG5cbiAgICAgICAgICAgICAgICB3aGlsZShsLS0pIHtcbiAgICAgICAgICAgICAgICAgICAgZSA9IGV2ZW50c0J5VHlwZVtsXTtcbiAgICAgICAgICAgICAgICAgICAgaWYgKCFuYW1lc3BhY2UgfHwgKG5hbWVzcGFjZSAmJiBlLm5hbWVzcGFjZSA9PT0gbmFtZXNwYWNlKSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgcmVtb3ZlTGlzdGVuZXIoZXZlbnRzLCBldmVudFR5cGUsIGwsIGVsLCBlKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIHJlbW92ZSBhbGwgbmFtZXNwYWNlZCBldmVudHNcbiAgICAgICAgICAgIGVsc2UgaWYgKG5hbWVzcGFjZSkge1xuICAgICAgICAgICAgICAgIGtleXMoZXZlbnRzKS5mb3JFYWNoKGZ1bmN0aW9uKGV2ZW50VHlwZSkge1xuICAgICAgICAgICAgICAgICAgICBldmVudHNCeVR5cGUgPSBldmVudHNbZXZlbnRUeXBlXTtcbiAgICAgICAgICAgICAgICAgICAgbCA9IGV2ZW50c0J5VHlwZS5sZW5ndGg7XG5cbiAgICAgICAgICAgICAgICAgICAgd2hpbGUobC0tKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBlID0gZXZlbnRzQnlUeXBlW2xdO1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGUubmFtZXNwYWNlLnNwbGl0KFwiLlwiKVswXSA9PT0gbmFtZXNwYWNlLnNwbGl0KFwiLlwiKVswXSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlbW92ZUxpc3RlbmVyKGV2ZW50cywgZXZlbnRUeXBlLCBsLCBlbCwgZSk7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgfTtcblxuICAgIGZvciAoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgcmVtb3ZlTGlzdGVuZXJzKHRoaXNbaV0pO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzO1xufTtcblxuZm4uZmluZCA9IGZ1bmN0aW9uKHNlbGVjdG9yKSB7XG4gICAgdmFyIHJlc3VsdHMgPSBbXSxcbiAgICAgICAgaSA9IDAsXG4gICAgICAgIGxlbmd0aCA9IHRoaXMubGVuZ3RoLFxuICAgICAgICBmaW5kZXIgPSBmdW5jdGlvbihlbCkge1xuICAgICAgICAgICAgaWYgKGlzRnVuY3Rpb24oZWwucXVlcnlTZWxlY3RvckFsbCkpIHtcbiAgICAgICAgICAgICAgICBbXS5mb3JFYWNoLmNhbGwoZWwucXVlcnlTZWxlY3RvckFsbChzZWxlY3RvciksIGZ1bmN0aW9uKGZvdW5kKSB7XG4gICAgICAgICAgICAgICAgICAgIHJlc3VsdHMucHVzaChmb3VuZCk7XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH07XG5cbiAgICBmb3IgKDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGZpbmRlcih0aGlzW2ldKTtcbiAgICB9XG5cbiAgICByZXR1cm4gakJvbmUocmVzdWx0cyk7XG59O1xuXG5mbi5nZXQgPSBmdW5jdGlvbihpbmRleCkge1xuICAgIHJldHVybiB0aGlzW2luZGV4XTtcbn07XG5cbmZuLmVxID0gZnVuY3Rpb24oaW5kZXgpIHtcbiAgICByZXR1cm4gakJvbmUodGhpc1tpbmRleF0pO1xufTtcblxuZm4ucGFyZW50ID0gZnVuY3Rpb24oKSB7XG4gICAgdmFyIHJlc3VsdHMgPSBbXSxcbiAgICAgICAgcGFyZW50LFxuICAgICAgICBpID0gMCxcbiAgICAgICAgbGVuZ3RoID0gdGhpcy5sZW5ndGg7XG5cbiAgICBmb3IgKDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGlmICghfnJlc3VsdHMuaW5kZXhPZihwYXJlbnQgPSB0aGlzW2ldLnBhcmVudEVsZW1lbnQpICYmIHBhcmVudCkge1xuICAgICAgICAgICAgcmVzdWx0cy5wdXNoKHBhcmVudCk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gakJvbmUocmVzdWx0cyk7XG59O1xuXG5mbi50b0FycmF5ID0gZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIHNsaWNlLmNhbGwodGhpcyk7XG59O1xuXG5mbi5pcyA9IGZ1bmN0aW9uKCkge1xuICAgIHZhciBhcmdzID0gYXJndW1lbnRzO1xuXG4gICAgcmV0dXJuIHRoaXMuc29tZShmdW5jdGlvbihlbCkge1xuICAgICAgICByZXR1cm4gZWwudGFnTmFtZS50b0xvd2VyQ2FzZSgpID09PSBhcmdzWzBdO1xuICAgIH0pO1xufTtcblxuZm4uaGFzID0gZnVuY3Rpb24oKSB7XG4gICAgdmFyIGFyZ3MgPSBhcmd1bWVudHM7XG5cbiAgICByZXR1cm4gdGhpcy5zb21lKGZ1bmN0aW9uKGVsKSB7XG4gICAgICAgIHJldHVybiBlbC5xdWVyeVNlbGVjdG9yQWxsKGFyZ3NbMF0pLmxlbmd0aDtcbiAgICB9KTtcbn07XG5cbmZuLmF0dHIgPSBmdW5jdGlvbihrZXksIHZhbHVlKSB7XG4gICAgdmFyIGFyZ3MgPSBhcmd1bWVudHMsXG4gICAgICAgIGkgPSAwLFxuICAgICAgICBsZW5ndGggPSB0aGlzLmxlbmd0aCxcbiAgICAgICAgc2V0dGVyO1xuXG4gICAgaWYgKGlzU3RyaW5nKGtleSkgJiYgYXJncy5sZW5ndGggPT09IDEpIHtcbiAgICAgICAgcmV0dXJuIHRoaXNbMF0gJiYgdGhpc1swXS5nZXRBdHRyaWJ1dGUoa2V5KTtcbiAgICB9XG5cbiAgICBpZiAoYXJncy5sZW5ndGggPT09IDIpIHtcbiAgICAgICAgc2V0dGVyID0gZnVuY3Rpb24oZWwpIHtcbiAgICAgICAgICAgIGVsLnNldEF0dHJpYnV0ZShrZXksIHZhbHVlKTtcbiAgICAgICAgfTtcbiAgICB9IGVsc2UgaWYgKGlzT2JqZWN0KGtleSkpIHtcbiAgICAgICAgc2V0dGVyID0gZnVuY3Rpb24oZWwpIHtcbiAgICAgICAgICAgIGtleXMoa2V5KS5mb3JFYWNoKGZ1bmN0aW9uKG5hbWUpIHtcbiAgICAgICAgICAgICAgICBlbC5zZXRBdHRyaWJ1dGUobmFtZSwga2V5W25hbWVdKTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9O1xuICAgIH1cblxuICAgIGZvciAoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgc2V0dGVyKHRoaXNbaV0pO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzO1xufTtcblxuZm4ucmVtb3ZlQXR0ciA9IGZ1bmN0aW9uKGtleSkge1xuICAgIHZhciBpID0gMCxcbiAgICAgICAgbGVuZ3RoID0gdGhpcy5sZW5ndGg7XG5cbiAgICBmb3IgKDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIHRoaXNbaV0ucmVtb3ZlQXR0cmlidXRlKGtleSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXM7XG59O1xuXG5mbi52YWwgPSBmdW5jdGlvbih2YWx1ZSkge1xuICAgIHZhciBpID0gMCxcbiAgICAgICAgbGVuZ3RoID0gdGhpcy5sZW5ndGg7XG5cbiAgICBpZiAoYXJndW1lbnRzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICByZXR1cm4gdGhpc1swXSAmJiB0aGlzWzBdLnZhbHVlO1xuICAgIH1cblxuICAgIGZvciAoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgdGhpc1tpXS52YWx1ZSA9IHZhbHVlO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzO1xufTtcblxuZm4uY3NzID0gZnVuY3Rpb24oa2V5LCB2YWx1ZSkge1xuICAgIHZhciBhcmdzID0gYXJndW1lbnRzLFxuICAgICAgICBpID0gMCxcbiAgICAgICAgbGVuZ3RoID0gdGhpcy5sZW5ndGgsXG4gICAgICAgIHNldHRlcjtcblxuICAgIC8vIEdldCBhdHRyaWJ1dGVcbiAgICBpZiAoaXNTdHJpbmcoa2V5KSAmJiBhcmdzLmxlbmd0aCA9PT0gMSkge1xuICAgICAgICByZXR1cm4gdGhpc1swXSAmJiB3aW4uZ2V0Q29tcHV0ZWRTdHlsZSh0aGlzWzBdKVtrZXldO1xuICAgIH1cblxuICAgIC8vIFNldCBhdHRyaWJ1dGVzXG4gICAgaWYgKGFyZ3MubGVuZ3RoID09PSAyKSB7XG4gICAgICAgIHNldHRlciA9IGZ1bmN0aW9uKGVsKSB7XG4gICAgICAgICAgICBlbC5zdHlsZVtrZXldID0gdmFsdWU7XG4gICAgICAgIH07XG4gICAgfSBlbHNlIGlmIChpc09iamVjdChrZXkpKSB7XG4gICAgICAgIHNldHRlciA9IGZ1bmN0aW9uKGVsKSB7XG4gICAgICAgICAgICBrZXlzKGtleSkuZm9yRWFjaChmdW5jdGlvbihuYW1lKSB7XG4gICAgICAgICAgICAgICAgZWwuc3R5bGVbbmFtZV0gPSBrZXlbbmFtZV07XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfTtcbiAgICB9XG5cbiAgICBmb3IgKDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIHNldHRlcih0aGlzW2ldKTtcbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcztcbn07XG5cbmZuLmRhdGEgPSBmdW5jdGlvbihrZXksIHZhbHVlKSB7XG4gICAgdmFyIGFyZ3MgPSBhcmd1bWVudHMsIGRhdGEgPSB7fSxcbiAgICAgICAgaSA9IDAsXG4gICAgICAgIGxlbmd0aCA9IHRoaXMubGVuZ3RoLFxuICAgICAgICBzZXR0ZXIsXG4gICAgICAgIHNldFZhbHVlID0gZnVuY3Rpb24oZWwsIGtleSwgdmFsdWUpIHtcbiAgICAgICAgICAgIGlmIChpc09iamVjdCh2YWx1ZSkpIHtcbiAgICAgICAgICAgICAgICBlbC5qZGF0YSA9IGVsLmpkYXRhIHx8IHt9O1xuICAgICAgICAgICAgICAgIGVsLmpkYXRhW2tleV0gPSB2YWx1ZTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgZWwuZGF0YXNldFtrZXldID0gdmFsdWU7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIGdldFZhbHVlID0gZnVuY3Rpb24odmFsdWUpIHtcbiAgICAgICAgICAgIGlmICh2YWx1ZSA9PT0gXCJ0cnVlXCIpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgICAgIH0gZWxzZSBpZiAodmFsdWUgPT09IFwiZmFsc2VcIikge1xuICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHZhbHVlO1xuICAgICAgICAgICAgfVxuICAgICAgICB9O1xuXG4gICAgLy8gR2V0IGFsbCBkYXRhXG4gICAgaWYgKGFyZ3MubGVuZ3RoID09PSAwKSB7XG4gICAgICAgIHRoaXNbMF0uamRhdGEgJiYgKGRhdGEgPSB0aGlzWzBdLmpkYXRhKTtcblxuICAgICAgICBrZXlzKHRoaXNbMF0uZGF0YXNldCkuZm9yRWFjaChmdW5jdGlvbihrZXkpIHtcbiAgICAgICAgICAgIGRhdGFba2V5XSA9IGdldFZhbHVlKHRoaXNbMF0uZGF0YXNldFtrZXldKTtcbiAgICAgICAgfSwgdGhpcyk7XG5cbiAgICAgICAgcmV0dXJuIGRhdGE7XG4gICAgfVxuICAgIC8vIEdldCBkYXRhIGJ5IG5hbWVcbiAgICBpZiAoYXJncy5sZW5ndGggPT09IDEgJiYgaXNTdHJpbmcoa2V5KSkge1xuICAgICAgICByZXR1cm4gdGhpc1swXSAmJiBnZXRWYWx1ZSh0aGlzWzBdLmRhdGFzZXRba2V5XSB8fCB0aGlzWzBdLmpkYXRhICYmIHRoaXNbMF0uamRhdGFba2V5XSk7XG4gICAgfVxuXG4gICAgLy8gU2V0IGRhdGFcbiAgICBpZiAoYXJncy5sZW5ndGggPT09IDEgJiYgaXNPYmplY3Qoa2V5KSkge1xuICAgICAgICBzZXR0ZXIgPSBmdW5jdGlvbihlbCkge1xuICAgICAgICAgICAga2V5cyhrZXkpLmZvckVhY2goZnVuY3Rpb24obmFtZSkge1xuICAgICAgICAgICAgICAgIHNldFZhbHVlKGVsLCBuYW1lLCBrZXlbbmFtZV0pO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgIH07XG4gICAgfSBlbHNlIGlmIChhcmdzLmxlbmd0aCA9PT0gMikge1xuICAgICAgICBzZXR0ZXIgPSBmdW5jdGlvbihlbCkge1xuICAgICAgICAgICAgc2V0VmFsdWUoZWwsIGtleSwgdmFsdWUpO1xuICAgICAgICB9O1xuICAgIH1cblxuICAgIGZvciAoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgc2V0dGVyKHRoaXNbaV0pO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzO1xufTtcblxuZm4ucmVtb3ZlRGF0YSA9IGZ1bmN0aW9uKGtleSkge1xuICAgIHZhciBpID0gMCxcbiAgICAgICAgbGVuZ3RoID0gdGhpcy5sZW5ndGgsXG4gICAgICAgIGpkYXRhLCBkYXRhc2V0O1xuXG4gICAgZm9yICg7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgICAgICBqZGF0YSA9IHRoaXNbaV0uamRhdGE7XG4gICAgICAgIGRhdGFzZXQgPSB0aGlzW2ldLmRhdGFzZXQ7XG5cbiAgICAgICAgaWYgKGtleSkge1xuICAgICAgICAgICAgamRhdGEgJiYgamRhdGFba2V5XSAmJiBkZWxldGUgamRhdGFba2V5XTtcbiAgICAgICAgICAgIGRlbGV0ZSBkYXRhc2V0W2tleV07XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBmb3IgKGtleSBpbiBqZGF0YSkge1xuICAgICAgICAgICAgICAgIGRlbGV0ZSBqZGF0YVtrZXldO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBmb3IgKGtleSBpbiBkYXRhc2V0KSB7XG4gICAgICAgICAgICAgICAgZGVsZXRlIGRhdGFzZXRba2V5XTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiB0aGlzO1xufTtcblxuZm4uaHRtbCA9IGZ1bmN0aW9uKHZhbHVlKSB7XG4gICAgdmFyIGFyZ3MgPSBhcmd1bWVudHMsXG4gICAgICAgIGVsO1xuXG4gICAgLy8gYWRkIEhUTUwgaW50byBlbGVtZW50c1xuICAgIGlmIChhcmdzLmxlbmd0aCA9PT0gMSAmJiB2YWx1ZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmVtcHR5KCkuYXBwZW5kKHZhbHVlKTtcbiAgICB9XG4gICAgLy8gZ2V0IEhUTUwgZnJvbSBlbGVtZW50XG4gICAgZWxzZSBpZiAoYXJncy5sZW5ndGggPT09IDAgJiYgKGVsID0gdGhpc1swXSkpIHtcbiAgICAgICAgcmV0dXJuIGVsLmlubmVySFRNTDtcbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcztcbn07XG5cbmZuLmFwcGVuZCA9IGZ1bmN0aW9uKGFwcGVuZGVkKSB7XG4gICAgdmFyIGkgPSAwLFxuICAgICAgICBsZW5ndGggPSB0aGlzLmxlbmd0aCxcbiAgICAgICAgc2V0dGVyO1xuXG4gICAgLy8gY3JlYXRlIGpCb25lIG9iamVjdCBhbmQgdGhlbiBhcHBlbmRcbiAgICBpZiAoaXNTdHJpbmcoYXBwZW5kZWQpICYmIHJxdWlja0V4cHIuZXhlYyhhcHBlbmRlZCkpIHtcbiAgICAgICAgYXBwZW5kZWQgPSBqQm9uZShhcHBlbmRlZCk7XG4gICAgfVxuICAgIC8vIGNyZWF0ZSB0ZXh0IG5vZGUgZm9yIGluc2VydGluZ1xuICAgIGVsc2UgaWYgKCFpc09iamVjdChhcHBlbmRlZCkpIHtcbiAgICAgICAgYXBwZW5kZWQgPSBkb2N1bWVudC5jcmVhdGVUZXh0Tm9kZShhcHBlbmRlZCk7XG4gICAgfVxuXG4gICAgYXBwZW5kZWQgPSBhcHBlbmRlZCBpbnN0YW5jZW9mIGpCb25lID8gYXBwZW5kZWQgOiBqQm9uZShhcHBlbmRlZCk7XG5cbiAgICBzZXR0ZXIgPSBmdW5jdGlvbihlbCwgaSkge1xuICAgICAgICBhcHBlbmRlZC5mb3JFYWNoKGZ1bmN0aW9uKG5vZGUpIHtcbiAgICAgICAgICAgIGlmIChpKSB7XG4gICAgICAgICAgICAgICAgZWwuYXBwZW5kQ2hpbGQobm9kZS5jbG9uZU5vZGUoKSk7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIGVsLmFwcGVuZENoaWxkKG5vZGUpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICB9O1xuXG4gICAgZm9yICg7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgICAgICBzZXR0ZXIodGhpc1tpXSwgaSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXM7XG59O1xuXG5mbi5hcHBlbmRUbyA9IGZ1bmN0aW9uKHRvKSB7XG4gICAgakJvbmUodG8pLmFwcGVuZCh0aGlzKTtcblxuICAgIHJldHVybiB0aGlzO1xufTtcblxuZm4uZW1wdHkgPSBmdW5jdGlvbigpIHtcbiAgICB2YXIgaSA9IDAsXG4gICAgICAgIGxlbmd0aCA9IHRoaXMubGVuZ3RoLFxuICAgICAgICBlbDtcblxuICAgIGZvciAoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgZWwgPSB0aGlzW2ldO1xuXG4gICAgICAgIHdoaWxlIChlbC5sYXN0Q2hpbGQpIHtcbiAgICAgICAgICAgIGVsLnJlbW92ZUNoaWxkKGVsLmxhc3RDaGlsZCk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcztcbn07XG5cbmZuLnJlbW92ZSA9IGZ1bmN0aW9uKCkge1xuICAgIHZhciBpID0gMCxcbiAgICAgICAgbGVuZ3RoID0gdGhpcy5sZW5ndGgsXG4gICAgICAgIGVsO1xuXG4gICAgLy8gcmVtb3ZlIGFsbCBsaXN0bmVyc1xuICAgIHRoaXMub2ZmKCk7XG5cbiAgICBmb3IgKDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGVsID0gdGhpc1tpXTtcblxuICAgICAgICAvLyByZW1vdmUgZGF0YSBhbmQgbm9kZXNcbiAgICAgICAgZGVsZXRlIGVsLmpkYXRhO1xuICAgICAgICBlbC5wYXJlbnROb2RlICYmIGVsLnBhcmVudE5vZGUucmVtb3ZlQ2hpbGQoZWwpO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzO1xufTtcblxuaWYgKHR5cGVvZiBtb2R1bGUgPT09IFwib2JqZWN0XCIgJiYgbW9kdWxlICYmIHR5cGVvZiBtb2R1bGUuZXhwb3J0cyA9PT0gXCJvYmplY3RcIikge1xuICAgIC8vIEV4cG9zZSBqQm9uZSBhcyBtb2R1bGUuZXhwb3J0cyBpbiBsb2FkZXJzIHRoYXQgaW1wbGVtZW50IHRoZSBOb2RlXG4gICAgLy8gbW9kdWxlIHBhdHRlcm4gKGluY2x1ZGluZyBicm93c2VyaWZ5KS4gRG8gbm90IGNyZWF0ZSB0aGUgZ2xvYmFsLCBzaW5jZVxuICAgIC8vIHRoZSB1c2VyIHdpbGwgYmUgc3RvcmluZyBpdCB0aGVtc2VsdmVzIGxvY2FsbHksIGFuZCBnbG9iYWxzIGFyZSBmcm93bmVkXG4gICAgLy8gdXBvbiBpbiB0aGUgTm9kZSBtb2R1bGUgd29ybGQuXG4gICAgbW9kdWxlLmV4cG9ydHMgPSBqQm9uZTtcbn1cbi8vIFJlZ2lzdGVyIGFzIGEgQU1EIG1vZHVsZVxuZWxzZSBpZiAodHlwZW9mIGRlZmluZSA9PT0gXCJmdW5jdGlvblwiICYmIGRlZmluZS5hbWQpIHtcbiAgICBkZWZpbmUoZnVuY3Rpb24oKSB7XG4gICAgICAgIHJldHVybiBqQm9uZTtcbiAgICB9KTtcblxuICAgIHdpbi5qQm9uZSA9IHdpbi4kID0gakJvbmU7XG59IGVsc2UgaWYgKHR5cGVvZiB3aW4gPT09IFwib2JqZWN0XCIgJiYgdHlwZW9mIHdpbi5kb2N1bWVudCA9PT0gXCJvYmplY3RcIikge1xuICAgIHdpbi5qQm9uZSA9IHdpbi4kID0gakJvbmU7XG59XG5cbn0od2luZG93KSk7XG4iLCJ2YXIgTW91c2U7XG5cbm1vZHVsZS5leHBvcnRzID0gTW91c2UgPSB7XG4gIHJlbDogZnVuY3Rpb24oZSkge1xuICAgIHZhciBtb3VzZVgsIG1vdXNlWSwgcmVjdCwgdGFyZ2V0O1xuICAgIG1vdXNlWCA9IGUub2Zmc2V0WDtcbiAgICBtb3VzZVkgPSBlLm9mZnNldFk7XG4gICAgaWYgKG1vdXNlWCA9PSBudWxsKSB7XG4gICAgICByZWN0ID0gdGFyZ2V0LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgICAgdGFyZ2V0ID0gZS50YXJnZXQgfHwgZS5zcmNFbGVtZW50O1xuICAgICAgaWYgKG1vdXNlWCA9PSBudWxsKSB7XG4gICAgICAgIG1vdXNlWCA9IGUuY2xpZW50WCAtIHJlY3QubGVmdDtcbiAgICAgICAgbW91c2VZID0gZS5jbGllbnRZIC0gcmVjdC50b3A7XG4gICAgICB9XG4gICAgICBpZiAobW91c2VYID09IG51bGwpIHtcbiAgICAgICAgbW91c2VYID0gZS5wYWdlWCAtIHRhcmdldC5vZmZzZXRMZWZ0O1xuICAgICAgICBtb3VzZVkgPSBlLnBhZ2VZIC0gdGFyZ2V0Lm9mZnNldFRvcDtcbiAgICAgIH1cbiAgICAgIGlmIChtb3VzZVggPT0gbnVsbCkge1xuICAgICAgICBjb25zb2xlLmxvZyhlLCBcIm5vIG1vdXNlIGV2ZW50IGRlZmluZWQuIHlvdXIgYnJvd3NlciBzdWNrc1wiKTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gW21vdXNlWCwgbW91c2VZXTtcbiAgfSxcbiAgYWJzOiBmdW5jdGlvbihlKSB7XG4gICAgdmFyIG1vdXNlWCwgbW91c2VZO1xuICAgIG1vdXNlWCA9IGUucGFnZVg7XG4gICAgbW91c2VZID0gZS5wYWdlWTtcbiAgICBpZiAobW91c2VYID09IG51bGwpIHtcbiAgICAgIG1vdXNlWCA9IGUubGF5ZXJYO1xuICAgICAgbW91c2VZID0gZS5sYXllclk7XG4gICAgfVxuICAgIGlmIChtb3VzZVggPT0gbnVsbCkge1xuICAgICAgbW91c2VYID0gZS5jbGllbnRYO1xuICAgICAgbW91c2VZID0gZS5jbGllbnRZO1xuICAgIH1cbiAgICBpZiAobW91c2VYID09IG51bGwpIHtcbiAgICAgIG1vdXNlWCA9IGUueDtcbiAgICAgIG1vdXNlWSA9IGUueTtcbiAgICB9XG4gICAgcmV0dXJuIFttb3VzZVgsIG1vdXNlWV07XG4gIH0sXG4gIHdoZWVsRGVsdGE6IGZ1bmN0aW9uKGUpIHtcbiAgICB2YXIgZGVsdGEsIGRpcjtcbiAgICBkZWx0YSA9IFtlLmRlbHRhWCwgZS5kZWx0YVldO1xuICAgIGlmIChkZWx0YVswXSA9PSBudWxsKSB7XG4gICAgICBkaXIgPSBNYXRoLmZsb29yKGUuZGV0YWlsIC8gMyk7XG4gICAgICBkZWx0YSA9IFswLCBlLm1vek1vdmVtZW50WCAqIGRpcl07XG4gICAgfVxuICAgIHJldHVybiBkZWx0YTtcbiAgfVxufTtcbiIsInZhciB3aW5kb3cgPSByZXF1aXJlKFwiZ2xvYmFsL3dpbmRvd1wiKVxudmFyIG9uY2UgPSByZXF1aXJlKFwib25jZVwiKVxudmFyIHBhcnNlSGVhZGVycyA9IHJlcXVpcmUoJ3BhcnNlLWhlYWRlcnMnKVxuXG52YXIgbWVzc2FnZXMgPSB7XG4gICAgXCIwXCI6IFwiSW50ZXJuYWwgWE1MSHR0cFJlcXVlc3QgRXJyb3JcIixcbiAgICBcIjRcIjogXCI0eHggQ2xpZW50IEVycm9yXCIsXG4gICAgXCI1XCI6IFwiNXh4IFNlcnZlciBFcnJvclwiXG59XG5cbnZhciBYSFIgPSB3aW5kb3cuWE1MSHR0cFJlcXVlc3QgfHwgbm9vcFxudmFyIFhEUiA9IFwid2l0aENyZWRlbnRpYWxzXCIgaW4gKG5ldyBYSFIoKSkgPyBYSFIgOiB3aW5kb3cuWERvbWFpblJlcXVlc3RcblxubW9kdWxlLmV4cG9ydHMgPSBjcmVhdGVYSFJcblxuZnVuY3Rpb24gY3JlYXRlWEhSKG9wdGlvbnMsIGNhbGxiYWNrKSB7XG4gICAgaWYgKHR5cGVvZiBvcHRpb25zID09PSBcInN0cmluZ1wiKSB7XG4gICAgICAgIG9wdGlvbnMgPSB7IHVyaTogb3B0aW9ucyB9XG4gICAgfVxuXG4gICAgb3B0aW9ucyA9IG9wdGlvbnMgfHwge31cbiAgICBjYWxsYmFjayA9IG9uY2UoY2FsbGJhY2spXG5cbiAgICB2YXIgeGhyID0gb3B0aW9ucy54aHIgfHwgbnVsbFxuXG4gICAgaWYgKCF4aHIpIHtcbiAgICAgICAgaWYgKG9wdGlvbnMuY29ycyB8fCBvcHRpb25zLnVzZVhEUikge1xuICAgICAgICAgICAgeGhyID0gbmV3IFhEUigpXG4gICAgICAgIH1lbHNle1xuICAgICAgICAgICAgeGhyID0gbmV3IFhIUigpXG4gICAgICAgIH1cbiAgICB9XG5cbiAgICB2YXIgdXJpID0geGhyLnVybCA9IG9wdGlvbnMudXJpIHx8IG9wdGlvbnMudXJsXG4gICAgdmFyIG1ldGhvZCA9IHhoci5tZXRob2QgPSBvcHRpb25zLm1ldGhvZCB8fCBcIkdFVFwiXG4gICAgdmFyIGJvZHkgPSBvcHRpb25zLmJvZHkgfHwgb3B0aW9ucy5kYXRhXG4gICAgdmFyIGhlYWRlcnMgPSB4aHIuaGVhZGVycyA9IG9wdGlvbnMuaGVhZGVycyB8fCB7fVxuICAgIHZhciBzeW5jID0gISFvcHRpb25zLnN5bmNcbiAgICB2YXIgaXNKc29uID0gZmFsc2VcbiAgICB2YXIga2V5XG4gICAgdmFyIGxvYWQgPSBvcHRpb25zLnJlc3BvbnNlID8gbG9hZFJlc3BvbnNlIDogbG9hZFhoclxuXG4gICAgaWYgKFwianNvblwiIGluIG9wdGlvbnMpIHtcbiAgICAgICAgaXNKc29uID0gdHJ1ZVxuICAgICAgICBoZWFkZXJzW1wiQWNjZXB0XCJdID0gXCJhcHBsaWNhdGlvbi9qc29uXCJcbiAgICAgICAgaWYgKG1ldGhvZCAhPT0gXCJHRVRcIiAmJiBtZXRob2QgIT09IFwiSEVBRFwiKSB7XG4gICAgICAgICAgICBoZWFkZXJzW1wiQ29udGVudC1UeXBlXCJdID0gXCJhcHBsaWNhdGlvbi9qc29uXCJcbiAgICAgICAgICAgIGJvZHkgPSBKU09OLnN0cmluZ2lmeShvcHRpb25zLmpzb24pXG4gICAgICAgIH1cbiAgICB9XG5cbiAgICB4aHIub25yZWFkeXN0YXRlY2hhbmdlID0gcmVhZHlzdGF0ZWNoYW5nZVxuICAgIHhoci5vbmxvYWQgPSBsb2FkXG4gICAgeGhyLm9uZXJyb3IgPSBlcnJvclxuICAgIC8vIElFOSBtdXN0IGhhdmUgb25wcm9ncmVzcyBiZSBzZXQgdG8gYSB1bmlxdWUgZnVuY3Rpb24uXG4gICAgeGhyLm9ucHJvZ3Jlc3MgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgIC8vIElFIG11c3QgZGllXG4gICAgfVxuICAgIC8vIGhhdGUgSUVcbiAgICB4aHIub250aW1lb3V0ID0gbm9vcFxuICAgIHhoci5vcGVuKG1ldGhvZCwgdXJpLCAhc3luYylcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vYmFja3dhcmQgY29tcGF0aWJpbGl0eVxuICAgIGlmIChvcHRpb25zLndpdGhDcmVkZW50aWFscyB8fCAob3B0aW9ucy5jb3JzICYmIG9wdGlvbnMud2l0aENyZWRlbnRpYWxzICE9PSBmYWxzZSkpIHtcbiAgICAgICAgeGhyLndpdGhDcmVkZW50aWFscyA9IHRydWVcbiAgICB9XG5cbiAgICAvLyBDYW5ub3Qgc2V0IHRpbWVvdXQgd2l0aCBzeW5jIHJlcXVlc3RcbiAgICBpZiAoIXN5bmMpIHtcbiAgICAgICAgeGhyLnRpbWVvdXQgPSBcInRpbWVvdXRcIiBpbiBvcHRpb25zID8gb3B0aW9ucy50aW1lb3V0IDogNTAwMFxuICAgIH1cblxuICAgIGlmICh4aHIuc2V0UmVxdWVzdEhlYWRlcikge1xuICAgICAgICBmb3Ioa2V5IGluIGhlYWRlcnMpe1xuICAgICAgICAgICAgaWYoaGVhZGVycy5oYXNPd25Qcm9wZXJ0eShrZXkpKXtcbiAgICAgICAgICAgICAgICB4aHIuc2V0UmVxdWVzdEhlYWRlcihrZXksIGhlYWRlcnNba2V5XSlcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH0gZWxzZSBpZiAob3B0aW9ucy5oZWFkZXJzKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcIkhlYWRlcnMgY2Fubm90IGJlIHNldCBvbiBhbiBYRG9tYWluUmVxdWVzdCBvYmplY3RcIilcbiAgICB9XG5cbiAgICBpZiAoXCJyZXNwb25zZVR5cGVcIiBpbiBvcHRpb25zKSB7XG4gICAgICAgIHhoci5yZXNwb25zZVR5cGUgPSBvcHRpb25zLnJlc3BvbnNlVHlwZVxuICAgIH1cbiAgICBcbiAgICBpZiAoXCJiZWZvcmVTZW5kXCIgaW4gb3B0aW9ucyAmJiBcbiAgICAgICAgdHlwZW9mIG9wdGlvbnMuYmVmb3JlU2VuZCA9PT0gXCJmdW5jdGlvblwiXG4gICAgKSB7XG4gICAgICAgIG9wdGlvbnMuYmVmb3JlU2VuZCh4aHIpXG4gICAgfVxuXG4gICAgeGhyLnNlbmQoYm9keSlcblxuICAgIHJldHVybiB4aHJcblxuICAgIGZ1bmN0aW9uIHJlYWR5c3RhdGVjaGFuZ2UoKSB7XG4gICAgICAgIGlmICh4aHIucmVhZHlTdGF0ZSA9PT0gNCkge1xuICAgICAgICAgICAgbG9hZCgpXG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBmdW5jdGlvbiBnZXRCb2R5KCkge1xuICAgICAgICAvLyBDaHJvbWUgd2l0aCByZXF1ZXN0VHlwZT1ibG9iIHRocm93cyBlcnJvcnMgYXJyb3VuZCB3aGVuIGV2ZW4gdGVzdGluZyBhY2Nlc3MgdG8gcmVzcG9uc2VUZXh0XG4gICAgICAgIHZhciBib2R5ID0gbnVsbFxuXG4gICAgICAgIGlmICh4aHIucmVzcG9uc2UpIHtcbiAgICAgICAgICAgIGJvZHkgPSB4aHIucmVzcG9uc2VcbiAgICAgICAgfSBlbHNlIGlmICh4aHIucmVzcG9uc2VUeXBlID09PSAndGV4dCcgfHwgIXhoci5yZXNwb25zZVR5cGUpIHtcbiAgICAgICAgICAgIGJvZHkgPSB4aHIucmVzcG9uc2VUZXh0IHx8IHhoci5yZXNwb25zZVhNTFxuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGlzSnNvbikge1xuICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICBib2R5ID0gSlNPTi5wYXJzZShib2R5KVxuICAgICAgICAgICAgfSBjYXRjaCAoZSkge31cbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBib2R5XG4gICAgfVxuXG4gICAgZnVuY3Rpb24gZ2V0U3RhdHVzQ29kZSgpIHtcbiAgICAgICAgcmV0dXJuIHhoci5zdGF0dXMgPT09IDEyMjMgPyAyMDQgOiB4aHIuc3RhdHVzXG4gICAgfVxuXG4gICAgLy8gaWYgd2UncmUgZ2V0dGluZyBhIG5vbmUtb2sgc3RhdHVzQ29kZSwgYnVpbGQgJiByZXR1cm4gYW4gZXJyb3JcbiAgICBmdW5jdGlvbiBlcnJvckZyb21TdGF0dXNDb2RlKHN0YXR1cykge1xuICAgICAgICB2YXIgZXJyb3IgPSBudWxsXG4gICAgICAgIGlmIChzdGF0dXMgPT09IDAgfHwgKHN0YXR1cyA+PSA0MDAgJiYgc3RhdHVzIDwgNjAwKSkge1xuICAgICAgICAgICAgdmFyIG1lc3NhZ2UgPSAodHlwZW9mIGJvZHkgPT09IFwic3RyaW5nXCIgPyBib2R5IDogZmFsc2UpIHx8XG4gICAgICAgICAgICAgICAgbWVzc2FnZXNbU3RyaW5nKHN0YXR1cykuY2hhckF0KDApXVxuICAgICAgICAgICAgZXJyb3IgPSBuZXcgRXJyb3IobWVzc2FnZSlcbiAgICAgICAgICAgIGVycm9yLnN0YXR1c0NvZGUgPSBzdGF0dXNcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBlcnJvclxuICAgIH1cblxuICAgIC8vIHdpbGwgbG9hZCB0aGUgZGF0YSAmIHByb2Nlc3MgdGhlIHJlc3BvbnNlIGluIGEgc3BlY2lhbCByZXNwb25zZSBvYmplY3RcbiAgICBmdW5jdGlvbiBsb2FkUmVzcG9uc2UoKSB7XG4gICAgICAgIHZhciBzdGF0dXMgPSBnZXRTdGF0dXNDb2RlKClcbiAgICAgICAgdmFyIGVycm9yID0gZXJyb3JGcm9tU3RhdHVzQ29kZShzdGF0dXMpXG4gICAgICAgIHZhciByZXNwb25zZSA9IHtcbiAgICAgICAgICAgIGJvZHk6IGdldEJvZHkoKSxcbiAgICAgICAgICAgIHN0YXR1c0NvZGU6IHN0YXR1cyxcbiAgICAgICAgICAgIHN0YXR1c1RleHQ6IHhoci5zdGF0dXNUZXh0LFxuICAgICAgICAgICAgcmF3OiB4aHJcbiAgICAgICAgfVxuICAgICAgICBpZih4aHIuZ2V0QWxsUmVzcG9uc2VIZWFkZXJzKXsgLy9yZW1lbWJlciB4aHIgY2FuIGluIGZhY3QgYmUgWERSIGZvciBDT1JTIGluIElFXG4gICAgICAgICAgICByZXNwb25zZS5oZWFkZXJzID0gcGFyc2VIZWFkZXJzKHhoci5nZXRBbGxSZXNwb25zZUhlYWRlcnMoKSlcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHJlc3BvbnNlLmhlYWRlcnMgPSB7fVxuICAgICAgICB9XG5cbiAgICAgICAgY2FsbGJhY2soZXJyb3IsIHJlc3BvbnNlLCByZXNwb25zZS5ib2R5KVxuICAgIH1cblxuICAgIC8vIHdpbGwgbG9hZCB0aGUgZGF0YSBhbmQgYWRkIHNvbWUgcmVzcG9uc2UgcHJvcGVydGllcyB0byB0aGUgc291cmNlIHhoclxuICAgIC8vIGFuZCB0aGVuIHJlc3BvbmQgd2l0aCB0aGF0XG4gICAgZnVuY3Rpb24gbG9hZFhocigpIHtcbiAgICAgICAgdmFyIHN0YXR1cyA9IGdldFN0YXR1c0NvZGUoKVxuICAgICAgICB2YXIgZXJyb3IgPSBlcnJvckZyb21TdGF0dXNDb2RlKHN0YXR1cylcblxuICAgICAgICB4aHIuc3RhdHVzID0geGhyLnN0YXR1c0NvZGUgPSBzdGF0dXNcbiAgICAgICAgeGhyLmJvZHkgPSBnZXRCb2R5KClcbiAgICAgICAgeGhyLmhlYWRlcnMgPSBwYXJzZUhlYWRlcnMoeGhyLmdldEFsbFJlc3BvbnNlSGVhZGVycygpKVxuXG4gICAgICAgIGNhbGxiYWNrKGVycm9yLCB4aHIsIHhoci5ib2R5KVxuICAgIH1cblxuICAgIGZ1bmN0aW9uIGVycm9yKGV2dCkge1xuICAgICAgICBjYWxsYmFjayhldnQsIHhocilcbiAgICB9XG59XG5cblxuZnVuY3Rpb24gbm9vcCgpIHt9XG4iLCIoZnVuY3Rpb24gKGdsb2JhbCl7XG5pZiAodHlwZW9mIHdpbmRvdyAhPT0gXCJ1bmRlZmluZWRcIikge1xuICAgIG1vZHVsZS5leHBvcnRzID0gd2luZG93O1xufSBlbHNlIGlmICh0eXBlb2YgZ2xvYmFsICE9PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgbW9kdWxlLmV4cG9ydHMgPSBnbG9iYWw7XG59IGVsc2UgaWYgKHR5cGVvZiBzZWxmICE9PSBcInVuZGVmaW5lZFwiKXtcbiAgICBtb2R1bGUuZXhwb3J0cyA9IHNlbGY7XG59IGVsc2Uge1xuICAgIG1vZHVsZS5leHBvcnRzID0ge307XG59XG5cbn0pLmNhbGwodGhpcyx0eXBlb2YgZ2xvYmFsICE9PSBcInVuZGVmaW5lZFwiID8gZ2xvYmFsIDogdHlwZW9mIHNlbGYgIT09IFwidW5kZWZpbmVkXCIgPyBzZWxmIDogdHlwZW9mIHdpbmRvdyAhPT0gXCJ1bmRlZmluZWRcIiA/IHdpbmRvdyA6IHt9KSIsIm1vZHVsZS5leHBvcnRzID0gb25jZVxuXG5vbmNlLnByb3RvID0gb25jZShmdW5jdGlvbiAoKSB7XG4gIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShGdW5jdGlvbi5wcm90b3R5cGUsICdvbmNlJywge1xuICAgIHZhbHVlOiBmdW5jdGlvbiAoKSB7XG4gICAgICByZXR1cm4gb25jZSh0aGlzKVxuICAgIH0sXG4gICAgY29uZmlndXJhYmxlOiB0cnVlXG4gIH0pXG59KVxuXG5mdW5jdGlvbiBvbmNlIChmbikge1xuICB2YXIgY2FsbGVkID0gZmFsc2VcbiAgcmV0dXJuIGZ1bmN0aW9uICgpIHtcbiAgICBpZiAoY2FsbGVkKSByZXR1cm5cbiAgICBjYWxsZWQgPSB0cnVlXG4gICAgcmV0dXJuIGZuLmFwcGx5KHRoaXMsIGFyZ3VtZW50cylcbiAgfVxufVxuIiwidmFyIGlzRnVuY3Rpb24gPSByZXF1aXJlKCdpcy1mdW5jdGlvbicpXG5cbm1vZHVsZS5leHBvcnRzID0gZm9yRWFjaFxuXG52YXIgdG9TdHJpbmcgPSBPYmplY3QucHJvdG90eXBlLnRvU3RyaW5nXG52YXIgaGFzT3duUHJvcGVydHkgPSBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5XG5cbmZ1bmN0aW9uIGZvckVhY2gobGlzdCwgaXRlcmF0b3IsIGNvbnRleHQpIHtcbiAgICBpZiAoIWlzRnVuY3Rpb24oaXRlcmF0b3IpKSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ2l0ZXJhdG9yIG11c3QgYmUgYSBmdW5jdGlvbicpXG4gICAgfVxuXG4gICAgaWYgKGFyZ3VtZW50cy5sZW5ndGggPCAzKSB7XG4gICAgICAgIGNvbnRleHQgPSB0aGlzXG4gICAgfVxuICAgIFxuICAgIGlmICh0b1N0cmluZy5jYWxsKGxpc3QpID09PSAnW29iamVjdCBBcnJheV0nKVxuICAgICAgICBmb3JFYWNoQXJyYXkobGlzdCwgaXRlcmF0b3IsIGNvbnRleHQpXG4gICAgZWxzZSBpZiAodHlwZW9mIGxpc3QgPT09ICdzdHJpbmcnKVxuICAgICAgICBmb3JFYWNoU3RyaW5nKGxpc3QsIGl0ZXJhdG9yLCBjb250ZXh0KVxuICAgIGVsc2VcbiAgICAgICAgZm9yRWFjaE9iamVjdChsaXN0LCBpdGVyYXRvciwgY29udGV4dClcbn1cblxuZnVuY3Rpb24gZm9yRWFjaEFycmF5KGFycmF5LCBpdGVyYXRvciwgY29udGV4dCkge1xuICAgIGZvciAodmFyIGkgPSAwLCBsZW4gPSBhcnJheS5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICBpZiAoaGFzT3duUHJvcGVydHkuY2FsbChhcnJheSwgaSkpIHtcbiAgICAgICAgICAgIGl0ZXJhdG9yLmNhbGwoY29udGV4dCwgYXJyYXlbaV0sIGksIGFycmF5KVxuICAgICAgICB9XG4gICAgfVxufVxuXG5mdW5jdGlvbiBmb3JFYWNoU3RyaW5nKHN0cmluZywgaXRlcmF0b3IsIGNvbnRleHQpIHtcbiAgICBmb3IgKHZhciBpID0gMCwgbGVuID0gc3RyaW5nLmxlbmd0aDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgIC8vIG5vIHN1Y2ggdGhpbmcgYXMgYSBzcGFyc2Ugc3RyaW5nLlxuICAgICAgICBpdGVyYXRvci5jYWxsKGNvbnRleHQsIHN0cmluZy5jaGFyQXQoaSksIGksIHN0cmluZylcbiAgICB9XG59XG5cbmZ1bmN0aW9uIGZvckVhY2hPYmplY3Qob2JqZWN0LCBpdGVyYXRvciwgY29udGV4dCkge1xuICAgIGZvciAodmFyIGsgaW4gb2JqZWN0KSB7XG4gICAgICAgIGlmIChoYXNPd25Qcm9wZXJ0eS5jYWxsKG9iamVjdCwgaykpIHtcbiAgICAgICAgICAgIGl0ZXJhdG9yLmNhbGwoY29udGV4dCwgb2JqZWN0W2tdLCBrLCBvYmplY3QpXG4gICAgICAgIH1cbiAgICB9XG59XG4iLCJtb2R1bGUuZXhwb3J0cyA9IGlzRnVuY3Rpb25cblxudmFyIHRvU3RyaW5nID0gT2JqZWN0LnByb3RvdHlwZS50b1N0cmluZ1xuXG5mdW5jdGlvbiBpc0Z1bmN0aW9uIChmbikge1xuICB2YXIgc3RyaW5nID0gdG9TdHJpbmcuY2FsbChmbilcbiAgcmV0dXJuIHN0cmluZyA9PT0gJ1tvYmplY3QgRnVuY3Rpb25dJyB8fFxuICAgICh0eXBlb2YgZm4gPT09ICdmdW5jdGlvbicgJiYgc3RyaW5nICE9PSAnW29iamVjdCBSZWdFeHBdJykgfHxcbiAgICAodHlwZW9mIHdpbmRvdyAhPT0gJ3VuZGVmaW5lZCcgJiZcbiAgICAgLy8gSUU4IGFuZCBiZWxvd1xuICAgICAoZm4gPT09IHdpbmRvdy5zZXRUaW1lb3V0IHx8XG4gICAgICBmbiA9PT0gd2luZG93LmFsZXJ0IHx8XG4gICAgICBmbiA9PT0gd2luZG93LmNvbmZpcm0gfHxcbiAgICAgIGZuID09PSB3aW5kb3cucHJvbXB0KSlcbn07XG4iLCJcbmV4cG9ydHMgPSBtb2R1bGUuZXhwb3J0cyA9IHRyaW07XG5cbmZ1bmN0aW9uIHRyaW0oc3RyKXtcbiAgcmV0dXJuIHN0ci5yZXBsYWNlKC9eXFxzKnxcXHMqJC9nLCAnJyk7XG59XG5cbmV4cG9ydHMubGVmdCA9IGZ1bmN0aW9uKHN0cil7XG4gIHJldHVybiBzdHIucmVwbGFjZSgvXlxccyovLCAnJyk7XG59O1xuXG5leHBvcnRzLnJpZ2h0ID0gZnVuY3Rpb24oc3RyKXtcbiAgcmV0dXJuIHN0ci5yZXBsYWNlKC9cXHMqJC8sICcnKTtcbn07XG4iLCJ2YXIgdHJpbSA9IHJlcXVpcmUoJ3RyaW0nKVxuICAsIGZvckVhY2ggPSByZXF1aXJlKCdmb3ItZWFjaCcpXG4gICwgaXNBcnJheSA9IGZ1bmN0aW9uKGFyZykge1xuICAgICAgcmV0dXJuIE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcuY2FsbChhcmcpID09PSAnW29iamVjdCBBcnJheV0nO1xuICAgIH1cblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAoaGVhZGVycykge1xuICBpZiAoIWhlYWRlcnMpXG4gICAgcmV0dXJuIHt9XG5cbiAgdmFyIHJlc3VsdCA9IHt9XG5cbiAgZm9yRWFjaChcbiAgICAgIHRyaW0oaGVhZGVycykuc3BsaXQoJ1xcbicpXG4gICAgLCBmdW5jdGlvbiAocm93KSB7XG4gICAgICAgIHZhciBpbmRleCA9IHJvdy5pbmRleE9mKCc6JylcbiAgICAgICAgICAsIGtleSA9IHRyaW0ocm93LnNsaWNlKDAsIGluZGV4KSkudG9Mb3dlckNhc2UoKVxuICAgICAgICAgICwgdmFsdWUgPSB0cmltKHJvdy5zbGljZShpbmRleCArIDEpKVxuXG4gICAgICAgIGlmICh0eXBlb2YocmVzdWx0W2tleV0pID09PSAndW5kZWZpbmVkJykge1xuICAgICAgICAgIHJlc3VsdFtrZXldID0gdmFsdWVcbiAgICAgICAgfSBlbHNlIGlmIChpc0FycmF5KHJlc3VsdFtrZXldKSkge1xuICAgICAgICAgIHJlc3VsdFtrZXldLnB1c2godmFsdWUpXG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgcmVzdWx0W2tleV0gPSBbIHJlc3VsdFtrZXldLCB2YWx1ZSBdXG4gICAgICAgIH1cbiAgICAgIH1cbiAgKVxuXG4gIHJldHVybiByZXN1bHRcbn0iLCIvLyAgICAgVW5kZXJzY29yZS5qcyAxLjcuMFxuLy8gICAgIGh0dHA6Ly91bmRlcnNjb3JlanMub3JnXG4vLyAgICAgKGMpIDIwMDktMjAxNCBKZXJlbXkgQXNoa2VuYXMsIERvY3VtZW50Q2xvdWQgYW5kIEludmVzdGlnYXRpdmUgUmVwb3J0ZXJzICYgRWRpdG9yc1xuLy8gICAgIFVuZGVyc2NvcmUgbWF5IGJlIGZyZWVseSBkaXN0cmlidXRlZCB1bmRlciB0aGUgTUlUIGxpY2Vuc2UuXG5cbihmdW5jdGlvbigpIHtcblxuICAvLyBCYXNlbGluZSBzZXR1cFxuICAvLyAtLS0tLS0tLS0tLS0tLVxuXG4gIC8vIEVzdGFibGlzaCB0aGUgcm9vdCBvYmplY3QsIGB3aW5kb3dgIGluIHRoZSBicm93c2VyLCBvciBgZXhwb3J0c2Agb24gdGhlIHNlcnZlci5cbiAgdmFyIHJvb3QgPSB0aGlzO1xuXG4gIC8vIFNhdmUgdGhlIHByZXZpb3VzIHZhbHVlIG9mIHRoZSBgX2AgdmFyaWFibGUuXG4gIHZhciBwcmV2aW91c1VuZGVyc2NvcmUgPSByb290Ll87XG5cbiAgLy8gU2F2ZSBieXRlcyBpbiB0aGUgbWluaWZpZWQgKGJ1dCBub3QgZ3ppcHBlZCkgdmVyc2lvbjpcbiAgdmFyIEFycmF5UHJvdG8gPSBBcnJheS5wcm90b3R5cGUsIE9ialByb3RvID0gT2JqZWN0LnByb3RvdHlwZSwgRnVuY1Byb3RvID0gRnVuY3Rpb24ucHJvdG90eXBlO1xuXG4gIC8vIENyZWF0ZSBxdWljayByZWZlcmVuY2UgdmFyaWFibGVzIGZvciBzcGVlZCBhY2Nlc3MgdG8gY29yZSBwcm90b3R5cGVzLlxuICB2YXJcbiAgICBwdXNoICAgICAgICAgICAgID0gQXJyYXlQcm90by5wdXNoLFxuICAgIHNsaWNlICAgICAgICAgICAgPSBBcnJheVByb3RvLnNsaWNlLFxuICAgIGNvbmNhdCAgICAgICAgICAgPSBBcnJheVByb3RvLmNvbmNhdCxcbiAgICB0b1N0cmluZyAgICAgICAgID0gT2JqUHJvdG8udG9TdHJpbmcsXG4gICAgaGFzT3duUHJvcGVydHkgICA9IE9ialByb3RvLmhhc093blByb3BlcnR5O1xuXG4gIC8vIEFsbCAqKkVDTUFTY3JpcHQgNSoqIG5hdGl2ZSBmdW5jdGlvbiBpbXBsZW1lbnRhdGlvbnMgdGhhdCB3ZSBob3BlIHRvIHVzZVxuICAvLyBhcmUgZGVjbGFyZWQgaGVyZS5cbiAgdmFyXG4gICAgbmF0aXZlSXNBcnJheSAgICAgID0gQXJyYXkuaXNBcnJheSxcbiAgICBuYXRpdmVLZXlzICAgICAgICAgPSBPYmplY3Qua2V5cyxcbiAgICBuYXRpdmVCaW5kICAgICAgICAgPSBGdW5jUHJvdG8uYmluZDtcblxuICAvLyBDcmVhdGUgYSBzYWZlIHJlZmVyZW5jZSB0byB0aGUgVW5kZXJzY29yZSBvYmplY3QgZm9yIHVzZSBiZWxvdy5cbiAgdmFyIF8gPSBmdW5jdGlvbihvYmopIHtcbiAgICBpZiAob2JqIGluc3RhbmNlb2YgXykgcmV0dXJuIG9iajtcbiAgICBpZiAoISh0aGlzIGluc3RhbmNlb2YgXykpIHJldHVybiBuZXcgXyhvYmopO1xuICAgIHRoaXMuX3dyYXBwZWQgPSBvYmo7XG4gIH07XG5cbiAgLy8gRXhwb3J0IHRoZSBVbmRlcnNjb3JlIG9iamVjdCBmb3IgKipOb2RlLmpzKiosIHdpdGhcbiAgLy8gYmFja3dhcmRzLWNvbXBhdGliaWxpdHkgZm9yIHRoZSBvbGQgYHJlcXVpcmUoKWAgQVBJLiBJZiB3ZSdyZSBpblxuICAvLyB0aGUgYnJvd3NlciwgYWRkIGBfYCBhcyBhIGdsb2JhbCBvYmplY3QuXG4gIGlmICh0eXBlb2YgZXhwb3J0cyAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICBpZiAodHlwZW9mIG1vZHVsZSAhPT0gJ3VuZGVmaW5lZCcgJiYgbW9kdWxlLmV4cG9ydHMpIHtcbiAgICAgIGV4cG9ydHMgPSBtb2R1bGUuZXhwb3J0cyA9IF87XG4gICAgfVxuICAgIGV4cG9ydHMuXyA9IF87XG4gIH0gZWxzZSB7XG4gICAgcm9vdC5fID0gXztcbiAgfVxuXG4gIC8vIEN1cnJlbnQgdmVyc2lvbi5cbiAgXy5WRVJTSU9OID0gJzEuNy4wJztcblxuICAvLyBJbnRlcm5hbCBmdW5jdGlvbiB0aGF0IHJldHVybnMgYW4gZWZmaWNpZW50IChmb3IgY3VycmVudCBlbmdpbmVzKSB2ZXJzaW9uXG4gIC8vIG9mIHRoZSBwYXNzZWQtaW4gY2FsbGJhY2ssIHRvIGJlIHJlcGVhdGVkbHkgYXBwbGllZCBpbiBvdGhlciBVbmRlcnNjb3JlXG4gIC8vIGZ1bmN0aW9ucy5cbiAgdmFyIGNyZWF0ZUNhbGxiYWNrID0gZnVuY3Rpb24oZnVuYywgY29udGV4dCwgYXJnQ291bnQpIHtcbiAgICBpZiAoY29udGV4dCA9PT0gdm9pZCAwKSByZXR1cm4gZnVuYztcbiAgICBzd2l0Y2ggKGFyZ0NvdW50ID09IG51bGwgPyAzIDogYXJnQ291bnQpIHtcbiAgICAgIGNhc2UgMTogcmV0dXJuIGZ1bmN0aW9uKHZhbHVlKSB7XG4gICAgICAgIHJldHVybiBmdW5jLmNhbGwoY29udGV4dCwgdmFsdWUpO1xuICAgICAgfTtcbiAgICAgIGNhc2UgMjogcmV0dXJuIGZ1bmN0aW9uKHZhbHVlLCBvdGhlcikge1xuICAgICAgICByZXR1cm4gZnVuYy5jYWxsKGNvbnRleHQsIHZhbHVlLCBvdGhlcik7XG4gICAgICB9O1xuICAgICAgY2FzZSAzOiByZXR1cm4gZnVuY3Rpb24odmFsdWUsIGluZGV4LCBjb2xsZWN0aW9uKSB7XG4gICAgICAgIHJldHVybiBmdW5jLmNhbGwoY29udGV4dCwgdmFsdWUsIGluZGV4LCBjb2xsZWN0aW9uKTtcbiAgICAgIH07XG4gICAgICBjYXNlIDQ6IHJldHVybiBmdW5jdGlvbihhY2N1bXVsYXRvciwgdmFsdWUsIGluZGV4LCBjb2xsZWN0aW9uKSB7XG4gICAgICAgIHJldHVybiBmdW5jLmNhbGwoY29udGV4dCwgYWNjdW11bGF0b3IsIHZhbHVlLCBpbmRleCwgY29sbGVjdGlvbik7XG4gICAgICB9O1xuICAgIH1cbiAgICByZXR1cm4gZnVuY3Rpb24oKSB7XG4gICAgICByZXR1cm4gZnVuYy5hcHBseShjb250ZXh0LCBhcmd1bWVudHMpO1xuICAgIH07XG4gIH07XG5cbiAgLy8gQSBtb3N0bHktaW50ZXJuYWwgZnVuY3Rpb24gdG8gZ2VuZXJhdGUgY2FsbGJhY2tzIHRoYXQgY2FuIGJlIGFwcGxpZWRcbiAgLy8gdG8gZWFjaCBlbGVtZW50IGluIGEgY29sbGVjdGlvbiwgcmV0dXJuaW5nIHRoZSBkZXNpcmVkIHJlc3VsdCDigJQgZWl0aGVyXG4gIC8vIGlkZW50aXR5LCBhbiBhcmJpdHJhcnkgY2FsbGJhY2ssIGEgcHJvcGVydHkgbWF0Y2hlciwgb3IgYSBwcm9wZXJ0eSBhY2Nlc3Nvci5cbiAgXy5pdGVyYXRlZSA9IGZ1bmN0aW9uKHZhbHVlLCBjb250ZXh0LCBhcmdDb3VudCkge1xuICAgIGlmICh2YWx1ZSA9PSBudWxsKSByZXR1cm4gXy5pZGVudGl0eTtcbiAgICBpZiAoXy5pc0Z1bmN0aW9uKHZhbHVlKSkgcmV0dXJuIGNyZWF0ZUNhbGxiYWNrKHZhbHVlLCBjb250ZXh0LCBhcmdDb3VudCk7XG4gICAgaWYgKF8uaXNPYmplY3QodmFsdWUpKSByZXR1cm4gXy5tYXRjaGVzKHZhbHVlKTtcbiAgICByZXR1cm4gXy5wcm9wZXJ0eSh2YWx1ZSk7XG4gIH07XG5cbiAgLy8gQ29sbGVjdGlvbiBGdW5jdGlvbnNcbiAgLy8gLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuICAvLyBUaGUgY29ybmVyc3RvbmUsIGFuIGBlYWNoYCBpbXBsZW1lbnRhdGlvbiwgYWthIGBmb3JFYWNoYC5cbiAgLy8gSGFuZGxlcyByYXcgb2JqZWN0cyBpbiBhZGRpdGlvbiB0byBhcnJheS1saWtlcy4gVHJlYXRzIGFsbFxuICAvLyBzcGFyc2UgYXJyYXktbGlrZXMgYXMgaWYgdGhleSB3ZXJlIGRlbnNlLlxuICBfLmVhY2ggPSBfLmZvckVhY2ggPSBmdW5jdGlvbihvYmosIGl0ZXJhdGVlLCBjb250ZXh0KSB7XG4gICAgaWYgKG9iaiA9PSBudWxsKSByZXR1cm4gb2JqO1xuICAgIGl0ZXJhdGVlID0gY3JlYXRlQ2FsbGJhY2soaXRlcmF0ZWUsIGNvbnRleHQpO1xuICAgIHZhciBpLCBsZW5ndGggPSBvYmoubGVuZ3RoO1xuICAgIGlmIChsZW5ndGggPT09ICtsZW5ndGgpIHtcbiAgICAgIGZvciAoaSA9IDA7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgICAgICBpdGVyYXRlZShvYmpbaV0sIGksIG9iaik7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIHZhciBrZXlzID0gXy5rZXlzKG9iaik7XG4gICAgICBmb3IgKGkgPSAwLCBsZW5ndGggPSBrZXlzLmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGl0ZXJhdGVlKG9ialtrZXlzW2ldXSwga2V5c1tpXSwgb2JqKTtcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIG9iajtcbiAgfTtcblxuICAvLyBSZXR1cm4gdGhlIHJlc3VsdHMgb2YgYXBwbHlpbmcgdGhlIGl0ZXJhdGVlIHRvIGVhY2ggZWxlbWVudC5cbiAgXy5tYXAgPSBfLmNvbGxlY3QgPSBmdW5jdGlvbihvYmosIGl0ZXJhdGVlLCBjb250ZXh0KSB7XG4gICAgaWYgKG9iaiA9PSBudWxsKSByZXR1cm4gW107XG4gICAgaXRlcmF0ZWUgPSBfLml0ZXJhdGVlKGl0ZXJhdGVlLCBjb250ZXh0KTtcbiAgICB2YXIga2V5cyA9IG9iai5sZW5ndGggIT09ICtvYmoubGVuZ3RoICYmIF8ua2V5cyhvYmopLFxuICAgICAgICBsZW5ndGggPSAoa2V5cyB8fCBvYmopLmxlbmd0aCxcbiAgICAgICAgcmVzdWx0cyA9IEFycmF5KGxlbmd0aCksXG4gICAgICAgIGN1cnJlbnRLZXk7XG4gICAgZm9yICh2YXIgaW5kZXggPSAwOyBpbmRleCA8IGxlbmd0aDsgaW5kZXgrKykge1xuICAgICAgY3VycmVudEtleSA9IGtleXMgPyBrZXlzW2luZGV4XSA6IGluZGV4O1xuICAgICAgcmVzdWx0c1tpbmRleF0gPSBpdGVyYXRlZShvYmpbY3VycmVudEtleV0sIGN1cnJlbnRLZXksIG9iaik7XG4gICAgfVxuICAgIHJldHVybiByZXN1bHRzO1xuICB9O1xuXG4gIHZhciByZWR1Y2VFcnJvciA9ICdSZWR1Y2Ugb2YgZW1wdHkgYXJyYXkgd2l0aCBubyBpbml0aWFsIHZhbHVlJztcblxuICAvLyAqKlJlZHVjZSoqIGJ1aWxkcyB1cCBhIHNpbmdsZSByZXN1bHQgZnJvbSBhIGxpc3Qgb2YgdmFsdWVzLCBha2EgYGluamVjdGAsXG4gIC8vIG9yIGBmb2xkbGAuXG4gIF8ucmVkdWNlID0gXy5mb2xkbCA9IF8uaW5qZWN0ID0gZnVuY3Rpb24ob2JqLCBpdGVyYXRlZSwgbWVtbywgY29udGV4dCkge1xuICAgIGlmIChvYmogPT0gbnVsbCkgb2JqID0gW107XG4gICAgaXRlcmF0ZWUgPSBjcmVhdGVDYWxsYmFjayhpdGVyYXRlZSwgY29udGV4dCwgNCk7XG4gICAgdmFyIGtleXMgPSBvYmoubGVuZ3RoICE9PSArb2JqLmxlbmd0aCAmJiBfLmtleXMob2JqKSxcbiAgICAgICAgbGVuZ3RoID0gKGtleXMgfHwgb2JqKS5sZW5ndGgsXG4gICAgICAgIGluZGV4ID0gMCwgY3VycmVudEtleTtcbiAgICBpZiAoYXJndW1lbnRzLmxlbmd0aCA8IDMpIHtcbiAgICAgIGlmICghbGVuZ3RoKSB0aHJvdyBuZXcgVHlwZUVycm9yKHJlZHVjZUVycm9yKTtcbiAgICAgIG1lbW8gPSBvYmpba2V5cyA/IGtleXNbaW5kZXgrK10gOiBpbmRleCsrXTtcbiAgICB9XG4gICAgZm9yICg7IGluZGV4IDwgbGVuZ3RoOyBpbmRleCsrKSB7XG4gICAgICBjdXJyZW50S2V5ID0ga2V5cyA/IGtleXNbaW5kZXhdIDogaW5kZXg7XG4gICAgICBtZW1vID0gaXRlcmF0ZWUobWVtbywgb2JqW2N1cnJlbnRLZXldLCBjdXJyZW50S2V5LCBvYmopO1xuICAgIH1cbiAgICByZXR1cm4gbWVtbztcbiAgfTtcblxuICAvLyBUaGUgcmlnaHQtYXNzb2NpYXRpdmUgdmVyc2lvbiBvZiByZWR1Y2UsIGFsc28ga25vd24gYXMgYGZvbGRyYC5cbiAgXy5yZWR1Y2VSaWdodCA9IF8uZm9sZHIgPSBmdW5jdGlvbihvYmosIGl0ZXJhdGVlLCBtZW1vLCBjb250ZXh0KSB7XG4gICAgaWYgKG9iaiA9PSBudWxsKSBvYmogPSBbXTtcbiAgICBpdGVyYXRlZSA9IGNyZWF0ZUNhbGxiYWNrKGl0ZXJhdGVlLCBjb250ZXh0LCA0KTtcbiAgICB2YXIga2V5cyA9IG9iai5sZW5ndGggIT09ICsgb2JqLmxlbmd0aCAmJiBfLmtleXMob2JqKSxcbiAgICAgICAgaW5kZXggPSAoa2V5cyB8fCBvYmopLmxlbmd0aCxcbiAgICAgICAgY3VycmVudEtleTtcbiAgICBpZiAoYXJndW1lbnRzLmxlbmd0aCA8IDMpIHtcbiAgICAgIGlmICghaW5kZXgpIHRocm93IG5ldyBUeXBlRXJyb3IocmVkdWNlRXJyb3IpO1xuICAgICAgbWVtbyA9IG9ialtrZXlzID8ga2V5c1stLWluZGV4XSA6IC0taW5kZXhdO1xuICAgIH1cbiAgICB3aGlsZSAoaW5kZXgtLSkge1xuICAgICAgY3VycmVudEtleSA9IGtleXMgPyBrZXlzW2luZGV4XSA6IGluZGV4O1xuICAgICAgbWVtbyA9IGl0ZXJhdGVlKG1lbW8sIG9ialtjdXJyZW50S2V5XSwgY3VycmVudEtleSwgb2JqKTtcbiAgICB9XG4gICAgcmV0dXJuIG1lbW87XG4gIH07XG5cbiAgLy8gUmV0dXJuIHRoZSBmaXJzdCB2YWx1ZSB3aGljaCBwYXNzZXMgYSB0cnV0aCB0ZXN0LiBBbGlhc2VkIGFzIGBkZXRlY3RgLlxuICBfLmZpbmQgPSBfLmRldGVjdCA9IGZ1bmN0aW9uKG9iaiwgcHJlZGljYXRlLCBjb250ZXh0KSB7XG4gICAgdmFyIHJlc3VsdDtcbiAgICBwcmVkaWNhdGUgPSBfLml0ZXJhdGVlKHByZWRpY2F0ZSwgY29udGV4dCk7XG4gICAgXy5zb21lKG9iaiwgZnVuY3Rpb24odmFsdWUsIGluZGV4LCBsaXN0KSB7XG4gICAgICBpZiAocHJlZGljYXRlKHZhbHVlLCBpbmRleCwgbGlzdCkpIHtcbiAgICAgICAgcmVzdWx0ID0gdmFsdWU7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgfVxuICAgIH0pO1xuICAgIHJldHVybiByZXN1bHQ7XG4gIH07XG5cbiAgLy8gUmV0dXJuIGFsbCB0aGUgZWxlbWVudHMgdGhhdCBwYXNzIGEgdHJ1dGggdGVzdC5cbiAgLy8gQWxpYXNlZCBhcyBgc2VsZWN0YC5cbiAgXy5maWx0ZXIgPSBfLnNlbGVjdCA9IGZ1bmN0aW9uKG9iaiwgcHJlZGljYXRlLCBjb250ZXh0KSB7XG4gICAgdmFyIHJlc3VsdHMgPSBbXTtcbiAgICBpZiAob2JqID09IG51bGwpIHJldHVybiByZXN1bHRzO1xuICAgIHByZWRpY2F0ZSA9IF8uaXRlcmF0ZWUocHJlZGljYXRlLCBjb250ZXh0KTtcbiAgICBfLmVhY2gob2JqLCBmdW5jdGlvbih2YWx1ZSwgaW5kZXgsIGxpc3QpIHtcbiAgICAgIGlmIChwcmVkaWNhdGUodmFsdWUsIGluZGV4LCBsaXN0KSkgcmVzdWx0cy5wdXNoKHZhbHVlKTtcbiAgICB9KTtcbiAgICByZXR1cm4gcmVzdWx0cztcbiAgfTtcblxuICAvLyBSZXR1cm4gYWxsIHRoZSBlbGVtZW50cyBmb3Igd2hpY2ggYSB0cnV0aCB0ZXN0IGZhaWxzLlxuICBfLnJlamVjdCA9IGZ1bmN0aW9uKG9iaiwgcHJlZGljYXRlLCBjb250ZXh0KSB7XG4gICAgcmV0dXJuIF8uZmlsdGVyKG9iaiwgXy5uZWdhdGUoXy5pdGVyYXRlZShwcmVkaWNhdGUpKSwgY29udGV4dCk7XG4gIH07XG5cbiAgLy8gRGV0ZXJtaW5lIHdoZXRoZXIgYWxsIG9mIHRoZSBlbGVtZW50cyBtYXRjaCBhIHRydXRoIHRlc3QuXG4gIC8vIEFsaWFzZWQgYXMgYGFsbGAuXG4gIF8uZXZlcnkgPSBfLmFsbCA9IGZ1bmN0aW9uKG9iaiwgcHJlZGljYXRlLCBjb250ZXh0KSB7XG4gICAgaWYgKG9iaiA9PSBudWxsKSByZXR1cm4gdHJ1ZTtcbiAgICBwcmVkaWNhdGUgPSBfLml0ZXJhdGVlKHByZWRpY2F0ZSwgY29udGV4dCk7XG4gICAgdmFyIGtleXMgPSBvYmoubGVuZ3RoICE9PSArb2JqLmxlbmd0aCAmJiBfLmtleXMob2JqKSxcbiAgICAgICAgbGVuZ3RoID0gKGtleXMgfHwgb2JqKS5sZW5ndGgsXG4gICAgICAgIGluZGV4LCBjdXJyZW50S2V5O1xuICAgIGZvciAoaW5kZXggPSAwOyBpbmRleCA8IGxlbmd0aDsgaW5kZXgrKykge1xuICAgICAgY3VycmVudEtleSA9IGtleXMgPyBrZXlzW2luZGV4XSA6IGluZGV4O1xuICAgICAgaWYgKCFwcmVkaWNhdGUob2JqW2N1cnJlbnRLZXldLCBjdXJyZW50S2V5LCBvYmopKSByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIHJldHVybiB0cnVlO1xuICB9O1xuXG4gIC8vIERldGVybWluZSBpZiBhdCBsZWFzdCBvbmUgZWxlbWVudCBpbiB0aGUgb2JqZWN0IG1hdGNoZXMgYSB0cnV0aCB0ZXN0LlxuICAvLyBBbGlhc2VkIGFzIGBhbnlgLlxuICBfLnNvbWUgPSBfLmFueSA9IGZ1bmN0aW9uKG9iaiwgcHJlZGljYXRlLCBjb250ZXh0KSB7XG4gICAgaWYgKG9iaiA9PSBudWxsKSByZXR1cm4gZmFsc2U7XG4gICAgcHJlZGljYXRlID0gXy5pdGVyYXRlZShwcmVkaWNhdGUsIGNvbnRleHQpO1xuICAgIHZhciBrZXlzID0gb2JqLmxlbmd0aCAhPT0gK29iai5sZW5ndGggJiYgXy5rZXlzKG9iaiksXG4gICAgICAgIGxlbmd0aCA9IChrZXlzIHx8IG9iaikubGVuZ3RoLFxuICAgICAgICBpbmRleCwgY3VycmVudEtleTtcbiAgICBmb3IgKGluZGV4ID0gMDsgaW5kZXggPCBsZW5ndGg7IGluZGV4KyspIHtcbiAgICAgIGN1cnJlbnRLZXkgPSBrZXlzID8ga2V5c1tpbmRleF0gOiBpbmRleDtcbiAgICAgIGlmIChwcmVkaWNhdGUob2JqW2N1cnJlbnRLZXldLCBjdXJyZW50S2V5LCBvYmopKSByZXR1cm4gdHJ1ZTtcbiAgICB9XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9O1xuXG4gIC8vIERldGVybWluZSBpZiB0aGUgYXJyYXkgb3Igb2JqZWN0IGNvbnRhaW5zIGEgZ2l2ZW4gdmFsdWUgKHVzaW5nIGA9PT1gKS5cbiAgLy8gQWxpYXNlZCBhcyBgaW5jbHVkZWAuXG4gIF8uY29udGFpbnMgPSBfLmluY2x1ZGUgPSBmdW5jdGlvbihvYmosIHRhcmdldCkge1xuICAgIGlmIChvYmogPT0gbnVsbCkgcmV0dXJuIGZhbHNlO1xuICAgIGlmIChvYmoubGVuZ3RoICE9PSArb2JqLmxlbmd0aCkgb2JqID0gXy52YWx1ZXMob2JqKTtcbiAgICByZXR1cm4gXy5pbmRleE9mKG9iaiwgdGFyZ2V0KSA+PSAwO1xuICB9O1xuXG4gIC8vIEludm9rZSBhIG1ldGhvZCAod2l0aCBhcmd1bWVudHMpIG9uIGV2ZXJ5IGl0ZW0gaW4gYSBjb2xsZWN0aW9uLlxuICBfLmludm9rZSA9IGZ1bmN0aW9uKG9iaiwgbWV0aG9kKSB7XG4gICAgdmFyIGFyZ3MgPSBzbGljZS5jYWxsKGFyZ3VtZW50cywgMik7XG4gICAgdmFyIGlzRnVuYyA9IF8uaXNGdW5jdGlvbihtZXRob2QpO1xuICAgIHJldHVybiBfLm1hcChvYmosIGZ1bmN0aW9uKHZhbHVlKSB7XG4gICAgICByZXR1cm4gKGlzRnVuYyA/IG1ldGhvZCA6IHZhbHVlW21ldGhvZF0pLmFwcGx5KHZhbHVlLCBhcmdzKTtcbiAgICB9KTtcbiAgfTtcblxuICAvLyBDb252ZW5pZW5jZSB2ZXJzaW9uIG9mIGEgY29tbW9uIHVzZSBjYXNlIG9mIGBtYXBgOiBmZXRjaGluZyBhIHByb3BlcnR5LlxuICBfLnBsdWNrID0gZnVuY3Rpb24ob2JqLCBrZXkpIHtcbiAgICByZXR1cm4gXy5tYXAob2JqLCBfLnByb3BlcnR5KGtleSkpO1xuICB9O1xuXG4gIC8vIENvbnZlbmllbmNlIHZlcnNpb24gb2YgYSBjb21tb24gdXNlIGNhc2Ugb2YgYGZpbHRlcmA6IHNlbGVjdGluZyBvbmx5IG9iamVjdHNcbiAgLy8gY29udGFpbmluZyBzcGVjaWZpYyBga2V5OnZhbHVlYCBwYWlycy5cbiAgXy53aGVyZSA9IGZ1bmN0aW9uKG9iaiwgYXR0cnMpIHtcbiAgICByZXR1cm4gXy5maWx0ZXIob2JqLCBfLm1hdGNoZXMoYXR0cnMpKTtcbiAgfTtcblxuICAvLyBDb252ZW5pZW5jZSB2ZXJzaW9uIG9mIGEgY29tbW9uIHVzZSBjYXNlIG9mIGBmaW5kYDogZ2V0dGluZyB0aGUgZmlyc3Qgb2JqZWN0XG4gIC8vIGNvbnRhaW5pbmcgc3BlY2lmaWMgYGtleTp2YWx1ZWAgcGFpcnMuXG4gIF8uZmluZFdoZXJlID0gZnVuY3Rpb24ob2JqLCBhdHRycykge1xuICAgIHJldHVybiBfLmZpbmQob2JqLCBfLm1hdGNoZXMoYXR0cnMpKTtcbiAgfTtcblxuICAvLyBSZXR1cm4gdGhlIG1heGltdW0gZWxlbWVudCAob3IgZWxlbWVudC1iYXNlZCBjb21wdXRhdGlvbikuXG4gIF8ubWF4ID0gZnVuY3Rpb24ob2JqLCBpdGVyYXRlZSwgY29udGV4dCkge1xuICAgIHZhciByZXN1bHQgPSAtSW5maW5pdHksIGxhc3RDb21wdXRlZCA9IC1JbmZpbml0eSxcbiAgICAgICAgdmFsdWUsIGNvbXB1dGVkO1xuICAgIGlmIChpdGVyYXRlZSA9PSBudWxsICYmIG9iaiAhPSBudWxsKSB7XG4gICAgICBvYmogPSBvYmoubGVuZ3RoID09PSArb2JqLmxlbmd0aCA/IG9iaiA6IF8udmFsdWVzKG9iaik7XG4gICAgICBmb3IgKHZhciBpID0gMCwgbGVuZ3RoID0gb2JqLmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIHZhbHVlID0gb2JqW2ldO1xuICAgICAgICBpZiAodmFsdWUgPiByZXN1bHQpIHtcbiAgICAgICAgICByZXN1bHQgPSB2YWx1ZTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICBpdGVyYXRlZSA9IF8uaXRlcmF0ZWUoaXRlcmF0ZWUsIGNvbnRleHQpO1xuICAgICAgXy5lYWNoKG9iaiwgZnVuY3Rpb24odmFsdWUsIGluZGV4LCBsaXN0KSB7XG4gICAgICAgIGNvbXB1dGVkID0gaXRlcmF0ZWUodmFsdWUsIGluZGV4LCBsaXN0KTtcbiAgICAgICAgaWYgKGNvbXB1dGVkID4gbGFzdENvbXB1dGVkIHx8IGNvbXB1dGVkID09PSAtSW5maW5pdHkgJiYgcmVzdWx0ID09PSAtSW5maW5pdHkpIHtcbiAgICAgICAgICByZXN1bHQgPSB2YWx1ZTtcbiAgICAgICAgICBsYXN0Q29tcHV0ZWQgPSBjb21wdXRlZDtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgfVxuICAgIHJldHVybiByZXN1bHQ7XG4gIH07XG5cbiAgLy8gUmV0dXJuIHRoZSBtaW5pbXVtIGVsZW1lbnQgKG9yIGVsZW1lbnQtYmFzZWQgY29tcHV0YXRpb24pLlxuICBfLm1pbiA9IGZ1bmN0aW9uKG9iaiwgaXRlcmF0ZWUsIGNvbnRleHQpIHtcbiAgICB2YXIgcmVzdWx0ID0gSW5maW5pdHksIGxhc3RDb21wdXRlZCA9IEluZmluaXR5LFxuICAgICAgICB2YWx1ZSwgY29tcHV0ZWQ7XG4gICAgaWYgKGl0ZXJhdGVlID09IG51bGwgJiYgb2JqICE9IG51bGwpIHtcbiAgICAgIG9iaiA9IG9iai5sZW5ndGggPT09ICtvYmoubGVuZ3RoID8gb2JqIDogXy52YWx1ZXMob2JqKTtcbiAgICAgIGZvciAodmFyIGkgPSAwLCBsZW5ndGggPSBvYmoubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgdmFsdWUgPSBvYmpbaV07XG4gICAgICAgIGlmICh2YWx1ZSA8IHJlc3VsdCkge1xuICAgICAgICAgIHJlc3VsdCA9IHZhbHVlO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIGl0ZXJhdGVlID0gXy5pdGVyYXRlZShpdGVyYXRlZSwgY29udGV4dCk7XG4gICAgICBfLmVhY2gob2JqLCBmdW5jdGlvbih2YWx1ZSwgaW5kZXgsIGxpc3QpIHtcbiAgICAgICAgY29tcHV0ZWQgPSBpdGVyYXRlZSh2YWx1ZSwgaW5kZXgsIGxpc3QpO1xuICAgICAgICBpZiAoY29tcHV0ZWQgPCBsYXN0Q29tcHV0ZWQgfHwgY29tcHV0ZWQgPT09IEluZmluaXR5ICYmIHJlc3VsdCA9PT0gSW5maW5pdHkpIHtcbiAgICAgICAgICByZXN1bHQgPSB2YWx1ZTtcbiAgICAgICAgICBsYXN0Q29tcHV0ZWQgPSBjb21wdXRlZDtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgfVxuICAgIHJldHVybiByZXN1bHQ7XG4gIH07XG5cbiAgLy8gU2h1ZmZsZSBhIGNvbGxlY3Rpb24sIHVzaW5nIHRoZSBtb2Rlcm4gdmVyc2lvbiBvZiB0aGVcbiAgLy8gW0Zpc2hlci1ZYXRlcyBzaHVmZmxlXShodHRwOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0Zpc2hlcuKAk1lhdGVzX3NodWZmbGUpLlxuICBfLnNodWZmbGUgPSBmdW5jdGlvbihvYmopIHtcbiAgICB2YXIgc2V0ID0gb2JqICYmIG9iai5sZW5ndGggPT09ICtvYmoubGVuZ3RoID8gb2JqIDogXy52YWx1ZXMob2JqKTtcbiAgICB2YXIgbGVuZ3RoID0gc2V0Lmxlbmd0aDtcbiAgICB2YXIgc2h1ZmZsZWQgPSBBcnJheShsZW5ndGgpO1xuICAgIGZvciAodmFyIGluZGV4ID0gMCwgcmFuZDsgaW5kZXggPCBsZW5ndGg7IGluZGV4KyspIHtcbiAgICAgIHJhbmQgPSBfLnJhbmRvbSgwLCBpbmRleCk7XG4gICAgICBpZiAocmFuZCAhPT0gaW5kZXgpIHNodWZmbGVkW2luZGV4XSA9IHNodWZmbGVkW3JhbmRdO1xuICAgICAgc2h1ZmZsZWRbcmFuZF0gPSBzZXRbaW5kZXhdO1xuICAgIH1cbiAgICByZXR1cm4gc2h1ZmZsZWQ7XG4gIH07XG5cbiAgLy8gU2FtcGxlICoqbioqIHJhbmRvbSB2YWx1ZXMgZnJvbSBhIGNvbGxlY3Rpb24uXG4gIC8vIElmICoqbioqIGlzIG5vdCBzcGVjaWZpZWQsIHJldHVybnMgYSBzaW5nbGUgcmFuZG9tIGVsZW1lbnQuXG4gIC8vIFRoZSBpbnRlcm5hbCBgZ3VhcmRgIGFyZ3VtZW50IGFsbG93cyBpdCB0byB3b3JrIHdpdGggYG1hcGAuXG4gIF8uc2FtcGxlID0gZnVuY3Rpb24ob2JqLCBuLCBndWFyZCkge1xuICAgIGlmIChuID09IG51bGwgfHwgZ3VhcmQpIHtcbiAgICAgIGlmIChvYmoubGVuZ3RoICE9PSArb2JqLmxlbmd0aCkgb2JqID0gXy52YWx1ZXMob2JqKTtcbiAgICAgIHJldHVybiBvYmpbXy5yYW5kb20ob2JqLmxlbmd0aCAtIDEpXTtcbiAgICB9XG4gICAgcmV0dXJuIF8uc2h1ZmZsZShvYmopLnNsaWNlKDAsIE1hdGgubWF4KDAsIG4pKTtcbiAgfTtcblxuICAvLyBTb3J0IHRoZSBvYmplY3QncyB2YWx1ZXMgYnkgYSBjcml0ZXJpb24gcHJvZHVjZWQgYnkgYW4gaXRlcmF0ZWUuXG4gIF8uc29ydEJ5ID0gZnVuY3Rpb24ob2JqLCBpdGVyYXRlZSwgY29udGV4dCkge1xuICAgIGl0ZXJhdGVlID0gXy5pdGVyYXRlZShpdGVyYXRlZSwgY29udGV4dCk7XG4gICAgcmV0dXJuIF8ucGx1Y2soXy5tYXAob2JqLCBmdW5jdGlvbih2YWx1ZSwgaW5kZXgsIGxpc3QpIHtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIHZhbHVlOiB2YWx1ZSxcbiAgICAgICAgaW5kZXg6IGluZGV4LFxuICAgICAgICBjcml0ZXJpYTogaXRlcmF0ZWUodmFsdWUsIGluZGV4LCBsaXN0KVxuICAgICAgfTtcbiAgICB9KS5zb3J0KGZ1bmN0aW9uKGxlZnQsIHJpZ2h0KSB7XG4gICAgICB2YXIgYSA9IGxlZnQuY3JpdGVyaWE7XG4gICAgICB2YXIgYiA9IHJpZ2h0LmNyaXRlcmlhO1xuICAgICAgaWYgKGEgIT09IGIpIHtcbiAgICAgICAgaWYgKGEgPiBiIHx8IGEgPT09IHZvaWQgMCkgcmV0dXJuIDE7XG4gICAgICAgIGlmIChhIDwgYiB8fCBiID09PSB2b2lkIDApIHJldHVybiAtMTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBsZWZ0LmluZGV4IC0gcmlnaHQuaW5kZXg7XG4gICAgfSksICd2YWx1ZScpO1xuICB9O1xuXG4gIC8vIEFuIGludGVybmFsIGZ1bmN0aW9uIHVzZWQgZm9yIGFnZ3JlZ2F0ZSBcImdyb3VwIGJ5XCIgb3BlcmF0aW9ucy5cbiAgdmFyIGdyb3VwID0gZnVuY3Rpb24oYmVoYXZpb3IpIHtcbiAgICByZXR1cm4gZnVuY3Rpb24ob2JqLCBpdGVyYXRlZSwgY29udGV4dCkge1xuICAgICAgdmFyIHJlc3VsdCA9IHt9O1xuICAgICAgaXRlcmF0ZWUgPSBfLml0ZXJhdGVlKGl0ZXJhdGVlLCBjb250ZXh0KTtcbiAgICAgIF8uZWFjaChvYmosIGZ1bmN0aW9uKHZhbHVlLCBpbmRleCkge1xuICAgICAgICB2YXIga2V5ID0gaXRlcmF0ZWUodmFsdWUsIGluZGV4LCBvYmopO1xuICAgICAgICBiZWhhdmlvcihyZXN1bHQsIHZhbHVlLCBrZXkpO1xuICAgICAgfSk7XG4gICAgICByZXR1cm4gcmVzdWx0O1xuICAgIH07XG4gIH07XG5cbiAgLy8gR3JvdXBzIHRoZSBvYmplY3QncyB2YWx1ZXMgYnkgYSBjcml0ZXJpb24uIFBhc3MgZWl0aGVyIGEgc3RyaW5nIGF0dHJpYnV0ZVxuICAvLyB0byBncm91cCBieSwgb3IgYSBmdW5jdGlvbiB0aGF0IHJldHVybnMgdGhlIGNyaXRlcmlvbi5cbiAgXy5ncm91cEJ5ID0gZ3JvdXAoZnVuY3Rpb24ocmVzdWx0LCB2YWx1ZSwga2V5KSB7XG4gICAgaWYgKF8uaGFzKHJlc3VsdCwga2V5KSkgcmVzdWx0W2tleV0ucHVzaCh2YWx1ZSk7IGVsc2UgcmVzdWx0W2tleV0gPSBbdmFsdWVdO1xuICB9KTtcblxuICAvLyBJbmRleGVzIHRoZSBvYmplY3QncyB2YWx1ZXMgYnkgYSBjcml0ZXJpb24sIHNpbWlsYXIgdG8gYGdyb3VwQnlgLCBidXQgZm9yXG4gIC8vIHdoZW4geW91IGtub3cgdGhhdCB5b3VyIGluZGV4IHZhbHVlcyB3aWxsIGJlIHVuaXF1ZS5cbiAgXy5pbmRleEJ5ID0gZ3JvdXAoZnVuY3Rpb24ocmVzdWx0LCB2YWx1ZSwga2V5KSB7XG4gICAgcmVzdWx0W2tleV0gPSB2YWx1ZTtcbiAgfSk7XG5cbiAgLy8gQ291bnRzIGluc3RhbmNlcyBvZiBhbiBvYmplY3QgdGhhdCBncm91cCBieSBhIGNlcnRhaW4gY3JpdGVyaW9uLiBQYXNzXG4gIC8vIGVpdGhlciBhIHN0cmluZyBhdHRyaWJ1dGUgdG8gY291bnQgYnksIG9yIGEgZnVuY3Rpb24gdGhhdCByZXR1cm5zIHRoZVxuICAvLyBjcml0ZXJpb24uXG4gIF8uY291bnRCeSA9IGdyb3VwKGZ1bmN0aW9uKHJlc3VsdCwgdmFsdWUsIGtleSkge1xuICAgIGlmIChfLmhhcyhyZXN1bHQsIGtleSkpIHJlc3VsdFtrZXldKys7IGVsc2UgcmVzdWx0W2tleV0gPSAxO1xuICB9KTtcblxuICAvLyBVc2UgYSBjb21wYXJhdG9yIGZ1bmN0aW9uIHRvIGZpZ3VyZSBvdXQgdGhlIHNtYWxsZXN0IGluZGV4IGF0IHdoaWNoXG4gIC8vIGFuIG9iamVjdCBzaG91bGQgYmUgaW5zZXJ0ZWQgc28gYXMgdG8gbWFpbnRhaW4gb3JkZXIuIFVzZXMgYmluYXJ5IHNlYXJjaC5cbiAgXy5zb3J0ZWRJbmRleCA9IGZ1bmN0aW9uKGFycmF5LCBvYmosIGl0ZXJhdGVlLCBjb250ZXh0KSB7XG4gICAgaXRlcmF0ZWUgPSBfLml0ZXJhdGVlKGl0ZXJhdGVlLCBjb250ZXh0LCAxKTtcbiAgICB2YXIgdmFsdWUgPSBpdGVyYXRlZShvYmopO1xuICAgIHZhciBsb3cgPSAwLCBoaWdoID0gYXJyYXkubGVuZ3RoO1xuICAgIHdoaWxlIChsb3cgPCBoaWdoKSB7XG4gICAgICB2YXIgbWlkID0gbG93ICsgaGlnaCA+Pj4gMTtcbiAgICAgIGlmIChpdGVyYXRlZShhcnJheVttaWRdKSA8IHZhbHVlKSBsb3cgPSBtaWQgKyAxOyBlbHNlIGhpZ2ggPSBtaWQ7XG4gICAgfVxuICAgIHJldHVybiBsb3c7XG4gIH07XG5cbiAgLy8gU2FmZWx5IGNyZWF0ZSBhIHJlYWwsIGxpdmUgYXJyYXkgZnJvbSBhbnl0aGluZyBpdGVyYWJsZS5cbiAgXy50b0FycmF5ID0gZnVuY3Rpb24ob2JqKSB7XG4gICAgaWYgKCFvYmopIHJldHVybiBbXTtcbiAgICBpZiAoXy5pc0FycmF5KG9iaikpIHJldHVybiBzbGljZS5jYWxsKG9iaik7XG4gICAgaWYgKG9iai5sZW5ndGggPT09ICtvYmoubGVuZ3RoKSByZXR1cm4gXy5tYXAob2JqLCBfLmlkZW50aXR5KTtcbiAgICByZXR1cm4gXy52YWx1ZXMob2JqKTtcbiAgfTtcblxuICAvLyBSZXR1cm4gdGhlIG51bWJlciBvZiBlbGVtZW50cyBpbiBhbiBvYmplY3QuXG4gIF8uc2l6ZSA9IGZ1bmN0aW9uKG9iaikge1xuICAgIGlmIChvYmogPT0gbnVsbCkgcmV0dXJuIDA7XG4gICAgcmV0dXJuIG9iai5sZW5ndGggPT09ICtvYmoubGVuZ3RoID8gb2JqLmxlbmd0aCA6IF8ua2V5cyhvYmopLmxlbmd0aDtcbiAgfTtcblxuICAvLyBTcGxpdCBhIGNvbGxlY3Rpb24gaW50byB0d28gYXJyYXlzOiBvbmUgd2hvc2UgZWxlbWVudHMgYWxsIHNhdGlzZnkgdGhlIGdpdmVuXG4gIC8vIHByZWRpY2F0ZSwgYW5kIG9uZSB3aG9zZSBlbGVtZW50cyBhbGwgZG8gbm90IHNhdGlzZnkgdGhlIHByZWRpY2F0ZS5cbiAgXy5wYXJ0aXRpb24gPSBmdW5jdGlvbihvYmosIHByZWRpY2F0ZSwgY29udGV4dCkge1xuICAgIHByZWRpY2F0ZSA9IF8uaXRlcmF0ZWUocHJlZGljYXRlLCBjb250ZXh0KTtcbiAgICB2YXIgcGFzcyA9IFtdLCBmYWlsID0gW107XG4gICAgXy5lYWNoKG9iaiwgZnVuY3Rpb24odmFsdWUsIGtleSwgb2JqKSB7XG4gICAgICAocHJlZGljYXRlKHZhbHVlLCBrZXksIG9iaikgPyBwYXNzIDogZmFpbCkucHVzaCh2YWx1ZSk7XG4gICAgfSk7XG4gICAgcmV0dXJuIFtwYXNzLCBmYWlsXTtcbiAgfTtcblxuICAvLyBBcnJheSBGdW5jdGlvbnNcbiAgLy8gLS0tLS0tLS0tLS0tLS0tXG5cbiAgLy8gR2V0IHRoZSBmaXJzdCBlbGVtZW50IG9mIGFuIGFycmF5LiBQYXNzaW5nICoqbioqIHdpbGwgcmV0dXJuIHRoZSBmaXJzdCBOXG4gIC8vIHZhbHVlcyBpbiB0aGUgYXJyYXkuIEFsaWFzZWQgYXMgYGhlYWRgIGFuZCBgdGFrZWAuIFRoZSAqKmd1YXJkKiogY2hlY2tcbiAgLy8gYWxsb3dzIGl0IHRvIHdvcmsgd2l0aCBgXy5tYXBgLlxuICBfLmZpcnN0ID0gXy5oZWFkID0gXy50YWtlID0gZnVuY3Rpb24oYXJyYXksIG4sIGd1YXJkKSB7XG4gICAgaWYgKGFycmF5ID09IG51bGwpIHJldHVybiB2b2lkIDA7XG4gICAgaWYgKG4gPT0gbnVsbCB8fCBndWFyZCkgcmV0dXJuIGFycmF5WzBdO1xuICAgIGlmIChuIDwgMCkgcmV0dXJuIFtdO1xuICAgIHJldHVybiBzbGljZS5jYWxsKGFycmF5LCAwLCBuKTtcbiAgfTtcblxuICAvLyBSZXR1cm5zIGV2ZXJ5dGhpbmcgYnV0IHRoZSBsYXN0IGVudHJ5IG9mIHRoZSBhcnJheS4gRXNwZWNpYWxseSB1c2VmdWwgb25cbiAgLy8gdGhlIGFyZ3VtZW50cyBvYmplY3QuIFBhc3NpbmcgKipuKiogd2lsbCByZXR1cm4gYWxsIHRoZSB2YWx1ZXMgaW5cbiAgLy8gdGhlIGFycmF5LCBleGNsdWRpbmcgdGhlIGxhc3QgTi4gVGhlICoqZ3VhcmQqKiBjaGVjayBhbGxvd3MgaXQgdG8gd29yayB3aXRoXG4gIC8vIGBfLm1hcGAuXG4gIF8uaW5pdGlhbCA9IGZ1bmN0aW9uKGFycmF5LCBuLCBndWFyZCkge1xuICAgIHJldHVybiBzbGljZS5jYWxsKGFycmF5LCAwLCBNYXRoLm1heCgwLCBhcnJheS5sZW5ndGggLSAobiA9PSBudWxsIHx8IGd1YXJkID8gMSA6IG4pKSk7XG4gIH07XG5cbiAgLy8gR2V0IHRoZSBsYXN0IGVsZW1lbnQgb2YgYW4gYXJyYXkuIFBhc3NpbmcgKipuKiogd2lsbCByZXR1cm4gdGhlIGxhc3QgTlxuICAvLyB2YWx1ZXMgaW4gdGhlIGFycmF5LiBUaGUgKipndWFyZCoqIGNoZWNrIGFsbG93cyBpdCB0byB3b3JrIHdpdGggYF8ubWFwYC5cbiAgXy5sYXN0ID0gZnVuY3Rpb24oYXJyYXksIG4sIGd1YXJkKSB7XG4gICAgaWYgKGFycmF5ID09IG51bGwpIHJldHVybiB2b2lkIDA7XG4gICAgaWYgKG4gPT0gbnVsbCB8fCBndWFyZCkgcmV0dXJuIGFycmF5W2FycmF5Lmxlbmd0aCAtIDFdO1xuICAgIHJldHVybiBzbGljZS5jYWxsKGFycmF5LCBNYXRoLm1heChhcnJheS5sZW5ndGggLSBuLCAwKSk7XG4gIH07XG5cbiAgLy8gUmV0dXJucyBldmVyeXRoaW5nIGJ1dCB0aGUgZmlyc3QgZW50cnkgb2YgdGhlIGFycmF5LiBBbGlhc2VkIGFzIGB0YWlsYCBhbmQgYGRyb3BgLlxuICAvLyBFc3BlY2lhbGx5IHVzZWZ1bCBvbiB0aGUgYXJndW1lbnRzIG9iamVjdC4gUGFzc2luZyBhbiAqKm4qKiB3aWxsIHJldHVyblxuICAvLyB0aGUgcmVzdCBOIHZhbHVlcyBpbiB0aGUgYXJyYXkuIFRoZSAqKmd1YXJkKipcbiAgLy8gY2hlY2sgYWxsb3dzIGl0IHRvIHdvcmsgd2l0aCBgXy5tYXBgLlxuICBfLnJlc3QgPSBfLnRhaWwgPSBfLmRyb3AgPSBmdW5jdGlvbihhcnJheSwgbiwgZ3VhcmQpIHtcbiAgICByZXR1cm4gc2xpY2UuY2FsbChhcnJheSwgbiA9PSBudWxsIHx8IGd1YXJkID8gMSA6IG4pO1xuICB9O1xuXG4gIC8vIFRyaW0gb3V0IGFsbCBmYWxzeSB2YWx1ZXMgZnJvbSBhbiBhcnJheS5cbiAgXy5jb21wYWN0ID0gZnVuY3Rpb24oYXJyYXkpIHtcbiAgICByZXR1cm4gXy5maWx0ZXIoYXJyYXksIF8uaWRlbnRpdHkpO1xuICB9O1xuXG4gIC8vIEludGVybmFsIGltcGxlbWVudGF0aW9uIG9mIGEgcmVjdXJzaXZlIGBmbGF0dGVuYCBmdW5jdGlvbi5cbiAgdmFyIGZsYXR0ZW4gPSBmdW5jdGlvbihpbnB1dCwgc2hhbGxvdywgc3RyaWN0LCBvdXRwdXQpIHtcbiAgICBpZiAoc2hhbGxvdyAmJiBfLmV2ZXJ5KGlucHV0LCBfLmlzQXJyYXkpKSB7XG4gICAgICByZXR1cm4gY29uY2F0LmFwcGx5KG91dHB1dCwgaW5wdXQpO1xuICAgIH1cbiAgICBmb3IgKHZhciBpID0gMCwgbGVuZ3RoID0gaW5wdXQubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgIHZhciB2YWx1ZSA9IGlucHV0W2ldO1xuICAgICAgaWYgKCFfLmlzQXJyYXkodmFsdWUpICYmICFfLmlzQXJndW1lbnRzKHZhbHVlKSkge1xuICAgICAgICBpZiAoIXN0cmljdCkgb3V0cHV0LnB1c2godmFsdWUpO1xuICAgICAgfSBlbHNlIGlmIChzaGFsbG93KSB7XG4gICAgICAgIHB1c2guYXBwbHkob3V0cHV0LCB2YWx1ZSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBmbGF0dGVuKHZhbHVlLCBzaGFsbG93LCBzdHJpY3QsIG91dHB1dCk7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBvdXRwdXQ7XG4gIH07XG5cbiAgLy8gRmxhdHRlbiBvdXQgYW4gYXJyYXksIGVpdGhlciByZWN1cnNpdmVseSAoYnkgZGVmYXVsdCksIG9yIGp1c3Qgb25lIGxldmVsLlxuICBfLmZsYXR0ZW4gPSBmdW5jdGlvbihhcnJheSwgc2hhbGxvdykge1xuICAgIHJldHVybiBmbGF0dGVuKGFycmF5LCBzaGFsbG93LCBmYWxzZSwgW10pO1xuICB9O1xuXG4gIC8vIFJldHVybiBhIHZlcnNpb24gb2YgdGhlIGFycmF5IHRoYXQgZG9lcyBub3QgY29udGFpbiB0aGUgc3BlY2lmaWVkIHZhbHVlKHMpLlxuICBfLndpdGhvdXQgPSBmdW5jdGlvbihhcnJheSkge1xuICAgIHJldHVybiBfLmRpZmZlcmVuY2UoYXJyYXksIHNsaWNlLmNhbGwoYXJndW1lbnRzLCAxKSk7XG4gIH07XG5cbiAgLy8gUHJvZHVjZSBhIGR1cGxpY2F0ZS1mcmVlIHZlcnNpb24gb2YgdGhlIGFycmF5LiBJZiB0aGUgYXJyYXkgaGFzIGFscmVhZHlcbiAgLy8gYmVlbiBzb3J0ZWQsIHlvdSBoYXZlIHRoZSBvcHRpb24gb2YgdXNpbmcgYSBmYXN0ZXIgYWxnb3JpdGhtLlxuICAvLyBBbGlhc2VkIGFzIGB1bmlxdWVgLlxuICBfLnVuaXEgPSBfLnVuaXF1ZSA9IGZ1bmN0aW9uKGFycmF5LCBpc1NvcnRlZCwgaXRlcmF0ZWUsIGNvbnRleHQpIHtcbiAgICBpZiAoYXJyYXkgPT0gbnVsbCkgcmV0dXJuIFtdO1xuICAgIGlmICghXy5pc0Jvb2xlYW4oaXNTb3J0ZWQpKSB7XG4gICAgICBjb250ZXh0ID0gaXRlcmF0ZWU7XG4gICAgICBpdGVyYXRlZSA9IGlzU29ydGVkO1xuICAgICAgaXNTb3J0ZWQgPSBmYWxzZTtcbiAgICB9XG4gICAgaWYgKGl0ZXJhdGVlICE9IG51bGwpIGl0ZXJhdGVlID0gXy5pdGVyYXRlZShpdGVyYXRlZSwgY29udGV4dCk7XG4gICAgdmFyIHJlc3VsdCA9IFtdO1xuICAgIHZhciBzZWVuID0gW107XG4gICAgZm9yICh2YXIgaSA9IDAsIGxlbmd0aCA9IGFycmF5Lmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICB2YXIgdmFsdWUgPSBhcnJheVtpXTtcbiAgICAgIGlmIChpc1NvcnRlZCkge1xuICAgICAgICBpZiAoIWkgfHwgc2VlbiAhPT0gdmFsdWUpIHJlc3VsdC5wdXNoKHZhbHVlKTtcbiAgICAgICAgc2VlbiA9IHZhbHVlO1xuICAgICAgfSBlbHNlIGlmIChpdGVyYXRlZSkge1xuICAgICAgICB2YXIgY29tcHV0ZWQgPSBpdGVyYXRlZSh2YWx1ZSwgaSwgYXJyYXkpO1xuICAgICAgICBpZiAoXy5pbmRleE9mKHNlZW4sIGNvbXB1dGVkKSA8IDApIHtcbiAgICAgICAgICBzZWVuLnB1c2goY29tcHV0ZWQpO1xuICAgICAgICAgIHJlc3VsdC5wdXNoKHZhbHVlKTtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIGlmIChfLmluZGV4T2YocmVzdWx0LCB2YWx1ZSkgPCAwKSB7XG4gICAgICAgIHJlc3VsdC5wdXNoKHZhbHVlKTtcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfTtcblxuICAvLyBQcm9kdWNlIGFuIGFycmF5IHRoYXQgY29udGFpbnMgdGhlIHVuaW9uOiBlYWNoIGRpc3RpbmN0IGVsZW1lbnQgZnJvbSBhbGwgb2ZcbiAgLy8gdGhlIHBhc3NlZC1pbiBhcnJheXMuXG4gIF8udW5pb24gPSBmdW5jdGlvbigpIHtcbiAgICByZXR1cm4gXy51bmlxKGZsYXR0ZW4oYXJndW1lbnRzLCB0cnVlLCB0cnVlLCBbXSkpO1xuICB9O1xuXG4gIC8vIFByb2R1Y2UgYW4gYXJyYXkgdGhhdCBjb250YWlucyBldmVyeSBpdGVtIHNoYXJlZCBiZXR3ZWVuIGFsbCB0aGVcbiAgLy8gcGFzc2VkLWluIGFycmF5cy5cbiAgXy5pbnRlcnNlY3Rpb24gPSBmdW5jdGlvbihhcnJheSkge1xuICAgIGlmIChhcnJheSA9PSBudWxsKSByZXR1cm4gW107XG4gICAgdmFyIHJlc3VsdCA9IFtdO1xuICAgIHZhciBhcmdzTGVuZ3RoID0gYXJndW1lbnRzLmxlbmd0aDtcbiAgICBmb3IgKHZhciBpID0gMCwgbGVuZ3RoID0gYXJyYXkubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgIHZhciBpdGVtID0gYXJyYXlbaV07XG4gICAgICBpZiAoXy5jb250YWlucyhyZXN1bHQsIGl0ZW0pKSBjb250aW51ZTtcbiAgICAgIGZvciAodmFyIGogPSAxOyBqIDwgYXJnc0xlbmd0aDsgaisrKSB7XG4gICAgICAgIGlmICghXy5jb250YWlucyhhcmd1bWVudHNbal0sIGl0ZW0pKSBicmVhaztcbiAgICAgIH1cbiAgICAgIGlmIChqID09PSBhcmdzTGVuZ3RoKSByZXN1bHQucHVzaChpdGVtKTtcbiAgICB9XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfTtcblxuICAvLyBUYWtlIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gb25lIGFycmF5IGFuZCBhIG51bWJlciBvZiBvdGhlciBhcnJheXMuXG4gIC8vIE9ubHkgdGhlIGVsZW1lbnRzIHByZXNlbnQgaW4ganVzdCB0aGUgZmlyc3QgYXJyYXkgd2lsbCByZW1haW4uXG4gIF8uZGlmZmVyZW5jZSA9IGZ1bmN0aW9uKGFycmF5KSB7XG4gICAgdmFyIHJlc3QgPSBmbGF0dGVuKHNsaWNlLmNhbGwoYXJndW1lbnRzLCAxKSwgdHJ1ZSwgdHJ1ZSwgW10pO1xuICAgIHJldHVybiBfLmZpbHRlcihhcnJheSwgZnVuY3Rpb24odmFsdWUpe1xuICAgICAgcmV0dXJuICFfLmNvbnRhaW5zKHJlc3QsIHZhbHVlKTtcbiAgICB9KTtcbiAgfTtcblxuICAvLyBaaXAgdG9nZXRoZXIgbXVsdGlwbGUgbGlzdHMgaW50byBhIHNpbmdsZSBhcnJheSAtLSBlbGVtZW50cyB0aGF0IHNoYXJlXG4gIC8vIGFuIGluZGV4IGdvIHRvZ2V0aGVyLlxuICBfLnppcCA9IGZ1bmN0aW9uKGFycmF5KSB7XG4gICAgaWYgKGFycmF5ID09IG51bGwpIHJldHVybiBbXTtcbiAgICB2YXIgbGVuZ3RoID0gXy5tYXgoYXJndW1lbnRzLCAnbGVuZ3RoJykubGVuZ3RoO1xuICAgIHZhciByZXN1bHRzID0gQXJyYXkobGVuZ3RoKTtcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICByZXN1bHRzW2ldID0gXy5wbHVjayhhcmd1bWVudHMsIGkpO1xuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0cztcbiAgfTtcblxuICAvLyBDb252ZXJ0cyBsaXN0cyBpbnRvIG9iamVjdHMuIFBhc3MgZWl0aGVyIGEgc2luZ2xlIGFycmF5IG9mIGBba2V5LCB2YWx1ZV1gXG4gIC8vIHBhaXJzLCBvciB0d28gcGFyYWxsZWwgYXJyYXlzIG9mIHRoZSBzYW1lIGxlbmd0aCAtLSBvbmUgb2Yga2V5cywgYW5kIG9uZSBvZlxuICAvLyB0aGUgY29ycmVzcG9uZGluZyB2YWx1ZXMuXG4gIF8ub2JqZWN0ID0gZnVuY3Rpb24obGlzdCwgdmFsdWVzKSB7XG4gICAgaWYgKGxpc3QgPT0gbnVsbCkgcmV0dXJuIHt9O1xuICAgIHZhciByZXN1bHQgPSB7fTtcbiAgICBmb3IgKHZhciBpID0gMCwgbGVuZ3RoID0gbGlzdC5sZW5ndGg7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgICAgaWYgKHZhbHVlcykge1xuICAgICAgICByZXN1bHRbbGlzdFtpXV0gPSB2YWx1ZXNbaV07XG4gICAgICB9IGVsc2Uge1xuICAgICAgICByZXN1bHRbbGlzdFtpXVswXV0gPSBsaXN0W2ldWzFdO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9O1xuXG4gIC8vIFJldHVybiB0aGUgcG9zaXRpb24gb2YgdGhlIGZpcnN0IG9jY3VycmVuY2Ugb2YgYW4gaXRlbSBpbiBhbiBhcnJheSxcbiAgLy8gb3IgLTEgaWYgdGhlIGl0ZW0gaXMgbm90IGluY2x1ZGVkIGluIHRoZSBhcnJheS5cbiAgLy8gSWYgdGhlIGFycmF5IGlzIGxhcmdlIGFuZCBhbHJlYWR5IGluIHNvcnQgb3JkZXIsIHBhc3MgYHRydWVgXG4gIC8vIGZvciAqKmlzU29ydGVkKiogdG8gdXNlIGJpbmFyeSBzZWFyY2guXG4gIF8uaW5kZXhPZiA9IGZ1bmN0aW9uKGFycmF5LCBpdGVtLCBpc1NvcnRlZCkge1xuICAgIGlmIChhcnJheSA9PSBudWxsKSByZXR1cm4gLTE7XG4gICAgdmFyIGkgPSAwLCBsZW5ndGggPSBhcnJheS5sZW5ndGg7XG4gICAgaWYgKGlzU29ydGVkKSB7XG4gICAgICBpZiAodHlwZW9mIGlzU29ydGVkID09ICdudW1iZXInKSB7XG4gICAgICAgIGkgPSBpc1NvcnRlZCA8IDAgPyBNYXRoLm1heCgwLCBsZW5ndGggKyBpc1NvcnRlZCkgOiBpc1NvcnRlZDtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGkgPSBfLnNvcnRlZEluZGV4KGFycmF5LCBpdGVtKTtcbiAgICAgICAgcmV0dXJuIGFycmF5W2ldID09PSBpdGVtID8gaSA6IC0xO1xuICAgICAgfVxuICAgIH1cbiAgICBmb3IgKDsgaSA8IGxlbmd0aDsgaSsrKSBpZiAoYXJyYXlbaV0gPT09IGl0ZW0pIHJldHVybiBpO1xuICAgIHJldHVybiAtMTtcbiAgfTtcblxuICBfLmxhc3RJbmRleE9mID0gZnVuY3Rpb24oYXJyYXksIGl0ZW0sIGZyb20pIHtcbiAgICBpZiAoYXJyYXkgPT0gbnVsbCkgcmV0dXJuIC0xO1xuICAgIHZhciBpZHggPSBhcnJheS5sZW5ndGg7XG4gICAgaWYgKHR5cGVvZiBmcm9tID09ICdudW1iZXInKSB7XG4gICAgICBpZHggPSBmcm9tIDwgMCA/IGlkeCArIGZyb20gKyAxIDogTWF0aC5taW4oaWR4LCBmcm9tICsgMSk7XG4gICAgfVxuICAgIHdoaWxlICgtLWlkeCA+PSAwKSBpZiAoYXJyYXlbaWR4XSA9PT0gaXRlbSkgcmV0dXJuIGlkeDtcbiAgICByZXR1cm4gLTE7XG4gIH07XG5cbiAgLy8gR2VuZXJhdGUgYW4gaW50ZWdlciBBcnJheSBjb250YWluaW5nIGFuIGFyaXRobWV0aWMgcHJvZ3Jlc3Npb24uIEEgcG9ydCBvZlxuICAvLyB0aGUgbmF0aXZlIFB5dGhvbiBgcmFuZ2UoKWAgZnVuY3Rpb24uIFNlZVxuICAvLyBbdGhlIFB5dGhvbiBkb2N1bWVudGF0aW9uXShodHRwOi8vZG9jcy5weXRob24ub3JnL2xpYnJhcnkvZnVuY3Rpb25zLmh0bWwjcmFuZ2UpLlxuICBfLnJhbmdlID0gZnVuY3Rpb24oc3RhcnQsIHN0b3AsIHN0ZXApIHtcbiAgICBpZiAoYXJndW1lbnRzLmxlbmd0aCA8PSAxKSB7XG4gICAgICBzdG9wID0gc3RhcnQgfHwgMDtcbiAgICAgIHN0YXJ0ID0gMDtcbiAgICB9XG4gICAgc3RlcCA9IHN0ZXAgfHwgMTtcblxuICAgIHZhciBsZW5ndGggPSBNYXRoLm1heChNYXRoLmNlaWwoKHN0b3AgLSBzdGFydCkgLyBzdGVwKSwgMCk7XG4gICAgdmFyIHJhbmdlID0gQXJyYXkobGVuZ3RoKTtcblxuICAgIGZvciAodmFyIGlkeCA9IDA7IGlkeCA8IGxlbmd0aDsgaWR4KyssIHN0YXJ0ICs9IHN0ZXApIHtcbiAgICAgIHJhbmdlW2lkeF0gPSBzdGFydDtcbiAgICB9XG5cbiAgICByZXR1cm4gcmFuZ2U7XG4gIH07XG5cbiAgLy8gRnVuY3Rpb24gKGFoZW0pIEZ1bmN0aW9uc1xuICAvLyAtLS0tLS0tLS0tLS0tLS0tLS1cblxuICAvLyBSZXVzYWJsZSBjb25zdHJ1Y3RvciBmdW5jdGlvbiBmb3IgcHJvdG90eXBlIHNldHRpbmcuXG4gIHZhciBDdG9yID0gZnVuY3Rpb24oKXt9O1xuXG4gIC8vIENyZWF0ZSBhIGZ1bmN0aW9uIGJvdW5kIHRvIGEgZ2l2ZW4gb2JqZWN0IChhc3NpZ25pbmcgYHRoaXNgLCBhbmQgYXJndW1lbnRzLFxuICAvLyBvcHRpb25hbGx5KS4gRGVsZWdhdGVzIHRvICoqRUNNQVNjcmlwdCA1KioncyBuYXRpdmUgYEZ1bmN0aW9uLmJpbmRgIGlmXG4gIC8vIGF2YWlsYWJsZS5cbiAgXy5iaW5kID0gZnVuY3Rpb24oZnVuYywgY29udGV4dCkge1xuICAgIHZhciBhcmdzLCBib3VuZDtcbiAgICBpZiAobmF0aXZlQmluZCAmJiBmdW5jLmJpbmQgPT09IG5hdGl2ZUJpbmQpIHJldHVybiBuYXRpdmVCaW5kLmFwcGx5KGZ1bmMsIHNsaWNlLmNhbGwoYXJndW1lbnRzLCAxKSk7XG4gICAgaWYgKCFfLmlzRnVuY3Rpb24oZnVuYykpIHRocm93IG5ldyBUeXBlRXJyb3IoJ0JpbmQgbXVzdCBiZSBjYWxsZWQgb24gYSBmdW5jdGlvbicpO1xuICAgIGFyZ3MgPSBzbGljZS5jYWxsKGFyZ3VtZW50cywgMik7XG4gICAgYm91bmQgPSBmdW5jdGlvbigpIHtcbiAgICAgIGlmICghKHRoaXMgaW5zdGFuY2VvZiBib3VuZCkpIHJldHVybiBmdW5jLmFwcGx5KGNvbnRleHQsIGFyZ3MuY29uY2F0KHNsaWNlLmNhbGwoYXJndW1lbnRzKSkpO1xuICAgICAgQ3Rvci5wcm90b3R5cGUgPSBmdW5jLnByb3RvdHlwZTtcbiAgICAgIHZhciBzZWxmID0gbmV3IEN0b3I7XG4gICAgICBDdG9yLnByb3RvdHlwZSA9IG51bGw7XG4gICAgICB2YXIgcmVzdWx0ID0gZnVuYy5hcHBseShzZWxmLCBhcmdzLmNvbmNhdChzbGljZS5jYWxsKGFyZ3VtZW50cykpKTtcbiAgICAgIGlmIChfLmlzT2JqZWN0KHJlc3VsdCkpIHJldHVybiByZXN1bHQ7XG4gICAgICByZXR1cm4gc2VsZjtcbiAgICB9O1xuICAgIHJldHVybiBib3VuZDtcbiAgfTtcblxuICAvLyBQYXJ0aWFsbHkgYXBwbHkgYSBmdW5jdGlvbiBieSBjcmVhdGluZyBhIHZlcnNpb24gdGhhdCBoYXMgaGFkIHNvbWUgb2YgaXRzXG4gIC8vIGFyZ3VtZW50cyBwcmUtZmlsbGVkLCB3aXRob3V0IGNoYW5naW5nIGl0cyBkeW5hbWljIGB0aGlzYCBjb250ZXh0LiBfIGFjdHNcbiAgLy8gYXMgYSBwbGFjZWhvbGRlciwgYWxsb3dpbmcgYW55IGNvbWJpbmF0aW9uIG9mIGFyZ3VtZW50cyB0byBiZSBwcmUtZmlsbGVkLlxuICBfLnBhcnRpYWwgPSBmdW5jdGlvbihmdW5jKSB7XG4gICAgdmFyIGJvdW5kQXJncyA9IHNsaWNlLmNhbGwoYXJndW1lbnRzLCAxKTtcbiAgICByZXR1cm4gZnVuY3Rpb24oKSB7XG4gICAgICB2YXIgcG9zaXRpb24gPSAwO1xuICAgICAgdmFyIGFyZ3MgPSBib3VuZEFyZ3Muc2xpY2UoKTtcbiAgICAgIGZvciAodmFyIGkgPSAwLCBsZW5ndGggPSBhcmdzLmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGlmIChhcmdzW2ldID09PSBfKSBhcmdzW2ldID0gYXJndW1lbnRzW3Bvc2l0aW9uKytdO1xuICAgICAgfVxuICAgICAgd2hpbGUgKHBvc2l0aW9uIDwgYXJndW1lbnRzLmxlbmd0aCkgYXJncy5wdXNoKGFyZ3VtZW50c1twb3NpdGlvbisrXSk7XG4gICAgICByZXR1cm4gZnVuYy5hcHBseSh0aGlzLCBhcmdzKTtcbiAgICB9O1xuICB9O1xuXG4gIC8vIEJpbmQgYSBudW1iZXIgb2YgYW4gb2JqZWN0J3MgbWV0aG9kcyB0byB0aGF0IG9iamVjdC4gUmVtYWluaW5nIGFyZ3VtZW50c1xuICAvLyBhcmUgdGhlIG1ldGhvZCBuYW1lcyB0byBiZSBib3VuZC4gVXNlZnVsIGZvciBlbnN1cmluZyB0aGF0IGFsbCBjYWxsYmFja3NcbiAgLy8gZGVmaW5lZCBvbiBhbiBvYmplY3QgYmVsb25nIHRvIGl0LlxuICBfLmJpbmRBbGwgPSBmdW5jdGlvbihvYmopIHtcbiAgICB2YXIgaSwgbGVuZ3RoID0gYXJndW1lbnRzLmxlbmd0aCwga2V5O1xuICAgIGlmIChsZW5ndGggPD0gMSkgdGhyb3cgbmV3IEVycm9yKCdiaW5kQWxsIG11c3QgYmUgcGFzc2VkIGZ1bmN0aW9uIG5hbWVzJyk7XG4gICAgZm9yIChpID0gMTsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICBrZXkgPSBhcmd1bWVudHNbaV07XG4gICAgICBvYmpba2V5XSA9IF8uYmluZChvYmpba2V5XSwgb2JqKTtcbiAgICB9XG4gICAgcmV0dXJuIG9iajtcbiAgfTtcblxuICAvLyBNZW1vaXplIGFuIGV4cGVuc2l2ZSBmdW5jdGlvbiBieSBzdG9yaW5nIGl0cyByZXN1bHRzLlxuICBfLm1lbW9pemUgPSBmdW5jdGlvbihmdW5jLCBoYXNoZXIpIHtcbiAgICB2YXIgbWVtb2l6ZSA9IGZ1bmN0aW9uKGtleSkge1xuICAgICAgdmFyIGNhY2hlID0gbWVtb2l6ZS5jYWNoZTtcbiAgICAgIHZhciBhZGRyZXNzID0gaGFzaGVyID8gaGFzaGVyLmFwcGx5KHRoaXMsIGFyZ3VtZW50cykgOiBrZXk7XG4gICAgICBpZiAoIV8uaGFzKGNhY2hlLCBhZGRyZXNzKSkgY2FjaGVbYWRkcmVzc10gPSBmdW5jLmFwcGx5KHRoaXMsIGFyZ3VtZW50cyk7XG4gICAgICByZXR1cm4gY2FjaGVbYWRkcmVzc107XG4gICAgfTtcbiAgICBtZW1vaXplLmNhY2hlID0ge307XG4gICAgcmV0dXJuIG1lbW9pemU7XG4gIH07XG5cbiAgLy8gRGVsYXlzIGEgZnVuY3Rpb24gZm9yIHRoZSBnaXZlbiBudW1iZXIgb2YgbWlsbGlzZWNvbmRzLCBhbmQgdGhlbiBjYWxsc1xuICAvLyBpdCB3aXRoIHRoZSBhcmd1bWVudHMgc3VwcGxpZWQuXG4gIF8uZGVsYXkgPSBmdW5jdGlvbihmdW5jLCB3YWl0KSB7XG4gICAgdmFyIGFyZ3MgPSBzbGljZS5jYWxsKGFyZ3VtZW50cywgMik7XG4gICAgcmV0dXJuIHNldFRpbWVvdXQoZnVuY3Rpb24oKXtcbiAgICAgIHJldHVybiBmdW5jLmFwcGx5KG51bGwsIGFyZ3MpO1xuICAgIH0sIHdhaXQpO1xuICB9O1xuXG4gIC8vIERlZmVycyBhIGZ1bmN0aW9uLCBzY2hlZHVsaW5nIGl0IHRvIHJ1biBhZnRlciB0aGUgY3VycmVudCBjYWxsIHN0YWNrIGhhc1xuICAvLyBjbGVhcmVkLlxuICBfLmRlZmVyID0gZnVuY3Rpb24oZnVuYykge1xuICAgIHJldHVybiBfLmRlbGF5LmFwcGx5KF8sIFtmdW5jLCAxXS5jb25jYXQoc2xpY2UuY2FsbChhcmd1bWVudHMsIDEpKSk7XG4gIH07XG5cbiAgLy8gUmV0dXJucyBhIGZ1bmN0aW9uLCB0aGF0LCB3aGVuIGludm9rZWQsIHdpbGwgb25seSBiZSB0cmlnZ2VyZWQgYXQgbW9zdCBvbmNlXG4gIC8vIGR1cmluZyBhIGdpdmVuIHdpbmRvdyBvZiB0aW1lLiBOb3JtYWxseSwgdGhlIHRocm90dGxlZCBmdW5jdGlvbiB3aWxsIHJ1blxuICAvLyBhcyBtdWNoIGFzIGl0IGNhbiwgd2l0aG91dCBldmVyIGdvaW5nIG1vcmUgdGhhbiBvbmNlIHBlciBgd2FpdGAgZHVyYXRpb247XG4gIC8vIGJ1dCBpZiB5b3UnZCBsaWtlIHRvIGRpc2FibGUgdGhlIGV4ZWN1dGlvbiBvbiB0aGUgbGVhZGluZyBlZGdlLCBwYXNzXG4gIC8vIGB7bGVhZGluZzogZmFsc2V9YC4gVG8gZGlzYWJsZSBleGVjdXRpb24gb24gdGhlIHRyYWlsaW5nIGVkZ2UsIGRpdHRvLlxuICBfLnRocm90dGxlID0gZnVuY3Rpb24oZnVuYywgd2FpdCwgb3B0aW9ucykge1xuICAgIHZhciBjb250ZXh0LCBhcmdzLCByZXN1bHQ7XG4gICAgdmFyIHRpbWVvdXQgPSBudWxsO1xuICAgIHZhciBwcmV2aW91cyA9IDA7XG4gICAgaWYgKCFvcHRpb25zKSBvcHRpb25zID0ge307XG4gICAgdmFyIGxhdGVyID0gZnVuY3Rpb24oKSB7XG4gICAgICBwcmV2aW91cyA9IG9wdGlvbnMubGVhZGluZyA9PT0gZmFsc2UgPyAwIDogXy5ub3coKTtcbiAgICAgIHRpbWVvdXQgPSBudWxsO1xuICAgICAgcmVzdWx0ID0gZnVuYy5hcHBseShjb250ZXh0LCBhcmdzKTtcbiAgICAgIGlmICghdGltZW91dCkgY29udGV4dCA9IGFyZ3MgPSBudWxsO1xuICAgIH07XG4gICAgcmV0dXJuIGZ1bmN0aW9uKCkge1xuICAgICAgdmFyIG5vdyA9IF8ubm93KCk7XG4gICAgICBpZiAoIXByZXZpb3VzICYmIG9wdGlvbnMubGVhZGluZyA9PT0gZmFsc2UpIHByZXZpb3VzID0gbm93O1xuICAgICAgdmFyIHJlbWFpbmluZyA9IHdhaXQgLSAobm93IC0gcHJldmlvdXMpO1xuICAgICAgY29udGV4dCA9IHRoaXM7XG4gICAgICBhcmdzID0gYXJndW1lbnRzO1xuICAgICAgaWYgKHJlbWFpbmluZyA8PSAwIHx8IHJlbWFpbmluZyA+IHdhaXQpIHtcbiAgICAgICAgY2xlYXJUaW1lb3V0KHRpbWVvdXQpO1xuICAgICAgICB0aW1lb3V0ID0gbnVsbDtcbiAgICAgICAgcHJldmlvdXMgPSBub3c7XG4gICAgICAgIHJlc3VsdCA9IGZ1bmMuYXBwbHkoY29udGV4dCwgYXJncyk7XG4gICAgICAgIGlmICghdGltZW91dCkgY29udGV4dCA9IGFyZ3MgPSBudWxsO1xuICAgICAgfSBlbHNlIGlmICghdGltZW91dCAmJiBvcHRpb25zLnRyYWlsaW5nICE9PSBmYWxzZSkge1xuICAgICAgICB0aW1lb3V0ID0gc2V0VGltZW91dChsYXRlciwgcmVtYWluaW5nKTtcbiAgICAgIH1cbiAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgfTtcbiAgfTtcblxuICAvLyBSZXR1cm5zIGEgZnVuY3Rpb24sIHRoYXQsIGFzIGxvbmcgYXMgaXQgY29udGludWVzIHRvIGJlIGludm9rZWQsIHdpbGwgbm90XG4gIC8vIGJlIHRyaWdnZXJlZC4gVGhlIGZ1bmN0aW9uIHdpbGwgYmUgY2FsbGVkIGFmdGVyIGl0IHN0b3BzIGJlaW5nIGNhbGxlZCBmb3JcbiAgLy8gTiBtaWxsaXNlY29uZHMuIElmIGBpbW1lZGlhdGVgIGlzIHBhc3NlZCwgdHJpZ2dlciB0aGUgZnVuY3Rpb24gb24gdGhlXG4gIC8vIGxlYWRpbmcgZWRnZSwgaW5zdGVhZCBvZiB0aGUgdHJhaWxpbmcuXG4gIF8uZGVib3VuY2UgPSBmdW5jdGlvbihmdW5jLCB3YWl0LCBpbW1lZGlhdGUpIHtcbiAgICB2YXIgdGltZW91dCwgYXJncywgY29udGV4dCwgdGltZXN0YW1wLCByZXN1bHQ7XG5cbiAgICB2YXIgbGF0ZXIgPSBmdW5jdGlvbigpIHtcbiAgICAgIHZhciBsYXN0ID0gXy5ub3coKSAtIHRpbWVzdGFtcDtcblxuICAgICAgaWYgKGxhc3QgPCB3YWl0ICYmIGxhc3QgPiAwKSB7XG4gICAgICAgIHRpbWVvdXQgPSBzZXRUaW1lb3V0KGxhdGVyLCB3YWl0IC0gbGFzdCk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aW1lb3V0ID0gbnVsbDtcbiAgICAgICAgaWYgKCFpbW1lZGlhdGUpIHtcbiAgICAgICAgICByZXN1bHQgPSBmdW5jLmFwcGx5KGNvbnRleHQsIGFyZ3MpO1xuICAgICAgICAgIGlmICghdGltZW91dCkgY29udGV4dCA9IGFyZ3MgPSBudWxsO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfTtcblxuICAgIHJldHVybiBmdW5jdGlvbigpIHtcbiAgICAgIGNvbnRleHQgPSB0aGlzO1xuICAgICAgYXJncyA9IGFyZ3VtZW50cztcbiAgICAgIHRpbWVzdGFtcCA9IF8ubm93KCk7XG4gICAgICB2YXIgY2FsbE5vdyA9IGltbWVkaWF0ZSAmJiAhdGltZW91dDtcbiAgICAgIGlmICghdGltZW91dCkgdGltZW91dCA9IHNldFRpbWVvdXQobGF0ZXIsIHdhaXQpO1xuICAgICAgaWYgKGNhbGxOb3cpIHtcbiAgICAgICAgcmVzdWx0ID0gZnVuYy5hcHBseShjb250ZXh0LCBhcmdzKTtcbiAgICAgICAgY29udGV4dCA9IGFyZ3MgPSBudWxsO1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gcmVzdWx0O1xuICAgIH07XG4gIH07XG5cbiAgLy8gUmV0dXJucyB0aGUgZmlyc3QgZnVuY3Rpb24gcGFzc2VkIGFzIGFuIGFyZ3VtZW50IHRvIHRoZSBzZWNvbmQsXG4gIC8vIGFsbG93aW5nIHlvdSB0byBhZGp1c3QgYXJndW1lbnRzLCBydW4gY29kZSBiZWZvcmUgYW5kIGFmdGVyLCBhbmRcbiAgLy8gY29uZGl0aW9uYWxseSBleGVjdXRlIHRoZSBvcmlnaW5hbCBmdW5jdGlvbi5cbiAgXy53cmFwID0gZnVuY3Rpb24oZnVuYywgd3JhcHBlcikge1xuICAgIHJldHVybiBfLnBhcnRpYWwod3JhcHBlciwgZnVuYyk7XG4gIH07XG5cbiAgLy8gUmV0dXJucyBhIG5lZ2F0ZWQgdmVyc2lvbiBvZiB0aGUgcGFzc2VkLWluIHByZWRpY2F0ZS5cbiAgXy5uZWdhdGUgPSBmdW5jdGlvbihwcmVkaWNhdGUpIHtcbiAgICByZXR1cm4gZnVuY3Rpb24oKSB7XG4gICAgICByZXR1cm4gIXByZWRpY2F0ZS5hcHBseSh0aGlzLCBhcmd1bWVudHMpO1xuICAgIH07XG4gIH07XG5cbiAgLy8gUmV0dXJucyBhIGZ1bmN0aW9uIHRoYXQgaXMgdGhlIGNvbXBvc2l0aW9uIG9mIGEgbGlzdCBvZiBmdW5jdGlvbnMsIGVhY2hcbiAgLy8gY29uc3VtaW5nIHRoZSByZXR1cm4gdmFsdWUgb2YgdGhlIGZ1bmN0aW9uIHRoYXQgZm9sbG93cy5cbiAgXy5jb21wb3NlID0gZnVuY3Rpb24oKSB7XG4gICAgdmFyIGFyZ3MgPSBhcmd1bWVudHM7XG4gICAgdmFyIHN0YXJ0ID0gYXJncy5sZW5ndGggLSAxO1xuICAgIHJldHVybiBmdW5jdGlvbigpIHtcbiAgICAgIHZhciBpID0gc3RhcnQ7XG4gICAgICB2YXIgcmVzdWx0ID0gYXJnc1tzdGFydF0uYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgICAgIHdoaWxlIChpLS0pIHJlc3VsdCA9IGFyZ3NbaV0uY2FsbCh0aGlzLCByZXN1bHQpO1xuICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICB9O1xuICB9O1xuXG4gIC8vIFJldHVybnMgYSBmdW5jdGlvbiB0aGF0IHdpbGwgb25seSBiZSBleGVjdXRlZCBhZnRlciBiZWluZyBjYWxsZWQgTiB0aW1lcy5cbiAgXy5hZnRlciA9IGZ1bmN0aW9uKHRpbWVzLCBmdW5jKSB7XG4gICAgcmV0dXJuIGZ1bmN0aW9uKCkge1xuICAgICAgaWYgKC0tdGltZXMgPCAxKSB7XG4gICAgICAgIHJldHVybiBmdW5jLmFwcGx5KHRoaXMsIGFyZ3VtZW50cyk7XG4gICAgICB9XG4gICAgfTtcbiAgfTtcblxuICAvLyBSZXR1cm5zIGEgZnVuY3Rpb24gdGhhdCB3aWxsIG9ubHkgYmUgZXhlY3V0ZWQgYmVmb3JlIGJlaW5nIGNhbGxlZCBOIHRpbWVzLlxuICBfLmJlZm9yZSA9IGZ1bmN0aW9uKHRpbWVzLCBmdW5jKSB7XG4gICAgdmFyIG1lbW87XG4gICAgcmV0dXJuIGZ1bmN0aW9uKCkge1xuICAgICAgaWYgKC0tdGltZXMgPiAwKSB7XG4gICAgICAgIG1lbW8gPSBmdW5jLmFwcGx5KHRoaXMsIGFyZ3VtZW50cyk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBmdW5jID0gbnVsbDtcbiAgICAgIH1cbiAgICAgIHJldHVybiBtZW1vO1xuICAgIH07XG4gIH07XG5cbiAgLy8gUmV0dXJucyBhIGZ1bmN0aW9uIHRoYXQgd2lsbCBiZSBleGVjdXRlZCBhdCBtb3N0IG9uZSB0aW1lLCBubyBtYXR0ZXIgaG93XG4gIC8vIG9mdGVuIHlvdSBjYWxsIGl0LiBVc2VmdWwgZm9yIGxhenkgaW5pdGlhbGl6YXRpb24uXG4gIF8ub25jZSA9IF8ucGFydGlhbChfLmJlZm9yZSwgMik7XG5cbiAgLy8gT2JqZWN0IEZ1bmN0aW9uc1xuICAvLyAtLS0tLS0tLS0tLS0tLS0tXG5cbiAgLy8gUmV0cmlldmUgdGhlIG5hbWVzIG9mIGFuIG9iamVjdCdzIHByb3BlcnRpZXMuXG4gIC8vIERlbGVnYXRlcyB0byAqKkVDTUFTY3JpcHQgNSoqJ3MgbmF0aXZlIGBPYmplY3Qua2V5c2BcbiAgXy5rZXlzID0gZnVuY3Rpb24ob2JqKSB7XG4gICAgaWYgKCFfLmlzT2JqZWN0KG9iaikpIHJldHVybiBbXTtcbiAgICBpZiAobmF0aXZlS2V5cykgcmV0dXJuIG5hdGl2ZUtleXMob2JqKTtcbiAgICB2YXIga2V5cyA9IFtdO1xuICAgIGZvciAodmFyIGtleSBpbiBvYmopIGlmIChfLmhhcyhvYmosIGtleSkpIGtleXMucHVzaChrZXkpO1xuICAgIHJldHVybiBrZXlzO1xuICB9O1xuXG4gIC8vIFJldHJpZXZlIHRoZSB2YWx1ZXMgb2YgYW4gb2JqZWN0J3MgcHJvcGVydGllcy5cbiAgXy52YWx1ZXMgPSBmdW5jdGlvbihvYmopIHtcbiAgICB2YXIga2V5cyA9IF8ua2V5cyhvYmopO1xuICAgIHZhciBsZW5ndGggPSBrZXlzLmxlbmd0aDtcbiAgICB2YXIgdmFsdWVzID0gQXJyYXkobGVuZ3RoKTtcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICB2YWx1ZXNbaV0gPSBvYmpba2V5c1tpXV07XG4gICAgfVxuICAgIHJldHVybiB2YWx1ZXM7XG4gIH07XG5cbiAgLy8gQ29udmVydCBhbiBvYmplY3QgaW50byBhIGxpc3Qgb2YgYFtrZXksIHZhbHVlXWAgcGFpcnMuXG4gIF8ucGFpcnMgPSBmdW5jdGlvbihvYmopIHtcbiAgICB2YXIga2V5cyA9IF8ua2V5cyhvYmopO1xuICAgIHZhciBsZW5ndGggPSBrZXlzLmxlbmd0aDtcbiAgICB2YXIgcGFpcnMgPSBBcnJheShsZW5ndGgpO1xuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgIHBhaXJzW2ldID0gW2tleXNbaV0sIG9ialtrZXlzW2ldXV07XG4gICAgfVxuICAgIHJldHVybiBwYWlycztcbiAgfTtcblxuICAvLyBJbnZlcnQgdGhlIGtleXMgYW5kIHZhbHVlcyBvZiBhbiBvYmplY3QuIFRoZSB2YWx1ZXMgbXVzdCBiZSBzZXJpYWxpemFibGUuXG4gIF8uaW52ZXJ0ID0gZnVuY3Rpb24ob2JqKSB7XG4gICAgdmFyIHJlc3VsdCA9IHt9O1xuICAgIHZhciBrZXlzID0gXy5rZXlzKG9iaik7XG4gICAgZm9yICh2YXIgaSA9IDAsIGxlbmd0aCA9IGtleXMubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgIHJlc3VsdFtvYmpba2V5c1tpXV1dID0ga2V5c1tpXTtcbiAgICB9XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfTtcblxuICAvLyBSZXR1cm4gYSBzb3J0ZWQgbGlzdCBvZiB0aGUgZnVuY3Rpb24gbmFtZXMgYXZhaWxhYmxlIG9uIHRoZSBvYmplY3QuXG4gIC8vIEFsaWFzZWQgYXMgYG1ldGhvZHNgXG4gIF8uZnVuY3Rpb25zID0gXy5tZXRob2RzID0gZnVuY3Rpb24ob2JqKSB7XG4gICAgdmFyIG5hbWVzID0gW107XG4gICAgZm9yICh2YXIga2V5IGluIG9iaikge1xuICAgICAgaWYgKF8uaXNGdW5jdGlvbihvYmpba2V5XSkpIG5hbWVzLnB1c2goa2V5KTtcbiAgICB9XG4gICAgcmV0dXJuIG5hbWVzLnNvcnQoKTtcbiAgfTtcblxuICAvLyBFeHRlbmQgYSBnaXZlbiBvYmplY3Qgd2l0aCBhbGwgdGhlIHByb3BlcnRpZXMgaW4gcGFzc2VkLWluIG9iamVjdChzKS5cbiAgXy5leHRlbmQgPSBmdW5jdGlvbihvYmopIHtcbiAgICBpZiAoIV8uaXNPYmplY3Qob2JqKSkgcmV0dXJuIG9iajtcbiAgICB2YXIgc291cmNlLCBwcm9wO1xuICAgIGZvciAodmFyIGkgPSAxLCBsZW5ndGggPSBhcmd1bWVudHMubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgIHNvdXJjZSA9IGFyZ3VtZW50c1tpXTtcbiAgICAgIGZvciAocHJvcCBpbiBzb3VyY2UpIHtcbiAgICAgICAgaWYgKGhhc093blByb3BlcnR5LmNhbGwoc291cmNlLCBwcm9wKSkge1xuICAgICAgICAgICAgb2JqW3Byb3BdID0gc291cmNlW3Byb3BdO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBvYmo7XG4gIH07XG5cbiAgLy8gUmV0dXJuIGEgY29weSBvZiB0aGUgb2JqZWN0IG9ubHkgY29udGFpbmluZyB0aGUgd2hpdGVsaXN0ZWQgcHJvcGVydGllcy5cbiAgXy5waWNrID0gZnVuY3Rpb24ob2JqLCBpdGVyYXRlZSwgY29udGV4dCkge1xuICAgIHZhciByZXN1bHQgPSB7fSwga2V5O1xuICAgIGlmIChvYmogPT0gbnVsbCkgcmV0dXJuIHJlc3VsdDtcbiAgICBpZiAoXy5pc0Z1bmN0aW9uKGl0ZXJhdGVlKSkge1xuICAgICAgaXRlcmF0ZWUgPSBjcmVhdGVDYWxsYmFjayhpdGVyYXRlZSwgY29udGV4dCk7XG4gICAgICBmb3IgKGtleSBpbiBvYmopIHtcbiAgICAgICAgdmFyIHZhbHVlID0gb2JqW2tleV07XG4gICAgICAgIGlmIChpdGVyYXRlZSh2YWx1ZSwga2V5LCBvYmopKSByZXN1bHRba2V5XSA9IHZhbHVlO1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICB2YXIga2V5cyA9IGNvbmNhdC5hcHBseShbXSwgc2xpY2UuY2FsbChhcmd1bWVudHMsIDEpKTtcbiAgICAgIG9iaiA9IG5ldyBPYmplY3Qob2JqKTtcbiAgICAgIGZvciAodmFyIGkgPSAwLCBsZW5ndGggPSBrZXlzLmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGtleSA9IGtleXNbaV07XG4gICAgICAgIGlmIChrZXkgaW4gb2JqKSByZXN1bHRba2V5XSA9IG9ialtrZXldO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9O1xuXG4gICAvLyBSZXR1cm4gYSBjb3B5IG9mIHRoZSBvYmplY3Qgd2l0aG91dCB0aGUgYmxhY2tsaXN0ZWQgcHJvcGVydGllcy5cbiAgXy5vbWl0ID0gZnVuY3Rpb24ob2JqLCBpdGVyYXRlZSwgY29udGV4dCkge1xuICAgIGlmIChfLmlzRnVuY3Rpb24oaXRlcmF0ZWUpKSB7XG4gICAgICBpdGVyYXRlZSA9IF8ubmVnYXRlKGl0ZXJhdGVlKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdmFyIGtleXMgPSBfLm1hcChjb25jYXQuYXBwbHkoW10sIHNsaWNlLmNhbGwoYXJndW1lbnRzLCAxKSksIFN0cmluZyk7XG4gICAgICBpdGVyYXRlZSA9IGZ1bmN0aW9uKHZhbHVlLCBrZXkpIHtcbiAgICAgICAgcmV0dXJuICFfLmNvbnRhaW5zKGtleXMsIGtleSk7XG4gICAgICB9O1xuICAgIH1cbiAgICByZXR1cm4gXy5waWNrKG9iaiwgaXRlcmF0ZWUsIGNvbnRleHQpO1xuICB9O1xuXG4gIC8vIEZpbGwgaW4gYSBnaXZlbiBvYmplY3Qgd2l0aCBkZWZhdWx0IHByb3BlcnRpZXMuXG4gIF8uZGVmYXVsdHMgPSBmdW5jdGlvbihvYmopIHtcbiAgICBpZiAoIV8uaXNPYmplY3Qob2JqKSkgcmV0dXJuIG9iajtcbiAgICBmb3IgKHZhciBpID0gMSwgbGVuZ3RoID0gYXJndW1lbnRzLmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICB2YXIgc291cmNlID0gYXJndW1lbnRzW2ldO1xuICAgICAgZm9yICh2YXIgcHJvcCBpbiBzb3VyY2UpIHtcbiAgICAgICAgaWYgKG9ialtwcm9wXSA9PT0gdm9pZCAwKSBvYmpbcHJvcF0gPSBzb3VyY2VbcHJvcF07XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBvYmo7XG4gIH07XG5cbiAgLy8gQ3JlYXRlIGEgKHNoYWxsb3ctY2xvbmVkKSBkdXBsaWNhdGUgb2YgYW4gb2JqZWN0LlxuICBfLmNsb25lID0gZnVuY3Rpb24ob2JqKSB7XG4gICAgaWYgKCFfLmlzT2JqZWN0KG9iaikpIHJldHVybiBvYmo7XG4gICAgcmV0dXJuIF8uaXNBcnJheShvYmopID8gb2JqLnNsaWNlKCkgOiBfLmV4dGVuZCh7fSwgb2JqKTtcbiAgfTtcblxuICAvLyBJbnZva2VzIGludGVyY2VwdG9yIHdpdGggdGhlIG9iaiwgYW5kIHRoZW4gcmV0dXJucyBvYmouXG4gIC8vIFRoZSBwcmltYXJ5IHB1cnBvc2Ugb2YgdGhpcyBtZXRob2QgaXMgdG8gXCJ0YXAgaW50b1wiIGEgbWV0aG9kIGNoYWluLCBpblxuICAvLyBvcmRlciB0byBwZXJmb3JtIG9wZXJhdGlvbnMgb24gaW50ZXJtZWRpYXRlIHJlc3VsdHMgd2l0aGluIHRoZSBjaGFpbi5cbiAgXy50YXAgPSBmdW5jdGlvbihvYmosIGludGVyY2VwdG9yKSB7XG4gICAgaW50ZXJjZXB0b3Iob2JqKTtcbiAgICByZXR1cm4gb2JqO1xuICB9O1xuXG4gIC8vIEludGVybmFsIHJlY3Vyc2l2ZSBjb21wYXJpc29uIGZ1bmN0aW9uIGZvciBgaXNFcXVhbGAuXG4gIHZhciBlcSA9IGZ1bmN0aW9uKGEsIGIsIGFTdGFjaywgYlN0YWNrKSB7XG4gICAgLy8gSWRlbnRpY2FsIG9iamVjdHMgYXJlIGVxdWFsLiBgMCA9PT0gLTBgLCBidXQgdGhleSBhcmVuJ3QgaWRlbnRpY2FsLlxuICAgIC8vIFNlZSB0aGUgW0hhcm1vbnkgYGVnYWxgIHByb3Bvc2FsXShodHRwOi8vd2lraS5lY21hc2NyaXB0Lm9yZy9kb2t1LnBocD9pZD1oYXJtb255OmVnYWwpLlxuICAgIGlmIChhID09PSBiKSByZXR1cm4gYSAhPT0gMCB8fCAxIC8gYSA9PT0gMSAvIGI7XG4gICAgLy8gQSBzdHJpY3QgY29tcGFyaXNvbiBpcyBuZWNlc3NhcnkgYmVjYXVzZSBgbnVsbCA9PSB1bmRlZmluZWRgLlxuICAgIGlmIChhID09IG51bGwgfHwgYiA9PSBudWxsKSByZXR1cm4gYSA9PT0gYjtcbiAgICAvLyBVbndyYXAgYW55IHdyYXBwZWQgb2JqZWN0cy5cbiAgICBpZiAoYSBpbnN0YW5jZW9mIF8pIGEgPSBhLl93cmFwcGVkO1xuICAgIGlmIChiIGluc3RhbmNlb2YgXykgYiA9IGIuX3dyYXBwZWQ7XG4gICAgLy8gQ29tcGFyZSBgW1tDbGFzc11dYCBuYW1lcy5cbiAgICB2YXIgY2xhc3NOYW1lID0gdG9TdHJpbmcuY2FsbChhKTtcbiAgICBpZiAoY2xhc3NOYW1lICE9PSB0b1N0cmluZy5jYWxsKGIpKSByZXR1cm4gZmFsc2U7XG4gICAgc3dpdGNoIChjbGFzc05hbWUpIHtcbiAgICAgIC8vIFN0cmluZ3MsIG51bWJlcnMsIHJlZ3VsYXIgZXhwcmVzc2lvbnMsIGRhdGVzLCBhbmQgYm9vbGVhbnMgYXJlIGNvbXBhcmVkIGJ5IHZhbHVlLlxuICAgICAgY2FzZSAnW29iamVjdCBSZWdFeHBdJzpcbiAgICAgIC8vIFJlZ0V4cHMgYXJlIGNvZXJjZWQgdG8gc3RyaW5ncyBmb3IgY29tcGFyaXNvbiAoTm90ZTogJycgKyAvYS9pID09PSAnL2EvaScpXG4gICAgICBjYXNlICdbb2JqZWN0IFN0cmluZ10nOlxuICAgICAgICAvLyBQcmltaXRpdmVzIGFuZCB0aGVpciBjb3JyZXNwb25kaW5nIG9iamVjdCB3cmFwcGVycyBhcmUgZXF1aXZhbGVudDsgdGh1cywgYFwiNVwiYCBpc1xuICAgICAgICAvLyBlcXVpdmFsZW50IHRvIGBuZXcgU3RyaW5nKFwiNVwiKWAuXG4gICAgICAgIHJldHVybiAnJyArIGEgPT09ICcnICsgYjtcbiAgICAgIGNhc2UgJ1tvYmplY3QgTnVtYmVyXSc6XG4gICAgICAgIC8vIGBOYU5gcyBhcmUgZXF1aXZhbGVudCwgYnV0IG5vbi1yZWZsZXhpdmUuXG4gICAgICAgIC8vIE9iamVjdChOYU4pIGlzIGVxdWl2YWxlbnQgdG8gTmFOXG4gICAgICAgIGlmICgrYSAhPT0gK2EpIHJldHVybiArYiAhPT0gK2I7XG4gICAgICAgIC8vIEFuIGBlZ2FsYCBjb21wYXJpc29uIGlzIHBlcmZvcm1lZCBmb3Igb3RoZXIgbnVtZXJpYyB2YWx1ZXMuXG4gICAgICAgIHJldHVybiArYSA9PT0gMCA/IDEgLyArYSA9PT0gMSAvIGIgOiArYSA9PT0gK2I7XG4gICAgICBjYXNlICdbb2JqZWN0IERhdGVdJzpcbiAgICAgIGNhc2UgJ1tvYmplY3QgQm9vbGVhbl0nOlxuICAgICAgICAvLyBDb2VyY2UgZGF0ZXMgYW5kIGJvb2xlYW5zIHRvIG51bWVyaWMgcHJpbWl0aXZlIHZhbHVlcy4gRGF0ZXMgYXJlIGNvbXBhcmVkIGJ5IHRoZWlyXG4gICAgICAgIC8vIG1pbGxpc2Vjb25kIHJlcHJlc2VudGF0aW9ucy4gTm90ZSB0aGF0IGludmFsaWQgZGF0ZXMgd2l0aCBtaWxsaXNlY29uZCByZXByZXNlbnRhdGlvbnNcbiAgICAgICAgLy8gb2YgYE5hTmAgYXJlIG5vdCBlcXVpdmFsZW50LlxuICAgICAgICByZXR1cm4gK2EgPT09ICtiO1xuICAgIH1cbiAgICBpZiAodHlwZW9mIGEgIT0gJ29iamVjdCcgfHwgdHlwZW9mIGIgIT0gJ29iamVjdCcpIHJldHVybiBmYWxzZTtcbiAgICAvLyBBc3N1bWUgZXF1YWxpdHkgZm9yIGN5Y2xpYyBzdHJ1Y3R1cmVzLiBUaGUgYWxnb3JpdGhtIGZvciBkZXRlY3RpbmcgY3ljbGljXG4gICAgLy8gc3RydWN0dXJlcyBpcyBhZGFwdGVkIGZyb20gRVMgNS4xIHNlY3Rpb24gMTUuMTIuMywgYWJzdHJhY3Qgb3BlcmF0aW9uIGBKT2AuXG4gICAgdmFyIGxlbmd0aCA9IGFTdGFjay5sZW5ndGg7XG4gICAgd2hpbGUgKGxlbmd0aC0tKSB7XG4gICAgICAvLyBMaW5lYXIgc2VhcmNoLiBQZXJmb3JtYW5jZSBpcyBpbnZlcnNlbHkgcHJvcG9ydGlvbmFsIHRvIHRoZSBudW1iZXIgb2ZcbiAgICAgIC8vIHVuaXF1ZSBuZXN0ZWQgc3RydWN0dXJlcy5cbiAgICAgIGlmIChhU3RhY2tbbGVuZ3RoXSA9PT0gYSkgcmV0dXJuIGJTdGFja1tsZW5ndGhdID09PSBiO1xuICAgIH1cbiAgICAvLyBPYmplY3RzIHdpdGggZGlmZmVyZW50IGNvbnN0cnVjdG9ycyBhcmUgbm90IGVxdWl2YWxlbnQsIGJ1dCBgT2JqZWN0YHNcbiAgICAvLyBmcm9tIGRpZmZlcmVudCBmcmFtZXMgYXJlLlxuICAgIHZhciBhQ3RvciA9IGEuY29uc3RydWN0b3IsIGJDdG9yID0gYi5jb25zdHJ1Y3RvcjtcbiAgICBpZiAoXG4gICAgICBhQ3RvciAhPT0gYkN0b3IgJiZcbiAgICAgIC8vIEhhbmRsZSBPYmplY3QuY3JlYXRlKHgpIGNhc2VzXG4gICAgICAnY29uc3RydWN0b3InIGluIGEgJiYgJ2NvbnN0cnVjdG9yJyBpbiBiICYmXG4gICAgICAhKF8uaXNGdW5jdGlvbihhQ3RvcikgJiYgYUN0b3IgaW5zdGFuY2VvZiBhQ3RvciAmJlxuICAgICAgICBfLmlzRnVuY3Rpb24oYkN0b3IpICYmIGJDdG9yIGluc3RhbmNlb2YgYkN0b3IpXG4gICAgKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIC8vIEFkZCB0aGUgZmlyc3Qgb2JqZWN0IHRvIHRoZSBzdGFjayBvZiB0cmF2ZXJzZWQgb2JqZWN0cy5cbiAgICBhU3RhY2sucHVzaChhKTtcbiAgICBiU3RhY2sucHVzaChiKTtcbiAgICB2YXIgc2l6ZSwgcmVzdWx0O1xuICAgIC8vIFJlY3Vyc2l2ZWx5IGNvbXBhcmUgb2JqZWN0cyBhbmQgYXJyYXlzLlxuICAgIGlmIChjbGFzc05hbWUgPT09ICdbb2JqZWN0IEFycmF5XScpIHtcbiAgICAgIC8vIENvbXBhcmUgYXJyYXkgbGVuZ3RocyB0byBkZXRlcm1pbmUgaWYgYSBkZWVwIGNvbXBhcmlzb24gaXMgbmVjZXNzYXJ5LlxuICAgICAgc2l6ZSA9IGEubGVuZ3RoO1xuICAgICAgcmVzdWx0ID0gc2l6ZSA9PT0gYi5sZW5ndGg7XG4gICAgICBpZiAocmVzdWx0KSB7XG4gICAgICAgIC8vIERlZXAgY29tcGFyZSB0aGUgY29udGVudHMsIGlnbm9yaW5nIG5vbi1udW1lcmljIHByb3BlcnRpZXMuXG4gICAgICAgIHdoaWxlIChzaXplLS0pIHtcbiAgICAgICAgICBpZiAoIShyZXN1bHQgPSBlcShhW3NpemVdLCBiW3NpemVdLCBhU3RhY2ssIGJTdGFjaykpKSBicmVhaztcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICAvLyBEZWVwIGNvbXBhcmUgb2JqZWN0cy5cbiAgICAgIHZhciBrZXlzID0gXy5rZXlzKGEpLCBrZXk7XG4gICAgICBzaXplID0ga2V5cy5sZW5ndGg7XG4gICAgICAvLyBFbnN1cmUgdGhhdCBib3RoIG9iamVjdHMgY29udGFpbiB0aGUgc2FtZSBudW1iZXIgb2YgcHJvcGVydGllcyBiZWZvcmUgY29tcGFyaW5nIGRlZXAgZXF1YWxpdHkuXG4gICAgICByZXN1bHQgPSBfLmtleXMoYikubGVuZ3RoID09PSBzaXplO1xuICAgICAgaWYgKHJlc3VsdCkge1xuICAgICAgICB3aGlsZSAoc2l6ZS0tKSB7XG4gICAgICAgICAgLy8gRGVlcCBjb21wYXJlIGVhY2ggbWVtYmVyXG4gICAgICAgICAga2V5ID0ga2V5c1tzaXplXTtcbiAgICAgICAgICBpZiAoIShyZXN1bHQgPSBfLmhhcyhiLCBrZXkpICYmIGVxKGFba2V5XSwgYltrZXldLCBhU3RhY2ssIGJTdGFjaykpKSBicmVhaztcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICAvLyBSZW1vdmUgdGhlIGZpcnN0IG9iamVjdCBmcm9tIHRoZSBzdGFjayBvZiB0cmF2ZXJzZWQgb2JqZWN0cy5cbiAgICBhU3RhY2sucG9wKCk7XG4gICAgYlN0YWNrLnBvcCgpO1xuICAgIHJldHVybiByZXN1bHQ7XG4gIH07XG5cbiAgLy8gUGVyZm9ybSBhIGRlZXAgY29tcGFyaXNvbiB0byBjaGVjayBpZiB0d28gb2JqZWN0cyBhcmUgZXF1YWwuXG4gIF8uaXNFcXVhbCA9IGZ1bmN0aW9uKGEsIGIpIHtcbiAgICByZXR1cm4gZXEoYSwgYiwgW10sIFtdKTtcbiAgfTtcblxuICAvLyBJcyBhIGdpdmVuIGFycmF5LCBzdHJpbmcsIG9yIG9iamVjdCBlbXB0eT9cbiAgLy8gQW4gXCJlbXB0eVwiIG9iamVjdCBoYXMgbm8gZW51bWVyYWJsZSBvd24tcHJvcGVydGllcy5cbiAgXy5pc0VtcHR5ID0gZnVuY3Rpb24ob2JqKSB7XG4gICAgaWYgKG9iaiA9PSBudWxsKSByZXR1cm4gdHJ1ZTtcbiAgICBpZiAoXy5pc0FycmF5KG9iaikgfHwgXy5pc1N0cmluZyhvYmopIHx8IF8uaXNBcmd1bWVudHMob2JqKSkgcmV0dXJuIG9iai5sZW5ndGggPT09IDA7XG4gICAgZm9yICh2YXIga2V5IGluIG9iaikgaWYgKF8uaGFzKG9iaiwga2V5KSkgcmV0dXJuIGZhbHNlO1xuICAgIHJldHVybiB0cnVlO1xuICB9O1xuXG4gIC8vIElzIGEgZ2l2ZW4gdmFsdWUgYSBET00gZWxlbWVudD9cbiAgXy5pc0VsZW1lbnQgPSBmdW5jdGlvbihvYmopIHtcbiAgICByZXR1cm4gISEob2JqICYmIG9iai5ub2RlVHlwZSA9PT0gMSk7XG4gIH07XG5cbiAgLy8gSXMgYSBnaXZlbiB2YWx1ZSBhbiBhcnJheT9cbiAgLy8gRGVsZWdhdGVzIHRvIEVDTUE1J3MgbmF0aXZlIEFycmF5LmlzQXJyYXlcbiAgXy5pc0FycmF5ID0gbmF0aXZlSXNBcnJheSB8fCBmdW5jdGlvbihvYmopIHtcbiAgICByZXR1cm4gdG9TdHJpbmcuY2FsbChvYmopID09PSAnW29iamVjdCBBcnJheV0nO1xuICB9O1xuXG4gIC8vIElzIGEgZ2l2ZW4gdmFyaWFibGUgYW4gb2JqZWN0P1xuICBfLmlzT2JqZWN0ID0gZnVuY3Rpb24ob2JqKSB7XG4gICAgdmFyIHR5cGUgPSB0eXBlb2Ygb2JqO1xuICAgIHJldHVybiB0eXBlID09PSAnZnVuY3Rpb24nIHx8IHR5cGUgPT09ICdvYmplY3QnICYmICEhb2JqO1xuICB9O1xuXG4gIC8vIEFkZCBzb21lIGlzVHlwZSBtZXRob2RzOiBpc0FyZ3VtZW50cywgaXNGdW5jdGlvbiwgaXNTdHJpbmcsIGlzTnVtYmVyLCBpc0RhdGUsIGlzUmVnRXhwLlxuICBfLmVhY2goWydBcmd1bWVudHMnLCAnRnVuY3Rpb24nLCAnU3RyaW5nJywgJ051bWJlcicsICdEYXRlJywgJ1JlZ0V4cCddLCBmdW5jdGlvbihuYW1lKSB7XG4gICAgX1snaXMnICsgbmFtZV0gPSBmdW5jdGlvbihvYmopIHtcbiAgICAgIHJldHVybiB0b1N0cmluZy5jYWxsKG9iaikgPT09ICdbb2JqZWN0ICcgKyBuYW1lICsgJ10nO1xuICAgIH07XG4gIH0pO1xuXG4gIC8vIERlZmluZSBhIGZhbGxiYWNrIHZlcnNpb24gb2YgdGhlIG1ldGhvZCBpbiBicm93c2VycyAoYWhlbSwgSUUpLCB3aGVyZVxuICAvLyB0aGVyZSBpc24ndCBhbnkgaW5zcGVjdGFibGUgXCJBcmd1bWVudHNcIiB0eXBlLlxuICBpZiAoIV8uaXNBcmd1bWVudHMoYXJndW1lbnRzKSkge1xuICAgIF8uaXNBcmd1bWVudHMgPSBmdW5jdGlvbihvYmopIHtcbiAgICAgIHJldHVybiBfLmhhcyhvYmosICdjYWxsZWUnKTtcbiAgICB9O1xuICB9XG5cbiAgLy8gT3B0aW1pemUgYGlzRnVuY3Rpb25gIGlmIGFwcHJvcHJpYXRlLiBXb3JrIGFyb3VuZCBhbiBJRSAxMSBidWcuXG4gIGlmICh0eXBlb2YgLy4vICE9PSAnZnVuY3Rpb24nKSB7XG4gICAgXy5pc0Z1bmN0aW9uID0gZnVuY3Rpb24ob2JqKSB7XG4gICAgICByZXR1cm4gdHlwZW9mIG9iaiA9PSAnZnVuY3Rpb24nIHx8IGZhbHNlO1xuICAgIH07XG4gIH1cblxuICAvLyBJcyBhIGdpdmVuIG9iamVjdCBhIGZpbml0ZSBudW1iZXI/XG4gIF8uaXNGaW5pdGUgPSBmdW5jdGlvbihvYmopIHtcbiAgICByZXR1cm4gaXNGaW5pdGUob2JqKSAmJiAhaXNOYU4ocGFyc2VGbG9hdChvYmopKTtcbiAgfTtcblxuICAvLyBJcyB0aGUgZ2l2ZW4gdmFsdWUgYE5hTmA/IChOYU4gaXMgdGhlIG9ubHkgbnVtYmVyIHdoaWNoIGRvZXMgbm90IGVxdWFsIGl0c2VsZikuXG4gIF8uaXNOYU4gPSBmdW5jdGlvbihvYmopIHtcbiAgICByZXR1cm4gXy5pc051bWJlcihvYmopICYmIG9iaiAhPT0gK29iajtcbiAgfTtcblxuICAvLyBJcyBhIGdpdmVuIHZhbHVlIGEgYm9vbGVhbj9cbiAgXy5pc0Jvb2xlYW4gPSBmdW5jdGlvbihvYmopIHtcbiAgICByZXR1cm4gb2JqID09PSB0cnVlIHx8IG9iaiA9PT0gZmFsc2UgfHwgdG9TdHJpbmcuY2FsbChvYmopID09PSAnW29iamVjdCBCb29sZWFuXSc7XG4gIH07XG5cbiAgLy8gSXMgYSBnaXZlbiB2YWx1ZSBlcXVhbCB0byBudWxsP1xuICBfLmlzTnVsbCA9IGZ1bmN0aW9uKG9iaikge1xuICAgIHJldHVybiBvYmogPT09IG51bGw7XG4gIH07XG5cbiAgLy8gSXMgYSBnaXZlbiB2YXJpYWJsZSB1bmRlZmluZWQ/XG4gIF8uaXNVbmRlZmluZWQgPSBmdW5jdGlvbihvYmopIHtcbiAgICByZXR1cm4gb2JqID09PSB2b2lkIDA7XG4gIH07XG5cbiAgLy8gU2hvcnRjdXQgZnVuY3Rpb24gZm9yIGNoZWNraW5nIGlmIGFuIG9iamVjdCBoYXMgYSBnaXZlbiBwcm9wZXJ0eSBkaXJlY3RseVxuICAvLyBvbiBpdHNlbGYgKGluIG90aGVyIHdvcmRzLCBub3Qgb24gYSBwcm90b3R5cGUpLlxuICBfLmhhcyA9IGZ1bmN0aW9uKG9iaiwga2V5KSB7XG4gICAgcmV0dXJuIG9iaiAhPSBudWxsICYmIGhhc093blByb3BlcnR5LmNhbGwob2JqLCBrZXkpO1xuICB9O1xuXG4gIC8vIFV0aWxpdHkgRnVuY3Rpb25zXG4gIC8vIC0tLS0tLS0tLS0tLS0tLS0tXG5cbiAgLy8gUnVuIFVuZGVyc2NvcmUuanMgaW4gKm5vQ29uZmxpY3QqIG1vZGUsIHJldHVybmluZyB0aGUgYF9gIHZhcmlhYmxlIHRvIGl0c1xuICAvLyBwcmV2aW91cyBvd25lci4gUmV0dXJucyBhIHJlZmVyZW5jZSB0byB0aGUgVW5kZXJzY29yZSBvYmplY3QuXG4gIF8ubm9Db25mbGljdCA9IGZ1bmN0aW9uKCkge1xuICAgIHJvb3QuXyA9IHByZXZpb3VzVW5kZXJzY29yZTtcbiAgICByZXR1cm4gdGhpcztcbiAgfTtcblxuICAvLyBLZWVwIHRoZSBpZGVudGl0eSBmdW5jdGlvbiBhcm91bmQgZm9yIGRlZmF1bHQgaXRlcmF0ZWVzLlxuICBfLmlkZW50aXR5ID0gZnVuY3Rpb24odmFsdWUpIHtcbiAgICByZXR1cm4gdmFsdWU7XG4gIH07XG5cbiAgXy5jb25zdGFudCA9IGZ1bmN0aW9uKHZhbHVlKSB7XG4gICAgcmV0dXJuIGZ1bmN0aW9uKCkge1xuICAgICAgcmV0dXJuIHZhbHVlO1xuICAgIH07XG4gIH07XG5cbiAgXy5ub29wID0gZnVuY3Rpb24oKXt9O1xuXG4gIF8ucHJvcGVydHkgPSBmdW5jdGlvbihrZXkpIHtcbiAgICByZXR1cm4gZnVuY3Rpb24ob2JqKSB7XG4gICAgICByZXR1cm4gb2JqW2tleV07XG4gICAgfTtcbiAgfTtcblxuICAvLyBSZXR1cm5zIGEgcHJlZGljYXRlIGZvciBjaGVja2luZyB3aGV0aGVyIGFuIG9iamVjdCBoYXMgYSBnaXZlbiBzZXQgb2YgYGtleTp2YWx1ZWAgcGFpcnMuXG4gIF8ubWF0Y2hlcyA9IGZ1bmN0aW9uKGF0dHJzKSB7XG4gICAgdmFyIHBhaXJzID0gXy5wYWlycyhhdHRycyksIGxlbmd0aCA9IHBhaXJzLmxlbmd0aDtcbiAgICByZXR1cm4gZnVuY3Rpb24ob2JqKSB7XG4gICAgICBpZiAob2JqID09IG51bGwpIHJldHVybiAhbGVuZ3RoO1xuICAgICAgb2JqID0gbmV3IE9iamVjdChvYmopO1xuICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgICAgICB2YXIgcGFpciA9IHBhaXJzW2ldLCBrZXkgPSBwYWlyWzBdO1xuICAgICAgICBpZiAocGFpclsxXSAhPT0gb2JqW2tleV0gfHwgIShrZXkgaW4gb2JqKSkgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfTtcbiAgfTtcblxuICAvLyBSdW4gYSBmdW5jdGlvbiAqKm4qKiB0aW1lcy5cbiAgXy50aW1lcyA9IGZ1bmN0aW9uKG4sIGl0ZXJhdGVlLCBjb250ZXh0KSB7XG4gICAgdmFyIGFjY3VtID0gQXJyYXkoTWF0aC5tYXgoMCwgbikpO1xuICAgIGl0ZXJhdGVlID0gY3JlYXRlQ2FsbGJhY2soaXRlcmF0ZWUsIGNvbnRleHQsIDEpO1xuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgbjsgaSsrKSBhY2N1bVtpXSA9IGl0ZXJhdGVlKGkpO1xuICAgIHJldHVybiBhY2N1bTtcbiAgfTtcblxuICAvLyBSZXR1cm4gYSByYW5kb20gaW50ZWdlciBiZXR3ZWVuIG1pbiBhbmQgbWF4IChpbmNsdXNpdmUpLlxuICBfLnJhbmRvbSA9IGZ1bmN0aW9uKG1pbiwgbWF4KSB7XG4gICAgaWYgKG1heCA9PSBudWxsKSB7XG4gICAgICBtYXggPSBtaW47XG4gICAgICBtaW4gPSAwO1xuICAgIH1cbiAgICByZXR1cm4gbWluICsgTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpICogKG1heCAtIG1pbiArIDEpKTtcbiAgfTtcblxuICAvLyBBIChwb3NzaWJseSBmYXN0ZXIpIHdheSB0byBnZXQgdGhlIGN1cnJlbnQgdGltZXN0YW1wIGFzIGFuIGludGVnZXIuXG4gIF8ubm93ID0gRGF0ZS5ub3cgfHwgZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIG5ldyBEYXRlKCkuZ2V0VGltZSgpO1xuICB9O1xuXG4gICAvLyBMaXN0IG9mIEhUTUwgZW50aXRpZXMgZm9yIGVzY2FwaW5nLlxuICB2YXIgZXNjYXBlTWFwID0ge1xuICAgICcmJzogJyZhbXA7JyxcbiAgICAnPCc6ICcmbHQ7JyxcbiAgICAnPic6ICcmZ3Q7JyxcbiAgICAnXCInOiAnJnF1b3Q7JyxcbiAgICBcIidcIjogJyYjeDI3OycsXG4gICAgJ2AnOiAnJiN4NjA7J1xuICB9O1xuICB2YXIgdW5lc2NhcGVNYXAgPSBfLmludmVydChlc2NhcGVNYXApO1xuXG4gIC8vIEZ1bmN0aW9ucyBmb3IgZXNjYXBpbmcgYW5kIHVuZXNjYXBpbmcgc3RyaW5ncyB0by9mcm9tIEhUTUwgaW50ZXJwb2xhdGlvbi5cbiAgdmFyIGNyZWF0ZUVzY2FwZXIgPSBmdW5jdGlvbihtYXApIHtcbiAgICB2YXIgZXNjYXBlciA9IGZ1bmN0aW9uKG1hdGNoKSB7XG4gICAgICByZXR1cm4gbWFwW21hdGNoXTtcbiAgICB9O1xuICAgIC8vIFJlZ2V4ZXMgZm9yIGlkZW50aWZ5aW5nIGEga2V5IHRoYXQgbmVlZHMgdG8gYmUgZXNjYXBlZFxuICAgIHZhciBzb3VyY2UgPSAnKD86JyArIF8ua2V5cyhtYXApLmpvaW4oJ3wnKSArICcpJztcbiAgICB2YXIgdGVzdFJlZ2V4cCA9IFJlZ0V4cChzb3VyY2UpO1xuICAgIHZhciByZXBsYWNlUmVnZXhwID0gUmVnRXhwKHNvdXJjZSwgJ2cnKTtcbiAgICByZXR1cm4gZnVuY3Rpb24oc3RyaW5nKSB7XG4gICAgICBzdHJpbmcgPSBzdHJpbmcgPT0gbnVsbCA/ICcnIDogJycgKyBzdHJpbmc7XG4gICAgICByZXR1cm4gdGVzdFJlZ2V4cC50ZXN0KHN0cmluZykgPyBzdHJpbmcucmVwbGFjZShyZXBsYWNlUmVnZXhwLCBlc2NhcGVyKSA6IHN0cmluZztcbiAgICB9O1xuICB9O1xuICBfLmVzY2FwZSA9IGNyZWF0ZUVzY2FwZXIoZXNjYXBlTWFwKTtcbiAgXy51bmVzY2FwZSA9IGNyZWF0ZUVzY2FwZXIodW5lc2NhcGVNYXApO1xuXG4gIC8vIElmIHRoZSB2YWx1ZSBvZiB0aGUgbmFtZWQgYHByb3BlcnR5YCBpcyBhIGZ1bmN0aW9uIHRoZW4gaW52b2tlIGl0IHdpdGggdGhlXG4gIC8vIGBvYmplY3RgIGFzIGNvbnRleHQ7IG90aGVyd2lzZSwgcmV0dXJuIGl0LlxuICBfLnJlc3VsdCA9IGZ1bmN0aW9uKG9iamVjdCwgcHJvcGVydHkpIHtcbiAgICBpZiAob2JqZWN0ID09IG51bGwpIHJldHVybiB2b2lkIDA7XG4gICAgdmFyIHZhbHVlID0gb2JqZWN0W3Byb3BlcnR5XTtcbiAgICByZXR1cm4gXy5pc0Z1bmN0aW9uKHZhbHVlKSA/IG9iamVjdFtwcm9wZXJ0eV0oKSA6IHZhbHVlO1xuICB9O1xuXG4gIC8vIEdlbmVyYXRlIGEgdW5pcXVlIGludGVnZXIgaWQgKHVuaXF1ZSB3aXRoaW4gdGhlIGVudGlyZSBjbGllbnQgc2Vzc2lvbikuXG4gIC8vIFVzZWZ1bCBmb3IgdGVtcG9yYXJ5IERPTSBpZHMuXG4gIHZhciBpZENvdW50ZXIgPSAwO1xuICBfLnVuaXF1ZUlkID0gZnVuY3Rpb24ocHJlZml4KSB7XG4gICAgdmFyIGlkID0gKytpZENvdW50ZXIgKyAnJztcbiAgICByZXR1cm4gcHJlZml4ID8gcHJlZml4ICsgaWQgOiBpZDtcbiAgfTtcblxuICAvLyBCeSBkZWZhdWx0LCBVbmRlcnNjb3JlIHVzZXMgRVJCLXN0eWxlIHRlbXBsYXRlIGRlbGltaXRlcnMsIGNoYW5nZSB0aGVcbiAgLy8gZm9sbG93aW5nIHRlbXBsYXRlIHNldHRpbmdzIHRvIHVzZSBhbHRlcm5hdGl2ZSBkZWxpbWl0ZXJzLlxuICBfLnRlbXBsYXRlU2V0dGluZ3MgPSB7XG4gICAgZXZhbHVhdGUgICAgOiAvPCUoW1xcc1xcU10rPyklPi9nLFxuICAgIGludGVycG9sYXRlIDogLzwlPShbXFxzXFxTXSs/KSU+L2csXG4gICAgZXNjYXBlICAgICAgOiAvPCUtKFtcXHNcXFNdKz8pJT4vZ1xuICB9O1xuXG4gIC8vIFdoZW4gY3VzdG9taXppbmcgYHRlbXBsYXRlU2V0dGluZ3NgLCBpZiB5b3UgZG9uJ3Qgd2FudCB0byBkZWZpbmUgYW5cbiAgLy8gaW50ZXJwb2xhdGlvbiwgZXZhbHVhdGlvbiBvciBlc2NhcGluZyByZWdleCwgd2UgbmVlZCBvbmUgdGhhdCBpc1xuICAvLyBndWFyYW50ZWVkIG5vdCB0byBtYXRjaC5cbiAgdmFyIG5vTWF0Y2ggPSAvKC4pXi87XG5cbiAgLy8gQ2VydGFpbiBjaGFyYWN0ZXJzIG5lZWQgdG8gYmUgZXNjYXBlZCBzbyB0aGF0IHRoZXkgY2FuIGJlIHB1dCBpbnRvIGFcbiAgLy8gc3RyaW5nIGxpdGVyYWwuXG4gIHZhciBlc2NhcGVzID0ge1xuICAgIFwiJ1wiOiAgICAgIFwiJ1wiLFxuICAgICdcXFxcJzogICAgICdcXFxcJyxcbiAgICAnXFxyJzogICAgICdyJyxcbiAgICAnXFxuJzogICAgICduJyxcbiAgICAnXFx1MjAyOCc6ICd1MjAyOCcsXG4gICAgJ1xcdTIwMjknOiAndTIwMjknXG4gIH07XG5cbiAgdmFyIGVzY2FwZXIgPSAvXFxcXHwnfFxccnxcXG58XFx1MjAyOHxcXHUyMDI5L2c7XG5cbiAgdmFyIGVzY2FwZUNoYXIgPSBmdW5jdGlvbihtYXRjaCkge1xuICAgIHJldHVybiAnXFxcXCcgKyBlc2NhcGVzW21hdGNoXTtcbiAgfTtcblxuICAvLyBKYXZhU2NyaXB0IG1pY3JvLXRlbXBsYXRpbmcsIHNpbWlsYXIgdG8gSm9obiBSZXNpZydzIGltcGxlbWVudGF0aW9uLlxuICAvLyBVbmRlcnNjb3JlIHRlbXBsYXRpbmcgaGFuZGxlcyBhcmJpdHJhcnkgZGVsaW1pdGVycywgcHJlc2VydmVzIHdoaXRlc3BhY2UsXG4gIC8vIGFuZCBjb3JyZWN0bHkgZXNjYXBlcyBxdW90ZXMgd2l0aGluIGludGVycG9sYXRlZCBjb2RlLlxuICAvLyBOQjogYG9sZFNldHRpbmdzYCBvbmx5IGV4aXN0cyBmb3IgYmFja3dhcmRzIGNvbXBhdGliaWxpdHkuXG4gIF8udGVtcGxhdGUgPSBmdW5jdGlvbih0ZXh0LCBzZXR0aW5ncywgb2xkU2V0dGluZ3MpIHtcbiAgICBpZiAoIXNldHRpbmdzICYmIG9sZFNldHRpbmdzKSBzZXR0aW5ncyA9IG9sZFNldHRpbmdzO1xuICAgIHNldHRpbmdzID0gXy5kZWZhdWx0cyh7fSwgc2V0dGluZ3MsIF8udGVtcGxhdGVTZXR0aW5ncyk7XG5cbiAgICAvLyBDb21iaW5lIGRlbGltaXRlcnMgaW50byBvbmUgcmVndWxhciBleHByZXNzaW9uIHZpYSBhbHRlcm5hdGlvbi5cbiAgICB2YXIgbWF0Y2hlciA9IFJlZ0V4cChbXG4gICAgICAoc2V0dGluZ3MuZXNjYXBlIHx8IG5vTWF0Y2gpLnNvdXJjZSxcbiAgICAgIChzZXR0aW5ncy5pbnRlcnBvbGF0ZSB8fCBub01hdGNoKS5zb3VyY2UsXG4gICAgICAoc2V0dGluZ3MuZXZhbHVhdGUgfHwgbm9NYXRjaCkuc291cmNlXG4gICAgXS5qb2luKCd8JykgKyAnfCQnLCAnZycpO1xuXG4gICAgLy8gQ29tcGlsZSB0aGUgdGVtcGxhdGUgc291cmNlLCBlc2NhcGluZyBzdHJpbmcgbGl0ZXJhbHMgYXBwcm9wcmlhdGVseS5cbiAgICB2YXIgaW5kZXggPSAwO1xuICAgIHZhciBzb3VyY2UgPSBcIl9fcCs9J1wiO1xuICAgIHRleHQucmVwbGFjZShtYXRjaGVyLCBmdW5jdGlvbihtYXRjaCwgZXNjYXBlLCBpbnRlcnBvbGF0ZSwgZXZhbHVhdGUsIG9mZnNldCkge1xuICAgICAgc291cmNlICs9IHRleHQuc2xpY2UoaW5kZXgsIG9mZnNldCkucmVwbGFjZShlc2NhcGVyLCBlc2NhcGVDaGFyKTtcbiAgICAgIGluZGV4ID0gb2Zmc2V0ICsgbWF0Y2gubGVuZ3RoO1xuXG4gICAgICBpZiAoZXNjYXBlKSB7XG4gICAgICAgIHNvdXJjZSArPSBcIicrXFxuKChfX3Q9KFwiICsgZXNjYXBlICsgXCIpKT09bnVsbD8nJzpfLmVzY2FwZShfX3QpKStcXG4nXCI7XG4gICAgICB9IGVsc2UgaWYgKGludGVycG9sYXRlKSB7XG4gICAgICAgIHNvdXJjZSArPSBcIicrXFxuKChfX3Q9KFwiICsgaW50ZXJwb2xhdGUgKyBcIikpPT1udWxsPycnOl9fdCkrXFxuJ1wiO1xuICAgICAgfSBlbHNlIGlmIChldmFsdWF0ZSkge1xuICAgICAgICBzb3VyY2UgKz0gXCInO1xcblwiICsgZXZhbHVhdGUgKyBcIlxcbl9fcCs9J1wiO1xuICAgICAgfVxuXG4gICAgICAvLyBBZG9iZSBWTXMgbmVlZCB0aGUgbWF0Y2ggcmV0dXJuZWQgdG8gcHJvZHVjZSB0aGUgY29ycmVjdCBvZmZlc3QuXG4gICAgICByZXR1cm4gbWF0Y2g7XG4gICAgfSk7XG4gICAgc291cmNlICs9IFwiJztcXG5cIjtcblxuICAgIC8vIElmIGEgdmFyaWFibGUgaXMgbm90IHNwZWNpZmllZCwgcGxhY2UgZGF0YSB2YWx1ZXMgaW4gbG9jYWwgc2NvcGUuXG4gICAgaWYgKCFzZXR0aW5ncy52YXJpYWJsZSkgc291cmNlID0gJ3dpdGgob2JqfHx7fSl7XFxuJyArIHNvdXJjZSArICd9XFxuJztcblxuICAgIHNvdXJjZSA9IFwidmFyIF9fdCxfX3A9JycsX19qPUFycmF5LnByb3RvdHlwZS5qb2luLFwiICtcbiAgICAgIFwicHJpbnQ9ZnVuY3Rpb24oKXtfX3ArPV9fai5jYWxsKGFyZ3VtZW50cywnJyk7fTtcXG5cIiArXG4gICAgICBzb3VyY2UgKyAncmV0dXJuIF9fcDtcXG4nO1xuXG4gICAgdHJ5IHtcbiAgICAgIHZhciByZW5kZXIgPSBuZXcgRnVuY3Rpb24oc2V0dGluZ3MudmFyaWFibGUgfHwgJ29iaicsICdfJywgc291cmNlKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICBlLnNvdXJjZSA9IHNvdXJjZTtcbiAgICAgIHRocm93IGU7XG4gICAgfVxuXG4gICAgdmFyIHRlbXBsYXRlID0gZnVuY3Rpb24oZGF0YSkge1xuICAgICAgcmV0dXJuIHJlbmRlci5jYWxsKHRoaXMsIGRhdGEsIF8pO1xuICAgIH07XG5cbiAgICAvLyBQcm92aWRlIHRoZSBjb21waWxlZCBzb3VyY2UgYXMgYSBjb252ZW5pZW5jZSBmb3IgcHJlY29tcGlsYXRpb24uXG4gICAgdmFyIGFyZ3VtZW50ID0gc2V0dGluZ3MudmFyaWFibGUgfHwgJ29iaic7XG4gICAgdGVtcGxhdGUuc291cmNlID0gJ2Z1bmN0aW9uKCcgKyBhcmd1bWVudCArICcpe1xcbicgKyBzb3VyY2UgKyAnfSc7XG5cbiAgICByZXR1cm4gdGVtcGxhdGU7XG4gIH07XG5cbiAgLy8gQWRkIGEgXCJjaGFpblwiIGZ1bmN0aW9uLiBTdGFydCBjaGFpbmluZyBhIHdyYXBwZWQgVW5kZXJzY29yZSBvYmplY3QuXG4gIF8uY2hhaW4gPSBmdW5jdGlvbihvYmopIHtcbiAgICB2YXIgaW5zdGFuY2UgPSBfKG9iaik7XG4gICAgaW5zdGFuY2UuX2NoYWluID0gdHJ1ZTtcbiAgICByZXR1cm4gaW5zdGFuY2U7XG4gIH07XG5cbiAgLy8gT09QXG4gIC8vIC0tLS0tLS0tLS0tLS0tLVxuICAvLyBJZiBVbmRlcnNjb3JlIGlzIGNhbGxlZCBhcyBhIGZ1bmN0aW9uLCBpdCByZXR1cm5zIGEgd3JhcHBlZCBvYmplY3QgdGhhdFxuICAvLyBjYW4gYmUgdXNlZCBPTy1zdHlsZS4gVGhpcyB3cmFwcGVyIGhvbGRzIGFsdGVyZWQgdmVyc2lvbnMgb2YgYWxsIHRoZVxuICAvLyB1bmRlcnNjb3JlIGZ1bmN0aW9ucy4gV3JhcHBlZCBvYmplY3RzIG1heSBiZSBjaGFpbmVkLlxuXG4gIC8vIEhlbHBlciBmdW5jdGlvbiB0byBjb250aW51ZSBjaGFpbmluZyBpbnRlcm1lZGlhdGUgcmVzdWx0cy5cbiAgdmFyIHJlc3VsdCA9IGZ1bmN0aW9uKG9iaikge1xuICAgIHJldHVybiB0aGlzLl9jaGFpbiA/IF8ob2JqKS5jaGFpbigpIDogb2JqO1xuICB9O1xuXG4gIC8vIEFkZCB5b3VyIG93biBjdXN0b20gZnVuY3Rpb25zIHRvIHRoZSBVbmRlcnNjb3JlIG9iamVjdC5cbiAgXy5taXhpbiA9IGZ1bmN0aW9uKG9iaikge1xuICAgIF8uZWFjaChfLmZ1bmN0aW9ucyhvYmopLCBmdW5jdGlvbihuYW1lKSB7XG4gICAgICB2YXIgZnVuYyA9IF9bbmFtZV0gPSBvYmpbbmFtZV07XG4gICAgICBfLnByb3RvdHlwZVtuYW1lXSA9IGZ1bmN0aW9uKCkge1xuICAgICAgICB2YXIgYXJncyA9IFt0aGlzLl93cmFwcGVkXTtcbiAgICAgICAgcHVzaC5hcHBseShhcmdzLCBhcmd1bWVudHMpO1xuICAgICAgICByZXR1cm4gcmVzdWx0LmNhbGwodGhpcywgZnVuYy5hcHBseShfLCBhcmdzKSk7XG4gICAgICB9O1xuICAgIH0pO1xuICB9O1xuXG4gIC8vIEFkZCBhbGwgb2YgdGhlIFVuZGVyc2NvcmUgZnVuY3Rpb25zIHRvIHRoZSB3cmFwcGVyIG9iamVjdC5cbiAgXy5taXhpbihfKTtcblxuICAvLyBBZGQgYWxsIG11dGF0b3IgQXJyYXkgZnVuY3Rpb25zIHRvIHRoZSB3cmFwcGVyLlxuICBfLmVhY2goWydwb3AnLCAncHVzaCcsICdyZXZlcnNlJywgJ3NoaWZ0JywgJ3NvcnQnLCAnc3BsaWNlJywgJ3Vuc2hpZnQnXSwgZnVuY3Rpb24obmFtZSkge1xuICAgIHZhciBtZXRob2QgPSBBcnJheVByb3RvW25hbWVdO1xuICAgIF8ucHJvdG90eXBlW25hbWVdID0gZnVuY3Rpb24oKSB7XG4gICAgICB2YXIgb2JqID0gdGhpcy5fd3JhcHBlZDtcbiAgICAgIG1ldGhvZC5hcHBseShvYmosIGFyZ3VtZW50cyk7XG4gICAgICBpZiAoKG5hbWUgPT09ICdzaGlmdCcgfHwgbmFtZSA9PT0gJ3NwbGljZScpICYmIG9iai5sZW5ndGggPT09IDApIGRlbGV0ZSBvYmpbMF07XG4gICAgICByZXR1cm4gcmVzdWx0LmNhbGwodGhpcywgb2JqKTtcbiAgICB9O1xuICB9KTtcblxuICAvLyBBZGQgYWxsIGFjY2Vzc29yIEFycmF5IGZ1bmN0aW9ucyB0byB0aGUgd3JhcHBlci5cbiAgXy5lYWNoKFsnY29uY2F0JywgJ2pvaW4nLCAnc2xpY2UnXSwgZnVuY3Rpb24obmFtZSkge1xuICAgIHZhciBtZXRob2QgPSBBcnJheVByb3RvW25hbWVdO1xuICAgIF8ucHJvdG90eXBlW25hbWVdID0gZnVuY3Rpb24oKSB7XG4gICAgICByZXR1cm4gcmVzdWx0LmNhbGwodGhpcywgbWV0aG9kLmFwcGx5KHRoaXMuX3dyYXBwZWQsIGFyZ3VtZW50cykpO1xuICAgIH07XG4gIH0pO1xuXG4gIC8vIEV4dHJhY3RzIHRoZSByZXN1bHQgZnJvbSBhIHdyYXBwZWQgYW5kIGNoYWluZWQgb2JqZWN0LlxuICBfLnByb3RvdHlwZS52YWx1ZSA9IGZ1bmN0aW9uKCkge1xuICAgIHJldHVybiB0aGlzLl93cmFwcGVkO1xuICB9O1xuXG4gIC8vIEFNRCByZWdpc3RyYXRpb24gaGFwcGVucyBhdCB0aGUgZW5kIGZvciBjb21wYXRpYmlsaXR5IHdpdGggQU1EIGxvYWRlcnNcbiAgLy8gdGhhdCBtYXkgbm90IGVuZm9yY2UgbmV4dC10dXJuIHNlbWFudGljcyBvbiBtb2R1bGVzLiBFdmVuIHRob3VnaCBnZW5lcmFsXG4gIC8vIHByYWN0aWNlIGZvciBBTUQgcmVnaXN0cmF0aW9uIGlzIHRvIGJlIGFub255bW91cywgdW5kZXJzY29yZSByZWdpc3RlcnNcbiAgLy8gYXMgYSBuYW1lZCBtb2R1bGUgYmVjYXVzZSwgbGlrZSBqUXVlcnksIGl0IGlzIGEgYmFzZSBsaWJyYXJ5IHRoYXQgaXNcbiAgLy8gcG9wdWxhciBlbm91Z2ggdG8gYmUgYnVuZGxlZCBpbiBhIHRoaXJkIHBhcnR5IGxpYiwgYnV0IG5vdCBiZSBwYXJ0IG9mXG4gIC8vIGFuIEFNRCBsb2FkIHJlcXVlc3QuIFRob3NlIGNhc2VzIGNvdWxkIGdlbmVyYXRlIGFuIGVycm9yIHdoZW4gYW5cbiAgLy8gYW5vbnltb3VzIGRlZmluZSgpIGlzIGNhbGxlZCBvdXRzaWRlIG9mIGEgbG9hZGVyIHJlcXVlc3QuXG4gIGlmICh0eXBlb2YgZGVmaW5lID09PSAnZnVuY3Rpb24nICYmIGRlZmluZS5hbWQpIHtcbiAgICBkZWZpbmUoJ3VuZGVyc2NvcmUnLCBbXSwgZnVuY3Rpb24oKSB7XG4gICAgICByZXR1cm4gXztcbiAgICB9KTtcbiAgfVxufS5jYWxsKHRoaXMpKTtcbiIsIl8gPSByZXF1aXJlIFwidW5kZXJzY29yZVwiXG5cbiMgY2FsY3VsYXRlIHRoZSBjb25zZW5zdXMgc2VxXG4jIFRPRE86IHZlcnkgbmFpdmUgd2F5XG5tb2R1bGUuZXhwb3J0cyA9IChzZXFzKSAtPlxuXG4gIHNlcXMgPSBzZXFzLm1hcCAoZWwpIC0+IGVsLmdldCBcInNlcVwiXG4gIG9jY3MgPSBuZXcgQXJyYXkgc2Vxcy5sZW5ndGhcblxuICAjIGNvdW50IHRoZSBvY2N1cmVuY2VzIG9mIHRoZSBjaGFycyBvZiBhIHBvc2l0aW9uXG4gIF8uZWFjaCBzZXFzLCAoZWwsaSkgLT5cbiAgICBfLmVhY2ggZWwsIChjaGFyLCBwb3MpIC0+XG4gICAgICBvY2NzW3Bvc10gPSB7fSB1bmxlc3Mgb2Njc1twb3NdP1xuICAgICAgb2Njc1twb3NdW2NoYXJdID0gMCB1bmxlc3Mgb2Njc1twb3NdW2NoYXJdP1xuICAgICAgb2Njc1twb3NdW2NoYXJdKytcblxuICAjIG5vdyBwaWNrIHRoZSBjaGFyIHdpdGggbW9zdCBvY2N1cmVuY2VzXG4gIF8ucmVkdWNlIG9jY3MsIChtZW1vLG9jYykgLT5cbiAgICBrZXlzID0gXy5rZXlzIG9jY1xuICAgIG1lbW8gKz0gIF8ubWF4IGtleXMsIChrZXkpIC0+IG9jY1trZXldXG4gICwgXCJcIlxuIiwiIyBmb3IgZWFjaCBzZXF1ZW5jZVxuIyAqIGNvdW50cyB0aGUgbWF0Y2hlcyB3aXRoIHRoZSBjb25zZW5zdXMgc2VxXG4jICogZXhjbHVkaW5nIGdhcHNcbiMgKiBpZGVudGl0eSA9IG1hdGNoZWRDaGFycyAvIHRvdGFsQ2hhcnMgKGV4Y2x1ZGluZyBnYXBzKVxubW9kdWxlLmV4cG9ydHMgPSBpZGVudGl0aXlDYWxjID0gKHNlcXMsIGNvbnNlbnN1cykgLT5cbiAgIyBkbyBub3RoaW5nIG9uIGludmFsaWQgY29uc2Vuc3VzXG4gIGlmIGNvbnNlbnN1cyBpcyB1bmRlZmluZWRcbiAgICBjb25zb2xlLndhcm4gXCJidWcgb24gY29uc2VudXMgY2FsY1wiXG4gICAgcmV0dXJuXG4gIHNlcXMuZWFjaCAoc2VxT2JqKSAtPlxuICAgIHNlcSA9IHNlcU9iai5nZXQgXCJzZXFcIlxuICAgIG1hdGNoZXMgPSAwXG4gICAgdG90YWwgPSAwXG4gICAgZm9yIGkgaW4gWzAuLnNlcS5sZW5ndGggLSAxXVxuICAgICAgaWYgc2VxW2ldIGlzbnQgXCItXCIgYW5kIGNvbnNlbnN1c1tpXSBpc250IFwiLVwiXG4gICAgICAgIHRvdGFsKytcbiAgICAgICAgbWF0Y2hlcysrIGlmIHNlcVtpXSBpcyBjb25zZW5zdXNbaV1cbiAgICBzZXFPYmouc2V0IFwiaWRlbnRpdHlcIiwgbWF0Y2hlcyAvIHRvdGFsXG4iLCJtb2R1bGUuZXhwb3J0cy5jb25zZW5zdXMgPSByZXF1aXJlIFwiLi9Db25zZW5zdXNDYWxjXCJcbiIsIk1vZGVsID0gcmVxdWlyZShcImJhY2tib25lLXRoaW5cIikuTW9kZWxcblxuIyB0aGlzIGlzIGFuIGV4YW1wbGUgb2YgaG93IG9uZSBjb3VsZCBjb2xvciB0aGUgTVNBXG4jIGZlZWwgZnJlZSB0byBjcmVhdGUgeW91ciBvd24gY29sb3Igc2NoZW1lIGluIHRoZSAvY3NzL3NjaGVtZXMgZm9sZGVyXG5tb2R1bGUuZXhwb3J0cyA9IENvbG9yYXRvciA9IE1vZGVsLmV4dGVuZFxuXG4gIGRlZmF1bHRzOlxuICAgIHNjaGVtZTogXCJ0YXlsb3JcIiAjIG5hbWUgb2YgeW91ciBjb2xvciBzY2hlbWUgKGNzcyBzdWZmaXgpXG4gICAgY29sb3JCYWNrZ3JvdW5kOiB0cnVlICMgb3RoZXJ3aXNlIG9ubHkgdGhlIHRleHQgd2lsbCBiZSBjb2xvcmVkXG4gICAgc2hvd0xvd2VyQ2FzZTogdHJ1ZSAjIHVzZWQgdG8gaGlkZSBhbmQgc2hvdyBsb3dlcmNhc2UgY2hhcnMgaW4gdGhlIG92ZXJ2aWV3Ym94XG4gICAgb3BhY2l0eTogMC42ICMgb3BhY2l0eSBmb3IgdGhlIHJlc2lkdWVzXG4iLCJNb2RlbCA9IHJlcXVpcmUoXCJiYWNrYm9uZS10aGluXCIpLk1vZGVsXG5jb25zZW51cyA9IHJlcXVpcmUgXCIuLi9hbGdvL0NvbnNlbnN1c0NhbGNcIlxuXyA9IHJlcXVpcmUgXCJ1bmRlcnNjb3JlXCJcblxuIyBtb2RlbCBmb3IgY29sdW1uIHByb3BlcnRpZXMgKGxpa2UgdGhlaXIgaGlkZGVuIHN0YXRlKVxubW9kdWxlLmV4cG9ydHMgPSBDb2x1bW5zID0gTW9kZWwuZXh0ZW5kXG5cbiAgZGVmYXVsdHM6XG4gICAgc2NhbGluZzogXCJsaW5cIiAjIG9mIHRoZSBjb25zZXJ2YXRpb24gY2hhcnQgZS5nLiBcImxpblwiLCBcImV4cFwiLCBcImxvZ1wiXG5cbiAgaW5pdGlhbGl6ZTogLT5cbiAgICAjIGhpZGRlbiBjb2x1bW5zXG4gICAgQC5zZXQgXCJoaWRkZW5cIiwgW10gdW5sZXNzIEAuZ2V0KFwiaGlkZGVuXCIpP1xuXG4gICMgYXNzdW1lcyBoaWRkZW4gY29sdW1ucyBhcmUgc29ydGVkXG4gICMgQHJldHVybnMgbiBbaW50XSBudW1iZXIgb2YgaGlkZGVuIGNvbHVtbnMgdW50aWwgblxuICBjYWxjSGlkZGVuQ29sdW1uczogKG4pIC0+XG4gICAgaGlkZGVuID0gQGdldCBcImhpZGRlblwiXG4gICAgbmV3WCA9IG5cbiAgICBmb3IgaSBpbiBoaWRkZW5cbiAgICAgIGlmIGkgPD0gbmV3WFxuICAgICAgICBuZXdYKytcbiAgICBuZXdYIC0gblxuXG4gICMgY2FsY3MgY29uc2VydmF0aW9uXG4gIF9jYWxjQ29uc2VydmF0aW9uUHJlOiAoc2VxcykgLT5cblxuICAgICMgZW1lcmdlbmN5IGN1dG9mZlxuICAgIGNvbnNvbGUubG9nIHNlcXMubGVuZ3RoXG4gICAgaWYgc2Vxcy5sZW5ndGggPiAxMDAwXG4gICAgICByZXR1cm5cblxuICAgICMgY2FsYyBjb25zZW5zdXNcbiAgICBjb25zID0gY29uc2VudXMoc2VxcylcbiAgICBzZXFzID0gc2Vxcy5tYXAgKGVsKSAtPiBlbC5nZXQgXCJzZXFcIlxuICAgIG5NYXggPSAoXy5tYXggc2VxcywgKGVsKSAtPiBlbC5sZW5ndGgpLmxlbmd0aFxuXG4gICAgdG90YWwgPSBuZXcgQXJyYXkgbk1heFxuICAgIG1hdGNoZXMgPSBuZXcgQXJyYXkgbk1heFxuICAgICMgY2FsYyBkZXJpdmF0aW9uIGZyb20gY29uc2VudXNcbiAgICBfLmVhY2ggc2VxcywgKGVsLGkpIC0+XG4gICAgICBfLmVhY2ggZWwsIChjaGFyLCBwb3MpIC0+XG4gICAgICAgICNpZiBjb25zW3Bvc10gaXNudCBcIi1cIiBhbmQgbWF0Y2hlc1twb3NdIGlzbnQgZ2FwXG4gICAgICAgIHRvdGFsW3Bvc10gPSB0b3RhbFtwb3NdICsgMSB8fCAxXG4gICAgICAgIG1hdGNoZXNbcG9zXSA9IG1hdGNoZXNbcG9zXSArIDEgfHwgMSBpZiBjb25zW3Bvc10gaXMgY2hhclxuICAgIFttYXRjaGVzLCB0b3RhbCwgbk1heF1cblxuICBjYWxjQ29uc2VydmF0aW9uOiAoc2VxcykgLT5cbiAgICBpZiBAYXR0cmlidXRlcy5zY2FsaW5nIGlzIFwiZXhwXCJcbiAgICAgIHJldHVybiBAY2FsY0NvbnNlcnZhdGlvbkV4cCBzZXFzXG4gICAgZWxzZSBpZiBAYXR0cmlidXRlcy5zY2FsaW5nIGlzIFwibG9nXCJcbiAgICAgIHJldHVybiBAY2FsY0NvbnNlcnZhdGlvbkxvZyBzZXFzXG4gICAgZWxzZSBpZiBAYXR0cmlidXRlcy5zY2FsaW5nIGlzIFwibGluXCJcbiAgICAgIHJldHVybiBAY2FsY0NvbnNlcnZhdGlvbkxpbiBzZXFzXG5cbiAgIyAocGVyY2VudGFnZSBvZiBjaGFycyBvZiB0aGUgY29uc2VudXMgc2VxKVxuICBjYWxjQ29uc2VydmF0aW9uTGluOiAoc2VxcykgLT5cbiAgICBbbWF0Y2hlcyx0b3RhbCwgbk1heF0gPSBAX2NhbGNDb25zZXJ2YXRpb25QcmUgc2Vxc1xuICAgIGZvciBpIGluIFswIC4uIG5NYXggLSAxXVxuICAgICAgbWF0Y2hlc1tpXSA9IG1hdGNoZXNbaV0gLyB0b3RhbFtpXVxuICAgIEAuc2V0IFwiY29uc2VydlwiLCBtYXRjaGVzXG4gICAgbWF0Y2hlc1xuXG4gICMgKHBlcmNlbnRhZ2Ugb2YgY2hhcnMgb2YgdGhlIGNvbnNlbnVzIHNlcSlcbiAgY2FsY0NvbnNlcnZhdGlvbkxvZzogKHNlcXMpIC0+XG4gICAgW21hdGNoZXMsdG90YWwsIG5NYXhdID0gQF9jYWxjQ29uc2VydmF0aW9uUHJlIHNlcXNcbiAgICBmb3IgaSBpbiBbMCAuLiBuTWF4IC0gMV1cbiAgICAgIG1hdGNoZXNbaV0gPSBNYXRoLmxvZyhtYXRjaGVzW2ldICsgMSkgLyBNYXRoLmxvZyh0b3RhbFtpXSArIDEpXG4gICAgQC5zZXQgXCJjb25zZXJ2XCIsIG1hdGNoZXNcbiAgICBtYXRjaGVzXG5cbiAgY2FsY0NvbnNlcnZhdGlvbkV4cDogKHNlcXMpIC0+XG4gICAgW21hdGNoZXMsdG90YWwsIG5NYXhdID0gQF9jYWxjQ29uc2VydmF0aW9uUHJlIHNlcXNcbiAgICBmb3IgaSBpbiBbMCAuLiBuTWF4IC0gMV1cbiAgICAgIG1hdGNoZXNbaV0gPSBNYXRoLmV4cChtYXRjaGVzW2ldICsgMSkgLyBNYXRoLmV4cCh0b3RhbFtpXSArIDEpXG4gICAgQC5zZXQgXCJjb25zZXJ2XCIsIG1hdGNoZXNcbiAgICBtYXRjaGVzXG4iLCJNb2RlbCA9IHJlcXVpcmUoXCJiYWNrYm9uZS10aGluXCIpLk1vZGVsXG5cbiMgc2ltcGxlIHVzZXIgY29uZmlnXG5tb2R1bGUuZXhwb3J0cyA9IENvbmZpZyA9IE1vZGVsLmV4dGVuZFxuXG4gIGRlZmF1bHRzOlxuICAgIHJlZ2lzdGVyTW91c2VIb3ZlcjogZmFsc2UsXG4gICAgcmVnaXN0ZXJNb3VzZUNsaWNrczogdHJ1ZSxcbiAgICBpbXBvcnRQcm94eTogXCJodHRwczovL2NvcnMtYW55d2hlcmUuaGVyb2t1YXBwLmNvbS9cIlxuICAgIGV2ZW50QnVzOiB0cnVlXG4iLCJNb2RlbCA9IHJlcXVpcmUoXCJiYWNrYm9uZS10aGluXCIpLk1vZGVsXG5jb25zZW51c0NhbGMgPSByZXF1aXJlIFwiLi4vYWxnby9Db25zZW5zdXNDYWxjXCJcblxuIyBzaW1wbHkgc2F2ZSB0aGUgY29uc2VudXMgc2VxdWVuY2VzIGdsb2JhbGx5XG5tb2R1bGUuZXhwb3J0cyA9IENvbnNlbnVzID0gTW9kZWwuZXh0ZW5kXG5cbiAgZGVmYXVsdHM6XG4gICAgY29uc2VudXMgOiBcIlwiXG5cbiAgZ2V0Q29uc2Vuc3VzOiAoc2VxcykgLT5cbiAgICAjIGVtZXJnZW5jeSBjdXRvZmZcbiAgICBpZiBzZXFzLmxlbmd0aCA+IDEwMDBcbiAgICAgIHJldHVyblxuXG4gICAgY29ucyA9IGNvbnNlbnVzQ2FsYyhzZXFzKVxuICAgIEAuc2V0IFwiY29uc2VudXNcIiwgY29uc1xuICAgIGNvbnNcbiIsIl8gPSByZXF1aXJlIFwidW5kZXJzY29yZVwiXG5Nb2RlbCA9IHJlcXVpcmUoXCJiYWNrYm9uZS10aGluXCIpLk1vZGVsXG5cbiMgaG9sZHMgdGhlIGN1cnJlbnQgdXNlciBzZWxlY3Rpb25cblNlbGVjdGlvbiA9IE1vZGVsLmV4dGVuZFxuICBkZWZhdWx0czpcbiAgICB0eXBlOiBcInN1cGVyXCJcblxuUm93U2VsZWN0aW9uID0gU2VsZWN0aW9uLmV4dGVuZFxuICBkZWZhdWx0czogXy5leHRlbmQge30sIFNlbGVjdGlvbjo6LmRlZmF1bHRzLFxuICAgIHR5cGU6IFwicm93XCJcbiAgICBzZXFJZDogXCJcIlxuXG4gIGluUm93OiAoc2VxSWQpIC0+XG4gICAgc2VxSWQgaXMgQC5nZXQgXCJzZXFJZFwiXG5cbiAgaW5Db2x1bW46IChyb3dQb3MpIC0+XG4gICAgdHJ1ZVxuXG4gIGdldExlbmd0aDogLT5cbiAgICAxXG5cbkNvbHVtblNlbGVjdGlvbiA9IFNlbGVjdGlvbi5leHRlbmRcbiAgZGVmYXVsdHM6IF8uZXh0ZW5kIHt9LCBTZWxlY3Rpb246Oi5kZWZhdWx0cyxcbiAgICB0eXBlOiBcImNvbHVtblwiXG4gICAgeFN0YXJ0OiAtMVxuICAgIHhFbmQ6IC0xXG5cbiAgaW5Sb3c6ICgpIC0+XG4gICAgdHJ1ZVxuXG4gIGluQ29sdW1uOiAocm93UG9zKSAtPlxuICAgIHhTdGFydCA8PSByb3dQb3MgJiYgcm93UG9zIDw9IHhFbmRcblxuICBnZXRMZW5ndGg6IC0+XG4gICAgeEVuZCAtIHhTdGFydFxuXG4jIHBvcyBpcyBhIG1peGluIG9mIGNvbHVtbiBhbmQgcm93XG4jIHN0YXJ0IHdpdGggUm93IGFuZCBvbmx5IG92ZXJ3cml0ZSBcImluQ29sdW1uXCIgZnJvbSBDb2x1bW5cblBvc1NlbGVjdGlvbiA9IFJvd1NlbGVjdGlvbi5leHRlbmQgXy5leHRlbmQge30sXy5waWNrKENvbHVtblNlbGVjdGlvbixcImluQ29sdW1uXCIpLFxuICBfLnBpY2soQ29sdW1uU2VsZWN0aW9uLFwiZ2V0TGVuZ3RoXCIpXG5cbiAgIyBtZXJnZSBib3RoIGRlZmF1bHRzXG4gIGRlZmF1bHRzOiBfLmV4dGVuZCB7fSwgQ29sdW1uU2VsZWN0aW9uOjouZGVmYXVsdHMsIFJvd1NlbGVjdGlvbjo6LmRlZmF1bHRzLFxuICAgIHR5cGU6IFwicG9zXCJcblxubW9kdWxlLmV4cG9ydHMuc2VsID0gU2VsZWN0aW9uXG5tb2R1bGUuZXhwb3J0cy5wb3NzZWwgPSBQb3NTZWxlY3Rpb25cbm1vZHVsZS5leHBvcnRzLnJvd3NlbCA9IFJvd1NlbGVjdGlvblxubW9kdWxlLmV4cG9ydHMuY29sdW1uc2VsID0gQ29sdW1uU2VsZWN0aW9uXG4iLCJzZWwgPSByZXF1aXJlIFwiLi9TZWxlY3Rpb25cIlxuXyA9IHJlcXVpcmUgXCJ1bmRlcnNjb3JlXCJcbkNvbGxlY3Rpb24gPSByZXF1aXJlKFwiYmFja2JvbmUtdGhpblwiKS5Db2xsZWN0aW9uXG5cbiMgaG9sZHMgdGhlIGN1cnJlbnQgdXNlciBzZWxlY3Rpb25cbm1vZHVsZS5leHBvcnRzID0gU2VsZWN0aW9uTWFuYWdlciA9IENvbGxlY3Rpb24uZXh0ZW5kXG5cbiAgbW9kZWw6IHNlbC5zZWxcblxuICBpbml0aWFsaXplOiAoZGF0YSwgb3B0cykgLT5cbiAgICBAZyA9IG9wdHMuZ1xuXG4gICAgQGxpc3RlblRvIEBnLCBcInJlc2lkdWU6Y2xpY2tcIiwgKGUpIC0+XG4gICAgICBAX2hhbmRsZUUgZS5ldnQsIG5ldyBzZWwucG9zc2VsXG4gICAgICAgIHhTdGFydDogZS5yb3dQb3NcbiAgICAgICAgeEVuZDogZS5yb3dQb3NcbiAgICAgICAgc2VxSWQ6IGUuc2VxSWRcblxuICAgIEBsaXN0ZW5UbyBAZywgXCJyb3c6Y2xpY2tcIiwgKGUpIC0+XG4gICAgICBAX2hhbmRsZUUgZS5ldnQsIG5ldyBzZWwucm93c2VsXG4gICAgICAgIHhTdGFydDogZS5yb3dQb3NcbiAgICAgICAgeEVuZDogZS5yb3dQb3NcbiAgICAgICAgc2VxSWQ6IGUuc2VxSWRcblxuICAgIEBsaXN0ZW5UbyBAZywgXCJjb2x1bW46Y2xpY2tcIiwgKGUpIC0+XG4gICAgICBAX2hhbmRsZUUgZS5ldnQsIG5ldyBzZWwuY29sdW1uc2VsXG4gICAgICAgIHhTdGFydDogZS5yb3dQb3NcbiAgICAgICAgeEVuZDogZS5yb3dQb3MgKyBlLnN0ZXBTaXplIC0gMVxuXG4gICAgI0BsaXN0ZW5UbyBALCBcImFkZCByZXNldFwiLCAoZSkgLT5cbiAgICAgICNAX3JlZHVjZUNvbHVtbnMoKVxuXG4gIGdldFNlbEZvclJvdzogKHNlcUlkKSAtPlxuICAgIEBmaWx0ZXIgKGVsKSAtPiBlbC5pblJvdyBzZXFJZFxuXG4gIGdldFNlbEZvckNvbHVtbnM6IChyb3dQb3MpIC0+XG4gICAgQGZpbHRlciAoZWwpIC0+IGVsLmluQ29sdW1uIHJvd1Bvc1xuXG4gICMgQHJldHVybnMgYXJyYXkgb2YgYWxsIHNlbGVjdGVkIHJlc2lkdWVzIGZvciBhIHJvd1xuICBnZXRCbG9ja3NGb3JSb3c6IChzZXFJZCwgbWF4TGVuKSAtPlxuICAgIHNlbGlzID0gQGZpbHRlciAoZWwpIC0+IGVsLmluUm93IHNlcUlkXG4gICAgYmxvY2tzID0gW11cbiAgICBmb3Igc2VsaSBpbiBzZWxpc1xuICAgICAgaWYgc2VsaS5hdHRyaWJ1dGVzLnR5cGUgaXMgXCJyb3dcIlxuICAgICAgICBibG9ja3MgPSBbMC4ubWF4TGVuXVxuICAgICAgICBicmVha1xuICAgICAgZWxzZVxuICAgICAgICBibG9ja3MgPSBibG9ja3MuY29uY2F0IFtzZWxpLmF0dHJpYnV0ZXMueFN0YXJ0IC4uIHNlbGkuYXR0cmlidXRlcy54RW5kXVxuICAgIGJsb2Nrc1xuXG4gICMgQHJldHVybnMgYXJyYXkgd2l0aCBhbGwgY29sdW1ucyBiZWluZyBzZWxlY3RlZFxuICAjIGV4YW1wbGU6IDAtNC4uLiAxMi0xNCBzZWxlY3RlZCAtPiBbMCwxLDIsMyw0LDEyLDEzLDE0XVxuICBnZXRBbGxDb2x1bW5CbG9ja3M6IChjb25mKSAtPlxuICAgIG1heExlbiA9IGNvbmYubWF4TGVuXG4gICAgd2l0aFBvcyA9IGNvbmYud2l0aFBvc1xuICAgIGJsb2NrcyA9IFtdXG4gICAgaWYgY29uZi53aXRoUG9zXG4gICAgICBmaWx0ZXJlZCA9IChAZmlsdGVyIChlbCkgLT4gZWwuZ2V0KCd4U3RhcnQnKT8gKVxuICAgIGVsc2VcbiAgICAgIGZpbHRlcmVkID0gKEBmaWx0ZXIgKGVsKSAtPiBlbC5nZXQoJ3R5cGUnKSBpcyBcImNvbHVtblwiKVxuICAgIGZvciBzZWxpIGluIGZpbHRlcmVkXG4gICAgICBibG9ja3MgPSBibG9ja3MuY29uY2F0IFtzZWxpLmF0dHJpYnV0ZXMueFN0YXJ0Li5zZWxpLmF0dHJpYnV0ZXMueEVuZF1cbiAgICBibG9ja3MgPSBfLnVuaXEgYmxvY2tzXG4gICAgcmV0dXJuIGJsb2Nrc1xuXG4gICMgaW52ZXJ0cyB0aGUgY3VycmVudCBzZWxlY3Rpb24gZm9yIGNvbHVtbnNcbiAgIyBAcGFyYW0gcm93cyBbQXJyYXldIGFsbCBhdmFpbGFibGUgc2VxSWRcbiAgaW52ZXJ0Um93OiAocm93cykgLT5cbiAgICBzZWxSb3dzID0gQHdoZXJlKHR5cGU6XCJyb3dcIilcbiAgICBzZWxSb3dzID0gXy5tYXAgc2VsUm93cywgKGVsKSAtPiBlbC5hdHRyaWJ1dGVzLnNlcUlkXG4gICAgaW52ZXJ0ZWQgPSBfLmZpbHRlciByb3dzLCAoZWwpIC0+XG4gICAgICByZXR1cm4gZmFsc2UgaWYgc2VsUm93cy5pbmRleE9mKGVsKSA+PSAwICMgZXhpc3Rpbmcgc2VsZWN0aW9uXG4gICAgICB0cnVlXG4gICAgIyBtYXNzIGluc2VydFxuICAgIHMgPSBbXVxuICAgIGZvciBlbCBpbiBpbnZlcnRlZFxuICAgICAgcy5wdXNoIG5ldyBzZWwucm93c2VsKHNlcUlkOmVsKVxuICAgIGNvbnNvbGUubG9nIHNcbiAgICBAcmVzZXQgc1xuXG4gICMgaW52ZXJ0cyB0aGUgY3VycmVudCBzZWxlY3Rpb24gZm9yIHJvd3NcbiAgIyBAcGFyYW0gcm93cyBbQXJyYXldIGFsbCBhdmFpbGFibGUgcm93cyAoMC4ubWF4Lmxlbmd0aClcbiAgaW52ZXJ0Q29sOiAoY29sdW1ucykgLT5cbiAgICBzZWxDb2x1bW5zID0gQHdoZXJlKHR5cGU6XCJjb2x1bW5cIilcbiAgICBzZWxDb2x1bW5zID0gXy5yZWR1Y2Ugc2VsQ29sdW1ucywgKG1lbW8sZWwpIC0+XG4gICAgICBtZW1vLmNvbmNhdCBbZWwuYXR0cmlidXRlcy54U3RhcnQgLi4gZWwuYXR0cmlidXRlcy54RW5kXVxuICAgICwgW11cbiAgICBpbnZlcnRlZCA9IF8uZmlsdGVyIGNvbHVtbnMsIChlbCkgLT5cbiAgICAgIGlmIHNlbENvbHVtbnMuaW5kZXhPZihlbCkgPj0gMFxuICAgICAgICAjIGV4aXN0aW5nIHNlbGVjdGlvblxuICAgICAgICByZXR1cm4gZmFsc2VcbiAgICAgIHRydWVcbiAgICAjIG1hc3MgaW5zZXJ0XG4gICAgcmV0dXJuIGlmIGludmVydGVkLmxlbmd0aCA9PSAwXG4gICAgcyA9IFtdXG4gICAgY29uc29sZS5sb2cgaW52ZXJ0ZWRcbiAgICB4U3RhcnQgPSB4RW5kID0gaW52ZXJ0ZWRbMF1cbiAgICBmb3IgZWwgaW4gaW52ZXJ0ZWRcbiAgICAgIGlmIHhFbmQgKyAxIGlzIGVsXG4gICAgICAgICMgY29udGlndW91c1xuICAgICAgICB4RW5kID0gZWxcbiAgICAgIGVsc2VcbiAgICAgICAgIyBnYXAgYmV0d2VlblxuICAgICAgICBzLnB1c2ggbmV3IHNlbC5jb2x1bW5zZWwoeFN0YXJ0OnhTdGFydCwgeEVuZDogeEVuZClcbiAgICAgICAgeFN0YXJ0ID0geEVuZCA9IGVsXG4gICAgIyBjaGVjayBmb3IgbGFzdCBnYXBcbiAgICBzLnB1c2ggbmV3IHNlbC5jb2x1bW5zZWwoeFN0YXJ0OnhTdGFydCwgeEVuZDogaW52ZXJ0ZWRbaW52ZXJ0ZWQubGVuZ3RoIC0gMV0pIGlmIHhTdGFydCBpc250IHhFbmRcbiAgICBAcmVzZXQgc1xuXG4gICMgbWV0aG9kIHRvIGRlY2lkZSB3aGV0aGVyIHRvIHN0YXJ0IGEgbmV3IHNlbGVjdGlvblxuICAjIG9yIGFwcGVuZCB0byB0aGUgb2xkIG9uZSAoZGVwZW5kaW5nIHdoZXRoZXIgQ1RSTCB3YXMgcHJlc3NlZClcbiAgX2hhbmRsZUU6IChlLCBzZWxlY3Rpb24pIC0+XG4gICAgaWYgZS5jdHJsS2V5IG9yIGUubWV0YUtleVxuICAgICAgQGFkZCBzZWxlY3Rpb25cbiAgICBlbHNlXG4gICAgICBAcmVzZXQgW3NlbGVjdGlvbl1cblxuICAjIGV4cGVyaW1lbnRhbCByZWR1Y2UgbWV0aG9kIGZvciBjb2x1bW5zXG4gIF9yZWR1Y2VDb2x1bW5zOiAtPlxuICAgIEBlYWNoIChlbCwgaW5kZXgsIGFycikgLT5cbiAgICAgIGNvbHMgPSBfLmZpbHRlciBhcnIsIChlbCkgLT4gZWwuZ2V0KCd0eXBlJykgaXMgJ2NvbHVtbidcbiAgICAgIHhTdGFydCA9IGVsLmdldCgneFN0YXJ0JylcbiAgICAgIHhFbmQgPSBlbC5nZXQoJ3hFbmQnKVxuXG4gICAgICBsZWZ0cyA9IF8uZmlsdGVyIGNvbHMsIChlbCkgLT4gZWwuZ2V0KCd4RW5kJykgaXMgKHhTdGFydCAtIDEpXG4gICAgICBmb3IgbGVmdCBpbiBsZWZ0c1xuICAgICAgICBsZWZ0LnNldCAneEVuZCcsIHhTdGFydFxuXG4gICAgICByaWdodHMgPSBfLmZpbHRlciBjb2xzLCAoZWwpIC0+IGVsLmdldCgneFN0YXJ0JykgaXMgKHhFbmQgKyAxKVxuICAgICAgZm9yIHJpZ2h0IGluIHJpZ2h0c1xuICAgICAgICByaWdodC5zZXQgJ3hTdGFydCcsIHhFbmRcblxuICAgICAgaWYgbGVmdHMubGVuZ3RoID4gMCBvciByaWdodHMubGVuZ3RoID4gMFxuICAgICAgICBjb25zb2xlLmxvZyBcInJlbW92ZWQgZWxcIlxuICAgICAgICBlbC5jb2xsZWN0aW9uLnJlbW92ZSBlbFxuIiwiTW9kZWwgPSByZXF1aXJlKFwiYmFja2JvbmUtdGhpblwiKS5Nb2RlbFxuXG4jIHZpc2libGUgYXJlYXNcbm1vZHVsZS5leHBvcnRzID0gVmlzaWJpbGl0eSA9IE1vZGVsLmV4dGVuZFxuXG4gIGRlZmF1bHRzOlxuXG4gICAgIyBmb3IgdGhlIFN0YWdlXG4gICAgb3ZlcnZpZXdCb3g6IDMwXG4gICAgaGVhZGVyQm94OiAtMVxuICAgIGFsaWdubWVudEJvZHk6IDBcbiIsIk1vZGVsID0gcmVxdWlyZShcImJhY2tib25lLXRoaW5cIikuTW9kZWxcblxuIyB2aXNpYmxlIGFyZWFzXG5tb2R1bGUuZXhwb3J0cyA9IFZpc2liaWxpdHkgPSBNb2RlbC5leHRlbmRcblxuICBkZWZhdWx0czpcbiAgICBzZXF1ZW5jZXM6IHRydWVcbiAgICBtYXJrZXJzOiB0cnVlXG4gICAgbWV0YWNlbGw6IGZhbHNlXG4gICAgY29uc2VydjogdHJ1ZVxuICAgIG92ZXJ2aWV3Ym94OiBmYWxzZVxuXG4gICAgIyBhYm91dCB0aGUgbGFiZWxzXG4gICAgbGFiZWxzOiB0cnVlXG4gICAgbGFiZWxOYW1lOiB0cnVlXG4gICAgbGFiZWxJZDogdHJ1ZVxuICAgIGxhYmVsUGFydGl0aW9uOiBmYWxzZVxuICAgIGxhYmVsQ2hlY2tib3g6IGZhbHNlXG4iLCJNb2RlbCA9IHJlcXVpcmUoXCJiYWNrYm9uZS10aGluXCIpLk1vZGVsXG4jIHBpeGVsIHByb3BlcnRpZXMgZm9yIHNvbWUgY29tcG9uZW50c1xubW9kdWxlLmV4cG9ydHMgPSBab29tZXIgPSBNb2RlbC5leHRlbmRcblxuICBjb25zdHJ1Y3RvcjogKGF0dHJpYnV0ZXMsb3B0aW9ucykgLT5cbiAgICBNb2RlbC5hcHBseSBALCBhcmd1bWVudHNcbiAgICBAZyA9IG9wdGlvbnMuZ1xuICAgIEBcblxuICBkZWZhdWx0czpcblxuICAgICMgZ2VuZXJhbFxuICAgIGFsaWdubWVudFdpZHRoOiBcImF1dG9cIlxuICAgIGFsaWdubWVudEhlaWdodDogMTk1XG4gICAgY29sdW1uV2lkdGg6IDE1XG4gICAgcm93SGVpZ2h0OiAxNVxuXG4gICAgIyBsYWJlbHNcbiAgICBsYWJlbFdpZHRoOiAxMDBcbiAgICBtZXRhV2lkdGg6IDEwMFxuICAgIHRleHRWaXNpYmxlOiB0cnVlXG4gICAgbGFiZWxJZExlbmd0aDogMzBcbiAgICBsYWJlbEZvbnRzaXplOiBcIjEzcHhcIlxuICAgIGxhYmVsTGluZUhlaWdodDogXCIxM3B4XCJcblxuICAgICMgbWFya2VyXG4gICAgbWFya2VyRm9udHNpemU6IFwiMTBweFwiXG4gICAgc3RlcFNpemU6IDFcbiAgICBtYXJrZXJTdGVwU2l6ZTogMlxuXG4gICAgIyBjYW52YXNcbiAgICByZXNpZHVlRm9udDogXCIxM3B4IG1vbm9cIlxuICAgIGNhbnZhc0V2ZW50U2NhbGU6IDFcblxuICAgIGJveFJlY3RIZWlnaHQ6IDVcbiAgICBib3hSZWN0V2lkdGg6IDVcblxuICAgICMgbWVudVxuICAgIG1lbnVGb250c2l6ZTogXCIyMHB4XCJcbiAgICBtZW51SXRlbUZvbnRzaXplOiBcIjE4cHhcIlxuICAgIG1lbnVJdGVtTGluZUhlaWdodDogXCIxOHB4XCJcbiAgICBtZW51TWFyZ2luTGVmdDogXCI1cHhcIlxuICAgIG1lbnVQYWRkaW5nOiBcIjNweCA1cHggM3B4IDVweFwiXG5cbiAgICAjIGludGVybmFsIHByb3BzXG4gICAgX2FsaWdubWVudFNjcm9sbExlZnQ6IDBcbiAgICBfYWxpZ25tZW50U2Nyb2xsVG9wOiAwXG5cbiAgIyBAcGFyYW0gbiBbaW50XSBtYXhMZW5ndGggb2YgYWxsIHNlcXNcbiAgZ2V0QWxpZ25tZW50V2lkdGg6IChuKSAtPlxuICAgIGlmIEBnZXQoXCJhbGlnbm1lbnRXaWR0aFwiKSBpcyBcImF1dG9cIlxuICAgICAgQGdldChcImNvbHVtbldpZHRoXCIpICogblxuICAgIGVsc2VcbiAgICAgIEBnZXQgXCJhbGlnbm1lbnRXaWR0aFwiXG5cbiAgIyBAcGFyYW0gbiBbaW50XSBudW1iZXIgb2YgcmVzaWR1ZXMgdG8gc2Nyb2xsIHRvIHRoZSByaWdodFxuICBzZXRMZWZ0T2Zmc2V0OiAobikgLT5cbiAgICB2YWwgPSAobiAtIDEpICogQGdldCgnY29sdW1uV2lkdGgnKVxuICAgIHZhbCA9IE1hdGgubWF4IDAsIHZhbFxuICAgIEBzZXQgXCJfYWxpZ25tZW50U2Nyb2xsTGVmdFwiLCB2YWxcblxuICAjIEBwYXJhbSBuIFtpbnRdIHJvdyB0aGF0IHNob3VsZCBiZSBvbiB0b3BcbiAgc2V0VG9wT2Zmc2V0OiAobikgLT5cbiAgICB2YWwgPSAobiAtIDEpICogQGdldCgncm93SGVpZ2h0JylcbiAgICB2YWwgPSBNYXRoLm1heCAwLCB2YWxcbiAgICBAc2V0IFwiX2FsaWdubWVudFNjcm9sbFRvcFwiLHZhbFxuXG4gICMgbGVuZ3RoIG9mIGFsbCBlbGVtZW50cyBsZWZ0IHRvIHRoZSBtYWluIHNlcXVlbmNlIGJvZHk6IGxhYmVscywgbWV0YWNlbGwsIC4uXG4gIGdldExhYmVsV2lkdGg6IC0+XG4gICAgIHBhZGRpbmdMZWZ0ID0gMFxuICAgICBwYWRkaW5nTGVmdCArPSBAZ2V0IFwibGFiZWxXaWR0aFwiIGlmIEBnLnZpcy5nZXQgXCJsYWJlbHNcIlxuICAgICBwYWRkaW5nTGVmdCArPSBAZ2V0IFwibWV0YVdpZHRoXCIgaWYgQGcudmlzLmdldCBcIm1ldGFjZWxsXCJcbiAgICAgcmV0dXJuIHBhZGRpbmdMZWZ0XG5cbiAgX2FkanVzdFdpZHRoOiAoZWwsIG1vZGVsKSAtPlxuICAgIGlmIGVsLnBhcmVudE5vZGU/IGFuZCBlbC5wYXJlbnROb2RlLm9mZnNldFdpZHRoIGlzbnQgMFxuICAgICAgcGFyZW50V2lkdGggPSBlbC5wYXJlbnROb2RlLm9mZnNldFdpZHRoXG4gICAgZWxzZVxuICAgICAgcGFyZW50V2lkdGggPSBkb2N1bWVudC5ib2R5LmNsaWVudFdpZHRoIC0gMzVcblxuICAgICMgVE9ETzogZGlydHkgaGFja1xuICAgIG1heFdpZHRoID0gcGFyZW50V2lkdGggLSBAZ2V0TGFiZWxXaWR0aCgpXG4gICAgY2FsY1dpZHRoID0gQGdldEFsaWdubWVudFdpZHRoKCBtb2RlbC5nZXRNYXhMZW5ndGgoKSAtIEBnLmNvbHVtbnMuZ2V0KCdoaWRkZW4nKS5sZW5ndGgpXG4gICAgdmFsID0gTWF0aC5taW4obWF4V2lkdGgsY2FsY1dpZHRoKVxuICAgICMgcm91bmQgdG8gYSB2YWxpZCBBQSBib3hcbiAgICB2YWwgPSBNYXRoLmZsb29yKCB2YWwgLyBAZ2V0KFwiY29sdW1uV2lkdGhcIikpICogQGdldChcImNvbHVtbldpZHRoXCIpXG4gICAgQHNldCBcImFsaWdubWVudFdpZHRoXCIsIHZhbFxuICAgICNlbC5zdHlsZS53aWR0aCA9IE1hdGgubWluIGNhbGNXaWR0aCwgbWF4V2lkdGhcblxuICAjIHVwZGF0ZXMgYm90aCBzY3JvbGwgcHJvcGVydGllcyAoaWYgbmVlZGVkKVxuICBfY2hlY2tTY3JvbGxpbmc6IChzY3JvbGxPYmosIG9wdHMpIC0+XG4gICAgeFNjcm9sbCA9IHNjcm9sbE9ialswXVxuICAgIHlTY3JvbGwgPSBzY3JvbGxPYmpbMV1cblxuICAgIEBzZXQgXCJfYWxpZ25tZW50U2Nyb2xsTGVmdFwiLCB4U2Nyb2xsLCBvcHRzXG4gICAgQHNldCBcIl9hbGlnbm1lbnRTY3JvbGxUb3BcIiwgeVNjcm9sbCwgb3B0c1xuIiwibW9kdWxlLmV4cG9ydHMubXNhID0gcmVxdWlyZShcIi4vbXNhXCIpXG5cbiMgbW9kZWxzXG5tb2R1bGUuZXhwb3J0cy5tb2RlbCA9IHJlcXVpcmUoXCIuL21vZGVsXCIpXG5cbiMgZXh0cmEgcGx1Z2lucywgZXh0ZW5zaW9uc1xubW9kdWxlLmV4cG9ydHMuYWxnbyA9IHJlcXVpcmUoXCIuL2FsZ29cIilcbm1vZHVsZS5leHBvcnRzLm1lbnUgPSByZXF1aXJlKFwiLi9tZW51XCIpXG5tb2R1bGUuZXhwb3J0cy51dGlscyA9IHJlcXVpcmUoXCIuL3V0aWxzXCIpXG5cbiMgcHJvYmFibHkgbmVlZGVkIG1vcmUgb2Z0ZW5cbm1vZHVsZS5leHBvcnRzLnNlbGVjdGlvbiA9IHJlcXVpcmUoXCIuL2cvc2VsZWN0aW9uL1NlbGVjdGlvblwiKVxubW9kdWxlLmV4cG9ydHMudmlldyA9IHJlcXVpcmUoXCJiYWNrYm9uZS12aWV3alwiKVxubW9kdWxlLmV4cG9ydHMuYm9uZVZpZXcgPSByZXF1aXJlKFwiYmFja2JvbmUtY2hpbGRzXCIpXG5cbiMgY29udmVuaWVuY2Vcbm1vZHVsZS5leHBvcnRzLl8gPSByZXF1aXJlICd1bmRlcnNjb3JlJ1xubW9kdWxlLmV4cG9ydHMuJCA9IHJlcXVpcmUgJ2pib25lJ1xuXG5tb2R1bGUuZXhwb3J0cy52ZXJzaW9uID0gXCIwLjEuMFwiXG4iLCJib25lVmlldyA9IHJlcXVpcmUoXCJiYWNrYm9uZS1jaGlsZHNcIilcblxuIyBtZW51IHZpZXdzXG5JbXBvcnRNZW51ID0gcmVxdWlyZSBcIi4vdmlld3MvSW1wb3J0TWVudVwiXG5GaWx0ZXJNZW51ID0gcmVxdWlyZSBcIi4vdmlld3MvRmlsdGVyTWVudVwiXG5TZWxlY3Rpb25NZW51ID0gcmVxdWlyZSBcIi4vdmlld3MvU2VsZWN0aW9uTWVudVwiXG5WaXNNZW51ID0gcmVxdWlyZSBcIi4vdmlld3MvVmlzTWVudVwiXG5Db2xvck1lbnUgPSByZXF1aXJlIFwiLi92aWV3cy9Db2xvck1lbnVcIlxuT3JkZXJpbmdNZW51ID0gcmVxdWlyZSBcIi4vdmlld3MvT3JkZXJpbmdNZW51XCJcbkV4dHJhTWVudSA9IHJlcXVpcmUgXCIuL3ZpZXdzL0V4dHJhTWVudVwiXG5FeHBvcnRNZW51ID0gcmVxdWlyZSBcIi4vdmlld3MvRXhwb3J0TWVudVwiXG5IZWxwTWVudSA9IHJlcXVpcmUgXCIuL3ZpZXdzL0hlbHBNZW51XCJcblxuIyB0aGlzIHZlcnkgYmFzaWMgbWVudSBkZW1vbnN0cmF0ZXMgY2FsbHMgdG8gdGhlIE1TQSBjb21wb25lbnRcbm1vZHVsZS5leHBvcnRzID0gTWVudVZpZXcgPSBib25lVmlldy5leHRlbmRcblxuICBpbml0aWFsaXplOiAoZGF0YSkgLT5cbiAgICBAbXNhID0gZGF0YS5tc2FcblxuICAgIEBhZGRWaWV3ICBcIjEwX2ltcG9ydFwiLCBuZXcgSW1wb3J0TWVudSBtb2RlbDogQG1zYS5zZXFzLCBnOkBtc2EuZ1xuICAgIEBhZGRWaWV3ICBcIjIwX2ZpbHRlclwiLCBuZXcgRmlsdGVyTWVudSBtb2RlbDogQG1zYS5zZXFzLCBnOkBtc2EuZ1xuICAgIEBhZGRWaWV3ICBcIjMwX3NlbGVjdGlvblwiLCBuZXcgU2VsZWN0aW9uTWVudSBtb2RlbDogQG1zYS5zZXFzLCBnOkBtc2EuZ1xuICAgIEBhZGRWaWV3ICBcIjQwX3Zpc1wiLCBuZXcgVmlzTWVudSBtb2RlbDogQG1zYS5zZXFzLCBnOkBtc2EuZ1xuICAgIEBhZGRWaWV3ICBcIjUwX2NvbG9yXCIsIG5ldyBDb2xvck1lbnUgbW9kZWw6IEBtc2Euc2VxcywgZzpAbXNhLmdcbiAgICBAYWRkVmlldyAgXCI2MF9vcmRlcmluZ1wiLCBuZXcgT3JkZXJpbmdNZW51IG1vZGVsOiBAbXNhLnNlcXMsIGc6QG1zYS5nXG4gICAgQGFkZFZpZXcgIFwiNzBfZXh0cmFcIiwgbmV3IEV4dHJhTWVudSBtb2RlbDogQG1zYS5zZXFzLCBnOkBtc2EuZ1xuICAgIEBhZGRWaWV3ICBcIjgwX2V4cG9ydFwiLCBuZXcgRXhwb3J0TWVudSBtb2RlbDogQG1zYS5zZXFzLCBnOkBtc2EuZywgbXNhOkBtc2FcbiAgICBAYWRkVmlldyAgXCI5MF9oZWxwXCIsIG5ldyBIZWxwTWVudSAgZzpAbXNhLmdcblxuICByZW5kZXI6IC0+XG4gICAgQHJlbmRlclN1YnZpZXdzKClcbiAgICAjIG90aGVyXG4gICAgQGVsLnNldEF0dHJpYnV0ZSBcImNsYXNzXCIsIFwiYmlvanNfbXNhX21lbnViYXJcIlxuICAgIEBlbC5hcHBlbmRDaGlsZCBkb2N1bWVudC5jcmVhdGVFbGVtZW50KFwicFwiKVxuIiwibW9kdWxlLmV4cG9ydHMuZGVmYXVsdG1lbnUgPSByZXF1aXJlKFwiLi9kZWZhdWx0bWVudVwiKVxubW9kdWxlLmV4cG9ydHMubWVudWJ1aWxkZXIgPSByZXF1aXJlKFwiLi9tZW51YnVpbGRlclwiKVxuIiwiQk1hdGggPSByZXF1aXJlIFwiLi4vdXRpbHMvYm1hdGhcIlxuamJvbmUgPSByZXF1aXJlIFwiamJvbmVcIlxudmlldyA9IHJlcXVpcmUoXCJiYWNrYm9uZS12aWV3alwiKVxuXG4jamJvbmUuZm4uYWRkQ2xhc3MgPSAoY2xhc3NOYW1lKSAtPlxuIyAgZm9yIGkgaW4gWzAuLiBALmxlbmd0aCAtIDFdIGJ5IDFcbiMgICAgQFtpXS5jbGFzc0xpc3QuYWRkIGNsYXNzTmFtZVxuIyAgQFxuXG5tb2R1bGUuZXhwb3J0cyA9IE1lbnVCdWlsZGVyID0gdmlldy5leHRlbmRcblxuICAgIHNldE5hbWU6IChAbmFtZSkgLT5cbiAgICAgIEBfbm9kZXMgPSAgW11cblxuICAgIGFkZE5vZGU6IChsYWJlbCwgY2FsbGJhY2ssIGRhdGEpIC0+XG4gICAgICBzdHlsZSA9IGRhdGEuc3R5bGUgaWYgZGF0YT9cbiAgICAgIEBfbm9kZXMgPSBbXSB1bmxlc3MgQF9ub2Rlcz9cbiAgICAgIEBfbm9kZXMucHVzaCB7bGFiZWw6IGxhYmVsLCBjYWxsYmFjazogY2FsbGJhY2ssIHN0eWxlOiBzdHlsZX1cblxuICAgIGJ1aWxkRE9NOiAtPlxuICAgICAgQF9idWlsZE1cbiAgICAgICAgbm9kZXM6IEBfbm9kZXNcbiAgICAgICAgbmFtZTogQG5hbWVcblxuICAgIF9idWlsZE06IChkYXRhKSAtPlxuICAgICAgbm9kZXMgPSBkYXRhLm5vZGVzXG4gICAgICBuYW1lID0gZGF0YS5uYW1lXG5cbiAgICAgIG1lbnUgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50IFwiZGl2XCJcbiAgICAgIG1lbnUuY2xhc3NOYW1lID0gXCJkcm9wZG93biBkcm9wZG93bi10aXBcIlxuICAgICAgbWVudS5pZCA9IFwiYWRyb3AtXCIgKyBCTWF0aC51bmlxdWVJZCgpXG4gICAgICBtZW51LnN0eWxlLmRpc3BsYXkgPSBcIm5vbmVcIlxuXG4gICAgICBtZW51VWwgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50IFwidWxcIlxuICAgICAgbWVudVVsLmNsYXNzTmFtZSA9IFwiZHJvcGRvd24tbWVudVwiXG5cbiAgICAgICMgZHJvcGRvd24gbWVudVxuICAgICAgZm9yIG5vZGUgaW4gbm9kZXNcbiAgICAgICAgbGkgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50IFwibGlcIlxuXG4gICAgICAgIGxpLnRleHRDb250ZW50ID0gbm9kZS5sYWJlbFxuICAgICAgICBmb3Iga2V5LHN0eWxlIG9mIG5vZGUuc3R5bGVcbiAgICAgICAgICBsaS5zdHlsZVtrZXldID0gc3R5bGVcbiAgICAgICAgbGkuYWRkRXZlbnRMaXN0ZW5lciBcImNsaWNrXCIsIG5vZGUuY2FsbGJhY2tcbiAgICAgICAgaWYgQGc/XG4gICAgICAgICAgbGkuc3R5bGUubGluZUhlaWdodCA9IEBnLnpvb21lci5nZXQgXCJtZW51SXRlbUxpbmVIZWlnaHRcIlxuXG4gICAgICAgIG1lbnVVbC5hcHBlbmRDaGlsZCBsaVxuXG4gICAgICBtZW51LmFwcGVuZENoaWxkIG1lbnVVbFxuXG4gICAgICBmcmFnID0gZG9jdW1lbnQuY3JlYXRlRG9jdW1lbnRGcmFnbWVudCgpXG4gICAgICAjIGRpcGxheSBpdFxuICAgICAgZGlzcGxheWVkQnV0dG9uID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCBcImFcIlxuICAgICAgZGlzcGxheWVkQnV0dG9uLnRleHRDb250ZW50ID0gbmFtZVxuICAgICAgZGlzcGxheWVkQnV0dG9uLmNsYXNzTmFtZSA9IFwiYmlvanNfbXNhX21lbnViYXJfYWxpbmtcIlxuXG4gICAgICAjIHRpbnkgc3R5bGVcbiAgICAgIGlmIEBnP1xuICAgICAgICBtZW51VWwuc3R5bGUuZm9udFNpemUgPSBAZy56b29tZXIuZ2V0IFwibWVudUl0ZW1Gb250c2l6ZVwiXG4gICAgICAgIGRpc3BsYXllZEJ1dHRvbi5zdHlsZS5mb250U2l6ZSA9IEBnLnpvb21lci5nZXQgXCJtZW51Rm9udHNpemVcIlxuICAgICAgICBkaXNwbGF5ZWRCdXR0b24uc3R5bGUubWFyZ2luTGVmdCA9IEBnLnpvb21lci5nZXQgXCJtZW51TWFyZ2luTGVmdFwiXG4gICAgICAgIGRpc3BsYXllZEJ1dHRvbi5zdHlsZS5wYWRkaW5nID0gQGcuem9vbWVyLmdldCBcIm1lbnVQYWRkaW5nXCJcblxuICAgICAgamJvbmUoZGlzcGxheWVkQnV0dG9uKS5vbiBcImNsaWNrXCIsIChlKSA9PlxuICAgICAgICBAX3Nob3dNZW51IGUsbWVudSxkaXNwbGF5ZWRCdXR0b25cblxuICAgICAgICAjIHdhaXQgdW50aWwgZXZlbnQgaXMgYnViYmxlZCB0byB0aGUgdG9wXG4gICAgICAgIHdpbmRvdy5zZXRUaW1lb3V0IC0+XG4gICAgICAgICAgamJvbmUoZG9jdW1lbnQuYm9keSkub25lIFwiY2xpY2tcIiwgKGUpIC0+XG4gICAgICAgICAgICBjb25zb2xlLmxvZyBcIm5leHQgY2xpY2tcIlxuICAgICAgICAgICAgbWVudS5zdHlsZS5kaXNwbGF5ID0gXCJub25lXCJcbiAgICAgICAgLCA1XG5cblxuICAgICAgZnJhZy5hcHBlbmRDaGlsZCBtZW51XG4gICAgICBmcmFnLmFwcGVuZENoaWxkIGRpc3BsYXllZEJ1dHRvblxuICAgICAgcmV0dXJuICBmcmFnXG5cbiAgICBfc2hvd01lbnU6IChlLCBtZW51LCB0YXJnZXQpIC0+XG4gICAgICAjamJvbmUobWVudSkuYWRkQ2xhc3MgXCJkcm9wZG93bi1vcGVuXCJcbiAgICAgIG1lbnUuc3R5bGUuZGlzcGxheSA9IFwiYmxvY2tcIlxuICAgICAgbWVudS5zdHlsZS5wb3NpdGlvbiA9IFwiYWJzb2x1dGVcIlxuXG4gICAgICByZWN0ID0gdGFyZ2V0LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpXG4gICAgICBtZW51LnN0eWxlLmxlZnQgPSByZWN0LmxlZnQgKyBcInB4XCJcbiAgICAgIG1lbnUuc3R5bGUudG9wID0gKHJlY3QudG9wICsgdGFyZ2V0Lm9mZnNldEhlaWdodCkgKyBcInB4XCJcbiIsIk1lbnVCdWlsZGVyID0gcmVxdWlyZSBcIi4uL21lbnVidWlsZGVyXCJcbl8gPSByZXF1aXJlIFwidW5kZXJzY29yZVwiXG5kb20gPSByZXF1aXJlIFwiZG9tLWhlbHBlclwiXG5cbm1vZHVsZS5leHBvcnRzID0gQ29sb3JNZW51ID0gTWVudUJ1aWxkZXIuZXh0ZW5kXG5cbiAgaW5pdGlhbGl6ZTogKGRhdGEpIC0+XG4gICAgQGcgPSBkYXRhLmdcbiAgICBAZWwuc3R5bGUuZGlzcGxheSA9IFwiaW5saW5lLWJsb2NrXCJcbiAgICBAbGlzdGVuVG8gQGcuY29sb3JzY2hlbWUsIFwiY2hhbmdlXCIsIC0+XG4gICAgICBAcmVuZGVyKClcblxuICByZW5kZXI6IC0+XG4gICAgbWVudUNvbG9yID0gQHNldE5hbWUoXCJDb2xvciBzY2hlbWVcIilcblxuICAgIGNvbG9yc2NoZW1lcyA9IEBnZXRDb2xvcnNjaGVtZXMoKVxuICAgIGZvciBzY2hlbWUgaW4gY29sb3JzY2hlbWVzXG4gICAgICBAYWRkU2NoZW1lIG1lbnVDb2xvciwgc2NoZW1lXG5cbiAgICB0ZXh0ID0gXCJCYWNrZ3JvdW5kXCJcbiAgICBpZiBAZy5jb2xvcnNjaGVtZS5nZXQoXCJjb2xvckJhY2tncm91bmRcIilcbiAgICAgIHRleHQgPSBcIkhpZGUgXCIgKyB0ZXh0XG4gICAgZWxzZVxuICAgICAgdGV4dCA9IFwiU2hvdyBcIiArIHRleHRcblxuICAgIEBhZGROb2RlIHRleHQsID0+XG4gICAgICBAZy5jb2xvcnNjaGVtZS5zZXQgXCJjb2xvckJhY2tncm91bmRcIiwgIUBnLmNvbG9yc2NoZW1lLmdldChcImNvbG9yQmFja2dyb3VuZFwiKVxuXG4gICAgQGdyZXkgbWVudUNvbG9yXG5cbiAgICAjIFRPRE86IG1ha2UgbW9yZSBlZmZpY2llbnRcbiAgICBkb20ucmVtb3ZlQWxsQ2hpbGRzIEBlbFxuICAgIEBlbC5hcHBlbmRDaGlsZCBAYnVpbGRET00oKVxuICAgIEBcblxuICBhZGRTY2hlbWU6IChtZW51Q29sb3Isc2NoZW1lKSAtPlxuICAgIHN0eWxlID0ge31cbiAgICBjdXJyZW50ID0gQGcuY29sb3JzY2hlbWUuZ2V0KFwic2NoZW1lXCIpXG4gICAgaWYgY3VycmVudCBpcyBzY2hlbWUuaWRcbiAgICAgIHN0eWxlLmJhY2tncm91bmRDb2xvciA9IFwiIzc3RUQ4MFwiXG5cbiAgICBAYWRkTm9kZSBzY2hlbWUubmFtZSwgPT5cbiAgICAgIEBnLmNvbG9yc2NoZW1lLnNldCBcInNjaGVtZVwiLCBzY2hlbWUuaWRcbiAgICAsXG4gICAgICBzdHlsZTogc3R5bGVcblxuICBnZXRDb2xvcnNjaGVtZXM6IC0+XG4gICAgc2NoZW1lcyAgPSBbXVxuICAgIHNjaGVtZXMucHVzaCBuYW1lOiBcIlphcHBvXCIsIGlkOiBcInphcHBvXCJcbiAgICBzY2hlbWVzLnB1c2ggbmFtZTogXCJUYXlsb3JcIiwgaWQ6IFwidGF5bG9yXCJcbiAgICBzY2hlbWVzLnB1c2ggbmFtZTogXCJIeWRyb3Bob2JpY2l0eVwiLCBpZDogXCJoeWRyb1wiXG4gICAgc2NoZW1lcy5wdXNoIG5hbWU6IFwiTGVza1wiLCBpZDogXCJsZXNrXCJcbiAgICBzY2hlbWVzLnB1c2ggbmFtZTogXCJDaW5lbWFcIiwgaWQ6IFwiY2luZW1hXCJcbiAgICBzY2hlbWVzLnB1c2ggbmFtZTogXCJNQUVcIiwgaWQ6IFwibWFlXCJcbiAgICBzY2hlbWVzLnB1c2ggbmFtZTogXCJDbHVzdGFsXCIsIGlkOiBcImNsdXN0YWxcIlxuICAgIHNjaGVtZXMucHVzaCBuYW1lOiBcIkNsdXN0YWwyXCIsIGlkOiBcImNsdXN0YWwyXCJcbiAgICBzY2hlbWVzLnB1c2ggbmFtZTogXCJUdXJuXCIsIGlkOiBcInR1cm5cIlxuICAgIHNjaGVtZXMucHVzaCBuYW1lOiBcIlN0cmFuZFwiLCBpZDogXCJzdHJhbmRcIlxuICAgIHNjaGVtZXMucHVzaCBuYW1lOiBcIkJ1cmllZFwiLCBpZDogXCJidXJpZWRcIlxuICAgIHNjaGVtZXMucHVzaCBuYW1lOiBcIkhlbGl4XCIsIGlkOiBcImhlbGl4XCJcbiAgICBzY2hlbWVzLnB1c2ggbmFtZTogXCJOdWNsZW90aWRlXCIsIGlkOiBcIm51Y2xlb3RpZGVcIlxuICAgIHNjaGVtZXMucHVzaCBuYW1lOiBcIlB1cmluZVwiLCBpZDogXCJwdXJpbmVcIlxuICAgIHNjaGVtZXMucHVzaCBuYW1lOiBcIlBJRFwiLCBpZDogXCJwaWRcIlxuICAgIHNjaGVtZXMucHVzaCBuYW1lOiBcIk5vIGNvbG9yXCIsIGlkOiBcImZvb1wiXG4gICAgc2NoZW1lc1xuXG4gIGdyZXk6IChtZW51Q29sb3IpIC0+XG4gICAgIyBncmV5cyBhbGwgbG93ZXJjYXNlIGxldHRlcnNcbiAgICBAYWRkTm9kZSBcIkdyZXlcIiwgPT5cbiAgICAgIEBnLmNvbG9yc2NoZW1lLnNldCBcInNob3dMb3dlckNhc2VcIiwgZmFsc2VcbiAgICAgIEBtb2RlbC5lYWNoIChzZXEpIC0+XG4gICAgICAgIHJlc2lkdWVzID0gc2VxLmdldCBcInNlcVwiXG4gICAgICAgIGdyZXkgPSBbXVxuICAgICAgICBfLmVhY2ggcmVzaWR1ZXMsIChlbCwgaW5kZXgpIC0+XG4gICAgICAgICAgaWYgZWwgaXMgZWwudG9Mb3dlckNhc2UoKVxuICAgICAgICAgICAgZ3JleS5wdXNoIGluZGV4XG4gICAgICAgIHNlcS5zZXQgXCJncmV5XCIsIGdyZXlcblxuICAgIEBhZGROb2RlIFwiR3JleSBieSB0aHJlc2hvbGRcIiwgPT5cbiAgICAgIHRocmVzaG9sZCA9IHByb21wdCBcIkVudGVyIHRocmVzaG9sZCAoaW4gcGVyY2VudClcIiwgMjBcbiAgICAgIHRocmVzaG9sZCA9IHRocmVzaG9sZCAvIDEwMFxuICAgICAgbWF4TGVuID0gQG1vZGVsLmdldE1heExlbmd0aCgpXG4gICAgICBjb25zZXJ2ID0gQGcuY29sdW1ucy5nZXQoXCJjb25zZXJ2XCIpXG4gICAgICBncmV5ID0gW11cbiAgICAgIGZvciBpIGluIFswLi4gbWF4TGVuIC0gMV1cbiAgICAgICAgY29uc29sZS5sb2cgY29uc2VydltpXVxuICAgICAgICBpZiBjb25zZXJ2W2ldIDwgdGhyZXNob2xkXG4gICAgICAgICAgZ3JleS5wdXNoIGlcbiAgICAgIEBtb2RlbC5lYWNoIChzZXEpIC0+XG4gICAgICAgIHNlcS5zZXQgXCJncmV5XCIsIGdyZXlcblxuICAgIEBhZGROb2RlIFwiR3JleSBzZWxlY3Rpb25cIiwgPT5cbiAgICAgIG1heExlbiA9IEBtb2RlbC5nZXRNYXhMZW5ndGgoKVxuICAgICAgQG1vZGVsLmVhY2ggKHNlcSkgPT5cbiAgICAgICAgYmxvY2tzID0gQGcuc2VsY29sLmdldEJsb2Nrc0ZvclJvdyhzZXEuZ2V0KFwiaWRcIiksbWF4TGVuKVxuICAgICAgICBzZXEuc2V0IFwiZ3JleVwiLCBibG9ja3NcblxuICAgIEBhZGROb2RlIFwiUmVzZXQgZ3JleVwiLCA9PlxuICAgICAgQGcuY29sb3JzY2hlbWUuc2V0IFwic2hvd0xvd2VyQ2FzZVwiLCB0cnVlXG4gICAgICBAbW9kZWwuZWFjaCAoc2VxKSAtPlxuICAgICAgICBzZXEuc2V0IFwiZ3JleVwiLCBbXVxuIiwiTWVudUJ1aWxkZXIgPSByZXF1aXJlIFwiLi4vbWVudWJ1aWxkZXJcIlxuc2F2ZUFzID0gcmVxdWlyZSBcImJyb3dzZXItc2F2ZWFzXCJcbkZhc3RhRXhwb3J0ZXIgPSByZXF1aXJlKFwiYmlvanMtaW8tZmFzdGFcIikud3JpdGVyXG5fID0gcmVxdWlyZSBcInVuZGVyc2NvcmVcIlxuYmxvYlVSTCA9IHJlcXVpcmUgXCJibHVlaW1wX2NhbnZhc3RvYmxvYlwiXG5cbm1vZHVsZS5leHBvcnRzID0gRXhwb3J0TWVudSA9IE1lbnVCdWlsZGVyLmV4dGVuZFxuXG4gIGluaXRpYWxpemU6IChkYXRhKSAtPlxuICAgIEBnID0gZGF0YS5nXG4gICAgQG1zYSA9IGRhdGEubXNhXG4gICAgQGVsLnN0eWxlLmRpc3BsYXkgPSBcImlubGluZS1ibG9ja1wiXG5cbiAgcmVuZGVyOiAtPlxuICAgIEBzZXROYW1lKFwiRXhwb3J0XCIpXG5cbiAgICBAYWRkTm9kZSBcIkV4cG9ydCBzZXF1ZW5jZXNcIiwgPT5cbiAgICAgICMgbGltaXQgYXQgYWJvdXQgMjU2a1xuICAgICAgdGV4dCA9IEZhc3RhRXhwb3J0ZXIuZXhwb3J0IEBtb2RlbC50b0pTT04oKVxuICAgICAgYmxvYiA9IG5ldyBCbG9iKFt0ZXh0XSwge3R5cGUgOiAndGV4dC9wbGFpbid9KVxuICAgICAgc2F2ZUFzIGJsb2IsIFwiYWxsLmZhc3RhXCJcblxuICAgIEBhZGROb2RlIFwiRXhwb3J0IHNlbGVjdGlvblwiLCA9PlxuICAgICAgc2VsZWN0aW9uID0gQGcuc2VsY29sLnBsdWNrIFwic2VxSWRcIlxuICAgICAgaWYgc2VsZWN0aW9uP1xuICAgICAgICAjIGZpbHRlciB0aG9zZSBzZXFpZHNcbiAgICAgICAgc2VsZWN0aW9uID0gQG1vZGVsLmZpbHRlciAoZWwpIC0+XG4gICAgICAgICAgXy5jb250YWlucyBzZWxlY3Rpb24sIGVsLmdldCBcImlkXCJcbiAgICAgICAgZm9yIGkgaW4gWzAuLiBzZWxlY3Rpb24ubGVuZ3RoIC0gMV0gYnkgMVxuICAgICAgICAgIHNlbGVjdGlvbltpXSA9IHNlbGVjdGlvbltpXS50b0pTT04oKVxuICAgICAgZWxzZVxuICAgICAgICBzZWxlY3Rpb24gPSBAbW9kZWwudG9KU09OKClcbiAgICAgICAgY29uc29sZS5sb2cgXCJubyBzZWxlY3Rpb24gZm91bmRcIlxuICAgICAgdGV4dCA9IEZhc3RhRXhwb3J0ZXIuZXhwb3J0IHNlbGVjdGlvblxuICAgICAgYmxvYiA9IG5ldyBCbG9iKFt0ZXh0XSwge3R5cGUgOiAndGV4dC9wbGFpbid9KVxuICAgICAgc2F2ZUFzIGJsb2IsIFwic2VsZWN0aW9uLmZhc3RhXCJcblxuICAgICMgVE9ETzogdXNlIGh0dHBzOi8vZ2l0aHViLmNvbS9ibHVlaW1wL0phdmFTY3JpcHQtQ2FudmFzLXRvLUJsb2IvYmxvYi9tYXN0ZXIvanMvY2FudmFzLXRvLWJsb2IuanNcbiAgICBAYWRkTm9kZSBcIkV4cG9ydCBpbWFnZVwiLCA9PlxuICAgICAgIyBUT0RPOiB0aGlzIGlzIHZlcnkgdWdseVxuICAgICAgY2FudmFzID0gQG1zYS5nZXRWaWV3KCdzdGFnZScpLmdldFZpZXcoJ2JvZHknKS5nZXRWaWV3KCdzZXFibG9jaycpLmVsXG4gICAgICBpZiBjYW52YXM/XG4gICAgICAgIHVybCA9IGNhbnZhcy50b0RhdGFVUkwoJ2ltYWdlL3BuZycpXG4gICAgICAgIHNhdmVBcyBibG9iVVJMKHVybCksIFwiYmlvanMtbXNhLnBuZ1wiLCBcImltYWdlL3BuZ1wiXG5cbiAgICAgICMgYWRkIG9jdGV0LXN0cmVhbVxuICAgICAgI3VybCA9IHVybC5yZXBsYWNlKCAvLy8gIyBjcyBoZXJlZ2V4ZXNcbiAgICAgICMvXmRhdGFbOl1pbWFnZVxcLyhwbmd8anBnfGpwZWcpWztdL2lcbiAgICAgICMvLy8sIFwiZGF0YTphcHBsaWNhdGlvbi9vY3RldC1zdHJlYW07XCIpXG5cbiAgICBAZWwuYXBwZW5kQ2hpbGQgQGJ1aWxkRE9NKClcbiAgICBAXG4iLCJNZW51QnVpbGRlciA9IHJlcXVpcmUgXCIuLi9tZW51YnVpbGRlclwiXG5jb25zZW51cyA9IHJlcXVpcmUgXCIuLi8uLi9hbGdvL0NvbnNlbnN1c0NhbGNcIlxuU2VxID0gcmVxdWlyZSBcIi4uLy4uL21vZGVsL1NlcXVlbmNlXCJcblxubW9kdWxlLmV4cG9ydHMgPSBFeHRyYU1lbnUgPSBNZW51QnVpbGRlci5leHRlbmRcblxuICBpbml0aWFsaXplOiAoZGF0YSkgLT5cbiAgICBAZyA9IGRhdGEuZ1xuICAgIEBlbC5zdHlsZS5kaXNwbGF5ID0gXCJpbmxpbmUtYmxvY2tcIlxuXG4gIHJlbmRlcjogLT5cbiAgICBAc2V0TmFtZShcIkV4dHJhc1wiKVxuICAgIEBhZGROb2RlIFwiQWRkIGNvbnNlbnN1cyBzZXFcIiwgPT5cbiAgICAgIGNvbiA9IGNvbnNlbnVzKEBtb2RlbClcbiAgICAgIGNvbnNvbGUubG9nIGNvblxuICAgICAgc2VxID0gbmV3IFNlcVxuICAgICAgICBzZXE6IGNvblxuICAgICAgICBpZDogXCIwY1wiXG4gICAgICAgIG5hbWU6IFwiY29uc2VudXNcIlxuICAgICAgQG1vZGVsLmFkZCBzZXFcbiAgICAgIEBtb2RlbC5jb21wYXJhdG9yID0gKHNlcSkgLT5cbiAgICAgICAgc2VxLmdldCBcImlkXCJcbiAgICAgIEBtb2RlbC5zb3J0KClcbiAgICBAYWRkTm9kZSBcIkluY3JlYXNlIGZvbnQgc2l6ZVwiLCA9PlxuICAgICAgQGcuem9vbWVyLnNldCBcImNvbHVtbldpZHRoXCIsIEBnLnpvb21lci5nZXQoXCJjb2x1bW5XaWR0aFwiKSArIDJcbiAgICAgIEBnLnpvb21lci5zZXQgXCJsYWJlbFdpZHRoXCIsIEBnLnpvb21lci5nZXQoXCJjb2x1bW5XaWR0aFwiKSArIDVcbiAgICAgIEBnLnpvb21lci5zZXQgXCJyb3dIZWlnaHRcIiwgQGcuem9vbWVyLmdldChcInJvd0hlaWdodFwiKSArIDJcbiAgICAgIEBnLnpvb21lci5zZXQgXCJsYWJlbEZvbnRTaXplXCIsIEBnLnpvb21lci5nZXQoXCJsYWJlbEZvbnRTaXplXCIpICsgMlxuICAgIEBhZGROb2RlIFwiRGVjcmVhc2UgZm9udCBzaXplXCIsID0+XG4gICAgICBAZy56b29tZXIuc2V0IFwiY29sdW1uV2lkdGhcIiwgQGcuem9vbWVyLmdldChcImNvbHVtbldpZHRoXCIpIC0gMlxuICAgICAgQGcuem9vbWVyLnNldCBcInJvd0hlaWdodFwiLCBAZy56b29tZXIuZ2V0KFwicm93SGVpZ2h0XCIpIC0gMlxuICAgICAgQGcuem9vbWVyLnNldCBcImxhYmVsRm9udFNpemVcIiwgQGcuem9vbWVyLmdldChcImxhYmVsRm9udFNpemVcIikgLSAyXG4gICAgICBpZiBAZy56b29tZXIuZ2V0KFwiY29sdW1uV2lkdGhcIikgPCA4XG4gICAgICAgIEBnLnpvb21lci5zZXQgXCJ0ZXh0VmlzaWJsZVwiLCBmYWxzZVxuXG4gICAgQGFkZE5vZGUgXCJCYXIgY2hhcnQgZXhwIHNjYWxpbmdcIiwgPT5cbiAgICAgIEBnLmNvbHVtbnMuc2V0IFwic2NhbGluZ1wiLCBcImV4cFwiXG4gICAgQGFkZE5vZGUgXCJCYXIgY2hhcnQgbGluZWFyIHNjYWxpbmdcIiwgPT5cbiAgICAgIEBnLmNvbHVtbnMuc2V0IFwic2NhbGluZ1wiLCBcImxpblwiXG4gICAgQGFkZE5vZGUgXCJCYXIgY2hhcnQgbG9nIHNjYWxpbmdcIiwgPT5cbiAgICAgIEBnLmNvbHVtbnMuc2V0IFwic2NhbGluZ1wiLCBcImxvZ1wiXG5cbiAgICBAYWRkTm9kZSBcIk1pbmltaXplZCB3aWR0aFwiLCA9PlxuICAgICAgQGcuem9vbWVyLnNldCBcImFsaWdubWVudFdpZHRoXCIsIDYwMFxuICAgIEBhZGROb2RlIFwiTWluaW1pemVkIGhlaWdodFwiLCA9PlxuICAgICAgQGcuem9vbWVyLnNldCBcImFsaWdubWVudEhlaWdodFwiLCAxMjBcblxuICAgIEBhZGROb2RlIFwiSnVtcCB0byBhIGNvbHVtblwiLCA9PlxuICAgICAgb2Zmc2V0ID0gcHJvbXB0IFwiQ29sdW1uXCIsIFwiMjBcIlxuICAgICAgaWYgb2Zmc2V0IDwgMCBvciBvZmZzZXQgPiBAbW9kZWwuZ2V0TWF4TGVuZ3RoKCkgb3IgaXNOYU4ob2Zmc2V0KVxuICAgICAgICBhbGVydCBcImludmFsaWQgY29sdW1uXCJcbiAgICAgICAgcmV0dXJuXG4gICAgICBAZy56b29tZXIuc2V0TGVmdE9mZnNldChvZmZzZXQpXG5cbiAgICBAZWwuYXBwZW5kQ2hpbGQgQGJ1aWxkRE9NKClcbiAgICBAXG4iLCJNZW51QnVpbGRlciA9IHJlcXVpcmUgXCIuLi9tZW51YnVpbGRlclwiXG5fID0gcmVxdWlyZSBcInVuZGVyc2NvcmVcIlxuXG5tb2R1bGUuZXhwb3J0cyA9IEZpbHRlck1lbnUgPSBNZW51QnVpbGRlci5leHRlbmRcblxuICBpbml0aWFsaXplOiAoZGF0YSkgLT5cbiAgICBAZyA9IGRhdGEuZ1xuICAgIEBlbC5zdHlsZS5kaXNwbGF5ID0gXCJpbmxpbmUtYmxvY2tcIlxuXG4gIHJlbmRlcjogLT5cbiAgICBAc2V0TmFtZShcIkZpbHRlclwiKVxuICAgIEBhZGROb2RlIFwiSGlkZSBjb2x1bW5zIGJ5IHRocmVzaG9sZFwiLChlKSA9PlxuICAgICAgdGhyZXNob2xkID0gcHJvbXB0IFwiRW50ZXIgdGhyZXNob2xkIChpbiBwZXJjZW50KVwiLCAyMFxuICAgICAgdGhyZXNob2xkID0gdGhyZXNob2xkIC8gMTAwXG4gICAgICBtYXhMZW4gPSBAbW9kZWwuZ2V0TWF4TGVuZ3RoKClcbiAgICAgIGhpZGRlbiA9IFtdXG4gICAgICBjb25zZXJ2ID0gQGcuY29sdW1ucy5nZXQoXCJjb25zZXJ2XCIpXG4gICAgICBmb3IgaSBpbiBbMC4uIG1heExlbiAtIDFdXG4gICAgICAgIGlmIGNvbnNlcnZbaV0gPCB0aHJlc2hvbGRcbiAgICAgICAgICBoaWRkZW4ucHVzaCBpXG4gICAgICBAZy5jb2x1bW5zLnNldCBcImhpZGRlblwiLCBoaWRkZW5cblxuICAgIEBhZGROb2RlIFwiSGlkZSBjb2x1bW5zIGJ5IHNlbGVjdGlvblwiLCA9PlxuICAgICAgaGlkZGVuT2xkID0gQGcuY29sdW1ucy5nZXQgXCJoaWRkZW5cIlxuICAgICAgaGlkZGVuID0gaGlkZGVuT2xkLmNvbmNhdCBAZy5zZWxjb2wuZ2V0QWxsQ29sdW1uQmxvY2tzIG1heExlbjogQG1vZGVsLmdldE1heExlbmd0aCgpLCB3aXRoUG9zOiB0cnVlXG4gICAgICBAZy5zZWxjb2wucmVzZXQgW11cbiAgICAgIEBnLmNvbHVtbnMuc2V0IFwiaGlkZGVuXCIsIGhpZGRlblxuXG4gICAgQGFkZE5vZGUgXCJIaWRlIGNvbHVtbnMgYnkgZ2Fwc1wiLCA9PlxuICAgICAgdGhyZXNob2xkID0gcHJvbXB0IFwiRW50ZXIgdGhyZXNob2xkIChpbiBwZXJjZW50KVwiLCAyMFxuICAgICAgdGhyZXNob2xkID0gdGhyZXNob2xkIC8gMTAwXG4gICAgICBtYXhMZW4gPSBAbW9kZWwuZ2V0TWF4TGVuZ3RoKClcbiAgICAgIGhpZGRlbiA9IFtdXG4gICAgICBmb3IgaSBpbiBbMC4uIG1heExlbiAtIDFdXG4gICAgICAgIGdhcHMgPSAwXG4gICAgICAgIHRvdGFsID0gMFxuICAgICAgICBAbW9kZWwuZWFjaCAoZWwpIC0+XG4gICAgICAgICAgZ2FwcysrIGlmIGVsLmdldCgnc2VxJylbaV0gaXMgXCItXCJcbiAgICAgICAgICB0b3RhbCsrXG4gICAgICAgIGdhcENvbnRlbnQgPSBnYXBzIC8gdG90YWxcbiAgICAgICAgaWYgZ2FwQ29udGVudCA+IHRocmVzaG9sZFxuICAgICAgICAgIGhpZGRlbi5wdXNoIGlcbiAgICAgIEBnLmNvbHVtbnMuc2V0IFwiaGlkZGVuXCIsIGhpZGRlblxuXG4gICAgQGFkZE5vZGUgXCJIaWRlIHNlcXMgYnkgaWRlbnRpdHlcIiwgPT5cbiAgICAgIHRocmVzaG9sZCA9IHByb21wdCBcIkVudGVyIHRocmVzaG9sZCAoaW4gcGVyY2VudClcIiwgMjBcbiAgICAgIHRocmVzaG9sZCA9IHRocmVzaG9sZCAvIDEwMFxuICAgICAgQG1vZGVsLmVhY2ggKGVsKSAtPlxuICAgICAgICBpZiBlbC5nZXQoJ2lkZW50aXR5JykgPCB0aHJlc2hvbGRcbiAgICAgICAgICBlbC5zZXQoJ2hpZGRlbicsIHRydWUpXG5cbiAgICBAYWRkTm9kZSBcIkhpZGUgc2VxcyBieSBzZWxlY3Rpb25cIiwgPT5cbiAgICAgIGhpZGRlbiA9IEBnLnNlbGNvbC53aGVyZSB0eXBlOiBcInJvd1wiXG4gICAgICBpZHMgPSBfLm1hcCBoaWRkZW4sIChlbCkgLT4gZWwuZ2V0KCdzZXFJZCcpXG4gICAgICBAZy5zZWxjb2wucmVzZXQgW11cbiAgICAgIEBtb2RlbC5lYWNoIChlbCkgLT5cbiAgICAgICAgaWYgaWRzLmluZGV4T2YoZWwuZ2V0KCdpZCcpKSA+PSAwXG4gICAgICAgICAgZWwuc2V0KCdoaWRkZW4nLCB0cnVlKVxuXG4gICAgQGFkZE5vZGUgXCJIaWRlIHNlcXMgYnkgZ2Fwc1wiLCA9PlxuICAgICAgdGhyZXNob2xkID0gcHJvbXB0IFwiRW50ZXIgdGhyZXNob2xkIChpbiBwZXJjZW50KVwiLCA0MFxuICAgICAgQG1vZGVsLmVhY2ggKGVsLGkpIC0+XG4gICAgICAgIHNlcSA9IGVsLmdldCgnc2VxJylcbiAgICAgICAgZ2FwcyA9IF8ucmVkdWNlIHNlcSwgKChtZW1vLCBjKSAtPiBtZW1vKysgaWYgYyBpcyAnLSc7bWVtbyksMFxuICAgICAgICBjb25zb2xlLmxvZyBnYXBzXG4gICAgICAgIGlmIGdhcHMgPiAgdGhyZXNob2xkXG4gICAgICAgICAgZWwuc2V0KCdoaWRkZW4nLCB0cnVlKVxuXG4gICAgQGFkZE5vZGUgXCJSZXNldFwiLCA9PlxuICAgICAgQGcuY29sdW1ucy5zZXQgXCJoaWRkZW5cIiwgW11cbiAgICAgIEBtb2RlbC5lYWNoIChlbCkgLT5cbiAgICAgICAgaWYgZWwuZ2V0KCdoaWRkZW4nKVxuICAgICAgICAgIGVsLnNldCgnaGlkZGVuJywgZmFsc2UpXG5cbiAgICBAZWwuYXBwZW5kQ2hpbGQgQGJ1aWxkRE9NKClcbiAgICBAXG4iLCJNZW51QnVpbGRlciA9IHJlcXVpcmUgXCIuLi9tZW51YnVpbGRlclwiXG5cbm1vZHVsZS5leHBvcnRzID0gSGVscE1lbnUgPSBNZW51QnVpbGRlci5leHRlbmRcblxuICBpbml0aWFsaXplOiAoZGF0YSkgLT5cbiAgICBAZyA9IGRhdGEuZ1xuXG4gIHJlbmRlcjogLT5cbiAgICBAc2V0TmFtZShcIkhlbHBcIilcbiAgICBAYWRkTm9kZSBcIkFib3V0IHRoZSBwcm9qZWN0XCIsID0+XG4gICAgICB3aW5kb3cub3BlbiBcImh0dHBzOi8vZ2l0aHViLmNvbS9ncmVlbmlmeS9iaW9qcy12aXMtbXNhXCJcbiAgICBAYWRkTm9kZSBcIlJlcG9ydCBpc3N1ZXNcIiwgPT5cbiAgICAgIHdpbmRvdy5vcGVuIFwiaHR0cHM6Ly9naXRodWIuY29tL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2EvaXNzdWVzXCJcbiAgICBAYWRkTm9kZSBcIlVzZXIgbWFudWFsXCIsID0+XG4gICAgICB3aW5kb3cub3BlbiBcImh0dHBzOi8vZ2l0aHViLmNvbS9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3dpa2lcIlxuICAgIEBlbC5zdHlsZS5kaXNwbGF5ID0gXCJpbmxpbmUtYmxvY2tcIlxuICAgIEBlbC5hcHBlbmRDaGlsZCBAYnVpbGRET00oKVxuICAgIEBcbiIsIkNsdXN0YWwgPSByZXF1aXJlIFwiYmlvanMtaW8tY2x1c3RhbFwiXG5GYXN0YVJlYWRlciA9IHJlcXVpcmUoXCJiaW9qcy1pby1mYXN0YVwiKS5wYXJzZVxuTWVudUJ1aWxkZXIgPSByZXF1aXJlIFwiLi4vbWVudWJ1aWxkZXJcIlxuY29yc1VSTCA9IHJlcXVpcmUoXCIuLi8uLi91dGlscy9wcm94eVwiKS5jb3JzVVJMXG5cbm1vZHVsZS5leHBvcnRzID0gSW1wb3J0TWVudSA9IE1lbnVCdWlsZGVyLmV4dGVuZFxuXG4gIGluaXRpYWxpemU6IChkYXRhKSAtPlxuICAgIEBnID0gZGF0YS5nXG4gICAgQGVsLnN0eWxlLmRpc3BsYXkgPSBcImlubGluZS1ibG9ja1wiXG5cbiAgcmVuZGVyOiAtPlxuICAgIEBzZXROYW1lKFwiSW1wb3J0XCIpXG4gICAgQGFkZE5vZGUgXCJGQVNUQVwiLChlKSA9PlxuICAgICAgdXJsID0gcHJvbXB0IFwiVVJMXCIsIFwiL3Rlc3QvZHVtbXkvc2FtcGxlcy9wNTMuY2x1c3RhbG8uZmFzdGFcIlxuICAgICAgdXJsID0gY29yc1VSTCB1cmwsIEBnXG4gICAgICBGYXN0YVJlYWRlci5yZWFkIHVybCwgKHNlcXMpID0+XG4gICAgICAgICMgbWFzcyB1cGRhdGUgb24gem9vbWVyXG4gICAgICAgIHpvb21lciA9IEBnLnpvb21lci50b0pTT04oKVxuICAgICAgICAjem9vbWVyLnRleHRWaXNpYmxlID0gZmFsc2VcbiAgICAgICAgI3pvb21lci5jb2x1bW5XaWR0aCA9IDRcbiAgICAgICAgem9vbWVyLmxhYmVsV2lkdGggPSAyMDBcbiAgICAgICAgem9vbWVyLmJveFJlY3RIZWlnaHQgPSAyXG4gICAgICAgIHpvb21lci5ib3hSZWN0V2lkdGggPSAyXG4gICAgICAgIEBtb2RlbC5yZXNldCBbXVxuICAgICAgICBAZy56b29tZXIuc2V0IHpvb21lclxuICAgICAgICBAbW9kZWwucmVzZXQgc2Vxc1xuICAgICAgICBAZy5jb2x1bW5zLmNhbGNDb25zZXJ2YXRpb24gQG1vZGVsXG5cbiAgICBAYWRkTm9kZSBcIkNMVVNUQUxcIiwgPT5cbiAgICAgIHVybCA9IHByb21wdCBcIlVSTFwiLCBcIi90ZXN0L2R1bW15L3NhbXBsZXMvcDUzLmNsdXN0YWxvLmNsdXN0YWxcIlxuICAgICAgdXJsID0gY29yc1VSTCB1cmwsIEBnXG4gICAgICBDbHVzdGFsLnJlYWQgdXJsLCAoc2VxcykgPT5cbiAgICAgICAgem9vbWVyID0gQGcuem9vbWVyLnRvSlNPTigpXG4gICAgICAgICN6b29tZXIudGV4dFZpc2libGUgPSBmYWxzZVxuICAgICAgICAjem9vbWVyLmNvbHVtbldpZHRoID0gNFxuICAgICAgICB6b29tZXIubGFiZWxXaWR0aCA9IDIwMFxuICAgICAgICB6b29tZXIuYm94UmVjdEhlaWdodCA9IDJcbiAgICAgICAgem9vbWVyLmJveFJlY3RXaWR0aCA9IDJcbiAgICAgICAgQG1vZGVsLnJlc2V0IFtdXG4gICAgICAgIEBnLnpvb21lci5zZXQgem9vbWVyXG4gICAgICAgIEBtb2RlbC5yZXNldCBzZXFzXG4gICAgICAgIEBnLmNvbHVtbnMuY2FsY0NvbnNlcnZhdGlvbiBAbW9kZWxcblxuICAgIEBhZGROb2RlIFwiYWRkIHlvdXIgb3duIFBhcnNlclwiLCA9PlxuICAgICAgd2luZG93Lm9wZW4gXCJodHRwczovL2dpdGh1Yi5jb20vYmlvanMvYmlvanMyXCJcblxuICAgIEBlbC5hcHBlbmRDaGlsZCBAYnVpbGRET00oKVxuICAgIEBcbiIsIk1lbnVCdWlsZGVyID0gcmVxdWlyZSBcIi4uL21lbnVidWlsZGVyXCJcbmRvbSA9IHJlcXVpcmUgXCJkb20taGVscGVyXCJcbl8gPSByZXF1aXJlKCd1bmRlcnNjb3JlJylcblxubW9kdWxlLmV4cG9ydHMgPSBPcmRlcmluZ01lbnUgPSBNZW51QnVpbGRlci5leHRlbmRcblxuICBpbml0aWFsaXplOiAoZGF0YSkgLT5cbiAgICBAZyA9IGRhdGEuZ1xuICAgIEBvcmRlciA9IFwiSURcIlxuICAgIEBlbC5zdHlsZS5kaXNwbGF5ID0gXCJpbmxpbmUtYmxvY2tcIlxuXG4gIHNldE9yZGVyOiAob3JkZXIpIC0+XG4gICAgQG9yZGVyID0gb3JkZXJcbiAgICBAcmVuZGVyKClcblxuICAjIFRPRE86IG1ha2UgbW9yZSBnZW5lcmljXG4gIHJlbmRlcjogLT5cbiAgICBAc2V0TmFtZShcIk9yZGVyaW5nXCIpXG5cbiAgICBjb21wcyA9IEBnZXRDb21wYXJhdG9ycygpXG4gICAgZm9yIG0gaW4gY29tcHNcbiAgICAgIEBfYWRkTm9kZSBtXG5cbiAgICBlbCA9IEBidWlsZERPTSgpXG5cbiAgICAjIFRPRE86IG1ha2UgbW9yZSBlZmZpY2llbnRcbiAgICBkb20ucmVtb3ZlQWxsQ2hpbGRzIEBlbFxuICAgIEBlbC5hcHBlbmRDaGlsZCBlbFxuICAgIEBcblxuICBfYWRkTm9kZTogKG0pIC0+XG4gICAgdGV4dCA9IG0udGV4dFxuICAgIHN0eWxlID0ge31cbiAgICBpZiB0ZXh0IGlzIEBvcmRlclxuICAgICAgc3R5bGUuYmFja2dyb3VuZENvbG9yID0gXCIjNzdFRDgwXCJcbiAgICBAYWRkTm9kZSB0ZXh0LCA9PlxuICAgICAgbS5wcmVjb2RlKCkgaWYgbS5wcmVjb2RlP1xuICAgICAgQG1vZGVsLmNvbXBhcmF0b3IgPSBtLmNvbXBhcmF0b3JcbiAgICAgIEBtb2RlbC5zb3J0KClcbiAgICAgIEBzZXRPcmRlciBtLnRleHRcbiAgICAsXG4gICAgICBzdHlsZTogc3R5bGVcblxuICBnZXRDb21wYXJhdG9yczogLT5cbiAgICBtb2RlbHMgPSBbXVxuXG4gICAgbW9kZWxzLnB1c2ggdGV4dDogXCJJRFwiLCBjb21wYXJhdG9yOiBcImlkXCJcblxuICAgIG1vZGVscy5wdXNoIHRleHQ6IFwiSUQgRGVzY1wiLCBjb21wYXJhdG9yOiAoYSwgYikgLT5cbiAgICAgICAgLSBhLmdldChcImlkXCIpLmxvY2FsZUNvbXBhcmUoYi5nZXQoXCJpZFwiKSlcblxuICAgIG1vZGVscy5wdXNoIHRleHQ6IFwiTGFiZWxcIiwgY29tcGFyYXRvcjogXCJuYW1lXCJcblxuICAgIG1vZGVscy5wdXNoIHRleHQ6IFwiTGFiZWwgRGVzY1wiLCBjb21wYXJhdG9yOiAoYSwgYikgLT5cbiAgICAgICAgLSBhLmdldChcIm5hbWVcIikubG9jYWxlQ29tcGFyZShiLmdldChcIm5hbWVcIikpXG5cbiAgICBtb2RlbHMucHVzaCB0ZXh0OiBcIlNlcVwiLCBjb21wYXJhdG9yOiBcInNlcVwiXG5cbiAgICBtb2RlbHMucHVzaCB0ZXh0OiBcIlNlcSBEZXNjXCIsIGNvbXBhcmF0b3I6IChhLGIpIC0+XG4gICAgICAgIC0gYS5nZXQoXCJzZXFcIikubG9jYWxlQ29tcGFyZShiLmdldChcInNlcVwiKSlcblxuICAgIG1vZGVscy5wdXNoIHRleHQ6IFwiSWRlbnRpdHlcIiwgY29tcGFyYXRvcjogXCJpZGVudGl0eVwiXG5cbiAgICBtb2RlbHMucHVzaCB0ZXh0OiBcIklkZW50aXR5IERlc2NcIiwgY29tcGFyYXRvcjogKHNlcSkgLT5cbiAgICAgICAgLSBzZXEuZ2V0IFwiaWRlbnRpdHlcIlxuXG4gICAgbW9kZWxzLnB1c2ggdGV4dDogXCJQYXJ0aXRpb24gY29kZXNcIiwgY29tcGFyYXRvcjogXCJwYXJ0aXRpb25cIiwgcHJlY29kZTogPT5cbiAgICAgICMgc2V0IHBhcnRpdGlvbnMgcmFuZG9tXG4gICAgICBAZy52aXMuc2V0KCdsYWJlbFBhcnRpdGlvbicsIHRydWUpXG4gICAgICBAbW9kZWwuZWFjaCAoZWwpIC0+XG4gICAgICAgIGVsLnNldCgncGFydGl0aW9uJywgXy5yYW5kb20oMSwzKSlcblxuXG4gICAgcmV0dXJuIG1vZGVsc1xuIiwic2VsID0gcmVxdWlyZSBcIi4uLy4uL2cvc2VsZWN0aW9uL1NlbGVjdGlvblwiXG5cbk1lbnVCdWlsZGVyID0gcmVxdWlyZSBcIi4uL21lbnVidWlsZGVyXCJcblxubW9kdWxlLmV4cG9ydHMgPSBTZWxlY3Rpb25NZW51ID0gTWVudUJ1aWxkZXIuZXh0ZW5kXG5cbiAgaW5pdGlhbGl6ZTogKGRhdGEpIC0+XG4gICAgQGcgPSBkYXRhLmdcbiAgICBAZWwuc3R5bGUuZGlzcGxheSA9IFwiaW5saW5lLWJsb2NrXCJcblxuICByZW5kZXI6IC0+XG4gICAgQHNldE5hbWUoXCJTZWxlY3Rpb25cIilcbiAgICBAYWRkTm9kZSBcIkZpbmQgTW90aWYgKHN1cHBvcnRzIFJlZ0V4KVwiLCA9PlxuICAgICAgc2VhcmNoID0gcHJvbXB0IFwieW91ciBzZWFyY2hcIiwgXCJEXCJcbiAgICAgICMgbWFya3MgYWxsIGhpdHNcbiAgICAgIHNlYXJjaCA9IG5ldyBSZWdFeHAgc2VhcmNoLCBcImdpXCJcbiAgICAgIHNlbGNvbCA9IEBnLnNlbGNvbFxuICAgICAgbmV3U2VsaSA9IFtdXG4gICAgICBsZWZ0ZXN0SW5kZXggPSBvcmlnSW5kZXggPSAxMDAwNDJcbiAgICAgIEBtb2RlbC5lYWNoIChzZXEpIC0+XG4gICAgICAgIHN0clNlcSA9IHNlcS5nZXQoXCJzZXFcIilcbiAgICAgICAgd2hpbGUgbWF0Y2ggPSBzZWFyY2guZXhlYyBzdHJTZXFcbiAgICAgICAgICBpbmRleCA9IG1hdGNoLmluZGV4XG4gICAgICAgICAgYXJncyA9IHt4U3RhcnQ6IGluZGV4LCB4RW5kOiBpbmRleCArIG1hdGNoWzBdLmxlbmd0aCAtIDEsIHNlcUlkOlxuICAgICAgICAgICAgc2VxLmdldChcImlkXCIpfVxuICAgICAgICAgIG5ld1NlbGkucHVzaCBuZXcgc2VsLnBvc3NlbChhcmdzKVxuICAgICAgICAgIGxlZnRlc3RJbmRleCA9IE1hdGgubWluIGluZGV4LCBsZWZ0ZXN0SW5kZXhcblxuICAgICAgaWYgbmV3U2VsaS5sZW5ndGggaXMgMFxuICAgICAgICBhbGVydCBcIm5vIHNlbGVjdGlvbiBmb3VuZFwiXG4gICAgICBzZWxjb2wucmVzZXQgbmV3U2VsaVxuXG4gICAgICAjIHNhZmV0eSBjaGVjayArIHVwZGF0ZSBvZmZzZXRcbiAgICAgIGxlZnRlc3RJbmRleCA9IDAgaWYgbGVmdGVzdEluZGV4IGlzIG9yaWdJbmRleFxuICAgICAgQGcuem9vbWVyLnNldExlZnRPZmZzZXQgbGVmdGVzdEluZGV4XG5cbiAgICBAYWRkTm9kZSBcIkludmVydCBjb2x1bW5zXCIsID0+XG4gICAgICBAZy5zZWxjb2wuaW52ZXJ0Q29sIFswLi5AbW9kZWwuZ2V0TWF4TGVuZ3RoKCldXG4gICAgQGFkZE5vZGUgXCJJbnZlcnQgcm93c1wiLCA9PlxuICAgICAgQGcuc2VsY29sLmludmVydFJvdyBAbW9kZWwucGx1Y2sgXCJpZFwiXG4gICAgQGFkZE5vZGUgXCJSZXNldFwiLCA9PlxuICAgICAgQGcuc2VsY29sLnJlc2V0KClcbiAgICBAZWwuYXBwZW5kQ2hpbGQgQGJ1aWxkRE9NKClcbiAgICBAXG4iLCJNZW51QnVpbGRlciA9IHJlcXVpcmUgXCIuLi9tZW51YnVpbGRlclwiXG5kb20gPSByZXF1aXJlIFwiZG9tLWhlbHBlclwiXG5cbm1vZHVsZS5leHBvcnRzID0gSW1wb3J0TWVudSA9IE1lbnVCdWlsZGVyLmV4dGVuZFxuXG4gIGluaXRpYWxpemU6IChkYXRhKSAtPlxuICAgIEBnID0gZGF0YS5nXG4gICAgQGVsLnN0eWxlLmRpc3BsYXkgPSBcImlubGluZS1ibG9ja1wiXG4gICAgQGxpc3RlblRvIEBnLnZpcywgXCJjaGFuZ2VcIiwgQHJlbmRlclxuXG4gIHJlbmRlcjogLT5cbiAgICBAc2V0TmFtZShcIlZpcy4gZWxlbWVudHNcIilcblxuICAgIHZpc0VsZW1lbnRzID0gQGdldFZpc0VsZW1lbnRzKClcbiAgICBmb3IgdmlzRWwgaW4gdmlzRWxlbWVudHNcbiAgICAgIEBfYWRkVmlzRWwgdmlzRWxcblxuICAgICMgb3RoZXJcbiAgICBAYWRkTm9kZSBcIlJlc2V0XCIsID0+XG4gICAgICBAZy52aXMuc2V0IFwibGFiZWxzXCIsIHRydWVcbiAgICAgIEBnLnZpcy5zZXQgXCJzZXF1ZW5jZXNcIiwgdHJ1ZVxuICAgICAgQGcudmlzLnNldCBcIm1ldGFjZWxsXCIsIHRydWVcbiAgICAgIEBnLnZpcy5zZXQgXCJjb25zZXJ2XCIsIHRydWVcbiAgICAgIEBnLnZpcy5zZXQgXCJsYWJlbElkXCIsIHRydWVcbiAgICAgIEBnLnZpcy5zZXQgXCJsYWJlbE5hbWVcIiwgdHJ1ZVxuICAgICAgQGcudmlzLnNldCBcImxhYmVsQ2hlY2tib3hcIiwgZmFsc2VcblxuICAgIEBhZGROb2RlIFwiVG9nZ2xlIG1vdXNlb3ZlciBldmVudHNcIiwgPT5cbiAgICAgIEBnLmNvbmZpZy5zZXQgXCJyZWdpc3Rlck1vdXNlSG92ZXJcIiwgIUBnLmNvbmZpZy5nZXQgXCJyZWdpc3Rlck1vdXNlSG92ZXJcIlxuXG4gICAgIyBUT0RPOiBtYWtlIG1vcmUgZWZmaWNpZW50XG4gICAgZG9tLnJlbW92ZUFsbENoaWxkcyBAZWxcbiAgICBAZWwuYXBwZW5kQ2hpbGQgQGJ1aWxkRE9NKClcbiAgICBAXG5cbiAgX2FkZFZpc0VsOiAodmlzRWwpIC0+XG4gICAgc3R5bGUgPSB7fVxuXG4gICAgaWYgQGcudmlzLmdldCB2aXNFbC5pZFxuICAgICAgcHJlID0gXCJIaWRlIFwiXG4gICAgICBzdHlsZS5jb2xvciA9IFwicmVkXCJcbiAgICBlbHNlXG4gICAgICBwcmUgPSBcIlNob3cgXCJcbiAgICAgIHN0eWxlLmNvbG9yID0gXCJncmVlblwiXG5cbiAgICBAYWRkTm9kZSAocHJlICsgdmlzRWwubmFtZSksID0+XG4gICAgICBAZy52aXMuc2V0IHZpc0VsLmlkLCAhIEBnLnZpcy5nZXQgdmlzRWwuaWRcbiAgICAsXG4gICAgICBzdHlsZTogc3R5bGVcblxuICBnZXRWaXNFbGVtZW50czogLT5cbiAgICB2aXMgPSBbXVxuICAgIHZpcy5wdXNoIG5hbWU6IFwiTWFya2Vyc1wiLCBpZDogXCJtYXJrZXJzXCJcbiAgICB2aXMucHVzaCBuYW1lOiBcIkxhYmVsc1wiLCBpZDogXCJsYWJlbHNcIlxuICAgIHZpcy5wdXNoIG5hbWU6IFwiU2VxdWVuY2VzXCIsIGlkOiBcInNlcXVlbmNlc1wiXG4gICAgdmlzLnB1c2ggbmFtZTogXCJNZXRhIGluZm9cIiwgaWQ6IFwibWV0YWNlbGxcIlxuICAgIHZpcy5wdXNoIG5hbWU6IFwiT3ZlcnZpZXdib3hcIiwgaWQ6IFwib3ZlcnZpZXdib3hcIlxuICAgIHZpcy5wdXNoIG5hbWU6IFwiY29uc2VydlwiLCBpZDogXCJjb25zZXJ2XCJcbiAgICB2aXMucHVzaCBuYW1lOiBcIkxhYmVsTmFtZVwiLCBpZDogXCJsYWJlbE5hbWVcIlxuICAgIHZpcy5wdXNoIG5hbWU6IFwiTGFiZWxJZFwiLCBpZDogXCJsYWJlbElkXCJcbiAgICB2aXMucHVzaCBuYW1lOiBcIkxhYmVsQ2hlY2tib3hcIiwgaWQ6IFwibGFiZWxDaGVja2JveFwiXG4gICAgcmV0dXJuIHZpc1xuIiwiRmVhdHVyZSA9IHJlcXVpcmUgXCIuL0ZlYXR1cmVcIlxuTW9kZWwgPSByZXF1aXJlKFwiYmFja2JvbmUtdGhpblwiKS5Nb2RlbFxuXG5tb2R1bGUuZXhwb3J0cyA9IEZlYXR1cmUgPSBNb2RlbC5leHRlbmRcblxuICBkZWZhdWx0czpcbiAgICB4U3RhcnQ6IC0xXG4gICAgeEVuZDogLTFcbiAgICBoZWlnaHQ6IC0xXG4gICAgdGV4dDogXCJcIlxuICAgIGZpbGxDb2xvcjogXCJyZWRcIlxuICAgIGZpbGxPcGFjaXR5OiAwLjVcbiAgICB0eXBlOiBcInJlY3RhbmdsZVwiXG4gICAgYm9yZGVyU2l6ZTogMVxuICAgIGJvcmRlckNvbG9yOiBcImJsYWNrXCJcbiAgICBib3JkZXJPcGFjaXR5OiAwLjVcbiAgICB2YWxpZGF0ZTogdHJ1ZVxuXG4gIHZhbGlkYXRlOiAtPlxuICAgIGlmIGlzTmFOIEBhdHRyaWJ1dGVzLnhTdGFydCBvciBpc05hTiBAYXR0cmlidXRlcy54RW5kXG4gICAgICBcImZlYXR1cmVzIG5lZWQgaW50ZWdlciBzdGFydCBhbmQgZW5kLlwiXG5cbiAgY29udGFpbnM6IChpbmRleCkgLT5cbiAgICByZXR1cm4gIEBhdHRyaWJ1dGVzLnhTdGFydCA8PSBpbmRleCAmJiBpbmRleCA8PSBAYXR0cmlidXRlcy54RW5kXG5cbiIsIkZlYXR1cmUgPSByZXF1aXJlIFwiLi9GZWF0dXJlXCJcbkNvbGxlY3Rpb24gPSByZXF1aXJlKFwiYmFja2JvbmUtdGhpblwiKS5Db2xsZWN0aW9uXG5fID0gcmVxdWlyZSBcInVuZGVyc2NvcmVcIlxuXG5tb2R1bGUuZXhwb3J0cyA9IEZlYXR1cmVDb2wgPSBDb2xsZWN0aW9uLmV4dGVuZFxuICBtb2RlbDogRmVhdHVyZVxuXG4gIGNvbnN0cnVjdG9yOiAtPlxuICAgIEBzdGFydE9uQ2FjaGUgPSBbXVxuICAgICMgaW52YWxpZGF0ZSBjYWNoZVxuICAgIEBvbiBcImFsbFwiLCAtPlxuICAgICAgQHN0YXJ0T25DYWNoZSA9IFtdXG4gICAgLCBAXG4gICAgQ29sbGVjdGlvbi5hcHBseSBALCBhcmd1bWVudHNcblxuICAjIHJldHVybnMgYWxsIGZlYXR1cmVzIHN0YXJ0aW5nIG9uIGluZGV4XG4gIHN0YXJ0T246IChpbmRleCkgLT5cbiAgICB1bmxlc3MgQHN0YXJ0T25DYWNoZVtpbmRleF0/XG4gICAgICBAc3RhcnRPbkNhY2hlW2luZGV4XSA9IEB3aGVyZSh7eFN0YXJ0OiBpbmRleH0pXG4gICAgcmV0dXJuIEBzdGFydE9uQ2FjaGVbaW5kZXhdXG5cbiAgY29udGFpbnM6IChpbmRleCkgLT5cbiAgICBAcmVkdWNlIChlbCxtZW1vKSAtPlxuICAgICAgbWVtbyB8fCBlbC5jb250YWlucyBpbmRleFxuICAgICwgZmFsc2VcblxuICAjIGdpdmVzIHRoZSBtaW5pbWFsIG5lZWRlZCBudW1iZXIgb2Ygcm93c1xuICAjIG5vdCBhIHZlcnkgZWZmaWNpZW50IGFsZ29yaXRobVxuICAjICh0aGVyZSBpcyBvbmUgaW4gTyhuKSApXG4gIGdldE1pblJvd3M6IC0+XG5cbiAgICBsZW4gPSBAbWF4IChlbCkgLT4gZWwuZ2V0IFwieEVuZFwiXG4gICAgcm93cyA9ICgwIGZvciB4IGluIFsxLi5sZW5dKVxuXG4gICAgQGVhY2ggKGVsKSAtPlxuICAgICAgZm9yIHggaW4gW2VsLmdldChcInhTdGFydFwiKS4uZmVhdHVyZS5nZXQoXCJ4RW5kXCIpXSBieSAxXG4gICAgICAgIHJvd3NbeF0rK1xuXG4gICAgXy5tYXggcm93c1xuIiwiU2VxdWVuY2UgPSByZXF1aXJlIFwiLi9TZXF1ZW5jZVwiXG5Db2xsZWN0aW9uID0gcmVxdWlyZShcImJhY2tib25lLXRoaW5cIikuQ29sbGVjdGlvblxuXG5tb2R1bGUuZXhwb3J0cyA9IFNlcU1hbmFnZXIgPSBDb2xsZWN0aW9uLmV4dGVuZFxuICBtb2RlbDogU2VxdWVuY2VcblxuICBjb25zdHJ1Y3RvcjogLT5cblxuICAgIENvbGxlY3Rpb24uYXBwbHkgQCwgYXJndW1lbnRzXG5cbiAgICAjIGludmFsaWRhdGUgY2FjaGVcbiAgICBAb24gXCJhbGxcIiwgLT5cbiAgICAgIEBsZW5ndGhDYWNoZSA9IG51bGxcbiAgICAsIEBcbiAgICBAbGVuZ3RoQ2FjaGUgPSBudWxsXG5cbiAgICBAXG5cbiAgIyBnaXZlcyB0aGUgbWF4IGxlbmd0aCBvZiBhbGwgc2VxdWVuY2VzXG4gICMgKGNhY2hlZClcbiAgZ2V0TWF4TGVuZ3RoOiAoKSAtPlxuICAgIHJldHVybiAwIGlmIEBtb2RlbHMubGVuZ3RoIGlzIDBcbiAgICBpZiBAbGVuZ3RoQ2FjaGUgaXMgbnVsbFxuICAgICAgQGxlbmd0aENhY2hlID0gQG1heCgoc2VxKSAtPiBzZXEuZ2V0KFwic2VxXCIpLmxlbmd0aCkuZ2V0KFwic2VxXCIpLmxlbmd0aFxuICAgIHJldHVybiBAbGVuZ3RoQ2FjaGVcblxuICAjIGdldHMgdGhlIHByZXZpb3VzIG1vZGVsXG4gICMgQHBhcmFtIGVuZGxlc3MgW2Jvb2xlYW5dIGZvciB0aGUgZmlyc3QgZWxlbWVudFxuICAjIHRydWU6IHJldHVybnMgdGhlIGxhc3QgZWxlbWVudCwgZmFsc2U6IHJldHVybnMgdW5kZWZpbmVkXG4gIHByZXY6IChtb2RlbCwgZW5kbGVzcykgLT5cbiAgICBpbmRleCA9IEBpbmRleE9mKG1vZGVsKSAtIDFcbiAgICBpbmRleCA9IEAubGVuZ3RoIC0gMSBpZiBpbmRleCA8IDAgYW5kIGVuZGxlc3NcbiAgICBAYXQoaW5kZXgpXG5cbiAgIyBnZXRzIHRoZSBuZXh0IG1vZGVsXG4gICMgQHBhcmFtIGVuZGxlc3MgW2Jvb2xlYW5dIGZvciB0aGUgbGFzdCBlbGVtZW50XG4gICMgdHJ1ZTogcmV0dXJucyB0aGUgZmlyc3QgZWxlbWVudCwgZmFsc2U6IHJldHVybnMgdW5kZWZpbmVkXG4gIG5leHQ6IChtb2RlbCwgZW5kbGVzcykgLT5cbiAgICBpbmRleCA9IEBpbmRleE9mKG1vZGVsKSArIDFcbiAgICBpbmRleCA9IDAgaWYgaW5kZXggPT0gQC5sZW5ndGggYW5kIGVuZGxlc3NcbiAgICBAYXQoaW5kZXgpXG5cbiAgIyBAcmV0dXJucyBuIFtpbnRdIG51bWJlciBvZiBoaWRkZW4gY29sdW1ucyB1bnRpbCBuXG4gIGNhbGNIaWRkZW5TZXFzOiAobikgLT5cbiAgICBuTmV3ID0gblxuICAgIGZvciBpIGluIFswLi5uTmV3XVxuICAgICAgaWYgQGF0KGkpLmdldChcImhpZGRlblwiKVxuICAgICAgICBuTmV3KytcbiAgICBuTmV3IC0gblxuXG4iLCJNb2RlbCA9IHJlcXVpcmUoXCJiYWNrYm9uZS10aGluXCIpLk1vZGVsXG5GZWF0dXJlQ29sID0gcmVxdWlyZSBcIi4vRmVhdHVyZUNvbFwiXG5cbm1vZHVsZS5leHBvcnRzID0gU2VxdWVuY2UgPSBNb2RlbC5leHRlbmRcblxuICBkZWZhdWx0czpcbiAgICBuYW1lOiBcIlwiXG4gICAgaWQ6IFwiXCJcbiAgICBzZXE6IFwiXCJcblxuICBpbml0aWFsaXplOiAtPlxuICAgICMgcmVzaWR1ZXMgd2l0aG91dCBjb2xvclxuICAgIEAuc2V0IFwiZ3JleVwiLCBbXVxuICAgIEAuc2V0IFwiZmVhdHVyZXNcIiwgbmV3IEZlYXR1cmVDb2woKVxuIiwibW9kdWxlLmV4cG9ydHMuc2VxID0gcmVxdWlyZSBcIi4vU2VxdWVuY2VcIlxubW9kdWxlLmV4cG9ydHMuc2VxY29sID0gcmVxdWlyZSBcIi4vU2VxQ29sbGVjdGlvblwiXG5tb2R1bGUuZXhwb3J0cy5mZWF0dXJlID0gcmVxdWlyZSBcIi4vRmVhdHVyZVwiXG5tb2R1bGUuZXhwb3J0cy5mZWF0dXJlY29sID0gcmVxdWlyZSBcIi4vRmVhdHVyZUNvbFwiXG4iLCIjIG1vZGVsc1xuU2VxQ29sbGVjdGlvbiA9IHJlcXVpcmUgXCIuL21vZGVsL1NlcUNvbGxlY3Rpb25cIlxuXG4jIGdsb2JhbHNcbkNvbG9yYXRvciA9IHJlcXVpcmUgXCIuL2cvY29sb3JhdG9yXCJcbkNvbnNlbnN1cyA9IHJlcXVpcmUgXCIuL2cvY29uc2Vuc3VzXCJcbkNvbHVtbnMgPSByZXF1aXJlIFwiLi9nL2NvbHVtbnNcIlxuQ29uZmlnID0gcmVxdWlyZSBcIi4vZy9jb25maWdcIlxuU2VsQ29sID0gcmVxdWlyZSBcIi4vZy9zZWxlY3Rpb24vU2VsZWN0aW9uQ29sXCJcblZpc2liaWxpdHkgPSByZXF1aXJlIFwiLi9nL3Zpc2liaWxpdHlcIlxuVmlzT3JkZXJpbmcgPSByZXF1aXJlIFwiLi9nL3Zpc09yZGVyaW5nXCJcblpvb21lciA9IHJlcXVpcmUgXCIuL2cvem9vbWVyXCJcblxuIyBNViBmcm9tIGJhY2tib25lXG5ib25lVmlldyA9IHJlcXVpcmUoXCJiYWNrYm9uZS1jaGlsZHNcIilcbkV2ZW50aGFuZGxlciA9IHJlcXVpcmUgXCJiaW9qcy1ldmVudHNcIlxuXG4jIE1TQSB2aWV3c1xuU3RhZ2UgPSByZXF1aXJlIFwiLi92aWV3cy9TdGFnZVwiXG5cbiMgb3B0cyBpcyBhIGRpY3Rpb25hcnkgY29uc2lzdGluZyBvZlxuIyBAcGFyYW0gZWwgW1N0cmluZ10gaWQgb3IgcmVmZXJlbmNlIHRvIGEgRE9NIGVsZW1lbnRcbiMgQHBhcmFtIHNlcXMgW1NlcUFycmF5XSBBcnJheSBvZiBzZXF1ZW5jZXMgZm9yIGluaXRsaXphdGlvblxuIyBAcGFyYW0gY29uZiBbRGljdF0gdXNlciBjb25maWdcbiMgQHBhcmFtIHZpcyBbRGljdF0gY29uZmlnIG9mIHZpc2libGUgdmlld3NcbiMgQHBhcmFtIHpvb21lciBbRGljdF0gZGlzcGxheSBzZXR0aW5ncyBsaWtlIGNvbHVtbldpZHRoXG5tb2R1bGUuZXhwb3J0cyA9IGJvbmVWaWV3LmV4dGVuZFxuXG4gIGluaXRpYWxpemU6IChkYXRhKSAtPlxuXG4gICAgIyBjaGVjayBmb3IgZGVmYXVsdCBhcnJheXNcbiAgICBkYXRhLmNvbHVtbnMgPSB7fSB1bmxlc3MgZGF0YS5jb2x1bW5zP1xuICAgIGRhdGEuY29uZiA9IHt9IHVubGVzcyBkYXRhLmNvbmY/XG4gICAgZGF0YS52aXMgPSB7fSB1bmxlc3MgZGF0YS52aXM/XG4gICAgZGF0YS52aXNvcmRlciA9IHt9IHVubGVzcyBkYXRhLnZpc29yZGVyID9cbiAgICBkYXRhLnpvb21lciA9IHt9IHVubGVzcyBkYXRhLnpvb21lcj9cblxuICAgICMgZyBpcyBvdXIgZ2xvYmFsIE1lZGlhdG9yXG4gICAgQGcgPSBFdmVudGhhbmRsZXIubWl4aW4ge31cblxuICAgIGlmIGRhdGEuc2VxcyBpcyB1bmRlZmluZWQgb3IgZGF0YS5zZXFzLmxlbmd0aCBpcyAwXG4gICAgICBjb25zb2xlLmxvZyBcIndhcm5pbmcuIGVtcHR5IHNlcXMuXCJcblxuICAgICMgbG9hZCBzZXFzIGFuZCBhZGQgc3Vidmlld3NcbiAgICBAc2VxcyA9IG5ldyBTZXFDb2xsZWN0aW9uIGRhdGEuc2Vxc1xuXG4gICAgIyBwb3B1bGF0ZSBpdCBhbmQgaW5pdCB0aGUgZ2xvYmFsIG1vZGVsc1xuICAgIEBnLmNvbmZpZyA9IG5ldyBDb25maWcgZGF0YS5jb25mXG4gICAgQGcuY29uc2Vuc3VzID0gbmV3IENvbnNlbnN1cygpXG4gICAgQGcuY29sdW1ucyA9IG5ldyBDb2x1bW5zIGRhdGEuY29sdW1ucyAgIyBmb3IgYWN0aW9uIG9uIHRoZSBjb2x1bW5zIGxpa2UgaGlkaW5nXG4gICAgQGcuY29sb3JzY2hlbWUgPSBuZXcgQ29sb3JhdG9yKClcbiAgICBAZy5zZWxjb2wgPSBuZXcgU2VsQ29sIFtdLHtnOkBnfVxuICAgIEBnLnZpcyA9IG5ldyBWaXNpYmlsaXR5IGRhdGEudmlzXG4gICAgQGcudmlzb3JkZXIgPSBuZXcgVmlzT3JkZXJpbmcgZGF0YS52aXNvcmRlclxuICAgIEBnLnpvb21lciA9IG5ldyBab29tZXIgZGF0YS56b29tZXIse2c6QGd9XG5cbiAgICBAYWRkVmlldyBcInN0YWdlXCIsbmV3IFN0YWdlIHttb2RlbDogQHNlcXMsIGc6IEBnfVxuICAgIEBlbC5zZXRBdHRyaWJ1dGUgXCJjbGFzc1wiLCBcImJpb2pzX21zYV9kaXZcIlxuXG4gICAgaWYgQGcuY29uZmlnLmdldChcImV2ZW50QnVzXCIpIGlzIHRydWVcbiAgICAgIEBzdGFydEV2ZW50QnVzKClcblxuICBzdGFydEV2ZW50QnVzOiAtPlxuICAgIGJ1c09ianMgPSBbXCJjb25maWdcIiwgXCJjb25zZW5zdXNcIiwgXCJjb2x1bW5zXCIsIFwiY29sb3JzY2hlbWVcIiwgXCJzZWxjb2xcIlxuICAgICxcInZpc1wiLCBcInZpc29yZGVyXCIsIFwiem9vbWVyXCJdXG4gICAgZm9yIGtleSBpbiBidXNPYmpzXG4gICAgICBAX3Byb3h5VG9HIGtleVxuXG4gIF9wcm94eVRvRzogKGtleSkgLT5cbiAgICBAbGlzdGVuVG8gQGdba2V5XSwgXCJhbGxcIiwobmFtZSxwcmV2LG5vdykgLT5cbiAgICAgICMgc3VwcHJlc3MgZHVwbGljYXRlIGV2ZW50c1xuICAgICAgcmV0dXJuIGlmIG5hbWUgaXMgXCJjaGFuZ2VcIlxuICAgICAgIyBiYWNrYm9uZSB1c2VzIHRoZSBzZWNvbmQgYXJndW1lbnQgZm9yIHRoZSBuZXh0IHZhbHVlIC0+IHN3YXBcbiAgICAgIEBnLnRyaWdnZXIoa2V5ICsgXCI6XCIgKyBuYW1lLG5vdylcblxuICByZW5kZXI6IC0+XG4gICAgQHJlbmRlclN1YnZpZXdzKClcbiAgICBAZy52aXMuc2V0IFwibG9hZGVkXCIsIHRydWVcbiAgICBAXG4iLCJtb2R1bGUuZXhwb3J0cyA9XG4gICMgbWF0aCB1dGlsaXRpZXNcbiAgY2xhc3MgQk1hdGhcbiAgICBAcmFuZG9tSW50OiAobG93ZXIsIHVwcGVyKSAtPlxuICAgICAgIyBDYWxsZWQgd2l0aCBvbmUgYXJndW1lbnRcbiAgICAgIFtsb3dlciwgdXBwZXJdID0gWzAsIGxvd2VyXSAgICAgdW5sZXNzIHVwcGVyP1xuICAgICAgIyBMb3dlciBtdXN0IGJlIGxlc3MgdGhlbiB1cHBlclxuICAgICAgW2xvd2VyLCB1cHBlcl0gPSBbdXBwZXIsIGxvd2VyXSBpZiBsb3dlciA+IHVwcGVyXG4gICAgICAjIExhc3Qgc3RhdGVtZW50IGlzIGEgcmV0dXJuIHZhbHVlXG4gICAgICBNYXRoLmZsb29yKE1hdGgucmFuZG9tKCkgKiAodXBwZXIgLSBsb3dlciArIDEpICsgbG93ZXIpXG5cbiAgICAjIEByZXR1cm4gW0ludGVnZXJdIHJhbmRvbSBpZFxuICAgIEB1bmlxdWVJZDogKGxlbmd0aCA9IDgpIC0+XG4gICAgICBpZCA9IFwiXCJcbiAgICAgIGlkICs9IE1hdGgucmFuZG9tKCkudG9TdHJpbmcoMzYpLnN1YnN0cigyKSB3aGlsZSBpZC5sZW5ndGggPCBsZW5ndGhcbiAgICAgIGlkLnN1YnN0ciAwLCBsZW5ndGhcblxuICAgICMgUmV0dXJucyBhIHJhbmRvbSBpbnRlZ2VyIGJldHdlZW4gbWluIChpbmNsdXNpdmUpIGFuZCBtYXggKGluY2x1c2l2ZSlcbiAgICBAZ2V0UmFuZG9tSW50OiAobWluLCBtYXgpIC0+XG4gICAgICByZXR1cm4gTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpICogKG1heCAtIG1pbiArIDEpKSArIG1pblxuIiwibW9kdWxlLmV4cG9ydHMuYm1hdGggPSByZXF1aXJlKFwiLi9ibWF0aFwiKVxubW9kdWxlLmV4cG9ydHMucHJveHkgPSByZXF1aXJlKFwiLi9wcm94eVwiKVxubW9kdWxlLmV4cG9ydHMuc2VxZ2VuID0gcmVxdWlyZShcIi4vc2VxZ2VuXCIpXG4iLCJtb2R1bGUuZXhwb3J0cyA9IHByb3h5ID1cblxuICAgIGNvcnNVUkw6ICh1cmwsIEBnKSA9PlxuICAgICAgIyBkbyBub3QgZmlsdGVyIG9uIGxvY2FsaG9zdFxuICAgICAgcmV0dXJuIHVybCBpZiBkb2N1bWVudC5VUkwuaW5kZXhPZignbG9jYWxob3N0JykgPj0gMCBhbmQgdXJsWzBdIGlzIFwiL1wiXG5cbiAgICAgICMgcmVtb3ZlIHd3dyArIGh0dHBcbiAgICAgIHVybCA9IHVybC5yZXBsYWNlIFwid3d3XFwuXCIsIFwiXCJcbiAgICAgIHVybCA9IHVybC5yZXBsYWNlIFwiaHR0cDovL1wiLCBcIlwiXG5cbiAgICAgICMgcHJlcGVuZCBwcm94eVxuICAgICAgdXJsID0gQGcuY29uZmlnLmdldCgnaW1wb3J0UHJveHknKSArIHVybFxuICAgICAgdXJsXG4iLCJTZXF1ZW5jZSA9IHJlcXVpcmUoXCJiaW9qcy1tb2RlbFwiKS5zZXFcbkJNYXRoID0gcmVxdWlyZSBcIi4vYm1hdGhcIlxuXG5zZXFnZW4gPSBtb2R1bGUuZXhwb3J0cyA9XG4gIF9nZW5lcmF0ZVNlcXVlbmNlOiAobGVuKSAtPlxuICAgIHRleHQgPSBcIlwiXG4gICAgcG9zc2libGUgPSBcIkFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpcIlxuXG4gICAgZm9yIGkgaW4gWzAuLmxlbiAtIDFdIGJ5IDFcbiAgICAgIHRleHQgKz0gcG9zc2libGUuY2hhckF0IE1hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIHBvc3NpYmxlLmxlbmd0aClcbiAgICByZXR1cm4gdGV4dFxuXG4gICMgZ2VuZXJhdGVzIGEgZHVtbXkgc2VxdWVuY2VzXG4gICMgQHBhcmFtIGxlbiBbaW50XSBudW1iZXIgb2YgZ2VuZXJhdGVkIHNlcXVlbmNlc1xuICAjIEBwYXJhbSBzZXFMZW4gW2ludF0gbGVuZ3RoIG9mIHRoZSBnZW5lcmF0ZWQgc2VxdWVuY2VzXG4gIGdldER1bW15U2VxdWVuY2VzOiAobGVuLCBzZXFMZW4pIC0+XG4gICAgc2VxcyA9IFtdXG4gICAgbGVuID0gQk1hdGguZ2V0UmFuZG9tSW50IDMsNSB1bmxlc3MgbGVuP1xuICAgIHNlcUxlbiA9IEJNYXRoLmdldFJhbmRvbUludCA1MCwyMDAgdW5sZXNzIHNlcUxlbj9cblxuICAgIGZvciBpIGluIFsxLi5sZW5dIGJ5IDFcbiAgICAgIHNlcXMucHVzaCBuZXcgU2VxdWVuY2Uoc2VxZ2VuLl9nZW5lcmF0ZVNlcXVlbmNlKHNlcUxlbiksIFwic2VxXCIgKyBpLFxuICAgICAgXCJyXCIgKyBpKVxuICAgIHJldHVybiBzZXFzXG4iLCIjIG1pbmkgc3ZnIGhlbHBlclxuXG5zdmducyA9IFwiaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmdcIlxuXG5zZXRBdHRyID0gKG9iaixvcHRzKSAtPlxuICBmb3IgbmFtZSwgdmFsdWUgb2Ygb3B0c1xuICAgIG9iai5zZXRBdHRyaWJ1dGVOUyBudWxsLCBuYW1lLCB2YWx1ZVxuICBvYmpcblxuQmFzZSA9IChvcHRzKSAtPlxuICBzdmcgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50TlMgc3ZnbnMsICdzdmcnXG4gIHN2Zy5zZXRBdHRyaWJ1dGUgXCJ3aWR0aFwiLCBvcHRzLndpZHRoXG4gIHN2Zy5zZXRBdHRyaWJ1dGUgXCJoZWlnaHRcIiwgb3B0cy5oZWlnaHRcbiAgc3ZnXG5cblJlY3QgPSAob3B0cykgLT5cbiAgcmVjdCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnROUyBzdmducywgJ3JlY3QnXG4gIHNldEF0dHIgcmVjdCxvcHRzXG5cbkxpbmUgPSAob3B0cykgLT5cbiAgbGluZSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnROUyBzdmducywgJ2xpbmUnXG4gIHNldEF0dHIgbGluZSxvcHRzXG5cblBvbHlnb24gPSAob3B0cykgLT5cbiAgbGluZSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnROUyBzdmducywgJ3BvbHlnb24nXG4gIHNldEF0dHIgbGluZSxvcHRzXG5cbm1vZHVsZS5leHBvcnRzLnJlY3QgPSBSZWN0XG5tb2R1bGUuZXhwb3J0cy5saW5lID0gTGluZVxubW9kdWxlLmV4cG9ydHMucG9seWdvbiA9IFBvbHlnb25cbm1vZHVsZS5leHBvcnRzLmJhc2UgPSBCYXNlXG4iLCJib25lVmlldyA9IHJlcXVpcmUoXCJiYWNrYm9uZS1jaGlsZHNcIilcblNlcUJsb2NrID0gcmVxdWlyZSBcIi4vQ2FudmFzU2VxQmxvY2tcIlxuTGFiZWxCbG9jayA9IHJlcXVpcmUgXCIuL2xhYmVscy9MYWJlbEJsb2NrXCJcblxubW9kdWxlLmV4cG9ydHMgPSBib25lVmlldy5leHRlbmRcblxuICBpbml0aWFsaXplOiAoZGF0YSkgLT5cbiAgICBAZyA9IGRhdGEuZ1xuXG4gICAgaWYgdHJ1ZVxuICAgICAgbGFiZWxibG9jayA9IG5ldyBMYWJlbEJsb2NrIHttb2RlbDogQG1vZGVsLCBnOiBAZ31cbiAgICAgIGxhYmVsYmxvY2sub3JkZXJpbmcgPSAtMVxuICAgICAgQGFkZFZpZXcgXCJsYWJlbGJsb2NrXCIsbGFiZWxibG9ja1xuXG4gICAgaWYgQGcudmlzLmdldCBcInNlcXVlbmNlc1wiXG4gICAgICBzZXFibG9jayA9IG5ldyBTZXFCbG9jayB7bW9kZWw6IEBtb2RlbCwgZzogQGd9XG4gICAgICBzZXFibG9jay5vcmRlcmluZyA9IDBcbiAgICAgIEBhZGRWaWV3IFwic2VxYmxvY2tcIixzZXFibG9ja1xuXG4gICAgQGxpc3RlblRvIEBnLnpvb21lciwgXCJjaGFuZ2U6YWxpZ25tZW50SGVpZ2h0XCIsIEBhZGp1c3RIZWlnaHRcbiAgICBAbGlzdGVuVG8gQGcuY29sdW1ucywgXCJjaGFuZ2U6aGlkZGVuXCIsIEBhZGp1c3RIZWlnaHRcblxuICByZW5kZXI6IC0+XG4gICAgQHJlbmRlclN1YnZpZXdzKClcbiAgICBAZWwuY2xhc3NOYW1lID0gXCJiaW9qc19tc2FfYWxib2R5XCJcbiAgICBAZWwuc3R5bGUud2hpdGVTcGFjZSA9IFwibm93cmFwXCJcbiAgICBAYWRqdXN0SGVpZ2h0KClcbiAgICBAXG5cbiAgYWRqdXN0SGVpZ2h0OiAtPlxuICAgIGlmIEBnLnpvb21lci5nZXQoXCJhbGlnbm1lbnRIZWlnaHRcIikgaXMgXCJhdXRvXCJcbiAgICAgICMgVE9ETzogZml4IHRoZSBtYWdpYyA1XG4gICAgICBAZWwuc3R5bGUuaGVpZ2h0ID0gKEBnLnpvb21lci5nZXQoXCJyb3dIZWlnaHRcIikgKiBAbW9kZWwubGVuZ3RoKSArIDVcbiAgICBlbHNlXG4gICAgICBAZWwuc3R5bGUuaGVpZ2h0ID0gQGcuem9vbWVyLmdldCBcImFsaWdubWVudEhlaWdodFwiXG5cbiAgICAjIFRPRE86IDE1IGlzIHRoZSB3aWR0aCBvZiB0aGUgc2Nyb2xsYmFyXG4gICAgQGVsLnN0eWxlLndpZHRoID0gQGdldFdpZHRoKCkgKyAxNVxuXG4gIGdldFdpZHRoOiAtPlxuICAgIHdpZHRoID0gMFxuICAgIGlmIEBnLnZpcy5nZXQgXCJsYWJlbHNcIlxuICAgICAgd2lkdGggKz0gQGcuem9vbWVyLmdldCBcImxhYmVsV2lkdGhcIlxuICAgIGlmIEBnLnZpcy5nZXQgXCJtZXRhY2VsbFwiXG4gICAgICB3aWR0aCArPSBAZy56b29tZXIuZ2V0IFwibWV0YVdpZHRoXCJcbiAgICBpZiBAZy52aXMuZ2V0IFwic2VxdWVuY2VzXCJcbiAgICAgIHdpZHRoICs9IEBnLnpvb21lci5nZXQgXCJhbGlnbm1lbnRXaWR0aFwiXG4gICAgd2lkdGhcbiIsIkV2ZW50cyA9IHJlcXVpcmUoXCJiaW9qcy1ldmVudHNcIilcblxubW9kdWxlLmV4cG9ydHMgPSBjbGFzcyBDYW52YXNDaGFyQ2FjaGVcblxuICBjb25zdHJ1Y3RvcjogKEBnKSAtPlxuICAgIEBjYWNoZSA9IHt9XG4gICAgQGNhY2hlSGVpZ2h0ID0gMFxuICAgIEBjYWNoZVdpZHRoID0gMFxuXG4gICMgcmV0dXJucyBhIGNhY2hlZCBjYW52YXNcbiAgZ2V0Rm9udFRpbGU6IChsZXR0ZXIsIHdpZHRoLCBoZWlnaHQpIC0+XG4gICAgIyB2YWxpZGF0ZSBjYWNoZVxuICAgIGlmIHdpZHRoIGlzbnQgQGNhY2hlV2lkdGggb3IgaGVpZ2h0IGlzbnQgQGNhY2hlSGVpZ2h0XG4gICAgICBAY2FjaGVIZWlnaHQgPSBoZWlnaHRcbiAgICAgIEBjYWNoZVdpZHRoID0gd2lkdGhcbiAgICAgIEBjYWNoZSA9IHt9XG5cbiAgICBpZiBAY2FjaGVbbGV0dGVyXSBpcyB1bmRlZmluZWRcbiAgICAgIEBjcmVhdGVUaWxlIGxldHRlciwgd2lkdGgsIGhlaWdodFxuXG4gICAgcmV0dXJuIEBjYWNoZVtsZXR0ZXJdXG5cbiAgIyBjcmVhdGVzIGEgY2FudmFzIHdpdGggYSBzaW5nbGUgbGV0dGVyXG4gICMgKGZvciB0aGUgZmFzdCBmb250IGNhY2hlKVxuICBjcmVhdGVUaWxlOiAobGV0dGVyLCB3aWR0aCwgaGVpZ2h0KSAtPlxuXG4gICAgY2FudmFzID0gQGNhY2hlW2xldHRlcl0gPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50IFwiY2FudmFzXCJcbiAgICBjYW52YXMud2lkdGggPSB3aWR0aFxuICAgIGNhbnZhcy5oZWlnaHQgPSBoZWlnaHRcbiAgICBAY3R4ID0gY2FudmFzLmdldENvbnRleHQgJzJkJ1xuICAgIEBjdHguZm9udCA9IEBnLnpvb21lci5nZXQgXCJyZXNpZHVlRm9udFwiXG4gICAgQGN0eC50ZXh0QmFzZWxpbmUgPSAnbWlkZGxlJ1xuICAgIEBjdHgudGV4dEFsaWduID0gXCJjZW50ZXJcIlxuXG4gICAgQGN0eC5maWxsVGV4dCBsZXR0ZXIsd2lkdGggLyAyLGhlaWdodCAvIDIsd2lkdGhcbiIsImJvbmVWaWV3ID0gcmVxdWlyZShcImJhY2tib25lLWNoaWxkc1wiKVxubW91c2UgPSByZXF1aXJlIFwibW91c2UtcG9zXCJcbmNvbG9yU2VsZWN0b3IgPSByZXF1aXJlKFwiYmlvanMtdXRpbC1jb2xvcnNjaGVtZXNcIikuc2VsZWN0b3Jcbl8gPSByZXF1aXJlIFwidW5kZXJzY29yZVwiXG5qYm9uZSA9IHJlcXVpcmUgXCJqYm9uZVwiXG5DaGFyQ2FjaGUgPSByZXF1aXJlIFwiLi9DYW52YXNDaGFyQ2FjaGVcIlxuXG5tb2R1bGUuZXhwb3J0cyA9IGJvbmVWaWV3LmV4dGVuZFxuXG4gIHRhZ05hbWU6IFwiY2FudmFzXCJcblxuICBpbml0aWFsaXplOiAoZGF0YSkgLT5cbiAgICBAZyA9IGRhdGEuZ1xuXG4gICAgQGxpc3RlblRvIEBnLnpvb21lciwgXCJjaGFuZ2U6X2FsaWdubWVudFNjcm9sbExlZnQgY2hhbmdlOl9hbGlnbm1lbnRTY3JvbGxUb3BcIiwgKG1vZGVsLHZhbHVlLCBvcHRpb25zKSAtPlxuICAgICAgaWYgKG5vdCBvcHRpb25zPy5vcmlnaW4/KSBvciBvcHRpb25zLm9yaWdpbiBpc250IFwiY2FudmFzc2VxXCJcbiAgICAgICAgQHJlbmRlcigpXG5cbiAgICBAbGlzdGVuVG8gQGcuY29sdW1ucyxcImNoYW5nZTpoaWRkZW5cIiwgQHJlbmRlclxuICAgIEBsaXN0ZW5UbyBAZy56b29tZXIsXCJjaGFuZ2U6YWxpZ25tZW50V2lkdGhcIiwgQHJlbmRlclxuICAgIEBsaXN0ZW5UbyBAZy5jb2xvcnNjaGVtZSwgXCJjaGFuZ2VcIiwgQHJlbmRlclxuICAgIEBsaXN0ZW5UbyBAZy5zZWxjb2wsIFwicmVzZXQgYWRkXCIsIEByZW5kZXJcblxuICAgICMgZWwgcHJvcHNcbiAgICBAZWwuc3R5bGUuZGlzcGxheSA9IFwiaW5saW5lLWJsb2NrXCJcbiAgICBAZWwuc3R5bGUub3ZlcmZsb3dYID0gXCJoaWRkZW5cIlxuICAgIEBlbC5zdHlsZS5vdmVyZmxvd1kgPSBcImhpZGRlblwiXG4gICAgQGVsLmNsYXNzTmFtZSA9IFwiYmlvanNfbXNhX3NlcWJsb2NrXCJcblxuICAgIEBjdHggPSBAZWwuZ2V0Q29udGV4dCAnMmQnXG4gICAgQGNhY2hlID0gbmV3IENoYXJDYWNoZSBAZ1xuXG4gICAgIyB0aHJvdHRsZSB0aGUgZXhwZW5zaXZlIGRyYXcgZnVuY3Rpb25cbiAgICBAdGhyb3R0bGVUaW1lID0gMFxuICAgIEB0aHJvdHRsZUNvdW50cyA9IDBcbiAgICBpZiBkb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc3R5bGUud2Via2l0QXBwZWFyYW5jZT9cbiAgICAgICMgd2Via2l0IGJyb3dzZXIgLSBubyB0aHJvdHRsaW5nIG5lZWRlZFxuICAgICAgQHRocm90dGxlZERyYXcgPSAtPlxuICAgICAgICBzdGFydCA9ICtuZXcgRGF0ZSgpXG4gICAgICAgIEBkcmF3KClcbiAgICAgICAgQHRocm90dGxlVGltZSArPSArbmV3IERhdGUoKSAtIHN0YXJ0XG4gICAgICAgIEB0aHJvdHRsZUNvdW50cysrXG4gICAgICAgIGlmIEB0aHJvdHRsZUNvdW50cyA+IDE1XG4gICAgICAgICAgdFRpbWUgPSBNYXRoLmNlaWwoQHRocm90dGxlVGltZSAvIEB0aHJvdHRsZUNvdW50cylcbiAgICAgICAgICBjb25zb2xlLmxvZyBcImF2Z0RyYXdUaW1lL1dlYktpdFwiLCB0VGltZVxuICAgICAgICAgICMgcmVtb3ZlIHBlcmYgYW5hbHlzZXJcbiAgICAgICAgICBAdGhyb3R0bGVkRHJhdyA9IEBkcmF3XG4gICAgZWxzZVxuICAgICAgIyBzbG93IGJyb3dzZXJzIGxpa2UgR2Vja29cbiAgICAgIEB0aHJvdHRsZWREcmF3ID0gXy50aHJvdHRsZSBAdGhyb3R0bGVkRHJhdywgMzBcblxuICAgIEBtYW5hZ2VFdmVudHMoKVxuXG4gICMgbWVhc3VyZXMgdGhlIHRpbWUgb2YgYSByZWRyYXcgYW5kIHRodXMgc2V0IHRoZSB0aHJvdHRsZSBsaW1pdFxuICB0aHJvdHRsZWREcmF3OiAtPlxuICAgICMgK25ldyBpcyB0aGUgZmFzdGVzdDogaHR0cDovL2pzcGVyZi5jb20vbmV3LWRhdGUtdnMtZGF0ZS1ub3ctdnMtcGVyZm9ybWFuY2Utbm93LzZcbiAgICBzdGFydCA9ICtuZXcgRGF0ZSgpXG4gICAgQGRyYXcoKVxuICAgIEB0aHJvdHRsZVRpbWUgKz0gK25ldyBEYXRlKCkgLSBzdGFydFxuICAgIEB0aHJvdHRsZUNvdW50cysrXG5cbiAgICAjIHJlbW92ZSBpdHNlbGYgYWZ0ZXIgYW5hbHlzaXNcbiAgICBpZiBAdGhyb3R0bGVDb3VudHMgPiAxNVxuICAgICAgdFRpbWUgPSBNYXRoLmNlaWwoQHRocm90dGxlVGltZSAvIEB0aHJvdHRsZUNvdW50cylcbiAgICAgIGNvbnNvbGUubG9nIFwiYXZnRHJhd1RpbWVcIiwgdFRpbWVcbiAgICAgIHRUaW1lICo9ICAxLjIgIyBhZGQgc2FmZXR5IHRpbWVcbiAgICAgIHRUaW1lID0gTWF0aC5tYXggMjAsIHRUaW1lICMgbGltaXQgZm9yIHVsdHJhIGZhc3QgY29tcHV0ZXJzXG4gICAgICBAdGhyb3R0bGVkRHJhdyA9IF8udGhyb3R0bGUgQGRyYXcsIHRUaW1lXG5cbiAgbWFuYWdlRXZlbnRzOiAtPlxuICAgIGV2ZW50cyA9IHt9XG4gICAgZXZlbnRzLm1vdXNlZG93biA9IFwiX29ubW91c2Vkb3duXCJcbiAgICBldmVudHMudG91Y2hzdGFydCA9IFwiX29udG91Y2hzdGFydFwiXG5cbiAgICBpZiBAZy5jb25maWcuZ2V0IFwicmVnaXN0ZXJNb3VzZUNsaWNrc1wiXG4gICAgICBldmVudHMuZGJsY2xpY2sgPSBcIl9vbmNsaWNrXCJcbiAgICBpZiBAZy5jb25maWcuZ2V0IFwicmVnaXN0ZXJNb3VzZUhvdmVyXCJcbiAgICAgIGV2ZW50cy5tb3VzZWluID0gXCJfb25tb3VzZWluXCJcbiAgICAgIGV2ZW50cy5tb3VzZW91dCA9IFwiX29ubW91c2VvdXRcIlxuXG4gICAgZXZlbnRzLm1vdXNld2hlZWwgPSBcIl9vbm1vdXNld2hlZWxcIlxuICAgIGV2ZW50cy5ET01Nb3VzZVNjcm9sbCA9IFwiX29ubW91c2V3aGVlbFwiXG4gICAgQGRlbGVnYXRlRXZlbnRzIGV2ZW50c1xuXG4gICAgIyBsaXN0ZW4gZm9yIGNoYW5nZXNcbiAgICBAbGlzdGVuVG8gQGcuY29uZmlnLCBcImNoYW5nZTpyZWdpc3Rlck1vdXNlSG92ZXJcIiwgQG1hbmFnZUV2ZW50c1xuICAgIEBsaXN0ZW5UbyBAZy5jb25maWcsIFwiY2hhbmdlOnJlZ2lzdGVyTW91c2VDbGlja1wiLCBAbWFuYWdlRXZlbnRzXG4gICAgQGRyYWdTdGFydCA9IFtdXG5cbiAgZHJhdzogLT5cblxuICAgICMgZmFzdGVzdCB3YXkgdG8gY2xlYXIgdGhlIGNhbnZhc1xuICAgICMgaHR0cDovL2pzcGVyZi5jb20vY2FudmFzLWNsZWFyLXNwZWVkLzI1XG4gICAgQGVsLndpZHRoID0gQGVsLndpZHRoXG5cbiAgICByZWN0SGVpZ2h0ID0gQGcuem9vbWVyLmdldCBcInJvd0hlaWdodFwiXG5cbiAgICAjIHJlY3RzXG4gICAgQGN0eC5nbG9iYWxBbHBoYSA9IEBnLmNvbG9yc2NoZW1lLmdldCBcIm9wYWNpdHlcIlxuICAgIEBkcmF3U2VxcyAoZGF0YSkgLT4gQGRyYXdTZXEoZGF0YSwgQF9kcmF3UmVjdClcbiAgICBAY3R4Lmdsb2JhbEFscGhhID0gMVxuXG4gICAgIyBsZXR0ZXJzXG4gICAgQGRyYXdTZXFzIChkYXRhKSAtPiBAZHJhd1NlcShkYXRhLCBAX2RyYXdMZXR0ZXIpXG5cbiAgICAjIGZlYXR1cmVzLCBzZWxlY3Rpb25cbiAgICBAZHJhd1NlcXMgQGRyYXdTZXFFeHRlbmRlZFxuXG4gIGRyYXdTZXFzOiAoY2FsbGJhY2spIC0+XG4gICAgcmVjdEhlaWdodCA9IEBnLnpvb21lci5nZXQgXCJyb3dIZWlnaHRcIlxuICAgIGhpZGRlbiA9IEBnLmNvbHVtbnMuZ2V0IFwiaGlkZGVuXCJcblxuICAgIHN0YXJ0ID0gTWF0aC5tYXggMCwgTWF0aC5hYnMoTWF0aC5jZWlsKCAtIEBnLnpvb21lci5nZXQoJ19hbGlnbm1lbnRTY3JvbGxUb3AnKSAvIHJlY3RIZWlnaHQpKVxuICAgIHkgPSAtIE1hdGguYWJzKCAtIEBnLnpvb21lci5nZXQoJ19hbGlnbm1lbnRTY3JvbGxUb3AnKSAlIHJlY3RIZWlnaHQpXG4gICAgZm9yIGkgaW4gW3N0YXJ0Li4gQG1vZGVsLmxlbmd0aCAtIDFdIGJ5IDFcbiAgICAgIGNvbnRpbnVlIGlmIEBtb2RlbC5hdChpKS5nZXQoJ2hpZGRlbicpXG4gICAgICBjYWxsYmFjay5jYWxsIEAsIHttb2RlbDogQG1vZGVsLmF0KGkpLCB5OiB5LCBoaWRkZW46IGhpZGRlbn1cbiAgICAgIHkgPSB5ICsgcmVjdEhlaWdodFxuICAgICAgIyBvdXQgb2Ygdmlld3BvcnQgLSBzdG9wXG4gICAgICBpZiB5ID4gQGVsLmhlaWdodFxuICAgICAgICBicmVha1xuXG4gICMgVE9ETzogdmVyeSBleHBlbnNpdmUgbWV0aG9kXG4gIGRyYXdTZXE6IChkYXRhLCBjYWxsYmFjaykgLT5cbiAgICBzZXEgPSBkYXRhLm1vZGVsLmdldCBcInNlcVwiXG4gICAgeSA9IGRhdGEueVxuICAgIHJlY3RXaWR0aCA9IEBnLnpvb21lci5nZXQgXCJjb2x1bW5XaWR0aFwiXG4gICAgcmVjdEhlaWdodCA9IEBnLnpvb21lci5nZXQgXCJyb3dIZWlnaHRcIlxuXG4gICAgIyBza2lwIHVubmVlZGVkIGJsb2NrcyBhdCB0aGUgYmVnaW5uaW5nXG4gICAgc3RhcnQgPSBNYXRoLm1heCAwLCBNYXRoLmFicyhNYXRoLmNlaWwoIC0gQGcuem9vbWVyLmdldCgnX2FsaWdubWVudFNjcm9sbExlZnQnKSAvIHJlY3RXaWR0aCkpXG4gICAgeCA9IC0gTWF0aC5hYnMoIC0gQGcuem9vbWVyLmdldCgnX2FsaWdubWVudFNjcm9sbExlZnQnKSAlIHJlY3RXaWR0aClcblxuICAgIHJlcyA9IHtyZWN0V2lkdGg6IHJlY3RXaWR0aCwgcmVjdEhlaWdodDogcmVjdEhlaWdodCwgeTogeX1cbiAgICBlbFdpZHRoID0gQGVsLndpZHRoXG5cbiAgICBmb3IgaiBpbiBbc3RhcnQuLiBzZXEubGVuZ3RoIC0gMV0gYnkgMVxuICAgICAgYyA9IHNlcVtqXVxuICAgICAgYyA9IGMudG9VcHBlckNhc2UoKVxuXG4gICAgICAjIGNhbGwgdGhlIGN1c3RvbSBmdW5jdGlvblxuICAgICAgcmVzLnggPSB4XG4gICAgICByZXMuYyA9IGNcblxuICAgICAgIyBsb2NhbCBjYWxsIGlzIGZhc3RlciB0aGFuIGFwcGx5XG4gICAgICAjIGh0dHA6Ly9qc3BlcmYuY29tL2Z1bmN0aW9uLWNhbGxzLWRpcmVjdC12cy1hcHBseS12cy1jYWxsLXZzLWJpbmQvNlxuICAgICAgaWYgZGF0YS5oaWRkZW4uaW5kZXhPZihqKSA8IDBcbiAgICAgICAgY2FsbGJhY2sgQCxyZXNcbiAgICAgIGVsc2VcbiAgICAgICAgY29udGludWVcblxuICAgICAgIyBtb3ZlIHRvIHRoZSByaWdodFxuICAgICAgeCA9IHggKyByZWN0V2lkdGhcblxuICAgICAgIyBvdXQgb2Ygdmlld3BvcnQgLSBzdG9wXG4gICAgICBpZiB4ID4gZWxXaWR0aFxuICAgICAgICBicmVha1xuXG4gIF9kcmF3UmVjdDogKHRoYXQsIGRhdGEpIC0+XG4gICAgY29sb3IgPSB0aGF0LmNvbG9yW2RhdGEuY11cbiAgICBpZiBjb2xvcj9cbiAgICAgIHRoYXQuY3R4LmZpbGxTdHlsZSA9IGNvbG9yXG4gICAgICB0aGF0LmN0eC5maWxsUmVjdCBkYXRhLngsZGF0YS55LGRhdGEucmVjdFdpZHRoLGRhdGEucmVjdEhlaWdodFxuXG4gICMgUkVBTExZIGV4cGVuc2l2ZSBjYWxsIG9uIEZGXG4gICMgUGVyZm9ybWFuY2U6XG4gICMgY2hyb21lOiAyMDAwbXMgZHJhd0xldHRlciAtIDEwMDBtcyBkcmF3UmVjdFxuICAjIEZGOiAxNzAwbXMgZHJhd0xldHRlciAtIDMwMG1zIGRyYXdSZWN0XG4gIF9kcmF3TGV0dGVyOiAodGhhdCxkYXRhKSAtPlxuICAgIHRoYXQuY3R4LmRyYXdJbWFnZSB0aGF0LmNhY2hlLmdldEZvbnRUaWxlKGRhdGEuYywgZGF0YS5yZWN0V2lkdGgsXG4gICAgICBkYXRhLnJlY3RIZWlnaHQpLCBkYXRhLngsIGRhdGEueSxkYXRhLnJlY3RXaWR0aCxkYXRhLnJlY3RIZWlnaHRcblxuICBkcmF3U2VxRXh0ZW5kZWQ6IChkYXRhKSAtPlxuICAgIHNlcSA9IGRhdGEubW9kZWwuZ2V0IFwic2VxXCJcbiAgICByZWN0V2lkdGggPSBAZy56b29tZXIuZ2V0IFwiY29sdW1uV2lkdGhcIlxuICAgIHJlY3RIZWlnaHQgPSBAZy56b29tZXIuZ2V0IFwicm93SGVpZ2h0XCJcblxuICAgIHN0YXJ0ID0gTWF0aC5tYXggMCwgTWF0aC5hYnMoTWF0aC5jZWlsKCAtIEBnLnpvb21lci5nZXQoJ19hbGlnbm1lbnRTY3JvbGxMZWZ0JykgLyByZWN0V2lkdGgpKVxuICAgIHggPSAtIE1hdGguYWJzKCAtIEBnLnpvb21lci5nZXQoJ19hbGlnbm1lbnRTY3JvbGxMZWZ0JykgJSByZWN0V2lkdGgpXG4gICAgeFplcm8gPSB4IC0gc3RhcnQgKiByZWN0V2lkdGhcblxuICAgIHNlbGVjdGlvbiA9IEBfZ2V0U2VsZWN0aW9uIGRhdGEubW9kZWxcbiAgICBbbVByZXZTZWwsbU5leHRTZWxdID0gQF9nZXRQcmV2TmV4dFNlbGVjdGlvbiBkYXRhLm1vZGVsXG4gICAgZmVhdHVyZXMgPSBkYXRhLm1vZGVsLmdldCBcImZlYXR1cmVzXCJcblxuICAgIHlaZXJvID0gZGF0YS55XG5cbiAgICBmb3IgaiBpbiBbc3RhcnQuLiBzZXEubGVuZ3RoIC0gMV0gYnkgMVxuICAgICAgc3RhcnRzID0gZmVhdHVyZXMuc3RhcnRPbiBqXG5cbiAgICAgIGlmIGRhdGEuaGlkZGVuLmluZGV4T2YoaikgPj0gMFxuICAgICAgICBjb250aW51ZVxuXG4gICAgICBpZiBzdGFydHMubGVuZ3RoID4gMFxuICAgICAgICBmb3IgZiBpbiBzdGFydHNcbiAgICAgICAgICBAYXBwZW5kRmVhdHVyZSBmOiBmLHhaZXJvOiB4LCB5WmVybzogeVplcm9cblxuICAgICAgeCA9IHggKyByZWN0V2lkdGhcbiAgICAgICMgb3V0IG9mIHZpZXdwb3J0IC0gc3RvcFxuICAgICAgaWYgeCA+IEBlbC53aWR0aFxuICAgICAgICBicmVha1xuXG4gICAgQF9hcHBlbmRTZWxlY3Rpb24gbW9kZWw6IGRhdGEubW9kZWwsIHhaZXJvOiB4WmVybywgeVplcm86IHlaZXJvLCBoaWRkZW46XG4gICAgICBkYXRhLmhpZGRlblxuXG4gIHJlbmRlcjogLT5cblxuICAgIEBlbC5zZXRBdHRyaWJ1dGUgJ2hlaWdodCcsIEBnLnpvb21lci5nZXQgXCJhbGlnbm1lbnRIZWlnaHRcIlxuICAgIEBlbC5zZXRBdHRyaWJ1dGUgJ3dpZHRoJywgQGcuem9vbWVyLmdldCBcImFsaWdubWVudFdpZHRoXCJcblxuICAgIEBnLnpvb21lci5fYWRqdXN0V2lkdGggQGVsLCBAbW9kZWxcbiAgICBAZy56b29tZXIuX2NoZWNrU2Nyb2xsaW5nKCBAX2NoZWNrU2Nyb2xsaW5nKFtAZy56b29tZXIuZ2V0KCdfYWxpZ25tZW50U2Nyb2xsTGVmdCcpLFxuICAgIEBnLnpvb21lci5nZXQoJ19hbGlnbm1lbnRTY3JvbGxUb3AnKV0gKSx7aGVhZGVyOiBcImNhbnZhc3NlcVwifSlcblxuICAgIEBjb2xvciA9IGNvbG9yU2VsZWN0b3IuZ2V0Q29sb3IgQGcuY29sb3JzY2hlbWUuZ2V0KFwic2NoZW1lXCIpXG5cbiAgICBAdGhyb3R0bGVkRHJhdygpXG4gICAgQFxuXG4gIF9vbm1vdXNlbW92ZTogKGUsIHJldmVyc2VkKSAtPlxuICAgIHJldHVybiBpZiBAZHJhZ1N0YXJ0Lmxlbmd0aCBpcyAwXG5cbiAgICBkcmFnRW5kID0gbW91c2UuYWJzIGVcbiAgICAjIHJlbGF0aXZlIHRvIGZpcnN0IGNsaWNrXG4gICAgcmVsRW5kID0gW2RyYWdFbmRbMF0gLSBAZHJhZ1N0YXJ0WzBdLCBkcmFnRW5kWzFdIC0gQGRyYWdTdGFydFsxXV1cbiAgICAjIHJlbGF0aXZlIHRvIGluaXRpYWwgc2Nyb2xsIHN0YXR1c1xuXG4gICAgIyBzY2FsZSBldmVudHNcbiAgICBzY2FsZUZhY3RvciA9IEBnLnpvb21lci5nZXQgXCJjYW52YXNFdmVudFNjYWxlXCJcbiAgICBpZiByZXZlcnNlZFxuICAgICAgc2NhbGVGYWN0b3IgPSAzXG4gICAgZm9yIGkgaW4gWzAuLjFdIGJ5IDFcbiAgICAgIHJlbEVuZFtpXSA9IHJlbEVuZFtpXSAqIHNjYWxlRmFjdG9yXG5cbiAgICAjIGNhbGN1bGF0ZSBuZXcgc2Nyb2xsaW5nIHZhbHNcbiAgICByZWxEaXN0ID0gW0BkcmFnU3RhcnRTY3JvbGxbMF0gLSByZWxFbmRbMF0sIEBkcmFnU3RhcnRTY3JvbGxbMV0gLSByZWxFbmRbMV1dXG5cbiAgICAjIHJvdW5kIHZhbHVlc1xuICAgIGZvciBpIGluIFswLi4xXSBieSAxXG4gICAgICByZWxEaXN0W2ldID0gTWF0aC5yb3VuZCByZWxEaXN0W2ldXG5cbiAgICAjIHVwZGF0ZSBzY3JvbGxiYXJcbiAgICBzY3JvbGxDb3JyZWN0ZWQgPSBAX2NoZWNrU2Nyb2xsaW5nKCByZWxEaXN0KVxuICAgIEBnLnpvb21lci5fY2hlY2tTY3JvbGxpbmcgc2Nyb2xsQ29ycmVjdGVkLCB7b3JpZ2luOiBcImNhbnZhc3NlcVwifVxuXG4gICAgIyByZXNldCBzdGFydCBpZiB1c2Ugc2Nyb2xscyBvdXQgb2YgYm91bmRzXG4gICAgZm9yIGkgaW4gWzAuLjFdIGJ5IDFcbiAgICAgIGlmIHNjcm9sbENvcnJlY3RlZFtpXSBpc250IHJlbERpc3RbaV1cbiAgICAgICAgaWYgc2Nyb2xsQ29ycmVjdGVkW2ldIGlzIDBcbiAgICAgICAgICAjIHJlc2V0IG9mIGxlZnQsIHRvcFxuICAgICAgICAgIEBkcmFnU3RhcnRbaV0gPSBkcmFnRW5kW2ldXG4gICAgICAgICAgQGRyYWdTdGFydFNjcm9sbFtpXSA9IDBcbiAgICAgICAgZWxzZVxuICAgICAgICAgICMgcmVjYWxpYnJhdGUgb24gcmlnaHQsIGJvdHRvbVxuICAgICAgICAgIEBkcmFnU3RhcnRbaV0gPSBkcmFnRW5kW2ldIC0gc2Nyb2xsQ29ycmVjdGVkW2ldXG5cbiAgICBAdGhyb3R0bGVkRHJhdygpXG5cbiAgICAjIGFib3J0IHNlbGVjdGlvbiBldmVudHMgb2YgdGhlIGJyb3dzZXIgKG1vdXNlIG9ubHkpXG4gICAgaWYgZS5wcmV2ZW50RGVmYXVsdD9cbiAgICAgIGUucHJldmVudERlZmF1bHQoKVxuICAgICAgZS5zdG9wUHJvcGFnYXRpb24oKVxuXG4gICMgY29udmVydHMgdG91Y2hlcyBpbnRvIG9sZCBtb3VzZSBldmVudFxuICBfb250b3VjaG1vdmU6IChlKSAtPlxuICAgIEBfb25tb3VzZW1vdmUgZS5jaGFuZ2VkVG91Y2hlc1swXSwgdHJ1ZVxuICAgIGUucHJldmVudERlZmF1bHQoKVxuICAgIGUuc3RvcFByb3BhZ2F0aW9uKClcblxuICAjIHN0YXJ0IHRoZSBkcmFnZ2luZyBtb2RlXG4gIF9vbm1vdXNlZG93bjogKGUpIC0+XG4gICAgQGRyYWdTdGFydCA9IG1vdXNlLmFicyBlXG4gICAgQGRyYWdTdGFydFNjcm9sbCA9IFtAZy56b29tZXIuZ2V0KCdfYWxpZ25tZW50U2Nyb2xsTGVmdCcpLCBAZy56b29tZXIuZ2V0KCdfYWxpZ25tZW50U2Nyb2xsVG9wJyldXG4gICAgamJvbmUoZG9jdW1lbnQuYm9keSkub24gJ21vdXNlbW92ZS5vdmVybW92ZScsIChlKSA9PiBAX29ubW91c2Vtb3ZlKGUpXG4gICAgamJvbmUoZG9jdW1lbnQuYm9keSkub24gJ21vdXNldXAub3ZlcnVwJywgPT4gQF9jbGVhbnVwKClcbiAgICAjamJvbmUoZG9jdW1lbnQuYm9keSkub24gJ21vdXNlb3V0Lm92ZXJvdXQnLCAoZSkgPT4gQF9vbm1vdXNld2lub3V0KGUpXG4gICAgZS5wcmV2ZW50RGVmYXVsdCgpXG5cbiAgIyBzdGFydHMgdGhlIHRvdWNoIG1vZGVcbiAgX29udG91Y2hzdGFydDogKGUpIC0+XG4gICAgQGRyYWdTdGFydCA9IG1vdXNlLmFicyBlLmNoYW5nZWRUb3VjaGVzWzBdXG4gICAgQGRyYWdTdGFydFNjcm9sbCA9IFtAZy56b29tZXIuZ2V0KCdfYWxpZ25tZW50U2Nyb2xsTGVmdCcpLCBAZy56b29tZXIuZ2V0KCdfYWxpZ25tZW50U2Nyb2xsVG9wJyldXG4gICAgamJvbmUoZG9jdW1lbnQuYm9keSkub24gJ3RvdWNobW92ZS5vdmVydG1vdmUnLCAoZSkgPT4gQF9vbnRvdWNobW92ZShlKVxuICAgIGpib25lKGRvY3VtZW50LmJvZHkpLm9uICd0b3VjaGVuZC5vdmVydGVuZCB0b3VjaGxlYXZlLm92ZXJ0bGVhdmVcbiAgICB0b3VjaGNhbmNlbC5vdmVydGNhbmVsJywgKGUpID0+IEBfdG91Y2hDbGVhbnVwKGUpXG5cbiAgIyBjaGVja3Mgd2hldGhlciBtb3VzZSBtb3ZlZCBvdXQgb2YgdGhlIHdpbmRvd1xuICAjIC0+IHRlcm1pbmF0ZSBkcmFnZ2luZ1xuICBfb25tb3VzZXdpbm91dDogKGUpIC0+XG4gICAgaWYgZS50b0VsZW1lbnQgaXMgZG9jdW1lbnQuYm9keS5wYXJlbnROb2RlXG4gICAgICBAX2NsZWFudXAoKVxuXG4gICMgdGVybWluYXRlcyBkcmFnZ2luZ1xuICBfY2xlYW51cDogLT5cbiAgICBAZHJhZ1N0YXJ0ID0gW11cbiAgICAjIHJlbW92ZSBhbGwgbGlzdGVuZXJzXG4gICAgamJvbmUoZG9jdW1lbnQuYm9keSkub2ZmKCcub3Zlcm1vdmUnKVxuICAgIGpib25lKGRvY3VtZW50LmJvZHkpLm9mZignLm92ZXJ1cCcpXG4gICAgamJvbmUoZG9jdW1lbnQuYm9keSkub2ZmKCcub3Zlcm91dCcpXG5cbiAgIyB0ZXJtaW5hdGVzIHRvdWNoaW5nXG4gIF90b3VjaENsZWFudXA6IChlKSAtPlxuICAgIGlmIGUuY2hhbmdlZFRvdWNoZXMubGVuZ3RoID4gMFxuICAgICAgIyBtYXliZSB3ZSBjYW4gc2VuZCBhIGZpbmFsIGV2ZW50XG4gICAgICBAX29ubW91c2Vtb3ZlIGUuY2hhbmdlZFRvdWNoZXNbMF0sIHRydWVcblxuICAgIEBkcmFnU3RhcnQgPSBbXVxuICAgICMgcmVtb3ZlIGFsbCBsaXN0ZW5lcnNcbiAgICBqYm9uZShkb2N1bWVudC5ib2R5KS5vZmYoJy5vdmVydG1vdmUnKVxuICAgIGpib25lKGRvY3VtZW50LmJvZHkpLm9mZignLm92ZXJ0ZW5kJylcbiAgICBqYm9uZShkb2N1bWVudC5ib2R5KS5vZmYoJy5vdmVydGxlYXZlJylcbiAgICBqYm9uZShkb2N1bWVudC5ib2R5KS5vZmYoJy5vdmVydGNhbmNlbCcpXG5cbiAgIyBtaWdodCBiZSBpbmNvbXBhdGlibGUgd2l0aCBzb21lIGJyb3dzZXJzXG4gIF9vbm1vdXNld2hlZWw6IChlKSAtPlxuICAgIGRlbHRhID0gbW91c2Uud2hlZWxEZWx0YSBlXG4gICAgQGcuem9vbWVyLnNldCAnX2FsaWdubWVudFNjcm9sbExlZnQnLCBAZy56b29tZXIuZ2V0KCdfYWxpZ25tZW50U2Nyb2xsTGVmdCcpICsgZGVsdGFbMF1cbiAgICBAZy56b29tZXIuc2V0ICdfYWxpZ25tZW50U2Nyb2xsVG9wJywgQGcuem9vbWVyLmdldCgnX2FsaWdubWVudFNjcm9sbFRvcCcpICsgZGVsdGFbMV1cbiAgICBlLnByZXZlbnREZWZhdWx0KClcblxuICBfb25jbGljazogKGUpIC0+XG4gICAgQGcudHJpZ2dlciBcInJlc2lkdWU6Y2xpY2tcIiwgQF9nZXRDbGlja1BvcyhlKVxuICAgIEB0aHJvdHRsZWREcmF3KClcblxuICBfb25tb3VzZWluOiAoZSkgLT5cbiAgICBAZy50cmlnZ2VyIFwicmVzaWR1ZTpjbGlja1wiLCBAX2dldENsaWNrUG9zKGUpXG4gICAgQHRocm90dGxlZERyYXcoKVxuXG4gIF9vbm1vdXNlb3V0OiAoZSkgLT5cbiAgICBAZy50cmlnZ2VyIFwicmVzaWR1ZTpjbGlja1wiLCBAX2dldENsaWNrUG9zKGUpXG4gICAgQHRocm90dGxlZERyYXcoKVxuXG4gIF9nZXRDbGlja1BvczogKGUpIC0+XG4gICAgY29vcmRzID0gbW91c2UucmVsIGVcbiAgICBjb29yZHNbMF0gKz0gQGcuem9vbWVyLmdldChcIl9hbGlnbm1lbnRTY3JvbGxMZWZ0XCIpXG4gICAgY29vcmRzWzFdICs9IEBnLnpvb21lci5nZXQoXCJfYWxpZ25tZW50U2Nyb2xsVG9wXCIpXG4gICAgeCA9IE1hdGguZmxvb3IoY29vcmRzWzBdIC8gQGcuem9vbWVyLmdldChcImNvbHVtbldpZHRoXCIpIClcbiAgICB5ID0gTWF0aC5mbG9vcihjb29yZHNbMV0gLyBAZy56b29tZXIuZ2V0KFwicm93SGVpZ2h0XCIpKVxuXG4gICAgIyBhZGQgaGlkZGVuIGNvbHVtbnNcbiAgICB4ICs9IEBnLmNvbHVtbnMuY2FsY0hpZGRlbkNvbHVtbnMgeFxuICAgICMgYWRkIGhpZGRlbiBzZXFzXG4gICAgeSArPSBAbW9kZWwuY2FsY0hpZGRlblNlcXMgeVxuXG4gICAgeCA9IE1hdGgubWF4IDAseFxuICAgIHkgPSBNYXRoLm1heCAwLHlcbiAgICBzZXFJZCA9IEBtb2RlbC5hdCh5KS5nZXQgXCJpZFwiXG4gICAgcmV0dXJuIHtzZXFJZDpzZXFJZCwgcm93UG9zOiB4LCBldnQ6ZX1cblxuICAjIGNoZWNrcyB3aGV0aGVyIHRoZSBzY3JvbGxpbmcgY29vcmRpbmF0ZXMgYXJlIHZhbGlkXG4gICMgQHJldHVybnM6IFt4U2Nyb2xsLHlTY3JvbGxdIHZhbGlkIGNvb3JkaW5hdGVzXG4gIF9jaGVja1Njcm9sbGluZzogKHNjcm9sbE9iaikgLT5cblxuICAgICMgMDogbWF4TGVmdCwgMTogbWF4VG9wXG4gICAgbWF4ID0gW0Btb2RlbC5nZXRNYXhMZW5ndGgoKSAqIEBnLnpvb21lci5nZXQoXCJjb2x1bW5XaWR0aFwiKSAtIEBnLnpvb21lci5nZXQoJ2FsaWdubWVudFdpZHRoJyksXG4gICAgQG1vZGVsLmxlbmd0aCAgKiBAZy56b29tZXIuZ2V0KFwicm93SGVpZ2h0XCIpIC0gQGcuem9vbWVyLmdldCgnYWxpZ25tZW50SGVpZ2h0JyldXG5cbiAgICBmb3IgaSBpbiBbMC4uMV0gYnkgMVxuICAgICAgaWYgc2Nyb2xsT2JqW2ldID4gbWF4W2ldXG4gICAgICAgIHNjcm9sbE9ialtpXSA9IG1heFtpXVxuXG4gICAgICBpZiBzY3JvbGxPYmpbaV0gPCAwXG4gICAgICAgIHNjcm9sbE9ialtpXSA9IDBcblxuICAgIHJldHVybiBzY3JvbGxPYmpcblxuICAjIFRPRE86IHNob3VsZCBJIGJlIG1vdmVkIHRvIHRoZSBzZWxlY3Rpb24gbWFuYWdlcj9cbiAgIyByZXR1cm5zIGFuIGFycmF5IHdpdGggdGhlIGN1cnJlbnRseSBzZWxlY3RlZCByZXNpZHVlc1xuICAjIGUuZy4gWzAsM10gPSBwb3MgMCBhbmQgMyBhcmUgc2VsZWN0ZWRcbiAgX2dldFNlbGVjdGlvbjogKG1vZGVsKSAtPlxuICAgIG1heExlbiA9IG1vZGVsLmdldChcInNlcVwiKS5sZW5ndGhcbiAgICBzZWxlY3Rpb24gPSBbXVxuICAgIHNlbHMgPSBAZy5zZWxjb2wuZ2V0U2VsRm9yUm93IG1vZGVsLmdldCBcImlkXCJcbiAgICByb3dzID0gXy5maW5kIHNlbHMsIChlbCkgLT4gZWwuZ2V0KFwidHlwZVwiKSBpcyBcInJvd1wiXG4gICAgaWYgcm93cz9cbiAgICAgICMgZnVsbCBtYXRjaFxuICAgICAgZm9yIG4gaW4gWzAuLm1heExlbiAtIDFdIGJ5IDFcbiAgICAgICAgc2VsZWN0aW9uLnB1c2ggblxuICAgIGVsc2UgaWYgc2Vscy5sZW5ndGggPiAwXG4gICAgICBmb3Igc2VsIGluIHNlbHNcbiAgICAgICAgZm9yIG4gaW4gW3NlbC5nZXQoXCJ4U3RhcnRcIikuLnNlbC5nZXQoXCJ4RW5kXCIpXSBieSAxXG4gICAgICAgICAgc2VsZWN0aW9uLnB1c2ggblxuXG4gICAgcmV0dXJuIHNlbGVjdGlvblxuXG4gICMgZHJhd3MgZmVhdHVyZXNcbiAgYXBwZW5kRmVhdHVyZTogKGRhdGEpIC0+XG4gICAgZiA9IGRhdGEuZlxuICAgICMgVE9ETzogdGhpcyBpcyBhIHZlcnkgbmFpdmUgd2F5XG4gICAgYm94V2lkdGggPSBAZy56b29tZXIuZ2V0KFwiY29sdW1uV2lkdGhcIilcbiAgICBib3hIZWlnaHQgPSBAZy56b29tZXIuZ2V0KFwicm93SGVpZ2h0XCIpXG4gICAgd2lkdGggPSAoZi5nZXQoXCJ4RW5kXCIpIC0gZi5nZXQoXCJ4U3RhcnRcIikpICogYm94V2lkdGhcblxuICAgIGJlZm9yZVdpZHRoID0gQGN0eC5saW5lV2lkdGhcbiAgICBAY3R4LmxpbmVXaWR0aCA9IDNcbiAgICBiZWZvcmVTdHlsZSA9IEBjdHguc3Ryb2tlU3R5bGVcbiAgICBAY3R4LnN0cm9rZVN0eWxlID0gZi5nZXQgXCJmaWxsQ29sb3JcIlxuXG4gICAgQGN0eC5zdHJva2VSZWN0IGRhdGEueFplcm8sIGRhdGEueVplcm8sIHdpZHRoLGJveEhlaWdodFxuICAgIEBjdHguc3Ryb2tlU3R5bGUgPSBiZWZvcmVTdHlsZVxuICAgIEBjdHgubGluZVdpZHRoID0gYmVmb3JlV2lkdGhcblxuXG4gICMgbG9vcHMgb3ZlciBhbGwgc2VsZWN0aW9uIGFuZCBjYWxscyB0aGUgcmVuZGVyIG1ldGhvZFxuICBfYXBwZW5kU2VsZWN0aW9uOiAoZGF0YSkgLT5cbiAgICBzZXEgPSBkYXRhLm1vZGVsLmdldChcInNlcVwiKVxuICAgIHNlbGVjdGlvbiA9IEBfZ2V0U2VsZWN0aW9uIGRhdGEubW9kZWxcbiAgICAjIGdldCB0aGUgc3RhdHVzIG9mIHRoZSB1cHBlciBhbmQgbG93ZXIgcm93XG4gICAgW21QcmV2U2VsLG1OZXh0U2VsXSA9IEBfZ2V0UHJldk5leHRTZWxlY3Rpb24gZGF0YS5tb2RlbFxuXG4gICAgYm94V2lkdGggPSBAZy56b29tZXIuZ2V0KFwiY29sdW1uV2lkdGhcIilcbiAgICBib3hIZWlnaHQgPSBAZy56b29tZXIuZ2V0KFwicm93SGVpZ2h0XCIpXG5cbiAgICAjIGF2b2lkIHVubmVjZXNzYXJ5IGxvb3BzXG4gICAgcmV0dXJuIGlmIHNlbGVjdGlvbi5sZW5ndGggaXMgMFxuXG4gICAgaGlkZGVuT2Zmc2V0ID0gMFxuICAgIGZvciBuIGluIFswLi5zZXEubGVuZ3RoIC0gMV0gYnkgMVxuICAgICAgaWYgZGF0YS5oaWRkZW4uaW5kZXhPZihuKSA+PSAwXG4gICAgICAgIGhpZGRlbk9mZnNldCsrXG4gICAgICBlbHNlXG4gICAgICAgIGsgPSBuIC0gaGlkZGVuT2Zmc2V0XG4gICAgICAgICMgb25seSBpZiBpdHMgYSBuZXcgc2VsZWN0aW9uXG4gICAgICAgIGlmIHNlbGVjdGlvbi5pbmRleE9mKG4pID49IDAgYW5kIChrIGlzIDAgb3Igc2VsZWN0aW9uLmluZGV4T2YobiAtIDEpIDwgMCApXG4gICAgICAgICAgQF9yZW5kZXJTZWxlY3Rpb24gbjpuLGs6ayxzZWxlY3Rpb246IHNlbGVjdGlvbixtUHJldlNlbDogbVByZXZTZWwsbU5leHRTZWw6bU5leHRTZWwsIHhaZXJvOiBkYXRhLnhaZXJvLCB5WmVybzogZGF0YS55WmVybywgbW9kZWw6IGRhdGEubW9kZWxcblxuICAjIGRyYXdzIGEgc2luZ2xlIHVzZXIgc2VsZWN0aW9uXG4gIF9yZW5kZXJTZWxlY3Rpb246IChkYXRhKSAtPlxuXG4gICAgeFplcm8gPSBkYXRhLnhaZXJvXG4gICAgeVplcm8gPSBkYXRhLnlaZXJvXG4gICAgbiA9IGRhdGEublxuICAgIGsgPSBkYXRhLmtcbiAgICBzZWxlY3Rpb24gPSBkYXRhLnNlbGVjdGlvblxuICAgICMgYW5kIGNoZWNrcyB0aGUgcHJldiBhbmQgbmV4dCByb3cgZm9yIHNlbGVjdGlvbiAgLT4gbm8gYm9yZGVycyBpbiBhIHNlbGVjdGlvblxuICAgIG1QcmV2U2VsPSBkYXRhLm1QcmV2U2VsXG4gICAgbU5leHRTZWwgPSBkYXRhLm1OZXh0U2VsXG5cbiAgICAjIGdldCB0aGUgbGVuZ3RoIG9mIHRoaXMgc2VsZWN0aW9uXG4gICAgc2VsZWN0aW9uTGVuZ3RoID0gMFxuICAgIGZvciBpIGluIFtuLi4gZGF0YS5tb2RlbC5nZXQoXCJzZXFcIikubGVuZ3RoIC0gMV0gYnkgMVxuICAgICAgaWYgc2VsZWN0aW9uLmluZGV4T2YoaSkgPj0gMFxuICAgICAgICBzZWxlY3Rpb25MZW5ndGgrK1xuICAgICAgZWxzZVxuICAgICAgICBicmVha1xuXG4gICAgIyBUT0RPOiB1Z2x5IVxuICAgIGJveFdpZHRoID0gQGcuem9vbWVyLmdldChcImNvbHVtbldpZHRoXCIpXG4gICAgYm94SGVpZ2h0ID0gQGcuem9vbWVyLmdldChcInJvd0hlaWdodFwiKVxuICAgIHRvdGFsV2lkdGggPSAoYm94V2lkdGggKiBzZWxlY3Rpb25MZW5ndGgpICsgMVxuXG4gICAgaGlkZGVuID0gQGcuY29sdW1ucy5nZXQoJ2hpZGRlbicpXG5cbiAgICBAY3R4LmJlZ2luUGF0aCgpXG4gICAgYmVmb3JlV2lkdGggPSBAY3R4LmxpbmVXaWR0aFxuICAgIEBjdHgubGluZVdpZHRoID0gM1xuICAgIGJlZm9yZVN0eWxlID0gQGN0eC5zdHJva2VTdHlsZVxuICAgIEBjdHguc3Ryb2tlU3R5bGUgPSBcIiNGRjAwMDBcIlxuXG4gICAgeFplcm8gKz0gayAqIGJveFdpZHRoXG5cbiAgICAjIHNwbGl0IHVwIHRoZSBzZWxlY3Rpb24gaW50byBzaW5nbGUgY2VsbHNcbiAgICB4UGFydCA9IDBcbiAgICBmb3IgaSBpbiBbMC4uIHNlbGVjdGlvbkxlbmd0aCAtIDFdXG4gICAgICB4UG9zID0gbiArIGlcbiAgICAgIGlmIGhpZGRlbi5pbmRleE9mKHhQb3MpID49IDBcbiAgICAgICAgY29udGludWVcbiAgICAgICMgdXBwZXIgbGluZVxuICAgICAgdW5sZXNzIG1QcmV2U2VsPyBhbmQgbVByZXZTZWwuaW5kZXhPZih4UG9zKSA+PSAwXG4gICAgICAgIEBjdHgubW92ZVRvIHhaZXJvICsgeFBhcnQsIHlaZXJvXG4gICAgICAgIEBjdHgubGluZVRvIHhQYXJ0ICsgYm94V2lkdGggKyB4WmVybywgeVplcm9cbiAgICAgICMgbG93ZXIgbGluZVxuICAgICAgdW5sZXNzIG1OZXh0U2VsPyBhbmQgbU5leHRTZWwuaW5kZXhPZih4UG9zKSA+PSAwXG4gICAgICAgIEBjdHgubW92ZVRvIHhQYXJ0ICsgeFplcm8sIGJveEhlaWdodCArIHlaZXJvXG4gICAgICAgIEBjdHgubGluZVRvIHhQYXJ0ICsgYm94V2lkdGggKyB4WmVybywgYm94SGVpZ2h0ICsgeVplcm9cblxuICAgICAgeFBhcnQgKz0gYm94V2lkdGhcblxuICAgICMgbGVmdFxuICAgIEBjdHgubW92ZVRvIHhaZXJvLHlaZXJvXG4gICAgQGN0eC5saW5lVG8geFplcm8sIGJveEhlaWdodCArIHlaZXJvXG5cbiAgICAjIHJpZ2h0XG4gICAgQGN0eC5tb3ZlVG8geFplcm8gKyB0b3RhbFdpZHRoLHlaZXJvXG4gICAgQGN0eC5saW5lVG8geFplcm8gKyB0b3RhbFdpZHRoLCBib3hIZWlnaHQgKyB5WmVyb1xuXG4gICAgQGN0eC5zdHJva2UoKVxuICAgIEBjdHguc3Ryb2tlU3R5bGUgPSBiZWZvcmVTdHlsZVxuICAgIEBjdHgubGluZVdpZHRoID0gYmVmb3JlV2lkdGhcblxuICAjIGxvb2tzIGF0IHRoZSBzZWxlY3Rpb24gb2YgdGhlIHByZXYgYW5kIG5leHQgZWxcbiAgIyBUT0RPOiB0aGlzIGlzIHZlcnkgbmFpdmUsIGFzIHRoZXJlIG1pZ2h0IGJlIGdhcHMgYWJvdmUgb3IgYmVsb3dcbiAgX2dldFByZXZOZXh0U2VsZWN0aW9uOiAobW9kZWwpIC0+XG5cbiAgICBtb2RlbFByZXYgPSBtb2RlbC5jb2xsZWN0aW9uLnByZXYgbW9kZWxcbiAgICBtb2RlbE5leHQgPSBtb2RlbC5jb2xsZWN0aW9uLm5leHQgbW9kZWxcbiAgICBtUHJldlNlbCA9IEBfZ2V0U2VsZWN0aW9uIG1vZGVsUHJldiBpZiBtb2RlbFByZXY/XG4gICAgbU5leHRTZWwgPSBAX2dldFNlbGVjdGlvbiBtb2RlbE5leHQgaWYgbW9kZWxOZXh0P1xuICAgIFttUHJldlNlbCxtTmV4dFNlbF1cbiIsInZpZXcgPSByZXF1aXJlKFwiYmFja2JvbmUtdmlld2pcIilcbm1vdXNlID0gcmVxdWlyZSBcIm1vdXNlLXBvc1wiXG5zZWxlY3Rpb24gPSByZXF1aXJlIFwiLi4vZy9zZWxlY3Rpb24vU2VsZWN0aW9uXCJcbmNvbG9yU2VsZWN0b3IgPSByZXF1aXJlKFwiYmlvanMtdXRpbC1jb2xvcnNjaGVtZXNcIikuc2VsZWN0b3Jcbmpib25lID0gcmVxdWlyZSBcImpib25lXCJcbl8gPSByZXF1aXJlIFwidW5kZXJzY29yZVwiXG5cbm1vZHVsZS5leHBvcnRzID0gT3ZlcnZpZXdCb3ggPSB2aWV3LmV4dGVuZFxuXG4gIGNsYXNzTmFtZTogXCJiaW9qc19tc2Ffb3ZlcnZpZXdib3hcIlxuICB0YWdOYW1lOiBcImNhbnZhc1wiXG5cbiAgaW5pdGlhbGl6ZTogKGRhdGEpIC0+XG4gICAgQGcgPSBkYXRhLmdcbiAgICBAbGlzdGVuVG8gQGcuem9vbWVyLFwiY2hhbmdlOmJveFJlY3RXaWR0aCBjaGFuZ2U6Ym94UmVjdEhlaWdodFwiLCBAcmVuZGVyXG4gICAgQGxpc3RlblRvIEBnLnNlbGNvbCwgXCJhZGQgcmVzZXQgY2hhbmdlXCIsIEByZW5kZXJcbiAgICBAbGlzdGVuVG8gQGcuY29sdW1ucywgXCJjaGFuZ2U6aGlkZGVuXCIsIEByZW5kZXJcbiAgICBAbGlzdGVuVG8gQGcuY29sb3JzY2hlbWUsIFwiY2hhbmdlOnNob3dMb3dlckNhc2VcIiwgQHJlbmRlclxuICAgIEBsaXN0ZW5UbyBAbW9kZWwsIFwiY2hhbmdlXCIsIF8uZGVib3VuY2UgQHJlbmRlciwgNVxuXG4gICAgIyBjb2xvclxuICAgIEBjb2xvciA9IGNvbG9yU2VsZWN0b3IuZ2V0Q29sb3IgQGcuY29sb3JzY2hlbWUuZ2V0KFwic2NoZW1lXCIpXG4gICAgQGxpc3RlblRvIEBnLmNvbG9yc2NoZW1lLCBcImNoYW5nZTpzY2hlbWVcIiwgLT5cbiAgICAgIEBjb2xvciA9IGNvbG9yU2VsZWN0b3IuZ2V0Q29sb3IgQGcuY29sb3JzY2hlbWUuZ2V0KFwic2NoZW1lXCIpXG4gICAgICBAcmVuZGVyKClcbiAgICBAZHJhZ1N0YXJ0ID0gW11cblxuICBldmVudHM6XG4gICAgY2xpY2s6IFwiX29uY2xpY2tcIlxuICAgIG1vdXNlZG93bjogXCJfb25tb3VzZWRvd25cIlxuXG4gIHJlbmRlcjogLT5cbiAgICBAX2NyZWF0ZUNhbnZhcygpXG4gICAgQGVsLnRleHRDb250ZW50ID0gXCJvdmVydmlld1wiXG5cbiAgICAjIGJhY2tncm91bmQgYmcgZm9yIG5vbi1kcmF3ZWQgYXJlYVxuICAgIEBjdHguZmlsbFN0eWxlID0gXCIjOTk5OTk5XCJcbiAgICBAY3R4LmZpbGxSZWN0IDAsMCxAZWwud2lkdGgsQGVsLmhlaWdodFxuXG4gICAgcmVjdFdpZHRoID0gQGcuem9vbWVyLmdldCBcImJveFJlY3RXaWR0aFwiXG4gICAgcmVjdEhlaWdodCA9IEBnLnpvb21lci5nZXQgXCJib3hSZWN0SGVpZ2h0XCJcbiAgICBoaWRkZW4gPSBAZy5jb2x1bW5zLmdldCBcImhpZGRlblwiXG4gICAgc2hvd0xvd2VyQ2FzZSA9IEBnLmNvbG9yc2NoZW1lLmdldCBcInNob3dMb3dlckNhc2VcIlxuXG4gICAgeSA9IC1yZWN0SGVpZ2h0XG4gICAgZm9yIGkgaW4gWzAuLiBAbW9kZWwubGVuZ3RoIC0gMV0gYnkgMVxuICAgICAgc2VxID0gQG1vZGVsLmF0KGkpLmdldCBcInNlcVwiXG4gICAgICB4ID0gMFxuICAgICAgeSA9IHkgKyByZWN0SGVpZ2h0XG5cblxuICAgICAgaWYgQG1vZGVsLmF0KGkpLmdldCBcImhpZGRlblwiXG4gICAgICAgICMgaGlkZGVuIHNlcVxuICAgICAgICBjb25zb2xlLmxvZyBAbW9kZWwuYXQoaSkuZ2V0IFwiaGlkZGVuXCJcbiAgICAgICAgQGN0eC5maWxsU3R5bGUgPSBcImdyZXlcIlxuICAgICAgICBAY3R4LmZpbGxSZWN0IDAseSxzZXEubGVuZ3RoICogcmVjdFdpZHRoLHJlY3RIZWlnaHRcbiAgICAgICAgY29udGludWVcblxuICAgICAgZm9yIGogaW4gWzAuLiBzZXEubGVuZ3RoIC0gMV0gYnkgMVxuICAgICAgICBjID0gc2VxW2pdXG4gICAgICAgICMgdG9kbzogb3B0aW9uYWwgdXBwZXJjYXNpbmdcbiAgICAgICAgYyA9IGMudG9VcHBlckNhc2UoKSBpZiBzaG93TG93ZXJDYXNlXG4gICAgICAgIGNvbG9yID0gQGNvbG9yW2NdXG5cbiAgICAgICAgaWYgaGlkZGVuLmluZGV4T2YoaikgPj0gMFxuICAgICAgICAgIGNvbG9yID0gXCJncmV5XCJcblxuICAgICAgICBpZiBjb2xvcj9cbiAgICAgICAgICBAY3R4LmZpbGxTdHlsZSA9IGNvbG9yXG4gICAgICAgICAgQGN0eC5maWxsUmVjdCB4LHkscmVjdFdpZHRoLHJlY3RIZWlnaHRcblxuICAgICAgICB4ID0geCArIHJlY3RXaWR0aFxuXG4gICAgQF9kcmF3U2VsZWN0aW9uKClcblxuICBfZHJhd1NlbGVjdGlvbjogLT5cbiAgICAjIGhpZGUgZHVyaW5nIHNlbGVjdGlvblxuICAgIHJldHVybiBpZiBAZHJhZ1N0YXJ0Lmxlbmd0aCA+IDAgYW5kIG5vdCBAcHJvbG9uZ1NlbGVjdGlvblxuXG4gICAgcmVjdFdpZHRoID0gQGcuem9vbWVyLmdldCBcImJveFJlY3RXaWR0aFwiXG4gICAgcmVjdEhlaWdodCA9IEBnLnpvb21lci5nZXQgXCJib3hSZWN0SGVpZ2h0XCJcbiAgICBtYXhIZWlnaHQgPSByZWN0SGVpZ2h0ICogQG1vZGVsLmxlbmd0aFxuICAgIEBjdHguZmlsbFN0eWxlID0gXCIjZmZmZjAwXCJcbiAgICBAY3R4Lmdsb2JhbEFscGhhID0gMC45XG4gICAgZm9yIGkgaW4gWzAuLiBAZy5zZWxjb2wubGVuZ3RoIC0gMV0gYnkgMVxuICAgICAgc2VsID0gQGcuc2VsY29sLmF0KGkpXG4gICAgICBpZiBzZWwuZ2V0KCd0eXBlJykgaXMgJ2NvbHVtbidcbiAgICAgICAgQGN0eC5maWxsUmVjdCByZWN0V2lkdGggKiBzZWwuZ2V0KCd4U3RhcnQnKSwwLHJlY3RXaWR0aCAqXG4gICAgICAgIChzZWwuZ2V0KCd4RW5kJykgLSBzZWwuZ2V0KCd4U3RhcnQnKSArIDEpLG1heEhlaWdodFxuICAgICAgZWxzZSBpZiBzZWwuZ2V0KCd0eXBlJykgaXMgJ3JvdydcbiAgICAgICAgc2VxID0gKEBtb2RlbC5maWx0ZXIgKGVsKSAtPiBlbC5nZXQoJ2lkJykgaXMgc2VsLmdldCgnc2VxSWQnKSlbMF1cbiAgICAgICAgcG9zID0gQG1vZGVsLmluZGV4T2Yoc2VxKVxuICAgICAgICBAY3R4LmZpbGxSZWN0IDAscmVjdEhlaWdodCAqIHBvcywgcmVjdFdpZHRoICogc2VxLmdldCgnc2VxJykubGVuZ3RoLCByZWN0SGVpZ2h0XG4gICAgICBlbHNlIGlmIHNlbC5nZXQoJ3R5cGUnKSBpcyAncG9zJ1xuICAgICAgICBzZXEgPSAoQG1vZGVsLmZpbHRlciAoZWwpIC0+IGVsLmdldCgnaWQnKSBpcyBzZWwuZ2V0KCdzZXFJZCcpKVswXVxuICAgICAgICBwb3MgPSBAbW9kZWwuaW5kZXhPZihzZXEpXG4gICAgICAgIEBjdHguZmlsbFJlY3QgcmVjdFdpZHRoICogc2VsLmdldCgneFN0YXJ0JykscmVjdEhlaWdodCAqIHBvcywgcmVjdFdpZHRoICogKHNlbC5nZXQoJ3hFbmQnKSAtIHNlbC5nZXQoJ3hTdGFydCcpICsgMSksIHJlY3RIZWlnaHRcblxuICAgIEBjdHguZ2xvYmFsQWxwaGEgPSAxXG5cbiAgX29uY2xpY2s6IChldnQpIC0+XG4gICAgQGcudHJpZ2dlciBcIm1ldGE6Y2xpY2tcIiwge3NlcUlkOiBAbW9kZWwuZ2V0IFwiaWRcIiwgZXZ0OmV2dH1cblxuICBfb25tb3VzZW1vdmU6IChlKSAtPlxuICAgICMgZHVwbGljYXRlIGV2ZW50c1xuICAgIHJldHVybiBpZiBAZHJhZ1N0YXJ0Lmxlbmd0aCBpcyAwXG5cbiAgICBAcmVuZGVyKClcbiAgICBAY3R4LmZpbGxTdHlsZSA9IFwiI2ZmZmYwMFwiXG4gICAgQGN0eC5nbG9iYWxBbHBoYSA9IDAuOVxuXG4gICAgcmVjdCA9IEBfY2FsY1NlbGVjdGlvbiggbW91c2UuYWJzIGUgKVxuICAgIEBjdHguZmlsbFJlY3QgcmVjdFswXVswXSxyZWN0WzFdWzBdLHJlY3RbMF1bMV0gLSByZWN0WzBdWzBdLCByZWN0WzFdWzFdIC0gcmVjdFsxXVswXVxuXG4gICAgIyBhYm9ydCBzZWxlY3Rpb24gZXZlbnRzIG9mIHRoZSBicm93c2VyXG4gICAgZS5wcmV2ZW50RGVmYXVsdCgpXG4gICAgZS5zdG9wUHJvcGFnYXRpb24oKVxuXG4gICMgc3RhcnQgdGhlIHNlbGVjdGlvbiBtb2RlXG4gIF9vbm1vdXNlZG93bjogKGUpIC0+XG4gICAgQGRyYWdTdGFydCA9IG1vdXNlLmFicyBlXG4gICAgQGRyYWdTdGFydFJlbCA9IG1vdXNlLnJlbCBlXG5cbiAgICBpZiBlLmN0cmxLZXkgb3IgZS5tZXRhS2V5XG4gICAgICBAcHJvbG9uZ1NlbGVjdGlvbiA9IHRydWVcbiAgICBlbHNlXG4gICAgICBAcHJvbG9uZ1NlbGVjdGlvbiA9IGZhbHNlXG4gICAgIyBlbmFibGUgZ2xvYmFsIGxpc3RlbmVyc1xuICAgIGpib25lKGRvY3VtZW50LmJvZHkpLm9uICdtb3VzZW1vdmUub3Zlcm1vdmUnLCAoZSkgPT4gQF9vbm1vdXNlbW92ZShlKVxuICAgIGpib25lKGRvY3VtZW50LmJvZHkpLm9uICdtb3VzZXVwLm92ZXJ1cCcsIChlKSA9PiBAX29ubW91c2V1cChlKVxuICAgIHJldHVybiBAZHJhZ1N0YXJ0XG5cbiAgIyBjYWxjdWxhdGVzIHRoZSBjdXJyZW50IHNlbGVjdGlvblxuICBfY2FsY1NlbGVjdGlvbjogKGRyYWdNb3ZlKSAtPlxuICAgICMgcmVsYXRpdmUgdG8gZmlyc3QgY2xpY2tcbiAgICBkcmFnUmVsID0gW2RyYWdNb3ZlWzBdIC0gQGRyYWdTdGFydFswXSwgZHJhZ01vdmVbMV0gLSBAZHJhZ1N0YXJ0WzFdXVxuXG4gICAgIyByZWxhdGl2ZSB0byB0YXJnZXRcbiAgICBmb3IgaSBpbiBbMC4uMV0gYnkgMVxuICAgICAgZHJhZ1JlbFtpXSA9IEBkcmFnU3RhcnRSZWxbaV0gKyBkcmFnUmVsW2ldXG5cbiAgICAjIDA6eCwgMTogeVxuICAgIHJlY3QgPSBbW0BkcmFnU3RhcnRSZWxbMF0sIGRyYWdSZWxbMF1dLCBbQGRyYWdTdGFydFJlbFsxXSwgZHJhZ1JlbFsxXV1dXG5cbiAgICAjIHN3YXAgdGhlIGNvb3JkaW5hdGVzIGlmIG5lZWRlZFxuICAgIGZvciBpIGluIFswLi4xXSBieSAxXG4gICAgICBpZiByZWN0W2ldWzFdIDwgcmVjdFtpXVswXVxuICAgICAgICByZWN0W2ldID0gW3JlY3RbaV1bMV0sIHJlY3RbaV1bMF1dXG5cbiAgICAgICMgbG93ZXIgbGltaXRcbiAgICAgIHJlY3RbaV1bMF0gPSBNYXRoLm1heCByZWN0W2ldWzBdLCAwXG5cbiAgICByZXR1cm4gcmVjdFxuXG4gIF9lbmRTZWxlY3Rpb246IChkcmFnRW5kKSAtPlxuICAgICMgcmVtb3ZlIGxpc3RlbmVyc1xuICAgIGpib25lKGRvY3VtZW50LmJvZHkpLm9mZignLm92ZXJtb3ZlJylcbiAgICBqYm9uZShkb2N1bWVudC5ib2R5KS5vZmYoJy5vdmVydXAnKVxuXG4gICAgIyBkdXBsaWNhdGUgZXZlbnRzXG4gICAgcmV0dXJuIGlmIEBkcmFnU3RhcnQubGVuZ3RoIGlzIDBcblxuICAgIHJlY3QgPSBAX2NhbGNTZWxlY3Rpb24gZHJhZ0VuZFxuXG4gICAgIyB4XG4gICAgZm9yIGkgaW4gWzAuLjFdXG4gICAgICByZWN0WzBdW2ldID0gTWF0aC5mbG9vciggcmVjdFswXVtpXSAvIEBnLnpvb21lci5nZXQoXCJib3hSZWN0V2lkdGhcIikpXG5cbiAgICAjIHlcbiAgICBmb3IgaSBpbiBbMC4uMV1cbiAgICAgIHJlY3RbMV1baV0gPSBNYXRoLmZsb29yKCByZWN0WzFdW2ldIC8gQGcuem9vbWVyLmdldChcImJveFJlY3RIZWlnaHRcIikgKVxuXG4gICAgIyB1cHBlciBsaW1pdFxuICAgIHJlY3RbMF1bMV0gPSBNYXRoLm1pbihAbW9kZWwuZ2V0TWF4TGVuZ3RoKCkgLSAxLCByZWN0WzBdWzFdKVxuICAgIHJlY3RbMV1bMV0gPSBNYXRoLm1pbihAbW9kZWwubGVuZ3RoIC0gMSwgcmVjdFsxXVsxXSlcblxuICAgICMgc2VsZWN0XG4gICAgc2VsaXMgPSBbXVxuICAgIGZvciBqIGluIFtyZWN0WzFdWzBdLi5yZWN0WzFdWzFdXSBieSAxXG4gICAgICBhcmdzID0gc2VxSWQ6IEBtb2RlbC5hdChqKS5nZXQoJ2lkJyksIHhTdGFydDogcmVjdFswXVswXSwgeEVuZDogcmVjdFswXVsxXVxuICAgICAgc2VsaXMucHVzaCBuZXcgc2VsZWN0aW9uLnBvc3NlbCBhcmdzXG5cbiAgICAjIHJlc2V0XG4gICAgQGRyYWdTdGFydCA9IFtdXG4gICAgIyBsb29rIGZvciBjdHJsIGtleVxuICAgIGlmIEBwcm9sb25nU2VsZWN0aW9uXG4gICAgICBAZy5zZWxjb2wuYWRkIHNlbGlzXG4gICAgZWxzZVxuICAgICAgQGcuc2VsY29sLnJlc2V0IHNlbGlzXG5cbiAgICAjIHNhZmV0eSBjaGVjayArIHVwZGF0ZSBvZmZzZXRcbiAgICBAZy56b29tZXIuc2V0TGVmdE9mZnNldCByZWN0WzBdWzBdXG4gICAgQGcuem9vbWVyLnNldFRvcE9mZnNldCByZWN0WzFdWzBdXG5cbiAgIyBlbmRzIHRoZSBzZWxlY3Rpb24gbW9kZVxuICBfb25tb3VzZXVwOiAoZSkgLT5cbiAgICBAX2VuZFNlbGVjdGlvbiBtb3VzZS5hYnMgZVxuXG4gIF9vbm1vdXNlb3V0OiAoZSkgLT5cbiAgICBAX2VuZFNlbGVjdGlvbiBtb3VzZS5hYnMgZVxuXG4gIyBpbml0IHRoZSBjYW52YXNcbiAgX2NyZWF0ZUNhbnZhczogLT5cbiAgICByZWN0V2lkdGggPSBAZy56b29tZXIuZ2V0IFwiYm94UmVjdFdpZHRoXCJcbiAgICByZWN0SGVpZ2h0ID0gQGcuem9vbWVyLmdldCBcImJveFJlY3RIZWlnaHRcIlxuXG4gICAgQGVsLmhlaWdodCA9IEBtb2RlbC5sZW5ndGggKiByZWN0SGVpZ2h0XG4gICAgQGVsLndpZHRoID0gQG1vZGVsLmdldE1heExlbmd0aCgpICogcmVjdFdpZHRoXG4gICAgQGN0eCA9IEBlbC5nZXRDb250ZXh0IFwiMmRcIlxuICAgIEBlbC5zdHlsZS5vdmVyZmxvdyA9IFwic2Nyb2xsXCJcbiAgICBAZWwuc3R5bGUuY3Vyc29yID0gXCJjcm9zc2hhaXJcIlxuIiwiYm9uZVZpZXcgPSByZXF1aXJlKFwiYmFja2JvbmUtY2hpbGRzXCIpXG5BbGlnbm1lbnRCb2R5ID0gcmVxdWlyZSBcIi4vQWxpZ25tZW50Qm9keVwiXG5IZWFkZXJCbG9jayA9IHJlcXVpcmUgXCIuL2hlYWRlci9IZWFkZXJCbG9ja1wiXG5PdmVydmlld0JveCA9IHJlcXVpcmUgXCIuL092ZXJ2aWV3Qm94XCJcbmlkZW50aXR5Q2FsYyA9IHJlcXVpcmUgXCIuLi9hbGdvL2lkZW50aXR5Q2FsY1wiXG5fID0gcmVxdWlyZSAndW5kZXJzY29yZSdcblxuIyBhIG5lYXQgY29sbGVjdGlvbiB2aWV3XG5tb2R1bGUuZXhwb3J0cyA9IGJvbmVWaWV3LmV4dGVuZFxuXG4gIGluaXRpYWxpemU6IChkYXRhKSAtPlxuICAgIEBnID0gZGF0YS5nXG5cbiAgICBAZHJhdygpXG4gICAgQGxpc3RlblRvIEBtb2RlbCxcInJlc2V0XCIsIC0+XG4gICAgICBAaXNOb3REaXJ0eSA9IGZhbHNlXG4gICAgICBAcmVyZW5kZXIoKVxuXG4gICAgIyBkZWJvdW5jZSBhIGJ1bGsgb3BlcmF0aW9uXG4gICAgQGxpc3RlblRvIEBtb2RlbCxcImNoYW5nZTpoaWRkZW5cIiwgXy5kZWJvdW5jZSBAcmVyZW5kZXIsIDEwXG5cbiAgICBAbGlzdGVuVG8gQG1vZGVsLFwic29ydFwiLCBAcmVyZW5kZXJcbiAgICBAbGlzdGVuVG8gQG1vZGVsLFwiYWRkXCIsIC0+XG4gICAgICBjb25zb2xlLmxvZyBcInNlcSBhZGRcIlxuXG4gICAgQGxpc3RlblRvIEBnLnZpcyxcImNoYW5nZTpzZXF1ZW5jZXNcIiwgQHJlcmVuZGVyXG4gICAgQGxpc3RlblRvIEBnLnZpcyxcImNoYW5nZTpvdmVydmlld2JveFwiLCBAcmVyZW5kZXJcbiAgICBAbGlzdGVuVG8gQGcudmlzb3JkZXIsXCJjaGFuZ2VcIiwgQHJlcmVuZGVyXG5cbiAgZHJhdzogLT5cbiAgICBAcmVtb3ZlVmlld3MoKVxuXG4gICAgdW5sZXNzIEBpc05vdERpcnR5XG4gICAgICAjIG9ubHkgZXhlY3V0ZWQgd2hlbiBuZXcgc2VxdWVuY2VzIGFyZSBhZGRlZCBvciBvbiBzdGFydFxuICAgICAgY29uc2Vuc3VzID0gQGcuY29uc2Vuc3VzLmdldENvbnNlbnN1cyBAbW9kZWxcbiAgICAgIGlkZW50aXR5Q2FsYyBAbW9kZWwsIGNvbnNlbnN1c1xuICAgICAgQGlzTm90RGlydHkgPSB0cnVlXG5cbiAgICBpZiBAZy52aXMuZ2V0IFwib3ZlcnZpZXdib3hcIlxuICAgICAgb3ZlcnZpZXdib3ggPSBuZXcgT3ZlcnZpZXdCb3gge21vZGVsOiBAbW9kZWwsIGc6IEBnfVxuICAgICAgb3ZlcnZpZXdib3gub3JkZXJpbmcgPSBAZy52aXNvcmRlci5nZXQgJ292ZXJ2aWV3Qm94J1xuICAgICAgQGFkZFZpZXcgXCJvdmVydmlld2JveFwiLG92ZXJ2aWV3Ym94XG5cbiAgICBpZiB0cnVlXG4gICAgICBoZWFkZXJibG9jayA9IG5ldyBIZWFkZXJCbG9jayB7bW9kZWw6IEBtb2RlbCwgZzogQGd9XG4gICAgICBoZWFkZXJibG9jay5vcmRlcmluZyA9IEBnLnZpc29yZGVyLmdldCAnaGVhZGVyQm94J1xuICAgICAgQGFkZFZpZXcgXCJoZWFkZXJibG9ja1wiLGhlYWRlcmJsb2NrXG5cbiAgICBib2R5ID0gbmV3IEFsaWdubWVudEJvZHkge21vZGVsOiBAbW9kZWwsIGc6IEBnfVxuICAgIGJvZHkub3JkZXJpbmcgPSBAZy52aXNvcmRlci5nZXQgJ2FsaWdubWVudEJvZHknXG4gICAgQGFkZFZpZXcgXCJib2R5XCIsYm9keVxuXG4gIHJlbmRlcjogLT5cbiAgICBAcmVuZGVyU3Vidmlld3MoKVxuICAgIEBlbC5jbGFzc05hbWUgPSBcImJpb2pzX21zYV9zdGFnZVwiXG4gICAgQFxuXG4gIHJlcmVuZGVyOiAtPlxuICAgIEBkcmF3KClcbiAgICBAcmVuZGVyKClcbiIsInZpZXcgPSByZXF1aXJlKFwiYmFja2JvbmUtdmlld2pcIilcbmRvbSA9IHJlcXVpcmUoXCJkb20taGVscGVyXCIpXG5zdmcgPSByZXF1aXJlKFwiLi4vLi4vdXRpbHMvc3ZnXCIpXG5cbkNvbnNlcnZhdGlvblZpZXcgPSB2aWV3LmV4dGVuZFxuXG4gIGNsYXNzTmFtZTogXCJiaW9qc19tc2FfY29uc2VydlwiXG5cbiAgaW5pdGlhbGl6ZTogKGRhdGEpIC0+XG4gICAgQGcgPSBkYXRhLmdcbiAgICBAbGlzdGVuVG8gQGcuem9vbWVyLFwiY2hhbmdlOnN0ZXBTaXplIGNoYW5nZTpsYWJlbFdpZHRoIGNoYW5nZTpjb2x1bW5XaWR0aFwiLCBAcmVuZGVyXG4gICAgQGxpc3RlblRvIEBnLnZpcyxcImNoYW5nZTpsYWJlbHMgY2hhbmdlOm1ldGFjZWxsXCIsIEByZW5kZXJcbiAgICBAbGlzdGVuVG8gQGcuY29sdW1ucywgXCJjaGFuZ2U6c2NhbGluZ1wiLCBAcmVuZGVyXG4gICAgQGxpc3RlblRvIEBtb2RlbCwgXCJyZXNldFwiLEByZW5kZXJcbiAgICBAbWFuYWdlRXZlbnRzKClcblxuICByZW5kZXI6IC0+XG4gICAgQGcuY29sdW1ucy5jYWxjQ29uc2VydmF0aW9uIEBtb2RlbFxuXG4gICAgZG9tLnJlbW92ZUFsbENoaWxkcyBAZWxcblxuICAgIG5NYXggPSBAbW9kZWwuZ2V0TWF4TGVuZ3RoKClcbiAgICBjZWxsV2lkdGggPSBAZy56b29tZXIuZ2V0IFwiY29sdW1uV2lkdGhcIlxuICAgIG1heEhlaWdodCA9IDIwXG4gICAgd2lkdGggPSBjZWxsV2lkdGggKiAobk1heCAtIEBnLmNvbHVtbnMuZ2V0KCdoaWRkZW4nKS5sZW5ndGgpXG4gICAgY29uc29sZS5sb2cgQGcuY29sdW1ucy5nZXQoJ2hpZGRlbicpXG5cbiAgICBzID0gc3ZnLmJhc2UgaGVpZ2h0OiBtYXhIZWlnaHQsIHdpZHRoOiB3aWR0aFxuICAgIHMuc3R5bGUuZGlzcGxheSA9IFwiaW5saW5lLWJsb2NrXCJcbiAgICBzLnN0eWxlLmN1cnNvciA9IFwicG9pbnRlclwiXG5cbiAgICBzdGVwU2l6ZSA9IEBnLnpvb21lci5nZXQgXCJzdGVwU2l6ZVwiXG4gICAgaGlkZGVuID0gQGcuY29sdW1ucy5nZXQgXCJoaWRkZW5cIlxuICAgIHggPSAwXG4gICAgbiA9IDBcbiAgICB3aGlsZSBuIDwgbk1heFxuICAgICAgaWYgaGlkZGVuLmluZGV4T2YobikgPj0gMFxuICAgICAgICBuICs9IHN0ZXBTaXplXG4gICAgICAgIGNvbnRpbnVlXG4gICAgICB3aWR0aCA9IGNlbGxXaWR0aCAqIHN0ZXBTaXplXG4gICAgICBhdmdIZWlnaHQgPSAwXG4gICAgICBmb3IgaSBpbiBbMCAuLiBzdGVwU2l6ZSAtIDFdXG4gICAgICAgIGF2Z0hlaWdodCArPSBAZy5jb2x1bW5zLmdldChcImNvbnNlcnZcIilbbl1cbiAgICAgIGhlaWdodCA9IG1heEhlaWdodCAqICAoYXZnSGVpZ2h0IC8gc3RlcFNpemUpXG5cbiAgICAgIHJlY3QgPSAgc3ZnLnJlY3QgeDp4LHk6IG1heEhlaWdodCAtIGhlaWdodCx3aWR0aDp3aWR0aCAtIGNlbGxXaWR0aCAvIDQsaGVpZ2h0OmhlaWdodCxzdHlsZTpcbiAgICAgICAgXCJzdHJva2U6cmVkO3N0cm9rZS13aWR0aDoxO1wiXG4gICAgICByZWN0LnJvd1BvcyA9IG5cbiAgICAgIHMuYXBwZW5kQ2hpbGQgcmVjdFxuICAgICAgeCArPSB3aWR0aFxuICAgICAgbiArPSBzdGVwU2l6ZVxuXG4gICAgQGVsLmFwcGVuZENoaWxkIHNcbiAgICBAXG5cbiAgI1RPRE86IG1ha2UgbW9yZSBnZW5lcmFsIHdpdGggSGVhZGVyVmlld1xuICBfb25jbGljazogKGV2dCkgLT5cbiAgICByb3dQb3MgPSBldnQudGFyZ2V0LnJvd1Bvc1xuICAgIHN0ZXBTaXplID0gQGcuem9vbWVyLmdldChcInN0ZXBTaXplXCIpXG4gICAgIyBzaW11bGF0ZSBoaWRkZW4gY29sdW1uc1xuICAgIGZvciBpIGluIFswLi5zdGVwU2l6ZSAtIDFdIGJ5IDFcbiAgICAgIEBnLnRyaWdnZXIgXCJiYXI6Y2xpY2tcIiwge3Jvd1Bvczogcm93UG9zICsgaSwgZXZ0OmV2dH1cblxuICBtYW5hZ2VFdmVudHM6IC0+XG4gICAgZXZlbnRzID0ge31cbiAgICBpZiBAZy5jb25maWcuZ2V0IFwicmVnaXN0ZXJNb3VzZUNsaWNrc1wiXG4gICAgICBldmVudHMuY2xpY2sgPSBcIl9vbmNsaWNrXCJcbiAgICBpZiBAZy5jb25maWcuZ2V0IFwicmVnaXN0ZXJNb3VzZUhvdmVyXCJcbiAgICAgIGV2ZW50cy5tb3VzZWluID0gXCJfb25tb3VzZWluXCJcbiAgICAgIGV2ZW50cy5tb3VzZW91dCA9IFwiX29ubW91c2VvdXRcIlxuICAgIEBkZWxlZ2F0ZUV2ZW50cyBldmVudHNcbiAgICBAbGlzdGVuVG8gQGcuY29uZmlnLCBcImNoYW5nZTpyZWdpc3Rlck1vdXNlSG92ZXJcIiwgQG1hbmFnZUV2ZW50c1xuICAgIEBsaXN0ZW5UbyBAZy5jb25maWcsIFwiY2hhbmdlOnJlZ2lzdGVyTW91c2VDbGlja1wiLCBAbWFuYWdlRXZlbnRzXG5cbiAgX29ubW91c2VpbjogKGV2dCkgLT5cbiAgICByb3dQb3MgPSBAZy56b29tZXIuZ2V0IFwic3RlcFNpemVcIiAqIGV2dC5yb3dQb3NcbiAgICBAZy50cmlnZ2VyIFwiYmFyOm1vdXNlaW5cIiwge3Jvd1Bvczogcm93UG9zLCBldnQ6ZXZ0fVxuXG4gIF9vbm1vdXNlb3V0OiAoZXZ0KSAtPlxuICAgIHJvd1BvcyA9IEBnLnpvb21lci5nZXQgXCJzdGVwU2l6ZVwiICogZXZ0LnJvd1Bvc1xuICAgIEBnLnRyaWdnZXIgXCJiYXI6bW91c2VvdXRcIiwge3Jvd1Bvczogcm93UG9zLCBldnQ6ZXZ0fVxuXG5tb2R1bGUuZXhwb3J0cyA9IENvbnNlcnZhdGlvblZpZXdcbiIsIk1hcmtlclZpZXcgPSByZXF1aXJlIFwiLi9NYXJrZXJWaWV3XCJcbkNvbnNlcnZhdGlvblZpZXcgPSByZXF1aXJlIFwiLi9Db25zZXJ2YXRpb25WaWV3XCJcbmlkZW50aXR5Q2FsYyA9IHJlcXVpcmUgXCIuLi8uLi9hbGdvL2lkZW50aXR5Q2FsY1wiXG5ib25lVmlldyA9IHJlcXVpcmUoXCJiYWNrYm9uZS1jaGlsZHNcIilcbl8gPSByZXF1aXJlICd1bmRlcnNjb3JlJ1xuXG5tb2R1bGUuZXhwb3J0cyA9IGJvbmVWaWV3LmV4dGVuZFxuXG4gIGluaXRpYWxpemU6IChkYXRhKSAtPlxuICAgIEBnID0gZGF0YS5nXG4gICAgQGJsb2NrRXZlbnRzID0gZmFsc2VcblxuICAgIEBsaXN0ZW5UbyBAZy52aXMsXCJjaGFuZ2U6bWFya2VycyBjaGFuZ2U6Y29uc2VydlwiLCAtPlxuICAgICAgQGRyYXcoKVxuICAgICAgQHJlbmRlcigpXG4gICAgQGxpc3RlblRvIEBnLnZpcyxcImNoYW5nZVwiLCBAX3NldFNwYWNlclxuICAgIEBsaXN0ZW5UbyBAZy56b29tZXIsXCJjaGFuZ2U6YWxpZ25tZW50V2lkdGhcIiwgLT5cbiAgICAgIEBfYWRqdXN0V2lkdGgoKVxuICAgIEBsaXN0ZW5UbyBAZy56b29tZXIsIFwiY2hhbmdlOl9hbGlnbm1lbnRTY3JvbGxMZWZ0XCIsIEBfYWRqdXN0U2Nyb2xsaW5nTGVmdFxuXG4gICAgIyBUT0RPOiBkdXBsaWNhdGUgcmVuZGVyaW5nXG4gICAgQGxpc3RlblRvIEBnLmNvbHVtbnMsIFwiY2hhbmdlOmhpZGRlblwiLCAtPlxuICAgICAgQGRyYXcoKVxuICAgICAgQHJlbmRlcigpXG5cbiAgICBAZHJhdygpXG4gICAgQF9vbnNjcm9sbCA9IEBfc2VuZFNjcm9sbEV2ZW50XG5cbiAgICBAZy52aXMub25jZSAnY2hhbmdlOmxvYWRlZCcsIEBfYWRqdXN0U2Nyb2xsaW5nTGVmdCwgQFxuXG4gIGV2ZW50czpcbiAgICBcInNjcm9sbFwiOiBcIl9vbnNjcm9sbFwiXG5cbiAgZHJhdzogLT5cbiAgICBAcmVtb3ZlVmlld3MoKVxuXG4gICAgdW5sZXNzIEBpc05vdERpcnR5XG4gICAgICAjIG9ubHkgZXhlY3V0ZWQgd2hlbiBuZXcgc2VxdWVuY2VzIGFyZSBhZGRlZCBvciBvbiBzdGFydFxuICAgICAgY29uc2Vuc3VzID0gQGcuY29uc2Vuc3VzLmdldENvbnNlbnN1cyBAbW9kZWxcbiAgICAgIGlkZW50aXR5Q2FsYyBAbW9kZWwsIGNvbnNlbnN1c1xuICAgICAgQGlzTm90RGlydHkgPSB0cnVlXG5cbiAgICBpZiBAZy52aXMuZ2V0IFwiY29uc2VydlwiXG4gICAgICBjb25zZXJ2ID0gbmV3IENvbnNlcnZhdGlvblZpZXcge21vZGVsOiBAbW9kZWwsIGc6IEBnfVxuICAgICAgY29uc2Vydi5vcmRlcmluZyA9IC0yMFxuICAgICAgQGFkZFZpZXcgXCJjb25zZXJ2XCIsY29uc2VydlxuXG4gICAgaWYgQGcudmlzLmdldCBcIm1hcmtlcnNcIlxuICAgICAgbWFya2VyID0gbmV3IE1hcmtlclZpZXcge21vZGVsOiBAbW9kZWwsIGc6IEBnfVxuICAgICAgbWFya2VyLm9yZGVyaW5nID0gLTEwXG4gICAgICBAYWRkVmlldyBcIm1hcmtlclwiLG1hcmtlclxuXG4gIHJlbmRlcjogLT5cbiAgICBAcmVuZGVyU3Vidmlld3MoKVxuXG4gICAgQF9zZXRTcGFjZXIoKVxuXG4gICAgQGVsLmNsYXNzTmFtZSA9IFwiYmlvanNfbXNhX2hlYWRlclwiXG4gICAgQGVsLnN0eWxlLm92ZXJmbG93WCA9IFwiYXV0b1wiXG4gICAgQF9hZGp1c3RXaWR0aCgpXG4gICAgQF9hZGp1c3RTY3JvbGxpbmdMZWZ0KClcbiAgICBAXG5cbiAgIyBzY3JvbGxMZWZ0IHRyaWdnZXJzIGEgcmVmbG93IG9mIHRoZSB3aG9sZSBhcmVhIChldmVuIG9ubHkgZ2V0KVxuICBfc2VuZFNjcm9sbEV2ZW50OiAtPlxuICAgIHVubGVzcyBAYmxvY2tFdmVudHNcbiAgICAgIEBnLnpvb21lci5zZXQgXCJfYWxpZ25tZW50U2Nyb2xsTGVmdFwiLCBAZWwuc2Nyb2xsTGVmdCwge29yaWdpbjogXCJoZWFkZXJcIn1cbiAgICBAYmxvY2tFdmVudHMgPSBmYWxzZVxuXG4gIF9hZGp1c3RTY3JvbGxpbmdMZWZ0OiAobW9kZWwsdmFsdWUsb3B0aW9ucykgLT5cbiAgICBpZiAobm90IG9wdGlvbnM/Lm9yaWdpbj8pIG9yIG9wdGlvbnMub3JpZ2luIGlzbnQgXCJoZWFkZXJcIlxuICAgICAgc2Nyb2xsTGVmdCA9IEBnLnpvb21lci5nZXQgXCJfYWxpZ25tZW50U2Nyb2xsTGVmdFwiXG4gICAgICBAYmxvY2tFdmVudHMgPSB0cnVlXG4gICAgICBAZWwuc2Nyb2xsTGVmdCA9IHNjcm9sbExlZnRcblxuICBfc2V0U3BhY2VyOiAtPlxuICAgICMgc3BhY2VyIC8gcGFkZGluZyBlbGVtZW50XG4gICAgQGVsLnN0eWxlLm1hcmdpbkxlZnQgPSBAX2dldExhYmVsV2lkdGgoKSArIFwicHhcIlxuXG4gIF9nZXRMYWJlbFdpZHRoOiAtPlxuICAgIHBhZGRpbmdMZWZ0ID0gMFxuICAgIHBhZGRpbmdMZWZ0ICs9IEBnLnpvb21lci5nZXQgXCJsYWJlbFdpZHRoXCIgaWYgQGcudmlzLmdldCBcImxhYmVsc1wiXG4gICAgcGFkZGluZ0xlZnQgKz0gQGcuem9vbWVyLmdldCBcIm1ldGFXaWR0aFwiIGlmIEBnLnZpcy5nZXQgXCJtZXRhY2VsbFwiXG4gICAgcmV0dXJuIHBhZGRpbmdMZWZ0XG5cbiAgX2FkanVzdFdpZHRoOiAtPlxuICAgIEBlbC5zdHlsZS53aWR0aCA9IEBnLnpvb21lci5nZXQoXCJhbGlnbm1lbnRXaWR0aFwiKSArIFwicHhcIlxuIiwidmlldyA9IHJlcXVpcmUoXCJiYWNrYm9uZS12aWV3alwiKVxuZG9tID0gcmVxdWlyZShcImRvbS1oZWxwZXJcIilcbnN2ZyA9IHJlcXVpcmUoXCIuLi8uLi91dGlscy9zdmdcIilcbmpib25lID0gcmVxdWlyZSBcImpib25lXCJcblxuSGVhZGVyVmlldyA9IHZpZXcuZXh0ZW5kXG5cbiAgY2xhc3NOYW1lOiBcImJpb2pzX21zYV9tYXJrZXJcIlxuXG4gIGluaXRpYWxpemU6IChkYXRhKSAtPlxuICAgIEBnID0gZGF0YS5nXG4gICAgQGxpc3RlblRvIEBnLnpvb21lcixcImNoYW5nZTpzdGVwU2l6ZSBjaGFuZ2U6bGFiZWxXaWR0aCBjaGFuZ2U6Y29sdW1uV2lkdGggY2hhbmdlOm1hcmtlclN0ZXBTaXplIGNoYW5nZTptYXJrZXJGb250c2l6ZVwiLCBAcmVuZGVyXG4gICAgQGxpc3RlblRvIEBnLnZpcyxcImNoYW5nZTpsYWJlbHMgY2hhbmdlOm1ldGFjZWxsXCIsIEByZW5kZXJcbiAgICBAbWFuYWdlRXZlbnRzKClcblxuICByZW5kZXI6IC0+XG4gICAgZG9tLnJlbW92ZUFsbENoaWxkcyBAZWxcblxuICAgIEBlbC5zdHlsZS5mb250U2l6ZSA9IEBnLnpvb21lci5nZXQgXCJtYXJrZXJGb250c2l6ZVwiXG5cbiAgICBjb250YWluZXIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50IFwic3BhblwiXG4gICAgbiA9IDBcbiAgICBjZWxsV2lkdGggPSBAZy56b29tZXIuZ2V0IFwiY29sdW1uV2lkdGhcIlxuXG4gICAgbk1heCA9IEBtb2RlbC5nZXRNYXhMZW5ndGgoKVxuICAgIHN0ZXBTaXplID0gQGcuem9vbWVyLmdldChcInN0ZXBTaXplXCIpXG4gICAgaGlkZGVuID0gQGcuY29sdW1ucy5nZXQgXCJoaWRkZW5cIlxuXG4gICAgd2hpbGUgbiA8IG5NYXhcbiAgICAgIGlmIGhpZGRlbi5pbmRleE9mKG4pID49IDBcbiAgICAgICAgQG1hcmtlckhpZGRlbihzcGFuLG4sIHN0ZXBTaXplKVxuICAgICAgICBuICs9IHN0ZXBTaXplXG4gICAgICAgIGNvbnRpbnVlXG4gICAgICBzcGFuID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCBcInNwYW5cIlxuICAgICAgc3Bhbi5zdHlsZS53aWR0aCA9IChjZWxsV2lkdGggKiBzdGVwU2l6ZSkgKyBcInB4XCJcbiAgICAgIHNwYW4uc3R5bGUuZGlzcGxheSA9IFwiaW5saW5lLWJsb2NrXCJcbiAgICAgICMgVE9ETzogdGhpcyBkb2Vzbid0IHdvcmsgZm9yIGEgbGFyZ2VyIHN0ZXBTaXplXG4gICAgICBpZiAobiArIDEpICUgQGcuem9vbWVyLmdldCgnbWFya2VyU3RlcFNpemUnKSBpcyAwXG4gICAgICAgIHNwYW4udGV4dENvbnRlbnQgPSAobiArIDEpXG4gICAgICBlbHNlXG4gICAgICAgIHNwYW4udGV4dENvbnRlbnQgPSBcIi5cIlxuICAgICAgc3Bhbi5yb3dQb3MgPSBuXG5cbiAgICAgIG4gKz0gc3RlcFNpemVcbiAgICAgIGNvbnRhaW5lci5hcHBlbmRDaGlsZCBzcGFuXG5cbiAgICBAZWwuYXBwZW5kQ2hpbGQgY29udGFpbmVyXG4gICAgQFxuXG4gIG1hcmtlckhpZGRlbjogKHNwYW4sbixzdGVwU2l6ZSkgLT5cbiAgICBoaWRkZW4gPSBAZy5jb2x1bW5zLmdldChcImhpZGRlblwiKS5zbGljZSAwXG5cbiAgICBtaW4gPSBNYXRoLm1heCAwLCBuIC0gc3RlcFNpemVcbiAgICBwcmV2SGlkZGVuID0gdHJ1ZVxuICAgIGZvciBqIGluICBbbWluIC4uIG5dIGJ5IDFcbiAgICAgIHByZXZIaWRkZW4gJj0gaGlkZGVuLmluZGV4T2YoaikgPj0gMFxuXG4gICAgIyBmaWx0ZXIgZHVwbGljYXRlc1xuICAgIHJldHVybiBpZiBwcmV2SGlkZGVuXG5cbiAgICBuTWF4ID0gQG1vZGVsLmdldE1heExlbmd0aCgpXG5cbiAgICBsZW5ndGggPSAwXG4gICAgaW5kZXggPSAtMVxuICAgICMgYWNjdW1sYXRlIG11bHRpcGxlIHJvd3NcbiAgICBmb3IgbiBpbiBbbi4ubk1heF0gYnkgMVxuICAgICAgaW5kZXggPSBoaWRkZW4uaW5kZXhPZihuKSB1bmxlc3MgaW5kZXggPj0gMCMgc2V0cyB0aGUgZmlyc3QgaW5kZXhcbiAgICAgIGlmIGhpZGRlbi5pbmRleE9mKG4pID49IDBcbiAgICAgICAgbGVuZ3RoKytcbiAgICAgIGVsc2VcbiAgICAgICAgYnJlYWtcblxuICAgIHMgPSBzdmcuYmFzZSBoZWlnaHQ6IDEwLCB3aWR0aDogMTBcbiAgICBzLnN0eWxlLnBvc2l0aW9uID0gXCJyZWxhdGl2ZVwiXG4gICAgdHJpYW5nbGUgPSBzdmcucG9seWdvbiBwb2ludHM6IFwiMCwwIDUsNSAxMCwwXCIsIHN0eWxlOlxuICAgICAgXCJmaWxsOmxpbWU7c3Ryb2tlOnB1cnBsZTtzdHJva2Utd2lkdGg6MVwiXG4gICAgamJvbmUodHJpYW5nbGUpLm9uIFwiY2xpY2tcIiwgKGV2dCkgPT5cbiAgICAgIGhpZGRlbi5zcGxpY2UgaW5kZXgsIGxlbmd0aFxuICAgICAgQGcuY29sdW1ucy5zZXQgXCJoaWRkZW5cIiwgaGlkZGVuXG5cbiAgICBzLmFwcGVuZENoaWxkIHRyaWFuZ2xlXG4gICAgc3Bhbi5hcHBlbmRDaGlsZCBzXG4gICAgcmV0dXJuIHNcblxuICBtYW5hZ2VFdmVudHM6IC0+XG4gICAgZXZlbnRzID0ge31cbiAgICBpZiBAZy5jb25maWcuZ2V0IFwicmVnaXN0ZXJNb3VzZUNsaWNrc1wiXG4gICAgICBldmVudHMuY2xpY2sgPSBcIl9vbmNsaWNrXCJcbiAgICBpZiBAZy5jb25maWcuZ2V0IFwicmVnaXN0ZXJNb3VzZUhvdmVyXCJcbiAgICAgIGV2ZW50cy5tb3VzZWluID0gXCJfb25tb3VzZWluXCJcbiAgICAgIGV2ZW50cy5tb3VzZW91dCA9IFwiX29ubW91c2VvdXRcIlxuICAgIEBkZWxlZ2F0ZUV2ZW50cyBldmVudHNcbiAgICBAbGlzdGVuVG8gQGcuY29uZmlnLCBcImNoYW5nZTpyZWdpc3Rlck1vdXNlSG92ZXJcIiwgQG1hbmFnZUV2ZW50c1xuICAgIEBsaXN0ZW5UbyBAZy5jb25maWcsIFwiY2hhbmdlOnJlZ2lzdGVyTW91c2VDbGlja1wiLCBAbWFuYWdlRXZlbnRzXG5cbiAgX29uY2xpY2s6IChldnQpIC0+XG4gICAgcm93UG9zID0gZXZ0LnRhcmdldC5yb3dQb3NcbiAgICBzdGVwU2l6ZSA9IEBnLnpvb21lci5nZXQoXCJzdGVwU2l6ZVwiKVxuICAgIEBnLnRyaWdnZXIgXCJjb2x1bW46Y2xpY2tcIiwge3Jvd1Bvczogcm93UG9zLHN0ZXBTaXplOiBzdGVwU2l6ZSwgZXZ0OmV2dH1cblxuICBfb25tb3VzZWluOiAoZXZ0KSAtPlxuICAgIHJvd1BvcyA9IEBnLnpvb21lci5nZXQgXCJzdGVwU2l6ZVwiICogZXZ0LnJvd1Bvc1xuICAgIHN0ZXBTaXplID0gQGcuem9vbWVyLmdldChcInN0ZXBTaXplXCIpXG4gICAgQGcudHJpZ2dlciBcImNvbHVtbjptb3VzZWluXCIsIHtyb3dQb3M6IHJvd1BvcyxzdGVwU2l6ZTogc3RlcFNpemUsIGV2dDpldnR9XG5cbiAgX29ubW91c2VvdXQ6IChldnQpIC0+XG4gICAgcm93UG9zID0gQGcuem9vbWVyLmdldCBcInN0ZXBTaXplXCIgKiBldnQucm93UG9zXG4gICAgc3RlcFNpemUgPSBAZy56b29tZXIuZ2V0KFwic3RlcFNpemVcIilcbiAgICBAZy50cmlnZ2VyIFwiY29sdW1uOm1vdXNlb3V0XCIsIHtyb3dQb3M6IHJvd1BvcyxzdGVwU2l6ZTogc3RlcFNpemUsIGV2dDpldnR9XG5cbm1vZHVsZS5leHBvcnRzID0gSGVhZGVyVmlld1xuIiwiTGFiZWxSb3dWaWV3ID0gcmVxdWlyZSBcIi4vTGFiZWxSb3dWaWV3XCJcbmJvbmVWaWV3ID0gcmVxdWlyZShcImJhY2tib25lLWNoaWxkc1wiKVxuXG5tb2R1bGUuZXhwb3J0cyA9IGJvbmVWaWV3LmV4dGVuZFxuXG4gIGluaXRpYWxpemU6IChkYXRhKSAtPlxuICAgIEBnID0gZGF0YS5nXG4gICAgQGRyYXcoKVxuICAgIEBsaXN0ZW5UbyBAZy56b29tZXIsIFwiY2hhbmdlOl9hbGlnbm1lbnRTY3JvbGxUb3BcIiwgQF9hZGp1c3RTY3JvbGxpbmdUb3BcbiAgICBAZy52aXMub25jZSAnY2hhbmdlOmxvYWRlZCcsIEBfYWRqdXN0U2Nyb2xsaW5nVG9wICwgQFxuXG4gIGRyYXc6IC0+XG4gICAgQHJlbW92ZVZpZXdzKClcbiAgICBmb3IgaSBpbiBbMC4uIEBtb2RlbC5sZW5ndGggLSAxXSBieSAxXG4gICAgICBjb250aW51ZSBpZiBAbW9kZWwuYXQoaSkuZ2V0KCdoaWRkZW4nKVxuICAgICAgdmlldyA9IG5ldyBMYWJlbFJvd1ZpZXcge21vZGVsOiBAbW9kZWwuYXQoaSksIGc6IEBnfVxuICAgICAgdmlldy5vcmRlcmluZyA9IGlcbiAgICAgIEBhZGRWaWV3IFwicm93XyN7aX1cIiwgdmlld1xuXG4gIGV2ZW50czpcbiAgICBcInNjcm9sbFwiOiBcIl9zZW5kU2Nyb2xsRXZlbnRcIlxuXG4gICMgYnJvYWRjYXN0IHRoZSBzY3JvbGxpbmcgZXZlbnQgKGJ5IHRoZSBzY3JvbGxiYXIpXG4gIF9zZW5kU2Nyb2xsRXZlbnQ6IC0+XG4gICAgQGcuem9vbWVyLnNldCBcIl9hbGlnbm1lbnRTY3JvbGxUb3BcIiwgQGVsLnNjcm9sbFRvcCwge29yaWdpbjogXCJsYWJlbFwifVxuXG4gICMgc2V0cyB0aGUgc2Nyb2xsaW5nIHByb3BlcnR5IChmcm9tIGFub3RoZXIgZXZlbnQgZS5nLiBkcmFnZ2luZylcbiAgX2FkanVzdFNjcm9sbGluZ1RvcDogLT5cbiAgICBAZWwuc2Nyb2xsVG9wID0gIEBnLnpvb21lci5nZXQgXCJfYWxpZ25tZW50U2Nyb2xsVG9wXCJcblxuICByZW5kZXI6IC0+XG4gICAgQHJlbmRlclN1YnZpZXdzKClcbiAgICBAZWwuY2xhc3NOYW1lID0gXCJiaW9qc19tc2FfbGFiZWxibG9ja1wiXG4gICAgQGVsLnN0eWxlLmRpc3BsYXkgPSBcImlubGluZS1ibG9ja1wiXG4gICAgQGVsLnN0eWxlLnZlcnRpY2FsQWxpZ24gPSBcInRvcFwiXG4gICAgQGVsLnN0eWxlLmhlaWdodCA9ICBAZy56b29tZXIuZ2V0KFwiYWxpZ25tZW50SGVpZ2h0XCIpICsgXCJweFwiXG4gICAgQGVsLnN0eWxlLm92ZXJmbG93WSA9IFwiYXV0b1wiXG4gICAgQGVsLnN0eWxlLm92ZXJmbG93WCA9IFwiaGlkZGVuXCJcbiAgICBAZWwuc3R5bGUuZm9udFNpemUgPSBcIiN7QGcuem9vbWVyLmdldCBcImxhYmVsRm9udHNpemVcIn1cIlxuICAgIEBlbC5zdHlsZS5saW5lSGVpZ2h0ID0gXCIje0BnLnpvb21lci5nZXQgXCJsYWJlbExpbmVIZWlnaHRcIn1cIlxuICAgIEBcbiIsImJvbmVWaWV3ID0gcmVxdWlyZShcImJhY2tib25lLWNoaWxkc1wiKVxuTGFiZWxWaWV3ID0gcmVxdWlyZShcIi4vTGFiZWxWaWV3XCIpXG5NZXRhVmlldyA9IHJlcXVpcmUoXCIuL01ldGFWaWV3XCIpXG5cbm1vZHVsZS5leHBvcnRzID0gYm9uZVZpZXcuZXh0ZW5kXG5cbiAgaW5pdGlhbGl6ZTogKGRhdGEpIC0+XG4gICAgQGcgPSBkYXRhLmdcbiAgICBAZHJhdygpXG5cbiAgICBAbGlzdGVuVG8gQGcudmlzLFwiY2hhbmdlOmxhYmVsc1wiLCBAZHJhd1JcbiAgICBAbGlzdGVuVG8gQGcudmlzLFwiY2hhbmdlOm1ldGFjZWxsXCIsIEBkcmF3UlxuXG4gIGRyYXc6IC0+XG4gICAgQHJlbW92ZVZpZXdzKClcbiAgICBpZiBAZy52aXMuZ2V0IFwibGFiZWxzXCJcbiAgICAgIEBhZGRWaWV3IFwibGFiZWxzXCIsIG5ldyBMYWJlbFZpZXcge21vZGVsOiBAbW9kZWwsIGc6QGd9XG4gICAgaWYgQGcudmlzLmdldCBcIm1ldGFjZWxsXCJcbiAgICAgIEBhZGRWaWV3IFwibWV0YWNlbGxcIiwgbmV3IE1ldGFWaWV3IHttb2RlbDogQG1vZGVsLCBnOkBnfVxuXG4gIGRyYXdSOiAtPlxuICAgIEBkcmF3KClcbiAgICBAcmVuZGVyKClcblxuICByZW5kZXI6IC0+XG4gICAgQHJlbmRlclN1YnZpZXdzKClcbiAgICBAZWwuc2V0QXR0cmlidXRlIFwiY2xhc3NcIiwgXCJiaW9qc19tc2FfbGFiZWxyb3dcIlxuICAgIEBlbC5zdHlsZS5oZWlnaHQgPSBAZy56b29tZXIuZ2V0IFwicm93SGVpZ2h0XCJcbiAgICBAXG4iLCJ2aWV3ID0gcmVxdWlyZShcImJhY2tib25lLXZpZXdqXCIpXG5kb20gPSByZXF1aXJlIFwiZG9tLWhlbHBlclwiXG5cbkxhYmVsVmlldyA9IHZpZXcuZXh0ZW5kXG5cbiAgaW5pdGlhbGl6ZTogKGRhdGEpIC0+XG4gICAgQHNlcSA9IGRhdGEuc2VxXG4gICAgQGcgPSBkYXRhLmdcblxuICAgIEBtYW5hZ2VFdmVudHMoKVxuXG4gIG1hbmFnZUV2ZW50czogLT5cbiAgICBldmVudHMgPSB7fVxuICAgIGlmIEBnLmNvbmZpZy5nZXQgXCJyZWdpc3Rlck1vdXNlQ2xpY2tzXCJcbiAgICAgIGV2ZW50cy5jbGljayA9IFwiX29uY2xpY2tcIlxuICAgIGlmIEBnLmNvbmZpZy5nZXQgXCJyZWdpc3Rlck1vdXNlSG92ZXJcIlxuICAgICAgZXZlbnRzLm1vdXNlaW4gPSBcIl9vbm1vdXNlaW5cIlxuICAgICAgZXZlbnRzLm1vdXNlb3V0ID0gXCJfb25tb3VzZW91dFwiXG4gICAgQGRlbGVnYXRlRXZlbnRzIGV2ZW50c1xuICAgIEBsaXN0ZW5UbyBAZy5jb25maWcsIFwiY2hhbmdlOnJlZ2lzdGVyTW91c2VIb3ZlclwiLCBAbWFuYWdlRXZlbnRzXG4gICAgQGxpc3RlblRvIEBnLmNvbmZpZywgXCJjaGFuZ2U6cmVnaXN0ZXJNb3VzZUNsaWNrXCIsIEBtYW5hZ2VFdmVudHNcbiAgICBAbGlzdGVuVG8gQGcudmlzLCBcImNoYW5nZTpsYWJlbE5hbWVcIiwgQHJlbmRlclxuICAgIEBsaXN0ZW5UbyBAZy52aXMsIFwiY2hhbmdlOmxhYmVsSWRcIiwgQHJlbmRlclxuICAgIEBsaXN0ZW5UbyBAZy52aXMsIFwiY2hhbmdlOmxhYmVsUGFydGl0aW9uXCIsIEByZW5kZXJcbiAgICBAbGlzdGVuVG8gQGcudmlzLCBcImNoYW5nZTpsYWJlbENoZWNrYm94XCIsIEByZW5kZXJcblxuICByZW5kZXI6IC0+XG4gICAgZG9tLnJlbW92ZUFsbENoaWxkcyBAZWxcblxuICAgIEBlbC5zdHlsZS53aWR0aCA9IFwiI3tAZy56b29tZXIuZ2V0IFwibGFiZWxXaWR0aFwifXB4XCJcbiAgICBAZWwuc3R5bGUuaGVpZ2h0ID0gXCIje0BnLnpvb21lci5nZXQgXCJyb3dIZWlnaHRcIn1weFwiXG4gICAgQGVsLnNldEF0dHJpYnV0ZSBcImNsYXNzXCIsIFwiYmlvanNfbXNhX2xhYmVsc1wiXG5cbiAgICBpZiBALmcudmlzLmdldCBcImxhYmVsQ2hlY2tib3hcIlxuICAgICAgY2hlY2tCb3ggPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50IFwiaW5wdXRcIlxuICAgICAgY2hlY2tCb3guc2V0QXR0cmlidXRlIFwidHlwZVwiLCBcImNoZWNrYm94XCJcbiAgICAgIGNoZWNrQm94LnZhbHVlID0gQG1vZGVsLmdldCgnaWQnKVxuICAgICAgY2hlY2tCb3gubmFtZSA9IFwic2VxXCJcbiAgICAgIEBlbC5hcHBlbmRDaGlsZCBjaGVja0JveFxuXG4gICAgaWYgQC5nLnZpcy5nZXQgXCJsYWJlbElkXCJcbiAgICAgIGlkID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCBcInNwYW5cIlxuICAgICAgaWQudGV4dENvbnRlbnQgPSBAbW9kZWwuZ2V0IFwiaWRcIlxuICAgICAgaWQuc3R5bGUud2lkdGggPSBAZy56b29tZXIuZ2V0IFwibGFiZWxJZExlbmd0aFwiXG4gICAgICBpZC5zdHlsZS5kaXNwbGF5ID0gXCJpbmxpbmUtYmxvY2tcIlxuICAgICAgQGVsLmFwcGVuZENoaWxkIGlkXG5cbiAgICBpZiBALmcudmlzLmdldCBcImxhYmVsUGFydGl0aW9uXCJcbiAgICAgIHBhcnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50IFwic3BhblwiXG4gICAgICBwYXJ0LnN0eWxlLndpZHRoID0gMTVcbiAgICAgIHBhcnQudGV4dENvbnRlbnQgPSBAbW9kZWwuZ2V0KFwicGFydGl0aW9uXCIpXG4gICAgICBwYXJ0LnN0eWxlLmRpc3BsYXkgPSBcImlubGluZS1ibG9ja1wiXG4gICAgICBAZWwuYXBwZW5kQ2hpbGQgaWRcbiAgICAgIEBlbC5hcHBlbmRDaGlsZCBwYXJ0XG5cbiAgICBpZiBALmcudmlzLmdldCBcImxhYmVsTmFtZVwiXG4gICAgICBuYW1lID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCBcInNwYW5cIlxuICAgICAgbmFtZS50ZXh0Q29udGVudCA9IEBtb2RlbC5nZXQoXCJuYW1lXCIpXG4gICAgICBAZWwuYXBwZW5kQ2hpbGQgbmFtZVxuXG5cbiAgICBAZWwuc3R5bGUub3ZlcmZsb3cgPSBzY3JvbGxcbiAgICBAXG5cbiAgX29uY2xpY2s6IChldnQpIC0+XG4gICAgc2VxSWQgPSBAbW9kZWwuZ2V0IFwiaWRcIlxuICAgIEBnLnRyaWdnZXIgXCJyb3c6Y2xpY2tcIiwge3NlcUlkOnNlcUlkLCBldnQ6ZXZ0fVxuXG4gIF9vbm1vdXNlaW46IChldnQpIC0+XG4gICAgc2VxSWQgPSBAbW9kZWwuZ2V0IFwiaWRcIlxuICAgIEBnLnRyaWdnZXIgXCJyb3c6bW91c2VvdXRcIiwge3NlcUlkOnNlcUlkLCBldnQ6ZXZ0fVxuXG4gIF9vbm1vdXNlb3V0OiAoZXZ0KSAtPlxuICAgIHNlcUlkID0gQG1vZGVsLmdldCBcImlkXCJcbiAgICBAZy50cmlnZ2VyIFwicm93Om1vdXNlb3V0XCIsIHtzZXFJZDpzZXFJZCwgZXZ0OmV2dH1cblxubW9kdWxlLmV4cG9ydHMgPSBMYWJlbFZpZXdcbiIsInZpZXcgPSByZXF1aXJlKFwiYmFja2JvbmUtdmlld2pcIilcbk1lbnVCdWlsZGVyID0gcmVxdWlyZSBcIi4uLy4uL21lbnUvbWVudWJ1aWxkZXJcIlxuXyA9IHJlcXVpcmUgJ3VuZGVyc2NvcmUnXG5kb20gPSByZXF1aXJlIFwiZG9tLWhlbHBlclwiXG5cbm1vZHVsZS5leHBvcnRzID0gTWV0YVZpZXcgPSB2aWV3LmV4dGVuZFxuXG4gIGNsYXNzTmFtZTogXCJiaW9qc19tc2FfbWV0YXZpZXdcIlxuXG4gIGluaXRpYWxpemU6IChkYXRhKSAtPlxuICAgIEBnID0gZGF0YS5nXG5cbiAgZXZlbnRzOlxuICAgIGNsaWNrOiBcIl9vbmNsaWNrXCJcbiAgICBtb3VzZWluOiBcIl9vbm1vdXNlaW5cIlxuICAgIG1vdXNlb3V0OiBcIl9vbm1vdXNlb3V0XCJcblxuICByZW5kZXI6IC0+XG4gICAgZG9tLnJlbW92ZUFsbENoaWxkcyBAZWxcblxuICAgIEBlbC5zdHlsZS5kaXNwbGF5ID0gXCJpbmxpbmUtYmxvY2tcIlxuXG4gICAgd2lkdGggPSBAZy56b29tZXIuZ2V0IFwibWV0YVdpZHRoXCJcbiAgICBAZWwuc3R5bGUud2lkdGggPSB3aWR0aCAtIDVcbiAgICBAZWwuc3R5bGUucGFkZGluZ1JpZ2h0ID0gNVxuXG4gICAgIyBhZGRzIGdhcHNcbiAgICBzZXEgPSBAbW9kZWwuZ2V0KCdzZXEnKVxuICAgIGdhcHMgPSBfLnJlZHVjZSBzZXEsICgobWVtbywgYykgLT4gbWVtbysrIGlmIGMgaXMgJy0nO21lbW8pLDBcbiAgICBnYXBzID0gKGdhcHMgLyBzZXEubGVuZ3RoKS50b0ZpeGVkKDEpXG5cbiAgICAjIGFwcGVuZCBnYXAgY291bnRcbiAgICBnYXBTcGFuID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCAnc3BhbidcbiAgICBnYXBTcGFuLnRleHRDb250ZW50ID0gZ2Fwc1xuICAgIGdhcFNwYW4uc3R5bGUuZGlzcGxheSA9IFwiaW5saW5lLWJsb2NrXCJcbiAgICBnYXBTcGFuLnN0eWxlLndpZHRoID0gMzVcbiAgICBAZWwuYXBwZW5kQ2hpbGQgZ2FwU3BhblxuXG4gICAgIyBpZGVudGl0eVxuICAgIGlkZW50ID0gQG1vZGVsLmdldCgnaWRlbnRpdHknKVxuICAgIGlkZW50U3BhbiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQgJ3NwYW4nXG4gICAgaWRlbnRTcGFuLnRleHRDb250ZW50ID0gaWRlbnQudG9GaXhlZCgyKVxuICAgIGlkZW50U3Bhbi5zdHlsZS5kaXNwbGF5ID0gXCJpbmxpbmUtYmxvY2tcIlxuICAgIGlkZW50U3Bhbi5zdHlsZS53aWR0aCA9IDQwXG4gICAgQGVsLmFwcGVuZENoaWxkIGlkZW50U3BhblxuXG4gICAgIyBUT0RPOiB0aGlzIG1lbnUgYnVpbGRlciBpcyBqdXN0IGFuIGV4YW1wbGUgaG93IG9uZSBjb3VsZCBjdXN0b21pemUgdGhpc1xuICAgICMgdmlld1xuICAgIG1lbnUgPSBuZXcgTWVudUJ1aWxkZXIoXCLihpdcIilcbiAgICBtZW51LmFkZE5vZGUgXCJVbmlwcm90XCIsKGUpID0+XG4gICAgICB3aW5kb3cub3BlbiBcImh0dHA6Ly9iZXRhLnVuaXByb3Qub3JnL3VuaXByb3QvUTdUMk44XCJcbiAgICBAZWwuYXBwZW5kQ2hpbGQgbWVudS5idWlsZERPTSgpXG4gICAgQGVsLndpZHRoID0gMTBcblxuICAgIEBlbC5zdHlsZS5oZWlnaHQgPSBcIiN7QGcuem9vbWVyLmdldCBcInJvd0hlaWdodFwifXB4XCJcbiAgICBAZWwuc3R5bGUuY3Vyc29yID0gXCJwb2ludGVyXCJcblxuICBfb25jbGljazogKGV2dCkgLT5cbiAgICBAZy50cmlnZ2VyIFwibWV0YTpjbGlja1wiLCB7c2VxSWQ6IEBtb2RlbC5nZXQgXCJpZFwiLCBldnQ6ZXZ0fVxuXG4gIF9vbm1vdXNlaW46IChldnQpIC0+XG4gICAgQGcudHJpZ2dlciBcIm1ldGE6bW91c2VpblwiLCB7c2VxSWQ6IEBtb2RlbC5nZXQgXCJpZFwiLCBldnQ6ZXZ0fVxuXG4gIF9vbm1vdXNlb3V0OiAoZXZ0KSAtPlxuICAgIEBnLnRyaWdnZXIgXCJtZXRhOm1vdXNlb3V0XCIsIHtzZXFJZDogQG1vZGVsLmdldCBcImlkXCIsIGV2dDpldnR9XG4iLCIvLyBHZW5lcmF0ZWQgYnkgQ29mZmVlU2NyaXB0IDEuOC4wXG52YXIgQ2x1c3RhbCwgR2VuZXJpY1JlYWRlciwgU2VxLCBTdHIsXG4gIF9faGFzUHJvcCA9IHt9Lmhhc093blByb3BlcnR5LFxuICBfX2V4dGVuZHMgPSBmdW5jdGlvbihjaGlsZCwgcGFyZW50KSB7IGZvciAodmFyIGtleSBpbiBwYXJlbnQpIHsgaWYgKF9faGFzUHJvcC5jYWxsKHBhcmVudCwga2V5KSkgY2hpbGRba2V5XSA9IHBhcmVudFtrZXldOyB9IGZ1bmN0aW9uIGN0b3IoKSB7IHRoaXMuY29uc3RydWN0b3IgPSBjaGlsZDsgfSBjdG9yLnByb3RvdHlwZSA9IHBhcmVudC5wcm90b3R5cGU7IGNoaWxkLnByb3RvdHlwZSA9IG5ldyBjdG9yKCk7IGNoaWxkLl9fc3VwZXJfXyA9IHBhcmVudC5wcm90b3R5cGU7IHJldHVybiBjaGlsZDsgfTtcblxuU3RyID0gcmVxdWlyZShcIi4vc3RyaW5nc1wiKTtcblxuR2VuZXJpY1JlYWRlciA9IHJlcXVpcmUoXCIuL2dlbmVyaWNfcmVhZGVyXCIpO1xuXG5TZXEgPSByZXF1aXJlKFwiLi9zZXFcIik7XG5cbm1vZHVsZS5leHBvcnRzID0gQ2x1c3RhbCA9IChmdW5jdGlvbihfc3VwZXIpIHtcbiAgX19leHRlbmRzKENsdXN0YWwsIF9zdXBlcik7XG5cbiAgZnVuY3Rpb24gQ2x1c3RhbCgpIHtcbiAgICByZXR1cm4gQ2x1c3RhbC5fX3N1cGVyX18uY29uc3RydWN0b3IuYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgfVxuXG4gIENsdXN0YWwucGFyc2UgPSBmdW5jdGlvbih0ZXh0KSB7XG4gICAgdmFyIGJsb2Nrc3RhdGUsIGssIGxhYmVsLCBsaW5lLCBsaW5lcywgbWF0Y2gsIHJlZ2V4LCBzZXFDb3VudGVyLCBzZXFzLCBzZXF1ZW5jZTtcbiAgICBzZXFzID0gW107XG4gICAgaWYgKE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcuY2FsbCh0ZXh0KSA9PT0gJ1tvYmplY3QgQXJyYXldJykge1xuICAgICAgbGluZXMgPSB0ZXh0O1xuICAgIH0gZWxzZSB7XG4gICAgICBsaW5lcyA9IHRleHQuc3BsaXQoXCJcXG5cIik7XG4gICAgfVxuICAgIGlmIChsaW5lc1swXS5zbGljZSgwLCA2KSA9PT0gIVwiQ0xVU1RBTFwiKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJJbnZhbGlkIENMVVNUQUwgSGVhZGVyXCIpO1xuICAgIH1cbiAgICBrID0gMDtcbiAgICBibG9ja3N0YXRlID0gMTtcbiAgICBzZXFDb3VudGVyID0gMDtcbiAgICB3aGlsZSAoayA8IGxpbmVzLmxlbmd0aCkge1xuICAgICAgaysrO1xuICAgICAgbGluZSA9IGxpbmVzW2tdO1xuICAgICAgaWYgKChsaW5lID09IG51bGwpIHx8IGxpbmUubGVuZ3RoID09PSAwKSB7XG4gICAgICAgIGJsb2Nrc3RhdGUgPSAxO1xuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cbiAgICAgIGlmIChsaW5lLnRyaW0oKS5sZW5ndGggPT09IDApIHtcbiAgICAgICAgYmxvY2tzdGF0ZSA9IDE7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgaWYgKFN0ci5jb250YWlucyhsaW5lLCBcIipcIikpIHtcbiAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoYmxvY2tzdGF0ZSA9PT0gMSkge1xuICAgICAgICAgIHNlcUNvdW50ZXIgPSAwO1xuICAgICAgICAgIGJsb2Nrc3RhdGUgPSAwO1xuICAgICAgICB9XG4gICAgICAgIHJlZ2V4ID0gL14oPzpcXHMqKShcXFMrKSg/OlxccyspKFxcUyspKD86XFxzKikoXFxkKikoPzpcXHMqfCQpL2c7XG4gICAgICAgIG1hdGNoID0gcmVnZXguZXhlYyhsaW5lKTtcbiAgICAgICAgaWYgKG1hdGNoICE9IG51bGwpIHtcbiAgICAgICAgICBsYWJlbCA9IG1hdGNoWzFdO1xuICAgICAgICAgIHNlcXVlbmNlID0gbWF0Y2hbMl07XG4gICAgICAgICAgaWYgKHNlcUNvdW50ZXIgPj0gc2Vxcy5sZW5ndGgpIHtcbiAgICAgICAgICAgIHNlcXMucHVzaChuZXcgU2VxKHNlcXVlbmNlLCBsYWJlbCwgc2VxQ291bnRlcikpO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBzZXFzW3NlcUNvdW50ZXJdLnNlcSArPSBzZXF1ZW5jZTtcbiAgICAgICAgICB9XG4gICAgICAgICAgc2VxQ291bnRlcisrO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGNvbnNvbGUubG9nKGxpbmUpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBzZXFzO1xuICB9O1xuXG4gIHJldHVybiBDbHVzdGFsO1xuXG59KShHZW5lcmljUmVhZGVyKTtcbiIsIi8vIEdlbmVyYXRlZCBieSBDb2ZmZWVTY3JpcHQgMS44LjBcbm1vZHVsZS5leHBvcnRzLnBhcnNlID0gcmVxdWlyZShcIi4vcGFyc2VyXCIpO1xuXG5tb2R1bGUuZXhwb3J0cy53cml0ZXIgPSByZXF1aXJlKFwiLi93cml0ZXJcIik7XG4iLCJpZiAodHlwZW9mIGJpb2pzID09PSAndW5kZWZpbmVkJykge1xuICBiaW9qcyA9IHt9O1xufVxuaWYgKHR5cGVvZiBiaW9qcy52aXMgPT09ICd1bmRlZmluZWQnKSB7XG4gIGJpb2pzLnZpcyA9IHt9O1xufVxuLy8gdXNlIHR3byBuYW1lc3BhY2VzXG53aW5kb3cubXNhID0gYmlvanMudmlzLm1zYSA9IG1vZHVsZS5leHBvcnRzID0gcmVxdWlyZSgnLi9pbmRleCcpO1xuXG4vLyBUT0RPOiBob3cgc2hvdWxkIHRoaXMgYmUgYnVuZGxlZFxuXG5pZiAodHlwZW9mIGJpb2pzLmlvID09PSAndW5kZWZpbmVkJykge1xuICBiaW9qcy5pbyA9IHt9O1xufVxuLy8ganVzdCBidW5kbGUgdGhlIHR3byBwYXJzZXJzXG53aW5kb3cuYmlvanMuaW8uZmFzdGEgPSByZXF1aXJlKFwiYmlvanMtaW8tZmFzdGFcIik7XG53aW5kb3cuYmlvanMuaW8uY2x1c3RhbCA9IHJlcXVpcmUoXCJiaW9qcy1pby1jbHVzdGFsXCIpO1xud2luZG93LmJpb2pzLnhociA9IHJlcXVpcmUoXCJuZXRzXCIpO1xuXG4vLyBzaW11bGF0ZSBzdGFuZGFsb25lIGZsYWdcbndpbmRvdy5iaW9qc1Zpc01zYSA9IHdpbmRvdy5tc2E7XG5cbnJlcXVpcmUoJy4vYnVpbGQvbXNhLmNzcycpO1xuIiwidmFyIHJlcSA9IHJlcXVpcmUoJ3JlcXVlc3QnKVxuXG5tb2R1bGUuZXhwb3J0cyA9IE5ldHNcblxuZnVuY3Rpb24gTmV0cyh1cmksIG9wdHMsIGNiKSB7XG4gIHJlcSh1cmksIG9wdHMsIGNiKVxufSJdfQ==
+
+
+
+// this is a way how you use a bundled file parser
+biojs.io.clustal.read("#", function(seqs){
+var opts = {};
+
+// set your custom properties
+// @see: https://github.com/greenify/biojs-vis-msa/tree/master/src/g 
+
+var jalviewData = JSON.parse(document.getElementById("seqData").value); 
+opts.seqs = jalviewData['seqs'];
+
+opts.el = document.getElementById("yourDiv");
+opts.vis = {conserv: false, overviewbox: false, labelId: false};
+opts.zoomer = {alignmentHeight: 225, labelWidth: 130,labelFontsize: "13px",labelIdLength: 20,   menuFontsize: "12px",menuMarginLeft: "3px", menuPadding: "3px 4px 3px 4px", menuItemFontsize: "14px", menuItemLineHeight: "14px"};
+
+
+
+// init msa
+var m = new msa.msa(opts);
+
+m.g.colorscheme.set("scheme", jalviewData['globalColorScheme']);
+
+var x = 0;
+jalviewData.seqs.forEach( function (seq)
+{
+m.seqs.at(x++).set("features", new msa.model.featurecol(seq.features));
+});
+
+// the menu is independent to the MSA container
+var menuOpts = {};
+menuOpts.el = document.getElementById('div');
+menuOpts.msa = m;
+var defMenu = new msa.menu.defaultmenu(menuOpts);
+m.addView("menu", defMenu);
+
+// call render at the end to display the whole MSA
+m.render();
+toggleMenuVisibility(); 
+toggleMenuVisibility(); 
+});
+</script>
diff --git a/examples/testdata/uniref50_seqref.jva b/examples/testdata/uniref50_seqref.jva
new file mode 100644 (file)
index 0000000..5e7d2da
--- /dev/null
@@ -0,0 +1,176 @@
+JALVIEW_ANNOTATION
+# Created: Fri Jun 21 13:44:50 BST 2013
+
+BAR_GRAPH      Conservation    Conservation of total alignment less than 25% gaps      1.0,1,1.0,[5d1500]|3.0,3,3.0,[7d3f00]|0.0,-,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,-,0.0,[4d0000]|1.0,1,1.0,[5d1500]|2.0,2,2.0,[6d2a00]|0.0,0,0.0,[4d0000]|1.0,1,1.0,[5d1500]|0.0,0,0.0,[4d0000]|1.0,1,1.0,[5d1500]|0.0,0,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,0,0.0,[4d0000]|1.0,1,1.0,[5d1500]|0.0,0,0.0,[4d0000]|0.0,0,0.0,[4d0000]|0.0,0,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,0,0.0,[4d0000]|0.0,0,0.0,[4d0000]|0.0,0,0.0,[4d0000]|0.0,0,0.0,[4d0000]|0.0,0,0.0,[4d0000]|0.0,0,0.0,[4d0000]|0.0,0,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,-,0.0,[4d0000]|2.0,2,2.0,[6d2a00]|1.0,1,1.0,[5d1500]|1.0,1,1.0,[5d1500]|1.0,1,1.0,[5d1500]|0.0,0,0.0,[4d0000]|0.0,0,0.0,[4d0000]|1.0,1,1.0,[5d1500]|0.0,-,0.0,[4d0000]|2.0,2,2.0,[6d2a00]|0.0,-,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,0,0.0,[4d0000]|0.0,0,0.0,[4d0000]|0.0,0,0.0,[4d0000]|1.0,1,1.0,[5d1500]|0.0,0,0.0,[4d0000]|11.0,*,11.0,[ffe600]|7.0,7,7.0,[be9200]|11.0,*,11.0,[ffe600]|6.0,6,6.0,[ae7d00]|11.0,*,11.0,[ffe600]|6.0,6,6.0,[ae7d00]|8.0,8,8.0,[cea700]|9.0,9,9.0,[dfbc00]|11.0,*,11.0,[ffe600]|11.0,*,11.0,[ffe600]|7.0,7,7.0,[be9200]|11.0,*,11.0,[ffe600]|3.0,3,3.0,[7d3f00]|6.0,6,6.0,[ae7d00]|11.0,*,11.0,[ffe600]|7.0,7,7.0,[be9200]|7.0,7,7.0,[be9200]|9.0,9,9.0,[dfbc00]|5.0,5,5.0,[9e6800]|6.0,6,6.0,[ae7d00]|8.0,8,8.0,[cea700]|11.0,*,11.0,[ffe600]|11.0,*,11.0,[ffe600]|9.0,9,9.0,[dfbc00]|11.0,*,11.0,[ffe600]|11.0,*,11.0,[ffe600]|3.0,3,3.0,[7d3f00]|11.0,*,11.0,[ffe600]|11.0,*,11.0,[ffe600]|11.0,*,11.0,[ffe600]|3.0,3,3.0,[7d3f00]|11.0,*,11.0,[ffe600]|5.0,5,5.0,[9e6800]|9.0,9,9.0,[dfbc00]|11.0,*,11.0,[ffe600]|11.0,*,11.0,[ffe600]|9.0,9,9.0,[dfbc00]|11.0,*,11.0,[ffe600]|11.0,*,11.0,[ffe600]|11.0,*,11.0,[ffe600]|11.0,*,11.0,[ffe600]|11.0,*,11.0,[ffe600]|11.0,*,11.0,[ffe600]|11.0,*,11.0,[ffe600]|11.0,*,11.0,[ffe600]|11.0,*,11.0,[ffe600]|11.0,*,11.0,[ffe600]|11.0,*,11.0,[ffe600]|11.0,*,11.0,[ffe600]|11.0,*,11.0,[ffe600]|9.0,9,9.0,[dfbc00]|3.0,3,3.0,[7d3f00]|7.0,7,7.0,[be9200]|11.0,*,11.0,[ffe600]|3.0,3,3.0,[7d3f00]|9.0,9,9.0,[dfbc00]|8.0,8,8.0,[cea700]|11.0,*,11.0,[ffe600]|6.0,6,6.0,[ae7d00]|11.0,*,11.0,[ffe600]|4.0,4,4.0,[8d5300]|5.0,5,5.0,[9e6800]|9.0,9,9.0,[dfbc00]|11.0,*,11.0,[ffe600]|8.0,8,8.0,[cea700]|9.0,9,9.0,[dfbc00]|0.0,0,0.0,[4d0000]|1.0,1,1.0,[5d1500]|0.0,0,0.0,[4d0000]|0.0,0,0.0,[4d0000]|0.0,0,0.0,[4d0000]|3.0,3,3.0,[7d3f00]|2.0,2,2.0,[6d2a00]|3.0,3,3.0,[7d3f00]|2.0,2,2.0,[6d2a00]|2.0,2,2.0,[6d2a00]|2.0,2,2.0,[6d2a00]|1.0,1,1.0,[5d1500]|3.0,3,3.0,[7d3f00]|2.0,2,2.0,[6d2a00]|2.0,2,2.0,[6d2a00]|0.0,0,0.0,[4d0000]|1.0,1,1.0,[5d1500]|4.0,4,4.0,[8d5300]|3.0,3,3.0,[7d3f00]|1.0,1,1.0,[5d1500]|2.0,2,2.0,[6d2a00]|3.0,3,3.0,[7d3f00]|2.0,2,2.0,[6d2a00]|5.0,5,5.0,[9e6800]|3.0,3,3.0,[7d3f00]|3.0,3,3.0,[7d3f00]|0.0,0,0.0,[4d0000]|0.0,0,0.0,[4d0000]|1.0,1,1.0,[5d1500]|0.0,0,0.0,[4d0000]|0.0,-,0.0,[4d0000]|0.0,-,0.0,[4d0000]|
+BAR_GRAPH      Quality Alignment Quality based on Blosum62 scores      29.151815,29.151815,[cba200]|30.574812,30.574812,[d1aa00]|14.400199,14.400199,[8b5000]|16.93248,16.93248,[965e00]|2.1220763,2.1220763,[560c00]|2.2049963,2.2049963,[560c00]|3.647952,3.647952,[5c1400]|4.814642,4.814642,[611b00]|18.351393,18.351393,[9c6600]|17.532412,17.532412,[996200]|7.5054855,7.5054855,[6d2a00]|15.490477,15.490477,[905600]|14.241707,14.241707,[8a4f00]|9.905579,9.905579,[773700]|20.354017,20.354017,[a57100]|26.870352,26.870352,[c19600]|24.969187,24.969187,[b98b00]|27.419409,27.419409,[c39900]|15.350427,15.350427,[8f5600]|21.116522,21.116522,[a87600]|9.34032,9.34032,[753400]|14.1895275,14.1895275,[8a4f00]|10.104505,10.104505,[783800]|8.587312,8.587312,[723000]|18.694708,18.694708,[9e6800]|11.420612,11.420612,[7e4000]|6.8467255,6.8467255,[6a2600]|17.449827,17.449827,[986100]|16.825909,16.825909,[955e00]|2.4334474,2.4334474,[570e00]|15.685622,15.685622,[915700]|9.836516,9.836516,[773700]|3.4712791,3.4712791,[5c1300]|4.531816,4.531816,[601900]|7.8744216,7.8744216,[6f2c00]|0.0,0.0,[4d0000]|9.01113,9.01113,[743200]|3.174218,3.174218,[5a1200]|2.0395048,2.0395048,[550b00]|2.1654668,2.1654668,[560c00]|21.517344,21.517344,[aa7800]|15.738462,15.738462,[915800]|14.844854,14.844854,[8d5300]|22.159096,22.159096,[ad7c00]|13.956608,13.956608,[894e00]|20.147892,20.147892,[a47000]|25.067545,25.067545,[b98c00]|2.0943506,2.0943506,[560c00]|20.30842,20.30842,[a57100]|10.254437,10.254437,[793900]|6.5836596,6.5836596,[692500]|19.446732,19.446732,[a16c00]|6.2202287,6.2202287,[672300]|9.796006,9.796006,[773700]|6.0385494,6.0385494,[672200]|13.963727,13.963727,[894e00]|13.838549,13.838549,[884d00]|18.48424,18.48424,[9d6700]|18.302633,18.302633,[9c6600]|41.172745,41.172745,[ffe600]|32.59908,32.59908,[dab600]|41.172745,41.172745,[ffe600]|34.495945,34.495945,[e2c000]|41.172745,41.172745,[ffe600]|33.501804,33.501804,[debb00]|27.034466,27.034466,[c29700]|40.020737,40.020737,[fadf00]|41.172745,41.172745,[ffe600]|41.172745,41.172745,[ffe600]|29.252739,29.252739,[cba300]|41.172745,41.172745,[ffe600]|14.935498,14.935498,[8d5300]|0.88449144,0.88449144,[500500]|41.172745,41.172745,[ffe600]|20.81991,20.81991,[a77400]|31.73346,31.73346,[d6b100]|29.757969,29.757969,[cea600]|16.279755,16.279755,[935b00]|32.647984,32.647984,[dab600]|38.352337,38.352337,[f3d600]|41.172745,41.172745,[ffe600]|41.172745,41.172745,[ffe600]|39.6125,39.6125,[f8dd00]|41.172745,41.172745,[ffe600]|41.172745,41.172745,[ffe600]|9.671661,9.671661,[763600]|41.172745,41.172745,[ffe600]|41.172745,41.172745,[ffe600]|41.172745,41.172745,[ffe600]|11.17141,11.17141,[7d3e00]|41.172745,41.172745,[ffe600]|3.2054348,3.2054348,[5a1200]|38.569283,38.569283,[f4d700]|41.172745,41.172745,[ffe600]|41.172745,41.172745,[ffe600]|39.707684,39.707684,[f9dd00]|41.172745,41.172745,[ffe600]|41.172745,41.172745,[ffe600]|41.172745,41.172745,[ffe600]|41.172745,41.172745,[ffe600]|41.172745,41.172745,[ffe600]|41.172745,41.172745,[ffe600]|41.172745,41.172745,[ffe600]|41.172745,41.172745,[ffe600]|41.172745,41.172745,[ffe600]|41.172745,41.172745,[ffe600]|41.172745,41.172745,[ffe600]|41.172745,41.172745,[ffe600]|41.172745,41.172745,[ffe600]|39.447525,39.447525,[f8dc00]|21.229778,21.229778,[a97600]|27.779364,27.779364,[c59b00]|41.172745,41.172745,[ffe600]|24.149143,24.149143,[b58700]|39.447525,39.447525,[f8dc00]|33.857655,33.857655,[dfbd00]|41.172745,41.172745,[ffe600]|28.69976,28.69976,[c9a000]|41.172745,41.172745,[ffe600]|14.611241,14.611241,[8c5100]|32.346375,32.346375,[d9b400]|39.24055,39.24055,[f7db00]|41.172745,41.172745,[ffe600]|38.352337,38.352337,[f3d600]|38.569283,38.569283,[f4d700]|22.374107,22.374107,[ae7d00]|36.98438,36.98438,[edce00]|12.34415,12.34415,[824500]|11.811655,11.811655,[804200]|14.798897,14.798897,[8d5200]|36.60138,36.60138,[ebcc00]|14.0762415,14.0762415,[8a4e00]|36.817875,36.817875,[eccd00]|36.53897,36.53897,[ebcc00]|37.583706,37.583706,[efd100]|35.536423,35.536423,[e7c600]|18.004925,18.004925,[9b6400]|37.633095,37.633095,[f0d200]|34.738743,34.738743,[e3c200]|36.451622,36.451622,[ebcb00]|19.948801,19.948801,[a36f00]|28.285,28.285,[c79e00]|35.93151,35.93151,[e8c800]|36.817875,36.817875,[eccd00]|26.762383,26.762383,[c19500]|36.37101,36.37101,[eacb00]|36.413998,36.413998,[eacb00]|37.583706,37.583706,[efd100]|36.282784,36.282784,[eaca00]|34.729824,34.729824,[e3c200]|36.413998,36.413998,[eacb00]|24.348654,24.348654,[b68800]|24.349539,24.349539,[b68800]|34.091675,34.091675,[e0be00]|21.114677,21.114677,[a87600]|13.676696,13.676696,[884c00]|2.1107035,2.1107035,[560c00]|
+BAR_GRAPH      Consensus       PID     80.0,M,M 80%|80.0,A,A 80%|20.0,+,[AST] 20%|53.333332,T,T 53%|0.0,-,- 0%|0.0,-,- 0%|13.333333,T,T 13%|20.0,P,P 20%|40.0,A,A 40%|40.0,L,L 40%|46.666668,S,S 46%|40.0,G,G 40%|33.333332,T,T 33%|20.0,+,[AIM] 20%|33.333332,V,V 33%|66.666664,S,S 66%|66.666664,T,T 66%|66.666664,S,S 66%|66.666664,F,F 66%|46.666668,L,L 46%|46.666668,R,R 46%|46.666668,R,R 46%|26.666666,Q,Q 26%|46.666668,P,P 46%|40.0,A,A 40%|60.0,P,P 60%|26.666666,T,T 26%|53.333332,S,S 53%|40.0,L,L 40%|20.0,R,R 20%|26.666666,S,S 26%|33.333332,L,L 33%|40.0,P,P 40%|26.666666,S,S 26%|33.333332,+,[AN] 33%|26.666666,V,V 26%|33.333332,G,G 33%|20.0,+,[EQ] 20%|0.0,-,- 0%|0.0,-,- 0%|33.333332,+,[AS] 33%|53.333332,L,L 53%|60.0,F,F 60%|73.333336,G,G 73%|66.666664,L,L 66%|66.666664,K,K 66%|53.333332,S,S 53%|0.0,-,- 0%|40.0,S,S 40%|26.666666,T,T 26%|20.0,A,A 20%|66.666664,R,R 66%|40.0,G,G 40%|53.333332,G,G 53%|46.666668,R,R 46%|33.333332,V,V 33%|46.666668,T,T 46%|53.333332,A,A 53%|66.666664,M,M 66%|100.0,A,A 100%|53.333332,T,T 53%|100.0,Y,Y 100%|86.666664,K,K 86%|100.0,V,V 100%|86.666664,K,K 86%|66.666664,L,L 66%|80.0,I,I 80%|100.0,T,T 100%|100.0,P,P 100%|66.666664,E,E 66%|100.0,G,G 100%|46.666668,E,E 46%|46.666668,Q,Q 46%|100.0,E,E 100%|46.666668,F,F 46%|60.0,E,E 60%|86.666664,C,C 86%|66.666664,P,P 66%|80.0,D,D 80%|93.333336,D,D 93%|100.0,V,V 100%|100.0,Y,Y 100%|66.666664,I,I 66%|100.0,L,L 100%|100.0,D,D 100%|46.666668,A,A 46%|100.0,A,A 100%|100.0,E,E 100%|100.0,E,E 100%|53.333332,A,A 53%|100.0,G,G 100%|60.0,I,I 60%|93.333336,D,D 93%|100.0,L,L 100%|100.0,P,P 100%|93.333336,Y,Y 93%|100.0,S,S 100%|100.0,C,C 100%|100.0,R,R 100%|100.0,A,A 100%|100.0,G,G 100%|100.0,S,S 100%|100.0,C,C 100%|100.0,S,S 100%|100.0,S,S 100%|100.0,C,C 100%|100.0,A,A 100%|100.0,G,G 100%|100.0,K,K 100%|80.0,V,V 80%|60.0,V,V 60%|53.333332,S,S 53%|100.0,G,G 100%|60.0,S,S 60%|80.0,V,V 80%|80.0,D,D 80%|100.0,Q,Q 100%|66.666664,S,S 66%|100.0,D,D 100%|46.666668,+,[GQ] 46%|73.333336,S,S 73%|93.333336,F,F 93%|100.0,L,L 100%|93.333336,D,D 93%|93.333336,D,D 93%|53.333332,D,D 53%|93.333336,Q,Q 93%|53.333332,I,I 53%|40.0,A,A 40%|53.333332,E,E 53%|93.333336,G,G 93%|46.666668,W,W 46%|93.333336,V,V 93%|93.333336,L,L 93%|93.333336,T,T 93%|93.333336,C,C 93%|53.333332,V,V 53%|93.333336,A,A 93%|86.666664,Y,Y 86%|93.333336,P,P 93%|60.0,T,T 60%|73.333336,S,S 73%|93.333336,D,D 93%|93.333336,V,V 93%|66.666664,T,T 66%|93.333336,I,I 93%|93.333336,E,E 93%|93.333336,T,T 93%|93.333336,H,H 93%|86.666664,K,K 86%|93.333336,E,E 93%|73.333336,E,E 73%|60.0,E,E 60%|73.333336,L,L 73%|46.666668,T,T 46%|40.0,A,A 40%|0.0,-,- 0%|
+
+SEQUENCE_REF   FER2_ARATH
+LINE_GRAPH     IUPredWS (Long) <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.1671,0.1671,[72144e]|0.2129,0.2129,[72144e]|0.247,0.247,[72144e]|0.1554,0.1554,[72144e]|0.1162,0.1162,[72144e]|0.1449,0.1449,[72144e]|0.1731,0.1731,[72144e]|0.2064,0.2064,[72144e]|0.2436,0.2436,[72144e]|0.2715,0.2715,[72144e]|0.3249,0.3249,[72144e]|0.2783,0.2783,[72144e]|0.3019,0.3019,[72144e]|0.2399,0.2399,[72144e]|0.2364,0.2364,[72144e]|0.2645,0.2645,[72144e]|0.2609,0.2609,[72144e]|0.2988,0.2988,[72144e]|0.2064,0.2064,[72144e]|0.2094,0.2094,[72144e]|0.2849,0.2849,[72144e]|0.3426,0.3426,[72144e]|0.3529,0.3529,[72144e]|0.3426,0.3426,[72144e]|0.2817,0.2817,[72144e]|0.2783,0.2783,[72144e]|0.3426,0.3426,[72144e]|0.2715,0.2715,[72144e]|0.2609,0.2609,[72144e]|0.2503,0.2503,[72144e]|0.2364,0.2364,[72144e]|0.2364,0.2364,[72144e]|0.1791,0.1791,[72144e]|0.247,0.247,[72144e]|0.2503,0.2503,[72144e]|0.3149,0.3149,[72144e]|0.3149,0.3149,[72144e]|0.2645,0.2645,[72144e]|0.3356,0.3356,[72144e]|0.2752,0.2752,[72144e]|0.346,0.346,[72144e]|0.346,0.346,[72144e]|0.3426,0.3426,[72144e]|0.2645,0.2645,[72144e]|0.247,0.247,[72144e]|0.1969,0.1969,[72144e]|0.2503,0.2503,[72144e]|0.2503,0.2503,[72144e]|0.1852,0.1852,[72144e]|0.2541,0.2541,[72144e]|0.2951,0.2951,[72144e]|0.3182,0.3182,[72144e]|0.3215,0.3215,[72144e]|0.3392,0.3392,[72144e]|0.374,0.374,[72144e]|0.3948,0.3948,[72144e]|0.3392,0.3392,[72144e]|0.3599,0.3599,[72144e]|0.268,0.268,[72144e]|0.3426,0.3426,[72144e]|0.3599,0.3599,[72144e]|0.4051,0.4051,[72144e]|0.374,0.374,[72144e]|0.3249,0.3249,[72144e]|0.268,0.268,[72144e]|0.2783,0.2783,[72144e]|0.3019,0.3019,[72144e]|0.3286,0.3286,[72144e]|0.3117,0.3117,[72144e]|0.4292,0.4292,[72144e]|0.5296,0.5296,[72144e]|0.4901,0.4901,[72144e]|0.4582,0.4582,[72144e]|0.3667,0.3667,[72144e]|0.384,0.384,[72144e]|0.2918,0.2918,[72144e]|0.3182,0.3182,[72144e]|0.2193,0.2193,[72144e]|0.2752,0.2752,[72144e]|0.1643,0.1643,[72144e]|0.2541,0.2541,[72144e]|0.2034,0.2034,[72144e]|0.1852,0.1852,[72144e]|0.1702,0.1702,[72144e]|0.1399,0.1399,[72144e]|0.2034,0.2034,[72144e]|0.2541,0.2541,[72144e]|0.2292,0.2292,[72144e]|0.1881,0.1881,[72144e]|0.2129,0.2129,[72144e]|0.2292,0.2292,[72144e]|0.1643,0.1643,[72144e]|0.107,0.107,[72144e]|0.1251,0.1251,[72144e]|0.1251,0.1251,[72144e]|0.1759,0.1759,[72144e]|0.1115,0.1115,[72144e]|0.1731,0.1731,[72144e]|0.1501,0.1501,[72144e]|0.2164,0.2164,[72144e]|0.2328,0.2328,[72144e]|0.3286,0.3286,[72144e]|0.3286,0.3286,[72144e]|0.2645,0.2645,[72144e]|0.2034,0.2034,[72144e]|0.2193,0.2193,[72144e]|0.3286,0.3286,[72144e]|0.3529,0.3529,[72144e]|0.3631,0.3631,[72144e]|0.4292,0.4292,[72144e]|0.4541,0.4541,[72144e]|0.4766,0.4766,[72144e]|0.4864,0.4864,[72144e]|0.4652,0.4652,[72144e]|0.4685,0.4685,[72144e]|0.4051,0.4051,[72144e]|0.4087,0.4087,[72144e]|0.3117,0.3117,[72144e]|0.3321,0.3321,[72144e]|0.2918,0.2918,[72144e]|0.2164,0.2164,[72144e]|0.2503,0.2503,[72144e]|0.2364,0.2364,[72144e]|0.2292,0.2292,[72144e]|0.2503,0.2503,[72144e]|0.2918,0.2918,[72144e]|0.3053,0.3053,[72144e]|0.2258,0.2258,[72144e]|0.2292,0.2292,[72144e]|0.2129,0.2129,[72144e]|0.2064,0.2064,[72144e]|0.2164,0.2164,[72144e]|0.2436,0.2436,[72144e]|0.2436,0.2436,[72144e]|0.2193,0.2193,[72144e]|0.2258,0.2258,[72144e]|0.2503,0.2503,[72144e]|0.3117,0.3117,[72144e]|0.2884,0.2884,[72144e]|0.374,0.374,[72144e]|0.4149,0.4149,[72144e]|0.4292,0.4292,[72144e]|0.5098,0.5098,[72144e]|0.4476,0.4476,[72144e]|0.422,0.422,[72144e]|0.4017,0.4017,[72144e]|0.3494,0.3494,[72144e]|0.4087,0.4087,[72144e]|
+GRAPHLINE      IUPredWS (Long) 0.5     Above 0.5 indicates disorder    ff0000
+LINE_GRAPH     IUPredWS (Short)        <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.82,0.82,[329440]|0.7147,0.7147,[329440]|0.6715,0.6715,[329440]|0.6334,0.6334,[329440]|0.59,0.59,[329440]|0.4513,0.4513,[329440]|0.3263,0.3263,[329440]|0.2963,0.2963,[329440]|0.2657,0.2657,[329440]|0.2292,0.2292,[329440]|0.1998,0.1998,[329440]|0.2209,0.2209,[329440]|0.2865,0.2865,[329440]|0.2122,0.2122,[329440]|0.2041,0.2041,[329440]|0.1667,0.1667,[329440]|0.2602,0.2602,[329440]|0.2558,0.2558,[329440]|0.1844,0.1844,[329440]|0.2483,0.2483,[329440]|0.2292,0.2292,[329440]|0.2657,0.2657,[329440]|0.2748,0.2748,[329440]|0.2748,0.2748,[329440]|0.2865,0.2865,[329440]|0.3762,0.3762,[329440]|0.3847,0.3847,[329440]|0.2786,0.2786,[329440]|0.27,0.27,[329440]|0.1878,0.1878,[329440]|0.1805,0.1805,[329440]|0.1805,0.1805,[329440]|0.1532,0.1532,[329440]|0.2255,0.2255,[329440]|0.1958,0.1958,[329440]|0.2786,0.2786,[329440]|0.2657,0.2657,[329440]|0.2748,0.2748,[329440]|0.3491,0.3491,[329440]|0.2531,0.2531,[329440]|0.3456,0.3456,[329440]|0.3491,0.3491,[329440]|0.3146,0.3146,[329440]|0.282,0.282,[329440]|0.27,0.27,[329440]|0.1998,0.1998,[329440]|0.2558,0.2558,[329440]|0.282,0.282,[329440]|0.2602,0.2602,[329440]|0.2602,0.2602,[329440]|0.1878,0.1878,[329440]|0.1844,0.1844,[329440]|0.2122,0.2122,[329440]|0.2292,0.2292,[329440]|0.2602,0.2602,[329440]|0.2786,0.2786,[329440]|0.282,0.282,[329440]|0.3005,0.3005,[329440]|0.2385,0.2385,[329440]|0.3184,0.3184,[329440]|0.2255,0.2255,[329440]|0.2748,0.2748,[329440]|0.3225,0.3225,[329440]|0.3762,0.3762,[329440]|0.3225,0.3225,[329440]|0.3225,0.3225,[329440]|0.2865,0.2865,[329440]|0.2865,0.2865,[329440]|0.3263,0.3263,[329440]|0.3717,0.3717,[329440]|0.4037,0.4037,[329440]|0.4245,0.4245,[329440]|0.4116,0.4116,[329440]|0.3578,0.3578,[329440]|0.3535,0.3535,[329440]|0.2748,0.2748,[329440]|0.2963,0.2963,[329440]|0.2167,0.2167,[329440]|0.3096,0.3096,[329440]|0.2255,0.2255,[329440]|0.3225,0.3225,[329440]|0.1732,0.1732,[329440]|0.1495,0.1495,[329440]|0.1088,0.1088,[329440]|0.1456,0.1456,[329440]|0.2041,0.2041,[329440]|0.1532,0.1532,[329440]|0.2041,0.2041,[329440]|0.1878,0.1878,[329440]|0.1322,0.1322,[329440]|0.1349,0.1349,[329440]|0.1205,0.1205,[329440]|0.0965,0.0965,[329440]|0.0884,0.0884,[329440]|0.0643,0.0643,[329440]|0.0935,0.0935,[329440]|0.0771,0.0771,[329440]|0.1088,0.1088,[329440]|0.0587,0.0587,[329440]|0.0991,0.0991,[329440]|0.0991,0.0991,[329440]|0.1958,0.1958,[329440]|0.2255,0.2255,[329440]|0.2602,0.2602,[329440]|0.2657,0.2657,[329440]|0.1844,0.1844,[329440]|0.2292,0.2292,[329440]|0.2483,0.2483,[329440]|0.2657,0.2657,[329440]|0.3939,0.3939,[329440]|0.4245,0.4245,[329440]|0.4037,0.4037,[329440]|0.4203,0.4203,[329440]|0.4967,0.4967,[329440]|0.5473,0.5473,[329440]|0.4781,0.4781,[329440]|0.4282,0.4282,[329440]|0.3578,0.3578,[329440]|0.4203,0.4203,[329440]|0.3491,0.3491,[329440]|0.2913,0.2913,[329440]|0.2657,0.2657,[329440]|0.1805,0.1805,[329440]|0.2167,0.2167,[329440]|0.208,0.208,[329440]|0.2963,0.2963,[329440]|0.3806,0.3806,[329440]|0.2963,0.2963,[329440]|0.2255,0.2255,[329440]|0.1456,0.1456,[329440]|0.1635,0.1635,[329440]|0.1766,0.1766,[329440]|0.1698,0.1698,[329440]|0.1349,0.1349,[329440]|0.1532,0.1532,[329440]|0.2292,0.2292,[329440]|0.2483,0.2483,[329440]|0.2432,0.2432,[329440]|0.2041,0.2041,[329440]|0.3005,0.3005,[329440]|0.4037,0.4037,[329440]|0.4749,0.4749,[329440]|0.59,0.59,[329440]|0.5941,0.5941,[329440]|0.6442,0.6442,[329440]|0.6827,0.6827,[329440]|0.7034,0.7034,[329440]|0.8001,0.8001,[329440]|
+GRAPHLINE      IUPredWS (Short)        0.5     Above 0.5 indicates disorder    ff0000
+
+SEQUENCE_REF   Q93Z60_ARATH
+LINE_GRAPH     IUPredWS (Long) <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.1671,0.1671,[72144e]|0.2129,0.2129,[72144e]|0.247,0.247,[72144e]|0.1554,0.1554,[72144e]|0.1162,0.1162,[72144e]|0.1449,0.1449,[72144e]|0.1731,0.1731,[72144e]|0.2064,0.2064,[72144e]|0.2436,0.2436,[72144e]|0.2715,0.2715,[72144e]|0.3215,0.3215,[72144e]|0.2752,0.2752,[72144e]|0.2988,0.2988,[72144e]|0.2364,0.2364,[72144e]|0.2328,0.2328,[72144e]|0.2575,0.2575,[72144e]|0.2575,0.2575,[72144e]|0.2918,0.2918,[72144e]|0.2034,0.2034,[72144e]|0.2034,0.2034,[72144e]|0.2817,0.2817,[72144e]|0.3392,0.3392,[72144e]|0.3494,0.3494,[72144e]|0.3426,0.3426,[72144e]|0.2783,0.2783,[72144e]|0.2752,0.2752,[72144e]|0.3426,0.3426,[72144e]|0.2715,0.2715,[72144e]|0.2609,0.2609,[72144e]|0.247,0.247,[72144e]|0.2364,0.2364,[72144e]|0.2364,0.2364,[72144e]|0.1791,0.1791,[72144e]|0.247,0.247,[72144e]|0.2503,0.2503,[72144e]|0.3215,0.3215,[72144e]|0.3215,0.3215,[72144e]|0.2715,0.2715,[72144e]|0.3426,0.3426,[72144e]|0.2817,0.2817,[72144e]|0.3566,0.3566,[72144e]|0.3566,0.3566,[72144e]|0.3529,0.3529,[72144e]|0.2715,0.2715,[72144e]|0.2575,0.2575,[72144e]|0.2064,0.2064,[72144e]|0.2645,0.2645,[72144e]|0.2645,0.2645,[72144e]|0.1942,0.1942,[72144e]|0.2645,0.2645,[72144e]|0.3053,0.3053,[72144e]|0.3286,0.3286,[72144e]|0.3321,0.3321,[72144e]|0.3494,0.3494,[72144e]|0.384,0.384,[72144e]|0.4051,0.4051,[72144e]|0.3494,0.3494,[72144e]|0.3667,0.3667,[72144e]|0.2752,0.2752,[72144e]|0.346,0.346,[72144e]|0.3631,0.3631,[72144e]|0.4051,0.4051,[72144e]|0.3704,0.3704,[72144e]|0.3215,0.3215,[72144e]|0.268,0.268,[72144e]|0.2783,0.2783,[72144e]|0.2988,0.2988,[72144e]|0.3249,0.3249,[72144e]|0.3053,0.3053,[72144e]|0.422,0.422,[72144e]|0.5139,0.5139,[72144e]|0.4801,0.4801,[72144e]|0.4476,0.4476,[72144e]|0.3566,0.3566,[72144e]|0.374,0.374,[72144e]|0.2849,0.2849,[72144e]|0.3087,0.3087,[72144e]|0.2129,0.2129,[72144e]|0.2645,0.2645,[72144e]|0.1583,0.1583,[72144e]|0.247,0.247,[72144e]|0.2002,0.2002,[72144e]|0.1852,0.1852,[72144e]|0.1702,0.1702,[72144e]|0.1399,0.1399,[72144e]|0.2002,0.2002,[72144e]|0.247,0.247,[72144e]|0.2224,0.2224,[72144e]|0.1852,0.1852,[72144e]|0.2094,0.2094,[72144e]|0.2258,0.2258,[72144e]|0.1611,0.1611,[72144e]|0.1092,0.1092,[72144e]|0.1251,0.1251,[72144e]|0.1251,0.1251,[72144e]|0.1759,0.1759,[72144e]|0.1115,0.1115,[72144e]|0.1731,0.1731,[72144e]|0.1501,0.1501,[72144e]|0.2129,0.2129,[72144e]|0.2292,0.2292,[72144e]|0.3249,0.3249,[72144e]|0.3215,0.3215,[72144e]|0.2645,0.2645,[72144e]|0.2002,0.2002,[72144e]|0.2193,0.2193,[72144e]|0.3286,0.3286,[72144e]|0.3249,0.3249,[72144e]|0.3117,0.3117,[72144e]|0.391,0.391,[72144e]|0.4051,0.4051,[72144e]|0.384,0.384,[72144e]|0.3774,0.3774,[72144e]|0.4292,0.4292,[72144e]|0.4901,0.4901,[72144e]|0.4801,0.4801,[72144e]|0.4582,0.4582,[72144e]|0.4409,0.4409,[72144e]|
+GRAPHLINE      IUPredWS (Long) 0.5     Above 0.5 indicates disorder    ff0000
+LINE_GRAPH     IUPredWS (Short)        <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.82,0.82,[329440]|0.7147,0.7147,[329440]|0.6715,0.6715,[329440]|0.6334,0.6334,[329440]|0.59,0.59,[329440]|0.4513,0.4513,[329440]|0.3263,0.3263,[329440]|0.2963,0.2963,[329440]|0.2657,0.2657,[329440]|0.2292,0.2292,[329440]|0.1998,0.1998,[329440]|0.2209,0.2209,[329440]|0.2865,0.2865,[329440]|0.2122,0.2122,[329440]|0.2041,0.2041,[329440]|0.1667,0.1667,[329440]|0.2602,0.2602,[329440]|0.2558,0.2558,[329440]|0.1844,0.1844,[329440]|0.2483,0.2483,[329440]|0.2292,0.2292,[329440]|0.2657,0.2657,[329440]|0.2748,0.2748,[329440]|0.2748,0.2748,[329440]|0.2865,0.2865,[329440]|0.3762,0.3762,[329440]|0.3847,0.3847,[329440]|0.2786,0.2786,[329440]|0.27,0.27,[329440]|0.1878,0.1878,[329440]|0.1805,0.1805,[329440]|0.1805,0.1805,[329440]|0.1532,0.1532,[329440]|0.2255,0.2255,[329440]|0.1958,0.1958,[329440]|0.2786,0.2786,[329440]|0.2657,0.2657,[329440]|0.2748,0.2748,[329440]|0.3491,0.3491,[329440]|0.2531,0.2531,[329440]|0.3456,0.3456,[329440]|0.3491,0.3491,[329440]|0.3146,0.3146,[329440]|0.282,0.282,[329440]|0.27,0.27,[329440]|0.1998,0.1998,[329440]|0.2558,0.2558,[329440]|0.282,0.282,[329440]|0.2602,0.2602,[329440]|0.2602,0.2602,[329440]|0.1878,0.1878,[329440]|0.1844,0.1844,[329440]|0.2122,0.2122,[329440]|0.2292,0.2292,[329440]|0.2602,0.2602,[329440]|0.2786,0.2786,[329440]|0.282,0.282,[329440]|0.3005,0.3005,[329440]|0.2385,0.2385,[329440]|0.3184,0.3184,[329440]|0.2255,0.2255,[329440]|0.2748,0.2748,[329440]|0.3225,0.3225,[329440]|0.3762,0.3762,[329440]|0.3225,0.3225,[329440]|0.3225,0.3225,[329440]|0.2865,0.2865,[329440]|0.2865,0.2865,[329440]|0.3263,0.3263,[329440]|0.3717,0.3717,[329440]|0.4037,0.4037,[329440]|0.4245,0.4245,[329440]|0.4116,0.4116,[329440]|0.3578,0.3578,[329440]|0.3535,0.3535,[329440]|0.2748,0.2748,[329440]|0.2963,0.2963,[329440]|0.2167,0.2167,[329440]|0.3096,0.3096,[329440]|0.2255,0.2255,[329440]|0.3225,0.3225,[329440]|0.1732,0.1732,[329440]|0.1495,0.1495,[329440]|0.1088,0.1088,[329440]|0.1456,0.1456,[329440]|0.2041,0.2041,[329440]|0.1532,0.1532,[329440]|0.1998,0.1998,[329440]|0.1844,0.1844,[329440]|0.1205,0.1205,[329440]|0.124,0.124,[329440]|0.1088,0.1088,[329440]|0.0858,0.0858,[329440]|0.0789,0.0789,[329440]|0.0607,0.0607,[329440]|0.0858,0.0858,[329440]|0.0701,0.0701,[329440]|0.1018,0.1018,[329440]|0.0567,0.0567,[329440]|0.0935,0.0935,[329440]|0.0965,0.0965,[329440]|0.1921,0.1921,[329440]|0.2167,0.2167,[329440]|0.2558,0.2558,[329440]|0.2602,0.2602,[329440]|0.1921,0.1921,[329440]|0.2602,0.2602,[329440]|0.2786,0.2786,[329440]|0.2913,0.2913,[329440]|0.4556,0.4556,[329440]|0.5331,0.5331,[329440]|0.5802,0.5802,[329440]|0.6412,0.6412,[329440]|0.7232,0.7232,[329440]|0.8074,0.8074,[329440]|0.8457,0.8457,[329440]|0.8823,0.8823,[329440]|0.9141,0.9141,[329440]|
+GRAPHLINE      IUPredWS (Short)        0.5     Above 0.5 indicates disorder    ff0000
+
+SEQUENCE_REF   FER1_ARATH
+LINE_GRAPH     IUPredWS (Long) <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.1583,0.1583,[72144e]|0.2034,0.2034,[72144e]|0.2364,0.2364,[72144e]|0.1476,0.1476,[72144e]|0.1048,0.1048,[72144e]|0.1349,0.1349,[72144e]|0.1643,0.1643,[72144e]|0.1881,0.1881,[72144e]|0.247,0.247,[72144e]|0.2399,0.2399,[72144e]|0.2951,0.2951,[72144e]|0.2503,0.2503,[72144e]|0.2783,0.2783,[72144e]|0.2129,0.2129,[72144e]|0.2094,0.2094,[72144e]|0.2364,0.2364,[72144e]|0.2328,0.2328,[72144e]|0.2715,0.2715,[72144e]|0.2715,0.2715,[72144e]|0.2715,0.2715,[72144e]|0.346,0.346,[72144e]|0.4051,0.4051,[72144e]|0.4149,0.4149,[72144e]|0.4087,0.4087,[72144e]|0.3392,0.3392,[72144e]|0.3392,0.3392,[72144e]|0.4087,0.4087,[72144e]|0.3321,0.3321,[72144e]|0.3215,0.3215,[72144e]|0.3215,0.3215,[72144e]|0.2884,0.2884,[72144e]|0.3215,0.3215,[72144e]|0.2609,0.2609,[72144e]|0.3321,0.3321,[72144e]|0.3356,0.3356,[72144e]|0.4051,0.4051,[72144e]|0.4017,0.4017,[72144e]|0.3494,0.3494,[72144e]|0.4292,0.4292,[72144e]|0.3667,0.3667,[72144e]|0.346,0.346,[72144e]|0.3494,0.3494,[72144e]|0.346,0.346,[72144e]|0.268,0.268,[72144e]|0.247,0.247,[72144e]|0.1969,0.1969,[72144e]|0.2541,0.2541,[72144e]|0.2541,0.2541,[72144e]|0.1881,0.1881,[72144e]|0.2609,0.2609,[72144e]|0.3019,0.3019,[72144e]|0.3249,0.3249,[72144e]|0.3249,0.3249,[72144e]|0.346,0.346,[72144e]|0.3053,0.3053,[72144e]|0.3249,0.3249,[72144e]|0.2715,0.2715,[72144e]|0.2951,0.2951,[72144e]|0.2034,0.2034,[72144e]|0.2715,0.2715,[72144e]|0.2817,0.2817,[72144e]|0.3249,0.3249,[72144e]|0.2951,0.2951,[72144e]|0.247,0.247,[72144e]|0.1942,0.1942,[72144e]|0.2002,0.2002,[72144e]|0.2224,0.2224,[72144e]|0.2503,0.2503,[72144e]|0.2328,0.2328,[72144e]|0.346,0.346,[72144e]|0.4409,0.4409,[72144e]|0.4087,0.4087,[72144e]|0.3774,0.3774,[72144e]|0.2849,0.2849,[72144e]|0.2988,0.2988,[72144e]|0.2064,0.2064,[72144e]|0.3087,0.3087,[72144e]|0.2094,0.2094,[72144e]|0.2645,0.2645,[72144e]|0.1554,0.1554,[72144e]|0.2399,0.2399,[72144e]|0.1969,0.1969,[72144e]|0.1852,0.1852,[72144e]|0.1702,0.1702,[72144e]|0.1399,0.1399,[72144e]|0.2002,0.2002,[72144e]|0.2503,0.2503,[72144e]|0.2258,0.2258,[72144e]|0.1881,0.1881,[72144e]|0.2129,0.2129,[72144e]|0.2292,0.2292,[72144e]|0.1611,0.1611,[72144e]|0.107,0.107,[72144e]|0.1251,0.1251,[72144e]|0.1229,0.1229,[72144e]|0.1759,0.1759,[72144e]|0.1229,0.1229,[72144e]|0.1914,0.1914,[72144e]|0.1702,0.1702,[72144e]|0.2364,0.2364,[72144e]|0.2503,0.2503,[72144e]|0.3529,0.3529,[72144e]|0.3494,0.3494,[72144e]|0.2884,0.2884,[72144e]|0.2258,0.2258,[72144e]|0.2436,0.2436,[72144e]|0.3529,0.3529,[72144e]|0.3774,0.3774,[72144e]|0.3872,0.3872,[72144e]|0.4119,0.4119,[72144e]|0.4369,0.4369,[72144e]|0.4619,0.4619,[72144e]|0.4725,0.4725,[72144e]|0.433,0.433,[72144e]|0.4369,0.4369,[72144e]|0.374,0.374,[72144e]|0.3774,0.3774,[72144e]|0.2817,0.2817,[72144e]|0.3087,0.3087,[72144e]|0.2715,0.2715,[72144e]|0.1942,0.1942,[72144e]|0.2292,0.2292,[72144e]|0.2164,0.2164,[72144e]|0.2064,0.2064,[72144e]|0.2224,0.2224,[72144e]|0.2609,0.2609,[72144e]|0.3249,0.3249,[72144e]|0.2436,0.2436,[72144e]|0.2503,0.2503,[72144e]|0.2328,0.2328,[72144e]|0.2193,0.2193,[72144e]|0.2715,0.2715,[72144e]|0.2951,0.2951,[72144e]|0.2988,0.2988,[72144e]|0.3149,0.3149,[72144e]|0.3392,0.3392,[72144e]|0.3356,0.3356,[72144e]|0.3948,0.3948,[72144e]|0.3774,0.3774,[72144e]|0.4725,0.4725,[72144e]|0.4864,0.4864,[72144e]|0.5055,0.5055,[72144e]|0.6043,0.6043,[72144e]|0.5296,0.5296,[72144e]|0.5098,0.5098,[72144e]|0.4979,0.4979,[72144e]|0.4507,0.4507,[72144e]|0.5296,0.5296,[72144e]|
+GRAPHLINE      IUPredWS (Long) 0.5     Above 0.5 indicates disorder    ff0000
+LINE_GRAPH     IUPredWS (Short)        <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.8242,0.8242,[329440]|0.7275,0.7275,[329440]|0.6906,0.6906,[329440]|0.6516,0.6516,[329440]|0.6124,0.6124,[329440]|0.4781,0.4781,[329440]|0.3535,0.3535,[329440]|0.3225,0.3225,[329440]|0.2913,0.2913,[329440]|0.2432,0.2432,[329440]|0.2292,0.2292,[329440]|0.2209,0.2209,[329440]|0.2913,0.2913,[329440]|0.2167,0.2167,[329440]|0.208,0.208,[329440]|0.1766,0.1766,[329440]|0.2657,0.2657,[329440]|0.2657,0.2657,[329440]|0.1958,0.1958,[329440]|0.2558,0.2558,[329440]|0.3184,0.3184,[329440]|0.3456,0.3456,[329440]|0.3456,0.3456,[329440]|0.3399,0.3399,[329440]|0.3578,0.3578,[329440]|0.4458,0.4458,[329440]|0.46,0.46,[329440]|0.3578,0.3578,[329440]|0.3456,0.3456,[329440]|0.2786,0.2786,[329440]|0.2558,0.2558,[329440]|0.2913,0.2913,[329440]|0.2602,0.2602,[329440]|0.3399,0.3399,[329440]|0.3096,0.3096,[329440]|0.3885,0.3885,[329440]|0.3717,0.3717,[329440]|0.3762,0.3762,[329440]|0.4556,0.4556,[329440]|0.363,0.363,[329440]|0.3668,0.3668,[329440]|0.3668,0.3668,[329440]|0.3359,0.3359,[329440]|0.3005,0.3005,[329440]|0.282,0.282,[329440]|0.208,0.208,[329440]|0.2602,0.2602,[329440]|0.282,0.282,[329440]|0.2602,0.2602,[329440]|0.2483,0.2483,[329440]|0.1732,0.1732,[329440]|0.1698,0.1698,[329440]|0.1998,0.1998,[329440]|0.2122,0.2122,[329440]|0.2432,0.2432,[329440]|0.2602,0.2602,[329440]|0.1878,0.1878,[329440]|0.208,0.208,[329440]|0.1532,0.1532,[329440]|0.2209,0.2209,[329440]|0.1456,0.1456,[329440]|0.1844,0.1844,[329440]|0.2255,0.2255,[329440]|0.2748,0.2748,[329440]|0.2209,0.2209,[329440]|0.2255,0.2255,[329440]|0.1878,0.1878,[329440]|0.1844,0.1844,[329440]|0.2167,0.2167,[329440]|0.2657,0.2657,[329440]|0.3005,0.3005,[329440]|0.3225,0.3225,[329440]|0.3096,0.3096,[329440]|0.2483,0.2483,[329440]|0.2483,0.2483,[329440]|0.1698,0.1698,[329440]|0.2602,0.2602,[329440]|0.1805,0.1805,[329440]|0.27,0.27,[329440]|0.1878,0.1878,[329440]|0.2786,0.2786,[329440]|0.138,0.138,[329440]|0.1205,0.1205,[329440]|0.0884,0.0884,[329440]|0.1178,0.1178,[329440]|0.1732,0.1732,[329440]|0.1292,0.1292,[329440]|0.1805,0.1805,[329440]|0.1698,0.1698,[329440]|0.1178,0.1178,[329440]|0.124,0.124,[329440]|0.1088,0.1088,[329440]|0.0858,0.0858,[329440]|0.0771,0.0771,[329440]|0.0554,0.0554,[329440]|0.0832,0.0832,[329440]|0.0677,0.0677,[329440]|0.1018,0.1018,[329440]|0.0554,0.0554,[329440]|0.0965,0.0965,[329440]|0.0965,0.0965,[329440]|0.1921,0.1921,[329440]|0.2167,0.2167,[329440]|0.2558,0.2558,[329440]|0.2602,0.2602,[329440]|0.1844,0.1844,[329440]|0.2333,0.2333,[329440]|0.2531,0.2531,[329440]|0.2657,0.2657,[329440]|0.3992,0.3992,[329440]|0.4333,0.4333,[329440]|0.3762,0.3762,[329440]|0.3992,0.3992,[329440]|0.4781,0.4781,[329440]|0.5374,0.5374,[329440]|0.4651,0.4651,[329440]|0.4203,0.4203,[329440]|0.3535,0.3535,[329440]|0.4078,0.4078,[329440]|0.3311,0.3311,[329440]|0.3096,0.3096,[329440]|0.2865,0.2865,[329440]|0.208,0.208,[329440]|0.2531,0.2531,[329440]|0.2483,0.2483,[329440]|0.3263,0.3263,[329440]|0.4078,0.4078,[329440]|0.3225,0.3225,[329440]|0.3005,0.3005,[329440]|0.2041,0.2041,[329440]|0.2255,0.2255,[329440]|0.2748,0.2748,[329440]|0.2602,0.2602,[329440]|0.208,0.208,[329440]|0.2292,0.2292,[329440]|0.3263,0.3263,[329440]|0.3885,0.3885,[329440]|0.3939,0.3939,[329440]|0.3456,0.3456,[329440]|0.4458,0.4458,[329440]|0.5173,0.5173,[329440]|0.5802,0.5802,[329440]|0.6789,0.6789,[329440]|0.6789,0.6789,[329440]|0.7275,0.7275,[329440]|0.7644,0.7644,[329440]|0.7912,0.7912,[329440]|0.8823,0.8823,[329440]|
+GRAPHLINE      IUPredWS (Short)        0.5     Above 0.5 indicates disorder    ff0000
+
+SEQUENCE_REF   FER_BRANA
+LINE_GRAPH     IUPredWS (Long) <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.1969,0.1969,[72144e]|0.2783,0.2783,[72144e]|0.3182,0.3182,[72144e]|0.384,0.384,[72144e]|0.3321,0.3321,[72144e]|0.3872,0.3872,[72144e]|0.2752,0.2752,[72144e]|0.3182,0.3182,[72144e]|0.3599,0.3599,[72144e]|0.3948,0.3948,[72144e]|0.3566,0.3566,[72144e]|0.3149,0.3149,[72144e]|0.2609,0.2609,[72144e]|0.268,0.268,[72144e]|0.3053,0.3053,[72144e]|0.3321,0.3321,[72144e]|0.3249,0.3249,[72144e]|0.4476,0.4476,[72144e]|0.5456,0.5456,[72144e]|0.5098,0.5098,[72144e]|0.4766,0.4766,[72144e]|0.384,0.384,[72144e]|0.4017,0.4017,[72144e]|0.3087,0.3087,[72144e]|0.3321,0.3321,[72144e]|0.2399,0.2399,[72144e]|0.2918,0.2918,[72144e]|0.1643,0.1643,[72144e]|0.2541,0.2541,[72144e]|0.2094,0.2094,[72144e]|0.1942,0.1942,[72144e]|0.1759,0.1759,[72144e]|0.1323,0.1323,[72144e]|0.1852,0.1852,[72144e]|0.2292,0.2292,[72144e]|0.1914,0.1914,[72144e]|0.1528,0.1528,[72144e]|0.1759,0.1759,[72144e]|0.1791,0.1791,[72144e]|0.1206,0.1206,[72144e]|0.0765,0.0765,[72144e]|0.0888,0.0888,[72144e]|0.0888,0.0888,[72144e]|0.0749,0.0749,[72144e]|0.0473,0.0473,[72144e]|0.0817,0.0817,[72144e]|0.0704,0.0704,[72144e]|0.1048,0.1048,[72144e]|0.1162,0.1162,[72144e]|0.2258,0.2258,[72144e]|0.2364,0.2364,[72144e]|0.1731,0.1731,[72144e]|0.1251,0.1251,[72144e]|0.1399,0.1399,[72144e]|0.2503,0.2503,[72144e]|0.2715,0.2715,[72144e]|0.2783,0.2783,[72144e]|0.3215,0.3215,[72144e]|0.3215,0.3215,[72144e]|0.346,0.346,[72144e]|0.3667,0.3667,[72144e]|0.3249,0.3249,[72144e]|0.3249,0.3249,[72144e]|0.2645,0.2645,[72144e]|0.268,0.268,[72144e]|0.2541,0.2541,[72144e]|0.2817,0.2817,[72144e]|0.2399,0.2399,[72144e]|0.1702,0.1702,[72144e]|0.2034,0.2034,[72144e]|0.1914,0.1914,[72144e]|0.1643,0.1643,[72144e]|0.1823,0.1823,[72144e]|0.2164,0.2164,[72144e]|0.2849,0.2849,[72144e]|0.2002,0.2002,[72144e]|0.2064,0.2064,[72144e]|0.1942,0.1942,[72144e]|0.1823,0.1823,[72144e]|0.2224,0.2224,[72144e]|0.2752,0.2752,[72144e]|0.2752,0.2752,[72144e]|0.2988,0.2988,[72144e]|0.3249,0.3249,[72144e]|0.3249,0.3249,[72144e]|0.3872,0.3872,[72144e]|0.3667,0.3667,[72144e]|0.4685,0.4685,[72144e]|0.4864,0.4864,[72144e]|0.5017,0.5017,[72144e]|0.5951,0.5951,[72144e]|0.5296,0.5296,[72144e]|0.5055,0.5055,[72144e]|0.494,0.494,[72144e]|0.4441,0.4441,[72144e]|0.5254,0.5254,[72144e]|
+GRAPHLINE      IUPredWS (Long) 0.5     Above 0.5 indicates disorder    ff0000
+LINE_GRAPH     IUPredWS (Short)        <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.7605,0.7605,[329440]|0.7458,0.7458,[329440]|0.7079,0.7079,[329440]|0.6906,0.6906,[329440]|0.6557,0.6557,[329440]|0.6412,0.6412,[329440]|0.5374,0.5374,[329440]|0.5173,0.5173,[329440]|0.3806,0.3806,[329440]|0.3578,0.3578,[329440]|0.3311,0.3311,[329440]|0.3885,0.3885,[329440]|0.3456,0.3456,[329440]|0.3456,0.3456,[329440]|0.3225,0.3225,[329440]|0.3225,0.3225,[329440]|0.3806,0.3806,[329440]|0.4116,0.4116,[329440]|0.4379,0.4379,[329440]|0.4651,0.4651,[329440]|0.4458,0.4458,[329440]|0.3806,0.3806,[329440]|0.3762,0.3762,[329440]|0.2913,0.2913,[329440]|0.3096,0.3096,[329440]|0.2167,0.2167,[329440]|0.3146,0.3146,[329440]|0.2255,0.2255,[329440]|0.3184,0.3184,[329440]|0.1698,0.1698,[329440]|0.138,0.138,[329440]|0.1018,0.1018,[329440]|0.1322,0.1322,[329440]|0.1805,0.1805,[329440]|0.1322,0.1322,[329440]|0.1805,0.1805,[329440]|0.1667,0.1667,[329440]|0.1117,0.1117,[329440]|0.115,0.115,[329440]|0.1018,0.1018,[329440]|0.0771,0.0771,[329440]|0.066,0.066,[329440]|0.0478,0.0478,[329440]|0.0677,0.0677,[329440]|0.0554,0.0554,[329440]|0.0478,0.0478,[329440]|0.0268,0.0268,[329440]|0.0441,0.0441,[329440]|0.0455,0.0455,[329440]|0.1018,0.1018,[329440]|0.124,0.124,[329440]|0.1667,0.1667,[329440]|0.1698,0.1698,[329440]|0.1088,0.1088,[329440]|0.138,0.138,[329440]|0.1532,0.1532,[329440]|0.1667,0.1667,[329440]|0.2786,0.2786,[329440]|0.3184,0.3184,[329440]|0.2602,0.2602,[329440]|0.2558,0.2558,[329440]|0.3399,0.3399,[329440]|0.4078,0.4078,[329440]|0.3263,0.3263,[329440]|0.2786,0.2786,[329440]|0.2913,0.2913,[329440]|0.3491,0.3491,[329440]|0.27,0.27,[329440]|0.2432,0.2432,[329440]|0.2209,0.2209,[329440]|0.1532,0.1532,[329440]|0.1732,0.1732,[329440]|0.1698,0.1698,[329440]|0.2483,0.2483,[329440]|0.3311,0.3311,[329440]|0.2483,0.2483,[329440]|0.2292,0.2292,[329440]|0.1495,0.1495,[329440]|0.1698,0.1698,[329440]|0.2122,0.2122,[329440]|0.2292,0.2292,[329440]|0.1805,0.1805,[329440]|0.1998,0.1998,[329440]|0.3053,0.3053,[329440]|0.3806,0.3806,[329440]|0.3939,0.3939,[329440]|0.3399,0.3399,[329440]|0.4513,0.4513,[329440]|0.5173,0.5173,[329440]|0.5846,0.5846,[329440]|0.6827,0.6827,[329440]|0.6827,0.6827,[329440]|0.7317,0.7317,[329440]|0.7688,0.7688,[329440]|0.7951,0.7951,[329440]|0.8857,0.8857,[329440]|
+GRAPHLINE      IUPredWS (Short)        0.5     Above 0.5 indicates disorder    ff0000
+
+SEQUENCE_REF   FER3_RAPSA
+LINE_GRAPH     IUPredWS (Long) <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.2224,0.2224,[72144e]|0.3053,0.3053,[72144e]|0.346,0.346,[72144e]|0.4087,0.4087,[72144e]|0.3599,0.3599,[72144e]|0.4149,0.4149,[72144e]|0.2988,0.2988,[72144e]|0.3426,0.3426,[72144e]|0.384,0.384,[72144e]|0.4186,0.4186,[72144e]|0.384,0.384,[72144e]|0.3426,0.3426,[72144e]|0.2918,0.2918,[72144e]|0.2988,0.2988,[72144e]|0.3356,0.3356,[72144e]|0.3599,0.3599,[72144e]|0.3529,0.3529,[72144e]|0.4685,0.4685,[72144e]|0.5665,0.5665,[72144e]|0.5342,0.5342,[72144e]|0.4979,0.4979,[72144e]|0.4087,0.4087,[72144e]|0.4256,0.4256,[72144e]|0.3356,0.3356,[72144e]|0.3599,0.3599,[72144e]|0.268,0.268,[72144e]|0.3182,0.3182,[72144e]|0.1881,0.1881,[72144e]|0.2849,0.2849,[72144e]|0.2399,0.2399,[72144e]|0.2224,0.2224,[72144e]|0.2034,0.2034,[72144e]|0.1528,0.1528,[72144e]|0.2094,0.2094,[72144e]|0.2575,0.2575,[72144e]|0.2129,0.2129,[72144e]|0.1759,0.1759,[72144e]|0.1969,0.1969,[72144e]|0.2034,0.2034,[72144e]|0.1424,0.1424,[72144e]|0.0929,0.0929,[72144e]|0.107,0.107,[72144e]|0.107,0.107,[72144e]|0.1501,0.1501,[72144e]|0.1028,0.1028,[72144e]|0.1611,0.1611,[72144e]|0.1399,0.1399,[72144e]|0.1942,0.1942,[72144e]|0.2129,0.2129,[72144e]|0.3249,0.3249,[72144e]|0.3321,0.3321,[72144e]|0.2715,0.2715,[72144e]|0.2094,0.2094,[72144e]|0.2258,0.2258,[72144e]|0.3529,0.3529,[72144e]|0.374,0.374,[72144e]|0.3807,0.3807,[72144e]|0.4256,0.4256,[72144e]|0.4256,0.4256,[72144e]|0.4507,0.4507,[72144e]|0.4685,0.4685,[72144e]|0.4256,0.4256,[72144e]|0.4292,0.4292,[72144e]|0.3667,0.3667,[72144e]|0.3704,0.3704,[72144e]|0.2645,0.2645,[72144e]|0.2918,0.2918,[72144e]|0.2503,0.2503,[72144e]|0.1823,0.1823,[72144e]|0.2129,0.2129,[72144e]|0.2002,0.2002,[72144e]|0.1914,0.1914,[72144e]|0.2094,0.2094,[72144e]|0.247,0.247,[72144e]|0.3117,0.3117,[72144e]|0.2292,0.2292,[72144e]|0.2328,0.2328,[72144e]|0.2193,0.2193,[72144e]|0.2064,0.2064,[72144e]|0.2609,0.2609,[72144e]|0.3087,0.3087,[72144e]|0.3087,0.3087,[72144e]|0.3286,0.3286,[72144e]|0.3983,0.3983,[72144e]|0.3948,0.3948,[72144e]|0.4541,0.4541,[72144e]|0.4369,0.4369,[72144e]|0.5533,0.5533,[72144e]|0.5758,0.5758,[72144e]|0.5992,0.5992,[72144e]|0.708,0.708,[72144e]|0.6375,0.6375,[72144e]|0.6227,0.6227,[72144e]|0.6136,0.6136,[72144e]|0.5577,0.5577,[72144e]|0.6661,0.6661,[72144e]|
+GRAPHLINE      IUPredWS (Long) 0.5     Above 0.5 indicates disorder    ff0000
+LINE_GRAPH     IUPredWS (Short)        <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.7605,0.7605,[329440]|0.7458,0.7458,[329440]|0.7079,0.7079,[329440]|0.6906,0.6906,[329440]|0.6557,0.6557,[329440]|0.6412,0.6412,[329440]|0.5374,0.5374,[329440]|0.5173,0.5173,[329440]|0.3806,0.3806,[329440]|0.3578,0.3578,[329440]|0.3311,0.3311,[329440]|0.3885,0.3885,[329440]|0.3456,0.3456,[329440]|0.3456,0.3456,[329440]|0.3225,0.3225,[329440]|0.3225,0.3225,[329440]|0.3806,0.3806,[329440]|0.4116,0.4116,[329440]|0.4379,0.4379,[329440]|0.4651,0.4651,[329440]|0.4458,0.4458,[329440]|0.3806,0.3806,[329440]|0.3806,0.3806,[329440]|0.2963,0.2963,[329440]|0.3184,0.3184,[329440]|0.2292,0.2292,[329440]|0.3225,0.3225,[329440]|0.2385,0.2385,[329440]|0.3359,0.3359,[329440]|0.1844,0.1844,[329440]|0.1566,0.1566,[329440]|0.115,0.115,[329440]|0.1495,0.1495,[329440]|0.2041,0.2041,[329440]|0.1532,0.1532,[329440]|0.2041,0.2041,[329440]|0.1878,0.1878,[329440]|0.1322,0.1322,[329440]|0.1349,0.1349,[329440]|0.1205,0.1205,[329440]|0.0965,0.0965,[329440]|0.0832,0.0832,[329440]|0.0607,0.0607,[329440]|0.0858,0.0858,[329440]|0.0701,0.0701,[329440]|0.1018,0.1018,[329440]|0.0554,0.0554,[329440]|0.0909,0.0909,[329440]|0.0909,0.0909,[329440]|0.1844,0.1844,[329440]|0.208,0.208,[329440]|0.2483,0.2483,[329440]|0.2531,0.2531,[329440]|0.1732,0.1732,[329440]|0.2167,0.2167,[329440]|0.2333,0.2333,[329440]|0.2483,0.2483,[329440]|0.3717,0.3717,[329440]|0.4078,0.4078,[329440]|0.3535,0.3535,[329440]|0.3456,0.3456,[329440]|0.4282,0.4282,[329440]|0.4879,0.4879,[329440]|0.4116,0.4116,[329440]|0.3668,0.3668,[329440]|0.3005,0.3005,[329440]|0.3578,0.3578,[329440]|0.282,0.282,[329440]|0.2558,0.2558,[329440]|0.2385,0.2385,[329440]|0.1667,0.1667,[329440]|0.2041,0.2041,[329440]|0.1998,0.1998,[329440]|0.282,0.282,[329440]|0.363,0.363,[329440]|0.282,0.282,[329440]|0.2602,0.2602,[329440]|0.1766,0.1766,[329440]|0.1998,0.1998,[329440]|0.2483,0.2483,[329440]|0.2657,0.2657,[329440]|0.2255,0.2255,[329440]|0.2531,0.2531,[329440]|0.3491,0.3491,[329440]|0.4116,0.4116,[329440]|0.4458,0.4458,[329440]|0.3992,0.3992,[329440]|0.5008,0.5008,[329440]|0.5623,0.5623,[329440]|0.6293,0.6293,[329440]|0.7275,0.7275,[329440]|0.7275,0.7275,[329440]|0.7724,0.7724,[329440]|0.8118,0.8118,[329440]|0.8391,0.8391,[329440]|0.9141,0.9141,[329440]|
+GRAPHLINE      IUPredWS (Short)        0.5     Above 0.5 indicates disorder    ff0000
+
+SEQUENCE_REF   FER1_MAIZE
+LINE_GRAPH     IUPredWS (Long) <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.422,0.422,[72144e]|0.2918,0.2918,[72144e]|0.1852,0.1852,[72144e]|0.1115,0.1115,[72144e]|0.1373,0.1373,[72144e]|0.1643,0.1643,[72144e]|0.1914,0.1914,[72144e]|0.2193,0.2193,[72144e]|0.1731,0.1731,[72144e]|0.2002,0.2002,[72144e]|0.1969,0.1969,[72144e]|0.1969,0.1969,[72144e]|0.2575,0.2575,[72144e]|0.2193,0.2193,[72144e]|0.3087,0.3087,[72144e]|0.384,0.384,[72144e]|0.3566,0.3566,[72144e]|0.3019,0.3019,[72144e]|0.2436,0.2436,[72144e]|0.1731,0.1731,[72144e]|0.2292,0.2292,[72144e]|0.1759,0.1759,[72144e]|0.1759,0.1759,[72144e]|0.2575,0.2575,[72144e]|0.2918,0.2918,[72144e]|0.384,0.384,[72144e]|0.3117,0.3117,[72144e]|0.2817,0.2817,[72144e]|0.2849,0.2849,[72144e]|0.2849,0.2849,[72144e]|0.3529,0.3529,[72144e]|0.3215,0.3215,[72144e]|0.3494,0.3494,[72144e]|0.374,0.374,[72144e]|0.3426,0.3426,[72144e]|0.374,0.374,[72144e]|0.3426,0.3426,[72144e]|0.268,0.268,[72144e]|0.2951,0.2951,[72144e]|0.3249,0.3249,[72144e]|0.3631,0.3631,[72144e]|0.4087,0.4087,[72144e]|0.384,0.384,[72144e]|0.3321,0.3321,[72144e]|0.3704,0.3704,[72144e]|0.3215,0.3215,[72144e]|0.3704,0.3704,[72144e]|0.3019,0.3019,[72144e]|0.3053,0.3053,[72144e]|0.3392,0.3392,[72144e]|0.3704,0.3704,[72144e]|0.3872,0.3872,[72144e]|0.391,0.391,[72144e]|0.4409,0.4409,[72144e]|0.3872,0.3872,[72144e]|0.4087,0.4087,[72144e]|0.3392,0.3392,[72144e]|0.3494,0.3494,[72144e]|0.2951,0.2951,[72144e]|0.3948,0.3948,[72144e]|0.4119,0.4119,[72144e]|0.4582,0.4582,[72144e]|0.391,0.391,[72144e]|0.3426,0.3426,[72144e]|0.2645,0.2645,[72144e]|0.2783,0.2783,[72144e]|0.2849,0.2849,[72144e]|0.3494,0.3494,[72144e]|0.3286,0.3286,[72144e]|0.4149,0.4149,[72144e]|0.5139,0.5139,[72144e]|0.5296,0.5296,[72144e]|0.494,0.494,[72144e]|0.3983,0.3983,[72144e]|0.4119,0.4119,[72144e]|0.3215,0.3215,[72144e]|0.4087,0.4087,[72144e]|0.3087,0.3087,[72144e]|0.3774,0.3774,[72144e]|0.2817,0.2817,[72144e]|0.3356,0.3356,[72144e]|0.2817,0.2817,[72144e]|0.2645,0.2645,[72144e]|0.247,0.247,[72144e]|0.2164,0.2164,[72144e]|0.2918,0.2918,[72144e]|0.3631,0.3631,[72144e]|0.346,0.346,[72144e]|0.3019,0.3019,[72144e]|0.2951,0.2951,[72144e]|0.3117,0.3117,[72144e]|0.2399,0.2399,[72144e]|0.1702,0.1702,[72144e]|0.1554,0.1554,[72144e]|0.1554,0.1554,[72144e]|0.2193,0.2193,[72144e]|0.1554,0.1554,[72144e]|0.2364,0.2364,[72144e]|0.2129,0.2129,[72144e]|0.2918,0.2918,[72144e]|0.3087,0.3087,[72144e]|0.4017,0.4017,[72144e]|0.4017,0.4017,[72144e]|0.3566,0.3566,[72144e]|0.2884,0.2884,[72144e]|0.3087,0.3087,[72144e]|0.4119,0.4119,[72144e]|0.4119,0.4119,[72144e]|0.422,0.422,[72144e]|0.4369,0.4369,[72144e]|0.4409,0.4409,[72144e]|0.4541,0.4541,[72144e]|0.4619,0.4619,[72144e]|0.4409,0.4409,[72144e]|0.4441,0.4441,[72144e]|0.374,0.374,[72144e]|0.3774,0.3774,[72144e]|0.2918,0.2918,[72144e]|0.3494,0.3494,[72144e]|0.3087,0.3087,[72144e]|0.2328,0.2328,[72144e]|0.268,0.268,[72144e]|0.2541,0.2541,[72144e]|0.247,0.247,[72144e]|0.2645,0.2645,[72144e]|0.2849,0.2849,[72144e]|0.3053,0.3053,[72144e]|0.2193,0.2193,[72144e]|0.2224,0.2224,[72144e]|0.2224,0.2224,[72144e]|0.2164,0.2164,[72144e]|0.2752,0.2752,[72144e]|0.3149,0.3149,[72144e]|0.3149,0.3149,[72144e]|0.3356,0.3356,[72144e]|0.346,0.346,[72144e]|0.4017,0.4017,[72144e]|0.4685,0.4685,[72144e]|0.4409,0.4409,[72144e]|0.5296,0.5296,[72144e]|0.5139,0.5139,[72144e]|0.5296,0.5296,[72144e]|0.6136,0.6136,[72144e]|0.5493,0.5493,[72144e]|0.5296,0.5296,[72144e]|0.5139,0.5139,[72144e]|0.4685,0.4685,[72144e]|0.5342,0.5342,[72144e]|0.6136,0.6136,[72144e]|0.7547,0.7547,[72144e]|
+GRAPHLINE      IUPredWS (Long) 0.5     Above 0.5 indicates disorder    ff0000
+LINE_GRAPH     IUPredWS (Short)        <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.865,0.865,[329440]|0.8488,0.8488,[329440]|0.7724,0.7724,[329440]|0.6557,0.6557,[329440]|0.5253,0.5253,[329440]|0.3847,0.3847,[329440]|0.3399,0.3399,[329440]|0.2913,0.2913,[329440]|0.2432,0.2432,[329440]|0.1958,0.1958,[329440]|0.106,0.106,[329440]|0.1456,0.1456,[329440]|0.1456,0.1456,[329440]|0.115,0.115,[329440]|0.1844,0.1844,[329440]|0.2122,0.2122,[329440]|0.2333,0.2333,[329440]|0.2531,0.2531,[329440]|0.1998,0.1998,[329440]|0.1322,0.1322,[329440]|0.1456,0.1456,[329440]|0.0858,0.0858,[329440]|0.1266,0.1266,[329440]|0.1667,0.1667,[329440]|0.2122,0.2122,[329440]|0.3225,0.3225,[329440]|0.2483,0.2483,[329440]|0.2483,0.2483,[329440]|0.1766,0.1766,[329440]|0.1602,0.1602,[329440]|0.2255,0.2255,[329440]|0.2209,0.2209,[329440]|0.2558,0.2558,[329440]|0.2657,0.2657,[329440]|0.2432,0.2432,[329440]|0.2865,0.2865,[329440]|0.27,0.27,[329440]|0.2748,0.2748,[329440]|0.3146,0.3146,[329440]|0.3184,0.3184,[329440]|0.3491,0.3491,[329440]|0.3847,0.3847,[329440]|0.3668,0.3668,[329440]|0.363,0.363,[329440]|0.4037,0.4037,[329440]|0.3184,0.3184,[329440]|0.4037,0.4037,[329440]|0.3359,0.3359,[329440]|0.4116,0.4116,[329440]|0.3717,0.3717,[329440]|0.3005,0.3005,[329440]|0.2865,0.2865,[329440]|0.3263,0.3263,[329440]|0.3668,0.3668,[329440]|0.3717,0.3717,[329440]|0.3806,0.3806,[329440]|0.3053,0.3053,[329440]|0.3146,0.3146,[329440]|0.2292,0.2292,[329440]|0.3096,0.3096,[329440]|0.2432,0.2432,[329440]|0.3146,0.3146,[329440]|0.3225,0.3225,[329440]|0.3806,0.3806,[329440]|0.3184,0.3184,[329440]|0.3311,0.3311,[329440]|0.2657,0.2657,[329440]|0.2657,0.2657,[329440]|0.2913,0.2913,[329440]|0.3578,0.3578,[329440]|0.3939,0.3939,[329440]|0.4116,0.4116,[329440]|0.3885,0.3885,[329440]|0.4037,0.4037,[329440]|0.3939,0.3939,[329440]|0.3184,0.3184,[329440]|0.4116,0.4116,[329440]|0.3263,0.3263,[329440]|0.4379,0.4379,[329440]|0.363,0.363,[329440]|0.4282,0.4282,[329440]|0.2865,0.2865,[329440]|0.2602,0.2602,[329440]|0.2041,0.2041,[329440]|0.2558,0.2558,[329440]|0.3225,0.3225,[329440]|0.2748,0.2748,[329440]|0.3399,0.3399,[329440]|0.3225,0.3225,[329440]|0.2122,0.2122,[329440]|0.2209,0.2209,[329440]|0.1998,0.1998,[329440]|0.1698,0.1698,[329440]|0.1178,0.1178,[329440]|0.0909,0.0909,[329440]|0.1292,0.1292,[329440]|0.106,0.106,[329440]|0.1532,0.1532,[329440]|0.0935,0.0935,[329440]|0.138,0.138,[329440]|0.138,0.138,[329440]|0.2292,0.2292,[329440]|0.2531,0.2531,[329440]|0.2865,0.2865,[329440]|0.2865,0.2865,[329440]|0.2122,0.2122,[329440]|0.2558,0.2558,[329440]|0.27,0.27,[329440]|0.2786,0.2786,[329440]|0.3847,0.3847,[329440]|0.4203,0.4203,[329440]|0.363,0.363,[329440]|0.3535,0.3535,[329440]|0.4149,0.4149,[329440]|0.4703,0.4703,[329440]|0.3939,0.3939,[329440]|0.3535,0.3535,[329440]|0.2865,0.2865,[329440]|0.3359,0.3359,[329440]|0.2602,0.2602,[329440]|0.2558,0.2558,[329440]|0.2333,0.2333,[329440]|0.1635,0.1635,[329440]|0.1958,0.1958,[329440]|0.1878,0.1878,[329440]|0.2657,0.2657,[329440]|0.3456,0.3456,[329440]|0.2748,0.2748,[329440]|0.2167,0.2167,[329440]|0.1602,0.1602,[329440]|0.1766,0.1766,[329440]|0.2255,0.2255,[329440]|0.2333,0.2333,[329440]|0.1998,0.1998,[329440]|0.2209,0.2209,[329440]|0.3184,0.3184,[329440]|0.3847,0.3847,[329440]|0.3847,0.3847,[329440]|0.3847,0.3847,[329440]|0.4513,0.4513,[329440]|0.4282,0.4282,[329440]|0.4967,0.4967,[329440]|0.6079,0.6079,[329440]|0.6124,0.6124,[329440]|0.6604,0.6604,[329440]|0.6944,0.6944,[329440]|0.7232,0.7232,[329440]|0.8118,0.8118,[329440]|0.8857,0.8857,[329440]|0.9491,0.9491,[329440]|
+GRAPHLINE      IUPredWS (Short)        0.5     Above 0.5 indicates disorder    ff0000
+
+SEQUENCE_REF   O80429_MAIZE
+LINE_GRAPH     IUPredWS (Long) <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.1007,0.1007,[72144e]|0.1028,0.1028,[72144e]|0.1731,0.1731,[72144e]|0.2503,0.2503,[72144e]|0.3249,0.3249,[72144e]|0.2292,0.2292,[72144e]|0.1476,0.1476,[72144e]|0.1731,0.1731,[72144e]|0.1942,0.1942,[72144e]|0.2503,0.2503,[72144e]|0.1969,0.1969,[72144e]|0.2258,0.2258,[72144e]|0.1823,0.1823,[72144e]|0.2094,0.2094,[72144e]|0.1501,0.1501,[72144e]|0.1501,0.1501,[72144e]|0.1611,0.1611,[72144e]|0.1399,0.1399,[72144e]|0.1554,0.1554,[72144e]|0.1823,0.1823,[72144e]|0.1881,0.1881,[72144e]|0.2258,0.2258,[72144e]|0.1969,0.1969,[72144e]|0.2541,0.2541,[72144e]|0.1942,0.1942,[72144e]|0.1671,0.1671,[72144e]|0.1399,0.1399,[72144e]|0.2292,0.2292,[72144e]|0.2575,0.2575,[72144e]|0.1881,0.1881,[72144e]|0.1881,0.1881,[72144e]|0.1399,0.1399,[72144e]|0.2129,0.2129,[72144e]|0.1852,0.1852,[72144e]|0.2645,0.2645,[72144e]|0.1852,0.1852,[72144e]|0.2503,0.2503,[72144e]|0.2193,0.2193,[72144e]|0.2752,0.2752,[72144e]|0.2292,0.2292,[72144e]|0.1611,0.1611,[72144e]|0.1424,0.1424,[72144e]|0.2364,0.2364,[72144e]|0.2884,0.2884,[72144e]|0.3182,0.3182,[72144e]|0.3087,0.3087,[72144e]|0.2783,0.2783,[72144e]|0.2988,0.2988,[72144e]|0.2224,0.2224,[72144e]|0.2224,0.2224,[72144e]|0.2364,0.2364,[72144e]|0.3392,0.3392,[72144e]|0.3566,0.3566,[72144e]|0.4051,0.4051,[72144e]|0.3356,0.3356,[72144e]|0.2817,0.2817,[72144e]|0.1969,0.1969,[72144e]|0.2094,0.2094,[72144e]|0.2164,0.2164,[72144e]|0.1791,0.1791,[72144e]|0.1583,0.1583,[72144e]|0.2436,0.2436,[72144e]|0.3426,0.3426,[72144e]|0.3566,0.3566,[72144e]|0.3286,0.3286,[72144e]|0.2258,0.2258,[72144e]|0.2436,0.2436,[72144e]|0.1554,0.1554,[72144e]|0.2364,0.2364,[72144e]|0.1298,0.1298,[72144e]|0.1914,0.1914,[72144e]|0.1115,0.1115,[72144e]|0.1528,0.1528,[72144e]|0.1115,0.1115,[72144e]|0.1007,0.1007,[72144e]|0.0909,0.0909,[72144e]|0.0734,0.0734,[72144e]|0.1206,0.1206,[72144e]|0.1823,0.1823,[72144e]|0.1643,0.1643,[72144e]|0.1298,0.1298,[72144e]|0.2164,0.2164,[72144e]|0.2399,0.2399,[72144e]|0.1671,0.1671,[72144e]|0.1092,0.1092,[72144e]|0.0948,0.0948,[72144e]|0.0948,0.0948,[72144e]|0.1501,0.1501,[72144e]|0.0967,0.0967,[72144e]|0.1611,0.1611,[72144e]|0.1424,0.1424,[72144e]|0.2328,0.2328,[72144e]|0.2503,0.2503,[72144e]|0.3529,0.3529,[72144e]|0.3494,0.3494,[72144e]|0.2817,0.2817,[72144e]|0.2064,0.2064,[72144e]|0.2193,0.2193,[72144e]|0.3286,0.3286,[72144e]|0.3392,0.3392,[72144e]|0.3494,0.3494,[72144e]|0.384,0.384,[72144e]|0.3872,0.3872,[72144e]|0.4017,0.4017,[72144e]|0.4087,0.4087,[72144e]|0.3807,0.3807,[72144e]|0.384,0.384,[72144e]|0.3117,0.3117,[72144e]|0.3182,0.3182,[72144e]|0.2328,0.2328,[72144e]|0.268,0.268,[72144e]|0.2258,0.2258,[72144e]|0.1501,0.1501,[72144e]|0.1791,0.1791,[72144e]|0.1702,0.1702,[72144e]|0.1611,0.1611,[72144e]|0.1791,0.1791,[72144e]|0.2164,0.2164,[72144e]|0.2328,0.2328,[72144e]|0.1554,0.1554,[72144e]|0.1583,0.1583,[72144e]|0.1528,0.1528,[72144e]|0.1424,0.1424,[72144e]|0.1823,0.1823,[72144e]|0.2224,0.2224,[72144e]|0.2224,0.2224,[72144e]|0.2436,0.2436,[72144e]|0.2541,0.2541,[72144e]|0.2399,0.2399,[72144e]|0.3087,0.3087,[72144e]|0.2817,0.2817,[72144e]|0.3566,0.3566,[72144e]|0.3667,0.3667,[72144e]|0.3774,0.3774,[72144e]|0.4541,0.4541,[72144e]|0.391,0.391,[72144e]|0.3599,0.3599,[72144e]|0.3356,0.3356,[72144e]|0.2783,0.2783,[72144e]|0.3426,0.3426,[72144e]|
+GRAPHLINE      IUPredWS (Long) 0.5     Above 0.5 indicates disorder    ff0000
+LINE_GRAPH     IUPredWS (Short)        <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.7573,0.7573,[329440]|0.6516,0.6516,[329440]|0.6293,0.6293,[329440]|0.5412,0.5412,[329440]|0.5229,0.5229,[329440]|0.5008,0.5008,[329440]|0.4825,0.4825,[329440]|0.3806,0.3806,[329440]|0.2292,0.2292,[329440]|0.1921,0.1921,[329440]|0.1602,0.1602,[329440]|0.208,0.208,[329440]|0.1667,0.1667,[329440]|0.2167,0.2167,[329440]|0.1416,0.1416,[329440]|0.1878,0.1878,[329440]|0.1921,0.1921,[329440]|0.1635,0.1635,[329440]|0.1322,0.1322,[329440]|0.1088,0.1088,[329440]|0.1805,0.1805,[329440]|0.2748,0.2748,[329440]|0.1805,0.1805,[329440]|0.1921,0.1921,[329440]|0.1495,0.1495,[329440]|0.1495,0.1495,[329440]|0.1088,0.1088,[329440]|0.1532,0.1532,[329440]|0.2531,0.2531,[329440]|0.2531,0.2531,[329440]|0.1805,0.1805,[329440]|0.106,0.106,[329440]|0.1667,0.1667,[329440]|0.1292,0.1292,[329440]|0.1998,0.1998,[329440]|0.1566,0.1566,[329440]|0.2292,0.2292,[329440]|0.1844,0.1844,[329440]|0.27,0.27,[329440]|0.2255,0.2255,[329440]|0.2041,0.2041,[329440]|0.124,0.124,[329440]|0.1205,0.1205,[329440]|0.1416,0.1416,[329440]|0.1998,0.1998,[329440]|0.1958,0.1958,[329440]|0.2292,0.2292,[329440]|0.2483,0.2483,[329440]|0.1732,0.1732,[329440]|0.1805,0.1805,[329440]|0.1667,0.1667,[329440]|0.2432,0.2432,[329440]|0.1732,0.1732,[329440]|0.2385,0.2385,[329440]|0.2531,0.2531,[329440]|0.3096,0.3096,[329440]|0.2385,0.2385,[329440]|0.2531,0.2531,[329440]|0.1844,0.1844,[329440]|0.1921,0.1921,[329440]|0.2167,0.2167,[329440]|0.208,0.208,[329440]|0.2558,0.2558,[329440]|0.27,0.27,[329440]|0.2558,0.2558,[329440]|0.2558,0.2558,[329440]|0.2531,0.2531,[329440]|0.1732,0.1732,[329440]|0.27,0.27,[329440]|0.1844,0.1844,[329440]|0.3005,0.3005,[329440]|0.208,0.208,[329440]|0.282,0.282,[329440]|0.1456,0.1456,[329440]|0.124,0.124,[329440]|0.0884,0.0884,[329440]|0.1292,0.1292,[329440]|0.1805,0.1805,[329440]|0.1416,0.1416,[329440]|0.1998,0.1998,[329440]|0.1844,0.1844,[329440]|0.1635,0.1635,[329440]|0.1667,0.1667,[329440]|0.1532,0.1532,[329440]|0.124,0.124,[329440]|0.0789,0.0789,[329440]|0.0554,0.0554,[329440]|0.0858,0.0858,[329440]|0.0701,0.0701,[329440]|0.106,0.106,[329440]|0.0587,0.0587,[329440]|0.1088,0.1088,[329440]|0.1088,0.1088,[329440]|0.1878,0.1878,[329440]|0.2122,0.2122,[329440]|0.2483,0.2483,[329440]|0.2432,0.2432,[329440]|0.1667,0.1667,[329440]|0.208,0.208,[329440]|0.2041,0.2041,[329440]|0.2209,0.2209,[329440]|0.3311,0.3311,[329440]|0.3668,0.3668,[329440]|0.3184,0.3184,[329440]|0.3096,0.3096,[329440]|0.3762,0.3762,[329440]|0.4282,0.4282,[329440]|0.3578,0.3578,[329440]|0.3096,0.3096,[329440]|0.2385,0.2385,[329440]|0.2865,0.2865,[329440]|0.2167,0.2167,[329440]|0.1878,0.1878,[329440]|0.1732,0.1732,[329440]|0.115,0.115,[329440]|0.1416,0.1416,[329440]|0.1349,0.1349,[329440]|0.208,0.208,[329440]|0.2865,0.2865,[329440]|0.2255,0.2255,[329440]|0.1667,0.1667,[329440]|0.106,0.106,[329440]|0.1205,0.1205,[329440]|0.1456,0.1456,[329440]|0.1495,0.1495,[329440]|0.1266,0.1266,[329440]|0.1416,0.1416,[329440]|0.208,0.208,[329440]|0.27,0.27,[329440]|0.27,0.27,[329440]|0.208,0.208,[329440]|0.3146,0.3146,[329440]|0.3806,0.3806,[329440]|0.4513,0.4513,[329440]|0.5623,0.5623,[329440]|0.5623,0.5623,[329440]|0.6219,0.6219,[329440]|0.6604,0.6604,[329440]|0.6906,0.6906,[329440]|0.7817,0.7817,[329440]|
+GRAPHLINE      IUPredWS (Short)        0.5     Above 0.5 indicates disorder    ff0000
+
+SEQUENCE_REF   Q7XA98_TRIPR
+LINE_GRAPH     IUPredWS (Long) <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.2292,0.2292,[72144e]|0.2645,0.2645,[72144e]|0.3019,0.3019,[72144e]|0.3286,0.3286,[72144e]|0.2328,0.2328,[72144e]|0.2258,0.2258,[72144e]|0.2645,0.2645,[72144e]|0.2988,0.2988,[72144e]|0.3286,0.3286,[72144e]|0.3774,0.3774,[72144e]|0.346,0.346,[72144e]|0.4051,0.4051,[72144e]|0.4051,0.4051,[72144e]|0.4017,0.4017,[72144e]|0.3494,0.3494,[72144e]|0.2988,0.2988,[72144e]|0.3286,0.3286,[72144e]|0.3948,0.3948,[72144e]|0.4864,0.4864,[72144e]|0.494,0.494,[72144e]|0.494,0.494,[72144e]|0.5139,0.5139,[72144e]|0.5419,0.5419,[72144e]|0.4441,0.4441,[72144e]|0.4685,0.4685,[72144e]|0.4685,0.4685,[72144e]|0.5707,0.5707,[72144e]|0.4901,0.4901,[72144e]|0.4831,0.4831,[72144e]|0.4087,0.4087,[72144e]|0.3872,0.3872,[72144e]|0.3566,0.3566,[72144e]|0.3566,0.3566,[72144e]|0.3215,0.3215,[72144e]|0.3566,0.3566,[72144e]|0.346,0.346,[72144e]|0.3983,0.3983,[72144e]|0.4256,0.4256,[72144e]|0.4369,0.4369,[72144e]|0.3667,0.3667,[72144e]|0.3392,0.3392,[72144e]|0.2884,0.2884,[72144e]|0.2609,0.2609,[72144e]|0.2436,0.2436,[72144e]|0.2436,0.2436,[72144e]|0.3392,0.3392,[72144e]|0.2328,0.2328,[72144e]|0.2258,0.2258,[72144e]|0.1791,0.1791,[72144e]|0.2575,0.2575,[72144e]|0.1969,0.1969,[72144e]|0.1969,0.1969,[72144e]|0.2094,0.2094,[72144e]|0.2399,0.2399,[72144e]|0.3149,0.3149,[72144e]|0.3182,0.3182,[72144e]|0.3426,0.3426,[72144e]|0.3599,0.3599,[72144e]|0.3807,0.3807,[72144e]|0.2884,0.2884,[72144e]|0.2884,0.2884,[72144e]|0.2609,0.2609,[72144e]|0.3149,0.3149,[72144e]|0.3774,0.3774,[72144e]|0.4119,0.4119,[72144e]|0.391,0.391,[72144e]|0.3356,0.3356,[72144e]|0.268,0.268,[72144e]|0.2849,0.2849,[72144e]|0.3117,0.3117,[72144e]|0.3599,0.3599,[72144e]|0.346,0.346,[72144e]|0.4292,0.4292,[72144e]|0.5254,0.5254,[72144e]|0.4652,0.4652,[72144e]|0.4369,0.4369,[72144e]|0.346,0.346,[72144e]|0.3704,0.3704,[72144e]|0.2783,0.2783,[72144e]|0.2988,0.2988,[72144e]|0.1969,0.1969,[72144e]|0.2849,0.2849,[72144e]|0.1852,0.1852,[72144e]|0.2752,0.2752,[72144e]|0.2193,0.2193,[72144e]|0.2064,0.2064,[72144e]|0.1942,0.1942,[72144e]|0.1583,0.1583,[72144e]|0.2258,0.2258,[72144e]|0.2918,0.2918,[72144e]|0.2609,0.2609,[72144e]|0.2224,0.2224,[72144e]|0.2258,0.2258,[72144e]|0.2399,0.2399,[72144e]|0.1731,0.1731,[72144e]|0.1184,0.1184,[72144e]|0.1611,0.1611,[72144e]|0.1611,0.1611,[72144e]|0.2292,0.2292,[72144e]|0.1643,0.1643,[72144e]|0.2292,0.2292,[72144e]|0.2064,0.2064,[72144e]|0.3053,0.3053,[72144e]|0.3215,0.3215,[72144e]|0.4149,0.4149,[72144e]|0.4119,0.4119,[72144e]|0.3494,0.3494,[72144e]|0.2884,0.2884,[72144e]|0.3053,0.3053,[72144e]|0.4087,0.4087,[72144e]|0.433,0.433,[72144e]|0.4409,0.4409,[72144e]|0.4685,0.4685,[72144e]|0.5176,0.5176,[72144e]|0.5139,0.5139,[72144e]|0.5296,0.5296,[72144e]|0.5098,0.5098,[72144e]|0.5098,0.5098,[72144e]|0.433,0.433,[72144e]|0.4369,0.4369,[72144e]|0.3249,0.3249,[72144e]|0.3249,0.3249,[72144e]|0.2951,0.2951,[72144e]|0.1969,0.1969,[72144e]|0.2064,0.2064,[72144e]|0.2002,0.2002,[72144e]|0.1942,0.1942,[72144e]|0.2094,0.2094,[72144e]|0.2436,0.2436,[72144e]|0.3087,0.3087,[72144e]|0.2258,0.2258,[72144e]|0.2328,0.2328,[72144e]|0.2164,0.2164,[72144e]|0.2034,0.2034,[72144e]|0.2541,0.2541,[72144e]|0.2575,0.2575,[72144e]|0.2783,0.2783,[72144e]|0.3019,0.3019,[72144e]|0.3019,0.3019,[72144e]|0.3529,0.3529,[72144e]|0.3948,0.3948,[72144e]|0.384,0.384,[72144e]|0.4766,0.4766,[72144e]|0.5254,0.5254,[72144e]|0.5419,0.5419,[72144e]|0.6755,0.6755,[72144e]|0.6136,0.6136,[72144e]|0.5901,0.5901,[72144e]|0.5807,0.5807,[72144e]|0.5382,0.5382,[72144e]|0.6322,0.6322,[72144e]|0.6089,0.6089,[72144e]|
+GRAPHLINE      IUPredWS (Long) 0.5     Above 0.5 indicates disorder    ff0000
+LINE_GRAPH     IUPredWS (Short)        <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.892,0.892,[329440]|0.835,0.835,[329440]|0.7388,0.7388,[329440]|0.6944,0.6944,[329440]|0.6557,0.6557,[329440]|0.6124,0.6124,[329440]|0.4967,0.4967,[329440]|0.4149,0.4149,[329440]|0.3939,0.3939,[329440]|0.3717,0.3717,[329440]|0.3359,0.3359,[329440]|0.3939,0.3939,[329440]|0.363,0.363,[329440]|0.3847,0.3847,[329440]|0.3535,0.3535,[329440]|0.3311,0.3311,[329440]|0.3053,0.3053,[329440]|0.3225,0.3225,[329440]|0.3992,0.3992,[329440]|0.3939,0.3939,[329440]|0.3939,0.3939,[329440]|0.4203,0.4203,[329440]|0.4781,0.4781,[329440]|0.4825,0.4825,[329440]|0.4513,0.4513,[329440]|0.363,0.363,[329440]|0.4651,0.4651,[329440]|0.4967,0.4967,[329440]|0.4749,0.4749,[329440]|0.3535,0.3535,[329440]|0.3456,0.3456,[329440]|0.2558,0.2558,[329440]|0.3225,0.3225,[329440]|0.2963,0.2963,[329440]|0.2602,0.2602,[329440]|0.2657,0.2657,[329440]|0.3184,0.3184,[329440]|0.3359,0.3359,[329440]|0.3491,0.3491,[329440]|0.3491,0.3491,[329440]|0.3578,0.3578,[329440]|0.3096,0.3096,[329440]|0.2865,0.2865,[329440]|0.2292,0.2292,[329440]|0.2292,0.2292,[329440]|0.2865,0.2865,[329440]|0.2292,0.2292,[329440]|0.2255,0.2255,[329440]|0.1566,0.1566,[329440]|0.2333,0.2333,[329440]|0.1766,0.1766,[329440]|0.2209,0.2209,[329440]|0.1602,0.1602,[329440]|0.1088,0.1088,[329440]|0.1495,0.1495,[329440]|0.1766,0.1766,[329440]|0.1921,0.1921,[329440]|0.1998,0.1998,[329440]|0.2122,0.2122,[329440]|0.2255,0.2255,[329440]|0.2385,0.2385,[329440]|0.2041,0.2041,[329440]|0.2483,0.2483,[329440]|0.2122,0.2122,[329440]|0.27,0.27,[329440]|0.3225,0.3225,[329440]|0.3762,0.3762,[329440]|0.3225,0.3225,[329440]|0.3311,0.3311,[329440]|0.2865,0.2865,[329440]|0.282,0.282,[329440]|0.3184,0.3184,[329440]|0.3668,0.3668,[329440]|0.4037,0.4037,[329440]|0.4245,0.4245,[329440]|0.4116,0.4116,[329440]|0.3359,0.3359,[329440]|0.3311,0.3311,[329440]|0.2483,0.2483,[329440]|0.2558,0.2558,[329440]|0.1766,0.1766,[329440]|0.2913,0.2913,[329440]|0.2041,0.2041,[329440]|0.3005,0.3005,[329440]|0.1416,0.1416,[329440]|0.1205,0.1205,[329440]|0.0884,0.0884,[329440]|0.1205,0.1205,[329440]|0.1766,0.1766,[329440]|0.1266,0.1266,[329440]|0.1766,0.1766,[329440]|0.1667,0.1667,[329440]|0.0991,0.0991,[329440]|0.1041,0.1041,[329440]|0.0935,0.0935,[329440]|0.0723,0.0723,[329440]|0.0771,0.0771,[329440]|0.0554,0.0554,[329440]|0.0771,0.0771,[329440]|0.066,0.066,[329440]|0.0935,0.0935,[329440]|0.0526,0.0526,[329440]|0.0771,0.0771,[329440]|0.0744,0.0744,[329440]|0.1805,0.1805,[329440]|0.2041,0.2041,[329440]|0.2333,0.2333,[329440]|0.2385,0.2385,[329440]|0.1667,0.1667,[329440]|0.2167,0.2167,[329440]|0.2255,0.2255,[329440]|0.2333,0.2333,[329440]|0.3717,0.3717,[329440]|0.4037,0.4037,[329440]|0.3399,0.3399,[329440]|0.3806,0.3806,[329440]|0.4333,0.4333,[329440]|0.4918,0.4918,[329440]|0.442,0.442,[329440]|0.3992,0.3992,[329440]|0.3399,0.3399,[329440]|0.4037,0.4037,[329440]|0.3456,0.3456,[329440]|0.2963,0.2963,[329440]|0.2602,0.2602,[329440]|0.1805,0.1805,[329440]|0.2209,0.2209,[329440]|0.2167,0.2167,[329440]|0.2963,0.2963,[329440]|0.3717,0.3717,[329440]|0.2913,0.2913,[329440]|0.2786,0.2786,[329440]|0.1921,0.1921,[329440]|0.2122,0.2122,[329440]|0.27,0.27,[329440]|0.2292,0.2292,[329440]|0.1998,0.1998,[329440]|0.2209,0.2209,[329440]|0.2963,0.2963,[329440]|0.3717,0.3717,[329440]|0.363,0.363,[329440]|0.3578,0.3578,[329440]|0.3992,0.3992,[329440]|0.4967,0.4967,[329440]|0.5549,0.5549,[329440]|0.665,0.665,[329440]|0.6681,0.6681,[329440]|0.7111,0.7111,[329440]|0.7501,0.7501,[329440]|0.7772,0.7772,[329440]|0.8677,0.8677,[329440]|0.904,0.904,[329440]|
+GRAPHLINE      IUPredWS (Short)        0.5     Above 0.5 indicates disorder    ff0000
+
+SEQUENCE_REF   FER1_PEA
+LINE_GRAPH     IUPredWS (Long) <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.1914,0.1914,[72144e]|0.2258,0.2258,[72144e]|0.268,0.268,[72144e]|0.2951,0.2951,[72144e]|0.1914,0.1914,[72144e]|0.1476,0.1476,[72144e]|0.1823,0.1823,[72144e]|0.2094,0.2094,[72144e]|0.247,0.247,[72144e]|0.3019,0.3019,[72144e]|0.2918,0.2918,[72144e]|0.3566,0.3566,[72144e]|0.3566,0.3566,[72144e]|0.3529,0.3529,[72144e]|0.2988,0.2988,[72144e]|0.2752,0.2752,[72144e]|0.3087,0.3087,[72144e]|0.3774,0.3774,[72144e]|0.4541,0.4541,[72144e]|0.4256,0.4256,[72144e]|0.3215,0.3215,[72144e]|0.346,0.346,[72144e]|0.4087,0.4087,[72144e]|0.4087,0.4087,[72144e]|0.3087,0.3087,[72144e]|0.2436,0.2436,[72144e]|0.3392,0.3392,[72144e]|0.3321,0.3321,[72144e]|0.3182,0.3182,[72144e]|0.3182,0.3182,[72144e]|0.3053,0.3053,[72144e]|0.2064,0.2064,[72144e]|0.2258,0.2258,[72144e]|0.1969,0.1969,[72144e]|0.2258,0.2258,[72144e]|0.2399,0.2399,[72144e]|0.2292,0.2292,[72144e]|0.2002,0.2002,[72144e]|0.1501,0.1501,[72144e]|0.1275,0.1275,[72144e]|0.1162,0.1162,[72144e]|0.1162,0.1162,[72144e]|0.1942,0.1942,[72144e]|0.1298,0.1298,[72144e]|0.1162,0.1162,[72144e]|0.0851,0.0851,[72144e]|0.1424,0.1424,[72144e]|0.1424,0.1424,[72144e]|0.1048,0.1048,[72144e]|0.1554,0.1554,[72144e]|0.1914,0.1914,[72144e]|0.2002,0.2002,[72144e]|0.2034,0.2034,[72144e]|0.2752,0.2752,[72144e]|0.2918,0.2918,[72144e]|0.3117,0.3117,[72144e]|0.2164,0.2164,[72144e]|0.2258,0.2258,[72144e]|0.2002,0.2002,[72144e]|0.2575,0.2575,[72144e]|0.3087,0.3087,[72144e]|0.3494,0.3494,[72144e]|0.3249,0.3249,[72144e]|0.2752,0.2752,[72144e]|0.2064,0.2064,[72144e]|0.2193,0.2193,[72144e]|0.247,0.247,[72144e]|0.2988,0.2988,[72144e]|0.2817,0.2817,[72144e]|0.3704,0.3704,[72144e]|0.4476,0.4476,[72144e]|0.391,0.391,[72144e]|0.3599,0.3599,[72144e]|0.2752,0.2752,[72144e]|0.2884,0.2884,[72144e]|0.2164,0.2164,[72144e]|0.2399,0.2399,[72144e]|0.1476,0.1476,[72144e]|0.2328,0.2328,[72144e]|0.1323,0.1323,[72144e]|0.2129,0.2129,[72144e]|0.1583,0.1583,[72144e]|0.1611,0.1611,[72144e]|0.1476,0.1476,[72144e]|0.1206,0.1206,[72144e]|0.1791,0.1791,[72144e]|0.2436,0.2436,[72144e]|0.2164,0.2164,[72144e]|0.1791,0.1791,[72144e]|0.1823,0.1823,[72144e]|0.1969,0.1969,[72144e]|0.1349,0.1349,[72144e]|0.0851,0.0851,[72144e]|0.1184,0.1184,[72144e]|0.1162,0.1162,[72144e]|0.1914,0.1914,[72144e]|0.1373,0.1373,[72144e]|0.2034,0.2034,[72144e]|0.1823,0.1823,[72144e]|0.2575,0.2575,[72144e]|0.2715,0.2715,[72144e]|0.3631,0.3631,[72144e]|0.3599,0.3599,[72144e]|0.2918,0.2918,[72144e]|0.2258,0.2258,[72144e]|0.2436,0.2436,[72144e]|0.3494,0.3494,[72144e]|0.374,0.374,[72144e]|0.384,0.384,[72144e]|0.4051,0.4051,[72144e]|0.4541,0.4541,[72144e]|0.4256,0.4256,[72144e]|0.4369,0.4369,[72144e]|0.391,0.391,[72144e]|0.3948,0.3948,[72144e]|0.3249,0.3249,[72144e]|0.3286,0.3286,[72144e]|0.2064,0.2064,[72144e]|0.2034,0.2034,[72144e]|0.1702,0.1702,[72144e]|0.1092,0.1092,[72144e]|0.1323,0.1323,[72144e]|0.1251,0.1251,[72144e]|0.1251,0.1251,[72144e]|0.1373,0.1373,[72144e]|0.1702,0.1702,[72144e]|0.1852,0.1852,[72144e]|0.1184,0.1184,[72144e]|0.1251,0.1251,[72144e]|0.1115,0.1115,[72144e]|0.1028,0.1028,[72144e]|0.1424,0.1424,[72144e]|0.1449,0.1449,[72144e]|0.1881,0.1881,[72144e]|0.2064,0.2064,[72144e]|0.2328,0.2328,[72144e]|0.2849,0.2849,[72144e]|0.3249,0.3249,[72144e]|0.3182,0.3182,[72144e]|0.4051,0.4051,[72144e]|0.4541,0.4541,[72144e]|0.4725,0.4725,[72144e]|0.5577,0.5577,[72144e]|0.494,0.494,[72144e]|0.4766,0.4766,[72144e]|0.4619,0.4619,[72144e]|0.4149,0.4149,[72144e]|0.4831,0.4831,[72144e]|0.5665,0.5665,[72144e]|
+GRAPHLINE      IUPredWS (Long) 0.5     Above 0.5 indicates disorder    ff0000
+LINE_GRAPH     IUPredWS (Short)        <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.8781,0.8781,[329440]|0.82,0.82,[329440]|0.7192,0.7192,[329440]|0.6756,0.6756,[329440]|0.6374,0.6374,[329440]|0.59,0.59,[329440]|0.4651,0.4651,[329440]|0.3578,0.3578,[329440]|0.3359,0.3359,[329440]|0.2913,0.2913,[329440]|0.2531,0.2531,[329440]|0.3184,0.3184,[329440]|0.3184,0.3184,[329440]|0.3491,0.3491,[329440]|0.3184,0.3184,[329440]|0.2913,0.2913,[329440]|0.2531,0.2531,[329440]|0.2963,0.2963,[329440]|0.3806,0.3806,[329440]|0.3762,0.3762,[329440]|0.3806,0.3806,[329440]|0.3717,0.3717,[329440]|0.3399,0.3399,[329440]|0.3456,0.3456,[329440]|0.3535,0.3535,[329440]|0.3535,0.3535,[329440]|0.3359,0.3359,[329440]|0.3263,0.3263,[329440]|0.3053,0.3053,[329440]|0.2333,0.2333,[329440]|0.2255,0.2255,[329440]|0.1958,0.1958,[329440]|0.2292,0.2292,[329440]|0.1456,0.1456,[329440]|0.1698,0.1698,[329440]|0.1805,0.1805,[329440]|0.2385,0.2385,[329440]|0.2483,0.2483,[329440]|0.1878,0.1878,[329440]|0.1635,0.1635,[329440]|0.1178,0.1178,[329440]|0.1205,0.1205,[329440]|0.1667,0.1667,[329440]|0.138,0.138,[329440]|0.1322,0.1322,[329440]|0.0813,0.0813,[329440]|0.1416,0.1416,[329440]|0.1456,0.1456,[329440]|0.1349,0.1349,[329440]|0.1322,0.1322,[329440]|0.0909,0.0909,[329440]|0.0884,0.0884,[329440]|0.1088,0.1088,[329440]|0.1602,0.1602,[329440]|0.1635,0.1635,[329440]|0.1495,0.1495,[329440]|0.1635,0.1635,[329440]|0.1698,0.1698,[329440]|0.1456,0.1456,[329440]|0.1844,0.1844,[329440]|0.1602,0.1602,[329440]|0.2167,0.2167,[329440]|0.2483,0.2483,[329440]|0.3053,0.3053,[329440]|0.2483,0.2483,[329440]|0.2602,0.2602,[329440]|0.2122,0.2122,[329440]|0.2167,0.2167,[329440]|0.2432,0.2432,[329440]|0.3053,0.3053,[329440]|0.3263,0.3263,[329440]|0.3491,0.3491,[329440]|0.3359,0.3359,[329440]|0.2748,0.2748,[329440]|0.27,0.27,[329440]|0.2209,0.2209,[329440]|0.2292,0.2292,[329440]|0.1602,0.1602,[329440]|0.2748,0.2748,[329440]|0.1921,0.1921,[329440]|0.282,0.282,[329440]|0.1322,0.1322,[329440]|0.124,0.124,[329440]|0.0909,0.0909,[329440]|0.124,0.124,[329440]|0.1805,0.1805,[329440]|0.1322,0.1322,[329440]|0.1844,0.1844,[329440]|0.1732,0.1732,[329440]|0.1041,0.1041,[329440]|0.1088,0.1088,[329440]|0.0991,0.0991,[329440]|0.0771,0.0771,[329440]|0.0771,0.0771,[329440]|0.0554,0.0554,[329440]|0.0771,0.0771,[329440]|0.0643,0.0643,[329440]|0.1088,0.1088,[329440]|0.0607,0.0607,[329440]|0.1041,0.1041,[329440]|0.1041,0.1041,[329440]|0.208,0.208,[329440]|0.2385,0.2385,[329440]|0.2657,0.2657,[329440]|0.2748,0.2748,[329440]|0.1878,0.1878,[329440]|0.2292,0.2292,[329440]|0.2483,0.2483,[329440]|0.2602,0.2602,[329440]|0.3992,0.3992,[329440]|0.4282,0.4282,[329440]|0.363,0.363,[329440]|0.4116,0.4116,[329440]|0.4333,0.4333,[329440]|0.4967,0.4967,[329440]|0.4203,0.4203,[329440]|0.3762,0.3762,[329440]|0.282,0.282,[329440]|0.3399,0.3399,[329440]|0.2558,0.2558,[329440]|0.1998,0.1998,[329440]|0.1844,0.1844,[329440]|0.1178,0.1178,[329440]|0.1566,0.1566,[329440]|0.1495,0.1495,[329440]|0.2209,0.2209,[329440]|0.3053,0.3053,[329440]|0.2209,0.2209,[329440]|0.1566,0.1566,[329440]|0.0884,0.0884,[329440]|0.1041,0.1041,[329440]|0.1416,0.1416,[329440]|0.1178,0.1178,[329440]|0.1205,0.1205,[329440]|0.1349,0.1349,[329440]|0.2209,0.2209,[329440]|0.282,0.282,[329440]|0.2748,0.2748,[329440]|0.27,0.27,[329440]|0.3005,0.3005,[329440]|0.3992,0.3992,[329440]|0.4703,0.4703,[329440]|0.5762,0.5762,[329440]|0.5762,0.5762,[329440]|0.6293,0.6293,[329440]|0.6715,0.6715,[329440]|0.6944,0.6944,[329440]|0.7951,0.7951,[329440]|0.8781,0.8781,[329440]|
+GRAPHLINE      IUPredWS (Short)        0.5     Above 0.5 indicates disorder    ff0000
+
+SEQUENCE_REF   FER1_SPIOL
+LINE_GRAPH     IUPredWS (Long) <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.3704,0.3704,[72144e]|0.4017,0.4017,[72144e]|0.4292,0.4292,[72144e]|0.3215,0.3215,[72144e]|0.2884,0.2884,[72144e]|0.3566,0.3566,[72144e]|0.3704,0.3704,[72144e]|0.4292,0.4292,[72144e]|0.4619,0.4619,[72144e]|0.4476,0.4476,[72144e]|0.4979,0.4979,[72144e]|0.5807,0.5807,[72144e]|0.5758,0.5758,[72144e]|0.5758,0.5758,[72144e]|0.5382,0.5382,[72144e]|0.5055,0.5055,[72144e]|0.4409,0.4409,[72144e]|0.4685,0.4685,[72144e]|0.5055,0.5055,[72144e]|0.5493,0.5493,[72144e]|0.5533,0.5533,[72144e]|0.5951,0.5951,[72144e]|0.6375,0.6375,[72144e]|0.6322,0.6322,[72144e]|0.5493,0.5493,[72144e]|0.5456,0.5456,[72144e]|0.6043,0.6043,[72144e]|0.4864,0.4864,[72144e]|0.4864,0.4864,[72144e]|0.4507,0.4507,[72144e]|0.4369,0.4369,[72144e]|0.4652,0.4652,[72144e]|0.433,0.433,[72144e]|0.3983,0.3983,[72144e]|0.4292,0.4292,[72144e]|0.4619,0.4619,[72144e]|0.4541,0.4541,[72144e]|0.4831,0.4831,[72144e]|0.5176,0.5176,[72144e]|0.4541,0.4541,[72144e]|0.433,0.433,[72144e]|0.3392,0.3392,[72144e]|0.3286,0.3286,[72144e]|0.2849,0.2849,[72144e]|0.2817,0.2817,[72144e]|0.2224,0.2224,[72144e]|0.2364,0.2364,[72144e]|0.3249,0.3249,[72144e]|0.3599,0.3599,[72144e]|0.422,0.422,[72144e]|0.433,0.433,[72144e]|0.4409,0.4409,[72144e]|0.391,0.391,[72144e]|0.4186,0.4186,[72144e]|0.3249,0.3249,[72144e]|0.3392,0.3392,[72144e]|0.247,0.247,[72144e]|0.2783,0.2783,[72144e]|0.3215,0.3215,[72144e]|0.3321,0.3321,[72144e]|0.3182,0.3182,[72144e]|0.2609,0.2609,[72144e]|0.2193,0.2193,[72144e]|0.247,0.247,[72144e]|0.268,0.268,[72144e]|0.2918,0.2918,[72144e]|0.2645,0.2645,[72144e]|0.3494,0.3494,[72144e]|0.422,0.422,[72144e]|0.4476,0.4476,[72144e]|0.4119,0.4119,[72144e]|0.346,0.346,[72144e]|0.3599,0.3599,[72144e]|0.2918,0.2918,[72144e]|0.3704,0.3704,[72144e]|0.2609,0.2609,[72144e]|0.3494,0.3494,[72144e]|0.2436,0.2436,[72144e]|0.3392,0.3392,[72144e]|0.2817,0.2817,[72144e]|0.268,0.268,[72144e]|0.2541,0.2541,[72144e]|0.2094,0.2094,[72144e]|0.2951,0.2951,[72144e]|0.3566,0.3566,[72144e]|0.3215,0.3215,[72144e]|0.2849,0.2849,[72144e]|0.3117,0.3117,[72144e]|0.3249,0.3249,[72144e]|0.2399,0.2399,[72144e]|0.2064,0.2064,[72144e]|0.1852,0.1852,[72144e]|0.1852,0.1852,[72144e]|0.247,0.247,[72144e]|0.1759,0.1759,[72144e]|0.2399,0.2399,[72144e]|0.2193,0.2193,[72144e]|0.3182,0.3182,[72144e]|0.3321,0.3321,[72144e]|0.4369,0.4369,[72144e]|0.433,0.433,[72144e]|0.3704,0.3704,[72144e]|0.3087,0.3087,[72144e]|0.3249,0.3249,[72144e]|0.433,0.433,[72144e]|0.4476,0.4476,[72144e]|0.4582,0.4582,[72144e]|0.4864,0.4864,[72144e]|0.5296,0.5296,[72144e]|0.5533,0.5533,[72144e]|0.5665,0.5665,[72144e]|0.5456,0.5456,[72144e]|0.5017,0.5017,[72144e]|0.4369,0.4369,[72144e]|0.4369,0.4369,[72144e]|0.3356,0.3356,[72144e]|0.374,0.374,[72144e]|0.3426,0.3426,[72144e]|0.2609,0.2609,[72144e]|0.2817,0.2817,[72144e]|0.2164,0.2164,[72144e]|0.2034,0.2034,[72144e]|0.2164,0.2164,[72144e]|0.2541,0.2541,[72144e]|0.3117,0.3117,[72144e]|0.2328,0.2328,[72144e]|0.2399,0.2399,[72144e]|0.2224,0.2224,[72144e]|0.2064,0.2064,[72144e]|0.2575,0.2575,[72144e]|0.268,0.268,[72144e]|0.268,0.268,[72144e]|0.2918,0.2918,[72144e]|0.3019,0.3019,[72144e]|0.3494,0.3494,[72144e]|0.3872,0.3872,[72144e]|0.3807,0.3807,[72144e]|0.4685,0.4685,[72144e]|0.4831,0.4831,[72144e]|0.494,0.494,[72144e]|0.5854,0.5854,[72144e]|0.5176,0.5176,[72144e]|0.5807,0.5807,[72144e]|0.5707,0.5707,[72144e]|0.5296,0.5296,[72144e]|0.6183,0.6183,[72144e]|0.5992,0.5992,[72144e]|
+GRAPHLINE      IUPredWS (Long) 0.5     Above 0.5 indicates disorder    ff0000
+LINE_GRAPH     IUPredWS (Short)        <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.928,0.928,[329440]|0.8823,0.8823,[329440]|0.8283,0.8283,[329440]|0.7912,0.7912,[329440]|0.754,0.754,[329440]|0.6474,0.6474,[329440]|0.5473,0.5473,[329440]|0.5296,0.5296,[329440]|0.4918,0.4918,[329440]|0.4703,0.4703,[329440]|0.4379,0.4379,[329440]|0.442,0.442,[329440]|0.4967,0.4967,[329440]|0.5514,0.5514,[329440]|0.5229,0.5229,[329440]|0.4879,0.4879,[329440]|0.4556,0.4556,[329440]|0.4245,0.4245,[329440]|0.4116,0.4116,[329440]|0.4651,0.4651,[329440]|0.4703,0.4703,[329440]|0.5008,0.5008,[329440]|0.5296,0.5296,[329440]|0.5229,0.5229,[329440]|0.5374,0.5374,[329440]|0.6174,0.6174,[329440]|0.6255,0.6255,[329440]|0.5253,0.5253,[329440]|0.5229,0.5229,[329440]|0.4458,0.4458,[329440]|0.442,0.442,[329440]|0.4781,0.4781,[329440]|0.4513,0.4513,[329440]|0.4282,0.4282,[329440]|0.4749,0.4749,[329440]|0.4967,0.4967,[329440]|0.5173,0.5173,[329440]|0.5549,0.5549,[329440]|0.5667,0.5667,[329440]|0.5412,0.5412,[329440]|0.5043,0.5043,[329440]|0.4703,0.4703,[329440]|0.4333,0.4333,[329440]|0.3578,0.3578,[329440]|0.3311,0.3311,[329440]|0.2657,0.2657,[329440]|0.3146,0.3146,[329440]|0.3263,0.3263,[329440]|0.27,0.27,[329440]|0.3184,0.3184,[329440]|0.3456,0.3456,[329440]|0.3311,0.3311,[329440]|0.3311,0.3311,[329440]|0.3311,0.3311,[329440]|0.2531,0.2531,[329440]|0.2748,0.2748,[329440]|0.1921,0.1921,[329440]|0.1921,0.1921,[329440]|0.1456,0.1456,[329440]|0.1698,0.1698,[329440]|0.2122,0.2122,[329440]|0.2657,0.2657,[329440]|0.2333,0.2333,[329440]|0.2385,0.2385,[329440]|0.1844,0.1844,[329440]|0.1844,0.1844,[329440]|0.1998,0.1998,[329440]|0.2209,0.2209,[329440]|0.2432,0.2432,[329440]|0.2657,0.2657,[329440]|0.2483,0.2483,[329440]|0.27,0.27,[329440]|0.2657,0.2657,[329440]|0.208,0.208,[329440]|0.282,0.282,[329440]|0.1998,0.1998,[329440]|0.3184,0.3184,[329440]|0.2432,0.2432,[329440]|0.3359,0.3359,[329440]|0.1732,0.1732,[329440]|0.1566,0.1566,[329440]|0.1205,0.1205,[329440]|0.1602,0.1602,[329440]|0.2255,0.2255,[329440]|0.1766,0.1766,[329440]|0.2385,0.2385,[329440]|0.2255,0.2255,[329440]|0.1635,0.1635,[329440]|0.1732,0.1732,[329440]|0.1602,0.1602,[329440]|0.1292,0.1292,[329440]|0.0771,0.0771,[329440]|0.0701,0.0701,[329440]|0.0965,0.0965,[329440]|0.0858,0.0858,[329440]|0.124,0.124,[329440]|0.0643,0.0643,[329440]|0.0935,0.0935,[329440]|0.0935,0.0935,[329440]|0.2041,0.2041,[329440]|0.2209,0.2209,[329440]|0.2602,0.2602,[329440]|0.2657,0.2657,[329440]|0.1878,0.1878,[329440]|0.2333,0.2333,[329440]|0.2432,0.2432,[329440]|0.2531,0.2531,[329440]|0.3668,0.3668,[329440]|0.3992,0.3992,[329440]|0.3359,0.3359,[329440]|0.3717,0.3717,[329440]|0.4556,0.4556,[329440]|0.4825,0.4825,[329440]|0.4245,0.4245,[329440]|0.3806,0.3806,[329440]|0.3146,0.3146,[329440]|0.3762,0.3762,[329440]|0.3263,0.3263,[329440]|0.3005,0.3005,[329440]|0.2748,0.2748,[329440]|0.1998,0.1998,[329440]|0.2432,0.2432,[329440]|0.1878,0.1878,[329440]|0.2657,0.2657,[329440]|0.3456,0.3456,[329440]|0.2748,0.2748,[329440]|0.2558,0.2558,[329440]|0.1766,0.1766,[329440]|0.1998,0.1998,[329440]|0.2483,0.2483,[329440]|0.2209,0.2209,[329440]|0.1732,0.1732,[329440]|0.1921,0.1921,[329440]|0.282,0.282,[329440]|0.3535,0.3535,[329440]|0.3456,0.3456,[329440]|0.3491,0.3491,[329440]|0.3806,0.3806,[329440]|0.4458,0.4458,[329440]|0.5084,0.5084,[329440]|0.6174,0.6174,[329440]|0.6174,0.6174,[329440]|0.7034,0.7034,[329440]|0.7418,0.7418,[329440]|0.7644,0.7644,[329440]|0.8595,0.8595,[329440]|0.8945,0.8945,[329440]|
+GRAPHLINE      IUPredWS (Short)        0.5     Above 0.5 indicates disorder    ff0000
+
+SEQUENCE_REF   FER1_MESCR
+LINE_GRAPH     IUPredWS (Long) <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.3117,0.3117,[72144e]|0.2951,0.2951,[72144e]|0.3249,0.3249,[72144e]|0.3566,0.3566,[72144e]|0.3426,0.3426,[72144e]|0.2503,0.2503,[72144e]|0.2436,0.2436,[72144e]|0.3053,0.3053,[72144e]|0.3182,0.3182,[72144e]|0.3426,0.3426,[72144e]|0.3948,0.3948,[72144e]|0.4619,0.4619,[72144e]|0.4619,0.4619,[72144e]|0.4979,0.4979,[72144e]|0.4652,0.4652,[72144e]|0.433,0.433,[72144e]|0.3983,0.3983,[72144e]|0.4619,0.4619,[72144e]|0.5296,0.5296,[72144e]|0.5419,0.5419,[72144e]|0.4864,0.4864,[72144e]|0.5176,0.5176,[72144e]|0.5176,0.5176,[72144e]|0.5176,0.5176,[72144e]|0.4541,0.4541,[72144e]|0.3599,0.3599,[72144e]|0.3872,0.3872,[72144e]|0.4119,0.4119,[72144e]|0.4292,0.4292,[72144e]|0.3948,0.3948,[72144e]|0.4017,0.4017,[72144e]|0.3704,0.3704,[72144e]|0.3356,0.3356,[72144e]|0.3053,0.3053,[72144e]|0.3286,0.3286,[72144e]|0.3286,0.3286,[72144e]|0.3087,0.3087,[72144e]|0.3392,0.3392,[72144e]|0.3774,0.3774,[72144e]|0.3182,0.3182,[72144e]|0.2918,0.2918,[72144e]|0.2609,0.2609,[72144e]|0.2292,0.2292,[72144e]|0.2224,0.2224,[72144e]|0.1731,0.1731,[72144e]|0.2002,0.2002,[72144e]|0.2002,0.2002,[72144e]|0.2436,0.2436,[72144e]|0.247,0.247,[72144e]|0.3426,0.3426,[72144e]|0.3774,0.3774,[72144e]|0.3774,0.3774,[72144e]|0.3704,0.3704,[72144e]|0.4087,0.4087,[72144e]|0.4369,0.4369,[72144e]|0.3704,0.3704,[72144e]|0.3948,0.3948,[72144e]|0.2988,0.2988,[72144e]|0.3807,0.3807,[72144e]|0.391,0.391,[72144e]|0.4292,0.4292,[72144e]|0.4087,0.4087,[72144e]|0.3529,0.3529,[72144e]|0.3149,0.3149,[72144e]|0.3356,0.3356,[72144e]|0.3566,0.3566,[72144e]|0.3807,0.3807,[72144e]|0.3529,0.3529,[72144e]|0.4369,0.4369,[72144e]|0.5139,0.5139,[72144e]|0.4831,0.4831,[72144e]|0.4507,0.4507,[72144e]|0.3566,0.3566,[72144e]|0.3704,0.3704,[72144e]|0.3182,0.3182,[72144e]|0.3392,0.3392,[72144e]|0.2328,0.2328,[72144e]|0.2951,0.2951,[72144e]|0.1791,0.1791,[72144e]|0.2715,0.2715,[72144e]|0.2164,0.2164,[72144e]|0.2002,0.2002,[72144e]|0.1881,0.1881,[72144e]|0.1501,0.1501,[72144e]|0.2164,0.2164,[72144e]|0.2817,0.2817,[72144e]|0.247,0.247,[72144e]|0.2094,0.2094,[72144e]|0.2328,0.2328,[72144e]|0.2503,0.2503,[72144e]|0.1823,0.1823,[72144e]|0.1643,0.1643,[72144e]|0.1852,0.1852,[72144e]|0.1852,0.1852,[72144e]|0.247,0.247,[72144e]|0.1881,0.1881,[72144e]|0.2541,0.2541,[72144e]|0.2328,0.2328,[72144e]|0.3249,0.3249,[72144e]|0.3426,0.3426,[72144e]|0.4369,0.4369,[72144e]|0.4292,0.4292,[72144e]|0.3704,0.3704,[72144e]|0.3117,0.3117,[72144e]|0.3249,0.3249,[72144e]|0.4369,0.4369,[72144e]|0.4507,0.4507,[72144e]|0.4619,0.4619,[72144e]|0.4901,0.4901,[72144e]|0.5055,0.5055,[72144e]|0.5342,0.5342,[72144e]|0.5419,0.5419,[72144e]|0.5176,0.5176,[72144e]|0.4619,0.4619,[72144e]|0.4017,0.4017,[72144e]|0.4051,0.4051,[72144e]|0.3053,0.3053,[72144e]|0.3053,0.3053,[72144e]|0.2783,0.2783,[72144e]|0.1942,0.1942,[72144e]|0.2129,0.2129,[72144e]|0.2002,0.2002,[72144e]|0.2002,0.2002,[72144e]|0.2129,0.2129,[72144e]|0.2503,0.2503,[72144e]|0.3117,0.3117,[72144e]|0.2328,0.2328,[72144e]|0.2399,0.2399,[72144e]|0.2258,0.2258,[72144e]|0.2129,0.2129,[72144e]|0.268,0.268,[72144e]|0.2988,0.2988,[72144e]|0.2988,0.2988,[72144e]|0.3215,0.3215,[72144e]|0.3249,0.3249,[72144e]|0.3774,0.3774,[72144e]|0.4149,0.4149,[72144e]|0.4051,0.4051,[72144e]|0.4979,0.4979,[72144e]|0.5456,0.5456,[72144e]|0.5665,0.5665,[72144e]|0.6755,0.6755,[72144e]|0.6089,0.6089,[72144e]|0.5901,0.5901,[72144e]|0.5807,0.5807,[72144e]|0.5382,0.5382,[72144e]|0.6227,0.6227,[72144e]|0.6043,0.6043,[72144e]|
+GRAPHLINE      IUPredWS (Long) 0.5     Above 0.5 indicates disorder    ff0000
+LINE_GRAPH     IUPredWS (Short)        <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.8857,0.8857,[329440]|0.8283,0.8283,[329440]|0.7912,0.7912,[329440]|0.7111,0.7111,[329440]|0.6681,0.6681,[329440]|0.6334,0.6334,[329440]|0.5549,0.5549,[329440]|0.4282,0.4282,[329440]|0.3491,0.3491,[329440]|0.3311,0.3311,[329440]|0.2913,0.2913,[329440]|0.3225,0.3225,[329440]|0.3806,0.3806,[329440]|0.442,0.442,[329440]|0.4078,0.4078,[329440]|0.4078,0.4078,[329440]|0.4078,0.4078,[329440]|0.4037,0.4037,[329440]|0.3939,0.3939,[329440]|0.4203,0.4203,[329440]|0.4245,0.4245,[329440]|0.46,0.46,[329440]|0.3885,0.3885,[329440]|0.4282,0.4282,[329440]|0.4556,0.4556,[329440]|0.4149,0.4149,[329440]|0.3885,0.3885,[329440]|0.3847,0.3847,[329440]|0.4203,0.4203,[329440]|0.3263,0.3263,[329440]|0.3311,0.3311,[329440]|0.3263,0.3263,[329440]|0.3005,0.3005,[329440]|0.2432,0.2432,[329440]|0.2786,0.2786,[329440]|0.2865,0.2865,[329440]|0.3146,0.3146,[329440]|0.3578,0.3578,[329440]|0.3535,0.3535,[329440]|0.3263,0.3263,[329440]|0.2913,0.2913,[329440]|0.2558,0.2558,[329440]|0.2913,0.2913,[329440]|0.2657,0.2657,[329440]|0.1698,0.1698,[329440]|0.1958,0.1958,[329440]|0.1878,0.1878,[329440]|0.2748,0.2748,[329440]|0.2041,0.2041,[329440]|0.208,0.208,[329440]|0.1998,0.1998,[329440]|0.2333,0.2333,[329440]|0.2483,0.2483,[329440]|0.2748,0.2748,[329440]|0.2602,0.2602,[329440]|0.2602,0.2602,[329440]|0.2786,0.2786,[329440]|0.1998,0.1998,[329440]|0.2748,0.2748,[329440]|0.1844,0.1844,[329440]|0.2483,0.2483,[329440]|0.2913,0.2913,[329440]|0.3456,0.3456,[329440]|0.3184,0.3184,[329440]|0.3225,0.3225,[329440]|0.27,0.27,[329440]|0.2748,0.2748,[329440]|0.2913,0.2913,[329440]|0.3225,0.3225,[329440]|0.3491,0.3491,[329440]|0.3717,0.3717,[329440]|0.3578,0.3578,[329440]|0.3053,0.3053,[329440]|0.3005,0.3005,[329440]|0.2602,0.2602,[329440]|0.2748,0.2748,[329440]|0.1958,0.1958,[329440]|0.3005,0.3005,[329440]|0.2209,0.2209,[329440]|0.3146,0.3146,[329440]|0.1602,0.1602,[329440]|0.138,0.138,[329440]|0.1041,0.1041,[329440]|0.1349,0.1349,[329440]|0.1958,0.1958,[329440]|0.1532,0.1532,[329440]|0.2041,0.2041,[329440]|0.1878,0.1878,[329440]|0.1322,0.1322,[329440]|0.1349,0.1349,[329440]|0.124,0.124,[329440]|0.0991,0.0991,[329440]|0.0884,0.0884,[329440]|0.0858,0.0858,[329440]|0.1205,0.1205,[329440]|0.1018,0.1018,[329440]|0.138,0.138,[329440]|0.0832,0.0832,[329440]|0.1178,0.1178,[329440]|0.1178,0.1178,[329440]|0.2385,0.2385,[329440]|0.2602,0.2602,[329440]|0.2865,0.2865,[329440]|0.2963,0.2963,[329440]|0.2209,0.2209,[329440]|0.2748,0.2748,[329440]|0.2865,0.2865,[329440]|0.2963,0.2963,[329440]|0.4116,0.4116,[329440]|0.442,0.442,[329440]|0.3885,0.3885,[329440]|0.3885,0.3885,[329440]|0.4651,0.4651,[329440]|0.4749,0.4749,[329440]|0.4149,0.4149,[329440]|0.3762,0.3762,[329440]|0.3184,0.3184,[329440]|0.3668,0.3668,[329440]|0.3146,0.3146,[329440]|0.2657,0.2657,[329440]|0.2333,0.2333,[329440]|0.1667,0.1667,[329440]|0.208,0.208,[329440]|0.1998,0.1998,[329440]|0.2786,0.2786,[329440]|0.3456,0.3456,[329440]|0.2786,0.2786,[329440]|0.2602,0.2602,[329440]|0.1844,0.1844,[329440]|0.1998,0.1998,[329440]|0.2483,0.2483,[329440]|0.2531,0.2531,[329440]|0.2041,0.2041,[329440]|0.2209,0.2209,[329440]|0.2963,0.2963,[329440]|0.3578,0.3578,[329440]|0.3491,0.3491,[329440]|0.3491,0.3491,[329440]|0.3847,0.3847,[329440]|0.4781,0.4781,[329440]|0.5374,0.5374,[329440]|0.6442,0.6442,[329440]|0.6474,0.6474,[329440]|0.6944,0.6944,[329440]|0.7317,0.7317,[329440]|0.7605,0.7605,[329440]|0.8521,0.8521,[329440]|0.892,0.892,[329440]|
+GRAPHLINE      IUPredWS (Short)        0.5     Above 0.5 indicates disorder    ff0000
+
+SEQUENCE_REF   FER_CAPAN
+LINE_GRAPH     IUPredWS (Long) <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.2817,0.2817,[72144e]|0.1671,0.1671,[72144e]|0.1611,0.1611,[72144e]|0.2364,0.2364,[72144e]|0.2783,0.2783,[72144e]|0.2951,0.2951,[72144e]|0.3566,0.3566,[72144e]|0.3426,0.3426,[72144e]|0.3053,0.3053,[72144e]|0.3286,0.3286,[72144e]|0.346,0.346,[72144e]|0.3087,0.3087,[72144e]|0.3321,0.3321,[72144e]|0.3631,0.3631,[72144e]|0.346,0.346,[72144e]|0.3774,0.3774,[72144e]|0.4119,0.4119,[72144e]|0.3566,0.3566,[72144e]|0.384,0.384,[72144e]|0.4766,0.4766,[72144e]|0.4507,0.4507,[72144e]|0.3807,0.3807,[72144e]|0.2884,0.2884,[72144e]|0.3774,0.3774,[72144e]|0.3392,0.3392,[72144e]|0.3019,0.3019,[72144e]|0.2918,0.2918,[72144e]|0.2752,0.2752,[72144e]|0.2503,0.2503,[72144e]|0.2752,0.2752,[72144e]|0.3249,0.3249,[72144e]|0.3149,0.3149,[72144e]|0.2645,0.2645,[72144e]|0.3356,0.3356,[72144e]|0.2399,0.2399,[72144e]|0.1852,0.1852,[72144e]|0.2258,0.2258,[72144e]|0.1969,0.1969,[72144e]|0.1251,0.1251,[72144e]|0.1583,0.1583,[72144e]|0.1229,0.1229,[72144e]|0.1028,0.1028,[72144e]|0.0817,0.0817,[72144e]|0.0799,0.0799,[72144e]|0.1399,0.1399,[72144e]|0.1611,0.1611,[72144e]|0.2399,0.2399,[72144e]|0.247,0.247,[72144e]|0.2783,0.2783,[72144e]|0.2328,0.2328,[72144e]|0.2503,0.2503,[72144e]|0.1643,0.1643,[72144e]|0.1791,0.1791,[72144e]|0.1092,0.1092,[72144e]|0.1702,0.1702,[72144e]|0.1791,0.1791,[72144e]|0.2849,0.2849,[72144e]|0.2645,0.2645,[72144e]|0.2094,0.2094,[72144e]|0.1501,0.1501,[72144e]|0.1643,0.1643,[72144e]|0.1852,0.1852,[72144e]|0.247,0.247,[72144e]|0.2258,0.2258,[72144e]|0.3149,0.3149,[72144e]|0.4087,0.4087,[72144e]|0.3774,0.3774,[72144e]|0.346,0.346,[72144e]|0.3286,0.3286,[72144e]|0.3494,0.3494,[72144e]|0.2541,0.2541,[72144e]|0.3529,0.3529,[72144e]|0.247,0.247,[72144e]|0.3392,0.3392,[72144e]|0.2224,0.2224,[72144e]|0.3249,0.3249,[72144e]|0.268,0.268,[72144e]|0.2541,0.2541,[72144e]|0.2436,0.2436,[72144e]|0.1969,0.1969,[72144e]|0.2752,0.2752,[72144e]|0.3426,0.3426,[72144e]|0.3053,0.3053,[72144e]|0.2645,0.2645,[72144e]|0.2503,0.2503,[72144e]|0.2715,0.2715,[72144e]|0.1823,0.1823,[72144e]|0.1424,0.1424,[72144e]|0.1611,0.1611,[72144e]|0.1611,0.1611,[72144e]|0.1424,0.1424,[72144e]|0.0967,0.0967,[72144e]|0.1528,0.1528,[72144e]|0.1399,0.1399,[72144e]|0.2129,0.2129,[72144e]|0.2292,0.2292,[72144e]|0.3286,0.3286,[72144e]|0.3356,0.3356,[72144e]|0.2752,0.2752,[72144e]|0.2094,0.2094,[72144e]|0.2292,0.2292,[72144e]|0.346,0.346,[72144e]|0.3631,0.3631,[72144e]|0.374,0.374,[72144e]|0.4119,0.4119,[72144e]|0.4619,0.4619,[72144e]|0.4864,0.4864,[72144e]|0.4901,0.4901,[72144e]|0.4979,0.4979,[72144e]|0.4725,0.4725,[72144e]|0.4051,0.4051,[72144e]|0.4087,0.4087,[72144e]|0.3182,0.3182,[72144e]|0.3215,0.3215,[72144e]|0.2817,0.2817,[72144e]|0.1969,0.1969,[72144e]|0.2224,0.2224,[72144e]|0.2164,0.2164,[72144e]|0.2129,0.2129,[72144e]|0.2193,0.2193,[72144e]|0.2575,0.2575,[72144e]|0.3215,0.3215,[72144e]|0.2399,0.2399,[72144e]|0.2436,0.2436,[72144e]|0.2292,0.2292,[72144e]|0.2164,0.2164,[72144e]|0.268,0.268,[72144e]|0.268,0.268,[72144e]|0.2193,0.2193,[72144e]|0.2436,0.2436,[72144e]|0.2436,0.2436,[72144e]|0.247,0.247,[72144e]|0.3087,0.3087,[72144e]|0.3019,0.3019,[72144e]|0.4017,0.4017,[72144e]|0.4441,0.4441,[72144e]|0.4541,0.4541,[72144e]|0.5419,0.5419,[72144e]|0.4831,0.4831,[72144e]|0.4507,0.4507,[72144e]|0.4369,0.4369,[72144e]|0.391,0.391,[72144e]|0.4541,0.4541,[72144e]|0.4292,0.4292,[72144e]|
+GRAPHLINE      IUPredWS (Long) 0.5     Above 0.5 indicates disorder    ff0000
+LINE_GRAPH     IUPredWS (Short)        <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.8424,0.8424,[329440]|0.8074,0.8074,[329440]|0.7644,0.7644,[329440]|0.6442,0.6442,[329440]|0.5762,0.5762,[329440]|0.5549,0.5549,[329440]|0.5253,0.5253,[329440]|0.4879,0.4879,[329440]|0.4651,0.4651,[329440]|0.3847,0.3847,[329440]|0.2748,0.2748,[329440]|0.2963,0.2963,[329440]|0.3311,0.3311,[329440]|0.2657,0.2657,[329440]|0.3311,0.3311,[329440]|0.3535,0.3535,[329440]|0.3146,0.3146,[329440]|0.3311,0.3311,[329440]|0.3578,0.3578,[329440]|0.363,0.363,[329440]|0.3578,0.3578,[329440]|0.363,0.363,[329440]|0.3225,0.3225,[329440]|0.3491,0.3491,[329440]|0.2913,0.2913,[329440]|0.2657,0.2657,[329440]|0.1878,0.1878,[329440]|0.1878,0.1878,[329440]|0.1732,0.1732,[329440]|0.1732,0.1732,[329440]|0.2292,0.2292,[329440]|0.2255,0.2255,[329440]|0.2255,0.2255,[329440]|0.282,0.282,[329440]|0.2209,0.2209,[329440]|0.1958,0.1958,[329440]|0.1844,0.1844,[329440]|0.138,0.138,[329440]|0.1088,0.1088,[329440]|0.1602,0.1602,[329440]|0.1041,0.1041,[329440]|0.0965,0.0965,[329440]|0.0813,0.0813,[329440]|0.1088,0.1088,[329440]|0.1205,0.1205,[329440]|0.0789,0.0789,[329440]|0.1117,0.1117,[329440]|0.1322,0.1322,[329440]|0.1349,0.1349,[329440]|0.1602,0.1602,[329440]|0.1805,0.1805,[329440]|0.1322,0.1322,[329440]|0.1495,0.1495,[329440]|0.0965,0.0965,[329440]|0.1495,0.1495,[329440]|0.0771,0.0771,[329440]|0.1495,0.1495,[329440]|0.1921,0.1921,[329440]|0.2209,0.2209,[329440]|0.1698,0.1698,[329440]|0.1805,0.1805,[329440]|0.1456,0.1456,[329440]|0.1495,0.1495,[329440]|0.1732,0.1732,[329440]|0.2432,0.2432,[329440]|0.2786,0.2786,[329440]|0.3053,0.3053,[329440]|0.2913,0.2913,[329440]|0.2432,0.2432,[329440]|0.2432,0.2432,[329440]|0.1958,0.1958,[329440]|0.2865,0.2865,[329440]|0.2041,0.2041,[329440]|0.3225,0.3225,[329440]|0.2385,0.2385,[329440]|0.3578,0.3578,[329440]|0.1844,0.1844,[329440]|0.1698,0.1698,[329440]|0.1495,0.1495,[329440]|0.1844,0.1844,[329440]|0.2558,0.2558,[329440]|0.1844,0.1844,[329440]|0.2483,0.2483,[329440]|0.2385,0.2385,[329440]|0.1266,0.1266,[329440]|0.1322,0.1322,[329440]|0.1205,0.1205,[329440]|0.0965,0.0965,[329440]|0.0832,0.0832,[329440]|0.0744,0.0744,[329440]|0.0813,0.0813,[329440]|0.0677,0.0677,[329440]|0.0832,0.0832,[329440]|0.0478,0.0478,[329440]|0.0813,0.0813,[329440]|0.0813,0.0813,[329440]|0.1766,0.1766,[329440]|0.1958,0.1958,[329440]|0.2209,0.2209,[329440]|0.2255,0.2255,[329440]|0.1566,0.1566,[329440]|0.208,0.208,[329440]|0.2209,0.2209,[329440]|0.2292,0.2292,[329440]|0.3668,0.3668,[329440]|0.3939,0.3939,[329440]|0.3263,0.3263,[329440]|0.3717,0.3717,[329440]|0.4513,0.4513,[329440]|0.4825,0.4825,[329440]|0.4245,0.4245,[329440]|0.3762,0.3762,[329440]|0.3399,0.3399,[329440]|0.3885,0.3885,[329440]|0.3053,0.3053,[329440]|0.2531,0.2531,[329440]|0.2385,0.2385,[329440]|0.1635,0.1635,[329440]|0.2041,0.2041,[329440]|0.208,0.208,[329440]|0.2865,0.2865,[329440]|0.3668,0.3668,[329440]|0.2865,0.2865,[329440]|0.27,0.27,[329440]|0.1805,0.1805,[329440]|0.1998,0.1998,[329440]|0.2602,0.2602,[329440]|0.2209,0.2209,[329440]|0.1766,0.1766,[329440]|0.1958,0.1958,[329440]|0.2209,0.2209,[329440]|0.2913,0.2913,[329440]|0.2913,0.2913,[329440]|0.2385,0.2385,[329440]|0.3096,0.3096,[329440]|0.4078,0.4078,[329440]|0.4749,0.4749,[329440]|0.5846,0.5846,[329440]|0.59,0.59,[329440]|0.6334,0.6334,[329440]|0.6681,0.6681,[329440]|0.6906,0.6906,[329440]|0.7869,0.7869,[329440]|0.835,0.835,[329440]|
+GRAPHLINE      IUPredWS (Short)        0.5     Above 0.5 indicates disorder    ff0000
+
+SEQUENCE_REF   FER_CAPAA
+LINE_GRAPH     IUPredWS (Long) <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.247,0.247,[72144e]|0.3392,0.3392,[72144e]|0.2609,0.2609,[72144e]|0.3249,0.3249,[72144e]|0.2258,0.2258,[72144e]|0.2817,0.2817,[72144e]|0.1759,0.1759,[72144e]|0.2364,0.2364,[72144e]|0.2817,0.2817,[72144e]|0.3215,0.3215,[72144e]|0.2918,0.2918,[72144e]|0.2436,0.2436,[72144e]|0.1852,0.1852,[72144e]|0.1942,0.1942,[72144e]|0.2258,0.2258,[72144e]|0.2884,0.2884,[72144e]|0.2783,0.2783,[72144e]|0.3667,0.3667,[72144e]|0.4541,0.4541,[72144e]|0.4292,0.4292,[72144e]|0.3948,0.3948,[72144e]|0.3704,0.3704,[72144e]|0.391,0.391,[72144e]|0.2918,0.2918,[72144e]|0.3948,0.3948,[72144e]|0.2951,0.2951,[72144e]|0.3872,0.3872,[72144e]|0.2575,0.2575,[72144e]|0.3599,0.3599,[72144e]|0.3019,0.3019,[72144e]|0.2849,0.2849,[72144e]|0.2645,0.2645,[72144e]|0.2034,0.2034,[72144e]|0.2752,0.2752,[72144e]|0.3356,0.3356,[72144e]|0.2918,0.2918,[72144e]|0.247,0.247,[72144e]|0.2364,0.2364,[72144e]|0.247,0.247,[72144e]|0.1643,0.1643,[72144e]|0.1275,0.1275,[72144e]|0.1449,0.1449,[72144e]|0.1449,0.1449,[72144e]|0.1323,0.1323,[72144e]|0.0888,0.0888,[72144e]|0.1449,0.1449,[72144e]|0.1275,0.1275,[72144e]|0.1881,0.1881,[72144e]|0.2064,0.2064,[72144e]|0.3149,0.3149,[72144e]|0.3286,0.3286,[72144e]|0.268,0.268,[72144e]|0.2034,0.2034,[72144e]|0.2224,0.2224,[72144e]|0.3494,0.3494,[72144e]|0.3704,0.3704,[72144e]|0.3807,0.3807,[72144e]|0.422,0.422,[72144e]|0.4725,0.4725,[72144e]|0.5017,0.5017,[72144e]|0.5176,0.5176,[72144e]|0.5176,0.5176,[72144e]|0.4901,0.4901,[72144e]|0.422,0.422,[72144e]|0.422,0.422,[72144e]|0.3356,0.3356,[72144e]|0.3356,0.3356,[72144e]|0.2951,0.2951,[72144e]|0.2129,0.2129,[72144e]|0.2436,0.2436,[72144e]|0.2328,0.2328,[72144e]|0.2328,0.2328,[72144e]|0.247,0.247,[72144e]|0.2849,0.2849,[72144e]|0.3494,0.3494,[72144e]|0.2609,0.2609,[72144e]|0.268,0.268,[72144e]|0.2541,0.2541,[72144e]|0.2328,0.2328,[72144e]|0.2849,0.2849,[72144e]|0.2884,0.2884,[72144e]|0.2364,0.2364,[72144e]|0.2609,0.2609,[72144e]|0.2645,0.2645,[72144e]|0.2645,0.2645,[72144e]|0.3286,0.3286,[72144e]|0.3215,0.3215,[72144e]|0.4186,0.4186,[72144e]|0.4582,0.4582,[72144e]|0.4725,0.4725,[72144e]|0.5533,0.5533,[72144e]|0.494,0.494,[72144e]|0.4652,0.4652,[72144e]|0.4476,0.4476,[72144e]|0.3983,0.3983,[72144e]|0.4619,0.4619,[72144e]|0.433,0.433,[72144e]|
+GRAPHLINE      IUPredWS (Long) 0.5     Above 0.5 indicates disorder    ff0000
+LINE_GRAPH     IUPredWS (Short)        <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.7724,0.7724,[329440]|0.7501,0.7501,[329440]|0.7111,0.7111,[329440]|0.6984,0.6984,[329440]|0.59,0.59,[329440]|0.5711,0.5711,[329440]|0.442,0.442,[329440]|0.4203,0.4203,[329440]|0.2786,0.2786,[329440]|0.27,0.27,[329440]|0.2385,0.2385,[329440]|0.3005,0.3005,[329440]|0.2385,0.2385,[329440]|0.2483,0.2483,[329440]|0.2167,0.2167,[329440]|0.2255,0.2255,[329440]|0.2657,0.2657,[329440]|0.3456,0.3456,[329440]|0.3717,0.3717,[329440]|0.3992,0.3992,[329440]|0.3847,0.3847,[329440]|0.3311,0.3311,[329440]|0.3225,0.3225,[329440]|0.27,0.27,[329440]|0.3668,0.3668,[329440]|0.2786,0.2786,[329440]|0.4037,0.4037,[329440]|0.3096,0.3096,[329440]|0.4116,0.4116,[329440]|0.2255,0.2255,[329440]|0.2041,0.2041,[329440]|0.1602,0.1602,[329440]|0.1998,0.1998,[329440]|0.2657,0.2657,[329440]|0.1921,0.1921,[329440]|0.2558,0.2558,[329440]|0.2483,0.2483,[329440]|0.1322,0.1322,[329440]|0.138,0.138,[329440]|0.124,0.124,[329440]|0.0991,0.0991,[329440]|0.0884,0.0884,[329440]|0.0771,0.0771,[329440]|0.0858,0.0858,[329440]|0.0701,0.0701,[329440]|0.0858,0.0858,[329440]|0.049,0.049,[329440]|0.0813,0.0813,[329440]|0.0813,0.0813,[329440]|0.1766,0.1766,[329440]|0.1998,0.1998,[329440]|0.2255,0.2255,[329440]|0.2255,0.2255,[329440]|0.1566,0.1566,[329440]|0.208,0.208,[329440]|0.2209,0.2209,[329440]|0.2292,0.2292,[329440]|0.3668,0.3668,[329440]|0.3939,0.3939,[329440]|0.3263,0.3263,[329440]|0.3717,0.3717,[329440]|0.4513,0.4513,[329440]|0.4825,0.4825,[329440]|0.4245,0.4245,[329440]|0.3762,0.3762,[329440]|0.3399,0.3399,[329440]|0.3885,0.3885,[329440]|0.3053,0.3053,[329440]|0.2531,0.2531,[329440]|0.2385,0.2385,[329440]|0.1635,0.1635,[329440]|0.2041,0.2041,[329440]|0.208,0.208,[329440]|0.2865,0.2865,[329440]|0.3668,0.3668,[329440]|0.2865,0.2865,[329440]|0.27,0.27,[329440]|0.1805,0.1805,[329440]|0.1998,0.1998,[329440]|0.2602,0.2602,[329440]|0.2209,0.2209,[329440]|0.1766,0.1766,[329440]|0.1958,0.1958,[329440]|0.2209,0.2209,[329440]|0.2913,0.2913,[329440]|0.2913,0.2913,[329440]|0.2385,0.2385,[329440]|0.3096,0.3096,[329440]|0.4078,0.4078,[329440]|0.4749,0.4749,[329440]|0.5846,0.5846,[329440]|0.59,0.59,[329440]|0.6334,0.6334,[329440]|0.6681,0.6681,[329440]|0.6906,0.6906,[329440]|0.7869,0.7869,[329440]|0.835,0.835,[329440]|
+GRAPHLINE      IUPredWS (Short)        0.5     Above 0.5 indicates disorder    ff0000
+
+SEQUENCE_REF   Q93XJ9_SOLTU
+LINE_GRAPH     IUPredWS (Long) <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.3182,0.3182,[72144e]|0.2002,0.2002,[72144e]|0.1449,0.1449,[72144e]|0.2164,0.2164,[72144e]|0.2503,0.2503,[72144e]|0.268,0.268,[72144e]|0.3286,0.3286,[72144e]|0.2951,0.2951,[72144e]|0.2609,0.2609,[72144e]|0.2849,0.2849,[72144e]|0.3053,0.3053,[72144e]|0.2609,0.2609,[72144e]|0.2817,0.2817,[72144e]|0.2541,0.2541,[72144e]|0.2575,0.2575,[72144e]|0.2575,0.2575,[72144e]|0.2645,0.2645,[72144e]|0.2064,0.2064,[72144e]|0.2292,0.2292,[72144e]|0.3149,0.3149,[72144e]|0.2884,0.2884,[72144e]|0.2164,0.2164,[72144e]|0.1399,0.1399,[72144e]|0.2129,0.2129,[72144e]|0.2094,0.2094,[72144e]|0.1731,0.1731,[72144e]|0.1702,0.1702,[72144e]|0.1759,0.1759,[72144e]|0.1501,0.1501,[72144e]|0.2002,0.2002,[72144e]|0.247,0.247,[72144e]|0.2436,0.2436,[72144e]|0.1823,0.1823,[72144e]|0.2503,0.2503,[72144e]|0.1583,0.1583,[72144e]|0.1611,0.1611,[72144e]|0.2002,0.2002,[72144e]|0.2002,0.2002,[72144e]|0.1298,0.1298,[72144e]|0.1643,0.1643,[72144e]|0.1275,0.1275,[72144e]|0.107,0.107,[72144e]|0.087,0.087,[72144e]|0.087,0.087,[72144e]|0.1501,0.1501,[72144e]|0.1759,0.1759,[72144e]|0.2503,0.2503,[72144e]|0.2609,0.2609,[72144e]|0.2918,0.2918,[72144e]|0.2258,0.2258,[72144e]|0.2436,0.2436,[72144e]|0.1554,0.1554,[72144e]|0.1731,0.1731,[72144e]|0.0948,0.0948,[72144e]|0.1611,0.1611,[72144e]|0.1702,0.1702,[72144e]|0.2849,0.2849,[72144e]|0.2609,0.2609,[72144e]|0.2094,0.2094,[72144e]|0.1554,0.1554,[72144e]|0.1671,0.1671,[72144e]|0.1852,0.1852,[72144e]|0.2436,0.2436,[72144e]|0.2258,0.2258,[72144e]|0.3117,0.3117,[72144e]|0.3983,0.3983,[72144e]|0.4186,0.4186,[72144e]|0.3872,0.3872,[72144e]|0.374,0.374,[72144e]|0.3872,0.3872,[72144e]|0.2918,0.2918,[72144e]|0.391,0.391,[72144e]|0.2918,0.2918,[72144e]|0.384,0.384,[72144e]|0.2541,0.2541,[72144e]|0.3631,0.3631,[72144e]|0.3053,0.3053,[72144e]|0.2918,0.2918,[72144e]|0.2783,0.2783,[72144e]|0.2193,0.2193,[72144e]|0.2951,0.2951,[72144e]|0.3599,0.3599,[72144e]|0.3149,0.3149,[72144e]|0.2752,0.2752,[72144e]|0.2645,0.2645,[72144e]|0.2817,0.2817,[72144e]|0.2094,0.2094,[72144e]|0.1942,0.1942,[72144e]|0.1528,0.1528,[72144e]|0.1528,0.1528,[72144e]|0.1554,0.1554,[72144e]|0.1092,0.1092,[72144e]|0.1671,0.1671,[72144e]|0.1501,0.1501,[72144e]|0.2164,0.2164,[72144e]|0.2292,0.2292,[72144e]|0.3392,0.3392,[72144e]|0.3249,0.3249,[72144e]|0.2645,0.2645,[72144e]|0.2034,0.2034,[72144e]|0.2164,0.2164,[72144e]|0.3356,0.3356,[72144e]|0.3494,0.3494,[72144e]|0.3599,0.3599,[72144e]|0.494,0.494,[72144e]|0.494,0.494,[72144e]|0.4652,0.4652,[72144e]|0.4766,0.4766,[72144e]|0.4369,0.4369,[72144e]|0.3872,0.3872,[72144e]|0.3494,0.3494,[72144e]|0.3529,0.3529,[72144e]|0.2292,0.2292,[72144e]|0.2328,0.2328,[72144e]|0.2002,0.2002,[72144e]|0.1349,0.1349,[72144e]|0.1611,0.1611,[72144e]|0.1399,0.1399,[72144e]|0.0676,0.0676,[72144e]|0.078,0.078,[72144e]|0.1007,0.1007,[72144e]|0.1424,0.1424,[72144e]|0.0948,0.0948,[72144e]|0.0985,0.0985,[72144e]|0.0929,0.0929,[72144e]|0.0851,0.0851,[72144e]|0.0676,0.0676,[72144e]|0.0909,0.0909,[72144e]|0.1229,0.1229,[72144e]|0.1399,0.1399,[72144e]|0.1583,0.1583,[72144e]|0.1969,0.1969,[72144e]|0.2364,0.2364,[72144e]|0.2292,0.2292,[72144e]|0.3321,0.3321,[72144e]|0.3667,0.3667,[72144e]|0.3774,0.3774,[72144e]|0.4441,0.4441,[72144e]|0.3872,0.3872,[72144e]|0.384,0.384,[72144e]|0.5577,0.5577,[72144e]|0.5176,0.5176,[72144e]|0.6043,0.6043,[72144e]|0.5854,0.5854,[72144e]|
+GRAPHLINE      IUPredWS (Long) 0.5     Above 0.5 indicates disorder    ff0000
+LINE_GRAPH     IUPredWS (Short)        <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.8311,0.8311,[329440]|0.7912,0.7912,[329440]|0.7501,0.7501,[329440]|0.6255,0.6255,[329440]|0.5008,0.5008,[329440]|0.4825,0.4825,[329440]|0.4513,0.4513,[329440]|0.4078,0.4078,[329440]|0.3885,0.3885,[329440]|0.2786,0.2786,[329440]|0.1732,0.1732,[329440]|0.1921,0.1921,[329440]|0.2292,0.2292,[329440]|0.1635,0.1635,[329440]|0.2385,0.2385,[329440]|0.2041,0.2041,[329440]|0.138,0.138,[329440]|0.138,0.138,[329440]|0.1566,0.1566,[329440]|0.1698,0.1698,[329440]|0.1698,0.1698,[329440]|0.1698,0.1698,[329440]|0.138,0.138,[329440]|0.1566,0.1566,[329440]|0.1456,0.1456,[329440]|0.1266,0.1266,[329440]|0.0744,0.0744,[329440]|0.0744,0.0744,[329440]|0.0621,0.0621,[329440]|0.0965,0.0965,[329440]|0.1416,0.1416,[329440]|0.138,0.138,[329440]|0.1349,0.1349,[329440]|0.2041,0.2041,[329440]|0.138,0.138,[329440]|0.1667,0.1667,[329440]|0.1635,0.1635,[329440]|0.138,0.138,[329440]|0.1117,0.1117,[329440]|0.1635,0.1635,[329440]|0.1088,0.1088,[329440]|0.1041,0.1041,[329440]|0.0884,0.0884,[329440]|0.1266,0.1266,[329440]|0.1416,0.1416,[329440]|0.1018,0.1018,[329440]|0.1495,0.1495,[329440]|0.1732,0.1732,[329440]|0.1805,0.1805,[329440]|0.1805,0.1805,[329440]|0.2041,0.2041,[329440]|0.1566,0.1566,[329440]|0.1732,0.1732,[329440]|0.1117,0.1117,[329440]|0.1766,0.1766,[329440]|0.0991,0.0991,[329440]|0.1805,0.1805,[329440]|0.2292,0.2292,[329440]|0.2865,0.2865,[329440]|0.2292,0.2292,[329440]|0.2432,0.2432,[329440]|0.2041,0.2041,[329440]|0.2122,0.2122,[329440]|0.2432,0.2432,[329440]|0.3184,0.3184,[329440]|0.3535,0.3535,[329440]|0.3762,0.3762,[329440]|0.363,0.363,[329440]|0.3762,0.3762,[329440]|0.3717,0.3717,[329440]|0.3225,0.3225,[329440]|0.4078,0.4078,[329440]|0.3359,0.3359,[329440]|0.4458,0.4458,[329440]|0.3668,0.3668,[329440]|0.4879,0.4879,[329440]|0.3096,0.3096,[329440]|0.2865,0.2865,[329440]|0.2385,0.2385,[329440]|0.2865,0.2865,[329440]|0.3578,0.3578,[329440]|0.2865,0.2865,[329440]|0.3491,0.3491,[329440]|0.3359,0.3359,[329440]|0.208,0.208,[329440]|0.2167,0.2167,[329440]|0.1958,0.1958,[329440]|0.1635,0.1635,[329440]|0.1117,0.1117,[329440]|0.1088,0.1088,[329440]|0.1041,0.1041,[329440]|0.0858,0.0858,[329440]|0.1117,0.1117,[329440]|0.0643,0.0643,[329440]|0.1041,0.1041,[329440]|0.1041,0.1041,[329440]|0.2122,0.2122,[329440]|0.2292,0.2292,[329440]|0.2483,0.2483,[329440]|0.2333,0.2333,[329440]|0.1602,0.1602,[329440]|0.208,0.208,[329440]|0.2167,0.2167,[329440]|0.2209,0.2209,[329440]|0.3491,0.3491,[329440]|0.3717,0.3717,[329440]|0.3885,0.3885,[329440]|0.3806,0.3806,[329440]|0.3992,0.3992,[329440]|0.4116,0.4116,[329440]|0.3668,0.3668,[329440]|0.3263,0.3263,[329440]|0.2748,0.2748,[329440]|0.3225,0.3225,[329440]|0.2122,0.2122,[329440]|0.1635,0.1635,[329440]|0.1495,0.1495,[329440]|0.0991,0.0991,[329440]|0.1322,0.1322,[329440]|0.1292,0.1292,[329440]|0.1117,0.1117,[329440]|0.1667,0.1667,[329440]|0.1117,0.1117,[329440]|0.1018,0.1018,[329440]|0.0607,0.0607,[329440]|0.0723,0.0723,[329440]|0.0587,0.0587,[329440]|0.0643,0.0643,[329440]|0.0643,0.0643,[329440]|0.0771,0.0771,[329440]|0.1292,0.1292,[329440]|0.1766,0.1766,[329440]|0.1667,0.1667,[329440]|0.1667,0.1667,[329440]|0.2255,0.2255,[329440]|0.3225,0.3225,[329440]|0.3885,0.3885,[329440]|0.5008,0.5008,[329440]|0.5008,0.5008,[329440]|0.5667,0.5667,[329440]|0.7079,0.7079,[329440]|0.7342,0.7342,[329440]|0.835,0.835,[329440]|0.8746,0.8746,[329440]|
+GRAPHLINE      IUPredWS (Short)        0.5     Above 0.5 indicates disorder    ff0000
+
+SEQUENCE_REF   FER1_SOLLC
+LINE_GRAPH     IUPredWS (Long) <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.3286,0.3286,[72144e]|0.2094,0.2094,[72144e]|0.1528,0.1528,[72144e]|0.2258,0.2258,[72144e]|0.2609,0.2609,[72144e]|0.2752,0.2752,[72144e]|0.3356,0.3356,[72144e]|0.3249,0.3249,[72144e]|0.2918,0.2918,[72144e]|0.3149,0.3149,[72144e]|0.3321,0.3321,[72144e]|0.2884,0.2884,[72144e]|0.3087,0.3087,[72144e]|0.2849,0.2849,[72144e]|0.2849,0.2849,[72144e]|0.2849,0.2849,[72144e]|0.2951,0.2951,[72144e]|0.2364,0.2364,[72144e]|0.2575,0.2575,[72144e]|0.3426,0.3426,[72144e]|0.3149,0.3149,[72144e]|0.247,0.247,[72144e]|0.1643,0.1643,[72144e]|0.2436,0.2436,[72144e]|0.2399,0.2399,[72144e]|0.2034,0.2034,[72144e]|0.2002,0.2002,[72144e]|0.2064,0.2064,[72144e]|0.1823,0.1823,[72144e]|0.2094,0.2094,[72144e]|0.2609,0.2609,[72144e]|0.2575,0.2575,[72144e]|0.1942,0.1942,[72144e]|0.2645,0.2645,[72144e]|0.1759,0.1759,[72144e]|0.1791,0.1791,[72144e]|0.2193,0.2193,[72144e]|0.2193,0.2193,[72144e]|0.1476,0.1476,[72144e]|0.1852,0.1852,[72144e]|0.1424,0.1424,[72144e]|0.1206,0.1206,[72144e]|0.0985,0.0985,[72144e]|0.0985,0.0985,[72144e]|0.1671,0.1671,[72144e]|0.1942,0.1942,[72144e]|0.2817,0.2817,[72144e]|0.2918,0.2918,[72144e]|0.3215,0.3215,[72144e]|0.2575,0.2575,[72144e]|0.2752,0.2752,[72144e]|0.1823,0.1823,[72144e]|0.2034,0.2034,[72144e]|0.1206,0.1206,[72144e]|0.1969,0.1969,[72144e]|0.2034,0.2034,[72144e]|0.3182,0.3182,[72144e]|0.2951,0.2951,[72144e]|0.2436,0.2436,[72144e]|0.1852,0.1852,[72144e]|0.1969,0.1969,[72144e]|0.2193,0.2193,[72144e]|0.2817,0.2817,[72144e]|0.2645,0.2645,[72144e]|0.3494,0.3494,[72144e]|0.4369,0.4369,[72144e]|0.4541,0.4541,[72144e]|0.422,0.422,[72144e]|0.4017,0.4017,[72144e]|0.4186,0.4186,[72144e]|0.3215,0.3215,[72144e]|0.422,0.422,[72144e]|0.3215,0.3215,[72144e]|0.4119,0.4119,[72144e]|0.2884,0.2884,[72144e]|0.391,0.391,[72144e]|0.3321,0.3321,[72144e]|0.3182,0.3182,[72144e]|0.3053,0.3053,[72144e]|0.2541,0.2541,[72144e]|0.3286,0.3286,[72144e]|0.391,0.391,[72144e]|0.3529,0.3529,[72144e]|0.3117,0.3117,[72144e]|0.3019,0.3019,[72144e]|0.3182,0.3182,[72144e]|0.247,0.247,[72144e]|0.2292,0.2292,[72144e]|0.1852,0.1852,[72144e]|0.1852,0.1852,[72144e]|0.1823,0.1823,[72144e]|0.1298,0.1298,[72144e]|0.1942,0.1942,[72144e]|0.1759,0.1759,[72144e]|0.2436,0.2436,[72144e]|0.2609,0.2609,[72144e]|0.3631,0.3631,[72144e]|0.3667,0.3667,[72144e]|0.3087,0.3087,[72144e]|0.247,0.247,[72144e]|0.2609,0.2609,[72144e]|0.384,0.384,[72144e]|0.3983,0.3983,[72144e]|0.4087,0.4087,[72144e]|0.5419,0.5419,[72144e]|0.5419,0.5419,[72144e]|0.5139,0.5139,[72144e]|0.5211,0.5211,[72144e]|0.4831,0.4831,[72144e]|0.4292,0.4292,[72144e]|0.3948,0.3948,[72144e]|0.3983,0.3983,[72144e]|0.2884,0.2884,[72144e]|0.2884,0.2884,[72144e]|0.2541,0.2541,[72144e]|0.1791,0.1791,[72144e]|0.2094,0.2094,[72144e]|0.1823,0.1823,[72144e]|0.1823,0.1823,[72144e]|0.1881,0.1881,[72144e]|0.2224,0.2224,[72144e]|0.2884,0.2884,[72144e]|0.2164,0.2164,[72144e]|0.2164,0.2164,[72144e]|0.2064,0.2064,[72144e]|0.1942,0.1942,[72144e]|0.1643,0.1643,[72144e]|0.2064,0.2064,[72144e]|0.2541,0.2541,[72144e]|0.2783,0.2783,[72144e]|0.3019,0.3019,[72144e]|0.3494,0.3494,[72144e]|0.391,0.391,[72144e]|0.3807,0.3807,[72144e]|0.4864,0.4864,[72144e]|0.5342,0.5342,[72144e]|0.5533,0.5533,[72144e]|0.6576,0.6576,[72144e]|0.5901,0.5901,[72144e]|0.6043,0.6043,[72144e]|0.5951,0.5951,[72144e]|0.5493,0.5493,[72144e]|0.6427,0.6427,[72144e]|0.6227,0.6227,[72144e]|
+GRAPHLINE      IUPredWS (Long) 0.5     Above 0.5 indicates disorder    ff0000
+LINE_GRAPH     IUPredWS (Short)        <html>Protein Disorder with IUPredWS - raw scores<br/>Above 0.5 indicates disorder</html>       0.8391,0.8391,[329440]|0.8001,0.8001,[329440]|0.7573,0.7573,[329440]|0.6334,0.6334,[329440]|0.5173,0.5173,[329440]|0.5008,0.5008,[329440]|0.4703,0.4703,[329440]|0.4245,0.4245,[329440]|0.4116,0.4116,[329440]|0.3263,0.3263,[329440]|0.2167,0.2167,[329440]|0.2385,0.2385,[329440]|0.2786,0.2786,[329440]|0.2041,0.2041,[329440]|0.2865,0.2865,[329440]|0.2531,0.2531,[329440]|0.1805,0.1805,[329440]|0.1766,0.1766,[329440]|0.1958,0.1958,[329440]|0.2122,0.2122,[329440]|0.208,0.208,[329440]|0.2122,0.2122,[329440]|0.1766,0.1766,[329440]|0.1921,0.1921,[329440]|0.1844,0.1844,[329440]|0.1602,0.1602,[329440]|0.0991,0.0991,[329440]|0.1018,0.1018,[329440]|0.0858,0.0858,[329440]|0.1041,0.1041,[329440]|0.1566,0.1566,[329440]|0.1495,0.1495,[329440]|0.1495,0.1495,[329440]|0.2167,0.2167,[329440]|0.1495,0.1495,[329440]|0.1766,0.1766,[329440]|0.1732,0.1732,[329440]|0.1456,0.1456,[329440]|0.1205,0.1205,[329440]|0.1732,0.1732,[329440]|0.115,0.115,[329440]|0.1088,0.1088,[329440]|0.0935,0.0935,[329440]|0.1292,0.1292,[329440]|0.1456,0.1456,[329440]|0.1018,0.1018,[329440]|0.1532,0.1532,[329440]|0.1732,0.1732,[329440]|0.1878,0.1878,[329440]|0.1878,0.1878,[329440]|0.2122,0.2122,[329440]|0.1602,0.1602,[329440]|0.1766,0.1766,[329440]|0.1178,0.1178,[329440]|0.1844,0.1844,[329440]|0.1018,0.1018,[329440]|0.1844,0.1844,[329440]|0.2333,0.2333,[329440]|0.2913,0.2913,[329440]|0.2333,0.2333,[329440]|0.2483,0.2483,[329440]|0.208,0.208,[329440]|0.2167,0.2167,[329440]|0.2531,0.2531,[329440]|0.3263,0.3263,[329440]|0.3578,0.3578,[329440]|0.3806,0.3806,[329440]|0.3668,0.3668,[329440]|0.3762,0.3762,[329440]|0.3717,0.3717,[329440]|0.3225,0.3225,[329440]|0.4116,0.4116,[329440]|0.3399,0.3399,[329440]|0.4513,0.4513,[329440]|0.3717,0.3717,[329440]|0.4879,0.4879,[329440]|0.3146,0.3146,[329440]|0.2865,0.2865,[329440]|0.2385,0.2385,[329440]|0.2865,0.2865,[329440]|0.363,0.363,[329440]|0.2913,0.2913,[329440]|0.3535,0.3535,[329440]|0.3359,0.3359,[329440]|0.2122,0.2122,[329440]|0.2209,0.2209,[329440]|0.1998,0.1998,[329440]|0.1667,0.1667,[329440]|0.115,0.115,[329440]|0.115,0.115,[329440]|0.106,0.106,[329440]|0.0884,0.0884,[329440]|0.1205,0.1205,[329440]|0.0723,0.0723,[329440]|0.115,0.115,[329440]|0.115,0.115,[329440]|0.2292,0.2292,[329440]|0.2558,0.2558,[329440]|0.2786,0.2786,[329440]|0.2786,0.2786,[329440]|0.1958,0.1958,[329440]|0.2558,0.2558,[329440]|0.27,0.27,[329440]|0.2865,0.2865,[329440]|0.4149,0.4149,[329440]|0.442,0.442,[329440]|0.4651,0.4651,[329440]|0.46,0.46,[329440]|0.4781,0.4781,[329440]|0.4879,0.4879,[329440]|0.4513,0.4513,[329440]|0.4078,0.4078,[329440]|0.3491,0.3491,[329440]|0.3992,0.3992,[329440]|0.3146,0.3146,[329440]|0.2602,0.2602,[329440]|0.2385,0.2385,[329440]|0.1698,0.1698,[329440]|0.2167,0.2167,[329440]|0.1921,0.1921,[329440]|0.2748,0.2748,[329440]|0.3456,0.3456,[329440]|0.27,0.27,[329440]|0.2432,0.2432,[329440]|0.1667,0.1667,[329440]|0.1844,0.1844,[329440]|0.1635,0.1635,[329440]|0.1805,0.1805,[329440]|0.1844,0.1844,[329440]|0.2041,0.2041,[329440]|0.2913,0.2913,[329440]|0.3535,0.3535,[329440]|0.3399,0.3399,[329440]|0.3399,0.3399,[329440]|0.3806,0.3806,[329440]|0.4781,0.4781,[329440]|0.5374,0.5374,[329440]|0.6442,0.6442,[329440]|0.6442,0.6442,[329440]|0.7111,0.7111,[329440]|0.7458,0.7458,[329440]|0.7724,0.7724,[329440]|0.865,0.865,[329440]|0.9009,0.9009,[329440]|
+GRAPHLINE      IUPredWS (Short)        0.5     Above 0.5 indicates disorder    ff0000
+
+COLOUR IUPredWS (Long) 72144e
+COLOUR IUPredWS (Short)        329440
+COLOUR IUPredWS (Long) 72144e
+COLOUR IUPredWS (Short)        329440
+COLOUR IUPredWS (Long) 72144e
+COLOUR IUPredWS (Short)        329440
+COLOUR IUPredWS (Long) 72144e
+COLOUR IUPredWS (Short)        329440
+COLOUR IUPredWS (Long) 72144e
+COLOUR IUPredWS (Short)        329440
+COLOUR IUPredWS (Long) 72144e
+COLOUR IUPredWS (Short)        329440
+COLOUR IUPredWS (Long) 72144e
+COLOUR IUPredWS (Short)        329440
+COLOUR IUPredWS (Long) 72144e
+COLOUR IUPredWS (Short)        329440
+COLOUR IUPredWS (Long) 72144e
+COLOUR IUPredWS (Short)        329440
+COLOUR IUPredWS (Long) 72144e
+COLOUR IUPredWS (Short)        329440
+COLOUR IUPredWS (Long) 72144e
+COLOUR IUPredWS (Short)        329440
+COLOUR IUPredWS (Long) 72144e
+COLOUR IUPredWS (Short)        329440
+COLOUR IUPredWS (Long) 72144e
+COLOUR IUPredWS (Short)        329440
+COLOUR IUPredWS (Long) 72144e
+COLOUR IUPredWS (Short)        329440
+COLOUR IUPredWS (Long) 72144e
+COLOUR IUPredWS (Short)        329440
+COMBINE        IUPredWS (Long) IUPredWS (Short)
+
+SEQUENCE_REF   Q93XJ9_SOLTU
+COMBINE        IUPredWS (Long) IUPredWS (Short)
+
+SEQUENCE_REF   FER_CAPAA
+COMBINE        IUPredWS (Long) IUPredWS (Short)
+
+SEQUENCE_REF   FER_CAPAN
+COMBINE        IUPredWS (Long) IUPredWS (Short)
+
+SEQUENCE_REF   FER1_MESCR
+COMBINE        IUPredWS (Long) IUPredWS (Short)
+
+SEQUENCE_REF   FER1_SPIOL
+COMBINE        IUPredWS (Long) IUPredWS (Short)
+
+SEQUENCE_REF   FER1_PEA
+COMBINE        IUPredWS (Long) IUPredWS (Short)
+
+SEQUENCE_REF   Q7XA98_TRIPR
+COMBINE        IUPredWS (Long) IUPredWS (Short)
+
+SEQUENCE_REF   O80429_MAIZE
+COMBINE        IUPredWS (Long) IUPredWS (Short)
+
+SEQUENCE_REF   FER1_MAIZE
+COMBINE        IUPredWS (Long) IUPredWS (Short)
+
+SEQUENCE_REF   FER3_RAPSA
+COMBINE        IUPredWS (Long) IUPredWS (Short)
+
+SEQUENCE_REF   FER_BRANA
+COMBINE        IUPredWS (Long) IUPredWS (Short)
+
+SEQUENCE_REF   FER1_ARATH
+COMBINE        IUPredWS (Long) IUPredWS (Short)
+
+SEQUENCE_REF   Q93Z60_ARATH
+COMBINE        IUPredWS (Long) IUPredWS (Short)
+
+SEQUENCE_REF   FER2_ARATH
+COMBINE        IUPredWS (Long) IUPredWS (Short)
+
+SEQUENCE_REF   FER1_SOLLC
+# takes the current reference sequence and makes it the reference for the view
+VIEW_SETREF
+# nominal name for new hide columns command
+HIDE_INSERTIONS
index af010a4..eafcc68 100755 (executable)
    <mapID target="calcs.alquality" url="html/calculations/quality.html"/>   
    <mapID target="calcs.alconserv" url="html/calculations/conservation.html"/>
    <mapID target="calcs.alstrconsensus" url="html/calculations/structureconsensus.html"/>  
-   <mapID target="calcs.consensus" url="html/calculations/consensus.html"/>
+   <mapID target="calcs.consensus" url="html/calculations/consensus.html"/>   
+   <mapID target="calcs.annotation" url="html/calculations/columnFilterByAnnotation.html"/>
    
    <mapID target="nucleicAcids" url="html/na/index.html"/>
    
index b67eb6f..9cfd17e 100755 (executable)
@@ -90,6 +90,7 @@
                        <tocitem text="Tree/PCA Input Data" target="recoverInputdata" />
                        <tocitem text="Pairwise Alignments" target="pairwise" />
                        <tocitem text="Remove Redundancy" target="redundancy" />
+                       <tocitem text="Select Column by Annotation" target="calcs.annotation" />
                </tocitem>
                <tocitem text="Sequence Annotations" target="seqannots" expand="true">
                        <tocitem text="Annotation from Structure" target="xsspannotation" expand="false" />
diff --git a/help/html/calculations/AnnotationColumnSelectionWithSM.gif b/help/html/calculations/AnnotationColumnSelectionWithSM.gif
new file mode 100644 (file)
index 0000000..4373493
Binary files /dev/null and b/help/html/calculations/AnnotationColumnSelectionWithSM.gif differ
diff --git a/help/html/calculations/AnnotationColumnSelectionWithoutSM.gif b/help/html/calculations/AnnotationColumnSelectionWithoutSM.gif
new file mode 100644 (file)
index 0000000..921f028
Binary files /dev/null and b/help/html/calculations/AnnotationColumnSelectionWithoutSM.gif differ
diff --git a/help/html/calculations/columnFilterByAnnotation.html b/help/html/calculations/columnFilterByAnnotation.html
new file mode 100644 (file)
index 0000000..62bfcf1
--- /dev/null
@@ -0,0 +1,95 @@
+<html>
+<!--
+ * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
+ * Copyright (C) 2014 The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ -->
+<head>
+<title>Filter Columns by Annotation</title>
+</head>
+
+<body>
+       <p>
+               <strong> Filter Columns by Annotation </strong>
+       </p>
+       <p>Jalview allows the columns of an alignment to be filtered using
+               any annotation rows added to that alignment.</p>
+       From &quot;Select&quot; menu
+       <strong>&#8594;</strong> &quot;Select by Annotation...&quot; to bring
+       up the Select by Annotation window. The filter options vary depending
+       on the type of annotation selected. If an annotation that has a numeric
+       values is selected, the threshold filter option is activated as seen in the
+       figure on the right below.
+       <br>
+       <br> 
+
+       <div style="width: 48%; float: left; margin-left: 4%">
+               <img src="annotationColumnSelectionWithoutSM.gif">
+       </div>
+       <div style="width: 48%; float: right">
+               <img src="annotationColumnSelectionWithSM.gif">
+       </div>
+       <div>&nbsp</div>
+
+       <ul>
+               <li>Select which annotation to base the filtering on using the
+                       top-most selection box.</li>
+
+               <li><strong>Search Filter</strong>
+                       <ul>
+                               <li>When a text is entered in the textfield on the search
+                                       filter section, the &quot;Display Label&quot; and &quot;Description&quot;
+                                       checkboxes becomes selectable.</li>
+                               <li>On selecting any of the checkboxes, a regular expresion
+                                       search (RegEx) is executed on the specified field of the current
+                                       annotation row selected, and the matching columns will be highlighted
+                                       in the alignment.</li>
+                       </ul>
+               <li><strong>Structure Filter</strong>
+                       <ul>
+                               <li>Alignment columns can also be filtered by the type of
+                                       secondary structure present in a choosen annotation row.</li>
+                               <li>This can be achieved by ticking the type of structure you
+                                       wish to filter by under the structures filter section.</li>
+                       </ul>
+               <li><strong>Threshold Filter</strong>
+                       <ul>
+                               <li>This filter is only activated for annotation which contain
+                                       numeric values</li>
+                               <li>Select whether to Filter the alignment above or below an
+                                       adjustable threshold with the selection box within the threshold
+                                       filter section.</li>
+                               <li>Change the threshold value with the slider, or enter the
+                                       exact value in the text box.</li>
+                       </ul>
+               <li><strong>Actions</strong>
+                       <ul>
+                               <li>The &quot;Select&quot; and &quot;Hide&quot; radio buttons
+                                       determines the action that will be carried out on the matching
+                                       columns in the alignment during the filtering process.</li>
+                               <li>The default option is &quot;Select&quot; and this simply
+                                       enables column selection on the matching alignment column.</li>
+                               <li>While the &quot;Hide&quot; option enables the matching columns to
+                                       be hidden automatically during the filtering process.</li>
+                               <li>The &quot;Ok&quot; button applies the filter when clicked.</li>
+                               <li>And finally, The &quot;Cancel&quot; button restores the alignment to its previous state before the filtering when clicked. 
+                       </ul></li>
+       </ul>
+
+</body>
+</html>
index 141379f..72691ce 100755 (executable)
@@ -30,8 +30,10 @@ version 2.08 of Jalview, via an annotations file. It is a simple ASCII
 text file consisting of tab delimited records similar to the <a
        href="featuresFormat.html">Sequence Features File</a>, and introduced
 primarily for use with the Jalview applet.</p>
-<p>Alignment annotations files are imported into Jalview in the
-following ways:<br>
+
+<p><strong>Importing annotation files</strong><br/>
+Alignment annotations files are imported into Jalview in the
+following ways:<br/>
 <ul>
        <li>from the command line<strong><pre>
  -annotations &lt;<em>Annotations filename</em>&gt;</pre></strong></li>
@@ -40,53 +42,142 @@ following ways:<br>
        menu of an alignment window.</li>
 </ul>
 </p>
-<p><h3><font face="Arial, Helvetica, sans-serif">Format of an Annotations File</font></h3>
-<p>The file consists of lines containing an instruction followed by
-tab delimited fields, and any lines starting with &quot;#&quot; are
-ignored. The first non-commented out line of a valid Annotations file
+<p>
+  <strong>Exporting annotation files</strong><br /> An annotation file
+  can be created for any alignment view from the &quot;Export
+  Annotations ...&quot; entry in the <strong>File</strong> menu of an
+  alignment window.
+</p>
+<p><strong>THE ANNOTATION FILE FORMAT</strong>
+<br/>An annotation file consists of lines containing an instruction followed by
+tab delimited fields. Any lines starting with &quot;#&quot; are considered comments, and
+ignored. The sections below describe the structure of an annotation file.
+</p><ul>
+<li><a href="#annheader">JALVIEW_ANNOTATION</a> mandatory header</li>
+<li><a href="#annrows">LINE_GRAPH, BAR_GRAPH and NO_GRAPH</a> to create annotation rows</li>
+<li><a href="#combine">COMBINE, COLOUR and GRAPHLINE</a> for thresholds and complex line graphs</li>
+<li><a href="#annrowprops">ROWPROPERTIES</a> control the display of individual annotation rows</li>
+<li><a href="#groupdefs">SEQUENCE_GROUP</a> to define groups of sequences for further annotation</li>
+<li><a href="#groupprops">PROPERTIES</a> to set visualisation properties for sequence groups</li>
+<li><a href="#seqgrprefs">SEQUENCE_REF and GROUP_REF</a> for attaching annotation to sequences and groups</li>
+               <li><a href="#refsandviews">VIEW_SETREF, VIEW_HIDECOLS and HIDE_INSERTIONS</a>
+                       for defining a reference sequence on the alignment and hiding regions
+                       based on gaps in a reference sequence</li>
+       </ul>
+       <p>
+               At the end of this document, you can also find notes on <a
+                       href="#compatibility">compatibility</a> of annotation files across
+               different versions of Jalview. An <a href="#exampleann">example
+                       annotation file</a> is also provided along with instructions on how to
+               import it to Jalview.
+       </p>
+       <hr/>
+<p><strong><em><a name="annheader">Header line</a></em></strong><br/>The first non-commented out line of a valid Annotations file
 must begin with :<strong><pre>JALVIEW_ANNOTATION</pre></strong></p>
-<p>A row of annotation is added with a line like <strong><pre><em>GRAPH_TYPE</em>&#9;<em>Label</em>&#9;<em>Description</em> (optional)&#9;<em>Values</em></pre></strong></p>
+<hr/>
+<p><strong><em><a name="annrows">LINE_GRAPH, BAR_GRAPH and NO_GRAPH</a></em></strong><br/>
+Labels, secondary structure, histograms and line graphs are added with a line like <strong><pre><em>GRAPH_TYPE</em>&#9;<em>Label</em>&#9;<em>Description</em> (optional)&#9;<em>Values</em></pre></strong></p>
        <p>
-               The <em>GRAPH_TYPE</em> field, which appears first, defines the
+               Here, the <em>GRAPH_TYPE</em> field in the first column defines the
                appearance of the annotation row when rendered by Jalview. The next
                field is the row <em>label</em> for the annotation. This may be
                followed by a <em>description</em> for the row, which is shown in a
                tooltip when the user mouses over the annotation row's label. Since
-               Jalview 2.7, the description field may also contain html in the same
-               way as a <a href="featuresFile.html">sequence feature's</a> label,
-               providing the html is enclosed in an &lt;html/&gt; tag.
+               Jalview 2.7, the description field may also contain HTML tags (in the same
+               way as a <a href="featuresFile.html">sequence feature's</a> label),
+               providing the text is enclosed in an &lt;html/&gt; tag.
        
-       <ul><em>Please note: URL links embedded in HTML descriptions will
-                               be supported in a future release of Jalview</em>
+       <ul><em>Please note: URL links embedded in HTML descriptions are not yet supported.</em>
        </ul>
        </p>
                <p>The final <em>Values</em>
                field contains a series of &quot;|&quot; separated value fields. Each
                value field is itself a comma separated list of fields of a particular
-               type defined by the annotation row's GRAPH_TYPE. The allowed values of
-               GRAPH_TYPE and the format of their respective value fields (with the
-               trailing &quot;<strong>|</strong>&quot; symbol) are shown below:
+               type defined by the annotation row's <em>GRAPH_TYPE</em>. The allowed values of
+               <em>GRAPH_TYPE</em> and corresponding interpretation of each <em>Value</em> are shown below:
        
        <ul>
-               <li>BAR_GRAPH<br> Plots a histogram with labels below each
+               <li><strong>BAR_GRAPH</strong><br> Plots a histogram with labels below each
                        bar.<br> <em>number</em>,<em>text character</em>,<em>Tooltip
                                text</em>
                </li>
-               <li>LINE_GRAPH<br> Draws a line between values on the
+               <li><strong>LINE_GRAPH</strong><br> Draws a line between values on the
                        annotation row.<br> <em>number</em>
                </li>
-               <li>NO_GRAPH<br> For a row consisting of text labels and/or
-                       secondary structure symbols.<br> <em>{Secondary Structure
-                               Symbol}</em>,<em>text label</em>,<em>Tooltip text</em><br> Currently
-                       supported secondary structure structure symbols are <em>H</em> (for
-                       helix) and <em>E</em> (for strand)</li>
+               <li><strong>NO_GRAPH</strong><br>For a row consisting of text labels and/or
+                       secondary structure symbols.<br><em>{Secondary Structure
+                               Symbol}</em>,<em>text label</em>,<em>Tooltip text</em><br/><br/>The type of secondary structure symbol depends on the alignment being annotated being either Protein or RNA. <br/>For proteins, structure symbols are <em>H</em> (for
+                       helix) and <em>E</em> (for strand)<br/><br/>For RNA, VIENNA, WUSS or extended notation can be used to specify positions that are paired (e.g. &quot;(|(||)|)&quot; or &quot;|A|A|A|(|a|a|a|)&quot;)</li>
        </ul>
        Any or all value fields may be left empty, as well as the BAR_GRAPH's
 text character field, and either or both of the text-label and secondary
 structure symbol fields of the NO_GRAPH type annotation rows.</p>
 <p>Color strings can be embedded in a value field by enclosing an RGB triplet in square brackets to colour that position in an annotation row.  
 </p>
-<p><h3><font face="Arial, Helvetica, sans-serif">SEQUENCE_REF and GROUP_REF</font></h3>
+<hr/>
+<p><strong><a name="combine">COMBINE, COLOUR and GRAPHLINE</a> for line graphs</font></strong><br/>
+<em>LINE_GRAPH</em> type annotations can be given a colour
+(specified as 24 bit RGB triplet in hexadecimal or comma separated
+values), combined onto the same vertical axis, and have ordinate lines
+(horizontal lines at a particular vertical axis value) using the
+following commands (respectively): 
+<pre>COLOUR&#9;<em>graph_name</em>&#9;<em>colour</em>
+COMBINE&#9;<em>graph_1_name</em>&#9;<em>graph_2_name</em>
+GRAPHLINE&#9;<em>graph_name</em>&#9;<em>value</em>&#9;<em>label</em>&#9;<em>colour</em><strong><em>
+</em></strong></pre>
+</p>
+<hr/>
+<p><strong><a name="annrowprops">ROWPROPERTIES</a></strong><br/>
+The visual display properties for a set of annotation rows can be modified using the following tab-delimited line:</p>
+<pre>ROWPROPERTIES&#9;<em>Row label</em>&#9;<em>centrelabs=true( or false)</em>&#9;<em>showalllabs=true(default is false)</em>&#9;<em>scaletofit=true (default is false)</em></pre>
+<p>This sets the visual display properties according to the given values for all the annotation rows with labels matching <em>Row label</em>. The properties mostly affect the display of multi-character column labels, and are as follows:
+<ul><li><em>centrelabs</em> Centre each label on its column.</li>
+<li><em>showalllabs</em> Show every column label rather than only the first of a run of identical labels (setting this to true can have a drastic effect on secondary structure rows).</li>
+<li><em>scaletofit</em> Shrink each label's font size so that the label fits within the column. Useful when annotating an alignment with a specific column numbering system. (<em>Not available in Jalview applet due to AWT 1.1 limitations</em>)</li>
+</ul></p>
+<p><strong><a name="groupdefs">SEQUENCE_GROUP</a></strong><br/>
+Groups of sequences and column ranges can be defined using a tab delimited statement like:</p>
+<pre>SEQUENCE_GROUP&#9;Group_Name&#9;Group_Start&#9;Group_End&#9;<em>Sequences</em></pre>
+<p>The sequences can be defined by alignment index and a range of sequences can 
+  be defined in a comma delimited field such as</p>
+<p>2-5,8-15,20,22</p>
+<p>Enter * to select all groups. </p>
+<p><strong>Note:</strong> If the alignment indices are not known, enter -1, followed by a tab and then a tab delimited list 
+of sequence IDs. </p>
+<p>If a <a href="#seqgrprefs"><strong>SEQUENCE_REF</strong></a> has been defined, then <em>group_start</em> and <em>group_end</em> will be 
+  relative to the sequence residue numbering, otherwise the <em>group_start</em> and <em>group_end</em> 
+  will be alignment column indices. </p>
+<hr/>
+<p><strong><a name="groupprops">PROPERTIES</a></strong><br/>This statement allows various visualisation properties to be assigned to a named group. This takes a series of tab-delimited <em>key</em>=<em>value</em> pairs:</p>
+<pre>PROPERTIES&#9;Group_name&#9;tab_delimited_key_value_pairs
+</pre>
+<p>The currently supported set of sequence group key-value pairs that can be provided here are :</p>
+<table border="1">
+<tbody><tr><td width="50%">Key</td><td>Value</td></tr>
+<tr><td width="50%">description</td><td>Text - may include simple HTML tags</td></tr>
+<tr><td width="50%">colour</td><td>A string resolving to a valid Jalview colourscheme (e.g. Helix Propensity)</td></tr>
+<tr><td width="50%">pidThreshold</td><td>A number from 0-100 specifying the Percent Identity Threshold for colouring columns in the group or alignment</td></tr>
+<tr><td width="50%">consThreshold</td><td>A number from 0-100 specifying the degree of bleaching applied for conservation colouring</td></tr>
+<tr><td width="50%">outlineColour</td><td>Line colour used for outlining the group (default is red)</td></tr>
+<tr><td width="50%">displayBoxes</td><td>Boolean (default true) controlling display of shaded box for each alignment position</td></tr>
+<tr><td width="50%">displayText</td><td>Boolean (default true) controlling display of text for each alignment position</td></tr>
+<tr><td width="50%">colourText</td><td>Boolean (default false) specifying whether text should be shaded by applied colourscheme</td></tr>
+<tr><td width="50%">textCol1</td><td>Colour for text when shown on a light background</td></tr>
+<tr><td width="50%">textCol2</td><td>Colour for text when shown on a dark background</td></tr>
+<tr><td width="50%">textColThreshold</td><td>Number from 0-100 specifying switching threshold between light and dark background</td></tr>
+<tr><td width="50%">idColour</td><td>Colour for highlighting the Sequence ID labels for this group<br/>If <em>idColour</em> is given but <em>colour</em> is not, then idColor will also be used for the group background colour.</td></tr>
+<tr><td width="50%">showunconserved</td><td>Boolean (default false) indicating whether residues should only be shown that are different from current reference or consensus sequence</td></tr>
+<tr><td width="50%">hide</td><td>Boolean (default false) indicating whether the rows in this group should be marked as hidden.<br/><em>Note:</em> if the group is sequence associated (specified by SEQUENCE_REF), then all members will be hidden and marked as represented by the reference sequence.</td></tr>
+<!-- <tr><td width="50%">hidecols</td><td>Boolean (default false) indicating whether columns in this groushould be marked as hidden</td></tr> --></tbody>
+</table>
+
+<p><strong>Specifying colours in PROPERTIES key-value pairs</strong><br/>
+The <strong>colour</strong> property can take either a colour scheme name,
+ or a single colour specification (either a colour name like 'red' or an RGB
+ triplet like 'ff0066'). If a single colour is specified, then the group
+ will be coloured with that colour.</p>
+ <hr/>
+ <p><strong><a name="seqgrprefs">SEQUENCE_REF and GROUP_REF</a></strong><br/>
        By
                default, annotation is associated with the alignment as a whole.
                However, it is also possible to have an annotation row associated with
@@ -114,79 +205,42 @@ definitions by:
 Group association is turned off for subsequent annotation rows by: 
 <pre>GROUP_REF&#9;<em>ALIGNMENT</em></pre>
 </p>
-<h3><font face="Arial, Helvetica, sans-serif">LINE_GRAPH Grouping</font></h3>
-<p><em>LINE_GRAPH</em> type annotations can be given a colour
-(specified as 24 bit RGB triplet in hexadecimal or comma separated
-values), combined onto the same vertical axis, and have ordinate lines
-(horizontal lines at a particular vertical axis value) using the
-following commands (respectively): 
-<pre>COLOUR&#9;<em>graph_name</em>&#9;<em>colour</em>
-COMBINE&#9;<em>graph_1_name</em>&#9;<em>graph_2_name</em>
-GRAPHLINE&#9;<em>graph_name</em>&#9;<em>value</em>&#9;<em>label</em>&#9;<em>colour</em><strong><em>
-</em></strong></pre>
-</p>
-<h3><font face="Arial, Helvetica, sans-serif">(Since Jalview 2.5) ROWPROPERTIES</font></h3>
-<p>The visual display properties for a set of annotation rows can be modified using the following tab-delimited line:</p>
-<pre>ROWPROPERTIES&#9;<em>Row label</em>&#9;<em>centrelabs=true( or false)</em>&#9;<em>showalllabs=true(default is false)</em>&#9;<em>scaletofit=true (default is false)</em></pre>
-<p>This sets the visual display properties according to the given values for all the annotation rows with labels matching <em>Row label</em>. The properties mostly affect the display of multi-character column labels, and are as follows:
-<ul><li><em>centrelabs</em> Centre each label on its column.</li>
-<li><em>showalllabs</em> Show every column label rather than only the first of a run of identical labels (setting this to true can have a drastic effect on secondary structure rows).</li>
-<li><em>scaletofit</em> Shrink each label's font size so that the label fits within the column. Useful when annotating an alignment with a specific column numbering system. (<em>Not available in Jalview applet due to AWT 1.1 limitations</em>)</li>
-</ul></p>
-<h3><font face="Arial, Helvetica, sans-serif">(Since Jalview 2.2.1) SEQUENCE_GROUP</font></h3>
-<p>Groups of sequences can be defined using the tab delimited line</p>
-<pre>SEQUENCE_GROUP    Group_Name      Group_Start     Group_End       <em>Sequences</em></pre>
-<p>The sequences can be defined by alignment index and a range of sequences can 
-  be defined in a comma delimited field such as</p>
-<p>2-5,8-15,20,22</p>
-<p>Enter * to select all groups. </p>
-<p><strong>Note:</strong> If the alignment indices are not known, enter -1, followed by a tab and then a tab delimited list 
-specifying the sequence ids. </p>
-<p>If the SEQUENCE_REF has been defined, the group_start and group_end will be 
-  relative to the sequence residue numbering, otherwise the group_start and group_end 
-  will be the alignment column indices. </p>
-<p>The group can (optionally) be assigned various visualisation properties via 
-  another tab delimited line thus:</p>
-<pre>PROPERTIES        Group_name      tab_delimited_key_value_pairs
-</pre>
-<p>The key_value_pairs allow you to define a description and to colour the group 
-  in various ways. All, none or some of the following values could be used for 
-  a group:</p>
-<p>description=Text <br>
-  colour=Helix Propensity<br>
-  pidThreshold=0<br>
-  consThreshold=0<br>
-  outlineColour=red <br>
-  displayBoxes=true<br>
-  displayText=false<br>
-  colourText=false<br>
-  textCol1=black<br>
-  textCol2=black<br>
-  textColThreshold=0<br>
-  idColour=ff3322<br>
- <!-- Not yet implemented in 2.5 release 
-  hide=false<br>
-  hidecols=false<br> -->
-  showunconserved=false</p>
-<ul><li><em>New Features in 2.4:</em><br>if the <strong>idColour</strong> property
-is given without specifying a colour scheme with the <strong>colour</strong>
-property, then the idColour will also be used to colour the sequence.</li>
-<li>the <strong>colour</strong> property can take either a colour scheme name,
- or a single colour specification (either a colour name like 'red' or an RGB
- triplet like 'ff0066'). If a single colour is specified, then the group
- will be coloured with that colour.</li>
- <!--  <li><em>New Features in 2.5</em></li>
- <li>hide and hidecols instruct jalview to hide the sequences or columns covered by the group.</li> -->
-  <li>Sequence associated Groups<br>If a group is defined after a valid
- <em>SEQUENCE_REF</em> sequence reference statement, the sequence representative
- for the group will be set to the referenced sequence.<!-- <br><strong>Note:</strong> if the <em>hide</em> 
- property is set then only the representative sequence for the group will be shown in the alignment.--></li>
- <li>The interpretation of the COMBINE statement in <em>Version 2.8.1</em> was refined
+<hr/>
+<p><strong><a name="refsandviews">VIEW_SETREF, VIEW_HIDECOL and HIDE_INSERTIONS</a></strong><br/>
+Since Jalview 2.9, the Annotations file has also supported the definition of views on the alignment, and definition of hidden regions.</p>
+<!--   <p>
+               <em>VIEW_DEF</em> allows the current view to be named according to the
+               first argument after the tab character. If a second argument is
+               provided, then a new view is created with the given name, and
+               properties.
+       </p> -->
+       <p>
+               <em>VIEW_SETREF</em> takes either a single sequence ID string, or a
+               numeric index (second argument), and attempts to assign a
+               corresponding sequence as the <a href="../features/refsequence.html">reference
+                       sequence</a> for the alignment.
+       </p>
+       <em>VIEW_HIDECOLS</em> takes either a single argument consisting of a
+       comma separated series of integer pairs like
+       <em>3-4</em>. These integer pairs define columns (starting from the
+       left-hand column 0) that should be marked as hidden in the alignment
+       view.
+       </p>
+       <p>
+               <em>HIDE_INSERTIONS</em> takes a either a single sequence ID or a
+               numeric index, or no arguments. This command marks all gapped
+               positions in a specified sequence (either the one located by the
+               arguments, the current SEQUENCE_REF, or the reference sequence for the
+               view).
+       <hr/>
+<p><strong><a name="compatibility">COMPATIBILITY NOTES</a></strong><br/>
+ The interpretation of the COMBINE statement in <em>Version 2.8.1</em> was refined
  so that only annotation line graphs with the given names ands the same 
- <strong>SEQUENCE_REF</strong> and <strong>GROUP_REF</strong> scope are grouped.</li>
-</ul>
-<p> </p>
-<p>An example Annotation file is given below:
+ <strong>SEQUENCE_REF</strong> and <strong>GROUP_REF</strong> scope are grouped.</p>
+ <hr/>
+
+<p><strong><a name="exampleann">EXAMPLES</a></strong><br/>
+An example Annotation file is given below. Copy and paste the contents into a text file and load it onto the Jalview example protein alignment.</p>
 <pre>#Comment lines follow the hash symbol
 JALVIEW_ANNOTATION
 SEQUENCE_REF&#9;FER1_MESCR&#9;5
@@ -211,6 +265,5 @@ PROPERTIES&#9;Group_B&#9;outlineColour=red
 PROPERTIES&#9;Group_C&#9;colour=Clustal
 </pre>
 </p>
-<p><em>Last updated for version 2.8.1</em></p>
 </body>
 </html>
diff --git a/lib/jsoup-1.8.1.jar b/lib/jsoup-1.8.1.jar
new file mode 100644 (file)
index 0000000..ae717d4
Binary files /dev/null and b/lib/jsoup-1.8.1.jar differ
index fce8470..cfec3c9 100644 (file)
@@ -57,8 +57,10 @@ action.by_pairwise_id = by Pairwise Identity
 action.by_id = by Id
 action.by_length = by Length
 action.by_group = by Group
+action.unmark_as_reference = Unmark as Reference 
+action.set_as_reference = Set as Reference 
 action.remove = Remove
-action.remove_redundancy = Remove Redundancy
+action.remove_redundancy = Remove Redundancy...
 action.pairwise_alignment = Pairwise Alignments...
 action.by_rna_helixes = by RNA Helices
 action.user_defined = User Defined...
@@ -476,8 +478,8 @@ label.settings_for_type = Settings for {0}
 label.view_full_application = View in Full Application
 label.load_associated_tree = Load Associated Tree ...
 label.load_features_annotations = Load Features/Annotations ...
-label.export_features = Export Features
-label.export_annotations = Export Annotations
+label.export_features = Export Features ...
+label.export_annotations = Export Annotations ...
 label.jalview_copy = Copy (Jalview Only)
 label.jalview_cut = Cut (Jalview Only)
 label.to_upper_case = To Upper Case
@@ -595,7 +597,7 @@ label.figure_id_column_width = Figure ID column width
 label.use_modeller_output = Use Modeller Output
 label.wrap_alignment = Wrap Alignment
 label.right_align_ids = Right Align Ids
-label.sequence_name_italics = Seq Name Italics
+label.sequence_name_italics = Sequence Name Italics
 label.open_overview = Open Overview
 label.default_colour_scheme_for_alignment = Default Colour Scheme for alignment
 label.annotation_shading_default = Annotation Shading Default
@@ -1176,3 +1178,16 @@ label.show_logo = Show Logo
 label.normalise_logo = Normalise Logo
 label.no_colour_selection_in_scheme = Please, make a colour selection before to apply colour scheme
 label.no_colour_selection_warn = Error saving colour scheme
+label.select_by_annotation = Select By Annotation
+action.select_by_annotation = Select by Annotation...
+label.threshold_filter =  Threshold Filter
+action.hide = Hide
+action.select = Select
+label.alpha_helix = Alpha Helix
+label.beta_strand = Beta Strand
+label.turn = Turn
+label.select_all = Select All
+label.structures_filter = Structures Filter
+label.search_filter = Search Filter
+label.display_name = Display Label
+label.description = Description
diff --git a/resources/templates/BioJSTemplate.txt b/resources/templates/BioJSTemplate.txt
new file mode 100644 (file)
index 0000000..bf780bb
--- /dev/null
@@ -0,0 +1,9032 @@
+<html>
+<header><title>BioJS viewer</title></header>
+
+<body>
+
+<!-- include MSA js + css -->
+<!-- <script src="https://s3-eu-west-1.amazonaws.com/biojs/msa/latest/msa.js"></script> -->
+<!-- <link type=text/css rel=stylesheet href=https://s3-eu-west-1.amazonaws.com/biojs/msa/latest/msa.css /> -->
+ <img src="http://www.jalview.org/help/html/Jalview_Logo.png" alt="Jalview Logo" title="This html page was generated from Jalview, to import the data back to Jalview, please drag the generated html file and drop it unto the Jalview workbench.
+ Alternatively, you could copy the url from the address bar and use Jalview's url importer (main menu-> File-> Input Alignment-> from URL) to import back the alignment jalview." >
+
+</br>
+</br>
+
+<input type="button" name="divToggleButton" id="divToggleButton" onclick="javascipt:toggleMenuVisibility();" value="Show Menu"></input>
+<button onclick="javascipt:openJalviewUsingCurrentUrl();">Launch in Jalview</button>
+
+</br>
+</br> 
+  
+<div id="yourDiv">press "Run with JS"</div>
+<input type='hidden' id='seqData' name='seqData' value='#sequenceData#'/>
+
+</body>
+</html>
+
+
+
+<script>
+
+function toggleMenuVisibility(){
+       var menu = document.getElementsByClassName("biojs_msa_menubar");
+       var divToggleButton = document.getElementById("divToggleButton");
+       if(menu[0].style.display == 'block'){
+          menu[0].style.display = 'none';
+          divToggleButton.value="Show Menu";
+       }else{
+          menu[0].style.display = 'block'; 
+          divToggleButton.value="Hide Menu";
+          }
+}
+
+
+function openJalviewUsingCurrentUrl(){
+       var jalviewData = JSON.parse(document.getElementById("seqData").value)
+    var jalviewVersion = jalviewData['jalviewVersion'];
+    var url = jalviewData['webStartUrl'];
+       var myForm = document.createElement("form");
+       myForm.action = url;
+       
+    var heap = document.createElement("input") ;
+    heap.setAttribute("name", "jvm-max-heap") ;
+    heap.setAttribute("value", "2G");
+    myForm.appendChild(heap) ;
+    
+    var target = document.createElement("input") ;
+    target.setAttribute("name", "open") ;
+    target.setAttribute("value", document.URL);
+    myForm.appendChild(target) ;
+    
+    var jvVersion = document.createElement("input") ;
+    jvVersion.setAttribute("name", "version") ;
+    jvVersion.setAttribute("value", jalviewVersion);
+    myForm.appendChild(jvVersion) ;
+    
+
+       document.body.appendChild(myForm) ;
+       myForm.submit() ;
+       document.body.removeChild(myForm) ;
+}
+
+
+require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+var css = ".biojs_msa_stage {\n  cursor: default;\n  line-height: normal; }\n\n.biojs_msa_labels {\n  color: black;\n  display: inline-block;\n  white-space: nowrap;\n  cursor: pointer;\n  vertical-align: top; }\n\n.biojs_msa_seqblock {\n  cursor: move; }\n\n.biojs_msa_layer {\n  display: block;\n  white-space: nowrap; }\n\n.biojs_msa_labelblock::-webkit-scrollbar, .biojs_msa_header::-webkit-scrollbar {\n  -webkit-appearance: none;\n  width: 7px;\n  height: 7px; }\n\n.biojs_msa_labelblock::-webkit-scrollbar-thumb, .biojs_msa_header::-webkit-scrollbar-thumb {\n  border-radius: 4px;\n  background-color: rgba(0, 0, 0, 0.5);\n  box-shadow: 0 0 1px rgba(255, 255, 255, 0.5); }\n\n.biojs_msa_marker {\n  color: grey;\n  white-space: nowrap;\n  cursor: pointer; }\n\n.biojs_msa_marker span {\n  text-align: center; }\n\n.biojs_msa_menubar .biojs_msa_menubar_alink {\n  background: #3498db;\n  background-image: -webkit-linear-gradient(top, #3498db, #2980b9);\n  background-image: -moz-linear-gradient(top, #3498db, #2980b9);\n  background-image: -ms-linear-gradient(top, #3498db, #2980b9);\n  background-image: -o-linear-gradient(top, #3498db, #2980b9);\n  background-image: linear-gradient(to bottom, #3498db, #2980b9);\n  -webkit-border-radius: 28;\n  -moz-border-radius: 28;\n  border-radius: 28px;\n  font-family: Arial;\n  color: #ffffff;\n  padding: 3px 10px 3px 10px;\n  margin-left: 10px;\n  text-decoration: none; }\n\n.biojs_msa_menubar .biojs_msa_menubar_alink:hover {\n  cursor: pointer; }\n\n/* jquery dropdown CSS */\n.dropdown {\n  position: absolute;\n  z-index: 9999999;\n  display: none; }\n\n.dropdown .dropdown-menu,\n.dropdown .dropdown-panel {\n  min-width: 160px;\n  max-width: 360px;\n  list-style: none;\n  background: #FFF;\n  border: solid 1px #DDD;\n  border: solid 1px rgba(0, 0, 0, 0.2);\n  border-radius: 6px;\n  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n  overflow: visible;\n  padding: 4px 0;\n  margin: 0; }\n\n.dropdown .dropdown-panel {\n  padding: 10px; }\n\n.dropdown.dropdown-scroll .dropdown-menu,\n.dropdown.dropdown-scroll .dropdown-panel {\n  max-height: 358px;\n  overflow: auto; }\n\n.dropdown .dropdown-menu LI {\n  list-style: none;\n  padding: 0 0;\n  margin: 0;\n  line-height: 18px; }\n\n.dropdown .dropdown-menu LI,\n.dropdown .dropdown-menu LABEL {\n  display: block;\n  color: #555;\n  text-decoration: none;\n  line-height: 18px;\n  padding: 3px 15px;\n  white-space: nowrap; }\n\n.dropdown .dropdown-menu LI:hover,\n.dropdown .dropdown-menu LABEL:hover {\n  background-color: #08C;\n  color: #FFF;\n  cursor: pointer; }\n\n.dropdown .dropdown-menu .dropdown-divider {\n  font-size: 1px;\n  border-top: solid 1px #E5E5E5;\n  padding: 0;\n  margin: 5px 0; }\n"; (require("/home/travis/build/greenify/biojs-vis-msa/node_modules/cssify"))(css); module.exports = css;
+},{"/home/travis/build/greenify/biojs-vis-msa/node_modules/cssify":48}],2:[function(require,module,exports){
+module.exports = require("./src/index");
+
+},{"./src/index":72}],3:[function(require,module,exports){
+var _ = require('underscore');
+var viewType = require("backbone-viewj");
+var pluginator;
+
+module.exports = pluginator = viewType.extend({
+  renderSubviews: function() {
+    var oldEl = this.el;
+    var el = document.createElement("div");
+    this.setElement(el);
+    var frag = document.createDocumentFragment();
+    if (oldEl.parentNode != null) {
+      oldEl.parentNode.replaceChild(this.el, oldEl);
+    }
+    var views = this._views();
+    var viewsSorted = _.sortBy(views, function(el) {
+      return el.ordering;
+    });
+    var view, node;
+    for (var i = 0; i <  viewsSorted.length; i++) {
+      view = viewsSorted[i];
+      view.render();
+      node = view.el;
+      if (node != null) {
+        frag.appendChild(node);
+      }
+    }
+    el.appendChild(frag);
+    return el;
+  },
+  addView: function(key, view) {
+    var views = this._views();
+    if (view == null) {
+      throw "Invalid plugin. ";
+    }
+    if (view.ordering == null) {
+      view.ordering = key;
+    }
+    return views[key] = view;
+  },
+  removeViews: function() {
+    var el, key;
+    var views = this._views();
+    for (key in views) {
+      el = views[key];
+      el.undelegateEvents();
+      el.unbind();
+      if (el.removeViews != null) {
+        el.removeViews();
+      }
+      el.remove();
+    }
+    return this.views = {};
+  },
+  removeView: function(key) {
+    var views = this._views();
+    views[key].remove();
+    return delete views[key];
+  },
+  getView: function(key) {
+    var views = this._views();
+    return views[key];
+  },
+  remove: function() {
+    this.removeViews();
+    return viewType.prototype.remove.apply(this);
+  },
+  _views: function() {
+    if (this.views == null) {
+      this.views = {};
+    }
+    return this.views;
+  }
+});
+
+},{"backbone-viewj":10,"underscore":59}],4:[function(require,module,exports){
+//     Backbone.js 1.1.2
+
+//     (c) 2010-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+//     Backbone may be freely distributed under the MIT license.
+//     For all details and documentation:
+//     http://backbonejs.org
+
+var Events = require("backbone-events-standalone");
+var extend = require("backbone-extend-standalone");
+var _ = require("underscore");
+var Model = require("./model");
+
+// Create local references to array methods we'll want to use later.
+var array = [];
+var slice = array.slice;
+
+// Backbone.Collection
+// -------------------
+
+// If models tend to represent a single row of data, a Backbone Collection is
+// more analogous to a table full of data ... or a small slice or page of that
+// table, or a collection of rows that belong together for a particular reason
+// -- all of the messages in this particular folder, all of the documents
+// belonging to this particular author, and so on. Collections maintain
+// indexes of their models, both in order, and for lookup by `id`.
+
+// Create a new **Collection**, perhaps to contain a specific type of `model`.
+// If a `comparator` is specified, the Collection will maintain
+// its models in sort order, as they're added and removed.
+var Collection = function(models, options) {
+  options || (options = {});
+  if (options.model) this.model = options.model;
+  if (options.comparator !== void 0) this.comparator = options.comparator;
+  this._reset();
+  this.initialize.apply(this, arguments);
+  if (models) this.reset(models, _.extend({silent: true}, options));
+};
+
+// Default options for `Collection#set`.
+var setOptions = {add: true, remove: true, merge: true};
+var addOptions = {add: true, remove: false};
+
+// Define the Collection's inheritable methods.
+_.extend(Collection.prototype, Events, {
+
+  // The default model for a collection is just a **Backbone.Model**.
+  // This should be overridden in most cases.
+  model: Model,
+
+  // Initialize is an empty function by default. Override it with your own
+  // initialization logic.
+  initialize: function(){},
+
+    // The JSON representation of a Collection is an array of the
+    // models' attributes.
+  toJSON: function(options) {
+    return this.map(function(model){ return model.toJSON(options); });
+  },
+
+    // Proxy `Backbone.sync` by default.
+  sync: function() {
+    return Backbone.sync.apply(this, arguments);
+  },
+
+    // Add a model, or list of models to the set.
+  add: function(models, options) {
+    return this.set(models, _.extend({merge: false}, options, addOptions));
+  },
+
+    // Remove a model, or a list of models from the set.
+  remove: function(models, options) {
+    var singular = !_.isArray(models);
+    models = singular ? [models] : _.clone(models);
+    options || (options = {});
+    for (var i = 0, length = models.length; i < length; i++) {
+      var model = models[i] = this.get(models[i]);
+      if (!model) continue;
+      var id = this.modelId(model.attributes);
+      if (id != null) delete this._byId[id];
+      delete this._byId[model.cid];
+      var index = this.indexOf(model);
+      this.models.splice(index, 1);
+      this.length--;
+      if (!options.silent) {
+        options.index = index;
+        model.trigger('remove', model, this, options);
+      }
+      this._removeReference(model, options);
+    }
+    return singular ? models[0] : models;
+  },
+
+    // Update a collection by `set`-ing a new list of models, adding new ones,
+    // removing models that are no longer present, and merging models that
+    // already exist in the collection, as necessary. Similar to **Model#set**,
+    // the core operation for updating the data contained by the collection.
+  set: function(models, options) {
+    options = _.defaults({}, options, setOptions);
+    if (options.parse) models = this.parse(models, options);
+    var singular = !_.isArray(models);
+    models = singular ? (models ? [models] : []) : models.slice();
+    var id, model, attrs, existing, sort;
+    var at = options.at;
+    var sortable = this.comparator && (at == null) && options.sort !== false;
+    var sortAttr = _.isString(this.comparator) ? this.comparator : null;
+    var toAdd = [], toRemove = [], modelMap = {};
+    var add = options.add, merge = options.merge, remove = options.remove;
+    var order = !sortable && add && remove ? [] : false;
+
+    // Turn bare objects into model references, and prevent invalid models
+    // from being added.
+    for (var i = 0, length = models.length; i < length; i++) {
+      attrs = models[i];
+
+      // If a duplicate is found, prevent it from being added and
+      // optionally merge it into the existing model.
+      if (existing = this.get(attrs)) {
+        if (remove) modelMap[existing.cid] = true;
+        if (merge && attrs !== existing) {
+          attrs = this._isModel(attrs) ? attrs.attributes : attrs;
+          if (options.parse) attrs = existing.parse(attrs, options);
+          existing.set(attrs, options);
+          if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true;
+        }
+        models[i] = existing;
+
+        // If this is a new, valid model, push it to the `toAdd` list.
+      } else if (add) {
+        model = models[i] = this._prepareModel(attrs, options);
+        if (!model) continue;
+        toAdd.push(model);
+        this._addReference(model, options);
+      }
+
+      // Do not add multiple models with the same `id`.
+      model = existing || model;
+      if (!model) continue;
+      id = this.modelId(model.attributes);
+      if (order && (model.isNew() || !modelMap[id])) order.push(model);
+      modelMap[id] = true;
+    }
+
+    // Remove nonexistent models if appropriate.
+    if (remove) {
+      for (var i = 0, length = this.length; i < length; i++) {
+        if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
+      }
+      if (toRemove.length) this.remove(toRemove, options);
+    }
+
+    // See if sorting is needed, update `length` and splice in new models.
+    if (toAdd.length || (order && order.length)) {
+      if (sortable) sort = true;
+      this.length += toAdd.length;
+      if (at != null) {
+        for (var i = 0, length = toAdd.length; i < length; i++) {
+          this.models.splice(at + i, 0, toAdd[i]);
+        }
+      } else {
+        if (order) this.models.length = 0;
+        var orderedModels = order || toAdd;
+        for (var i = 0, length = orderedModels.length; i < length; i++) {
+          this.models.push(orderedModels[i]);
+        }
+      }
+    }
+
+    // Silently sort the collection if appropriate.
+    if (sort) this.sort({silent: true});
+
+    // Unless silenced, it's time to fire all appropriate add/sort events.
+    if (!options.silent) {
+      var addOpts = at != null ? _.clone(options) : options;
+      for (var i = 0, length = toAdd.length; i < length; i++) {
+        if (at != null) addOpts.index = at + i;
+        (model = toAdd[i]).trigger('add', model, this, addOpts);
+      }
+      if (sort || (order && order.length)) this.trigger('sort', this, options);
+    }
+
+    // Return the added (or merged) model (or models).
+    return singular ? models[0] : models;
+  },
+
+    // When you have more items than you want to add or remove individually,
+    // you can reset the entire set with a new list of models, without firing
+    // any granular `add` or `remove` events. Fires `reset` when finished.
+    // Useful for bulk operations and optimizations.
+  reset: function(models, options) {
+    options || (options = {});
+    for (var i = 0, length = this.models.length; i < length; i++) {
+      this._removeReference(this.models[i], options);
+    }
+    options.previousModels = this.models;
+    this._reset();
+    models = this.add(models, _.extend({silent: true}, options));
+    if (!options.silent) this.trigger('reset', this, options);
+    return models;
+  },
+
+    // Add a model to the end of the collection.
+  push: function(model, options) {
+    return this.add(model, _.extend({at: this.length}, options));
+  },
+
+    // Remove a model from the end of the collection.
+  pop: function(options) {
+    var model = this.at(this.length - 1);
+    this.remove(model, options);
+    return model;
+  },
+
+    // Add a model to the beginning of the collection.
+  unshift: function(model, options) {
+    return this.add(model, _.extend({at: 0}, options));
+  },
+
+    // Remove a model from the beginning of the collection.
+  shift: function(options) {
+    var model = this.at(0);
+    this.remove(model, options);
+    return model;
+  },
+
+    // Slice out a sub-array of models from the collection.
+  slice: function() {
+    return slice.apply(this.models, arguments);
+  },
+
+    // Get a model from the set by id.
+  get: function(obj) {
+    if (obj == null) return void 0;
+    var id = this.modelId(this._isModel(obj) ? obj.attributes : obj);
+    return this._byId[obj] || this._byId[id] || this._byId[obj.cid];
+  },
+
+    // Get the model at the given index.
+  at: function(index) {
+    if (index < 0) index += this.length;
+    return this.models[index];
+  },
+
+    // Return models with matching attributes. Useful for simple cases of
+    // `filter`.
+  where: function(attrs, first) {
+    if (_.isEmpty(attrs)) return first ? void 0 : [];
+    return this[first ? 'find' : 'filter'](function(model) {
+      for (var key in attrs) {
+        if (attrs[key] !== model.get(key)) return false;
+      }
+      return true;
+    });
+  },
+
+    // Return the first model with matching attributes. Useful for simple cases
+    // of `find`.
+  findWhere: function(attrs) {
+    return this.where(attrs, true);
+  },
+
+    // Force the collection to re-sort itself. You don't need to call this under
+    // normal circumstances, as the set will maintain sort order as each item
+    // is added.
+  sort: function(options) {
+    if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
+    options || (options = {});
+
+    // Run sort based on type of `comparator`.
+    if (_.isString(this.comparator) || this.comparator.length === 1) {
+      this.models = this.sortBy(this.comparator, this);
+    } else {
+      this.models.sort(_.bind(this.comparator, this));
+    }
+
+    if (!options.silent) this.trigger('sort', this, options);
+    return this;
+  },
+
+    // Pluck an attribute from each model in the collection.
+  pluck: function(attr) {
+    return _.invoke(this.models, 'get', attr);
+  },
+
+    // Fetch the default set of models for this collection, resetting the
+    // collection when they arrive. If `reset: true` is passed, the response
+    // data will be passed through the `reset` method instead of `set`.
+  fetch: function(options) {
+    options = options ? _.clone(options) : {};
+    if (options.parse === void 0) options.parse = true;
+    var success = options.success;
+    var collection = this;
+    options.success = function(resp) {
+      var method = options.reset ? 'reset' : 'set';
+      collection[method](resp, options);
+      if (success) success(collection, resp, options);
+      collection.trigger('sync', collection, resp, options);
+    };
+    wrapError(this, options);
+    return this.sync('read', this, options);
+  },
+
+    // Create a new instance of a model in this collection. Add the model to the
+    // collection immediately, unless `wait: true` is passed, in which case we
+    // wait for the server to agree.
+  create: function(model, options) {
+    options = options ? _.clone(options) : {};
+    if (!(model = this._prepareModel(model, options))) return false;
+    if (!options.wait) this.add(model, options);
+    var collection = this;
+    var success = options.success;
+    options.success = function(model, resp) {
+      if (options.wait) collection.add(model, options);
+      if (success) success(model, resp, options);
+    };
+    model.save(null, options);
+    return model;
+  },
+
+    // **parse** converts a response into a list of models to be added to the
+    // collection. The default implementation is just to pass it through.
+  parse: function(resp, options) {
+    return resp;
+  },
+
+    // Create a new collection with an identical list of models as this one.
+  clone: function() {
+    return new this.constructor(this.models, {
+      model: this.model,
+      comparator: this.comparator
+    });
+  },
+
+    // Define how to uniquely identify models in the collection.
+  modelId: function (attrs) {
+    return attrs[this.model.prototype.idAttribute || 'id'];
+  },
+
+    // Private method to reset all internal state. Called when the collection
+    // is first initialized or reset.
+  _reset: function() {
+    this.length = 0;
+    this.models = [];
+    this._byId  = {};
+  },
+
+    // Prepare a hash of attributes (or other model) to be added to this
+    // collection.
+  _prepareModel: function(attrs, options) {
+    if (this._isModel(attrs)) {
+      if (!attrs.collection) attrs.collection = this;
+      return attrs;
+    }
+    options = options ? _.clone(options) : {};
+    options.collection = this;
+    var model = new this.model(attrs, options);
+    if (!model.validationError) return model;
+    this.trigger('invalid', this, model.validationError, options);
+    return false;
+  },
+
+    // Method for checking whether an object should be considered a model for
+    // the purposes of adding to the collection.
+  _isModel: function (model) {
+    return model instanceof Model;
+  },
+
+    // Internal method to create a model's ties to a collection.
+  _addReference: function(model, options) {
+    this._byId[model.cid] = model;
+    var id = this.modelId(model.attributes);
+    if (id != null) this._byId[id] = model;
+    model.on('all', this._onModelEvent, this);
+  },
+
+    // Internal method to sever a model's ties to a collection.
+  _removeReference: function(model, options) {
+    if (this === model.collection) delete model.collection;
+    model.off('all', this._onModelEvent, this);
+  },
+
+    // Internal method called every time a model in the set fires an event.
+    // Sets need to update their indexes when models change ids. All other
+    // events simply proxy through. "add" and "remove" events that originate
+    // in other collections are ignored.
+  _onModelEvent: function(event, model, collection, options) {
+    if ((event === 'add' || event === 'remove') && collection !== this) return;
+    if (event === 'destroy') this.remove(model, options);
+    if (event === 'change') {
+      var prevId = this.modelId(model.previousAttributes());
+      var id = this.modelId(model.attributes);
+      if (prevId !== id) {
+        if (prevId != null) delete this._byId[prevId];
+        if (id != null) this._byId[id] = model;
+      }
+    }
+    this.trigger.apply(this, arguments);
+  }
+
+});
+
+// Underscore methods that we want to implement on the Collection.
+// 90% of the core usefulness of Backbone Collections is actually implemented
+// right here:
+var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
+    'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
+    'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
+    'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
+    'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle',
+    'lastIndexOf', 'isEmpty', 'chain', 'sample', 'partition'];
+
+// Mix in each Underscore method as a proxy to `Collection#models`.
+_.each(methods, function(method) {
+  if (!_[method]) return;
+  Collection.prototype[method] = function() {
+    var args = slice.call(arguments);
+    args.unshift(this.models);
+    return _[method].apply(_, args);
+  };
+});
+
+// Underscore methods that take a property name as an argument.
+var attributeMethods = ['groupBy', 'countBy', 'sortBy', 'indexBy'];
+
+// Use attributes instead of properties.
+_.each(attributeMethods, function(method) {
+  if (!_[method]) return;
+  Collection.prototype[method] = function(value, context) {
+    var iterator = _.isFunction(value) ? value : function(model) {
+      return model.get(value);
+    };
+    return _[method](this.models, iterator, context);
+  };
+});
+
+// setup inheritance
+Collection.extend = extend;
+module.exports = Collection;
+
+},{"./model":6,"backbone-events-standalone":8,"backbone-extend-standalone":9,"underscore":59}],5:[function(require,module,exports){
+module.exports.Model = require("./model");
+module.exports.Collection = require("./collection");
+module.exports.Events = require("backbone-events-standalone");
+module.exports.extend = require("backbone-extend-standalone");
+
+},{"./collection":4,"./model":6,"backbone-events-standalone":8,"backbone-extend-standalone":9}],6:[function(require,module,exports){
+//     Backbone.js 1.1.2
+
+//     (c) 2010-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+//     Backbone may be freely distributed under the MIT license.
+//     For all details and documentation:
+//     http://backbonejs.org
+
+var Events = require("backbone-events-standalone");
+var extend = require("backbone-extend-standalone");
+var _ = require("underscore");
+
+// Backbone.Model
+// --------------
+
+// Backbone **Models** are the basic data object in the framework --
+// frequently representing a row in a table in a database on your server.
+// A discrete chunk of data and a bunch of useful, related methods for
+// performing computations and transformations on that data.
+
+// Create a new model with the specified attributes. A client id (`cid`)
+// is automatically generated and assigned for you.
+var Model = function(attributes, options) {
+  var attrs = attributes || {};
+  options || (options = {});
+  this.cid = _.uniqueId('c');
+  this.attributes = {};
+  if (options.collection) this.collection = options.collection;
+  if (options.parse) attrs = this.parse(attrs, options) || {};
+  attrs = _.defaults({}, attrs, _.result(this, 'defaults'));
+  this.set(attrs, options);
+  this.changed = {};
+  this.initialize.apply(this, arguments);
+};
+
+// Attach all inheritable methods to the Model prototype.
+_.extend(Model.prototype, Events, {
+
+  // A hash of attributes whose current and previous value differ.
+  changed: null,
+
+  // The value returned during the last failed validation.
+  validationError: null,
+
+    // The default name for the JSON `id` attribute is `"id"`. MongoDB and
+    // CouchDB users may want to set this to `"_id"`.
+  idAttribute: 'id',
+
+    // Initialize is an empty function by default. Override it with your own
+    // initialization logic.
+  initialize: function(){},
+
+    // Return a copy of the model's `attributes` object.
+  toJSON: function(options) {
+    return _.clone(this.attributes);
+  },
+
+    // Proxy `Backbone.sync` by default -- but override this if you need
+    // custom syncing semantics for *this* particular model.
+  sync: function() {
+    return Backbone.sync.apply(this, arguments);
+  },
+
+    // Get the value of an attribute.
+  get: function(attr) {
+    return this.attributes[attr];
+  },
+
+    // Get the HTML-escaped value of an attribute.
+  escape: function(attr) {
+    return _.escape(this.get(attr));
+  },
+
+    // Returns `true` if the attribute contains a value that is not null
+    // or undefined.
+  has: function(attr) {
+    return this.get(attr) != null;
+  },
+
+    // Set a hash of model attributes on the object, firing `"change"`. This is
+    // the core primitive operation of a model, updating the data and notifying
+    // anyone who needs to know about the change in state. The heart of the beast.
+  set: function(key, val, options) {
+    var attr, attrs, unset, changes, silent, changing, prev, current;
+    if (key == null) return this;
+
+    // Handle both `"key", value` and `{key: value}` -style arguments.
+    if (typeof key === 'object') {
+      attrs = key;
+      options = val;
+    } else {
+      (attrs = {})[key] = val;
+    }
+
+    options || (options = {});
+
+    // Run validation.
+    if (!this._validate(attrs, options)) return false;
+
+    // Extract attributes and options.
+    unset           = options.unset;
+    silent          = options.silent;
+    changes         = [];
+    changing        = this._changing;
+    this._changing  = true;
+
+    if (!changing) {
+      this._previousAttributes = _.clone(this.attributes);
+      this.changed = {};
+    }
+    current = this.attributes, prev = this._previousAttributes;
+
+    // Check for changes of `id`.
+    if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
+
+    // For each `set` attribute, update or delete the current value.
+    for (attr in attrs) {
+      val = attrs[attr];
+      if (!_.isEqual(current[attr], val)) changes.push(attr);
+      if (!_.isEqual(prev[attr], val)) {
+        this.changed[attr] = val;
+      } else {
+        delete this.changed[attr];
+      }
+      unset ? delete current[attr] : current[attr] = val;
+    }
+
+    // Trigger all relevant attribute changes.
+    if (!silent) {
+      if (changes.length) this._pending = options;
+      for (var i = 0, length = changes.length; i < length; i++) {
+        this.trigger('change:' + changes[i], this, current[changes[i]], options);
+      }
+    }
+
+    // You might be wondering why there's a `while` loop here. Changes can
+    // be recursively nested within `"change"` events.
+    if (changing) return this;
+    if (!silent) {
+      while (this._pending) {
+        options = this._pending;
+        this._pending = false;
+        this.trigger('change', this, options);
+      }
+    }
+    this._pending = false;
+    this._changing = false;
+    return this;
+  },
+
+    // Remove an attribute from the model, firing `"change"`. `unset` is a noop
+    // if the attribute doesn't exist.
+  unset: function(attr, options) {
+    return this.set(attr, void 0, _.extend({}, options, {unset: true}));
+  },
+
+    // Clear all attributes on the model, firing `"change"`.
+  clear: function(options) {
+    var attrs = {};
+    for (var key in this.attributes) attrs[key] = void 0;
+    return this.set(attrs, _.extend({}, options, {unset: true}));
+  },
+
+    // Determine if the model has changed since the last `"change"` event.
+    // If you specify an attribute name, determine if that attribute has changed.
+  hasChanged: function(attr) {
+    if (attr == null) return !_.isEmpty(this.changed);
+    return _.has(this.changed, attr);
+  },
+
+    // Return an object containing all the attributes that have changed, or
+    // false if there are no changed attributes. Useful for determining what
+    // parts of a view need to be updated and/or what attributes need to be
+    // persisted to the server. Unset attributes will be set to undefined.
+    // You can also pass an attributes object to diff against the model,
+    // determining if there *would be* a change.
+  changedAttributes: function(diff) {
+    if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
+    var val, changed = false;
+    var old = this._changing ? this._previousAttributes : this.attributes;
+    for (var attr in diff) {
+      if (_.isEqual(old[attr], (val = diff[attr]))) continue;
+      (changed || (changed = {}))[attr] = val;
+    }
+    return changed;
+  },
+
+    // Get the previous value of an attribute, recorded at the time the last
+    // `"change"` event was fired.
+  previous: function(attr) {
+    if (attr == null || !this._previousAttributes) return null;
+    return this._previousAttributes[attr];
+  },
+
+    // Get all of the attributes of the model at the time of the previous
+    // `"change"` event.
+  previousAttributes: function() {
+    return _.clone(this._previousAttributes);
+  },
+
+    // Fetch the model from the server. If the server's representation of the
+    // model differs from its current attributes, they will be overridden,
+    // triggering a `"change"` event.
+  fetch: function(options) {
+    options = options ? _.clone(options) : {};
+    if (options.parse === void 0) options.parse = true;
+    var model = this;
+    var success = options.success;
+    options.success = function(resp) {
+      if (!model.set(model.parse(resp, options), options)) return false;
+      if (success) success(model, resp, options);
+      model.trigger('sync', model, resp, options);
+    };
+    wrapError(this, options);
+    return this.sync('read', this, options);
+  },
+
+    // Set a hash of model attributes, and sync the model to the server.
+    // If the server returns an attributes hash that differs, the model's
+    // state will be `set` again.
+  save: function(key, val, options) {
+    var attrs, method, xhr, attributes = this.attributes;
+
+    // Handle both `"key", value` and `{key: value}` -style arguments.
+    if (key == null || typeof key === 'object') {
+      attrs = key;
+      options = val;
+    } else {
+      (attrs = {})[key] = val;
+    }
+
+    options = _.extend({validate: true}, options);
+
+    // If we're not waiting and attributes exist, save acts as
+    // `set(attr).save(null, opts)` with validation. Otherwise, check if
+    // the model will be valid when the attributes, if any, are set.
+    if (attrs && !options.wait) {
+      if (!this.set(attrs, options)) return false;
+    } else {
+      if (!this._validate(attrs, options)) return false;
+    }
+
+    // Set temporary attributes if `{wait: true}`.
+    if (attrs && options.wait) {
+      this.attributes = _.extend({}, attributes, attrs);
+    }
+
+    // After a successful server-side save, the client is (optionally)
+    // updated with the server-side state.
+    if (options.parse === void 0) options.parse = true;
+    var model = this;
+    var success = options.success;
+    options.success = function(resp) {
+      // Ensure attributes are restored during synchronous saves.
+      model.attributes = attributes;
+      var serverAttrs = model.parse(resp, options);
+      if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
+      if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
+        return false;
+      }
+      if (success) success(model, resp, options);
+      model.trigger('sync', model, resp, options);
+    };
+    wrapError(this, options);
+
+    method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
+    if (method === 'patch' && !options.attrs) options.attrs = attrs;
+    xhr = this.sync(method, this, options);
+
+    // Restore attributes.
+    if (attrs && options.wait) this.attributes = attributes;
+
+    return xhr;
+  },
+
+    // Destroy this model on the server if it was already persisted.
+    // Optimistically removes the model from its collection, if it has one.
+    // If `wait: true` is passed, waits for the server to respond before removal.
+  destroy: function(options) {
+    options = options ? _.clone(options) : {};
+    var model = this;
+    var success = options.success;
+
+    var destroy = function() {
+      model.stopListening();
+      model.trigger('destroy', model, model.collection, options);
+    };
+
+    options.success = function(resp) {
+      if (options.wait || model.isNew()) destroy();
+      if (success) success(model, resp, options);
+      if (!model.isNew()) model.trigger('sync', model, resp, options);
+    };
+
+    if (this.isNew()) {
+      options.success();
+      return false;
+    }
+    wrapError(this, options);
+
+    var xhr = this.sync('delete', this, options);
+    if (!options.wait) destroy();
+    return xhr;
+  },
+
+    // Default URL for the model's representation on the server -- if you're
+    // using Backbone's restful methods, override this to change the endpoint
+    // that will be called.
+  url: function() {
+    var base =
+      _.result(this, 'urlRoot') ||
+      _.result(this.collection, 'url') ||
+      urlError();
+    if (this.isNew()) return base;
+    return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id);
+  },
+
+    // **parse** converts a response into the hash of attributes to be `set` on
+    // the model. The default implementation is just to pass the response along.
+  parse: function(resp, options) {
+    return resp;
+  },
+
+    // Create a new model with identical attributes to this one.
+  clone: function() {
+    return new this.constructor(this.attributes);
+  },
+
+    // A model is new if it has never been saved to the server, and lacks an id.
+  isNew: function() {
+    return !this.has(this.idAttribute);
+  },
+
+    // Check if the model is currently in a valid state.
+  isValid: function(options) {
+    return this._validate({}, _.extend(options || {}, { validate: true }));
+  },
+
+    // Run validation against the next complete set of model attributes,
+    // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
+  _validate: function(attrs, options) {
+    if (!options.validate || !this.validate) return true;
+    attrs = _.extend({}, this.attributes, attrs);
+    var error = this.validationError = this.validate(attrs, options) || null;
+    if (!error) return true;
+    this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
+    return false;
+  }
+
+});
+
+// Underscore methods that we want to implement on the Model.
+var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit', 'chain', 'isEmpty'];
+
+// Mix in each Underscore method as a proxy to `Model#attributes`.
+_.each(modelMethods, function(method) {
+  if (!_[method]) return;
+  Model.prototype[method] = function() {
+    var args = slice.call(arguments);
+    args.unshift(this.attributes);
+    return _[method].apply(_, args);
+  };
+});
+
+// setup inheritance
+Model.extend = extend;
+module.exports = Model;
+
+},{"backbone-events-standalone":8,"backbone-extend-standalone":9,"underscore":59}],7:[function(require,module,exports){
+/**
+ * Standalone extraction of Backbone.Events, no external dependency required.
+ * Degrades nicely when Backone/underscore are already available in the current
+ * global context.
+ *
+ * Note that docs suggest to use underscore's `_.extend()` method to add Events
+ * support to some given object. A `mixin()` method has been added to the Events
+ * prototype to avoid using underscore for that sole purpose:
+ *
+ *     var myEventEmitter = BackboneEvents.mixin({});
+ *
+ * Or for a function constructor:
+ *
+ *     function MyConstructor(){}
+ *     MyConstructor.prototype.foo = function(){}
+ *     BackboneEvents.mixin(MyConstructor.prototype);
+ *
+ * (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc.
+ * (c) 2013 Nicolas Perriault
+ */
+/* global exports:true, define, module */
+(function() {
+  var root = this,
+      breaker = {},
+      nativeForEach = Array.prototype.forEach,
+      hasOwnProperty = Object.prototype.hasOwnProperty,
+      slice = Array.prototype.slice,
+      idCounter = 0;
+
+  // Returns a partial implementation matching the minimal API subset required
+  // by Backbone.Events
+  function miniscore() {
+    return {
+      keys: Object.keys || function (obj) {
+        if (typeof obj !== "object" && typeof obj !== "function" || obj === null) {
+          throw new TypeError("keys() called on a non-object");
+        }
+        var key, keys = [];
+        for (key in obj) {
+          if (obj.hasOwnProperty(key)) {
+            keys[keys.length] = key;
+          }
+        }
+        return keys;
+      },
+
+      uniqueId: function(prefix) {
+        var id = ++idCounter + '';
+        return prefix ? prefix + id : id;
+      },
+
+      has: function(obj, key) {
+        return hasOwnProperty.call(obj, key);
+      },
+
+      each: function(obj, iterator, context) {
+        if (obj == null) return;
+        if (nativeForEach && obj.forEach === nativeForEach) {
+          obj.forEach(iterator, context);
+        } else if (obj.length === +obj.length) {
+          for (var i = 0, l = obj.length; i < l; i++) {
+            if (iterator.call(context, obj[i], i, obj) === breaker) return;
+          }
+        } else {
+          for (var key in obj) {
+            if (this.has(obj, key)) {
+              if (iterator.call(context, obj[key], key, obj) === breaker) return;
+            }
+          }
+        }
+      },
+
+      once: function(func) {
+        var ran = false, memo;
+        return function() {
+          if (ran) return memo;
+          ran = true;
+          memo = func.apply(this, arguments);
+          func = null;
+          return memo;
+        };
+      }
+    };
+  }
+
+  var _ = miniscore(), Events;
+
+  // Backbone.Events
+  // ---------------
+
+  // A module that can be mixed in to *any object* in order to provide it with
+  // custom events. You may bind with `on` or remove with `off` callback
+  // functions to an event; `trigger`-ing an event fires all callbacks in
+  // succession.
+  //
+  //     var object = {};
+  //     _.extend(object, Backbone.Events);
+  //     object.on('expand', function(){ alert('expanded'); });
+  //     object.trigger('expand');
+  //
+  Events = {
+
+    // Bind an event to a `callback` function. Passing `"all"` will bind
+    // the callback to all events fired.
+    on: function(name, callback, context) {
+      if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
+      this._events || (this._events = {});
+      var events = this._events[name] || (this._events[name] = []);
+      events.push({callback: callback, context: context, ctx: context || this});
+      return this;
+    },
+
+    // Bind an event to only be triggered a single time. After the first time
+    // the callback is invoked, it will be removed.
+    once: function(name, callback, context) {
+      if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
+      var self = this;
+      var once = _.once(function() {
+        self.off(name, once);
+        callback.apply(this, arguments);
+      });
+      once._callback = callback;
+      return this.on(name, once, context);
+    },
+
+    // Remove one or many callbacks. If `context` is null, removes all
+    // callbacks with that function. If `callback` is null, removes all
+    // callbacks for the event. If `name` is null, removes all bound
+    // callbacks for all events.
+    off: function(name, callback, context) {
+      var retain, ev, events, names, i, l, j, k;
+      if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
+      if (!name && !callback && !context) {
+        this._events = {};
+        return this;
+      }
+
+      names = name ? [name] : _.keys(this._events);
+      for (i = 0, l = names.length; i < l; i++) {
+        name = names[i];
+        if (events = this._events[name]) {
+          this._events[name] = retain = [];
+          if (callback || context) {
+            for (j = 0, k = events.length; j < k; j++) {
+              ev = events[j];
+              if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
+                  (context && context !== ev.context)) {
+                retain.push(ev);
+              }
+            }
+          }
+          if (!retain.length) delete this._events[name];
+        }
+      }
+
+      return this;
+    },
+
+    // Trigger one or many events, firing all bound callbacks. Callbacks are
+    // passed the same arguments as `trigger` is, apart from the event name
+    // (unless you're listening on `"all"`, which will cause your callback to
+    // receive the true name of the event as the first argument).
+    trigger: function(name) {
+      if (!this._events) return this;
+      var args = slice.call(arguments, 1);
+      if (!eventsApi(this, 'trigger', name, args)) return this;
+      var events = this._events[name];
+      var allEvents = this._events.all;
+      if (events) triggerEvents(events, args);
+      if (allEvents) triggerEvents(allEvents, arguments);
+      return this;
+    },
+
+    // Tell this object to stop listening to either specific events ... or
+    // to every object it's currently listening to.
+    stopListening: function(obj, name, callback) {
+      var listeners = this._listeners;
+      if (!listeners) return this;
+      var deleteListener = !name && !callback;
+      if (typeof name === 'object') callback = this;
+      if (obj) (listeners = {})[obj._listenerId] = obj;
+      for (var id in listeners) {
+        listeners[id].off(name, callback, this);
+        if (deleteListener) delete this._listeners[id];
+      }
+      return this;
+    }
+
+  };
+
+  // Regular expression used to split event strings.
+  var eventSplitter = /\s+/;
+
+  // Implement fancy features of the Events API such as multiple event
+  // names `"change blur"` and jQuery-style event maps `{change: action}`
+  // in terms of the existing API.
+  var eventsApi = function(obj, action, name, rest) {
+    if (!name) return true;
+
+    // Handle event maps.
+    if (typeof name === 'object') {
+      for (var key in name) {
+        obj[action].apply(obj, [key, name[key]].concat(rest));
+      }
+      return false;
+    }
+
+    // Handle space separated event names.
+    if (eventSplitter.test(name)) {
+      var names = name.split(eventSplitter);
+      for (var i = 0, l = names.length; i < l; i++) {
+        obj[action].apply(obj, [names[i]].concat(rest));
+      }
+      return false;
+    }
+
+    return true;
+  };
+
+  // A difficult-to-believe, but optimized internal dispatch function for
+  // triggering events. Tries to keep the usual cases speedy (most internal
+  // Backbone events have 3 arguments).
+  var triggerEvents = function(events, args) {
+    var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
+    switch (args.length) {
+      case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
+      case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
+      case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
+      case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
+      default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
+    }
+  };
+
+  var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
+
+  // Inversion-of-control versions of `on` and `once`. Tell *this* object to
+  // listen to an event in another object ... keeping track of what it's
+  // listening to.
+  _.each(listenMethods, function(implementation, method) {
+    Events[method] = function(obj, name, callback) {
+      var listeners = this._listeners || (this._listeners = {});
+      var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
+      listeners[id] = obj;
+      if (typeof name === 'object') callback = this;
+      obj[implementation](name, callback, this);
+      return this;
+    };
+  });
+
+  // Aliases for backwards compatibility.
+  Events.bind   = Events.on;
+  Events.unbind = Events.off;
+
+  // Mixin utility
+  Events.mixin = function(proto) {
+    var exports = ['on', 'once', 'off', 'trigger', 'stopListening', 'listenTo',
+                   'listenToOnce', 'bind', 'unbind'];
+    _.each(exports, function(name) {
+      proto[name] = this[name];
+    }, this);
+    return proto;
+  };
+
+  // Export Events as BackboneEvents depending on current context
+  if (typeof define === "function") {
+    define(function() {
+      return Events;
+    });
+  } else if (typeof exports !== 'undefined') {
+    if (typeof module !== 'undefined' && module.exports) {
+      exports = module.exports = Events;
+    }
+    exports.BackboneEvents = Events;
+  } else {
+    root.BackboneEvents = Events;
+  }
+})(this);
+
+},{}],8:[function(require,module,exports){
+module.exports = require('./backbone-events-standalone');
+
+},{"./backbone-events-standalone":7}],9:[function(require,module,exports){
+(function (definition) {
+  if (typeof exports === "object") {
+    module.exports = definition();
+  }
+  else if (typeof define === 'function' && define.amd) {
+    define(definition);
+  }
+  else {
+    window.BackboneExtend = definition();
+  }
+})(function () {
+  "use strict";
+  
+  // mini-underscore
+  var _ = {
+    has: function (obj, key) {
+      return Object.prototype.hasOwnProperty.call(obj, key);
+    },
+  
+    extend: function(obj) {
+      for (var i=1; i<arguments.length; ++i) {
+        var source = arguments[i];
+        if (source) {
+          for (var prop in source) {
+            obj[prop] = source[prop];
+          }
+        }
+      }
+      return obj;
+    }
+  };
+
+  /// Following code is pasted from Backbone.js ///
+
+  // Helper function to correctly set up the prototype chain, for subclasses.
+  // Similar to `goog.inherits`, but uses a hash of prototype properties and
+  // class properties to be extended.
+  var extend = function(protoProps, staticProps) {
+    var parent = this;
+    var child;
+
+    // The constructor function for the new subclass is either defined by you
+    // (the "constructor" property in your `extend` definition), or defaulted
+    // by us to simply call the parent's constructor.
+    if (protoProps && _.has(protoProps, 'constructor')) {
+      child = protoProps.constructor;
+    } else {
+      child = function(){ return parent.apply(this, arguments); };
+    }
+
+    // Add static properties to the constructor function, if supplied.
+    _.extend(child, parent, staticProps);
+
+    // Set the prototype chain to inherit from `parent`, without calling
+    // `parent`'s constructor function.
+    var Surrogate = function(){ this.constructor = child; };
+    Surrogate.prototype = parent.prototype;
+    child.prototype = new Surrogate();
+
+    // Add prototype properties (instance properties) to the subclass,
+    // if supplied.
+    if (protoProps) _.extend(child.prototype, protoProps);
+
+    // Set a convenience property in case the parent's prototype is needed
+    // later.
+    child.__super__ = parent.prototype;
+
+    return child;
+  };
+
+  // Expose the extend function
+  return extend;
+});
+
+},{}],10:[function(require,module,exports){
+// this is the extracted view model from backbone
+// note that we inject jbone as jquery replacment
+// (and underscore directly)
+//
+// Views are almost more convention than they are actual code.
+//  MVC pattern
+// Backbone.View
+// -------------
+
+var _ = require("underscore");
+var Events = require("backbone-events-standalone");
+var extend = require("backbone-extend-standalone");
+var $ = require('jbone');
+
+// Backbone Views are almost more convention than they are actual code. A View
+// is simply a JavaScript object that represents a logical chunk of UI in the
+// DOM. This might be a single item, an entire list, a sidebar or panel, or
+// even the surrounding frame which wraps your whole app. Defining a chunk of
+// UI as a **View** allows you to define your DOM events declaratively, without
+// having to worry about render order ... and makes it easy for the view to
+// react to specific changes in the state of your models.
+
+// Creating a Backbone.View creates its initial element outside of the DOM,
+// if an existing element is not provided...
+var View =  function(options) {
+  this.cid = _.uniqueId('view');
+  options || (options = {});
+  _.extend(this, _.pick(options, viewOptions));
+  this._ensureElement();
+  this.initialize.apply(this, arguments);
+};
+
+// Cached regex to split keys for `delegate`.
+var delegateEventSplitter = /^(\S+)\s*(.*)$/;
+
+// List of view options to be merged as properties.
+var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
+
+// Set up all inheritable **Backbone.View** properties and methods.
+_.extend(View.prototype, Events, {
+
+  // The default `tagName` of a View's element is `"div"`.
+  tagName: 'div',
+
+  // jQuery delegate for element lookup, scoped to DOM elements within the
+  // current view. This should be preferred to global lookups where possible.
+  $: function(selector) {
+    return this.$el.find(selector);
+  },
+
+    // Initialize is an empty function by default. Override it with your own
+    // initialization logic.
+  initialize: function(){},
+
+    // **render** is the core function that your view should override, in order
+    // to populate its element (`this.el`), with the appropriate HTML. The
+    // convention is for **render** to always return `this`.
+  render: function() {
+    return this;
+  },
+
+    // Remove this view by taking the element out of the DOM, and removing any
+    // applicable Backbone.Events listeners.
+  remove: function() {
+    this._removeElement();
+    this.stopListening();
+    return this;
+  },
+
+    // Remove this view's element from the document and all event listeners
+    // attached to it. Exposed for subclasses using an alternative DOM
+    // manipulation API.
+  _removeElement: function() {
+    this.$el.remove();
+  },
+
+    // Change the view's element (`this.el` property) and re-delegate the
+    // view's events on the new element.
+  setElement: function(element) {
+    this.undelegateEvents();
+    this._setElement(element);
+    this.delegateEvents();
+    return this;
+  },
+
+    // Creates the `this.el` and `this.$el` references for this view using the
+    // given `el`. `el` can be a CSS selector or an HTML string, a jQuery
+    // context or an element. Subclasses can override this to utilize an
+    // alternative DOM manipulation API and are only required to set the
+    // `this.el` property.
+  _setElement: function(el) {
+    this.$el = el instanceof $ ? el : $(el);
+    this.el = this.$el[0];
+  },
+
+    // Set callbacks, where `this.events` is a hash of
+    //
+    // *{"event selector": "callback"}*
+    //
+    //     {
+    //       'mousedown .title':  'edit',
+    //       'click .button':     'save',
+    //       'click .open':       function(e) { ... }
+    //     }
+    //
+    // pairs. Callbacks will be bound to the view, with `this` set properly.
+    // Uses event delegation for efficiency.
+    // Omitting the selector binds the event to `this.el`.
+  delegateEvents: function(events) {
+    if (!(events || (events = _.result(this, 'events')))) return this;
+    this.undelegateEvents();
+    for (var key in events) {
+      var method = events[key];
+      if (!_.isFunction(method)) method = this[events[key]];
+      if (!method) continue;
+      var match = key.match(delegateEventSplitter);
+      this.delegate(match[1], match[2], _.bind(method, this));
+    }
+    return this;
+  },
+
+    // Add a single event listener to the view's element (or a child element
+    // using `selector`). This only works for delegate-able events: not `focus`,
+    // `blur`, and not `change`, `submit`, and `reset` in Internet Explorer.
+  delegate: function(eventName, selector, listener) {
+    this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
+  },
+
+    // Clears all callbacks previously bound to the view by `delegateEvents`.
+    // You usually don't need to use this, but may wish to if you have multiple
+    // Backbone views attached to the same DOM element.
+  undelegateEvents: function() {
+    if (this.$el) this.$el.off('.delegateEvents' + this.cid);
+    return this;
+  },
+
+    // A finer-grained `undelegateEvents` for removing a single delegated event.
+    // `selector` and `listener` are both optional.
+  undelegate: function(eventName, selector, listener) {
+    this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener);
+  },
+
+    // Produces a DOM element to be assigned to your view. Exposed for
+    // subclasses using an alternative DOM manipulation API.
+  _createElement: function(tagName) {
+    return document.createElement(tagName);
+  },
+
+    // Ensure that the View has a DOM element to render into.
+    // If `this.el` is a string, pass it through `$()`, take the first
+    // matching element, and re-assign it to `el`. Otherwise, create
+    // an element from the `id`, `className` and `tagName` properties.
+  _ensureElement: function() {
+    if (!this.el) {
+      var attrs = _.extend({}, _.result(this, 'attributes'));
+      if (this.id) attrs.id = _.result(this, 'id');
+      if (this.className) attrs['class'] = _.result(this, 'className');
+      this.setElement(this._createElement(_.result(this, 'tagName')));
+      this._setAttributes(attrs);
+    } else {
+      this.setElement(_.result(this, 'el'));
+    }
+  },
+
+    // Set attributes from a hash on this view's element.  Exposed for
+    // subclasses using an alternative DOM manipulation API.
+  _setAttributes: function(attributes) {
+    this.$el.attr(attributes);
+  }
+
+});
+
+// setup inheritance
+View.extend = extend;
+module.exports = View;
+
+},{"backbone-events-standalone":12,"backbone-extend-standalone":13,"jbone":50,"underscore":59}],11:[function(require,module,exports){
+module.exports=require(7)
+},{"/home/travis/build/greenify/biojs-vis-msa/node_modules/backbone-thin/node_modules/backbone-events-standalone/backbone-events-standalone.js":7}],12:[function(require,module,exports){
+module.exports=require(8)
+},{"./backbone-events-standalone":11,"/home/travis/build/greenify/biojs-vis-msa/node_modules/backbone-thin/node_modules/backbone-events-standalone/index.js":8}],13:[function(require,module,exports){
+module.exports=require(9)
+},{"/home/travis/build/greenify/biojs-vis-msa/node_modules/backbone-thin/node_modules/backbone-extend-standalone/backbone-extend-standalone.js":9}],14:[function(require,module,exports){
+var events = require("backbone-events-standalone");
+
+events.onAll = function(callback,context){
+  this.on("all", callback,context);
+  return this;
+};
+
+// Mixin utility
+events.oldMixin = events.mixin;
+events.mixin = function(proto) {
+  events.oldMixin(proto);
+  // add custom onAll
+  var exports = ['onAll'];
+  for(var i=0; i < exports.length;i++){
+    var name = exports[i];
+    proto[name] = this[name];
+  }
+  return proto;
+};
+
+module.exports = events;
+
+},{"backbone-events-standalone":16}],15:[function(require,module,exports){
+module.exports=require(7)
+},{"/home/travis/build/greenify/biojs-vis-msa/node_modules/backbone-thin/node_modules/backbone-events-standalone/backbone-events-standalone.js":7}],16:[function(require,module,exports){
+module.exports=require(8)
+},{"./backbone-events-standalone":15,"/home/travis/build/greenify/biojs-vis-msa/node_modules/backbone-thin/node_modules/backbone-events-standalone/index.js":8}],17:[function(require,module,exports){
+// Generated by CoffeeScript 1.8.0
+var GenericReader, xhr;
+
+xhr = require('nets');
+
+module.exports = GenericReader = (function() {
+  function GenericReader() {}
+
+  GenericReader.read = function(url, callback) {
+    var onret;
+    onret = (function(_this) {
+      return function(err, response, text) {
+        return _this._onRetrieval(text, callback);
+      };
+    })(this);
+    return xhr(url, onret);
+  };
+
+  GenericReader._onRetrieval = function(text, callback) {
+    var rText;
+    rText = this.parse(text);
+    return callback(rText);
+  };
+
+  return GenericReader;
+
+})();
+
+},{"nets":undefined}],18:[function(require,module,exports){
+// Generated by CoffeeScript 1.8.0
+var Seq;
+
+module.exports = Seq = (function() {
+  function Seq(seq, name, id) {
+    var meta;
+    this.seq = seq;
+    this.name = name;
+    this.id = id;
+    meta = {};
+  }
+
+  return Seq;
+
+})();
+
+},{}],19:[function(require,module,exports){
+// Generated by CoffeeScript 1.8.0
+var strings;
+
+strings = {
+  contains: function(text, search) {
+    return ''.indexOf.call(text, search, 0) !== -1;
+  }
+};
+
+module.exports = strings;
+
+},{}],20:[function(require,module,exports){
+module.exports=require(17)
+},{"/home/travis/build/greenify/biojs-vis-msa/node_modules/biojs-io-clustal/lib/generic_reader.js":17,"nets":undefined}],21:[function(require,module,exports){
+// Generated by CoffeeScript 1.8.0
+var Fasta, GenericReader, Seq, Str,
+  __hasProp = {}.hasOwnProperty,
+  __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+Str = require("./strings");
+
+GenericReader = require("./generic_reader");
+
+Seq = require("biojs-model").seq;
+
+module.exports = Fasta = (function(_super) {
+  __extends(Fasta, _super);
+
+  function Fasta() {
+    return Fasta.__super__.constructor.apply(this, arguments);
+  }
+
+  Fasta.parse = function(text) {
+    var currentSeq, database, databaseID, identifiers, k, label, line, seqs, _i, _len;
+    seqs = [];
+    if (Object.prototype.toString.call(text) !== '[object Array]') {
+      text = text.split("\n");
+    }
+    for (_i = 0, _len = text.length; _i < _len; _i++) {
+      line = text[_i];
+      if (line[0] === ">" || line[0] === ";") {
+        label = line.slice(1);
+        currentSeq = new Seq("", label, seqs.length);
+        seqs.push(currentSeq);
+        if (Str.contains("|", line)) {
+          identifiers = label.split("|");
+          k = 1;
+          while (k < identifiers.length) {
+            database = identifiers[k];
+            databaseID = identifiers[k + 1];
+            currentSeq.meta[database] = databaseID;
+            k += 2;
+          }
+          currentSeq.name = identifiers[identifiers.length - 1];
+        }
+      } else {
+        currentSeq.seq += line;
+      }
+    }
+    return seqs;
+  };
+
+  return Fasta;
+
+})(GenericReader);
+
+},{"./generic_reader":20,"./strings":22,"biojs-model":25}],22:[function(require,module,exports){
+module.exports=require(19)
+},{"/home/travis/build/greenify/biojs-vis-msa/node_modules/biojs-io-clustal/lib/strings.js":19}],23:[function(require,module,exports){
+// Generated by CoffeeScript 1.8.0
+var Utils;
+
+Utils = {};
+
+Utils.splitNChars = function(txt, num) {
+  var i, result, _i, _ref;
+  result = [];
+  for (i = _i = 0, _ref = txt.length - 1; num > 0 ? _i <= _ref : _i >= _ref; i = _i += num) {
+    result.push(txt.substr(i, num));
+  }
+  return result;
+};
+
+module.exports = Utils;
+
+},{}],24:[function(require,module,exports){
+// Generated by CoffeeScript 1.8.0
+var FastaExporter, Utils;
+
+Utils = require("./utils");
+
+module.exports = FastaExporter = (function() {
+  function FastaExporter() {}
+
+  FastaExporter["export"] = function(seqs, access) {
+    var seq, text, _i, _len;
+    text = "";
+    for (_i = 0, _len = seqs.length; _i < _len; _i++) {
+      seq = seqs[_i];
+      if (access != null) {
+        seq = access(seq);
+      }
+      text += ">" + seq.name + "\n";
+      text += (Utils.splitNChars(seq.seq, 80)).join("\n");
+      text += "\n";
+    }
+    return text;
+  };
+
+  return FastaExporter;
+
+})();
+
+},{"./utils":23}],25:[function(require,module,exports){
+module.exports.seq = require("./seq");
+
+},{"./seq":26}],26:[function(require,module,exports){
+module.exports = function(seq, name, id) {
+    this.seq = seq;
+    this.name = name;
+    this.id = id;
+    this.meta = {};
+};
+
+},{}],27:[function(require,module,exports){
+module.exports=require(25)
+},{"./seq":28,"/home/travis/build/greenify/biojs-vis-msa/node_modules/biojs-io-fasta/node_modules/biojs-model/src/index.js":25}],28:[function(require,module,exports){
+module.exports=require(26)
+},{"/home/travis/build/greenify/biojs-vis-msa/node_modules/biojs-io-fasta/node_modules/biojs-model/src/seq.js":26}],29:[function(require,module,exports){
+module.exports = require('./src/index.js')
+
+},{"./src/index.js":36}],30:[function(require,module,exports){
+module.exports = {
+  A: "#00a35c",
+  R: "#00fc03",
+  N: "#00eb14",
+  D: "#00eb14",
+  C: "#0000ff",
+  Q: "#00f10e",
+  E: "#00f10e",
+  G: "#009d62",
+  H: "#00d52a",
+  I: "#0054ab",
+  L: "#007b84",
+  K: "#00ff00",
+  M: "#009768",
+  F: "#008778",
+  P: "#00e01f",
+  S: "#00d52a",
+  T: "#00db24",
+  W: "#00a857",
+  Y: "#00e619",
+  V: "#005fa0",
+  B: "#00eb14",
+  X: "#00b649",
+  Z: "#00f10e"
+};
+
+},{}],31:[function(require,module,exports){
+module.exports = {
+  A: "#BBBBBB",
+  B: "grey",
+  C: "yellow",
+  D: "red",
+  E: "red",
+  F: "magenta",
+  G: "brown",
+  H: "#00FFFF",
+  I: "#BBBBBB",
+  J: "#fff",
+  K: "#00FFFF",
+  L: "#BBBBBB",
+  M: "#BBBBBB",
+  N: "green",
+  O: "#fff",
+  P: "brown",
+  Q: "green",
+  R: "#00FFFF",
+  S: "green",
+  T: "green",
+  U: "#fff",
+  V: "#BBBBBB",
+  W: "magenta",
+  X: "grey",
+  Y: "magenta",
+  Z: "grey",
+  Gap: "grey"
+};
+
+},{}],32:[function(require,module,exports){
+module.exports = {
+  A: "orange",
+  B: "#fff",
+  C: "green",
+  D: "red",
+  E: "red",
+  F: "blue",
+  G: "orange",
+  H: "red",
+  I: "green",
+  J: "#fff",
+  K: "red",
+  L: "green",
+  M: "green",
+  N: "#fff",
+  O: "#fff",
+  P: "orange",
+  Q: "#fff",
+  R: "red",
+  S: "orange",
+  T: "orange",
+  U: "#fff",
+  V: "green",
+  W: "blue",
+  X: "#fff",
+  Y: "blue",
+  Z: "#fff",
+  Gap: "#fff"
+};
+
+},{}],33:[function(require,module,exports){
+module.exports = {
+  A: "#80a0f0",
+  R: "#f01505",
+  N: "#00ff00",
+  D: "#c048c0",
+  C: "#f08080",
+  Q: "#00ff00",
+  E: "#c048c0",
+  G: "#f09048",
+  H: "#15a4a4",
+  I: "#80a0f0",
+  L: "#80a0f0",
+  K: "#f01505",
+  M: "#80a0f0",
+  F: "#80a0f0",
+  P: "#ffff00",
+  S: "#00ff00",
+  T: "#00ff00",
+  W: "#80a0f0",
+  Y: "#15a4a4",
+  V: "#80a0f0",
+  B: "#fff",
+  X: "#fff",
+  Z: "#fff"
+};
+
+},{}],34:[function(require,module,exports){
+module.exports = {
+  A: "#e718e7",
+  R: "#6f906f",
+  N: "#1be41b",
+  D: "#778877",
+  C: "#23dc23",
+  Q: "#926d92",
+  E: "#ff00ff",
+  G: "#00ff00",
+  H: "#758a75",
+  I: "#8a758a",
+  L: "#ae51ae",
+  K: "#a05fa0",
+  M: "#ef10ef",
+  F: "#986798",
+  P: "#00ff00",
+  S: "#36c936",
+  T: "#47b847",
+  W: "#8a758a",
+  Y: "#21de21",
+  V: "#857a85",
+  B: "#49b649",
+  X: "#758a75",
+  Z: "#c936c9"
+};
+
+},{}],35:[function(require,module,exports){
+module.exports = {
+  A: "#ad0052",
+  B: "#0c00f3",
+  C: "#c2003d",
+  D: "#0c00f3",
+  E: "#0c00f3",
+  F: "#cb0034",
+  G: "#6a0095",
+  H: "#1500ea",
+  I: "#ff0000",
+  J: "#fff",
+  K: "#0000ff",
+  L: "#ea0015",
+  M: "#b0004f",
+  N: "#0c00f3",
+  O: "#fff",
+  P: "#4600b9",
+  Q: "#0c00f3",
+  R: "#0000ff",
+  S: "#5e00a1",
+  T: "#61009e",
+  U: "#fff",
+  V: "#f60009",
+  W: "#5b00a4",
+  X: "#680097",
+  Y: "#4f00b0",
+  Z: "#0c00f3"
+};
+
+},{}],36:[function(require,module,exports){
+module.exports.selector = require("./selector");
+
+// basics
+module.exports.taylor = require("./taylor");
+module.exports.zappo= require("./zappo");
+module.exports.hydro= require("./hydrophobicity");
+
+module.exports.clustal = require("./clustal");
+module.exports.clustal2 = require("./clustal2");
+
+module.exports.curied = require("./buried");
+module.exports.cinema = require("./cinema");
+module.exports.nucleotide  = require("./nucleotide");
+module.exports.helix  = require("./helix");
+module.exports.lesk  = require("./lesk");
+module.exports.mae = require("./mae");
+module.exports.purine = require("./purine");
+module.exports.strand = require("./strand");
+module.exports.turn = require("./turn");
+
+},{"./buried":30,"./cinema":31,"./clustal":32,"./clustal2":33,"./helix":34,"./hydrophobicity":35,"./lesk":37,"./mae":38,"./nucleotide":39,"./purine":40,"./selector":41,"./strand":42,"./taylor":43,"./turn":44,"./zappo":45}],37:[function(require,module,exports){
+module.exports = {
+  A: " orange",
+  B: " #fff",
+  C: " green",
+  D: " red",
+  E: " red",
+  F: " green",
+  G: " orange",
+  H: " magenta",
+  I: " green",
+  J: " #fff",
+  K: " red",
+  L: " green",
+  M: " green",
+  N: " magenta",
+  O: " #fff",
+  P: " green",
+  Q: " magenta",
+  R: " red",
+  S: " orange",
+  T: " orange",
+  U: " #fff",
+  V: " green",
+  W: " green",
+  X: " #fff",
+  Y: " green",
+  Z: " #fff",
+  Gap: " #fff"
+};
+
+},{}],38:[function(require,module,exports){
+module.exports = {
+  A: " #77dd88",
+  B: " #fff",
+  C: " #99ee66",
+  D: " #55bb33",
+  E: " #55bb33",
+  F: " #9999ff",
+  G: " #77dd88",
+  H: " #5555ff",
+  I: " #66bbff",
+  J: " #fff",
+  K: " #ffcc77",
+  L: " #66bbff",
+  M: " #66bbff",
+  N: " #55bb33",
+  O: " #fff",
+  P: " #eeaaaa",
+  Q: " #55bb33",
+  R: " #ffcc77",
+  S: " #ff4455",
+  T: " #ff4455",
+  U: " #fff",
+  V: " #66bbff",
+  W: " #9999ff",
+  X: " #fff",
+  Y: " #9999ff",
+  Z: " #fff",
+  Gap: " #fff"
+};
+
+},{}],39:[function(require,module,exports){
+module.exports = {
+  A: " #64F73F",
+  C: " #FFB340",
+  G: " #EB413C",
+  T: " #3C88EE",
+  U: " #3C88EE"
+};
+
+},{}],40:[function(require,module,exports){
+module.exports = {
+  A: " #FF83FA",
+  C: " #40E0D0",
+  G: " #FF83FA",
+  R: " #FF83FA",
+  T: " #40E0D0",
+  U: " #40E0D0",
+  Y: " #40E0D0"
+};
+
+},{}],41:[function(require,module,exports){
+var Buried = require("./buried");
+var Cinema = require("./cinema");
+var Clustal = require("./clustal");
+var Clustal2 = require("./clustal2");
+var Helix = require("./helix");
+var Hydro = require("./hydrophobicity");
+var Lesk = require("./lesk");
+var Mae = require("./mae");
+var Nucleotide = require("./nucleotide");
+var Purine = require("./purine");
+var Strand = require("./strand");
+var Taylor = require("./taylor");
+var Turn = require("./turn");
+var Zappo = require("./zappo");
+
+module.exports = Colors = {
+  mapping: {
+    buried: Buried,
+    buried_index: Buried,
+    cinema: Cinema,
+    clustal2: Clustal2,
+    clustal: Clustal,
+    helix: Helix,
+    helix_propensity: Helix,
+    hydro: Hydro,
+    lesk: Lesk,
+    mae: Mae,
+    nucleotide: Nucleotide,
+    purine: Purine,
+    purine_pyrimidine: Purine,
+    strand: Strand,
+    strand_propensity: Strand,
+    taylor: Taylor,
+    turn: Turn,
+    turn_propensity: Turn,
+    zappo: Zappo,
+  },
+  getColor: function(scheme) {
+    var color = Colors.mapping[scheme];
+    if (color === undefined) {
+      color = {};
+    }
+    return color;
+  }
+};
+
+},{"./buried":30,"./cinema":31,"./clustal":32,"./clustal2":33,"./helix":34,"./hydrophobicity":35,"./lesk":37,"./mae":38,"./nucleotide":39,"./purine":40,"./strand":42,"./taylor":43,"./turn":44,"./zappo":45}],42:[function(require,module,exports){
+module.exports = {
+  A: "#5858a7",
+  R: "#6b6b94",
+  N: "#64649b",
+  D: "#2121de",
+  C: "#9d9d62",
+  Q: "#8c8c73",
+  E: "#0000ff",
+  G: "#4949b6",
+  H: "#60609f",
+  I: "#ecec13",
+  L: "#b2b24d",
+  K: "#4747b8",
+  M: "#82827d",
+  F: "#c2c23d",
+  P: "#2323dc",
+  S: "#4949b6",
+  T: "#9d9d62",
+  W: "#c0c03f",
+  Y: "#d3d32c",
+  V: "#ffff00",
+  B: "#4343bc",
+  X: "#797986",
+  Z: "#4747b8"
+};
+
+},{}],43:[function(require,module,exports){
+module.exports = {
+  A: "#ccff00",
+  R: "#0000ff",
+  N: "#cc00ff",
+  D: "#ff0000",
+  C: "#ffff00",
+  Q: "#ff00cc",
+  E: "#ff0066",
+  G: "#ff9900",
+  H: "#0066ff",
+  I: "#66ff00",
+  L: "#33ff00",
+  K: "#6600ff",
+  M: "#00ff00",
+  F: "#00ff66",
+  P: "#ffcc00",
+  S: "#ff3300",
+  T: "#ff6600",
+  W: "#00ccff",
+  Y: "#00ffcc",
+  V: "#99ff00",
+  B: "#fff",
+  X: "#fff",
+  Z: "#fff"
+};
+
+},{}],44:[function(require,module,exports){
+module.exports = {
+  A: "#2cd3d3",
+  R: "#708f8f",
+  N: "#ff0000",
+  D: "#e81717",
+  C: "#a85757",
+  Q: "#3fc0c0",
+  E: "#778888",
+  G: "#ff0000",
+  H: "#708f8f",
+  I: "#00ffff",
+  L: "#1ce3e3",
+  K: "#7e8181",
+  M: "#1ee1e1",
+  F: "#1ee1e1",
+  P: "#f60909",
+  S: "#e11e1e",
+  T: "#738c8c",
+  W: "#738c8c",
+  Y: "#9d6262",
+  V: "#07f8f8",
+  B: "#f30c0c",
+  X: "#7c8383",
+  Z: "#5ba4a4"
+};
+
+},{}],45:[function(require,module,exports){
+module.exports = {
+  A: "#ffafaf",
+  R: "#6464ff",
+  N: "#00ff00",
+  D: "#ff0000",
+  C: "#ffff00",
+  Q: "#00ff00",
+  E: "#ff0000",
+  G: "#ff00ff",
+  H: "#6464ff",
+  I: "#ffafaf",
+  L: "#ffafaf",
+  K: "#6464ff",
+  M: "#ffafaf",
+  F: "#ffc800",
+  P: "#ff00ff",
+  S: "#00ff00",
+  T: "#00ff00",
+  W: "#ffc800",
+  Y: "#ffc800",
+  V: "#ffafaf",
+  B: "#fff",
+  X: "#fff",
+  Z: "#fff"
+};
+
+},{}],46:[function(require,module,exports){
+/*
+ * JavaScript Canvas to Blob 2.0.5
+ * https://github.com/blueimp/JavaScript-Canvas-to-Blob
+ *
+ * Copyright 2012, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ *
+ * Based on stackoverflow user Stoive's code snippet:
+ * http://stackoverflow.com/q/4998908
+ */
+var CanvasPrototype = window.HTMLCanvasElement &&
+window.HTMLCanvasElement.prototype,
+  hasBlobConstructor = window.Blob && (function () {
+    try {
+      return Boolean(new Blob());
+    } catch (e) {
+      return false;
+    }
+  }()),
+  hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array &&
+  (function () {
+    try {
+      return new Blob([new Uint8Array(100)]).size === 100;
+    } catch (e) {
+      return false;
+    }
+  }()),
+  BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
+  window.MozBlobBuilder || window.MSBlobBuilder,
+  dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob &&
+  window.ArrayBuffer && window.Uint8Array && function (dataURI) {
+    var byteString,
+    arrayBuffer,
+    intArray,
+      i,
+      mimeString,
+        bb;
+    if (dataURI.split(',')[0].indexOf('base64') >= 0) {
+      // Convert base64 to raw binary data held in a string:
+      byteString = atob(dataURI.split(',')[1]);
+    } else {
+      // Convert base64/URLEncoded data component to raw binary data:
+      byteString = decodeURIComponent(dataURI.split(',')[1]);
+    }
+    // Write the bytes of the string to an ArrayBuffer:
+    arrayBuffer = new ArrayBuffer(byteString.length);
+    intArray = new Uint8Array(arrayBuffer);
+    for (i = 0; i < byteString.length; i += 1) {
+      intArray[i] = byteString.charCodeAt(i);
+    }
+    // Separate out the mime component:
+    mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
+    // Write the ArrayBuffer (or ArrayBufferView) to a blob:
+    if (hasBlobConstructor) {
+      return new Blob(
+          [hasArrayBufferViewSupport ? intArray : arrayBuffer],
+          {type: mimeString}
+          );
+    }
+    bb = new BlobBuilder();
+    bb.append(arrayBuffer);
+    return bb.getBlob(mimeString);
+  };
+if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {
+  if (CanvasPrototype.mozGetAsFile) {
+    CanvasPrototype.toBlob = function (callback, type, quality) {
+      if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {
+        callback(dataURLtoBlob(this.toDataURL(type, quality)));
+      } else {
+        callback(this.mozGetAsFile('blob', type));
+      }
+    };
+  } else if (CanvasPrototype.toDataURL && dataURLtoBlob) {
+    CanvasPrototype.toBlob = function (callback, type, quality) {
+      callback(dataURLtoBlob(this.toDataURL(type, quality)));
+    };
+  }
+}
+
+module.exports = dataURLtoBlob;
+
+},{}],47:[function(require,module,exports){
+/* FileSaver.js
+ *  A saveAs() FileSaver implementation.
+ *  2014-05-27
+ *
+ *  By Eli Grey, http://eligrey.com
+ *  License: X11/MIT
+ *    See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
+ */
+
+/*global self */
+/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
+
+/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
+
+var saveAs = saveAs
+  // IE 10+ (native saveAs)
+  || (typeof navigator !== "undefined" &&
+      navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator))
+  // Everyone else
+  || (function(view) {
+       "use strict";
+       // IE <10 is explicitly unsupported
+       if (typeof navigator !== "undefined" &&
+           /MSIE [1-9]\./.test(navigator.userAgent)) {
+               return;
+       }
+       var
+                 doc = view.document
+                 // only get URL when necessary in case Blob.js hasn't overridden it yet
+               , get_URL = function() {
+                       return view.URL || view.webkitURL || view;
+               }
+               , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
+               , can_use_save_link = !view.externalHost && "download" in save_link
+               , click = function(node) {
+                       var event = doc.createEvent("MouseEvents");
+                       event.initMouseEvent(
+                               "click", true, false, view, 0, 0, 0, 0, 0
+                               , false, false, false, false, 0, null
+                       );
+                       node.dispatchEvent(event);
+               }
+               , webkit_req_fs = view.webkitRequestFileSystem
+               , req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
+               , throw_outside = function(ex) {
+                       (view.setImmediate || view.setTimeout)(function() {
+                               throw ex;
+                       }, 0);
+               }
+               , force_saveable_type = "application/octet-stream"
+               , fs_min_size = 0
+               , deletion_queue = []
+               , process_deletion_queue = function() {
+                       var i = deletion_queue.length;
+                       while (i--) {
+                               var file = deletion_queue[i];
+                               if (typeof file === "string") { // file is an object URL
+                                       get_URL().revokeObjectURL(file);
+                               } else { // file is a File
+                                       file.remove();
+                               }
+                       }
+                       deletion_queue.length = 0; // clear queue
+               }
+               , dispatch = function(filesaver, event_types, event) {
+                       event_types = [].concat(event_types);
+                       var i = event_types.length;
+                       while (i--) {
+                               var listener = filesaver["on" + event_types[i]];
+                               if (typeof listener === "function") {
+                                       try {
+                                               listener.call(filesaver, event || filesaver);
+                                       } catch (ex) {
+                                               throw_outside(ex);
+                                       }
+                               }
+                       }
+               }
+               , FileSaver = function(blob, name) {
+                       // First try a.download, then web filesystem, then object URLs
+                       var
+                                 filesaver = this
+                               , type = blob.type
+                               , blob_changed = false
+                               , object_url
+                               , target_view
+                               , get_object_url = function() {
+                                       var object_url = get_URL().createObjectURL(blob);
+                                       deletion_queue.push(object_url);
+                                       return object_url;
+                               }
+                               , dispatch_all = function() {
+                                       dispatch(filesaver, "writestart progress write writeend".split(" "));
+                               }
+                               // on any filesys errors revert to saving with object URLs
+                               , fs_error = function() {
+                                       // don't create more object URLs than needed
+                                       if (blob_changed || !object_url) {
+                                               object_url = get_object_url(blob);
+                                       }
+                                       if (target_view) {
+                                               target_view.location.href = object_url;
+                                       } else {
+                                               window.open(object_url, "_blank");
+                                       }
+                                       filesaver.readyState = filesaver.DONE;
+                                       dispatch_all();
+                               }
+                               , abortable = function(func) {
+                                       return function() {
+                                               if (filesaver.readyState !== filesaver.DONE) {
+                                                       return func.apply(this, arguments);
+                                               }
+                                       };
+                               }
+                               , create_if_not_found = {create: true, exclusive: false}
+                               , slice
+                       ;
+                       filesaver.readyState = filesaver.INIT;
+                       if (!name) {
+                               name = "download";
+                       }
+                       if (can_use_save_link) {
+                               object_url = get_object_url(blob);
+                               save_link.href = object_url;
+                               save_link.download = name;
+                               click(save_link);
+                               filesaver.readyState = filesaver.DONE;
+                               dispatch_all();
+                               return;
+                       }
+                       // Object and web filesystem URLs have a problem saving in Google Chrome when
+                       // viewed in a tab, so I force save with application/octet-stream
+                       // http://code.google.com/p/chromium/issues/detail?id=91158
+                       if (view.chrome && type && type !== force_saveable_type) {
+                               slice = blob.slice || blob.webkitSlice;
+                               blob = slice.call(blob, 0, blob.size, force_saveable_type);
+                               blob_changed = true;
+                       }
+                       // Since I can't be sure that the guessed media type will trigger a download
+                       // in WebKit, I append .download to the filename.
+                       // https://bugs.webkit.org/show_bug.cgi?id=65440
+                       if (webkit_req_fs && name !== "download") {
+                               name += ".download";
+                       }
+                       if (type === force_saveable_type || webkit_req_fs) {
+                               target_view = view;
+                       }
+                       if (!req_fs) {
+                               fs_error();
+                               return;
+                       }
+                       fs_min_size += blob.size;
+                       req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
+                               fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
+                                       var save = function() {
+                                               dir.getFile(name, create_if_not_found, abortable(function(file) {
+                                                       file.createWriter(abortable(function(writer) {
+                                                               writer.onwriteend = function(event) {
+                                                                       target_view.location.href = file.toURL();
+                                                                       deletion_queue.push(file);
+                                                                       filesaver.readyState = filesaver.DONE;
+                                                                       dispatch(filesaver, "writeend", event);
+                                                               };
+                                                               writer.onerror = function() {
+                                                                       var error = writer.error;
+                                                                       if (error.code !== error.ABORT_ERR) {
+                                                                               fs_error();
+                                                                       }
+                                                               };
+                                                               "writestart progress write abort".split(" ").forEach(function(event) {
+                                                                       writer["on" + event] = filesaver["on" + event];
+                                                               });
+                                                               writer.write(blob);
+                                                               filesaver.abort = function() {
+                                                                       writer.abort();
+                                                                       filesaver.readyState = filesaver.DONE;
+                                                               };
+                                                               filesaver.readyState = filesaver.WRITING;
+                                                       }), fs_error);
+                                               }), fs_error);
+                                       };
+                                       dir.getFile(name, {create: false}, abortable(function(file) {
+                                               // delete file if it already exists
+                                               file.remove();
+                                               save();
+                                       }), abortable(function(ex) {
+                                               if (ex.code === ex.NOT_FOUND_ERR) {
+                                                       save();
+                                               } else {
+                                                       fs_error();
+                                               }
+                                       }));
+                               }), fs_error);
+                       }), fs_error);
+               }
+               , FS_proto = FileSaver.prototype
+               , saveAs = function(blob, name) {
+                       return new FileSaver(blob, name);
+               }
+       ;
+       FS_proto.abort = function() {
+               var filesaver = this;
+               filesaver.readyState = filesaver.DONE;
+               dispatch(filesaver, "abort");
+       };
+       FS_proto.readyState = FS_proto.INIT = 0;
+       FS_proto.WRITING = 1;
+       FS_proto.DONE = 2;
+
+       FS_proto.error =
+       FS_proto.onwritestart =
+       FS_proto.onprogress =
+       FS_proto.onwrite =
+       FS_proto.onabort =
+       FS_proto.onerror =
+       FS_proto.onwriteend =
+               null;
+
+       view.addEventListener("unload", process_deletion_queue, false);
+       saveAs.unload = function() {
+               process_deletion_queue();
+               view.removeEventListener("unload", process_deletion_queue, false);
+       };
+       return saveAs;
+}(
+          typeof self !== "undefined" && self
+       || typeof window !== "undefined" && window
+       || this.content
+));
+// `self` is undefined in Firefox for Android content script context
+// while `this` is nsIContentFrameMessageManager
+// with an attribute `content` that corresponds to the window
+
+amdDefine = window.define;
+if( typeof amdDefine === "undefined" && (typeof window.almond !== "undefined" 
+    && "define" in window.almond )){
+  amdDefine = window.almond.define;
+}
+
+if (typeof module !== "undefined" && module !== null) {
+  module.exports = saveAs;
+} else if ((typeof amdDefine !== "undefined" && amdDefine !== null) && (amdDefine.amd != null)) {
+  amdDefine("saveAs",[], function() {
+    return saveAs;
+  });
+}
+
+},{}],48:[function(require,module,exports){
+module.exports = function (css, customDocument) {
+  var doc = customDocument || document;
+  if (doc.createStyleSheet) {
+    var sheet = doc.createStyleSheet()
+    sheet.cssText = css;
+    return sheet.ownerNode;
+  } else {
+    var head = doc.getElementsByTagName('head')[0],
+        style = doc.createElement('style');
+
+    style.type = 'text/css';
+
+    if (style.styleSheet) {
+      style.styleSheet.cssText = css;
+    } else {
+      style.appendChild(doc.createTextNode(css));
+    }
+
+    head.appendChild(style);
+    return style;
+  }
+};
+
+module.exports.byUrl = function(url) {
+  if (document.createStyleSheet) {
+    return document.createStyleSheet(url).ownerNode;
+  } else {
+    var head = document.getElementsByTagName('head')[0],
+        link = document.createElement('link');
+
+    link.rel = 'stylesheet';
+    link.href = url;
+
+    head.appendChild(link);
+    return link;
+  }
+};
+
+},{}],49:[function(require,module,exports){
+var Utils = {};
+
+
+/*
+Remove an element and provide a function that inserts it into its original position
+https://developers.google.com/speed/articles/javascript-dom
+@param element {Element} The element to be temporarily removed
+@return {Function} A function that inserts the element into its original position
+ */
+
+Utils.removeToInsertLater = function(element) {
+  var nextSibling, parentNode;
+  parentNode = element.parentNode;
+  nextSibling = element.nextSibling;
+  parentNode.removeChild(element);
+  return function() {
+    if (nextSibling) {
+      parentNode.insertBefore(element, nextSibling);
+    } else {
+      parentNode.appendChild(element);
+    }
+  };
+};
+
+
+/*
+fastest possible way to destroy all sub nodes (aka childs)
+http://jsperf.com/innerhtml-vs-removechild/15
+@param element {Element} The element for which all childs should be removed
+ */
+
+Utils.removeAllChilds = function(element) {
+  var count;
+  count = 0;
+  while (element.firstChild) {
+    count++;
+    element.removeChild(element.firstChild);
+  }
+};
+
+module.exports = Utils;
+
+},{}],50:[function(require,module,exports){
+/*!
+ * jBone v1.0.19 - 2014-10-12 - Library for DOM manipulation
+ *
+ * https://github.com/kupriyanenko/jbone
+ *
+ * Copyright 2014 Alexey Kupriyanenko
+ * Released under the MIT license.
+ */
+
+(function (win) {
+
+var
+// cache previous versions
+_$ = win.$,
+_jBone = win.jBone,
+
+// Quick match a standalone tag
+rquickSingleTag = /^<(\w+)\s*\/?>$/,
+
+// A simple way to check for HTML strings
+// Prioritize #id over <tag> to avoid XSS via location.hash
+rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+// Alias for function
+slice = [].slice,
+splice = [].splice,
+keys = Object.keys,
+
+// Alias for global variables
+doc = document,
+
+isString = function(el) {
+    return typeof el === "string";
+},
+isObject = function(el) {
+    return el instanceof Object;
+},
+isFunction = function(el) {
+    var getType = {};
+    return el && getType.toString.call(el) === "[object Function]";
+},
+isArray = function(el) {
+    return Array.isArray(el);
+},
+jBone = function(element, data) {
+    return new fn.init(element, data);
+},
+fn;
+
+// set previous values and return the instance upon calling the no-conflict mode
+jBone.noConflict = function() {
+    win.$ = _$;
+    win.jBone = _jBone;
+
+    return jBone;
+};
+
+fn = jBone.fn = jBone.prototype = {
+    init: function(element, data) {
+        var elements, tag, wraper, fragment;
+
+        if (!element) {
+            return this;
+        }
+        if (isString(element)) {
+            // Create single DOM element
+            if (tag = rquickSingleTag.exec(element)) {
+                this[0] = doc.createElement(tag[1]);
+                this.length = 1;
+
+                if (isObject(data)) {
+                    this.attr(data);
+                }
+
+                return this;
+            }
+            // Create DOM collection
+            if ((tag = rquickExpr.exec(element)) && tag[1]) {
+                fragment = doc.createDocumentFragment();
+                wraper = doc.createElement("div");
+                wraper.innerHTML = element;
+                while (wraper.lastChild) {
+                    fragment.appendChild(wraper.firstChild);
+                }
+                elements = slice.call(fragment.childNodes);
+
+                return jBone.merge(this, elements);
+            }
+            // Find DOM elements with querySelectorAll
+            if (jBone.isElement(data)) {
+                return jBone(data).find(element);
+            }
+
+            try {
+                elements = doc.querySelectorAll(element);
+
+                return jBone.merge(this, elements);
+            } catch (e) {
+                return this;
+            }
+        }
+        // Wrap DOMElement
+        if (element.nodeType) {
+            this[0] = element;
+            this.length = 1;
+
+            return this;
+        }
+        // Run function
+        if (isFunction(element)) {
+            return element();
+        }
+        // Return jBone element as is
+        if (element instanceof jBone) {
+            return element;
+        }
+
+        // Return element wrapped by jBone
+        return jBone.makeArray(element, this);
+    },
+
+    pop: [].pop,
+    push: [].push,
+    reverse: [].reverse,
+    shift: [].shift,
+    sort: [].sort,
+    splice: [].splice,
+    slice: [].slice,
+    indexOf: [].indexOf,
+    forEach: [].forEach,
+    unshift: [].unshift,
+    concat: [].concat,
+    join: [].join,
+    every: [].every,
+    some: [].some,
+    filter: [].filter,
+    map: [].map,
+    reduce: [].reduce,
+    reduceRight: [].reduceRight,
+    length: 0
+};
+
+fn.constructor = jBone;
+
+fn.init.prototype = fn;
+
+jBone.setId = function(el) {
+    var jid = el.jid;
+
+    if (el === win) {
+        jid = "window";
+    } else if (el.jid === undefined) {
+        el.jid = jid = ++jBone._cache.jid;
+    }
+
+    if (!jBone._cache.events[jid]) {
+        jBone._cache.events[jid] = {};
+    }
+};
+
+jBone.getData = function(el) {
+    el = el instanceof jBone ? el[0] : el;
+
+    var jid = el === win ? "window" : el.jid;
+
+    return {
+        jid: jid,
+        events: jBone._cache.events[jid]
+    };
+};
+
+jBone.isElement = function(el) {
+    return el && el instanceof jBone || el instanceof HTMLElement || isString(el);
+};
+
+jBone._cache = {
+    events: {},
+    jid: 0
+};
+
+function isArraylike(obj) {
+    var length = obj.length,
+        type = typeof obj;
+
+    if (isFunction(type) || obj === win) {
+        return false;
+    }
+
+    if (obj.nodeType === 1 && length) {
+        return true;
+    }
+
+    return isArray(type) || length === 0 ||
+        typeof length === "number" && length > 0 && (length - 1) in obj;
+}
+
+jBone.merge = function(first, second) {
+    var l = second.length,
+        i = first.length,
+        j = 0;
+
+    while (j < l) {
+        first[i++] = second[j++];
+    }
+
+    first.length = i;
+
+    return first;
+};
+
+jBone.contains = function(container, contained) {
+    var result;
+
+    container.reverse().some(function(el) {
+        if (el.contains(contained)) {
+            return result = el;
+        }
+    });
+
+    return result;
+};
+
+jBone.extend = function(target) {
+    var k, kl, i, tg;
+
+    splice.call(arguments, 1).forEach(function(object) {
+        if (!object) {
+            return;
+        }
+
+        k = keys(object);
+        kl = k.length;
+        i = 0;
+        tg = target; //caching target for perf improvement
+
+        for (; i < kl; i++) {
+            tg[k[i]] = object[k[i]];
+        }
+    });
+
+    return target;
+};
+
+jBone.makeArray = function(arr, results) {
+    var ret = results || [];
+
+    if (arr !== null) {
+        if (isArraylike(arr)) {
+            jBone.merge(ret, isString(arr) ? [arr] : arr);
+        } else {
+            ret.push(arr);
+        }
+    }
+
+    return ret;
+};
+
+function BoneEvent(e, data) {
+    var key, setter;
+
+    this.originalEvent = e;
+
+    setter = function(key, e) {
+        if (key === "preventDefault") {
+            this[key] = function() {
+                this.defaultPrevented = true;
+                return e[key]();
+            };
+        } else if (isFunction(e[key])) {
+            this[key] = function() {
+                return e[key]();
+            };
+        } else {
+            this[key] = e[key];
+        }
+    };
+
+    for (key in e) {
+        if (e[key] || typeof e[key] === "function") {
+            setter.call(this, key, e);
+        }
+    }
+
+    jBone.extend(this, data);
+}
+
+jBone.Event = function(event, data) {
+    var namespace, eventType;
+
+    if (event.type && !data) {
+        data = event;
+        event = event.type;
+    }
+
+    namespace = event.split(".").splice(1).join(".");
+    eventType = event.split(".")[0];
+
+    event = doc.createEvent("Event");
+    event.initEvent(eventType, true, true);
+
+    return jBone.extend(event, {
+        namespace: namespace,
+        isDefaultPrevented: function() {
+            return event.defaultPrevented;
+        }
+    }, data);
+};
+
+fn.on = function(event) {
+    var args = arguments,
+        length = this.length,
+        i = 0,
+        callback, target, namespace, fn, events, eventType, expectedTarget, addListener;
+
+    if (args.length === 2) {
+        callback = args[1];
+    } else {
+        target = args[1];
+        callback = args[2];
+    }
+
+    addListener = function(el) {
+        jBone.setId(el);
+        events = jBone.getData(el).events;
+        event.split(" ").forEach(function(event) {
+            eventType = event.split(".")[0];
+            namespace = event.split(".").splice(1).join(".");
+            events[eventType] = events[eventType] || [];
+
+            fn = function(e) {
+                if (e.namespace && e.namespace !== namespace) {
+                    return;
+                }
+
+                expectedTarget = null;
+                if (!target) {
+                    callback.call(el, e);
+                } else if (~jBone(el).find(target).indexOf(e.target) || (expectedTarget = jBone.contains(jBone(el).find(target), e.target))) {
+                    expectedTarget = expectedTarget || e.target;
+                    e = new BoneEvent(e, {
+                        currentTarget: expectedTarget
+                    });
+
+                    callback.call(expectedTarget, e);
+                }
+            };
+
+            events[eventType].push({
+                namespace: namespace,
+                fn: fn,
+                originfn: callback
+            });
+
+            el.addEventListener && el.addEventListener(eventType, fn, false);
+        });
+    };
+
+    for (; i < length; i++) {
+        addListener(this[i]);
+    }
+
+    return this;
+};
+
+fn.one = function(event) {
+    var args = arguments,
+        i = 0,
+        length = this.length,
+        callback, target, addListener;
+
+    if (args.length === 2) {
+        callback = args[1];
+    } else {
+        target = args[1], callback = args[2];
+    }
+
+    addListener = function(el) {
+        event.split(" ").forEach(function(event) {
+            var fn = function(e) {
+                jBone(el).off(event, fn);
+                callback.call(el, e);
+            };
+
+            if (!target) {
+                jBone(el).on(event, fn);
+            } else {
+                jBone(el).on(event, target, fn);
+            }
+        });
+    };
+
+    for (; i < length; i++) {
+        addListener(this[i]);
+    }
+
+    return this;
+};
+
+fn.trigger = function(event) {
+    var events = [],
+        i = 0,
+        length = this.length,
+        dispatchEvents;
+
+    if (!event) {
+        return this;
+    }
+
+    if (isString(event)) {
+        events = event.split(" ").map(function(event) {
+            return jBone.Event(event);
+        });
+    } else {
+        event = event instanceof Event ? event : jBone.Event(event);
+        events = [event];
+    }
+
+    dispatchEvents = function(el) {
+        events.forEach(function(event) {
+            if (!event.type) {
+                return;
+            }
+
+            el.dispatchEvent && el.dispatchEvent(event);
+        });
+    };
+
+    for (; i < length; i++) {
+        dispatchEvents(this[i]);
+    }
+
+    return this;
+};
+
+fn.off = function(event, fn) {
+    var i = 0,
+        length = this.length,
+        removeListener = function(events, eventType, index, el, e) {
+            var callback;
+
+            // get callback
+            if ((fn && e.originfn === fn) || !fn) {
+                callback = e.fn;
+            }
+
+            if (events[eventType][index].fn === callback) {
+                el.removeEventListener(eventType, callback);
+
+                // remove handler from cache
+                jBone._cache.events[jBone.getData(el).jid][eventType].splice(index, 1);
+            }
+        },
+        events, namespace, removeListeners, eventType;
+
+    removeListeners = function(el) {
+        var l, eventsByType, e;
+
+        events = jBone.getData(el).events;
+
+        if (!events) {
+            return;
+        }
+
+        // remove all events
+        if (!event && events) {
+            return keys(events).forEach(function(eventType) {
+                eventsByType = events[eventType];
+                l = eventsByType.length;
+
+                while(l--) {
+                    removeListener(events, eventType, l, el, eventsByType[l]);
+                }
+            });
+        }
+
+        event.split(" ").forEach(function(event) {
+            eventType = event.split(".")[0];
+            namespace = event.split(".").splice(1).join(".");
+
+            // remove named events
+            if (events[eventType]) {
+                eventsByType = events[eventType];
+                l = eventsByType.length;
+
+                while(l--) {
+                    e = eventsByType[l];
+                    if (!namespace || (namespace && e.namespace === namespace)) {
+                        removeListener(events, eventType, l, el, e);
+                    }
+                }
+            }
+            // remove all namespaced events
+            else if (namespace) {
+                keys(events).forEach(function(eventType) {
+                    eventsByType = events[eventType];
+                    l = eventsByType.length;
+
+                    while(l--) {
+                        e = eventsByType[l];
+                        if (e.namespace.split(".")[0] === namespace.split(".")[0]) {
+                            removeListener(events, eventType, l, el, e);
+                        }
+                    }
+                });
+            }
+        });
+    };
+
+    for (; i < length; i++) {
+        removeListeners(this[i]);
+    }
+
+    return this;
+};
+
+fn.find = function(selector) {
+    var results = [],
+        i = 0,
+        length = this.length,
+        finder = function(el) {
+            if (isFunction(el.querySelectorAll)) {
+                [].forEach.call(el.querySelectorAll(selector), function(found) {
+                    results.push(found);
+                });
+            }
+        };
+
+    for (; i < length; i++) {
+        finder(this[i]);
+    }
+
+    return jBone(results);
+};
+
+fn.get = function(index) {
+    return this[index];
+};
+
+fn.eq = function(index) {
+    return jBone(this[index]);
+};
+
+fn.parent = function() {
+    var results = [],
+        parent,
+        i = 0,
+        length = this.length;
+
+    for (; i < length; i++) {
+        if (!~results.indexOf(parent = this[i].parentElement) && parent) {
+            results.push(parent);
+        }
+    }
+
+    return jBone(results);
+};
+
+fn.toArray = function() {
+    return slice.call(this);
+};
+
+fn.is = function() {
+    var args = arguments;
+
+    return this.some(function(el) {
+        return el.tagName.toLowerCase() === args[0];
+    });
+};
+
+fn.has = function() {
+    var args = arguments;
+
+    return this.some(function(el) {
+        return el.querySelectorAll(args[0]).length;
+    });
+};
+
+fn.attr = function(key, value) {
+    var args = arguments,
+        i = 0,
+        length = this.length,
+        setter;
+
+    if (isString(key) && args.length === 1) {
+        return this[0] && this[0].getAttribute(key);
+    }
+
+    if (args.length === 2) {
+        setter = function(el) {
+            el.setAttribute(key, value);
+        };
+    } else if (isObject(key)) {
+        setter = function(el) {
+            keys(key).forEach(function(name) {
+                el.setAttribute(name, key[name]);
+            });
+        };
+    }
+
+    for (; i < length; i++) {
+        setter(this[i]);
+    }
+
+    return this;
+};
+
+fn.removeAttr = function(key) {
+    var i = 0,
+        length = this.length;
+
+    for (; i < length; i++) {
+        this[i].removeAttribute(key);
+    }
+
+    return this;
+};
+
+fn.val = function(value) {
+    var i = 0,
+        length = this.length;
+
+    if (arguments.length === 0) {
+        return this[0] && this[0].value;
+    }
+
+    for (; i < length; i++) {
+        this[i].value = value;
+    }
+
+    return this;
+};
+
+fn.css = function(key, value) {
+    var args = arguments,
+        i = 0,
+        length = this.length,
+        setter;
+
+    // Get attribute
+    if (isString(key) && args.length === 1) {
+        return this[0] && win.getComputedStyle(this[0])[key];
+    }
+
+    // Set attributes
+    if (args.length === 2) {
+        setter = function(el) {
+            el.style[key] = value;
+        };
+    } else if (isObject(key)) {
+        setter = function(el) {
+            keys(key).forEach(function(name) {
+                el.style[name] = key[name];
+            });
+        };
+    }
+
+    for (; i < length; i++) {
+        setter(this[i]);
+    }
+
+    return this;
+};
+
+fn.data = function(key, value) {
+    var args = arguments, data = {},
+        i = 0,
+        length = this.length,
+        setter,
+        setValue = function(el, key, value) {
+            if (isObject(value)) {
+                el.jdata = el.jdata || {};
+                el.jdata[key] = value;
+            } else {
+                el.dataset[key] = value;
+            }
+        },
+        getValue = function(value) {
+            if (value === "true") {
+                return true;
+            } else if (value === "false") {
+                return false;
+            } else {
+                return value;
+            }
+        };
+
+    // Get all data
+    if (args.length === 0) {
+        this[0].jdata && (data = this[0].jdata);
+
+        keys(this[0].dataset).forEach(function(key) {
+            data[key] = getValue(this[0].dataset[key]);
+        }, this);
+
+        return data;
+    }
+    // Get data by name
+    if (args.length === 1 && isString(key)) {
+        return this[0] && getValue(this[0].dataset[key] || this[0].jdata && this[0].jdata[key]);
+    }
+
+    // Set data
+    if (args.length === 1 && isObject(key)) {
+        setter = function(el) {
+            keys(key).forEach(function(name) {
+                setValue(el, name, key[name]);
+            });
+        };
+    } else if (args.length === 2) {
+        setter = function(el) {
+            setValue(el, key, value);
+        };
+    }
+
+    for (; i < length; i++) {
+        setter(this[i]);
+    }
+
+    return this;
+};
+
+fn.removeData = function(key) {
+    var i = 0,
+        length = this.length,
+        jdata, dataset;
+
+    for (; i < length; i++) {
+        jdata = this[i].jdata;
+        dataset = this[i].dataset;
+
+        if (key) {
+            jdata && jdata[key] && delete jdata[key];
+            delete dataset[key];
+        } else {
+            for (key in jdata) {
+                delete jdata[key];
+            }
+
+            for (key in dataset) {
+                delete dataset[key];
+            }
+        }
+    }
+
+    return this;
+};
+
+fn.html = function(value) {
+    var args = arguments,
+        el;
+
+    // add HTML into elements
+    if (args.length === 1 && value !== undefined) {
+        return this.empty().append(value);
+    }
+    // get HTML from element
+    else if (args.length === 0 && (el = this[0])) {
+        return el.innerHTML;
+    }
+
+    return this;
+};
+
+fn.append = function(appended) {
+    var i = 0,
+        length = this.length,
+        setter;
+
+    // create jBone object and then append
+    if (isString(appended) && rquickExpr.exec(appended)) {
+        appended = jBone(appended);
+    }
+    // create text node for inserting
+    else if (!isObject(appended)) {
+        appended = document.createTextNode(appended);
+    }
+
+    appended = appended instanceof jBone ? appended : jBone(appended);
+
+    setter = function(el, i) {
+        appended.forEach(function(node) {
+            if (i) {
+                el.appendChild(node.cloneNode());
+            } else {
+                el.appendChild(node);
+            }
+        });
+    };
+
+    for (; i < length; i++) {
+        setter(this[i], i);
+    }
+
+    return this;
+};
+
+fn.appendTo = function(to) {
+    jBone(to).append(this);
+
+    return this;
+};
+
+fn.empty = function() {
+    var i = 0,
+        length = this.length,
+        el;
+
+    for (; i < length; i++) {
+        el = this[i];
+
+        while (el.lastChild) {
+            el.removeChild(el.lastChild);
+        }
+    }
+
+    return this;
+};
+
+fn.remove = function() {
+    var i = 0,
+        length = this.length,
+        el;
+
+    // remove all listners
+    this.off();
+
+    for (; i < length; i++) {
+        el = this[i];
+
+        // remove data and nodes
+        delete el.jdata;
+        el.parentNode && el.parentNode.removeChild(el);
+    }
+
+    return this;
+};
+
+if (typeof module === "object" && module && typeof module.exports === "object") {
+    // Expose jBone as module.exports in loaders that implement the Node
+    // module pattern (including browserify). Do not create the global, since
+    // the user will be storing it themselves locally, and globals are frowned
+    // upon in the Node module world.
+    module.exports = jBone;
+}
+// Register as a AMD module
+else if (typeof define === "function" && define.amd) {
+    define(function() {
+        return jBone;
+    });
+
+    win.jBone = win.$ = jBone;
+} else if (typeof win === "object" && typeof win.document === "object") {
+    win.jBone = win.$ = jBone;
+}
+
+}(window));
+
+},{}],51:[function(require,module,exports){
+var Mouse;
+
+module.exports = Mouse = {
+  rel: function(e) {
+    var mouseX, mouseY, rect, target;
+    mouseX = e.offsetX;
+    mouseY = e.offsetY;
+    if (mouseX == null) {
+      rect = target.getBoundingClientRect();
+      target = e.target || e.srcElement;
+      if (mouseX == null) {
+        mouseX = e.clientX - rect.left;
+        mouseY = e.clientY - rect.top;
+      }
+      if (mouseX == null) {
+        mouseX = e.pageX - target.offsetLeft;
+        mouseY = e.pageY - target.offsetTop;
+      }
+      if (mouseX == null) {
+        console.log(e, "no mouse event defined. your browser sucks");
+        return;
+      }
+    }
+    return [mouseX, mouseY];
+  },
+  abs: function(e) {
+    var mouseX, mouseY;
+    mouseX = e.pageX;
+    mouseY = e.pageY;
+    if (mouseX == null) {
+      mouseX = e.layerX;
+      mouseY = e.layerY;
+    }
+    if (mouseX == null) {
+      mouseX = e.clientX;
+      mouseY = e.clientY;
+    }
+    if (mouseX == null) {
+      mouseX = e.x;
+      mouseY = e.y;
+    }
+    return [mouseX, mouseY];
+  },
+  wheelDelta: function(e) {
+    var delta, dir;
+    delta = [e.deltaX, e.deltaY];
+    if (delta[0] == null) {
+      dir = Math.floor(e.detail / 3);
+      delta = [0, e.mozMovementX * dir];
+    }
+    return delta;
+  }
+};
+
+},{}],52:[function(require,module,exports){
+var window = require("global/window")
+var once = require("once")
+var parseHeaders = require('parse-headers')
+
+var messages = {
+    "0": "Internal XMLHttpRequest Error",
+    "4": "4xx Client Error",
+    "5": "5xx Server Error"
+}
+
+var XHR = window.XMLHttpRequest || noop
+var XDR = "withCredentials" in (new XHR()) ? XHR : window.XDomainRequest
+
+module.exports = createXHR
+
+function createXHR(options, callback) {
+    if (typeof options === "string") {
+        options = { uri: options }
+    }
+
+    options = options || {}
+    callback = once(callback)
+
+    var xhr = options.xhr || null
+
+    if (!xhr) {
+        if (options.cors || options.useXDR) {
+            xhr = new XDR()
+        }else{
+            xhr = new XHR()
+        }
+    }
+
+    var uri = xhr.url = options.uri || options.url
+    var method = xhr.method = options.method || "GET"
+    var body = options.body || options.data
+    var headers = xhr.headers = options.headers || {}
+    var sync = !!options.sync
+    var isJson = false
+    var key
+    var load = options.response ? loadResponse : loadXhr
+
+    if ("json" in options) {
+        isJson = true
+        headers["Accept"] = "application/json"
+        if (method !== "GET" && method !== "HEAD") {
+            headers["Content-Type"] = "application/json"
+            body = JSON.stringify(options.json)
+        }
+    }
+
+    xhr.onreadystatechange = readystatechange
+    xhr.onload = load
+    xhr.onerror = error
+    // IE9 must have onprogress be set to a unique function.
+    xhr.onprogress = function () {
+        // IE must die
+    }
+    // hate IE
+    xhr.ontimeout = noop
+    xhr.open(method, uri, !sync)
+                                    //backward compatibility
+    if (options.withCredentials || (options.cors && options.withCredentials !== false)) {
+        xhr.withCredentials = true
+    }
+
+    // Cannot set timeout with sync request
+    if (!sync) {
+        xhr.timeout = "timeout" in options ? options.timeout : 5000
+    }
+
+    if (xhr.setRequestHeader) {
+        for(key in headers){
+            if(headers.hasOwnProperty(key)){
+                xhr.setRequestHeader(key, headers[key])
+            }
+        }
+    } else if (options.headers) {
+        throw new Error("Headers cannot be set on an XDomainRequest object")
+    }
+
+    if ("responseType" in options) {
+        xhr.responseType = options.responseType
+    }
+    
+    if ("beforeSend" in options && 
+        typeof options.beforeSend === "function"
+    ) {
+        options.beforeSend(xhr)
+    }
+
+    xhr.send(body)
+
+    return xhr
+
+    function readystatechange() {
+        if (xhr.readyState === 4) {
+            load()
+        }
+    }
+
+    function getBody() {
+        // Chrome with requestType=blob throws errors arround when even testing access to responseText
+        var body = null
+
+        if (xhr.response) {
+            body = xhr.response
+        } else if (xhr.responseType === 'text' || !xhr.responseType) {
+            body = xhr.responseText || xhr.responseXML
+        }
+
+        if (isJson) {
+            try {
+                body = JSON.parse(body)
+            } catch (e) {}
+        }
+
+        return body
+    }
+
+    function getStatusCode() {
+        return xhr.status === 1223 ? 204 : xhr.status
+    }
+
+    // if we're getting a none-ok statusCode, build & return an error
+    function errorFromStatusCode(status) {
+        var error = null
+        if (status === 0 || (status >= 400 && status < 600)) {
+            var message = (typeof body === "string" ? body : false) ||
+                messages[String(status).charAt(0)]
+            error = new Error(message)
+            error.statusCode = status
+        }
+
+        return error
+    }
+
+    // will load the data & process the response in a special response object
+    function loadResponse() {
+        var status = getStatusCode()
+        var error = errorFromStatusCode(status)
+        var response = {
+            body: getBody(),
+            statusCode: status,
+            statusText: xhr.statusText,
+            raw: xhr
+        }
+        if(xhr.getAllResponseHeaders){ //remember xhr can in fact be XDR for CORS in IE
+            response.headers = parseHeaders(xhr.getAllResponseHeaders())
+        } else {
+            response.headers = {}
+        }
+
+        callback(error, response, response.body)
+    }
+
+    // will load the data and add some response properties to the source xhr
+    // and then respond with that
+    function loadXhr() {
+        var status = getStatusCode()
+        var error = errorFromStatusCode(status)
+
+        xhr.status = xhr.statusCode = status
+        xhr.body = getBody()
+        xhr.headers = parseHeaders(xhr.getAllResponseHeaders())
+
+        callback(error, xhr, xhr.body)
+    }
+
+    function error(evt) {
+        callback(evt, xhr)
+    }
+}
+
+
+function noop() {}
+
+},{"global/window":53,"once":54,"parse-headers":58}],53:[function(require,module,exports){
+(function (global){
+if (typeof window !== "undefined") {
+    module.exports = window;
+} else if (typeof global !== "undefined") {
+    module.exports = global;
+} else if (typeof self !== "undefined"){
+    module.exports = self;
+} else {
+    module.exports = {};
+}
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],54:[function(require,module,exports){
+module.exports = once
+
+once.proto = once(function () {
+  Object.defineProperty(Function.prototype, 'once', {
+    value: function () {
+      return once(this)
+    },
+    configurable: true
+  })
+})
+
+function once (fn) {
+  var called = false
+  return function () {
+    if (called) return
+    called = true
+    return fn.apply(this, arguments)
+  }
+}
+
+},{}],55:[function(require,module,exports){
+var isFunction = require('is-function')
+
+module.exports = forEach
+
+var toString = Object.prototype.toString
+var hasOwnProperty = Object.prototype.hasOwnProperty
+
+function forEach(list, iterator, context) {
+    if (!isFunction(iterator)) {
+        throw new TypeError('iterator must be a function')
+    }
+
+    if (arguments.length < 3) {
+        context = this
+    }
+    
+    if (toString.call(list) === '[object Array]')
+        forEachArray(list, iterator, context)
+    else if (typeof list === 'string')
+        forEachString(list, iterator, context)
+    else
+        forEachObject(list, iterator, context)
+}
+
+function forEachArray(array, iterator, context) {
+    for (var i = 0, len = array.length; i < len; i++) {
+        if (hasOwnProperty.call(array, i)) {
+            iterator.call(context, array[i], i, array)
+        }
+    }
+}
+
+function forEachString(string, iterator, context) {
+    for (var i = 0, len = string.length; i < len; i++) {
+        // no such thing as a sparse string.
+        iterator.call(context, string.charAt(i), i, string)
+    }
+}
+
+function forEachObject(object, iterator, context) {
+    for (var k in object) {
+        if (hasOwnProperty.call(object, k)) {
+            iterator.call(context, object[k], k, object)
+        }
+    }
+}
+
+},{"is-function":56}],56:[function(require,module,exports){
+module.exports = isFunction
+
+var toString = Object.prototype.toString
+
+function isFunction (fn) {
+  var string = toString.call(fn)
+  return string === '[object Function]' ||
+    (typeof fn === 'function' && string !== '[object RegExp]') ||
+    (typeof window !== 'undefined' &&
+     // IE8 and below
+     (fn === window.setTimeout ||
+      fn === window.alert ||
+      fn === window.confirm ||
+      fn === window.prompt))
+};
+
+},{}],57:[function(require,module,exports){
+
+exports = module.exports = trim;
+
+function trim(str){
+  return str.replace(/^\s*|\s*$/g, '');
+}
+
+exports.left = function(str){
+  return str.replace(/^\s*/, '');
+};
+
+exports.right = function(str){
+  return str.replace(/\s*$/, '');
+};
+
+},{}],58:[function(require,module,exports){
+var trim = require('trim')
+  , forEach = require('for-each')
+  , isArray = function(arg) {
+      return Object.prototype.toString.call(arg) === '[object Array]';
+    }
+
+module.exports = function (headers) {
+  if (!headers)
+    return {}
+
+  var result = {}
+
+  forEach(
+      trim(headers).split('\n')
+    , function (row) {
+        var index = row.indexOf(':')
+          , key = trim(row.slice(0, index)).toLowerCase()
+          , value = trim(row.slice(index + 1))
+
+        if (typeof(result[key]) === 'undefined') {
+          result[key] = value
+        } else if (isArray(result[key])) {
+          result[key].push(value)
+        } else {
+          result[key] = [ result[key], value ]
+        }
+      }
+  )
+
+  return result
+}
+},{"for-each":55,"trim":57}],59:[function(require,module,exports){
+//     Underscore.js 1.7.0
+//     http://underscorejs.org
+//     (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+//     Underscore may be freely distributed under the MIT license.
+
+(function() {
+
+  // Baseline setup
+  // --------------
+
+  // Establish the root object, `window` in the browser, or `exports` on the server.
+  var root = this;
+
+  // Save the previous value of the `_` variable.
+  var previousUnderscore = root._;
+
+  // Save bytes in the minified (but not gzipped) version:
+  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
+
+  // Create quick reference variables for speed access to core prototypes.
+  var
+    push             = ArrayProto.push,
+    slice            = ArrayProto.slice,
+    concat           = ArrayProto.concat,
+    toString         = ObjProto.toString,
+    hasOwnProperty   = ObjProto.hasOwnProperty;
+
+  // All **ECMAScript 5** native function implementations that we hope to use
+  // are declared here.
+  var
+    nativeIsArray      = Array.isArray,
+    nativeKeys         = Object.keys,
+    nativeBind         = FuncProto.bind;
+
+  // Create a safe reference to the Underscore object for use below.
+  var _ = function(obj) {
+    if (obj instanceof _) return obj;
+    if (!(this instanceof _)) return new _(obj);
+    this._wrapped = obj;
+  };
+
+  // Export the Underscore object for **Node.js**, with
+  // backwards-compatibility for the old `require()` API. If we're in
+  // the browser, add `_` as a global object.
+  if (typeof exports !== 'undefined') {
+    if (typeof module !== 'undefined' && module.exports) {
+      exports = module.exports = _;
+    }
+    exports._ = _;
+  } else {
+    root._ = _;
+  }
+
+  // Current version.
+  _.VERSION = '1.7.0';
+
+  // Internal function that returns an efficient (for current engines) version
+  // of the passed-in callback, to be repeatedly applied in other Underscore
+  // functions.
+  var createCallback = function(func, context, argCount) {
+    if (context === void 0) return func;
+    switch (argCount == null ? 3 : argCount) {
+      case 1: return function(value) {
+        return func.call(context, value);
+      };
+      case 2: return function(value, other) {
+        return func.call(context, value, other);
+      };
+      case 3: return function(value, index, collection) {
+        return func.call(context, value, index, collection);
+      };
+      case 4: return function(accumulator, value, index, collection) {
+        return func.call(context, accumulator, value, index, collection);
+      };
+    }
+    return function() {
+      return func.apply(context, arguments);
+    };
+  };
+
+  // A mostly-internal function to generate callbacks that can be applied
+  // to each element in a collection, returning the desired result â€” either
+  // identity, an arbitrary callback, a property matcher, or a property accessor.
+  _.iteratee = function(value, context, argCount) {
+    if (value == null) return _.identity;
+    if (_.isFunction(value)) return createCallback(value, context, argCount);
+    if (_.isObject(value)) return _.matches(value);
+    return _.property(value);
+  };
+
+  // Collection Functions
+  // --------------------
+
+  // The cornerstone, an `each` implementation, aka `forEach`.
+  // Handles raw objects in addition to array-likes. Treats all
+  // sparse array-likes as if they were dense.
+  _.each = _.forEach = function(obj, iteratee, context) {
+    if (obj == null) return obj;
+    iteratee = createCallback(iteratee, context);
+    var i, length = obj.length;
+    if (length === +length) {
+      for (i = 0; i < length; i++) {
+        iteratee(obj[i], i, obj);
+      }
+    } else {
+      var keys = _.keys(obj);
+      for (i = 0, length = keys.length; i < length; i++) {
+        iteratee(obj[keys[i]], keys[i], obj);
+      }
+    }
+    return obj;
+  };
+
+  // Return the results of applying the iteratee to each element.
+  _.map = _.collect = function(obj, iteratee, context) {
+    if (obj == null) return [];
+    iteratee = _.iteratee(iteratee, context);
+    var keys = obj.length !== +obj.length && _.keys(obj),
+        length = (keys || obj).length,
+        results = Array(length),
+        currentKey;
+    for (var index = 0; index < length; index++) {
+      currentKey = keys ? keys[index] : index;
+      results[index] = iteratee(obj[currentKey], currentKey, obj);
+    }
+    return results;
+  };
+
+  var reduceError = 'Reduce of empty array with no initial value';
+
+  // **Reduce** builds up a single result from a list of values, aka `inject`,
+  // or `foldl`.
+  _.reduce = _.foldl = _.inject = function(obj, iteratee, memo, context) {
+    if (obj == null) obj = [];
+    iteratee = createCallback(iteratee, context, 4);
+    var keys = obj.length !== +obj.length && _.keys(obj),
+        length = (keys || obj).length,
+        index = 0, currentKey;
+    if (arguments.length < 3) {
+      if (!length) throw new TypeError(reduceError);
+      memo = obj[keys ? keys[index++] : index++];
+    }
+    for (; index < length; index++) {
+      currentKey = keys ? keys[index] : index;
+      memo = iteratee(memo, obj[currentKey], currentKey, obj);
+    }
+    return memo;
+  };
+
+  // The right-associative version of reduce, also known as `foldr`.
+  _.reduceRight = _.foldr = function(obj, iteratee, memo, context) {
+    if (obj == null) obj = [];
+    iteratee = createCallback(iteratee, context, 4);
+    var keys = obj.length !== + obj.length && _.keys(obj),
+        index = (keys || obj).length,
+        currentKey;
+    if (arguments.length < 3) {
+      if (!index) throw new TypeError(reduceError);
+      memo = obj[keys ? keys[--index] : --index];
+    }
+    while (index--) {
+      currentKey = keys ? keys[index] : index;
+      memo = iteratee(memo, obj[currentKey], currentKey, obj);
+    }
+    return memo;
+  };
+
+  // Return the first value which passes a truth test. Aliased as `detect`.
+  _.find = _.detect = function(obj, predicate, context) {
+    var result;
+    predicate = _.iteratee(predicate, context);
+    _.some(obj, function(value, index, list) {
+      if (predicate(value, index, list)) {
+        result = value;
+        return true;
+      }
+    });
+    return result;
+  };
+
+  // Return all the elements that pass a truth test.
+  // Aliased as `select`.
+  _.filter = _.select = function(obj, predicate, context) {
+    var results = [];
+    if (obj == null) return results;
+    predicate = _.iteratee(predicate, context);
+    _.each(obj, function(value, index, list) {
+      if (predicate(value, index, list)) results.push(value);
+    });
+    return results;
+  };
+
+  // Return all the elements for which a truth test fails.
+  _.reject = function(obj, predicate, context) {
+    return _.filter(obj, _.negate(_.iteratee(predicate)), context);
+  };
+
+  // Determine whether all of the elements match a truth test.
+  // Aliased as `all`.
+  _.every = _.all = function(obj, predicate, context) {
+    if (obj == null) return true;
+    predicate = _.iteratee(predicate, context);
+    var keys = obj.length !== +obj.length && _.keys(obj),
+        length = (keys || obj).length,
+        index, currentKey;
+    for (index = 0; index < length; index++) {
+      currentKey = keys ? keys[index] : index;
+      if (!predicate(obj[currentKey], currentKey, obj)) return false;
+    }
+    return true;
+  };
+
+  // Determine if at least one element in the object matches a truth test.
+  // Aliased as `any`.
+  _.some = _.any = function(obj, predicate, context) {
+    if (obj == null) return false;
+    predicate = _.iteratee(predicate, context);
+    var keys = obj.length !== +obj.length && _.keys(obj),
+        length = (keys || obj).length,
+        index, currentKey;
+    for (index = 0; index < length; index++) {
+      currentKey = keys ? keys[index] : index;
+      if (predicate(obj[currentKey], currentKey, obj)) return true;
+    }
+    return false;
+  };
+
+  // Determine if the array or object contains a given value (using `===`).
+  // Aliased as `include`.
+  _.contains = _.include = function(obj, target) {
+    if (obj == null) return false;
+    if (obj.length !== +obj.length) obj = _.values(obj);
+    return _.indexOf(obj, target) >= 0;
+  };
+
+  // Invoke a method (with arguments) on every item in a collection.
+  _.invoke = function(obj, method) {
+    var args = slice.call(arguments, 2);
+    var isFunc = _.isFunction(method);
+    return _.map(obj, function(value) {
+      return (isFunc ? method : value[method]).apply(value, args);
+    });
+  };
+
+  // Convenience version of a common use case of `map`: fetching a property.
+  _.pluck = function(obj, key) {
+    return _.map(obj, _.property(key));
+  };
+
+  // Convenience version of a common use case of `filter`: selecting only objects
+  // containing specific `key:value` pairs.
+  _.where = function(obj, attrs) {
+    return _.filter(obj, _.matches(attrs));
+  };
+
+  // Convenience version of a common use case of `find`: getting the first object
+  // containing specific `key:value` pairs.
+  _.findWhere = function(obj, attrs) {
+    return _.find(obj, _.matches(attrs));
+  };
+
+  // Return the maximum element (or element-based computation).
+  _.max = function(obj, iteratee, context) {
+    var result = -Infinity, lastComputed = -Infinity,
+        value, computed;
+    if (iteratee == null && obj != null) {
+      obj = obj.length === +obj.length ? obj : _.values(obj);
+      for (var i = 0, length = obj.length; i < length; i++) {
+        value = obj[i];
+        if (value > result) {
+          result = value;
+        }
+      }
+    } else {
+      iteratee = _.iteratee(iteratee, context);
+      _.each(obj, function(value, index, list) {
+        computed = iteratee(value, index, list);
+        if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
+          result = value;
+          lastComputed = computed;
+        }
+      });
+    }
+    return result;
+  };
+
+  // Return the minimum element (or element-based computation).
+  _.min = function(obj, iteratee, context) {
+    var result = Infinity, lastComputed = Infinity,
+        value, computed;
+    if (iteratee == null && obj != null) {
+      obj = obj.length === +obj.length ? obj : _.values(obj);
+      for (var i = 0, length = obj.length; i < length; i++) {
+        value = obj[i];
+        if (value < result) {
+          result = value;
+        }
+      }
+    } else {
+      iteratee = _.iteratee(iteratee, context);
+      _.each(obj, function(value, index, list) {
+        computed = iteratee(value, index, list);
+        if (computed < lastComputed || computed === Infinity && result === Infinity) {
+          result = value;
+          lastComputed = computed;
+        }
+      });
+    }
+    return result;
+  };
+
+  // Shuffle a collection, using the modern version of the
+  // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
+  _.shuffle = function(obj) {
+    var set = obj && obj.length === +obj.length ? obj : _.values(obj);
+    var length = set.length;
+    var shuffled = Array(length);
+    for (var index = 0, rand; index < length; index++) {
+      rand = _.random(0, index);
+      if (rand !== index) shuffled[index] = shuffled[rand];
+      shuffled[rand] = set[index];
+    }
+    return shuffled;
+  };
+
+  // Sample **n** random values from a collection.
+  // If **n** is not specified, returns a single random element.
+  // The internal `guard` argument allows it to work with `map`.
+  _.sample = function(obj, n, guard) {
+    if (n == null || guard) {
+      if (obj.length !== +obj.length) obj = _.values(obj);
+      return obj[_.random(obj.length - 1)];
+    }
+    return _.shuffle(obj).slice(0, Math.max(0, n));
+  };
+
+  // Sort the object's values by a criterion produced by an iteratee.
+  _.sortBy = function(obj, iteratee, context) {
+    iteratee = _.iteratee(iteratee, context);
+    return _.pluck(_.map(obj, function(value, index, list) {
+      return {
+        value: value,
+        index: index,
+        criteria: iteratee(value, index, list)
+      };
+    }).sort(function(left, right) {
+      var a = left.criteria;
+      var b = right.criteria;
+      if (a !== b) {
+        if (a > b || a === void 0) return 1;
+        if (a < b || b === void 0) return -1;
+      }
+      return left.index - right.index;
+    }), 'value');
+  };
+
+  // An internal function used for aggregate "group by" operations.
+  var group = function(behavior) {
+    return function(obj, iteratee, context) {
+      var result = {};
+      iteratee = _.iteratee(iteratee, context);
+      _.each(obj, function(value, index) {
+        var key = iteratee(value, index, obj);
+        behavior(result, value, key);
+      });
+      return result;
+    };
+  };
+
+  // Groups the object's values by a criterion. Pass either a string attribute
+  // to group by, or a function that returns the criterion.
+  _.groupBy = group(function(result, value, key) {
+    if (_.has(result, key)) result[key].push(value); else result[key] = [value];
+  });
+
+  // Indexes the object's values by a criterion, similar to `groupBy`, but for
+  // when you know that your index values will be unique.
+  _.indexBy = group(function(result, value, key) {
+    result[key] = value;
+  });
+
+  // Counts instances of an object that group by a certain criterion. Pass
+  // either a string attribute to count by, or a function that returns the
+  // criterion.
+  _.countBy = group(function(result, value, key) {
+    if (_.has(result, key)) result[key]++; else result[key] = 1;
+  });
+
+  // Use a comparator function to figure out the smallest index at which
+  // an object should be inserted so as to maintain order. Uses binary search.
+  _.sortedIndex = function(array, obj, iteratee, context) {
+    iteratee = _.iteratee(iteratee, context, 1);
+    var value = iteratee(obj);
+    var low = 0, high = array.length;
+    while (low < high) {
+      var mid = low + high >>> 1;
+      if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
+    }
+    return low;
+  };
+
+  // Safely create a real, live array from anything iterable.
+  _.toArray = function(obj) {
+    if (!obj) return [];
+    if (_.isArray(obj)) return slice.call(obj);
+    if (obj.length === +obj.length) return _.map(obj, _.identity);
+    return _.values(obj);
+  };
+
+  // Return the number of elements in an object.
+  _.size = function(obj) {
+    if (obj == null) return 0;
+    return obj.length === +obj.length ? obj.length : _.keys(obj).length;
+  };
+
+  // Split a collection into two arrays: one whose elements all satisfy the given
+  // predicate, and one whose elements all do not satisfy the predicate.
+  _.partition = function(obj, predicate, context) {
+    predicate = _.iteratee(predicate, context);
+    var pass = [], fail = [];
+    _.each(obj, function(value, key, obj) {
+      (predicate(value, key, obj) ? pass : fail).push(value);
+    });
+    return [pass, fail];
+  };
+
+  // Array Functions
+  // ---------------
+
+  // Get the first element of an array. Passing **n** will return the first N
+  // values in the array. Aliased as `head` and `take`. The **guard** check
+  // allows it to work with `_.map`.
+  _.first = _.head = _.take = function(array, n, guard) {
+    if (array == null) return void 0;
+    if (n == null || guard) return array[0];
+    if (n < 0) return [];
+    return slice.call(array, 0, n);
+  };
+
+  // Returns everything but the last entry of the array. Especially useful on
+  // the arguments object. Passing **n** will return all the values in
+  // the array, excluding the last N. The **guard** check allows it to work with
+  // `_.map`.
+  _.initial = function(array, n, guard) {
+    return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
+  };
+
+  // Get the last element of an array. Passing **n** will return the last N
+  // values in the array. The **guard** check allows it to work with `_.map`.
+  _.last = function(array, n, guard) {
+    if (array == null) return void 0;
+    if (n == null || guard) return array[array.length - 1];
+    return slice.call(array, Math.max(array.length - n, 0));
+  };
+
+  // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
+  // Especially useful on the arguments object. Passing an **n** will return
+  // the rest N values in the array. The **guard**
+  // check allows it to work with `_.map`.
+  _.rest = _.tail = _.drop = function(array, n, guard) {
+    return slice.call(array, n == null || guard ? 1 : n);
+  };
+
+  // Trim out all falsy values from an array.
+  _.compact = function(array) {
+    return _.filter(array, _.identity);
+  };
+
+  // Internal implementation of a recursive `flatten` function.
+  var flatten = function(input, shallow, strict, output) {
+    if (shallow && _.every(input, _.isArray)) {
+      return concat.apply(output, input);
+    }
+    for (var i = 0, length = input.length; i < length; i++) {
+      var value = input[i];
+      if (!_.isArray(value) && !_.isArguments(value)) {
+        if (!strict) output.push(value);
+      } else if (shallow) {
+        push.apply(output, value);
+      } else {
+        flatten(value, shallow, strict, output);
+      }
+    }
+    return output;
+  };
+
+  // Flatten out an array, either recursively (by default), or just one level.
+  _.flatten = function(array, shallow) {
+    return flatten(array, shallow, false, []);
+  };
+
+  // Return a version of the array that does not contain the specified value(s).
+  _.without = function(array) {
+    return _.difference(array, slice.call(arguments, 1));
+  };
+
+  // Produce a duplicate-free version of the array. If the array has already
+  // been sorted, you have the option of using a faster algorithm.
+  // Aliased as `unique`.
+  _.uniq = _.unique = function(array, isSorted, iteratee, context) {
+    if (array == null) return [];
+    if (!_.isBoolean(isSorted)) {
+      context = iteratee;
+      iteratee = isSorted;
+      isSorted = false;
+    }
+    if (iteratee != null) iteratee = _.iteratee(iteratee, context);
+    var result = [];
+    var seen = [];
+    for (var i = 0, length = array.length; i < length; i++) {
+      var value = array[i];
+      if (isSorted) {
+        if (!i || seen !== value) result.push(value);
+        seen = value;
+      } else if (iteratee) {
+        var computed = iteratee(value, i, array);
+        if (_.indexOf(seen, computed) < 0) {
+          seen.push(computed);
+          result.push(value);
+        }
+      } else if (_.indexOf(result, value) < 0) {
+        result.push(value);
+      }
+    }
+    return result;
+  };
+
+  // Produce an array that contains the union: each distinct element from all of
+  // the passed-in arrays.
+  _.union = function() {
+    return _.uniq(flatten(arguments, true, true, []));
+  };
+
+  // Produce an array that contains every item shared between all the
+  // passed-in arrays.
+  _.intersection = function(array) {
+    if (array == null) return [];
+    var result = [];
+    var argsLength = arguments.length;
+    for (var i = 0, length = array.length; i < length; i++) {
+      var item = array[i];
+      if (_.contains(result, item)) continue;
+      for (var j = 1; j < argsLength; j++) {
+        if (!_.contains(arguments[j], item)) break;
+      }
+      if (j === argsLength) result.push(item);
+    }
+    return result;
+  };
+
+  // Take the difference between one array and a number of other arrays.
+  // Only the elements present in just the first array will remain.
+  _.difference = function(array) {
+    var rest = flatten(slice.call(arguments, 1), true, true, []);
+    return _.filter(array, function(value){
+      return !_.contains(rest, value);
+    });
+  };
+
+  // Zip together multiple lists into a single array -- elements that share
+  // an index go together.
+  _.zip = function(array) {
+    if (array == null) return [];
+    var length = _.max(arguments, 'length').length;
+    var results = Array(length);
+    for (var i = 0; i < length; i++) {
+      results[i] = _.pluck(arguments, i);
+    }
+    return results;
+  };
+
+  // Converts lists into objects. Pass either a single array of `[key, value]`
+  // pairs, or two parallel arrays of the same length -- one of keys, and one of
+  // the corresponding values.
+  _.object = function(list, values) {
+    if (list == null) return {};
+    var result = {};
+    for (var i = 0, length = list.length; i < length; i++) {
+      if (values) {
+        result[list[i]] = values[i];
+      } else {
+        result[list[i][0]] = list[i][1];
+      }
+    }
+    return result;
+  };
+
+  // Return the position of the first occurrence of an item in an array,
+  // or -1 if the item is not included in the array.
+  // If the array is large and already in sort order, pass `true`
+  // for **isSorted** to use binary search.
+  _.indexOf = function(array, item, isSorted) {
+    if (array == null) return -1;
+    var i = 0, length = array.length;
+    if (isSorted) {
+      if (typeof isSorted == 'number') {
+        i = isSorted < 0 ? Math.max(0, length + isSorted) : isSorted;
+      } else {
+        i = _.sortedIndex(array, item);
+        return array[i] === item ? i : -1;
+      }
+    }
+    for (; i < length; i++) if (array[i] === item) return i;
+    return -1;
+  };
+
+  _.lastIndexOf = function(array, item, from) {
+    if (array == null) return -1;
+    var idx = array.length;
+    if (typeof from == 'number') {
+      idx = from < 0 ? idx + from + 1 : Math.min(idx, from + 1);
+    }
+    while (--idx >= 0) if (array[idx] === item) return idx;
+    return -1;
+  };
+
+  // Generate an integer Array containing an arithmetic progression. A port of
+  // the native Python `range()` function. See
+  // [the Python documentation](http://docs.python.org/library/functions.html#range).
+  _.range = function(start, stop, step) {
+    if (arguments.length <= 1) {
+      stop = start || 0;
+      start = 0;
+    }
+    step = step || 1;
+
+    var length = Math.max(Math.ceil((stop - start) / step), 0);
+    var range = Array(length);
+
+    for (var idx = 0; idx < length; idx++, start += step) {
+      range[idx] = start;
+    }
+
+    return range;
+  };
+
+  // Function (ahem) Functions
+  // ------------------
+
+  // Reusable constructor function for prototype setting.
+  var Ctor = function(){};
+
+  // Create a function bound to a given object (assigning `this`, and arguments,
+  // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
+  // available.
+  _.bind = function(func, context) {
+    var args, bound;
+    if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
+    if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
+    args = slice.call(arguments, 2);
+    bound = function() {
+      if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
+      Ctor.prototype = func.prototype;
+      var self = new Ctor;
+      Ctor.prototype = null;
+      var result = func.apply(self, args.concat(slice.call(arguments)));
+      if (_.isObject(result)) return result;
+      return self;
+    };
+    return bound;
+  };
+
+  // Partially apply a function by creating a version that has had some of its
+  // arguments pre-filled, without changing its dynamic `this` context. _ acts
+  // as a placeholder, allowing any combination of arguments to be pre-filled.
+  _.partial = function(func) {
+    var boundArgs = slice.call(arguments, 1);
+    return function() {
+      var position = 0;
+      var args = boundArgs.slice();
+      for (var i = 0, length = args.length; i < length; i++) {
+        if (args[i] === _) args[i] = arguments[position++];
+      }
+      while (position < arguments.length) args.push(arguments[position++]);
+      return func.apply(this, args);
+    };
+  };
+
+  // Bind a number of an object's methods to that object. Remaining arguments
+  // are the method names to be bound. Useful for ensuring that all callbacks
+  // defined on an object belong to it.
+  _.bindAll = function(obj) {
+    var i, length = arguments.length, key;
+    if (length <= 1) throw new Error('bindAll must be passed function names');
+    for (i = 1; i < length; i++) {
+      key = arguments[i];
+      obj[key] = _.bind(obj[key], obj);
+    }
+    return obj;
+  };
+
+  // Memoize an expensive function by storing its results.
+  _.memoize = function(func, hasher) {
+    var memoize = function(key) {
+      var cache = memoize.cache;
+      var address = hasher ? hasher.apply(this, arguments) : key;
+      if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
+      return cache[address];
+    };
+    memoize.cache = {};
+    return memoize;
+  };
+
+  // Delays a function for the given number of milliseconds, and then calls
+  // it with the arguments supplied.
+  _.delay = function(func, wait) {
+    var args = slice.call(arguments, 2);
+    return setTimeout(function(){
+      return func.apply(null, args);
+    }, wait);
+  };
+
+  // Defers a function, scheduling it to run after the current call stack has
+  // cleared.
+  _.defer = function(func) {
+    return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
+  };
+
+  // Returns a function, that, when invoked, will only be triggered at most once
+  // during a given window of time. Normally, the throttled function will run
+  // as much as it can, without ever going more than once per `wait` duration;
+  // but if you'd like to disable the execution on the leading edge, pass
+  // `{leading: false}`. To disable execution on the trailing edge, ditto.
+  _.throttle = function(func, wait, options) {
+    var context, args, result;
+    var timeout = null;
+    var previous = 0;
+    if (!options) options = {};
+    var later = function() {
+      previous = options.leading === false ? 0 : _.now();
+      timeout = null;
+      result = func.apply(context, args);
+      if (!timeout) context = args = null;
+    };
+    return function() {
+      var now = _.now();
+      if (!previous && options.leading === false) previous = now;
+      var remaining = wait - (now - previous);
+      context = this;
+      args = arguments;
+      if (remaining <= 0 || remaining > wait) {
+        clearTimeout(timeout);
+        timeout = null;
+        previous = now;
+        result = func.apply(context, args);
+        if (!timeout) context = args = null;
+      } else if (!timeout && options.trailing !== false) {
+        timeout = setTimeout(later, remaining);
+      }
+      return result;
+    };
+  };
+
+  // Returns a function, that, as long as it continues to be invoked, will not
+  // be triggered. The function will be called after it stops being called for
+  // N milliseconds. If `immediate` is passed, trigger the function on the
+  // leading edge, instead of the trailing.
+  _.debounce = function(func, wait, immediate) {
+    var timeout, args, context, timestamp, result;
+
+    var later = function() {
+      var last = _.now() - timestamp;
+
+      if (last < wait && last > 0) {
+        timeout = setTimeout(later, wait - last);
+      } else {
+        timeout = null;
+        if (!immediate) {
+          result = func.apply(context, args);
+          if (!timeout) context = args = null;
+        }
+      }
+    };
+
+    return function() {
+      context = this;
+      args = arguments;
+      timestamp = _.now();
+      var callNow = immediate && !timeout;
+      if (!timeout) timeout = setTimeout(later, wait);
+      if (callNow) {
+        result = func.apply(context, args);
+        context = args = null;
+      }
+
+      return result;
+    };
+  };
+
+  // Returns the first function passed as an argument to the second,
+  // allowing you to adjust arguments, run code before and after, and
+  // conditionally execute the original function.
+  _.wrap = function(func, wrapper) {
+    return _.partial(wrapper, func);
+  };
+
+  // Returns a negated version of the passed-in predicate.
+  _.negate = function(predicate) {
+    return function() {
+      return !predicate.apply(this, arguments);
+    };
+  };
+
+  // Returns a function that is the composition of a list of functions, each
+  // consuming the return value of the function that follows.
+  _.compose = function() {
+    var args = arguments;
+    var start = args.length - 1;
+    return function() {
+      var i = start;
+      var result = args[start].apply(this, arguments);
+      while (i--) result = args[i].call(this, result);
+      return result;
+    };
+  };
+
+  // Returns a function that will only be executed after being called N times.
+  _.after = function(times, func) {
+    return function() {
+      if (--times < 1) {
+        return func.apply(this, arguments);
+      }
+    };
+  };
+
+  // Returns a function that will only be executed before being called N times.
+  _.before = function(times, func) {
+    var memo;
+    return function() {
+      if (--times > 0) {
+        memo = func.apply(this, arguments);
+      } else {
+        func = null;
+      }
+      return memo;
+    };
+  };
+
+  // Returns a function that will be executed at most one time, no matter how
+  // often you call it. Useful for lazy initialization.
+  _.once = _.partial(_.before, 2);
+
+  // Object Functions
+  // ----------------
+
+  // Retrieve the names of an object's properties.
+  // Delegates to **ECMAScript 5**'s native `Object.keys`
+  _.keys = function(obj) {
+    if (!_.isObject(obj)) return [];
+    if (nativeKeys) return nativeKeys(obj);
+    var keys = [];
+    for (var key in obj) if (_.has(obj, key)) keys.push(key);
+    return keys;
+  };
+
+  // Retrieve the values of an object's properties.
+  _.values = function(obj) {
+    var keys = _.keys(obj);
+    var length = keys.length;
+    var values = Array(length);
+    for (var i = 0; i < length; i++) {
+      values[i] = obj[keys[i]];
+    }
+    return values;
+  };
+
+  // Convert an object into a list of `[key, value]` pairs.
+  _.pairs = function(obj) {
+    var keys = _.keys(obj);
+    var length = keys.length;
+    var pairs = Array(length);
+    for (var i = 0; i < length; i++) {
+      pairs[i] = [keys[i], obj[keys[i]]];
+    }
+    return pairs;
+  };
+
+  // Invert the keys and values of an object. The values must be serializable.
+  _.invert = function(obj) {
+    var result = {};
+    var keys = _.keys(obj);
+    for (var i = 0, length = keys.length; i < length; i++) {
+      result[obj[keys[i]]] = keys[i];
+    }
+    return result;
+  };
+
+  // Return a sorted list of the function names available on the object.
+  // Aliased as `methods`
+  _.functions = _.methods = function(obj) {
+    var names = [];
+    for (var key in obj) {
+      if (_.isFunction(obj[key])) names.push(key);
+    }
+    return names.sort();
+  };
+
+  // Extend a given object with all the properties in passed-in object(s).
+  _.extend = function(obj) {
+    if (!_.isObject(obj)) return obj;
+    var source, prop;
+    for (var i = 1, length = arguments.length; i < length; i++) {
+      source = arguments[i];
+      for (prop in source) {
+        if (hasOwnProperty.call(source, prop)) {
+            obj[prop] = source[prop];
+        }
+      }
+    }
+    return obj;
+  };
+
+  // Return a copy of the object only containing the whitelisted properties.
+  _.pick = function(obj, iteratee, context) {
+    var result = {}, key;
+    if (obj == null) return result;
+    if (_.isFunction(iteratee)) {
+      iteratee = createCallback(iteratee, context);
+      for (key in obj) {
+        var value = obj[key];
+        if (iteratee(value, key, obj)) result[key] = value;
+      }
+    } else {
+      var keys = concat.apply([], slice.call(arguments, 1));
+      obj = new Object(obj);
+      for (var i = 0, length = keys.length; i < length; i++) {
+        key = keys[i];
+        if (key in obj) result[key] = obj[key];
+      }
+    }
+    return result;
+  };
+
+   // Return a copy of the object without the blacklisted properties.
+  _.omit = function(obj, iteratee, context) {
+    if (_.isFunction(iteratee)) {
+      iteratee = _.negate(iteratee);
+    } else {
+      var keys = _.map(concat.apply([], slice.call(arguments, 1)), String);
+      iteratee = function(value, key) {
+        return !_.contains(keys, key);
+      };
+    }
+    return _.pick(obj, iteratee, context);
+  };
+
+  // Fill in a given object with default properties.
+  _.defaults = function(obj) {
+    if (!_.isObject(obj)) return obj;
+    for (var i = 1, length = arguments.length; i < length; i++) {
+      var source = arguments[i];
+      for (var prop in source) {
+        if (obj[prop] === void 0) obj[prop] = source[prop];
+      }
+    }
+    return obj;
+  };
+
+  // Create a (shallow-cloned) duplicate of an object.
+  _.clone = function(obj) {
+    if (!_.isObject(obj)) return obj;
+    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
+  };
+
+  // Invokes interceptor with the obj, and then returns obj.
+  // The primary purpose of this method is to "tap into" a method chain, in
+  // order to perform operations on intermediate results within the chain.
+  _.tap = function(obj, interceptor) {
+    interceptor(obj);
+    return obj;
+  };
+
+  // Internal recursive comparison function for `isEqual`.
+  var eq = function(a, b, aStack, bStack) {
+    // Identical objects are equal. `0 === -0`, but they aren't identical.
+    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
+    if (a === b) return a !== 0 || 1 / a === 1 / b;
+    // A strict comparison is necessary because `null == undefined`.
+    if (a == null || b == null) return a === b;
+    // Unwrap any wrapped objects.
+    if (a instanceof _) a = a._wrapped;
+    if (b instanceof _) b = b._wrapped;
+    // Compare `[[Class]]` names.
+    var className = toString.call(a);
+    if (className !== toString.call(b)) return false;
+    switch (className) {
+      // Strings, numbers, regular expressions, dates, and booleans are compared by value.
+      case '[object RegExp]':
+      // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
+      case '[object String]':
+        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+        // equivalent to `new String("5")`.
+        return '' + a === '' + b;
+      case '[object Number]':
+        // `NaN`s are equivalent, but non-reflexive.
+        // Object(NaN) is equivalent to NaN
+        if (+a !== +a) return +b !== +b;
+        // An `egal` comparison is performed for other numeric values.
+        return +a === 0 ? 1 / +a === 1 / b : +a === +b;
+      case '[object Date]':
+      case '[object Boolean]':
+        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+        // millisecond representations. Note that invalid dates with millisecond representations
+        // of `NaN` are not equivalent.
+        return +a === +b;
+    }
+    if (typeof a != 'object' || typeof b != 'object') return false;
+    // Assume equality for cyclic structures. The algorithm for detecting cyclic
+    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+    var length = aStack.length;
+    while (length--) {
+      // Linear search. Performance is inversely proportional to the number of
+      // unique nested structures.
+      if (aStack[length] === a) return bStack[length] === b;
+    }
+    // Objects with different constructors are not equivalent, but `Object`s
+    // from different frames are.
+    var aCtor = a.constructor, bCtor = b.constructor;
+    if (
+      aCtor !== bCtor &&
+      // Handle Object.create(x) cases
+      'constructor' in a && 'constructor' in b &&
+      !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
+        _.isFunction(bCtor) && bCtor instanceof bCtor)
+    ) {
+      return false;
+    }
+    // Add the first object to the stack of traversed objects.
+    aStack.push(a);
+    bStack.push(b);
+    var size, result;
+    // Recursively compare objects and arrays.
+    if (className === '[object Array]') {
+      // Compare array lengths to determine if a deep comparison is necessary.
+      size = a.length;
+      result = size === b.length;
+      if (result) {
+        // Deep compare the contents, ignoring non-numeric properties.
+        while (size--) {
+          if (!(result = eq(a[size], b[size], aStack, bStack))) break;
+        }
+      }
+    } else {
+      // Deep compare objects.
+      var keys = _.keys(a), key;
+      size = keys.length;
+      // Ensure that both objects contain the same number of properties before comparing deep equality.
+      result = _.keys(b).length === size;
+      if (result) {
+        while (size--) {
+          // Deep compare each member
+          key = keys[size];
+          if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
+        }
+      }
+    }
+    // Remove the first object from the stack of traversed objects.
+    aStack.pop();
+    bStack.pop();
+    return result;
+  };
+
+  // Perform a deep comparison to check if two objects are equal.
+  _.isEqual = function(a, b) {
+    return eq(a, b, [], []);
+  };
+
+  // Is a given array, string, or object empty?
+  // An "empty" object has no enumerable own-properties.
+  _.isEmpty = function(obj) {
+    if (obj == null) return true;
+    if (_.isArray(obj) || _.isString(obj) || _.isArguments(obj)) return obj.length === 0;
+    for (var key in obj) if (_.has(obj, key)) return false;
+    return true;
+  };
+
+  // Is a given value a DOM element?
+  _.isElement = function(obj) {
+    return !!(obj && obj.nodeType === 1);
+  };
+
+  // Is a given value an array?
+  // Delegates to ECMA5's native Array.isArray
+  _.isArray = nativeIsArray || function(obj) {
+    return toString.call(obj) === '[object Array]';
+  };
+
+  // Is a given variable an object?
+  _.isObject = function(obj) {
+    var type = typeof obj;
+    return type === 'function' || type === 'object' && !!obj;
+  };
+
+  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
+  _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) {
+    _['is' + name] = function(obj) {
+      return toString.call(obj) === '[object ' + name + ']';
+    };
+  });
+
+  // Define a fallback version of the method in browsers (ahem, IE), where
+  // there isn't any inspectable "Arguments" type.
+  if (!_.isArguments(arguments)) {
+    _.isArguments = function(obj) {
+      return _.has(obj, 'callee');
+    };
+  }
+
+  // Optimize `isFunction` if appropriate. Work around an IE 11 bug.
+  if (typeof /./ !== 'function') {
+    _.isFunction = function(obj) {
+      return typeof obj == 'function' || false;
+    };
+  }
+
+  // Is a given object a finite number?
+  _.isFinite = function(obj) {
+    return isFinite(obj) && !isNaN(parseFloat(obj));
+  };
+
+  // Is the given value `NaN`? (NaN is the only number which does not equal itself).
+  _.isNaN = function(obj) {
+    return _.isNumber(obj) && obj !== +obj;
+  };
+
+  // Is a given value a boolean?
+  _.isBoolean = function(obj) {
+    return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
+  };
+
+  // Is a given value equal to null?
+  _.isNull = function(obj) {
+    return obj === null;
+  };
+
+  // Is a given variable undefined?
+  _.isUndefined = function(obj) {
+    return obj === void 0;
+  };
+
+  // Shortcut function for checking if an object has a given property directly
+  // on itself (in other words, not on a prototype).
+  _.has = function(obj, key) {
+    return obj != null && hasOwnProperty.call(obj, key);
+  };
+
+  // Utility Functions
+  // -----------------
+
+  // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
+  // previous owner. Returns a reference to the Underscore object.
+  _.noConflict = function() {
+    root._ = previousUnderscore;
+    return this;
+  };
+
+  // Keep the identity function around for default iteratees.
+  _.identity = function(value) {
+    return value;
+  };
+
+  _.constant = function(value) {
+    return function() {
+      return value;
+    };
+  };
+
+  _.noop = function(){};
+
+  _.property = function(key) {
+    return function(obj) {
+      return obj[key];
+    };
+  };
+
+  // Returns a predicate for checking whether an object has a given set of `key:value` pairs.
+  _.matches = function(attrs) {
+    var pairs = _.pairs(attrs), length = pairs.length;
+    return function(obj) {
+      if (obj == null) return !length;
+      obj = new Object(obj);
+      for (var i = 0; i < length; i++) {
+        var pair = pairs[i], key = pair[0];
+        if (pair[1] !== obj[key] || !(key in obj)) return false;
+      }
+      return true;
+    };
+  };
+
+  // Run a function **n** times.
+  _.times = function(n, iteratee, context) {
+    var accum = Array(Math.max(0, n));
+    iteratee = createCallback(iteratee, context, 1);
+    for (var i = 0; i < n; i++) accum[i] = iteratee(i);
+    return accum;
+  };
+
+  // Return a random integer between min and max (inclusive).
+  _.random = function(min, max) {
+    if (max == null) {
+      max = min;
+      min = 0;
+    }
+    return min + Math.floor(Math.random() * (max - min + 1));
+  };
+
+  // A (possibly faster) way to get the current timestamp as an integer.
+  _.now = Date.now || function() {
+    return new Date().getTime();
+  };
+
+   // List of HTML entities for escaping.
+  var escapeMap = {
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;',
+    '"': '&quot;',
+    "'": '&#x27;',
+    '`': '&#x60;'
+  };
+  var unescapeMap = _.invert(escapeMap);
+
+  // Functions for escaping and unescaping strings to/from HTML interpolation.
+  var createEscaper = function(map) {
+    var escaper = function(match) {
+      return map[match];
+    };
+    // Regexes for identifying a key that needs to be escaped
+    var source = '(?:' + _.keys(map).join('|') + ')';
+    var testRegexp = RegExp(source);
+    var replaceRegexp = RegExp(source, 'g');
+    return function(string) {
+      string = string == null ? '' : '' + string;
+      return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
+    };
+  };
+  _.escape = createEscaper(escapeMap);
+  _.unescape = createEscaper(unescapeMap);
+
+  // If the value of the named `property` is a function then invoke it with the
+  // `object` as context; otherwise, return it.
+  _.result = function(object, property) {
+    if (object == null) return void 0;
+    var value = object[property];
+    return _.isFunction(value) ? object[property]() : value;
+  };
+
+  // Generate a unique integer id (unique within the entire client session).
+  // Useful for temporary DOM ids.
+  var idCounter = 0;
+  _.uniqueId = function(prefix) {
+    var id = ++idCounter + '';
+    return prefix ? prefix + id : id;
+  };
+
+  // By default, Underscore uses ERB-style template delimiters, change the
+  // following template settings to use alternative delimiters.
+  _.templateSettings = {
+    evaluate    : /<%([\s\S]+?)%>/g,
+    interpolate : /<%=([\s\S]+?)%>/g,
+    escape      : /<%-([\s\S]+?)%>/g
+  };
+
+  // When customizing `templateSettings`, if you don't want to define an
+  // interpolation, evaluation or escaping regex, we need one that is
+  // guaranteed not to match.
+  var noMatch = /(.)^/;
+
+  // Certain characters need to be escaped so that they can be put into a
+  // string literal.
+  var escapes = {
+    "'":      "'",
+    '\\':     '\\',
+    '\r':     'r',
+    '\n':     'n',
+    '\u2028': 'u2028',
+    '\u2029': 'u2029'
+  };
+
+  var escaper = /\\|'|\r|\n|\u2028|\u2029/g;
+
+  var escapeChar = function(match) {
+    return '\\' + escapes[match];
+  };
+
+  // JavaScript micro-templating, similar to John Resig's implementation.
+  // Underscore templating handles arbitrary delimiters, preserves whitespace,
+  // and correctly escapes quotes within interpolated code.
+  // NB: `oldSettings` only exists for backwards compatibility.
+  _.template = function(text, settings, oldSettings) {
+    if (!settings && oldSettings) settings = oldSettings;
+    settings = _.defaults({}, settings, _.templateSettings);
+
+    // Combine delimiters into one regular expression via alternation.
+    var matcher = RegExp([
+      (settings.escape || noMatch).source,
+      (settings.interpolate || noMatch).source,
+      (settings.evaluate || noMatch).source
+    ].join('|') + '|$', 'g');
+
+    // Compile the template source, escaping string literals appropriately.
+    var index = 0;
+    var source = "__p+='";
+    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
+      source += text.slice(index, offset).replace(escaper, escapeChar);
+      index = offset + match.length;
+
+      if (escape) {
+        source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
+      } else if (interpolate) {
+        source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
+      } else if (evaluate) {
+        source += "';\n" + evaluate + "\n__p+='";
+      }
+
+      // Adobe VMs need the match returned to produce the correct offest.
+      return match;
+    });
+    source += "';\n";
+
+    // If a variable is not specified, place data values in local scope.
+    if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
+
+    source = "var __t,__p='',__j=Array.prototype.join," +
+      "print=function(){__p+=__j.call(arguments,'');};\n" +
+      source + 'return __p;\n';
+
+    try {
+      var render = new Function(settings.variable || 'obj', '_', source);
+    } catch (e) {
+      e.source = source;
+      throw e;
+    }
+
+    var template = function(data) {
+      return render.call(this, data, _);
+    };
+
+    // Provide the compiled source as a convenience for precompilation.
+    var argument = settings.variable || 'obj';
+    template.source = 'function(' + argument + '){\n' + source + '}';
+
+    return template;
+  };
+
+  // Add a "chain" function. Start chaining a wrapped Underscore object.
+  _.chain = function(obj) {
+    var instance = _(obj);
+    instance._chain = true;
+    return instance;
+  };
+
+  // OOP
+  // ---------------
+  // If Underscore is called as a function, it returns a wrapped object that
+  // can be used OO-style. This wrapper holds altered versions of all the
+  // underscore functions. Wrapped objects may be chained.
+
+  // Helper function to continue chaining intermediate results.
+  var result = function(obj) {
+    return this._chain ? _(obj).chain() : obj;
+  };
+
+  // Add your own custom functions to the Underscore object.
+  _.mixin = function(obj) {
+    _.each(_.functions(obj), function(name) {
+      var func = _[name] = obj[name];
+      _.prototype[name] = function() {
+        var args = [this._wrapped];
+        push.apply(args, arguments);
+        return result.call(this, func.apply(_, args));
+      };
+    });
+  };
+
+  // Add all of the Underscore functions to the wrapper object.
+  _.mixin(_);
+
+  // Add all mutator Array functions to the wrapper.
+  _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
+    var method = ArrayProto[name];
+    _.prototype[name] = function() {
+      var obj = this._wrapped;
+      method.apply(obj, arguments);
+      if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
+      return result.call(this, obj);
+    };
+  });
+
+  // Add all accessor Array functions to the wrapper.
+  _.each(['concat', 'join', 'slice'], function(name) {
+    var method = ArrayProto[name];
+    _.prototype[name] = function() {
+      return result.call(this, method.apply(this._wrapped, arguments));
+    };
+  });
+
+  // Extracts the result from a wrapped and chained object.
+  _.prototype.value = function() {
+    return this._wrapped;
+  };
+
+  // AMD registration happens at the end for compatibility with AMD loaders
+  // that may not enforce next-turn semantics on modules. Even though general
+  // practice for AMD registration is to be anonymous, underscore registers
+  // as a named module because, like jQuery, it is a base library that is
+  // popular enough to be bundled in a third party lib, but not be part of
+  // an AMD load request. Those cases could generate an error when an
+  // anonymous define() is called outside of a loader request.
+  if (typeof define === 'function' && define.amd) {
+    define('underscore', [], function() {
+      return _;
+    });
+  }
+}.call(this));
+
+},{}],60:[function(require,module,exports){
+var _;
+
+_ = require("underscore");
+
+module.exports = function(seqs) {
+  var occs;
+  seqs = seqs.map(function(el) {
+    return el.get("seq");
+  });
+  occs = new Array(seqs.length);
+  _.each(seqs, function(el, i) {
+    return _.each(el, function(char, pos) {
+      if (occs[pos] == null) {
+        occs[pos] = {};
+      }
+      if (occs[pos][char] == null) {
+        occs[pos][char] = 0;
+      }
+      return occs[pos][char]++;
+    });
+  });
+  return _.reduce(occs, function(memo, occ) {
+    var keys;
+    keys = _.keys(occ);
+    return memo += _.max(keys, function(key) {
+      return occ[key];
+    });
+  }, "");
+};
+
+
+
+},{"underscore":59}],61:[function(require,module,exports){
+var identitiyCalc;
+
+module.exports = identitiyCalc = function(seqs, consensus) {
+  if (consensus === void 0) {
+    console.warn("bug on consenus calc");
+    return;
+  }
+  return seqs.each(function(seqObj) {
+    var i, matches, seq, total, _i, _ref;
+    seq = seqObj.get("seq");
+    matches = 0;
+    total = 0;
+    for (i = _i = 0, _ref = seq.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
+      if (seq[i] !== "-" && consensus[i] !== "-") {
+        total++;
+        if (seq[i] === consensus[i]) {
+          matches++;
+        }
+      }
+    }
+    return seqObj.set("identity", matches / total);
+  });
+};
+
+
+
+},{}],62:[function(require,module,exports){
+module.exports.consensus = require("./ConsensusCalc");
+
+
+
+},{"./ConsensusCalc":60}],63:[function(require,module,exports){
+var Colorator, Model;
+
+Model = require("backbone-thin").Model;
+
+module.exports = Colorator = Model.extend({
+  defaults: {
+    scheme: "taylor",
+    colorBackground: true,
+    showLowerCase: true,
+    opacity: 0.6
+  }
+});
+
+
+
+},{"backbone-thin":5}],64:[function(require,module,exports){
+var Columns, Model, consenus, _;
+
+Model = require("backbone-thin").Model;
+
+consenus = require("../algo/ConsensusCalc");
+
+_ = require("underscore");
+
+module.exports = Columns = Model.extend({
+  defaults: {
+    scaling: "lin"
+  },
+  initialize: function() {
+    if (this.get("hidden") == null) {
+      return this.set("hidden", []);
+    }
+  },
+  calcHiddenColumns: function(n) {
+    var hidden, i, newX, _i, _len;
+    hidden = this.get("hidden");
+    newX = n;
+    for (_i = 0, _len = hidden.length; _i < _len; _i++) {
+      i = hidden[_i];
+      if (i <= newX) {
+        newX++;
+      }
+    }
+    return newX - n;
+  },
+  _calcConservationPre: function(seqs) {
+    var cons, matches, nMax, total;
+    console.log(seqs.length);
+    if (seqs.length > 1000) {
+      return;
+    }
+    cons = consenus(seqs);
+    seqs = seqs.map(function(el) {
+      return el.get("seq");
+    });
+    nMax = (_.max(seqs, function(el) {
+      return el.length;
+    })).length;
+    total = new Array(nMax);
+    matches = new Array(nMax);
+    _.each(seqs, function(el, i) {
+      return _.each(el, function(char, pos) {
+        total[pos] = total[pos] + 1 || 1;
+        if (cons[pos] === char) {
+          return matches[pos] = matches[pos] + 1 || 1;
+        }
+      });
+    });
+    return [matches, total, nMax];
+  },
+  calcConservation: function(seqs) {
+    if (this.attributes.scaling === "exp") {
+      return this.calcConservationExp(seqs);
+    } else if (this.attributes.scaling === "log") {
+      return this.calcConservationLog(seqs);
+    } else if (this.attributes.scaling === "lin") {
+      return this.calcConservationLin(seqs);
+    }
+  },
+  calcConservationLin: function(seqs) {
+    var i, matches, nMax, total, _i, _ref, _ref1;
+    _ref = this._calcConservationPre(seqs), matches = _ref[0], total = _ref[1], nMax = _ref[2];
+    for (i = _i = 0, _ref1 = nMax - 1; 0 <= _ref1 ? _i <= _ref1 : _i >= _ref1; i = 0 <= _ref1 ? ++_i : --_i) {
+      matches[i] = matches[i] / total[i];
+    }
+    this.set("conserv", matches);
+    return matches;
+  },
+  calcConservationLog: function(seqs) {
+    var i, matches, nMax, total, _i, _ref, _ref1;
+    _ref = this._calcConservationPre(seqs), matches = _ref[0], total = _ref[1], nMax = _ref[2];
+    for (i = _i = 0, _ref1 = nMax - 1; 0 <= _ref1 ? _i <= _ref1 : _i >= _ref1; i = 0 <= _ref1 ? ++_i : --_i) {
+      matches[i] = Math.log(matches[i] + 1) / Math.log(total[i] + 1);
+    }
+    this.set("conserv", matches);
+    return matches;
+  },
+  calcConservationExp: function(seqs) {
+    var i, matches, nMax, total, _i, _ref, _ref1;
+    _ref = this._calcConservationPre(seqs), matches = _ref[0], total = _ref[1], nMax = _ref[2];
+    for (i = _i = 0, _ref1 = nMax - 1; 0 <= _ref1 ? _i <= _ref1 : _i >= _ref1; i = 0 <= _ref1 ? ++_i : --_i) {
+      matches[i] = Math.exp(matches[i] + 1) / Math.exp(total[i] + 1);
+    }
+    this.set("conserv", matches);
+    return matches;
+  }
+});
+
+
+
+},{"../algo/ConsensusCalc":60,"backbone-thin":5,"underscore":59}],65:[function(require,module,exports){
+var Config, Model;
+
+Model = require("backbone-thin").Model;
+
+module.exports = Config = Model.extend({
+  defaults: {
+    registerMouseHover: false,
+    registerMouseClicks: true,
+    importProxy: "https://cors-anywhere.herokuapp.com/",
+    eventBus: true
+  }
+});
+
+
+
+},{"backbone-thin":5}],66:[function(require,module,exports){
+var Consenus, Model, consenusCalc;
+
+Model = require("backbone-thin").Model;
+
+consenusCalc = require("../algo/ConsensusCalc");
+
+module.exports = Consenus = Model.extend({
+  defaults: {
+    consenus: ""
+  },
+  getConsensus: function(seqs) {
+    var cons;
+    if (seqs.length > 1000) {
+      return;
+    }
+    cons = consenusCalc(seqs);
+    this.set("consenus", cons);
+    return cons;
+  }
+});
+
+
+
+},{"../algo/ConsensusCalc":60,"backbone-thin":5}],67:[function(require,module,exports){
+var ColumnSelection, Model, PosSelection, RowSelection, Selection, _;
+
+_ = require("underscore");
+
+Model = require("backbone-thin").Model;
+
+Selection = Model.extend({
+  defaults: {
+    type: "super"
+  }
+});
+
+RowSelection = Selection.extend({
+  defaults: _.extend({}, Selection.prototype.defaults, {
+    type: "row",
+    seqId: ""
+  }),
+  inRow: function(seqId) {
+    return seqId === this.get("seqId");
+  },
+  inColumn: function(rowPos) {
+    return true;
+  },
+  getLength: function() {
+    return 1;
+  }
+});
+
+ColumnSelection = Selection.extend({
+  defaults: _.extend({}, Selection.prototype.defaults, {
+    type: "column",
+    xStart: -1,
+    xEnd: -1
+  }),
+  inRow: function() {
+    return true;
+  },
+  inColumn: function(rowPos) {
+    return xStart <= rowPos && rowPos <= xEnd;
+  },
+  getLength: function() {
+    return xEnd - xStart;
+  }
+});
+
+PosSelection = RowSelection.extend(_.extend({}, _.pick(ColumnSelection, "inColumn"), _.pick(ColumnSelection, "getLength"), {
+  defaults: _.extend({}, ColumnSelection.prototype.defaults, RowSelection.prototype.defaults, {
+    type: "pos"
+  })
+}));
+
+module.exports.sel = Selection;
+
+module.exports.possel = PosSelection;
+
+module.exports.rowsel = RowSelection;
+
+module.exports.columnsel = ColumnSelection;
+
+
+
+},{"backbone-thin":5,"underscore":59}],68:[function(require,module,exports){
+var Collection, SelectionManager, sel, _;
+
+sel = require("./Selection");
+
+_ = require("underscore");
+
+Collection = require("backbone-thin").Collection;
+
+module.exports = SelectionManager = Collection.extend({
+  model: sel.sel,
+  initialize: function(data, opts) {
+    this.g = opts.g;
+    this.listenTo(this.g, "residue:click", function(e) {
+      return this._handleE(e.evt, new sel.possel({
+        xStart: e.rowPos,
+        xEnd: e.rowPos,
+        seqId: e.seqId
+      }));
+    });
+    this.listenTo(this.g, "row:click", function(e) {
+      return this._handleE(e.evt, new sel.rowsel({
+        xStart: e.rowPos,
+        xEnd: e.rowPos,
+        seqId: e.seqId
+      }));
+    });
+    return this.listenTo(this.g, "column:click", function(e) {
+      return this._handleE(e.evt, new sel.columnsel({
+        xStart: e.rowPos,
+        xEnd: e.rowPos + e.stepSize - 1
+      }));
+    });
+  },
+  getSelForRow: function(seqId) {
+    return this.filter(function(el) {
+      return el.inRow(seqId);
+    });
+  },
+  getSelForColumns: function(rowPos) {
+    return this.filter(function(el) {
+      return el.inColumn(rowPos);
+    });
+  },
+  getBlocksForRow: function(seqId, maxLen) {
+    var blocks, seli, selis, _i, _j, _k, _len, _ref, _ref1, _results, _results1;
+    selis = this.filter(function(el) {
+      return el.inRow(seqId);
+    });
+    blocks = [];
+    for (_i = 0, _len = selis.length; _i < _len; _i++) {
+      seli = selis[_i];
+      if (seli.attributes.type === "row") {
+        blocks = (function() {
+          _results = [];
+          for (var _j = 0; 0 <= maxLen ? _j <= maxLen : _j >= maxLen; 0 <= maxLen ? _j++ : _j--){ _results.push(_j); }
+          return _results;
+        }).apply(this);
+        break;
+      } else {
+        blocks = blocks.concat((function() {
+          _results1 = [];
+          for (var _k = _ref = seli.attributes.xStart, _ref1 = seli.attributes.xEnd; _ref <= _ref1 ? _k <= _ref1 : _k >= _ref1; _ref <= _ref1 ? _k++ : _k--){ _results1.push(_k); }
+          return _results1;
+        }).apply(this));
+      }
+    }
+    return blocks;
+  },
+  getAllColumnBlocks: function(conf) {
+    var blocks, filtered, maxLen, seli, withPos, _i, _j, _len, _ref, _ref1, _results;
+    maxLen = conf.maxLen;
+    withPos = conf.withPos;
+    blocks = [];
+    if (conf.withPos) {
+      filtered = this.filter(function(el) {
+        return el.get('xStart') != null;
+      });
+    } else {
+      filtered = this.filter(function(el) {
+        return el.get('type') === "column";
+      });
+    }
+    for (_i = 0, _len = filtered.length; _i < _len; _i++) {
+      seli = filtered[_i];
+      blocks = blocks.concat((function() {
+        _results = [];
+        for (var _j = _ref = seli.attributes.xStart, _ref1 = seli.attributes.xEnd; _ref <= _ref1 ? _j <= _ref1 : _j >= _ref1; _ref <= _ref1 ? _j++ : _j--){ _results.push(_j); }
+        return _results;
+      }).apply(this));
+    }
+    blocks = _.uniq(blocks);
+    return blocks;
+  },
+  invertRow: function(rows) {
+    var el, inverted, s, selRows, _i, _len;
+    selRows = this.where({
+      type: "row"
+    });
+    selRows = _.map(selRows, function(el) {
+      return el.attributes.seqId;
+    });
+    inverted = _.filter(rows, function(el) {
+      if (selRows.indexOf(el) >= 0) {
+        return false;
+      }
+      return true;
+    });
+    s = [];
+    for (_i = 0, _len = inverted.length; _i < _len; _i++) {
+      el = inverted[_i];
+      s.push(new sel.rowsel({
+        seqId: el
+      }));
+    }
+    console.log(s);
+    return this.reset(s);
+  },
+  invertCol: function(columns) {
+    var el, inverted, s, selColumns, xEnd, xStart, _i, _len;
+    selColumns = this.where({
+      type: "column"
+    });
+    selColumns = _.reduce(selColumns, function(memo, el) {
+      var _i, _ref, _ref1, _results;
+      return memo.concat((function() {
+        _results = [];
+        for (var _i = _ref = el.attributes.xStart, _ref1 = el.attributes.xEnd; _ref <= _ref1 ? _i <= _ref1 : _i >= _ref1; _ref <= _ref1 ? _i++ : _i--){ _results.push(_i); }
+        return _results;
+      }).apply(this));
+    }, []);
+    inverted = _.filter(columns, function(el) {
+      if (selColumns.indexOf(el) >= 0) {
+        return false;
+      }
+      return true;
+    });
+    if (inverted.length === 0) {
+      return;
+    }
+    s = [];
+    console.log(inverted);
+    xStart = xEnd = inverted[0];
+    for (_i = 0, _len = inverted.length; _i < _len; _i++) {
+      el = inverted[_i];
+      if (xEnd + 1 === el) {
+        xEnd = el;
+      } else {
+        s.push(new sel.columnsel({
+          xStart: xStart,
+          xEnd: xEnd
+        }));
+        xStart = xEnd = el;
+      }
+    }
+    if (xStart !== xEnd) {
+      s.push(new sel.columnsel({
+        xStart: xStart,
+        xEnd: inverted[inverted.length - 1]
+      }));
+    }
+    return this.reset(s);
+  },
+  _handleE: function(e, selection) {
+    if (e.ctrlKey || e.metaKey) {
+      return this.add(selection);
+    } else {
+      return this.reset([selection]);
+    }
+  },
+  _reduceColumns: function() {
+    return this.each(function(el, index, arr) {
+      var cols, left, lefts, right, rights, xEnd, xStart, _i, _j, _len, _len1;
+      cols = _.filter(arr, function(el) {
+        return el.get('type') === 'column';
+      });
+      xStart = el.get('xStart');
+      xEnd = el.get('xEnd');
+      lefts = _.filter(cols, function(el) {
+        return el.get('xEnd') === (xStart - 1);
+      });
+      for (_i = 0, _len = lefts.length; _i < _len; _i++) {
+        left = lefts[_i];
+        left.set('xEnd', xStart);
+      }
+      rights = _.filter(cols, function(el) {
+        return el.get('xStart') === (xEnd + 1);
+      });
+      for (_j = 0, _len1 = rights.length; _j < _len1; _j++) {
+        right = rights[_j];
+        right.set('xStart', xEnd);
+      }
+      if (lefts.length > 0 || rights.length > 0) {
+        console.log("removed el");
+        return el.collection.remove(el);
+      }
+    });
+  }
+});
+
+
+
+},{"./Selection":67,"backbone-thin":5,"underscore":59}],69:[function(require,module,exports){
+var Model, Visibility;
+
+Model = require("backbone-thin").Model;
+
+module.exports = Visibility = Model.extend({
+  defaults: {
+    overviewBox: 30,
+    headerBox: -1,
+    alignmentBody: 0
+  }
+});
+
+
+
+},{"backbone-thin":5}],70:[function(require,module,exports){
+var Model, Visibility;
+
+Model = require("backbone-thin").Model;
+
+module.exports = Visibility = Model.extend({
+  defaults: {
+    sequences: true,
+    markers: true,
+    metacell: false,
+    conserv: true,
+    overviewbox: false,
+    labels: true,
+    labelName: true,
+    labelId: true,
+    labelPartition: false,
+    labelCheckbox: false
+  }
+});
+
+
+
+},{"backbone-thin":5}],71:[function(require,module,exports){
+var Model, Zoomer;
+
+Model = require("backbone-thin").Model;
+
+module.exports = Zoomer = Model.extend({
+  constructor: function(attributes, options) {
+    Model.apply(this, arguments);
+    this.g = options.g;
+    return this;
+  },
+  defaults: {
+    alignmentWidth: "auto",
+    alignmentHeight: 195,
+    columnWidth: 15,
+    rowHeight: 15,
+    labelWidth: 100,
+    metaWidth: 100,
+    textVisible: true,
+    labelIdLength: 30,
+    labelFontsize: "13px",
+    labelLineHeight: "13px",
+    markerFontsize: "10px",
+    stepSize: 1,
+    markerStepSize: 2,
+    residueFont: "13px mono",
+    canvasEventScale: 1,
+    boxRectHeight: 5,
+    boxRectWidth: 5,
+    menuFontsize: "20px",
+    menuItemFontsize: "18px",
+    menuItemLineHeight: "18px",
+    menuMarginLeft: "5px",
+    menuPadding: "3px 5px 3px 5px",
+    _alignmentScrollLeft: 0,
+    _alignmentScrollTop: 0
+  },
+  getAlignmentWidth: function(n) {
+    if (this.get("alignmentWidth") === "auto") {
+      return this.get("columnWidth") * n;
+    } else {
+      return this.get("alignmentWidth");
+    }
+  },
+  setLeftOffset: function(n) {
+    var val;
+    val = (n - 1) * this.get('columnWidth');
+    val = Math.max(0, val);
+    return this.set("_alignmentScrollLeft", val);
+  },
+  setTopOffset: function(n) {
+    var val;
+    val = (n - 1) * this.get('rowHeight');
+    val = Math.max(0, val);
+    return this.set("_alignmentScrollTop", val);
+  },
+  getLabelWidth: function() {
+    var paddingLeft;
+    paddingLeft = 0;
+    if (this.g.vis.get("labels")) {
+      paddingLeft += this.get("labelWidth");
+    }
+    if (this.g.vis.get("metacell")) {
+      paddingLeft += this.get("metaWidth");
+    }
+    return paddingLeft;
+  },
+  _adjustWidth: function(el, model) {
+    var calcWidth, maxWidth, parentWidth, val;
+    if ((el.parentNode != null) && el.parentNode.offsetWidth !== 0) {
+      parentWidth = el.parentNode.offsetWidth;
+    } else {
+      parentWidth = document.body.clientWidth - 35;
+    }
+    maxWidth = parentWidth - this.getLabelWidth();
+    calcWidth = this.getAlignmentWidth(model.getMaxLength() - this.g.columns.get('hidden').length);
+    val = Math.min(maxWidth, calcWidth);
+    val = Math.floor(val / this.get("columnWidth")) * this.get("columnWidth");
+    return this.set("alignmentWidth", val);
+  },
+  _checkScrolling: function(scrollObj, opts) {
+    var xScroll, yScroll;
+    xScroll = scrollObj[0];
+    yScroll = scrollObj[1];
+    this.set("_alignmentScrollLeft", xScroll, opts);
+    return this.set("_alignmentScrollTop", yScroll, opts);
+  }
+});
+
+
+
+},{"backbone-thin":5}],72:[function(require,module,exports){
+module.exports.msa = require("./msa");
+
+module.exports.model = require("./model");
+
+module.exports.algo = require("./algo");
+
+module.exports.menu = require("./menu");
+
+module.exports.utils = require("./utils");
+
+module.exports.selection = require("./g/selection/Selection");
+
+module.exports.view = require("backbone-viewj");
+
+module.exports.boneView = require("backbone-childs");
+
+module.exports._ = require('underscore');
+
+module.exports.$ = require('jbone');
+
+module.exports.version = "0.1.0";
+
+
+
+},{"./algo":62,"./g/selection/Selection":67,"./menu":74,"./model":89,"./msa":90,"./utils":92,"backbone-childs":3,"backbone-viewj":10,"jbone":50,"underscore":59}],73:[function(require,module,exports){
+var ColorMenu, ExportMenu, ExtraMenu, FilterMenu, HelpMenu, ImportMenu, MenuView, OrderingMenu, SelectionMenu, VisMenu, boneView;
+
+boneView = require("backbone-childs");
+
+ImportMenu = require("./views/ImportMenu");
+
+FilterMenu = require("./views/FilterMenu");
+
+SelectionMenu = require("./views/SelectionMenu");
+
+VisMenu = require("./views/VisMenu");
+
+ColorMenu = require("./views/ColorMenu");
+
+OrderingMenu = require("./views/OrderingMenu");
+
+ExtraMenu = require("./views/ExtraMenu");
+
+ExportMenu = require("./views/ExportMenu");
+
+HelpMenu = require("./views/HelpMenu");
+
+module.exports = MenuView = boneView.extend({
+  initialize: function(data) {
+    this.msa = data.msa;
+    this.addView("10_import", new ImportMenu({
+      model: this.msa.seqs,
+      g: this.msa.g
+    }));
+    this.addView("20_filter", new FilterMenu({
+      model: this.msa.seqs,
+      g: this.msa.g
+    }));
+    this.addView("30_selection", new SelectionMenu({
+      model: this.msa.seqs,
+      g: this.msa.g
+    }));
+    this.addView("40_vis", new VisMenu({
+      model: this.msa.seqs,
+      g: this.msa.g
+    }));
+    this.addView("50_color", new ColorMenu({
+      model: this.msa.seqs,
+      g: this.msa.g
+    }));
+    this.addView("60_ordering", new OrderingMenu({
+      model: this.msa.seqs,
+      g: this.msa.g
+    }));
+    this.addView("70_extra", new ExtraMenu({
+      model: this.msa.seqs,
+      g: this.msa.g
+    }));
+    this.addView("80_export", new ExportMenu({
+      model: this.msa.seqs,
+      g: this.msa.g,
+      msa: this.msa
+    }));
+    return this.addView("90_help", new HelpMenu({
+      g: this.msa.g
+    }));
+  },
+  render: function() {
+    this.renderSubviews();
+    this.el.setAttribute("class", "biojs_msa_menubar");
+    return this.el.appendChild(document.createElement("p"));
+  }
+});
+
+
+
+},{"./views/ColorMenu":76,"./views/ExportMenu":77,"./views/ExtraMenu":78,"./views/FilterMenu":79,"./views/HelpMenu":80,"./views/ImportMenu":81,"./views/OrderingMenu":82,"./views/SelectionMenu":83,"./views/VisMenu":84,"backbone-childs":3}],74:[function(require,module,exports){
+module.exports.defaultmenu = require("./defaultmenu");
+
+module.exports.menubuilder = require("./menubuilder");
+
+
+
+},{"./defaultmenu":73,"./menubuilder":75}],75:[function(require,module,exports){
+var BMath, MenuBuilder, jbone, view;
+
+BMath = require("../utils/bmath");
+
+jbone = require("jbone");
+
+view = require("backbone-viewj");
+
+module.exports = MenuBuilder = view.extend({
+  setName: function(name) {
+    this.name = name;
+    return this._nodes = [];
+  },
+  addNode: function(label, callback, data) {
+    var style;
+    if (data != null) {
+      style = data.style;
+    }
+    if (this._nodes == null) {
+      this._nodes = [];
+    }
+    return this._nodes.push({
+      label: label,
+      callback: callback,
+      style: style
+    });
+  },
+  buildDOM: function() {
+    return this._buildM({
+      nodes: this._nodes,
+      name: this.name
+    });
+  },
+  _buildM: function(data) {
+    var displayedButton, frag, key, li, menu, menuUl, name, node, nodes, style, _i, _len, _ref;
+    nodes = data.nodes;
+    name = data.name;
+    menu = document.createElement("div");
+    menu.className = "dropdown dropdown-tip";
+    menu.id = "adrop-" + BMath.uniqueId();
+    menu.style.display = "none";
+    menuUl = document.createElement("ul");
+    menuUl.className = "dropdown-menu";
+    for (_i = 0, _len = nodes.length; _i < _len; _i++) {
+      node = nodes[_i];
+      li = document.createElement("li");
+      li.textContent = node.label;
+      _ref = node.style;
+      for (key in _ref) {
+        style = _ref[key];
+        li.style[key] = style;
+      }
+      li.addEventListener("click", node.callback);
+      if (this.g != null) {
+        li.style.lineHeight = this.g.zoomer.get("menuItemLineHeight");
+      }
+      menuUl.appendChild(li);
+    }
+    menu.appendChild(menuUl);
+    frag = document.createDocumentFragment();
+    displayedButton = document.createElement("a");
+    displayedButton.textContent = name;
+    displayedButton.className = "biojs_msa_menubar_alink";
+    if (this.g != null) {
+      menuUl.style.fontSize = this.g.zoomer.get("menuItemFontsize");
+      displayedButton.style.fontSize = this.g.zoomer.get("menuFontsize");
+      displayedButton.style.marginLeft = this.g.zoomer.get("menuMarginLeft");
+      displayedButton.style.padding = this.g.zoomer.get("menuPadding");
+    }
+    jbone(displayedButton).on("click", (function(_this) {
+      return function(e) {
+        _this._showMenu(e, menu, displayedButton);
+        return window.setTimeout(function() {
+          return jbone(document.body).one("click", function(e) {
+            console.log("next click");
+            return menu.style.display = "none";
+          });
+        }, 5);
+      };
+    })(this));
+    frag.appendChild(menu);
+    frag.appendChild(displayedButton);
+    return frag;
+  },
+  _showMenu: function(e, menu, target) {
+    var rect;
+    menu.style.display = "block";
+    menu.style.position = "absolute";
+    rect = target.getBoundingClientRect();
+    menu.style.left = rect.left + "px";
+    return menu.style.top = (rect.top + target.offsetHeight) + "px";
+  }
+});
+
+
+
+},{"../utils/bmath":91,"backbone-viewj":10,"jbone":50}],76:[function(require,module,exports){
+var ColorMenu, MenuBuilder, dom, _;
+
+MenuBuilder = require("../menubuilder");
+
+_ = require("underscore");
+
+dom = require("dom-helper");
+
+module.exports = ColorMenu = MenuBuilder.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    this.el.style.display = "inline-block";
+    return this.listenTo(this.g.colorscheme, "change", function() {
+      return this.render();
+    });
+  },
+  render: function() {
+    var colorschemes, menuColor, scheme, text, _i, _len;
+    menuColor = this.setName("Color scheme");
+    colorschemes = this.getColorschemes();
+    for (_i = 0, _len = colorschemes.length; _i < _len; _i++) {
+      scheme = colorschemes[_i];
+      this.addScheme(menuColor, scheme);
+    }
+    text = "Background";
+    if (this.g.colorscheme.get("colorBackground")) {
+      text = "Hide " + text;
+    } else {
+      text = "Show " + text;
+    }
+    this.addNode(text, (function(_this) {
+      return function() {
+        return _this.g.colorscheme.set("colorBackground", !_this.g.colorscheme.get("colorBackground"));
+      };
+    })(this));
+    this.grey(menuColor);
+    dom.removeAllChilds(this.el);
+    this.el.appendChild(this.buildDOM());
+    return this;
+  },
+  addScheme: function(menuColor, scheme) {
+    var current, style;
+    style = {};
+    current = this.g.colorscheme.get("scheme");
+    if (current === scheme.id) {
+      style.backgroundColor = "#77ED80";
+    }
+    return this.addNode(scheme.name, (function(_this) {
+      return function() {
+        return _this.g.colorscheme.set("scheme", scheme.id);
+      };
+    })(this), {
+      style: style
+    });
+  },
+  getColorschemes: function() {
+    var schemes;
+    schemes = [];
+    schemes.push({
+      name: "Zappo",
+      id: "zappo"
+    });
+    schemes.push({
+      name: "Taylor",
+      id: "taylor"
+    });
+    schemes.push({
+      name: "Hydrophobicity",
+      id: "hydro"
+    });
+    schemes.push({
+      name: "Lesk",
+      id: "lesk"
+    });
+    schemes.push({
+      name: "Cinema",
+      id: "cinema"
+    });
+    schemes.push({
+      name: "MAE",
+      id: "mae"
+    });
+    schemes.push({
+      name: "Clustal",
+      id: "clustal"
+    });
+    schemes.push({
+      name: "Clustal2",
+      id: "clustal2"
+    });
+    schemes.push({
+      name: "Turn",
+      id: "turn"
+    });
+    schemes.push({
+      name: "Strand",
+      id: "strand"
+    });
+    schemes.push({
+      name: "Buried",
+      id: "buried"
+    });
+    schemes.push({
+      name: "Helix",
+      id: "helix"
+    });
+    schemes.push({
+      name: "Nucleotide",
+      id: "nucleotide"
+    });
+    schemes.push({
+      name: "Purine",
+      id: "purine"
+    });
+    schemes.push({
+      name: "PID",
+      id: "pid"
+    });
+    schemes.push({
+      name: "No color",
+      id: "foo"
+    });
+    return schemes;
+  },
+  grey: function(menuColor) {
+    this.addNode("Grey", (function(_this) {
+      return function() {
+        _this.g.colorscheme.set("showLowerCase", false);
+        return _this.model.each(function(seq) {
+          var grey, residues;
+          residues = seq.get("seq");
+          grey = [];
+          _.each(residues, function(el, index) {
+            if (el === el.toLowerCase()) {
+              return grey.push(index);
+            }
+          });
+          return seq.set("grey", grey);
+        });
+      };
+    })(this));
+    this.addNode("Grey by threshold", (function(_this) {
+      return function() {
+        var conserv, grey, i, maxLen, threshold, _i, _ref;
+        threshold = prompt("Enter threshold (in percent)", 20);
+        threshold = threshold / 100;
+        maxLen = _this.model.getMaxLength();
+        conserv = _this.g.columns.get("conserv");
+        grey = [];
+        for (i = _i = 0, _ref = maxLen - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
+          console.log(conserv[i]);
+          if (conserv[i] < threshold) {
+            grey.push(i);
+          }
+        }
+        return _this.model.each(function(seq) {
+          return seq.set("grey", grey);
+        });
+      };
+    })(this));
+    this.addNode("Grey selection", (function(_this) {
+      return function() {
+        var maxLen;
+        maxLen = _this.model.getMaxLength();
+        return _this.model.each(function(seq) {
+          var blocks;
+          blocks = _this.g.selcol.getBlocksForRow(seq.get("id"), maxLen);
+          return seq.set("grey", blocks);
+        });
+      };
+    })(this));
+    return this.addNode("Reset grey", (function(_this) {
+      return function() {
+        _this.g.colorscheme.set("showLowerCase", true);
+        return _this.model.each(function(seq) {
+          return seq.set("grey", []);
+        });
+      };
+    })(this));
+  }
+});
+
+
+
+},{"../menubuilder":75,"dom-helper":49,"underscore":59}],77:[function(require,module,exports){
+var ExportMenu, FastaExporter, MenuBuilder, blobURL, saveAs, _;
+
+MenuBuilder = require("../menubuilder");
+
+saveAs = require("browser-saveas");
+
+FastaExporter = require("biojs-io-fasta").writer;
+
+_ = require("underscore");
+
+blobURL = require("blueimp_canvastoblob");
+
+module.exports = ExportMenu = MenuBuilder.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    this.msa = data.msa;
+    return this.el.style.display = "inline-block";
+  },
+  render: function() {
+    this.setName("Export");
+    this.addNode("Export sequences", (function(_this) {
+      return function() {
+        var blob, text;
+        text = FastaExporter["export"](_this.model.toJSON());
+        blob = new Blob([text], {
+          type: 'text/plain'
+        });
+        return saveAs(blob, "all.fasta");
+      };
+    })(this));
+    this.addNode("Export selection", (function(_this) {
+      return function() {
+        var blob, i, selection, text, _i, _ref;
+        selection = _this.g.selcol.pluck("seqId");
+        if (selection != null) {
+          selection = _this.model.filter(function(el) {
+            return _.contains(selection, el.get("id"));
+          });
+          for (i = _i = 0, _ref = selection.length - 1; _i <= _ref; i = _i += 1) {
+            selection[i] = selection[i].toJSON();
+          }
+        } else {
+          selection = _this.model.toJSON();
+          console.log("no selection found");
+        }
+        text = FastaExporter["export"](selection);
+        blob = new Blob([text], {
+          type: 'text/plain'
+        });
+        return saveAs(blob, "selection.fasta");
+      };
+    })(this));
+    this.addNode("Export image", (function(_this) {
+      return function() {
+        var canvas, url;
+        canvas = _this.msa.getView('stage').getView('body').getView('seqblock').el;
+        if (canvas != null) {
+          url = canvas.toDataURL('image/png');
+          return saveAs(blobURL(url), "biojs-msa.png", "image/png");
+        }
+      };
+    })(this));
+    this.el.appendChild(this.buildDOM());
+    return this;
+  }
+});
+
+
+
+},{"../menubuilder":75,"biojs-io-fasta":undefined,"blueimp_canvastoblob":46,"browser-saveas":47,"underscore":59}],78:[function(require,module,exports){
+var ExtraMenu, MenuBuilder, Seq, consenus;
+
+MenuBuilder = require("../menubuilder");
+
+consenus = require("../../algo/ConsensusCalc");
+
+Seq = require("../../model/Sequence");
+
+module.exports = ExtraMenu = MenuBuilder.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    return this.el.style.display = "inline-block";
+  },
+  render: function() {
+    this.setName("Extras");
+    this.addNode("Add consensus seq", (function(_this) {
+      return function() {
+        var con, seq;
+        con = consenus(_this.model);
+        console.log(con);
+        seq = new Seq({
+          seq: con,
+          id: "0c",
+          name: "consenus"
+        });
+        _this.model.add(seq);
+        _this.model.comparator = function(seq) {
+          return seq.get("id");
+        };
+        return _this.model.sort();
+      };
+    })(this));
+    this.addNode("Increase font size", (function(_this) {
+      return function() {
+        _this.g.zoomer.set("columnWidth", _this.g.zoomer.get("columnWidth") + 2);
+        _this.g.zoomer.set("labelWidth", _this.g.zoomer.get("columnWidth") + 5);
+        _this.g.zoomer.set("rowHeight", _this.g.zoomer.get("rowHeight") + 2);
+        return _this.g.zoomer.set("labelFontSize", _this.g.zoomer.get("labelFontSize") + 2);
+      };
+    })(this));
+    this.addNode("Decrease font size", (function(_this) {
+      return function() {
+        _this.g.zoomer.set("columnWidth", _this.g.zoomer.get("columnWidth") - 2);
+        _this.g.zoomer.set("rowHeight", _this.g.zoomer.get("rowHeight") - 2);
+        _this.g.zoomer.set("labelFontSize", _this.g.zoomer.get("labelFontSize") - 2);
+        if (_this.g.zoomer.get("columnWidth") < 8) {
+          return _this.g.zoomer.set("textVisible", false);
+        }
+      };
+    })(this));
+    this.addNode("Bar chart exp scaling", (function(_this) {
+      return function() {
+        return _this.g.columns.set("scaling", "exp");
+      };
+    })(this));
+    this.addNode("Bar chart linear scaling", (function(_this) {
+      return function() {
+        return _this.g.columns.set("scaling", "lin");
+      };
+    })(this));
+    this.addNode("Bar chart log scaling", (function(_this) {
+      return function() {
+        return _this.g.columns.set("scaling", "log");
+      };
+    })(this));
+    this.addNode("Minimized width", (function(_this) {
+      return function() {
+        return _this.g.zoomer.set("alignmentWidth", 600);
+      };
+    })(this));
+    this.addNode("Minimized height", (function(_this) {
+      return function() {
+        return _this.g.zoomer.set("alignmentHeight", 120);
+      };
+    })(this));
+    this.addNode("Jump to a column", (function(_this) {
+      return function() {
+        var offset;
+        offset = prompt("Column", "20");
+        if (offset < 0 || offset > _this.model.getMaxLength() || isNaN(offset)) {
+          alert("invalid column");
+          return;
+        }
+        return _this.g.zoomer.setLeftOffset(offset);
+      };
+    })(this));
+    this.el.appendChild(this.buildDOM());
+    return this;
+  }
+});
+
+
+
+},{"../../algo/ConsensusCalc":60,"../../model/Sequence":88,"../menubuilder":75}],79:[function(require,module,exports){
+var FilterMenu, MenuBuilder, _;
+
+MenuBuilder = require("../menubuilder");
+
+_ = require("underscore");
+
+module.exports = FilterMenu = MenuBuilder.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    return this.el.style.display = "inline-block";
+  },
+  render: function() {
+    this.setName("Filter");
+    this.addNode("Hide columns by threshold", (function(_this) {
+      return function(e) {
+        var conserv, hidden, i, maxLen, threshold, _i, _ref;
+        threshold = prompt("Enter threshold (in percent)", 20);
+        threshold = threshold / 100;
+        maxLen = _this.model.getMaxLength();
+        hidden = [];
+        conserv = _this.g.columns.get("conserv");
+        for (i = _i = 0, _ref = maxLen - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
+          if (conserv[i] < threshold) {
+            hidden.push(i);
+          }
+        }
+        return _this.g.columns.set("hidden", hidden);
+      };
+    })(this));
+    this.addNode("Hide columns by selection", (function(_this) {
+      return function() {
+        var hidden, hiddenOld;
+        hiddenOld = _this.g.columns.get("hidden");
+        hidden = hiddenOld.concat(_this.g.selcol.getAllColumnBlocks({
+          maxLen: _this.model.getMaxLength(),
+          withPos: true
+        }));
+        _this.g.selcol.reset([]);
+        return _this.g.columns.set("hidden", hidden);
+      };
+    })(this));
+    this.addNode("Hide columns by gaps", (function(_this) {
+      return function() {
+        var gapContent, gaps, hidden, i, maxLen, threshold, total, _i, _ref;
+        threshold = prompt("Enter threshold (in percent)", 20);
+        threshold = threshold / 100;
+        maxLen = _this.model.getMaxLength();
+        hidden = [];
+        for (i = _i = 0, _ref = maxLen - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
+          gaps = 0;
+          total = 0;
+          _this.model.each(function(el) {
+            if (el.get('seq')[i] === "-") {
+              gaps++;
+            }
+            return total++;
+          });
+          gapContent = gaps / total;
+          if (gapContent > threshold) {
+            hidden.push(i);
+          }
+        }
+        return _this.g.columns.set("hidden", hidden);
+      };
+    })(this));
+    this.addNode("Hide seqs by identity", (function(_this) {
+      return function() {
+        var threshold;
+        threshold = prompt("Enter threshold (in percent)", 20);
+        threshold = threshold / 100;
+        return _this.model.each(function(el) {
+          if (el.get('identity') < threshold) {
+            return el.set('hidden', true);
+          }
+        });
+      };
+    })(this));
+    this.addNode("Hide seqs by selection", (function(_this) {
+      return function() {
+        var hidden, ids;
+        hidden = _this.g.selcol.where({
+          type: "row"
+        });
+        ids = _.map(hidden, function(el) {
+          return el.get('seqId');
+        });
+        _this.g.selcol.reset([]);
+        return _this.model.each(function(el) {
+          if (ids.indexOf(el.get('id')) >= 0) {
+            return el.set('hidden', true);
+          }
+        });
+      };
+    })(this));
+    this.addNode("Hide seqs by gaps", (function(_this) {
+      return function() {
+        var threshold;
+        threshold = prompt("Enter threshold (in percent)", 40);
+        return _this.model.each(function(el, i) {
+          var gaps, seq;
+          seq = el.get('seq');
+          gaps = _.reduce(seq, (function(memo, c) {
+            if (c === '-') {
+              memo++;
+            }
+            return memo;
+          }), 0);
+          console.log(gaps);
+          if (gaps > threshold) {
+            return el.set('hidden', true);
+          }
+        });
+      };
+    })(this));
+    this.addNode("Reset", (function(_this) {
+      return function() {
+        _this.g.columns.set("hidden", []);
+        return _this.model.each(function(el) {
+          if (el.get('hidden')) {
+            return el.set('hidden', false);
+          }
+        });
+      };
+    })(this));
+    this.el.appendChild(this.buildDOM());
+    return this;
+  }
+});
+
+
+
+},{"../menubuilder":75,"underscore":59}],80:[function(require,module,exports){
+var HelpMenu, MenuBuilder;
+
+MenuBuilder = require("../menubuilder");
+
+module.exports = HelpMenu = MenuBuilder.extend({
+  initialize: function(data) {
+    return this.g = data.g;
+  },
+  render: function() {
+    this.setName("Help");
+    this.addNode("About the project", (function(_this) {
+      return function() {
+        return window.open("https://github.com/greenify/biojs-vis-msa");
+      };
+    })(this));
+    this.addNode("Report issues", (function(_this) {
+      return function() {
+        return window.open("https://github.com/greenify/biojs-vis-msa/issues");
+      };
+    })(this));
+    this.addNode("User manual", (function(_this) {
+      return function() {
+        return window.open("https://github.com/greenify/biojs-vis-msa/wiki");
+      };
+    })(this));
+    this.el.style.display = "inline-block";
+    this.el.appendChild(this.buildDOM());
+    return this;
+  }
+});
+
+
+
+},{"../menubuilder":75}],81:[function(require,module,exports){
+var Clustal, FastaReader, ImportMenu, MenuBuilder, corsURL;
+
+Clustal = require("biojs-io-clustal");
+
+FastaReader = require("biojs-io-fasta").parse;
+
+MenuBuilder = require("../menubuilder");
+
+corsURL = require("../../utils/proxy").corsURL;
+
+module.exports = ImportMenu = MenuBuilder.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    return this.el.style.display = "inline-block";
+  },
+  render: function() {
+    this.setName("Import");
+    this.addNode("FASTA", (function(_this) {
+      return function(e) {
+        var url;
+        url = prompt("URL", "/test/dummy/samples/p53.clustalo.fasta");
+        url = corsURL(url, _this.g);
+        return FastaReader.read(url, function(seqs) {
+          var zoomer;
+          zoomer = _this.g.zoomer.toJSON();
+          zoomer.labelWidth = 200;
+          zoomer.boxRectHeight = 2;
+          zoomer.boxRectWidth = 2;
+          _this.model.reset([]);
+          _this.g.zoomer.set(zoomer);
+          _this.model.reset(seqs);
+          return _this.g.columns.calcConservation(_this.model);
+        });
+      };
+    })(this));
+    this.addNode("CLUSTAL", (function(_this) {
+      return function() {
+        var url;
+        url = prompt("URL", "/test/dummy/samples/p53.clustalo.clustal");
+        url = corsURL(url, _this.g);
+        return Clustal.read(url, function(seqs) {
+          var zoomer;
+          zoomer = _this.g.zoomer.toJSON();
+          zoomer.labelWidth = 200;
+          zoomer.boxRectHeight = 2;
+          zoomer.boxRectWidth = 2;
+          _this.model.reset([]);
+          _this.g.zoomer.set(zoomer);
+          _this.model.reset(seqs);
+          return _this.g.columns.calcConservation(_this.model);
+        });
+      };
+    })(this));
+    this.addNode("add your own Parser", (function(_this) {
+      return function() {
+        return window.open("https://github.com/biojs/biojs2");
+      };
+    })(this));
+    this.el.appendChild(this.buildDOM());
+    return this;
+  }
+});
+
+
+
+},{"../../utils/proxy":93,"../menubuilder":75,"biojs-io-clustal":undefined,"biojs-io-fasta":undefined}],82:[function(require,module,exports){
+var MenuBuilder, OrderingMenu, dom, _;
+
+MenuBuilder = require("../menubuilder");
+
+dom = require("dom-helper");
+
+_ = require('underscore');
+
+module.exports = OrderingMenu = MenuBuilder.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    this.order = "ID";
+    return this.el.style.display = "inline-block";
+  },
+  setOrder: function(order) {
+    this.order = order;
+    return this.render();
+  },
+  render: function() {
+    var comps, el, m, _i, _len;
+    this.setName("Ordering");
+    comps = this.getComparators();
+    for (_i = 0, _len = comps.length; _i < _len; _i++) {
+      m = comps[_i];
+      this._addNode(m);
+    }
+    el = this.buildDOM();
+    dom.removeAllChilds(this.el);
+    this.el.appendChild(el);
+    return this;
+  },
+  _addNode: function(m) {
+    var style, text;
+    text = m.text;
+    style = {};
+    if (text === this.order) {
+      style.backgroundColor = "#77ED80";
+    }
+    return this.addNode(text, (function(_this) {
+      return function() {
+        if (m.precode != null) {
+          m.precode();
+        }
+        _this.model.comparator = m.comparator;
+        _this.model.sort();
+        return _this.setOrder(m.text);
+      };
+    })(this), {
+      style: style
+    });
+  },
+  getComparators: function() {
+    var models;
+    models = [];
+    models.push({
+      text: "ID",
+      comparator: "id"
+    });
+    models.push({
+      text: "ID Desc",
+      comparator: function(a, b) {
+        return -a.get("id").localeCompare(b.get("id"));
+      }
+    });
+    models.push({
+      text: "Label",
+      comparator: "name"
+    });
+    models.push({
+      text: "Label Desc",
+      comparator: function(a, b) {
+        return -a.get("name").localeCompare(b.get("name"));
+      }
+    });
+    models.push({
+      text: "Seq",
+      comparator: "seq"
+    });
+    models.push({
+      text: "Seq Desc",
+      comparator: function(a, b) {
+        return -a.get("seq").localeCompare(b.get("seq"));
+      }
+    });
+    models.push({
+      text: "Identity",
+      comparator: "identity"
+    });
+    models.push({
+      text: "Identity Desc",
+      comparator: function(seq) {
+        return -seq.get("identity");
+      }
+    });
+    models.push({
+      text: "Partition codes",
+      comparator: "partition",
+      precode: (function(_this) {
+        return function() {
+          _this.g.vis.set('labelPartition', true);
+          return _this.model.each(function(el) {
+            return el.set('partition', _.random(1, 3));
+          });
+        };
+      })(this)
+    });
+    return models;
+  }
+});
+
+
+
+},{"../menubuilder":75,"dom-helper":49,"underscore":59}],83:[function(require,module,exports){
+var MenuBuilder, SelectionMenu, sel;
+
+sel = require("../../g/selection/Selection");
+
+MenuBuilder = require("../menubuilder");
+
+module.exports = SelectionMenu = MenuBuilder.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    return this.el.style.display = "inline-block";
+  },
+  render: function() {
+    this.setName("Selection");
+    this.addNode("Find Motif (supports RegEx)", (function(_this) {
+      return function() {
+        var leftestIndex, newSeli, origIndex, search, selcol;
+        search = prompt("your search", "D");
+        search = new RegExp(search, "gi");
+        selcol = _this.g.selcol;
+        newSeli = [];
+        leftestIndex = origIndex = 100042;
+        _this.model.each(function(seq) {
+          var args, index, match, strSeq, _results;
+          strSeq = seq.get("seq");
+          _results = [];
+          while (match = search.exec(strSeq)) {
+            index = match.index;
+            args = {
+              xStart: index,
+              xEnd: index + match[0].length - 1,
+              seqId: seq.get("id")
+            };
+            newSeli.push(new sel.possel(args));
+            _results.push(leftestIndex = Math.min(index, leftestIndex));
+          }
+          return _results;
+        });
+        if (newSeli.length === 0) {
+          alert("no selection found");
+        }
+        selcol.reset(newSeli);
+        if (leftestIndex === origIndex) {
+          leftestIndex = 0;
+        }
+        return _this.g.zoomer.setLeftOffset(leftestIndex);
+      };
+    })(this));
+    this.addNode("Invert columns", (function(_this) {
+      return function() {
+        var _i, _ref, _results;
+        return _this.g.selcol.invertCol((function() {
+          _results = [];
+          for (var _i = 0, _ref = _this.model.getMaxLength(); 0 <= _ref ? _i <= _ref : _i >= _ref; 0 <= _ref ? _i++ : _i--){ _results.push(_i); }
+          return _results;
+        }).apply(this));
+      };
+    })(this));
+    this.addNode("Invert rows", (function(_this) {
+      return function() {
+        return _this.g.selcol.invertRow(_this.model.pluck("id"));
+      };
+    })(this));
+    this.addNode("Reset", (function(_this) {
+      return function() {
+        return _this.g.selcol.reset();
+      };
+    })(this));
+    this.el.appendChild(this.buildDOM());
+    return this;
+  }
+});
+
+
+
+},{"../../g/selection/Selection":67,"../menubuilder":75}],84:[function(require,module,exports){
+var ImportMenu, MenuBuilder, dom;
+
+MenuBuilder = require("../menubuilder");
+
+dom = require("dom-helper");
+
+module.exports = ImportMenu = MenuBuilder.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    this.el.style.display = "inline-block";
+    return this.listenTo(this.g.vis, "change", this.render);
+  },
+  render: function() {
+    var visEl, visElements, _i, _len;
+    this.setName("Vis. elements");
+    visElements = this.getVisElements();
+    for (_i = 0, _len = visElements.length; _i < _len; _i++) {
+      visEl = visElements[_i];
+      this._addVisEl(visEl);
+    }
+    this.addNode("Reset", (function(_this) {
+      return function() {
+        _this.g.vis.set("labels", true);
+        _this.g.vis.set("sequences", true);
+        _this.g.vis.set("metacell", true);
+        _this.g.vis.set("conserv", true);
+        _this.g.vis.set("labelId", true);
+        _this.g.vis.set("labelName", true);
+        return _this.g.vis.set("labelCheckbox", false);
+      };
+    })(this));
+    this.addNode("Toggle mouseover events", (function(_this) {
+      return function() {
+        return _this.g.config.set("registerMouseHover", !_this.g.config.get("registerMouseHover"));
+      };
+    })(this));
+    dom.removeAllChilds(this.el);
+    this.el.appendChild(this.buildDOM());
+    return this;
+  },
+  _addVisEl: function(visEl) {
+    var pre, style;
+    style = {};
+    if (this.g.vis.get(visEl.id)) {
+      pre = "Hide ";
+      style.color = "red";
+    } else {
+      pre = "Show ";
+      style.color = "green";
+    }
+    return this.addNode(pre + visEl.name, (function(_this) {
+      return function() {
+        return _this.g.vis.set(visEl.id, !_this.g.vis.get(visEl.id));
+      };
+    })(this), {
+      style: style
+    });
+  },
+  getVisElements: function() {
+    var vis;
+    vis = [];
+    vis.push({
+      name: "Markers",
+      id: "markers"
+    });
+    vis.push({
+      name: "Labels",
+      id: "labels"
+    });
+    vis.push({
+      name: "Sequences",
+      id: "sequences"
+    });
+    vis.push({
+      name: "Meta info",
+      id: "metacell"
+    });
+    vis.push({
+      name: "Overviewbox",
+      id: "overviewbox"
+    });
+    vis.push({
+      name: "conserv",
+      id: "conserv"
+    });
+    vis.push({
+      name: "LabelName",
+      id: "labelName"
+    });
+    vis.push({
+      name: "LabelId",
+      id: "labelId"
+    });
+    vis.push({
+      name: "LabelCheckbox",
+      id: "labelCheckbox"
+    });
+    return vis;
+  }
+});
+
+
+
+},{"../menubuilder":75,"dom-helper":49}],85:[function(require,module,exports){
+var Feature, Model;
+
+Feature = require("./Feature");
+
+Model = require("backbone-thin").Model;
+
+module.exports = Feature = Model.extend({
+  defaults: {
+    xStart: -1,
+    xEnd: -1,
+    height: -1,
+    text: "",
+    fillColor: "red",
+    fillOpacity: 0.5,
+    type: "rectangle",
+    borderSize: 1,
+    borderColor: "black",
+    borderOpacity: 0.5,
+    validate: true
+  },
+  validate: function() {
+    if (isNaN(this.attributes.xStart || isNaN(this.attributes.xEnd))) {
+      return "features need integer start and end.";
+    }
+  },
+  contains: function(index) {
+    return this.attributes.xStart <= index && index <= this.attributes.xEnd;
+  }
+});
+
+
+
+},{"./Feature":85,"backbone-thin":5}],86:[function(require,module,exports){
+var Collection, Feature, FeatureCol, _;
+
+Feature = require("./Feature");
+
+Collection = require("backbone-thin").Collection;
+
+_ = require("underscore");
+
+module.exports = FeatureCol = Collection.extend({
+  model: Feature,
+  constructor: function() {
+    this.startOnCache = [];
+    this.on("all", function() {
+      return this.startOnCache = [];
+    }, this);
+    return Collection.apply(this, arguments);
+  },
+  startOn: function(index) {
+    if (this.startOnCache[index] == null) {
+      this.startOnCache[index] = this.where({
+        xStart: index
+      });
+    }
+    return this.startOnCache[index];
+  },
+  contains: function(index) {
+    return this.reduce(function(el, memo) {
+      return memo || el.contains(index);
+    }, false);
+  },
+  getMinRows: function() {
+    var len, rows, x;
+    len = this.max(function(el) {
+      return el.get("xEnd");
+    });
+    rows = (function() {
+      var _i, _results;
+      _results = [];
+      for (x = _i = 1; 1 <= len ? _i <= len : _i >= len; x = 1 <= len ? ++_i : --_i) {
+        _results.push(0);
+      }
+      return _results;
+    })();
+    this.each(function(el) {
+      var _i, _ref, _ref1, _results;
+      _results = [];
+      for (x = _i = _ref = el.get("xStart"), _ref1 = feature.get("xEnd"); _i <= _ref1; x = _i += 1) {
+        _results.push(rows[x]++);
+      }
+      return _results;
+    });
+    return _.max(rows);
+  }
+});
+
+
+
+},{"./Feature":85,"backbone-thin":5,"underscore":59}],87:[function(require,module,exports){
+var Collection, SeqManager, Sequence;
+
+Sequence = require("./Sequence");
+
+Collection = require("backbone-thin").Collection;
+
+module.exports = SeqManager = Collection.extend({
+  model: Sequence,
+  constructor: function() {
+    Collection.apply(this, arguments);
+    this.on("all", function() {
+      return this.lengthCache = null;
+    }, this);
+    this.lengthCache = null;
+    return this;
+  },
+  getMaxLength: function() {
+    if (this.models.length === 0) {
+      return 0;
+    }
+    if (this.lengthCache === null) {
+      this.lengthCache = this.max(function(seq) {
+        return seq.get("seq").length;
+      }).get("seq").length;
+    }
+    return this.lengthCache;
+  },
+  prev: function(model, endless) {
+    var index;
+    index = this.indexOf(model) - 1;
+    if (index < 0 && endless) {
+      index = this.length - 1;
+    }
+    return this.at(index);
+  },
+  next: function(model, endless) {
+    var index;
+    index = this.indexOf(model) + 1;
+    if (index === this.length && endless) {
+      index = 0;
+    }
+    return this.at(index);
+  },
+  calcHiddenSeqs: function(n) {
+    var i, nNew, _i;
+    nNew = n;
+    for (i = _i = 0; 0 <= nNew ? _i <= nNew : _i >= nNew; i = 0 <= nNew ? ++_i : --_i) {
+      if (this.at(i).get("hidden")) {
+        nNew++;
+      }
+    }
+    return nNew - n;
+  }
+});
+
+
+
+},{"./Sequence":88,"backbone-thin":5}],88:[function(require,module,exports){
+var FeatureCol, Model, Sequence;
+
+Model = require("backbone-thin").Model;
+
+FeatureCol = require("./FeatureCol");
+
+module.exports = Sequence = Model.extend({
+  defaults: {
+    name: "",
+    id: "",
+    seq: ""
+  },
+  initialize: function() {
+    this.set("grey", []);
+    return this.set("features", new FeatureCol());
+  }
+});
+
+
+
+},{"./FeatureCol":86,"backbone-thin":5}],89:[function(require,module,exports){
+module.exports.seq = require("./Sequence");
+
+module.exports.seqcol = require("./SeqCollection");
+
+module.exports.feature = require("./Feature");
+
+module.exports.featurecol = require("./FeatureCol");
+
+
+
+},{"./Feature":85,"./FeatureCol":86,"./SeqCollection":87,"./Sequence":88}],90:[function(require,module,exports){
+var Colorator, Columns, Config, Consensus, Eventhandler, SelCol, SeqCollection, Stage, VisOrdering, Visibility, Zoomer, boneView;
+
+SeqCollection = require("./model/SeqCollection");
+
+Colorator = require("./g/colorator");
+
+Consensus = require("./g/consensus");
+
+Columns = require("./g/columns");
+
+Config = require("./g/config");
+
+SelCol = require("./g/selection/SelectionCol");
+
+Visibility = require("./g/visibility");
+
+VisOrdering = require("./g/visOrdering");
+
+Zoomer = require("./g/zoomer");
+
+boneView = require("backbone-childs");
+
+Eventhandler = require("biojs-events");
+
+Stage = require("./views/Stage");
+
+module.exports = boneView.extend({
+  initialize: function(data) {
+    var _ref;
+    if (data.columns == null) {
+      data.columns = {};
+    }
+    if (data.conf == null) {
+      data.conf = {};
+    }
+    if (data.vis == null) {
+      data.vis = {};
+    }
+    if (data.zoomer == null) {
+      if (!((_ref = data.visorder) != null ? _ref : data.zoomer = {})) {
+        data.visorder = {};
+      }
+    }
+    this.g = Eventhandler.mixin({});
+    if (data.seqs === void 0 || data.seqs.length === 0) {
+      console.log("warning. empty seqs.");
+    }
+    this.seqs = new SeqCollection(data.seqs);
+    this.g.config = new Config(data.conf);
+    this.g.consensus = new Consensus();
+    this.g.columns = new Columns(data.columns);
+    this.g.colorscheme = new Colorator();
+    this.g.selcol = new SelCol([], {
+      g: this.g
+    });
+    this.g.vis = new Visibility(data.vis);
+    this.g.visorder = new VisOrdering(data.visorder);
+    this.g.zoomer = new Zoomer(data.zoomer, {
+      g: this.g
+    });
+    this.addView("stage", new Stage({
+      model: this.seqs,
+      g: this.g
+    }));
+    this.el.setAttribute("class", "biojs_msa_div");
+    if (this.g.config.get("eventBus") === true) {
+      return this.startEventBus();
+    }
+  },
+  startEventBus: function() {
+    var busObjs, key, _i, _len, _results;
+    busObjs = ["config", "consensus", "columns", "colorscheme", "selcol", "vis", "visorder", "zoomer"];
+    _results = [];
+    for (_i = 0, _len = busObjs.length; _i < _len; _i++) {
+      key = busObjs[_i];
+      _results.push(this._proxyToG(key));
+    }
+    return _results;
+  },
+  _proxyToG: function(key) {
+    return this.listenTo(this.g[key], "all", function(name, prev, now) {
+      if (name === "change") {
+        return;
+      }
+      return this.g.trigger(key + ":" + name, now);
+    });
+  },
+  render: function() {
+    this.renderSubviews();
+    this.g.vis.set("loaded", true);
+    return this;
+  }
+});
+
+
+
+},{"./g/colorator":63,"./g/columns":64,"./g/config":65,"./g/consensus":66,"./g/selection/SelectionCol":68,"./g/visOrdering":69,"./g/visibility":70,"./g/zoomer":71,"./model/SeqCollection":87,"./views/Stage":100,"backbone-childs":3,"biojs-events":14}],91:[function(require,module,exports){
+var BMath;
+
+module.exports = BMath = (function() {
+  function BMath() {}
+
+  BMath.randomInt = function(lower, upper) {
+    var _ref, _ref1;
+    if (upper == null) {
+      _ref = [0, lower], lower = _ref[0], upper = _ref[1];
+    }
+    if (lower > upper) {
+      _ref1 = [upper, lower], lower = _ref1[0], upper = _ref1[1];
+    }
+    return Math.floor(Math.random() * (upper - lower + 1) + lower);
+  };
+
+  BMath.uniqueId = function(length) {
+    var id;
+    if (length == null) {
+      length = 8;
+    }
+    id = "";
+    while (id.length < length) {
+      id += Math.random().toString(36).substr(2);
+    }
+    return id.substr(0, length);
+  };
+
+  BMath.getRandomInt = function(min, max) {
+    return Math.floor(Math.random() * (max - min + 1)) + min;
+  };
+
+  return BMath;
+
+})();
+
+
+
+},{}],92:[function(require,module,exports){
+module.exports.bmath = require("./bmath");
+
+module.exports.proxy = require("./proxy");
+
+module.exports.seqgen = require("./seqgen");
+
+
+
+},{"./bmath":91,"./proxy":93,"./seqgen":94}],93:[function(require,module,exports){
+var proxy;
+
+module.exports = proxy = {
+  corsURL: (function(_this) {
+    return function(url, g) {
+      _this.g = g;
+      if (document.URL.indexOf('localhost') >= 0 && url[0] === "/") {
+        return url;
+      }
+      url = url.replace("www\.", "");
+      url = url.replace("http://", "");
+      url = _this.g.config.get('importProxy') + url;
+      return url;
+    };
+  })(this)
+};
+
+
+
+},{}],94:[function(require,module,exports){
+var BMath, Sequence, seqgen;
+
+Sequence = require("biojs-model").seq;
+
+BMath = require("./bmath");
+
+seqgen = module.exports = {
+  _generateSequence: function(len) {
+    var i, possible, text, _i, _ref;
+    text = "";
+    possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+    for (i = _i = 0, _ref = len - 1; _i <= _ref; i = _i += 1) {
+      text += possible.charAt(Math.floor(Math.random() * possible.length));
+    }
+    return text;
+  },
+  getDummySequences: function(len, seqLen) {
+    var i, seqs, _i;
+    seqs = [];
+    if (len == null) {
+      len = BMath.getRandomInt(3, 5);
+    }
+    if (seqLen == null) {
+      seqLen = BMath.getRandomInt(50, 200);
+    }
+    for (i = _i = 1; _i <= len; i = _i += 1) {
+      seqs.push(new Sequence(seqgen._generateSequence(seqLen), "seq" + i, "r" + i));
+    }
+    return seqs;
+  }
+};
+
+
+
+},{"./bmath":91,"biojs-model":27}],95:[function(require,module,exports){
+var Base, Line, Polygon, Rect, setAttr, svgns;
+
+svgns = "http://www.w3.org/2000/svg";
+
+setAttr = function(obj, opts) {
+  var name, value;
+  for (name in opts) {
+    value = opts[name];
+    obj.setAttributeNS(null, name, value);
+  }
+  return obj;
+};
+
+Base = function(opts) {
+  var svg;
+  svg = document.createElementNS(svgns, 'svg');
+  svg.setAttribute("width", opts.width);
+  svg.setAttribute("height", opts.height);
+  return svg;
+};
+
+Rect = function(opts) {
+  var rect;
+  rect = document.createElementNS(svgns, 'rect');
+  return setAttr(rect, opts);
+};
+
+Line = function(opts) {
+  var line;
+  line = document.createElementNS(svgns, 'line');
+  return setAttr(line, opts);
+};
+
+Polygon = function(opts) {
+  var line;
+  line = document.createElementNS(svgns, 'polygon');
+  return setAttr(line, opts);
+};
+
+module.exports.rect = Rect;
+
+module.exports.line = Line;
+
+module.exports.polygon = Polygon;
+
+module.exports.base = Base;
+
+
+
+},{}],96:[function(require,module,exports){
+var LabelBlock, SeqBlock, boneView;
+
+boneView = require("backbone-childs");
+
+SeqBlock = require("./CanvasSeqBlock");
+
+LabelBlock = require("./labels/LabelBlock");
+
+module.exports = boneView.extend({
+  initialize: function(data) {
+    var labelblock, seqblock;
+    this.g = data.g;
+    if (true) {
+      labelblock = new LabelBlock({
+        model: this.model,
+        g: this.g
+      });
+      labelblock.ordering = -1;
+      this.addView("labelblock", labelblock);
+    }
+    if (this.g.vis.get("sequences")) {
+      seqblock = new SeqBlock({
+        model: this.model,
+        g: this.g
+      });
+      seqblock.ordering = 0;
+      this.addView("seqblock", seqblock);
+    }
+    this.listenTo(this.g.zoomer, "change:alignmentHeight", this.adjustHeight);
+    return this.listenTo(this.g.columns, "change:hidden", this.adjustHeight);
+  },
+  render: function() {
+    this.renderSubviews();
+    this.el.className = "biojs_msa_albody";
+    this.el.style.whiteSpace = "nowrap";
+    this.adjustHeight();
+    return this;
+  },
+  adjustHeight: function() {
+    if (this.g.zoomer.get("alignmentHeight") === "auto") {
+      this.el.style.height = (this.g.zoomer.get("rowHeight") * this.model.length) + 5;
+    } else {
+      this.el.style.height = this.g.zoomer.get("alignmentHeight");
+    }
+    return this.el.style.width = this.getWidth() + 15;
+  },
+  getWidth: function() {
+    var width;
+    width = 0;
+    if (this.g.vis.get("labels")) {
+      width += this.g.zoomer.get("labelWidth");
+    }
+    if (this.g.vis.get("metacell")) {
+      width += this.g.zoomer.get("metaWidth");
+    }
+    if (this.g.vis.get("sequences")) {
+      width += this.g.zoomer.get("alignmentWidth");
+    }
+    return width;
+  }
+});
+
+
+
+},{"./CanvasSeqBlock":98,"./labels/LabelBlock":104,"backbone-childs":3}],97:[function(require,module,exports){
+var CanvasCharCache, Events;
+
+Events = require("biojs-events");
+
+module.exports = CanvasCharCache = (function() {
+  function CanvasCharCache(g) {
+    this.g = g;
+    this.cache = {};
+    this.cacheHeight = 0;
+    this.cacheWidth = 0;
+  }
+
+  CanvasCharCache.prototype.getFontTile = function(letter, width, height) {
+    if (width !== this.cacheWidth || height !== this.cacheHeight) {
+      this.cacheHeight = height;
+      this.cacheWidth = width;
+      this.cache = {};
+    }
+    if (this.cache[letter] === void 0) {
+      this.createTile(letter, width, height);
+    }
+    return this.cache[letter];
+  };
+
+  CanvasCharCache.prototype.createTile = function(letter, width, height) {
+    var canvas;
+    canvas = this.cache[letter] = document.createElement("canvas");
+    canvas.width = width;
+    canvas.height = height;
+    this.ctx = canvas.getContext('2d');
+    this.ctx.font = this.g.zoomer.get("residueFont");
+    this.ctx.textBaseline = 'middle';
+    this.ctx.textAlign = "center";
+    return this.ctx.fillText(letter, width / 2, height / 2, width);
+  };
+
+  return CanvasCharCache;
+
+})();
+
+
+
+},{"biojs-events":14}],98:[function(require,module,exports){
+var CharCache, boneView, colorSelector, jbone, mouse, _;
+
+boneView = require("backbone-childs");
+
+mouse = require("mouse-pos");
+
+colorSelector = require("biojs-util-colorschemes").selector;
+
+_ = require("underscore");
+
+jbone = require("jbone");
+
+CharCache = require("./CanvasCharCache");
+
+module.exports = boneView.extend({
+  tagName: "canvas",
+  initialize: function(data) {
+    this.g = data.g;
+    this.listenTo(this.g.zoomer, "change:_alignmentScrollLeft change:_alignmentScrollTop", function(model, value, options) {
+      if (((options != null ? options.origin : void 0) == null) || options.origin !== "canvasseq") {
+        return this.render();
+      }
+    });
+    this.listenTo(this.g.columns, "change:hidden", this.render);
+    this.listenTo(this.g.zoomer, "change:alignmentWidth", this.render);
+    this.listenTo(this.g.colorscheme, "change", this.render);
+    this.listenTo(this.g.selcol, "reset add", this.render);
+    this.el.style.display = "inline-block";
+    this.el.style.overflowX = "hidden";
+    this.el.style.overflowY = "hidden";
+    this.el.className = "biojs_msa_seqblock";
+    this.ctx = this.el.getContext('2d');
+    this.cache = new CharCache(this.g);
+    this.throttleTime = 0;
+    this.throttleCounts = 0;
+    if (document.documentElement.style.webkitAppearance != null) {
+      this.throttledDraw = function() {
+        var start, tTime;
+        start = +new Date();
+        this.draw();
+        this.throttleTime += +new Date() - start;
+        this.throttleCounts++;
+        if (this.throttleCounts > 15) {
+          tTime = Math.ceil(this.throttleTime / this.throttleCounts);
+          console.log("avgDrawTime/WebKit", tTime);
+          return this.throttledDraw = this.draw;
+        }
+      };
+    } else {
+      this.throttledDraw = _.throttle(this.throttledDraw, 30);
+    }
+    return this.manageEvents();
+  },
+  throttledDraw: function() {
+    var start, tTime;
+    start = +new Date();
+    this.draw();
+    this.throttleTime += +new Date() - start;
+    this.throttleCounts++;
+    if (this.throttleCounts > 15) {
+      tTime = Math.ceil(this.throttleTime / this.throttleCounts);
+      console.log("avgDrawTime", tTime);
+      tTime *= 1.2;
+      tTime = Math.max(20, tTime);
+      return this.throttledDraw = _.throttle(this.draw, tTime);
+    }
+  },
+  manageEvents: function() {
+    var events;
+    events = {};
+    events.mousedown = "_onmousedown";
+    events.touchstart = "_ontouchstart";
+    if (this.g.config.get("registerMouseClicks")) {
+      events.dblclick = "_onclick";
+    }
+    if (this.g.config.get("registerMouseHover")) {
+      events.mousein = "_onmousein";
+      events.mouseout = "_onmouseout";
+    }
+    events.mousewheel = "_onmousewheel";
+    events.DOMMouseScroll = "_onmousewheel";
+    this.delegateEvents(events);
+    this.listenTo(this.g.config, "change:registerMouseHover", this.manageEvents);
+    this.listenTo(this.g.config, "change:registerMouseClick", this.manageEvents);
+    return this.dragStart = [];
+  },
+  draw: function() {
+    var rectHeight;
+    this.el.width = this.el.width;
+    rectHeight = this.g.zoomer.get("rowHeight");
+    this.ctx.globalAlpha = this.g.colorscheme.get("opacity");
+    this.drawSeqs(function(data) {
+      return this.drawSeq(data, this._drawRect);
+    });
+    this.ctx.globalAlpha = 1;
+    this.drawSeqs(function(data) {
+      return this.drawSeq(data, this._drawLetter);
+    });
+    return this.drawSeqs(this.drawSeqExtended);
+  },
+  drawSeqs: function(callback) {
+    var hidden, i, rectHeight, start, y, _i, _ref, _results;
+    rectHeight = this.g.zoomer.get("rowHeight");
+    hidden = this.g.columns.get("hidden");
+    start = Math.max(0, Math.abs(Math.ceil(-this.g.zoomer.get('_alignmentScrollTop') / rectHeight)));
+    y = -Math.abs(-this.g.zoomer.get('_alignmentScrollTop') % rectHeight);
+    _results = [];
+    for (i = _i = start, _ref = this.model.length - 1; _i <= _ref; i = _i += 1) {
+      if (this.model.at(i).get('hidden')) {
+        continue;
+      }
+      callback.call(this, {
+        model: this.model.at(i),
+        y: y,
+        hidden: hidden
+      });
+      y = y + rectHeight;
+      if (y > this.el.height) {
+        break;
+      } else {
+        _results.push(void 0);
+      }
+    }
+    return _results;
+  },
+  drawSeq: function(data, callback) {
+    var c, elWidth, j, rectHeight, rectWidth, res, seq, start, x, y, _i, _ref, _results;
+    seq = data.model.get("seq");
+    y = data.y;
+    rectWidth = this.g.zoomer.get("columnWidth");
+    rectHeight = this.g.zoomer.get("rowHeight");
+    start = Math.max(0, Math.abs(Math.ceil(-this.g.zoomer.get('_alignmentScrollLeft') / rectWidth)));
+    x = -Math.abs(-this.g.zoomer.get('_alignmentScrollLeft') % rectWidth);
+    res = {
+      rectWidth: rectWidth,
+      rectHeight: rectHeight,
+      y: y
+    };
+    elWidth = this.el.width;
+    _results = [];
+    for (j = _i = start, _ref = seq.length - 1; _i <= _ref; j = _i += 1) {
+      c = seq[j];
+      c = c.toUpperCase();
+      res.x = x;
+      res.c = c;
+      if (data.hidden.indexOf(j) < 0) {
+        callback(this, res);
+      } else {
+        continue;
+      }
+      x = x + rectWidth;
+      if (x > elWidth) {
+        break;
+      } else {
+        _results.push(void 0);
+      }
+    }
+    return _results;
+  },
+  _drawRect: function(that, data) {
+    var color;
+    color = that.color[data.c];
+    if (color != null) {
+      that.ctx.fillStyle = color;
+      return that.ctx.fillRect(data.x, data.y, data.rectWidth, data.rectHeight);
+    }
+  },
+  _drawLetter: function(that, data) {
+    return that.ctx.drawImage(that.cache.getFontTile(data.c, data.rectWidth, data.rectHeight), data.x, data.y, data.rectWidth, data.rectHeight);
+  },
+  drawSeqExtended: function(data) {
+    var f, features, j, mNextSel, mPrevSel, rectHeight, rectWidth, selection, seq, start, starts, x, xZero, yZero, _i, _j, _len, _ref, _ref1;
+    seq = data.model.get("seq");
+    rectWidth = this.g.zoomer.get("columnWidth");
+    rectHeight = this.g.zoomer.get("rowHeight");
+    start = Math.max(0, Math.abs(Math.ceil(-this.g.zoomer.get('_alignmentScrollLeft') / rectWidth)));
+    x = -Math.abs(-this.g.zoomer.get('_alignmentScrollLeft') % rectWidth);
+    xZero = x - start * rectWidth;
+    selection = this._getSelection(data.model);
+    _ref = this._getPrevNextSelection(data.model), mPrevSel = _ref[0], mNextSel = _ref[1];
+    features = data.model.get("features");
+    yZero = data.y;
+    for (j = _i = start, _ref1 = seq.length - 1; _i <= _ref1; j = _i += 1) {
+      starts = features.startOn(j);
+      if (data.hidden.indexOf(j) >= 0) {
+        continue;
+      }
+      if (starts.length > 0) {
+        for (_j = 0, _len = starts.length; _j < _len; _j++) {
+          f = starts[_j];
+          this.appendFeature({
+            f: f,
+            xZero: x,
+            yZero: yZero
+          });
+        }
+      }
+      x = x + rectWidth;
+      if (x > this.el.width) {
+        break;
+      }
+    }
+    return this._appendSelection({
+      model: data.model,
+      xZero: xZero,
+      yZero: yZero,
+      hidden: data.hidden
+    });
+  },
+  render: function() {
+    this.el.setAttribute('height', this.g.zoomer.get("alignmentHeight"));
+    this.el.setAttribute('width', this.g.zoomer.get("alignmentWidth"));
+    this.g.zoomer._adjustWidth(this.el, this.model);
+    this.g.zoomer._checkScrolling(this._checkScrolling([this.g.zoomer.get('_alignmentScrollLeft'), this.g.zoomer.get('_alignmentScrollTop')]), {
+      header: "canvasseq"
+    });
+    this.color = colorSelector.getColor(this.g.colorscheme.get("scheme"));
+    this.throttledDraw();
+    return this;
+  },
+  _onmousemove: function(e, reversed) {
+    var dragEnd, i, relDist, relEnd, scaleFactor, scrollCorrected, _i, _j, _k;
+    if (this.dragStart.length === 0) {
+      return;
+    }
+    dragEnd = mouse.abs(e);
+    relEnd = [dragEnd[0] - this.dragStart[0], dragEnd[1] - this.dragStart[1]];
+    scaleFactor = this.g.zoomer.get("canvasEventScale");
+    if (reversed) {
+      scaleFactor = 3;
+    }
+    for (i = _i = 0; _i <= 1; i = _i += 1) {
+      relEnd[i] = relEnd[i] * scaleFactor;
+    }
+    relDist = [this.dragStartScroll[0] - relEnd[0], this.dragStartScroll[1] - relEnd[1]];
+    for (i = _j = 0; _j <= 1; i = _j += 1) {
+      relDist[i] = Math.round(relDist[i]);
+    }
+    scrollCorrected = this._checkScrolling(relDist);
+    this.g.zoomer._checkScrolling(scrollCorrected, {
+      origin: "canvasseq"
+    });
+    for (i = _k = 0; _k <= 1; i = _k += 1) {
+      if (scrollCorrected[i] !== relDist[i]) {
+        if (scrollCorrected[i] === 0) {
+          this.dragStart[i] = dragEnd[i];
+          this.dragStartScroll[i] = 0;
+        } else {
+          this.dragStart[i] = dragEnd[i] - scrollCorrected[i];
+        }
+      }
+    }
+    this.throttledDraw();
+    if (e.preventDefault != null) {
+      e.preventDefault();
+      return e.stopPropagation();
+    }
+  },
+  _ontouchmove: function(e) {
+    this._onmousemove(e.changedTouches[0], true);
+    e.preventDefault();
+    return e.stopPropagation();
+  },
+  _onmousedown: function(e) {
+    this.dragStart = mouse.abs(e);
+    this.dragStartScroll = [this.g.zoomer.get('_alignmentScrollLeft'), this.g.zoomer.get('_alignmentScrollTop')];
+    jbone(document.body).on('mousemove.overmove', (function(_this) {
+      return function(e) {
+        return _this._onmousemove(e);
+      };
+    })(this));
+    jbone(document.body).on('mouseup.overup', (function(_this) {
+      return function() {
+        return _this._cleanup();
+      };
+    })(this));
+    return e.preventDefault();
+  },
+  _ontouchstart: function(e) {
+    this.dragStart = mouse.abs(e.changedTouches[0]);
+    this.dragStartScroll = [this.g.zoomer.get('_alignmentScrollLeft'), this.g.zoomer.get('_alignmentScrollTop')];
+    jbone(document.body).on('touchmove.overtmove', (function(_this) {
+      return function(e) {
+        return _this._ontouchmove(e);
+      };
+    })(this));
+    return jbone(document.body).on('touchend.overtend touchleave.overtleave touchcancel.overtcanel', (function(_this) {
+      return function(e) {
+        return _this._touchCleanup(e);
+      };
+    })(this));
+  },
+  _onmousewinout: function(e) {
+    if (e.toElement === document.body.parentNode) {
+      return this._cleanup();
+    }
+  },
+  _cleanup: function() {
+    this.dragStart = [];
+    jbone(document.body).off('.overmove');
+    jbone(document.body).off('.overup');
+    return jbone(document.body).off('.overout');
+  },
+  _touchCleanup: function(e) {
+    if (e.changedTouches.length > 0) {
+      this._onmousemove(e.changedTouches[0], true);
+    }
+    this.dragStart = [];
+    jbone(document.body).off('.overtmove');
+    jbone(document.body).off('.overtend');
+    jbone(document.body).off('.overtleave');
+    return jbone(document.body).off('.overtcancel');
+  },
+  _onmousewheel: function(e) {
+    var delta;
+    delta = mouse.wheelDelta(e);
+    this.g.zoomer.set('_alignmentScrollLeft', this.g.zoomer.get('_alignmentScrollLeft') + delta[0]);
+    this.g.zoomer.set('_alignmentScrollTop', this.g.zoomer.get('_alignmentScrollTop') + delta[1]);
+    return e.preventDefault();
+  },
+  _onclick: function(e) {
+    this.g.trigger("residue:click", this._getClickPos(e));
+    return this.throttledDraw();
+  },
+  _onmousein: function(e) {
+    this.g.trigger("residue:click", this._getClickPos(e));
+    return this.throttledDraw();
+  },
+  _onmouseout: function(e) {
+    this.g.trigger("residue:click", this._getClickPos(e));
+    return this.throttledDraw();
+  },
+  _getClickPos: function(e) {
+    var coords, seqId, x, y;
+    coords = mouse.rel(e);
+    coords[0] += this.g.zoomer.get("_alignmentScrollLeft");
+    coords[1] += this.g.zoomer.get("_alignmentScrollTop");
+    x = Math.floor(coords[0] / this.g.zoomer.get("columnWidth"));
+    y = Math.floor(coords[1] / this.g.zoomer.get("rowHeight"));
+    x += this.g.columns.calcHiddenColumns(x);
+    y += this.model.calcHiddenSeqs(y);
+    x = Math.max(0, x);
+    y = Math.max(0, y);
+    seqId = this.model.at(y).get("id");
+    return {
+      seqId: seqId,
+      rowPos: x,
+      evt: e
+    };
+  },
+  _checkScrolling: function(scrollObj) {
+    var i, max, _i;
+    max = [this.model.getMaxLength() * this.g.zoomer.get("columnWidth") - this.g.zoomer.get('alignmentWidth'), this.model.length * this.g.zoomer.get("rowHeight") - this.g.zoomer.get('alignmentHeight')];
+    for (i = _i = 0; _i <= 1; i = _i += 1) {
+      if (scrollObj[i] > max[i]) {
+        scrollObj[i] = max[i];
+      }
+      if (scrollObj[i] < 0) {
+        scrollObj[i] = 0;
+      }
+    }
+    return scrollObj;
+  },
+  _getSelection: function(model) {
+    var maxLen, n, rows, sel, selection, sels, _i, _j, _k, _len, _ref, _ref1, _ref2;
+    maxLen = model.get("seq").length;
+    selection = [];
+    sels = this.g.selcol.getSelForRow(model.get("id"));
+    rows = _.find(sels, function(el) {
+      return el.get("type") === "row";
+    });
+    if (rows != null) {
+      for (n = _i = 0, _ref = maxLen - 1; _i <= _ref; n = _i += 1) {
+        selection.push(n);
+      }
+    } else if (sels.length > 0) {
+      for (_j = 0, _len = sels.length; _j < _len; _j++) {
+        sel = sels[_j];
+        for (n = _k = _ref1 = sel.get("xStart"), _ref2 = sel.get("xEnd"); _k <= _ref2; n = _k += 1) {
+          selection.push(n);
+        }
+      }
+    }
+    return selection;
+  },
+  appendFeature: function(data) {
+    var beforeStyle, beforeWidth, boxHeight, boxWidth, f, width;
+    f = data.f;
+    boxWidth = this.g.zoomer.get("columnWidth");
+    boxHeight = this.g.zoomer.get("rowHeight");
+    width = (f.get("xEnd") - f.get("xStart")) * boxWidth;
+    beforeWidth = this.ctx.lineWidth;
+    this.ctx.lineWidth = 3;
+    beforeStyle = this.ctx.strokeStyle;
+    this.ctx.strokeStyle = f.get("fillColor");
+    this.ctx.strokeRect(data.xZero, data.yZero, width, boxHeight);
+    this.ctx.strokeStyle = beforeStyle;
+    return this.ctx.lineWidth = beforeWidth;
+  },
+  _appendSelection: function(data) {
+    var boxHeight, boxWidth, hiddenOffset, k, mNextSel, mPrevSel, n, selection, seq, _i, _ref, _ref1, _results;
+    seq = data.model.get("seq");
+    selection = this._getSelection(data.model);
+    _ref = this._getPrevNextSelection(data.model), mPrevSel = _ref[0], mNextSel = _ref[1];
+    boxWidth = this.g.zoomer.get("columnWidth");
+    boxHeight = this.g.zoomer.get("rowHeight");
+    if (selection.length === 0) {
+      return;
+    }
+    hiddenOffset = 0;
+    _results = [];
+    for (n = _i = 0, _ref1 = seq.length - 1; _i <= _ref1; n = _i += 1) {
+      if (data.hidden.indexOf(n) >= 0) {
+        _results.push(hiddenOffset++);
+      } else {
+        k = n - hiddenOffset;
+        if (selection.indexOf(n) >= 0 && (k === 0 || selection.indexOf(n - 1) < 0)) {
+          _results.push(this._renderSelection({
+            n: n,
+            k: k,
+            selection: selection,
+            mPrevSel: mPrevSel,
+            mNextSel: mNextSel,
+            xZero: data.xZero,
+            yZero: data.yZero,
+            model: data.model
+          }));
+        } else {
+          _results.push(void 0);
+        }
+      }
+    }
+    return _results;
+  },
+  _renderSelection: function(data) {
+    var beforeStyle, beforeWidth, boxHeight, boxWidth, hidden, i, k, mNextSel, mPrevSel, n, selection, selectionLength, totalWidth, xPart, xPos, xZero, yZero, _i, _j, _ref, _ref1;
+    xZero = data.xZero;
+    yZero = data.yZero;
+    n = data.n;
+    k = data.k;
+    selection = data.selection;
+    mPrevSel = data.mPrevSel;
+    mNextSel = data.mNextSel;
+    selectionLength = 0;
+    for (i = _i = n, _ref = data.model.get("seq").length - 1; _i <= _ref; i = _i += 1) {
+      if (selection.indexOf(i) >= 0) {
+        selectionLength++;
+      } else {
+        break;
+      }
+    }
+    boxWidth = this.g.zoomer.get("columnWidth");
+    boxHeight = this.g.zoomer.get("rowHeight");
+    totalWidth = (boxWidth * selectionLength) + 1;
+    hidden = this.g.columns.get('hidden');
+    this.ctx.beginPath();
+    beforeWidth = this.ctx.lineWidth;
+    this.ctx.lineWidth = 3;
+    beforeStyle = this.ctx.strokeStyle;
+    this.ctx.strokeStyle = "#FF0000";
+    xZero += k * boxWidth;
+    xPart = 0;
+    for (i = _j = 0, _ref1 = selectionLength - 1; 0 <= _ref1 ? _j <= _ref1 : _j >= _ref1; i = 0 <= _ref1 ? ++_j : --_j) {
+      xPos = n + i;
+      if (hidden.indexOf(xPos) >= 0) {
+        continue;
+      }
+      if (!((mPrevSel != null) && mPrevSel.indexOf(xPos) >= 0)) {
+        this.ctx.moveTo(xZero + xPart, yZero);
+        this.ctx.lineTo(xPart + boxWidth + xZero, yZero);
+      }
+      if (!((mNextSel != null) && mNextSel.indexOf(xPos) >= 0)) {
+        this.ctx.moveTo(xPart + xZero, boxHeight + yZero);
+        this.ctx.lineTo(xPart + boxWidth + xZero, boxHeight + yZero);
+      }
+      xPart += boxWidth;
+    }
+    this.ctx.moveTo(xZero, yZero);
+    this.ctx.lineTo(xZero, boxHeight + yZero);
+    this.ctx.moveTo(xZero + totalWidth, yZero);
+    this.ctx.lineTo(xZero + totalWidth, boxHeight + yZero);
+    this.ctx.stroke();
+    this.ctx.strokeStyle = beforeStyle;
+    return this.ctx.lineWidth = beforeWidth;
+  },
+  _getPrevNextSelection: function(model) {
+    var mNextSel, mPrevSel, modelNext, modelPrev;
+    modelPrev = model.collection.prev(model);
+    modelNext = model.collection.next(model);
+    if (modelPrev != null) {
+      mPrevSel = this._getSelection(modelPrev);
+    }
+    if (modelNext != null) {
+      mNextSel = this._getSelection(modelNext);
+    }
+    return [mPrevSel, mNextSel];
+  }
+});
+
+
+
+},{"./CanvasCharCache":97,"backbone-childs":3,"biojs-util-colorschemes":29,"jbone":50,"mouse-pos":51,"underscore":59}],99:[function(require,module,exports){
+var OverviewBox, colorSelector, jbone, mouse, selection, view, _;
+
+view = require("backbone-viewj");
+
+mouse = require("mouse-pos");
+
+selection = require("../g/selection/Selection");
+
+colorSelector = require("biojs-util-colorschemes").selector;
+
+jbone = require("jbone");
+
+_ = require("underscore");
+
+module.exports = OverviewBox = view.extend({
+  className: "biojs_msa_overviewbox",
+  tagName: "canvas",
+  initialize: function(data) {
+    this.g = data.g;
+    this.listenTo(this.g.zoomer, "change:boxRectWidth change:boxRectHeight", this.render);
+    this.listenTo(this.g.selcol, "add reset change", this.render);
+    this.listenTo(this.g.columns, "change:hidden", this.render);
+    this.listenTo(this.g.colorscheme, "change:showLowerCase", this.render);
+    this.listenTo(this.model, "change", _.debounce(this.render, 5));
+    this.color = colorSelector.getColor(this.g.colorscheme.get("scheme"));
+    this.listenTo(this.g.colorscheme, "change:scheme", function() {
+      this.color = colorSelector.getColor(this.g.colorscheme.get("scheme"));
+      return this.render();
+    });
+    return this.dragStart = [];
+  },
+  events: {
+    click: "_onclick",
+    mousedown: "_onmousedown"
+  },
+  render: function() {
+    var c, color, hidden, i, j, rectHeight, rectWidth, seq, showLowerCase, x, y, _i, _j, _ref, _ref1;
+    this._createCanvas();
+    this.el.textContent = "overview";
+    this.ctx.fillStyle = "#999999";
+    this.ctx.fillRect(0, 0, this.el.width, this.el.height);
+    rectWidth = this.g.zoomer.get("boxRectWidth");
+    rectHeight = this.g.zoomer.get("boxRectHeight");
+    hidden = this.g.columns.get("hidden");
+    showLowerCase = this.g.colorscheme.get("showLowerCase");
+    y = -rectHeight;
+    for (i = _i = 0, _ref = this.model.length - 1; _i <= _ref; i = _i += 1) {
+      seq = this.model.at(i).get("seq");
+      x = 0;
+      y = y + rectHeight;
+      if (this.model.at(i).get("hidden")) {
+        console.log(this.model.at(i).get("hidden"));
+        this.ctx.fillStyle = "grey";
+        this.ctx.fillRect(0, y, seq.length * rectWidth, rectHeight);
+        continue;
+      }
+      for (j = _j = 0, _ref1 = seq.length - 1; _j <= _ref1; j = _j += 1) {
+        c = seq[j];
+        if (showLowerCase) {
+          c = c.toUpperCase();
+        }
+        color = this.color[c];
+        if (hidden.indexOf(j) >= 0) {
+          color = "grey";
+        }
+        if (color != null) {
+          this.ctx.fillStyle = color;
+          this.ctx.fillRect(x, y, rectWidth, rectHeight);
+        }
+        x = x + rectWidth;
+      }
+    }
+    return this._drawSelection();
+  },
+  _drawSelection: function() {
+    var i, maxHeight, pos, rectHeight, rectWidth, sel, seq, _i, _ref;
+    if (this.dragStart.length > 0 && !this.prolongSelection) {
+      return;
+    }
+    rectWidth = this.g.zoomer.get("boxRectWidth");
+    rectHeight = this.g.zoomer.get("boxRectHeight");
+    maxHeight = rectHeight * this.model.length;
+    this.ctx.fillStyle = "#ffff00";
+    this.ctx.globalAlpha = 0.9;
+    for (i = _i = 0, _ref = this.g.selcol.length - 1; _i <= _ref; i = _i += 1) {
+      sel = this.g.selcol.at(i);
+      if (sel.get('type') === 'column') {
+        this.ctx.fillRect(rectWidth * sel.get('xStart'), 0, rectWidth * (sel.get('xEnd') - sel.get('xStart') + 1), maxHeight);
+      } else if (sel.get('type') === 'row') {
+        seq = (this.model.filter(function(el) {
+          return el.get('id') === sel.get('seqId');
+        }))[0];
+        pos = this.model.indexOf(seq);
+        this.ctx.fillRect(0, rectHeight * pos, rectWidth * seq.get('seq').length, rectHeight);
+      } else if (sel.get('type') === 'pos') {
+        seq = (this.model.filter(function(el) {
+          return el.get('id') === sel.get('seqId');
+        }))[0];
+        pos = this.model.indexOf(seq);
+        this.ctx.fillRect(rectWidth * sel.get('xStart'), rectHeight * pos, rectWidth * (sel.get('xEnd') - sel.get('xStart') + 1), rectHeight);
+      }
+    }
+    return this.ctx.globalAlpha = 1;
+  },
+  _onclick: function(evt) {
+    return this.g.trigger("meta:click", {
+      seqId: this.model.get("id", {
+        evt: evt
+      })
+    });
+  },
+  _onmousemove: function(e) {
+    var rect;
+    if (this.dragStart.length === 0) {
+      return;
+    }
+    this.render();
+    this.ctx.fillStyle = "#ffff00";
+    this.ctx.globalAlpha = 0.9;
+    rect = this._calcSelection(mouse.abs(e));
+    this.ctx.fillRect(rect[0][0], rect[1][0], rect[0][1] - rect[0][0], rect[1][1] - rect[1][0]);
+    e.preventDefault();
+    return e.stopPropagation();
+  },
+  _onmousedown: function(e) {
+    this.dragStart = mouse.abs(e);
+    this.dragStartRel = mouse.rel(e);
+    if (e.ctrlKey || e.metaKey) {
+      this.prolongSelection = true;
+    } else {
+      this.prolongSelection = false;
+    }
+    jbone(document.body).on('mousemove.overmove', (function(_this) {
+      return function(e) {
+        return _this._onmousemove(e);
+      };
+    })(this));
+    jbone(document.body).on('mouseup.overup', (function(_this) {
+      return function(e) {
+        return _this._onmouseup(e);
+      };
+    })(this));
+    return this.dragStart;
+  },
+  _calcSelection: function(dragMove) {
+    var dragRel, i, rect, _i, _j;
+    dragRel = [dragMove[0] - this.dragStart[0], dragMove[1] - this.dragStart[1]];
+    for (i = _i = 0; _i <= 1; i = _i += 1) {
+      dragRel[i] = this.dragStartRel[i] + dragRel[i];
+    }
+    rect = [[this.dragStartRel[0], dragRel[0]], [this.dragStartRel[1], dragRel[1]]];
+    for (i = _j = 0; _j <= 1; i = _j += 1) {
+      if (rect[i][1] < rect[i][0]) {
+        rect[i] = [rect[i][1], rect[i][0]];
+      }
+      rect[i][0] = Math.max(rect[i][0], 0);
+    }
+    return rect;
+  },
+  _endSelection: function(dragEnd) {
+    var args, i, j, rect, selis, _i, _j, _k, _ref, _ref1;
+    jbone(document.body).off('.overmove');
+    jbone(document.body).off('.overup');
+    if (this.dragStart.length === 0) {
+      return;
+    }
+    rect = this._calcSelection(dragEnd);
+    for (i = _i = 0; _i <= 1; i = ++_i) {
+      rect[0][i] = Math.floor(rect[0][i] / this.g.zoomer.get("boxRectWidth"));
+    }
+    for (i = _j = 0; _j <= 1; i = ++_j) {
+      rect[1][i] = Math.floor(rect[1][i] / this.g.zoomer.get("boxRectHeight"));
+    }
+    rect[0][1] = Math.min(this.model.getMaxLength() - 1, rect[0][1]);
+    rect[1][1] = Math.min(this.model.length - 1, rect[1][1]);
+    selis = [];
+    for (j = _k = _ref = rect[1][0], _ref1 = rect[1][1]; _k <= _ref1; j = _k += 1) {
+      args = {
+        seqId: this.model.at(j).get('id'),
+        xStart: rect[0][0],
+        xEnd: rect[0][1]
+      };
+      selis.push(new selection.possel(args));
+    }
+    this.dragStart = [];
+    if (this.prolongSelection) {
+      this.g.selcol.add(selis);
+    } else {
+      this.g.selcol.reset(selis);
+    }
+    this.g.zoomer.setLeftOffset(rect[0][0]);
+    return this.g.zoomer.setTopOffset(rect[1][0]);
+  },
+  _onmouseup: function(e) {
+    return this._endSelection(mouse.abs(e));
+  },
+  _onmouseout: function(e) {
+    return this._endSelection(mouse.abs(e));
+  },
+  _createCanvas: function() {
+    var rectHeight, rectWidth;
+    rectWidth = this.g.zoomer.get("boxRectWidth");
+    rectHeight = this.g.zoomer.get("boxRectHeight");
+    this.el.height = this.model.length * rectHeight;
+    this.el.width = this.model.getMaxLength() * rectWidth;
+    this.ctx = this.el.getContext("2d");
+    this.el.style.overflow = "scroll";
+    return this.el.style.cursor = "crosshair";
+  }
+});
+
+
+
+},{"../g/selection/Selection":67,"backbone-viewj":10,"biojs-util-colorschemes":29,"jbone":50,"mouse-pos":51,"underscore":59}],100:[function(require,module,exports){
+var AlignmentBody, HeaderBlock, OverviewBox, boneView, identityCalc, _;
+
+boneView = require("backbone-childs");
+
+AlignmentBody = require("./AlignmentBody");
+
+HeaderBlock = require("./header/HeaderBlock");
+
+OverviewBox = require("./OverviewBox");
+
+identityCalc = require("../algo/identityCalc");
+
+_ = require('underscore');
+
+module.exports = boneView.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    this.draw();
+    this.listenTo(this.model, "reset", function() {
+      this.isNotDirty = false;
+      return this.rerender();
+    });
+    this.listenTo(this.model, "change:hidden", _.debounce(this.rerender, 10));
+    this.listenTo(this.model, "sort", this.rerender);
+    this.listenTo(this.model, "add", function() {
+      return console.log("seq add");
+    });
+    this.listenTo(this.g.vis, "change:sequences", this.rerender);
+    this.listenTo(this.g.vis, "change:overviewbox", this.rerender);
+    return this.listenTo(this.g.visorder, "change", this.rerender);
+  },
+  draw: function() {
+    var body, consensus, headerblock, overviewbox;
+    this.removeViews();
+    if (!this.isNotDirty) {
+      consensus = this.g.consensus.getConsensus(this.model);
+      identityCalc(this.model, consensus);
+      this.isNotDirty = true;
+    }
+    if (this.g.vis.get("overviewbox")) {
+      overviewbox = new OverviewBox({
+        model: this.model,
+        g: this.g
+      });
+      overviewbox.ordering = this.g.visorder.get('overviewBox');
+      this.addView("overviewbox", overviewbox);
+    }
+    if (true) {
+      headerblock = new HeaderBlock({
+        model: this.model,
+        g: this.g
+      });
+      headerblock.ordering = this.g.visorder.get('headerBox');
+      this.addView("headerblock", headerblock);
+    }
+    body = new AlignmentBody({
+      model: this.model,
+      g: this.g
+    });
+    body.ordering = this.g.visorder.get('alignmentBody');
+    return this.addView("body", body);
+  },
+  render: function() {
+    this.renderSubviews();
+    this.el.className = "biojs_msa_stage";
+    return this;
+  },
+  rerender: function() {
+    this.draw();
+    return this.render();
+  }
+});
+
+
+
+},{"../algo/identityCalc":61,"./AlignmentBody":96,"./OverviewBox":99,"./header/HeaderBlock":102,"backbone-childs":3,"underscore":59}],101:[function(require,module,exports){
+var ConservationView, dom, svg, view;
+
+view = require("backbone-viewj");
+
+dom = require("dom-helper");
+
+svg = require("../../utils/svg");
+
+ConservationView = view.extend({
+  className: "biojs_msa_conserv",
+  initialize: function(data) {
+    this.g = data.g;
+    this.listenTo(this.g.zoomer, "change:stepSize change:labelWidth change:columnWidth", this.render);
+    this.listenTo(this.g.vis, "change:labels change:metacell", this.render);
+    this.listenTo(this.g.columns, "change:scaling", this.render);
+    this.listenTo(this.model, "reset", this.render);
+    return this.manageEvents();
+  },
+  render: function() {
+    var avgHeight, cellWidth, height, hidden, i, maxHeight, n, nMax, rect, s, stepSize, width, x, _i, _ref;
+    this.g.columns.calcConservation(this.model);
+    dom.removeAllChilds(this.el);
+    nMax = this.model.getMaxLength();
+    cellWidth = this.g.zoomer.get("columnWidth");
+    maxHeight = 20;
+    width = cellWidth * (nMax - this.g.columns.get('hidden').length);
+    console.log(this.g.columns.get('hidden'));
+    s = svg.base({
+      height: maxHeight,
+      width: width
+    });
+    s.style.display = "inline-block";
+    s.style.cursor = "pointer";
+    stepSize = this.g.zoomer.get("stepSize");
+    hidden = this.g.columns.get("hidden");
+    x = 0;
+    n = 0;
+    while (n < nMax) {
+      if (hidden.indexOf(n) >= 0) {
+        n += stepSize;
+        continue;
+      }
+      width = cellWidth * stepSize;
+      avgHeight = 0;
+      for (i = _i = 0, _ref = stepSize - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
+        avgHeight += this.g.columns.get("conserv")[n];
+      }
+      height = maxHeight * (avgHeight / stepSize);
+      rect = svg.rect({
+        x: x,
+        y: maxHeight - height,
+        width: width - cellWidth / 4,
+        height: height,
+        style: "stroke:red;stroke-width:1;"
+      });
+      rect.rowPos = n;
+      s.appendChild(rect);
+      x += width;
+      n += stepSize;
+    }
+    this.el.appendChild(s);
+    return this;
+  },
+  _onclick: function(evt) {
+    var i, rowPos, stepSize, _i, _ref, _results;
+    rowPos = evt.target.rowPos;
+    stepSize = this.g.zoomer.get("stepSize");
+    _results = [];
+    for (i = _i = 0, _ref = stepSize - 1; _i <= _ref; i = _i += 1) {
+      _results.push(this.g.trigger("bar:click", {
+        rowPos: rowPos + i,
+        evt: evt
+      }));
+    }
+    return _results;
+  },
+  manageEvents: function() {
+    var events;
+    events = {};
+    if (this.g.config.get("registerMouseClicks")) {
+      events.click = "_onclick";
+    }
+    if (this.g.config.get("registerMouseHover")) {
+      events.mousein = "_onmousein";
+      events.mouseout = "_onmouseout";
+    }
+    this.delegateEvents(events);
+    this.listenTo(this.g.config, "change:registerMouseHover", this.manageEvents);
+    return this.listenTo(this.g.config, "change:registerMouseClick", this.manageEvents);
+  },
+  _onmousein: function(evt) {
+    var rowPos;
+    rowPos = this.g.zoomer.get("stepSize" * evt.rowPos);
+    return this.g.trigger("bar:mousein", {
+      rowPos: rowPos,
+      evt: evt
+    });
+  },
+  _onmouseout: function(evt) {
+    var rowPos;
+    rowPos = this.g.zoomer.get("stepSize" * evt.rowPos);
+    return this.g.trigger("bar:mouseout", {
+      rowPos: rowPos,
+      evt: evt
+    });
+  }
+});
+
+module.exports = ConservationView;
+
+
+
+},{"../../utils/svg":95,"backbone-viewj":10,"dom-helper":49}],102:[function(require,module,exports){
+var ConservationView, MarkerView, boneView, identityCalc, _;
+
+MarkerView = require("./MarkerView");
+
+ConservationView = require("./ConservationView");
+
+identityCalc = require("../../algo/identityCalc");
+
+boneView = require("backbone-childs");
+
+_ = require('underscore');
+
+module.exports = boneView.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    this.blockEvents = false;
+    this.listenTo(this.g.vis, "change:markers change:conserv", function() {
+      this.draw();
+      return this.render();
+    });
+    this.listenTo(this.g.vis, "change", this._setSpacer);
+    this.listenTo(this.g.zoomer, "change:alignmentWidth", function() {
+      return this._adjustWidth();
+    });
+    this.listenTo(this.g.zoomer, "change:_alignmentScrollLeft", this._adjustScrollingLeft);
+    this.listenTo(this.g.columns, "change:hidden", function() {
+      this.draw();
+      return this.render();
+    });
+    this.draw();
+    this._onscroll = this._sendScrollEvent;
+    return this.g.vis.once('change:loaded', this._adjustScrollingLeft, this);
+  },
+  events: {
+    "scroll": "_onscroll"
+  },
+  draw: function() {
+    var consensus, conserv, marker;
+    this.removeViews();
+    if (!this.isNotDirty) {
+      consensus = this.g.consensus.getConsensus(this.model);
+      identityCalc(this.model, consensus);
+      this.isNotDirty = true;
+    }
+    if (this.g.vis.get("conserv")) {
+      conserv = new ConservationView({
+        model: this.model,
+        g: this.g
+      });
+      conserv.ordering = -20;
+      this.addView("conserv", conserv);
+    }
+    if (this.g.vis.get("markers")) {
+      marker = new MarkerView({
+        model: this.model,
+        g: this.g
+      });
+      marker.ordering = -10;
+      return this.addView("marker", marker);
+    }
+  },
+  render: function() {
+    this.renderSubviews();
+    this._setSpacer();
+    this.el.className = "biojs_msa_header";
+    this.el.style.overflowX = "auto";
+    this._adjustWidth();
+    this._adjustScrollingLeft();
+    return this;
+  },
+  _sendScrollEvent: function() {
+    if (!this.blockEvents) {
+      this.g.zoomer.set("_alignmentScrollLeft", this.el.scrollLeft, {
+        origin: "header"
+      });
+    }
+    return this.blockEvents = false;
+  },
+  _adjustScrollingLeft: function(model, value, options) {
+    var scrollLeft;
+    if (((options != null ? options.origin : void 0) == null) || options.origin !== "header") {
+      scrollLeft = this.g.zoomer.get("_alignmentScrollLeft");
+      this.blockEvents = true;
+      return this.el.scrollLeft = scrollLeft;
+    }
+  },
+  _setSpacer: function() {
+    return this.el.style.marginLeft = this._getLabelWidth() + "px";
+  },
+  _getLabelWidth: function() {
+    var paddingLeft;
+    paddingLeft = 0;
+    if (this.g.vis.get("labels")) {
+      paddingLeft += this.g.zoomer.get("labelWidth");
+    }
+    if (this.g.vis.get("metacell")) {
+      paddingLeft += this.g.zoomer.get("metaWidth");
+    }
+    return paddingLeft;
+  },
+  _adjustWidth: function() {
+    return this.el.style.width = this.g.zoomer.get("alignmentWidth") + "px";
+  }
+});
+
+
+
+},{"../../algo/identityCalc":61,"./ConservationView":101,"./MarkerView":103,"backbone-childs":3,"underscore":59}],103:[function(require,module,exports){
+var HeaderView, dom, jbone, svg, view;
+
+view = require("backbone-viewj");
+
+dom = require("dom-helper");
+
+svg = require("../../utils/svg");
+
+jbone = require("jbone");
+
+HeaderView = view.extend({
+  className: "biojs_msa_marker",
+  initialize: function(data) {
+    this.g = data.g;
+    this.listenTo(this.g.zoomer, "change:stepSize change:labelWidth change:columnWidth change:markerStepSize change:markerFontsize", this.render);
+    this.listenTo(this.g.vis, "change:labels change:metacell", this.render);
+    return this.manageEvents();
+  },
+  render: function() {
+    var cellWidth, container, hidden, n, nMax, span, stepSize;
+    dom.removeAllChilds(this.el);
+    this.el.style.fontSize = this.g.zoomer.get("markerFontsize");
+    container = document.createElement("span");
+    n = 0;
+    cellWidth = this.g.zoomer.get("columnWidth");
+    nMax = this.model.getMaxLength();
+    stepSize = this.g.zoomer.get("stepSize");
+    hidden = this.g.columns.get("hidden");
+    while (n < nMax) {
+      if (hidden.indexOf(n) >= 0) {
+        this.markerHidden(span, n, stepSize);
+        n += stepSize;
+        continue;
+      }
+      span = document.createElement("span");
+      span.style.width = (cellWidth * stepSize) + "px";
+      span.style.display = "inline-block";
+      if ((n + 1) % this.g.zoomer.get('markerStepSize') === 0) {
+        span.textContent = n + 1;
+      } else {
+        span.textContent = ".";
+      }
+      span.rowPos = n;
+      n += stepSize;
+      container.appendChild(span);
+    }
+    this.el.appendChild(container);
+    return this;
+  },
+  markerHidden: function(span, n, stepSize) {
+    var hidden, index, j, length, min, nMax, prevHidden, s, triangle, _i, _j;
+    hidden = this.g.columns.get("hidden").slice(0);
+    min = Math.max(0, n - stepSize);
+    prevHidden = true;
+    for (j = _i = min; _i <= n; j = _i += 1) {
+      prevHidden &= hidden.indexOf(j) >= 0;
+    }
+    if (prevHidden) {
+      return;
+    }
+    nMax = this.model.getMaxLength();
+    length = 0;
+    index = -1;
+    for (n = _j = n; _j <= nMax; n = _j += 1) {
+      if (!(index >= 0)) {
+        index = hidden.indexOf(n);
+      }
+      if (hidden.indexOf(n) >= 0) {
+        length++;
+      } else {
+        break;
+      }
+    }
+    s = svg.base({
+      height: 10,
+      width: 10
+    });
+    s.style.position = "relative";
+    triangle = svg.polygon({
+      points: "0,0 5,5 10,0",
+      style: "fill:lime;stroke:purple;stroke-width:1"
+    });
+    jbone(triangle).on("click", (function(_this) {
+      return function(evt) {
+        hidden.splice(index, length);
+        return _this.g.columns.set("hidden", hidden);
+      };
+    })(this));
+    s.appendChild(triangle);
+    span.appendChild(s);
+    return s;
+  },
+  manageEvents: function() {
+    var events;
+    events = {};
+    if (this.g.config.get("registerMouseClicks")) {
+      events.click = "_onclick";
+    }
+    if (this.g.config.get("registerMouseHover")) {
+      events.mousein = "_onmousein";
+      events.mouseout = "_onmouseout";
+    }
+    this.delegateEvents(events);
+    this.listenTo(this.g.config, "change:registerMouseHover", this.manageEvents);
+    return this.listenTo(this.g.config, "change:registerMouseClick", this.manageEvents);
+  },
+  _onclick: function(evt) {
+    var rowPos, stepSize;
+    rowPos = evt.target.rowPos;
+    stepSize = this.g.zoomer.get("stepSize");
+    return this.g.trigger("column:click", {
+      rowPos: rowPos,
+      stepSize: stepSize,
+      evt: evt
+    });
+  },
+  _onmousein: function(evt) {
+    var rowPos, stepSize;
+    rowPos = this.g.zoomer.get("stepSize" * evt.rowPos);
+    stepSize = this.g.zoomer.get("stepSize");
+    return this.g.trigger("column:mousein", {
+      rowPos: rowPos,
+      stepSize: stepSize,
+      evt: evt
+    });
+  },
+  _onmouseout: function(evt) {
+    var rowPos, stepSize;
+    rowPos = this.g.zoomer.get("stepSize" * evt.rowPos);
+    stepSize = this.g.zoomer.get("stepSize");
+    return this.g.trigger("column:mouseout", {
+      rowPos: rowPos,
+      stepSize: stepSize,
+      evt: evt
+    });
+  }
+});
+
+module.exports = HeaderView;
+
+
+
+},{"../../utils/svg":95,"backbone-viewj":10,"dom-helper":49,"jbone":50}],104:[function(require,module,exports){
+var LabelRowView, boneView;
+
+LabelRowView = require("./LabelRowView");
+
+boneView = require("backbone-childs");
+
+module.exports = boneView.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    this.draw();
+    this.listenTo(this.g.zoomer, "change:_alignmentScrollTop", this._adjustScrollingTop);
+    return this.g.vis.once('change:loaded', this._adjustScrollingTop, this);
+  },
+  draw: function() {
+    var i, view, _i, _ref, _results;
+    this.removeViews();
+    _results = [];
+    for (i = _i = 0, _ref = this.model.length - 1; _i <= _ref; i = _i += 1) {
+      if (this.model.at(i).get('hidden')) {
+        continue;
+      }
+      view = new LabelRowView({
+        model: this.model.at(i),
+        g: this.g
+      });
+      view.ordering = i;
+      _results.push(this.addView("row_" + i, view));
+    }
+    return _results;
+  },
+  events: {
+    "scroll": "_sendScrollEvent"
+  },
+  _sendScrollEvent: function() {
+    return this.g.zoomer.set("_alignmentScrollTop", this.el.scrollTop, {
+      origin: "label"
+    });
+  },
+  _adjustScrollingTop: function() {
+    return this.el.scrollTop = this.g.zoomer.get("_alignmentScrollTop");
+  },
+  render: function() {
+    this.renderSubviews();
+    this.el.className = "biojs_msa_labelblock";
+    this.el.style.display = "inline-block";
+    this.el.style.verticalAlign = "top";
+    this.el.style.height = this.g.zoomer.get("alignmentHeight") + "px";
+    this.el.style.overflowY = "auto";
+    this.el.style.overflowX = "hidden";
+    this.el.style.fontSize = "" + (this.g.zoomer.get("labelFontsize"));
+    this.el.style.lineHeight = "" + (this.g.zoomer.get("labelLineHeight"));
+    return this;
+  }
+});
+
+
+
+},{"./LabelRowView":105,"backbone-childs":3}],105:[function(require,module,exports){
+var LabelView, MetaView, boneView;
+
+boneView = require("backbone-childs");
+
+LabelView = require("./LabelView");
+
+MetaView = require("./MetaView");
+
+module.exports = boneView.extend({
+  initialize: function(data) {
+    this.g = data.g;
+    this.draw();
+    this.listenTo(this.g.vis, "change:labels", this.drawR);
+    return this.listenTo(this.g.vis, "change:metacell", this.drawR);
+  },
+  draw: function() {
+    this.removeViews();
+    if (this.g.vis.get("labels")) {
+      this.addView("labels", new LabelView({
+        model: this.model,
+        g: this.g
+      }));
+    }
+    if (this.g.vis.get("metacell")) {
+      return this.addView("metacell", new MetaView({
+        model: this.model,
+        g: this.g
+      }));
+    }
+  },
+  drawR: function() {
+    this.draw();
+    return this.render();
+  },
+  render: function() {
+    this.renderSubviews();
+    this.el.setAttribute("class", "biojs_msa_labelrow");
+    this.el.style.height = this.g.zoomer.get("rowHeight");
+    return this;
+  }
+});
+
+
+
+},{"./LabelView":106,"./MetaView":107,"backbone-childs":3}],106:[function(require,module,exports){
+var LabelView, dom, view;
+
+view = require("backbone-viewj");
+
+dom = require("dom-helper");
+
+LabelView = view.extend({
+  initialize: function(data) {
+    this.seq = data.seq;
+    this.g = data.g;
+    return this.manageEvents();
+  },
+  manageEvents: function() {
+    var events;
+    events = {};
+    if (this.g.config.get("registerMouseClicks")) {
+      events.click = "_onclick";
+    }
+    if (this.g.config.get("registerMouseHover")) {
+      events.mousein = "_onmousein";
+      events.mouseout = "_onmouseout";
+    }
+    this.delegateEvents(events);
+    this.listenTo(this.g.config, "change:registerMouseHover", this.manageEvents);
+    this.listenTo(this.g.config, "change:registerMouseClick", this.manageEvents);
+    this.listenTo(this.g.vis, "change:labelName", this.render);
+    this.listenTo(this.g.vis, "change:labelId", this.render);
+    this.listenTo(this.g.vis, "change:labelPartition", this.render);
+    return this.listenTo(this.g.vis, "change:labelCheckbox", this.render);
+  },
+  render: function() {
+    var checkBox, id, name, part;
+    dom.removeAllChilds(this.el);
+    this.el.style.width = "" + (this.g.zoomer.get("labelWidth")) + "px";
+    this.el.style.height = "" + (this.g.zoomer.get("rowHeight")) + "px";
+    this.el.setAttribute("class", "biojs_msa_labels");
+    if (this.g.vis.get("labelCheckbox")) {
+      checkBox = document.createElement("input");
+      checkBox.setAttribute("type", "checkbox");
+      checkBox.value = this.model.get('id');
+      checkBox.name = "seq";
+      this.el.appendChild(checkBox);
+    }
+    if (this.g.vis.get("labelId")) {
+      id = document.createElement("span");
+      id.textContent = this.model.get("id");
+      id.style.width = this.g.zoomer.get("labelIdLength");
+      id.style.display = "inline-block";
+      this.el.appendChild(id);
+    }
+    if (this.g.vis.get("labelPartition")) {
+      part = document.createElement("span");
+      part.style.width = 15;
+      part.textContent = this.model.get("partition");
+      part.style.display = "inline-block";
+      this.el.appendChild(id);
+      this.el.appendChild(part);
+    }
+    if (this.g.vis.get("labelName")) {
+      name = document.createElement("span");
+      name.textContent = this.model.get("name");
+      this.el.appendChild(name);
+    }
+    this.el.style.overflow = scroll;
+    return this;
+  },
+  _onclick: function(evt) {
+    var seqId;
+    seqId = this.model.get("id");
+    return this.g.trigger("row:click", {
+      seqId: seqId,
+      evt: evt
+    });
+  },
+  _onmousein: function(evt) {
+    var seqId;
+    seqId = this.model.get("id");
+    return this.g.trigger("row:mouseout", {
+      seqId: seqId,
+      evt: evt
+    });
+  },
+  _onmouseout: function(evt) {
+    var seqId;
+    seqId = this.model.get("id");
+    return this.g.trigger("row:mouseout", {
+      seqId: seqId,
+      evt: evt
+    });
+  }
+});
+
+module.exports = LabelView;
+
+
+
+},{"backbone-viewj":10,"dom-helper":49}],107:[function(require,module,exports){
+var MenuBuilder, MetaView, dom, view, _;
+
+view = require("backbone-viewj");
+
+MenuBuilder = require("../../menu/menubuilder");
+
+_ = require('underscore');
+
+dom = require("dom-helper");
+
+module.exports = MetaView = view.extend({
+  className: "biojs_msa_metaview",
+  initialize: function(data) {
+    return this.g = data.g;
+  },
+  events: {
+    click: "_onclick",
+    mousein: "_onmousein",
+    mouseout: "_onmouseout"
+  },
+  render: function() {
+    var gapSpan, gaps, ident, identSpan, menu, seq, width;
+    dom.removeAllChilds(this.el);
+    this.el.style.display = "inline-block";
+    width = this.g.zoomer.get("metaWidth");
+    this.el.style.width = width - 5;
+    this.el.style.paddingRight = 5;
+    seq = this.model.get('seq');
+    gaps = _.reduce(seq, (function(memo, c) {
+      if (c === '-') {
+        memo++;
+      }
+      return memo;
+    }), 0);
+    gaps = (gaps / seq.length).toFixed(1);
+    gapSpan = document.createElement('span');
+    gapSpan.textContent = gaps;
+    gapSpan.style.display = "inline-block";
+    gapSpan.style.width = 35;
+    this.el.appendChild(gapSpan);
+    ident = this.model.get('identity');
+    identSpan = document.createElement('span');
+    identSpan.textContent = ident.toFixed(2);
+    identSpan.style.display = "inline-block";
+    identSpan.style.width = 40;
+    this.el.appendChild(identSpan);
+    menu = new MenuBuilder("↗");
+    menu.addNode("Uniprot", (function(_this) {
+      return function(e) {
+        return window.open("http://beta.uniprot.org/uniprot/Q7T2N8");
+      };
+    })(this));
+    this.el.appendChild(menu.buildDOM());
+    this.el.width = 10;
+    this.el.style.height = "" + (this.g.zoomer.get("rowHeight")) + "px";
+    return this.el.style.cursor = "pointer";
+  },
+  _onclick: function(evt) {
+    return this.g.trigger("meta:click", {
+      seqId: this.model.get("id", {
+        evt: evt
+      })
+    });
+  },
+  _onmousein: function(evt) {
+    return this.g.trigger("meta:mousein", {
+      seqId: this.model.get("id", {
+        evt: evt
+      })
+    });
+  },
+  _onmouseout: function(evt) {
+    return this.g.trigger("meta:mouseout", {
+      seqId: this.model.get("id", {
+        evt: evt
+      })
+    });
+  }
+});
+
+
+
+},{"../../menu/menubuilder":75,"backbone-viewj":10,"dom-helper":49,"underscore":59}],"biojs-io-clustal":[function(require,module,exports){
+// Generated by CoffeeScript 1.8.0
+var Clustal, GenericReader, Seq, Str,
+  __hasProp = {}.hasOwnProperty,
+  __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+Str = require("./strings");
+
+GenericReader = require("./generic_reader");
+
+Seq = require("./seq");
+
+module.exports = Clustal = (function(_super) {
+  __extends(Clustal, _super);
+
+  function Clustal() {
+    return Clustal.__super__.constructor.apply(this, arguments);
+  }
+
+  Clustal.parse = function(text) {
+    var blockstate, k, label, line, lines, match, regex, seqCounter, seqs, sequence;
+    seqs = [];
+    if (Object.prototype.toString.call(text) === '[object Array]') {
+      lines = text;
+    } else {
+      lines = text.split("\n");
+    }
+    if (lines[0].slice(0, 6) === !"CLUSTAL") {
+      throw new Error("Invalid CLUSTAL Header");
+    }
+    k = 0;
+    blockstate = 1;
+    seqCounter = 0;
+    while (k < lines.length) {
+      k++;
+      line = lines[k];
+      if ((line == null) || line.length === 0) {
+        blockstate = 1;
+        continue;
+      }
+      if (line.trim().length === 0) {
+        blockstate = 1;
+        continue;
+      } else {
+        if (Str.contains(line, "*")) {
+          continue;
+        }
+        if (blockstate === 1) {
+          seqCounter = 0;
+          blockstate = 0;
+        }
+        regex = /^(?:\s*)(\S+)(?:\s+)(\S+)(?:\s*)(\d*)(?:\s*|$)/g;
+        match = regex.exec(line);
+        if (match != null) {
+          label = match[1];
+          sequence = match[2];
+          if (seqCounter >= seqs.length) {
+            seqs.push(new Seq(sequence, label, seqCounter));
+          } else {
+            seqs[seqCounter].seq += sequence;
+          }
+          seqCounter++;
+        } else {
+          console.log(line);
+        }
+      }
+    }
+    return seqs;
+  };
+
+  return Clustal;
+
+})(GenericReader);
+
+},{"./generic_reader":17,"./seq":18,"./strings":19}],"biojs-io-fasta":[function(require,module,exports){
+// Generated by CoffeeScript 1.8.0
+module.exports.parse = require("./parser");
+
+module.exports.writer = require("./writer");
+
+},{"./parser":21,"./writer":24}],"biojs-vis-msa":[function(require,module,exports){
+if (typeof biojs === 'undefined') {
+  biojs = {};
+}
+if (typeof biojs.vis === 'undefined') {
+  biojs.vis = {};
+}
+// use two namespaces
+window.msa = biojs.vis.msa = module.exports = require('./index');
+
+// TODO: how should this be bundled
+
+if (typeof biojs.io === 'undefined') {
+  biojs.io = {};
+}
+// just bundle the two parsers
+window.biojs.io.fasta = require("biojs-io-fasta");
+window.biojs.io.clustal = require("biojs-io-clustal");
+window.biojs.xhr = require("nets");
+
+// simulate standalone flag
+window.biojsVisMsa = window.msa;
+
+require('./build/msa.css');
+
+},{"./build/msa.css":1,"./index":2,"biojs-io-clustal":undefined,"biojs-io-fasta":undefined,"nets":undefined}],"nets":[function(require,module,exports){
+var req = require('request')
+
+module.exports = Nets
+
+function Nets(uri, opts, cb) {
+  req(uri, opts, cb)
+}
+},{"request":52}]},{},["biojs-vis-msa"])
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9idWlsZC9tc2EuY3NzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2EvaW5kZXguanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmFja2JvbmUtY2hpbGRzL2luZGV4LmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2JhY2tib25lLXRoaW4vY29sbGVjdGlvbi5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9iYWNrYm9uZS10aGluL2luZGV4LmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2JhY2tib25lLXRoaW4vbW9kZWwuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmFja2JvbmUtdGhpbi9ub2RlX21vZHVsZXMvYmFja2JvbmUtZXZlbnRzLXN0YW5kYWxvbmUvYmFja2JvbmUtZXZlbnRzLXN0YW5kYWxvbmUuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmFja2JvbmUtdGhpbi9ub2RlX21vZHVsZXMvYmFja2JvbmUtZXZlbnRzLXN0YW5kYWxvbmUvaW5kZXguanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmFja2JvbmUtdGhpbi9ub2RlX21vZHVsZXMvYmFja2JvbmUtZXh0ZW5kLXN0YW5kYWxvbmUvYmFja2JvbmUtZXh0ZW5kLXN0YW5kYWxvbmUuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmFja2JvbmUtdmlld2ovaW5kZXguanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmlvanMtZXZlbnRzL2luZGV4LmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLWlvLWNsdXN0YWwvbGliL2dlbmVyaWNfcmVhZGVyLmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLWlvLWNsdXN0YWwvbGliL3NlcS5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9iaW9qcy1pby1jbHVzdGFsL2xpYi9zdHJpbmdzLmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLWlvLWZhc3RhL2xpYi9wYXJzZXIuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmlvanMtaW8tZmFzdGEvbGliL3V0aWxzLmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLWlvLWZhc3RhL2xpYi93cml0ZXIuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmlvanMtaW8tZmFzdGEvbm9kZV9tb2R1bGVzL2Jpb2pzLW1vZGVsL3NyYy9pbmRleC5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9iaW9qcy1pby1mYXN0YS9ub2RlX21vZHVsZXMvYmlvanMtbW9kZWwvc3JjL3NlcS5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9iaW9qcy11dGlsLWNvbG9yc2NoZW1lcy9pbmRleC5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9iaW9qcy11dGlsLWNvbG9yc2NoZW1lcy9zcmMvYnVyaWVkLmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLXV0aWwtY29sb3JzY2hlbWVzL3NyYy9jaW5lbWEuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmlvanMtdXRpbC1jb2xvcnNjaGVtZXMvc3JjL2NsdXN0YWwuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmlvanMtdXRpbC1jb2xvcnNjaGVtZXMvc3JjL2NsdXN0YWwyLmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLXV0aWwtY29sb3JzY2hlbWVzL3NyYy9oZWxpeC5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9iaW9qcy11dGlsLWNvbG9yc2NoZW1lcy9zcmMvaHlkcm9waG9iaWNpdHkuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmlvanMtdXRpbC1jb2xvcnNjaGVtZXMvc3JjL2luZGV4LmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLXV0aWwtY29sb3JzY2hlbWVzL3NyYy9sZXNrLmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLXV0aWwtY29sb3JzY2hlbWVzL3NyYy9tYWUuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmlvanMtdXRpbC1jb2xvcnNjaGVtZXMvc3JjL251Y2xlb3RpZGUuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmlvanMtdXRpbC1jb2xvcnNjaGVtZXMvc3JjL3B1cmluZS5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9iaW9qcy11dGlsLWNvbG9yc2NoZW1lcy9zcmMvc2VsZWN0b3IuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvYmlvanMtdXRpbC1jb2xvcnNjaGVtZXMvc3JjL3N0cmFuZC5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9iaW9qcy11dGlsLWNvbG9yc2NoZW1lcy9zcmMvdGF5bG9yLmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLXV0aWwtY29sb3JzY2hlbWVzL3NyYy90dXJuLmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLXV0aWwtY29sb3JzY2hlbWVzL3NyYy96YXBwby5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9ibHVlaW1wX2NhbnZhc3RvYmxvYi9pbmRleC5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9icm93c2VyLXNhdmVhcy9pbmRleC5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9jc3NpZnkvYnJvd3Nlci5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9kb20taGVscGVyL2luZGV4LmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2pib25lL2Rpc3QvamJvbmUuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvbW91c2UtcG9zL2luZGV4LmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL25ldHMvbm9kZV9tb2R1bGVzL3hoci9pbmRleC5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9uZXRzL25vZGVfbW9kdWxlcy94aHIvbm9kZV9tb2R1bGVzL2dsb2JhbC93aW5kb3cuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvbmV0cy9ub2RlX21vZHVsZXMveGhyL25vZGVfbW9kdWxlcy9vbmNlL29uY2UuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvbmV0cy9ub2RlX21vZHVsZXMveGhyL25vZGVfbW9kdWxlcy9wYXJzZS1oZWFkZXJzL25vZGVfbW9kdWxlcy9mb3ItZWFjaC9pbmRleC5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9uZXRzL25vZGVfbW9kdWxlcy94aHIvbm9kZV9tb2R1bGVzL3BhcnNlLWhlYWRlcnMvbm9kZV9tb2R1bGVzL2Zvci1lYWNoL25vZGVfbW9kdWxlcy9pcy1mdW5jdGlvbi9pbmRleC5qcyIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9uZXRzL25vZGVfbW9kdWxlcy94aHIvbm9kZV9tb2R1bGVzL3BhcnNlLWhlYWRlcnMvbm9kZV9tb2R1bGVzL3RyaW0vaW5kZXguanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvbmV0cy9ub2RlX21vZHVsZXMveGhyL25vZGVfbW9kdWxlcy9wYXJzZS1oZWFkZXJzL3BhcnNlLWhlYWRlcnMuanMiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9ub2RlX21vZHVsZXMvdW5kZXJzY29yZS91bmRlcnNjb3JlLmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL2FsZ28vQ29uc2Vuc3VzQ2FsYy5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvYWxnby9pZGVudGl0eUNhbGMuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL2FsZ28vaW5kZXguY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL2cvY29sb3JhdG9yLmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9nL2NvbHVtbnMuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL2cvY29uZmlnLmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9nL2NvbnNlbnN1cy5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvZy9zZWxlY3Rpb24vU2VsZWN0aW9uLmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9nL3NlbGVjdGlvbi9TZWxlY3Rpb25Db2wuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL2cvdmlzT3JkZXJpbmcuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL2cvdmlzaWJpbGl0eS5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvZy96b29tZXIuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL2luZGV4LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9tZW51L2RlZmF1bHRtZW51LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9tZW51L2luZGV4LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9tZW51L21lbnVidWlsZGVyLmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9tZW51L3ZpZXdzL0NvbG9yTWVudS5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvbWVudS92aWV3cy9FeHBvcnRNZW51LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9tZW51L3ZpZXdzL0V4dHJhTWVudS5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvbWVudS92aWV3cy9GaWx0ZXJNZW51LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9tZW51L3ZpZXdzL0hlbHBNZW51LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9tZW51L3ZpZXdzL0ltcG9ydE1lbnUuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL21lbnUvdmlld3MvT3JkZXJpbmdNZW51LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9tZW51L3ZpZXdzL1NlbGVjdGlvbk1lbnUuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL21lbnUvdmlld3MvVmlzTWVudS5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvbW9kZWwvRmVhdHVyZS5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvbW9kZWwvRmVhdHVyZUNvbC5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvbW9kZWwvU2VxQ29sbGVjdGlvbi5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvbW9kZWwvU2VxdWVuY2UuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL21vZGVsL2luZGV4LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy9tc2EuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL3V0aWxzL2JtYXRoLmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy91dGlscy9pbmRleC5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvdXRpbHMvcHJveHkuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL3V0aWxzL3NlcWdlbi5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvdXRpbHMvc3ZnLmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy92aWV3cy9BbGlnbm1lbnRCb2R5LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy92aWV3cy9DYW52YXNDaGFyQ2FjaGUuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL3ZpZXdzL0NhbnZhc1NlcUJsb2NrLmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy92aWV3cy9PdmVydmlld0JveC5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvdmlld3MvU3RhZ2UuY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL3ZpZXdzL2hlYWRlci9Db25zZXJ2YXRpb25WaWV3LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy92aWV3cy9oZWFkZXIvSGVhZGVyQmxvY2suY29mZmVlIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evc3JjL3ZpZXdzL2hlYWRlci9NYXJrZXJWaWV3LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3NyYy92aWV3cy9sYWJlbHMvTGFiZWxCbG9jay5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvdmlld3MvbGFiZWxzL0xhYmVsUm93Vmlldy5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvdmlld3MvbGFiZWxzL0xhYmVsVmlldy5jb2ZmZWUiLCIvaG9tZS90cmF2aXMvYnVpbGQvZ3JlZW5pZnkvYmlvanMtdmlzLW1zYS9zcmMvdmlld3MvbGFiZWxzL01ldGFWaWV3LmNvZmZlZSIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9iaW9qcy1pby1jbHVzdGFsL2xpYi9jbHVzdGFsLmpzIiwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Jpb2pzLWlvLWZhc3RhL2xpYi9pbmRleC5qcyIsIi4vYnJvd3NlciIsIi9ob21lL3RyYXZpcy9idWlsZC9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL25vZGVfbW9kdWxlcy9uZXRzL2luZGV4LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FDQUE7O0FDQUE7QUFDQTs7QUNEQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDcmJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDSkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDOVdBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDclJBO0FBQ0E7O0FDREE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN6RUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7Ozs7Ozs7QUMvS0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7Ozs7OztBQ3JCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUMzQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDZkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7OztBQ1ZBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7O0FDbkRBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2ZBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUMxQkE7QUFDQTs7QUNEQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7Ozs7O0FDTkE7QUFDQTs7QUNEQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDN0JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM3QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN6QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN6QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUM1QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNuQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzdCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDN0JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDUEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDVEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDN0NBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDekJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDekJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDekJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDekJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNuRkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN2UEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNyQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3pDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3gxQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3JEQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDaExBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNYQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ25CQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzlDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNmQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDZEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDOUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN2NENBLElBQUEsQ0FBQTs7QUFBQSxDQUFBLEdBQUksT0FBQSxDQUFRLFlBQVIsQ0FBSixDQUFBOztBQUFBLE1BSU0sQ0FBQyxPQUFQLEdBQWlCLFNBQUMsSUFBRCxHQUFBO0FBRWYsTUFBQSxJQUFBO0FBQUEs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Q0FyRFo7QUFBQSxFQXdEQSxXQUFBLEVBQWEsU0FBQyxHQUFELEdBQUE7V0FDWCxJQUFDLENBQUEsQ0FBQyxDQUFDLE9BQUgsQ0FBVyxlQUFYLEVBQTRCO0FBQUEsTUFBQyxLQUFBLEVBQU8sSUFBQyxDQUFBLEtBQUssQ0FBQyxHQUFQLENBQVcsSUFBWCxFQUFpQjtBQUFBLFFBQUEsR0FBQSxFQUFJLEdBQUo7T0FBakIsQ0FBUjtLQUE1QixFQURXO0VBQUEsQ0F4RGI7Q0FGMEIsQ0FMNUIsQ0FBQTs7Ozs7QUNBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUN4RUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNKQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDdkJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsInZhciBjc3MgPSBcIi5iaW9qc19tc2Ffc3RhZ2Uge1xcbiAgY3Vyc29yOiBkZWZhdWx0O1xcbiAgbGluZS1oZWlnaHQ6IG5vcm1hbDsgfVxcblxcbi5iaW9qc19tc2FfbGFiZWxzIHtcXG4gIGNvbG9yOiBibGFjaztcXG4gIGRpc3BsYXk6IGlubGluZS1ibG9jaztcXG4gIHdoaXRlLXNwYWNlOiBub3dyYXA7XFxuICBjdXJzb3I6IHBvaW50ZXI7XFxuICB2ZXJ0aWNhbC1hbGlnbjogdG9wOyB9XFxuXFxuLmJpb2pzX21zYV9zZXFibG9jayB7XFxuICBjdXJzb3I6IG1vdmU7IH1cXG5cXG4uYmlvanNfbXNhX2xheWVyIHtcXG4gIGRpc3BsYXk6IGJsb2NrO1xcbiAgd2hpdGUtc3BhY2U6IG5vd3JhcDsgfVxcblxcbi5iaW9qc19tc2FfbGFiZWxibG9jazo6LXdlYmtpdC1zY3JvbGxiYXIsIC5iaW9qc19tc2FfaGVhZGVyOjotd2Via2l0LXNjcm9sbGJhciB7XFxuICAtd2Via2l0LWFwcGVhcmFuY2U6IG5vbmU7XFxuICB3aWR0aDogN3B4O1xcbiAgaGVpZ2h0OiA3cHg7IH1cXG5cXG4uYmlvanNfbXNhX2xhYmVsYmxvY2s6Oi13ZWJraXQtc2Nyb2xsYmFyLXRodW1iLCAuYmlvanNfbXNhX2hlYWRlcjo6LXdlYmtpdC1zY3JvbGxiYXItdGh1bWIge1xcbiAgYm9yZGVyLXJhZGl1czogNHB4O1xcbiAgYmFja2dyb3VuZC1jb2xvcjogcmdiYSgwLCAwLCAwLCAwLjUpO1xcbiAgYm94LXNoYWRvdzogMCAwIDFweCByZ2JhKDI1NSwgMjU1LCAyNTUsIDAuNSk7IH1cXG5cXG4uYmlvanNfbXNhX21hcmtlciB7XFxuICBjb2xvcjogZ3JleTtcXG4gIHdoaXRlLXNwYWNlOiBub3dyYXA7XFxuICBjdXJzb3I6IHBvaW50ZXI7IH1cXG5cXG4uYmlvanNfbXNhX21hcmtlciBzcGFuIHtcXG4gIHRleHQtYWxpZ246IGNlbnRlcjsgfVxcblxcbi5iaW9qc19tc2FfbWVudWJhciAuYmlvanNfbXNhX21lbnViYXJfYWxpbmsge1xcbiAgYmFja2dyb3VuZDogIzM0OThkYjtcXG4gIGJhY2tncm91bmQtaW1hZ2U6IC13ZWJraXQtbGluZWFyLWdyYWRpZW50KHRvcCwgIzM0OThkYiwgIzI5ODBiOSk7XFxuICBiYWNrZ3JvdW5kLWltYWdlOiAtbW96LWxpbmVhci1ncmFkaWVudCh0b3AsICMzNDk4ZGIsICMyOTgwYjkpO1xcbiAgYmFja2dyb3VuZC1pbWFnZTogLW1zLWxpbmVhci1ncmFkaWVudCh0b3AsICMzNDk4ZGIsICMyOTgwYjkpO1xcbiAgYmFja2dyb3VuZC1pbWFnZTogLW8tbGluZWFyLWdyYWRpZW50KHRvcCwgIzM0OThkYiwgIzI5ODBiOSk7XFxuICBiYWNrZ3JvdW5kLWltYWdlOiBsaW5lYXItZ3JhZGllbnQodG8gYm90dG9tLCAjMzQ5OGRiLCAjMjk4MGI5KTtcXG4gIC13ZWJraXQtYm9yZGVyLXJhZGl1czogMjg7XFxuICAtbW96LWJvcmRlci1yYWRpdXM6IDI4O1xcbiAgYm9yZGVyLXJhZGl1czogMjhweDtcXG4gIGZvbnQtZmFtaWx5OiBBcmlhbDtcXG4gIGNvbG9yOiAjZmZmZmZmO1xcbiAgcGFkZGluZzogM3B4IDEwcHggM3B4IDEwcHg7XFxuICBtYXJnaW4tbGVmdDogMTBweDtcXG4gIHRleHQtZGVjb3JhdGlvbjogbm9uZTsgfVxcblxcbi5iaW9qc19tc2FfbWVudWJhciAuYmlvanNfbXNhX21lbnViYXJfYWxpbms6aG92ZXIge1xcbiAgY3Vyc29yOiBwb2ludGVyOyB9XFxuXFxuLyoganF1ZXJ5IGRyb3Bkb3duIENTUyAqL1xcbi5kcm9wZG93biB7XFxuICBwb3NpdGlvbjogYWJzb2x1dGU7XFxuICB6LWluZGV4OiA5OTk5OTk5O1xcbiAgZGlzcGxheTogbm9uZTsgfVxcblxcbi5kcm9wZG93biAuZHJvcGRvd24tbWVudSxcXG4uZHJvcGRvd24gLmRyb3Bkb3duLXBhbmVsIHtcXG4gIG1pbi13aWR0aDogMTYwcHg7XFxuICBtYXgtd2lkdGg6IDM2MHB4O1xcbiAgbGlzdC1zdHlsZTogbm9uZTtcXG4gIGJhY2tncm91bmQ6ICNGRkY7XFxuICBib3JkZXI6IHNvbGlkIDFweCAjREREO1xcbiAgYm9yZGVyOiBzb2xpZCAxcHggcmdiYSgwLCAwLCAwLCAwLjIpO1xcbiAgYm9yZGVyLXJhZGl1czogNnB4O1xcbiAgYm94LXNoYWRvdzogMCA1cHggMTBweCByZ2JhKDAsIDAsIDAsIDAuMik7XFxuICBvdmVyZmxvdzogdmlzaWJsZTtcXG4gIHBhZGRpbmc6IDRweCAwO1xcbiAgbWFyZ2luOiAwOyB9XFxuXFxuLmRyb3Bkb3duIC5kcm9wZG93bi1wYW5lbCB7XFxuICBwYWRkaW5nOiAxMHB4OyB9XFxuXFxuLmRyb3Bkb3duLmRyb3Bkb3duLXNjcm9sbCAuZHJvcGRvd24tbWVudSxcXG4uZHJvcGRvd24uZHJvcGRvd24tc2Nyb2xsIC5kcm9wZG93bi1wYW5lbCB7XFxuICBtYXgtaGVpZ2h0OiAzNThweDtcXG4gIG92ZXJmbG93OiBhdXRvOyB9XFxuXFxuLmRyb3Bkb3duIC5kcm9wZG93bi1tZW51IExJIHtcXG4gIGxpc3Qtc3R5bGU6IG5vbmU7XFxuICBwYWRkaW5nOiAwIDA7XFxuICBtYXJnaW46IDA7XFxuICBsaW5lLWhlaWdodDogMThweDsgfVxcblxcbi5kcm9wZG93biAuZHJvcGRvd24tbWVudSBMSSxcXG4uZHJvcGRvd24gLmRyb3Bkb3duLW1lbnUgTEFCRUwge1xcbiAgZGlzcGxheTogYmxvY2s7XFxuICBjb2xvcjogIzU1NTtcXG4gIHRleHQtZGVjb3JhdGlvbjogbm9uZTtcXG4gIGxpbmUtaGVpZ2h0OiAxOHB4O1xcbiAgcGFkZGluZzogM3B4IDE1cHg7XFxuICB3aGl0ZS1zcGFjZTogbm93cmFwOyB9XFxuXFxuLmRyb3Bkb3duIC5kcm9wZG93bi1tZW51IExJOmhvdmVyLFxcbi5kcm9wZG93biAuZHJvcGRvd24tbWVudSBMQUJFTDpob3ZlciB7XFxuICBiYWNrZ3JvdW5kLWNvbG9yOiAjMDhDO1xcbiAgY29sb3I6ICNGRkY7XFxuICBjdXJzb3I6IHBvaW50ZXI7IH1cXG5cXG4uZHJvcGRvd24gLmRyb3Bkb3duLW1lbnUgLmRyb3Bkb3duLWRpdmlkZXIge1xcbiAgZm9udC1zaXplOiAxcHg7XFxuICBib3JkZXItdG9wOiBzb2xpZCAxcHggI0U1RTVFNTtcXG4gIHBhZGRpbmc6IDA7XFxuICBtYXJnaW46IDVweCAwOyB9XFxuXCI7IChyZXF1aXJlKFwiL2hvbWUvdHJhdmlzL2J1aWxkL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2Evbm9kZV9tb2R1bGVzL2Nzc2lmeVwiKSkoY3NzKTsgbW9kdWxlLmV4cG9ydHMgPSBjc3M7IiwibW9kdWxlLmV4cG9ydHMgPSByZXF1aXJlKFwiLi9zcmMvaW5kZXhcIik7XG4iLCJ2YXIgXyA9IHJlcXVpcmUoJ3VuZGVyc2NvcmUnKTtcbnZhciB2aWV3VHlwZSA9IHJlcXVpcmUoXCJiYWNrYm9uZS12aWV3alwiKTtcbnZhciBwbHVnaW5hdG9yO1xuXG5tb2R1bGUuZXhwb3J0cyA9IHBsdWdpbmF0b3IgPSB2aWV3VHlwZS5leHRlbmQoe1xuICByZW5kZXJTdWJ2aWV3czogZnVuY3Rpb24oKSB7XG4gICAgdmFyIG9sZEVsID0gdGhpcy5lbDtcbiAgICB2YXIgZWwgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KFwiZGl2XCIpO1xuICAgIHRoaXMuc2V0RWxlbWVudChlbCk7XG4gICAgdmFyIGZyYWcgPSBkb2N1bWVudC5jcmVhdGVEb2N1bWVudEZyYWdtZW50KCk7XG4gICAgaWYgKG9sZEVsLnBhcmVudE5vZGUgIT0gbnVsbCkge1xuICAgICAgb2xkRWwucGFyZW50Tm9kZS5yZXBsYWNlQ2hpbGQodGhpcy5lbCwgb2xkRWwpO1xuICAgIH1cbiAgICB2YXIgdmlld3MgPSB0aGlzLl92aWV3cygpO1xuICAgIHZhciB2aWV3c1NvcnRlZCA9IF8uc29ydEJ5KHZpZXdzLCBmdW5jdGlvbihlbCkge1xuICAgICAgcmV0dXJuIGVsLm9yZGVyaW5nO1xuICAgIH0pO1xuICAgIHZhciB2aWV3LCBub2RlO1xuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgIHZpZXdzU29ydGVkLmxlbmd0aDsgaSsrKSB7XG4gICAgICB2aWV3ID0gdmlld3NTb3J0ZWRbaV07XG4gICAgICB2aWV3LnJlbmRlcigpO1xuICAgICAgbm9kZSA9IHZpZXcuZWw7XG4gICAgICBpZiAobm9kZSAhPSBudWxsKSB7XG4gICAgICAgIGZyYWcuYXBwZW5kQ2hpbGQobm9kZSk7XG4gICAgICB9XG4gICAgfVxuICAgIGVsLmFwcGVuZENoaWxkKGZyYWcpO1xuICAgIHJldHVybiBlbDtcbiAgfSxcbiAgYWRkVmlldzogZnVuY3Rpb24oa2V5LCB2aWV3KSB7XG4gICAgdmFyIHZpZXdzID0gdGhpcy5fdmlld3MoKTtcbiAgICBpZiAodmlldyA9PSBudWxsKSB7XG4gICAgICB0aHJvdyBcIkludmFsaWQgcGx1Z2luLiBcIjtcbiAgICB9XG4gICAgaWYgKHZpZXcub3JkZXJpbmcgPT0gbnVsbCkge1xuICAgICAgdmlldy5vcmRlcmluZyA9IGtleTtcbiAgICB9XG4gICAgcmV0dXJuIHZpZXdzW2tleV0gPSB2aWV3O1xuICB9LFxuICByZW1vdmVWaWV3czogZnVuY3Rpb24oKSB7XG4gICAgdmFyIGVsLCBrZXk7XG4gICAgdmFyIHZpZXdzID0gdGhpcy5fdmlld3MoKTtcbiAgICBmb3IgKGtleSBpbiB2aWV3cykge1xuICAgICAgZWwgPSB2aWV3c1trZXldO1xuICAgICAgZWwudW5kZWxlZ2F0ZUV2ZW50cygpO1xuICAgICAgZWwudW5iaW5kKCk7XG4gICAgICBpZiAoZWwucmVtb3ZlVmlld3MgIT0gbnVsbCkge1xuICAgICAgICBlbC5yZW1vdmVWaWV3cygpO1xuICAgICAgfVxuICAgICAgZWwucmVtb3ZlKCk7XG4gICAgfVxuICAgIHJldHVybiB0aGlzLnZpZXdzID0ge307XG4gIH0sXG4gIHJlbW92ZVZpZXc6IGZ1bmN0aW9uKGtleSkge1xuICAgIHZhciB2aWV3cyA9IHRoaXMuX3ZpZXdzKCk7XG4gICAgdmlld3Nba2V5XS5yZW1vdmUoKTtcbiAgICByZXR1cm4gZGVsZXRlIHZpZXdzW2tleV07XG4gIH0sXG4gIGdldFZpZXc6IGZ1bmN0aW9uKGtleSkge1xuICAgIHZhciB2aWV3cyA9IHRoaXMuX3ZpZXdzKCk7XG4gICAgcmV0dXJuIHZpZXdzW2tleV07XG4gIH0sXG4gIHJlbW92ZTogZnVuY3Rpb24oKSB7XG4gICAgdGhpcy5yZW1vdmVWaWV3cygpO1xuICAgIHJldHVybiB2aWV3VHlwZS5wcm90b3R5cGUucmVtb3ZlLmFwcGx5KHRoaXMpO1xuICB9LFxuICBfdmlld3M6IGZ1bmN0aW9uKCkge1xuICAgIGlmICh0aGlzLnZpZXdzID09IG51bGwpIHtcbiAgICAgIHRoaXMudmlld3MgPSB7fTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXMudmlld3M7XG4gIH1cbn0pO1xuIiwiLy8gICAgIEJhY2tib25lLmpzIDEuMS4yXG5cbi8vICAgICAoYykgMjAxMC0yMDE0IEplcmVteSBBc2hrZW5hcywgRG9jdW1lbnRDbG91ZCBhbmQgSW52ZXN0aWdhdGl2ZSBSZXBvcnRlcnMgJiBFZGl0b3JzXG4vLyAgICAgQmFja2JvbmUgbWF5IGJlIGZyZWVseSBkaXN0cmlidXRlZCB1bmRlciB0aGUgTUlUIGxpY2Vuc2UuXG4vLyAgICAgRm9yIGFsbCBkZXRhaWxzIGFuZCBkb2N1bWVudGF0aW9uOlxuLy8gICAgIGh0dHA6Ly9iYWNrYm9uZWpzLm9yZ1xuXG52YXIgRXZlbnRzID0gcmVxdWlyZShcImJhY2tib25lLWV2ZW50cy1zdGFuZGFsb25lXCIpO1xudmFyIGV4dGVuZCA9IHJlcXVpcmUoXCJiYWNrYm9uZS1leHRlbmQtc3RhbmRhbG9uZVwiKTtcbnZhciBfID0gcmVxdWlyZShcInVuZGVyc2NvcmVcIik7XG52YXIgTW9kZWwgPSByZXF1aXJlKFwiLi9tb2RlbFwiKTtcblxuLy8gQ3JlYXRlIGxvY2FsIHJlZmVyZW5jZXMgdG8gYXJyYXkgbWV0aG9kcyB3ZSdsbCB3YW50IHRvIHVzZSBsYXRlci5cbnZhciBhcnJheSA9IFtdO1xudmFyIHNsaWNlID0gYXJyYXkuc2xpY2U7XG5cbi8vIEJhY2tib25lLkNvbGxlY3Rpb25cbi8vIC0tLS0tLS0tLS0tLS0tLS0tLS1cblxuLy8gSWYgbW9kZWxzIHRlbmQgdG8gcmVwcmVzZW50IGEgc2luZ2xlIHJvdyBvZiBkYXRhLCBhIEJhY2tib25lIENvbGxlY3Rpb24gaXNcbi8vIG1vcmUgYW5hbG9nb3VzIHRvIGEgdGFibGUgZnVsbCBvZiBkYXRhIC4uLiBvciBhIHNtYWxsIHNsaWNlIG9yIHBhZ2Ugb2YgdGhhdFxuLy8gdGFibGUsIG9yIGEgY29sbGVjdGlvbiBvZiByb3dzIHRoYXQgYmVsb25nIHRvZ2V0aGVyIGZvciBhIHBhcnRpY3VsYXIgcmVhc29uXG4vLyAtLSBhbGwgb2YgdGhlIG1lc3NhZ2VzIGluIHRoaXMgcGFydGljdWxhciBmb2xkZXIsIGFsbCBvZiB0aGUgZG9jdW1lbnRzXG4vLyBiZWxvbmdpbmcgdG8gdGhpcyBwYXJ0aWN1bGFyIGF1dGhvciwgYW5kIHNvIG9uLiBDb2xsZWN0aW9ucyBtYWludGFpblxuLy8gaW5kZXhlcyBvZiB0aGVpciBtb2RlbHMsIGJvdGggaW4gb3JkZXIsIGFuZCBmb3IgbG9va3VwIGJ5IGBpZGAuXG5cbi8vIENyZWF0ZSBhIG5ldyAqKkNvbGxlY3Rpb24qKiwgcGVyaGFwcyB0byBjb250YWluIGEgc3BlY2lmaWMgdHlwZSBvZiBgbW9kZWxgLlxuLy8gSWYgYSBgY29tcGFyYXRvcmAgaXMgc3BlY2lmaWVkLCB0aGUgQ29sbGVjdGlvbiB3aWxsIG1haW50YWluXG4vLyBpdHMgbW9kZWxzIGluIHNvcnQgb3JkZXIsIGFzIHRoZXkncmUgYWRkZWQgYW5kIHJlbW92ZWQuXG52YXIgQ29sbGVjdGlvbiA9IGZ1bmN0aW9uKG1vZGVscywgb3B0aW9ucykge1xuICBvcHRpb25zIHx8IChvcHRpb25zID0ge30pO1xuICBpZiAob3B0aW9ucy5tb2RlbCkgdGhpcy5tb2RlbCA9IG9wdGlvbnMubW9kZWw7XG4gIGlmIChvcHRpb25zLmNvbXBhcmF0b3IgIT09IHZvaWQgMCkgdGhpcy5jb21wYXJhdG9yID0gb3B0aW9ucy5jb21wYXJhdG9yO1xuICB0aGlzLl9yZXNldCgpO1xuICB0aGlzLmluaXRpYWxpemUuYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgaWYgKG1vZGVscykgdGhpcy5yZXNldChtb2RlbHMsIF8uZXh0ZW5kKHtzaWxlbnQ6IHRydWV9LCBvcHRpb25zKSk7XG59O1xuXG4vLyBEZWZhdWx0IG9wdGlvbnMgZm9yIGBDb2xsZWN0aW9uI3NldGAuXG52YXIgc2V0T3B0aW9ucyA9IHthZGQ6IHRydWUsIHJlbW92ZTogdHJ1ZSwgbWVyZ2U6IHRydWV9O1xudmFyIGFkZE9wdGlvbnMgPSB7YWRkOiB0cnVlLCByZW1vdmU6IGZhbHNlfTtcblxuLy8gRGVmaW5lIHRoZSBDb2xsZWN0aW9uJ3MgaW5oZXJpdGFibGUgbWV0aG9kcy5cbl8uZXh0ZW5kKENvbGxlY3Rpb24ucHJvdG90eXBlLCBFdmVudHMsIHtcblxuICAvLyBUaGUgZGVmYXVsdCBtb2RlbCBmb3IgYSBjb2xsZWN0aW9uIGlzIGp1c3QgYSAqKkJhY2tib25lLk1vZGVsKiouXG4gIC8vIFRoaXMgc2hvdWxkIGJlIG92ZXJyaWRkZW4gaW4gbW9zdCBjYXNlcy5cbiAgbW9kZWw6IE1vZGVsLFxuXG4gIC8vIEluaXRpYWxpemUgaXMgYW4gZW1wdHkgZnVuY3Rpb24gYnkgZGVmYXVsdC4gT3ZlcnJpZGUgaXQgd2l0aCB5b3VyIG93blxuICAvLyBpbml0aWFsaXphdGlvbiBsb2dpYy5cbiAgaW5pdGlhbGl6ZTogZnVuY3Rpb24oKXt9LFxuXG4gICAgLy8gVGhlIEpTT04gcmVwcmVzZW50YXRpb24gb2YgYSBDb2xsZWN0aW9uIGlzIGFuIGFycmF5IG9mIHRoZVxuICAgIC8vIG1vZGVscycgYXR0cmlidXRlcy5cbiAgdG9KU09OOiBmdW5jdGlvbihvcHRpb25zKSB7XG4gICAgcmV0dXJuIHRoaXMubWFwKGZ1bmN0aW9uKG1vZGVsKXsgcmV0dXJuIG1vZGVsLnRvSlNPTihvcHRpb25zKTsgfSk7XG4gIH0sXG5cbiAgICAvLyBQcm94eSBgQmFja2JvbmUuc3luY2AgYnkgZGVmYXVsdC5cbiAgc3luYzogZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIEJhY2tib25lLnN5bmMuYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgfSxcblxuICAgIC8vIEFkZCBhIG1vZGVsLCBvciBsaXN0IG9mIG1vZGVscyB0byB0aGUgc2V0LlxuICBhZGQ6IGZ1bmN0aW9uKG1vZGVscywgb3B0aW9ucykge1xuICAgIHJldHVybiB0aGlzLnNldChtb2RlbHMsIF8uZXh0ZW5kKHttZXJnZTogZmFsc2V9LCBvcHRpb25zLCBhZGRPcHRpb25zKSk7XG4gIH0sXG5cbiAgICAvLyBSZW1vdmUgYSBtb2RlbCwgb3IgYSBsaXN0IG9mIG1vZGVscyBmcm9tIHRoZSBzZXQuXG4gIHJlbW92ZTogZnVuY3Rpb24obW9kZWxzLCBvcHRpb25zKSB7XG4gICAgdmFyIHNpbmd1bGFyID0gIV8uaXNBcnJheShtb2RlbHMpO1xuICAgIG1vZGVscyA9IHNpbmd1bGFyID8gW21vZGVsc10gOiBfLmNsb25lKG1vZGVscyk7XG4gICAgb3B0aW9ucyB8fCAob3B0aW9ucyA9IHt9KTtcbiAgICBmb3IgKHZhciBpID0gMCwgbGVuZ3RoID0gbW9kZWxzLmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICB2YXIgbW9kZWwgPSBtb2RlbHNbaV0gPSB0aGlzLmdldChtb2RlbHNbaV0pO1xuICAgICAgaWYgKCFtb2RlbCkgY29udGludWU7XG4gICAgICB2YXIgaWQgPSB0aGlzLm1vZGVsSWQobW9kZWwuYXR0cmlidXRlcyk7XG4gICAgICBpZiAoaWQgIT0gbnVsbCkgZGVsZXRlIHRoaXMuX2J5SWRbaWRdO1xuICAgICAgZGVsZXRlIHRoaXMuX2J5SWRbbW9kZWwuY2lkXTtcbiAgICAgIHZhciBpbmRleCA9IHRoaXMuaW5kZXhPZihtb2RlbCk7XG4gICAgICB0aGlzLm1vZGVscy5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgdGhpcy5sZW5ndGgtLTtcbiAgICAgIGlmICghb3B0aW9ucy5zaWxlbnQpIHtcbiAgICAgICAgb3B0aW9ucy5pbmRleCA9IGluZGV4O1xuICAgICAgICBtb2RlbC50cmlnZ2VyKCdyZW1vdmUnLCBtb2RlbCwgdGhpcywgb3B0aW9ucyk7XG4gICAgICB9XG4gICAgICB0aGlzLl9yZW1vdmVSZWZlcmVuY2UobW9kZWwsIG9wdGlvbnMpO1xuICAgIH1cbiAgICByZXR1cm4gc2luZ3VsYXIgPyBtb2RlbHNbMF0gOiBtb2RlbHM7XG4gIH0sXG5cbiAgICAvLyBVcGRhdGUgYSBjb2xsZWN0aW9uIGJ5IGBzZXRgLWluZyBhIG5ldyBsaXN0IG9mIG1vZGVscywgYWRkaW5nIG5ldyBvbmVzLFxuICAgIC8vIHJlbW92aW5nIG1vZGVscyB0aGF0IGFyZSBubyBsb25nZXIgcHJlc2VudCwgYW5kIG1lcmdpbmcgbW9kZWxzIHRoYXRcbiAgICAvLyBhbHJlYWR5IGV4aXN0IGluIHRoZSBjb2xsZWN0aW9uLCBhcyBuZWNlc3NhcnkuIFNpbWlsYXIgdG8gKipNb2RlbCNzZXQqKixcbiAgICAvLyB0aGUgY29yZSBvcGVyYXRpb24gZm9yIHVwZGF0aW5nIHRoZSBkYXRhIGNvbnRhaW5lZCBieSB0aGUgY29sbGVjdGlvbi5cbiAgc2V0OiBmdW5jdGlvbihtb2RlbHMsIG9wdGlvbnMpIHtcbiAgICBvcHRpb25zID0gXy5kZWZhdWx0cyh7fSwgb3B0aW9ucywgc2V0T3B0aW9ucyk7XG4gICAgaWYgKG9wdGlvbnMucGFyc2UpIG1vZGVscyA9IHRoaXMucGFyc2UobW9kZWxzLCBvcHRpb25zKTtcbiAgICB2YXIgc2luZ3VsYXIgPSAhXy5pc0FycmF5KG1vZGVscyk7XG4gICAgbW9kZWxzID0gc2luZ3VsYXIgPyAobW9kZWxzID8gW21vZGVsc10gOiBbXSkgOiBtb2RlbHMuc2xpY2UoKTtcbiAgICB2YXIgaWQsIG1vZGVsLCBhdHRycywgZXhpc3RpbmcsIHNvcnQ7XG4gICAgdmFyIGF0ID0gb3B0aW9ucy5hdDtcbiAgICB2YXIgc29ydGFibGUgPSB0aGlzLmNvbXBhcmF0b3IgJiYgKGF0ID09IG51bGwpICYmIG9wdGlvbnMuc29ydCAhPT0gZmFsc2U7XG4gICAgdmFyIHNvcnRBdHRyID0gXy5pc1N0cmluZyh0aGlzLmNvbXBhcmF0b3IpID8gdGhpcy5jb21wYXJhdG9yIDogbnVsbDtcbiAgICB2YXIgdG9BZGQgPSBbXSwgdG9SZW1vdmUgPSBbXSwgbW9kZWxNYXAgPSB7fTtcbiAgICB2YXIgYWRkID0gb3B0aW9ucy5hZGQsIG1lcmdlID0gb3B0aW9ucy5tZXJnZSwgcmVtb3ZlID0gb3B0aW9ucy5yZW1vdmU7XG4gICAgdmFyIG9yZGVyID0gIXNvcnRhYmxlICYmIGFkZCAmJiByZW1vdmUgPyBbXSA6IGZhbHNlO1xuXG4gICAgLy8gVHVybiBiYXJlIG9iamVjdHMgaW50byBtb2RlbCByZWZlcmVuY2VzLCBhbmQgcHJldmVudCBpbnZhbGlkIG1vZGVsc1xuICAgIC8vIGZyb20gYmVpbmcgYWRkZWQuXG4gICAgZm9yICh2YXIgaSA9IDAsIGxlbmd0aCA9IG1vZGVscy5sZW5ndGg7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgICAgYXR0cnMgPSBtb2RlbHNbaV07XG5cbiAgICAgIC8vIElmIGEgZHVwbGljYXRlIGlzIGZvdW5kLCBwcmV2ZW50IGl0IGZyb20gYmVpbmcgYWRkZWQgYW5kXG4gICAgICAvLyBvcHRpb25hbGx5IG1lcmdlIGl0IGludG8gdGhlIGV4aXN0aW5nIG1vZGVsLlxuICAgICAgaWYgKGV4aXN0aW5nID0gdGhpcy5nZXQoYXR0cnMpKSB7XG4gICAgICAgIGlmIChyZW1vdmUpIG1vZGVsTWFwW2V4aXN0aW5nLmNpZF0gPSB0cnVlO1xuICAgICAgICBpZiAobWVyZ2UgJiYgYXR0cnMgIT09IGV4aXN0aW5nKSB7XG4gICAgICAgICAgYXR0cnMgPSB0aGlzLl9pc01vZGVsKGF0dHJzKSA/IGF0dHJzLmF0dHJpYnV0ZXMgOiBhdHRycztcbiAgICAgICAgICBpZiAob3B0aW9ucy5wYXJzZSkgYXR0cnMgPSBleGlzdGluZy5wYXJzZShhdHRycywgb3B0aW9ucyk7XG4gICAgICAgICAgZXhpc3Rpbmcuc2V0KGF0dHJzLCBvcHRpb25zKTtcbiAgICAgICAgICBpZiAoc29ydGFibGUgJiYgIXNvcnQgJiYgZXhpc3RpbmcuaGFzQ2hhbmdlZChzb3J0QXR0cikpIHNvcnQgPSB0cnVlO1xuICAgICAgICB9XG4gICAgICAgIG1vZGVsc1tpXSA9IGV4aXN0aW5nO1xuXG4gICAgICAgIC8vIElmIHRoaXMgaXMgYSBuZXcsIHZhbGlkIG1vZGVsLCBwdXNoIGl0IHRvIHRoZSBgdG9BZGRgIGxpc3QuXG4gICAgICB9IGVsc2UgaWYgKGFkZCkge1xuICAgICAgICBtb2RlbCA9IG1vZGVsc1tpXSA9IHRoaXMuX3ByZXBhcmVNb2RlbChhdHRycywgb3B0aW9ucyk7XG4gICAgICAgIGlmICghbW9kZWwpIGNvbnRpbnVlO1xuICAgICAgICB0b0FkZC5wdXNoKG1vZGVsKTtcbiAgICAgICAgdGhpcy5fYWRkUmVmZXJlbmNlKG1vZGVsLCBvcHRpb25zKTtcbiAgICAgIH1cblxuICAgICAgLy8gRG8gbm90IGFkZCBtdWx0aXBsZSBtb2RlbHMgd2l0aCB0aGUgc2FtZSBgaWRgLlxuICAgICAgbW9kZWwgPSBleGlzdGluZyB8fCBtb2RlbDtcbiAgICAgIGlmICghbW9kZWwpIGNvbnRpbnVlO1xuICAgICAgaWQgPSB0aGlzLm1vZGVsSWQobW9kZWwuYXR0cmlidXRlcyk7XG4gICAgICBpZiAob3JkZXIgJiYgKG1vZGVsLmlzTmV3KCkgfHwgIW1vZGVsTWFwW2lkXSkpIG9yZGVyLnB1c2gobW9kZWwpO1xuICAgICAgbW9kZWxNYXBbaWRdID0gdHJ1ZTtcbiAgICB9XG5cbiAgICAvLyBSZW1vdmUgbm9uZXhpc3RlbnQgbW9kZWxzIGlmIGFwcHJvcHJpYXRlLlxuICAgIGlmIChyZW1vdmUpIHtcbiAgICAgIGZvciAodmFyIGkgPSAwLCBsZW5ndGggPSB0aGlzLmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGlmICghbW9kZWxNYXBbKG1vZGVsID0gdGhpcy5tb2RlbHNbaV0pLmNpZF0pIHRvUmVtb3ZlLnB1c2gobW9kZWwpO1xuICAgICAgfVxuICAgICAgaWYgKHRvUmVtb3ZlLmxlbmd0aCkgdGhpcy5yZW1vdmUodG9SZW1vdmUsIG9wdGlvbnMpO1xuICAgIH1cblxuICAgIC8vIFNlZSBpZiBzb3J0aW5nIGlzIG5lZWRlZCwgdXBkYXRlIGBsZW5ndGhgIGFuZCBzcGxpY2UgaW4gbmV3IG1vZGVscy5cbiAgICBpZiAodG9BZGQubGVuZ3RoIHx8IChvcmRlciAmJiBvcmRlci5sZW5ndGgpKSB7XG4gICAgICBpZiAoc29ydGFibGUpIHNvcnQgPSB0cnVlO1xuICAgICAgdGhpcy5sZW5ndGggKz0gdG9BZGQubGVuZ3RoO1xuICAgICAgaWYgKGF0ICE9IG51bGwpIHtcbiAgICAgICAgZm9yICh2YXIgaSA9IDAsIGxlbmd0aCA9IHRvQWRkLmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgdGhpcy5tb2RlbHMuc3BsaWNlKGF0ICsgaSwgMCwgdG9BZGRbaV0pO1xuICAgICAgICB9XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBpZiAob3JkZXIpIHRoaXMubW9kZWxzLmxlbmd0aCA9IDA7XG4gICAgICAgIHZhciBvcmRlcmVkTW9kZWxzID0gb3JkZXIgfHwgdG9BZGQ7XG4gICAgICAgIGZvciAodmFyIGkgPSAwLCBsZW5ndGggPSBvcmRlcmVkTW9kZWxzLmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgdGhpcy5tb2RlbHMucHVzaChvcmRlcmVkTW9kZWxzW2ldKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIC8vIFNpbGVudGx5IHNvcnQgdGhlIGNvbGxlY3Rpb24gaWYgYXBwcm9wcmlhdGUuXG4gICAgaWYgKHNvcnQpIHRoaXMuc29ydCh7c2lsZW50OiB0cnVlfSk7XG5cbiAgICAvLyBVbmxlc3Mgc2lsZW5jZWQsIGl0J3MgdGltZSB0byBmaXJlIGFsbCBhcHByb3ByaWF0ZSBhZGQvc29ydCBldmVudHMuXG4gICAgaWYgKCFvcHRpb25zLnNpbGVudCkge1xuICAgICAgdmFyIGFkZE9wdHMgPSBhdCAhPSBudWxsID8gXy5jbG9uZShvcHRpb25zKSA6IG9wdGlvbnM7XG4gICAgICBmb3IgKHZhciBpID0gMCwgbGVuZ3RoID0gdG9BZGQubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgaWYgKGF0ICE9IG51bGwpIGFkZE9wdHMuaW5kZXggPSBhdCArIGk7XG4gICAgICAgIChtb2RlbCA9IHRvQWRkW2ldKS50cmlnZ2VyKCdhZGQnLCBtb2RlbCwgdGhpcywgYWRkT3B0cyk7XG4gICAgICB9XG4gICAgICBpZiAoc29ydCB8fCAob3JkZXIgJiYgb3JkZXIubGVuZ3RoKSkgdGhpcy50cmlnZ2VyKCdzb3J0JywgdGhpcywgb3B0aW9ucyk7XG4gICAgfVxuXG4gICAgLy8gUmV0dXJuIHRoZSBhZGRlZCAob3IgbWVyZ2VkKSBtb2RlbCAob3IgbW9kZWxzKS5cbiAgICByZXR1cm4gc2luZ3VsYXIgPyBtb2RlbHNbMF0gOiBtb2RlbHM7XG4gIH0sXG5cbiAgICAvLyBXaGVuIHlvdSBoYXZlIG1vcmUgaXRlbXMgdGhhbiB5b3Ugd2FudCB0byBhZGQgb3IgcmVtb3ZlIGluZGl2aWR1YWxseSxcbiAgICAvLyB5b3UgY2FuIHJlc2V0IHRoZSBlbnRpcmUgc2V0IHdpdGggYSBuZXcgbGlzdCBvZiBtb2RlbHMsIHdpdGhvdXQgZmlyaW5nXG4gICAgLy8gYW55IGdyYW51bGFyIGBhZGRgIG9yIGByZW1vdmVgIGV2ZW50cy4gRmlyZXMgYHJlc2V0YCB3aGVuIGZpbmlzaGVkLlxuICAgIC8vIFVzZWZ1bCBmb3IgYnVsayBvcGVyYXRpb25zIGFuZCBvcHRpbWl6YXRpb25zLlxuICByZXNldDogZnVuY3Rpb24obW9kZWxzLCBvcHRpb25zKSB7XG4gICAgb3B0aW9ucyB8fCAob3B0aW9ucyA9IHt9KTtcbiAgICBmb3IgKHZhciBpID0gMCwgbGVuZ3RoID0gdGhpcy5tb2RlbHMubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgIHRoaXMuX3JlbW92ZVJlZmVyZW5jZSh0aGlzLm1vZGVsc1tpXSwgb3B0aW9ucyk7XG4gICAgfVxuICAgIG9wdGlvbnMucHJldmlvdXNNb2RlbHMgPSB0aGlzLm1vZGVscztcbiAgICB0aGlzLl9yZXNldCgpO1xuICAgIG1vZGVscyA9IHRoaXMuYWRkKG1vZGVscywgXy5leHRlbmQoe3NpbGVudDogdHJ1ZX0sIG9wdGlvbnMpKTtcbiAgICBpZiAoIW9wdGlvbnMuc2lsZW50KSB0aGlzLnRyaWdnZXIoJ3Jlc2V0JywgdGhpcywgb3B0aW9ucyk7XG4gICAgcmV0dXJuIG1vZGVscztcbiAgfSxcblxuICAgIC8vIEFkZCBhIG1vZGVsIHRvIHRoZSBlbmQgb2YgdGhlIGNvbGxlY3Rpb24uXG4gIHB1c2g6IGZ1bmN0aW9uKG1vZGVsLCBvcHRpb25zKSB7XG4gICAgcmV0dXJuIHRoaXMuYWRkKG1vZGVsLCBfLmV4dGVuZCh7YXQ6IHRoaXMubGVuZ3RofSwgb3B0aW9ucykpO1xuICB9LFxuXG4gICAgLy8gUmVtb3ZlIGEgbW9kZWwgZnJvbSB0aGUgZW5kIG9mIHRoZSBjb2xsZWN0aW9uLlxuICBwb3A6IGZ1bmN0aW9uKG9wdGlvbnMpIHtcbiAgICB2YXIgbW9kZWwgPSB0aGlzLmF0KHRoaXMubGVuZ3RoIC0gMSk7XG4gICAgdGhpcy5yZW1vdmUobW9kZWwsIG9wdGlvbnMpO1xuICAgIHJldHVybiBtb2RlbDtcbiAgfSxcblxuICAgIC8vIEFkZCBhIG1vZGVsIHRvIHRoZSBiZWdpbm5pbmcgb2YgdGhlIGNvbGxlY3Rpb24uXG4gIHVuc2hpZnQ6IGZ1bmN0aW9uKG1vZGVsLCBvcHRpb25zKSB7XG4gICAgcmV0dXJuIHRoaXMuYWRkKG1vZGVsLCBfLmV4dGVuZCh7YXQ6IDB9LCBvcHRpb25zKSk7XG4gIH0sXG5cbiAgICAvLyBSZW1vdmUgYSBtb2RlbCBmcm9tIHRoZSBiZWdpbm5pbmcgb2YgdGhlIGNvbGxlY3Rpb24uXG4gIHNoaWZ0OiBmdW5jdGlvbihvcHRpb25zKSB7XG4gICAgdmFyIG1vZGVsID0gdGhpcy5hdCgwKTtcbiAgICB0aGlzLnJlbW92ZShtb2RlbCwgb3B0aW9ucyk7XG4gICAgcmV0dXJuIG1vZGVsO1xuICB9LFxuXG4gICAgLy8gU2xpY2Ugb3V0IGEgc3ViLWFycmF5IG9mIG1vZGVscyBmcm9tIHRoZSBjb2xsZWN0aW9uLlxuICBzbGljZTogZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIHNsaWNlLmFwcGx5KHRoaXMubW9kZWxzLCBhcmd1bWVudHMpO1xuICB9LFxuXG4gICAgLy8gR2V0IGEgbW9kZWwgZnJvbSB0aGUgc2V0IGJ5IGlkLlxuICBnZXQ6IGZ1bmN0aW9uKG9iaikge1xuICAgIGlmIChvYmogPT0gbnVsbCkgcmV0dXJuIHZvaWQgMDtcbiAgICB2YXIgaWQgPSB0aGlzLm1vZGVsSWQodGhpcy5faXNNb2RlbChvYmopID8gb2JqLmF0dHJpYnV0ZXMgOiBvYmopO1xuICAgIHJldHVybiB0aGlzLl9ieUlkW29ial0gfHwgdGhpcy5fYnlJZFtpZF0gfHwgdGhpcy5fYnlJZFtvYmouY2lkXTtcbiAgfSxcblxuICAgIC8vIEdldCB0aGUgbW9kZWwgYXQgdGhlIGdpdmVuIGluZGV4LlxuICBhdDogZnVuY3Rpb24oaW5kZXgpIHtcbiAgICBpZiAoaW5kZXggPCAwKSBpbmRleCArPSB0aGlzLmxlbmd0aDtcbiAgICByZXR1cm4gdGhpcy5tb2RlbHNbaW5kZXhdO1xuICB9LFxuXG4gICAgLy8gUmV0dXJuIG1vZGVscyB3aXRoIG1hdGNoaW5nIGF0dHJpYnV0ZXMuIFVzZWZ1bCBmb3Igc2ltcGxlIGNhc2VzIG9mXG4gICAgLy8gYGZpbHRlcmAuXG4gIHdoZXJlOiBmdW5jdGlvbihhdHRycywgZmlyc3QpIHtcbiAgICBpZiAoXy5pc0VtcHR5KGF0dHJzKSkgcmV0dXJuIGZpcnN0ID8gdm9pZCAwIDogW107XG4gICAgcmV0dXJuIHRoaXNbZmlyc3QgPyAnZmluZCcgOiAnZmlsdGVyJ10oZnVuY3Rpb24obW9kZWwpIHtcbiAgICAgIGZvciAodmFyIGtleSBpbiBhdHRycykge1xuICAgICAgICBpZiAoYXR0cnNba2V5XSAhPT0gbW9kZWwuZ2V0KGtleSkpIHJldHVybiBmYWxzZTtcbiAgICAgIH1cbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH0pO1xuICB9LFxuXG4gICAgLy8gUmV0dXJuIHRoZSBmaXJzdCBtb2RlbCB3aXRoIG1hdGNoaW5nIGF0dHJpYnV0ZXMuIFVzZWZ1bCBmb3Igc2ltcGxlIGNhc2VzXG4gICAgLy8gb2YgYGZpbmRgLlxuICBmaW5kV2hlcmU6IGZ1bmN0aW9uKGF0dHJzKSB7XG4gICAgcmV0dXJuIHRoaXMud2hlcmUoYXR0cnMsIHRydWUpO1xuICB9LFxuXG4gICAgLy8gRm9yY2UgdGhlIGNvbGxlY3Rpb24gdG8gcmUtc29ydCBpdHNlbGYuIFlvdSBkb24ndCBuZWVkIHRvIGNhbGwgdGhpcyB1bmRlclxuICAgIC8vIG5vcm1hbCBjaXJjdW1zdGFuY2VzLCBhcyB0aGUgc2V0IHdpbGwgbWFpbnRhaW4gc29ydCBvcmRlciBhcyBlYWNoIGl0ZW1cbiAgICAvLyBpcyBhZGRlZC5cbiAgc29ydDogZnVuY3Rpb24ob3B0aW9ucykge1xuICAgIGlmICghdGhpcy5jb21wYXJhdG9yKSB0aHJvdyBuZXcgRXJyb3IoJ0Nhbm5vdCBzb3J0IGEgc2V0IHdpdGhvdXQgYSBjb21wYXJhdG9yJyk7XG4gICAgb3B0aW9ucyB8fCAob3B0aW9ucyA9IHt9KTtcblxuICAgIC8vIFJ1biBzb3J0IGJhc2VkIG9uIHR5cGUgb2YgYGNvbXBhcmF0b3JgLlxuICAgIGlmIChfLmlzU3RyaW5nKHRoaXMuY29tcGFyYXRvcikgfHwgdGhpcy5jb21wYXJhdG9yLmxlbmd0aCA9PT0gMSkge1xuICAgICAgdGhpcy5tb2RlbHMgPSB0aGlzLnNvcnRCeSh0aGlzLmNvbXBhcmF0b3IsIHRoaXMpO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLm1vZGVscy5zb3J0KF8uYmluZCh0aGlzLmNvbXBhcmF0b3IsIHRoaXMpKTtcbiAgICB9XG5cbiAgICBpZiAoIW9wdGlvbnMuc2lsZW50KSB0aGlzLnRyaWdnZXIoJ3NvcnQnLCB0aGlzLCBvcHRpb25zKTtcbiAgICByZXR1cm4gdGhpcztcbiAgfSxcblxuICAgIC8vIFBsdWNrIGFuIGF0dHJpYnV0ZSBmcm9tIGVhY2ggbW9kZWwgaW4gdGhlIGNvbGxlY3Rpb24uXG4gIHBsdWNrOiBmdW5jdGlvbihhdHRyKSB7XG4gICAgcmV0dXJuIF8uaW52b2tlKHRoaXMubW9kZWxzLCAnZ2V0JywgYXR0cik7XG4gIH0sXG5cbiAgICAvLyBGZXRjaCB0aGUgZGVmYXVsdCBzZXQgb2YgbW9kZWxzIGZvciB0aGlzIGNvbGxlY3Rpb24sIHJlc2V0dGluZyB0aGVcbiAgICAvLyBjb2xsZWN0aW9uIHdoZW4gdGhleSBhcnJpdmUuIElmIGByZXNldDogdHJ1ZWAgaXMgcGFzc2VkLCB0aGUgcmVzcG9uc2VcbiAgICAvLyBkYXRhIHdpbGwgYmUgcGFzc2VkIHRocm91Z2ggdGhlIGByZXNldGAgbWV0aG9kIGluc3RlYWQgb2YgYHNldGAuXG4gIGZldGNoOiBmdW5jdGlvbihvcHRpb25zKSB7XG4gICAgb3B0aW9ucyA9IG9wdGlvbnMgPyBfLmNsb25lKG9wdGlvbnMpIDoge307XG4gICAgaWYgKG9wdGlvbnMucGFyc2UgPT09IHZvaWQgMCkgb3B0aW9ucy5wYXJzZSA9IHRydWU7XG4gICAgdmFyIHN1Y2Nlc3MgPSBvcHRpb25zLnN1Y2Nlc3M7XG4gICAgdmFyIGNvbGxlY3Rpb24gPSB0aGlzO1xuICAgIG9wdGlvbnMuc3VjY2VzcyA9IGZ1bmN0aW9uKHJlc3ApIHtcbiAgICAgIHZhciBtZXRob2QgPSBvcHRpb25zLnJlc2V0ID8gJ3Jlc2V0JyA6ICdzZXQnO1xuICAgICAgY29sbGVjdGlvblttZXRob2RdKHJlc3AsIG9wdGlvbnMpO1xuICAgICAgaWYgKHN1Y2Nlc3MpIHN1Y2Nlc3MoY29sbGVjdGlvbiwgcmVzcCwgb3B0aW9ucyk7XG4gICAgICBjb2xsZWN0aW9uLnRyaWdnZXIoJ3N5bmMnLCBjb2xsZWN0aW9uLCByZXNwLCBvcHRpb25zKTtcbiAgICB9O1xuICAgIHdyYXBFcnJvcih0aGlzLCBvcHRpb25zKTtcbiAgICByZXR1cm4gdGhpcy5zeW5jKCdyZWFkJywgdGhpcywgb3B0aW9ucyk7XG4gIH0sXG5cbiAgICAvLyBDcmVhdGUgYSBuZXcgaW5zdGFuY2Ugb2YgYSBtb2RlbCBpbiB0aGlzIGNvbGxlY3Rpb24uIEFkZCB0aGUgbW9kZWwgdG8gdGhlXG4gICAgLy8gY29sbGVjdGlvbiBpbW1lZGlhdGVseSwgdW5sZXNzIGB3YWl0OiB0cnVlYCBpcyBwYXNzZWQsIGluIHdoaWNoIGNhc2Ugd2VcbiAgICAvLyB3YWl0IGZvciB0aGUgc2VydmVyIHRvIGFncmVlLlxuICBjcmVhdGU6IGZ1bmN0aW9uKG1vZGVsLCBvcHRpb25zKSB7XG4gICAgb3B0aW9ucyA9IG9wdGlvbnMgPyBfLmNsb25lKG9wdGlvbnMpIDoge307XG4gICAgaWYgKCEobW9kZWwgPSB0aGlzLl9wcmVwYXJlTW9kZWwobW9kZWwsIG9wdGlvbnMpKSkgcmV0dXJuIGZhbHNlO1xuICAgIGlmICghb3B0aW9ucy53YWl0KSB0aGlzLmFkZChtb2RlbCwgb3B0aW9ucyk7XG4gICAgdmFyIGNvbGxlY3Rpb24gPSB0aGlzO1xuICAgIHZhciBzdWNjZXNzID0gb3B0aW9ucy5zdWNjZXNzO1xuICAgIG9wdGlvbnMuc3VjY2VzcyA9IGZ1bmN0aW9uKG1vZGVsLCByZXNwKSB7XG4gICAgICBpZiAob3B0aW9ucy53YWl0KSBjb2xsZWN0aW9uLmFkZChtb2RlbCwgb3B0aW9ucyk7XG4gICAgICBpZiAoc3VjY2Vzcykgc3VjY2Vzcyhtb2RlbCwgcmVzcCwgb3B0aW9ucyk7XG4gICAgfTtcbiAgICBtb2RlbC5zYXZlKG51bGwsIG9wdGlvbnMpO1xuICAgIHJldHVybiBtb2RlbDtcbiAgfSxcblxuICAgIC8vICoqcGFyc2UqKiBjb252ZXJ0cyBhIHJlc3BvbnNlIGludG8gYSBsaXN0IG9mIG1vZGVscyB0byBiZSBhZGRlZCB0byB0aGVcbiAgICAvLyBjb2xsZWN0aW9uLiBUaGUgZGVmYXVsdCBpbXBsZW1lbnRhdGlvbiBpcyBqdXN0IHRvIHBhc3MgaXQgdGhyb3VnaC5cbiAgcGFyc2U6IGZ1bmN0aW9uKHJlc3AsIG9wdGlvbnMpIHtcbiAgICByZXR1cm4gcmVzcDtcbiAgfSxcblxuICAgIC8vIENyZWF0ZSBhIG5ldyBjb2xsZWN0aW9uIHdpdGggYW4gaWRlbnRpY2FsIGxpc3Qgb2YgbW9kZWxzIGFzIHRoaXMgb25lLlxuICBjbG9uZTogZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIG5ldyB0aGlzLmNvbnN0cnVjdG9yKHRoaXMubW9kZWxzLCB7XG4gICAgICBtb2RlbDogdGhpcy5tb2RlbCxcbiAgICAgIGNvbXBhcmF0b3I6IHRoaXMuY29tcGFyYXRvclxuICAgIH0pO1xuICB9LFxuXG4gICAgLy8gRGVmaW5lIGhvdyB0byB1bmlxdWVseSBpZGVudGlmeSBtb2RlbHMgaW4gdGhlIGNvbGxlY3Rpb24uXG4gIG1vZGVsSWQ6IGZ1bmN0aW9uIChhdHRycykge1xuICAgIHJldHVybiBhdHRyc1t0aGlzLm1vZGVsLnByb3RvdHlwZS5pZEF0dHJpYnV0ZSB8fCAnaWQnXTtcbiAgfSxcblxuICAgIC8vIFByaXZhdGUgbWV0aG9kIHRvIHJlc2V0IGFsbCBpbnRlcm5hbCBzdGF0ZS4gQ2FsbGVkIHdoZW4gdGhlIGNvbGxlY3Rpb25cbiAgICAvLyBpcyBmaXJzdCBpbml0aWFsaXplZCBvciByZXNldC5cbiAgX3Jlc2V0OiBmdW5jdGlvbigpIHtcbiAgICB0aGlzLmxlbmd0aCA9IDA7XG4gICAgdGhpcy5tb2RlbHMgPSBbXTtcbiAgICB0aGlzLl9ieUlkICA9IHt9O1xuICB9LFxuXG4gICAgLy8gUHJlcGFyZSBhIGhhc2ggb2YgYXR0cmlidXRlcyAob3Igb3RoZXIgbW9kZWwpIHRvIGJlIGFkZGVkIHRvIHRoaXNcbiAgICAvLyBjb2xsZWN0aW9uLlxuICBfcHJlcGFyZU1vZGVsOiBmdW5jdGlvbihhdHRycywgb3B0aW9ucykge1xuICAgIGlmICh0aGlzLl9pc01vZGVsKGF0dHJzKSkge1xuICAgICAgaWYgKCFhdHRycy5jb2xsZWN0aW9uKSBhdHRycy5jb2xsZWN0aW9uID0gdGhpcztcbiAgICAgIHJldHVybiBhdHRycztcbiAgICB9XG4gICAgb3B0aW9ucyA9IG9wdGlvbnMgPyBfLmNsb25lKG9wdGlvbnMpIDoge307XG4gICAgb3B0aW9ucy5jb2xsZWN0aW9uID0gdGhpcztcbiAgICB2YXIgbW9kZWwgPSBuZXcgdGhpcy5tb2RlbChhdHRycywgb3B0aW9ucyk7XG4gICAgaWYgKCFtb2RlbC52YWxpZGF0aW9uRXJyb3IpIHJldHVybiBtb2RlbDtcbiAgICB0aGlzLnRyaWdnZXIoJ2ludmFsaWQnLCB0aGlzLCBtb2RlbC52YWxpZGF0aW9uRXJyb3IsIG9wdGlvbnMpO1xuICAgIHJldHVybiBmYWxzZTtcbiAgfSxcblxuICAgIC8vIE1ldGhvZCBmb3IgY2hlY2tpbmcgd2hldGhlciBhbiBvYmplY3Qgc2hvdWxkIGJlIGNvbnNpZGVyZWQgYSBtb2RlbCBmb3JcbiAgICAvLyB0aGUgcHVycG9zZXMgb2YgYWRkaW5nIHRvIHRoZSBjb2xsZWN0aW9uLlxuICBfaXNNb2RlbDogZnVuY3Rpb24gKG1vZGVsKSB7XG4gICAgcmV0dXJuIG1vZGVsIGluc3RhbmNlb2YgTW9kZWw7XG4gIH0sXG5cbiAgICAvLyBJbnRlcm5hbCBtZXRob2QgdG8gY3JlYXRlIGEgbW9kZWwncyB0aWVzIHRvIGEgY29sbGVjdGlvbi5cbiAgX2FkZFJlZmVyZW5jZTogZnVuY3Rpb24obW9kZWwsIG9wdGlvbnMpIHtcbiAgICB0aGlzLl9ieUlkW21vZGVsLmNpZF0gPSBtb2RlbDtcbiAgICB2YXIgaWQgPSB0aGlzLm1vZGVsSWQobW9kZWwuYXR0cmlidXRlcyk7XG4gICAgaWYgKGlkICE9IG51bGwpIHRoaXMuX2J5SWRbaWRdID0gbW9kZWw7XG4gICAgbW9kZWwub24oJ2FsbCcsIHRoaXMuX29uTW9kZWxFdmVudCwgdGhpcyk7XG4gIH0sXG5cbiAgICAvLyBJbnRlcm5hbCBtZXRob2QgdG8gc2V2ZXIgYSBtb2RlbCdzIHRpZXMgdG8gYSBjb2xsZWN0aW9uLlxuICBfcmVtb3ZlUmVmZXJlbmNlOiBmdW5jdGlvbihtb2RlbCwgb3B0aW9ucykge1xuICAgIGlmICh0aGlzID09PSBtb2RlbC5jb2xsZWN0aW9uKSBkZWxldGUgbW9kZWwuY29sbGVjdGlvbjtcbiAgICBtb2RlbC5vZmYoJ2FsbCcsIHRoaXMuX29uTW9kZWxFdmVudCwgdGhpcyk7XG4gIH0sXG5cbiAgICAvLyBJbnRlcm5hbCBtZXRob2QgY2FsbGVkIGV2ZXJ5IHRpbWUgYSBtb2RlbCBpbiB0aGUgc2V0IGZpcmVzIGFuIGV2ZW50LlxuICAgIC8vIFNldHMgbmVlZCB0byB1cGRhdGUgdGhlaXIgaW5kZXhlcyB3aGVuIG1vZGVscyBjaGFuZ2UgaWRzLiBBbGwgb3RoZXJcbiAgICAvLyBldmVudHMgc2ltcGx5IHByb3h5IHRocm91Z2guIFwiYWRkXCIgYW5kIFwicmVtb3ZlXCIgZXZlbnRzIHRoYXQgb3JpZ2luYXRlXG4gICAgLy8gaW4gb3RoZXIgY29sbGVjdGlvbnMgYXJlIGlnbm9yZWQuXG4gIF9vbk1vZGVsRXZlbnQ6IGZ1bmN0aW9uKGV2ZW50LCBtb2RlbCwgY29sbGVjdGlvbiwgb3B0aW9ucykge1xuICAgIGlmICgoZXZlbnQgPT09ICdhZGQnIHx8IGV2ZW50ID09PSAncmVtb3ZlJykgJiYgY29sbGVjdGlvbiAhPT0gdGhpcykgcmV0dXJuO1xuICAgIGlmIChldmVudCA9PT0gJ2Rlc3Ryb3knKSB0aGlzLnJlbW92ZShtb2RlbCwgb3B0aW9ucyk7XG4gICAgaWYgKGV2ZW50ID09PSAnY2hhbmdlJykge1xuICAgICAgdmFyIHByZXZJZCA9IHRoaXMubW9kZWxJZChtb2RlbC5wcmV2aW91c0F0dHJpYnV0ZXMoKSk7XG4gICAgICB2YXIgaWQgPSB0aGlzLm1vZGVsSWQobW9kZWwuYXR0cmlidXRlcyk7XG4gICAgICBpZiAocHJldklkICE9PSBpZCkge1xuICAgICAgICBpZiAocHJldklkICE9IG51bGwpIGRlbGV0ZSB0aGlzLl9ieUlkW3ByZXZJZF07XG4gICAgICAgIGlmIChpZCAhPSBudWxsKSB0aGlzLl9ieUlkW2lkXSA9IG1vZGVsO1xuICAgICAgfVxuICAgIH1cbiAgICB0aGlzLnRyaWdnZXIuYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgfVxuXG59KTtcblxuLy8gVW5kZXJzY29yZSBtZXRob2RzIHRoYXQgd2Ugd2FudCB0byBpbXBsZW1lbnQgb24gdGhlIENvbGxlY3Rpb24uXG4vLyA5MCUgb2YgdGhlIGNvcmUgdXNlZnVsbmVzcyBvZiBCYWNrYm9uZSBDb2xsZWN0aW9ucyBpcyBhY3R1YWxseSBpbXBsZW1lbnRlZFxuLy8gcmlnaHQgaGVyZTpcbnZhciBtZXRob2RzID0gWydmb3JFYWNoJywgJ2VhY2gnLCAnbWFwJywgJ2NvbGxlY3QnLCAncmVkdWNlJywgJ2ZvbGRsJyxcbiAgICAnaW5qZWN0JywgJ3JlZHVjZVJpZ2h0JywgJ2ZvbGRyJywgJ2ZpbmQnLCAnZGV0ZWN0JywgJ2ZpbHRlcicsICdzZWxlY3QnLFxuICAgICdyZWplY3QnLCAnZXZlcnknLCAnYWxsJywgJ3NvbWUnLCAnYW55JywgJ2luY2x1ZGUnLCAnY29udGFpbnMnLCAnaW52b2tlJyxcbiAgICAnbWF4JywgJ21pbicsICd0b0FycmF5JywgJ3NpemUnLCAnZmlyc3QnLCAnaGVhZCcsICd0YWtlJywgJ2luaXRpYWwnLCAncmVzdCcsXG4gICAgJ3RhaWwnLCAnZHJvcCcsICdsYXN0JywgJ3dpdGhvdXQnLCAnZGlmZmVyZW5jZScsICdpbmRleE9mJywgJ3NodWZmbGUnLFxuICAgICdsYXN0SW5kZXhPZicsICdpc0VtcHR5JywgJ2NoYWluJywgJ3NhbXBsZScsICdwYXJ0aXRpb24nXTtcblxuLy8gTWl4IGluIGVhY2ggVW5kZXJzY29yZSBtZXRob2QgYXMgYSBwcm94eSB0byBgQ29sbGVjdGlvbiNtb2RlbHNgLlxuXy5lYWNoKG1ldGhvZHMsIGZ1bmN0aW9uKG1ldGhvZCkge1xuICBpZiAoIV9bbWV0aG9kXSkgcmV0dXJuO1xuICBDb2xsZWN0aW9uLnByb3RvdHlwZVttZXRob2RdID0gZnVuY3Rpb24oKSB7XG4gICAgdmFyIGFyZ3MgPSBzbGljZS5jYWxsKGFyZ3VtZW50cyk7XG4gICAgYXJncy51bnNoaWZ0KHRoaXMubW9kZWxzKTtcbiAgICByZXR1cm4gX1ttZXRob2RdLmFwcGx5KF8sIGFyZ3MpO1xuICB9O1xufSk7XG5cbi8vIFVuZGVyc2NvcmUgbWV0aG9kcyB0aGF0IHRha2UgYSBwcm9wZXJ0eSBuYW1lIGFzIGFuIGFyZ3VtZW50LlxudmFyIGF0dHJpYnV0ZU1ldGhvZHMgPSBbJ2dyb3VwQnknLCAnY291bnRCeScsICdzb3J0QnknLCAnaW5kZXhCeSddO1xuXG4vLyBVc2UgYXR0cmlidXRlcyBpbnN0ZWFkIG9mIHByb3BlcnRpZXMuXG5fLmVhY2goYXR0cmlidXRlTWV0aG9kcywgZnVuY3Rpb24obWV0aG9kKSB7XG4gIGlmICghX1ttZXRob2RdKSByZXR1cm47XG4gIENvbGxlY3Rpb24ucHJvdG90eXBlW21ldGhvZF0gPSBmdW5jdGlvbih2YWx1ZSwgY29udGV4dCkge1xuICAgIHZhciBpdGVyYXRvciA9IF8uaXNGdW5jdGlvbih2YWx1ZSkgPyB2YWx1ZSA6IGZ1bmN0aW9uKG1vZGVsKSB7XG4gICAgICByZXR1cm4gbW9kZWwuZ2V0KHZhbHVlKTtcbiAgICB9O1xuICAgIHJldHVybiBfW21ldGhvZF0odGhpcy5tb2RlbHMsIGl0ZXJhdG9yLCBjb250ZXh0KTtcbiAgfTtcbn0pO1xuXG4vLyBzZXR1cCBpbmhlcml0YW5jZVxuQ29sbGVjdGlvbi5leHRlbmQgPSBleHRlbmQ7XG5tb2R1bGUuZXhwb3J0cyA9IENvbGxlY3Rpb247XG4iLCJtb2R1bGUuZXhwb3J0cy5Nb2RlbCA9IHJlcXVpcmUoXCIuL21vZGVsXCIpO1xubW9kdWxlLmV4cG9ydHMuQ29sbGVjdGlvbiA9IHJlcXVpcmUoXCIuL2NvbGxlY3Rpb25cIik7XG5tb2R1bGUuZXhwb3J0cy5FdmVudHMgPSByZXF1aXJlKFwiYmFja2JvbmUtZXZlbnRzLXN0YW5kYWxvbmVcIik7XG5tb2R1bGUuZXhwb3J0cy5leHRlbmQgPSByZXF1aXJlKFwiYmFja2JvbmUtZXh0ZW5kLXN0YW5kYWxvbmVcIik7XG4iLCIvLyAgICAgQmFja2JvbmUuanMgMS4xLjJcblxuLy8gICAgIChjKSAyMDEwLTIwMTQgSmVyZW15IEFzaGtlbmFzLCBEb2N1bWVudENsb3VkIGFuZCBJbnZlc3RpZ2F0aXZlIFJlcG9ydGVycyAmIEVkaXRvcnNcbi8vICAgICBCYWNrYm9uZSBtYXkgYmUgZnJlZWx5IGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBNSVQgbGljZW5zZS5cbi8vICAgICBGb3IgYWxsIGRldGFpbHMgYW5kIGRvY3VtZW50YXRpb246XG4vLyAgICAgaHR0cDovL2JhY2tib25lanMub3JnXG5cbnZhciBFdmVudHMgPSByZXF1aXJlKFwiYmFja2JvbmUtZXZlbnRzLXN0YW5kYWxvbmVcIik7XG52YXIgZXh0ZW5kID0gcmVxdWlyZShcImJhY2tib25lLWV4dGVuZC1zdGFuZGFsb25lXCIpO1xudmFyIF8gPSByZXF1aXJlKFwidW5kZXJzY29yZVwiKTtcblxuLy8gQmFja2JvbmUuTW9kZWxcbi8vIC0tLS0tLS0tLS0tLS0tXG5cbi8vIEJhY2tib25lICoqTW9kZWxzKiogYXJlIHRoZSBiYXNpYyBkYXRhIG9iamVjdCBpbiB0aGUgZnJhbWV3b3JrIC0tXG4vLyBmcmVxdWVudGx5IHJlcHJlc2VudGluZyBhIHJvdyBpbiBhIHRhYmxlIGluIGEgZGF0YWJhc2Ugb24geW91ciBzZXJ2ZXIuXG4vLyBBIGRpc2NyZXRlIGNodW5rIG9mIGRhdGEgYW5kIGEgYnVuY2ggb2YgdXNlZnVsLCByZWxhdGVkIG1ldGhvZHMgZm9yXG4vLyBwZXJmb3JtaW5nIGNvbXB1dGF0aW9ucyBhbmQgdHJhbnNmb3JtYXRpb25zIG9uIHRoYXQgZGF0YS5cblxuLy8gQ3JlYXRlIGEgbmV3IG1vZGVsIHdpdGggdGhlIHNwZWNpZmllZCBhdHRyaWJ1dGVzLiBBIGNsaWVudCBpZCAoYGNpZGApXG4vLyBpcyBhdXRvbWF0aWNhbGx5IGdlbmVyYXRlZCBhbmQgYXNzaWduZWQgZm9yIHlvdS5cbnZhciBNb2RlbCA9IGZ1bmN0aW9uKGF0dHJpYnV0ZXMsIG9wdGlvbnMpIHtcbiAgdmFyIGF0dHJzID0gYXR0cmlidXRlcyB8fCB7fTtcbiAgb3B0aW9ucyB8fCAob3B0aW9ucyA9IHt9KTtcbiAgdGhpcy5jaWQgPSBfLnVuaXF1ZUlkKCdjJyk7XG4gIHRoaXMuYXR0cmlidXRlcyA9IHt9O1xuICBpZiAob3B0aW9ucy5jb2xsZWN0aW9uKSB0aGlzLmNvbGxlY3Rpb24gPSBvcHRpb25zLmNvbGxlY3Rpb247XG4gIGlmIChvcHRpb25zLnBhcnNlKSBhdHRycyA9IHRoaXMucGFyc2UoYXR0cnMsIG9wdGlvbnMpIHx8IHt9O1xuICBhdHRycyA9IF8uZGVmYXVsdHMoe30sIGF0dHJzLCBfLnJlc3VsdCh0aGlzLCAnZGVmYXVsdHMnKSk7XG4gIHRoaXMuc2V0KGF0dHJzLCBvcHRpb25zKTtcbiAgdGhpcy5jaGFuZ2VkID0ge307XG4gIHRoaXMuaW5pdGlhbGl6ZS5hcHBseSh0aGlzLCBhcmd1bWVudHMpO1xufTtcblxuLy8gQXR0YWNoIGFsbCBpbmhlcml0YWJsZSBtZXRob2RzIHRvIHRoZSBNb2RlbCBwcm90b3R5cGUuXG5fLmV4dGVuZChNb2RlbC5wcm90b3R5cGUsIEV2ZW50cywge1xuXG4gIC8vIEEgaGFzaCBvZiBhdHRyaWJ1dGVzIHdob3NlIGN1cnJlbnQgYW5kIHByZXZpb3VzIHZhbHVlIGRpZmZlci5cbiAgY2hhbmdlZDogbnVsbCxcblxuICAvLyBUaGUgdmFsdWUgcmV0dXJuZWQgZHVyaW5nIHRoZSBsYXN0IGZhaWxlZCB2YWxpZGF0aW9uLlxuICB2YWxpZGF0aW9uRXJyb3I6IG51bGwsXG5cbiAgICAvLyBUaGUgZGVmYXVsdCBuYW1lIGZvciB0aGUgSlNPTiBgaWRgIGF0dHJpYnV0ZSBpcyBgXCJpZFwiYC4gTW9uZ29EQiBhbmRcbiAgICAvLyBDb3VjaERCIHVzZXJzIG1heSB3YW50IHRvIHNldCB0aGlzIHRvIGBcIl9pZFwiYC5cbiAgaWRBdHRyaWJ1dGU6ICdpZCcsXG5cbiAgICAvLyBJbml0aWFsaXplIGlzIGFuIGVtcHR5IGZ1bmN0aW9uIGJ5IGRlZmF1bHQuIE92ZXJyaWRlIGl0IHdpdGggeW91ciBvd25cbiAgICAvLyBpbml0aWFsaXphdGlvbiBsb2dpYy5cbiAgaW5pdGlhbGl6ZTogZnVuY3Rpb24oKXt9LFxuXG4gICAgLy8gUmV0dXJuIGEgY29weSBvZiB0aGUgbW9kZWwncyBgYXR0cmlidXRlc2Agb2JqZWN0LlxuICB0b0pTT046IGZ1bmN0aW9uKG9wdGlvbnMpIHtcbiAgICByZXR1cm4gXy5jbG9uZSh0aGlzLmF0dHJpYnV0ZXMpO1xuICB9LFxuXG4gICAgLy8gUHJveHkgYEJhY2tib25lLnN5bmNgIGJ5IGRlZmF1bHQgLS0gYnV0IG92ZXJyaWRlIHRoaXMgaWYgeW91IG5lZWRcbiAgICAvLyBjdXN0b20gc3luY2luZyBzZW1hbnRpY3MgZm9yICp0aGlzKiBwYXJ0aWN1bGFyIG1vZGVsLlxuICBzeW5jOiBmdW5jdGlvbigpIHtcbiAgICByZXR1cm4gQmFja2JvbmUuc3luYy5hcHBseSh0aGlzLCBhcmd1bWVudHMpO1xuICB9LFxuXG4gICAgLy8gR2V0IHRoZSB2YWx1ZSBvZiBhbiBhdHRyaWJ1dGUuXG4gIGdldDogZnVuY3Rpb24oYXR0cikge1xuICAgIHJldHVybiB0aGlzLmF0dHJpYnV0ZXNbYXR0cl07XG4gIH0sXG5cbiAgICAvLyBHZXQgdGhlIEhUTUwtZXNjYXBlZCB2YWx1ZSBvZiBhbiBhdHRyaWJ1dGUuXG4gIGVzY2FwZTogZnVuY3Rpb24oYXR0cikge1xuICAgIHJldHVybiBfLmVzY2FwZSh0aGlzLmdldChhdHRyKSk7XG4gIH0sXG5cbiAgICAvLyBSZXR1cm5zIGB0cnVlYCBpZiB0aGUgYXR0cmlidXRlIGNvbnRhaW5zIGEgdmFsdWUgdGhhdCBpcyBub3QgbnVsbFxuICAgIC8vIG9yIHVuZGVmaW5lZC5cbiAgaGFzOiBmdW5jdGlvbihhdHRyKSB7XG4gICAgcmV0dXJuIHRoaXMuZ2V0KGF0dHIpICE9IG51bGw7XG4gIH0sXG5cbiAgICAvLyBTZXQgYSBoYXNoIG9mIG1vZGVsIGF0dHJpYnV0ZXMgb24gdGhlIG9iamVjdCwgZmlyaW5nIGBcImNoYW5nZVwiYC4gVGhpcyBpc1xuICAgIC8vIHRoZSBjb3JlIHByaW1pdGl2ZSBvcGVyYXRpb24gb2YgYSBtb2RlbCwgdXBkYXRpbmcgdGhlIGRhdGEgYW5kIG5vdGlmeWluZ1xuICAgIC8vIGFueW9uZSB3aG8gbmVlZHMgdG8ga25vdyBhYm91dCB0aGUgY2hhbmdlIGluIHN0YXRlLiBUaGUgaGVhcnQgb2YgdGhlIGJlYXN0LlxuICBzZXQ6IGZ1bmN0aW9uKGtleSwgdmFsLCBvcHRpb25zKSB7XG4gICAgdmFyIGF0dHIsIGF0dHJzLCB1bnNldCwgY2hhbmdlcywgc2lsZW50LCBjaGFuZ2luZywgcHJldiwgY3VycmVudDtcbiAgICBpZiAoa2V5ID09IG51bGwpIHJldHVybiB0aGlzO1xuXG4gICAgLy8gSGFuZGxlIGJvdGggYFwia2V5XCIsIHZhbHVlYCBhbmQgYHtrZXk6IHZhbHVlfWAgLXN0eWxlIGFyZ3VtZW50cy5cbiAgICBpZiAodHlwZW9mIGtleSA9PT0gJ29iamVjdCcpIHtcbiAgICAgIGF0dHJzID0ga2V5O1xuICAgICAgb3B0aW9ucyA9IHZhbDtcbiAgICB9IGVsc2Uge1xuICAgICAgKGF0dHJzID0ge30pW2tleV0gPSB2YWw7XG4gICAgfVxuXG4gICAgb3B0aW9ucyB8fCAob3B0aW9ucyA9IHt9KTtcblxuICAgIC8vIFJ1biB2YWxpZGF0aW9uLlxuICAgIGlmICghdGhpcy5fdmFsaWRhdGUoYXR0cnMsIG9wdGlvbnMpKSByZXR1cm4gZmFsc2U7XG5cbiAgICAvLyBFeHRyYWN0IGF0dHJpYnV0ZXMgYW5kIG9wdGlvbnMuXG4gICAgdW5zZXQgICAgICAgICAgID0gb3B0aW9ucy51bnNldDtcbiAgICBzaWxlbnQgICAgICAgICAgPSBvcHRpb25zLnNpbGVudDtcbiAgICBjaGFuZ2VzICAgICAgICAgPSBbXTtcbiAgICBjaGFuZ2luZyAgICAgICAgPSB0aGlzLl9jaGFuZ2luZztcbiAgICB0aGlzLl9jaGFuZ2luZyAgPSB0cnVlO1xuXG4gICAgaWYgKCFjaGFuZ2luZykge1xuICAgICAgdGhpcy5fcHJldmlvdXNBdHRyaWJ1dGVzID0gXy5jbG9uZSh0aGlzLmF0dHJpYnV0ZXMpO1xuICAgICAgdGhpcy5jaGFuZ2VkID0ge307XG4gICAgfVxuICAgIGN1cnJlbnQgPSB0aGlzLmF0dHJpYnV0ZXMsIHByZXYgPSB0aGlzLl9wcmV2aW91c0F0dHJpYnV0ZXM7XG5cbiAgICAvLyBDaGVjayBmb3IgY2hhbmdlcyBvZiBgaWRgLlxuICAgIGlmICh0aGlzLmlkQXR0cmlidXRlIGluIGF0dHJzKSB0aGlzLmlkID0gYXR0cnNbdGhpcy5pZEF0dHJpYnV0ZV07XG5cbiAgICAvLyBGb3IgZWFjaCBgc2V0YCBhdHRyaWJ1dGUsIHVwZGF0ZSBvciBkZWxldGUgdGhlIGN1cnJlbnQgdmFsdWUuXG4gICAgZm9yIChhdHRyIGluIGF0dHJzKSB7XG4gICAgICB2YWwgPSBhdHRyc1thdHRyXTtcbiAgICAgIGlmICghXy5pc0VxdWFsKGN1cnJlbnRbYXR0cl0sIHZhbCkpIGNoYW5nZXMucHVzaChhdHRyKTtcbiAgICAgIGlmICghXy5pc0VxdWFsKHByZXZbYXR0cl0sIHZhbCkpIHtcbiAgICAgICAgdGhpcy5jaGFuZ2VkW2F0dHJdID0gdmFsO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgZGVsZXRlIHRoaXMuY2hhbmdlZFthdHRyXTtcbiAgICAgIH1cbiAgICAgIHVuc2V0ID8gZGVsZXRlIGN1cnJlbnRbYXR0cl0gOiBjdXJyZW50W2F0dHJdID0gdmFsO1xuICAgIH1cblxuICAgIC8vIFRyaWdnZXIgYWxsIHJlbGV2YW50IGF0dHJpYnV0ZSBjaGFuZ2VzLlxuICAgIGlmICghc2lsZW50KSB7XG4gICAgICBpZiAoY2hhbmdlcy5sZW5ndGgpIHRoaXMuX3BlbmRpbmcgPSBvcHRpb25zO1xuICAgICAgZm9yICh2YXIgaSA9IDAsIGxlbmd0aCA9IGNoYW5nZXMubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgdGhpcy50cmlnZ2VyKCdjaGFuZ2U6JyArIGNoYW5nZXNbaV0sIHRoaXMsIGN1cnJlbnRbY2hhbmdlc1tpXV0sIG9wdGlvbnMpO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIFlvdSBtaWdodCBiZSB3b25kZXJpbmcgd2h5IHRoZXJlJ3MgYSBgd2hpbGVgIGxvb3AgaGVyZS4gQ2hhbmdlcyBjYW5cbiAgICAvLyBiZSByZWN1cnNpdmVseSBuZXN0ZWQgd2l0aGluIGBcImNoYW5nZVwiYCBldmVudHMuXG4gICAgaWYgKGNoYW5naW5nKSByZXR1cm4gdGhpcztcbiAgICBpZiAoIXNpbGVudCkge1xuICAgICAgd2hpbGUgKHRoaXMuX3BlbmRpbmcpIHtcbiAgICAgICAgb3B0aW9ucyA9IHRoaXMuX3BlbmRpbmc7XG4gICAgICAgIHRoaXMuX3BlbmRpbmcgPSBmYWxzZTtcbiAgICAgICAgdGhpcy50cmlnZ2VyKCdjaGFuZ2UnLCB0aGlzLCBvcHRpb25zKTtcbiAgICAgIH1cbiAgICB9XG4gICAgdGhpcy5fcGVuZGluZyA9IGZhbHNlO1xuICAgIHRoaXMuX2NoYW5naW5nID0gZmFsc2U7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH0sXG5cbiAgICAvLyBSZW1vdmUgYW4gYXR0cmlidXRlIGZyb20gdGhlIG1vZGVsLCBmaXJpbmcgYFwiY2hhbmdlXCJgLiBgdW5zZXRgIGlzIGEgbm9vcFxuICAgIC8vIGlmIHRoZSBhdHRyaWJ1dGUgZG9lc24ndCBleGlzdC5cbiAgdW5zZXQ6IGZ1bmN0aW9uKGF0dHIsIG9wdGlvbnMpIHtcbiAgICByZXR1cm4gdGhpcy5zZXQoYXR0ciwgdm9pZCAwLCBfLmV4dGVuZCh7fSwgb3B0aW9ucywge3Vuc2V0OiB0cnVlfSkpO1xuICB9LFxuXG4gICAgLy8gQ2xlYXIgYWxsIGF0dHJpYnV0ZXMgb24gdGhlIG1vZGVsLCBmaXJpbmcgYFwiY2hhbmdlXCJgLlxuICBjbGVhcjogZnVuY3Rpb24ob3B0aW9ucykge1xuICAgIHZhciBhdHRycyA9IHt9O1xuICAgIGZvciAodmFyIGtleSBpbiB0aGlzLmF0dHJpYnV0ZXMpIGF0dHJzW2tleV0gPSB2b2lkIDA7XG4gICAgcmV0dXJuIHRoaXMuc2V0KGF0dHJzLCBfLmV4dGVuZCh7fSwgb3B0aW9ucywge3Vuc2V0OiB0cnVlfSkpO1xuICB9LFxuXG4gICAgLy8gRGV0ZXJtaW5lIGlmIHRoZSBtb2RlbCBoYXMgY2hhbmdlZCBzaW5jZSB0aGUgbGFzdCBgXCJjaGFuZ2VcImAgZXZlbnQuXG4gICAgLy8gSWYgeW91IHNwZWNpZnkgYW4gYXR0cmlidXRlIG5hbWUsIGRldGVybWluZSBpZiB0aGF0IGF0dHJpYnV0ZSBoYXMgY2hhbmdlZC5cbiAgaGFzQ2hhbmdlZDogZnVuY3Rpb24oYXR0cikge1xuICAgIGlmIChhdHRyID09IG51bGwpIHJldHVybiAhXy5pc0VtcHR5KHRoaXMuY2hhbmdlZCk7XG4gICAgcmV0dXJuIF8uaGFzKHRoaXMuY2hhbmdlZCwgYXR0cik7XG4gIH0sXG5cbiAgICAvLyBSZXR1cm4gYW4gb2JqZWN0IGNvbnRhaW5pbmcgYWxsIHRoZSBhdHRyaWJ1dGVzIHRoYXQgaGF2ZSBjaGFuZ2VkLCBvclxuICAgIC8vIGZhbHNlIGlmIHRoZXJlIGFyZSBubyBjaGFuZ2VkIGF0dHJpYnV0ZXMuIFVzZWZ1bCBmb3IgZGV0ZXJtaW5pbmcgd2hhdFxuICAgIC8vIHBhcnRzIG9mIGEgdmlldyBuZWVkIHRvIGJlIHVwZGF0ZWQgYW5kL29yIHdoYXQgYXR0cmlidXRlcyBuZWVkIHRvIGJlXG4gICAgLy8gcGVyc2lzdGVkIHRvIHRoZSBzZXJ2ZXIuIFVuc2V0IGF0dHJpYnV0ZXMgd2lsbCBiZSBzZXQgdG8gdW5kZWZpbmVkLlxuICAgIC8vIFlvdSBjYW4gYWxzbyBwYXNzIGFuIGF0dHJpYnV0ZXMgb2JqZWN0IHRvIGRpZmYgYWdhaW5zdCB0aGUgbW9kZWwsXG4gICAgLy8gZGV0ZXJtaW5pbmcgaWYgdGhlcmUgKndvdWxkIGJlKiBhIGNoYW5nZS5cbiAgY2hhbmdlZEF0dHJpYnV0ZXM6IGZ1bmN0aW9uKGRpZmYpIHtcbiAgICBpZiAoIWRpZmYpIHJldHVybiB0aGlzLmhhc0NoYW5nZWQoKSA/IF8uY2xvbmUodGhpcy5jaGFuZ2VkKSA6IGZhbHNlO1xuICAgIHZhciB2YWwsIGNoYW5nZWQgPSBmYWxzZTtcbiAgICB2YXIgb2xkID0gdGhpcy5fY2hhbmdpbmcgPyB0aGlzLl9wcmV2aW91c0F0dHJpYnV0ZXMgOiB0aGlzLmF0dHJpYnV0ZXM7XG4gICAgZm9yICh2YXIgYXR0ciBpbiBkaWZmKSB7XG4gICAgICBpZiAoXy5pc0VxdWFsKG9sZFthdHRyXSwgKHZhbCA9IGRpZmZbYXR0cl0pKSkgY29udGludWU7XG4gICAgICAoY2hhbmdlZCB8fCAoY2hhbmdlZCA9IHt9KSlbYXR0cl0gPSB2YWw7XG4gICAgfVxuICAgIHJldHVybiBjaGFuZ2VkO1xuICB9LFxuXG4gICAgLy8gR2V0IHRoZSBwcmV2aW91cyB2YWx1ZSBvZiBhbiBhdHRyaWJ1dGUsIHJlY29yZGVkIGF0IHRoZSB0aW1lIHRoZSBsYXN0XG4gICAgLy8gYFwiY2hhbmdlXCJgIGV2ZW50IHdhcyBmaXJlZC5cbiAgcHJldmlvdXM6IGZ1bmN0aW9uKGF0dHIpIHtcbiAgICBpZiAoYXR0ciA9PSBudWxsIHx8ICF0aGlzLl9wcmV2aW91c0F0dHJpYnV0ZXMpIHJldHVybiBudWxsO1xuICAgIHJldHVybiB0aGlzLl9wcmV2aW91c0F0dHJpYnV0ZXNbYXR0cl07XG4gIH0sXG5cbiAgICAvLyBHZXQgYWxsIG9mIHRoZSBhdHRyaWJ1dGVzIG9mIHRoZSBtb2RlbCBhdCB0aGUgdGltZSBvZiB0aGUgcHJldmlvdXNcbiAgICAvLyBgXCJjaGFuZ2VcImAgZXZlbnQuXG4gIHByZXZpb3VzQXR0cmlidXRlczogZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIF8uY2xvbmUodGhpcy5fcHJldmlvdXNBdHRyaWJ1dGVzKTtcbiAgfSxcblxuICAgIC8vIEZldGNoIHRoZSBtb2RlbCBmcm9tIHRoZSBzZXJ2ZXIuIElmIHRoZSBzZXJ2ZXIncyByZXByZXNlbnRhdGlvbiBvZiB0aGVcbiAgICAvLyBtb2RlbCBkaWZmZXJzIGZyb20gaXRzIGN1cnJlbnQgYXR0cmlidXRlcywgdGhleSB3aWxsIGJlIG92ZXJyaWRkZW4sXG4gICAgLy8gdHJpZ2dlcmluZyBhIGBcImNoYW5nZVwiYCBldmVudC5cbiAgZmV0Y2g6IGZ1bmN0aW9uKG9wdGlvbnMpIHtcbiAgICBvcHRpb25zID0gb3B0aW9ucyA/IF8uY2xvbmUob3B0aW9ucykgOiB7fTtcbiAgICBpZiAob3B0aW9ucy5wYXJzZSA9PT0gdm9pZCAwKSBvcHRpb25zLnBhcnNlID0gdHJ1ZTtcbiAgICB2YXIgbW9kZWwgPSB0aGlzO1xuICAgIHZhciBzdWNjZXNzID0gb3B0aW9ucy5zdWNjZXNzO1xuICAgIG9wdGlvbnMuc3VjY2VzcyA9IGZ1bmN0aW9uKHJlc3ApIHtcbiAgICAgIGlmICghbW9kZWwuc2V0KG1vZGVsLnBhcnNlKHJlc3AsIG9wdGlvbnMpLCBvcHRpb25zKSkgcmV0dXJuIGZhbHNlO1xuICAgICAgaWYgKHN1Y2Nlc3MpIHN1Y2Nlc3MobW9kZWwsIHJlc3AsIG9wdGlvbnMpO1xuICAgICAgbW9kZWwudHJpZ2dlcignc3luYycsIG1vZGVsLCByZXNwLCBvcHRpb25zKTtcbiAgICB9O1xuICAgIHdyYXBFcnJvcih0aGlzLCBvcHRpb25zKTtcbiAgICByZXR1cm4gdGhpcy5zeW5jKCdyZWFkJywgdGhpcywgb3B0aW9ucyk7XG4gIH0sXG5cbiAgICAvLyBTZXQgYSBoYXNoIG9mIG1vZGVsIGF0dHJpYnV0ZXMsIGFuZCBzeW5jIHRoZSBtb2RlbCB0byB0aGUgc2VydmVyLlxuICAgIC8vIElmIHRoZSBzZXJ2ZXIgcmV0dXJucyBhbiBhdHRyaWJ1dGVzIGhhc2ggdGhhdCBkaWZmZXJzLCB0aGUgbW9kZWwnc1xuICAgIC8vIHN0YXRlIHdpbGwgYmUgYHNldGAgYWdhaW4uXG4gIHNhdmU6IGZ1bmN0aW9uKGtleSwgdmFsLCBvcHRpb25zKSB7XG4gICAgdmFyIGF0dHJzLCBtZXRob2QsIHhociwgYXR0cmlidXRlcyA9IHRoaXMuYXR0cmlidXRlcztcblxuICAgIC8vIEhhbmRsZSBib3RoIGBcImtleVwiLCB2YWx1ZWAgYW5kIGB7a2V5OiB2YWx1ZX1gIC1zdHlsZSBhcmd1bWVudHMuXG4gICAgaWYgKGtleSA9PSBudWxsIHx8IHR5cGVvZiBrZXkgPT09ICdvYmplY3QnKSB7XG4gICAgICBhdHRycyA9IGtleTtcbiAgICAgIG9wdGlvbnMgPSB2YWw7XG4gICAgfSBlbHNlIHtcbiAgICAgIChhdHRycyA9IHt9KVtrZXldID0gdmFsO1xuICAgIH1cblxuICAgIG9wdGlvbnMgPSBfLmV4dGVuZCh7dmFsaWRhdGU6IHRydWV9LCBvcHRpb25zKTtcblxuICAgIC8vIElmIHdlJ3JlIG5vdCB3YWl0aW5nIGFuZCBhdHRyaWJ1dGVzIGV4aXN0LCBzYXZlIGFjdHMgYXNcbiAgICAvLyBgc2V0KGF0dHIpLnNhdmUobnVsbCwgb3B0cylgIHdpdGggdmFsaWRhdGlvbi4gT3RoZXJ3aXNlLCBjaGVjayBpZlxuICAgIC8vIHRoZSBtb2RlbCB3aWxsIGJlIHZhbGlkIHdoZW4gdGhlIGF0dHJpYnV0ZXMsIGlmIGFueSwgYXJlIHNldC5cbiAgICBpZiAoYXR0cnMgJiYgIW9wdGlvbnMud2FpdCkge1xuICAgICAgaWYgKCF0aGlzLnNldChhdHRycywgb3B0aW9ucykpIHJldHVybiBmYWxzZTtcbiAgICB9IGVsc2Uge1xuICAgICAgaWYgKCF0aGlzLl92YWxpZGF0ZShhdHRycywgb3B0aW9ucykpIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICAvLyBTZXQgdGVtcG9yYXJ5IGF0dHJpYnV0ZXMgaWYgYHt3YWl0OiB0cnVlfWAuXG4gICAgaWYgKGF0dHJzICYmIG9wdGlvbnMud2FpdCkge1xuICAgICAgdGhpcy5hdHRyaWJ1dGVzID0gXy5leHRlbmQoe30sIGF0dHJpYnV0ZXMsIGF0dHJzKTtcbiAgICB9XG5cbiAgICAvLyBBZnRlciBhIHN1Y2Nlc3NmdWwgc2VydmVyLXNpZGUgc2F2ZSwgdGhlIGNsaWVudCBpcyAob3B0aW9uYWxseSlcbiAgICAvLyB1cGRhdGVkIHdpdGggdGhlIHNlcnZlci1zaWRlIHN0YXRlLlxuICAgIGlmIChvcHRpb25zLnBhcnNlID09PSB2b2lkIDApIG9wdGlvbnMucGFyc2UgPSB0cnVlO1xuICAgIHZhciBtb2RlbCA9IHRoaXM7XG4gICAgdmFyIHN1Y2Nlc3MgPSBvcHRpb25zLnN1Y2Nlc3M7XG4gICAgb3B0aW9ucy5zdWNjZXNzID0gZnVuY3Rpb24ocmVzcCkge1xuICAgICAgLy8gRW5zdXJlIGF0dHJpYnV0ZXMgYXJlIHJlc3RvcmVkIGR1cmluZyBzeW5jaHJvbm91cyBzYXZlcy5cbiAgICAgIG1vZGVsLmF0dHJpYnV0ZXMgPSBhdHRyaWJ1dGVzO1xuICAgICAgdmFyIHNlcnZlckF0dHJzID0gbW9kZWwucGFyc2UocmVzcCwgb3B0aW9ucyk7XG4gICAgICBpZiAob3B0aW9ucy53YWl0KSBzZXJ2ZXJBdHRycyA9IF8uZXh0ZW5kKGF0dHJzIHx8IHt9LCBzZXJ2ZXJBdHRycyk7XG4gICAgICBpZiAoXy5pc09iamVjdChzZXJ2ZXJBdHRycykgJiYgIW1vZGVsLnNldChzZXJ2ZXJBdHRycywgb3B0aW9ucykpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuICAgICAgaWYgKHN1Y2Nlc3MpIHN1Y2Nlc3MobW9kZWwsIHJlc3AsIG9wdGlvbnMpO1xuICAgICAgbW9kZWwudHJpZ2dlcignc3luYycsIG1vZGVsLCByZXNwLCBvcHRpb25zKTtcbiAgICB9O1xuICAgIHdyYXBFcnJvcih0aGlzLCBvcHRpb25zKTtcblxuICAgIG1ldGhvZCA9IHRoaXMuaXNOZXcoKSA/ICdjcmVhdGUnIDogKG9wdGlvbnMucGF0Y2ggPyAncGF0Y2gnIDogJ3VwZGF0ZScpO1xuICAgIGlmIChtZXRob2QgPT09ICdwYXRjaCcgJiYgIW9wdGlvbnMuYXR0cnMpIG9wdGlvbnMuYXR0cnMgPSBhdHRycztcbiAgICB4aHIgPSB0aGlzLnN5bmMobWV0aG9kLCB0aGlzLCBvcHRpb25zKTtcblxuICAgIC8vIFJlc3RvcmUgYXR0cmlidXRlcy5cbiAgICBpZiAoYXR0cnMgJiYgb3B0aW9ucy53YWl0KSB0aGlzLmF0dHJpYnV0ZXMgPSBhdHRyaWJ1dGVzO1xuXG4gICAgcmV0dXJuIHhocjtcbiAgfSxcblxuICAgIC8vIERlc3Ryb3kgdGhpcyBtb2RlbCBvbiB0aGUgc2VydmVyIGlmIGl0IHdhcyBhbHJlYWR5IHBlcnNpc3RlZC5cbiAgICAvLyBPcHRpbWlzdGljYWxseSByZW1vdmVzIHRoZSBtb2RlbCBmcm9tIGl0cyBjb2xsZWN0aW9uLCBpZiBpdCBoYXMgb25lLlxuICAgIC8vIElmIGB3YWl0OiB0cnVlYCBpcyBwYXNzZWQsIHdhaXRzIGZvciB0aGUgc2VydmVyIHRvIHJlc3BvbmQgYmVmb3JlIHJlbW92YWwuXG4gIGRlc3Ryb3k6IGZ1bmN0aW9uKG9wdGlvbnMpIHtcbiAgICBvcHRpb25zID0gb3B0aW9ucyA/IF8uY2xvbmUob3B0aW9ucykgOiB7fTtcbiAgICB2YXIgbW9kZWwgPSB0aGlzO1xuICAgIHZhciBzdWNjZXNzID0gb3B0aW9ucy5zdWNjZXNzO1xuXG4gICAgdmFyIGRlc3Ryb3kgPSBmdW5jdGlvbigpIHtcbiAgICAgIG1vZGVsLnN0b3BMaXN0ZW5pbmcoKTtcbiAgICAgIG1vZGVsLnRyaWdnZXIoJ2Rlc3Ryb3knLCBtb2RlbCwgbW9kZWwuY29sbGVjdGlvbiwgb3B0aW9ucyk7XG4gICAgfTtcblxuICAgIG9wdGlvbnMuc3VjY2VzcyA9IGZ1bmN0aW9uKHJlc3ApIHtcbiAgICAgIGlmIChvcHRpb25zLndhaXQgfHwgbW9kZWwuaXNOZXcoKSkgZGVzdHJveSgpO1xuICAgICAgaWYgKHN1Y2Nlc3MpIHN1Y2Nlc3MobW9kZWwsIHJlc3AsIG9wdGlvbnMpO1xuICAgICAgaWYgKCFtb2RlbC5pc05ldygpKSBtb2RlbC50cmlnZ2VyKCdzeW5jJywgbW9kZWwsIHJlc3AsIG9wdGlvbnMpO1xuICAgIH07XG5cbiAgICBpZiAodGhpcy5pc05ldygpKSB7XG4gICAgICBvcHRpb25zLnN1Y2Nlc3MoKTtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgd3JhcEVycm9yKHRoaXMsIG9wdGlvbnMpO1xuXG4gICAgdmFyIHhociA9IHRoaXMuc3luYygnZGVsZXRlJywgdGhpcywgb3B0aW9ucyk7XG4gICAgaWYgKCFvcHRpb25zLndhaXQpIGRlc3Ryb3koKTtcbiAgICByZXR1cm4geGhyO1xuICB9LFxuXG4gICAgLy8gRGVmYXVsdCBVUkwgZm9yIHRoZSBtb2RlbCdzIHJlcHJlc2VudGF0aW9uIG9uIHRoZSBzZXJ2ZXIgLS0gaWYgeW91J3JlXG4gICAgLy8gdXNpbmcgQmFja2JvbmUncyByZXN0ZnVsIG1ldGhvZHMsIG92ZXJyaWRlIHRoaXMgdG8gY2hhbmdlIHRoZSBlbmRwb2ludFxuICAgIC8vIHRoYXQgd2lsbCBiZSBjYWxsZWQuXG4gIHVybDogZnVuY3Rpb24oKSB7XG4gICAgdmFyIGJhc2UgPVxuICAgICAgXy5yZXN1bHQodGhpcywgJ3VybFJvb3QnKSB8fFxuICAgICAgXy5yZXN1bHQodGhpcy5jb2xsZWN0aW9uLCAndXJsJykgfHxcbiAgICAgIHVybEVycm9yKCk7XG4gICAgaWYgKHRoaXMuaXNOZXcoKSkgcmV0dXJuIGJhc2U7XG4gICAgcmV0dXJuIGJhc2UucmVwbGFjZSgvKFteXFwvXSkkLywgJyQxLycpICsgZW5jb2RlVVJJQ29tcG9uZW50KHRoaXMuaWQpO1xuICB9LFxuXG4gICAgLy8gKipwYXJzZSoqIGNvbnZlcnRzIGEgcmVzcG9uc2UgaW50byB0aGUgaGFzaCBvZiBhdHRyaWJ1dGVzIHRvIGJlIGBzZXRgIG9uXG4gICAgLy8gdGhlIG1vZGVsLiBUaGUgZGVmYXVsdCBpbXBsZW1lbnRhdGlvbiBpcyBqdXN0IHRvIHBhc3MgdGhlIHJlc3BvbnNlIGFsb25nLlxuICBwYXJzZTogZnVuY3Rpb24ocmVzcCwgb3B0aW9ucykge1xuICAgIHJldHVybiByZXNwO1xuICB9LFxuXG4gICAgLy8gQ3JlYXRlIGEgbmV3IG1vZGVsIHdpdGggaWRlbnRpY2FsIGF0dHJpYnV0ZXMgdG8gdGhpcyBvbmUuXG4gIGNsb25lOiBmdW5jdGlvbigpIHtcbiAgICByZXR1cm4gbmV3IHRoaXMuY29uc3RydWN0b3IodGhpcy5hdHRyaWJ1dGVzKTtcbiAgfSxcblxuICAgIC8vIEEgbW9kZWwgaXMgbmV3IGlmIGl0IGhhcyBuZXZlciBiZWVuIHNhdmVkIHRvIHRoZSBzZXJ2ZXIsIGFuZCBsYWNrcyBhbiBpZC5cbiAgaXNOZXc6IGZ1bmN0aW9uKCkge1xuICAgIHJldHVybiAhdGhpcy5oYXModGhpcy5pZEF0dHJpYnV0ZSk7XG4gIH0sXG5cbiAgICAvLyBDaGVjayBpZiB0aGUgbW9kZWwgaXMgY3VycmVudGx5IGluIGEgdmFsaWQgc3RhdGUuXG4gIGlzVmFsaWQ6IGZ1bmN0aW9uKG9wdGlvbnMpIHtcbiAgICByZXR1cm4gdGhpcy5fdmFsaWRhdGUoe30sIF8uZXh0ZW5kKG9wdGlvbnMgfHwge30sIHsgdmFsaWRhdGU6IHRydWUgfSkpO1xuICB9LFxuXG4gICAgLy8gUnVuIHZhbGlkYXRpb24gYWdhaW5zdCB0aGUgbmV4dCBjb21wbGV0ZSBzZXQgb2YgbW9kZWwgYXR0cmlidXRlcyxcbiAgICAvLyByZXR1cm5pbmcgYHRydWVgIGlmIGFsbCBpcyB3ZWxsLiBPdGhlcndpc2UsIGZpcmUgYW4gYFwiaW52YWxpZFwiYCBldmVudC5cbiAgX3ZhbGlkYXRlOiBmdW5jdGlvbihhdHRycywgb3B0aW9ucykge1xuICAgIGlmICghb3B0aW9ucy52YWxpZGF0ZSB8fCAhdGhpcy52YWxpZGF0ZSkgcmV0dXJuIHRydWU7XG4gICAgYXR0cnMgPSBfLmV4dGVuZCh7fSwgdGhpcy5hdHRyaWJ1dGVzLCBhdHRycyk7XG4gICAgdmFyIGVycm9yID0gdGhpcy52YWxpZGF0aW9uRXJyb3IgPSB0aGlzLnZhbGlkYXRlKGF0dHJzLCBvcHRpb25zKSB8fCBudWxsO1xuICAgIGlmICghZXJyb3IpIHJldHVybiB0cnVlO1xuICAgIHRoaXMudHJpZ2dlcignaW52YWxpZCcsIHRoaXMsIGVycm9yLCBfLmV4dGVuZChvcHRpb25zLCB7dmFsaWRhdGlvbkVycm9yOiBlcnJvcn0pKTtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxufSk7XG5cbi8vIFVuZGVyc2NvcmUgbWV0aG9kcyB0aGF0IHdlIHdhbnQgdG8gaW1wbGVtZW50IG9uIHRoZSBNb2RlbC5cbnZhciBtb2RlbE1ldGhvZHMgPSBbJ2tleXMnLCAndmFsdWVzJywgJ3BhaXJzJywgJ2ludmVydCcsICdwaWNrJywgJ29taXQnLCAnY2hhaW4nLCAnaXNFbXB0eSddO1xuXG4vLyBNaXggaW4gZWFjaCBVbmRlcnNjb3JlIG1ldGhvZCBhcyBhIHByb3h5IHRvIGBNb2RlbCNhdHRyaWJ1dGVzYC5cbl8uZWFjaChtb2RlbE1ldGhvZHMsIGZ1bmN0aW9uKG1ldGhvZCkge1xuICBpZiAoIV9bbWV0aG9kXSkgcmV0dXJuO1xuICBNb2RlbC5wcm90b3R5cGVbbWV0aG9kXSA9IGZ1bmN0aW9uKCkge1xuICAgIHZhciBhcmdzID0gc2xpY2UuY2FsbChhcmd1bWVudHMpO1xuICAgIGFyZ3MudW5zaGlmdCh0aGlzLmF0dHJpYnV0ZXMpO1xuICAgIHJldHVybiBfW21ldGhvZF0uYXBwbHkoXywgYXJncyk7XG4gIH07XG59KTtcblxuLy8gc2V0dXAgaW5oZXJpdGFuY2Vcbk1vZGVsLmV4dGVuZCA9IGV4dGVuZDtcbm1vZHVsZS5leHBvcnRzID0gTW9kZWw7XG4iLCIvKipcbiAqIFN0YW5kYWxvbmUgZXh0cmFjdGlvbiBvZiBCYWNrYm9uZS5FdmVudHMsIG5vIGV4dGVybmFsIGRlcGVuZGVuY3kgcmVxdWlyZWQuXG4gKiBEZWdyYWRlcyBuaWNlbHkgd2hlbiBCYWNrb25lL3VuZGVyc2NvcmUgYXJlIGFscmVhZHkgYXZhaWxhYmxlIGluIHRoZSBjdXJyZW50XG4gKiBnbG9iYWwgY29udGV4dC5cbiAqXG4gKiBOb3RlIHRoYXQgZG9jcyBzdWdnZXN0IHRvIHVzZSB1bmRlcnNjb3JlJ3MgYF8uZXh0ZW5kKClgIG1ldGhvZCB0byBhZGQgRXZlbnRzXG4gKiBzdXBwb3J0IHRvIHNvbWUgZ2l2ZW4gb2JqZWN0LiBBIGBtaXhpbigpYCBtZXRob2QgaGFzIGJlZW4gYWRkZWQgdG8gdGhlIEV2ZW50c1xuICogcHJvdG90eXBlIHRvIGF2b2lkIHVzaW5nIHVuZGVyc2NvcmUgZm9yIHRoYXQgc29sZSBwdXJwb3NlOlxuICpcbiAqICAgICB2YXIgbXlFdmVudEVtaXR0ZXIgPSBCYWNrYm9uZUV2ZW50cy5taXhpbih7fSk7XG4gKlxuICogT3IgZm9yIGEgZnVuY3Rpb24gY29uc3RydWN0b3I6XG4gKlxuICogICAgIGZ1bmN0aW9uIE15Q29uc3RydWN0b3IoKXt9XG4gKiAgICAgTXlDb25zdHJ1Y3Rvci5wcm90b3R5cGUuZm9vID0gZnVuY3Rpb24oKXt9XG4gKiAgICAgQmFja2JvbmVFdmVudHMubWl4aW4oTXlDb25zdHJ1Y3Rvci5wcm90b3R5cGUpO1xuICpcbiAqIChjKSAyMDA5LTIwMTMgSmVyZW15IEFzaGtlbmFzLCBEb2N1bWVudENsb3VkIEluYy5cbiAqIChjKSAyMDEzIE5pY29sYXMgUGVycmlhdWx0XG4gKi9cbi8qIGdsb2JhbCBleHBvcnRzOnRydWUsIGRlZmluZSwgbW9kdWxlICovXG4oZnVuY3Rpb24oKSB7XG4gIHZhciByb290ID0gdGhpcyxcbiAgICAgIGJyZWFrZXIgPSB7fSxcbiAgICAgIG5hdGl2ZUZvckVhY2ggPSBBcnJheS5wcm90b3R5cGUuZm9yRWFjaCxcbiAgICAgIGhhc093blByb3BlcnR5ID0gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eSxcbiAgICAgIHNsaWNlID0gQXJyYXkucHJvdG90eXBlLnNsaWNlLFxuICAgICAgaWRDb3VudGVyID0gMDtcblxuICAvLyBSZXR1cm5zIGEgcGFydGlhbCBpbXBsZW1lbnRhdGlvbiBtYXRjaGluZyB0aGUgbWluaW1hbCBBUEkgc3Vic2V0IHJlcXVpcmVkXG4gIC8vIGJ5IEJhY2tib25lLkV2ZW50c1xuICBmdW5jdGlvbiBtaW5pc2NvcmUoKSB7XG4gICAgcmV0dXJuIHtcbiAgICAgIGtleXM6IE9iamVjdC5rZXlzIHx8IGZ1bmN0aW9uIChvYmopIHtcbiAgICAgICAgaWYgKHR5cGVvZiBvYmogIT09IFwib2JqZWN0XCIgJiYgdHlwZW9mIG9iaiAhPT0gXCJmdW5jdGlvblwiIHx8IG9iaiA9PT0gbnVsbCkge1xuICAgICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXCJrZXlzKCkgY2FsbGVkIG9uIGEgbm9uLW9iamVjdFwiKTtcbiAgICAgICAgfVxuICAgICAgICB2YXIga2V5LCBrZXlzID0gW107XG4gICAgICAgIGZvciAoa2V5IGluIG9iaikge1xuICAgICAgICAgIGlmIChvYmouaGFzT3duUHJvcGVydHkoa2V5KSkge1xuICAgICAgICAgICAga2V5c1trZXlzLmxlbmd0aF0gPSBrZXk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiBrZXlzO1xuICAgICAgfSxcblxuICAgICAgdW5pcXVlSWQ6IGZ1bmN0aW9uKHByZWZpeCkge1xuICAgICAgICB2YXIgaWQgPSArK2lkQ291bnRlciArICcnO1xuICAgICAgICByZXR1cm4gcHJlZml4ID8gcHJlZml4ICsgaWQgOiBpZDtcbiAgICAgIH0sXG5cbiAgICAgIGhhczogZnVuY3Rpb24ob2JqLCBrZXkpIHtcbiAgICAgICAgcmV0dXJuIGhhc093blByb3BlcnR5LmNhbGwob2JqLCBrZXkpO1xuICAgICAgfSxcblxuICAgICAgZWFjaDogZnVuY3Rpb24ob2JqLCBpdGVyYXRvciwgY29udGV4dCkge1xuICAgICAgICBpZiAob2JqID09IG51bGwpIHJldHVybjtcbiAgICAgICAgaWYgKG5hdGl2ZUZvckVhY2ggJiYgb2JqLmZvckVhY2ggPT09IG5hdGl2ZUZvckVhY2gpIHtcbiAgICAgICAgICBvYmouZm9yRWFjaChpdGVyYXRvciwgY29udGV4dCk7XG4gICAgICAgIH0gZWxzZSBpZiAob2JqLmxlbmd0aCA9PT0gK29iai5sZW5ndGgpIHtcbiAgICAgICAgICBmb3IgKHZhciBpID0gMCwgbCA9IG9iai5sZW5ndGg7IGkgPCBsOyBpKyspIHtcbiAgICAgICAgICAgIGlmIChpdGVyYXRvci5jYWxsKGNvbnRleHQsIG9ialtpXSwgaSwgb2JqKSA9PT0gYnJlYWtlcikgcmV0dXJuO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBmb3IgKHZhciBrZXkgaW4gb2JqKSB7XG4gICAgICAgICAgICBpZiAodGhpcy5oYXMob2JqLCBrZXkpKSB7XG4gICAgICAgICAgICAgIGlmIChpdGVyYXRvci5jYWxsKGNvbnRleHQsIG9ialtrZXldLCBrZXksIG9iaikgPT09IGJyZWFrZXIpIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH0sXG5cbiAgICAgIG9uY2U6IGZ1bmN0aW9uKGZ1bmMpIHtcbiAgICAgICAgdmFyIHJhbiA9IGZhbHNlLCBtZW1vO1xuICAgICAgICByZXR1cm4gZnVuY3Rpb24oKSB7XG4gICAgICAgICAgaWYgKHJhbikgcmV0dXJuIG1lbW87XG4gICAgICAgICAgcmFuID0gdHJ1ZTtcbiAgICAgICAgICBtZW1vID0gZnVuYy5hcHBseSh0aGlzLCBhcmd1bWVudHMpO1xuICAgICAgICAgIGZ1bmMgPSBudWxsO1xuICAgICAgICAgIHJldHVybiBtZW1vO1xuICAgICAgICB9O1xuICAgICAgfVxuICAgIH07XG4gIH1cblxuICB2YXIgXyA9IG1pbmlzY29yZSgpLCBFdmVudHM7XG5cbiAgLy8gQmFja2JvbmUuRXZlbnRzXG4gIC8vIC0tLS0tLS0tLS0tLS0tLVxuXG4gIC8vIEEgbW9kdWxlIHRoYXQgY2FuIGJlIG1peGVkIGluIHRvICphbnkgb2JqZWN0KiBpbiBvcmRlciB0byBwcm92aWRlIGl0IHdpdGhcbiAgLy8gY3VzdG9tIGV2ZW50cy4gWW91IG1heSBiaW5kIHdpdGggYG9uYCBvciByZW1vdmUgd2l0aCBgb2ZmYCBjYWxsYmFja1xuICAvLyBmdW5jdGlvbnMgdG8gYW4gZXZlbnQ7IGB0cmlnZ2VyYC1pbmcgYW4gZXZlbnQgZmlyZXMgYWxsIGNhbGxiYWNrcyBpblxuICAvLyBzdWNjZXNzaW9uLlxuICAvL1xuICAvLyAgICAgdmFyIG9iamVjdCA9IHt9O1xuICAvLyAgICAgXy5leHRlbmQob2JqZWN0LCBCYWNrYm9uZS5FdmVudHMpO1xuICAvLyAgICAgb2JqZWN0Lm9uKCdleHBhbmQnLCBmdW5jdGlvbigpeyBhbGVydCgnZXhwYW5kZWQnKTsgfSk7XG4gIC8vICAgICBvYmplY3QudHJpZ2dlcignZXhwYW5kJyk7XG4gIC8vXG4gIEV2ZW50cyA9IHtcblxuICAgIC8vIEJpbmQgYW4gZXZlbnQgdG8gYSBgY2FsbGJhY2tgIGZ1bmN0aW9uLiBQYXNzaW5nIGBcImFsbFwiYCB3aWxsIGJpbmRcbiAgICAvLyB0aGUgY2FsbGJhY2sgdG8gYWxsIGV2ZW50cyBmaXJlZC5cbiAgICBvbjogZnVuY3Rpb24obmFtZSwgY2FsbGJhY2ssIGNvbnRleHQpIHtcbiAgICAgIGlmICghZXZlbnRzQXBpKHRoaXMsICdvbicsIG5hbWUsIFtjYWxsYmFjaywgY29udGV4dF0pIHx8ICFjYWxsYmFjaykgcmV0dXJuIHRoaXM7XG4gICAgICB0aGlzLl9ldmVudHMgfHwgKHRoaXMuX2V2ZW50cyA9IHt9KTtcbiAgICAgIHZhciBldmVudHMgPSB0aGlzLl9ldmVudHNbbmFtZV0gfHwgKHRoaXMuX2V2ZW50c1tuYW1lXSA9IFtdKTtcbiAgICAgIGV2ZW50cy5wdXNoKHtjYWxsYmFjazogY2FsbGJhY2ssIGNvbnRleHQ6IGNvbnRleHQsIGN0eDogY29udGV4dCB8fCB0aGlzfSk7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9LFxuXG4gICAgLy8gQmluZCBhbiBldmVudCB0byBvbmx5IGJlIHRyaWdnZXJlZCBhIHNpbmdsZSB0aW1lLiBBZnRlciB0aGUgZmlyc3QgdGltZVxuICAgIC8vIHRoZSBjYWxsYmFjayBpcyBpbnZva2VkLCBpdCB3aWxsIGJlIHJlbW92ZWQuXG4gICAgb25jZTogZnVuY3Rpb24obmFtZSwgY2FsbGJhY2ssIGNvbnRleHQpIHtcbiAgICAgIGlmICghZXZlbnRzQXBpKHRoaXMsICdvbmNlJywgbmFtZSwgW2NhbGxiYWNrLCBjb250ZXh0XSkgfHwgIWNhbGxiYWNrKSByZXR1cm4gdGhpcztcbiAgICAgIHZhciBzZWxmID0gdGhpcztcbiAgICAgIHZhciBvbmNlID0gXy5vbmNlKGZ1bmN0aW9uKCkge1xuICAgICAgICBzZWxmLm9mZihuYW1lLCBvbmNlKTtcbiAgICAgICAgY2FsbGJhY2suYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgICAgIH0pO1xuICAgICAgb25jZS5fY2FsbGJhY2sgPSBjYWxsYmFjaztcbiAgICAgIHJldHVybiB0aGlzLm9uKG5hbWUsIG9uY2UsIGNvbnRleHQpO1xuICAgIH0sXG5cbiAgICAvLyBSZW1vdmUgb25lIG9yIG1hbnkgY2FsbGJhY2tzLiBJZiBgY29udGV4dGAgaXMgbnVsbCwgcmVtb3ZlcyBhbGxcbiAgICAvLyBjYWxsYmFja3Mgd2l0aCB0aGF0IGZ1bmN0aW9uLiBJZiBgY2FsbGJhY2tgIGlzIG51bGwsIHJlbW92ZXMgYWxsXG4gICAgLy8gY2FsbGJhY2tzIGZvciB0aGUgZXZlbnQuIElmIGBuYW1lYCBpcyBudWxsLCByZW1vdmVzIGFsbCBib3VuZFxuICAgIC8vIGNhbGxiYWNrcyBmb3IgYWxsIGV2ZW50cy5cbiAgICBvZmY6IGZ1bmN0aW9uKG5hbWUsIGNhbGxiYWNrLCBjb250ZXh0KSB7XG4gICAgICB2YXIgcmV0YWluLCBldiwgZXZlbnRzLCBuYW1lcywgaSwgbCwgaiwgaztcbiAgICAgIGlmICghdGhpcy5fZXZlbnRzIHx8ICFldmVudHNBcGkodGhpcywgJ29mZicsIG5hbWUsIFtjYWxsYmFjaywgY29udGV4dF0pKSByZXR1cm4gdGhpcztcbiAgICAgIGlmICghbmFtZSAmJiAhY2FsbGJhY2sgJiYgIWNvbnRleHQpIHtcbiAgICAgICAgdGhpcy5fZXZlbnRzID0ge307XG4gICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgfVxuXG4gICAgICBuYW1lcyA9IG5hbWUgPyBbbmFtZV0gOiBfLmtleXModGhpcy5fZXZlbnRzKTtcbiAgICAgIGZvciAoaSA9IDAsIGwgPSBuYW1lcy5sZW5ndGg7IGkgPCBsOyBpKyspIHtcbiAgICAgICAgbmFtZSA9IG5hbWVzW2ldO1xuICAgICAgICBpZiAoZXZlbnRzID0gdGhpcy5fZXZlbnRzW25hbWVdKSB7XG4gICAgICAgICAgdGhpcy5fZXZlbnRzW25hbWVdID0gcmV0YWluID0gW107XG4gICAgICAgICAgaWYgKGNhbGxiYWNrIHx8IGNvbnRleHQpIHtcbiAgICAgICAgICAgIGZvciAoaiA9IDAsIGsgPSBldmVudHMubGVuZ3RoOyBqIDwgazsgaisrKSB7XG4gICAgICAgICAgICAgIGV2ID0gZXZlbnRzW2pdO1xuICAgICAgICAgICAgICBpZiAoKGNhbGxiYWNrICYmIGNhbGxiYWNrICE9PSBldi5jYWxsYmFjayAmJiBjYWxsYmFjayAhPT0gZXYuY2FsbGJhY2suX2NhbGxiYWNrKSB8fFxuICAgICAgICAgICAgICAgICAgKGNvbnRleHQgJiYgY29udGV4dCAhPT0gZXYuY29udGV4dCkpIHtcbiAgICAgICAgICAgICAgICByZXRhaW4ucHVzaChldik7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgICAgaWYgKCFyZXRhaW4ubGVuZ3RoKSBkZWxldGUgdGhpcy5fZXZlbnRzW25hbWVdO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHJldHVybiB0aGlzO1xuICAgIH0sXG5cbiAgICAvLyBUcmlnZ2VyIG9uZSBvciBtYW55IGV2ZW50cywgZmlyaW5nIGFsbCBib3VuZCBjYWxsYmFja3MuIENhbGxiYWNrcyBhcmVcbiAgICAvLyBwYXNzZWQgdGhlIHNhbWUgYXJndW1lbnRzIGFzIGB0cmlnZ2VyYCBpcywgYXBhcnQgZnJvbSB0aGUgZXZlbnQgbmFtZVxuICAgIC8vICh1bmxlc3MgeW91J3JlIGxpc3RlbmluZyBvbiBgXCJhbGxcImAsIHdoaWNoIHdpbGwgY2F1c2UgeW91ciBjYWxsYmFjayB0b1xuICAgIC8vIHJlY2VpdmUgdGhlIHRydWUgbmFtZSBvZiB0aGUgZXZlbnQgYXMgdGhlIGZpcnN0IGFyZ3VtZW50KS5cbiAgICB0cmlnZ2VyOiBmdW5jdGlvbihuYW1lKSB7XG4gICAgICBpZiAoIXRoaXMuX2V2ZW50cykgcmV0dXJuIHRoaXM7XG4gICAgICB2YXIgYXJncyA9IHNsaWNlLmNhbGwoYXJndW1lbnRzLCAxKTtcbiAgICAgIGlmICghZXZlbnRzQXBpKHRoaXMsICd0cmlnZ2VyJywgbmFtZSwgYXJncykpIHJldHVybiB0aGlzO1xuICAgICAgdmFyIGV2ZW50cyA9IHRoaXMuX2V2ZW50c1tuYW1lXTtcbiAgICAgIHZhciBhbGxFdmVudHMgPSB0aGlzLl9ldmVudHMuYWxsO1xuICAgICAgaWYgKGV2ZW50cykgdHJpZ2dlckV2ZW50cyhldmVudHMsIGFyZ3MpO1xuICAgICAgaWYgKGFsbEV2ZW50cykgdHJpZ2dlckV2ZW50cyhhbGxFdmVudHMsIGFyZ3VtZW50cyk7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9LFxuXG4gICAgLy8gVGVsbCB0aGlzIG9iamVjdCB0byBzdG9wIGxpc3RlbmluZyB0byBlaXRoZXIgc3BlY2lmaWMgZXZlbnRzIC4uLiBvclxuICAgIC8vIHRvIGV2ZXJ5IG9iamVjdCBpdCdzIGN1cnJlbnRseSBsaXN0ZW5pbmcgdG8uXG4gICAgc3RvcExpc3RlbmluZzogZnVuY3Rpb24ob2JqLCBuYW1lLCBjYWxsYmFjaykge1xuICAgICAgdmFyIGxpc3RlbmVycyA9IHRoaXMuX2xpc3RlbmVycztcbiAgICAgIGlmICghbGlzdGVuZXJzKSByZXR1cm4gdGhpcztcbiAgICAgIHZhciBkZWxldGVMaXN0ZW5lciA9ICFuYW1lICYmICFjYWxsYmFjaztcbiAgICAgIGlmICh0eXBlb2YgbmFtZSA9PT0gJ29iamVjdCcpIGNhbGxiYWNrID0gdGhpcztcbiAgICAgIGlmIChvYmopIChsaXN0ZW5lcnMgPSB7fSlbb2JqLl9saXN0ZW5lcklkXSA9IG9iajtcbiAgICAgIGZvciAodmFyIGlkIGluIGxpc3RlbmVycykge1xuICAgICAgICBsaXN0ZW5lcnNbaWRdLm9mZihuYW1lLCBjYWxsYmFjaywgdGhpcyk7XG4gICAgICAgIGlmIChkZWxldGVMaXN0ZW5lcikgZGVsZXRlIHRoaXMuX2xpc3RlbmVyc1tpZF07XG4gICAgICB9XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9XG5cbiAgfTtcblxuICAvLyBSZWd1bGFyIGV4cHJlc3Npb24gdXNlZCB0byBzcGxpdCBldmVudCBzdHJpbmdzLlxuICB2YXIgZXZlbnRTcGxpdHRlciA9IC9cXHMrLztcblxuICAvLyBJbXBsZW1lbnQgZmFuY3kgZmVhdHVyZXMgb2YgdGhlIEV2ZW50cyBBUEkgc3VjaCBhcyBtdWx0aXBsZSBldmVudFxuICAvLyBuYW1lcyBgXCJjaGFuZ2UgYmx1clwiYCBhbmQgalF1ZXJ5LXN0eWxlIGV2ZW50IG1hcHMgYHtjaGFuZ2U6IGFjdGlvbn1gXG4gIC8vIGluIHRlcm1zIG9mIHRoZSBleGlzdGluZyBBUEkuXG4gIHZhciBldmVudHNBcGkgPSBmdW5jdGlvbihvYmosIGFjdGlvbiwgbmFtZSwgcmVzdCkge1xuICAgIGlmICghbmFtZSkgcmV0dXJuIHRydWU7XG5cbiAgICAvLyBIYW5kbGUgZXZlbnQgbWFwcy5cbiAgICBpZiAodHlwZW9mIG5hbWUgPT09ICdvYmplY3QnKSB7XG4gICAgICBmb3IgKHZhciBrZXkgaW4gbmFtZSkge1xuICAgICAgICBvYmpbYWN0aW9uXS5hcHBseShvYmosIFtrZXksIG5hbWVba2V5XV0uY29uY2F0KHJlc3QpKTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICAvLyBIYW5kbGUgc3BhY2Ugc2VwYXJhdGVkIGV2ZW50IG5hbWVzLlxuICAgIGlmIChldmVudFNwbGl0dGVyLnRlc3QobmFtZSkpIHtcbiAgICAgIHZhciBuYW1lcyA9IG5hbWUuc3BsaXQoZXZlbnRTcGxpdHRlcik7XG4gICAgICBmb3IgKHZhciBpID0gMCwgbCA9IG5hbWVzLmxlbmd0aDsgaSA8IGw7IGkrKykge1xuICAgICAgICBvYmpbYWN0aW9uXS5hcHBseShvYmosIFtuYW1lc1tpXV0uY29uY2F0KHJlc3QpKTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICByZXR1cm4gdHJ1ZTtcbiAgfTtcblxuICAvLyBBIGRpZmZpY3VsdC10by1iZWxpZXZlLCBidXQgb3B0aW1pemVkIGludGVybmFsIGRpc3BhdGNoIGZ1bmN0aW9uIGZvclxuICAvLyB0cmlnZ2VyaW5nIGV2ZW50cy4gVHJpZXMgdG8ga2VlcCB0aGUgdXN1YWwgY2FzZXMgc3BlZWR5IChtb3N0IGludGVybmFsXG4gIC8vIEJhY2tib25lIGV2ZW50cyBoYXZlIDMgYXJndW1lbnRzKS5cbiAgdmFyIHRyaWdnZXJFdmVudHMgPSBmdW5jdGlvbihldmVudHMsIGFyZ3MpIHtcbiAgICB2YXIgZXYsIGkgPSAtMSwgbCA9IGV2ZW50cy5sZW5ndGgsIGExID0gYXJnc1swXSwgYTIgPSBhcmdzWzFdLCBhMyA9IGFyZ3NbMl07XG4gICAgc3dpdGNoIChhcmdzLmxlbmd0aCkge1xuICAgICAgY2FzZSAwOiB3aGlsZSAoKytpIDwgbCkgKGV2ID0gZXZlbnRzW2ldKS5jYWxsYmFjay5jYWxsKGV2LmN0eCk7IHJldHVybjtcbiAgICAgIGNhc2UgMTogd2hpbGUgKCsraSA8IGwpIChldiA9IGV2ZW50c1tpXSkuY2FsbGJhY2suY2FsbChldi5jdHgsIGExKTsgcmV0dXJuO1xuICAgICAgY2FzZSAyOiB3aGlsZSAoKytpIDwgbCkgKGV2ID0gZXZlbnRzW2ldKS5jYWxsYmFjay5jYWxsKGV2LmN0eCwgYTEsIGEyKTsgcmV0dXJuO1xuICAgICAgY2FzZSAzOiB3aGlsZSAoKytpIDwgbCkgKGV2ID0gZXZlbnRzW2ldKS5jYWxsYmFjay5jYWxsKGV2LmN0eCwgYTEsIGEyLCBhMyk7IHJldHVybjtcbiAgICAgIGRlZmF1bHQ6IHdoaWxlICgrK2kgPCBsKSAoZXYgPSBldmVudHNbaV0pLmNhbGxiYWNrLmFwcGx5KGV2LmN0eCwgYXJncyk7XG4gICAgfVxuICB9O1xuXG4gIHZhciBsaXN0ZW5NZXRob2RzID0ge2xpc3RlblRvOiAnb24nLCBsaXN0ZW5Ub09uY2U6ICdvbmNlJ307XG5cbiAgLy8gSW52ZXJzaW9uLW9mLWNvbnRyb2wgdmVyc2lvbnMgb2YgYG9uYCBhbmQgYG9uY2VgLiBUZWxsICp0aGlzKiBvYmplY3QgdG9cbiAgLy8gbGlzdGVuIHRvIGFuIGV2ZW50IGluIGFub3RoZXIgb2JqZWN0IC4uLiBrZWVwaW5nIHRyYWNrIG9mIHdoYXQgaXQnc1xuICAvLyBsaXN0ZW5pbmcgdG8uXG4gIF8uZWFjaChsaXN0ZW5NZXRob2RzLCBmdW5jdGlvbihpbXBsZW1lbnRhdGlvbiwgbWV0aG9kKSB7XG4gICAgRXZlbnRzW21ldGhvZF0gPSBmdW5jdGlvbihvYmosIG5hbWUsIGNhbGxiYWNrKSB7XG4gICAgICB2YXIgbGlzdGVuZXJzID0gdGhpcy5fbGlzdGVuZXJzIHx8ICh0aGlzLl9saXN0ZW5lcnMgPSB7fSk7XG4gICAgICB2YXIgaWQgPSBvYmouX2xpc3RlbmVySWQgfHwgKG9iai5fbGlzdGVuZXJJZCA9IF8udW5pcXVlSWQoJ2wnKSk7XG4gICAgICBsaXN0ZW5lcnNbaWRdID0gb2JqO1xuICAgICAgaWYgKHR5cGVvZiBuYW1lID09PSAnb2JqZWN0JykgY2FsbGJhY2sgPSB0aGlzO1xuICAgICAgb2JqW2ltcGxlbWVudGF0aW9uXShuYW1lLCBjYWxsYmFjaywgdGhpcyk7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9O1xuICB9KTtcblxuICAvLyBBbGlhc2VzIGZvciBiYWNrd2FyZHMgY29tcGF0aWJpbGl0eS5cbiAgRXZlbnRzLmJpbmQgICA9IEV2ZW50cy5vbjtcbiAgRXZlbnRzLnVuYmluZCA9IEV2ZW50cy5vZmY7XG5cbiAgLy8gTWl4aW4gdXRpbGl0eVxuICBFdmVudHMubWl4aW4gPSBmdW5jdGlvbihwcm90bykge1xuICAgIHZhciBleHBvcnRzID0gWydvbicsICdvbmNlJywgJ29mZicsICd0cmlnZ2VyJywgJ3N0b3BMaXN0ZW5pbmcnLCAnbGlzdGVuVG8nLFxuICAgICAgICAgICAgICAgICAgICdsaXN0ZW5Ub09uY2UnLCAnYmluZCcsICd1bmJpbmQnXTtcbiAgICBfLmVhY2goZXhwb3J0cywgZnVuY3Rpb24obmFtZSkge1xuICAgICAgcHJvdG9bbmFtZV0gPSB0aGlzW25hbWVdO1xuICAgIH0sIHRoaXMpO1xuICAgIHJldHVybiBwcm90bztcbiAgfTtcblxuICAvLyBFeHBvcnQgRXZlbnRzIGFzIEJhY2tib25lRXZlbnRzIGRlcGVuZGluZyBvbiBjdXJyZW50IGNvbnRleHRcbiAgaWYgKHR5cGVvZiBkZWZpbmUgPT09IFwiZnVuY3Rpb25cIikge1xuICAgIGRlZmluZShmdW5jdGlvbigpIHtcbiAgICAgIHJldHVybiBFdmVudHM7XG4gICAgfSk7XG4gIH0gZWxzZSBpZiAodHlwZW9mIGV4cG9ydHMgIT09ICd1bmRlZmluZWQnKSB7XG4gICAgaWYgKHR5cGVvZiBtb2R1bGUgIT09ICd1bmRlZmluZWQnICYmIG1vZHVsZS5leHBvcnRzKSB7XG4gICAgICBleHBvcnRzID0gbW9kdWxlLmV4cG9ydHMgPSBFdmVudHM7XG4gICAgfVxuICAgIGV4cG9ydHMuQmFja2JvbmVFdmVudHMgPSBFdmVudHM7XG4gIH0gZWxzZSB7XG4gICAgcm9vdC5CYWNrYm9uZUV2ZW50cyA9IEV2ZW50cztcbiAgfVxufSkodGhpcyk7XG4iLCJtb2R1bGUuZXhwb3J0cyA9IHJlcXVpcmUoJy4vYmFja2JvbmUtZXZlbnRzLXN0YW5kYWxvbmUnKTtcbiIsIihmdW5jdGlvbiAoZGVmaW5pdGlvbikge1xuICBpZiAodHlwZW9mIGV4cG9ydHMgPT09IFwib2JqZWN0XCIpIHtcbiAgICBtb2R1bGUuZXhwb3J0cyA9IGRlZmluaXRpb24oKTtcbiAgfVxuICBlbHNlIGlmICh0eXBlb2YgZGVmaW5lID09PSAnZnVuY3Rpb24nICYmIGRlZmluZS5hbWQpIHtcbiAgICBkZWZpbmUoZGVmaW5pdGlvbik7XG4gIH1cbiAgZWxzZSB7XG4gICAgd2luZG93LkJhY2tib25lRXh0ZW5kID0gZGVmaW5pdGlvbigpO1xuICB9XG59KShmdW5jdGlvbiAoKSB7XG4gIFwidXNlIHN0cmljdFwiO1xuICBcbiAgLy8gbWluaS11bmRlcnNjb3JlXG4gIHZhciBfID0ge1xuICAgIGhhczogZnVuY3Rpb24gKG9iaiwga2V5KSB7XG4gICAgICByZXR1cm4gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKG9iaiwga2V5KTtcbiAgICB9LFxuICBcbiAgICBleHRlbmQ6IGZ1bmN0aW9uKG9iaikge1xuICAgICAgZm9yICh2YXIgaT0xOyBpPGFyZ3VtZW50cy5sZW5ndGg7ICsraSkge1xuICAgICAgICB2YXIgc291cmNlID0gYXJndW1lbnRzW2ldO1xuICAgICAgICBpZiAoc291cmNlKSB7XG4gICAgICAgICAgZm9yICh2YXIgcHJvcCBpbiBzb3VyY2UpIHtcbiAgICAgICAgICAgIG9ialtwcm9wXSA9IHNvdXJjZVtwcm9wXTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHJldHVybiBvYmo7XG4gICAgfVxuICB9O1xuXG4gIC8vLyBGb2xsb3dpbmcgY29kZSBpcyBwYXN0ZWQgZnJvbSBCYWNrYm9uZS5qcyAvLy9cblxuICAvLyBIZWxwZXIgZnVuY3Rpb24gdG8gY29ycmVjdGx5IHNldCB1cCB0aGUgcHJvdG90eXBlIGNoYWluLCBmb3Igc3ViY2xhc3Nlcy5cbiAgLy8gU2ltaWxhciB0byBgZ29vZy5pbmhlcml0c2AsIGJ1dCB1c2VzIGEgaGFzaCBvZiBwcm90b3R5cGUgcHJvcGVydGllcyBhbmRcbiAgLy8gY2xhc3MgcHJvcGVydGllcyB0byBiZSBleHRlbmRlZC5cbiAgdmFyIGV4dGVuZCA9IGZ1bmN0aW9uKHByb3RvUHJvcHMsIHN0YXRpY1Byb3BzKSB7XG4gICAgdmFyIHBhcmVudCA9IHRoaXM7XG4gICAgdmFyIGNoaWxkO1xuXG4gICAgLy8gVGhlIGNvbnN0cnVjdG9yIGZ1bmN0aW9uIGZvciB0aGUgbmV3IHN1YmNsYXNzIGlzIGVpdGhlciBkZWZpbmVkIGJ5IHlvdVxuICAgIC8vICh0aGUgXCJjb25zdHJ1Y3RvclwiIHByb3BlcnR5IGluIHlvdXIgYGV4dGVuZGAgZGVmaW5pdGlvbiksIG9yIGRlZmF1bHRlZFxuICAgIC8vIGJ5IHVzIHRvIHNpbXBseSBjYWxsIHRoZSBwYXJlbnQncyBjb25zdHJ1Y3Rvci5cbiAgICBpZiAocHJvdG9Qcm9wcyAmJiBfLmhhcyhwcm90b1Byb3BzLCAnY29uc3RydWN0b3InKSkge1xuICAgICAgY2hpbGQgPSBwcm90b1Byb3BzLmNvbnN0cnVjdG9yO1xuICAgIH0gZWxzZSB7XG4gICAgICBjaGlsZCA9IGZ1bmN0aW9uKCl7IHJldHVybiBwYXJlbnQuYXBwbHkodGhpcywgYXJndW1lbnRzKTsgfTtcbiAgICB9XG5cbiAgICAvLyBBZGQgc3RhdGljIHByb3BlcnRpZXMgdG8gdGhlIGNvbnN0cnVjdG9yIGZ1bmN0aW9uLCBpZiBzdXBwbGllZC5cbiAgICBfLmV4dGVuZChjaGlsZCwgcGFyZW50LCBzdGF0aWNQcm9wcyk7XG5cbiAgICAvLyBTZXQgdGhlIHByb3RvdHlwZSBjaGFpbiB0byBpbmhlcml0IGZyb20gYHBhcmVudGAsIHdpdGhvdXQgY2FsbGluZ1xuICAgIC8vIGBwYXJlbnRgJ3MgY29uc3RydWN0b3IgZnVuY3Rpb24uXG4gICAgdmFyIFN1cnJvZ2F0ZSA9IGZ1bmN0aW9uKCl7IHRoaXMuY29uc3RydWN0b3IgPSBjaGlsZDsgfTtcbiAgICBTdXJyb2dhdGUucHJvdG90eXBlID0gcGFyZW50LnByb3RvdHlwZTtcbiAgICBjaGlsZC5wcm90b3R5cGUgPSBuZXcgU3Vycm9nYXRlKCk7XG5cbiAgICAvLyBBZGQgcHJvdG90eXBlIHByb3BlcnRpZXMgKGluc3RhbmNlIHByb3BlcnRpZXMpIHRvIHRoZSBzdWJjbGFzcyxcbiAgICAvLyBpZiBzdXBwbGllZC5cbiAgICBpZiAocHJvdG9Qcm9wcykgXy5leHRlbmQoY2hpbGQucHJvdG90eXBlLCBwcm90b1Byb3BzKTtcblxuICAgIC8vIFNldCBhIGNvbnZlbmllbmNlIHByb3BlcnR5IGluIGNhc2UgdGhlIHBhcmVudCdzIHByb3RvdHlwZSBpcyBuZWVkZWRcbiAgICAvLyBsYXRlci5cbiAgICBjaGlsZC5fX3N1cGVyX18gPSBwYXJlbnQucHJvdG90eXBlO1xuXG4gICAgcmV0dXJuIGNoaWxkO1xuICB9O1xuXG4gIC8vIEV4cG9zZSB0aGUgZXh0ZW5kIGZ1bmN0aW9uXG4gIHJldHVybiBleHRlbmQ7XG59KTtcbiIsIi8vIHRoaXMgaXMgdGhlIGV4dHJhY3RlZCB2aWV3IG1vZGVsIGZyb20gYmFja2JvbmVcbi8vIG5vdGUgdGhhdCB3ZSBpbmplY3QgamJvbmUgYXMganF1ZXJ5IHJlcGxhY21lbnRcbi8vIChhbmQgdW5kZXJzY29yZSBkaXJlY3RseSlcbi8vXG4vLyBWaWV3cyBhcmUgYWxtb3N0IG1vcmUgY29udmVudGlvbiB0aGFuIHRoZXkgYXJlIGFjdHVhbCBjb2RlLlxuLy8gIE1WQyBwYXR0ZXJuXG4vLyBCYWNrYm9uZS5WaWV3XG4vLyAtLS0tLS0tLS0tLS0tXG5cbnZhciBfID0gcmVxdWlyZShcInVuZGVyc2NvcmVcIik7XG52YXIgRXZlbnRzID0gcmVxdWlyZShcImJhY2tib25lLWV2ZW50cy1zdGFuZGFsb25lXCIpO1xudmFyIGV4dGVuZCA9IHJlcXVpcmUoXCJiYWNrYm9uZS1leHRlbmQtc3RhbmRhbG9uZVwiKTtcbnZhciAkID0gcmVxdWlyZSgnamJvbmUnKTtcblxuLy8gQmFja2JvbmUgVmlld3MgYXJlIGFsbW9zdCBtb3JlIGNvbnZlbnRpb24gdGhhbiB0aGV5IGFyZSBhY3R1YWwgY29kZS4gQSBWaWV3XG4vLyBpcyBzaW1wbHkgYSBKYXZhU2NyaXB0IG9iamVjdCB0aGF0IHJlcHJlc2VudHMgYSBsb2dpY2FsIGNodW5rIG9mIFVJIGluIHRoZVxuLy8gRE9NLiBUaGlzIG1pZ2h0IGJlIGEgc2luZ2xlIGl0ZW0sIGFuIGVudGlyZSBsaXN0LCBhIHNpZGViYXIgb3IgcGFuZWwsIG9yXG4vLyBldmVuIHRoZSBzdXJyb3VuZGluZyBmcmFtZSB3aGljaCB3cmFwcyB5b3VyIHdob2xlIGFwcC4gRGVmaW5pbmcgYSBjaHVuayBvZlxuLy8gVUkgYXMgYSAqKlZpZXcqKiBhbGxvd3MgeW91IHRvIGRlZmluZSB5b3VyIERPTSBldmVudHMgZGVjbGFyYXRpdmVseSwgd2l0aG91dFxuLy8gaGF2aW5nIHRvIHdvcnJ5IGFib3V0IHJlbmRlciBvcmRlciAuLi4gYW5kIG1ha2VzIGl0IGVhc3kgZm9yIHRoZSB2aWV3IHRvXG4vLyByZWFjdCB0byBzcGVjaWZpYyBjaGFuZ2VzIGluIHRoZSBzdGF0ZSBvZiB5b3VyIG1vZGVscy5cblxuLy8gQ3JlYXRpbmcgYSBCYWNrYm9uZS5WaWV3IGNyZWF0ZXMgaXRzIGluaXRpYWwgZWxlbWVudCBvdXRzaWRlIG9mIHRoZSBET00sXG4vLyBpZiBhbiBleGlzdGluZyBlbGVtZW50IGlzIG5vdCBwcm92aWRlZC4uLlxudmFyIFZpZXcgPSAgZnVuY3Rpb24ob3B0aW9ucykge1xuICB0aGlzLmNpZCA9IF8udW5pcXVlSWQoJ3ZpZXcnKTtcbiAgb3B0aW9ucyB8fCAob3B0aW9ucyA9IHt9KTtcbiAgXy5leHRlbmQodGhpcywgXy5waWNrKG9wdGlvbnMsIHZpZXdPcHRpb25zKSk7XG4gIHRoaXMuX2Vuc3VyZUVsZW1lbnQoKTtcbiAgdGhpcy5pbml0aWFsaXplLmFwcGx5KHRoaXMsIGFyZ3VtZW50cyk7XG59O1xuXG4vLyBDYWNoZWQgcmVnZXggdG8gc3BsaXQga2V5cyBmb3IgYGRlbGVnYXRlYC5cbnZhciBkZWxlZ2F0ZUV2ZW50U3BsaXR0ZXIgPSAvXihcXFMrKVxccyooLiopJC87XG5cbi8vIExpc3Qgb2YgdmlldyBvcHRpb25zIHRvIGJlIG1lcmdlZCBhcyBwcm9wZXJ0aWVzLlxudmFyIHZpZXdPcHRpb25zID0gWydtb2RlbCcsICdjb2xsZWN0aW9uJywgJ2VsJywgJ2lkJywgJ2F0dHJpYnV0ZXMnLCAnY2xhc3NOYW1lJywgJ3RhZ05hbWUnLCAnZXZlbnRzJ107XG5cbi8vIFNldCB1cCBhbGwgaW5oZXJpdGFibGUgKipCYWNrYm9uZS5WaWV3KiogcHJvcGVydGllcyBhbmQgbWV0aG9kcy5cbl8uZXh0ZW5kKFZpZXcucHJvdG90eXBlLCBFdmVudHMsIHtcblxuICAvLyBUaGUgZGVmYXVsdCBgdGFnTmFtZWAgb2YgYSBWaWV3J3MgZWxlbWVudCBpcyBgXCJkaXZcImAuXG4gIHRhZ05hbWU6ICdkaXYnLFxuXG4gIC8vIGpRdWVyeSBkZWxlZ2F0ZSBmb3IgZWxlbWVudCBsb29rdXAsIHNjb3BlZCB0byBET00gZWxlbWVudHMgd2l0aGluIHRoZVxuICAvLyBjdXJyZW50IHZpZXcuIFRoaXMgc2hvdWxkIGJlIHByZWZlcnJlZCB0byBnbG9iYWwgbG9va3VwcyB3aGVyZSBwb3NzaWJsZS5cbiAgJDogZnVuY3Rpb24oc2VsZWN0b3IpIHtcbiAgICByZXR1cm4gdGhpcy4kZWwuZmluZChzZWxlY3Rvcik7XG4gIH0sXG5cbiAgICAvLyBJbml0aWFsaXplIGlzIGFuIGVtcHR5IGZ1bmN0aW9uIGJ5IGRlZmF1bHQuIE92ZXJyaWRlIGl0IHdpdGggeW91ciBvd25cbiAgICAvLyBpbml0aWFsaXphdGlvbiBsb2dpYy5cbiAgaW5pdGlhbGl6ZTogZnVuY3Rpb24oKXt9LFxuXG4gICAgLy8gKipyZW5kZXIqKiBpcyB0aGUgY29yZSBmdW5jdGlvbiB0aGF0IHlvdXIgdmlldyBzaG91bGQgb3ZlcnJpZGUsIGluIG9yZGVyXG4gICAgLy8gdG8gcG9wdWxhdGUgaXRzIGVsZW1lbnQgKGB0aGlzLmVsYCksIHdpdGggdGhlIGFwcHJvcHJpYXRlIEhUTUwuIFRoZVxuICAgIC8vIGNvbnZlbnRpb24gaXMgZm9yICoqcmVuZGVyKiogdG8gYWx3YXlzIHJldHVybiBgdGhpc2AuXG4gIHJlbmRlcjogZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH0sXG5cbiAgICAvLyBSZW1vdmUgdGhpcyB2aWV3IGJ5IHRha2luZyB0aGUgZWxlbWVudCBvdXQgb2YgdGhlIERPTSwgYW5kIHJlbW92aW5nIGFueVxuICAgIC8vIGFwcGxpY2FibGUgQmFja2JvbmUuRXZlbnRzIGxpc3RlbmVycy5cbiAgcmVtb3ZlOiBmdW5jdGlvbigpIHtcbiAgICB0aGlzLl9yZW1vdmVFbGVtZW50KCk7XG4gICAgdGhpcy5zdG9wTGlzdGVuaW5nKCk7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH0sXG5cbiAgICAvLyBSZW1vdmUgdGhpcyB2aWV3J3MgZWxlbWVudCBmcm9tIHRoZSBkb2N1bWVudCBhbmQgYWxsIGV2ZW50IGxpc3RlbmVyc1xuICAgIC8vIGF0dGFjaGVkIHRvIGl0LiBFeHBvc2VkIGZvciBzdWJjbGFzc2VzIHVzaW5nIGFuIGFsdGVybmF0aXZlIERPTVxuICAgIC8vIG1hbmlwdWxhdGlvbiBBUEkuXG4gIF9yZW1vdmVFbGVtZW50OiBmdW5jdGlvbigpIHtcbiAgICB0aGlzLiRlbC5yZW1vdmUoKTtcbiAgfSxcblxuICAgIC8vIENoYW5nZSB0aGUgdmlldydzIGVsZW1lbnQgKGB0aGlzLmVsYCBwcm9wZXJ0eSkgYW5kIHJlLWRlbGVnYXRlIHRoZVxuICAgIC8vIHZpZXcncyBldmVudHMgb24gdGhlIG5ldyBlbGVtZW50LlxuICBzZXRFbGVtZW50OiBmdW5jdGlvbihlbGVtZW50KSB7XG4gICAgdGhpcy51bmRlbGVnYXRlRXZlbnRzKCk7XG4gICAgdGhpcy5fc2V0RWxlbWVudChlbGVtZW50KTtcbiAgICB0aGlzLmRlbGVnYXRlRXZlbnRzKCk7XG4gICAgcmV0dXJuIHRoaXM7XG4gIH0sXG5cbiAgICAvLyBDcmVhdGVzIHRoZSBgdGhpcy5lbGAgYW5kIGB0aGlzLiRlbGAgcmVmZXJlbmNlcyBmb3IgdGhpcyB2aWV3IHVzaW5nIHRoZVxuICAgIC8vIGdpdmVuIGBlbGAuIGBlbGAgY2FuIGJlIGEgQ1NTIHNlbGVjdG9yIG9yIGFuIEhUTUwgc3RyaW5nLCBhIGpRdWVyeVxuICAgIC8vIGNvbnRleHQgb3IgYW4gZWxlbWVudC4gU3ViY2xhc3NlcyBjYW4gb3ZlcnJpZGUgdGhpcyB0byB1dGlsaXplIGFuXG4gICAgLy8gYWx0ZXJuYXRpdmUgRE9NIG1hbmlwdWxhdGlvbiBBUEkgYW5kIGFyZSBvbmx5IHJlcXVpcmVkIHRvIHNldCB0aGVcbiAgICAvLyBgdGhpcy5lbGAgcHJvcGVydHkuXG4gIF9zZXRFbGVtZW50OiBmdW5jdGlvbihlbCkge1xuICAgIHRoaXMuJGVsID0gZWwgaW5zdGFuY2VvZiAkID8gZWwgOiAkKGVsKTtcbiAgICB0aGlzLmVsID0gdGhpcy4kZWxbMF07XG4gIH0sXG5cbiAgICAvLyBTZXQgY2FsbGJhY2tzLCB3aGVyZSBgdGhpcy5ldmVudHNgIGlzIGEgaGFzaCBvZlxuICAgIC8vXG4gICAgLy8gKntcImV2ZW50IHNlbGVjdG9yXCI6IFwiY2FsbGJhY2tcIn0qXG4gICAgLy9cbiAgICAvLyAgICAge1xuICAgIC8vICAgICAgICdtb3VzZWRvd24gLnRpdGxlJzogICdlZGl0JyxcbiAgICAvLyAgICAgICAnY2xpY2sgLmJ1dHRvbic6ICAgICAnc2F2ZScsXG4gICAgLy8gICAgICAgJ2NsaWNrIC5vcGVuJzogICAgICAgZnVuY3Rpb24oZSkgeyAuLi4gfVxuICAgIC8vICAgICB9XG4gICAgLy9cbiAgICAvLyBwYWlycy4gQ2FsbGJhY2tzIHdpbGwgYmUgYm91bmQgdG8gdGhlIHZpZXcsIHdpdGggYHRoaXNgIHNldCBwcm9wZXJseS5cbiAgICAvLyBVc2VzIGV2ZW50IGRlbGVnYXRpb24gZm9yIGVmZmljaWVuY3kuXG4gICAgLy8gT21pdHRpbmcgdGhlIHNlbGVjdG9yIGJpbmRzIHRoZSBldmVudCB0byBgdGhpcy5lbGAuXG4gIGRlbGVnYXRlRXZlbnRzOiBmdW5jdGlvbihldmVudHMpIHtcbiAgICBpZiAoIShldmVudHMgfHwgKGV2ZW50cyA9IF8ucmVzdWx0KHRoaXMsICdldmVudHMnKSkpKSByZXR1cm4gdGhpcztcbiAgICB0aGlzLnVuZGVsZWdhdGVFdmVudHMoKTtcbiAgICBmb3IgKHZhciBrZXkgaW4gZXZlbnRzKSB7XG4gICAgICB2YXIgbWV0aG9kID0gZXZlbnRzW2tleV07XG4gICAgICBpZiAoIV8uaXNGdW5jdGlvbihtZXRob2QpKSBtZXRob2QgPSB0aGlzW2V2ZW50c1trZXldXTtcbiAgICAgIGlmICghbWV0aG9kKSBjb250aW51ZTtcbiAgICAgIHZhciBtYXRjaCA9IGtleS5tYXRjaChkZWxlZ2F0ZUV2ZW50U3BsaXR0ZXIpO1xuICAgICAgdGhpcy5kZWxlZ2F0ZShtYXRjaFsxXSwgbWF0Y2hbMl0sIF8uYmluZChtZXRob2QsIHRoaXMpKTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXM7XG4gIH0sXG5cbiAgICAvLyBBZGQgYSBzaW5nbGUgZXZlbnQgbGlzdGVuZXIgdG8gdGhlIHZpZXcncyBlbGVtZW50IChvciBhIGNoaWxkIGVsZW1lbnRcbiAgICAvLyB1c2luZyBgc2VsZWN0b3JgKS4gVGhpcyBvbmx5IHdvcmtzIGZvciBkZWxlZ2F0ZS1hYmxlIGV2ZW50czogbm90IGBmb2N1c2AsXG4gICAgLy8gYGJsdXJgLCBhbmQgbm90IGBjaGFuZ2VgLCBgc3VibWl0YCwgYW5kIGByZXNldGAgaW4gSW50ZXJuZXQgRXhwbG9yZXIuXG4gIGRlbGVnYXRlOiBmdW5jdGlvbihldmVudE5hbWUsIHNlbGVjdG9yLCBsaXN0ZW5lcikge1xuICAgIHRoaXMuJGVsLm9uKGV2ZW50TmFtZSArICcuZGVsZWdhdGVFdmVudHMnICsgdGhpcy5jaWQsIHNlbGVjdG9yLCBsaXN0ZW5lcik7XG4gIH0sXG5cbiAgICAvLyBDbGVhcnMgYWxsIGNhbGxiYWNrcyBwcmV2aW91c2x5IGJvdW5kIHRvIHRoZSB2aWV3IGJ5IGBkZWxlZ2F0ZUV2ZW50c2AuXG4gICAgLy8gWW91IHVzdWFsbHkgZG9uJ3QgbmVlZCB0byB1c2UgdGhpcywgYnV0IG1heSB3aXNoIHRvIGlmIHlvdSBoYXZlIG11bHRpcGxlXG4gICAgLy8gQmFja2JvbmUgdmlld3MgYXR0YWNoZWQgdG8gdGhlIHNhbWUgRE9NIGVsZW1lbnQuXG4gIHVuZGVsZWdhdGVFdmVudHM6IGZ1bmN0aW9uKCkge1xuICAgIGlmICh0aGlzLiRlbCkgdGhpcy4kZWwub2ZmKCcuZGVsZWdhdGVFdmVudHMnICsgdGhpcy5jaWQpO1xuICAgIHJldHVybiB0aGlzO1xuICB9LFxuXG4gICAgLy8gQSBmaW5lci1ncmFpbmVkIGB1bmRlbGVnYXRlRXZlbnRzYCBmb3IgcmVtb3ZpbmcgYSBzaW5nbGUgZGVsZWdhdGVkIGV2ZW50LlxuICAgIC8vIGBzZWxlY3RvcmAgYW5kIGBsaXN0ZW5lcmAgYXJlIGJvdGggb3B0aW9uYWwuXG4gIHVuZGVsZWdhdGU6IGZ1bmN0aW9uKGV2ZW50TmFtZSwgc2VsZWN0b3IsIGxpc3RlbmVyKSB7XG4gICAgdGhpcy4kZWwub2ZmKGV2ZW50TmFtZSArICcuZGVsZWdhdGVFdmVudHMnICsgdGhpcy5jaWQsIHNlbGVjdG9yLCBsaXN0ZW5lcik7XG4gIH0sXG5cbiAgICAvLyBQcm9kdWNlcyBhIERPTSBlbGVtZW50IHRvIGJlIGFzc2lnbmVkIHRvIHlvdXIgdmlldy4gRXhwb3NlZCBmb3JcbiAgICAvLyBzdWJjbGFzc2VzIHVzaW5nIGFuIGFsdGVybmF0aXZlIERPTSBtYW5pcHVsYXRpb24gQVBJLlxuICBfY3JlYXRlRWxlbWVudDogZnVuY3Rpb24odGFnTmFtZSkge1xuICAgIHJldHVybiBkb2N1bWVudC5jcmVhdGVFbGVtZW50KHRhZ05hbWUpO1xuICB9LFxuXG4gICAgLy8gRW5zdXJlIHRoYXQgdGhlIFZpZXcgaGFzIGEgRE9NIGVsZW1lbnQgdG8gcmVuZGVyIGludG8uXG4gICAgLy8gSWYgYHRoaXMuZWxgIGlzIGEgc3RyaW5nLCBwYXNzIGl0IHRocm91Z2ggYCQoKWAsIHRha2UgdGhlIGZpcnN0XG4gICAgLy8gbWF0Y2hpbmcgZWxlbWVudCwgYW5kIHJlLWFzc2lnbiBpdCB0byBgZWxgLiBPdGhlcndpc2UsIGNyZWF0ZVxuICAgIC8vIGFuIGVsZW1lbnQgZnJvbSB0aGUgYGlkYCwgYGNsYXNzTmFtZWAgYW5kIGB0YWdOYW1lYCBwcm9wZXJ0aWVzLlxuICBfZW5zdXJlRWxlbWVudDogZnVuY3Rpb24oKSB7XG4gICAgaWYgKCF0aGlzLmVsKSB7XG4gICAgICB2YXIgYXR0cnMgPSBfLmV4dGVuZCh7fSwgXy5yZXN1bHQodGhpcywgJ2F0dHJpYnV0ZXMnKSk7XG4gICAgICBpZiAodGhpcy5pZCkgYXR0cnMuaWQgPSBfLnJlc3VsdCh0aGlzLCAnaWQnKTtcbiAgICAgIGlmICh0aGlzLmNsYXNzTmFtZSkgYXR0cnNbJ2NsYXNzJ10gPSBfLnJlc3VsdCh0aGlzLCAnY2xhc3NOYW1lJyk7XG4gICAgICB0aGlzLnNldEVsZW1lbnQodGhpcy5fY3JlYXRlRWxlbWVudChfLnJlc3VsdCh0aGlzLCAndGFnTmFtZScpKSk7XG4gICAgICB0aGlzLl9zZXRBdHRyaWJ1dGVzKGF0dHJzKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5zZXRFbGVtZW50KF8ucmVzdWx0KHRoaXMsICdlbCcpKTtcbiAgICB9XG4gIH0sXG5cbiAgICAvLyBTZXQgYXR0cmlidXRlcyBmcm9tIGEgaGFzaCBvbiB0aGlzIHZpZXcncyBlbGVtZW50LiAgRXhwb3NlZCBmb3JcbiAgICAvLyBzdWJjbGFzc2VzIHVzaW5nIGFuIGFsdGVybmF0aXZlIERPTSBtYW5pcHVsYXRpb24gQVBJLlxuICBfc2V0QXR0cmlidXRlczogZnVuY3Rpb24oYXR0cmlidXRlcykge1xuICAgIHRoaXMuJGVsLmF0dHIoYXR0cmlidXRlcyk7XG4gIH1cblxufSk7XG5cbi8vIHNldHVwIGluaGVyaXRhbmNlXG5WaWV3LmV4dGVuZCA9IGV4dGVuZDtcbm1vZHVsZS5leHBvcnRzID0gVmlldztcbiIsInZhciBldmVudHMgPSByZXF1aXJlKFwiYmFja2JvbmUtZXZlbnRzLXN0YW5kYWxvbmVcIik7XG5cbmV2ZW50cy5vbkFsbCA9IGZ1bmN0aW9uKGNhbGxiYWNrLGNvbnRleHQpe1xuICB0aGlzLm9uKFwiYWxsXCIsIGNhbGxiYWNrLGNvbnRleHQpO1xuICByZXR1cm4gdGhpcztcbn07XG5cbi8vIE1peGluIHV0aWxpdHlcbmV2ZW50cy5vbGRNaXhpbiA9IGV2ZW50cy5taXhpbjtcbmV2ZW50cy5taXhpbiA9IGZ1bmN0aW9uKHByb3RvKSB7XG4gIGV2ZW50cy5vbGRNaXhpbihwcm90byk7XG4gIC8vIGFkZCBjdXN0b20gb25BbGxcbiAgdmFyIGV4cG9ydHMgPSBbJ29uQWxsJ107XG4gIGZvcih2YXIgaT0wOyBpIDwgZXhwb3J0cy5sZW5ndGg7aSsrKXtcbiAgICB2YXIgbmFtZSA9IGV4cG9ydHNbaV07XG4gICAgcHJvdG9bbmFtZV0gPSB0aGlzW25hbWVdO1xuICB9XG4gIHJldHVybiBwcm90bztcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gZXZlbnRzO1xuIiwiLy8gR2VuZXJhdGVkIGJ5IENvZmZlZVNjcmlwdCAxLjguMFxudmFyIEdlbmVyaWNSZWFkZXIsIHhocjtcblxueGhyID0gcmVxdWlyZSgnbmV0cycpO1xuXG5tb2R1bGUuZXhwb3J0cyA9IEdlbmVyaWNSZWFkZXIgPSAoZnVuY3Rpb24oKSB7XG4gIGZ1bmN0aW9uIEdlbmVyaWNSZWFkZXIoKSB7fVxuXG4gIEdlbmVyaWNSZWFkZXIucmVhZCA9IGZ1bmN0aW9uKHVybCwgY2FsbGJhY2spIHtcbiAgICB2YXIgb25yZXQ7XG4gICAgb25yZXQgPSAoZnVuY3Rpb24oX3RoaXMpIHtcbiAgICAgIHJldHVybiBmdW5jdGlvbihlcnIsIHJlc3BvbnNlLCB0ZXh0KSB7XG4gICAgICAgIHJldHVybiBfdGhpcy5fb25SZXRyaWV2YWwodGV4dCwgY2FsbGJhY2spO1xuICAgICAgfTtcbiAgICB9KSh0aGlzKTtcbiAgICByZXR1cm4geGhyKHVybCwgb25yZXQpO1xuICB9O1xuXG4gIEdlbmVyaWNSZWFkZXIuX29uUmV0cmlldmFsID0gZnVuY3Rpb24odGV4dCwgY2FsbGJhY2spIHtcbiAgICB2YXIgclRleHQ7XG4gICAgclRleHQgPSB0aGlzLnBhcnNlKHRleHQpO1xuICAgIHJldHVybiBjYWxsYmFjayhyVGV4dCk7XG4gIH07XG5cbiAgcmV0dXJuIEdlbmVyaWNSZWFkZXI7XG5cbn0pKCk7XG4iLCIvLyBHZW5lcmF0ZWQgYnkgQ29mZmVlU2NyaXB0IDEuOC4wXG52YXIgU2VxO1xuXG5tb2R1bGUuZXhwb3J0cyA9IFNlcSA9IChmdW5jdGlvbigpIHtcbiAgZnVuY3Rpb24gU2VxKHNlcSwgbmFtZSwgaWQpIHtcbiAgICB2YXIgbWV0YTtcbiAgICB0aGlzLnNlcSA9IHNlcTtcbiAgICB0aGlzLm5hbWUgPSBuYW1lO1xuICAgIHRoaXMuaWQgPSBpZDtcbiAgICBtZXRhID0ge307XG4gIH1cblxuICByZXR1cm4gU2VxO1xuXG59KSgpO1xuIiwiLy8gR2VuZXJhdGVkIGJ5IENvZmZlZVNjcmlwdCAxLjguMFxudmFyIHN0cmluZ3M7XG5cbnN0cmluZ3MgPSB7XG4gIGNvbnRhaW5zOiBmdW5jdGlvbih0ZXh0LCBzZWFyY2gpIHtcbiAgICByZXR1cm4gJycuaW5kZXhPZi5jYWxsKHRleHQsIHNlYXJjaCwgMCkgIT09IC0xO1xuICB9XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IHN0cmluZ3M7XG4iLCIvLyBHZW5lcmF0ZWQgYnkgQ29mZmVlU2NyaXB0IDEuOC4wXG52YXIgRmFzdGEsIEdlbmVyaWNSZWFkZXIsIFNlcSwgU3RyLFxuICBfX2hhc1Byb3AgPSB7fS5oYXNPd25Qcm9wZXJ0eSxcbiAgX19leHRlbmRzID0gZnVuY3Rpb24oY2hpbGQsIHBhcmVudCkgeyBmb3IgKHZhciBrZXkgaW4gcGFyZW50KSB7IGlmIChfX2hhc1Byb3AuY2FsbChwYXJlbnQsIGtleSkpIGNoaWxkW2tleV0gPSBwYXJlbnRba2V5XTsgfSBmdW5jdGlvbiBjdG9yKCkgeyB0aGlzLmNvbnN0cnVjdG9yID0gY2hpbGQ7IH0gY3Rvci5wcm90b3R5cGUgPSBwYXJlbnQucHJvdG90eXBlOyBjaGlsZC5wcm90b3R5cGUgPSBuZXcgY3RvcigpOyBjaGlsZC5fX3N1cGVyX18gPSBwYXJlbnQucHJvdG90eXBlOyByZXR1cm4gY2hpbGQ7IH07XG5cblN0ciA9IHJlcXVpcmUoXCIuL3N0cmluZ3NcIik7XG5cbkdlbmVyaWNSZWFkZXIgPSByZXF1aXJlKFwiLi9nZW5lcmljX3JlYWRlclwiKTtcblxuU2VxID0gcmVxdWlyZShcImJpb2pzLW1vZGVsXCIpLnNlcTtcblxubW9kdWxlLmV4cG9ydHMgPSBGYXN0YSA9IChmdW5jdGlvbihfc3VwZXIpIHtcbiAgX19leHRlbmRzKEZhc3RhLCBfc3VwZXIpO1xuXG4gIGZ1bmN0aW9uIEZhc3RhKCkge1xuICAgIHJldHVybiBGYXN0YS5fX3N1cGVyX18uY29uc3RydWN0b3IuYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgfVxuXG4gIEZhc3RhLnBhcnNlID0gZnVuY3Rpb24odGV4dCkge1xuICAgIHZhciBjdXJyZW50U2VxLCBkYXRhYmFzZSwgZGF0YWJhc2VJRCwgaWRlbnRpZmllcnMsIGssIGxhYmVsLCBsaW5lLCBzZXFzLCBfaSwgX2xlbjtcbiAgICBzZXFzID0gW107XG4gICAgaWYgKE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcuY2FsbCh0ZXh0KSAhPT0gJ1tvYmplY3QgQXJyYXldJykge1xuICAgICAgdGV4dCA9IHRleHQuc3BsaXQoXCJcXG5cIik7XG4gICAgfVxuICAgIGZvciAoX2kgPSAwLCBfbGVuID0gdGV4dC5sZW5ndGg7IF9pIDwgX2xlbjsgX2krKykge1xuICAgICAgbGluZSA9IHRleHRbX2ldO1xuICAgICAgaWYgKGxpbmVbMF0gPT09IFwiPlwiIHx8IGxpbmVbMF0gPT09IFwiO1wiKSB7XG4gICAgICAgIGxhYmVsID0gbGluZS5zbGljZSgxKTtcbiAgICAgICAgY3VycmVudFNlcSA9IG5ldyBTZXEoXCJcIiwgbGFiZWwsIHNlcXMubGVuZ3RoKTtcbiAgICAgICAgc2Vxcy5wdXNoKGN1cnJlbnRTZXEpO1xuICAgICAgICBpZiAoU3RyLmNvbnRhaW5zKFwifFwiLCBsaW5lKSkge1xuICAgICAgICAgIGlkZW50aWZpZXJzID0gbGFiZWwuc3BsaXQoXCJ8XCIpO1xuICAgICAgICAgIGsgPSAxO1xuICAgICAgICAgIHdoaWxlIChrIDwgaWRlbnRpZmllcnMubGVuZ3RoKSB7XG4gICAgICAgICAgICBkYXRhYmFzZSA9IGlkZW50aWZpZXJzW2tdO1xuICAgICAgICAgICAgZGF0YWJhc2VJRCA9IGlkZW50aWZpZXJzW2sgKyAxXTtcbiAgICAgICAgICAgIGN1cnJlbnRTZXEubWV0YVtkYXRhYmFzZV0gPSBkYXRhYmFzZUlEO1xuICAgICAgICAgICAgayArPSAyO1xuICAgICAgICAgIH1cbiAgICAgICAgICBjdXJyZW50U2VxLm5hbWUgPSBpZGVudGlmaWVyc1tpZGVudGlmaWVycy5sZW5ndGggLSAxXTtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgY3VycmVudFNlcS5zZXEgKz0gbGluZTtcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHNlcXM7XG4gIH07XG5cbiAgcmV0dXJuIEZhc3RhO1xuXG59KShHZW5lcmljUmVhZGVyKTtcbiIsIi8vIEdlbmVyYXRlZCBieSBDb2ZmZWVTY3JpcHQgMS44LjBcbnZhciBVdGlscztcblxuVXRpbHMgPSB7fTtcblxuVXRpbHMuc3BsaXROQ2hhcnMgPSBmdW5jdGlvbih0eHQsIG51bSkge1xuICB2YXIgaSwgcmVzdWx0LCBfaSwgX3JlZjtcbiAgcmVzdWx0ID0gW107XG4gIGZvciAoaSA9IF9pID0gMCwgX3JlZiA9IHR4dC5sZW5ndGggLSAxOyBudW0gPiAwID8gX2kgPD0gX3JlZiA6IF9pID49IF9yZWY7IGkgPSBfaSArPSBudW0pIHtcbiAgICByZXN1bHQucHVzaCh0eHQuc3Vic3RyKGksIG51bSkpO1xuICB9XG4gIHJldHVybiByZXN1bHQ7XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IFV0aWxzO1xuIiwiLy8gR2VuZXJhdGVkIGJ5IENvZmZlZVNjcmlwdCAxLjguMFxudmFyIEZhc3RhRXhwb3J0ZXIsIFV0aWxzO1xuXG5VdGlscyA9IHJlcXVpcmUoXCIuL3V0aWxzXCIpO1xuXG5tb2R1bGUuZXhwb3J0cyA9IEZhc3RhRXhwb3J0ZXIgPSAoZnVuY3Rpb24oKSB7XG4gIGZ1bmN0aW9uIEZhc3RhRXhwb3J0ZXIoKSB7fVxuXG4gIEZhc3RhRXhwb3J0ZXJbXCJleHBvcnRcIl0gPSBmdW5jdGlvbihzZXFzLCBhY2Nlc3MpIHtcbiAgICB2YXIgc2VxLCB0ZXh0LCBfaSwgX2xlbjtcbiAgICB0ZXh0ID0gXCJcIjtcbiAgICBmb3IgKF9pID0gMCwgX2xlbiA9IHNlcXMubGVuZ3RoOyBfaSA8IF9sZW47IF9pKyspIHtcbiAgICAgIHNlcSA9IHNlcXNbX2ldO1xuICAgICAgaWYgKGFjY2VzcyAhPSBudWxsKSB7XG4gICAgICAgIHNlcSA9IGFjY2VzcyhzZXEpO1xuICAgICAgfVxuICAgICAgdGV4dCArPSBcIj5cIiArIHNlcS5uYW1lICsgXCJcXG5cIjtcbiAgICAgIHRleHQgKz0gKFV0aWxzLnNwbGl0TkNoYXJzKHNlcS5zZXEsIDgwKSkuam9pbihcIlxcblwiKTtcbiAgICAgIHRleHQgKz0gXCJcXG5cIjtcbiAgICB9XG4gICAgcmV0dXJuIHRleHQ7XG4gIH07XG5cbiAgcmV0dXJuIEZhc3RhRXhwb3J0ZXI7XG5cbn0pKCk7XG4iLCJtb2R1bGUuZXhwb3J0cy5zZXEgPSByZXF1aXJlKFwiLi9zZXFcIik7XG4iLCJtb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uKHNlcSwgbmFtZSwgaWQpIHtcbiAgICB0aGlzLnNlcSA9IHNlcTtcbiAgICB0aGlzLm5hbWUgPSBuYW1lO1xuICAgIHRoaXMuaWQgPSBpZDtcbiAgICB0aGlzLm1ldGEgPSB7fTtcbn07XG4iLCJtb2R1bGUuZXhwb3J0cyA9IHJlcXVpcmUoJy4vc3JjL2luZGV4LmpzJylcbiIsIm1vZHVsZS5leHBvcnRzID0ge1xuICBBOiBcIiMwMGEzNWNcIixcbiAgUjogXCIjMDBmYzAzXCIsXG4gIE46IFwiIzAwZWIxNFwiLFxuICBEOiBcIiMwMGViMTRcIixcbiAgQzogXCIjMDAwMGZmXCIsXG4gIFE6IFwiIzAwZjEwZVwiLFxuICBFOiBcIiMwMGYxMGVcIixcbiAgRzogXCIjMDA5ZDYyXCIsXG4gIEg6IFwiIzAwZDUyYVwiLFxuICBJOiBcIiMwMDU0YWJcIixcbiAgTDogXCIjMDA3Yjg0XCIsXG4gIEs6IFwiIzAwZmYwMFwiLFxuICBNOiBcIiMwMDk3NjhcIixcbiAgRjogXCIjMDA4Nzc4XCIsXG4gIFA6IFwiIzAwZTAxZlwiLFxuICBTOiBcIiMwMGQ1MmFcIixcbiAgVDogXCIjMDBkYjI0XCIsXG4gIFc6IFwiIzAwYTg1N1wiLFxuICBZOiBcIiMwMGU2MTlcIixcbiAgVjogXCIjMDA1ZmEwXCIsXG4gIEI6IFwiIzAwZWIxNFwiLFxuICBYOiBcIiMwMGI2NDlcIixcbiAgWjogXCIjMDBmMTBlXCJcbn07XG4iLCJtb2R1bGUuZXhwb3J0cyA9IHtcbiAgQTogXCIjQkJCQkJCXCIsXG4gIEI6IFwiZ3JleVwiLFxuICBDOiBcInllbGxvd1wiLFxuICBEOiBcInJlZFwiLFxuICBFOiBcInJlZFwiLFxuICBGOiBcIm1hZ2VudGFcIixcbiAgRzogXCJicm93blwiLFxuICBIOiBcIiMwMEZGRkZcIixcbiAgSTogXCIjQkJCQkJCXCIsXG4gIEo6IFwiI2ZmZlwiLFxuICBLOiBcIiMwMEZGRkZcIixcbiAgTDogXCIjQkJCQkJCXCIsXG4gIE06IFwiI0JCQkJCQlwiLFxuICBOOiBcImdyZWVuXCIsXG4gIE86IFwiI2ZmZlwiLFxuICBQOiBcImJyb3duXCIsXG4gIFE6IFwiZ3JlZW5cIixcbiAgUjogXCIjMDBGRkZGXCIsXG4gIFM6IFwiZ3JlZW5cIixcbiAgVDogXCJncmVlblwiLFxuICBVOiBcIiNmZmZcIixcbiAgVjogXCIjQkJCQkJCXCIsXG4gIFc6IFwibWFnZW50YVwiLFxuICBYOiBcImdyZXlcIixcbiAgWTogXCJtYWdlbnRhXCIsXG4gIFo6IFwiZ3JleVwiLFxuICBHYXA6IFwiZ3JleVwiXG59O1xuIiwibW9kdWxlLmV4cG9ydHMgPSB7XG4gIEE6IFwib3JhbmdlXCIsXG4gIEI6IFwiI2ZmZlwiLFxuICBDOiBcImdyZWVuXCIsXG4gIEQ6IFwicmVkXCIsXG4gIEU6IFwicmVkXCIsXG4gIEY6IFwiYmx1ZVwiLFxuICBHOiBcIm9yYW5nZVwiLFxuICBIOiBcInJlZFwiLFxuICBJOiBcImdyZWVuXCIsXG4gIEo6IFwiI2ZmZlwiLFxuICBLOiBcInJlZFwiLFxuICBMOiBcImdyZWVuXCIsXG4gIE06IFwiZ3JlZW5cIixcbiAgTjogXCIjZmZmXCIsXG4gIE86IFwiI2ZmZlwiLFxuICBQOiBcIm9yYW5nZVwiLFxuICBROiBcIiNmZmZcIixcbiAgUjogXCJyZWRcIixcbiAgUzogXCJvcmFuZ2VcIixcbiAgVDogXCJvcmFuZ2VcIixcbiAgVTogXCIjZmZmXCIsXG4gIFY6IFwiZ3JlZW5cIixcbiAgVzogXCJibHVlXCIsXG4gIFg6IFwiI2ZmZlwiLFxuICBZOiBcImJsdWVcIixcbiAgWjogXCIjZmZmXCIsXG4gIEdhcDogXCIjZmZmXCJcbn07XG4iLCJtb2R1bGUuZXhwb3J0cyA9IHtcbiAgQTogXCIjODBhMGYwXCIsXG4gIFI6IFwiI2YwMTUwNVwiLFxuICBOOiBcIiMwMGZmMDBcIixcbiAgRDogXCIjYzA0OGMwXCIsXG4gIEM6IFwiI2YwODA4MFwiLFxuICBROiBcIiMwMGZmMDBcIixcbiAgRTogXCIjYzA0OGMwXCIsXG4gIEc6IFwiI2YwOTA0OFwiLFxuICBIOiBcIiMxNWE0YTRcIixcbiAgSTogXCIjODBhMGYwXCIsXG4gIEw6IFwiIzgwYTBmMFwiLFxuICBLOiBcIiNmMDE1MDVcIixcbiAgTTogXCIjODBhMGYwXCIsXG4gIEY6IFwiIzgwYTBmMFwiLFxuICBQOiBcIiNmZmZmMDBcIixcbiAgUzogXCIjMDBmZjAwXCIsXG4gIFQ6IFwiIzAwZmYwMFwiLFxuICBXOiBcIiM4MGEwZjBcIixcbiAgWTogXCIjMTVhNGE0XCIsXG4gIFY6IFwiIzgwYTBmMFwiLFxuICBCOiBcIiNmZmZcIixcbiAgWDogXCIjZmZmXCIsXG4gIFo6IFwiI2ZmZlwiXG59O1xuIiwibW9kdWxlLmV4cG9ydHMgPSB7XG4gIEE6IFwiI2U3MThlN1wiLFxuICBSOiBcIiM2ZjkwNmZcIixcbiAgTjogXCIjMWJlNDFiXCIsXG4gIEQ6IFwiIzc3ODg3N1wiLFxuICBDOiBcIiMyM2RjMjNcIixcbiAgUTogXCIjOTI2ZDkyXCIsXG4gIEU6IFwiI2ZmMDBmZlwiLFxuICBHOiBcIiMwMGZmMDBcIixcbiAgSDogXCIjNzU4YTc1XCIsXG4gIEk6IFwiIzhhNzU4YVwiLFxuICBMOiBcIiNhZTUxYWVcIixcbiAgSzogXCIjYTA1ZmEwXCIsXG4gIE06IFwiI2VmMTBlZlwiLFxuICBGOiBcIiM5ODY3OThcIixcbiAgUDogXCIjMDBmZjAwXCIsXG4gIFM6IFwiIzM2YzkzNlwiLFxuICBUOiBcIiM0N2I4NDdcIixcbiAgVzogXCIjOGE3NThhXCIsXG4gIFk6IFwiIzIxZGUyMVwiLFxuICBWOiBcIiM4NTdhODVcIixcbiAgQjogXCIjNDliNjQ5XCIsXG4gIFg6IFwiIzc1OGE3NVwiLFxuICBaOiBcIiNjOTM2YzlcIlxufTtcbiIsIm1vZHVsZS5leHBvcnRzID0ge1xuICBBOiBcIiNhZDAwNTJcIixcbiAgQjogXCIjMGMwMGYzXCIsXG4gIEM6IFwiI2MyMDAzZFwiLFxuICBEOiBcIiMwYzAwZjNcIixcbiAgRTogXCIjMGMwMGYzXCIsXG4gIEY6IFwiI2NiMDAzNFwiLFxuICBHOiBcIiM2YTAwOTVcIixcbiAgSDogXCIjMTUwMGVhXCIsXG4gIEk6IFwiI2ZmMDAwMFwiLFxuICBKOiBcIiNmZmZcIixcbiAgSzogXCIjMDAwMGZmXCIsXG4gIEw6IFwiI2VhMDAxNVwiLFxuICBNOiBcIiNiMDAwNGZcIixcbiAgTjogXCIjMGMwMGYzXCIsXG4gIE86IFwiI2ZmZlwiLFxuICBQOiBcIiM0NjAwYjlcIixcbiAgUTogXCIjMGMwMGYzXCIsXG4gIFI6IFwiIzAwMDBmZlwiLFxuICBTOiBcIiM1ZTAwYTFcIixcbiAgVDogXCIjNjEwMDllXCIsXG4gIFU6IFwiI2ZmZlwiLFxuICBWOiBcIiNmNjAwMDlcIixcbiAgVzogXCIjNWIwMGE0XCIsXG4gIFg6IFwiIzY4MDA5N1wiLFxuICBZOiBcIiM0ZjAwYjBcIixcbiAgWjogXCIjMGMwMGYzXCJcbn07XG4iLCJtb2R1bGUuZXhwb3J0cy5zZWxlY3RvciA9IHJlcXVpcmUoXCIuL3NlbGVjdG9yXCIpO1xuXG4vLyBiYXNpY3Ncbm1vZHVsZS5leHBvcnRzLnRheWxvciA9IHJlcXVpcmUoXCIuL3RheWxvclwiKTtcbm1vZHVsZS5leHBvcnRzLnphcHBvPSByZXF1aXJlKFwiLi96YXBwb1wiKTtcbm1vZHVsZS5leHBvcnRzLmh5ZHJvPSByZXF1aXJlKFwiLi9oeWRyb3Bob2JpY2l0eVwiKTtcblxubW9kdWxlLmV4cG9ydHMuY2x1c3RhbCA9IHJlcXVpcmUoXCIuL2NsdXN0YWxcIik7XG5tb2R1bGUuZXhwb3J0cy5jbHVzdGFsMiA9IHJlcXVpcmUoXCIuL2NsdXN0YWwyXCIpO1xuXG5tb2R1bGUuZXhwb3J0cy5jdXJpZWQgPSByZXF1aXJlKFwiLi9idXJpZWRcIik7XG5tb2R1bGUuZXhwb3J0cy5jaW5lbWEgPSByZXF1aXJlKFwiLi9jaW5lbWFcIik7XG5tb2R1bGUuZXhwb3J0cy5udWNsZW90aWRlICA9IHJlcXVpcmUoXCIuL251Y2xlb3RpZGVcIik7XG5tb2R1bGUuZXhwb3J0cy5oZWxpeCAgPSByZXF1aXJlKFwiLi9oZWxpeFwiKTtcbm1vZHVsZS5leHBvcnRzLmxlc2sgID0gcmVxdWlyZShcIi4vbGVza1wiKTtcbm1vZHVsZS5leHBvcnRzLm1hZSA9IHJlcXVpcmUoXCIuL21hZVwiKTtcbm1vZHVsZS5leHBvcnRzLnB1cmluZSA9IHJlcXVpcmUoXCIuL3B1cmluZVwiKTtcbm1vZHVsZS5leHBvcnRzLnN0cmFuZCA9IHJlcXVpcmUoXCIuL3N0cmFuZFwiKTtcbm1vZHVsZS5leHBvcnRzLnR1cm4gPSByZXF1aXJlKFwiLi90dXJuXCIpO1xuIiwibW9kdWxlLmV4cG9ydHMgPSB7XG4gIEE6IFwiIG9yYW5nZVwiLFxuICBCOiBcIiAjZmZmXCIsXG4gIEM6IFwiIGdyZWVuXCIsXG4gIEQ6IFwiIHJlZFwiLFxuICBFOiBcIiByZWRcIixcbiAgRjogXCIgZ3JlZW5cIixcbiAgRzogXCIgb3JhbmdlXCIsXG4gIEg6IFwiIG1hZ2VudGFcIixcbiAgSTogXCIgZ3JlZW5cIixcbiAgSjogXCIgI2ZmZlwiLFxuICBLOiBcIiByZWRcIixcbiAgTDogXCIgZ3JlZW5cIixcbiAgTTogXCIgZ3JlZW5cIixcbiAgTjogXCIgbWFnZW50YVwiLFxuICBPOiBcIiAjZmZmXCIsXG4gIFA6IFwiIGdyZWVuXCIsXG4gIFE6IFwiIG1hZ2VudGFcIixcbiAgUjogXCIgcmVkXCIsXG4gIFM6IFwiIG9yYW5nZVwiLFxuICBUOiBcIiBvcmFuZ2VcIixcbiAgVTogXCIgI2ZmZlwiLFxuICBWOiBcIiBncmVlblwiLFxuICBXOiBcIiBncmVlblwiLFxuICBYOiBcIiAjZmZmXCIsXG4gIFk6IFwiIGdyZWVuXCIsXG4gIFo6IFwiICNmZmZcIixcbiAgR2FwOiBcIiAjZmZmXCJcbn07XG4iLCJtb2R1bGUuZXhwb3J0cyA9IHtcbiAgQTogXCIgIzc3ZGQ4OFwiLFxuICBCOiBcIiAjZmZmXCIsXG4gIEM6IFwiICM5OWVlNjZcIixcbiAgRDogXCIgIzU1YmIzM1wiLFxuICBFOiBcIiAjNTViYjMzXCIsXG4gIEY6IFwiICM5OTk5ZmZcIixcbiAgRzogXCIgIzc3ZGQ4OFwiLFxuICBIOiBcIiAjNTU1NWZmXCIsXG4gIEk6IFwiICM2NmJiZmZcIixcbiAgSjogXCIgI2ZmZlwiLFxuICBLOiBcIiAjZmZjYzc3XCIsXG4gIEw6IFwiICM2NmJiZmZcIixcbiAgTTogXCIgIzY2YmJmZlwiLFxuICBOOiBcIiAjNTViYjMzXCIsXG4gIE86IFwiICNmZmZcIixcbiAgUDogXCIgI2VlYWFhYVwiLFxuICBROiBcIiAjNTViYjMzXCIsXG4gIFI6IFwiICNmZmNjNzdcIixcbiAgUzogXCIgI2ZmNDQ1NVwiLFxuICBUOiBcIiAjZmY0NDU1XCIsXG4gIFU6IFwiICNmZmZcIixcbiAgVjogXCIgIzY2YmJmZlwiLFxuICBXOiBcIiAjOTk5OWZmXCIsXG4gIFg6IFwiICNmZmZcIixcbiAgWTogXCIgIzk5OTlmZlwiLFxuICBaOiBcIiAjZmZmXCIsXG4gIEdhcDogXCIgI2ZmZlwiXG59O1xuIiwibW9kdWxlLmV4cG9ydHMgPSB7XG4gIEE6IFwiICM2NEY3M0ZcIixcbiAgQzogXCIgI0ZGQjM0MFwiLFxuICBHOiBcIiAjRUI0MTNDXCIsXG4gIFQ6IFwiICMzQzg4RUVcIixcbiAgVTogXCIgIzNDODhFRVwiXG59O1xuIiwibW9kdWxlLmV4cG9ydHMgPSB7XG4gIEE6IFwiICNGRjgzRkFcIixcbiAgQzogXCIgIzQwRTBEMFwiLFxuICBHOiBcIiAjRkY4M0ZBXCIsXG4gIFI6IFwiICNGRjgzRkFcIixcbiAgVDogXCIgIzQwRTBEMFwiLFxuICBVOiBcIiAjNDBFMEQwXCIsXG4gIFk6IFwiICM0MEUwRDBcIlxufTtcbiIsInZhciBCdXJpZWQgPSByZXF1aXJlKFwiLi9idXJpZWRcIik7XG52YXIgQ2luZW1hID0gcmVxdWlyZShcIi4vY2luZW1hXCIpO1xudmFyIENsdXN0YWwgPSByZXF1aXJlKFwiLi9jbHVzdGFsXCIpO1xudmFyIENsdXN0YWwyID0gcmVxdWlyZShcIi4vY2x1c3RhbDJcIik7XG52YXIgSGVsaXggPSByZXF1aXJlKFwiLi9oZWxpeFwiKTtcbnZhciBIeWRybyA9IHJlcXVpcmUoXCIuL2h5ZHJvcGhvYmljaXR5XCIpO1xudmFyIExlc2sgPSByZXF1aXJlKFwiLi9sZXNrXCIpO1xudmFyIE1hZSA9IHJlcXVpcmUoXCIuL21hZVwiKTtcbnZhciBOdWNsZW90aWRlID0gcmVxdWlyZShcIi4vbnVjbGVvdGlkZVwiKTtcbnZhciBQdXJpbmUgPSByZXF1aXJlKFwiLi9wdXJpbmVcIik7XG52YXIgU3RyYW5kID0gcmVxdWlyZShcIi4vc3RyYW5kXCIpO1xudmFyIFRheWxvciA9IHJlcXVpcmUoXCIuL3RheWxvclwiKTtcbnZhciBUdXJuID0gcmVxdWlyZShcIi4vdHVyblwiKTtcbnZhciBaYXBwbyA9IHJlcXVpcmUoXCIuL3phcHBvXCIpO1xuXG5tb2R1bGUuZXhwb3J0cyA9IENvbG9ycyA9IHtcbiAgbWFwcGluZzoge1xuICAgIGJ1cmllZDogQnVyaWVkLFxuICAgIGJ1cmllZF9pbmRleDogQnVyaWVkLFxuICAgIGNpbmVtYTogQ2luZW1hLFxuICAgIGNsdXN0YWwyOiBDbHVzdGFsMixcbiAgICBjbHVzdGFsOiBDbHVzdGFsLFxuICAgIGhlbGl4OiBIZWxpeCxcbiAgICBoZWxpeF9wcm9wZW5zaXR5OiBIZWxpeCxcbiAgICBoeWRybzogSHlkcm8sXG4gICAgbGVzazogTGVzayxcbiAgICBtYWU6IE1hZSxcbiAgICBudWNsZW90aWRlOiBOdWNsZW90aWRlLFxuICAgIHB1cmluZTogUHVyaW5lLFxuICAgIHB1cmluZV9weXJpbWlkaW5lOiBQdXJpbmUsXG4gICAgc3RyYW5kOiBTdHJhbmQsXG4gICAgc3RyYW5kX3Byb3BlbnNpdHk6IFN0cmFuZCxcbiAgICB0YXlsb3I6IFRheWxvcixcbiAgICB0dXJuOiBUdXJuLFxuICAgIHR1cm5fcHJvcGVuc2l0eTogVHVybixcbiAgICB6YXBwbzogWmFwcG8sXG4gIH0sXG4gIGdldENvbG9yOiBmdW5jdGlvbihzY2hlbWUpIHtcbiAgICB2YXIgY29sb3IgPSBDb2xvcnMubWFwcGluZ1tzY2hlbWVdO1xuICAgIGlmIChjb2xvciA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICBjb2xvciA9IHt9O1xuICAgIH1cbiAgICByZXR1cm4gY29sb3I7XG4gIH1cbn07XG4iLCJtb2R1bGUuZXhwb3J0cyA9IHtcbiAgQTogXCIjNTg1OGE3XCIsXG4gIFI6IFwiIzZiNmI5NFwiLFxuICBOOiBcIiM2NDY0OWJcIixcbiAgRDogXCIjMjEyMWRlXCIsXG4gIEM6IFwiIzlkOWQ2MlwiLFxuICBROiBcIiM4YzhjNzNcIixcbiAgRTogXCIjMDAwMGZmXCIsXG4gIEc6IFwiIzQ5NDliNlwiLFxuICBIOiBcIiM2MDYwOWZcIixcbiAgSTogXCIjZWNlYzEzXCIsXG4gIEw6IFwiI2IyYjI0ZFwiLFxuICBLOiBcIiM0NzQ3YjhcIixcbiAgTTogXCIjODI4MjdkXCIsXG4gIEY6IFwiI2MyYzIzZFwiLFxuICBQOiBcIiMyMzIzZGNcIixcbiAgUzogXCIjNDk0OWI2XCIsXG4gIFQ6IFwiIzlkOWQ2MlwiLFxuICBXOiBcIiNjMGMwM2ZcIixcbiAgWTogXCIjZDNkMzJjXCIsXG4gIFY6IFwiI2ZmZmYwMFwiLFxuICBCOiBcIiM0MzQzYmNcIixcbiAgWDogXCIjNzk3OTg2XCIsXG4gIFo6IFwiIzQ3NDdiOFwiXG59O1xuIiwibW9kdWxlLmV4cG9ydHMgPSB7XG4gIEE6IFwiI2NjZmYwMFwiLFxuICBSOiBcIiMwMDAwZmZcIixcbiAgTjogXCIjY2MwMGZmXCIsXG4gIEQ6IFwiI2ZmMDAwMFwiLFxuICBDOiBcIiNmZmZmMDBcIixcbiAgUTogXCIjZmYwMGNjXCIsXG4gIEU6IFwiI2ZmMDA2NlwiLFxuICBHOiBcIiNmZjk5MDBcIixcbiAgSDogXCIjMDA2NmZmXCIsXG4gIEk6IFwiIzY2ZmYwMFwiLFxuICBMOiBcIiMzM2ZmMDBcIixcbiAgSzogXCIjNjYwMGZmXCIsXG4gIE06IFwiIzAwZmYwMFwiLFxuICBGOiBcIiMwMGZmNjZcIixcbiAgUDogXCIjZmZjYzAwXCIsXG4gIFM6IFwiI2ZmMzMwMFwiLFxuICBUOiBcIiNmZjY2MDBcIixcbiAgVzogXCIjMDBjY2ZmXCIsXG4gIFk6IFwiIzAwZmZjY1wiLFxuICBWOiBcIiM5OWZmMDBcIixcbiAgQjogXCIjZmZmXCIsXG4gIFg6IFwiI2ZmZlwiLFxuICBaOiBcIiNmZmZcIlxufTtcbiIsIm1vZHVsZS5leHBvcnRzID0ge1xuICBBOiBcIiMyY2QzZDNcIixcbiAgUjogXCIjNzA4ZjhmXCIsXG4gIE46IFwiI2ZmMDAwMFwiLFxuICBEOiBcIiNlODE3MTdcIixcbiAgQzogXCIjYTg1NzU3XCIsXG4gIFE6IFwiIzNmYzBjMFwiLFxuICBFOiBcIiM3Nzg4ODhcIixcbiAgRzogXCIjZmYwMDAwXCIsXG4gIEg6IFwiIzcwOGY4ZlwiLFxuICBJOiBcIiMwMGZmZmZcIixcbiAgTDogXCIjMWNlM2UzXCIsXG4gIEs6IFwiIzdlODE4MVwiLFxuICBNOiBcIiMxZWUxZTFcIixcbiAgRjogXCIjMWVlMWUxXCIsXG4gIFA6IFwiI2Y2MDkwOVwiLFxuICBTOiBcIiNlMTFlMWVcIixcbiAgVDogXCIjNzM4YzhjXCIsXG4gIFc6IFwiIzczOGM4Y1wiLFxuICBZOiBcIiM5ZDYyNjJcIixcbiAgVjogXCIjMDdmOGY4XCIsXG4gIEI6IFwiI2YzMGMwY1wiLFxuICBYOiBcIiM3YzgzODNcIixcbiAgWjogXCIjNWJhNGE0XCJcbn07XG4iLCJtb2R1bGUuZXhwb3J0cyA9IHtcbiAgQTogXCIjZmZhZmFmXCIsXG4gIFI6IFwiIzY0NjRmZlwiLFxuICBOOiBcIiMwMGZmMDBcIixcbiAgRDogXCIjZmYwMDAwXCIsXG4gIEM6IFwiI2ZmZmYwMFwiLFxuICBROiBcIiMwMGZmMDBcIixcbiAgRTogXCIjZmYwMDAwXCIsXG4gIEc6IFwiI2ZmMDBmZlwiLFxuICBIOiBcIiM2NDY0ZmZcIixcbiAgSTogXCIjZmZhZmFmXCIsXG4gIEw6IFwiI2ZmYWZhZlwiLFxuICBLOiBcIiM2NDY0ZmZcIixcbiAgTTogXCIjZmZhZmFmXCIsXG4gIEY6IFwiI2ZmYzgwMFwiLFxuICBQOiBcIiNmZjAwZmZcIixcbiAgUzogXCIjMDBmZjAwXCIsXG4gIFQ6IFwiIzAwZmYwMFwiLFxuICBXOiBcIiNmZmM4MDBcIixcbiAgWTogXCIjZmZjODAwXCIsXG4gIFY6IFwiI2ZmYWZhZlwiLFxuICBCOiBcIiNmZmZcIixcbiAgWDogXCIjZmZmXCIsXG4gIFo6IFwiI2ZmZlwiXG59O1xuIiwiLypcbiAqIEphdmFTY3JpcHQgQ2FudmFzIHRvIEJsb2IgMi4wLjVcbiAqIGh0dHBzOi8vZ2l0aHViLmNvbS9ibHVlaW1wL0phdmFTY3JpcHQtQ2FudmFzLXRvLUJsb2JcbiAqXG4gKiBDb3B5cmlnaHQgMjAxMiwgU2ViYXN0aWFuIFRzY2hhblxuICogaHR0cHM6Ly9ibHVlaW1wLm5ldFxuICpcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgbGljZW5zZTpcbiAqIGh0dHA6Ly93d3cub3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvTUlUXG4gKlxuICogQmFzZWQgb24gc3RhY2tvdmVyZmxvdyB1c2VyIFN0b2l2ZSdzIGNvZGUgc25pcHBldDpcbiAqIGh0dHA6Ly9zdGFja292ZXJmbG93LmNvbS9xLzQ5OTg5MDhcbiAqL1xudmFyIENhbnZhc1Byb3RvdHlwZSA9IHdpbmRvdy5IVE1MQ2FudmFzRWxlbWVudCAmJlxud2luZG93LkhUTUxDYW52YXNFbGVtZW50LnByb3RvdHlwZSxcbiAgaGFzQmxvYkNvbnN0cnVjdG9yID0gd2luZG93LkJsb2IgJiYgKGZ1bmN0aW9uICgpIHtcbiAgICB0cnkge1xuICAgICAgcmV0dXJuIEJvb2xlYW4obmV3IEJsb2IoKSk7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgfSgpKSxcbiAgaGFzQXJyYXlCdWZmZXJWaWV3U3VwcG9ydCA9IGhhc0Jsb2JDb25zdHJ1Y3RvciAmJiB3aW5kb3cuVWludDhBcnJheSAmJlxuICAoZnVuY3Rpb24gKCkge1xuICAgIHRyeSB7XG4gICAgICByZXR1cm4gbmV3IEJsb2IoW25ldyBVaW50OEFycmF5KDEwMCldKS5zaXplID09PSAxMDA7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgfSgpKSxcbiAgQmxvYkJ1aWxkZXIgPSB3aW5kb3cuQmxvYkJ1aWxkZXIgfHwgd2luZG93LldlYktpdEJsb2JCdWlsZGVyIHx8XG4gIHdpbmRvdy5Nb3pCbG9iQnVpbGRlciB8fCB3aW5kb3cuTVNCbG9iQnVpbGRlcixcbiAgZGF0YVVSTHRvQmxvYiA9IChoYXNCbG9iQ29uc3RydWN0b3IgfHwgQmxvYkJ1aWxkZXIpICYmIHdpbmRvdy5hdG9iICYmXG4gIHdpbmRvdy5BcnJheUJ1ZmZlciAmJiB3aW5kb3cuVWludDhBcnJheSAmJiBmdW5jdGlvbiAoZGF0YVVSSSkge1xuICAgIHZhciBieXRlU3RyaW5nLFxuICAgIGFycmF5QnVmZmVyLFxuICAgIGludEFycmF5LFxuICAgICAgaSxcbiAgICAgIG1pbWVTdHJpbmcsXG4gICAgICAgIGJiO1xuICAgIGlmIChkYXRhVVJJLnNwbGl0KCcsJylbMF0uaW5kZXhPZignYmFzZTY0JykgPj0gMCkge1xuICAgICAgLy8gQ29udmVydCBiYXNlNjQgdG8gcmF3IGJpbmFyeSBkYXRhIGhlbGQgaW4gYSBzdHJpbmc6XG4gICAgICBieXRlU3RyaW5nID0gYXRvYihkYXRhVVJJLnNwbGl0KCcsJylbMV0pO1xuICAgIH0gZWxzZSB7XG4gICAgICAvLyBDb252ZXJ0IGJhc2U2NC9VUkxFbmNvZGVkIGRhdGEgY29tcG9uZW50IHRvIHJhdyBiaW5hcnkgZGF0YTpcbiAgICAgIGJ5dGVTdHJpbmcgPSBkZWNvZGVVUklDb21wb25lbnQoZGF0YVVSSS5zcGxpdCgnLCcpWzFdKTtcbiAgICB9XG4gICAgLy8gV3JpdGUgdGhlIGJ5dGVzIG9mIHRoZSBzdHJpbmcgdG8gYW4gQXJyYXlCdWZmZXI6XG4gICAgYXJyYXlCdWZmZXIgPSBuZXcgQXJyYXlCdWZmZXIoYnl0ZVN0cmluZy5sZW5ndGgpO1xuICAgIGludEFycmF5ID0gbmV3IFVpbnQ4QXJyYXkoYXJyYXlCdWZmZXIpO1xuICAgIGZvciAoaSA9IDA7IGkgPCBieXRlU3RyaW5nLmxlbmd0aDsgaSArPSAxKSB7XG4gICAgICBpbnRBcnJheVtpXSA9IGJ5dGVTdHJpbmcuY2hhckNvZGVBdChpKTtcbiAgICB9XG4gICAgLy8gU2VwYXJhdGUgb3V0IHRoZSBtaW1lIGNvbXBvbmVudDpcbiAgICBtaW1lU3RyaW5nID0gZGF0YVVSSS5zcGxpdCgnLCcpWzBdLnNwbGl0KCc6JylbMV0uc3BsaXQoJzsnKVswXTtcbiAgICAvLyBXcml0ZSB0aGUgQXJyYXlCdWZmZXIgKG9yIEFycmF5QnVmZmVyVmlldykgdG8gYSBibG9iOlxuICAgIGlmIChoYXNCbG9iQ29uc3RydWN0b3IpIHtcbiAgICAgIHJldHVybiBuZXcgQmxvYihcbiAgICAgICAgICBbaGFzQXJyYXlCdWZmZXJWaWV3U3VwcG9ydCA/IGludEFycmF5IDogYXJyYXlCdWZmZXJdLFxuICAgICAgICAgIHt0eXBlOiBtaW1lU3RyaW5nfVxuICAgICAgICAgICk7XG4gICAgfVxuICAgIGJiID0gbmV3IEJsb2JCdWlsZGVyKCk7XG4gICAgYmIuYXBwZW5kKGFycmF5QnVmZmVyKTtcbiAgICByZXR1cm4gYmIuZ2V0QmxvYihtaW1lU3RyaW5nKTtcbiAgfTtcbmlmICh3aW5kb3cuSFRNTENhbnZhc0VsZW1lbnQgJiYgIUNhbnZhc1Byb3RvdHlwZS50b0Jsb2IpIHtcbiAgaWYgKENhbnZhc1Byb3RvdHlwZS5tb3pHZXRBc0ZpbGUpIHtcbiAgICBDYW52YXNQcm90b3R5cGUudG9CbG9iID0gZnVuY3Rpb24gKGNhbGxiYWNrLCB0eXBlLCBxdWFsaXR5KSB7XG4gICAgICBpZiAocXVhbGl0eSAmJiBDYW52YXNQcm90b3R5cGUudG9EYXRhVVJMICYmIGRhdGFVUkx0b0Jsb2IpIHtcbiAgICAgICAgY2FsbGJhY2soZGF0YVVSTHRvQmxvYih0aGlzLnRvRGF0YVVSTCh0eXBlLCBxdWFsaXR5KSkpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgY2FsbGJhY2sodGhpcy5tb3pHZXRBc0ZpbGUoJ2Jsb2InLCB0eXBlKSk7XG4gICAgICB9XG4gICAgfTtcbiAgfSBlbHNlIGlmIChDYW52YXNQcm90b3R5cGUudG9EYXRhVVJMICYmIGRhdGFVUkx0b0Jsb2IpIHtcbiAgICBDYW52YXNQcm90b3R5cGUudG9CbG9iID0gZnVuY3Rpb24gKGNhbGxiYWNrLCB0eXBlLCBxdWFsaXR5KSB7XG4gICAgICBjYWxsYmFjayhkYXRhVVJMdG9CbG9iKHRoaXMudG9EYXRhVVJMKHR5cGUsIHF1YWxpdHkpKSk7XG4gICAgfTtcbiAgfVxufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGRhdGFVUkx0b0Jsb2I7XG4iLCIvKiBGaWxlU2F2ZXIuanNcbiAqICBBIHNhdmVBcygpIEZpbGVTYXZlciBpbXBsZW1lbnRhdGlvbi5cbiAqICAyMDE0LTA1LTI3XG4gKlxuICogIEJ5IEVsaSBHcmV5LCBodHRwOi8vZWxpZ3JleS5jb21cbiAqICBMaWNlbnNlOiBYMTEvTUlUXG4gKiAgICBTZWUgaHR0cHM6Ly9naXRodWIuY29tL2VsaWdyZXkvRmlsZVNhdmVyLmpzL2Jsb2IvbWFzdGVyL0xJQ0VOU0UubWRcbiAqL1xuXG4vKmdsb2JhbCBzZWxmICovXG4vKmpzbGludCBiaXR3aXNlOiB0cnVlLCBpbmRlbnQ6IDQsIGxheGJyZWFrOiB0cnVlLCBsYXhjb21tYTogdHJ1ZSwgc21hcnR0YWJzOiB0cnVlLCBwbHVzcGx1czogdHJ1ZSAqL1xuXG4vKiEgQHNvdXJjZSBodHRwOi8vcHVybC5lbGlncmV5LmNvbS9naXRodWIvRmlsZVNhdmVyLmpzL2Jsb2IvbWFzdGVyL0ZpbGVTYXZlci5qcyAqL1xuXG52YXIgc2F2ZUFzID0gc2F2ZUFzXG4gIC8vIElFIDEwKyAobmF0aXZlIHNhdmVBcylcbiAgfHwgKHR5cGVvZiBuYXZpZ2F0b3IgIT09IFwidW5kZWZpbmVkXCIgJiZcbiAgICAgIG5hdmlnYXRvci5tc1NhdmVPck9wZW5CbG9iICYmIG5hdmlnYXRvci5tc1NhdmVPck9wZW5CbG9iLmJpbmQobmF2aWdhdG9yKSlcbiAgLy8gRXZlcnlvbmUgZWxzZVxuICB8fCAoZnVuY3Rpb24odmlldykge1xuXHRcInVzZSBzdHJpY3RcIjtcblx0Ly8gSUUgPDEwIGlzIGV4cGxpY2l0bHkgdW5zdXBwb3J0ZWRcblx0aWYgKHR5cGVvZiBuYXZpZ2F0b3IgIT09IFwidW5kZWZpbmVkXCIgJiZcblx0ICAgIC9NU0lFIFsxLTldXFwuLy50ZXN0KG5hdmlnYXRvci51c2VyQWdlbnQpKSB7XG5cdFx0cmV0dXJuO1xuXHR9XG5cdHZhclxuXHRcdCAgZG9jID0gdmlldy5kb2N1bWVudFxuXHRcdCAgLy8gb25seSBnZXQgVVJMIHdoZW4gbmVjZXNzYXJ5IGluIGNhc2UgQmxvYi5qcyBoYXNuJ3Qgb3ZlcnJpZGRlbiBpdCB5ZXRcblx0XHQsIGdldF9VUkwgPSBmdW5jdGlvbigpIHtcblx0XHRcdHJldHVybiB2aWV3LlVSTCB8fCB2aWV3LndlYmtpdFVSTCB8fCB2aWV3O1xuXHRcdH1cblx0XHQsIHNhdmVfbGluayA9IGRvYy5jcmVhdGVFbGVtZW50TlMoXCJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sXCIsIFwiYVwiKVxuXHRcdCwgY2FuX3VzZV9zYXZlX2xpbmsgPSAhdmlldy5leHRlcm5hbEhvc3QgJiYgXCJkb3dubG9hZFwiIGluIHNhdmVfbGlua1xuXHRcdCwgY2xpY2sgPSBmdW5jdGlvbihub2RlKSB7XG5cdFx0XHR2YXIgZXZlbnQgPSBkb2MuY3JlYXRlRXZlbnQoXCJNb3VzZUV2ZW50c1wiKTtcblx0XHRcdGV2ZW50LmluaXRNb3VzZUV2ZW50KFxuXHRcdFx0XHRcImNsaWNrXCIsIHRydWUsIGZhbHNlLCB2aWV3LCAwLCAwLCAwLCAwLCAwXG5cdFx0XHRcdCwgZmFsc2UsIGZhbHNlLCBmYWxzZSwgZmFsc2UsIDAsIG51bGxcblx0XHRcdCk7XG5cdFx0XHRub2RlLmRpc3BhdGNoRXZlbnQoZXZlbnQpO1xuXHRcdH1cblx0XHQsIHdlYmtpdF9yZXFfZnMgPSB2aWV3LndlYmtpdFJlcXVlc3RGaWxlU3lzdGVtXG5cdFx0LCByZXFfZnMgPSB2aWV3LnJlcXVlc3RGaWxlU3lzdGVtIHx8IHdlYmtpdF9yZXFfZnMgfHwgdmlldy5tb3pSZXF1ZXN0RmlsZVN5c3RlbVxuXHRcdCwgdGhyb3dfb3V0c2lkZSA9IGZ1bmN0aW9uKGV4KSB7XG5cdFx0XHQodmlldy5zZXRJbW1lZGlhdGUgfHwgdmlldy5zZXRUaW1lb3V0KShmdW5jdGlvbigpIHtcblx0XHRcdFx0dGhyb3cgZXg7XG5cdFx0XHR9LCAwKTtcblx0XHR9XG5cdFx0LCBmb3JjZV9zYXZlYWJsZV90eXBlID0gXCJhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW1cIlxuXHRcdCwgZnNfbWluX3NpemUgPSAwXG5cdFx0LCBkZWxldGlvbl9xdWV1ZSA9IFtdXG5cdFx0LCBwcm9jZXNzX2RlbGV0aW9uX3F1ZXVlID0gZnVuY3Rpb24oKSB7XG5cdFx0XHR2YXIgaSA9IGRlbGV0aW9uX3F1ZXVlLmxlbmd0aDtcblx0XHRcdHdoaWxlIChpLS0pIHtcblx0XHRcdFx0dmFyIGZpbGUgPSBkZWxldGlvbl9xdWV1ZVtpXTtcblx0XHRcdFx0aWYgKHR5cGVvZiBmaWxlID09PSBcInN0cmluZ1wiKSB7IC8vIGZpbGUgaXMgYW4gb2JqZWN0IFVSTFxuXHRcdFx0XHRcdGdldF9VUkwoKS5yZXZva2VPYmplY3RVUkwoZmlsZSk7XG5cdFx0XHRcdH0gZWxzZSB7IC8vIGZpbGUgaXMgYSBGaWxlXG5cdFx0XHRcdFx0ZmlsZS5yZW1vdmUoKTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdFx0ZGVsZXRpb25fcXVldWUubGVuZ3RoID0gMDsgLy8gY2xlYXIgcXVldWVcblx0XHR9XG5cdFx0LCBkaXNwYXRjaCA9IGZ1bmN0aW9uKGZpbGVzYXZlciwgZXZlbnRfdHlwZXMsIGV2ZW50KSB7XG5cdFx0XHRldmVudF90eXBlcyA9IFtdLmNvbmNhdChldmVudF90eXBlcyk7XG5cdFx0XHR2YXIgaSA9IGV2ZW50X3R5cGVzLmxlbmd0aDtcblx0XHRcdHdoaWxlIChpLS0pIHtcblx0XHRcdFx0dmFyIGxpc3RlbmVyID0gZmlsZXNhdmVyW1wib25cIiArIGV2ZW50X3R5cGVzW2ldXTtcblx0XHRcdFx0aWYgKHR5cGVvZiBsaXN0ZW5lciA9PT0gXCJmdW5jdGlvblwiKSB7XG5cdFx0XHRcdFx0dHJ5IHtcblx0XHRcdFx0XHRcdGxpc3RlbmVyLmNhbGwoZmlsZXNhdmVyLCBldmVudCB8fCBmaWxlc2F2ZXIpO1xuXHRcdFx0XHRcdH0gY2F0Y2ggKGV4KSB7XG5cdFx0XHRcdFx0XHR0aHJvd19vdXRzaWRlKGV4KTtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHR9XG5cdFx0LCBGaWxlU2F2ZXIgPSBmdW5jdGlvbihibG9iLCBuYW1lKSB7XG5cdFx0XHQvLyBGaXJzdCB0cnkgYS5kb3dubG9hZCwgdGhlbiB3ZWIgZmlsZXN5c3RlbSwgdGhlbiBvYmplY3QgVVJMc1xuXHRcdFx0dmFyXG5cdFx0XHRcdCAgZmlsZXNhdmVyID0gdGhpc1xuXHRcdFx0XHQsIHR5cGUgPSBibG9iLnR5cGVcblx0XHRcdFx0LCBibG9iX2NoYW5nZWQgPSBmYWxzZVxuXHRcdFx0XHQsIG9iamVjdF91cmxcblx0XHRcdFx0LCB0YXJnZXRfdmlld1xuXHRcdFx0XHQsIGdldF9vYmplY3RfdXJsID0gZnVuY3Rpb24oKSB7XG5cdFx0XHRcdFx0dmFyIG9iamVjdF91cmwgPSBnZXRfVVJMKCkuY3JlYXRlT2JqZWN0VVJMKGJsb2IpO1xuXHRcdFx0XHRcdGRlbGV0aW9uX3F1ZXVlLnB1c2gob2JqZWN0X3VybCk7XG5cdFx0XHRcdFx0cmV0dXJuIG9iamVjdF91cmw7XG5cdFx0XHRcdH1cblx0XHRcdFx0LCBkaXNwYXRjaF9hbGwgPSBmdW5jdGlvbigpIHtcblx0XHRcdFx0XHRkaXNwYXRjaChmaWxlc2F2ZXIsIFwid3JpdGVzdGFydCBwcm9ncmVzcyB3cml0ZSB3cml0ZWVuZFwiLnNwbGl0KFwiIFwiKSk7XG5cdFx0XHRcdH1cblx0XHRcdFx0Ly8gb24gYW55IGZpbGVzeXMgZXJyb3JzIHJldmVydCB0byBzYXZpbmcgd2l0aCBvYmplY3QgVVJMc1xuXHRcdFx0XHQsIGZzX2Vycm9yID0gZnVuY3Rpb24oKSB7XG5cdFx0XHRcdFx0Ly8gZG9uJ3QgY3JlYXRlIG1vcmUgb2JqZWN0IFVSTHMgdGhhbiBuZWVkZWRcblx0XHRcdFx0XHRpZiAoYmxvYl9jaGFuZ2VkIHx8ICFvYmplY3RfdXJsKSB7XG5cdFx0XHRcdFx0XHRvYmplY3RfdXJsID0gZ2V0X29iamVjdF91cmwoYmxvYik7XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHRcdGlmICh0YXJnZXRfdmlldykge1xuXHRcdFx0XHRcdFx0dGFyZ2V0X3ZpZXcubG9jYXRpb24uaHJlZiA9IG9iamVjdF91cmw7XG5cdFx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHRcdHdpbmRvdy5vcGVuKG9iamVjdF91cmwsIFwiX2JsYW5rXCIpO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0XHRmaWxlc2F2ZXIucmVhZHlTdGF0ZSA9IGZpbGVzYXZlci5ET05FO1xuXHRcdFx0XHRcdGRpc3BhdGNoX2FsbCgpO1xuXHRcdFx0XHR9XG5cdFx0XHRcdCwgYWJvcnRhYmxlID0gZnVuY3Rpb24oZnVuYykge1xuXHRcdFx0XHRcdHJldHVybiBmdW5jdGlvbigpIHtcblx0XHRcdFx0XHRcdGlmIChmaWxlc2F2ZXIucmVhZHlTdGF0ZSAhPT0gZmlsZXNhdmVyLkRPTkUpIHtcblx0XHRcdFx0XHRcdFx0cmV0dXJuIGZ1bmMuYXBwbHkodGhpcywgYXJndW1lbnRzKTtcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9O1xuXHRcdFx0XHR9XG5cdFx0XHRcdCwgY3JlYXRlX2lmX25vdF9mb3VuZCA9IHtjcmVhdGU6IHRydWUsIGV4Y2x1c2l2ZTogZmFsc2V9XG5cdFx0XHRcdCwgc2xpY2Vcblx0XHRcdDtcblx0XHRcdGZpbGVzYXZlci5yZWFkeVN0YXRlID0gZmlsZXNhdmVyLklOSVQ7XG5cdFx0XHRpZiAoIW5hbWUpIHtcblx0XHRcdFx0bmFtZSA9IFwiZG93bmxvYWRcIjtcblx0XHRcdH1cblx0XHRcdGlmIChjYW5fdXNlX3NhdmVfbGluaykge1xuXHRcdFx0XHRvYmplY3RfdXJsID0gZ2V0X29iamVjdF91cmwoYmxvYik7XG5cdFx0XHRcdHNhdmVfbGluay5ocmVmID0gb2JqZWN0X3VybDtcblx0XHRcdFx0c2F2ZV9saW5rLmRvd25sb2FkID0gbmFtZTtcblx0XHRcdFx0Y2xpY2soc2F2ZV9saW5rKTtcblx0XHRcdFx0ZmlsZXNhdmVyLnJlYWR5U3RhdGUgPSBmaWxlc2F2ZXIuRE9ORTtcblx0XHRcdFx0ZGlzcGF0Y2hfYWxsKCk7XG5cdFx0XHRcdHJldHVybjtcblx0XHRcdH1cblx0XHRcdC8vIE9iamVjdCBhbmQgd2ViIGZpbGVzeXN0ZW0gVVJMcyBoYXZlIGEgcHJvYmxlbSBzYXZpbmcgaW4gR29vZ2xlIENocm9tZSB3aGVuXG5cdFx0XHQvLyB2aWV3ZWQgaW4gYSB0YWIsIHNvIEkgZm9yY2Ugc2F2ZSB3aXRoIGFwcGxpY2F0aW9uL29jdGV0LXN0cmVhbVxuXHRcdFx0Ly8gaHR0cDovL2NvZGUuZ29vZ2xlLmNvbS9wL2Nocm9taXVtL2lzc3Vlcy9kZXRhaWw/aWQ9OTExNThcblx0XHRcdGlmICh2aWV3LmNocm9tZSAmJiB0eXBlICYmIHR5cGUgIT09IGZvcmNlX3NhdmVhYmxlX3R5cGUpIHtcblx0XHRcdFx0c2xpY2UgPSBibG9iLnNsaWNlIHx8IGJsb2Iud2Via2l0U2xpY2U7XG5cdFx0XHRcdGJsb2IgPSBzbGljZS5jYWxsKGJsb2IsIDAsIGJsb2Iuc2l6ZSwgZm9yY2Vfc2F2ZWFibGVfdHlwZSk7XG5cdFx0XHRcdGJsb2JfY2hhbmdlZCA9IHRydWU7XG5cdFx0XHR9XG5cdFx0XHQvLyBTaW5jZSBJIGNhbid0IGJlIHN1cmUgdGhhdCB0aGUgZ3Vlc3NlZCBtZWRpYSB0eXBlIHdpbGwgdHJpZ2dlciBhIGRvd25sb2FkXG5cdFx0XHQvLyBpbiBXZWJLaXQsIEkgYXBwZW5kIC5kb3dubG9hZCB0byB0aGUgZmlsZW5hbWUuXG5cdFx0XHQvLyBodHRwczovL2J1Z3Mud2Via2l0Lm9yZy9zaG93X2J1Zy5jZ2k/aWQ9NjU0NDBcblx0XHRcdGlmICh3ZWJraXRfcmVxX2ZzICYmIG5hbWUgIT09IFwiZG93bmxvYWRcIikge1xuXHRcdFx0XHRuYW1lICs9IFwiLmRvd25sb2FkXCI7XG5cdFx0XHR9XG5cdFx0XHRpZiAodHlwZSA9PT0gZm9yY2Vfc2F2ZWFibGVfdHlwZSB8fCB3ZWJraXRfcmVxX2ZzKSB7XG5cdFx0XHRcdHRhcmdldF92aWV3ID0gdmlldztcblx0XHRcdH1cblx0XHRcdGlmICghcmVxX2ZzKSB7XG5cdFx0XHRcdGZzX2Vycm9yKCk7XG5cdFx0XHRcdHJldHVybjtcblx0XHRcdH1cblx0XHRcdGZzX21pbl9zaXplICs9IGJsb2Iuc2l6ZTtcblx0XHRcdHJlcV9mcyh2aWV3LlRFTVBPUkFSWSwgZnNfbWluX3NpemUsIGFib3J0YWJsZShmdW5jdGlvbihmcykge1xuXHRcdFx0XHRmcy5yb290LmdldERpcmVjdG9yeShcInNhdmVkXCIsIGNyZWF0ZV9pZl9ub3RfZm91bmQsIGFib3J0YWJsZShmdW5jdGlvbihkaXIpIHtcblx0XHRcdFx0XHR2YXIgc2F2ZSA9IGZ1bmN0aW9uKCkge1xuXHRcdFx0XHRcdFx0ZGlyLmdldEZpbGUobmFtZSwgY3JlYXRlX2lmX25vdF9mb3VuZCwgYWJvcnRhYmxlKGZ1bmN0aW9uKGZpbGUpIHtcblx0XHRcdFx0XHRcdFx0ZmlsZS5jcmVhdGVXcml0ZXIoYWJvcnRhYmxlKGZ1bmN0aW9uKHdyaXRlcikge1xuXHRcdFx0XHRcdFx0XHRcdHdyaXRlci5vbndyaXRlZW5kID0gZnVuY3Rpb24oZXZlbnQpIHtcblx0XHRcdFx0XHRcdFx0XHRcdHRhcmdldF92aWV3LmxvY2F0aW9uLmhyZWYgPSBmaWxlLnRvVVJMKCk7XG5cdFx0XHRcdFx0XHRcdFx0XHRkZWxldGlvbl9xdWV1ZS5wdXNoKGZpbGUpO1xuXHRcdFx0XHRcdFx0XHRcdFx0ZmlsZXNhdmVyLnJlYWR5U3RhdGUgPSBmaWxlc2F2ZXIuRE9ORTtcblx0XHRcdFx0XHRcdFx0XHRcdGRpc3BhdGNoKGZpbGVzYXZlciwgXCJ3cml0ZWVuZFwiLCBldmVudCk7XG5cdFx0XHRcdFx0XHRcdFx0fTtcblx0XHRcdFx0XHRcdFx0XHR3cml0ZXIub25lcnJvciA9IGZ1bmN0aW9uKCkge1xuXHRcdFx0XHRcdFx0XHRcdFx0dmFyIGVycm9yID0gd3JpdGVyLmVycm9yO1xuXHRcdFx0XHRcdFx0XHRcdFx0aWYgKGVycm9yLmNvZGUgIT09IGVycm9yLkFCT1JUX0VSUikge1xuXHRcdFx0XHRcdFx0XHRcdFx0XHRmc19lcnJvcigpO1xuXHRcdFx0XHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0XHRcdH07XG5cdFx0XHRcdFx0XHRcdFx0XCJ3cml0ZXN0YXJ0IHByb2dyZXNzIHdyaXRlIGFib3J0XCIuc3BsaXQoXCIgXCIpLmZvckVhY2goZnVuY3Rpb24oZXZlbnQpIHtcblx0XHRcdFx0XHRcdFx0XHRcdHdyaXRlcltcIm9uXCIgKyBldmVudF0gPSBmaWxlc2F2ZXJbXCJvblwiICsgZXZlbnRdO1xuXHRcdFx0XHRcdFx0XHRcdH0pO1xuXHRcdFx0XHRcdFx0XHRcdHdyaXRlci53cml0ZShibG9iKTtcblx0XHRcdFx0XHRcdFx0XHRmaWxlc2F2ZXIuYWJvcnQgPSBmdW5jdGlvbigpIHtcblx0XHRcdFx0XHRcdFx0XHRcdHdyaXRlci5hYm9ydCgpO1xuXHRcdFx0XHRcdFx0XHRcdFx0ZmlsZXNhdmVyLnJlYWR5U3RhdGUgPSBmaWxlc2F2ZXIuRE9ORTtcblx0XHRcdFx0XHRcdFx0XHR9O1xuXHRcdFx0XHRcdFx0XHRcdGZpbGVzYXZlci5yZWFkeVN0YXRlID0gZmlsZXNhdmVyLldSSVRJTkc7XG5cdFx0XHRcdFx0XHRcdH0pLCBmc19lcnJvcik7XG5cdFx0XHRcdFx0XHR9KSwgZnNfZXJyb3IpO1xuXHRcdFx0XHRcdH07XG5cdFx0XHRcdFx0ZGlyLmdldEZpbGUobmFtZSwge2NyZWF0ZTogZmFsc2V9LCBhYm9ydGFibGUoZnVuY3Rpb24oZmlsZSkge1xuXHRcdFx0XHRcdFx0Ly8gZGVsZXRlIGZpbGUgaWYgaXQgYWxyZWFkeSBleGlzdHNcblx0XHRcdFx0XHRcdGZpbGUucmVtb3ZlKCk7XG5cdFx0XHRcdFx0XHRzYXZlKCk7XG5cdFx0XHRcdFx0fSksIGFib3J0YWJsZShmdW5jdGlvbihleCkge1xuXHRcdFx0XHRcdFx0aWYgKGV4LmNvZGUgPT09IGV4Lk5PVF9GT1VORF9FUlIpIHtcblx0XHRcdFx0XHRcdFx0c2F2ZSgpO1xuXHRcdFx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHRcdFx0ZnNfZXJyb3IoKTtcblx0XHRcdFx0XHRcdH1cblx0XHRcdFx0XHR9KSk7XG5cdFx0XHRcdH0pLCBmc19lcnJvcik7XG5cdFx0XHR9KSwgZnNfZXJyb3IpO1xuXHRcdH1cblx0XHQsIEZTX3Byb3RvID0gRmlsZVNhdmVyLnByb3RvdHlwZVxuXHRcdCwgc2F2ZUFzID0gZnVuY3Rpb24oYmxvYiwgbmFtZSkge1xuXHRcdFx0cmV0dXJuIG5ldyBGaWxlU2F2ZXIoYmxvYiwgbmFtZSk7XG5cdFx0fVxuXHQ7XG5cdEZTX3Byb3RvLmFib3J0ID0gZnVuY3Rpb24oKSB7XG5cdFx0dmFyIGZpbGVzYXZlciA9IHRoaXM7XG5cdFx0ZmlsZXNhdmVyLnJlYWR5U3RhdGUgPSBmaWxlc2F2ZXIuRE9ORTtcblx0XHRkaXNwYXRjaChmaWxlc2F2ZXIsIFwiYWJvcnRcIik7XG5cdH07XG5cdEZTX3Byb3RvLnJlYWR5U3RhdGUgPSBGU19wcm90by5JTklUID0gMDtcblx0RlNfcHJvdG8uV1JJVElORyA9IDE7XG5cdEZTX3Byb3RvLkRPTkUgPSAyO1xuXG5cdEZTX3Byb3RvLmVycm9yID1cblx0RlNfcHJvdG8ub253cml0ZXN0YXJ0ID1cblx0RlNfcHJvdG8ub25wcm9ncmVzcyA9XG5cdEZTX3Byb3RvLm9ud3JpdGUgPVxuXHRGU19wcm90by5vbmFib3J0ID1cblx0RlNfcHJvdG8ub25lcnJvciA9XG5cdEZTX3Byb3RvLm9ud3JpdGVlbmQgPVxuXHRcdG51bGw7XG5cblx0dmlldy5hZGRFdmVudExpc3RlbmVyKFwidW5sb2FkXCIsIHByb2Nlc3NfZGVsZXRpb25fcXVldWUsIGZhbHNlKTtcblx0c2F2ZUFzLnVubG9hZCA9IGZ1bmN0aW9uKCkge1xuXHRcdHByb2Nlc3NfZGVsZXRpb25fcXVldWUoKTtcblx0XHR2aWV3LnJlbW92ZUV2ZW50TGlzdGVuZXIoXCJ1bmxvYWRcIiwgcHJvY2Vzc19kZWxldGlvbl9xdWV1ZSwgZmFsc2UpO1xuXHR9O1xuXHRyZXR1cm4gc2F2ZUFzO1xufShcblx0ICAgdHlwZW9mIHNlbGYgIT09IFwidW5kZWZpbmVkXCIgJiYgc2VsZlxuXHR8fCB0eXBlb2Ygd2luZG93ICE9PSBcInVuZGVmaW5lZFwiICYmIHdpbmRvd1xuXHR8fCB0aGlzLmNvbnRlbnRcbikpO1xuLy8gYHNlbGZgIGlzIHVuZGVmaW5lZCBpbiBGaXJlZm94IGZvciBBbmRyb2lkIGNvbnRlbnQgc2NyaXB0IGNvbnRleHRcbi8vIHdoaWxlIGB0aGlzYCBpcyBuc0lDb250ZW50RnJhbWVNZXNzYWdlTWFuYWdlclxuLy8gd2l0aCBhbiBhdHRyaWJ1dGUgYGNvbnRlbnRgIHRoYXQgY29ycmVzcG9uZHMgdG8gdGhlIHdpbmRvd1xuXG5hbWREZWZpbmUgPSB3aW5kb3cuZGVmaW5lO1xuaWYoIHR5cGVvZiBhbWREZWZpbmUgPT09IFwidW5kZWZpbmVkXCIgJiYgKHR5cGVvZiB3aW5kb3cuYWxtb25kICE9PSBcInVuZGVmaW5lZFwiIFxuICAgICYmIFwiZGVmaW5lXCIgaW4gd2luZG93LmFsbW9uZCApKXtcbiAgYW1kRGVmaW5lID0gd2luZG93LmFsbW9uZC5kZWZpbmU7XG59XG5cbmlmICh0eXBlb2YgbW9kdWxlICE9PSBcInVuZGVmaW5lZFwiICYmIG1vZHVsZSAhPT0gbnVsbCkge1xuICBtb2R1bGUuZXhwb3J0cyA9IHNhdmVBcztcbn0gZWxzZSBpZiAoKHR5cGVvZiBhbWREZWZpbmUgIT09IFwidW5kZWZpbmVkXCIgJiYgYW1kRGVmaW5lICE9PSBudWxsKSAmJiAoYW1kRGVmaW5lLmFtZCAhPSBudWxsKSkge1xuICBhbWREZWZpbmUoXCJzYXZlQXNcIixbXSwgZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIHNhdmVBcztcbiAgfSk7XG59XG4iLCJtb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uIChjc3MsIGN1c3RvbURvY3VtZW50KSB7XG4gIHZhciBkb2MgPSBjdXN0b21Eb2N1bWVudCB8fCBkb2N1bWVudDtcbiAgaWYgKGRvYy5jcmVhdGVTdHlsZVNoZWV0KSB7XG4gICAgdmFyIHNoZWV0ID0gZG9jLmNyZWF0ZVN0eWxlU2hlZXQoKVxuICAgIHNoZWV0LmNzc1RleHQgPSBjc3M7XG4gICAgcmV0dXJuIHNoZWV0Lm93bmVyTm9kZTtcbiAgfSBlbHNlIHtcbiAgICB2YXIgaGVhZCA9IGRvYy5nZXRFbGVtZW50c0J5VGFnTmFtZSgnaGVhZCcpWzBdLFxuICAgICAgICBzdHlsZSA9IGRvYy5jcmVhdGVFbGVtZW50KCdzdHlsZScpO1xuXG4gICAgc3R5bGUudHlwZSA9ICd0ZXh0L2Nzcyc7XG5cbiAgICBpZiAoc3R5bGUuc3R5bGVTaGVldCkge1xuICAgICAgc3R5bGUuc3R5bGVTaGVldC5jc3NUZXh0ID0gY3NzO1xuICAgIH0gZWxzZSB7XG4gICAgICBzdHlsZS5hcHBlbmRDaGlsZChkb2MuY3JlYXRlVGV4dE5vZGUoY3NzKSk7XG4gICAgfVxuXG4gICAgaGVhZC5hcHBlbmRDaGlsZChzdHlsZSk7XG4gICAgcmV0dXJuIHN0eWxlO1xuICB9XG59O1xuXG5tb2R1bGUuZXhwb3J0cy5ieVVybCA9IGZ1bmN0aW9uKHVybCkge1xuICBpZiAoZG9jdW1lbnQuY3JlYXRlU3R5bGVTaGVldCkge1xuICAgIHJldHVybiBkb2N1bWVudC5jcmVhdGVTdHlsZVNoZWV0KHVybCkub3duZXJOb2RlO1xuICB9IGVsc2Uge1xuICAgIHZhciBoZWFkID0gZG9jdW1lbnQuZ2V0RWxlbWVudHNCeVRhZ05hbWUoJ2hlYWQnKVswXSxcbiAgICAgICAgbGluayA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2xpbmsnKTtcblxuICAgIGxpbmsucmVsID0gJ3N0eWxlc2hlZXQnO1xuICAgIGxpbmsuaHJlZiA9IHVybDtcblxuICAgIGhlYWQuYXBwZW5kQ2hpbGQobGluayk7XG4gICAgcmV0dXJuIGxpbms7XG4gIH1cbn07XG4iLCJ2YXIgVXRpbHMgPSB7fTtcblxuXG4vKlxuUmVtb3ZlIGFuIGVsZW1lbnQgYW5kIHByb3ZpZGUgYSBmdW5jdGlvbiB0aGF0IGluc2VydHMgaXQgaW50byBpdHMgb3JpZ2luYWwgcG9zaXRpb25cbmh0dHBzOi8vZGV2ZWxvcGVycy5nb29nbGUuY29tL3NwZWVkL2FydGljbGVzL2phdmFzY3JpcHQtZG9tXG5AcGFyYW0gZWxlbWVudCB7RWxlbWVudH0gVGhlIGVsZW1lbnQgdG8gYmUgdGVtcG9yYXJpbHkgcmVtb3ZlZFxuQHJldHVybiB7RnVuY3Rpb259IEEgZnVuY3Rpb24gdGhhdCBpbnNlcnRzIHRoZSBlbGVtZW50IGludG8gaXRzIG9yaWdpbmFsIHBvc2l0aW9uXG4gKi9cblxuVXRpbHMucmVtb3ZlVG9JbnNlcnRMYXRlciA9IGZ1bmN0aW9uKGVsZW1lbnQpIHtcbiAgdmFyIG5leHRTaWJsaW5nLCBwYXJlbnROb2RlO1xuICBwYXJlbnROb2RlID0gZWxlbWVudC5wYXJlbnROb2RlO1xuICBuZXh0U2libGluZyA9IGVsZW1lbnQubmV4dFNpYmxpbmc7XG4gIHBhcmVudE5vZGUucmVtb3ZlQ2hpbGQoZWxlbWVudCk7XG4gIHJldHVybiBmdW5jdGlvbigpIHtcbiAgICBpZiAobmV4dFNpYmxpbmcpIHtcbiAgICAgIHBhcmVudE5vZGUuaW5zZXJ0QmVmb3JlKGVsZW1lbnQsIG5leHRTaWJsaW5nKTtcbiAgICB9IGVsc2Uge1xuICAgICAgcGFyZW50Tm9kZS5hcHBlbmRDaGlsZChlbGVtZW50KTtcbiAgICB9XG4gIH07XG59O1xuXG5cbi8qXG5mYXN0ZXN0IHBvc3NpYmxlIHdheSB0byBkZXN0cm95IGFsbCBzdWIgbm9kZXMgKGFrYSBjaGlsZHMpXG5odHRwOi8vanNwZXJmLmNvbS9pbm5lcmh0bWwtdnMtcmVtb3ZlY2hpbGQvMTVcbkBwYXJhbSBlbGVtZW50IHtFbGVtZW50fSBUaGUgZWxlbWVudCBmb3Igd2hpY2ggYWxsIGNoaWxkcyBzaG91bGQgYmUgcmVtb3ZlZFxuICovXG5cblV0aWxzLnJlbW92ZUFsbENoaWxkcyA9IGZ1bmN0aW9uKGVsZW1lbnQpIHtcbiAgdmFyIGNvdW50O1xuICBjb3VudCA9IDA7XG4gIHdoaWxlIChlbGVtZW50LmZpcnN0Q2hpbGQpIHtcbiAgICBjb3VudCsrO1xuICAgIGVsZW1lbnQucmVtb3ZlQ2hpbGQoZWxlbWVudC5maXJzdENoaWxkKTtcbiAgfVxufTtcblxubW9kdWxlLmV4cG9ydHMgPSBVdGlscztcbiIsIi8qIVxuICogakJvbmUgdjEuMC4xOSAtIDIwMTQtMTAtMTIgLSBMaWJyYXJ5IGZvciBET00gbWFuaXB1bGF0aW9uXG4gKlxuICogaHR0cHM6Ly9naXRodWIuY29tL2t1cHJpeWFuZW5rby9qYm9uZVxuICpcbiAqIENvcHlyaWdodCAyMDE0IEFsZXhleSBLdXByaXlhbmVua29cbiAqIFJlbGVhc2VkIHVuZGVyIHRoZSBNSVQgbGljZW5zZS5cbiAqL1xuXG4oZnVuY3Rpb24gKHdpbikge1xuXG52YXJcbi8vIGNhY2hlIHByZXZpb3VzIHZlcnNpb25zXG5fJCA9IHdpbi4kLFxuX2pCb25lID0gd2luLmpCb25lLFxuXG4vLyBRdWljayBtYXRjaCBhIHN0YW5kYWxvbmUgdGFnXG5ycXVpY2tTaW5nbGVUYWcgPSAvXjwoXFx3KylcXHMqXFwvPz4kLyxcblxuLy8gQSBzaW1wbGUgd2F5IHRvIGNoZWNrIGZvciBIVE1MIHN0cmluZ3Ncbi8vIFByaW9yaXRpemUgI2lkIG92ZXIgPHRhZz4gdG8gYXZvaWQgWFNTIHZpYSBsb2NhdGlvbi5oYXNoXG5ycXVpY2tFeHByID0gL14oPzpbXiM8XSooPFtcXHdcXFddKz4pW14+XSokfCMoW1xcd1xcLV0qKSQpLyxcblxuLy8gQWxpYXMgZm9yIGZ1bmN0aW9uXG5zbGljZSA9IFtdLnNsaWNlLFxuc3BsaWNlID0gW10uc3BsaWNlLFxua2V5cyA9IE9iamVjdC5rZXlzLFxuXG4vLyBBbGlhcyBmb3IgZ2xvYmFsIHZhcmlhYmxlc1xuZG9jID0gZG9jdW1lbnQsXG5cbmlzU3RyaW5nID0gZnVuY3Rpb24oZWwpIHtcbiAgICByZXR1cm4gdHlwZW9mIGVsID09PSBcInN0cmluZ1wiO1xufSxcbmlzT2JqZWN0ID0gZnVuY3Rpb24oZWwpIHtcbiAgICByZXR1cm4gZWwgaW5zdGFuY2VvZiBPYmplY3Q7XG59LFxuaXNGdW5jdGlvbiA9IGZ1bmN0aW9uKGVsKSB7XG4gICAgdmFyIGdldFR5cGUgPSB7fTtcbiAgICByZXR1cm4gZWwgJiYgZ2V0VHlwZS50b1N0cmluZy5jYWxsKGVsKSA9PT0gXCJbb2JqZWN0IEZ1bmN0aW9uXVwiO1xufSxcbmlzQXJyYXkgPSBmdW5jdGlvbihlbCkge1xuICAgIHJldHVybiBBcnJheS5pc0FycmF5KGVsKTtcbn0sXG5qQm9uZSA9IGZ1bmN0aW9uKGVsZW1lbnQsIGRhdGEpIHtcbiAgICByZXR1cm4gbmV3IGZuLmluaXQoZWxlbWVudCwgZGF0YSk7XG59LFxuZm47XG5cbi8vIHNldCBwcmV2aW91cyB2YWx1ZXMgYW5kIHJldHVybiB0aGUgaW5zdGFuY2UgdXBvbiBjYWxsaW5nIHRoZSBuby1jb25mbGljdCBtb2RlXG5qQm9uZS5ub0NvbmZsaWN0ID0gZnVuY3Rpb24oKSB7XG4gICAgd2luLiQgPSBfJDtcbiAgICB3aW4uakJvbmUgPSBfakJvbmU7XG5cbiAgICByZXR1cm4gakJvbmU7XG59O1xuXG5mbiA9IGpCb25lLmZuID0gakJvbmUucHJvdG90eXBlID0ge1xuICAgIGluaXQ6IGZ1bmN0aW9uKGVsZW1lbnQsIGRhdGEpIHtcbiAgICAgICAgdmFyIGVsZW1lbnRzLCB0YWcsIHdyYXBlciwgZnJhZ21lbnQ7XG5cbiAgICAgICAgaWYgKCFlbGVtZW50KSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuICAgICAgICBpZiAoaXNTdHJpbmcoZWxlbWVudCkpIHtcbiAgICAgICAgICAgIC8vIENyZWF0ZSBzaW5nbGUgRE9NIGVsZW1lbnRcbiAgICAgICAgICAgIGlmICh0YWcgPSBycXVpY2tTaW5nbGVUYWcuZXhlYyhlbGVtZW50KSkge1xuICAgICAgICAgICAgICAgIHRoaXNbMF0gPSBkb2MuY3JlYXRlRWxlbWVudCh0YWdbMV0pO1xuICAgICAgICAgICAgICAgIHRoaXMubGVuZ3RoID0gMTtcblxuICAgICAgICAgICAgICAgIGlmIChpc09iamVjdChkYXRhKSkge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLmF0dHIoZGF0YSk7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBDcmVhdGUgRE9NIGNvbGxlY3Rpb25cbiAgICAgICAgICAgIGlmICgodGFnID0gcnF1aWNrRXhwci5leGVjKGVsZW1lbnQpKSAmJiB0YWdbMV0pIHtcbiAgICAgICAgICAgICAgICBmcmFnbWVudCA9IGRvYy5jcmVhdGVEb2N1bWVudEZyYWdtZW50KCk7XG4gICAgICAgICAgICAgICAgd3JhcGVyID0gZG9jLmNyZWF0ZUVsZW1lbnQoXCJkaXZcIik7XG4gICAgICAgICAgICAgICAgd3JhcGVyLmlubmVySFRNTCA9IGVsZW1lbnQ7XG4gICAgICAgICAgICAgICAgd2hpbGUgKHdyYXBlci5sYXN0Q2hpbGQpIHtcbiAgICAgICAgICAgICAgICAgICAgZnJhZ21lbnQuYXBwZW5kQ2hpbGQod3JhcGVyLmZpcnN0Q2hpbGQpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbGVtZW50cyA9IHNsaWNlLmNhbGwoZnJhZ21lbnQuY2hpbGROb2Rlcyk7XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gakJvbmUubWVyZ2UodGhpcywgZWxlbWVudHMpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gRmluZCBET00gZWxlbWVudHMgd2l0aCBxdWVyeVNlbGVjdG9yQWxsXG4gICAgICAgICAgICBpZiAoakJvbmUuaXNFbGVtZW50KGRhdGEpKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGpCb25lKGRhdGEpLmZpbmQoZWxlbWVudCk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgZWxlbWVudHMgPSBkb2MucXVlcnlTZWxlY3RvckFsbChlbGVtZW50KTtcblxuICAgICAgICAgICAgICAgIHJldHVybiBqQm9uZS5tZXJnZSh0aGlzLCBlbGVtZW50cyk7XG4gICAgICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgLy8gV3JhcCBET01FbGVtZW50XG4gICAgICAgIGlmIChlbGVtZW50Lm5vZGVUeXBlKSB7XG4gICAgICAgICAgICB0aGlzWzBdID0gZWxlbWVudDtcbiAgICAgICAgICAgIHRoaXMubGVuZ3RoID0gMTtcblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cbiAgICAgICAgLy8gUnVuIGZ1bmN0aW9uXG4gICAgICAgIGlmIChpc0Z1bmN0aW9uKGVsZW1lbnQpKSB7XG4gICAgICAgICAgICByZXR1cm4gZWxlbWVudCgpO1xuICAgICAgICB9XG4gICAgICAgIC8vIFJldHVybiBqQm9uZSBlbGVtZW50IGFzIGlzXG4gICAgICAgIGlmIChlbGVtZW50IGluc3RhbmNlb2YgakJvbmUpIHtcbiAgICAgICAgICAgIHJldHVybiBlbGVtZW50O1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gUmV0dXJuIGVsZW1lbnQgd3JhcHBlZCBieSBqQm9uZVxuICAgICAgICByZXR1cm4gakJvbmUubWFrZUFycmF5KGVsZW1lbnQsIHRoaXMpO1xuICAgIH0sXG5cbiAgICBwb3A6IFtdLnBvcCxcbiAgICBwdXNoOiBbXS5wdXNoLFxuICAgIHJldmVyc2U6IFtdLnJldmVyc2UsXG4gICAgc2hpZnQ6IFtdLnNoaWZ0LFxuICAgIHNvcnQ6IFtdLnNvcnQsXG4gICAgc3BsaWNlOiBbXS5zcGxpY2UsXG4gICAgc2xpY2U6IFtdLnNsaWNlLFxuICAgIGluZGV4T2Y6IFtdLmluZGV4T2YsXG4gICAgZm9yRWFjaDogW10uZm9yRWFjaCxcbiAgICB1bnNoaWZ0OiBbXS51bnNoaWZ0LFxuICAgIGNvbmNhdDogW10uY29uY2F0LFxuICAgIGpvaW46IFtdLmpvaW4sXG4gICAgZXZlcnk6IFtdLmV2ZXJ5LFxuICAgIHNvbWU6IFtdLnNvbWUsXG4gICAgZmlsdGVyOiBbXS5maWx0ZXIsXG4gICAgbWFwOiBbXS5tYXAsXG4gICAgcmVkdWNlOiBbXS5yZWR1Y2UsXG4gICAgcmVkdWNlUmlnaHQ6IFtdLnJlZHVjZVJpZ2h0LFxuICAgIGxlbmd0aDogMFxufTtcblxuZm4uY29uc3RydWN0b3IgPSBqQm9uZTtcblxuZm4uaW5pdC5wcm90b3R5cGUgPSBmbjtcblxuakJvbmUuc2V0SWQgPSBmdW5jdGlvbihlbCkge1xuICAgIHZhciBqaWQgPSBlbC5qaWQ7XG5cbiAgICBpZiAoZWwgPT09IHdpbikge1xuICAgICAgICBqaWQgPSBcIndpbmRvd1wiO1xuICAgIH0gZWxzZSBpZiAoZWwuamlkID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgZWwuamlkID0gamlkID0gKytqQm9uZS5fY2FjaGUuamlkO1xuICAgIH1cblxuICAgIGlmICghakJvbmUuX2NhY2hlLmV2ZW50c1tqaWRdKSB7XG4gICAgICAgIGpCb25lLl9jYWNoZS5ldmVudHNbamlkXSA9IHt9O1xuICAgIH1cbn07XG5cbmpCb25lLmdldERhdGEgPSBmdW5jdGlvbihlbCkge1xuICAgIGVsID0gZWwgaW5zdGFuY2VvZiBqQm9uZSA/IGVsWzBdIDogZWw7XG5cbiAgICB2YXIgamlkID0gZWwgPT09IHdpbiA/IFwid2luZG93XCIgOiBlbC5qaWQ7XG5cbiAgICByZXR1cm4ge1xuICAgICAgICBqaWQ6IGppZCxcbiAgICAgICAgZXZlbnRzOiBqQm9uZS5fY2FjaGUuZXZlbnRzW2ppZF1cbiAgICB9O1xufTtcblxuakJvbmUuaXNFbGVtZW50ID0gZnVuY3Rpb24oZWwpIHtcbiAgICByZXR1cm4gZWwgJiYgZWwgaW5zdGFuY2VvZiBqQm9uZSB8fCBlbCBpbnN0YW5jZW9mIEhUTUxFbGVtZW50IHx8IGlzU3RyaW5nKGVsKTtcbn07XG5cbmpCb25lLl9jYWNoZSA9IHtcbiAgICBldmVudHM6IHt9LFxuICAgIGppZDogMFxufTtcblxuZnVuY3Rpb24gaXNBcnJheWxpa2Uob2JqKSB7XG4gICAgdmFyIGxlbmd0aCA9IG9iai5sZW5ndGgsXG4gICAgICAgIHR5cGUgPSB0eXBlb2Ygb2JqO1xuXG4gICAgaWYgKGlzRnVuY3Rpb24odHlwZSkgfHwgb2JqID09PSB3aW4pIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cblxuICAgIGlmIChvYmoubm9kZVR5cGUgPT09IDEgJiYgbGVuZ3RoKSB7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cblxuICAgIHJldHVybiBpc0FycmF5KHR5cGUpIHx8IGxlbmd0aCA9PT0gMCB8fFxuICAgICAgICB0eXBlb2YgbGVuZ3RoID09PSBcIm51bWJlclwiICYmIGxlbmd0aCA+IDAgJiYgKGxlbmd0aCAtIDEpIGluIG9iajtcbn1cblxuakJvbmUubWVyZ2UgPSBmdW5jdGlvbihmaXJzdCwgc2Vjb25kKSB7XG4gICAgdmFyIGwgPSBzZWNvbmQubGVuZ3RoLFxuICAgICAgICBpID0gZmlyc3QubGVuZ3RoLFxuICAgICAgICBqID0gMDtcblxuICAgIHdoaWxlIChqIDwgbCkge1xuICAgICAgICBmaXJzdFtpKytdID0gc2Vjb25kW2orK107XG4gICAgfVxuXG4gICAgZmlyc3QubGVuZ3RoID0gaTtcblxuICAgIHJldHVybiBmaXJzdDtcbn07XG5cbmpCb25lLmNvbnRhaW5zID0gZnVuY3Rpb24oY29udGFpbmVyLCBjb250YWluZWQpIHtcbiAgICB2YXIgcmVzdWx0O1xuXG4gICAgY29udGFpbmVyLnJldmVyc2UoKS5zb21lKGZ1bmN0aW9uKGVsKSB7XG4gICAgICAgIGlmIChlbC5jb250YWlucyhjb250YWluZWQpKSB7XG4gICAgICAgICAgICByZXR1cm4gcmVzdWx0ID0gZWw7XG4gICAgICAgIH1cbiAgICB9KTtcblxuICAgIHJldHVybiByZXN1bHQ7XG59O1xuXG5qQm9uZS5leHRlbmQgPSBmdW5jdGlvbih0YXJnZXQpIHtcbiAgICB2YXIgaywga2wsIGksIHRnO1xuXG4gICAgc3BsaWNlLmNhbGwoYXJndW1lbnRzLCAxKS5mb3JFYWNoKGZ1bmN0aW9uKG9iamVjdCkge1xuICAgICAgICBpZiAoIW9iamVjdCkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgayA9IGtleXMob2JqZWN0KTtcbiAgICAgICAga2wgPSBrLmxlbmd0aDtcbiAgICAgICAgaSA9IDA7XG4gICAgICAgIHRnID0gdGFyZ2V0OyAvL2NhY2hpbmcgdGFyZ2V0IGZvciBwZXJmIGltcHJvdmVtZW50XG5cbiAgICAgICAgZm9yICg7IGkgPCBrbDsgaSsrKSB7XG4gICAgICAgICAgICB0Z1trW2ldXSA9IG9iamVjdFtrW2ldXTtcbiAgICAgICAgfVxuICAgIH0pO1xuXG4gICAgcmV0dXJuIHRhcmdldDtcbn07XG5cbmpCb25lLm1ha2VBcnJheSA9IGZ1bmN0aW9uKGFyciwgcmVzdWx0cykge1xuICAgIHZhciByZXQgPSByZXN1bHRzIHx8IFtdO1xuXG4gICAgaWYgKGFyciAhPT0gbnVsbCkge1xuICAgICAgICBpZiAoaXNBcnJheWxpa2UoYXJyKSkge1xuICAgICAgICAgICAgakJvbmUubWVyZ2UocmV0LCBpc1N0cmluZyhhcnIpID8gW2Fycl0gOiBhcnIpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgcmV0LnB1c2goYXJyKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiByZXQ7XG59O1xuXG5mdW5jdGlvbiBCb25lRXZlbnQoZSwgZGF0YSkge1xuICAgIHZhciBrZXksIHNldHRlcjtcblxuICAgIHRoaXMub3JpZ2luYWxFdmVudCA9IGU7XG5cbiAgICBzZXR0ZXIgPSBmdW5jdGlvbihrZXksIGUpIHtcbiAgICAgICAgaWYgKGtleSA9PT0gXCJwcmV2ZW50RGVmYXVsdFwiKSB7XG4gICAgICAgICAgICB0aGlzW2tleV0gPSBmdW5jdGlvbigpIHtcbiAgICAgICAgICAgICAgICB0aGlzLmRlZmF1bHRQcmV2ZW50ZWQgPSB0cnVlO1xuICAgICAgICAgICAgICAgIHJldHVybiBlW2tleV0oKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0gZWxzZSBpZiAoaXNGdW5jdGlvbihlW2tleV0pKSB7XG4gICAgICAgICAgICB0aGlzW2tleV0gPSBmdW5jdGlvbigpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gZVtrZXldKCk7XG4gICAgICAgICAgICB9O1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgdGhpc1trZXldID0gZVtrZXldO1xuICAgICAgICB9XG4gICAgfTtcblxuICAgIGZvciAoa2V5IGluIGUpIHtcbiAgICAgICAgaWYgKGVba2V5XSB8fCB0eXBlb2YgZVtrZXldID09PSBcImZ1bmN0aW9uXCIpIHtcbiAgICAgICAgICAgIHNldHRlci5jYWxsKHRoaXMsIGtleSwgZSk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBqQm9uZS5leHRlbmQodGhpcywgZGF0YSk7XG59XG5cbmpCb25lLkV2ZW50ID0gZnVuY3Rpb24oZXZlbnQsIGRhdGEpIHtcbiAgICB2YXIgbmFtZXNwYWNlLCBldmVudFR5cGU7XG5cbiAgICBpZiAoZXZlbnQudHlwZSAmJiAhZGF0YSkge1xuICAgICAgICBkYXRhID0gZXZlbnQ7XG4gICAgICAgIGV2ZW50ID0gZXZlbnQudHlwZTtcbiAgICB9XG5cbiAgICBuYW1lc3BhY2UgPSBldmVudC5zcGxpdChcIi5cIikuc3BsaWNlKDEpLmpvaW4oXCIuXCIpO1xuICAgIGV2ZW50VHlwZSA9IGV2ZW50LnNwbGl0KFwiLlwiKVswXTtcblxuICAgIGV2ZW50ID0gZG9jLmNyZWF0ZUV2ZW50KFwiRXZlbnRcIik7XG4gICAgZXZlbnQuaW5pdEV2ZW50KGV2ZW50VHlwZSwgdHJ1ZSwgdHJ1ZSk7XG5cbiAgICByZXR1cm4gakJvbmUuZXh0ZW5kKGV2ZW50LCB7XG4gICAgICAgIG5hbWVzcGFjZTogbmFtZXNwYWNlLFxuICAgICAgICBpc0RlZmF1bHRQcmV2ZW50ZWQ6IGZ1bmN0aW9uKCkge1xuICAgICAgICAgICAgcmV0dXJuIGV2ZW50LmRlZmF1bHRQcmV2ZW50ZWQ7XG4gICAgICAgIH1cbiAgICB9LCBkYXRhKTtcbn07XG5cbmZuLm9uID0gZnVuY3Rpb24oZXZlbnQpIHtcbiAgICB2YXIgYXJncyA9IGFyZ3VtZW50cyxcbiAgICAgICAgbGVuZ3RoID0gdGhpcy5sZW5ndGgsXG4gICAgICAgIGkgPSAwLFxuICAgICAgICBjYWxsYmFjaywgdGFyZ2V0LCBuYW1lc3BhY2UsIGZuLCBldmVudHMsIGV2ZW50VHlwZSwgZXhwZWN0ZWRUYXJnZXQsIGFkZExpc3RlbmVyO1xuXG4gICAgaWYgKGFyZ3MubGVuZ3RoID09PSAyKSB7XG4gICAgICAgIGNhbGxiYWNrID0gYXJnc1sxXTtcbiAgICB9IGVsc2Uge1xuICAgICAgICB0YXJnZXQgPSBhcmdzWzFdO1xuICAgICAgICBjYWxsYmFjayA9IGFyZ3NbMl07XG4gICAgfVxuXG4gICAgYWRkTGlzdGVuZXIgPSBmdW5jdGlvbihlbCkge1xuICAgICAgICBqQm9uZS5zZXRJZChlbCk7XG4gICAgICAgIGV2ZW50cyA9IGpCb25lLmdldERhdGEoZWwpLmV2ZW50cztcbiAgICAgICAgZXZlbnQuc3BsaXQoXCIgXCIpLmZvckVhY2goZnVuY3Rpb24oZXZlbnQpIHtcbiAgICAgICAgICAgIGV2ZW50VHlwZSA9IGV2ZW50LnNwbGl0KFwiLlwiKVswXTtcbiAgICAgICAgICAgIG5hbWVzcGFjZSA9IGV2ZW50LnNwbGl0KFwiLlwiKS5zcGxpY2UoMSkuam9pbihcIi5cIik7XG4gICAgICAgICAgICBldmVudHNbZXZlbnRUeXBlXSA9IGV2ZW50c1tldmVudFR5cGVdIHx8IFtdO1xuXG4gICAgICAgICAgICBmbiA9IGZ1bmN0aW9uKGUpIHtcbiAgICAgICAgICAgICAgICBpZiAoZS5uYW1lc3BhY2UgJiYgZS5uYW1lc3BhY2UgIT09IG5hbWVzcGFjZSkge1xuICAgICAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgZXhwZWN0ZWRUYXJnZXQgPSBudWxsO1xuICAgICAgICAgICAgICAgIGlmICghdGFyZ2V0KSB7XG4gICAgICAgICAgICAgICAgICAgIGNhbGxiYWNrLmNhbGwoZWwsIGUpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSBpZiAofmpCb25lKGVsKS5maW5kKHRhcmdldCkuaW5kZXhPZihlLnRhcmdldCkgfHwgKGV4cGVjdGVkVGFyZ2V0ID0gakJvbmUuY29udGFpbnMoakJvbmUoZWwpLmZpbmQodGFyZ2V0KSwgZS50YXJnZXQpKSkge1xuICAgICAgICAgICAgICAgICAgICBleHBlY3RlZFRhcmdldCA9IGV4cGVjdGVkVGFyZ2V0IHx8IGUudGFyZ2V0O1xuICAgICAgICAgICAgICAgICAgICBlID0gbmV3IEJvbmVFdmVudChlLCB7XG4gICAgICAgICAgICAgICAgICAgICAgICBjdXJyZW50VGFyZ2V0OiBleHBlY3RlZFRhcmdldFxuICAgICAgICAgICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgICAgICAgICBjYWxsYmFjay5jYWxsKGV4cGVjdGVkVGFyZ2V0LCBlKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9O1xuXG4gICAgICAgICAgICBldmVudHNbZXZlbnRUeXBlXS5wdXNoKHtcbiAgICAgICAgICAgICAgICBuYW1lc3BhY2U6IG5hbWVzcGFjZSxcbiAgICAgICAgICAgICAgICBmbjogZm4sXG4gICAgICAgICAgICAgICAgb3JpZ2luZm46IGNhbGxiYWNrXG4gICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgZWwuYWRkRXZlbnRMaXN0ZW5lciAmJiBlbC5hZGRFdmVudExpc3RlbmVyKGV2ZW50VHlwZSwgZm4sIGZhbHNlKTtcbiAgICAgICAgfSk7XG4gICAgfTtcblxuICAgIGZvciAoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgYWRkTGlzdGVuZXIodGhpc1tpXSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXM7XG59O1xuXG5mbi5vbmUgPSBmdW5jdGlvbihldmVudCkge1xuICAgIHZhciBhcmdzID0gYXJndW1lbnRzLFxuICAgICAgICBpID0gMCxcbiAgICAgICAgbGVuZ3RoID0gdGhpcy5sZW5ndGgsXG4gICAgICAgIGNhbGxiYWNrLCB0YXJnZXQsIGFkZExpc3RlbmVyO1xuXG4gICAgaWYgKGFyZ3MubGVuZ3RoID09PSAyKSB7XG4gICAgICAgIGNhbGxiYWNrID0gYXJnc1sxXTtcbiAgICB9IGVsc2Uge1xuICAgICAgICB0YXJnZXQgPSBhcmdzWzFdLCBjYWxsYmFjayA9IGFyZ3NbMl07XG4gICAgfVxuXG4gICAgYWRkTGlzdGVuZXIgPSBmdW5jdGlvbihlbCkge1xuICAgICAgICBldmVudC5zcGxpdChcIiBcIikuZm9yRWFjaChmdW5jdGlvbihldmVudCkge1xuICAgICAgICAgICAgdmFyIGZuID0gZnVuY3Rpb24oZSkge1xuICAgICAgICAgICAgICAgIGpCb25lKGVsKS5vZmYoZXZlbnQsIGZuKTtcbiAgICAgICAgICAgICAgICBjYWxsYmFjay5jYWxsKGVsLCBlKTtcbiAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgIGlmICghdGFyZ2V0KSB7XG4gICAgICAgICAgICAgICAgakJvbmUoZWwpLm9uKGV2ZW50LCBmbik7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIGpCb25lKGVsKS5vbihldmVudCwgdGFyZ2V0LCBmbik7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH07XG5cbiAgICBmb3IgKDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGFkZExpc3RlbmVyKHRoaXNbaV0pO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzO1xufTtcblxuZm4udHJpZ2dlciA9IGZ1bmN0aW9uKGV2ZW50KSB7XG4gICAgdmFyIGV2ZW50cyA9IFtdLFxuICAgICAgICBpID0gMCxcbiAgICAgICAgbGVuZ3RoID0gdGhpcy5sZW5ndGgsXG4gICAgICAgIGRpc3BhdGNoRXZlbnRzO1xuXG4gICAgaWYgKCFldmVudCkge1xuICAgICAgICByZXR1cm4gdGhpcztcbiAgICB9XG5cbiAgICBpZiAoaXNTdHJpbmcoZXZlbnQpKSB7XG4gICAgICAgIGV2ZW50cyA9IGV2ZW50LnNwbGl0KFwiIFwiKS5tYXAoZnVuY3Rpb24oZXZlbnQpIHtcbiAgICAgICAgICAgIHJldHVybiBqQm9uZS5FdmVudChldmVudCk7XG4gICAgICAgIH0pO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIGV2ZW50ID0gZXZlbnQgaW5zdGFuY2VvZiBFdmVudCA/IGV2ZW50IDogakJvbmUuRXZlbnQoZXZlbnQpO1xuICAgICAgICBldmVudHMgPSBbZXZlbnRdO1xuICAgIH1cblxuICAgIGRpc3BhdGNoRXZlbnRzID0gZnVuY3Rpb24oZWwpIHtcbiAgICAgICAgZXZlbnRzLmZvckVhY2goZnVuY3Rpb24oZXZlbnQpIHtcbiAgICAgICAgICAgIGlmICghZXZlbnQudHlwZSkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgZWwuZGlzcGF0Y2hFdmVudCAmJiBlbC5kaXNwYXRjaEV2ZW50KGV2ZW50KTtcbiAgICAgICAgfSk7XG4gICAgfTtcblxuICAgIGZvciAoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgZGlzcGF0Y2hFdmVudHModGhpc1tpXSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXM7XG59O1xuXG5mbi5vZmYgPSBmdW5jdGlvbihldmVudCwgZm4pIHtcbiAgICB2YXIgaSA9IDAsXG4gICAgICAgIGxlbmd0aCA9IHRoaXMubGVuZ3RoLFxuICAgICAgICByZW1vdmVMaXN0ZW5lciA9IGZ1bmN0aW9uKGV2ZW50cywgZXZlbnRUeXBlLCBpbmRleCwgZWwsIGUpIHtcbiAgICAgICAgICAgIHZhciBjYWxsYmFjaztcblxuICAgICAgICAgICAgLy8gZ2V0IGNhbGxiYWNrXG4gICAgICAgICAgICBpZiAoKGZuICYmIGUub3JpZ2luZm4gPT09IGZuKSB8fCAhZm4pIHtcbiAgICAgICAgICAgICAgICBjYWxsYmFjayA9IGUuZm47XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChldmVudHNbZXZlbnRUeXBlXVtpbmRleF0uZm4gPT09IGNhbGxiYWNrKSB7XG4gICAgICAgICAgICAgICAgZWwucmVtb3ZlRXZlbnRMaXN0ZW5lcihldmVudFR5cGUsIGNhbGxiYWNrKTtcblxuICAgICAgICAgICAgICAgIC8vIHJlbW92ZSBoYW5kbGVyIGZyb20gY2FjaGVcbiAgICAgICAgICAgICAgICBqQm9uZS5fY2FjaGUuZXZlbnRzW2pCb25lLmdldERhdGEoZWwpLmppZF1bZXZlbnRUeXBlXS5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICBldmVudHMsIG5hbWVzcGFjZSwgcmVtb3ZlTGlzdGVuZXJzLCBldmVudFR5cGU7XG5cbiAgICByZW1vdmVMaXN0ZW5lcnMgPSBmdW5jdGlvbihlbCkge1xuICAgICAgICB2YXIgbCwgZXZlbnRzQnlUeXBlLCBlO1xuXG4gICAgICAgIGV2ZW50cyA9IGpCb25lLmdldERhdGEoZWwpLmV2ZW50cztcblxuICAgICAgICBpZiAoIWV2ZW50cykge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gcmVtb3ZlIGFsbCBldmVudHNcbiAgICAgICAgaWYgKCFldmVudCAmJiBldmVudHMpIHtcbiAgICAgICAgICAgIHJldHVybiBrZXlzKGV2ZW50cykuZm9yRWFjaChmdW5jdGlvbihldmVudFR5cGUpIHtcbiAgICAgICAgICAgICAgICBldmVudHNCeVR5cGUgPSBldmVudHNbZXZlbnRUeXBlXTtcbiAgICAgICAgICAgICAgICBsID0gZXZlbnRzQnlUeXBlLmxlbmd0aDtcblxuICAgICAgICAgICAgICAgIHdoaWxlKGwtLSkge1xuICAgICAgICAgICAgICAgICAgICByZW1vdmVMaXN0ZW5lcihldmVudHMsIGV2ZW50VHlwZSwgbCwgZWwsIGV2ZW50c0J5VHlwZVtsXSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cblxuICAgICAgICBldmVudC5zcGxpdChcIiBcIikuZm9yRWFjaChmdW5jdGlvbihldmVudCkge1xuICAgICAgICAgICAgZXZlbnRUeXBlID0gZXZlbnQuc3BsaXQoXCIuXCIpWzBdO1xuICAgICAgICAgICAgbmFtZXNwYWNlID0gZXZlbnQuc3BsaXQoXCIuXCIpLnNwbGljZSgxKS5qb2luKFwiLlwiKTtcblxuICAgICAgICAgICAgLy8gcmVtb3ZlIG5hbWVkIGV2ZW50c1xuICAgICAgICAgICAgaWYgKGV2ZW50c1tldmVudFR5cGVdKSB7XG4gICAgICAgICAgICAgICAgZXZlbnRzQnlUeXBlID0gZXZlbnRzW2V2ZW50VHlwZV07XG4gICAgICAgICAgICAgICAgbCA9IGV2ZW50c0J5VHlwZS5sZW5ndGg7XG5cbiAgICAgICAgICAgICAgICB3aGlsZShsLS0pIHtcbiAgICAgICAgICAgICAgICAgICAgZSA9IGV2ZW50c0J5VHlwZVtsXTtcbiAgICAgICAgICAgICAgICAgICAgaWYgKCFuYW1lc3BhY2UgfHwgKG5hbWVzcGFjZSAmJiBlLm5hbWVzcGFjZSA9PT0gbmFtZXNwYWNlKSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgcmVtb3ZlTGlzdGVuZXIoZXZlbnRzLCBldmVudFR5cGUsIGwsIGVsLCBlKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIHJlbW92ZSBhbGwgbmFtZXNwYWNlZCBldmVudHNcbiAgICAgICAgICAgIGVsc2UgaWYgKG5hbWVzcGFjZSkge1xuICAgICAgICAgICAgICAgIGtleXMoZXZlbnRzKS5mb3JFYWNoKGZ1bmN0aW9uKGV2ZW50VHlwZSkge1xuICAgICAgICAgICAgICAgICAgICBldmVudHNCeVR5cGUgPSBldmVudHNbZXZlbnRUeXBlXTtcbiAgICAgICAgICAgICAgICAgICAgbCA9IGV2ZW50c0J5VHlwZS5sZW5ndGg7XG5cbiAgICAgICAgICAgICAgICAgICAgd2hpbGUobC0tKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBlID0gZXZlbnRzQnlUeXBlW2xdO1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGUubmFtZXNwYWNlLnNwbGl0KFwiLlwiKVswXSA9PT0gbmFtZXNwYWNlLnNwbGl0KFwiLlwiKVswXSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlbW92ZUxpc3RlbmVyKGV2ZW50cywgZXZlbnRUeXBlLCBsLCBlbCwgZSk7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgfTtcblxuICAgIGZvciAoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgcmVtb3ZlTGlzdGVuZXJzKHRoaXNbaV0pO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzO1xufTtcblxuZm4uZmluZCA9IGZ1bmN0aW9uKHNlbGVjdG9yKSB7XG4gICAgdmFyIHJlc3VsdHMgPSBbXSxcbiAgICAgICAgaSA9IDAsXG4gICAgICAgIGxlbmd0aCA9IHRoaXMubGVuZ3RoLFxuICAgICAgICBmaW5kZXIgPSBmdW5jdGlvbihlbCkge1xuICAgICAgICAgICAgaWYgKGlzRnVuY3Rpb24oZWwucXVlcnlTZWxlY3RvckFsbCkpIHtcbiAgICAgICAgICAgICAgICBbXS5mb3JFYWNoLmNhbGwoZWwucXVlcnlTZWxlY3RvckFsbChzZWxlY3RvciksIGZ1bmN0aW9uKGZvdW5kKSB7XG4gICAgICAgICAgICAgICAgICAgIHJlc3VsdHMucHVzaChmb3VuZCk7XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH07XG5cbiAgICBmb3IgKDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGZpbmRlcih0aGlzW2ldKTtcbiAgICB9XG5cbiAgICByZXR1cm4gakJvbmUocmVzdWx0cyk7XG59O1xuXG5mbi5nZXQgPSBmdW5jdGlvbihpbmRleCkge1xuICAgIHJldHVybiB0aGlzW2luZGV4XTtcbn07XG5cbmZuLmVxID0gZnVuY3Rpb24oaW5kZXgpIHtcbiAgICByZXR1cm4gakJvbmUodGhpc1tpbmRleF0pO1xufTtcblxuZm4ucGFyZW50ID0gZnVuY3Rpb24oKSB7XG4gICAgdmFyIHJlc3VsdHMgPSBbXSxcbiAgICAgICAgcGFyZW50LFxuICAgICAgICBpID0gMCxcbiAgICAgICAgbGVuZ3RoID0gdGhpcy5sZW5ndGg7XG5cbiAgICBmb3IgKDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGlmICghfnJlc3VsdHMuaW5kZXhPZihwYXJlbnQgPSB0aGlzW2ldLnBhcmVudEVsZW1lbnQpICYmIHBhcmVudCkge1xuICAgICAgICAgICAgcmVzdWx0cy5wdXNoKHBhcmVudCk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gakJvbmUocmVzdWx0cyk7XG59O1xuXG5mbi50b0FycmF5ID0gZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIHNsaWNlLmNhbGwodGhpcyk7XG59O1xuXG5mbi5pcyA9IGZ1bmN0aW9uKCkge1xuICAgIHZhciBhcmdzID0gYXJndW1lbnRzO1xuXG4gICAgcmV0dXJuIHRoaXMuc29tZShmdW5jdGlvbihlbCkge1xuICAgICAgICByZXR1cm4gZWwudGFnTmFtZS50b0xvd2VyQ2FzZSgpID09PSBhcmdzWzBdO1xuICAgIH0pO1xufTtcblxuZm4uaGFzID0gZnVuY3Rpb24oKSB7XG4gICAgdmFyIGFyZ3MgPSBhcmd1bWVudHM7XG5cbiAgICByZXR1cm4gdGhpcy5zb21lKGZ1bmN0aW9uKGVsKSB7XG4gICAgICAgIHJldHVybiBlbC5xdWVyeVNlbGVjdG9yQWxsKGFyZ3NbMF0pLmxlbmd0aDtcbiAgICB9KTtcbn07XG5cbmZuLmF0dHIgPSBmdW5jdGlvbihrZXksIHZhbHVlKSB7XG4gICAgdmFyIGFyZ3MgPSBhcmd1bWVudHMsXG4gICAgICAgIGkgPSAwLFxuICAgICAgICBsZW5ndGggPSB0aGlzLmxlbmd0aCxcbiAgICAgICAgc2V0dGVyO1xuXG4gICAgaWYgKGlzU3RyaW5nKGtleSkgJiYgYXJncy5sZW5ndGggPT09IDEpIHtcbiAgICAgICAgcmV0dXJuIHRoaXNbMF0gJiYgdGhpc1swXS5nZXRBdHRyaWJ1dGUoa2V5KTtcbiAgICB9XG5cbiAgICBpZiAoYXJncy5sZW5ndGggPT09IDIpIHtcbiAgICAgICAgc2V0dGVyID0gZnVuY3Rpb24oZWwpIHtcbiAgICAgICAgICAgIGVsLnNldEF0dHJpYnV0ZShrZXksIHZhbHVlKTtcbiAgICAgICAgfTtcbiAgICB9IGVsc2UgaWYgKGlzT2JqZWN0KGtleSkpIHtcbiAgICAgICAgc2V0dGVyID0gZnVuY3Rpb24oZWwpIHtcbiAgICAgICAgICAgIGtleXMoa2V5KS5mb3JFYWNoKGZ1bmN0aW9uKG5hbWUpIHtcbiAgICAgICAgICAgICAgICBlbC5zZXRBdHRyaWJ1dGUobmFtZSwga2V5W25hbWVdKTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9O1xuICAgIH1cblxuICAgIGZvciAoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgc2V0dGVyKHRoaXNbaV0pO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzO1xufTtcblxuZm4ucmVtb3ZlQXR0ciA9IGZ1bmN0aW9uKGtleSkge1xuICAgIHZhciBpID0gMCxcbiAgICAgICAgbGVuZ3RoID0gdGhpcy5sZW5ndGg7XG5cbiAgICBmb3IgKDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIHRoaXNbaV0ucmVtb3ZlQXR0cmlidXRlKGtleSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXM7XG59O1xuXG5mbi52YWwgPSBmdW5jdGlvbih2YWx1ZSkge1xuICAgIHZhciBpID0gMCxcbiAgICAgICAgbGVuZ3RoID0gdGhpcy5sZW5ndGg7XG5cbiAgICBpZiAoYXJndW1lbnRzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICByZXR1cm4gdGhpc1swXSAmJiB0aGlzWzBdLnZhbHVlO1xuICAgIH1cblxuICAgIGZvciAoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgdGhpc1tpXS52YWx1ZSA9IHZhbHVlO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzO1xufTtcblxuZm4uY3NzID0gZnVuY3Rpb24oa2V5LCB2YWx1ZSkge1xuICAgIHZhciBhcmdzID0gYXJndW1lbnRzLFxuICAgICAgICBpID0gMCxcbiAgICAgICAgbGVuZ3RoID0gdGhpcy5sZW5ndGgsXG4gICAgICAgIHNldHRlcjtcblxuICAgIC8vIEdldCBhdHRyaWJ1dGVcbiAgICBpZiAoaXNTdHJpbmcoa2V5KSAmJiBhcmdzLmxlbmd0aCA9PT0gMSkge1xuICAgICAgICByZXR1cm4gdGhpc1swXSAmJiB3aW4uZ2V0Q29tcHV0ZWRTdHlsZSh0aGlzWzBdKVtrZXldO1xuICAgIH1cblxuICAgIC8vIFNldCBhdHRyaWJ1dGVzXG4gICAgaWYgKGFyZ3MubGVuZ3RoID09PSAyKSB7XG4gICAgICAgIHNldHRlciA9IGZ1bmN0aW9uKGVsKSB7XG4gICAgICAgICAgICBlbC5zdHlsZVtrZXldID0gdmFsdWU7XG4gICAgICAgIH07XG4gICAgfSBlbHNlIGlmIChpc09iamVjdChrZXkpKSB7XG4gICAgICAgIHNldHRlciA9IGZ1bmN0aW9uKGVsKSB7XG4gICAgICAgICAgICBrZXlzKGtleSkuZm9yRWFjaChmdW5jdGlvbihuYW1lKSB7XG4gICAgICAgICAgICAgICAgZWwuc3R5bGVbbmFtZV0gPSBrZXlbbmFtZV07XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfTtcbiAgICB9XG5cbiAgICBmb3IgKDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIHNldHRlcih0aGlzW2ldKTtcbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcztcbn07XG5cbmZuLmRhdGEgPSBmdW5jdGlvbihrZXksIHZhbHVlKSB7XG4gICAgdmFyIGFyZ3MgPSBhcmd1bWVudHMsIGRhdGEgPSB7fSxcbiAgICAgICAgaSA9IDAsXG4gICAgICAgIGxlbmd0aCA9IHRoaXMubGVuZ3RoLFxuICAgICAgICBzZXR0ZXIsXG4gICAgICAgIHNldFZhbHVlID0gZnVuY3Rpb24oZWwsIGtleSwgdmFsdWUpIHtcbiAgICAgICAgICAgIGlmIChpc09iamVjdCh2YWx1ZSkpIHtcbiAgICAgICAgICAgICAgICBlbC5qZGF0YSA9IGVsLmpkYXRhIHx8IHt9O1xuICAgICAgICAgICAgICAgIGVsLmpkYXRhW2tleV0gPSB2YWx1ZTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgZWwuZGF0YXNldFtrZXldID0gdmFsdWU7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICAgIGdldFZhbHVlID0gZnVuY3Rpb24odmFsdWUpIHtcbiAgICAgICAgICAgIGlmICh2YWx1ZSA9PT0gXCJ0cnVlXCIpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgICAgIH0gZWxzZSBpZiAodmFsdWUgPT09IFwiZmFsc2VcIikge1xuICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHZhbHVlO1xuICAgICAgICAgICAgfVxuICAgICAgICB9O1xuXG4gICAgLy8gR2V0IGFsbCBkYXRhXG4gICAgaWYgKGFyZ3MubGVuZ3RoID09PSAwKSB7XG4gICAgICAgIHRoaXNbMF0uamRhdGEgJiYgKGRhdGEgPSB0aGlzWzBdLmpkYXRhKTtcblxuICAgICAgICBrZXlzKHRoaXNbMF0uZGF0YXNldCkuZm9yRWFjaChmdW5jdGlvbihrZXkpIHtcbiAgICAgICAgICAgIGRhdGFba2V5XSA9IGdldFZhbHVlKHRoaXNbMF0uZGF0YXNldFtrZXldKTtcbiAgICAgICAgfSwgdGhpcyk7XG5cbiAgICAgICAgcmV0dXJuIGRhdGE7XG4gICAgfVxuICAgIC8vIEdldCBkYXRhIGJ5IG5hbWVcbiAgICBpZiAoYXJncy5sZW5ndGggPT09IDEgJiYgaXNTdHJpbmcoa2V5KSkge1xuICAgICAgICByZXR1cm4gdGhpc1swXSAmJiBnZXRWYWx1ZSh0aGlzWzBdLmRhdGFzZXRba2V5XSB8fCB0aGlzWzBdLmpkYXRhICYmIHRoaXNbMF0uamRhdGFba2V5XSk7XG4gICAgfVxuXG4gICAgLy8gU2V0IGRhdGFcbiAgICBpZiAoYXJncy5sZW5ndGggPT09IDEgJiYgaXNPYmplY3Qoa2V5KSkge1xuICAgICAgICBzZXR0ZXIgPSBmdW5jdGlvbihlbCkge1xuICAgICAgICAgICAga2V5cyhrZXkpLmZvckVhY2goZnVuY3Rpb24obmFtZSkge1xuICAgICAgICAgICAgICAgIHNldFZhbHVlKGVsLCBuYW1lLCBrZXlbbmFtZV0pO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgIH07XG4gICAgfSBlbHNlIGlmIChhcmdzLmxlbmd0aCA9PT0gMikge1xuICAgICAgICBzZXR0ZXIgPSBmdW5jdGlvbihlbCkge1xuICAgICAgICAgICAgc2V0VmFsdWUoZWwsIGtleSwgdmFsdWUpO1xuICAgICAgICB9O1xuICAgIH1cblxuICAgIGZvciAoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgc2V0dGVyKHRoaXNbaV0pO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzO1xufTtcblxuZm4ucmVtb3ZlRGF0YSA9IGZ1bmN0aW9uKGtleSkge1xuICAgIHZhciBpID0gMCxcbiAgICAgICAgbGVuZ3RoID0gdGhpcy5sZW5ndGgsXG4gICAgICAgIGpkYXRhLCBkYXRhc2V0O1xuXG4gICAgZm9yICg7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgICAgICBqZGF0YSA9IHRoaXNbaV0uamRhdGE7XG4gICAgICAgIGRhdGFzZXQgPSB0aGlzW2ldLmRhdGFzZXQ7XG5cbiAgICAgICAgaWYgKGtleSkge1xuICAgICAgICAgICAgamRhdGEgJiYgamRhdGFba2V5XSAmJiBkZWxldGUgamRhdGFba2V5XTtcbiAgICAgICAgICAgIGRlbGV0ZSBkYXRhc2V0W2tleV07XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBmb3IgKGtleSBpbiBqZGF0YSkge1xuICAgICAgICAgICAgICAgIGRlbGV0ZSBqZGF0YVtrZXldO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBmb3IgKGtleSBpbiBkYXRhc2V0KSB7XG4gICAgICAgICAgICAgICAgZGVsZXRlIGRhdGFzZXRba2V5XTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiB0aGlzO1xufTtcblxuZm4uaHRtbCA9IGZ1bmN0aW9uKHZhbHVlKSB7XG4gICAgdmFyIGFyZ3MgPSBhcmd1bWVudHMsXG4gICAgICAgIGVsO1xuXG4gICAgLy8gYWRkIEhUTUwgaW50byBlbGVtZW50c1xuICAgIGlmIChhcmdzLmxlbmd0aCA9PT0gMSAmJiB2YWx1ZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmVtcHR5KCkuYXBwZW5kKHZhbHVlKTtcbiAgICB9XG4gICAgLy8gZ2V0IEhUTUwgZnJvbSBlbGVtZW50XG4gICAgZWxzZSBpZiAoYXJncy5sZW5ndGggPT09IDAgJiYgKGVsID0gdGhpc1swXSkpIHtcbiAgICAgICAgcmV0dXJuIGVsLmlubmVySFRNTDtcbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcztcbn07XG5cbmZuLmFwcGVuZCA9IGZ1bmN0aW9uKGFwcGVuZGVkKSB7XG4gICAgdmFyIGkgPSAwLFxuICAgICAgICBsZW5ndGggPSB0aGlzLmxlbmd0aCxcbiAgICAgICAgc2V0dGVyO1xuXG4gICAgLy8gY3JlYXRlIGpCb25lIG9iamVjdCBhbmQgdGhlbiBhcHBlbmRcbiAgICBpZiAoaXNTdHJpbmcoYXBwZW5kZWQpICYmIHJxdWlja0V4cHIuZXhlYyhhcHBlbmRlZCkpIHtcbiAgICAgICAgYXBwZW5kZWQgPSBqQm9uZShhcHBlbmRlZCk7XG4gICAgfVxuICAgIC8vIGNyZWF0ZSB0ZXh0IG5vZGUgZm9yIGluc2VydGluZ1xuICAgIGVsc2UgaWYgKCFpc09iamVjdChhcHBlbmRlZCkpIHtcbiAgICAgICAgYXBwZW5kZWQgPSBkb2N1bWVudC5jcmVhdGVUZXh0Tm9kZShhcHBlbmRlZCk7XG4gICAgfVxuXG4gICAgYXBwZW5kZWQgPSBhcHBlbmRlZCBpbnN0YW5jZW9mIGpCb25lID8gYXBwZW5kZWQgOiBqQm9uZShhcHBlbmRlZCk7XG5cbiAgICBzZXR0ZXIgPSBmdW5jdGlvbihlbCwgaSkge1xuICAgICAgICBhcHBlbmRlZC5mb3JFYWNoKGZ1bmN0aW9uKG5vZGUpIHtcbiAgICAgICAgICAgIGlmIChpKSB7XG4gICAgICAgICAgICAgICAgZWwuYXBwZW5kQ2hpbGQobm9kZS5jbG9uZU5vZGUoKSk7XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIGVsLmFwcGVuZENoaWxkKG5vZGUpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICB9O1xuXG4gICAgZm9yICg7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgICAgICBzZXR0ZXIodGhpc1tpXSwgaSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXM7XG59O1xuXG5mbi5hcHBlbmRUbyA9IGZ1bmN0aW9uKHRvKSB7XG4gICAgakJvbmUodG8pLmFwcGVuZCh0aGlzKTtcblxuICAgIHJldHVybiB0aGlzO1xufTtcblxuZm4uZW1wdHkgPSBmdW5jdGlvbigpIHtcbiAgICB2YXIgaSA9IDAsXG4gICAgICAgIGxlbmd0aCA9IHRoaXMubGVuZ3RoLFxuICAgICAgICBlbDtcblxuICAgIGZvciAoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgZWwgPSB0aGlzW2ldO1xuXG4gICAgICAgIHdoaWxlIChlbC5sYXN0Q2hpbGQpIHtcbiAgICAgICAgICAgIGVsLnJlbW92ZUNoaWxkKGVsLmxhc3RDaGlsZCk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcztcbn07XG5cbmZuLnJlbW92ZSA9IGZ1bmN0aW9uKCkge1xuICAgIHZhciBpID0gMCxcbiAgICAgICAgbGVuZ3RoID0gdGhpcy5sZW5ndGgsXG4gICAgICAgIGVsO1xuXG4gICAgLy8gcmVtb3ZlIGFsbCBsaXN0bmVyc1xuICAgIHRoaXMub2ZmKCk7XG5cbiAgICBmb3IgKDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGVsID0gdGhpc1tpXTtcblxuICAgICAgICAvLyByZW1vdmUgZGF0YSBhbmQgbm9kZXNcbiAgICAgICAgZGVsZXRlIGVsLmpkYXRhO1xuICAgICAgICBlbC5wYXJlbnROb2RlICYmIGVsLnBhcmVudE5vZGUucmVtb3ZlQ2hpbGQoZWwpO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzO1xufTtcblxuaWYgKHR5cGVvZiBtb2R1bGUgPT09IFwib2JqZWN0XCIgJiYgbW9kdWxlICYmIHR5cGVvZiBtb2R1bGUuZXhwb3J0cyA9PT0gXCJvYmplY3RcIikge1xuICAgIC8vIEV4cG9zZSBqQm9uZSBhcyBtb2R1bGUuZXhwb3J0cyBpbiBsb2FkZXJzIHRoYXQgaW1wbGVtZW50IHRoZSBOb2RlXG4gICAgLy8gbW9kdWxlIHBhdHRlcm4gKGluY2x1ZGluZyBicm93c2VyaWZ5KS4gRG8gbm90IGNyZWF0ZSB0aGUgZ2xvYmFsLCBzaW5jZVxuICAgIC8vIHRoZSB1c2VyIHdpbGwgYmUgc3RvcmluZyBpdCB0aGVtc2VsdmVzIGxvY2FsbHksIGFuZCBnbG9iYWxzIGFyZSBmcm93bmVkXG4gICAgLy8gdXBvbiBpbiB0aGUgTm9kZSBtb2R1bGUgd29ybGQuXG4gICAgbW9kdWxlLmV4cG9ydHMgPSBqQm9uZTtcbn1cbi8vIFJlZ2lzdGVyIGFzIGEgQU1EIG1vZHVsZVxuZWxzZSBpZiAodHlwZW9mIGRlZmluZSA9PT0gXCJmdW5jdGlvblwiICYmIGRlZmluZS5hbWQpIHtcbiAgICBkZWZpbmUoZnVuY3Rpb24oKSB7XG4gICAgICAgIHJldHVybiBqQm9uZTtcbiAgICB9KTtcblxuICAgIHdpbi5qQm9uZSA9IHdpbi4kID0gakJvbmU7XG59IGVsc2UgaWYgKHR5cGVvZiB3aW4gPT09IFwib2JqZWN0XCIgJiYgdHlwZW9mIHdpbi5kb2N1bWVudCA9PT0gXCJvYmplY3RcIikge1xuICAgIHdpbi5qQm9uZSA9IHdpbi4kID0gakJvbmU7XG59XG5cbn0od2luZG93KSk7XG4iLCJ2YXIgTW91c2U7XG5cbm1vZHVsZS5leHBvcnRzID0gTW91c2UgPSB7XG4gIHJlbDogZnVuY3Rpb24oZSkge1xuICAgIHZhciBtb3VzZVgsIG1vdXNlWSwgcmVjdCwgdGFyZ2V0O1xuICAgIG1vdXNlWCA9IGUub2Zmc2V0WDtcbiAgICBtb3VzZVkgPSBlLm9mZnNldFk7XG4gICAgaWYgKG1vdXNlWCA9PSBudWxsKSB7XG4gICAgICByZWN0ID0gdGFyZ2V0LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgICAgdGFyZ2V0ID0gZS50YXJnZXQgfHwgZS5zcmNFbGVtZW50O1xuICAgICAgaWYgKG1vdXNlWCA9PSBudWxsKSB7XG4gICAgICAgIG1vdXNlWCA9IGUuY2xpZW50WCAtIHJlY3QubGVmdDtcbiAgICAgICAgbW91c2VZID0gZS5jbGllbnRZIC0gcmVjdC50b3A7XG4gICAgICB9XG4gICAgICBpZiAobW91c2VYID09IG51bGwpIHtcbiAgICAgICAgbW91c2VYID0gZS5wYWdlWCAtIHRhcmdldC5vZmZzZXRMZWZ0O1xuICAgICAgICBtb3VzZVkgPSBlLnBhZ2VZIC0gdGFyZ2V0Lm9mZnNldFRvcDtcbiAgICAgIH1cbiAgICAgIGlmIChtb3VzZVggPT0gbnVsbCkge1xuICAgICAgICBjb25zb2xlLmxvZyhlLCBcIm5vIG1vdXNlIGV2ZW50IGRlZmluZWQuIHlvdXIgYnJvd3NlciBzdWNrc1wiKTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gW21vdXNlWCwgbW91c2VZXTtcbiAgfSxcbiAgYWJzOiBmdW5jdGlvbihlKSB7XG4gICAgdmFyIG1vdXNlWCwgbW91c2VZO1xuICAgIG1vdXNlWCA9IGUucGFnZVg7XG4gICAgbW91c2VZID0gZS5wYWdlWTtcbiAgICBpZiAobW91c2VYID09IG51bGwpIHtcbiAgICAgIG1vdXNlWCA9IGUubGF5ZXJYO1xuICAgICAgbW91c2VZID0gZS5sYXllclk7XG4gICAgfVxuICAgIGlmIChtb3VzZVggPT0gbnVsbCkge1xuICAgICAgbW91c2VYID0gZS5jbGllbnRYO1xuICAgICAgbW91c2VZID0gZS5jbGllbnRZO1xuICAgIH1cbiAgICBpZiAobW91c2VYID09IG51bGwpIHtcbiAgICAgIG1vdXNlWCA9IGUueDtcbiAgICAgIG1vdXNlWSA9IGUueTtcbiAgICB9XG4gICAgcmV0dXJuIFttb3VzZVgsIG1vdXNlWV07XG4gIH0sXG4gIHdoZWVsRGVsdGE6IGZ1bmN0aW9uKGUpIHtcbiAgICB2YXIgZGVsdGEsIGRpcjtcbiAgICBkZWx0YSA9IFtlLmRlbHRhWCwgZS5kZWx0YVldO1xuICAgIGlmIChkZWx0YVswXSA9PSBudWxsKSB7XG4gICAgICBkaXIgPSBNYXRoLmZsb29yKGUuZGV0YWlsIC8gMyk7XG4gICAgICBkZWx0YSA9IFswLCBlLm1vek1vdmVtZW50WCAqIGRpcl07XG4gICAgfVxuICAgIHJldHVybiBkZWx0YTtcbiAgfVxufTtcbiIsInZhciB3aW5kb3cgPSByZXF1aXJlKFwiZ2xvYmFsL3dpbmRvd1wiKVxudmFyIG9uY2UgPSByZXF1aXJlKFwib25jZVwiKVxudmFyIHBhcnNlSGVhZGVycyA9IHJlcXVpcmUoJ3BhcnNlLWhlYWRlcnMnKVxuXG52YXIgbWVzc2FnZXMgPSB7XG4gICAgXCIwXCI6IFwiSW50ZXJuYWwgWE1MSHR0cFJlcXVlc3QgRXJyb3JcIixcbiAgICBcIjRcIjogXCI0eHggQ2xpZW50IEVycm9yXCIsXG4gICAgXCI1XCI6IFwiNXh4IFNlcnZlciBFcnJvclwiXG59XG5cbnZhciBYSFIgPSB3aW5kb3cuWE1MSHR0cFJlcXVlc3QgfHwgbm9vcFxudmFyIFhEUiA9IFwid2l0aENyZWRlbnRpYWxzXCIgaW4gKG5ldyBYSFIoKSkgPyBYSFIgOiB3aW5kb3cuWERvbWFpblJlcXVlc3RcblxubW9kdWxlLmV4cG9ydHMgPSBjcmVhdGVYSFJcblxuZnVuY3Rpb24gY3JlYXRlWEhSKG9wdGlvbnMsIGNhbGxiYWNrKSB7XG4gICAgaWYgKHR5cGVvZiBvcHRpb25zID09PSBcInN0cmluZ1wiKSB7XG4gICAgICAgIG9wdGlvbnMgPSB7IHVyaTogb3B0aW9ucyB9XG4gICAgfVxuXG4gICAgb3B0aW9ucyA9IG9wdGlvbnMgfHwge31cbiAgICBjYWxsYmFjayA9IG9uY2UoY2FsbGJhY2spXG5cbiAgICB2YXIgeGhyID0gb3B0aW9ucy54aHIgfHwgbnVsbFxuXG4gICAgaWYgKCF4aHIpIHtcbiAgICAgICAgaWYgKG9wdGlvbnMuY29ycyB8fCBvcHRpb25zLnVzZVhEUikge1xuICAgICAgICAgICAgeGhyID0gbmV3IFhEUigpXG4gICAgICAgIH1lbHNle1xuICAgICAgICAgICAgeGhyID0gbmV3IFhIUigpXG4gICAgICAgIH1cbiAgICB9XG5cbiAgICB2YXIgdXJpID0geGhyLnVybCA9IG9wdGlvbnMudXJpIHx8IG9wdGlvbnMudXJsXG4gICAgdmFyIG1ldGhvZCA9IHhoci5tZXRob2QgPSBvcHRpb25zLm1ldGhvZCB8fCBcIkdFVFwiXG4gICAgdmFyIGJvZHkgPSBvcHRpb25zLmJvZHkgfHwgb3B0aW9ucy5kYXRhXG4gICAgdmFyIGhlYWRlcnMgPSB4aHIuaGVhZGVycyA9IG9wdGlvbnMuaGVhZGVycyB8fCB7fVxuICAgIHZhciBzeW5jID0gISFvcHRpb25zLnN5bmNcbiAgICB2YXIgaXNKc29uID0gZmFsc2VcbiAgICB2YXIga2V5XG4gICAgdmFyIGxvYWQgPSBvcHRpb25zLnJlc3BvbnNlID8gbG9hZFJlc3BvbnNlIDogbG9hZFhoclxuXG4gICAgaWYgKFwianNvblwiIGluIG9wdGlvbnMpIHtcbiAgICAgICAgaXNKc29uID0gdHJ1ZVxuICAgICAgICBoZWFkZXJzW1wiQWNjZXB0XCJdID0gXCJhcHBsaWNhdGlvbi9qc29uXCJcbiAgICAgICAgaWYgKG1ldGhvZCAhPT0gXCJHRVRcIiAmJiBtZXRob2QgIT09IFwiSEVBRFwiKSB7XG4gICAgICAgICAgICBoZWFkZXJzW1wiQ29udGVudC1UeXBlXCJdID0gXCJhcHBsaWNhdGlvbi9qc29uXCJcbiAgICAgICAgICAgIGJvZHkgPSBKU09OLnN0cmluZ2lmeShvcHRpb25zLmpzb24pXG4gICAgICAgIH1cbiAgICB9XG5cbiAgICB4aHIub25yZWFkeXN0YXRlY2hhbmdlID0gcmVhZHlzdGF0ZWNoYW5nZVxuICAgIHhoci5vbmxvYWQgPSBsb2FkXG4gICAgeGhyLm9uZXJyb3IgPSBlcnJvclxuICAgIC8vIElFOSBtdXN0IGhhdmUgb25wcm9ncmVzcyBiZSBzZXQgdG8gYSB1bmlxdWUgZnVuY3Rpb24uXG4gICAgeGhyLm9ucHJvZ3Jlc3MgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgIC8vIElFIG11c3QgZGllXG4gICAgfVxuICAgIC8vIGhhdGUgSUVcbiAgICB4aHIub250aW1lb3V0ID0gbm9vcFxuICAgIHhoci5vcGVuKG1ldGhvZCwgdXJpLCAhc3luYylcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vYmFja3dhcmQgY29tcGF0aWJpbGl0eVxuICAgIGlmIChvcHRpb25zLndpdGhDcmVkZW50aWFscyB8fCAob3B0aW9ucy5jb3JzICYmIG9wdGlvbnMud2l0aENyZWRlbnRpYWxzICE9PSBmYWxzZSkpIHtcbiAgICAgICAgeGhyLndpdGhDcmVkZW50aWFscyA9IHRydWVcbiAgICB9XG5cbiAgICAvLyBDYW5ub3Qgc2V0IHRpbWVvdXQgd2l0aCBzeW5jIHJlcXVlc3RcbiAgICBpZiAoIXN5bmMpIHtcbiAgICAgICAgeGhyLnRpbWVvdXQgPSBcInRpbWVvdXRcIiBpbiBvcHRpb25zID8gb3B0aW9ucy50aW1lb3V0IDogNTAwMFxuICAgIH1cblxuICAgIGlmICh4aHIuc2V0UmVxdWVzdEhlYWRlcikge1xuICAgICAgICBmb3Ioa2V5IGluIGhlYWRlcnMpe1xuICAgICAgICAgICAgaWYoaGVhZGVycy5oYXNPd25Qcm9wZXJ0eShrZXkpKXtcbiAgICAgICAgICAgICAgICB4aHIuc2V0UmVxdWVzdEhlYWRlcihrZXksIGhlYWRlcnNba2V5XSlcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH0gZWxzZSBpZiAob3B0aW9ucy5oZWFkZXJzKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcIkhlYWRlcnMgY2Fubm90IGJlIHNldCBvbiBhbiBYRG9tYWluUmVxdWVzdCBvYmplY3RcIilcbiAgICB9XG5cbiAgICBpZiAoXCJyZXNwb25zZVR5cGVcIiBpbiBvcHRpb25zKSB7XG4gICAgICAgIHhoci5yZXNwb25zZVR5cGUgPSBvcHRpb25zLnJlc3BvbnNlVHlwZVxuICAgIH1cbiAgICBcbiAgICBpZiAoXCJiZWZvcmVTZW5kXCIgaW4gb3B0aW9ucyAmJiBcbiAgICAgICAgdHlwZW9mIG9wdGlvbnMuYmVmb3JlU2VuZCA9PT0gXCJmdW5jdGlvblwiXG4gICAgKSB7XG4gICAgICAgIG9wdGlvbnMuYmVmb3JlU2VuZCh4aHIpXG4gICAgfVxuXG4gICAgeGhyLnNlbmQoYm9keSlcblxuICAgIHJldHVybiB4aHJcblxuICAgIGZ1bmN0aW9uIHJlYWR5c3RhdGVjaGFuZ2UoKSB7XG4gICAgICAgIGlmICh4aHIucmVhZHlTdGF0ZSA9PT0gNCkge1xuICAgICAgICAgICAgbG9hZCgpXG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBmdW5jdGlvbiBnZXRCb2R5KCkge1xuICAgICAgICAvLyBDaHJvbWUgd2l0aCByZXF1ZXN0VHlwZT1ibG9iIHRocm93cyBlcnJvcnMgYXJyb3VuZCB3aGVuIGV2ZW4gdGVzdGluZyBhY2Nlc3MgdG8gcmVzcG9uc2VUZXh0XG4gICAgICAgIHZhciBib2R5ID0gbnVsbFxuXG4gICAgICAgIGlmICh4aHIucmVzcG9uc2UpIHtcbiAgICAgICAgICAgIGJvZHkgPSB4aHIucmVzcG9uc2VcbiAgICAgICAgfSBlbHNlIGlmICh4aHIucmVzcG9uc2VUeXBlID09PSAndGV4dCcgfHwgIXhoci5yZXNwb25zZVR5cGUpIHtcbiAgICAgICAgICAgIGJvZHkgPSB4aHIucmVzcG9uc2VUZXh0IHx8IHhoci5yZXNwb25zZVhNTFxuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGlzSnNvbikge1xuICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgICBib2R5ID0gSlNPTi5wYXJzZShib2R5KVxuICAgICAgICAgICAgfSBjYXRjaCAoZSkge31cbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBib2R5XG4gICAgfVxuXG4gICAgZnVuY3Rpb24gZ2V0U3RhdHVzQ29kZSgpIHtcbiAgICAgICAgcmV0dXJuIHhoci5zdGF0dXMgPT09IDEyMjMgPyAyMDQgOiB4aHIuc3RhdHVzXG4gICAgfVxuXG4gICAgLy8gaWYgd2UncmUgZ2V0dGluZyBhIG5vbmUtb2sgc3RhdHVzQ29kZSwgYnVpbGQgJiByZXR1cm4gYW4gZXJyb3JcbiAgICBmdW5jdGlvbiBlcnJvckZyb21TdGF0dXNDb2RlKHN0YXR1cykge1xuICAgICAgICB2YXIgZXJyb3IgPSBudWxsXG4gICAgICAgIGlmIChzdGF0dXMgPT09IDAgfHwgKHN0YXR1cyA+PSA0MDAgJiYgc3RhdHVzIDwgNjAwKSkge1xuICAgICAgICAgICAgdmFyIG1lc3NhZ2UgPSAodHlwZW9mIGJvZHkgPT09IFwic3RyaW5nXCIgPyBib2R5IDogZmFsc2UpIHx8XG4gICAgICAgICAgICAgICAgbWVzc2FnZXNbU3RyaW5nKHN0YXR1cykuY2hhckF0KDApXVxuICAgICAgICAgICAgZXJyb3IgPSBuZXcgRXJyb3IobWVzc2FnZSlcbiAgICAgICAgICAgIGVycm9yLnN0YXR1c0NvZGUgPSBzdGF0dXNcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBlcnJvclxuICAgIH1cblxuICAgIC8vIHdpbGwgbG9hZCB0aGUgZGF0YSAmIHByb2Nlc3MgdGhlIHJlc3BvbnNlIGluIGEgc3BlY2lhbCByZXNwb25zZSBvYmplY3RcbiAgICBmdW5jdGlvbiBsb2FkUmVzcG9uc2UoKSB7XG4gICAgICAgIHZhciBzdGF0dXMgPSBnZXRTdGF0dXNDb2RlKClcbiAgICAgICAgdmFyIGVycm9yID0gZXJyb3JGcm9tU3RhdHVzQ29kZShzdGF0dXMpXG4gICAgICAgIHZhciByZXNwb25zZSA9IHtcbiAgICAgICAgICAgIGJvZHk6IGdldEJvZHkoKSxcbiAgICAgICAgICAgIHN0YXR1c0NvZGU6IHN0YXR1cyxcbiAgICAgICAgICAgIHN0YXR1c1RleHQ6IHhoci5zdGF0dXNUZXh0LFxuICAgICAgICAgICAgcmF3OiB4aHJcbiAgICAgICAgfVxuICAgICAgICBpZih4aHIuZ2V0QWxsUmVzcG9uc2VIZWFkZXJzKXsgLy9yZW1lbWJlciB4aHIgY2FuIGluIGZhY3QgYmUgWERSIGZvciBDT1JTIGluIElFXG4gICAgICAgICAgICByZXNwb25zZS5oZWFkZXJzID0gcGFyc2VIZWFkZXJzKHhoci5nZXRBbGxSZXNwb25zZUhlYWRlcnMoKSlcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHJlc3BvbnNlLmhlYWRlcnMgPSB7fVxuICAgICAgICB9XG5cbiAgICAgICAgY2FsbGJhY2soZXJyb3IsIHJlc3BvbnNlLCByZXNwb25zZS5ib2R5KVxuICAgIH1cblxuICAgIC8vIHdpbGwgbG9hZCB0aGUgZGF0YSBhbmQgYWRkIHNvbWUgcmVzcG9uc2UgcHJvcGVydGllcyB0byB0aGUgc291cmNlIHhoclxuICAgIC8vIGFuZCB0aGVuIHJlc3BvbmQgd2l0aCB0aGF0XG4gICAgZnVuY3Rpb24gbG9hZFhocigpIHtcbiAgICAgICAgdmFyIHN0YXR1cyA9IGdldFN0YXR1c0NvZGUoKVxuICAgICAgICB2YXIgZXJyb3IgPSBlcnJvckZyb21TdGF0dXNDb2RlKHN0YXR1cylcblxuICAgICAgICB4aHIuc3RhdHVzID0geGhyLnN0YXR1c0NvZGUgPSBzdGF0dXNcbiAgICAgICAgeGhyLmJvZHkgPSBnZXRCb2R5KClcbiAgICAgICAgeGhyLmhlYWRlcnMgPSBwYXJzZUhlYWRlcnMoeGhyLmdldEFsbFJlc3BvbnNlSGVhZGVycygpKVxuXG4gICAgICAgIGNhbGxiYWNrKGVycm9yLCB4aHIsIHhoci5ib2R5KVxuICAgIH1cblxuICAgIGZ1bmN0aW9uIGVycm9yKGV2dCkge1xuICAgICAgICBjYWxsYmFjayhldnQsIHhocilcbiAgICB9XG59XG5cblxuZnVuY3Rpb24gbm9vcCgpIHt9XG4iLCIoZnVuY3Rpb24gKGdsb2JhbCl7XG5pZiAodHlwZW9mIHdpbmRvdyAhPT0gXCJ1bmRlZmluZWRcIikge1xuICAgIG1vZHVsZS5leHBvcnRzID0gd2luZG93O1xufSBlbHNlIGlmICh0eXBlb2YgZ2xvYmFsICE9PSBcInVuZGVmaW5lZFwiKSB7XG4gICAgbW9kdWxlLmV4cG9ydHMgPSBnbG9iYWw7XG59IGVsc2UgaWYgKHR5cGVvZiBzZWxmICE9PSBcInVuZGVmaW5lZFwiKXtcbiAgICBtb2R1bGUuZXhwb3J0cyA9IHNlbGY7XG59IGVsc2Uge1xuICAgIG1vZHVsZS5leHBvcnRzID0ge307XG59XG5cbn0pLmNhbGwodGhpcyx0eXBlb2YgZ2xvYmFsICE9PSBcInVuZGVmaW5lZFwiID8gZ2xvYmFsIDogdHlwZW9mIHNlbGYgIT09IFwidW5kZWZpbmVkXCIgPyBzZWxmIDogdHlwZW9mIHdpbmRvdyAhPT0gXCJ1bmRlZmluZWRcIiA/IHdpbmRvdyA6IHt9KSIsIm1vZHVsZS5leHBvcnRzID0gb25jZVxuXG5vbmNlLnByb3RvID0gb25jZShmdW5jdGlvbiAoKSB7XG4gIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShGdW5jdGlvbi5wcm90b3R5cGUsICdvbmNlJywge1xuICAgIHZhbHVlOiBmdW5jdGlvbiAoKSB7XG4gICAgICByZXR1cm4gb25jZSh0aGlzKVxuICAgIH0sXG4gICAgY29uZmlndXJhYmxlOiB0cnVlXG4gIH0pXG59KVxuXG5mdW5jdGlvbiBvbmNlIChmbikge1xuICB2YXIgY2FsbGVkID0gZmFsc2VcbiAgcmV0dXJuIGZ1bmN0aW9uICgpIHtcbiAgICBpZiAoY2FsbGVkKSByZXR1cm5cbiAgICBjYWxsZWQgPSB0cnVlXG4gICAgcmV0dXJuIGZuLmFwcGx5KHRoaXMsIGFyZ3VtZW50cylcbiAgfVxufVxuIiwidmFyIGlzRnVuY3Rpb24gPSByZXF1aXJlKCdpcy1mdW5jdGlvbicpXG5cbm1vZHVsZS5leHBvcnRzID0gZm9yRWFjaFxuXG52YXIgdG9TdHJpbmcgPSBPYmplY3QucHJvdG90eXBlLnRvU3RyaW5nXG52YXIgaGFzT3duUHJvcGVydHkgPSBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5XG5cbmZ1bmN0aW9uIGZvckVhY2gobGlzdCwgaXRlcmF0b3IsIGNvbnRleHQpIHtcbiAgICBpZiAoIWlzRnVuY3Rpb24oaXRlcmF0b3IpKSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ2l0ZXJhdG9yIG11c3QgYmUgYSBmdW5jdGlvbicpXG4gICAgfVxuXG4gICAgaWYgKGFyZ3VtZW50cy5sZW5ndGggPCAzKSB7XG4gICAgICAgIGNvbnRleHQgPSB0aGlzXG4gICAgfVxuICAgIFxuICAgIGlmICh0b1N0cmluZy5jYWxsKGxpc3QpID09PSAnW29iamVjdCBBcnJheV0nKVxuICAgICAgICBmb3JFYWNoQXJyYXkobGlzdCwgaXRlcmF0b3IsIGNvbnRleHQpXG4gICAgZWxzZSBpZiAodHlwZW9mIGxpc3QgPT09ICdzdHJpbmcnKVxuICAgICAgICBmb3JFYWNoU3RyaW5nKGxpc3QsIGl0ZXJhdG9yLCBjb250ZXh0KVxuICAgIGVsc2VcbiAgICAgICAgZm9yRWFjaE9iamVjdChsaXN0LCBpdGVyYXRvciwgY29udGV4dClcbn1cblxuZnVuY3Rpb24gZm9yRWFjaEFycmF5KGFycmF5LCBpdGVyYXRvciwgY29udGV4dCkge1xuICAgIGZvciAodmFyIGkgPSAwLCBsZW4gPSBhcnJheS5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICBpZiAoaGFzT3duUHJvcGVydHkuY2FsbChhcnJheSwgaSkpIHtcbiAgICAgICAgICAgIGl0ZXJhdG9yLmNhbGwoY29udGV4dCwgYXJyYXlbaV0sIGksIGFycmF5KVxuICAgICAgICB9XG4gICAgfVxufVxuXG5mdW5jdGlvbiBmb3JFYWNoU3RyaW5nKHN0cmluZywgaXRlcmF0b3IsIGNvbnRleHQpIHtcbiAgICBmb3IgKHZhciBpID0gMCwgbGVuID0gc3RyaW5nLmxlbmd0aDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgIC8vIG5vIHN1Y2ggdGhpbmcgYXMgYSBzcGFyc2Ugc3RyaW5nLlxuICAgICAgICBpdGVyYXRvci5jYWxsKGNvbnRleHQsIHN0cmluZy5jaGFyQXQoaSksIGksIHN0cmluZylcbiAgICB9XG59XG5cbmZ1bmN0aW9uIGZvckVhY2hPYmplY3Qob2JqZWN0LCBpdGVyYXRvciwgY29udGV4dCkge1xuICAgIGZvciAodmFyIGsgaW4gb2JqZWN0KSB7XG4gICAgICAgIGlmIChoYXNPd25Qcm9wZXJ0eS5jYWxsKG9iamVjdCwgaykpIHtcbiAgICAgICAgICAgIGl0ZXJhdG9yLmNhbGwoY29udGV4dCwgb2JqZWN0W2tdLCBrLCBvYmplY3QpXG4gICAgICAgIH1cbiAgICB9XG59XG4iLCJtb2R1bGUuZXhwb3J0cyA9IGlzRnVuY3Rpb25cblxudmFyIHRvU3RyaW5nID0gT2JqZWN0LnByb3RvdHlwZS50b1N0cmluZ1xuXG5mdW5jdGlvbiBpc0Z1bmN0aW9uIChmbikge1xuICB2YXIgc3RyaW5nID0gdG9TdHJpbmcuY2FsbChmbilcbiAgcmV0dXJuIHN0cmluZyA9PT0gJ1tvYmplY3QgRnVuY3Rpb25dJyB8fFxuICAgICh0eXBlb2YgZm4gPT09ICdmdW5jdGlvbicgJiYgc3RyaW5nICE9PSAnW29iamVjdCBSZWdFeHBdJykgfHxcbiAgICAodHlwZW9mIHdpbmRvdyAhPT0gJ3VuZGVmaW5lZCcgJiZcbiAgICAgLy8gSUU4IGFuZCBiZWxvd1xuICAgICAoZm4gPT09IHdpbmRvdy5zZXRUaW1lb3V0IHx8XG4gICAgICBmbiA9PT0gd2luZG93LmFsZXJ0IHx8XG4gICAgICBmbiA9PT0gd2luZG93LmNvbmZpcm0gfHxcbiAgICAgIGZuID09PSB3aW5kb3cucHJvbXB0KSlcbn07XG4iLCJcbmV4cG9ydHMgPSBtb2R1bGUuZXhwb3J0cyA9IHRyaW07XG5cbmZ1bmN0aW9uIHRyaW0oc3RyKXtcbiAgcmV0dXJuIHN0ci5yZXBsYWNlKC9eXFxzKnxcXHMqJC9nLCAnJyk7XG59XG5cbmV4cG9ydHMubGVmdCA9IGZ1bmN0aW9uKHN0cil7XG4gIHJldHVybiBzdHIucmVwbGFjZSgvXlxccyovLCAnJyk7XG59O1xuXG5leHBvcnRzLnJpZ2h0ID0gZnVuY3Rpb24oc3RyKXtcbiAgcmV0dXJuIHN0ci5yZXBsYWNlKC9cXHMqJC8sICcnKTtcbn07XG4iLCJ2YXIgdHJpbSA9IHJlcXVpcmUoJ3RyaW0nKVxuICAsIGZvckVhY2ggPSByZXF1aXJlKCdmb3ItZWFjaCcpXG4gICwgaXNBcnJheSA9IGZ1bmN0aW9uKGFyZykge1xuICAgICAgcmV0dXJuIE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcuY2FsbChhcmcpID09PSAnW29iamVjdCBBcnJheV0nO1xuICAgIH1cblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAoaGVhZGVycykge1xuICBpZiAoIWhlYWRlcnMpXG4gICAgcmV0dXJuIHt9XG5cbiAgdmFyIHJlc3VsdCA9IHt9XG5cbiAgZm9yRWFjaChcbiAgICAgIHRyaW0oaGVhZGVycykuc3BsaXQoJ1xcbicpXG4gICAgLCBmdW5jdGlvbiAocm93KSB7XG4gICAgICAgIHZhciBpbmRleCA9IHJvdy5pbmRleE9mKCc6JylcbiAgICAgICAgICAsIGtleSA9IHRyaW0ocm93LnNsaWNlKDAsIGluZGV4KSkudG9Mb3dlckNhc2UoKVxuICAgICAgICAgICwgdmFsdWUgPSB0cmltKHJvdy5zbGljZShpbmRleCArIDEpKVxuXG4gICAgICAgIGlmICh0eXBlb2YocmVzdWx0W2tleV0pID09PSAndW5kZWZpbmVkJykge1xuICAgICAgICAgIHJlc3VsdFtrZXldID0gdmFsdWVcbiAgICAgICAgfSBlbHNlIGlmIChpc0FycmF5KHJlc3VsdFtrZXldKSkge1xuICAgICAgICAgIHJlc3VsdFtrZXldLnB1c2godmFsdWUpXG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgcmVzdWx0W2tleV0gPSBbIHJlc3VsdFtrZXldLCB2YWx1ZSBdXG4gICAgICAgIH1cbiAgICAgIH1cbiAgKVxuXG4gIHJldHVybiByZXN1bHRcbn0iLCIvLyAgICAgVW5kZXJzY29yZS5qcyAxLjcuMFxuLy8gICAgIGh0dHA6Ly91bmRlcnNjb3JlanMub3JnXG4vLyAgICAgKGMpIDIwMDktMjAxNCBKZXJlbXkgQXNoa2VuYXMsIERvY3VtZW50Q2xvdWQgYW5kIEludmVzdGlnYXRpdmUgUmVwb3J0ZXJzICYgRWRpdG9yc1xuLy8gICAgIFVuZGVyc2NvcmUgbWF5IGJlIGZyZWVseSBkaXN0cmlidXRlZCB1bmRlciB0aGUgTUlUIGxpY2Vuc2UuXG5cbihmdW5jdGlvbigpIHtcblxuICAvLyBCYXNlbGluZSBzZXR1cFxuICAvLyAtLS0tLS0tLS0tLS0tLVxuXG4gIC8vIEVzdGFibGlzaCB0aGUgcm9vdCBvYmplY3QsIGB3aW5kb3dgIGluIHRoZSBicm93c2VyLCBvciBgZXhwb3J0c2Agb24gdGhlIHNlcnZlci5cbiAgdmFyIHJvb3QgPSB0aGlzO1xuXG4gIC8vIFNhdmUgdGhlIHByZXZpb3VzIHZhbHVlIG9mIHRoZSBgX2AgdmFyaWFibGUuXG4gIHZhciBwcmV2aW91c1VuZGVyc2NvcmUgPSByb290Ll87XG5cbiAgLy8gU2F2ZSBieXRlcyBpbiB0aGUgbWluaWZpZWQgKGJ1dCBub3QgZ3ppcHBlZCkgdmVyc2lvbjpcbiAgdmFyIEFycmF5UHJvdG8gPSBBcnJheS5wcm90b3R5cGUsIE9ialByb3RvID0gT2JqZWN0LnByb3RvdHlwZSwgRnVuY1Byb3RvID0gRnVuY3Rpb24ucHJvdG90eXBlO1xuXG4gIC8vIENyZWF0ZSBxdWljayByZWZlcmVuY2UgdmFyaWFibGVzIGZvciBzcGVlZCBhY2Nlc3MgdG8gY29yZSBwcm90b3R5cGVzLlxuICB2YXJcbiAgICBwdXNoICAgICAgICAgICAgID0gQXJyYXlQcm90by5wdXNoLFxuICAgIHNsaWNlICAgICAgICAgICAgPSBBcnJheVByb3RvLnNsaWNlLFxuICAgIGNvbmNhdCAgICAgICAgICAgPSBBcnJheVByb3RvLmNvbmNhdCxcbiAgICB0b1N0cmluZyAgICAgICAgID0gT2JqUHJvdG8udG9TdHJpbmcsXG4gICAgaGFzT3duUHJvcGVydHkgICA9IE9ialByb3RvLmhhc093blByb3BlcnR5O1xuXG4gIC8vIEFsbCAqKkVDTUFTY3JpcHQgNSoqIG5hdGl2ZSBmdW5jdGlvbiBpbXBsZW1lbnRhdGlvbnMgdGhhdCB3ZSBob3BlIHRvIHVzZVxuICAvLyBhcmUgZGVjbGFyZWQgaGVyZS5cbiAgdmFyXG4gICAgbmF0aXZlSXNBcnJheSAgICAgID0gQXJyYXkuaXNBcnJheSxcbiAgICBuYXRpdmVLZXlzICAgICAgICAgPSBPYmplY3Qua2V5cyxcbiAgICBuYXRpdmVCaW5kICAgICAgICAgPSBGdW5jUHJvdG8uYmluZDtcblxuICAvLyBDcmVhdGUgYSBzYWZlIHJlZmVyZW5jZSB0byB0aGUgVW5kZXJzY29yZSBvYmplY3QgZm9yIHVzZSBiZWxvdy5cbiAgdmFyIF8gPSBmdW5jdGlvbihvYmopIHtcbiAgICBpZiAob2JqIGluc3RhbmNlb2YgXykgcmV0dXJuIG9iajtcbiAgICBpZiAoISh0aGlzIGluc3RhbmNlb2YgXykpIHJldHVybiBuZXcgXyhvYmopO1xuICAgIHRoaXMuX3dyYXBwZWQgPSBvYmo7XG4gIH07XG5cbiAgLy8gRXhwb3J0IHRoZSBVbmRlcnNjb3JlIG9iamVjdCBmb3IgKipOb2RlLmpzKiosIHdpdGhcbiAgLy8gYmFja3dhcmRzLWNvbXBhdGliaWxpdHkgZm9yIHRoZSBvbGQgYHJlcXVpcmUoKWAgQVBJLiBJZiB3ZSdyZSBpblxuICAvLyB0aGUgYnJvd3NlciwgYWRkIGBfYCBhcyBhIGdsb2JhbCBvYmplY3QuXG4gIGlmICh0eXBlb2YgZXhwb3J0cyAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICBpZiAodHlwZW9mIG1vZHVsZSAhPT0gJ3VuZGVmaW5lZCcgJiYgbW9kdWxlLmV4cG9ydHMpIHtcbiAgICAgIGV4cG9ydHMgPSBtb2R1bGUuZXhwb3J0cyA9IF87XG4gICAgfVxuICAgIGV4cG9ydHMuXyA9IF87XG4gIH0gZWxzZSB7XG4gICAgcm9vdC5fID0gXztcbiAgfVxuXG4gIC8vIEN1cnJlbnQgdmVyc2lvbi5cbiAgXy5WRVJTSU9OID0gJzEuNy4wJztcblxuICAvLyBJbnRlcm5hbCBmdW5jdGlvbiB0aGF0IHJldHVybnMgYW4gZWZmaWNpZW50IChmb3IgY3VycmVudCBlbmdpbmVzKSB2ZXJzaW9uXG4gIC8vIG9mIHRoZSBwYXNzZWQtaW4gY2FsbGJhY2ssIHRvIGJlIHJlcGVhdGVkbHkgYXBwbGllZCBpbiBvdGhlciBVbmRlcnNjb3JlXG4gIC8vIGZ1bmN0aW9ucy5cbiAgdmFyIGNyZWF0ZUNhbGxiYWNrID0gZnVuY3Rpb24oZnVuYywgY29udGV4dCwgYXJnQ291bnQpIHtcbiAgICBpZiAoY29udGV4dCA9PT0gdm9pZCAwKSByZXR1cm4gZnVuYztcbiAgICBzd2l0Y2ggKGFyZ0NvdW50ID09IG51bGwgPyAzIDogYXJnQ291bnQpIHtcbiAgICAgIGNhc2UgMTogcmV0dXJuIGZ1bmN0aW9uKHZhbHVlKSB7XG4gICAgICAgIHJldHVybiBmdW5jLmNhbGwoY29udGV4dCwgdmFsdWUpO1xuICAgICAgfTtcbiAgICAgIGNhc2UgMjogcmV0dXJuIGZ1bmN0aW9uKHZhbHVlLCBvdGhlcikge1xuICAgICAgICByZXR1cm4gZnVuYy5jYWxsKGNvbnRleHQsIHZhbHVlLCBvdGhlcik7XG4gICAgICB9O1xuICAgICAgY2FzZSAzOiByZXR1cm4gZnVuY3Rpb24odmFsdWUsIGluZGV4LCBjb2xsZWN0aW9uKSB7XG4gICAgICAgIHJldHVybiBmdW5jLmNhbGwoY29udGV4dCwgdmFsdWUsIGluZGV4LCBjb2xsZWN0aW9uKTtcbiAgICAgIH07XG4gICAgICBjYXNlIDQ6IHJldHVybiBmdW5jdGlvbihhY2N1bXVsYXRvciwgdmFsdWUsIGluZGV4LCBjb2xsZWN0aW9uKSB7XG4gICAgICAgIHJldHVybiBmdW5jLmNhbGwoY29udGV4dCwgYWNjdW11bGF0b3IsIHZhbHVlLCBpbmRleCwgY29sbGVjdGlvbik7XG4gICAgICB9O1xuICAgIH1cbiAgICByZXR1cm4gZnVuY3Rpb24oKSB7XG4gICAgICByZXR1cm4gZnVuYy5hcHBseShjb250ZXh0LCBhcmd1bWVudHMpO1xuICAgIH07XG4gIH07XG5cbiAgLy8gQSBtb3N0bHktaW50ZXJuYWwgZnVuY3Rpb24gdG8gZ2VuZXJhdGUgY2FsbGJhY2tzIHRoYXQgY2FuIGJlIGFwcGxpZWRcbiAgLy8gdG8gZWFjaCBlbGVtZW50IGluIGEgY29sbGVjdGlvbiwgcmV0dXJuaW5nIHRoZSBkZXNpcmVkIHJlc3VsdCDigJQgZWl0aGVyXG4gIC8vIGlkZW50aXR5LCBhbiBhcmJpdHJhcnkgY2FsbGJhY2ssIGEgcHJvcGVydHkgbWF0Y2hlciwgb3IgYSBwcm9wZXJ0eSBhY2Nlc3Nvci5cbiAgXy5pdGVyYXRlZSA9IGZ1bmN0aW9uKHZhbHVlLCBjb250ZXh0LCBhcmdDb3VudCkge1xuICAgIGlmICh2YWx1ZSA9PSBudWxsKSByZXR1cm4gXy5pZGVudGl0eTtcbiAgICBpZiAoXy5pc0Z1bmN0aW9uKHZhbHVlKSkgcmV0dXJuIGNyZWF0ZUNhbGxiYWNrKHZhbHVlLCBjb250ZXh0LCBhcmdDb3VudCk7XG4gICAgaWYgKF8uaXNPYmplY3QodmFsdWUpKSByZXR1cm4gXy5tYXRjaGVzKHZhbHVlKTtcbiAgICByZXR1cm4gXy5wcm9wZXJ0eSh2YWx1ZSk7XG4gIH07XG5cbiAgLy8gQ29sbGVjdGlvbiBGdW5jdGlvbnNcbiAgLy8gLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuICAvLyBUaGUgY29ybmVyc3RvbmUsIGFuIGBlYWNoYCBpbXBsZW1lbnRhdGlvbiwgYWthIGBmb3JFYWNoYC5cbiAgLy8gSGFuZGxlcyByYXcgb2JqZWN0cyBpbiBhZGRpdGlvbiB0byBhcnJheS1saWtlcy4gVHJlYXRzIGFsbFxuICAvLyBzcGFyc2UgYXJyYXktbGlrZXMgYXMgaWYgdGhleSB3ZXJlIGRlbnNlLlxuICBfLmVhY2ggPSBfLmZvckVhY2ggPSBmdW5jdGlvbihvYmosIGl0ZXJhdGVlLCBjb250ZXh0KSB7XG4gICAgaWYgKG9iaiA9PSBudWxsKSByZXR1cm4gb2JqO1xuICAgIGl0ZXJhdGVlID0gY3JlYXRlQ2FsbGJhY2soaXRlcmF0ZWUsIGNvbnRleHQpO1xuICAgIHZhciBpLCBsZW5ndGggPSBvYmoubGVuZ3RoO1xuICAgIGlmIChsZW5ndGggPT09ICtsZW5ndGgpIHtcbiAgICAgIGZvciAoaSA9IDA7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgICAgICBpdGVyYXRlZShvYmpbaV0sIGksIG9iaik7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIHZhciBrZXlzID0gXy5rZXlzKG9iaik7XG4gICAgICBmb3IgKGkgPSAwLCBsZW5ndGggPSBrZXlzLmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGl0ZXJhdGVlKG9ialtrZXlzW2ldXSwga2V5c1tpXSwgb2JqKTtcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIG9iajtcbiAgfTtcblxuICAvLyBSZXR1cm4gdGhlIHJlc3VsdHMgb2YgYXBwbHlpbmcgdGhlIGl0ZXJhdGVlIHRvIGVhY2ggZWxlbWVudC5cbiAgXy5tYXAgPSBfLmNvbGxlY3QgPSBmdW5jdGlvbihvYmosIGl0ZXJhdGVlLCBjb250ZXh0KSB7XG4gICAgaWYgKG9iaiA9PSBudWxsKSByZXR1cm4gW107XG4gICAgaXRlcmF0ZWUgPSBfLml0ZXJhdGVlKGl0ZXJhdGVlLCBjb250ZXh0KTtcbiAgICB2YXIga2V5cyA9IG9iai5sZW5ndGggIT09ICtvYmoubGVuZ3RoICYmIF8ua2V5cyhvYmopLFxuICAgICAgICBsZW5ndGggPSAoa2V5cyB8fCBvYmopLmxlbmd0aCxcbiAgICAgICAgcmVzdWx0cyA9IEFycmF5KGxlbmd0aCksXG4gICAgICAgIGN1cnJlbnRLZXk7XG4gICAgZm9yICh2YXIgaW5kZXggPSAwOyBpbmRleCA8IGxlbmd0aDsgaW5kZXgrKykge1xuICAgICAgY3VycmVudEtleSA9IGtleXMgPyBrZXlzW2luZGV4XSA6IGluZGV4O1xuICAgICAgcmVzdWx0c1tpbmRleF0gPSBpdGVyYXRlZShvYmpbY3VycmVudEtleV0sIGN1cnJlbnRLZXksIG9iaik7XG4gICAgfVxuICAgIHJldHVybiByZXN1bHRzO1xuICB9O1xuXG4gIHZhciByZWR1Y2VFcnJvciA9ICdSZWR1Y2Ugb2YgZW1wdHkgYXJyYXkgd2l0aCBubyBpbml0aWFsIHZhbHVlJztcblxuICAvLyAqKlJlZHVjZSoqIGJ1aWxkcyB1cCBhIHNpbmdsZSByZXN1bHQgZnJvbSBhIGxpc3Qgb2YgdmFsdWVzLCBha2EgYGluamVjdGAsXG4gIC8vIG9yIGBmb2xkbGAuXG4gIF8ucmVkdWNlID0gXy5mb2xkbCA9IF8uaW5qZWN0ID0gZnVuY3Rpb24ob2JqLCBpdGVyYXRlZSwgbWVtbywgY29udGV4dCkge1xuICAgIGlmIChvYmogPT0gbnVsbCkgb2JqID0gW107XG4gICAgaXRlcmF0ZWUgPSBjcmVhdGVDYWxsYmFjayhpdGVyYXRlZSwgY29udGV4dCwgNCk7XG4gICAgdmFyIGtleXMgPSBvYmoubGVuZ3RoICE9PSArb2JqLmxlbmd0aCAmJiBfLmtleXMob2JqKSxcbiAgICAgICAgbGVuZ3RoID0gKGtleXMgfHwgb2JqKS5sZW5ndGgsXG4gICAgICAgIGluZGV4ID0gMCwgY3VycmVudEtleTtcbiAgICBpZiAoYXJndW1lbnRzLmxlbmd0aCA8IDMpIHtcbiAgICAgIGlmICghbGVuZ3RoKSB0aHJvdyBuZXcgVHlwZUVycm9yKHJlZHVjZUVycm9yKTtcbiAgICAgIG1lbW8gPSBvYmpba2V5cyA/IGtleXNbaW5kZXgrK10gOiBpbmRleCsrXTtcbiAgICB9XG4gICAgZm9yICg7IGluZGV4IDwgbGVuZ3RoOyBpbmRleCsrKSB7XG4gICAgICBjdXJyZW50S2V5ID0ga2V5cyA/IGtleXNbaW5kZXhdIDogaW5kZXg7XG4gICAgICBtZW1vID0gaXRlcmF0ZWUobWVtbywgb2JqW2N1cnJlbnRLZXldLCBjdXJyZW50S2V5LCBvYmopO1xuICAgIH1cbiAgICByZXR1cm4gbWVtbztcbiAgfTtcblxuICAvLyBUaGUgcmlnaHQtYXNzb2NpYXRpdmUgdmVyc2lvbiBvZiByZWR1Y2UsIGFsc28ga25vd24gYXMgYGZvbGRyYC5cbiAgXy5yZWR1Y2VSaWdodCA9IF8uZm9sZHIgPSBmdW5jdGlvbihvYmosIGl0ZXJhdGVlLCBtZW1vLCBjb250ZXh0KSB7XG4gICAgaWYgKG9iaiA9PSBudWxsKSBvYmogPSBbXTtcbiAgICBpdGVyYXRlZSA9IGNyZWF0ZUNhbGxiYWNrKGl0ZXJhdGVlLCBjb250ZXh0LCA0KTtcbiAgICB2YXIga2V5cyA9IG9iai5sZW5ndGggIT09ICsgb2JqLmxlbmd0aCAmJiBfLmtleXMob2JqKSxcbiAgICAgICAgaW5kZXggPSAoa2V5cyB8fCBvYmopLmxlbmd0aCxcbiAgICAgICAgY3VycmVudEtleTtcbiAgICBpZiAoYXJndW1lbnRzLmxlbmd0aCA8IDMpIHtcbiAgICAgIGlmICghaW5kZXgpIHRocm93IG5ldyBUeXBlRXJyb3IocmVkdWNlRXJyb3IpO1xuICAgICAgbWVtbyA9IG9ialtrZXlzID8ga2V5c1stLWluZGV4XSA6IC0taW5kZXhdO1xuICAgIH1cbiAgICB3aGlsZSAoaW5kZXgtLSkge1xuICAgICAgY3VycmVudEtleSA9IGtleXMgPyBrZXlzW2luZGV4XSA6IGluZGV4O1xuICAgICAgbWVtbyA9IGl0ZXJhdGVlKG1lbW8sIG9ialtjdXJyZW50S2V5XSwgY3VycmVudEtleSwgb2JqKTtcbiAgICB9XG4gICAgcmV0dXJuIG1lbW87XG4gIH07XG5cbiAgLy8gUmV0dXJuIHRoZSBmaXJzdCB2YWx1ZSB3aGljaCBwYXNzZXMgYSB0cnV0aCB0ZXN0LiBBbGlhc2VkIGFzIGBkZXRlY3RgLlxuICBfLmZpbmQgPSBfLmRldGVjdCA9IGZ1bmN0aW9uKG9iaiwgcHJlZGljYXRlLCBjb250ZXh0KSB7XG4gICAgdmFyIHJlc3VsdDtcbiAgICBwcmVkaWNhdGUgPSBfLml0ZXJhdGVlKHByZWRpY2F0ZSwgY29udGV4dCk7XG4gICAgXy5zb21lKG9iaiwgZnVuY3Rpb24odmFsdWUsIGluZGV4LCBsaXN0KSB7XG4gICAgICBpZiAocHJlZGljYXRlKHZhbHVlLCBpbmRleCwgbGlzdCkpIHtcbiAgICAgICAgcmVzdWx0ID0gdmFsdWU7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgfVxuICAgIH0pO1xuICAgIHJldHVybiByZXN1bHQ7XG4gIH07XG5cbiAgLy8gUmV0dXJuIGFsbCB0aGUgZWxlbWVudHMgdGhhdCBwYXNzIGEgdHJ1dGggdGVzdC5cbiAgLy8gQWxpYXNlZCBhcyBgc2VsZWN0YC5cbiAgXy5maWx0ZXIgPSBfLnNlbGVjdCA9IGZ1bmN0aW9uKG9iaiwgcHJlZGljYXRlLCBjb250ZXh0KSB7XG4gICAgdmFyIHJlc3VsdHMgPSBbXTtcbiAgICBpZiAob2JqID09IG51bGwpIHJldHVybiByZXN1bHRzO1xuICAgIHByZWRpY2F0ZSA9IF8uaXRlcmF0ZWUocHJlZGljYXRlLCBjb250ZXh0KTtcbiAgICBfLmVhY2gob2JqLCBmdW5jdGlvbih2YWx1ZSwgaW5kZXgsIGxpc3QpIHtcbiAgICAgIGlmIChwcmVkaWNhdGUodmFsdWUsIGluZGV4LCBsaXN0KSkgcmVzdWx0cy5wdXNoKHZhbHVlKTtcbiAgICB9KTtcbiAgICByZXR1cm4gcmVzdWx0cztcbiAgfTtcblxuICAvLyBSZXR1cm4gYWxsIHRoZSBlbGVtZW50cyBmb3Igd2hpY2ggYSB0cnV0aCB0ZXN0IGZhaWxzLlxuICBfLnJlamVjdCA9IGZ1bmN0aW9uKG9iaiwgcHJlZGljYXRlLCBjb250ZXh0KSB7XG4gICAgcmV0dXJuIF8uZmlsdGVyKG9iaiwgXy5uZWdhdGUoXy5pdGVyYXRlZShwcmVkaWNhdGUpKSwgY29udGV4dCk7XG4gIH07XG5cbiAgLy8gRGV0ZXJtaW5lIHdoZXRoZXIgYWxsIG9mIHRoZSBlbGVtZW50cyBtYXRjaCBhIHRydXRoIHRlc3QuXG4gIC8vIEFsaWFzZWQgYXMgYGFsbGAuXG4gIF8uZXZlcnkgPSBfLmFsbCA9IGZ1bmN0aW9uKG9iaiwgcHJlZGljYXRlLCBjb250ZXh0KSB7XG4gICAgaWYgKG9iaiA9PSBudWxsKSByZXR1cm4gdHJ1ZTtcbiAgICBwcmVkaWNhdGUgPSBfLml0ZXJhdGVlKHByZWRpY2F0ZSwgY29udGV4dCk7XG4gICAgdmFyIGtleXMgPSBvYmoubGVuZ3RoICE9PSArb2JqLmxlbmd0aCAmJiBfLmtleXMob2JqKSxcbiAgICAgICAgbGVuZ3RoID0gKGtleXMgfHwgb2JqKS5sZW5ndGgsXG4gICAgICAgIGluZGV4LCBjdXJyZW50S2V5O1xuICAgIGZvciAoaW5kZXggPSAwOyBpbmRleCA8IGxlbmd0aDsgaW5kZXgrKykge1xuICAgICAgY3VycmVudEtleSA9IGtleXMgPyBrZXlzW2luZGV4XSA6IGluZGV4O1xuICAgICAgaWYgKCFwcmVkaWNhdGUob2JqW2N1cnJlbnRLZXldLCBjdXJyZW50S2V5LCBvYmopKSByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIHJldHVybiB0cnVlO1xuICB9O1xuXG4gIC8vIERldGVybWluZSBpZiBhdCBsZWFzdCBvbmUgZWxlbWVudCBpbiB0aGUgb2JqZWN0IG1hdGNoZXMgYSB0cnV0aCB0ZXN0LlxuICAvLyBBbGlhc2VkIGFzIGBhbnlgLlxuICBfLnNvbWUgPSBfLmFueSA9IGZ1bmN0aW9uKG9iaiwgcHJlZGljYXRlLCBjb250ZXh0KSB7XG4gICAgaWYgKG9iaiA9PSBudWxsKSByZXR1cm4gZmFsc2U7XG4gICAgcHJlZGljYXRlID0gXy5pdGVyYXRlZShwcmVkaWNhdGUsIGNvbnRleHQpO1xuICAgIHZhciBrZXlzID0gb2JqLmxlbmd0aCAhPT0gK29iai5sZW5ndGggJiYgXy5rZXlzKG9iaiksXG4gICAgICAgIGxlbmd0aCA9IChrZXlzIHx8IG9iaikubGVuZ3RoLFxuICAgICAgICBpbmRleCwgY3VycmVudEtleTtcbiAgICBmb3IgKGluZGV4ID0gMDsgaW5kZXggPCBsZW5ndGg7IGluZGV4KyspIHtcbiAgICAgIGN1cnJlbnRLZXkgPSBrZXlzID8ga2V5c1tpbmRleF0gOiBpbmRleDtcbiAgICAgIGlmIChwcmVkaWNhdGUob2JqW2N1cnJlbnRLZXldLCBjdXJyZW50S2V5LCBvYmopKSByZXR1cm4gdHJ1ZTtcbiAgICB9XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9O1xuXG4gIC8vIERldGVybWluZSBpZiB0aGUgYXJyYXkgb3Igb2JqZWN0IGNvbnRhaW5zIGEgZ2l2ZW4gdmFsdWUgKHVzaW5nIGA9PT1gKS5cbiAgLy8gQWxpYXNlZCBhcyBgaW5jbHVkZWAuXG4gIF8uY29udGFpbnMgPSBfLmluY2x1ZGUgPSBmdW5jdGlvbihvYmosIHRhcmdldCkge1xuICAgIGlmIChvYmogPT0gbnVsbCkgcmV0dXJuIGZhbHNlO1xuICAgIGlmIChvYmoubGVuZ3RoICE9PSArb2JqLmxlbmd0aCkgb2JqID0gXy52YWx1ZXMob2JqKTtcbiAgICByZXR1cm4gXy5pbmRleE9mKG9iaiwgdGFyZ2V0KSA+PSAwO1xuICB9O1xuXG4gIC8vIEludm9rZSBhIG1ldGhvZCAod2l0aCBhcmd1bWVudHMpIG9uIGV2ZXJ5IGl0ZW0gaW4gYSBjb2xsZWN0aW9uLlxuICBfLmludm9rZSA9IGZ1bmN0aW9uKG9iaiwgbWV0aG9kKSB7XG4gICAgdmFyIGFyZ3MgPSBzbGljZS5jYWxsKGFyZ3VtZW50cywgMik7XG4gICAgdmFyIGlzRnVuYyA9IF8uaXNGdW5jdGlvbihtZXRob2QpO1xuICAgIHJldHVybiBfLm1hcChvYmosIGZ1bmN0aW9uKHZhbHVlKSB7XG4gICAgICByZXR1cm4gKGlzRnVuYyA/IG1ldGhvZCA6IHZhbHVlW21ldGhvZF0pLmFwcGx5KHZhbHVlLCBhcmdzKTtcbiAgICB9KTtcbiAgfTtcblxuICAvLyBDb252ZW5pZW5jZSB2ZXJzaW9uIG9mIGEgY29tbW9uIHVzZSBjYXNlIG9mIGBtYXBgOiBmZXRjaGluZyBhIHByb3BlcnR5LlxuICBfLnBsdWNrID0gZnVuY3Rpb24ob2JqLCBrZXkpIHtcbiAgICByZXR1cm4gXy5tYXAob2JqLCBfLnByb3BlcnR5KGtleSkpO1xuICB9O1xuXG4gIC8vIENvbnZlbmllbmNlIHZlcnNpb24gb2YgYSBjb21tb24gdXNlIGNhc2Ugb2YgYGZpbHRlcmA6IHNlbGVjdGluZyBvbmx5IG9iamVjdHNcbiAgLy8gY29udGFpbmluZyBzcGVjaWZpYyBga2V5OnZhbHVlYCBwYWlycy5cbiAgXy53aGVyZSA9IGZ1bmN0aW9uKG9iaiwgYXR0cnMpIHtcbiAgICByZXR1cm4gXy5maWx0ZXIob2JqLCBfLm1hdGNoZXMoYXR0cnMpKTtcbiAgfTtcblxuICAvLyBDb252ZW5pZW5jZSB2ZXJzaW9uIG9mIGEgY29tbW9uIHVzZSBjYXNlIG9mIGBmaW5kYDogZ2V0dGluZyB0aGUgZmlyc3Qgb2JqZWN0XG4gIC8vIGNvbnRhaW5pbmcgc3BlY2lmaWMgYGtleTp2YWx1ZWAgcGFpcnMuXG4gIF8uZmluZFdoZXJlID0gZnVuY3Rpb24ob2JqLCBhdHRycykge1xuICAgIHJldHVybiBfLmZpbmQob2JqLCBfLm1hdGNoZXMoYXR0cnMpKTtcbiAgfTtcblxuICAvLyBSZXR1cm4gdGhlIG1heGltdW0gZWxlbWVudCAob3IgZWxlbWVudC1iYXNlZCBjb21wdXRhdGlvbikuXG4gIF8ubWF4ID0gZnVuY3Rpb24ob2JqLCBpdGVyYXRlZSwgY29udGV4dCkge1xuICAgIHZhciByZXN1bHQgPSAtSW5maW5pdHksIGxhc3RDb21wdXRlZCA9IC1JbmZpbml0eSxcbiAgICAgICAgdmFsdWUsIGNvbXB1dGVkO1xuICAgIGlmIChpdGVyYXRlZSA9PSBudWxsICYmIG9iaiAhPSBudWxsKSB7XG4gICAgICBvYmogPSBvYmoubGVuZ3RoID09PSArb2JqLmxlbmd0aCA/IG9iaiA6IF8udmFsdWVzKG9iaik7XG4gICAgICBmb3IgKHZhciBpID0gMCwgbGVuZ3RoID0gb2JqLmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIHZhbHVlID0gb2JqW2ldO1xuICAgICAgICBpZiAodmFsdWUgPiByZXN1bHQpIHtcbiAgICAgICAgICByZXN1bHQgPSB2YWx1ZTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICBpdGVyYXRlZSA9IF8uaXRlcmF0ZWUoaXRlcmF0ZWUsIGNvbnRleHQpO1xuICAgICAgXy5lYWNoKG9iaiwgZnVuY3Rpb24odmFsdWUsIGluZGV4LCBsaXN0KSB7XG4gICAgICAgIGNvbXB1dGVkID0gaXRlcmF0ZWUodmFsdWUsIGluZGV4LCBsaXN0KTtcbiAgICAgICAgaWYgKGNvbXB1dGVkID4gbGFzdENvbXB1dGVkIHx8IGNvbXB1dGVkID09PSAtSW5maW5pdHkgJiYgcmVzdWx0ID09PSAtSW5maW5pdHkpIHtcbiAgICAgICAgICByZXN1bHQgPSB2YWx1ZTtcbiAgICAgICAgICBsYXN0Q29tcHV0ZWQgPSBjb21wdXRlZDtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgfVxuICAgIHJldHVybiByZXN1bHQ7XG4gIH07XG5cbiAgLy8gUmV0dXJuIHRoZSBtaW5pbXVtIGVsZW1lbnQgKG9yIGVsZW1lbnQtYmFzZWQgY29tcHV0YXRpb24pLlxuICBfLm1pbiA9IGZ1bmN0aW9uKG9iaiwgaXRlcmF0ZWUsIGNvbnRleHQpIHtcbiAgICB2YXIgcmVzdWx0ID0gSW5maW5pdHksIGxhc3RDb21wdXRlZCA9IEluZmluaXR5LFxuICAgICAgICB2YWx1ZSwgY29tcHV0ZWQ7XG4gICAgaWYgKGl0ZXJhdGVlID09IG51bGwgJiYgb2JqICE9IG51bGwpIHtcbiAgICAgIG9iaiA9IG9iai5sZW5ndGggPT09ICtvYmoubGVuZ3RoID8gb2JqIDogXy52YWx1ZXMob2JqKTtcbiAgICAgIGZvciAodmFyIGkgPSAwLCBsZW5ndGggPSBvYmoubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgdmFsdWUgPSBvYmpbaV07XG4gICAgICAgIGlmICh2YWx1ZSA8IHJlc3VsdCkge1xuICAgICAgICAgIHJlc3VsdCA9IHZhbHVlO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIGl0ZXJhdGVlID0gXy5pdGVyYXRlZShpdGVyYXRlZSwgY29udGV4dCk7XG4gICAgICBfLmVhY2gob2JqLCBmdW5jdGlvbih2YWx1ZSwgaW5kZXgsIGxpc3QpIHtcbiAgICAgICAgY29tcHV0ZWQgPSBpdGVyYXRlZSh2YWx1ZSwgaW5kZXgsIGxpc3QpO1xuICAgICAgICBpZiAoY29tcHV0ZWQgPCBsYXN0Q29tcHV0ZWQgfHwgY29tcHV0ZWQgPT09IEluZmluaXR5ICYmIHJlc3VsdCA9PT0gSW5maW5pdHkpIHtcbiAgICAgICAgICByZXN1bHQgPSB2YWx1ZTtcbiAgICAgICAgICBsYXN0Q29tcHV0ZWQgPSBjb21wdXRlZDtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgfVxuICAgIHJldHVybiByZXN1bHQ7XG4gIH07XG5cbiAgLy8gU2h1ZmZsZSBhIGNvbGxlY3Rpb24sIHVzaW5nIHRoZSBtb2Rlcm4gdmVyc2lvbiBvZiB0aGVcbiAgLy8gW0Zpc2hlci1ZYXRlcyBzaHVmZmxlXShodHRwOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0Zpc2hlcuKAk1lhdGVzX3NodWZmbGUpLlxuICBfLnNodWZmbGUgPSBmdW5jdGlvbihvYmopIHtcbiAgICB2YXIgc2V0ID0gb2JqICYmIG9iai5sZW5ndGggPT09ICtvYmoubGVuZ3RoID8gb2JqIDogXy52YWx1ZXMob2JqKTtcbiAgICB2YXIgbGVuZ3RoID0gc2V0Lmxlbmd0aDtcbiAgICB2YXIgc2h1ZmZsZWQgPSBBcnJheShsZW5ndGgpO1xuICAgIGZvciAodmFyIGluZGV4ID0gMCwgcmFuZDsgaW5kZXggPCBsZW5ndGg7IGluZGV4KyspIHtcbiAgICAgIHJhbmQgPSBfLnJhbmRvbSgwLCBpbmRleCk7XG4gICAgICBpZiAocmFuZCAhPT0gaW5kZXgpIHNodWZmbGVkW2luZGV4XSA9IHNodWZmbGVkW3JhbmRdO1xuICAgICAgc2h1ZmZsZWRbcmFuZF0gPSBzZXRbaW5kZXhdO1xuICAgIH1cbiAgICByZXR1cm4gc2h1ZmZsZWQ7XG4gIH07XG5cbiAgLy8gU2FtcGxlICoqbioqIHJhbmRvbSB2YWx1ZXMgZnJvbSBhIGNvbGxlY3Rpb24uXG4gIC8vIElmICoqbioqIGlzIG5vdCBzcGVjaWZpZWQsIHJldHVybnMgYSBzaW5nbGUgcmFuZG9tIGVsZW1lbnQuXG4gIC8vIFRoZSBpbnRlcm5hbCBgZ3VhcmRgIGFyZ3VtZW50IGFsbG93cyBpdCB0byB3b3JrIHdpdGggYG1hcGAuXG4gIF8uc2FtcGxlID0gZnVuY3Rpb24ob2JqLCBuLCBndWFyZCkge1xuICAgIGlmIChuID09IG51bGwgfHwgZ3VhcmQpIHtcbiAgICAgIGlmIChvYmoubGVuZ3RoICE9PSArb2JqLmxlbmd0aCkgb2JqID0gXy52YWx1ZXMob2JqKTtcbiAgICAgIHJldHVybiBvYmpbXy5yYW5kb20ob2JqLmxlbmd0aCAtIDEpXTtcbiAgICB9XG4gICAgcmV0dXJuIF8uc2h1ZmZsZShvYmopLnNsaWNlKDAsIE1hdGgubWF4KDAsIG4pKTtcbiAgfTtcblxuICAvLyBTb3J0IHRoZSBvYmplY3QncyB2YWx1ZXMgYnkgYSBjcml0ZXJpb24gcHJvZHVjZWQgYnkgYW4gaXRlcmF0ZWUuXG4gIF8uc29ydEJ5ID0gZnVuY3Rpb24ob2JqLCBpdGVyYXRlZSwgY29udGV4dCkge1xuICAgIGl0ZXJhdGVlID0gXy5pdGVyYXRlZShpdGVyYXRlZSwgY29udGV4dCk7XG4gICAgcmV0dXJuIF8ucGx1Y2soXy5tYXAob2JqLCBmdW5jdGlvbih2YWx1ZSwgaW5kZXgsIGxpc3QpIHtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIHZhbHVlOiB2YWx1ZSxcbiAgICAgICAgaW5kZXg6IGluZGV4LFxuICAgICAgICBjcml0ZXJpYTogaXRlcmF0ZWUodmFsdWUsIGluZGV4LCBsaXN0KVxuICAgICAgfTtcbiAgICB9KS5zb3J0KGZ1bmN0aW9uKGxlZnQsIHJpZ2h0KSB7XG4gICAgICB2YXIgYSA9IGxlZnQuY3JpdGVyaWE7XG4gICAgICB2YXIgYiA9IHJpZ2h0LmNyaXRlcmlhO1xuICAgICAgaWYgKGEgIT09IGIpIHtcbiAgICAgICAgaWYgKGEgPiBiIHx8IGEgPT09IHZvaWQgMCkgcmV0dXJuIDE7XG4gICAgICAgIGlmIChhIDwgYiB8fCBiID09PSB2b2lkIDApIHJldHVybiAtMTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBsZWZ0LmluZGV4IC0gcmlnaHQuaW5kZXg7XG4gICAgfSksICd2YWx1ZScpO1xuICB9O1xuXG4gIC8vIEFuIGludGVybmFsIGZ1bmN0aW9uIHVzZWQgZm9yIGFnZ3JlZ2F0ZSBcImdyb3VwIGJ5XCIgb3BlcmF0aW9ucy5cbiAgdmFyIGdyb3VwID0gZnVuY3Rpb24oYmVoYXZpb3IpIHtcbiAgICByZXR1cm4gZnVuY3Rpb24ob2JqLCBpdGVyYXRlZSwgY29udGV4dCkge1xuICAgICAgdmFyIHJlc3VsdCA9IHt9O1xuICAgICAgaXRlcmF0ZWUgPSBfLml0ZXJhdGVlKGl0ZXJhdGVlLCBjb250ZXh0KTtcbiAgICAgIF8uZWFjaChvYmosIGZ1bmN0aW9uKHZhbHVlLCBpbmRleCkge1xuICAgICAgICB2YXIga2V5ID0gaXRlcmF0ZWUodmFsdWUsIGluZGV4LCBvYmopO1xuICAgICAgICBiZWhhdmlvcihyZXN1bHQsIHZhbHVlLCBrZXkpO1xuICAgICAgfSk7XG4gICAgICByZXR1cm4gcmVzdWx0O1xuICAgIH07XG4gIH07XG5cbiAgLy8gR3JvdXBzIHRoZSBvYmplY3QncyB2YWx1ZXMgYnkgYSBjcml0ZXJpb24uIFBhc3MgZWl0aGVyIGEgc3RyaW5nIGF0dHJpYnV0ZVxuICAvLyB0byBncm91cCBieSwgb3IgYSBmdW5jdGlvbiB0aGF0IHJldHVybnMgdGhlIGNyaXRlcmlvbi5cbiAgXy5ncm91cEJ5ID0gZ3JvdXAoZnVuY3Rpb24ocmVzdWx0LCB2YWx1ZSwga2V5KSB7XG4gICAgaWYgKF8uaGFzKHJlc3VsdCwga2V5KSkgcmVzdWx0W2tleV0ucHVzaCh2YWx1ZSk7IGVsc2UgcmVzdWx0W2tleV0gPSBbdmFsdWVdO1xuICB9KTtcblxuICAvLyBJbmRleGVzIHRoZSBvYmplY3QncyB2YWx1ZXMgYnkgYSBjcml0ZXJpb24sIHNpbWlsYXIgdG8gYGdyb3VwQnlgLCBidXQgZm9yXG4gIC8vIHdoZW4geW91IGtub3cgdGhhdCB5b3VyIGluZGV4IHZhbHVlcyB3aWxsIGJlIHVuaXF1ZS5cbiAgXy5pbmRleEJ5ID0gZ3JvdXAoZnVuY3Rpb24ocmVzdWx0LCB2YWx1ZSwga2V5KSB7XG4gICAgcmVzdWx0W2tleV0gPSB2YWx1ZTtcbiAgfSk7XG5cbiAgLy8gQ291bnRzIGluc3RhbmNlcyBvZiBhbiBvYmplY3QgdGhhdCBncm91cCBieSBhIGNlcnRhaW4gY3JpdGVyaW9uLiBQYXNzXG4gIC8vIGVpdGhlciBhIHN0cmluZyBhdHRyaWJ1dGUgdG8gY291bnQgYnksIG9yIGEgZnVuY3Rpb24gdGhhdCByZXR1cm5zIHRoZVxuICAvLyBjcml0ZXJpb24uXG4gIF8uY291bnRCeSA9IGdyb3VwKGZ1bmN0aW9uKHJlc3VsdCwgdmFsdWUsIGtleSkge1xuICAgIGlmIChfLmhhcyhyZXN1bHQsIGtleSkpIHJlc3VsdFtrZXldKys7IGVsc2UgcmVzdWx0W2tleV0gPSAxO1xuICB9KTtcblxuICAvLyBVc2UgYSBjb21wYXJhdG9yIGZ1bmN0aW9uIHRvIGZpZ3VyZSBvdXQgdGhlIHNtYWxsZXN0IGluZGV4IGF0IHdoaWNoXG4gIC8vIGFuIG9iamVjdCBzaG91bGQgYmUgaW5zZXJ0ZWQgc28gYXMgdG8gbWFpbnRhaW4gb3JkZXIuIFVzZXMgYmluYXJ5IHNlYXJjaC5cbiAgXy5zb3J0ZWRJbmRleCA9IGZ1bmN0aW9uKGFycmF5LCBvYmosIGl0ZXJhdGVlLCBjb250ZXh0KSB7XG4gICAgaXRlcmF0ZWUgPSBfLml0ZXJhdGVlKGl0ZXJhdGVlLCBjb250ZXh0LCAxKTtcbiAgICB2YXIgdmFsdWUgPSBpdGVyYXRlZShvYmopO1xuICAgIHZhciBsb3cgPSAwLCBoaWdoID0gYXJyYXkubGVuZ3RoO1xuICAgIHdoaWxlIChsb3cgPCBoaWdoKSB7XG4gICAgICB2YXIgbWlkID0gbG93ICsgaGlnaCA+Pj4gMTtcbiAgICAgIGlmIChpdGVyYXRlZShhcnJheVttaWRdKSA8IHZhbHVlKSBsb3cgPSBtaWQgKyAxOyBlbHNlIGhpZ2ggPSBtaWQ7XG4gICAgfVxuICAgIHJldHVybiBsb3c7XG4gIH07XG5cbiAgLy8gU2FmZWx5IGNyZWF0ZSBhIHJlYWwsIGxpdmUgYXJyYXkgZnJvbSBhbnl0aGluZyBpdGVyYWJsZS5cbiAgXy50b0FycmF5ID0gZnVuY3Rpb24ob2JqKSB7XG4gICAgaWYgKCFvYmopIHJldHVybiBbXTtcbiAgICBpZiAoXy5pc0FycmF5KG9iaikpIHJldHVybiBzbGljZS5jYWxsKG9iaik7XG4gICAgaWYgKG9iai5sZW5ndGggPT09ICtvYmoubGVuZ3RoKSByZXR1cm4gXy5tYXAob2JqLCBfLmlkZW50aXR5KTtcbiAgICByZXR1cm4gXy52YWx1ZXMob2JqKTtcbiAgfTtcblxuICAvLyBSZXR1cm4gdGhlIG51bWJlciBvZiBlbGVtZW50cyBpbiBhbiBvYmplY3QuXG4gIF8uc2l6ZSA9IGZ1bmN0aW9uKG9iaikge1xuICAgIGlmIChvYmogPT0gbnVsbCkgcmV0dXJuIDA7XG4gICAgcmV0dXJuIG9iai5sZW5ndGggPT09ICtvYmoubGVuZ3RoID8gb2JqLmxlbmd0aCA6IF8ua2V5cyhvYmopLmxlbmd0aDtcbiAgfTtcblxuICAvLyBTcGxpdCBhIGNvbGxlY3Rpb24gaW50byB0d28gYXJyYXlzOiBvbmUgd2hvc2UgZWxlbWVudHMgYWxsIHNhdGlzZnkgdGhlIGdpdmVuXG4gIC8vIHByZWRpY2F0ZSwgYW5kIG9uZSB3aG9zZSBlbGVtZW50cyBhbGwgZG8gbm90IHNhdGlzZnkgdGhlIHByZWRpY2F0ZS5cbiAgXy5wYXJ0aXRpb24gPSBmdW5jdGlvbihvYmosIHByZWRpY2F0ZSwgY29udGV4dCkge1xuICAgIHByZWRpY2F0ZSA9IF8uaXRlcmF0ZWUocHJlZGljYXRlLCBjb250ZXh0KTtcbiAgICB2YXIgcGFzcyA9IFtdLCBmYWlsID0gW107XG4gICAgXy5lYWNoKG9iaiwgZnVuY3Rpb24odmFsdWUsIGtleSwgb2JqKSB7XG4gICAgICAocHJlZGljYXRlKHZhbHVlLCBrZXksIG9iaikgPyBwYXNzIDogZmFpbCkucHVzaCh2YWx1ZSk7XG4gICAgfSk7XG4gICAgcmV0dXJuIFtwYXNzLCBmYWlsXTtcbiAgfTtcblxuICAvLyBBcnJheSBGdW5jdGlvbnNcbiAgLy8gLS0tLS0tLS0tLS0tLS0tXG5cbiAgLy8gR2V0IHRoZSBmaXJzdCBlbGVtZW50IG9mIGFuIGFycmF5LiBQYXNzaW5nICoqbioqIHdpbGwgcmV0dXJuIHRoZSBmaXJzdCBOXG4gIC8vIHZhbHVlcyBpbiB0aGUgYXJyYXkuIEFsaWFzZWQgYXMgYGhlYWRgIGFuZCBgdGFrZWAuIFRoZSAqKmd1YXJkKiogY2hlY2tcbiAgLy8gYWxsb3dzIGl0IHRvIHdvcmsgd2l0aCBgXy5tYXBgLlxuICBfLmZpcnN0ID0gXy5oZWFkID0gXy50YWtlID0gZnVuY3Rpb24oYXJyYXksIG4sIGd1YXJkKSB7XG4gICAgaWYgKGFycmF5ID09IG51bGwpIHJldHVybiB2b2lkIDA7XG4gICAgaWYgKG4gPT0gbnVsbCB8fCBndWFyZCkgcmV0dXJuIGFycmF5WzBdO1xuICAgIGlmIChuIDwgMCkgcmV0dXJuIFtdO1xuICAgIHJldHVybiBzbGljZS5jYWxsKGFycmF5LCAwLCBuKTtcbiAgfTtcblxuICAvLyBSZXR1cm5zIGV2ZXJ5dGhpbmcgYnV0IHRoZSBsYXN0IGVudHJ5IG9mIHRoZSBhcnJheS4gRXNwZWNpYWxseSB1c2VmdWwgb25cbiAgLy8gdGhlIGFyZ3VtZW50cyBvYmplY3QuIFBhc3NpbmcgKipuKiogd2lsbCByZXR1cm4gYWxsIHRoZSB2YWx1ZXMgaW5cbiAgLy8gdGhlIGFycmF5LCBleGNsdWRpbmcgdGhlIGxhc3QgTi4gVGhlICoqZ3VhcmQqKiBjaGVjayBhbGxvd3MgaXQgdG8gd29yayB3aXRoXG4gIC8vIGBfLm1hcGAuXG4gIF8uaW5pdGlhbCA9IGZ1bmN0aW9uKGFycmF5LCBuLCBndWFyZCkge1xuICAgIHJldHVybiBzbGljZS5jYWxsKGFycmF5LCAwLCBNYXRoLm1heCgwLCBhcnJheS5sZW5ndGggLSAobiA9PSBudWxsIHx8IGd1YXJkID8gMSA6IG4pKSk7XG4gIH07XG5cbiAgLy8gR2V0IHRoZSBsYXN0IGVsZW1lbnQgb2YgYW4gYXJyYXkuIFBhc3NpbmcgKipuKiogd2lsbCByZXR1cm4gdGhlIGxhc3QgTlxuICAvLyB2YWx1ZXMgaW4gdGhlIGFycmF5LiBUaGUgKipndWFyZCoqIGNoZWNrIGFsbG93cyBpdCB0byB3b3JrIHdpdGggYF8ubWFwYC5cbiAgXy5sYXN0ID0gZnVuY3Rpb24oYXJyYXksIG4sIGd1YXJkKSB7XG4gICAgaWYgKGFycmF5ID09IG51bGwpIHJldHVybiB2b2lkIDA7XG4gICAgaWYgKG4gPT0gbnVsbCB8fCBndWFyZCkgcmV0dXJuIGFycmF5W2FycmF5Lmxlbmd0aCAtIDFdO1xuICAgIHJldHVybiBzbGljZS5jYWxsKGFycmF5LCBNYXRoLm1heChhcnJheS5sZW5ndGggLSBuLCAwKSk7XG4gIH07XG5cbiAgLy8gUmV0dXJucyBldmVyeXRoaW5nIGJ1dCB0aGUgZmlyc3QgZW50cnkgb2YgdGhlIGFycmF5LiBBbGlhc2VkIGFzIGB0YWlsYCBhbmQgYGRyb3BgLlxuICAvLyBFc3BlY2lhbGx5IHVzZWZ1bCBvbiB0aGUgYXJndW1lbnRzIG9iamVjdC4gUGFzc2luZyBhbiAqKm4qKiB3aWxsIHJldHVyblxuICAvLyB0aGUgcmVzdCBOIHZhbHVlcyBpbiB0aGUgYXJyYXkuIFRoZSAqKmd1YXJkKipcbiAgLy8gY2hlY2sgYWxsb3dzIGl0IHRvIHdvcmsgd2l0aCBgXy5tYXBgLlxuICBfLnJlc3QgPSBfLnRhaWwgPSBfLmRyb3AgPSBmdW5jdGlvbihhcnJheSwgbiwgZ3VhcmQpIHtcbiAgICByZXR1cm4gc2xpY2UuY2FsbChhcnJheSwgbiA9PSBudWxsIHx8IGd1YXJkID8gMSA6IG4pO1xuICB9O1xuXG4gIC8vIFRyaW0gb3V0IGFsbCBmYWxzeSB2YWx1ZXMgZnJvbSBhbiBhcnJheS5cbiAgXy5jb21wYWN0ID0gZnVuY3Rpb24oYXJyYXkpIHtcbiAgICByZXR1cm4gXy5maWx0ZXIoYXJyYXksIF8uaWRlbnRpdHkpO1xuICB9O1xuXG4gIC8vIEludGVybmFsIGltcGxlbWVudGF0aW9uIG9mIGEgcmVjdXJzaXZlIGBmbGF0dGVuYCBmdW5jdGlvbi5cbiAgdmFyIGZsYXR0ZW4gPSBmdW5jdGlvbihpbnB1dCwgc2hhbGxvdywgc3RyaWN0LCBvdXRwdXQpIHtcbiAgICBpZiAoc2hhbGxvdyAmJiBfLmV2ZXJ5KGlucHV0LCBfLmlzQXJyYXkpKSB7XG4gICAgICByZXR1cm4gY29uY2F0LmFwcGx5KG91dHB1dCwgaW5wdXQpO1xuICAgIH1cbiAgICBmb3IgKHZhciBpID0gMCwgbGVuZ3RoID0gaW5wdXQubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgIHZhciB2YWx1ZSA9IGlucHV0W2ldO1xuICAgICAgaWYgKCFfLmlzQXJyYXkodmFsdWUpICYmICFfLmlzQXJndW1lbnRzKHZhbHVlKSkge1xuICAgICAgICBpZiAoIXN0cmljdCkgb3V0cHV0LnB1c2godmFsdWUpO1xuICAgICAgfSBlbHNlIGlmIChzaGFsbG93KSB7XG4gICAgICAgIHB1c2guYXBwbHkob3V0cHV0LCB2YWx1ZSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBmbGF0dGVuKHZhbHVlLCBzaGFsbG93LCBzdHJpY3QsIG91dHB1dCk7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBvdXRwdXQ7XG4gIH07XG5cbiAgLy8gRmxhdHRlbiBvdXQgYW4gYXJyYXksIGVpdGhlciByZWN1cnNpdmVseSAoYnkgZGVmYXVsdCksIG9yIGp1c3Qgb25lIGxldmVsLlxuICBfLmZsYXR0ZW4gPSBmdW5jdGlvbihhcnJheSwgc2hhbGxvdykge1xuICAgIHJldHVybiBmbGF0dGVuKGFycmF5LCBzaGFsbG93LCBmYWxzZSwgW10pO1xuICB9O1xuXG4gIC8vIFJldHVybiBhIHZlcnNpb24gb2YgdGhlIGFycmF5IHRoYXQgZG9lcyBub3QgY29udGFpbiB0aGUgc3BlY2lmaWVkIHZhbHVlKHMpLlxuICBfLndpdGhvdXQgPSBmdW5jdGlvbihhcnJheSkge1xuICAgIHJldHVybiBfLmRpZmZlcmVuY2UoYXJyYXksIHNsaWNlLmNhbGwoYXJndW1lbnRzLCAxKSk7XG4gIH07XG5cbiAgLy8gUHJvZHVjZSBhIGR1cGxpY2F0ZS1mcmVlIHZlcnNpb24gb2YgdGhlIGFycmF5LiBJZiB0aGUgYXJyYXkgaGFzIGFscmVhZHlcbiAgLy8gYmVlbiBzb3J0ZWQsIHlvdSBoYXZlIHRoZSBvcHRpb24gb2YgdXNpbmcgYSBmYXN0ZXIgYWxnb3JpdGhtLlxuICAvLyBBbGlhc2VkIGFzIGB1bmlxdWVgLlxuICBfLnVuaXEgPSBfLnVuaXF1ZSA9IGZ1bmN0aW9uKGFycmF5LCBpc1NvcnRlZCwgaXRlcmF0ZWUsIGNvbnRleHQpIHtcbiAgICBpZiAoYXJyYXkgPT0gbnVsbCkgcmV0dXJuIFtdO1xuICAgIGlmICghXy5pc0Jvb2xlYW4oaXNTb3J0ZWQpKSB7XG4gICAgICBjb250ZXh0ID0gaXRlcmF0ZWU7XG4gICAgICBpdGVyYXRlZSA9IGlzU29ydGVkO1xuICAgICAgaXNTb3J0ZWQgPSBmYWxzZTtcbiAgICB9XG4gICAgaWYgKGl0ZXJhdGVlICE9IG51bGwpIGl0ZXJhdGVlID0gXy5pdGVyYXRlZShpdGVyYXRlZSwgY29udGV4dCk7XG4gICAgdmFyIHJlc3VsdCA9IFtdO1xuICAgIHZhciBzZWVuID0gW107XG4gICAgZm9yICh2YXIgaSA9IDAsIGxlbmd0aCA9IGFycmF5Lmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICB2YXIgdmFsdWUgPSBhcnJheVtpXTtcbiAgICAgIGlmIChpc1NvcnRlZCkge1xuICAgICAgICBpZiAoIWkgfHwgc2VlbiAhPT0gdmFsdWUpIHJlc3VsdC5wdXNoKHZhbHVlKTtcbiAgICAgICAgc2VlbiA9IHZhbHVlO1xuICAgICAgfSBlbHNlIGlmIChpdGVyYXRlZSkge1xuICAgICAgICB2YXIgY29tcHV0ZWQgPSBpdGVyYXRlZSh2YWx1ZSwgaSwgYXJyYXkpO1xuICAgICAgICBpZiAoXy5pbmRleE9mKHNlZW4sIGNvbXB1dGVkKSA8IDApIHtcbiAgICAgICAgICBzZWVuLnB1c2goY29tcHV0ZWQpO1xuICAgICAgICAgIHJlc3VsdC5wdXNoKHZhbHVlKTtcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIGlmIChfLmluZGV4T2YocmVzdWx0LCB2YWx1ZSkgPCAwKSB7XG4gICAgICAgIHJlc3VsdC5wdXNoKHZhbHVlKTtcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfTtcblxuICAvLyBQcm9kdWNlIGFuIGFycmF5IHRoYXQgY29udGFpbnMgdGhlIHVuaW9uOiBlYWNoIGRpc3RpbmN0IGVsZW1lbnQgZnJvbSBhbGwgb2ZcbiAgLy8gdGhlIHBhc3NlZC1pbiBhcnJheXMuXG4gIF8udW5pb24gPSBmdW5jdGlvbigpIHtcbiAgICByZXR1cm4gXy51bmlxKGZsYXR0ZW4oYXJndW1lbnRzLCB0cnVlLCB0cnVlLCBbXSkpO1xuICB9O1xuXG4gIC8vIFByb2R1Y2UgYW4gYXJyYXkgdGhhdCBjb250YWlucyBldmVyeSBpdGVtIHNoYXJlZCBiZXR3ZWVuIGFsbCB0aGVcbiAgLy8gcGFzc2VkLWluIGFycmF5cy5cbiAgXy5pbnRlcnNlY3Rpb24gPSBmdW5jdGlvbihhcnJheSkge1xuICAgIGlmIChhcnJheSA9PSBudWxsKSByZXR1cm4gW107XG4gICAgdmFyIHJlc3VsdCA9IFtdO1xuICAgIHZhciBhcmdzTGVuZ3RoID0gYXJndW1lbnRzLmxlbmd0aDtcbiAgICBmb3IgKHZhciBpID0gMCwgbGVuZ3RoID0gYXJyYXkubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgIHZhciBpdGVtID0gYXJyYXlbaV07XG4gICAgICBpZiAoXy5jb250YWlucyhyZXN1bHQsIGl0ZW0pKSBjb250aW51ZTtcbiAgICAgIGZvciAodmFyIGogPSAxOyBqIDwgYXJnc0xlbmd0aDsgaisrKSB7XG4gICAgICAgIGlmICghXy5jb250YWlucyhhcmd1bWVudHNbal0sIGl0ZW0pKSBicmVhaztcbiAgICAgIH1cbiAgICAgIGlmIChqID09PSBhcmdzTGVuZ3RoKSByZXN1bHQucHVzaChpdGVtKTtcbiAgICB9XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfTtcblxuICAvLyBUYWtlIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gb25lIGFycmF5IGFuZCBhIG51bWJlciBvZiBvdGhlciBhcnJheXMuXG4gIC8vIE9ubHkgdGhlIGVsZW1lbnRzIHByZXNlbnQgaW4ganVzdCB0aGUgZmlyc3QgYXJyYXkgd2lsbCByZW1haW4uXG4gIF8uZGlmZmVyZW5jZSA9IGZ1bmN0aW9uKGFycmF5KSB7XG4gICAgdmFyIHJlc3QgPSBmbGF0dGVuKHNsaWNlLmNhbGwoYXJndW1lbnRzLCAxKSwgdHJ1ZSwgdHJ1ZSwgW10pO1xuICAgIHJldHVybiBfLmZpbHRlcihhcnJheSwgZnVuY3Rpb24odmFsdWUpe1xuICAgICAgcmV0dXJuICFfLmNvbnRhaW5zKHJlc3QsIHZhbHVlKTtcbiAgICB9KTtcbiAgfTtcblxuICAvLyBaaXAgdG9nZXRoZXIgbXVsdGlwbGUgbGlzdHMgaW50byBhIHNpbmdsZSBhcnJheSAtLSBlbGVtZW50cyB0aGF0IHNoYXJlXG4gIC8vIGFuIGluZGV4IGdvIHRvZ2V0aGVyLlxuICBfLnppcCA9IGZ1bmN0aW9uKGFycmF5KSB7XG4gICAgaWYgKGFycmF5ID09IG51bGwpIHJldHVybiBbXTtcbiAgICB2YXIgbGVuZ3RoID0gXy5tYXgoYXJndW1lbnRzLCAnbGVuZ3RoJykubGVuZ3RoO1xuICAgIHZhciByZXN1bHRzID0gQXJyYXkobGVuZ3RoKTtcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICByZXN1bHRzW2ldID0gXy5wbHVjayhhcmd1bWVudHMsIGkpO1xuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0cztcbiAgfTtcblxuICAvLyBDb252ZXJ0cyBsaXN0cyBpbnRvIG9iamVjdHMuIFBhc3MgZWl0aGVyIGEgc2luZ2xlIGFycmF5IG9mIGBba2V5LCB2YWx1ZV1gXG4gIC8vIHBhaXJzLCBvciB0d28gcGFyYWxsZWwgYXJyYXlzIG9mIHRoZSBzYW1lIGxlbmd0aCAtLSBvbmUgb2Yga2V5cywgYW5kIG9uZSBvZlxuICAvLyB0aGUgY29ycmVzcG9uZGluZyB2YWx1ZXMuXG4gIF8ub2JqZWN0ID0gZnVuY3Rpb24obGlzdCwgdmFsdWVzKSB7XG4gICAgaWYgKGxpc3QgPT0gbnVsbCkgcmV0dXJuIHt9O1xuICAgIHZhciByZXN1bHQgPSB7fTtcbiAgICBmb3IgKHZhciBpID0gMCwgbGVuZ3RoID0gbGlzdC5sZW5ndGg7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgICAgaWYgKHZhbHVlcykge1xuICAgICAgICByZXN1bHRbbGlzdFtpXV0gPSB2YWx1ZXNbaV07XG4gICAgICB9IGVsc2Uge1xuICAgICAgICByZXN1bHRbbGlzdFtpXVswXV0gPSBsaXN0W2ldWzFdO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9O1xuXG4gIC8vIFJldHVybiB0aGUgcG9zaXRpb24gb2YgdGhlIGZpcnN0IG9jY3VycmVuY2Ugb2YgYW4gaXRlbSBpbiBhbiBhcnJheSxcbiAgLy8gb3IgLTEgaWYgdGhlIGl0ZW0gaXMgbm90IGluY2x1ZGVkIGluIHRoZSBhcnJheS5cbiAgLy8gSWYgdGhlIGFycmF5IGlzIGxhcmdlIGFuZCBhbHJlYWR5IGluIHNvcnQgb3JkZXIsIHBhc3MgYHRydWVgXG4gIC8vIGZvciAqKmlzU29ydGVkKiogdG8gdXNlIGJpbmFyeSBzZWFyY2guXG4gIF8uaW5kZXhPZiA9IGZ1bmN0aW9uKGFycmF5LCBpdGVtLCBpc1NvcnRlZCkge1xuICAgIGlmIChhcnJheSA9PSBudWxsKSByZXR1cm4gLTE7XG4gICAgdmFyIGkgPSAwLCBsZW5ndGggPSBhcnJheS5sZW5ndGg7XG4gICAgaWYgKGlzU29ydGVkKSB7XG4gICAgICBpZiAodHlwZW9mIGlzU29ydGVkID09ICdudW1iZXInKSB7XG4gICAgICAgIGkgPSBpc1NvcnRlZCA8IDAgPyBNYXRoLm1heCgwLCBsZW5ndGggKyBpc1NvcnRlZCkgOiBpc1NvcnRlZDtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGkgPSBfLnNvcnRlZEluZGV4KGFycmF5LCBpdGVtKTtcbiAgICAgICAgcmV0dXJuIGFycmF5W2ldID09PSBpdGVtID8gaSA6IC0xO1xuICAgICAgfVxuICAgIH1cbiAgICBmb3IgKDsgaSA8IGxlbmd0aDsgaSsrKSBpZiAoYXJyYXlbaV0gPT09IGl0ZW0pIHJldHVybiBpO1xuICAgIHJldHVybiAtMTtcbiAgfTtcblxuICBfLmxhc3RJbmRleE9mID0gZnVuY3Rpb24oYXJyYXksIGl0ZW0sIGZyb20pIHtcbiAgICBpZiAoYXJyYXkgPT0gbnVsbCkgcmV0dXJuIC0xO1xuICAgIHZhciBpZHggPSBhcnJheS5sZW5ndGg7XG4gICAgaWYgKHR5cGVvZiBmcm9tID09ICdudW1iZXInKSB7XG4gICAgICBpZHggPSBmcm9tIDwgMCA/IGlkeCArIGZyb20gKyAxIDogTWF0aC5taW4oaWR4LCBmcm9tICsgMSk7XG4gICAgfVxuICAgIHdoaWxlICgtLWlkeCA+PSAwKSBpZiAoYXJyYXlbaWR4XSA9PT0gaXRlbSkgcmV0dXJuIGlkeDtcbiAgICByZXR1cm4gLTE7XG4gIH07XG5cbiAgLy8gR2VuZXJhdGUgYW4gaW50ZWdlciBBcnJheSBjb250YWluaW5nIGFuIGFyaXRobWV0aWMgcHJvZ3Jlc3Npb24uIEEgcG9ydCBvZlxuICAvLyB0aGUgbmF0aXZlIFB5dGhvbiBgcmFuZ2UoKWAgZnVuY3Rpb24uIFNlZVxuICAvLyBbdGhlIFB5dGhvbiBkb2N1bWVudGF0aW9uXShodHRwOi8vZG9jcy5weXRob24ub3JnL2xpYnJhcnkvZnVuY3Rpb25zLmh0bWwjcmFuZ2UpLlxuICBfLnJhbmdlID0gZnVuY3Rpb24oc3RhcnQsIHN0b3AsIHN0ZXApIHtcbiAgICBpZiAoYXJndW1lbnRzLmxlbmd0aCA8PSAxKSB7XG4gICAgICBzdG9wID0gc3RhcnQgfHwgMDtcbiAgICAgIHN0YXJ0ID0gMDtcbiAgICB9XG4gICAgc3RlcCA9IHN0ZXAgfHwgMTtcblxuICAgIHZhciBsZW5ndGggPSBNYXRoLm1heChNYXRoLmNlaWwoKHN0b3AgLSBzdGFydCkgLyBzdGVwKSwgMCk7XG4gICAgdmFyIHJhbmdlID0gQXJyYXkobGVuZ3RoKTtcblxuICAgIGZvciAodmFyIGlkeCA9IDA7IGlkeCA8IGxlbmd0aDsgaWR4KyssIHN0YXJ0ICs9IHN0ZXApIHtcbiAgICAgIHJhbmdlW2lkeF0gPSBzdGFydDtcbiAgICB9XG5cbiAgICByZXR1cm4gcmFuZ2U7XG4gIH07XG5cbiAgLy8gRnVuY3Rpb24gKGFoZW0pIEZ1bmN0aW9uc1xuICAvLyAtLS0tLS0tLS0tLS0tLS0tLS1cblxuICAvLyBSZXVzYWJsZSBjb25zdHJ1Y3RvciBmdW5jdGlvbiBmb3IgcHJvdG90eXBlIHNldHRpbmcuXG4gIHZhciBDdG9yID0gZnVuY3Rpb24oKXt9O1xuXG4gIC8vIENyZWF0ZSBhIGZ1bmN0aW9uIGJvdW5kIHRvIGEgZ2l2ZW4gb2JqZWN0IChhc3NpZ25pbmcgYHRoaXNgLCBhbmQgYXJndW1lbnRzLFxuICAvLyBvcHRpb25hbGx5KS4gRGVsZWdhdGVzIHRvICoqRUNNQVNjcmlwdCA1KioncyBuYXRpdmUgYEZ1bmN0aW9uLmJpbmRgIGlmXG4gIC8vIGF2YWlsYWJsZS5cbiAgXy5iaW5kID0gZnVuY3Rpb24oZnVuYywgY29udGV4dCkge1xuICAgIHZhciBhcmdzLCBib3VuZDtcbiAgICBpZiAobmF0aXZlQmluZCAmJiBmdW5jLmJpbmQgPT09IG5hdGl2ZUJpbmQpIHJldHVybiBuYXRpdmVCaW5kLmFwcGx5KGZ1bmMsIHNsaWNlLmNhbGwoYXJndW1lbnRzLCAxKSk7XG4gICAgaWYgKCFfLmlzRnVuY3Rpb24oZnVuYykpIHRocm93IG5ldyBUeXBlRXJyb3IoJ0JpbmQgbXVzdCBiZSBjYWxsZWQgb24gYSBmdW5jdGlvbicpO1xuICAgIGFyZ3MgPSBzbGljZS5jYWxsKGFyZ3VtZW50cywgMik7XG4gICAgYm91bmQgPSBmdW5jdGlvbigpIHtcbiAgICAgIGlmICghKHRoaXMgaW5zdGFuY2VvZiBib3VuZCkpIHJldHVybiBmdW5jLmFwcGx5KGNvbnRleHQsIGFyZ3MuY29uY2F0KHNsaWNlLmNhbGwoYXJndW1lbnRzKSkpO1xuICAgICAgQ3Rvci5wcm90b3R5cGUgPSBmdW5jLnByb3RvdHlwZTtcbiAgICAgIHZhciBzZWxmID0gbmV3IEN0b3I7XG4gICAgICBDdG9yLnByb3RvdHlwZSA9IG51bGw7XG4gICAgICB2YXIgcmVzdWx0ID0gZnVuYy5hcHBseShzZWxmLCBhcmdzLmNvbmNhdChzbGljZS5jYWxsKGFyZ3VtZW50cykpKTtcbiAgICAgIGlmIChfLmlzT2JqZWN0KHJlc3VsdCkpIHJldHVybiByZXN1bHQ7XG4gICAgICByZXR1cm4gc2VsZjtcbiAgICB9O1xuICAgIHJldHVybiBib3VuZDtcbiAgfTtcblxuICAvLyBQYXJ0aWFsbHkgYXBwbHkgYSBmdW5jdGlvbiBieSBjcmVhdGluZyBhIHZlcnNpb24gdGhhdCBoYXMgaGFkIHNvbWUgb2YgaXRzXG4gIC8vIGFyZ3VtZW50cyBwcmUtZmlsbGVkLCB3aXRob3V0IGNoYW5naW5nIGl0cyBkeW5hbWljIGB0aGlzYCBjb250ZXh0LiBfIGFjdHNcbiAgLy8gYXMgYSBwbGFjZWhvbGRlciwgYWxsb3dpbmcgYW55IGNvbWJpbmF0aW9uIG9mIGFyZ3VtZW50cyB0byBiZSBwcmUtZmlsbGVkLlxuICBfLnBhcnRpYWwgPSBmdW5jdGlvbihmdW5jKSB7XG4gICAgdmFyIGJvdW5kQXJncyA9IHNsaWNlLmNhbGwoYXJndW1lbnRzLCAxKTtcbiAgICByZXR1cm4gZnVuY3Rpb24oKSB7XG4gICAgICB2YXIgcG9zaXRpb24gPSAwO1xuICAgICAgdmFyIGFyZ3MgPSBib3VuZEFyZ3Muc2xpY2UoKTtcbiAgICAgIGZvciAodmFyIGkgPSAwLCBsZW5ndGggPSBhcmdzLmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGlmIChhcmdzW2ldID09PSBfKSBhcmdzW2ldID0gYXJndW1lbnRzW3Bvc2l0aW9uKytdO1xuICAgICAgfVxuICAgICAgd2hpbGUgKHBvc2l0aW9uIDwgYXJndW1lbnRzLmxlbmd0aCkgYXJncy5wdXNoKGFyZ3VtZW50c1twb3NpdGlvbisrXSk7XG4gICAgICByZXR1cm4gZnVuYy5hcHBseSh0aGlzLCBhcmdzKTtcbiAgICB9O1xuICB9O1xuXG4gIC8vIEJpbmQgYSBudW1iZXIgb2YgYW4gb2JqZWN0J3MgbWV0aG9kcyB0byB0aGF0IG9iamVjdC4gUmVtYWluaW5nIGFyZ3VtZW50c1xuICAvLyBhcmUgdGhlIG1ldGhvZCBuYW1lcyB0byBiZSBib3VuZC4gVXNlZnVsIGZvciBlbnN1cmluZyB0aGF0IGFsbCBjYWxsYmFja3NcbiAgLy8gZGVmaW5lZCBvbiBhbiBvYmplY3QgYmVsb25nIHRvIGl0LlxuICBfLmJpbmRBbGwgPSBmdW5jdGlvbihvYmopIHtcbiAgICB2YXIgaSwgbGVuZ3RoID0gYXJndW1lbnRzLmxlbmd0aCwga2V5O1xuICAgIGlmIChsZW5ndGggPD0gMSkgdGhyb3cgbmV3IEVycm9yKCdiaW5kQWxsIG11c3QgYmUgcGFzc2VkIGZ1bmN0aW9uIG5hbWVzJyk7XG4gICAgZm9yIChpID0gMTsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICBrZXkgPSBhcmd1bWVudHNbaV07XG4gICAgICBvYmpba2V5XSA9IF8uYmluZChvYmpba2V5XSwgb2JqKTtcbiAgICB9XG4gICAgcmV0dXJuIG9iajtcbiAgfTtcblxuICAvLyBNZW1vaXplIGFuIGV4cGVuc2l2ZSBmdW5jdGlvbiBieSBzdG9yaW5nIGl0cyByZXN1bHRzLlxuICBfLm1lbW9pemUgPSBmdW5jdGlvbihmdW5jLCBoYXNoZXIpIHtcbiAgICB2YXIgbWVtb2l6ZSA9IGZ1bmN0aW9uKGtleSkge1xuICAgICAgdmFyIGNhY2hlID0gbWVtb2l6ZS5jYWNoZTtcbiAgICAgIHZhciBhZGRyZXNzID0gaGFzaGVyID8gaGFzaGVyLmFwcGx5KHRoaXMsIGFyZ3VtZW50cykgOiBrZXk7XG4gICAgICBpZiAoIV8uaGFzKGNhY2hlLCBhZGRyZXNzKSkgY2FjaGVbYWRkcmVzc10gPSBmdW5jLmFwcGx5KHRoaXMsIGFyZ3VtZW50cyk7XG4gICAgICByZXR1cm4gY2FjaGVbYWRkcmVzc107XG4gICAgfTtcbiAgICBtZW1vaXplLmNhY2hlID0ge307XG4gICAgcmV0dXJuIG1lbW9pemU7XG4gIH07XG5cbiAgLy8gRGVsYXlzIGEgZnVuY3Rpb24gZm9yIHRoZSBnaXZlbiBudW1iZXIgb2YgbWlsbGlzZWNvbmRzLCBhbmQgdGhlbiBjYWxsc1xuICAvLyBpdCB3aXRoIHRoZSBhcmd1bWVudHMgc3VwcGxpZWQuXG4gIF8uZGVsYXkgPSBmdW5jdGlvbihmdW5jLCB3YWl0KSB7XG4gICAgdmFyIGFyZ3MgPSBzbGljZS5jYWxsKGFyZ3VtZW50cywgMik7XG4gICAgcmV0dXJuIHNldFRpbWVvdXQoZnVuY3Rpb24oKXtcbiAgICAgIHJldHVybiBmdW5jLmFwcGx5KG51bGwsIGFyZ3MpO1xuICAgIH0sIHdhaXQpO1xuICB9O1xuXG4gIC8vIERlZmVycyBhIGZ1bmN0aW9uLCBzY2hlZHVsaW5nIGl0IHRvIHJ1biBhZnRlciB0aGUgY3VycmVudCBjYWxsIHN0YWNrIGhhc1xuICAvLyBjbGVhcmVkLlxuICBfLmRlZmVyID0gZnVuY3Rpb24oZnVuYykge1xuICAgIHJldHVybiBfLmRlbGF5LmFwcGx5KF8sIFtmdW5jLCAxXS5jb25jYXQoc2xpY2UuY2FsbChhcmd1bWVudHMsIDEpKSk7XG4gIH07XG5cbiAgLy8gUmV0dXJucyBhIGZ1bmN0aW9uLCB0aGF0LCB3aGVuIGludm9rZWQsIHdpbGwgb25seSBiZSB0cmlnZ2VyZWQgYXQgbW9zdCBvbmNlXG4gIC8vIGR1cmluZyBhIGdpdmVuIHdpbmRvdyBvZiB0aW1lLiBOb3JtYWxseSwgdGhlIHRocm90dGxlZCBmdW5jdGlvbiB3aWxsIHJ1blxuICAvLyBhcyBtdWNoIGFzIGl0IGNhbiwgd2l0aG91dCBldmVyIGdvaW5nIG1vcmUgdGhhbiBvbmNlIHBlciBgd2FpdGAgZHVyYXRpb247XG4gIC8vIGJ1dCBpZiB5b3UnZCBsaWtlIHRvIGRpc2FibGUgdGhlIGV4ZWN1dGlvbiBvbiB0aGUgbGVhZGluZyBlZGdlLCBwYXNzXG4gIC8vIGB7bGVhZGluZzogZmFsc2V9YC4gVG8gZGlzYWJsZSBleGVjdXRpb24gb24gdGhlIHRyYWlsaW5nIGVkZ2UsIGRpdHRvLlxuICBfLnRocm90dGxlID0gZnVuY3Rpb24oZnVuYywgd2FpdCwgb3B0aW9ucykge1xuICAgIHZhciBjb250ZXh0LCBhcmdzLCByZXN1bHQ7XG4gICAgdmFyIHRpbWVvdXQgPSBudWxsO1xuICAgIHZhciBwcmV2aW91cyA9IDA7XG4gICAgaWYgKCFvcHRpb25zKSBvcHRpb25zID0ge307XG4gICAgdmFyIGxhdGVyID0gZnVuY3Rpb24oKSB7XG4gICAgICBwcmV2aW91cyA9IG9wdGlvbnMubGVhZGluZyA9PT0gZmFsc2UgPyAwIDogXy5ub3coKTtcbiAgICAgIHRpbWVvdXQgPSBudWxsO1xuICAgICAgcmVzdWx0ID0gZnVuYy5hcHBseShjb250ZXh0LCBhcmdzKTtcbiAgICAgIGlmICghdGltZW91dCkgY29udGV4dCA9IGFyZ3MgPSBudWxsO1xuICAgIH07XG4gICAgcmV0dXJuIGZ1bmN0aW9uKCkge1xuICAgICAgdmFyIG5vdyA9IF8ubm93KCk7XG4gICAgICBpZiAoIXByZXZpb3VzICYmIG9wdGlvbnMubGVhZGluZyA9PT0gZmFsc2UpIHByZXZpb3VzID0gbm93O1xuICAgICAgdmFyIHJlbWFpbmluZyA9IHdhaXQgLSAobm93IC0gcHJldmlvdXMpO1xuICAgICAgY29udGV4dCA9IHRoaXM7XG4gICAgICBhcmdzID0gYXJndW1lbnRzO1xuICAgICAgaWYgKHJlbWFpbmluZyA8PSAwIHx8IHJlbWFpbmluZyA+IHdhaXQpIHtcbiAgICAgICAgY2xlYXJUaW1lb3V0KHRpbWVvdXQpO1xuICAgICAgICB0aW1lb3V0ID0gbnVsbDtcbiAgICAgICAgcHJldmlvdXMgPSBub3c7XG4gICAgICAgIHJlc3VsdCA9IGZ1bmMuYXBwbHkoY29udGV4dCwgYXJncyk7XG4gICAgICAgIGlmICghdGltZW91dCkgY29udGV4dCA9IGFyZ3MgPSBudWxsO1xuICAgICAgfSBlbHNlIGlmICghdGltZW91dCAmJiBvcHRpb25zLnRyYWlsaW5nICE9PSBmYWxzZSkge1xuICAgICAgICB0aW1lb3V0ID0gc2V0VGltZW91dChsYXRlciwgcmVtYWluaW5nKTtcbiAgICAgIH1cbiAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgfTtcbiAgfTtcblxuICAvLyBSZXR1cm5zIGEgZnVuY3Rpb24sIHRoYXQsIGFzIGxvbmcgYXMgaXQgY29udGludWVzIHRvIGJlIGludm9rZWQsIHdpbGwgbm90XG4gIC8vIGJlIHRyaWdnZXJlZC4gVGhlIGZ1bmN0aW9uIHdpbGwgYmUgY2FsbGVkIGFmdGVyIGl0IHN0b3BzIGJlaW5nIGNhbGxlZCBmb3JcbiAgLy8gTiBtaWxsaXNlY29uZHMuIElmIGBpbW1lZGlhdGVgIGlzIHBhc3NlZCwgdHJpZ2dlciB0aGUgZnVuY3Rpb24gb24gdGhlXG4gIC8vIGxlYWRpbmcgZWRnZSwgaW5zdGVhZCBvZiB0aGUgdHJhaWxpbmcuXG4gIF8uZGVib3VuY2UgPSBmdW5jdGlvbihmdW5jLCB3YWl0LCBpbW1lZGlhdGUpIHtcbiAgICB2YXIgdGltZW91dCwgYXJncywgY29udGV4dCwgdGltZXN0YW1wLCByZXN1bHQ7XG5cbiAgICB2YXIgbGF0ZXIgPSBmdW5jdGlvbigpIHtcbiAgICAgIHZhciBsYXN0ID0gXy5ub3coKSAtIHRpbWVzdGFtcDtcblxuICAgICAgaWYgKGxhc3QgPCB3YWl0ICYmIGxhc3QgPiAwKSB7XG4gICAgICAgIHRpbWVvdXQgPSBzZXRUaW1lb3V0KGxhdGVyLCB3YWl0IC0gbGFzdCk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aW1lb3V0ID0gbnVsbDtcbiAgICAgICAgaWYgKCFpbW1lZGlhdGUpIHtcbiAgICAgICAgICByZXN1bHQgPSBmdW5jLmFwcGx5KGNvbnRleHQsIGFyZ3MpO1xuICAgICAgICAgIGlmICghdGltZW91dCkgY29udGV4dCA9IGFyZ3MgPSBudWxsO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfTtcblxuICAgIHJldHVybiBmdW5jdGlvbigpIHtcbiAgICAgIGNvbnRleHQgPSB0aGlzO1xuICAgICAgYXJncyA9IGFyZ3VtZW50cztcbiAgICAgIHRpbWVzdGFtcCA9IF8ubm93KCk7XG4gICAgICB2YXIgY2FsbE5vdyA9IGltbWVkaWF0ZSAmJiAhdGltZW91dDtcbiAgICAgIGlmICghdGltZW91dCkgdGltZW91dCA9IHNldFRpbWVvdXQobGF0ZXIsIHdhaXQpO1xuICAgICAgaWYgKGNhbGxOb3cpIHtcbiAgICAgICAgcmVzdWx0ID0gZnVuYy5hcHBseShjb250ZXh0LCBhcmdzKTtcbiAgICAgICAgY29udGV4dCA9IGFyZ3MgPSBudWxsO1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gcmVzdWx0O1xuICAgIH07XG4gIH07XG5cbiAgLy8gUmV0dXJucyB0aGUgZmlyc3QgZnVuY3Rpb24gcGFzc2VkIGFzIGFuIGFyZ3VtZW50IHRvIHRoZSBzZWNvbmQsXG4gIC8vIGFsbG93aW5nIHlvdSB0byBhZGp1c3QgYXJndW1lbnRzLCBydW4gY29kZSBiZWZvcmUgYW5kIGFmdGVyLCBhbmRcbiAgLy8gY29uZGl0aW9uYWxseSBleGVjdXRlIHRoZSBvcmlnaW5hbCBmdW5jdGlvbi5cbiAgXy53cmFwID0gZnVuY3Rpb24oZnVuYywgd3JhcHBlcikge1xuICAgIHJldHVybiBfLnBhcnRpYWwod3JhcHBlciwgZnVuYyk7XG4gIH07XG5cbiAgLy8gUmV0dXJucyBhIG5lZ2F0ZWQgdmVyc2lvbiBvZiB0aGUgcGFzc2VkLWluIHByZWRpY2F0ZS5cbiAgXy5uZWdhdGUgPSBmdW5jdGlvbihwcmVkaWNhdGUpIHtcbiAgICByZXR1cm4gZnVuY3Rpb24oKSB7XG4gICAgICByZXR1cm4gIXByZWRpY2F0ZS5hcHBseSh0aGlzLCBhcmd1bWVudHMpO1xuICAgIH07XG4gIH07XG5cbiAgLy8gUmV0dXJucyBhIGZ1bmN0aW9uIHRoYXQgaXMgdGhlIGNvbXBvc2l0aW9uIG9mIGEgbGlzdCBvZiBmdW5jdGlvbnMsIGVhY2hcbiAgLy8gY29uc3VtaW5nIHRoZSByZXR1cm4gdmFsdWUgb2YgdGhlIGZ1bmN0aW9uIHRoYXQgZm9sbG93cy5cbiAgXy5jb21wb3NlID0gZnVuY3Rpb24oKSB7XG4gICAgdmFyIGFyZ3MgPSBhcmd1bWVudHM7XG4gICAgdmFyIHN0YXJ0ID0gYXJncy5sZW5ndGggLSAxO1xuICAgIHJldHVybiBmdW5jdGlvbigpIHtcbiAgICAgIHZhciBpID0gc3RhcnQ7XG4gICAgICB2YXIgcmVzdWx0ID0gYXJnc1tzdGFydF0uYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgICAgIHdoaWxlIChpLS0pIHJlc3VsdCA9IGFyZ3NbaV0uY2FsbCh0aGlzLCByZXN1bHQpO1xuICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICB9O1xuICB9O1xuXG4gIC8vIFJldHVybnMgYSBmdW5jdGlvbiB0aGF0IHdpbGwgb25seSBiZSBleGVjdXRlZCBhZnRlciBiZWluZyBjYWxsZWQgTiB0aW1lcy5cbiAgXy5hZnRlciA9IGZ1bmN0aW9uKHRpbWVzLCBmdW5jKSB7XG4gICAgcmV0dXJuIGZ1bmN0aW9uKCkge1xuICAgICAgaWYgKC0tdGltZXMgPCAxKSB7XG4gICAgICAgIHJldHVybiBmdW5jLmFwcGx5KHRoaXMsIGFyZ3VtZW50cyk7XG4gICAgICB9XG4gICAgfTtcbiAgfTtcblxuICAvLyBSZXR1cm5zIGEgZnVuY3Rpb24gdGhhdCB3aWxsIG9ubHkgYmUgZXhlY3V0ZWQgYmVmb3JlIGJlaW5nIGNhbGxlZCBOIHRpbWVzLlxuICBfLmJlZm9yZSA9IGZ1bmN0aW9uKHRpbWVzLCBmdW5jKSB7XG4gICAgdmFyIG1lbW87XG4gICAgcmV0dXJuIGZ1bmN0aW9uKCkge1xuICAgICAgaWYgKC0tdGltZXMgPiAwKSB7XG4gICAgICAgIG1lbW8gPSBmdW5jLmFwcGx5KHRoaXMsIGFyZ3VtZW50cyk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBmdW5jID0gbnVsbDtcbiAgICAgIH1cbiAgICAgIHJldHVybiBtZW1vO1xuICAgIH07XG4gIH07XG5cbiAgLy8gUmV0dXJucyBhIGZ1bmN0aW9uIHRoYXQgd2lsbCBiZSBleGVjdXRlZCBhdCBtb3N0IG9uZSB0aW1lLCBubyBtYXR0ZXIgaG93XG4gIC8vIG9mdGVuIHlvdSBjYWxsIGl0LiBVc2VmdWwgZm9yIGxhenkgaW5pdGlhbGl6YXRpb24uXG4gIF8ub25jZSA9IF8ucGFydGlhbChfLmJlZm9yZSwgMik7XG5cbiAgLy8gT2JqZWN0IEZ1bmN0aW9uc1xuICAvLyAtLS0tLS0tLS0tLS0tLS0tXG5cbiAgLy8gUmV0cmlldmUgdGhlIG5hbWVzIG9mIGFuIG9iamVjdCdzIHByb3BlcnRpZXMuXG4gIC8vIERlbGVnYXRlcyB0byAqKkVDTUFTY3JpcHQgNSoqJ3MgbmF0aXZlIGBPYmplY3Qua2V5c2BcbiAgXy5rZXlzID0gZnVuY3Rpb24ob2JqKSB7XG4gICAgaWYgKCFfLmlzT2JqZWN0KG9iaikpIHJldHVybiBbXTtcbiAgICBpZiAobmF0aXZlS2V5cykgcmV0dXJuIG5hdGl2ZUtleXMob2JqKTtcbiAgICB2YXIga2V5cyA9IFtdO1xuICAgIGZvciAodmFyIGtleSBpbiBvYmopIGlmIChfLmhhcyhvYmosIGtleSkpIGtleXMucHVzaChrZXkpO1xuICAgIHJldHVybiBrZXlzO1xuICB9O1xuXG4gIC8vIFJldHJpZXZlIHRoZSB2YWx1ZXMgb2YgYW4gb2JqZWN0J3MgcHJvcGVydGllcy5cbiAgXy52YWx1ZXMgPSBmdW5jdGlvbihvYmopIHtcbiAgICB2YXIga2V5cyA9IF8ua2V5cyhvYmopO1xuICAgIHZhciBsZW5ndGggPSBrZXlzLmxlbmd0aDtcbiAgICB2YXIgdmFsdWVzID0gQXJyYXkobGVuZ3RoKTtcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICB2YWx1ZXNbaV0gPSBvYmpba2V5c1tpXV07XG4gICAgfVxuICAgIHJldHVybiB2YWx1ZXM7XG4gIH07XG5cbiAgLy8gQ29udmVydCBhbiBvYmplY3QgaW50byBhIGxpc3Qgb2YgYFtrZXksIHZhbHVlXWAgcGFpcnMuXG4gIF8ucGFpcnMgPSBmdW5jdGlvbihvYmopIHtcbiAgICB2YXIga2V5cyA9IF8ua2V5cyhvYmopO1xuICAgIHZhciBsZW5ndGggPSBrZXlzLmxlbmd0aDtcbiAgICB2YXIgcGFpcnMgPSBBcnJheShsZW5ndGgpO1xuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgIHBhaXJzW2ldID0gW2tleXNbaV0sIG9ialtrZXlzW2ldXV07XG4gICAgfVxuICAgIHJldHVybiBwYWlycztcbiAgfTtcblxuICAvLyBJbnZlcnQgdGhlIGtleXMgYW5kIHZhbHVlcyBvZiBhbiBvYmplY3QuIFRoZSB2YWx1ZXMgbXVzdCBiZSBzZXJpYWxpemFibGUuXG4gIF8uaW52ZXJ0ID0gZnVuY3Rpb24ob2JqKSB7XG4gICAgdmFyIHJlc3VsdCA9IHt9O1xuICAgIHZhciBrZXlzID0gXy5rZXlzKG9iaik7XG4gICAgZm9yICh2YXIgaSA9IDAsIGxlbmd0aCA9IGtleXMubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgIHJlc3VsdFtvYmpba2V5c1tpXV1dID0ga2V5c1tpXTtcbiAgICB9XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfTtcblxuICAvLyBSZXR1cm4gYSBzb3J0ZWQgbGlzdCBvZiB0aGUgZnVuY3Rpb24gbmFtZXMgYXZhaWxhYmxlIG9uIHRoZSBvYmplY3QuXG4gIC8vIEFsaWFzZWQgYXMgYG1ldGhvZHNgXG4gIF8uZnVuY3Rpb25zID0gXy5tZXRob2RzID0gZnVuY3Rpb24ob2JqKSB7XG4gICAgdmFyIG5hbWVzID0gW107XG4gICAgZm9yICh2YXIga2V5IGluIG9iaikge1xuICAgICAgaWYgKF8uaXNGdW5jdGlvbihvYmpba2V5XSkpIG5hbWVzLnB1c2goa2V5KTtcbiAgICB9XG4gICAgcmV0dXJuIG5hbWVzLnNvcnQoKTtcbiAgfTtcblxuICAvLyBFeHRlbmQgYSBnaXZlbiBvYmplY3Qgd2l0aCBhbGwgdGhlIHByb3BlcnRpZXMgaW4gcGFzc2VkLWluIG9iamVjdChzKS5cbiAgXy5leHRlbmQgPSBmdW5jdGlvbihvYmopIHtcbiAgICBpZiAoIV8uaXNPYmplY3Qob2JqKSkgcmV0dXJuIG9iajtcbiAgICB2YXIgc291cmNlLCBwcm9wO1xuICAgIGZvciAodmFyIGkgPSAxLCBsZW5ndGggPSBhcmd1bWVudHMubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgIHNvdXJjZSA9IGFyZ3VtZW50c1tpXTtcbiAgICAgIGZvciAocHJvcCBpbiBzb3VyY2UpIHtcbiAgICAgICAgaWYgKGhhc093blByb3BlcnR5LmNhbGwoc291cmNlLCBwcm9wKSkge1xuICAgICAgICAgICAgb2JqW3Byb3BdID0gc291cmNlW3Byb3BdO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBvYmo7XG4gIH07XG5cbiAgLy8gUmV0dXJuIGEgY29weSBvZiB0aGUgb2JqZWN0IG9ubHkgY29udGFpbmluZyB0aGUgd2hpdGVsaXN0ZWQgcHJvcGVydGllcy5cbiAgXy5waWNrID0gZnVuY3Rpb24ob2JqLCBpdGVyYXRlZSwgY29udGV4dCkge1xuICAgIHZhciByZXN1bHQgPSB7fSwga2V5O1xuICAgIGlmIChvYmogPT0gbnVsbCkgcmV0dXJuIHJlc3VsdDtcbiAgICBpZiAoXy5pc0Z1bmN0aW9uKGl0ZXJhdGVlKSkge1xuICAgICAgaXRlcmF0ZWUgPSBjcmVhdGVDYWxsYmFjayhpdGVyYXRlZSwgY29udGV4dCk7XG4gICAgICBmb3IgKGtleSBpbiBvYmopIHtcbiAgICAgICAgdmFyIHZhbHVlID0gb2JqW2tleV07XG4gICAgICAgIGlmIChpdGVyYXRlZSh2YWx1ZSwga2V5LCBvYmopKSByZXN1bHRba2V5XSA9IHZhbHVlO1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICB2YXIga2V5cyA9IGNvbmNhdC5hcHBseShbXSwgc2xpY2UuY2FsbChhcmd1bWVudHMsIDEpKTtcbiAgICAgIG9iaiA9IG5ldyBPYmplY3Qob2JqKTtcbiAgICAgIGZvciAodmFyIGkgPSAwLCBsZW5ndGggPSBrZXlzLmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGtleSA9IGtleXNbaV07XG4gICAgICAgIGlmIChrZXkgaW4gb2JqKSByZXN1bHRba2V5XSA9IG9ialtrZXldO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9O1xuXG4gICAvLyBSZXR1cm4gYSBjb3B5IG9mIHRoZSBvYmplY3Qgd2l0aG91dCB0aGUgYmxhY2tsaXN0ZWQgcHJvcGVydGllcy5cbiAgXy5vbWl0ID0gZnVuY3Rpb24ob2JqLCBpdGVyYXRlZSwgY29udGV4dCkge1xuICAgIGlmIChfLmlzRnVuY3Rpb24oaXRlcmF0ZWUpKSB7XG4gICAgICBpdGVyYXRlZSA9IF8ubmVnYXRlKGl0ZXJhdGVlKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdmFyIGtleXMgPSBfLm1hcChjb25jYXQuYXBwbHkoW10sIHNsaWNlLmNhbGwoYXJndW1lbnRzLCAxKSksIFN0cmluZyk7XG4gICAgICBpdGVyYXRlZSA9IGZ1bmN0aW9uKHZhbHVlLCBrZXkpIHtcbiAgICAgICAgcmV0dXJuICFfLmNvbnRhaW5zKGtleXMsIGtleSk7XG4gICAgICB9O1xuICAgIH1cbiAgICByZXR1cm4gXy5waWNrKG9iaiwgaXRlcmF0ZWUsIGNvbnRleHQpO1xuICB9O1xuXG4gIC8vIEZpbGwgaW4gYSBnaXZlbiBvYmplY3Qgd2l0aCBkZWZhdWx0IHByb3BlcnRpZXMuXG4gIF8uZGVmYXVsdHMgPSBmdW5jdGlvbihvYmopIHtcbiAgICBpZiAoIV8uaXNPYmplY3Qob2JqKSkgcmV0dXJuIG9iajtcbiAgICBmb3IgKHZhciBpID0gMSwgbGVuZ3RoID0gYXJndW1lbnRzLmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICB2YXIgc291cmNlID0gYXJndW1lbnRzW2ldO1xuICAgICAgZm9yICh2YXIgcHJvcCBpbiBzb3VyY2UpIHtcbiAgICAgICAgaWYgKG9ialtwcm9wXSA9PT0gdm9pZCAwKSBvYmpbcHJvcF0gPSBzb3VyY2VbcHJvcF07XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBvYmo7XG4gIH07XG5cbiAgLy8gQ3JlYXRlIGEgKHNoYWxsb3ctY2xvbmVkKSBkdXBsaWNhdGUgb2YgYW4gb2JqZWN0LlxuICBfLmNsb25lID0gZnVuY3Rpb24ob2JqKSB7XG4gICAgaWYgKCFfLmlzT2JqZWN0KG9iaikpIHJldHVybiBvYmo7XG4gICAgcmV0dXJuIF8uaXNBcnJheShvYmopID8gb2JqLnNsaWNlKCkgOiBfLmV4dGVuZCh7fSwgb2JqKTtcbiAgfTtcblxuICAvLyBJbnZva2VzIGludGVyY2VwdG9yIHdpdGggdGhlIG9iaiwgYW5kIHRoZW4gcmV0dXJucyBvYmouXG4gIC8vIFRoZSBwcmltYXJ5IHB1cnBvc2Ugb2YgdGhpcyBtZXRob2QgaXMgdG8gXCJ0YXAgaW50b1wiIGEgbWV0aG9kIGNoYWluLCBpblxuICAvLyBvcmRlciB0byBwZXJmb3JtIG9wZXJhdGlvbnMgb24gaW50ZXJtZWRpYXRlIHJlc3VsdHMgd2l0aGluIHRoZSBjaGFpbi5cbiAgXy50YXAgPSBmdW5jdGlvbihvYmosIGludGVyY2VwdG9yKSB7XG4gICAgaW50ZXJjZXB0b3Iob2JqKTtcbiAgICByZXR1cm4gb2JqO1xuICB9O1xuXG4gIC8vIEludGVybmFsIHJlY3Vyc2l2ZSBjb21wYXJpc29uIGZ1bmN0aW9uIGZvciBgaXNFcXVhbGAuXG4gIHZhciBlcSA9IGZ1bmN0aW9uKGEsIGIsIGFTdGFjaywgYlN0YWNrKSB7XG4gICAgLy8gSWRlbnRpY2FsIG9iamVjdHMgYXJlIGVxdWFsLiBgMCA9PT0gLTBgLCBidXQgdGhleSBhcmVuJ3QgaWRlbnRpY2FsLlxuICAgIC8vIFNlZSB0aGUgW0hhcm1vbnkgYGVnYWxgIHByb3Bvc2FsXShodHRwOi8vd2lraS5lY21hc2NyaXB0Lm9yZy9kb2t1LnBocD9pZD1oYXJtb255OmVnYWwpLlxuICAgIGlmIChhID09PSBiKSByZXR1cm4gYSAhPT0gMCB8fCAxIC8gYSA9PT0gMSAvIGI7XG4gICAgLy8gQSBzdHJpY3QgY29tcGFyaXNvbiBpcyBuZWNlc3NhcnkgYmVjYXVzZSBgbnVsbCA9PSB1bmRlZmluZWRgLlxuICAgIGlmIChhID09IG51bGwgfHwgYiA9PSBudWxsKSByZXR1cm4gYSA9PT0gYjtcbiAgICAvLyBVbndyYXAgYW55IHdyYXBwZWQgb2JqZWN0cy5cbiAgICBpZiAoYSBpbnN0YW5jZW9mIF8pIGEgPSBhLl93cmFwcGVkO1xuICAgIGlmIChiIGluc3RhbmNlb2YgXykgYiA9IGIuX3dyYXBwZWQ7XG4gICAgLy8gQ29tcGFyZSBgW1tDbGFzc11dYCBuYW1lcy5cbiAgICB2YXIgY2xhc3NOYW1lID0gdG9TdHJpbmcuY2FsbChhKTtcbiAgICBpZiAoY2xhc3NOYW1lICE9PSB0b1N0cmluZy5jYWxsKGIpKSByZXR1cm4gZmFsc2U7XG4gICAgc3dpdGNoIChjbGFzc05hbWUpIHtcbiAgICAgIC8vIFN0cmluZ3MsIG51bWJlcnMsIHJlZ3VsYXIgZXhwcmVzc2lvbnMsIGRhdGVzLCBhbmQgYm9vbGVhbnMgYXJlIGNvbXBhcmVkIGJ5IHZhbHVlLlxuICAgICAgY2FzZSAnW29iamVjdCBSZWdFeHBdJzpcbiAgICAgIC8vIFJlZ0V4cHMgYXJlIGNvZXJjZWQgdG8gc3RyaW5ncyBmb3IgY29tcGFyaXNvbiAoTm90ZTogJycgKyAvYS9pID09PSAnL2EvaScpXG4gICAgICBjYXNlICdbb2JqZWN0IFN0cmluZ10nOlxuICAgICAgICAvLyBQcmltaXRpdmVzIGFuZCB0aGVpciBjb3JyZXNwb25kaW5nIG9iamVjdCB3cmFwcGVycyBhcmUgZXF1aXZhbGVudDsgdGh1cywgYFwiNVwiYCBpc1xuICAgICAgICAvLyBlcXVpdmFsZW50IHRvIGBuZXcgU3RyaW5nKFwiNVwiKWAuXG4gICAgICAgIHJldHVybiAnJyArIGEgPT09ICcnICsgYjtcbiAgICAgIGNhc2UgJ1tvYmplY3QgTnVtYmVyXSc6XG4gICAgICAgIC8vIGBOYU5gcyBhcmUgZXF1aXZhbGVudCwgYnV0IG5vbi1yZWZsZXhpdmUuXG4gICAgICAgIC8vIE9iamVjdChOYU4pIGlzIGVxdWl2YWxlbnQgdG8gTmFOXG4gICAgICAgIGlmICgrYSAhPT0gK2EpIHJldHVybiArYiAhPT0gK2I7XG4gICAgICAgIC8vIEFuIGBlZ2FsYCBjb21wYXJpc29uIGlzIHBlcmZvcm1lZCBmb3Igb3RoZXIgbnVtZXJpYyB2YWx1ZXMuXG4gICAgICAgIHJldHVybiArYSA9PT0gMCA/IDEgLyArYSA9PT0gMSAvIGIgOiArYSA9PT0gK2I7XG4gICAgICBjYXNlICdbb2JqZWN0IERhdGVdJzpcbiAgICAgIGNhc2UgJ1tvYmplY3QgQm9vbGVhbl0nOlxuICAgICAgICAvLyBDb2VyY2UgZGF0ZXMgYW5kIGJvb2xlYW5zIHRvIG51bWVyaWMgcHJpbWl0aXZlIHZhbHVlcy4gRGF0ZXMgYXJlIGNvbXBhcmVkIGJ5IHRoZWlyXG4gICAgICAgIC8vIG1pbGxpc2Vjb25kIHJlcHJlc2VudGF0aW9ucy4gTm90ZSB0aGF0IGludmFsaWQgZGF0ZXMgd2l0aCBtaWxsaXNlY29uZCByZXByZXNlbnRhdGlvbnNcbiAgICAgICAgLy8gb2YgYE5hTmAgYXJlIG5vdCBlcXVpdmFsZW50LlxuICAgICAgICByZXR1cm4gK2EgPT09ICtiO1xuICAgIH1cbiAgICBpZiAodHlwZW9mIGEgIT0gJ29iamVjdCcgfHwgdHlwZW9mIGIgIT0gJ29iamVjdCcpIHJldHVybiBmYWxzZTtcbiAgICAvLyBBc3N1bWUgZXF1YWxpdHkgZm9yIGN5Y2xpYyBzdHJ1Y3R1cmVzLiBUaGUgYWxnb3JpdGhtIGZvciBkZXRlY3RpbmcgY3ljbGljXG4gICAgLy8gc3RydWN0dXJlcyBpcyBhZGFwdGVkIGZyb20gRVMgNS4xIHNlY3Rpb24gMTUuMTIuMywgYWJzdHJhY3Qgb3BlcmF0aW9uIGBKT2AuXG4gICAgdmFyIGxlbmd0aCA9IGFTdGFjay5sZW5ndGg7XG4gICAgd2hpbGUgKGxlbmd0aC0tKSB7XG4gICAgICAvLyBMaW5lYXIgc2VhcmNoLiBQZXJmb3JtYW5jZSBpcyBpbnZlcnNlbHkgcHJvcG9ydGlvbmFsIHRvIHRoZSBudW1iZXIgb2ZcbiAgICAgIC8vIHVuaXF1ZSBuZXN0ZWQgc3RydWN0dXJlcy5cbiAgICAgIGlmIChhU3RhY2tbbGVuZ3RoXSA9PT0gYSkgcmV0dXJuIGJTdGFja1tsZW5ndGhdID09PSBiO1xuICAgIH1cbiAgICAvLyBPYmplY3RzIHdpdGggZGlmZmVyZW50IGNvbnN0cnVjdG9ycyBhcmUgbm90IGVxdWl2YWxlbnQsIGJ1dCBgT2JqZWN0YHNcbiAgICAvLyBmcm9tIGRpZmZlcmVudCBmcmFtZXMgYXJlLlxuICAgIHZhciBhQ3RvciA9IGEuY29uc3RydWN0b3IsIGJDdG9yID0gYi5jb25zdHJ1Y3RvcjtcbiAgICBpZiAoXG4gICAgICBhQ3RvciAhPT0gYkN0b3IgJiZcbiAgICAgIC8vIEhhbmRsZSBPYmplY3QuY3JlYXRlKHgpIGNhc2VzXG4gICAgICAnY29uc3RydWN0b3InIGluIGEgJiYgJ2NvbnN0cnVjdG9yJyBpbiBiICYmXG4gICAgICAhKF8uaXNGdW5jdGlvbihhQ3RvcikgJiYgYUN0b3IgaW5zdGFuY2VvZiBhQ3RvciAmJlxuICAgICAgICBfLmlzRnVuY3Rpb24oYkN0b3IpICYmIGJDdG9yIGluc3RhbmNlb2YgYkN0b3IpXG4gICAgKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIC8vIEFkZCB0aGUgZmlyc3Qgb2JqZWN0IHRvIHRoZSBzdGFjayBvZiB0cmF2ZXJzZWQgb2JqZWN0cy5cbiAgICBhU3RhY2sucHVzaChhKTtcbiAgICBiU3RhY2sucHVzaChiKTtcbiAgICB2YXIgc2l6ZSwgcmVzdWx0O1xuICAgIC8vIFJlY3Vyc2l2ZWx5IGNvbXBhcmUgb2JqZWN0cyBhbmQgYXJyYXlzLlxuICAgIGlmIChjbGFzc05hbWUgPT09ICdbb2JqZWN0IEFycmF5XScpIHtcbiAgICAgIC8vIENvbXBhcmUgYXJyYXkgbGVuZ3RocyB0byBkZXRlcm1pbmUgaWYgYSBkZWVwIGNvbXBhcmlzb24gaXMgbmVjZXNzYXJ5LlxuICAgICAgc2l6ZSA9IGEubGVuZ3RoO1xuICAgICAgcmVzdWx0ID0gc2l6ZSA9PT0gYi5sZW5ndGg7XG4gICAgICBpZiAocmVzdWx0KSB7XG4gICAgICAgIC8vIERlZXAgY29tcGFyZSB0aGUgY29udGVudHMsIGlnbm9yaW5nIG5vbi1udW1lcmljIHByb3BlcnRpZXMuXG4gICAgICAgIHdoaWxlIChzaXplLS0pIHtcbiAgICAgICAgICBpZiAoIShyZXN1bHQgPSBlcShhW3NpemVdLCBiW3NpemVdLCBhU3RhY2ssIGJTdGFjaykpKSBicmVhaztcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICAvLyBEZWVwIGNvbXBhcmUgb2JqZWN0cy5cbiAgICAgIHZhciBrZXlzID0gXy5rZXlzKGEpLCBrZXk7XG4gICAgICBzaXplID0ga2V5cy5sZW5ndGg7XG4gICAgICAvLyBFbnN1cmUgdGhhdCBib3RoIG9iamVjdHMgY29udGFpbiB0aGUgc2FtZSBudW1iZXIgb2YgcHJvcGVydGllcyBiZWZvcmUgY29tcGFyaW5nIGRlZXAgZXF1YWxpdHkuXG4gICAgICByZXN1bHQgPSBfLmtleXMoYikubGVuZ3RoID09PSBzaXplO1xuICAgICAgaWYgKHJlc3VsdCkge1xuICAgICAgICB3aGlsZSAoc2l6ZS0tKSB7XG4gICAgICAgICAgLy8gRGVlcCBjb21wYXJlIGVhY2ggbWVtYmVyXG4gICAgICAgICAga2V5ID0ga2V5c1tzaXplXTtcbiAgICAgICAgICBpZiAoIShyZXN1bHQgPSBfLmhhcyhiLCBrZXkpICYmIGVxKGFba2V5XSwgYltrZXldLCBhU3RhY2ssIGJTdGFjaykpKSBicmVhaztcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICAvLyBSZW1vdmUgdGhlIGZpcnN0IG9iamVjdCBmcm9tIHRoZSBzdGFjayBvZiB0cmF2ZXJzZWQgb2JqZWN0cy5cbiAgICBhU3RhY2sucG9wKCk7XG4gICAgYlN0YWNrLnBvcCgpO1xuICAgIHJldHVybiByZXN1bHQ7XG4gIH07XG5cbiAgLy8gUGVyZm9ybSBhIGRlZXAgY29tcGFyaXNvbiB0byBjaGVjayBpZiB0d28gb2JqZWN0cyBhcmUgZXF1YWwuXG4gIF8uaXNFcXVhbCA9IGZ1bmN0aW9uKGEsIGIpIHtcbiAgICByZXR1cm4gZXEoYSwgYiwgW10sIFtdKTtcbiAgfTtcblxuICAvLyBJcyBhIGdpdmVuIGFycmF5LCBzdHJpbmcsIG9yIG9iamVjdCBlbXB0eT9cbiAgLy8gQW4gXCJlbXB0eVwiIG9iamVjdCBoYXMgbm8gZW51bWVyYWJsZSBvd24tcHJvcGVydGllcy5cbiAgXy5pc0VtcHR5ID0gZnVuY3Rpb24ob2JqKSB7XG4gICAgaWYgKG9iaiA9PSBudWxsKSByZXR1cm4gdHJ1ZTtcbiAgICBpZiAoXy5pc0FycmF5KG9iaikgfHwgXy5pc1N0cmluZyhvYmopIHx8IF8uaXNBcmd1bWVudHMob2JqKSkgcmV0dXJuIG9iai5sZW5ndGggPT09IDA7XG4gICAgZm9yICh2YXIga2V5IGluIG9iaikgaWYgKF8uaGFzKG9iaiwga2V5KSkgcmV0dXJuIGZhbHNlO1xuICAgIHJldHVybiB0cnVlO1xuICB9O1xuXG4gIC8vIElzIGEgZ2l2ZW4gdmFsdWUgYSBET00gZWxlbWVudD9cbiAgXy5pc0VsZW1lbnQgPSBmdW5jdGlvbihvYmopIHtcbiAgICByZXR1cm4gISEob2JqICYmIG9iai5ub2RlVHlwZSA9PT0gMSk7XG4gIH07XG5cbiAgLy8gSXMgYSBnaXZlbiB2YWx1ZSBhbiBhcnJheT9cbiAgLy8gRGVsZWdhdGVzIHRvIEVDTUE1J3MgbmF0aXZlIEFycmF5LmlzQXJyYXlcbiAgXy5pc0FycmF5ID0gbmF0aXZlSXNBcnJheSB8fCBmdW5jdGlvbihvYmopIHtcbiAgICByZXR1cm4gdG9TdHJpbmcuY2FsbChvYmopID09PSAnW29iamVjdCBBcnJheV0nO1xuICB9O1xuXG4gIC8vIElzIGEgZ2l2ZW4gdmFyaWFibGUgYW4gb2JqZWN0P1xuICBfLmlzT2JqZWN0ID0gZnVuY3Rpb24ob2JqKSB7XG4gICAgdmFyIHR5cGUgPSB0eXBlb2Ygb2JqO1xuICAgIHJldHVybiB0eXBlID09PSAnZnVuY3Rpb24nIHx8IHR5cGUgPT09ICdvYmplY3QnICYmICEhb2JqO1xuICB9O1xuXG4gIC8vIEFkZCBzb21lIGlzVHlwZSBtZXRob2RzOiBpc0FyZ3VtZW50cywgaXNGdW5jdGlvbiwgaXNTdHJpbmcsIGlzTnVtYmVyLCBpc0RhdGUsIGlzUmVnRXhwLlxuICBfLmVhY2goWydBcmd1bWVudHMnLCAnRnVuY3Rpb24nLCAnU3RyaW5nJywgJ051bWJlcicsICdEYXRlJywgJ1JlZ0V4cCddLCBmdW5jdGlvbihuYW1lKSB7XG4gICAgX1snaXMnICsgbmFtZV0gPSBmdW5jdGlvbihvYmopIHtcbiAgICAgIHJldHVybiB0b1N0cmluZy5jYWxsKG9iaikgPT09ICdbb2JqZWN0ICcgKyBuYW1lICsgJ10nO1xuICAgIH07XG4gIH0pO1xuXG4gIC8vIERlZmluZSBhIGZhbGxiYWNrIHZlcnNpb24gb2YgdGhlIG1ldGhvZCBpbiBicm93c2VycyAoYWhlbSwgSUUpLCB3aGVyZVxuICAvLyB0aGVyZSBpc24ndCBhbnkgaW5zcGVjdGFibGUgXCJBcmd1bWVudHNcIiB0eXBlLlxuICBpZiAoIV8uaXNBcmd1bWVudHMoYXJndW1lbnRzKSkge1xuICAgIF8uaXNBcmd1bWVudHMgPSBmdW5jdGlvbihvYmopIHtcbiAgICAgIHJldHVybiBfLmhhcyhvYmosICdjYWxsZWUnKTtcbiAgICB9O1xuICB9XG5cbiAgLy8gT3B0aW1pemUgYGlzRnVuY3Rpb25gIGlmIGFwcHJvcHJpYXRlLiBXb3JrIGFyb3VuZCBhbiBJRSAxMSBidWcuXG4gIGlmICh0eXBlb2YgLy4vICE9PSAnZnVuY3Rpb24nKSB7XG4gICAgXy5pc0Z1bmN0aW9uID0gZnVuY3Rpb24ob2JqKSB7XG4gICAgICByZXR1cm4gdHlwZW9mIG9iaiA9PSAnZnVuY3Rpb24nIHx8IGZhbHNlO1xuICAgIH07XG4gIH1cblxuICAvLyBJcyBhIGdpdmVuIG9iamVjdCBhIGZpbml0ZSBudW1iZXI/XG4gIF8uaXNGaW5pdGUgPSBmdW5jdGlvbihvYmopIHtcbiAgICByZXR1cm4gaXNGaW5pdGUob2JqKSAmJiAhaXNOYU4ocGFyc2VGbG9hdChvYmopKTtcbiAgfTtcblxuICAvLyBJcyB0aGUgZ2l2ZW4gdmFsdWUgYE5hTmA/IChOYU4gaXMgdGhlIG9ubHkgbnVtYmVyIHdoaWNoIGRvZXMgbm90IGVxdWFsIGl0c2VsZikuXG4gIF8uaXNOYU4gPSBmdW5jdGlvbihvYmopIHtcbiAgICByZXR1cm4gXy5pc051bWJlcihvYmopICYmIG9iaiAhPT0gK29iajtcbiAgfTtcblxuICAvLyBJcyBhIGdpdmVuIHZhbHVlIGEgYm9vbGVhbj9cbiAgXy5pc0Jvb2xlYW4gPSBmdW5jdGlvbihvYmopIHtcbiAgICByZXR1cm4gb2JqID09PSB0cnVlIHx8IG9iaiA9PT0gZmFsc2UgfHwgdG9TdHJpbmcuY2FsbChvYmopID09PSAnW29iamVjdCBCb29sZWFuXSc7XG4gIH07XG5cbiAgLy8gSXMgYSBnaXZlbiB2YWx1ZSBlcXVhbCB0byBudWxsP1xuICBfLmlzTnVsbCA9IGZ1bmN0aW9uKG9iaikge1xuICAgIHJldHVybiBvYmogPT09IG51bGw7XG4gIH07XG5cbiAgLy8gSXMgYSBnaXZlbiB2YXJpYWJsZSB1bmRlZmluZWQ/XG4gIF8uaXNVbmRlZmluZWQgPSBmdW5jdGlvbihvYmopIHtcbiAgICByZXR1cm4gb2JqID09PSB2b2lkIDA7XG4gIH07XG5cbiAgLy8gU2hvcnRjdXQgZnVuY3Rpb24gZm9yIGNoZWNraW5nIGlmIGFuIG9iamVjdCBoYXMgYSBnaXZlbiBwcm9wZXJ0eSBkaXJlY3RseVxuICAvLyBvbiBpdHNlbGYgKGluIG90aGVyIHdvcmRzLCBub3Qgb24gYSBwcm90b3R5cGUpLlxuICBfLmhhcyA9IGZ1bmN0aW9uKG9iaiwga2V5KSB7XG4gICAgcmV0dXJuIG9iaiAhPSBudWxsICYmIGhhc093blByb3BlcnR5LmNhbGwob2JqLCBrZXkpO1xuICB9O1xuXG4gIC8vIFV0aWxpdHkgRnVuY3Rpb25zXG4gIC8vIC0tLS0tLS0tLS0tLS0tLS0tXG5cbiAgLy8gUnVuIFVuZGVyc2NvcmUuanMgaW4gKm5vQ29uZmxpY3QqIG1vZGUsIHJldHVybmluZyB0aGUgYF9gIHZhcmlhYmxlIHRvIGl0c1xuICAvLyBwcmV2aW91cyBvd25lci4gUmV0dXJucyBhIHJlZmVyZW5jZSB0byB0aGUgVW5kZXJzY29yZSBvYmplY3QuXG4gIF8ubm9Db25mbGljdCA9IGZ1bmN0aW9uKCkge1xuICAgIHJvb3QuXyA9IHByZXZpb3VzVW5kZXJzY29yZTtcbiAgICByZXR1cm4gdGhpcztcbiAgfTtcblxuICAvLyBLZWVwIHRoZSBpZGVudGl0eSBmdW5jdGlvbiBhcm91bmQgZm9yIGRlZmF1bHQgaXRlcmF0ZWVzLlxuICBfLmlkZW50aXR5ID0gZnVuY3Rpb24odmFsdWUpIHtcbiAgICByZXR1cm4gdmFsdWU7XG4gIH07XG5cbiAgXy5jb25zdGFudCA9IGZ1bmN0aW9uKHZhbHVlKSB7XG4gICAgcmV0dXJuIGZ1bmN0aW9uKCkge1xuICAgICAgcmV0dXJuIHZhbHVlO1xuICAgIH07XG4gIH07XG5cbiAgXy5ub29wID0gZnVuY3Rpb24oKXt9O1xuXG4gIF8ucHJvcGVydHkgPSBmdW5jdGlvbihrZXkpIHtcbiAgICByZXR1cm4gZnVuY3Rpb24ob2JqKSB7XG4gICAgICByZXR1cm4gb2JqW2tleV07XG4gICAgfTtcbiAgfTtcblxuICAvLyBSZXR1cm5zIGEgcHJlZGljYXRlIGZvciBjaGVja2luZyB3aGV0aGVyIGFuIG9iamVjdCBoYXMgYSBnaXZlbiBzZXQgb2YgYGtleTp2YWx1ZWAgcGFpcnMuXG4gIF8ubWF0Y2hlcyA9IGZ1bmN0aW9uKGF0dHJzKSB7XG4gICAgdmFyIHBhaXJzID0gXy5wYWlycyhhdHRycyksIGxlbmd0aCA9IHBhaXJzLmxlbmd0aDtcbiAgICByZXR1cm4gZnVuY3Rpb24ob2JqKSB7XG4gICAgICBpZiAob2JqID09IG51bGwpIHJldHVybiAhbGVuZ3RoO1xuICAgICAgb2JqID0gbmV3IE9iamVjdChvYmopO1xuICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgICAgICB2YXIgcGFpciA9IHBhaXJzW2ldLCBrZXkgPSBwYWlyWzBdO1xuICAgICAgICBpZiAocGFpclsxXSAhPT0gb2JqW2tleV0gfHwgIShrZXkgaW4gb2JqKSkgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHRydWU7XG4gICAgfTtcbiAgfTtcblxuICAvLyBSdW4gYSBmdW5jdGlvbiAqKm4qKiB0aW1lcy5cbiAgXy50aW1lcyA9IGZ1bmN0aW9uKG4sIGl0ZXJhdGVlLCBjb250ZXh0KSB7XG4gICAgdmFyIGFjY3VtID0gQXJyYXkoTWF0aC5tYXgoMCwgbikpO1xuICAgIGl0ZXJhdGVlID0gY3JlYXRlQ2FsbGJhY2soaXRlcmF0ZWUsIGNvbnRleHQsIDEpO1xuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgbjsgaSsrKSBhY2N1bVtpXSA9IGl0ZXJhdGVlKGkpO1xuICAgIHJldHVybiBhY2N1bTtcbiAgfTtcblxuICAvLyBSZXR1cm4gYSByYW5kb20gaW50ZWdlciBiZXR3ZWVuIG1pbiBhbmQgbWF4IChpbmNsdXNpdmUpLlxuICBfLnJhbmRvbSA9IGZ1bmN0aW9uKG1pbiwgbWF4KSB7XG4gICAgaWYgKG1heCA9PSBudWxsKSB7XG4gICAgICBtYXggPSBtaW47XG4gICAgICBtaW4gPSAwO1xuICAgIH1cbiAgICByZXR1cm4gbWluICsgTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpICogKG1heCAtIG1pbiArIDEpKTtcbiAgfTtcblxuICAvLyBBIChwb3NzaWJseSBmYXN0ZXIpIHdheSB0byBnZXQgdGhlIGN1cnJlbnQgdGltZXN0YW1wIGFzIGFuIGludGVnZXIuXG4gIF8ubm93ID0gRGF0ZS5ub3cgfHwgZnVuY3Rpb24oKSB7XG4gICAgcmV0dXJuIG5ldyBEYXRlKCkuZ2V0VGltZSgpO1xuICB9O1xuXG4gICAvLyBMaXN0IG9mIEhUTUwgZW50aXRpZXMgZm9yIGVzY2FwaW5nLlxuICB2YXIgZXNjYXBlTWFwID0ge1xuICAgICcmJzogJyZhbXA7JyxcbiAgICAnPCc6ICcmbHQ7JyxcbiAgICAnPic6ICcmZ3Q7JyxcbiAgICAnXCInOiAnJnF1b3Q7JyxcbiAgICBcIidcIjogJyYjeDI3OycsXG4gICAgJ2AnOiAnJiN4NjA7J1xuICB9O1xuICB2YXIgdW5lc2NhcGVNYXAgPSBfLmludmVydChlc2NhcGVNYXApO1xuXG4gIC8vIEZ1bmN0aW9ucyBmb3IgZXNjYXBpbmcgYW5kIHVuZXNjYXBpbmcgc3RyaW5ncyB0by9mcm9tIEhUTUwgaW50ZXJwb2xhdGlvbi5cbiAgdmFyIGNyZWF0ZUVzY2FwZXIgPSBmdW5jdGlvbihtYXApIHtcbiAgICB2YXIgZXNjYXBlciA9IGZ1bmN0aW9uKG1hdGNoKSB7XG4gICAgICByZXR1cm4gbWFwW21hdGNoXTtcbiAgICB9O1xuICAgIC8vIFJlZ2V4ZXMgZm9yIGlkZW50aWZ5aW5nIGEga2V5IHRoYXQgbmVlZHMgdG8gYmUgZXNjYXBlZFxuICAgIHZhciBzb3VyY2UgPSAnKD86JyArIF8ua2V5cyhtYXApLmpvaW4oJ3wnKSArICcpJztcbiAgICB2YXIgdGVzdFJlZ2V4cCA9IFJlZ0V4cChzb3VyY2UpO1xuICAgIHZhciByZXBsYWNlUmVnZXhwID0gUmVnRXhwKHNvdXJjZSwgJ2cnKTtcbiAgICByZXR1cm4gZnVuY3Rpb24oc3RyaW5nKSB7XG4gICAgICBzdHJpbmcgPSBzdHJpbmcgPT0gbnVsbCA/ICcnIDogJycgKyBzdHJpbmc7XG4gICAgICByZXR1cm4gdGVzdFJlZ2V4cC50ZXN0KHN0cmluZykgPyBzdHJpbmcucmVwbGFjZShyZXBsYWNlUmVnZXhwLCBlc2NhcGVyKSA6IHN0cmluZztcbiAgICB9O1xuICB9O1xuICBfLmVzY2FwZSA9IGNyZWF0ZUVzY2FwZXIoZXNjYXBlTWFwKTtcbiAgXy51bmVzY2FwZSA9IGNyZWF0ZUVzY2FwZXIodW5lc2NhcGVNYXApO1xuXG4gIC8vIElmIHRoZSB2YWx1ZSBvZiB0aGUgbmFtZWQgYHByb3BlcnR5YCBpcyBhIGZ1bmN0aW9uIHRoZW4gaW52b2tlIGl0IHdpdGggdGhlXG4gIC8vIGBvYmplY3RgIGFzIGNvbnRleHQ7IG90aGVyd2lzZSwgcmV0dXJuIGl0LlxuICBfLnJlc3VsdCA9IGZ1bmN0aW9uKG9iamVjdCwgcHJvcGVydHkpIHtcbiAgICBpZiAob2JqZWN0ID09IG51bGwpIHJldHVybiB2b2lkIDA7XG4gICAgdmFyIHZhbHVlID0gb2JqZWN0W3Byb3BlcnR5XTtcbiAgICByZXR1cm4gXy5pc0Z1bmN0aW9uKHZhbHVlKSA/IG9iamVjdFtwcm9wZXJ0eV0oKSA6IHZhbHVlO1xuICB9O1xuXG4gIC8vIEdlbmVyYXRlIGEgdW5pcXVlIGludGVnZXIgaWQgKHVuaXF1ZSB3aXRoaW4gdGhlIGVudGlyZSBjbGllbnQgc2Vzc2lvbikuXG4gIC8vIFVzZWZ1bCBmb3IgdGVtcG9yYXJ5IERPTSBpZHMuXG4gIHZhciBpZENvdW50ZXIgPSAwO1xuICBfLnVuaXF1ZUlkID0gZnVuY3Rpb24ocHJlZml4KSB7XG4gICAgdmFyIGlkID0gKytpZENvdW50ZXIgKyAnJztcbiAgICByZXR1cm4gcHJlZml4ID8gcHJlZml4ICsgaWQgOiBpZDtcbiAgfTtcblxuICAvLyBCeSBkZWZhdWx0LCBVbmRlcnNjb3JlIHVzZXMgRVJCLXN0eWxlIHRlbXBsYXRlIGRlbGltaXRlcnMsIGNoYW5nZSB0aGVcbiAgLy8gZm9sbG93aW5nIHRlbXBsYXRlIHNldHRpbmdzIHRvIHVzZSBhbHRlcm5hdGl2ZSBkZWxpbWl0ZXJzLlxuICBfLnRlbXBsYXRlU2V0dGluZ3MgPSB7XG4gICAgZXZhbHVhdGUgICAgOiAvPCUoW1xcc1xcU10rPyklPi9nLFxuICAgIGludGVycG9sYXRlIDogLzwlPShbXFxzXFxTXSs/KSU+L2csXG4gICAgZXNjYXBlICAgICAgOiAvPCUtKFtcXHNcXFNdKz8pJT4vZ1xuICB9O1xuXG4gIC8vIFdoZW4gY3VzdG9taXppbmcgYHRlbXBsYXRlU2V0dGluZ3NgLCBpZiB5b3UgZG9uJ3Qgd2FudCB0byBkZWZpbmUgYW5cbiAgLy8gaW50ZXJwb2xhdGlvbiwgZXZhbHVhdGlvbiBvciBlc2NhcGluZyByZWdleCwgd2UgbmVlZCBvbmUgdGhhdCBpc1xuICAvLyBndWFyYW50ZWVkIG5vdCB0byBtYXRjaC5cbiAgdmFyIG5vTWF0Y2ggPSAvKC4pXi87XG5cbiAgLy8gQ2VydGFpbiBjaGFyYWN0ZXJzIG5lZWQgdG8gYmUgZXNjYXBlZCBzbyB0aGF0IHRoZXkgY2FuIGJlIHB1dCBpbnRvIGFcbiAgLy8gc3RyaW5nIGxpdGVyYWwuXG4gIHZhciBlc2NhcGVzID0ge1xuICAgIFwiJ1wiOiAgICAgIFwiJ1wiLFxuICAgICdcXFxcJzogICAgICdcXFxcJyxcbiAgICAnXFxyJzogICAgICdyJyxcbiAgICAnXFxuJzogICAgICduJyxcbiAgICAnXFx1MjAyOCc6ICd1MjAyOCcsXG4gICAgJ1xcdTIwMjknOiAndTIwMjknXG4gIH07XG5cbiAgdmFyIGVzY2FwZXIgPSAvXFxcXHwnfFxccnxcXG58XFx1MjAyOHxcXHUyMDI5L2c7XG5cbiAgdmFyIGVzY2FwZUNoYXIgPSBmdW5jdGlvbihtYXRjaCkge1xuICAgIHJldHVybiAnXFxcXCcgKyBlc2NhcGVzW21hdGNoXTtcbiAgfTtcblxuICAvLyBKYXZhU2NyaXB0IG1pY3JvLXRlbXBsYXRpbmcsIHNpbWlsYXIgdG8gSm9obiBSZXNpZydzIGltcGxlbWVudGF0aW9uLlxuICAvLyBVbmRlcnNjb3JlIHRlbXBsYXRpbmcgaGFuZGxlcyBhcmJpdHJhcnkgZGVsaW1pdGVycywgcHJlc2VydmVzIHdoaXRlc3BhY2UsXG4gIC8vIGFuZCBjb3JyZWN0bHkgZXNjYXBlcyBxdW90ZXMgd2l0aGluIGludGVycG9sYXRlZCBjb2RlLlxuICAvLyBOQjogYG9sZFNldHRpbmdzYCBvbmx5IGV4aXN0cyBmb3IgYmFja3dhcmRzIGNvbXBhdGliaWxpdHkuXG4gIF8udGVtcGxhdGUgPSBmdW5jdGlvbih0ZXh0LCBzZXR0aW5ncywgb2xkU2V0dGluZ3MpIHtcbiAgICBpZiAoIXNldHRpbmdzICYmIG9sZFNldHRpbmdzKSBzZXR0aW5ncyA9IG9sZFNldHRpbmdzO1xuICAgIHNldHRpbmdzID0gXy5kZWZhdWx0cyh7fSwgc2V0dGluZ3MsIF8udGVtcGxhdGVTZXR0aW5ncyk7XG5cbiAgICAvLyBDb21iaW5lIGRlbGltaXRlcnMgaW50byBvbmUgcmVndWxhciBleHByZXNzaW9uIHZpYSBhbHRlcm5hdGlvbi5cbiAgICB2YXIgbWF0Y2hlciA9IFJlZ0V4cChbXG4gICAgICAoc2V0dGluZ3MuZXNjYXBlIHx8IG5vTWF0Y2gpLnNvdXJjZSxcbiAgICAgIChzZXR0aW5ncy5pbnRlcnBvbGF0ZSB8fCBub01hdGNoKS5zb3VyY2UsXG4gICAgICAoc2V0dGluZ3MuZXZhbHVhdGUgfHwgbm9NYXRjaCkuc291cmNlXG4gICAgXS5qb2luKCd8JykgKyAnfCQnLCAnZycpO1xuXG4gICAgLy8gQ29tcGlsZSB0aGUgdGVtcGxhdGUgc291cmNlLCBlc2NhcGluZyBzdHJpbmcgbGl0ZXJhbHMgYXBwcm9wcmlhdGVseS5cbiAgICB2YXIgaW5kZXggPSAwO1xuICAgIHZhciBzb3VyY2UgPSBcIl9fcCs9J1wiO1xuICAgIHRleHQucmVwbGFjZShtYXRjaGVyLCBmdW5jdGlvbihtYXRjaCwgZXNjYXBlLCBpbnRlcnBvbGF0ZSwgZXZhbHVhdGUsIG9mZnNldCkge1xuICAgICAgc291cmNlICs9IHRleHQuc2xpY2UoaW5kZXgsIG9mZnNldCkucmVwbGFjZShlc2NhcGVyLCBlc2NhcGVDaGFyKTtcbiAgICAgIGluZGV4ID0gb2Zmc2V0ICsgbWF0Y2gubGVuZ3RoO1xuXG4gICAgICBpZiAoZXNjYXBlKSB7XG4gICAgICAgIHNvdXJjZSArPSBcIicrXFxuKChfX3Q9KFwiICsgZXNjYXBlICsgXCIpKT09bnVsbD8nJzpfLmVzY2FwZShfX3QpKStcXG4nXCI7XG4gICAgICB9IGVsc2UgaWYgKGludGVycG9sYXRlKSB7XG4gICAgICAgIHNvdXJjZSArPSBcIicrXFxuKChfX3Q9KFwiICsgaW50ZXJwb2xhdGUgKyBcIikpPT1udWxsPycnOl9fdCkrXFxuJ1wiO1xuICAgICAgfSBlbHNlIGlmIChldmFsdWF0ZSkge1xuICAgICAgICBzb3VyY2UgKz0gXCInO1xcblwiICsgZXZhbHVhdGUgKyBcIlxcbl9fcCs9J1wiO1xuICAgICAgfVxuXG4gICAgICAvLyBBZG9iZSBWTXMgbmVlZCB0aGUgbWF0Y2ggcmV0dXJuZWQgdG8gcHJvZHVjZSB0aGUgY29ycmVjdCBvZmZlc3QuXG4gICAgICByZXR1cm4gbWF0Y2g7XG4gICAgfSk7XG4gICAgc291cmNlICs9IFwiJztcXG5cIjtcblxuICAgIC8vIElmIGEgdmFyaWFibGUgaXMgbm90IHNwZWNpZmllZCwgcGxhY2UgZGF0YSB2YWx1ZXMgaW4gbG9jYWwgc2NvcGUuXG4gICAgaWYgKCFzZXR0aW5ncy52YXJpYWJsZSkgc291cmNlID0gJ3dpdGgob2JqfHx7fSl7XFxuJyArIHNvdXJjZSArICd9XFxuJztcblxuICAgIHNvdXJjZSA9IFwidmFyIF9fdCxfX3A9JycsX19qPUFycmF5LnByb3RvdHlwZS5qb2luLFwiICtcbiAgICAgIFwicHJpbnQ9ZnVuY3Rpb24oKXtfX3ArPV9fai5jYWxsKGFyZ3VtZW50cywnJyk7fTtcXG5cIiArXG4gICAgICBzb3VyY2UgKyAncmV0dXJuIF9fcDtcXG4nO1xuXG4gICAgdHJ5IHtcbiAgICAgIHZhciByZW5kZXIgPSBuZXcgRnVuY3Rpb24oc2V0dGluZ3MudmFyaWFibGUgfHwgJ29iaicsICdfJywgc291cmNlKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICBlLnNvdXJjZSA9IHNvdXJjZTtcbiAgICAgIHRocm93IGU7XG4gICAgfVxuXG4gICAgdmFyIHRlbXBsYXRlID0gZnVuY3Rpb24oZGF0YSkge1xuICAgICAgcmV0dXJuIHJlbmRlci5jYWxsKHRoaXMsIGRhdGEsIF8pO1xuICAgIH07XG5cbiAgICAvLyBQcm92aWRlIHRoZSBjb21waWxlZCBzb3VyY2UgYXMgYSBjb252ZW5pZW5jZSBmb3IgcHJlY29tcGlsYXRpb24uXG4gICAgdmFyIGFyZ3VtZW50ID0gc2V0dGluZ3MudmFyaWFibGUgfHwgJ29iaic7XG4gICAgdGVtcGxhdGUuc291cmNlID0gJ2Z1bmN0aW9uKCcgKyBhcmd1bWVudCArICcpe1xcbicgKyBzb3VyY2UgKyAnfSc7XG5cbiAgICByZXR1cm4gdGVtcGxhdGU7XG4gIH07XG5cbiAgLy8gQWRkIGEgXCJjaGFpblwiIGZ1bmN0aW9uLiBTdGFydCBjaGFpbmluZyBhIHdyYXBwZWQgVW5kZXJzY29yZSBvYmplY3QuXG4gIF8uY2hhaW4gPSBmdW5jdGlvbihvYmopIHtcbiAgICB2YXIgaW5zdGFuY2UgPSBfKG9iaik7XG4gICAgaW5zdGFuY2UuX2NoYWluID0gdHJ1ZTtcbiAgICByZXR1cm4gaW5zdGFuY2U7XG4gIH07XG5cbiAgLy8gT09QXG4gIC8vIC0tLS0tLS0tLS0tLS0tLVxuICAvLyBJZiBVbmRlcnNjb3JlIGlzIGNhbGxlZCBhcyBhIGZ1bmN0aW9uLCBpdCByZXR1cm5zIGEgd3JhcHBlZCBvYmplY3QgdGhhdFxuICAvLyBjYW4gYmUgdXNlZCBPTy1zdHlsZS4gVGhpcyB3cmFwcGVyIGhvbGRzIGFsdGVyZWQgdmVyc2lvbnMgb2YgYWxsIHRoZVxuICAvLyB1bmRlcnNjb3JlIGZ1bmN0aW9ucy4gV3JhcHBlZCBvYmplY3RzIG1heSBiZSBjaGFpbmVkLlxuXG4gIC8vIEhlbHBlciBmdW5jdGlvbiB0byBjb250aW51ZSBjaGFpbmluZyBpbnRlcm1lZGlhdGUgcmVzdWx0cy5cbiAgdmFyIHJlc3VsdCA9IGZ1bmN0aW9uKG9iaikge1xuICAgIHJldHVybiB0aGlzLl9jaGFpbiA/IF8ob2JqKS5jaGFpbigpIDogb2JqO1xuICB9O1xuXG4gIC8vIEFkZCB5b3VyIG93biBjdXN0b20gZnVuY3Rpb25zIHRvIHRoZSBVbmRlcnNjb3JlIG9iamVjdC5cbiAgXy5taXhpbiA9IGZ1bmN0aW9uKG9iaikge1xuICAgIF8uZWFjaChfLmZ1bmN0aW9ucyhvYmopLCBmdW5jdGlvbihuYW1lKSB7XG4gICAgICB2YXIgZnVuYyA9IF9bbmFtZV0gPSBvYmpbbmFtZV07XG4gICAgICBfLnByb3RvdHlwZVtuYW1lXSA9IGZ1bmN0aW9uKCkge1xuICAgICAgICB2YXIgYXJncyA9IFt0aGlzLl93cmFwcGVkXTtcbiAgICAgICAgcHVzaC5hcHBseShhcmdzLCBhcmd1bWVudHMpO1xuICAgICAgICByZXR1cm4gcmVzdWx0LmNhbGwodGhpcywgZnVuYy5hcHBseShfLCBhcmdzKSk7XG4gICAgICB9O1xuICAgIH0pO1xuICB9O1xuXG4gIC8vIEFkZCBhbGwgb2YgdGhlIFVuZGVyc2NvcmUgZnVuY3Rpb25zIHRvIHRoZSB3cmFwcGVyIG9iamVjdC5cbiAgXy5taXhpbihfKTtcblxuICAvLyBBZGQgYWxsIG11dGF0b3IgQXJyYXkgZnVuY3Rpb25zIHRvIHRoZSB3cmFwcGVyLlxuICBfLmVhY2goWydwb3AnLCAncHVzaCcsICdyZXZlcnNlJywgJ3NoaWZ0JywgJ3NvcnQnLCAnc3BsaWNlJywgJ3Vuc2hpZnQnXSwgZnVuY3Rpb24obmFtZSkge1xuICAgIHZhciBtZXRob2QgPSBBcnJheVByb3RvW25hbWVdO1xuICAgIF8ucHJvdG90eXBlW25hbWVdID0gZnVuY3Rpb24oKSB7XG4gICAgICB2YXIgb2JqID0gdGhpcy5fd3JhcHBlZDtcbiAgICAgIG1ldGhvZC5hcHBseShvYmosIGFyZ3VtZW50cyk7XG4gICAgICBpZiAoKG5hbWUgPT09ICdzaGlmdCcgfHwgbmFtZSA9PT0gJ3NwbGljZScpICYmIG9iai5sZW5ndGggPT09IDApIGRlbGV0ZSBvYmpbMF07XG4gICAgICByZXR1cm4gcmVzdWx0LmNhbGwodGhpcywgb2JqKTtcbiAgICB9O1xuICB9KTtcblxuICAvLyBBZGQgYWxsIGFjY2Vzc29yIEFycmF5IGZ1bmN0aW9ucyB0byB0aGUgd3JhcHBlci5cbiAgXy5lYWNoKFsnY29uY2F0JywgJ2pvaW4nLCAnc2xpY2UnXSwgZnVuY3Rpb24obmFtZSkge1xuICAgIHZhciBtZXRob2QgPSBBcnJheVByb3RvW25hbWVdO1xuICAgIF8ucHJvdG90eXBlW25hbWVdID0gZnVuY3Rpb24oKSB7XG4gICAgICByZXR1cm4gcmVzdWx0LmNhbGwodGhpcywgbWV0aG9kLmFwcGx5KHRoaXMuX3dyYXBwZWQsIGFyZ3VtZW50cykpO1xuICAgIH07XG4gIH0pO1xuXG4gIC8vIEV4dHJhY3RzIHRoZSByZXN1bHQgZnJvbSBhIHdyYXBwZWQgYW5kIGNoYWluZWQgb2JqZWN0LlxuICBfLnByb3RvdHlwZS52YWx1ZSA9IGZ1bmN0aW9uKCkge1xuICAgIHJldHVybiB0aGlzLl93cmFwcGVkO1xuICB9O1xuXG4gIC8vIEFNRCByZWdpc3RyYXRpb24gaGFwcGVucyBhdCB0aGUgZW5kIGZvciBjb21wYXRpYmlsaXR5IHdpdGggQU1EIGxvYWRlcnNcbiAgLy8gdGhhdCBtYXkgbm90IGVuZm9yY2UgbmV4dC10dXJuIHNlbWFudGljcyBvbiBtb2R1bGVzLiBFdmVuIHRob3VnaCBnZW5lcmFsXG4gIC8vIHByYWN0aWNlIGZvciBBTUQgcmVnaXN0cmF0aW9uIGlzIHRvIGJlIGFub255bW91cywgdW5kZXJzY29yZSByZWdpc3RlcnNcbiAgLy8gYXMgYSBuYW1lZCBtb2R1bGUgYmVjYXVzZSwgbGlrZSBqUXVlcnksIGl0IGlzIGEgYmFzZSBsaWJyYXJ5IHRoYXQgaXNcbiAgLy8gcG9wdWxhciBlbm91Z2ggdG8gYmUgYnVuZGxlZCBpbiBhIHRoaXJkIHBhcnR5IGxpYiwgYnV0IG5vdCBiZSBwYXJ0IG9mXG4gIC8vIGFuIEFNRCBsb2FkIHJlcXVlc3QuIFRob3NlIGNhc2VzIGNvdWxkIGdlbmVyYXRlIGFuIGVycm9yIHdoZW4gYW5cbiAgLy8gYW5vbnltb3VzIGRlZmluZSgpIGlzIGNhbGxlZCBvdXRzaWRlIG9mIGEgbG9hZGVyIHJlcXVlc3QuXG4gIGlmICh0eXBlb2YgZGVmaW5lID09PSAnZnVuY3Rpb24nICYmIGRlZmluZS5hbWQpIHtcbiAgICBkZWZpbmUoJ3VuZGVyc2NvcmUnLCBbXSwgZnVuY3Rpb24oKSB7XG4gICAgICByZXR1cm4gXztcbiAgICB9KTtcbiAgfVxufS5jYWxsKHRoaXMpKTtcbiIsIl8gPSByZXF1aXJlIFwidW5kZXJzY29yZVwiXG5cbiMgY2FsY3VsYXRlIHRoZSBjb25zZW5zdXMgc2VxXG4jIFRPRE86IHZlcnkgbmFpdmUgd2F5XG5tb2R1bGUuZXhwb3J0cyA9IChzZXFzKSAtPlxuXG4gIHNlcXMgPSBzZXFzLm1hcCAoZWwpIC0+IGVsLmdldCBcInNlcVwiXG4gIG9jY3MgPSBuZXcgQXJyYXkgc2Vxcy5sZW5ndGhcblxuICAjIGNvdW50IHRoZSBvY2N1cmVuY2VzIG9mIHRoZSBjaGFycyBvZiBhIHBvc2l0aW9uXG4gIF8uZWFjaCBzZXFzLCAoZWwsaSkgLT5cbiAgICBfLmVhY2ggZWwsIChjaGFyLCBwb3MpIC0+XG4gICAgICBvY2NzW3Bvc10gPSB7fSB1bmxlc3Mgb2Njc1twb3NdP1xuICAgICAgb2Njc1twb3NdW2NoYXJdID0gMCB1bmxlc3Mgb2Njc1twb3NdW2NoYXJdP1xuICAgICAgb2Njc1twb3NdW2NoYXJdKytcblxuICAjIG5vdyBwaWNrIHRoZSBjaGFyIHdpdGggbW9zdCBvY2N1cmVuY2VzXG4gIF8ucmVkdWNlIG9jY3MsIChtZW1vLG9jYykgLT5cbiAgICBrZXlzID0gXy5rZXlzIG9jY1xuICAgIG1lbW8gKz0gIF8ubWF4IGtleXMsIChrZXkpIC0+IG9jY1trZXldXG4gICwgXCJcIlxuIiwiIyBmb3IgZWFjaCBzZXF1ZW5jZVxuIyAqIGNvdW50cyB0aGUgbWF0Y2hlcyB3aXRoIHRoZSBjb25zZW5zdXMgc2VxXG4jICogZXhjbHVkaW5nIGdhcHNcbiMgKiBpZGVudGl0eSA9IG1hdGNoZWRDaGFycyAvIHRvdGFsQ2hhcnMgKGV4Y2x1ZGluZyBnYXBzKVxubW9kdWxlLmV4cG9ydHMgPSBpZGVudGl0aXlDYWxjID0gKHNlcXMsIGNvbnNlbnN1cykgLT5cbiAgIyBkbyBub3RoaW5nIG9uIGludmFsaWQgY29uc2Vuc3VzXG4gIGlmIGNvbnNlbnN1cyBpcyB1bmRlZmluZWRcbiAgICBjb25zb2xlLndhcm4gXCJidWcgb24gY29uc2VudXMgY2FsY1wiXG4gICAgcmV0dXJuXG4gIHNlcXMuZWFjaCAoc2VxT2JqKSAtPlxuICAgIHNlcSA9IHNlcU9iai5nZXQgXCJzZXFcIlxuICAgIG1hdGNoZXMgPSAwXG4gICAgdG90YWwgPSAwXG4gICAgZm9yIGkgaW4gWzAuLnNlcS5sZW5ndGggLSAxXVxuICAgICAgaWYgc2VxW2ldIGlzbnQgXCItXCIgYW5kIGNvbnNlbnN1c1tpXSBpc250IFwiLVwiXG4gICAgICAgIHRvdGFsKytcbiAgICAgICAgbWF0Y2hlcysrIGlmIHNlcVtpXSBpcyBjb25zZW5zdXNbaV1cbiAgICBzZXFPYmouc2V0IFwiaWRlbnRpdHlcIiwgbWF0Y2hlcyAvIHRvdGFsXG4iLCJtb2R1bGUuZXhwb3J0cy5jb25zZW5zdXMgPSByZXF1aXJlIFwiLi9Db25zZW5zdXNDYWxjXCJcbiIsIk1vZGVsID0gcmVxdWlyZShcImJhY2tib25lLXRoaW5cIikuTW9kZWxcblxuIyB0aGlzIGlzIGFuIGV4YW1wbGUgb2YgaG93IG9uZSBjb3VsZCBjb2xvciB0aGUgTVNBXG4jIGZlZWwgZnJlZSB0byBjcmVhdGUgeW91ciBvd24gY29sb3Igc2NoZW1lIGluIHRoZSAvY3NzL3NjaGVtZXMgZm9sZGVyXG5tb2R1bGUuZXhwb3J0cyA9IENvbG9yYXRvciA9IE1vZGVsLmV4dGVuZFxuXG4gIGRlZmF1bHRzOlxuICAgIHNjaGVtZTogXCJ0YXlsb3JcIiAjIG5hbWUgb2YgeW91ciBjb2xvciBzY2hlbWUgKGNzcyBzdWZmaXgpXG4gICAgY29sb3JCYWNrZ3JvdW5kOiB0cnVlICMgb3RoZXJ3aXNlIG9ubHkgdGhlIHRleHQgd2lsbCBiZSBjb2xvcmVkXG4gICAgc2hvd0xvd2VyQ2FzZTogdHJ1ZSAjIHVzZWQgdG8gaGlkZSBhbmQgc2hvdyBsb3dlcmNhc2UgY2hhcnMgaW4gdGhlIG92ZXJ2aWV3Ym94XG4gICAgb3BhY2l0eTogMC42ICMgb3BhY2l0eSBmb3IgdGhlIHJlc2lkdWVzXG4iLCJNb2RlbCA9IHJlcXVpcmUoXCJiYWNrYm9uZS10aGluXCIpLk1vZGVsXG5jb25zZW51cyA9IHJlcXVpcmUgXCIuLi9hbGdvL0NvbnNlbnN1c0NhbGNcIlxuXyA9IHJlcXVpcmUgXCJ1bmRlcnNjb3JlXCJcblxuIyBtb2RlbCBmb3IgY29sdW1uIHByb3BlcnRpZXMgKGxpa2UgdGhlaXIgaGlkZGVuIHN0YXRlKVxubW9kdWxlLmV4cG9ydHMgPSBDb2x1bW5zID0gTW9kZWwuZXh0ZW5kXG5cbiAgZGVmYXVsdHM6XG4gICAgc2NhbGluZzogXCJsaW5cIiAjIG9mIHRoZSBjb25zZXJ2YXRpb24gY2hhcnQgZS5nLiBcImxpblwiLCBcImV4cFwiLCBcImxvZ1wiXG5cbiAgaW5pdGlhbGl6ZTogLT5cbiAgICAjIGhpZGRlbiBjb2x1bW5zXG4gICAgQC5zZXQgXCJoaWRkZW5cIiwgW10gdW5sZXNzIEAuZ2V0KFwiaGlkZGVuXCIpP1xuXG4gICMgYXNzdW1lcyBoaWRkZW4gY29sdW1ucyBhcmUgc29ydGVkXG4gICMgQHJldHVybnMgbiBbaW50XSBudW1iZXIgb2YgaGlkZGVuIGNvbHVtbnMgdW50aWwgblxuICBjYWxjSGlkZGVuQ29sdW1uczogKG4pIC0+XG4gICAgaGlkZGVuID0gQGdldCBcImhpZGRlblwiXG4gICAgbmV3WCA9IG5cbiAgICBmb3IgaSBpbiBoaWRkZW5cbiAgICAgIGlmIGkgPD0gbmV3WFxuICAgICAgICBuZXdYKytcbiAgICBuZXdYIC0gblxuXG4gICMgY2FsY3MgY29uc2VydmF0aW9uXG4gIF9jYWxjQ29uc2VydmF0aW9uUHJlOiAoc2VxcykgLT5cblxuICAgICMgZW1lcmdlbmN5IGN1dG9mZlxuICAgIGNvbnNvbGUubG9nIHNlcXMubGVuZ3RoXG4gICAgaWYgc2Vxcy5sZW5ndGggPiAxMDAwXG4gICAgICByZXR1cm5cblxuICAgICMgY2FsYyBjb25zZW5zdXNcbiAgICBjb25zID0gY29uc2VudXMoc2VxcylcbiAgICBzZXFzID0gc2Vxcy5tYXAgKGVsKSAtPiBlbC5nZXQgXCJzZXFcIlxuICAgIG5NYXggPSAoXy5tYXggc2VxcywgKGVsKSAtPiBlbC5sZW5ndGgpLmxlbmd0aFxuXG4gICAgdG90YWwgPSBuZXcgQXJyYXkgbk1heFxuICAgIG1hdGNoZXMgPSBuZXcgQXJyYXkgbk1heFxuICAgICMgY2FsYyBkZXJpdmF0aW9uIGZyb20gY29uc2VudXNcbiAgICBfLmVhY2ggc2VxcywgKGVsLGkpIC0+XG4gICAgICBfLmVhY2ggZWwsIChjaGFyLCBwb3MpIC0+XG4gICAgICAgICNpZiBjb25zW3Bvc10gaXNudCBcIi1cIiBhbmQgbWF0Y2hlc1twb3NdIGlzbnQgZ2FwXG4gICAgICAgIHRvdGFsW3Bvc10gPSB0b3RhbFtwb3NdICsgMSB8fCAxXG4gICAgICAgIG1hdGNoZXNbcG9zXSA9IG1hdGNoZXNbcG9zXSArIDEgfHwgMSBpZiBjb25zW3Bvc10gaXMgY2hhclxuICAgIFttYXRjaGVzLCB0b3RhbCwgbk1heF1cblxuICBjYWxjQ29uc2VydmF0aW9uOiAoc2VxcykgLT5cbiAgICBpZiBAYXR0cmlidXRlcy5zY2FsaW5nIGlzIFwiZXhwXCJcbiAgICAgIHJldHVybiBAY2FsY0NvbnNlcnZhdGlvbkV4cCBzZXFzXG4gICAgZWxzZSBpZiBAYXR0cmlidXRlcy5zY2FsaW5nIGlzIFwibG9nXCJcbiAgICAgIHJldHVybiBAY2FsY0NvbnNlcnZhdGlvbkxvZyBzZXFzXG4gICAgZWxzZSBpZiBAYXR0cmlidXRlcy5zY2FsaW5nIGlzIFwibGluXCJcbiAgICAgIHJldHVybiBAY2FsY0NvbnNlcnZhdGlvbkxpbiBzZXFzXG5cbiAgIyAocGVyY2VudGFnZSBvZiBjaGFycyBvZiB0aGUgY29uc2VudXMgc2VxKVxuICBjYWxjQ29uc2VydmF0aW9uTGluOiAoc2VxcykgLT5cbiAgICBbbWF0Y2hlcyx0b3RhbCwgbk1heF0gPSBAX2NhbGNDb25zZXJ2YXRpb25QcmUgc2Vxc1xuICAgIGZvciBpIGluIFswIC4uIG5NYXggLSAxXVxuICAgICAgbWF0Y2hlc1tpXSA9IG1hdGNoZXNbaV0gLyB0b3RhbFtpXVxuICAgIEAuc2V0IFwiY29uc2VydlwiLCBtYXRjaGVzXG4gICAgbWF0Y2hlc1xuXG4gICMgKHBlcmNlbnRhZ2Ugb2YgY2hhcnMgb2YgdGhlIGNvbnNlbnVzIHNlcSlcbiAgY2FsY0NvbnNlcnZhdGlvbkxvZzogKHNlcXMpIC0+XG4gICAgW21hdGNoZXMsdG90YWwsIG5NYXhdID0gQF9jYWxjQ29uc2VydmF0aW9uUHJlIHNlcXNcbiAgICBmb3IgaSBpbiBbMCAuLiBuTWF4IC0gMV1cbiAgICAgIG1hdGNoZXNbaV0gPSBNYXRoLmxvZyhtYXRjaGVzW2ldICsgMSkgLyBNYXRoLmxvZyh0b3RhbFtpXSArIDEpXG4gICAgQC5zZXQgXCJjb25zZXJ2XCIsIG1hdGNoZXNcbiAgICBtYXRjaGVzXG5cbiAgY2FsY0NvbnNlcnZhdGlvbkV4cDogKHNlcXMpIC0+XG4gICAgW21hdGNoZXMsdG90YWwsIG5NYXhdID0gQF9jYWxjQ29uc2VydmF0aW9uUHJlIHNlcXNcbiAgICBmb3IgaSBpbiBbMCAuLiBuTWF4IC0gMV1cbiAgICAgIG1hdGNoZXNbaV0gPSBNYXRoLmV4cChtYXRjaGVzW2ldICsgMSkgLyBNYXRoLmV4cCh0b3RhbFtpXSArIDEpXG4gICAgQC5zZXQgXCJjb25zZXJ2XCIsIG1hdGNoZXNcbiAgICBtYXRjaGVzXG4iLCJNb2RlbCA9IHJlcXVpcmUoXCJiYWNrYm9uZS10aGluXCIpLk1vZGVsXG5cbiMgc2ltcGxlIHVzZXIgY29uZmlnXG5tb2R1bGUuZXhwb3J0cyA9IENvbmZpZyA9IE1vZGVsLmV4dGVuZFxuXG4gIGRlZmF1bHRzOlxuICAgIHJlZ2lzdGVyTW91c2VIb3ZlcjogZmFsc2UsXG4gICAgcmVnaXN0ZXJNb3VzZUNsaWNrczogdHJ1ZSxcbiAgICBpbXBvcnRQcm94eTogXCJodHRwczovL2NvcnMtYW55d2hlcmUuaGVyb2t1YXBwLmNvbS9cIlxuICAgIGV2ZW50QnVzOiB0cnVlXG4iLCJNb2RlbCA9IHJlcXVpcmUoXCJiYWNrYm9uZS10aGluXCIpLk1vZGVsXG5jb25zZW51c0NhbGMgPSByZXF1aXJlIFwiLi4vYWxnby9Db25zZW5zdXNDYWxjXCJcblxuIyBzaW1wbHkgc2F2ZSB0aGUgY29uc2VudXMgc2VxdWVuY2VzIGdsb2JhbGx5XG5tb2R1bGUuZXhwb3J0cyA9IENvbnNlbnVzID0gTW9kZWwuZXh0ZW5kXG5cbiAgZGVmYXVsdHM6XG4gICAgY29uc2VudXMgOiBcIlwiXG5cbiAgZ2V0Q29uc2Vuc3VzOiAoc2VxcykgLT5cbiAgICAjIGVtZXJnZW5jeSBjdXRvZmZcbiAgICBpZiBzZXFzLmxlbmd0aCA+IDEwMDBcbiAgICAgIHJldHVyblxuXG4gICAgY29ucyA9IGNvbnNlbnVzQ2FsYyhzZXFzKVxuICAgIEAuc2V0IFwiY29uc2VudXNcIiwgY29uc1xuICAgIGNvbnNcbiIsIl8gPSByZXF1aXJlIFwidW5kZXJzY29yZVwiXG5Nb2RlbCA9IHJlcXVpcmUoXCJiYWNrYm9uZS10aGluXCIpLk1vZGVsXG5cbiMgaG9sZHMgdGhlIGN1cnJlbnQgdXNlciBzZWxlY3Rpb25cblNlbGVjdGlvbiA9IE1vZGVsLmV4dGVuZFxuICBkZWZhdWx0czpcbiAgICB0eXBlOiBcInN1cGVyXCJcblxuUm93U2VsZWN0aW9uID0gU2VsZWN0aW9uLmV4dGVuZFxuICBkZWZhdWx0czogXy5leHRlbmQge30sIFNlbGVjdGlvbjo6LmRlZmF1bHRzLFxuICAgIHR5cGU6IFwicm93XCJcbiAgICBzZXFJZDogXCJcIlxuXG4gIGluUm93OiAoc2VxSWQpIC0+XG4gICAgc2VxSWQgaXMgQC5nZXQgXCJzZXFJZFwiXG5cbiAgaW5Db2x1bW46IChyb3dQb3MpIC0+XG4gICAgdHJ1ZVxuXG4gIGdldExlbmd0aDogLT5cbiAgICAxXG5cbkNvbHVtblNlbGVjdGlvbiA9IFNlbGVjdGlvbi5leHRlbmRcbiAgZGVmYXVsdHM6IF8uZXh0ZW5kIHt9LCBTZWxlY3Rpb246Oi5kZWZhdWx0cyxcbiAgICB0eXBlOiBcImNvbHVtblwiXG4gICAgeFN0YXJ0OiAtMVxuICAgIHhFbmQ6IC0xXG5cbiAgaW5Sb3c6ICgpIC0+XG4gICAgdHJ1ZVxuXG4gIGluQ29sdW1uOiAocm93UG9zKSAtPlxuICAgIHhTdGFydCA8PSByb3dQb3MgJiYgcm93UG9zIDw9IHhFbmRcblxuICBnZXRMZW5ndGg6IC0+XG4gICAgeEVuZCAtIHhTdGFydFxuXG4jIHBvcyBpcyBhIG1peGluIG9mIGNvbHVtbiBhbmQgcm93XG4jIHN0YXJ0IHdpdGggUm93IGFuZCBvbmx5IG92ZXJ3cml0ZSBcImluQ29sdW1uXCIgZnJvbSBDb2x1bW5cblBvc1NlbGVjdGlvbiA9IFJvd1NlbGVjdGlvbi5leHRlbmQgXy5leHRlbmQge30sXy5waWNrKENvbHVtblNlbGVjdGlvbixcImluQ29sdW1uXCIpLFxuICBfLnBpY2soQ29sdW1uU2VsZWN0aW9uLFwiZ2V0TGVuZ3RoXCIpXG5cbiAgIyBtZXJnZSBib3RoIGRlZmF1bHRzXG4gIGRlZmF1bHRzOiBfLmV4dGVuZCB7fSwgQ29sdW1uU2VsZWN0aW9uOjouZGVmYXVsdHMsIFJvd1NlbGVjdGlvbjo6LmRlZmF1bHRzLFxuICAgIHR5cGU6IFwicG9zXCJcblxubW9kdWxlLmV4cG9ydHMuc2VsID0gU2VsZWN0aW9uXG5tb2R1bGUuZXhwb3J0cy5wb3NzZWwgPSBQb3NTZWxlY3Rpb25cbm1vZHVsZS5leHBvcnRzLnJvd3NlbCA9IFJvd1NlbGVjdGlvblxubW9kdWxlLmV4cG9ydHMuY29sdW1uc2VsID0gQ29sdW1uU2VsZWN0aW9uXG4iLCJzZWwgPSByZXF1aXJlIFwiLi9TZWxlY3Rpb25cIlxuXyA9IHJlcXVpcmUgXCJ1bmRlcnNjb3JlXCJcbkNvbGxlY3Rpb24gPSByZXF1aXJlKFwiYmFja2JvbmUtdGhpblwiKS5Db2xsZWN0aW9uXG5cbiMgaG9sZHMgdGhlIGN1cnJlbnQgdXNlciBzZWxlY3Rpb25cbm1vZHVsZS5leHBvcnRzID0gU2VsZWN0aW9uTWFuYWdlciA9IENvbGxlY3Rpb24uZXh0ZW5kXG5cbiAgbW9kZWw6IHNlbC5zZWxcblxuICBpbml0aWFsaXplOiAoZGF0YSwgb3B0cykgLT5cbiAgICBAZyA9IG9wdHMuZ1xuXG4gICAgQGxpc3RlblRvIEBnLCBcInJlc2lkdWU6Y2xpY2tcIiwgKGUpIC0+XG4gICAgICBAX2hhbmRsZUUgZS5ldnQsIG5ldyBzZWwucG9zc2VsXG4gICAgICAgIHhTdGFydDogZS5yb3dQb3NcbiAgICAgICAgeEVuZDogZS5yb3dQb3NcbiAgICAgICAgc2VxSWQ6IGUuc2VxSWRcblxuICAgIEBsaXN0ZW5UbyBAZywgXCJyb3c6Y2xpY2tcIiwgKGUpIC0+XG4gICAgICBAX2hhbmRsZUUgZS5ldnQsIG5ldyBzZWwucm93c2VsXG4gICAgICAgIHhTdGFydDogZS5yb3dQb3NcbiAgICAgICAgeEVuZDogZS5yb3dQb3NcbiAgICAgICAgc2VxSWQ6IGUuc2VxSWRcblxuICAgIEBsaXN0ZW5UbyBAZywgXCJjb2x1bW46Y2xpY2tcIiwgKGUpIC0+XG4gICAgICBAX2hhbmRsZUUgZS5ldnQsIG5ldyBzZWwuY29sdW1uc2VsXG4gICAgICAgIHhTdGFydDogZS5yb3dQb3NcbiAgICAgICAgeEVuZDogZS5yb3dQb3MgKyBlLnN0ZXBTaXplIC0gMVxuXG4gICAgI0BsaXN0ZW5UbyBALCBcImFkZCByZXNldFwiLCAoZSkgLT5cbiAgICAgICNAX3JlZHVjZUNvbHVtbnMoKVxuXG4gIGdldFNlbEZvclJvdzogKHNlcUlkKSAtPlxuICAgIEBmaWx0ZXIgKGVsKSAtPiBlbC5pblJvdyBzZXFJZFxuXG4gIGdldFNlbEZvckNvbHVtbnM6IChyb3dQb3MpIC0+XG4gICAgQGZpbHRlciAoZWwpIC0+IGVsLmluQ29sdW1uIHJvd1Bvc1xuXG4gICMgQHJldHVybnMgYXJyYXkgb2YgYWxsIHNlbGVjdGVkIHJlc2lkdWVzIGZvciBhIHJvd1xuICBnZXRCbG9ja3NGb3JSb3c6IChzZXFJZCwgbWF4TGVuKSAtPlxuICAgIHNlbGlzID0gQGZpbHRlciAoZWwpIC0+IGVsLmluUm93IHNlcUlkXG4gICAgYmxvY2tzID0gW11cbiAgICBmb3Igc2VsaSBpbiBzZWxpc1xuICAgICAgaWYgc2VsaS5hdHRyaWJ1dGVzLnR5cGUgaXMgXCJyb3dcIlxuICAgICAgICBibG9ja3MgPSBbMC4ubWF4TGVuXVxuICAgICAgICBicmVha1xuICAgICAgZWxzZVxuICAgICAgICBibG9ja3MgPSBibG9ja3MuY29uY2F0IFtzZWxpLmF0dHJpYnV0ZXMueFN0YXJ0IC4uIHNlbGkuYXR0cmlidXRlcy54RW5kXVxuICAgIGJsb2Nrc1xuXG4gICMgQHJldHVybnMgYXJyYXkgd2l0aCBhbGwgY29sdW1ucyBiZWluZyBzZWxlY3RlZFxuICAjIGV4YW1wbGU6IDAtNC4uLiAxMi0xNCBzZWxlY3RlZCAtPiBbMCwxLDIsMyw0LDEyLDEzLDE0XVxuICBnZXRBbGxDb2x1bW5CbG9ja3M6IChjb25mKSAtPlxuICAgIG1heExlbiA9IGNvbmYubWF4TGVuXG4gICAgd2l0aFBvcyA9IGNvbmYud2l0aFBvc1xuICAgIGJsb2NrcyA9IFtdXG4gICAgaWYgY29uZi53aXRoUG9zXG4gICAgICBmaWx0ZXJlZCA9IChAZmlsdGVyIChlbCkgLT4gZWwuZ2V0KCd4U3RhcnQnKT8gKVxuICAgIGVsc2VcbiAgICAgIGZpbHRlcmVkID0gKEBmaWx0ZXIgKGVsKSAtPiBlbC5nZXQoJ3R5cGUnKSBpcyBcImNvbHVtblwiKVxuICAgIGZvciBzZWxpIGluIGZpbHRlcmVkXG4gICAgICBibG9ja3MgPSBibG9ja3MuY29uY2F0IFtzZWxpLmF0dHJpYnV0ZXMueFN0YXJ0Li5zZWxpLmF0dHJpYnV0ZXMueEVuZF1cbiAgICBibG9ja3MgPSBfLnVuaXEgYmxvY2tzXG4gICAgcmV0dXJuIGJsb2Nrc1xuXG4gICMgaW52ZXJ0cyB0aGUgY3VycmVudCBzZWxlY3Rpb24gZm9yIGNvbHVtbnNcbiAgIyBAcGFyYW0gcm93cyBbQXJyYXldIGFsbCBhdmFpbGFibGUgc2VxSWRcbiAgaW52ZXJ0Um93OiAocm93cykgLT5cbiAgICBzZWxSb3dzID0gQHdoZXJlKHR5cGU6XCJyb3dcIilcbiAgICBzZWxSb3dzID0gXy5tYXAgc2VsUm93cywgKGVsKSAtPiBlbC5hdHRyaWJ1dGVzLnNlcUlkXG4gICAgaW52ZXJ0ZWQgPSBfLmZpbHRlciByb3dzLCAoZWwpIC0+XG4gICAgICByZXR1cm4gZmFsc2UgaWYgc2VsUm93cy5pbmRleE9mKGVsKSA+PSAwICMgZXhpc3Rpbmcgc2VsZWN0aW9uXG4gICAgICB0cnVlXG4gICAgIyBtYXNzIGluc2VydFxuICAgIHMgPSBbXVxuICAgIGZvciBlbCBpbiBpbnZlcnRlZFxuICAgICAgcy5wdXNoIG5ldyBzZWwucm93c2VsKHNlcUlkOmVsKVxuICAgIGNvbnNvbGUubG9nIHNcbiAgICBAcmVzZXQgc1xuXG4gICMgaW52ZXJ0cyB0aGUgY3VycmVudCBzZWxlY3Rpb24gZm9yIHJvd3NcbiAgIyBAcGFyYW0gcm93cyBbQXJyYXldIGFsbCBhdmFpbGFibGUgcm93cyAoMC4ubWF4Lmxlbmd0aClcbiAgaW52ZXJ0Q29sOiAoY29sdW1ucykgLT5cbiAgICBzZWxDb2x1bW5zID0gQHdoZXJlKHR5cGU6XCJjb2x1bW5cIilcbiAgICBzZWxDb2x1bW5zID0gXy5yZWR1Y2Ugc2VsQ29sdW1ucywgKG1lbW8sZWwpIC0+XG4gICAgICBtZW1vLmNvbmNhdCBbZWwuYXR0cmlidXRlcy54U3RhcnQgLi4gZWwuYXR0cmlidXRlcy54RW5kXVxuICAgICwgW11cbiAgICBpbnZlcnRlZCA9IF8uZmlsdGVyIGNvbHVtbnMsIChlbCkgLT5cbiAgICAgIGlmIHNlbENvbHVtbnMuaW5kZXhPZihlbCkgPj0gMFxuICAgICAgICAjIGV4aXN0aW5nIHNlbGVjdGlvblxuICAgICAgICByZXR1cm4gZmFsc2VcbiAgICAgIHRydWVcbiAgICAjIG1hc3MgaW5zZXJ0XG4gICAgcmV0dXJuIGlmIGludmVydGVkLmxlbmd0aCA9PSAwXG4gICAgcyA9IFtdXG4gICAgY29uc29sZS5sb2cgaW52ZXJ0ZWRcbiAgICB4U3RhcnQgPSB4RW5kID0gaW52ZXJ0ZWRbMF1cbiAgICBmb3IgZWwgaW4gaW52ZXJ0ZWRcbiAgICAgIGlmIHhFbmQgKyAxIGlzIGVsXG4gICAgICAgICMgY29udGlndW91c1xuICAgICAgICB4RW5kID0gZWxcbiAgICAgIGVsc2VcbiAgICAgICAgIyBnYXAgYmV0d2VlblxuICAgICAgICBzLnB1c2ggbmV3IHNlbC5jb2x1bW5zZWwoeFN0YXJ0OnhTdGFydCwgeEVuZDogeEVuZClcbiAgICAgICAgeFN0YXJ0ID0geEVuZCA9IGVsXG4gICAgIyBjaGVjayBmb3IgbGFzdCBnYXBcbiAgICBzLnB1c2ggbmV3IHNlbC5jb2x1bW5zZWwoeFN0YXJ0OnhTdGFydCwgeEVuZDogaW52ZXJ0ZWRbaW52ZXJ0ZWQubGVuZ3RoIC0gMV0pIGlmIHhTdGFydCBpc250IHhFbmRcbiAgICBAcmVzZXQgc1xuXG4gICMgbWV0aG9kIHRvIGRlY2lkZSB3aGV0aGVyIHRvIHN0YXJ0IGEgbmV3IHNlbGVjdGlvblxuICAjIG9yIGFwcGVuZCB0byB0aGUgb2xkIG9uZSAoZGVwZW5kaW5nIHdoZXRoZXIgQ1RSTCB3YXMgcHJlc3NlZClcbiAgX2hhbmRsZUU6IChlLCBzZWxlY3Rpb24pIC0+XG4gICAgaWYgZS5jdHJsS2V5IG9yIGUubWV0YUtleVxuICAgICAgQGFkZCBzZWxlY3Rpb25cbiAgICBlbHNlXG4gICAgICBAcmVzZXQgW3NlbGVjdGlvbl1cblxuICAjIGV4cGVyaW1lbnRhbCByZWR1Y2UgbWV0aG9kIGZvciBjb2x1bW5zXG4gIF9yZWR1Y2VDb2x1bW5zOiAtPlxuICAgIEBlYWNoIChlbCwgaW5kZXgsIGFycikgLT5cbiAgICAgIGNvbHMgPSBfLmZpbHRlciBhcnIsIChlbCkgLT4gZWwuZ2V0KCd0eXBlJykgaXMgJ2NvbHVtbidcbiAgICAgIHhTdGFydCA9IGVsLmdldCgneFN0YXJ0JylcbiAgICAgIHhFbmQgPSBlbC5nZXQoJ3hFbmQnKVxuXG4gICAgICBsZWZ0cyA9IF8uZmlsdGVyIGNvbHMsIChlbCkgLT4gZWwuZ2V0KCd4RW5kJykgaXMgKHhTdGFydCAtIDEpXG4gICAgICBmb3IgbGVmdCBpbiBsZWZ0c1xuICAgICAgICBsZWZ0LnNldCAneEVuZCcsIHhTdGFydFxuXG4gICAgICByaWdodHMgPSBfLmZpbHRlciBjb2xzLCAoZWwpIC0+IGVsLmdldCgneFN0YXJ0JykgaXMgKHhFbmQgKyAxKVxuICAgICAgZm9yIHJpZ2h0IGluIHJpZ2h0c1xuICAgICAgICByaWdodC5zZXQgJ3hTdGFydCcsIHhFbmRcblxuICAgICAgaWYgbGVmdHMubGVuZ3RoID4gMCBvciByaWdodHMubGVuZ3RoID4gMFxuICAgICAgICBjb25zb2xlLmxvZyBcInJlbW92ZWQgZWxcIlxuICAgICAgICBlbC5jb2xsZWN0aW9uLnJlbW92ZSBlbFxuIiwiTW9kZWwgPSByZXF1aXJlKFwiYmFja2JvbmUtdGhpblwiKS5Nb2RlbFxuXG4jIHZpc2libGUgYXJlYXNcbm1vZHVsZS5leHBvcnRzID0gVmlzaWJpbGl0eSA9IE1vZGVsLmV4dGVuZFxuXG4gIGRlZmF1bHRzOlxuXG4gICAgIyBmb3IgdGhlIFN0YWdlXG4gICAgb3ZlcnZpZXdCb3g6IDMwXG4gICAgaGVhZGVyQm94OiAtMVxuICAgIGFsaWdubWVudEJvZHk6IDBcbiIsIk1vZGVsID0gcmVxdWlyZShcImJhY2tib25lLXRoaW5cIikuTW9kZWxcblxuIyB2aXNpYmxlIGFyZWFzXG5tb2R1bGUuZXhwb3J0cyA9IFZpc2liaWxpdHkgPSBNb2RlbC5leHRlbmRcblxuICBkZWZhdWx0czpcbiAgICBzZXF1ZW5jZXM6IHRydWVcbiAgICBtYXJrZXJzOiB0cnVlXG4gICAgbWV0YWNlbGw6IGZhbHNlXG4gICAgY29uc2VydjogdHJ1ZVxuICAgIG92ZXJ2aWV3Ym94OiBmYWxzZVxuXG4gICAgIyBhYm91dCB0aGUgbGFiZWxzXG4gICAgbGFiZWxzOiB0cnVlXG4gICAgbGFiZWxOYW1lOiB0cnVlXG4gICAgbGFiZWxJZDogdHJ1ZVxuICAgIGxhYmVsUGFydGl0aW9uOiBmYWxzZVxuICAgIGxhYmVsQ2hlY2tib3g6IGZhbHNlXG4iLCJNb2RlbCA9IHJlcXVpcmUoXCJiYWNrYm9uZS10aGluXCIpLk1vZGVsXG4jIHBpeGVsIHByb3BlcnRpZXMgZm9yIHNvbWUgY29tcG9uZW50c1xubW9kdWxlLmV4cG9ydHMgPSBab29tZXIgPSBNb2RlbC5leHRlbmRcblxuICBjb25zdHJ1Y3RvcjogKGF0dHJpYnV0ZXMsb3B0aW9ucykgLT5cbiAgICBNb2RlbC5hcHBseSBALCBhcmd1bWVudHNcbiAgICBAZyA9IG9wdGlvbnMuZ1xuICAgIEBcblxuICBkZWZhdWx0czpcblxuICAgICMgZ2VuZXJhbFxuICAgIGFsaWdubWVudFdpZHRoOiBcImF1dG9cIlxuICAgIGFsaWdubWVudEhlaWdodDogMTk1XG4gICAgY29sdW1uV2lkdGg6IDE1XG4gICAgcm93SGVpZ2h0OiAxNVxuXG4gICAgIyBsYWJlbHNcbiAgICBsYWJlbFdpZHRoOiAxMDBcbiAgICBtZXRhV2lkdGg6IDEwMFxuICAgIHRleHRWaXNpYmxlOiB0cnVlXG4gICAgbGFiZWxJZExlbmd0aDogMzBcbiAgICBsYWJlbEZvbnRzaXplOiBcIjEzcHhcIlxuICAgIGxhYmVsTGluZUhlaWdodDogXCIxM3B4XCJcblxuICAgICMgbWFya2VyXG4gICAgbWFya2VyRm9udHNpemU6IFwiMTBweFwiXG4gICAgc3RlcFNpemU6IDFcbiAgICBtYXJrZXJTdGVwU2l6ZTogMlxuXG4gICAgIyBjYW52YXNcbiAgICByZXNpZHVlRm9udDogXCIxM3B4IG1vbm9cIlxuICAgIGNhbnZhc0V2ZW50U2NhbGU6IDFcblxuICAgIGJveFJlY3RIZWlnaHQ6IDVcbiAgICBib3hSZWN0V2lkdGg6IDVcblxuICAgICMgbWVudVxuICAgIG1lbnVGb250c2l6ZTogXCIyMHB4XCJcbiAgICBtZW51SXRlbUZvbnRzaXplOiBcIjE4cHhcIlxuICAgIG1lbnVJdGVtTGluZUhlaWdodDogXCIxOHB4XCJcbiAgICBtZW51TWFyZ2luTGVmdDogXCI1cHhcIlxuICAgIG1lbnVQYWRkaW5nOiBcIjNweCA1cHggM3B4IDVweFwiXG5cbiAgICAjIGludGVybmFsIHByb3BzXG4gICAgX2FsaWdubWVudFNjcm9sbExlZnQ6IDBcbiAgICBfYWxpZ25tZW50U2Nyb2xsVG9wOiAwXG5cbiAgIyBAcGFyYW0gbiBbaW50XSBtYXhMZW5ndGggb2YgYWxsIHNlcXNcbiAgZ2V0QWxpZ25tZW50V2lkdGg6IChuKSAtPlxuICAgIGlmIEBnZXQoXCJhbGlnbm1lbnRXaWR0aFwiKSBpcyBcImF1dG9cIlxuICAgICAgQGdldChcImNvbHVtbldpZHRoXCIpICogblxuICAgIGVsc2VcbiAgICAgIEBnZXQgXCJhbGlnbm1lbnRXaWR0aFwiXG5cbiAgIyBAcGFyYW0gbiBbaW50XSBudW1iZXIgb2YgcmVzaWR1ZXMgdG8gc2Nyb2xsIHRvIHRoZSByaWdodFxuICBzZXRMZWZ0T2Zmc2V0OiAobikgLT5cbiAgICB2YWwgPSAobiAtIDEpICogQGdldCgnY29sdW1uV2lkdGgnKVxuICAgIHZhbCA9IE1hdGgubWF4IDAsIHZhbFxuICAgIEBzZXQgXCJfYWxpZ25tZW50U2Nyb2xsTGVmdFwiLCB2YWxcblxuICAjIEBwYXJhbSBuIFtpbnRdIHJvdyB0aGF0IHNob3VsZCBiZSBvbiB0b3BcbiAgc2V0VG9wT2Zmc2V0OiAobikgLT5cbiAgICB2YWwgPSAobiAtIDEpICogQGdldCgncm93SGVpZ2h0JylcbiAgICB2YWwgPSBNYXRoLm1heCAwLCB2YWxcbiAgICBAc2V0IFwiX2FsaWdubWVudFNjcm9sbFRvcFwiLHZhbFxuXG4gICMgbGVuZ3RoIG9mIGFsbCBlbGVtZW50cyBsZWZ0IHRvIHRoZSBtYWluIHNlcXVlbmNlIGJvZHk6IGxhYmVscywgbWV0YWNlbGwsIC4uXG4gIGdldExhYmVsV2lkdGg6IC0+XG4gICAgIHBhZGRpbmdMZWZ0ID0gMFxuICAgICBwYWRkaW5nTGVmdCArPSBAZ2V0IFwibGFiZWxXaWR0aFwiIGlmIEBnLnZpcy5nZXQgXCJsYWJlbHNcIlxuICAgICBwYWRkaW5nTGVmdCArPSBAZ2V0IFwibWV0YVdpZHRoXCIgaWYgQGcudmlzLmdldCBcIm1ldGFjZWxsXCJcbiAgICAgcmV0dXJuIHBhZGRpbmdMZWZ0XG5cbiAgX2FkanVzdFdpZHRoOiAoZWwsIG1vZGVsKSAtPlxuICAgIGlmIGVsLnBhcmVudE5vZGU/IGFuZCBlbC5wYXJlbnROb2RlLm9mZnNldFdpZHRoIGlzbnQgMFxuICAgICAgcGFyZW50V2lkdGggPSBlbC5wYXJlbnROb2RlLm9mZnNldFdpZHRoXG4gICAgZWxzZVxuICAgICAgcGFyZW50V2lkdGggPSBkb2N1bWVudC5ib2R5LmNsaWVudFdpZHRoIC0gMzVcblxuICAgICMgVE9ETzogZGlydHkgaGFja1xuICAgIG1heFdpZHRoID0gcGFyZW50V2lkdGggLSBAZ2V0TGFiZWxXaWR0aCgpXG4gICAgY2FsY1dpZHRoID0gQGdldEFsaWdubWVudFdpZHRoKCBtb2RlbC5nZXRNYXhMZW5ndGgoKSAtIEBnLmNvbHVtbnMuZ2V0KCdoaWRkZW4nKS5sZW5ndGgpXG4gICAgdmFsID0gTWF0aC5taW4obWF4V2lkdGgsY2FsY1dpZHRoKVxuICAgICMgcm91bmQgdG8gYSB2YWxpZCBBQSBib3hcbiAgICB2YWwgPSBNYXRoLmZsb29yKCB2YWwgLyBAZ2V0KFwiY29sdW1uV2lkdGhcIikpICogQGdldChcImNvbHVtbldpZHRoXCIpXG4gICAgQHNldCBcImFsaWdubWVudFdpZHRoXCIsIHZhbFxuICAgICNlbC5zdHlsZS53aWR0aCA9IE1hdGgubWluIGNhbGNXaWR0aCwgbWF4V2lkdGhcblxuICAjIHVwZGF0ZXMgYm90aCBzY3JvbGwgcHJvcGVydGllcyAoaWYgbmVlZGVkKVxuICBfY2hlY2tTY3JvbGxpbmc6IChzY3JvbGxPYmosIG9wdHMpIC0+XG4gICAgeFNjcm9sbCA9IHNjcm9sbE9ialswXVxuICAgIHlTY3JvbGwgPSBzY3JvbGxPYmpbMV1cblxuICAgIEBzZXQgXCJfYWxpZ25tZW50U2Nyb2xsTGVmdFwiLCB4U2Nyb2xsLCBvcHRzXG4gICAgQHNldCBcIl9hbGlnbm1lbnRTY3JvbGxUb3BcIiwgeVNjcm9sbCwgb3B0c1xuIiwibW9kdWxlLmV4cG9ydHMubXNhID0gcmVxdWlyZShcIi4vbXNhXCIpXG5cbiMgbW9kZWxzXG5tb2R1bGUuZXhwb3J0cy5tb2RlbCA9IHJlcXVpcmUoXCIuL21vZGVsXCIpXG5cbiMgZXh0cmEgcGx1Z2lucywgZXh0ZW5zaW9uc1xubW9kdWxlLmV4cG9ydHMuYWxnbyA9IHJlcXVpcmUoXCIuL2FsZ29cIilcbm1vZHVsZS5leHBvcnRzLm1lbnUgPSByZXF1aXJlKFwiLi9tZW51XCIpXG5tb2R1bGUuZXhwb3J0cy51dGlscyA9IHJlcXVpcmUoXCIuL3V0aWxzXCIpXG5cbiMgcHJvYmFibHkgbmVlZGVkIG1vcmUgb2Z0ZW5cbm1vZHVsZS5leHBvcnRzLnNlbGVjdGlvbiA9IHJlcXVpcmUoXCIuL2cvc2VsZWN0aW9uL1NlbGVjdGlvblwiKVxubW9kdWxlLmV4cG9ydHMudmlldyA9IHJlcXVpcmUoXCJiYWNrYm9uZS12aWV3alwiKVxubW9kdWxlLmV4cG9ydHMuYm9uZVZpZXcgPSByZXF1aXJlKFwiYmFja2JvbmUtY2hpbGRzXCIpXG5cbiMgY29udmVuaWVuY2Vcbm1vZHVsZS5leHBvcnRzLl8gPSByZXF1aXJlICd1bmRlcnNjb3JlJ1xubW9kdWxlLmV4cG9ydHMuJCA9IHJlcXVpcmUgJ2pib25lJ1xuXG5tb2R1bGUuZXhwb3J0cy52ZXJzaW9uID0gXCIwLjEuMFwiXG4iLCJib25lVmlldyA9IHJlcXVpcmUoXCJiYWNrYm9uZS1jaGlsZHNcIilcblxuIyBtZW51IHZpZXdzXG5JbXBvcnRNZW51ID0gcmVxdWlyZSBcIi4vdmlld3MvSW1wb3J0TWVudVwiXG5GaWx0ZXJNZW51ID0gcmVxdWlyZSBcIi4vdmlld3MvRmlsdGVyTWVudVwiXG5TZWxlY3Rpb25NZW51ID0gcmVxdWlyZSBcIi4vdmlld3MvU2VsZWN0aW9uTWVudVwiXG5WaXNNZW51ID0gcmVxdWlyZSBcIi4vdmlld3MvVmlzTWVudVwiXG5Db2xvck1lbnUgPSByZXF1aXJlIFwiLi92aWV3cy9Db2xvck1lbnVcIlxuT3JkZXJpbmdNZW51ID0gcmVxdWlyZSBcIi4vdmlld3MvT3JkZXJpbmdNZW51XCJcbkV4dHJhTWVudSA9IHJlcXVpcmUgXCIuL3ZpZXdzL0V4dHJhTWVudVwiXG5FeHBvcnRNZW51ID0gcmVxdWlyZSBcIi4vdmlld3MvRXhwb3J0TWVudVwiXG5IZWxwTWVudSA9IHJlcXVpcmUgXCIuL3ZpZXdzL0hlbHBNZW51XCJcblxuIyB0aGlzIHZlcnkgYmFzaWMgbWVudSBkZW1vbnN0cmF0ZXMgY2FsbHMgdG8gdGhlIE1TQSBjb21wb25lbnRcbm1vZHVsZS5leHBvcnRzID0gTWVudVZpZXcgPSBib25lVmlldy5leHRlbmRcblxuICBpbml0aWFsaXplOiAoZGF0YSkgLT5cbiAgICBAbXNhID0gZGF0YS5tc2FcblxuICAgIEBhZGRWaWV3ICBcIjEwX2ltcG9ydFwiLCBuZXcgSW1wb3J0TWVudSBtb2RlbDogQG1zYS5zZXFzLCBnOkBtc2EuZ1xuICAgIEBhZGRWaWV3ICBcIjIwX2ZpbHRlclwiLCBuZXcgRmlsdGVyTWVudSBtb2RlbDogQG1zYS5zZXFzLCBnOkBtc2EuZ1xuICAgIEBhZGRWaWV3ICBcIjMwX3NlbGVjdGlvblwiLCBuZXcgU2VsZWN0aW9uTWVudSBtb2RlbDogQG1zYS5zZXFzLCBnOkBtc2EuZ1xuICAgIEBhZGRWaWV3ICBcIjQwX3Zpc1wiLCBuZXcgVmlzTWVudSBtb2RlbDogQG1zYS5zZXFzLCBnOkBtc2EuZ1xuICAgIEBhZGRWaWV3ICBcIjUwX2NvbG9yXCIsIG5ldyBDb2xvck1lbnUgbW9kZWw6IEBtc2Euc2VxcywgZzpAbXNhLmdcbiAgICBAYWRkVmlldyAgXCI2MF9vcmRlcmluZ1wiLCBuZXcgT3JkZXJpbmdNZW51IG1vZGVsOiBAbXNhLnNlcXMsIGc6QG1zYS5nXG4gICAgQGFkZFZpZXcgIFwiNzBfZXh0cmFcIiwgbmV3IEV4dHJhTWVudSBtb2RlbDogQG1zYS5zZXFzLCBnOkBtc2EuZ1xuICAgIEBhZGRWaWV3ICBcIjgwX2V4cG9ydFwiLCBuZXcgRXhwb3J0TWVudSBtb2RlbDogQG1zYS5zZXFzLCBnOkBtc2EuZywgbXNhOkBtc2FcbiAgICBAYWRkVmlldyAgXCI5MF9oZWxwXCIsIG5ldyBIZWxwTWVudSAgZzpAbXNhLmdcblxuICByZW5kZXI6IC0+XG4gICAgQHJlbmRlclN1YnZpZXdzKClcbiAgICAjIG90aGVyXG4gICAgQGVsLnNldEF0dHJpYnV0ZSBcImNsYXNzXCIsIFwiYmlvanNfbXNhX21lbnViYXJcIlxuICAgIEBlbC5hcHBlbmRDaGlsZCBkb2N1bWVudC5jcmVhdGVFbGVtZW50KFwicFwiKVxuIiwibW9kdWxlLmV4cG9ydHMuZGVmYXVsdG1lbnUgPSByZXF1aXJlKFwiLi9kZWZhdWx0bWVudVwiKVxubW9kdWxlLmV4cG9ydHMubWVudWJ1aWxkZXIgPSByZXF1aXJlKFwiLi9tZW51YnVpbGRlclwiKVxuIiwiQk1hdGggPSByZXF1aXJlIFwiLi4vdXRpbHMvYm1hdGhcIlxuamJvbmUgPSByZXF1aXJlIFwiamJvbmVcIlxudmlldyA9IHJlcXVpcmUoXCJiYWNrYm9uZS12aWV3alwiKVxuXG4jamJvbmUuZm4uYWRkQ2xhc3MgPSAoY2xhc3NOYW1lKSAtPlxuIyAgZm9yIGkgaW4gWzAuLiBALmxlbmd0aCAtIDFdIGJ5IDFcbiMgICAgQFtpXS5jbGFzc0xpc3QuYWRkIGNsYXNzTmFtZVxuIyAgQFxuXG5tb2R1bGUuZXhwb3J0cyA9IE1lbnVCdWlsZGVyID0gdmlldy5leHRlbmRcblxuICAgIHNldE5hbWU6IChAbmFtZSkgLT5cbiAgICAgIEBfbm9kZXMgPSAgW11cblxuICAgIGFkZE5vZGU6IChsYWJlbCwgY2FsbGJhY2ssIGRhdGEpIC0+XG4gICAgICBzdHlsZSA9IGRhdGEuc3R5bGUgaWYgZGF0YT9cbiAgICAgIEBfbm9kZXMgPSBbXSB1bmxlc3MgQF9ub2Rlcz9cbiAgICAgIEBfbm9kZXMucHVzaCB7bGFiZWw6IGxhYmVsLCBjYWxsYmFjazogY2FsbGJhY2ssIHN0eWxlOiBzdHlsZX1cblxuICAgIGJ1aWxkRE9NOiAtPlxuICAgICAgQF9idWlsZE1cbiAgICAgICAgbm9kZXM6IEBfbm9kZXNcbiAgICAgICAgbmFtZTogQG5hbWVcblxuICAgIF9idWlsZE06IChkYXRhKSAtPlxuICAgICAgbm9kZXMgPSBkYXRhLm5vZGVzXG4gICAgICBuYW1lID0gZGF0YS5uYW1lXG5cbiAgICAgIG1lbnUgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50IFwiZGl2XCJcbiAgICAgIG1lbnUuY2xhc3NOYW1lID0gXCJkcm9wZG93biBkcm9wZG93bi10aXBcIlxuICAgICAgbWVudS5pZCA9IFwiYWRyb3AtXCIgKyBCTWF0aC51bmlxdWVJZCgpXG4gICAgICBtZW51LnN0eWxlLmRpc3BsYXkgPSBcIm5vbmVcIlxuXG4gICAgICBtZW51VWwgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50IFwidWxcIlxuICAgICAgbWVudVVsLmNsYXNzTmFtZSA9IFwiZHJvcGRvd24tbWVudVwiXG5cbiAgICAgICMgZHJvcGRvd24gbWVudVxuICAgICAgZm9yIG5vZGUgaW4gbm9kZXNcbiAgICAgICAgbGkgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50IFwibGlcIlxuXG4gICAgICAgIGxpLnRleHRDb250ZW50ID0gbm9kZS5sYWJlbFxuICAgICAgICBmb3Iga2V5LHN0eWxlIG9mIG5vZGUuc3R5bGVcbiAgICAgICAgICBsaS5zdHlsZVtrZXldID0gc3R5bGVcbiAgICAgICAgbGkuYWRkRXZlbnRMaXN0ZW5lciBcImNsaWNrXCIsIG5vZGUuY2FsbGJhY2tcbiAgICAgICAgaWYgQGc/XG4gICAgICAgICAgbGkuc3R5bGUubGluZUhlaWdodCA9IEBnLnpvb21lci5nZXQgXCJtZW51SXRlbUxpbmVIZWlnaHRcIlxuXG4gICAgICAgIG1lbnVVbC5hcHBlbmRDaGlsZCBsaVxuXG4gICAgICBtZW51LmFwcGVuZENoaWxkIG1lbnVVbFxuXG4gICAgICBmcmFnID0gZG9jdW1lbnQuY3JlYXRlRG9jdW1lbnRGcmFnbWVudCgpXG4gICAgICAjIGRpcGxheSBpdFxuICAgICAgZGlzcGxheWVkQnV0dG9uID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCBcImFcIlxuICAgICAgZGlzcGxheWVkQnV0dG9uLnRleHRDb250ZW50ID0gbmFtZVxuICAgICAgZGlzcGxheWVkQnV0dG9uLmNsYXNzTmFtZSA9IFwiYmlvanNfbXNhX21lbnViYXJfYWxpbmtcIlxuXG4gICAgICAjIHRpbnkgc3R5bGVcbiAgICAgIGlmIEBnP1xuICAgICAgICBtZW51VWwuc3R5bGUuZm9udFNpemUgPSBAZy56b29tZXIuZ2V0IFwibWVudUl0ZW1Gb250c2l6ZVwiXG4gICAgICAgIGRpc3BsYXllZEJ1dHRvbi5zdHlsZS5mb250U2l6ZSA9IEBnLnpvb21lci5nZXQgXCJtZW51Rm9udHNpemVcIlxuICAgICAgICBkaXNwbGF5ZWRCdXR0b24uc3R5bGUubWFyZ2luTGVmdCA9IEBnLnpvb21lci5nZXQgXCJtZW51TWFyZ2luTGVmdFwiXG4gICAgICAgIGRpc3BsYXllZEJ1dHRvbi5zdHlsZS5wYWRkaW5nID0gQGcuem9vbWVyLmdldCBcIm1lbnVQYWRkaW5nXCJcblxuICAgICAgamJvbmUoZGlzcGxheWVkQnV0dG9uKS5vbiBcImNsaWNrXCIsIChlKSA9PlxuICAgICAgICBAX3Nob3dNZW51IGUsbWVudSxkaXNwbGF5ZWRCdXR0b25cblxuICAgICAgICAjIHdhaXQgdW50aWwgZXZlbnQgaXMgYnViYmxlZCB0byB0aGUgdG9wXG4gICAgICAgIHdpbmRvdy5zZXRUaW1lb3V0IC0+XG4gICAgICAgICAgamJvbmUoZG9jdW1lbnQuYm9keSkub25lIFwiY2xpY2tcIiwgKGUpIC0+XG4gICAgICAgICAgICBjb25zb2xlLmxvZyBcIm5leHQgY2xpY2tcIlxuICAgICAgICAgICAgbWVudS5zdHlsZS5kaXNwbGF5ID0gXCJub25lXCJcbiAgICAgICAgLCA1XG5cblxuICAgICAgZnJhZy5hcHBlbmRDaGlsZCBtZW51XG4gICAgICBmcmFnLmFwcGVuZENoaWxkIGRpc3BsYXllZEJ1dHRvblxuICAgICAgcmV0dXJuICBmcmFnXG5cbiAgICBfc2hvd01lbnU6IChlLCBtZW51LCB0YXJnZXQpIC0+XG4gICAgICAjamJvbmUobWVudSkuYWRkQ2xhc3MgXCJkcm9wZG93bi1vcGVuXCJcbiAgICAgIG1lbnUuc3R5bGUuZGlzcGxheSA9IFwiYmxvY2tcIlxuICAgICAgbWVudS5zdHlsZS5wb3NpdGlvbiA9IFwiYWJzb2x1dGVcIlxuXG4gICAgICByZWN0ID0gdGFyZ2V0LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpXG4gICAgICBtZW51LnN0eWxlLmxlZnQgPSByZWN0LmxlZnQgKyBcInB4XCJcbiAgICAgIG1lbnUuc3R5bGUudG9wID0gKHJlY3QudG9wICsgdGFyZ2V0Lm9mZnNldEhlaWdodCkgKyBcInB4XCJcbiIsIk1lbnVCdWlsZGVyID0gcmVxdWlyZSBcIi4uL21lbnVidWlsZGVyXCJcbl8gPSByZXF1aXJlIFwidW5kZXJzY29yZVwiXG5kb20gPSByZXF1aXJlIFwiZG9tLWhlbHBlclwiXG5cbm1vZHVsZS5leHBvcnRzID0gQ29sb3JNZW51ID0gTWVudUJ1aWxkZXIuZXh0ZW5kXG5cbiAgaW5pdGlhbGl6ZTogKGRhdGEpIC0+XG4gICAgQGcgPSBkYXRhLmdcbiAgICBAZWwuc3R5bGUuZGlzcGxheSA9IFwiaW5saW5lLWJsb2NrXCJcbiAgICBAbGlzdGVuVG8gQGcuY29sb3JzY2hlbWUsIFwiY2hhbmdlXCIsIC0+XG4gICAgICBAcmVuZGVyKClcblxuICByZW5kZXI6IC0+XG4gICAgbWVudUNvbG9yID0gQHNldE5hbWUoXCJDb2xvciBzY2hlbWVcIilcblxuICAgIGNvbG9yc2NoZW1lcyA9IEBnZXRDb2xvcnNjaGVtZXMoKVxuICAgIGZvciBzY2hlbWUgaW4gY29sb3JzY2hlbWVzXG4gICAgICBAYWRkU2NoZW1lIG1lbnVDb2xvciwgc2NoZW1lXG5cbiAgICB0ZXh0ID0gXCJCYWNrZ3JvdW5kXCJcbiAgICBpZiBAZy5jb2xvcnNjaGVtZS5nZXQoXCJjb2xvckJhY2tncm91bmRcIilcbiAgICAgIHRleHQgPSBcIkhpZGUgXCIgKyB0ZXh0XG4gICAgZWxzZVxuICAgICAgdGV4dCA9IFwiU2hvdyBcIiArIHRleHRcblxuICAgIEBhZGROb2RlIHRleHQsID0+XG4gICAgICBAZy5jb2xvcnNjaGVtZS5zZXQgXCJjb2xvckJhY2tncm91bmRcIiwgIUBnLmNvbG9yc2NoZW1lLmdldChcImNvbG9yQmFja2dyb3VuZFwiKVxuXG4gICAgQGdyZXkgbWVudUNvbG9yXG5cbiAgICAjIFRPRE86IG1ha2UgbW9yZSBlZmZpY2llbnRcbiAgICBkb20ucmVtb3ZlQWxsQ2hpbGRzIEBlbFxuICAgIEBlbC5hcHBlbmRDaGlsZCBAYnVpbGRET00oKVxuICAgIEBcblxuICBhZGRTY2hlbWU6IChtZW51Q29sb3Isc2NoZW1lKSAtPlxuICAgIHN0eWxlID0ge31cbiAgICBjdXJyZW50ID0gQGcuY29sb3JzY2hlbWUuZ2V0KFwic2NoZW1lXCIpXG4gICAgaWYgY3VycmVudCBpcyBzY2hlbWUuaWRcbiAgICAgIHN0eWxlLmJhY2tncm91bmRDb2xvciA9IFwiIzc3RUQ4MFwiXG5cbiAgICBAYWRkTm9kZSBzY2hlbWUubmFtZSwgPT5cbiAgICAgIEBnLmNvbG9yc2NoZW1lLnNldCBcInNjaGVtZVwiLCBzY2hlbWUuaWRcbiAgICAsXG4gICAgICBzdHlsZTogc3R5bGVcblxuICBnZXRDb2xvcnNjaGVtZXM6IC0+XG4gICAgc2NoZW1lcyAgPSBbXVxuICAgIHNjaGVtZXMucHVzaCBuYW1lOiBcIlphcHBvXCIsIGlkOiBcInphcHBvXCJcbiAgICBzY2hlbWVzLnB1c2ggbmFtZTogXCJUYXlsb3JcIiwgaWQ6IFwidGF5bG9yXCJcbiAgICBzY2hlbWVzLnB1c2ggbmFtZTogXCJIeWRyb3Bob2JpY2l0eVwiLCBpZDogXCJoeWRyb1wiXG4gICAgc2NoZW1lcy5wdXNoIG5hbWU6IFwiTGVza1wiLCBpZDogXCJsZXNrXCJcbiAgICBzY2hlbWVzLnB1c2ggbmFtZTogXCJDaW5lbWFcIiwgaWQ6IFwiY2luZW1hXCJcbiAgICBzY2hlbWVzLnB1c2ggbmFtZTogXCJNQUVcIiwgaWQ6IFwibWFlXCJcbiAgICBzY2hlbWVzLnB1c2ggbmFtZTogXCJDbHVzdGFsXCIsIGlkOiBcImNsdXN0YWxcIlxuICAgIHNjaGVtZXMucHVzaCBuYW1lOiBcIkNsdXN0YWwyXCIsIGlkOiBcImNsdXN0YWwyXCJcbiAgICBzY2hlbWVzLnB1c2ggbmFtZTogXCJUdXJuXCIsIGlkOiBcInR1cm5cIlxuICAgIHNjaGVtZXMucHVzaCBuYW1lOiBcIlN0cmFuZFwiLCBpZDogXCJzdHJhbmRcIlxuICAgIHNjaGVtZXMucHVzaCBuYW1lOiBcIkJ1cmllZFwiLCBpZDogXCJidXJpZWRcIlxuICAgIHNjaGVtZXMucHVzaCBuYW1lOiBcIkhlbGl4XCIsIGlkOiBcImhlbGl4XCJcbiAgICBzY2hlbWVzLnB1c2ggbmFtZTogXCJOdWNsZW90aWRlXCIsIGlkOiBcIm51Y2xlb3RpZGVcIlxuICAgIHNjaGVtZXMucHVzaCBuYW1lOiBcIlB1cmluZVwiLCBpZDogXCJwdXJpbmVcIlxuICAgIHNjaGVtZXMucHVzaCBuYW1lOiBcIlBJRFwiLCBpZDogXCJwaWRcIlxuICAgIHNjaGVtZXMucHVzaCBuYW1lOiBcIk5vIGNvbG9yXCIsIGlkOiBcImZvb1wiXG4gICAgc2NoZW1lc1xuXG4gIGdyZXk6IChtZW51Q29sb3IpIC0+XG4gICAgIyBncmV5cyBhbGwgbG93ZXJjYXNlIGxldHRlcnNcbiAgICBAYWRkTm9kZSBcIkdyZXlcIiwgPT5cbiAgICAgIEBnLmNvbG9yc2NoZW1lLnNldCBcInNob3dMb3dlckNhc2VcIiwgZmFsc2VcbiAgICAgIEBtb2RlbC5lYWNoIChzZXEpIC0+XG4gICAgICAgIHJlc2lkdWVzID0gc2VxLmdldCBcInNlcVwiXG4gICAgICAgIGdyZXkgPSBbXVxuICAgICAgICBfLmVhY2ggcmVzaWR1ZXMsIChlbCwgaW5kZXgpIC0+XG4gICAgICAgICAgaWYgZWwgaXMgZWwudG9Mb3dlckNhc2UoKVxuICAgICAgICAgICAgZ3JleS5wdXNoIGluZGV4XG4gICAgICAgIHNlcS5zZXQgXCJncmV5XCIsIGdyZXlcblxuICAgIEBhZGROb2RlIFwiR3JleSBieSB0aHJlc2hvbGRcIiwgPT5cbiAgICAgIHRocmVzaG9sZCA9IHByb21wdCBcIkVudGVyIHRocmVzaG9sZCAoaW4gcGVyY2VudClcIiwgMjBcbiAgICAgIHRocmVzaG9sZCA9IHRocmVzaG9sZCAvIDEwMFxuICAgICAgbWF4TGVuID0gQG1vZGVsLmdldE1heExlbmd0aCgpXG4gICAgICBjb25zZXJ2ID0gQGcuY29sdW1ucy5nZXQoXCJjb25zZXJ2XCIpXG4gICAgICBncmV5ID0gW11cbiAgICAgIGZvciBpIGluIFswLi4gbWF4TGVuIC0gMV1cbiAgICAgICAgY29uc29sZS5sb2cgY29uc2VydltpXVxuICAgICAgICBpZiBjb25zZXJ2W2ldIDwgdGhyZXNob2xkXG4gICAgICAgICAgZ3JleS5wdXNoIGlcbiAgICAgIEBtb2RlbC5lYWNoIChzZXEpIC0+XG4gICAgICAgIHNlcS5zZXQgXCJncmV5XCIsIGdyZXlcblxuICAgIEBhZGROb2RlIFwiR3JleSBzZWxlY3Rpb25cIiwgPT5cbiAgICAgIG1heExlbiA9IEBtb2RlbC5nZXRNYXhMZW5ndGgoKVxuICAgICAgQG1vZGVsLmVhY2ggKHNlcSkgPT5cbiAgICAgICAgYmxvY2tzID0gQGcuc2VsY29sLmdldEJsb2Nrc0ZvclJvdyhzZXEuZ2V0KFwiaWRcIiksbWF4TGVuKVxuICAgICAgICBzZXEuc2V0IFwiZ3JleVwiLCBibG9ja3NcblxuICAgIEBhZGROb2RlIFwiUmVzZXQgZ3JleVwiLCA9PlxuICAgICAgQGcuY29sb3JzY2hlbWUuc2V0IFwic2hvd0xvd2VyQ2FzZVwiLCB0cnVlXG4gICAgICBAbW9kZWwuZWFjaCAoc2VxKSAtPlxuICAgICAgICBzZXEuc2V0IFwiZ3JleVwiLCBbXVxuIiwiTWVudUJ1aWxkZXIgPSByZXF1aXJlIFwiLi4vbWVudWJ1aWxkZXJcIlxuc2F2ZUFzID0gcmVxdWlyZSBcImJyb3dzZXItc2F2ZWFzXCJcbkZhc3RhRXhwb3J0ZXIgPSByZXF1aXJlKFwiYmlvanMtaW8tZmFzdGFcIikud3JpdGVyXG5fID0gcmVxdWlyZSBcInVuZGVyc2NvcmVcIlxuYmxvYlVSTCA9IHJlcXVpcmUgXCJibHVlaW1wX2NhbnZhc3RvYmxvYlwiXG5cbm1vZHVsZS5leHBvcnRzID0gRXhwb3J0TWVudSA9IE1lbnVCdWlsZGVyLmV4dGVuZFxuXG4gIGluaXRpYWxpemU6IChkYXRhKSAtPlxuICAgIEBnID0gZGF0YS5nXG4gICAgQG1zYSA9IGRhdGEubXNhXG4gICAgQGVsLnN0eWxlLmRpc3BsYXkgPSBcImlubGluZS1ibG9ja1wiXG5cbiAgcmVuZGVyOiAtPlxuICAgIEBzZXROYW1lKFwiRXhwb3J0XCIpXG5cbiAgICBAYWRkTm9kZSBcIkV4cG9ydCBzZXF1ZW5jZXNcIiwgPT5cbiAgICAgICMgbGltaXQgYXQgYWJvdXQgMjU2a1xuICAgICAgdGV4dCA9IEZhc3RhRXhwb3J0ZXIuZXhwb3J0IEBtb2RlbC50b0pTT04oKVxuICAgICAgYmxvYiA9IG5ldyBCbG9iKFt0ZXh0XSwge3R5cGUgOiAndGV4dC9wbGFpbid9KVxuICAgICAgc2F2ZUFzIGJsb2IsIFwiYWxsLmZhc3RhXCJcblxuICAgIEBhZGROb2RlIFwiRXhwb3J0IHNlbGVjdGlvblwiLCA9PlxuICAgICAgc2VsZWN0aW9uID0gQGcuc2VsY29sLnBsdWNrIFwic2VxSWRcIlxuICAgICAgaWYgc2VsZWN0aW9uP1xuICAgICAgICAjIGZpbHRlciB0aG9zZSBzZXFpZHNcbiAgICAgICAgc2VsZWN0aW9uID0gQG1vZGVsLmZpbHRlciAoZWwpIC0+XG4gICAgICAgICAgXy5jb250YWlucyBzZWxlY3Rpb24sIGVsLmdldCBcImlkXCJcbiAgICAgICAgZm9yIGkgaW4gWzAuLiBzZWxlY3Rpb24ubGVuZ3RoIC0gMV0gYnkgMVxuICAgICAgICAgIHNlbGVjdGlvbltpXSA9IHNlbGVjdGlvbltpXS50b0pTT04oKVxuICAgICAgZWxzZVxuICAgICAgICBzZWxlY3Rpb24gPSBAbW9kZWwudG9KU09OKClcbiAgICAgICAgY29uc29sZS5sb2cgXCJubyBzZWxlY3Rpb24gZm91bmRcIlxuICAgICAgdGV4dCA9IEZhc3RhRXhwb3J0ZXIuZXhwb3J0IHNlbGVjdGlvblxuICAgICAgYmxvYiA9IG5ldyBCbG9iKFt0ZXh0XSwge3R5cGUgOiAndGV4dC9wbGFpbid9KVxuICAgICAgc2F2ZUFzIGJsb2IsIFwic2VsZWN0aW9uLmZhc3RhXCJcblxuICAgICMgVE9ETzogdXNlIGh0dHBzOi8vZ2l0aHViLmNvbS9ibHVlaW1wL0phdmFTY3JpcHQtQ2FudmFzLXRvLUJsb2IvYmxvYi9tYXN0ZXIvanMvY2FudmFzLXRvLWJsb2IuanNcbiAgICBAYWRkTm9kZSBcIkV4cG9ydCBpbWFnZVwiLCA9PlxuICAgICAgIyBUT0RPOiB0aGlzIGlzIHZlcnkgdWdseVxuICAgICAgY2FudmFzID0gQG1zYS5nZXRWaWV3KCdzdGFnZScpLmdldFZpZXcoJ2JvZHknKS5nZXRWaWV3KCdzZXFibG9jaycpLmVsXG4gICAgICBpZiBjYW52YXM/XG4gICAgICAgIHVybCA9IGNhbnZhcy50b0RhdGFVUkwoJ2ltYWdlL3BuZycpXG4gICAgICAgIHNhdmVBcyBibG9iVVJMKHVybCksIFwiYmlvanMtbXNhLnBuZ1wiLCBcImltYWdlL3BuZ1wiXG5cbiAgICAgICMgYWRkIG9jdGV0LXN0cmVhbVxuICAgICAgI3VybCA9IHVybC5yZXBsYWNlKCAvLy8gIyBjcyBoZXJlZ2V4ZXNcbiAgICAgICMvXmRhdGFbOl1pbWFnZVxcLyhwbmd8anBnfGpwZWcpWztdL2lcbiAgICAgICMvLy8sIFwiZGF0YTphcHBsaWNhdGlvbi9vY3RldC1zdHJlYW07XCIpXG5cbiAgICBAZWwuYXBwZW5kQ2hpbGQgQGJ1aWxkRE9NKClcbiAgICBAXG4iLCJNZW51QnVpbGRlciA9IHJlcXVpcmUgXCIuLi9tZW51YnVpbGRlclwiXG5jb25zZW51cyA9IHJlcXVpcmUgXCIuLi8uLi9hbGdvL0NvbnNlbnN1c0NhbGNcIlxuU2VxID0gcmVxdWlyZSBcIi4uLy4uL21vZGVsL1NlcXVlbmNlXCJcblxubW9kdWxlLmV4cG9ydHMgPSBFeHRyYU1lbnUgPSBNZW51QnVpbGRlci5leHRlbmRcblxuICBpbml0aWFsaXplOiAoZGF0YSkgLT5cbiAgICBAZyA9IGRhdGEuZ1xuICAgIEBlbC5zdHlsZS5kaXNwbGF5ID0gXCJpbmxpbmUtYmxvY2tcIlxuXG4gIHJlbmRlcjogLT5cbiAgICBAc2V0TmFtZShcIkV4dHJhc1wiKVxuICAgIEBhZGROb2RlIFwiQWRkIGNvbnNlbnN1cyBzZXFcIiwgPT5cbiAgICAgIGNvbiA9IGNvbnNlbnVzKEBtb2RlbClcbiAgICAgIGNvbnNvbGUubG9nIGNvblxuICAgICAgc2VxID0gbmV3IFNlcVxuICAgICAgICBzZXE6IGNvblxuICAgICAgICBpZDogXCIwY1wiXG4gICAgICAgIG5hbWU6IFwiY29uc2VudXNcIlxuICAgICAgQG1vZGVsLmFkZCBzZXFcbiAgICAgIEBtb2RlbC5jb21wYXJhdG9yID0gKHNlcSkgLT5cbiAgICAgICAgc2VxLmdldCBcImlkXCJcbiAgICAgIEBtb2RlbC5zb3J0KClcbiAgICBAYWRkTm9kZSBcIkluY3JlYXNlIGZvbnQgc2l6ZVwiLCA9PlxuICAgICAgQGcuem9vbWVyLnNldCBcImNvbHVtbldpZHRoXCIsIEBnLnpvb21lci5nZXQoXCJjb2x1bW5XaWR0aFwiKSArIDJcbiAgICAgIEBnLnpvb21lci5zZXQgXCJsYWJlbFdpZHRoXCIsIEBnLnpvb21lci5nZXQoXCJjb2x1bW5XaWR0aFwiKSArIDVcbiAgICAgIEBnLnpvb21lci5zZXQgXCJyb3dIZWlnaHRcIiwgQGcuem9vbWVyLmdldChcInJvd0hlaWdodFwiKSArIDJcbiAgICAgIEBnLnpvb21lci5zZXQgXCJsYWJlbEZvbnRTaXplXCIsIEBnLnpvb21lci5nZXQoXCJsYWJlbEZvbnRTaXplXCIpICsgMlxuICAgIEBhZGROb2RlIFwiRGVjcmVhc2UgZm9udCBzaXplXCIsID0+XG4gICAgICBAZy56b29tZXIuc2V0IFwiY29sdW1uV2lkdGhcIiwgQGcuem9vbWVyLmdldChcImNvbHVtbldpZHRoXCIpIC0gMlxuICAgICAgQGcuem9vbWVyLnNldCBcInJvd0hlaWdodFwiLCBAZy56b29tZXIuZ2V0KFwicm93SGVpZ2h0XCIpIC0gMlxuICAgICAgQGcuem9vbWVyLnNldCBcImxhYmVsRm9udFNpemVcIiwgQGcuem9vbWVyLmdldChcImxhYmVsRm9udFNpemVcIikgLSAyXG4gICAgICBpZiBAZy56b29tZXIuZ2V0KFwiY29sdW1uV2lkdGhcIikgPCA4XG4gICAgICAgIEBnLnpvb21lci5zZXQgXCJ0ZXh0VmlzaWJsZVwiLCBmYWxzZVxuXG4gICAgQGFkZE5vZGUgXCJCYXIgY2hhcnQgZXhwIHNjYWxpbmdcIiwgPT5cbiAgICAgIEBnLmNvbHVtbnMuc2V0IFwic2NhbGluZ1wiLCBcImV4cFwiXG4gICAgQGFkZE5vZGUgXCJCYXIgY2hhcnQgbGluZWFyIHNjYWxpbmdcIiwgPT5cbiAgICAgIEBnLmNvbHVtbnMuc2V0IFwic2NhbGluZ1wiLCBcImxpblwiXG4gICAgQGFkZE5vZGUgXCJCYXIgY2hhcnQgbG9nIHNjYWxpbmdcIiwgPT5cbiAgICAgIEBnLmNvbHVtbnMuc2V0IFwic2NhbGluZ1wiLCBcImxvZ1wiXG5cbiAgICBAYWRkTm9kZSBcIk1pbmltaXplZCB3aWR0aFwiLCA9PlxuICAgICAgQGcuem9vbWVyLnNldCBcImFsaWdubWVudFdpZHRoXCIsIDYwMFxuICAgIEBhZGROb2RlIFwiTWluaW1pemVkIGhlaWdodFwiLCA9PlxuICAgICAgQGcuem9vbWVyLnNldCBcImFsaWdubWVudEhlaWdodFwiLCAxMjBcblxuICAgIEBhZGROb2RlIFwiSnVtcCB0byBhIGNvbHVtblwiLCA9PlxuICAgICAgb2Zmc2V0ID0gcHJvbXB0IFwiQ29sdW1uXCIsIFwiMjBcIlxuICAgICAgaWYgb2Zmc2V0IDwgMCBvciBvZmZzZXQgPiBAbW9kZWwuZ2V0TWF4TGVuZ3RoKCkgb3IgaXNOYU4ob2Zmc2V0KVxuICAgICAgICBhbGVydCBcImludmFsaWQgY29sdW1uXCJcbiAgICAgICAgcmV0dXJuXG4gICAgICBAZy56b29tZXIuc2V0TGVmdE9mZnNldChvZmZzZXQpXG5cbiAgICBAZWwuYXBwZW5kQ2hpbGQgQGJ1aWxkRE9NKClcbiAgICBAXG4iLCJNZW51QnVpbGRlciA9IHJlcXVpcmUgXCIuLi9tZW51YnVpbGRlclwiXG5fID0gcmVxdWlyZSBcInVuZGVyc2NvcmVcIlxuXG5tb2R1bGUuZXhwb3J0cyA9IEZpbHRlck1lbnUgPSBNZW51QnVpbGRlci5leHRlbmRcblxuICBpbml0aWFsaXplOiAoZGF0YSkgLT5cbiAgICBAZyA9IGRhdGEuZ1xuICAgIEBlbC5zdHlsZS5kaXNwbGF5ID0gXCJpbmxpbmUtYmxvY2tcIlxuXG4gIHJlbmRlcjogLT5cbiAgICBAc2V0TmFtZShcIkZpbHRlclwiKVxuICAgIEBhZGROb2RlIFwiSGlkZSBjb2x1bW5zIGJ5IHRocmVzaG9sZFwiLChlKSA9PlxuICAgICAgdGhyZXNob2xkID0gcHJvbXB0IFwiRW50ZXIgdGhyZXNob2xkIChpbiBwZXJjZW50KVwiLCAyMFxuICAgICAgdGhyZXNob2xkID0gdGhyZXNob2xkIC8gMTAwXG4gICAgICBtYXhMZW4gPSBAbW9kZWwuZ2V0TWF4TGVuZ3RoKClcbiAgICAgIGhpZGRlbiA9IFtdXG4gICAgICBjb25zZXJ2ID0gQGcuY29sdW1ucy5nZXQoXCJjb25zZXJ2XCIpXG4gICAgICBmb3IgaSBpbiBbMC4uIG1heExlbiAtIDFdXG4gICAgICAgIGlmIGNvbnNlcnZbaV0gPCB0aHJlc2hvbGRcbiAgICAgICAgICBoaWRkZW4ucHVzaCBpXG4gICAgICBAZy5jb2x1bW5zLnNldCBcImhpZGRlblwiLCBoaWRkZW5cblxuICAgIEBhZGROb2RlIFwiSGlkZSBjb2x1bW5zIGJ5IHNlbGVjdGlvblwiLCA9PlxuICAgICAgaGlkZGVuT2xkID0gQGcuY29sdW1ucy5nZXQgXCJoaWRkZW5cIlxuICAgICAgaGlkZGVuID0gaGlkZGVuT2xkLmNvbmNhdCBAZy5zZWxjb2wuZ2V0QWxsQ29sdW1uQmxvY2tzIG1heExlbjogQG1vZGVsLmdldE1heExlbmd0aCgpLCB3aXRoUG9zOiB0cnVlXG4gICAgICBAZy5zZWxjb2wucmVzZXQgW11cbiAgICAgIEBnLmNvbHVtbnMuc2V0IFwiaGlkZGVuXCIsIGhpZGRlblxuXG4gICAgQGFkZE5vZGUgXCJIaWRlIGNvbHVtbnMgYnkgZ2Fwc1wiLCA9PlxuICAgICAgdGhyZXNob2xkID0gcHJvbXB0IFwiRW50ZXIgdGhyZXNob2xkIChpbiBwZXJjZW50KVwiLCAyMFxuICAgICAgdGhyZXNob2xkID0gdGhyZXNob2xkIC8gMTAwXG4gICAgICBtYXhMZW4gPSBAbW9kZWwuZ2V0TWF4TGVuZ3RoKClcbiAgICAgIGhpZGRlbiA9IFtdXG4gICAgICBmb3IgaSBpbiBbMC4uIG1heExlbiAtIDFdXG4gICAgICAgIGdhcHMgPSAwXG4gICAgICAgIHRvdGFsID0gMFxuICAgICAgICBAbW9kZWwuZWFjaCAoZWwpIC0+XG4gICAgICAgICAgZ2FwcysrIGlmIGVsLmdldCgnc2VxJylbaV0gaXMgXCItXCJcbiAgICAgICAgICB0b3RhbCsrXG4gICAgICAgIGdhcENvbnRlbnQgPSBnYXBzIC8gdG90YWxcbiAgICAgICAgaWYgZ2FwQ29udGVudCA+IHRocmVzaG9sZFxuICAgICAgICAgIGhpZGRlbi5wdXNoIGlcbiAgICAgIEBnLmNvbHVtbnMuc2V0IFwiaGlkZGVuXCIsIGhpZGRlblxuXG4gICAgQGFkZE5vZGUgXCJIaWRlIHNlcXMgYnkgaWRlbnRpdHlcIiwgPT5cbiAgICAgIHRocmVzaG9sZCA9IHByb21wdCBcIkVudGVyIHRocmVzaG9sZCAoaW4gcGVyY2VudClcIiwgMjBcbiAgICAgIHRocmVzaG9sZCA9IHRocmVzaG9sZCAvIDEwMFxuICAgICAgQG1vZGVsLmVhY2ggKGVsKSAtPlxuICAgICAgICBpZiBlbC5nZXQoJ2lkZW50aXR5JykgPCB0aHJlc2hvbGRcbiAgICAgICAgICBlbC5zZXQoJ2hpZGRlbicsIHRydWUpXG5cbiAgICBAYWRkTm9kZSBcIkhpZGUgc2VxcyBieSBzZWxlY3Rpb25cIiwgPT5cbiAgICAgIGhpZGRlbiA9IEBnLnNlbGNvbC53aGVyZSB0eXBlOiBcInJvd1wiXG4gICAgICBpZHMgPSBfLm1hcCBoaWRkZW4sIChlbCkgLT4gZWwuZ2V0KCdzZXFJZCcpXG4gICAgICBAZy5zZWxjb2wucmVzZXQgW11cbiAgICAgIEBtb2RlbC5lYWNoIChlbCkgLT5cbiAgICAgICAgaWYgaWRzLmluZGV4T2YoZWwuZ2V0KCdpZCcpKSA+PSAwXG4gICAgICAgICAgZWwuc2V0KCdoaWRkZW4nLCB0cnVlKVxuXG4gICAgQGFkZE5vZGUgXCJIaWRlIHNlcXMgYnkgZ2Fwc1wiLCA9PlxuICAgICAgdGhyZXNob2xkID0gcHJvbXB0IFwiRW50ZXIgdGhyZXNob2xkIChpbiBwZXJjZW50KVwiLCA0MFxuICAgICAgQG1vZGVsLmVhY2ggKGVsLGkpIC0+XG4gICAgICAgIHNlcSA9IGVsLmdldCgnc2VxJylcbiAgICAgICAgZ2FwcyA9IF8ucmVkdWNlIHNlcSwgKChtZW1vLCBjKSAtPiBtZW1vKysgaWYgYyBpcyAnLSc7bWVtbyksMFxuICAgICAgICBjb25zb2xlLmxvZyBnYXBzXG4gICAgICAgIGlmIGdhcHMgPiAgdGhyZXNob2xkXG4gICAgICAgICAgZWwuc2V0KCdoaWRkZW4nLCB0cnVlKVxuXG4gICAgQGFkZE5vZGUgXCJSZXNldFwiLCA9PlxuICAgICAgQGcuY29sdW1ucy5zZXQgXCJoaWRkZW5cIiwgW11cbiAgICAgIEBtb2RlbC5lYWNoIChlbCkgLT5cbiAgICAgICAgaWYgZWwuZ2V0KCdoaWRkZW4nKVxuICAgICAgICAgIGVsLnNldCgnaGlkZGVuJywgZmFsc2UpXG5cbiAgICBAZWwuYXBwZW5kQ2hpbGQgQGJ1aWxkRE9NKClcbiAgICBAXG4iLCJNZW51QnVpbGRlciA9IHJlcXVpcmUgXCIuLi9tZW51YnVpbGRlclwiXG5cbm1vZHVsZS5leHBvcnRzID0gSGVscE1lbnUgPSBNZW51QnVpbGRlci5leHRlbmRcblxuICBpbml0aWFsaXplOiAoZGF0YSkgLT5cbiAgICBAZyA9IGRhdGEuZ1xuXG4gIHJlbmRlcjogLT5cbiAgICBAc2V0TmFtZShcIkhlbHBcIilcbiAgICBAYWRkTm9kZSBcIkFib3V0IHRoZSBwcm9qZWN0XCIsID0+XG4gICAgICB3aW5kb3cub3BlbiBcImh0dHBzOi8vZ2l0aHViLmNvbS9ncmVlbmlmeS9iaW9qcy12aXMtbXNhXCJcbiAgICBAYWRkTm9kZSBcIlJlcG9ydCBpc3N1ZXNcIiwgPT5cbiAgICAgIHdpbmRvdy5vcGVuIFwiaHR0cHM6Ly9naXRodWIuY29tL2dyZWVuaWZ5L2Jpb2pzLXZpcy1tc2EvaXNzdWVzXCJcbiAgICBAYWRkTm9kZSBcIlVzZXIgbWFudWFsXCIsID0+XG4gICAgICB3aW5kb3cub3BlbiBcImh0dHBzOi8vZ2l0aHViLmNvbS9ncmVlbmlmeS9iaW9qcy12aXMtbXNhL3dpa2lcIlxuICAgIEBlbC5zdHlsZS5kaXNwbGF5ID0gXCJpbmxpbmUtYmxvY2tcIlxuICAgIEBlbC5hcHBlbmRDaGlsZCBAYnVpbGRET00oKVxuICAgIEBcbiIsIkNsdXN0YWwgPSByZXF1aXJlIFwiYmlvanMtaW8tY2x1c3RhbFwiXG5GYXN0YVJlYWRlciA9IHJlcXVpcmUoXCJiaW9qcy1pby1mYXN0YVwiKS5wYXJzZVxuTWVudUJ1aWxkZXIgPSByZXF1aXJlIFwiLi4vbWVudWJ1aWxkZXJcIlxuY29yc1VSTCA9IHJlcXVpcmUoXCIuLi8uLi91dGlscy9wcm94eVwiKS5jb3JzVVJMXG5cbm1vZHVsZS5leHBvcnRzID0gSW1wb3J0TWVudSA9IE1lbnVCdWlsZGVyLmV4dGVuZFxuXG4gIGluaXRpYWxpemU6IChkYXRhKSAtPlxuICAgIEBnID0gZGF0YS5nXG4gICAgQGVsLnN0eWxlLmRpc3BsYXkgPSBcImlubGluZS1ibG9ja1wiXG5cbiAgcmVuZGVyOiAtPlxuICAgIEBzZXROYW1lKFwiSW1wb3J0XCIpXG4gICAgQGFkZE5vZGUgXCJGQVNUQVwiLChlKSA9PlxuICAgICAgdXJsID0gcHJvbXB0IFwiVVJMXCIsIFwiL3Rlc3QvZHVtbXkvc2FtcGxlcy9wNTMuY2x1c3RhbG8uZmFzdGFcIlxuICAgICAgdXJsID0gY29yc1VSTCB1cmwsIEBnXG4gICAgICBGYXN0YVJlYWRlci5yZWFkIHVybCwgKHNlcXMpID0+XG4gICAgICAgICMgbWFzcyB1cGRhdGUgb24gem9vbWVyXG4gICAgICAgIHpvb21lciA9IEBnLnpvb21lci50b0pTT04oKVxuICAgICAgICAjem9vbWVyLnRleHRWaXNpYmxlID0gZmFsc2VcbiAgICAgICAgI3pvb21lci5jb2x1bW5XaWR0aCA9IDRcbiAgICAgICAgem9vbWVyLmxhYmVsV2lkdGggPSAyMDBcbiAgICAgICAgem9vbWVyLmJveFJlY3RIZWlnaHQgPSAyXG4gICAgICAgIHpvb21lci5ib3hSZWN0V2lkdGggPSAyXG4gICAgICAgIEBtb2RlbC5yZXNldCBbXVxuICAgICAgICBAZy56b29tZXIuc2V0IHpvb21lclxuICAgICAgICBAbW9kZWwucmVzZXQgc2Vxc1xuICAgICAgICBAZy5jb2x1bW5zLmNhbGNDb25zZXJ2YXRpb24gQG1vZGVsXG5cbiAgICBAYWRkTm9kZSBcIkNMVVNUQUxcIiwgPT5cbiAgICAgIHVybCA9IHByb21wdCBcIlVSTFwiLCBcIi90ZXN0L2R1bW15L3NhbXBsZXMvcDUzLmNsdXN0YWxvLmNsdXN0YWxcIlxuICAgICAgdXJsID0gY29yc1VSTCB1cmwsIEBnXG4gICAgICBDbHVzdGFsLnJlYWQgdXJsLCAoc2VxcykgPT5cbiAgICAgICAgem9vbWVyID0gQGcuem9vbWVyLnRvSlNPTigpXG4gICAgICAgICN6b29tZXIudGV4dFZpc2libGUgPSBmYWxzZVxuICAgICAgICAjem9vbWVyLmNvbHVtbldpZHRoID0gNFxuICAgICAgICB6b29tZXIubGFiZWxXaWR0aCA9IDIwMFxuICAgICAgICB6b29tZXIuYm94UmVjdEhlaWdodCA9IDJcbiAgICAgICAgem9vbWVyLmJveFJlY3RXaWR0aCA9IDJcbiAgICAgICAgQG1vZGVsLnJlc2V0IFtdXG4gICAgICAgIEBnLnpvb21lci5zZXQgem9vbWVyXG4gICAgICAgIEBtb2RlbC5yZXNldCBzZXFzXG4gICAgICAgIEBnLmNvbHVtbnMuY2FsY0NvbnNlcnZhdGlvbiBAbW9kZWxcblxuICAgIEBhZGROb2RlIFwiYWRkIHlvdXIgb3duIFBhcnNlclwiLCA9PlxuICAgICAgd2luZG93Lm9wZW4gXCJodHRwczovL2dpdGh1Yi5jb20vYmlvanMvYmlvanMyXCJcblxuICAgIEBlbC5hcHBlbmRDaGlsZCBAYnVpbGRET00oKVxuICAgIEBcbiIsIk1lbnVCdWlsZGVyID0gcmVxdWlyZSBcIi4uL21lbnVidWlsZGVyXCJcbmRvbSA9IHJlcXVpcmUgXCJkb20taGVscGVyXCJcbl8gPSByZXF1aXJlKCd1bmRlcnNjb3JlJylcblxubW9kdWxlLmV4cG9ydHMgPSBPcmRlcmluZ01lbnUgPSBNZW51QnVpbGRlci5leHRlbmRcblxuICBpbml0aWFsaXplOiAoZGF0YSkgLT5cbiAgICBAZyA9IGRhdGEuZ1xuICAgIEBvcmRlciA9IFwiSURcIlxuICAgIEBlbC5zdHlsZS5kaXNwbGF5ID0gXCJpbmxpbmUtYmxvY2tcIlxuXG4gIHNldE9yZGVyOiAob3JkZXIpIC0+XG4gICAgQG9yZGVyID0gb3JkZXJcbiAgICBAcmVuZGVyKClcblxuICAjIFRPRE86IG1ha2UgbW9yZSBnZW5lcmljXG4gIHJlbmRlcjogLT5cbiAgICBAc2V0TmFtZShcIk9yZGVyaW5nXCIpXG5cbiAgICBjb21wcyA9IEBnZXRDb21wYXJhdG9ycygpXG4gICAgZm9yIG0gaW4gY29tcHNcbiAgICAgIEBfYWRkTm9kZSBtXG5cbiAgICBlbCA9IEBidWlsZERPTSgpXG5cbiAgICAjIFRPRE86IG1ha2UgbW9yZSBlZmZpY2llbnRcbiAgICBkb20ucmVtb3ZlQWxsQ2hpbGRzIEBlbFxuICAgIEBlbC5hcHBlbmRDaGlsZCBlbFxuICAgIEBcblxuICBfYWRkTm9kZTogKG0pIC0+XG4gICAgdGV4dCA9IG0udGV4dFxuICAgIHN0eWxlID0ge31cbiAgICBpZiB0ZXh0IGlzIEBvcmRlclxuICAgICAgc3R5bGUuYmFja2dyb3VuZENvbG9yID0gXCIjNzdFRDgwXCJcbiAgICBAYWRkTm9kZSB0ZXh0LCA9PlxuICAgICAgbS5wcmVjb2RlKCkgaWYgbS5wcmVjb2RlP1xuICAgICAgQG1vZGVsLmNvbXBhcmF0b3IgPSBtLmNvbXBhcmF0b3JcbiAgICAgIEBtb2RlbC5zb3J0KClcbiAgICAgIEBzZXRPcmRlciBtLnRleHRcbiAgICAsXG4gICAgICBzdHlsZTogc3R5bGVcblxuICBnZXRDb21wYXJhdG9yczogLT5cbiAgICBtb2RlbHMgPSBbXVxuXG4gICAgbW9kZWxzLnB1c2ggdGV4dDogXCJJRFwiLCBjb21wYXJhdG9yOiBcImlkXCJcblxuICAgIG1vZGVscy5wdXNoIHRleHQ6IFwiSUQgRGVzY1wiLCBjb21wYXJhdG9yOiAoYSwgYikgLT5cbiAgICAgICAgLSBhLmdldChcImlkXCIpLmxvY2FsZUNvbXBhcmUoYi5nZXQoXCJpZFwiKSlcblxuICAgIG1vZGVscy5wdXNoIHRleHQ6IFwiTGFiZWxcIiwgY29tcGFyYXRvcjogXCJuYW1lXCJcblxuICAgIG1vZGVscy5wdXNoIHRleHQ6IFwiTGFiZWwgRGVzY1wiLCBjb21wYXJhdG9yOiAoYSwgYikgLT5cbiAgICAgICAgLSBhLmdldChcIm5hbWVcIikubG9jYWxlQ29tcGFyZShiLmdldChcIm5hbWVcIikpXG5cbiAgICBtb2RlbHMucHVzaCB0ZXh0OiBcIlNlcVwiLCBjb21wYXJhdG9yOiBcInNlcVwiXG5cbiAgICBtb2RlbHMucHVzaCB0ZXh0OiBcIlNlcSBEZXNjXCIsIGNvbXBhcmF0b3I6IChhLGIpIC0+XG4gICAgICAgIC0gYS5nZXQoXCJzZXFcIikubG9jYWxlQ29tcGFyZShiLmdldChcInNlcVwiKSlcblxuICAgIG1vZGVscy5wdXNoIHRleHQ6IFwiSWRlbnRpdHlcIiwgY29tcGFyYXRvcjogXCJpZGVudGl0eVwiXG5cbiAgICBtb2RlbHMucHVzaCB0ZXh0OiBcIklkZW50aXR5IERlc2NcIiwgY29tcGFyYXRvcjogKHNlcSkgLT5cbiAgICAgICAgLSBzZXEuZ2V0IFwiaWRlbnRpdHlcIlxuXG4gICAgbW9kZWxzLnB1c2ggdGV4dDogXCJQYXJ0aXRpb24gY29kZXNcIiwgY29tcGFyYXRvcjogXCJwYXJ0aXRpb25cIiwgcHJlY29kZTogPT5cbiAgICAgICMgc2V0IHBhcnRpdGlvbnMgcmFuZG9tXG4gICAgICBAZy52aXMuc2V0KCdsYWJlbFBhcnRpdGlvbicsIHRydWUpXG4gICAgICBAbW9kZWwuZWFjaCAoZWwpIC0+XG4gICAgICAgIGVsLnNldCgncGFydGl0aW9uJywgXy5yYW5kb20oMSwzKSlcblxuXG4gICAgcmV0dXJuIG1vZGVsc1xuIiwic2VsID0gcmVxdWlyZSBcIi4uLy4uL2cvc2VsZWN0aW9uL1NlbGVjdGlvblwiXG5cbk1lbnVCdWlsZGVyID0gcmVxdWlyZSBcIi4uL21lbnVidWlsZGVyXCJcblxubW9kdWxlLmV4cG9ydHMgPSBTZWxlY3Rpb25NZW51ID0gTWVudUJ1aWxkZXIuZXh0ZW5kXG5cbiAgaW5pdGlhbGl6ZTogKGRhdGEpIC0+XG4gICAgQGcgPSBkYXRhLmdcbiAgICBAZWwuc3R5bGUuZGlzcGxheSA9IFwiaW5saW5lLWJsb2NrXCJcblxuICByZW5kZXI6IC0+XG4gICAgQHNldE5hbWUoXCJTZWxlY3Rpb25cIilcbiAgICBAYWRkTm9kZSBcIkZpbmQgTW90aWYgKHN1cHBvcnRzIFJlZ0V4KVwiLCA9PlxuICAgICAgc2VhcmNoID0gcHJvbXB0IFwieW91ciBzZWFyY2hcIiwgXCJEXCJcbiAgICAgICMgbWFya3MgYWxsIGhpdHNcbiAgICAgIHNlYXJjaCA9IG5ldyBSZWdFeHAgc2VhcmNoLCBcImdpXCJcbiAgICAgIHNlbGNvbCA9IEBnLnNlbGNvbFxuICAgICAgbmV3U2VsaSA9IFtdXG4gICAgICBsZWZ0ZXN0SW5kZXggPSBvcmlnSW5kZXggPSAxMDAwNDJcbiAgICAgIEBtb2RlbC5lYWNoIChzZXEpIC0+XG4gICAgICAgIHN0clNlcSA9IHNlcS5nZXQoXCJzZXFcIilcbiAgICAgICAgd2hpbGUgbWF0Y2ggPSBzZWFyY2guZXhlYyBzdHJTZXFcbiAgICAgICAgICBpbmRleCA9IG1hdGNoLmluZGV4XG4gICAgICAgICAgYXJncyA9IHt4U3RhcnQ6IGluZGV4LCB4RW5kOiBpbmRleCArIG1hdGNoWzBdLmxlbmd0aCAtIDEsIHNlcUlkOlxuICAgICAgICAgICAgc2VxLmdldChcImlkXCIpfVxuICAgICAgICAgIG5ld1NlbGkucHVzaCBuZXcgc2VsLnBvc3NlbChhcmdzKVxuICAgICAgICAgIGxlZnRlc3RJbmRleCA9IE1hdGgubWluIGluZGV4LCBsZWZ0ZXN0SW5kZXhcblxuICAgICAgaWYgbmV3U2VsaS5sZW5ndGggaXMgMFxuICAgICAgICBhbGVydCBcIm5vIHNlbGVjdGlvbiBmb3VuZFwiXG4gICAgICBzZWxjb2wucmVzZXQgbmV3U2VsaVxuXG4gICAgICAjIHNhZmV0eSBjaGVjayArIHVwZGF0ZSBvZmZzZXRcbiAgICAgIGxlZnRlc3RJbmRleCA9IDAgaWYgbGVmdGVzdEluZGV4IGlzIG9yaWdJbmRleFxuICAgICAgQGcuem9vbWVyLnNldExlZnRPZmZzZXQgbGVmdGVzdEluZGV4XG5cbiAgICBAYWRkTm9kZSBcIkludmVydCBjb2x1bW5zXCIsID0+XG4gICAgICBAZy5zZWxjb2wuaW52ZXJ0Q29sIFswLi5AbW9kZWwuZ2V0TWF4TGVuZ3RoKCldXG4gICAgQGFkZE5vZGUgXCJJbnZlcnQgcm93c1wiLCA9PlxuICAgICAgQGcuc2VsY29sLmludmVydFJvdyBAbW9kZWwucGx1Y2sgXCJpZFwiXG4gICAgQGFkZE5vZGUgXCJSZXNldFwiLCA9PlxuICAgICAgQGcuc2VsY29sLnJlc2V0KClcbiAgICBAZWwuYXBwZW5kQ2hpbGQgQGJ1aWxkRE9NKClcbiAgICBAXG4iLCJNZW51QnVpbGRlciA9IHJlcXVpcmUgXCIuLi9tZW51YnVpbGRlclwiXG5kb20gPSByZXF1aXJlIFwiZG9tLWhlbHBlclwiXG5cbm1vZHVsZS5leHBvcnRzID0gSW1wb3J0TWVudSA9IE1lbnVCdWlsZGVyLmV4dGVuZFxuXG4gIGluaXRpYWxpemU6IChkYXRhKSAtPlxuICAgIEBnID0gZGF0YS5nXG4gICAgQGVsLnN0eWxlLmRpc3BsYXkgPSBcImlubGluZS1ibG9ja1wiXG4gICAgQGxpc3RlblRvIEBnLnZpcywgXCJjaGFuZ2VcIiwgQHJlbmRlclxuXG4gIHJlbmRlcjogLT5cbiAgICBAc2V0TmFtZShcIlZpcy4gZWxlbWVudHNcIilcblxuICAgIHZpc0VsZW1lbnRzID0gQGdldFZpc0VsZW1lbnRzKClcbiAgICBmb3IgdmlzRWwgaW4gdmlzRWxlbWVudHNcbiAgICAgIEBfYWRkVmlzRWwgdmlzRWxcblxuICAgICMgb3RoZXJcbiAgICBAYWRkTm9kZSBcIlJlc2V0XCIsID0+XG4gICAgICBAZy52aXMuc2V0IFwibGFiZWxzXCIsIHRydWVcbiAgICAgIEBnLnZpcy5zZXQgXCJzZXF1ZW5jZXNcIiwgdHJ1ZVxuICAgICAgQGcudmlzLnNldCBcIm1ldGFjZWxsXCIsIHRydWVcbiAgICAgIEBnLnZpcy5zZXQgXCJjb25zZXJ2XCIsIHRydWVcbiAgICAgIEBnLnZpcy5zZXQgXCJsYWJlbElkXCIsIHRydWVcbiAgICAgIEBnLnZpcy5zZXQgXCJsYWJlbE5hbWVcIiwgdHJ1ZVxuICAgICAgQGcudmlzLnNldCBcImxhYmVsQ2hlY2tib3hcIiwgZmFsc2VcblxuICAgIEBhZGROb2RlIFwiVG9nZ2xlIG1vdXNlb3ZlciBldmVudHNcIiwgPT5cbiAgICAgIEBnLmNvbmZpZy5zZXQgXCJyZWdpc3Rlck1vdXNlSG92ZXJcIiwgIUBnLmNvbmZpZy5nZXQgXCJyZWdpc3Rlck1vdXNlSG92ZXJcIlxuXG4gICAgIyBUT0RPOiBtYWtlIG1vcmUgZWZmaWNpZW50XG4gICAgZG9tLnJlbW92ZUFsbENoaWxkcyBAZWxcbiAgICBAZWwuYXBwZW5kQ2hpbGQgQGJ1aWxkRE9NKClcbiAgICBAXG5cbiAgX2FkZFZpc0VsOiAodmlzRWwpIC0+XG4gICAgc3R5bGUgPSB7fVxuXG4gICAgaWYgQGcudmlzLmdldCB2aXNFbC5pZFxuICAgICAgcHJlID0gXCJIaWRlIFwiXG4gICAgICBzdHlsZS5jb2xvciA9IFwicmVkXCJcbiAgICBlbHNlXG4gICAgICBwcmUgPSBcIlNob3cgXCJcbiAgICAgIHN0eWxlLmNvbG9yID0gXCJncmVlblwiXG5cbiAgICBAYWRkTm9kZSAocHJlICsgdmlzRWwubmFtZSksID0+XG4gICAgICBAZy52aXMuc2V0IHZpc0VsLmlkLCAhIEBnLnZpcy5nZXQgdmlzRWwuaWRcbiAgICAsXG4gICAgICBzdHlsZTogc3R5bGVcblxuICBnZXRWaXNFbGVtZW50czogLT5cbiAgICB2aXMgPSBbXVxuICAgIHZpcy5wdXNoIG5hbWU6IFwiTWFya2Vyc1wiLCBpZDogXCJtYXJrZXJzXCJcbiAgICB2aXMucHVzaCBuYW1lOiBcIkxhYmVsc1wiLCBpZDogXCJsYWJlbHNcIlxuICAgIHZpcy5wdXNoIG5hbWU6IFwiU2VxdWVuY2VzXCIsIGlkOiBcInNlcXVlbmNlc1wiXG4gICAgdmlzLnB1c2ggbmFtZTogXCJNZXRhIGluZm9cIiwgaWQ6IFwibWV0YWNlbGxcIlxuICAgIHZpcy5wdXNoIG5hbWU6IFwiT3ZlcnZpZXdib3hcIiwgaWQ6IFwib3ZlcnZpZXdib3hcIlxuICAgIHZpcy5wdXNoIG5hbWU6IFwiY29uc2VydlwiLCBpZDogXCJjb25zZXJ2XCJcbiAgICB2aXMucHVzaCBuYW1lOiBcIkxhYmVsTmFtZVwiLCBpZDogXCJsYWJlbE5hbWVcIlxuICAgIHZpcy5wdXNoIG5hbWU6IFwiTGFiZWxJZFwiLCBpZDogXCJsYWJlbElkXCJcbiAgICB2aXMucHVzaCBuYW1lOiBcIkxhYmVsQ2hlY2tib3hcIiwgaWQ6IFwibGFiZWxDaGVja2JveFwiXG4gICAgcmV0dXJuIHZpc1xuIiwiRmVhdHVyZSA9IHJlcXVpcmUgXCIuL0ZlYXR1cmVcIlxuTW9kZWwgPSByZXF1aXJlKFwiYmFja2JvbmUtdGhpblwiKS5Nb2RlbFxuXG5tb2R1bGUuZXhwb3J0cyA9IEZlYXR1cmUgPSBNb2RlbC5leHRlbmRcblxuICBkZWZhdWx0czpcbiAgICB4U3RhcnQ6IC0xXG4gICAgeEVuZDogLTFcbiAgICBoZWlnaHQ6IC0xXG4gICAgdGV4dDogXCJcIlxuICAgIGZpbGxDb2xvcjogXCJyZWRcIlxuICAgIGZpbGxPcGFjaXR5OiAwLjVcbiAgICB0eXBlOiBcInJlY3RhbmdsZVwiXG4gICAgYm9yZGVyU2l6ZTogMVxuICAgIGJvcmRlckNvbG9yOiBcImJsYWNrXCJcbiAgICBib3JkZXJPcGFjaXR5OiAwLjVcbiAgICB2YWxpZGF0ZTogdHJ1ZVxuXG4gIHZhbGlkYXRlOiAtPlxuICAgIGlmIGlzTmFOIEBhdHRyaWJ1dGVzLnhTdGFydCBvciBpc05hTiBAYXR0cmlidXRlcy54RW5kXG4gICAgICBcImZlYXR1cmVzIG5lZWQgaW50ZWdlciBzdGFydCBhbmQgZW5kLlwiXG5cbiAgY29udGFpbnM6IChpbmRleCkgLT5cbiAgICByZXR1cm4gIEBhdHRyaWJ1dGVzLnhTdGFydCA8PSBpbmRleCAmJiBpbmRleCA8PSBAYXR0cmlidXRlcy54RW5kXG5cbiIsIkZlYXR1cmUgPSByZXF1aXJlIFwiLi9GZWF0dXJlXCJcbkNvbGxlY3Rpb24gPSByZXF1aXJlKFwiYmFja2JvbmUtdGhpblwiKS5Db2xsZWN0aW9uXG5fID0gcmVxdWlyZSBcInVuZGVyc2NvcmVcIlxuXG5tb2R1bGUuZXhwb3J0cyA9IEZlYXR1cmVDb2wgPSBDb2xsZWN0aW9uLmV4dGVuZFxuICBtb2RlbDogRmVhdHVyZVxuXG4gIGNvbnN0cnVjdG9yOiAtPlxuICAgIEBzdGFydE9uQ2FjaGUgPSBbXVxuICAgICMgaW52YWxpZGF0ZSBjYWNoZVxuICAgIEBvbiBcImFsbFwiLCAtPlxuICAgICAgQHN0YXJ0T25DYWNoZSA9IFtdXG4gICAgLCBAXG4gICAgQ29sbGVjdGlvbi5hcHBseSBALCBhcmd1bWVudHNcblxuICAjIHJldHVybnMgYWxsIGZlYXR1cmVzIHN0YXJ0aW5nIG9uIGluZGV4XG4gIHN0YXJ0T246IChpbmRleCkgLT5cbiAgICB1bmxlc3MgQHN0YXJ0T25DYWNoZVtpbmRleF0/XG4gICAgICBAc3RhcnRPbkNhY2hlW2luZGV4XSA9IEB3aGVyZSh7eFN0YXJ0OiBpbmRleH0pXG4gICAgcmV0dXJuIEBzdGFydE9uQ2FjaGVbaW5kZXhdXG5cbiAgY29udGFpbnM6IChpbmRleCkgLT5cbiAgICBAcmVkdWNlIChlbCxtZW1vKSAtPlxuICAgICAgbWVtbyB8fCBlbC5jb250YWlucyBpbmRleFxuICAgICwgZmFsc2VcblxuICAjIGdpdmVzIHRoZSBtaW5pbWFsIG5lZWRlZCBudW1iZXIgb2Ygcm93c1xuICAjIG5vdCBhIHZlcnkgZWZmaWNpZW50IGFsZ29yaXRobVxuICAjICh0aGVyZSBpcyBvbmUgaW4gTyhuKSApXG4gIGdldE1pblJvd3M6IC0+XG5cbiAgICBsZW4gPSBAbWF4IChlbCkgLT4gZWwuZ2V0IFwieEVuZFwiXG4gICAgcm93cyA9ICgwIGZvciB4IGluIFsxLi5sZW5dKVxuXG4gICAgQGVhY2ggKGVsKSAtPlxuICAgICAgZm9yIHggaW4gW2VsLmdldChcInhTdGFydFwiKS4uZmVhdHVyZS5nZXQoXCJ4RW5kXCIpXSBieSAxXG4gICAgICAgIHJvd3NbeF0rK1xuXG4gICAgXy5tYXggcm93c1xuIiwiU2VxdWVuY2UgPSByZXF1aXJlIFwiLi9TZXF1ZW5jZVwiXG5Db2xsZWN0aW9uID0gcmVxdWlyZShcImJhY2tib25lLXRoaW5cIikuQ29sbGVjdGlvblxuXG5tb2R1bGUuZXhwb3J0cyA9IFNlcU1hbmFnZXIgPSBDb2xsZWN0aW9uLmV4dGVuZFxuICBtb2RlbDogU2VxdWVuY2VcblxuICBjb25zdHJ1Y3RvcjogLT5cblxuICAgIENvbGxlY3Rpb24uYXBwbHkgQCwgYXJndW1lbnRzXG5cbiAgICAjIGludmFsaWRhdGUgY2FjaGVcbiAgICBAb24gXCJhbGxcIiwgLT5cbiAgICAgIEBsZW5ndGhDYWNoZSA9IG51bGxcbiAgICAsIEBcbiAgICBAbGVuZ3RoQ2FjaGUgPSBudWxsXG5cbiAgICBAXG5cbiAgIyBnaXZlcyB0aGUgbWF4IGxlbmd0aCBvZiBhbGwgc2VxdWVuY2VzXG4gICMgKGNhY2hlZClcbiAgZ2V0TWF4TGVuZ3RoOiAoKSAtPlxuICAgIHJldHVybiAwIGlmIEBtb2RlbHMubGVuZ3RoIGlzIDBcbiAgICBpZiBAbGVuZ3RoQ2FjaGUgaXMgbnVsbFxuICAgICAgQGxlbmd0aENhY2hlID0gQG1heCgoc2VxKSAtPiBzZXEuZ2V0KFwic2VxXCIpLmxlbmd0aCkuZ2V0KFwic2VxXCIpLmxlbmd0aFxuICAgIHJldHVybiBAbGVuZ3RoQ2FjaGVcblxuICAjIGdldHMgdGhlIHByZXZpb3VzIG1vZGVsXG4gICMgQHBhcmFtIGVuZGxlc3MgW2Jvb2xlYW5dIGZvciB0aGUgZmlyc3QgZWxlbWVudFxuICAjIHRydWU6IHJldHVybnMgdGhlIGxhc3QgZWxlbWVudCwgZmFsc2U6IHJldHVybnMgdW5kZWZpbmVkXG4gIHByZXY6IChtb2RlbCwgZW5kbGVzcykgLT5cbiAgICBpbmRleCA9IEBpbmRleE9mKG1vZGVsKSAtIDFcbiAgICBpbmRleCA9IEAubGVuZ3RoIC0gMSBpZiBpbmRleCA8IDAgYW5kIGVuZGxlc3NcbiAgICBAYXQoaW5kZXgpXG5cbiAgIyBnZXRzIHRoZSBuZXh0IG1vZGVsXG4gICMgQHBhcmFtIGVuZGxlc3MgW2Jvb2xlYW5dIGZvciB0aGUgbGFzdCBlbGVtZW50XG4gICMgdHJ1ZTogcmV0dXJucyB0aGUgZmlyc3QgZWxlbWVudCwgZmFsc2U6IHJldHVybnMgdW5kZWZpbmVkXG4gIG5leHQ6IChtb2RlbCwgZW5kbGVzcykgLT5cbiAgICBpbmRleCA9IEBpbmRleE9mKG1vZGVsKSArIDFcbiAgICBpbmRleCA9IDAgaWYgaW5kZXggPT0gQC5sZW5ndGggYW5kIGVuZGxlc3NcbiAgICBAYXQoaW5kZXgpXG5cbiAgIyBAcmV0dXJucyBuIFtpbnRdIG51bWJlciBvZiBoaWRkZW4gY29sdW1ucyB1bnRpbCBuXG4gIGNhbGNIaWRkZW5TZXFzOiAobikgLT5cbiAgICBuTmV3ID0gblxuICAgIGZvciBpIGluIFswLi5uTmV3XVxuICAgICAgaWYgQGF0KGkpLmdldChcImhpZGRlblwiKVxuICAgICAgICBuTmV3KytcbiAgICBuTmV3IC0gblxuXG4iLCJNb2RlbCA9IHJlcXVpcmUoXCJiYWNrYm9uZS10aGluXCIpLk1vZGVsXG5GZWF0dXJlQ29sID0gcmVxdWlyZSBcIi4vRmVhdHVyZUNvbFwiXG5cbm1vZHVsZS5leHBvcnRzID0gU2VxdWVuY2UgPSBNb2RlbC5leHRlbmRcblxuICBkZWZhdWx0czpcbiAgICBuYW1lOiBcIlwiXG4gICAgaWQ6IFwiXCJcbiAgICBzZXE6IFwiXCJcblxuICBpbml0aWFsaXplOiAtPlxuICAgICMgcmVzaWR1ZXMgd2l0aG91dCBjb2xvclxuICAgIEAuc2V0IFwiZ3JleVwiLCBbXVxuICAgIEAuc2V0IFwiZmVhdHVyZXNcIiwgbmV3IEZlYXR1cmVDb2woKVxuIiwibW9kdWxlLmV4cG9ydHMuc2VxID0gcmVxdWlyZSBcIi4vU2VxdWVuY2VcIlxubW9kdWxlLmV4cG9ydHMuc2VxY29sID0gcmVxdWlyZSBcIi4vU2VxQ29sbGVjdGlvblwiXG5tb2R1bGUuZXhwb3J0cy5mZWF0dXJlID0gcmVxdWlyZSBcIi4vRmVhdHVyZVwiXG5tb2R1bGUuZXhwb3J0cy5mZWF0dXJlY29sID0gcmVxdWlyZSBcIi4vRmVhdHVyZUNvbFwiXG4iLCIjIG1vZGVsc1xuU2VxQ29sbGVjdGlvbiA9IHJlcXVpcmUgXCIuL21vZGVsL1NlcUNvbGxlY3Rpb25cIlxuXG4jIGdsb2JhbHNcbkNvbG9yYXRvciA9IHJlcXVpcmUgXCIuL2cvY29sb3JhdG9yXCJcbkNvbnNlbnN1cyA9IHJlcXVpcmUgXCIuL2cvY29uc2Vuc3VzXCJcbkNvbHVtbnMgPSByZXF1aXJlIFwiLi9nL2NvbHVtbnNcIlxuQ29uZmlnID0gcmVxdWlyZSBcIi4vZy9jb25maWdcIlxuU2VsQ29sID0gcmVxdWlyZSBcIi4vZy9zZWxlY3Rpb24vU2VsZWN0aW9uQ29sXCJcblZpc2liaWxpdHkgPSByZXF1aXJlIFwiLi9nL3Zpc2liaWxpdHlcIlxuVmlzT3JkZXJpbmcgPSByZXF1aXJlIFwiLi9nL3Zpc09yZGVyaW5nXCJcblpvb21lciA9IHJlcXVpcmUgXCIuL2cvem9vbWVyXCJcblxuIyBNViBmcm9tIGJhY2tib25lXG5ib25lVmlldyA9IHJlcXVpcmUoXCJiYWNrYm9uZS1jaGlsZHNcIilcbkV2ZW50aGFuZGxlciA9IHJlcXVpcmUgXCJiaW9qcy1ldmVudHNcIlxuXG4jIE1TQSB2aWV3c1xuU3RhZ2UgPSByZXF1aXJlIFwiLi92aWV3cy9TdGFnZVwiXG5cbiMgb3B0cyBpcyBhIGRpY3Rpb25hcnkgY29uc2lzdGluZyBvZlxuIyBAcGFyYW0gZWwgW1N0cmluZ10gaWQgb3IgcmVmZXJlbmNlIHRvIGEgRE9NIGVsZW1lbnRcbiMgQHBhcmFtIHNlcXMgW1NlcUFycmF5XSBBcnJheSBvZiBzZXF1ZW5jZXMgZm9yIGluaXRsaXphdGlvblxuIyBAcGFyYW0gY29uZiBbRGljdF0gdXNlciBjb25maWdcbiMgQHBhcmFtIHZpcyBbRGljdF0gY29uZmlnIG9mIHZpc2libGUgdmlld3NcbiMgQHBhcmFtIHpvb21lciBbRGljdF0gZGlzcGxheSBzZXR0aW5ncyBsaWtlIGNvbHVtbldpZHRoXG5tb2R1bGUuZXhwb3J0cyA9IGJvbmVWaWV3LmV4dGVuZFxuXG4gIGluaXRpYWxpemU6IChkYXRhKSAtPlxuXG4gICAgIyBjaGVjayBmb3IgZGVmYXVsdCBhcnJheXNcbiAgICBkYXRhLmNvbHVtbnMgPSB7fSB1bmxlc3MgZGF0YS5jb2x1bW5zP1xuICAgIGRhdGEuY29uZiA9IHt9IHVubGVzcyBkYXRhLmNvbmY/XG4gICAgZGF0YS52aXMgPSB7fSB1bmxlc3MgZGF0YS52aXM/XG4gICAgZGF0YS52aXNvcmRlciA9IHt9IHVubGVzcyBkYXRhLnZpc29yZGVyID9cbiAgICBkYXRhLnpvb21lciA9IHt9IHVubGVzcyBkYXRhLnpvb21lcj9cblxuICAgICMgZyBpcyBvdXIgZ2xvYmFsIE1lZGlhdG9yXG4gICAgQGcgPSBFdmVudGhhbmRsZXIubWl4aW4ge31cblxuICAgIGlmIGRhdGEuc2VxcyBpcyB1bmRlZmluZWQgb3IgZGF0YS5zZXFzLmxlbmd0aCBpcyAwXG4gICAgICBjb25zb2xlLmxvZyBcIndhcm5pbmcuIGVtcHR5IHNlcXMuXCJcblxuICAgICMgbG9hZCBzZXFzIGFuZCBhZGQgc3Vidmlld3NcbiAgICBAc2VxcyA9IG5ldyBTZXFDb2xsZWN0aW9uIGRhdGEuc2Vxc1xuXG4gICAgIyBwb3B1bGF0ZSBpdCBhbmQgaW5pdCB0aGUgZ2xvYmFsIG1vZGVsc1xuICAgIEBnLmNvbmZpZyA9IG5ldyBDb25maWcgZGF0YS5jb25mXG4gICAgQGcuY29uc2Vuc3VzID0gbmV3IENvbnNlbnN1cygpXG4gICAgQGcuY29sdW1ucyA9IG5ldyBDb2x1bW5zIGRhdGEuY29sdW1ucyAgIyBmb3IgYWN0aW9uIG9uIHRoZSBjb2x1bW5zIGxpa2UgaGlkaW5nXG4gICAgQGcuY29sb3JzY2hlbWUgPSBuZXcgQ29sb3JhdG9yKClcbiAgICBAZy5zZWxjb2wgPSBuZXcgU2VsQ29sIFtdLHtnOkBnfVxuICAgIEBnLnZpcyA9IG5ldyBWaXNpYmlsaXR5IGRhdGEudmlzXG4gICAgQGcudmlzb3JkZXIgPSBuZXcgVmlzT3JkZXJpbmcgZGF0YS52aXNvcmRlclxuICAgIEBnLnpvb21lciA9IG5ldyBab29tZXIgZGF0YS56b29tZXIse2c6QGd9XG5cbiAgICBAYWRkVmlldyBcInN0YWdlXCIsbmV3IFN0YWdlIHttb2RlbDogQHNlcXMsIGc6IEBnfVxuICAgIEBlbC5zZXRBdHRyaWJ1dGUgXCJjbGFzc1wiLCBcImJpb2pzX21zYV9kaXZcIlxuXG4gICAgaWYgQGcuY29uZmlnLmdldChcImV2ZW50QnVzXCIpIGlzIHRydWVcbiAgICAgIEBzdGFydEV2ZW50QnVzKClcblxuICBzdGFydEV2ZW50QnVzOiAtPlxuICAgIGJ1c09ianMgPSBbXCJjb25maWdcIiwgXCJjb25zZW5zdXNcIiwgXCJjb2x1bW5zXCIsIFwiY29sb3JzY2hlbWVcIiwgXCJzZWxjb2xcIlxuICAgICxcInZpc1wiLCBcInZpc29yZGVyXCIsIFwiem9vbWVyXCJdXG4gICAgZm9yIGtleSBpbiBidXNPYmpzXG4gICAgICBAX3Byb3h5VG9HIGtleVxuXG4gIF9wcm94eVRvRzogKGtleSkgLT5cbiAgICBAbGlzdGVuVG8gQGdba2V5XSwgXCJhbGxcIiwobmFtZSxwcmV2LG5vdykgLT5cbiAgICAgICMgc3VwcHJlc3MgZHVwbGljYXRlIGV2ZW50c1xuICAgICAgcmV0dXJuIGlmIG5hbWUgaXMgXCJjaGFuZ2VcIlxuICAgICAgIyBiYWNrYm9uZSB1c2VzIHRoZSBzZWNvbmQgYXJndW1lbnQgZm9yIHRoZSBuZXh0IHZhbHVlIC0+IHN3YXBcbiAgICAgIEBnLnRyaWdnZXIoa2V5ICsgXCI6XCIgKyBuYW1lLG5vdylcblxuICByZW5kZXI6IC0+XG4gICAgQHJlbmRlclN1YnZpZXdzKClcbiAgICBAZy52aXMuc2V0IFwibG9hZGVkXCIsIHRydWVcbiAgICBAXG4iLCJtb2R1bGUuZXhwb3J0cyA9XG4gICMgbWF0aCB1dGlsaXRpZXNcbiAgY2xhc3MgQk1hdGhcbiAgICBAcmFuZG9tSW50OiAobG93ZXIsIHVwcGVyKSAtPlxuICAgICAgIyBDYWxsZWQgd2l0aCBvbmUgYXJndW1lbnRcbiAgICAgIFtsb3dlciwgdXBwZXJdID0gWzAsIGxvd2VyXSAgICAgdW5sZXNzIHVwcGVyP1xuICAgICAgIyBMb3dlciBtdXN0IGJlIGxlc3MgdGhlbiB1cHBlclxuICAgICAgW2xvd2VyLCB1cHBlcl0gPSBbdXBwZXIsIGxvd2VyXSBpZiBsb3dlciA+IHVwcGVyXG4gICAgICAjIExhc3Qgc3RhdGVtZW50IGlzIGEgcmV0dXJuIHZhbHVlXG4gICAgICBNYXRoLmZsb29yKE1hdGgucmFuZG9tKCkgKiAodXBwZXIgLSBsb3dlciArIDEpICsgbG93ZXIpXG5cbiAgICAjIEByZXR1cm4gW0ludGVnZXJdIHJhbmRvbSBpZFxuICAgIEB1bmlxdWVJZDogKGxlbmd0aCA9IDgpIC0+XG4gICAgICBpZCA9IFwiXCJcbiAgICAgIGlkICs9IE1hdGgucmFuZG9tKCkudG9TdHJpbmcoMzYpLnN1YnN0cigyKSB3aGlsZSBpZC5sZW5ndGggPCBsZW5ndGhcbiAgICAgIGlkLnN1YnN0ciAwLCBsZW5ndGhcblxuICAgICMgUmV0dXJucyBhIHJhbmRvbSBpbnRlZ2VyIGJldHdlZW4gbWluIChpbmNsdXNpdmUpIGFuZCBtYXggKGluY2x1c2l2ZSlcbiAgICBAZ2V0UmFuZG9tSW50OiAobWluLCBtYXgpIC0+XG4gICAgICByZXR1cm4gTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpICogKG1heCAtIG1pbiArIDEpKSArIG1pblxuIiwibW9kdWxlLmV4cG9ydHMuYm1hdGggPSByZXF1aXJlKFwiLi9ibWF0aFwiKVxubW9kdWxlLmV4cG9ydHMucHJveHkgPSByZXF1aXJlKFwiLi9wcm94eVwiKVxubW9kdWxlLmV4cG9ydHMuc2VxZ2VuID0gcmVxdWlyZShcIi4vc2VxZ2VuXCIpXG4iLCJtb2R1bGUuZXhwb3J0cyA9IHByb3h5ID1cblxuICAgIGNvcnNVUkw6ICh1cmwsIEBnKSA9PlxuICAgICAgIyBkbyBub3QgZmlsdGVyIG9uIGxvY2FsaG9zdFxuICAgICAgcmV0dXJuIHVybCBpZiBkb2N1bWVudC5VUkwuaW5kZXhPZignbG9jYWxob3N0JykgPj0gMCBhbmQgdXJsWzBdIGlzIFwiL1wiXG5cbiAgICAgICMgcmVtb3ZlIHd3dyArIGh0dHBcbiAgICAgIHVybCA9IHVybC5yZXBsYWNlIFwid3d3XFwuXCIsIFwiXCJcbiAgICAgIHVybCA9IHVybC5yZXBsYWNlIFwiaHR0cDovL1wiLCBcIlwiXG5cbiAgICAgICMgcHJlcGVuZCBwcm94eVxuICAgICAgdXJsID0gQGcuY29uZmlnLmdldCgnaW1wb3J0UHJveHknKSArIHVybFxuICAgICAgdXJsXG4iLCJTZXF1ZW5jZSA9IHJlcXVpcmUoXCJiaW9qcy1tb2RlbFwiKS5zZXFcbkJNYXRoID0gcmVxdWlyZSBcIi4vYm1hdGhcIlxuXG5zZXFnZW4gPSBtb2R1bGUuZXhwb3J0cyA9XG4gIF9nZW5lcmF0ZVNlcXVlbmNlOiAobGVuKSAtPlxuICAgIHRleHQgPSBcIlwiXG4gICAgcG9zc2libGUgPSBcIkFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpcIlxuXG4gICAgZm9yIGkgaW4gWzAuLmxlbiAtIDFdIGJ5IDFcbiAgICAgIHRleHQgKz0gcG9zc2libGUuY2hhckF0IE1hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIHBvc3NpYmxlLmxlbmd0aClcbiAgICByZXR1cm4gdGV4dFxuXG4gICMgZ2VuZXJhdGVzIGEgZHVtbXkgc2VxdWVuY2VzXG4gICMgQHBhcmFtIGxlbiBbaW50XSBudW1iZXIgb2YgZ2VuZXJhdGVkIHNlcXVlbmNlc1xuICAjIEBwYXJhbSBzZXFMZW4gW2ludF0gbGVuZ3RoIG9mIHRoZSBnZW5lcmF0ZWQgc2VxdWVuY2VzXG4gIGdldER1bW15U2VxdWVuY2VzOiAobGVuLCBzZXFMZW4pIC0+XG4gICAgc2VxcyA9IFtdXG4gICAgbGVuID0gQk1hdGguZ2V0UmFuZG9tSW50IDMsNSB1bmxlc3MgbGVuP1xuICAgIHNlcUxlbiA9IEJNYXRoLmdldFJhbmRvbUludCA1MCwyMDAgdW5sZXNzIHNlcUxlbj9cblxuICAgIGZvciBpIGluIFsxLi5sZW5dIGJ5IDFcbiAgICAgIHNlcXMucHVzaCBuZXcgU2VxdWVuY2Uoc2VxZ2VuLl9nZW5lcmF0ZVNlcXVlbmNlKHNlcUxlbiksIFwic2VxXCIgKyBpLFxuICAgICAgXCJyXCIgKyBpKVxuICAgIHJldHVybiBzZXFzXG4iLCIjIG1pbmkgc3ZnIGhlbHBlclxuXG5zdmducyA9IFwiaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmdcIlxuXG5zZXRBdHRyID0gKG9iaixvcHRzKSAtPlxuICBmb3IgbmFtZSwgdmFsdWUgb2Ygb3B0c1xuICAgIG9iai5zZXRBdHRyaWJ1dGVOUyBudWxsLCBuYW1lLCB2YWx1ZVxuICBvYmpcblxuQmFzZSA9IChvcHRzKSAtPlxuICBzdmcgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50TlMgc3ZnbnMsICdzdmcnXG4gIHN2Zy5zZXRBdHRyaWJ1dGUgXCJ3aWR0aFwiLCBvcHRzLndpZHRoXG4gIHN2Zy5zZXRBdHRyaWJ1dGUgXCJoZWlnaHRcIiwgb3B0cy5oZWlnaHRcbiAgc3ZnXG5cblJlY3QgPSAob3B0cykgLT5cbiAgcmVjdCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnROUyBzdmducywgJ3JlY3QnXG4gIHNldEF0dHIgcmVjdCxvcHRzXG5cbkxpbmUgPSAob3B0cykgLT5cbiAgbGluZSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnROUyBzdmducywgJ2xpbmUnXG4gIHNldEF0dHIgbGluZSxvcHRzXG5cblBvbHlnb24gPSAob3B0cykgLT5cbiAgbGluZSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnROUyBzdmducywgJ3BvbHlnb24nXG4gIHNldEF0dHIgbGluZSxvcHRzXG5cbm1vZHVsZS5leHBvcnRzLnJlY3QgPSBSZWN0XG5tb2R1bGUuZXhwb3J0cy5saW5lID0gTGluZVxubW9kdWxlLmV4cG9ydHMucG9seWdvbiA9IFBvbHlnb25cbm1vZHVsZS5leHBvcnRzLmJhc2UgPSBCYXNlXG4iLCJib25lVmlldyA9IHJlcXVpcmUoXCJiYWNrYm9uZS1jaGlsZHNcIilcblNlcUJsb2NrID0gcmVxdWlyZSBcIi4vQ2FudmFzU2VxQmxvY2tcIlxuTGFiZWxCbG9jayA9IHJlcXVpcmUgXCIuL2xhYmVscy9MYWJlbEJsb2NrXCJcblxubW9kdWxlLmV4cG9ydHMgPSBib25lVmlldy5leHRlbmRcblxuICBpbml0aWFsaXplOiAoZGF0YSkgLT5cbiAgICBAZyA9IGRhdGEuZ1xuXG4gICAgaWYgdHJ1ZVxuICAgICAgbGFiZWxibG9jayA9IG5ldyBMYWJlbEJsb2NrIHttb2RlbDogQG1vZGVsLCBnOiBAZ31cbiAgICAgIGxhYmVsYmxvY2sub3JkZXJpbmcgPSAtMVxuICAgICAgQGFkZFZpZXcgXCJsYWJlbGJsb2NrXCIsbGFiZWxibG9ja1xuXG4gICAgaWYgQGcudmlzLmdldCBcInNlcXVlbmNlc1wiXG4gICAgICBzZXFibG9jayA9IG5ldyBTZXFCbG9jayB7bW9kZWw6IEBtb2RlbCwgZzogQGd9XG4gICAgICBzZXFibG9jay5vcmRlcmluZyA9IDBcbiAgICAgIEBhZGRWaWV3IFwic2VxYmxvY2tcIixzZXFibG9ja1xuXG4gICAgQGxpc3RlblRvIEBnLnpvb21lciwgXCJjaGFuZ2U6YWxpZ25tZW50SGVpZ2h0XCIsIEBhZGp1c3RIZWlnaHRcbiAgICBAbGlzdGVuVG8gQGcuY29sdW1ucywgXCJjaGFuZ2U6aGlkZGVuXCIsIEBhZGp1c3RIZWlnaHRcblxuICByZW5kZXI6IC0+XG4gICAgQHJlbmRlclN1YnZpZXdzKClcbiAgICBAZWwuY2xhc3NOYW1lID0gXCJiaW9qc19tc2FfYWxib2R5XCJcbiAgICBAZWwuc3R5bGUud2hpdGVTcGFjZSA9IFwibm93cmFwXCJcbiAgICBAYWRqdXN0SGVpZ2h0KClcbiAgICBAXG5cbiAgYWRqdXN0SGVpZ2h0OiAtPlxuICAgIGlmIEBnLnpvb21lci5nZXQoXCJhbGlnbm1lbnRIZWlnaHRcIikgaXMgXCJhdXRvXCJcbiAgICAgICMgVE9ETzogZml4IHRoZSBtYWdpYyA1XG4gICAgICBAZWwuc3R5bGUuaGVpZ2h0ID0gKEBnLnpvb21lci5nZXQoXCJyb3dIZWlnaHRcIikgKiBAbW9kZWwubGVuZ3RoKSArIDVcbiAgICBlbHNlXG4gICAgICBAZWwuc3R5bGUuaGVpZ2h0ID0gQGcuem9vbWVyLmdldCBcImFsaWdubWVudEhlaWdodFwiXG5cbiAgICAjIFRPRE86IDE1IGlzIHRoZSB3aWR0aCBvZiB0aGUgc2Nyb2xsYmFyXG4gICAgQGVsLnN0eWxlLndpZHRoID0gQGdldFdpZHRoKCkgKyAxNVxuXG4gIGdldFdpZHRoOiAtPlxuICAgIHdpZHRoID0gMFxuICAgIGlmIEBnLnZpcy5nZXQgXCJsYWJlbHNcIlxuICAgICAgd2lkdGggKz0gQGcuem9vbWVyLmdldCBcImxhYmVsV2lkdGhcIlxuICAgIGlmIEBnLnZpcy5nZXQgXCJtZXRhY2VsbFwiXG4gICAgICB3aWR0aCArPSBAZy56b29tZXIuZ2V0IFwibWV0YVdpZHRoXCJcbiAgICBpZiBAZy52aXMuZ2V0IFwic2VxdWVuY2VzXCJcbiAgICAgIHdpZHRoICs9IEBnLnpvb21lci5nZXQgXCJhbGlnbm1lbnRXaWR0aFwiXG4gICAgd2lkdGhcbiIsIkV2ZW50cyA9IHJlcXVpcmUoXCJiaW9qcy1ldmVudHNcIilcblxubW9kdWxlLmV4cG9ydHMgPSBjbGFzcyBDYW52YXNDaGFyQ2FjaGVcblxuICBjb25zdHJ1Y3RvcjogKEBnKSAtPlxuICAgIEBjYWNoZSA9IHt9XG4gICAgQGNhY2hlSGVpZ2h0ID0gMFxuICAgIEBjYWNoZVdpZHRoID0gMFxuXG4gICMgcmV0dXJucyBhIGNhY2hlZCBjYW52YXNcbiAgZ2V0Rm9udFRpbGU6IChsZXR0ZXIsIHdpZHRoLCBoZWlnaHQpIC0+XG4gICAgIyB2YWxpZGF0ZSBjYWNoZVxuICAgIGlmIHdpZHRoIGlzbnQgQGNhY2hlV2lkdGggb3IgaGVpZ2h0IGlzbnQgQGNhY2hlSGVpZ2h0XG4gICAgICBAY2FjaGVIZWlnaHQgPSBoZWlnaHRcbiAgICAgIEBjYWNoZVdpZHRoID0gd2lkdGhcbiAgICAgIEBjYWNoZSA9IHt9XG5cbiAgICBpZiBAY2FjaGVbbGV0dGVyXSBpcyB1bmRlZmluZWRcbiAgICAgIEBjcmVhdGVUaWxlIGxldHRlciwgd2lkdGgsIGhlaWdodFxuXG4gICAgcmV0dXJuIEBjYWNoZVtsZXR0ZXJdXG5cbiAgIyBjcmVhdGVzIGEgY2FudmFzIHdpdGggYSBzaW5nbGUgbGV0dGVyXG4gICMgKGZvciB0aGUgZmFzdCBmb250IGNhY2hlKVxuICBjcmVhdGVUaWxlOiAobGV0dGVyLCB3aWR0aCwgaGVpZ2h0KSAtPlxuXG4gICAgY2FudmFzID0gQGNhY2hlW2xldHRlcl0gPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50IFwiY2FudmFzXCJcbiAgICBjYW52YXMud2lkdGggPSB3aWR0aFxuICAgIGNhbnZhcy5oZWlnaHQgPSBoZWlnaHRcbiAgICBAY3R4ID0gY2FudmFzLmdldENvbnRleHQgJzJkJ1xuICAgIEBjdHguZm9udCA9IEBnLnpvb21lci5nZXQgXCJyZXNpZHVlRm9udFwiXG4gICAgQGN0eC50ZXh0QmFzZWxpbmUgPSAnbWlkZGxlJ1xuICAgIEBjdHgudGV4dEFsaWduID0gXCJjZW50ZXJcIlxuXG4gICAgQGN0eC5maWxsVGV4dCBsZXR0ZXIsd2lkdGggLyAyLGhlaWdodCAvIDIsd2lkdGhcbiIsImJvbmVWaWV3ID0gcmVxdWlyZShcImJhY2tib25lLWNoaWxkc1wiKVxubW91c2UgPSByZXF1aXJlIFwibW91c2UtcG9zXCJcbmNvbG9yU2VsZWN0b3IgPSByZXF1aXJlKFwiYmlvanMtdXRpbC1jb2xvcnNjaGVtZXNcIikuc2VsZWN0b3Jcbl8gPSByZXF1aXJlIFwidW5kZXJzY29yZVwiXG5qYm9uZSA9IHJlcXVpcmUgXCJqYm9uZVwiXG5DaGFyQ2FjaGUgPSByZXF1aXJlIFwiLi9DYW52YXNDaGFyQ2FjaGVcIlxuXG5tb2R1bGUuZXhwb3J0cyA9IGJvbmVWaWV3LmV4dGVuZFxuXG4gIHRhZ05hbWU6IFwiY2FudmFzXCJcblxuICBpbml0aWFsaXplOiAoZGF0YSkgLT5cbiAgICBAZyA9IGRhdGEuZ1xuXG4gICAgQGxpc3RlblRvIEBnLnpvb21lciwgXCJjaGFuZ2U6X2FsaWdubWVudFNjcm9sbExlZnQgY2hhbmdlOl9hbGlnbm1lbnRTY3JvbGxUb3BcIiwgKG1vZGVsLHZhbHVlLCBvcHRpb25zKSAtPlxuICAgICAgaWYgKG5vdCBvcHRpb25zPy5vcmlnaW4/KSBvciBvcHRpb25zLm9yaWdpbiBpc250IFwiY2FudmFzc2VxXCJcbiAgICAgICAgQHJlbmRlcigpXG5cbiAgICBAbGlzdGVuVG8gQGcuY29sdW1ucyxcImNoYW5nZTpoaWRkZW5cIiwgQHJlbmRlclxuICAgIEBsaXN0ZW5UbyBAZy56b29tZXIsXCJjaGFuZ2U6YWxpZ25tZW50V2lkdGhcIiwgQHJlbmRlclxuICAgIEBsaXN0ZW5UbyBAZy5jb2xvcnNjaGVtZSwgXCJjaGFuZ2VcIiwgQHJlbmRlclxuICAgIEBsaXN0ZW5UbyBAZy5zZWxjb2wsIFwicmVzZXQgYWRkXCIsIEByZW5kZXJcblxuICAgICMgZWwgcHJvcHNcbiAgICBAZWwuc3R5bGUuZGlzcGxheSA9IFwiaW5saW5lLWJsb2NrXCJcbiAgICBAZWwuc3R5bGUub3ZlcmZsb3dYID0gXCJoaWRkZW5cIlxuICAgIEBlbC5zdHlsZS5vdmVyZmxvd1kgPSBcImhpZGRlblwiXG4gICAgQGVsLmNsYXNzTmFtZSA9IFwiYmlvanNfbXNhX3NlcWJsb2NrXCJcblxuICAgIEBjdHggPSBAZWwuZ2V0Q29udGV4dCAnMmQnXG4gICAgQGNhY2hlID0gbmV3IENoYXJDYWNoZSBAZ1xuXG4gICAgIyB0aHJvdHRsZSB0aGUgZXhwZW5zaXZlIGRyYXcgZnVuY3Rpb25cbiAgICBAdGhyb3R0bGVUaW1lID0gMFxuICAgIEB0aHJvdHRsZUNvdW50cyA9IDBcbiAgICBpZiBkb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc3R5bGUud2Via2l0QXBwZWFyYW5jZT9cbiAgICAgICMgd2Via2l0IGJyb3dzZXIgLSBubyB0aHJvdHRsaW5nIG5lZWRlZFxuICAgICAgQHRocm90dGxlZERyYXcgPSAtPlxuICAgICAgICBzdGFydCA9ICtuZXcgRGF0ZSgpXG4gICAgICAgIEBkcmF3KClcbiAgICAgICAgQHRocm90dGxlVGltZSArPSArbmV3IERhdGUoKSAtIHN0YXJ0XG4gICAgICAgIEB0aHJvdHRsZUNvdW50cysrXG4gICAgICAgIGlmIEB0aHJvdHRsZUNvdW50cyA+IDE1XG4gICAgICAgICAgdFRpbWUgPSBNYXRoLmNlaWwoQHRocm90dGxlVGltZSAvIEB0aHJvdHRsZUNvdW50cylcbiAgICAgICAgICBjb25zb2xlLmxvZyBcImF2Z0RyYXdUaW1lL1dlYktpdFwiLCB0VGltZVxuICAgICAgICAgICMgcmVtb3ZlIHBlcmYgYW5hbHlzZXJcbiAgICAgICAgICBAdGhyb3R0bGVkRHJhdyA9IEBkcmF3XG4gICAgZWxzZVxuICAgICAgIyBzbG93IGJyb3dzZXJzIGxpa2UgR2Vja29cbiAgICAgIEB0aHJvdHRsZWREcmF3ID0gXy50aHJvdHRsZSBAdGhyb3R0bGVkRHJhdywgMzBcblxuICAgIEBtYW5hZ2VFdmVudHMoKVxuXG4gICMgbWVhc3VyZXMgdGhlIHRpbWUgb2YgYSByZWRyYXcgYW5kIHRodXMgc2V0IHRoZSB0aHJvdHRsZSBsaW1pdFxuICB0aHJvdHRsZWREcmF3OiAtPlxuICAgICMgK25ldyBpcyB0aGUgZmFzdGVzdDogaHR0cDovL2pzcGVyZi5jb20vbmV3LWRhdGUtdnMtZGF0ZS1ub3ctdnMtcGVyZm9ybWFuY2Utbm93LzZcbiAgICBzdGFydCA9ICtuZXcgRGF0ZSgpXG4gICAgQGRyYXcoKVxuICAgIEB0aHJvdHRsZVRpbWUgKz0gK25ldyBEYXRlKCkgLSBzdGFydFxuICAgIEB0aHJvdHRsZUNvdW50cysrXG5cbiAgICAjIHJlbW92ZSBpdHNlbGYgYWZ0ZXIgYW5hbHlzaXNcbiAgICBpZiBAdGhyb3R0bGVDb3VudHMgPiAxNVxuICAgICAgdFRpbWUgPSBNYXRoLmNlaWwoQHRocm90dGxlVGltZSAvIEB0aHJvdHRsZUNvdW50cylcbiAgICAgIGNvbnNvbGUubG9nIFwiYXZnRHJhd1RpbWVcIiwgdFRpbWVcbiAgICAgIHRUaW1lICo9ICAxLjIgIyBhZGQgc2FmZXR5IHRpbWVcbiAgICAgIHRUaW1lID0gTWF0aC5tYXggMjAsIHRUaW1lICMgbGltaXQgZm9yIHVsdHJhIGZhc3QgY29tcHV0ZXJzXG4gICAgICBAdGhyb3R0bGVkRHJhdyA9IF8udGhyb3R0bGUgQGRyYXcsIHRUaW1lXG5cbiAgbWFuYWdlRXZlbnRzOiAtPlxuICAgIGV2ZW50cyA9IHt9XG4gICAgZXZlbnRzLm1vdXNlZG93biA9IFwiX29ubW91c2Vkb3duXCJcbiAgICBldmVudHMudG91Y2hzdGFydCA9IFwiX29udG91Y2hzdGFydFwiXG5cbiAgICBpZiBAZy5jb25maWcuZ2V0IFwicmVnaXN0ZXJNb3VzZUNsaWNrc1wiXG4gICAgICBldmVudHMuZGJsY2xpY2sgPSBcIl9vbmNsaWNrXCJcbiAgICBpZiBAZy5jb25maWcuZ2V0IFwicmVnaXN0ZXJNb3VzZUhvdmVyXCJcbiAgICAgIGV2ZW50cy5tb3VzZWluID0gXCJfb25tb3VzZWluXCJcbiAgICAgIGV2ZW50cy5tb3VzZW91dCA9IFwiX29ubW91c2VvdXRcIlxuXG4gICAgZXZlbnRzLm1vdXNld2hlZWwgPSBcIl9vbm1vdXNld2hlZWxcIlxuICAgIGV2ZW50cy5ET01Nb3VzZVNjcm9sbCA9IFwiX29ubW91c2V3aGVlbFwiXG4gICAgQGRlbGVnYXRlRXZlbnRzIGV2ZW50c1xuXG4gICAgIyBsaXN0ZW4gZm9yIGNoYW5nZXNcbiAgICBAbGlzdGVuVG8gQGcuY29uZmlnLCBcImNoYW5nZTpyZWdpc3Rlck1vdXNlSG92ZXJcIiwgQG1hbmFnZUV2ZW50c1xuICAgIEBsaXN0ZW5UbyBAZy5jb25maWcsIFwiY2hhbmdlOnJlZ2lzdGVyTW91c2VDbGlja1wiLCBAbWFuYWdlRXZlbnRzXG4gICAgQGRyYWdTdGFydCA9IFtdXG5cbiAgZHJhdzogLT5cblxuICAgICMgZmFzdGVzdCB3YXkgdG8gY2xlYXIgdGhlIGNhbnZhc1xuICAgICMgaHR0cDovL2pzcGVyZi5jb20vY2FudmFzLWNsZWFyLXNwZWVkLzI1XG4gICAgQGVsLndpZHRoID0gQGVsLndpZHRoXG5cbiAgICByZWN0SGVpZ2h0ID0gQGcuem9vbWVyLmdldCBcInJvd0hlaWdodFwiXG5cbiAgICAjIHJlY3RzXG4gICAgQGN0eC5nbG9iYWxBbHBoYSA9IEBnLmNvbG9yc2NoZW1lLmdldCBcIm9wYWNpdHlcIlxuICAgIEBkcmF3U2VxcyAoZGF0YSkgLT4gQGRyYXdTZXEoZGF0YSwgQF9kcmF3UmVjdClcbiAgICBAY3R4Lmdsb2JhbEFscGhhID0gMVxuXG4gICAgIyBsZXR0ZXJzXG4gICAgQGRyYXdTZXFzIChkYXRhKSAtPiBAZHJhd1NlcShkYXRhLCBAX2RyYXdMZXR0ZXIpXG5cbiAgICAjIGZlYXR1cmVzLCBzZWxlY3Rpb25cbiAgICBAZHJhd1NlcXMgQGRyYXdTZXFFeHRlbmRlZFxuXG4gIGRyYXdTZXFzOiAoY2FsbGJhY2spIC0+XG4gICAgcmVjdEhlaWdodCA9IEBnLnpvb21lci5nZXQgXCJyb3dIZWlnaHRcIlxuICAgIGhpZGRlbiA9IEBnLmNvbHVtbnMuZ2V0IFwiaGlkZGVuXCJcblxuICAgIHN0YXJ0ID0gTWF0aC5tYXggMCwgTWF0aC5hYnMoTWF0aC5jZWlsKCAtIEBnLnpvb21lci5nZXQoJ19hbGlnbm1lbnRTY3JvbGxUb3AnKSAvIHJlY3RIZWlnaHQpKVxuICAgIHkgPSAtIE1hdGguYWJzKCAtIEBnLnpvb21lci5nZXQoJ19hbGlnbm1lbnRTY3JvbGxUb3AnKSAlIHJlY3RIZWlnaHQpXG4gICAgZm9yIGkgaW4gW3N0YXJ0Li4gQG1vZGVsLmxlbmd0aCAtIDFdIGJ5IDFcbiAgICAgIGNvbnRpbnVlIGlmIEBtb2RlbC5hdChpKS5nZXQoJ2hpZGRlbicpXG4gICAgICBjYWxsYmFjay5jYWxsIEAsIHttb2RlbDogQG1vZGVsLmF0KGkpLCB5OiB5LCBoaWRkZW46IGhpZGRlbn1cbiAgICAgIHkgPSB5ICsgcmVjdEhlaWdodFxuICAgICAgIyBvdXQgb2Ygdmlld3BvcnQgLSBzdG9wXG4gICAgICBpZiB5ID4gQGVsLmhlaWdodFxuICAgICAgICBicmVha1xuXG4gICMgVE9ETzogdmVyeSBleHBlbnNpdmUgbWV0aG9kXG4gIGRyYXdTZXE6IChkYXRhLCBjYWxsYmFjaykgLT5cbiAgICBzZXEgPSBkYXRhLm1vZGVsLmdldCBcInNlcVwiXG4gICAgeSA9IGRhdGEueVxuICAgIHJlY3RXaWR0aCA9IEBnLnpvb21lci5nZXQgXCJjb2x1bW5XaWR0aFwiXG4gICAgcmVjdEhlaWdodCA9IEBnLnpvb21lci5nZXQgXCJyb3dIZWlnaHRcIlxuXG4gICAgIyBza2lwIHVubmVlZGVkIGJsb2NrcyBhdCB0aGUgYmVnaW5uaW5nXG4gICAgc3RhcnQgPSBNYXRoLm1heCAwLCBNYXRoLmFicyhNYXRoLmNlaWwoIC0gQGcuem9vbWVyLmdldCgnX2FsaWdubWVudFNjcm9sbExlZnQnKSAvIHJlY3RXaWR0aCkpXG4gICAgeCA9IC0gTWF0aC5hYnMoIC0gQGcuem9vbWVyLmdldCgnX2FsaWdubWVudFNjcm9sbExlZnQnKSAlIHJlY3RXaWR0aClcblxuICAgIHJlcyA9IHtyZWN0V2lkdGg6IHJlY3RXaWR0aCwgcmVjdEhlaWdodDogcmVjdEhlaWdodCwgeTogeX1cbiAgICBlbFdpZHRoID0gQGVsLndpZHRoXG5cbiAgICBmb3IgaiBpbiBbc3RhcnQuLiBzZXEubGVuZ3RoIC0gMV0gYnkgMVxuICAgICAgYyA9IHNlcVtqXVxuICAgICAgYyA9IGMudG9VcHBlckNhc2UoKVxuXG4gICAgICAjIGNhbGwgdGhlIGN1c3RvbSBmdW5jdGlvblxuICAgICAgcmVzLnggPSB4XG4gICAgICByZXMuYyA9IGNcblxuICAgICAgIyBsb2NhbCBjYWxsIGlzIGZhc3RlciB0aGFuIGFwcGx5XG4gICAgICAjIGh0dHA6Ly9qc3BlcmYuY29tL2Z1bmN0aW9uLWNhbGxzLWRpcmVjdC12cy1hcHBseS12cy1jYWxsLXZzLWJpbmQvNlxuICAgICAgaWYgZGF0YS5oaWRkZW4uaW5kZXhPZihqKSA8IDBcbiAgICAgICAgY2FsbGJhY2sgQCxyZXNcbiAgICAgIGVsc2VcbiAgICAgICAgY29udGludWVcblxuICAgICAgIyBtb3ZlIHRvIHRoZSByaWdodFxuICAgICAgeCA9IHggKyByZWN0V2lkdGhcblxuICAgICAgIyBvdXQgb2Ygdmlld3BvcnQgLSBzdG9wXG4gICAgICBpZiB4ID4gZWxXaWR0aFxuICAgICAgICBicmVha1xuXG4gIF9kcmF3UmVjdDogKHRoYXQsIGRhdGEpIC0+XG4gICAgY29sb3IgPSB0aGF0LmNvbG9yW2RhdGEuY11cbiAgICBpZiBjb2xvcj9cbiAgICAgIHRoYXQuY3R4LmZpbGxTdHlsZSA9IGNvbG9yXG4gICAgICB0aGF0LmN0eC5maWxsUmVjdCBkYXRhLngsZGF0YS55LGRhdGEucmVjdFdpZHRoLGRhdGEucmVjdEhlaWdodFxuXG4gICMgUkVBTExZIGV4cGVuc2l2ZSBjYWxsIG9uIEZGXG4gICMgUGVyZm9ybWFuY2U6XG4gICMgY2hyb21lOiAyMDAwbXMgZHJhd0xldHRlciAtIDEwMDBtcyBkcmF3UmVjdFxuICAjIEZGOiAxNzAwbXMgZHJhd0xldHRlciAtIDMwMG1zIGRyYXdSZWN0XG4gIF9kcmF3TGV0dGVyOiAodGhhdCxkYXRhKSAtPlxuICAgIHRoYXQuY3R4LmRyYXdJbWFnZSB0aGF0LmNhY2hlLmdldEZvbnRUaWxlKGRhdGEuYywgZGF0YS5yZWN0V2lkdGgsXG4gICAgICBkYXRhLnJlY3RIZWlnaHQpLCBkYXRhLngsIGRhdGEueSxkYXRhLnJlY3RXaWR0aCxkYXRhLnJlY3RIZWlnaHRcblxuICBkcmF3U2VxRXh0ZW5kZWQ6IChkYXRhKSAtPlxuICAgIHNlcSA9IGRhdGEubW9kZWwuZ2V0IFwic2VxXCJcbiAgICByZWN0V2lkdGggPSBAZy56b29tZXIuZ2V0IFwiY29sdW1uV2lkdGhcIlxuICAgIHJlY3RIZWlnaHQgPSBAZy56b29tZXIuZ2V0IFwicm93SGVpZ2h0XCJcblxuICAgIHN0YXJ0ID0gTWF0aC5tYXggMCwgTWF0aC5hYnMoTWF0aC5jZWlsKCAtIEBnLnpvb21lci5nZXQoJ19hbGlnbm1lbnRTY3JvbGxMZWZ0JykgLyByZWN0V2lkdGgpKVxuICAgIHggPSAtIE1hdGguYWJzKCAtIEBnLnpvb21lci5nZXQoJ19hbGlnbm1lbnRTY3JvbGxMZWZ0JykgJSByZWN0V2lkdGgpXG4gICAgeFplcm8gPSB4IC0gc3RhcnQgKiByZWN0V2lkdGhcblxuICAgIHNlbGVjdGlvbiA9IEBfZ2V0U2VsZWN0aW9uIGRhdGEubW9kZWxcbiAgICBbbVByZXZTZWwsbU5leHRTZWxdID0gQF9nZXRQcmV2TmV4dFNlbGVjdGlvbiBkYXRhLm1vZGVsXG4gICAgZmVhdHVyZXMgPSBkYXRhLm1vZGVsLmdldCBcImZlYXR1cmVzXCJcblxuICAgIHlaZXJvID0gZGF0YS55XG5cbiAgICBmb3IgaiBpbiBbc3RhcnQuLiBzZXEubGVuZ3RoIC0gMV0gYnkgMVxuICAgICAgc3RhcnRzID0gZmVhdHVyZXMuc3RhcnRPbiBqXG5cbiAgICAgIGlmIGRhdGEuaGlkZGVuLmluZGV4T2YoaikgPj0gMFxuICAgICAgICBjb250aW51ZVxuXG4gICAgICBpZiBzdGFydHMubGVuZ3RoID4gMFxuICAgICAgICBmb3IgZiBpbiBzdGFydHNcbiAgICAgICAgICBAYXBwZW5kRmVhdHVyZSBmOiBmLHhaZXJvOiB4LCB5WmVybzogeVplcm9cblxuICAgICAgeCA9IHggKyByZWN0V2lkdGhcbiAgICAgICMgb3V0IG9mIHZpZXdwb3J0IC0gc3RvcFxuICAgICAgaWYgeCA+IEBlbC53aWR0aFxuICAgICAgICBicmVha1xuXG4gICAgQF9hcHBlbmRTZWxlY3Rpb24gbW9kZWw6IGRhdGEubW9kZWwsIHhaZXJvOiB4WmVybywgeVplcm86IHlaZXJvLCBoaWRkZW46XG4gICAgICBkYXRhLmhpZGRlblxuXG4gIHJlbmRlcjogLT5cblxuICAgIEBlbC5zZXRBdHRyaWJ1dGUgJ2hlaWdodCcsIEBnLnpvb21lci5nZXQgXCJhbGlnbm1lbnRIZWlnaHRcIlxuICAgIEBlbC5zZXRBdHRyaWJ1dGUgJ3dpZHRoJywgQGcuem9vbWVyLmdldCBcImFsaWdubWVudFdpZHRoXCJcblxuICAgIEBnLnpvb21lci5fYWRqdXN0V2lkdGggQGVsLCBAbW9kZWxcbiAgICBAZy56b29tZXIuX2NoZWNrU2Nyb2xsaW5nKCBAX2NoZWNrU2Nyb2xsaW5nKFtAZy56b29tZXIuZ2V0KCdfYWxpZ25tZW50U2Nyb2xsTGVmdCcpLFxuICAgIEBnLnpvb21lci5nZXQoJ19hbGlnbm1lbnRTY3JvbGxUb3AnKV0gKSx7aGVhZGVyOiBcImNhbnZhc3NlcVwifSlcblxuICAgIEBjb2xvciA9IGNvbG9yU2VsZWN0b3IuZ2V0Q29sb3IgQGcuY29sb3JzY2hlbWUuZ2V0KFwic2NoZW1lXCIpXG5cbiAgICBAdGhyb3R0bGVkRHJhdygpXG4gICAgQFxuXG4gIF9vbm1vdXNlbW92ZTogKGUsIHJldmVyc2VkKSAtPlxuICAgIHJldHVybiBpZiBAZHJhZ1N0YXJ0Lmxlbmd0aCBpcyAwXG5cbiAgICBkcmFnRW5kID0gbW91c2UuYWJzIGVcbiAgICAjIHJlbGF0aXZlIHRvIGZpcnN0IGNsaWNrXG4gICAgcmVsRW5kID0gW2RyYWdFbmRbMF0gLSBAZHJhZ1N0YXJ0WzBdLCBkcmFnRW5kWzFdIC0gQGRyYWdTdGFydFsxXV1cbiAgICAjIHJlbGF0aXZlIHRvIGluaXRpYWwgc2Nyb2xsIHN0YXR1c1xuXG4gICAgIyBzY2FsZSBldmVudHNcbiAgICBzY2FsZUZhY3RvciA9IEBnLnpvb21lci5nZXQgXCJjYW52YXNFdmVudFNjYWxlXCJcbiAgICBpZiByZXZlcnNlZFxuICAgICAgc2NhbGVGYWN0b3IgPSAzXG4gICAgZm9yIGkgaW4gWzAuLjFdIGJ5IDFcbiAgICAgIHJlbEVuZFtpXSA9IHJlbEVuZFtpXSAqIHNjYWxlRmFjdG9yXG5cbiAgICAjIGNhbGN1bGF0ZSBuZXcgc2Nyb2xsaW5nIHZhbHNcbiAgICByZWxEaXN0ID0gW0BkcmFnU3RhcnRTY3JvbGxbMF0gLSByZWxFbmRbMF0sIEBkcmFnU3RhcnRTY3JvbGxbMV0gLSByZWxFbmRbMV1dXG5cbiAgICAjIHJvdW5kIHZhbHVlc1xuICAgIGZvciBpIGluIFswLi4xXSBieSAxXG4gICAgICByZWxEaXN0W2ldID0gTWF0aC5yb3VuZCByZWxEaXN0W2ldXG5cbiAgICAjIHVwZGF0ZSBzY3JvbGxiYXJcbiAgICBzY3JvbGxDb3JyZWN0ZWQgPSBAX2NoZWNrU2Nyb2xsaW5nKCByZWxEaXN0KVxuICAgIEBnLnpvb21lci5fY2hlY2tTY3JvbGxpbmcgc2Nyb2xsQ29ycmVjdGVkLCB7b3JpZ2luOiBcImNhbnZhc3NlcVwifVxuXG4gICAgIyByZXNldCBzdGFydCBpZiB1c2Ugc2Nyb2xscyBvdXQgb2YgYm91bmRzXG4gICAgZm9yIGkgaW4gWzAuLjFdIGJ5IDFcbiAgICAgIGlmIHNjcm9sbENvcnJlY3RlZFtpXSBpc250IHJlbERpc3RbaV1cbiAgICAgICAgaWYgc2Nyb2xsQ29ycmVjdGVkW2ldIGlzIDBcbiAgICAgICAgICAjIHJlc2V0IG9mIGxlZnQsIHRvcFxuICAgICAgICAgIEBkcmFnU3RhcnRbaV0gPSBkcmFnRW5kW2ldXG4gICAgICAgICAgQGRyYWdTdGFydFNjcm9sbFtpXSA9IDBcbiAgICAgICAgZWxzZVxuICAgICAgICAgICMgcmVjYWxpYnJhdGUgb24gcmlnaHQsIGJvdHRvbVxuICAgICAgICAgIEBkcmFnU3RhcnRbaV0gPSBkcmFnRW5kW2ldIC0gc2Nyb2xsQ29ycmVjdGVkW2ldXG5cbiAgICBAdGhyb3R0bGVkRHJhdygpXG5cbiAgICAjIGFib3J0IHNlbGVjdGlvbiBldmVudHMgb2YgdGhlIGJyb3dzZXIgKG1vdXNlIG9ubHkpXG4gICAgaWYgZS5wcmV2ZW50RGVmYXVsdD9cbiAgICAgIGUucHJldmVudERlZmF1bHQoKVxuICAgICAgZS5zdG9wUHJvcGFnYXRpb24oKVxuXG4gICMgY29udmVydHMgdG91Y2hlcyBpbnRvIG9sZCBtb3VzZSBldmVudFxuICBfb250b3VjaG1vdmU6IChlKSAtPlxuICAgIEBfb25tb3VzZW1vdmUgZS5jaGFuZ2VkVG91Y2hlc1swXSwgdHJ1ZVxuICAgIGUucHJldmVudERlZmF1bHQoKVxuICAgIGUuc3RvcFByb3BhZ2F0aW9uKClcblxuICAjIHN0YXJ0IHRoZSBkcmFnZ2luZyBtb2RlXG4gIF9vbm1vdXNlZG93bjogKGUpIC0+XG4gICAgQGRyYWdTdGFydCA9IG1vdXNlLmFicyBlXG4gICAgQGRyYWdTdGFydFNjcm9sbCA9IFtAZy56b29tZXIuZ2V0KCdfYWxpZ25tZW50U2Nyb2xsTGVmdCcpLCBAZy56b29tZXIuZ2V0KCdfYWxpZ25tZW50U2Nyb2xsVG9wJyldXG4gICAgamJvbmUoZG9jdW1lbnQuYm9keSkub24gJ21vdXNlbW92ZS5vdmVybW92ZScsIChlKSA9PiBAX29ubW91c2Vtb3ZlKGUpXG4gICAgamJvbmUoZG9jdW1lbnQuYm9keSkub24gJ21vdXNldXAub3ZlcnVwJywgPT4gQF9jbGVhbnVwKClcbiAgICAjamJvbmUoZG9jdW1lbnQuYm9keSkub24gJ21vdXNlb3V0Lm92ZXJvdXQnLCAoZSkgPT4gQF9vbm1vdXNld2lub3V0KGUpXG4gICAgZS5wcmV2ZW50RGVmYXVsdCgpXG5cbiAgIyBzdGFydHMgdGhlIHRvdWNoIG1vZGVcbiAgX29udG91Y2hzdGFydDogKGUpIC0+XG4gICAgQGRyYWdTdGFydCA9IG1vdXNlLmFicyBlLmNoYW5nZWRUb3VjaGVzWzBdXG4gICAgQGRyYWdTdGFydFNjcm9sbCA9IFtAZy56b29tZXIuZ2V0KCdfYWxpZ25tZW50U2Nyb2xsTGVmdCcpLCBAZy56b29tZXIuZ2V0KCdfYWxpZ25tZW50U2Nyb2xsVG9wJyldXG4gICAgamJvbmUoZG9jdW1lbnQuYm9keSkub24gJ3RvdWNobW92ZS5vdmVydG1vdmUnLCAoZSkgPT4gQF9vbnRvdWNobW92ZShlKVxuICAgIGpib25lKGRvY3VtZW50LmJvZHkpLm9uICd0b3VjaGVuZC5vdmVydGVuZCB0b3VjaGxlYXZlLm92ZXJ0bGVhdmVcbiAgICB0b3VjaGNhbmNlbC5vdmVydGNhbmVsJywgKGUpID0+IEBfdG91Y2hDbGVhbnVwKGUpXG5cbiAgIyBjaGVja3Mgd2hldGhlciBtb3VzZSBtb3ZlZCBvdXQgb2YgdGhlIHdpbmRvd1xuICAjIC0+IHRlcm1pbmF0ZSBkcmFnZ2luZ1xuICBfb25tb3VzZXdpbm91dDogKGUpIC0+XG4gICAgaWYgZS50b0VsZW1lbnQgaXMgZG9jdW1lbnQuYm9keS5wYXJlbnROb2RlXG4gICAgICBAX2NsZWFudXAoKVxuXG4gICMgdGVybWluYXRlcyBkcmFnZ2luZ1xuICBfY2xlYW51cDogLT5cbiAgICBAZHJhZ1N0YXJ0ID0gW11cbiAgICAjIHJlbW92ZSBhbGwgbGlzdGVuZXJzXG4gICAgamJvbmUoZG9jdW1lbnQuYm9keSkub2ZmKCcub3Zlcm1vdmUnKVxuICAgIGpib25lKGRvY3VtZW50LmJvZHkpLm9mZignLm92ZXJ1cCcpXG4gICAgamJvbmUoZG9jdW1lbnQuYm9keSkub2ZmKCcub3Zlcm91dCcpXG5cbiAgIyB0ZXJtaW5hdGVzIHRvdWNoaW5nXG4gIF90b3VjaENsZWFudXA6IChlKSAtPlxuICAgIGlmIGUuY2hhbmdlZFRvdWNoZXMubGVuZ3RoID4gMFxuICAgICAgIyBtYXliZSB3ZSBjYW4gc2VuZCBhIGZpbmFsIGV2ZW50XG4gICAgICBAX29ubW91c2Vtb3ZlIGUuY2hhbmdlZFRvdWNoZXNbMF0sIHRydWVcblxuICAgIEBkcmFnU3RhcnQgPSBbXVxuICAgICMgcmVtb3ZlIGFsbCBsaXN0ZW5lcnNcbiAgICBqYm9uZShkb2N1bWVudC5ib2R5KS5vZmYoJy5vdmVydG1vdmUnKVxuICAgIGpib25lKGRvY3VtZW50LmJvZHkpLm9mZignLm92ZXJ0ZW5kJylcbiAgICBqYm9uZShkb2N1bWVudC5ib2R5KS5vZmYoJy5vdmVydGxlYXZlJylcbiAgICBqYm9uZShkb2N1bWVudC5ib2R5KS5vZmYoJy5vdmVydGNhbmNlbCcpXG5cbiAgIyBtaWdodCBiZSBpbmNvbXBhdGlibGUgd2l0aCBzb21lIGJyb3dzZXJzXG4gIF9vbm1vdXNld2hlZWw6IChlKSAtPlxuICAgIGRlbHRhID0gbW91c2Uud2hlZWxEZWx0YSBlXG4gICAgQGcuem9vbWVyLnNldCAnX2FsaWdubWVudFNjcm9sbExlZnQnLCBAZy56b29tZXIuZ2V0KCdfYWxpZ25tZW50U2Nyb2xsTGVmdCcpICsgZGVsdGFbMF1cbiAgICBAZy56b29tZXIuc2V0ICdfYWxpZ25tZW50U2Nyb2xsVG9wJywgQGcuem9vbWVyLmdldCgnX2FsaWdubWVudFNjcm9sbFRvcCcpICsgZGVsdGFbMV1cbiAgICBlLnByZXZlbnREZWZhdWx0KClcblxuICBfb25jbGljazogKGUpIC0+XG4gICAgQGcudHJpZ2dlciBcInJlc2lkdWU6Y2xpY2tcIiwgQF9nZXRDbGlja1BvcyhlKVxuICAgIEB0aHJvdHRsZWREcmF3KClcblxuICBfb25tb3VzZWluOiAoZSkgLT5cbiAgICBAZy50cmlnZ2VyIFwicmVzaWR1ZTpjbGlja1wiLCBAX2dldENsaWNrUG9zKGUpXG4gICAgQHRocm90dGxlZERyYXcoKVxuXG4gIF9vbm1vdXNlb3V0OiAoZSkgLT5cbiAgICBAZy50cmlnZ2VyIFwicmVzaWR1ZTpjbGlja1wiLCBAX2dldENsaWNrUG9zKGUpXG4gICAgQHRocm90dGxlZERyYXcoKVxuXG4gIF9nZXRDbGlja1BvczogKGUpIC0+XG4gICAgY29vcmRzID0gbW91c2UucmVsIGVcbiAgICBjb29yZHNbMF0gKz0gQGcuem9vbWVyLmdldChcIl9hbGlnbm1lbnRTY3JvbGxMZWZ0XCIpXG4gICAgY29vcmRzWzFdICs9IEBnLnpvb21lci5nZXQoXCJfYWxpZ25tZW50U2Nyb2xsVG9wXCIpXG4gICAgeCA9IE1hdGguZmxvb3IoY29vcmRzWzBdIC8gQGcuem9vbWVyLmdldChcImNvbHVtbldpZHRoXCIpIClcbiAgICB5ID0gTWF0aC5mbG9vcihjb29yZHNbMV0gLyBAZy56b29tZXIuZ2V0KFwicm93SGVpZ2h0XCIpKVxuXG4gICAgIyBhZGQgaGlkZGVuIGNvbHVtbnNcbiAgICB4ICs9IEBnLmNvbHVtbnMuY2FsY0hpZGRlbkNvbHVtbnMgeFxuICAgICMgYWRkIGhpZGRlbiBzZXFzXG4gICAgeSArPSBAbW9kZWwuY2FsY0hpZGRlblNlcXMgeVxuXG4gICAgeCA9IE1hdGgubWF4IDAseFxuICAgIHkgPSBNYXRoLm1heCAwLHlcbiAgICBzZXFJZCA9IEBtb2RlbC5hdCh5KS5nZXQgXCJpZFwiXG4gICAgcmV0dXJuIHtzZXFJZDpzZXFJZCwgcm93UG9zOiB4LCBldnQ6ZX1cblxuICAjIGNoZWNrcyB3aGV0aGVyIHRoZSBzY3JvbGxpbmcgY29vcmRpbmF0ZXMgYXJlIHZhbGlkXG4gICMgQHJldHVybnM6IFt4U2Nyb2xsLHlTY3JvbGxdIHZhbGlkIGNvb3JkaW5hdGVzXG4gIF9jaGVja1Njcm9sbGluZzogKHNjcm9sbE9iaikgLT5cblxuICAgICMgMDogbWF4TGVmdCwgMTogbWF4VG9wXG4gICAgbWF4ID0gW0Btb2RlbC5nZXRNYXhMZW5ndGgoKSAqIEBnLnpvb21lci5nZXQoXCJjb2x1bW5XaWR0aFwiKSAtIEBnLnpvb21lci5nZXQoJ2FsaWdubWVudFdpZHRoJyksXG4gICAgQG1vZGVsLmxlbmd0aCAgKiBAZy56b29tZXIuZ2V0KFwicm93SGVpZ2h0XCIpIC0gQGcuem9vbWVyLmdldCgnYWxpZ25tZW50SGVpZ2h0JyldXG5cbiAgICBmb3IgaSBpbiBbMC4uMV0gYnkgMVxuICAgICAgaWYgc2Nyb2xsT2JqW2ldID4gbWF4W2ldXG4gICAgICAgIHNjcm9sbE9ialtpXSA9IG1heFtpXVxuXG4gICAgICBpZiBzY3JvbGxPYmpbaV0gPCAwXG4gICAgICAgIHNjcm9sbE9ialtpXSA9IDBcblxuICAgIHJldHVybiBzY3JvbGxPYmpcblxuICAjIFRPRE86IHNob3VsZCBJIGJlIG1vdmVkIHRvIHRoZSBzZWxlY3Rpb24gbWFuYWdlcj9cbiAgIyByZXR1cm5zIGFuIGFycmF5IHdpdGggdGhlIGN1cnJlbnRseSBzZWxlY3RlZCByZXNpZHVlc1xuICAjIGUuZy4gWzAsM10gPSBwb3MgMCBhbmQgMyBhcmUgc2VsZWN0ZWRcbiAgX2dldFNlbGVjdGlvbjogKG1vZGVsKSAtPlxuICAgIG1heExlbiA9IG1vZGVsLmdldChcInNlcVwiKS5sZW5ndGhcbiAgICBzZWxlY3Rpb24gPSBbXVxuICAgIHNlbHMgPSBAZy5zZWxjb2wuZ2V0U2VsRm9yUm93IG1vZGVsLmdldCBcImlkXCJcbiAgICByb3dzID0gXy5maW5kIHNlbHMsIChlbCkgLT4gZWwuZ2V0KFwidHlwZVwiKSBpcyBcInJvd1wiXG4gICAgaWYgcm93cz9cbiAgICAgICMgZnVsbCBtYXRjaFxuICAgICAgZm9yIG4gaW4gWzAuLm1heExlbiAtIDFdIGJ5IDFcbiAgICAgICAgc2VsZWN0aW9uLnB1c2ggblxuICAgIGVsc2UgaWYgc2Vscy5sZW5ndGggPiAwXG4gICAgICBmb3Igc2VsIGluIHNlbHNcbiAgICAgICAgZm9yIG4gaW4gW3NlbC5nZXQoXCJ4U3RhcnRcIikuLnNlbC5nZXQoXCJ4RW5kXCIpXSBieSAxXG4gICAgICAgICAgc2VsZWN0aW9uLnB1c2ggblxuXG4gICAgcmV0dXJuIHNlbGVjdGlvblxuXG4gICMgZHJhd3MgZmVhdHVyZXNcbiAgYXBwZW5kRmVhdHVyZTogKGRhdGEpIC0+XG4gICAgZiA9IGRhdGEuZlxuICAgICMgVE9ETzogdGhpcyBpcyBhIHZlcnkgbmFpdmUgd2F5XG4gICAgYm94V2lkdGggPSBAZy56b29tZXIuZ2V0KFwiY29sdW1uV2lkdGhcIilcbiAgICBib3hIZWlnaHQgPSBAZy56b29tZXIuZ2V0KFwicm93SGVpZ2h0XCIpXG4gICAgd2lkdGggPSAoZi5nZXQoXCJ4RW5kXCIpIC0gZi5nZXQoXCJ4U3RhcnRcIikpICogYm94V2lkdGhcblxuICAgIGJlZm9yZVdpZHRoID0gQGN0eC5saW5lV2lkdGhcbiAgICBAY3R4LmxpbmVXaWR0aCA9IDNcbiAgICBiZWZvcmVTdHlsZSA9IEBjdHguc3Ryb2tlU3R5bGVcbiAgICBAY3R4LnN0cm9rZVN0eWxlID0gZi5nZXQgXCJmaWxsQ29sb3JcIlxuXG4gICAgQGN0eC5zdHJva2VSZWN0IGRhdGEueFplcm8sIGRhdGEueVplcm8sIHdpZHRoLGJveEhlaWdodFxuICAgIEBjdHguc3Ryb2tlU3R5bGUgPSBiZWZvcmVTdHlsZVxuICAgIEBjdHgubGluZVdpZHRoID0gYmVmb3JlV2lkdGhcblxuXG4gICMgbG9vcHMgb3ZlciBhbGwgc2VsZWN0aW9uIGFuZCBjYWxscyB0aGUgcmVuZGVyIG1ldGhvZFxuICBfYXBwZW5kU2VsZWN0aW9uOiAoZGF0YSkgLT5cbiAgICBzZXEgPSBkYXRhLm1vZGVsLmdldChcInNlcVwiKVxuICAgIHNlbGVjdGlvbiA9IEBfZ2V0U2VsZWN0aW9uIGRhdGEubW9kZWxcbiAgICAjIGdldCB0aGUgc3RhdHVzIG9mIHRoZSB1cHBlciBhbmQgbG93ZXIgcm93XG4gICAgW21QcmV2U2VsLG1OZXh0U2VsXSA9IEBfZ2V0UHJldk5leHRTZWxlY3Rpb24gZGF0YS5tb2RlbFxuXG4gICAgYm94V2lkdGggPSBAZy56b29tZXIuZ2V0KFwiY29sdW1uV2lkdGhcIilcbiAgICBib3hIZWlnaHQgPSBAZy56b29tZXIuZ2V0KFwicm93SGVpZ2h0XCIpXG5cbiAgICAjIGF2b2lkIHVubmVjZXNzYXJ5IGxvb3BzXG4gICAgcmV0dXJuIGlmIHNlbGVjdGlvbi5sZW5ndGggaXMgMFxuXG4gICAgaGlkZGVuT2Zmc2V0ID0gMFxuICAgIGZvciBuIGluIFswLi5zZXEubGVuZ3RoIC0gMV0gYnkgMVxuICAgICAgaWYgZGF0YS5oaWRkZW4uaW5kZXhPZihuKSA+PSAwXG4gICAgICAgIGhpZGRlbk9mZnNldCsrXG4gICAgICBlbHNlXG4gICAgICAgIGsgPSBuIC0gaGlkZGVuT2Zmc2V0XG4gICAgICAgICMgb25seSBpZiBpdHMgYSBuZXcgc2VsZWN0aW9uXG4gICAgICAgIGlmIHNlbGVjdGlvbi5pbmRleE9mKG4pID49IDAgYW5kIChrIGlzIDAgb3Igc2VsZWN0aW9uLmluZGV4T2YobiAtIDEpIDwgMCApXG4gICAgICAgICAgQF9yZW5kZXJTZWxlY3Rpb24gbjpuLGs6ayxzZWxlY3Rpb246IHNlbGVjdGlvbixtUHJldlNlbDogbVByZXZTZWwsbU5leHRTZWw6bU5leHRTZWwsIHhaZXJvOiBkYXRhLnhaZXJvLCB5WmVybzogZGF0YS55WmVybywgbW9kZWw6IGRhdGEubW9kZWxcblxuICAjIGRyYXdzIGEgc2luZ2xlIHVzZXIgc2VsZWN0aW9uXG4gIF9yZW5kZXJTZWxlY3Rpb246IChkYXRhKSAtPlxuXG4gICAgeFplcm8gPSBkYXRhLnhaZXJvXG4gICAgeVplcm8gPSBkYXRhLnlaZXJvXG4gICAgbiA9IGRhdGEublxuICAgIGsgPSBkYXRhLmtcbiAgICBzZWxlY3Rpb24gPSBkYXRhLnNlbGVjdGlvblxuICAgICMgYW5kIGNoZWNrcyB0aGUgcHJldiBhbmQgbmV4dCByb3cgZm9yIHNlbGVjdGlvbiAgLT4gbm8gYm9yZGVycyBpbiBhIHNlbGVjdGlvblxuICAgIG1QcmV2U2VsPSBkYXRhLm1QcmV2U2VsXG4gICAgbU5leHRTZWwgPSBkYXRhLm1OZXh0U2VsXG5cbiAgICAjIGdldCB0aGUgbGVuZ3RoIG9mIHRoaXMgc2VsZWN0aW9uXG4gICAgc2VsZWN0aW9uTGVuZ3RoID0gMFxuICAgIGZvciBpIGluIFtuLi4gZGF0YS5tb2RlbC5nZXQoXCJzZXFcIikubGVuZ3RoIC0gMV0gYnkgMVxuICAgICAgaWYgc2VsZWN0aW9uLmluZGV4T2YoaSkgPj0gMFxuICAgICAgICBzZWxlY3Rpb25MZW5ndGgrK1xuICAgICAgZWxzZVxuICAgICAgICBicmVha1xuXG4gICAgIyBUT0RPOiB1Z2x5IVxuICAgIGJveFdpZHRoID0gQGcuem9vbWVyLmdldChcImNvbHVtbldpZHRoXCIpXG4gICAgYm94SGVpZ2h0ID0gQGcuem9vbWVyLmdldChcInJvd0hlaWdodFwiKVxuICAgIHRvdGFsV2lkdGggPSAoYm94V2lkdGggKiBzZWxlY3Rpb25MZW5ndGgpICsgMVxuXG4gICAgaGlkZGVuID0gQGcuY29sdW1ucy5nZXQoJ2hpZGRlbicpXG5cbiAgICBAY3R4LmJlZ2luUGF0aCgpXG4gICAgYmVmb3JlV2lkdGggPSBAY3R4LmxpbmVXaWR0aFxuICAgIEBjdHgubGluZVdpZHRoID0gM1xuICAgIGJlZm9yZVN0eWxlID0gQGN0eC5zdHJva2VTdHlsZVxuICAgIEBjdHguc3Ryb2tlU3R5bGUgPSBcIiNGRjAwMDBcIlxuXG4gICAgeFplcm8gKz0gayAqIGJveFdpZHRoXG5cbiAgICAjIHNwbGl0IHVwIHRoZSBzZWxlY3Rpb24gaW50byBzaW5nbGUgY2VsbHNcbiAgICB4UGFydCA9IDBcbiAgICBmb3IgaSBpbiBbMC4uIHNlbGVjdGlvbkxlbmd0aCAtIDFdXG4gICAgICB4UG9zID0gbiArIGlcbiAgICAgIGlmIGhpZGRlbi5pbmRleE9mKHhQb3MpID49IDBcbiAgICAgICAgY29udGludWVcbiAgICAgICMgdXBwZXIgbGluZVxuICAgICAgdW5sZXNzIG1QcmV2U2VsPyBhbmQgbVByZXZTZWwuaW5kZXhPZih4UG9zKSA+PSAwXG4gICAgICAgIEBjdHgubW92ZVRvIHhaZXJvICsgeFBhcnQsIHlaZXJvXG4gICAgICAgIEBjdHgubGluZVRvIHhQYXJ0ICsgYm94V2lkdGggKyB4WmVybywgeVplcm9cbiAgICAgICMgbG93ZXIgbGluZVxuICAgICAgdW5sZXNzIG1OZXh0U2VsPyBhbmQgbU5leHRTZWwuaW5kZXhPZih4UG9zKSA+PSAwXG4gICAgICAgIEBjdHgubW92ZVRvIHhQYXJ0ICsgeFplcm8sIGJveEhlaWdodCArIHlaZXJvXG4gICAgICAgIEBjdHgubGluZVRvIHhQYXJ0ICsgYm94V2lkdGggKyB4WmVybywgYm94SGVpZ2h0ICsgeVplcm9cblxuICAgICAgeFBhcnQgKz0gYm94V2lkdGhcblxuICAgICMgbGVmdFxuICAgIEBjdHgubW92ZVRvIHhaZXJvLHlaZXJvXG4gICAgQGN0eC5saW5lVG8geFplcm8sIGJveEhlaWdodCArIHlaZXJvXG5cbiAgICAjIHJpZ2h0XG4gICAgQGN0eC5tb3ZlVG8geFplcm8gKyB0b3RhbFdpZHRoLHlaZXJvXG4gICAgQGN0eC5saW5lVG8geFplcm8gKyB0b3RhbFdpZHRoLCBib3hIZWlnaHQgKyB5WmVyb1xuXG4gICAgQGN0eC5zdHJva2UoKVxuICAgIEBjdHguc3Ryb2tlU3R5bGUgPSBiZWZvcmVTdHlsZVxuICAgIEBjdHgubGluZVdpZHRoID0gYmVmb3JlV2lkdGhcblxuICAjIGxvb2tzIGF0IHRoZSBzZWxlY3Rpb24gb2YgdGhlIHByZXYgYW5kIG5leHQgZWxcbiAgIyBUT0RPOiB0aGlzIGlzIHZlcnkgbmFpdmUsIGFzIHRoZXJlIG1pZ2h0IGJlIGdhcHMgYWJvdmUgb3IgYmVsb3dcbiAgX2dldFByZXZOZXh0U2VsZWN0aW9uOiAobW9kZWwpIC0+XG5cbiAgICBtb2RlbFByZXYgPSBtb2RlbC5jb2xsZWN0aW9uLnByZXYgbW9kZWxcbiAgICBtb2RlbE5leHQgPSBtb2RlbC5jb2xsZWN0aW9uLm5leHQgbW9kZWxcbiAgICBtUHJldlNlbCA9IEBfZ2V0U2VsZWN0aW9uIG1vZGVsUHJldiBpZiBtb2RlbFByZXY/XG4gICAgbU5leHRTZWwgPSBAX2dldFNlbGVjdGlvbiBtb2RlbE5leHQgaWYgbW9kZWxOZXh0P1xuICAgIFttUHJldlNlbCxtTmV4dFNlbF1cbiIsInZpZXcgPSByZXF1aXJlKFwiYmFja2JvbmUtdmlld2pcIilcbm1vdXNlID0gcmVxdWlyZSBcIm1vdXNlLXBvc1wiXG5zZWxlY3Rpb24gPSByZXF1aXJlIFwiLi4vZy9zZWxlY3Rpb24vU2VsZWN0aW9uXCJcbmNvbG9yU2VsZWN0b3IgPSByZXF1aXJlKFwiYmlvanMtdXRpbC1jb2xvcnNjaGVtZXNcIikuc2VsZWN0b3Jcbmpib25lID0gcmVxdWlyZSBcImpib25lXCJcbl8gPSByZXF1aXJlIFwidW5kZXJzY29yZVwiXG5cbm1vZHVsZS5leHBvcnRzID0gT3ZlcnZpZXdCb3ggPSB2aWV3LmV4dGVuZFxuXG4gIGNsYXNzTmFtZTogXCJiaW9qc19tc2Ffb3ZlcnZpZXdib3hcIlxuICB0YWdOYW1lOiBcImNhbnZhc1wiXG5cbiAgaW5pdGlhbGl6ZTogKGRhdGEpIC0+XG4gICAgQGcgPSBkYXRhLmdcbiAgICBAbGlzdGVuVG8gQGcuem9vbWVyLFwiY2hhbmdlOmJveFJlY3RXaWR0aCBjaGFuZ2U6Ym94UmVjdEhlaWdodFwiLCBAcmVuZGVyXG4gICAgQGxpc3RlblRvIEBnLnNlbGNvbCwgXCJhZGQgcmVzZXQgY2hhbmdlXCIsIEByZW5kZXJcbiAgICBAbGlzdGVuVG8gQGcuY29sdW1ucywgXCJjaGFuZ2U6aGlkZGVuXCIsIEByZW5kZXJcbiAgICBAbGlzdGVuVG8gQGcuY29sb3JzY2hlbWUsIFwiY2hhbmdlOnNob3dMb3dlckNhc2VcIiwgQHJlbmRlclxuICAgIEBsaXN0ZW5UbyBAbW9kZWwsIFwiY2hhbmdlXCIsIF8uZGVib3VuY2UgQHJlbmRlciwgNVxuXG4gICAgIyBjb2xvclxuICAgIEBjb2xvciA9IGNvbG9yU2VsZWN0b3IuZ2V0Q29sb3IgQGcuY29sb3JzY2hlbWUuZ2V0KFwic2NoZW1lXCIpXG4gICAgQGxpc3RlblRvIEBnLmNvbG9yc2NoZW1lLCBcImNoYW5nZTpzY2hlbWVcIiwgLT5cbiAgICAgIEBjb2xvciA9IGNvbG9yU2VsZWN0b3IuZ2V0Q29sb3IgQGcuY29sb3JzY2hlbWUuZ2V0KFwic2NoZW1lXCIpXG4gICAgICBAcmVuZGVyKClcbiAgICBAZHJhZ1N0YXJ0ID0gW11cblxuICBldmVudHM6XG4gICAgY2xpY2s6IFwiX29uY2xpY2tcIlxuICAgIG1vdXNlZG93bjogXCJfb25tb3VzZWRvd25cIlxuXG4gIHJlbmRlcjogLT5cbiAgICBAX2NyZWF0ZUNhbnZhcygpXG4gICAgQGVsLnRleHRDb250ZW50ID0gXCJvdmVydmlld1wiXG5cbiAgICAjIGJhY2tncm91bmQgYmcgZm9yIG5vbi1kcmF3ZWQgYXJlYVxuICAgIEBjdHguZmlsbFN0eWxlID0gXCIjOTk5OTk5XCJcbiAgICBAY3R4LmZpbGxSZWN0IDAsMCxAZWwud2lkdGgsQGVsLmhlaWdodFxuXG4gICAgcmVjdFdpZHRoID0gQGcuem9vbWVyLmdldCBcImJveFJlY3RXaWR0aFwiXG4gICAgcmVjdEhlaWdodCA9IEBnLnpvb21lci5nZXQgXCJib3hSZWN0SGVpZ2h0XCJcbiAgICBoaWRkZW4gPSBAZy5jb2x1bW5zLmdldCBcImhpZGRlblwiXG4gICAgc2hvd0xvd2VyQ2FzZSA9IEBnLmNvbG9yc2NoZW1lLmdldCBcInNob3dMb3dlckNhc2VcIlxuXG4gICAgeSA9IC1yZWN0SGVpZ2h0XG4gICAgZm9yIGkgaW4gWzAuLiBAbW9kZWwubGVuZ3RoIC0gMV0gYnkgMVxuICAgICAgc2VxID0gQG1vZGVsLmF0KGkpLmdldCBcInNlcVwiXG4gICAgICB4ID0gMFxuICAgICAgeSA9IHkgKyByZWN0SGVpZ2h0XG5cblxuICAgICAgaWYgQG1vZGVsLmF0KGkpLmdldCBcImhpZGRlblwiXG4gICAgICAgICMgaGlkZGVuIHNlcVxuICAgICAgICBjb25zb2xlLmxvZyBAbW9kZWwuYXQoaSkuZ2V0IFwiaGlkZGVuXCJcbiAgICAgICAgQGN0eC5maWxsU3R5bGUgPSBcImdyZXlcIlxuICAgICAgICBAY3R4LmZpbGxSZWN0IDAseSxzZXEubGVuZ3RoICogcmVjdFdpZHRoLHJlY3RIZWlnaHRcbiAgICAgICAgY29udGludWVcblxuICAgICAgZm9yIGogaW4gWzAuLiBzZXEubGVuZ3RoIC0gMV0gYnkgMVxuICAgICAgICBjID0gc2VxW2pdXG4gICAgICAgICMgdG9kbzogb3B0aW9uYWwgdXBwZXJjYXNpbmdcbiAgICAgICAgYyA9IGMudG9VcHBlckNhc2UoKSBpZiBzaG93TG93ZXJDYXNlXG4gICAgICAgIGNvbG9yID0gQGNvbG9yW2NdXG5cbiAgICAgICAgaWYgaGlkZGVuLmluZGV4T2YoaikgPj0gMFxuICAgICAgICAgIGNvbG9yID0gXCJncmV5XCJcblxuICAgICAgICBpZiBjb2xvcj9cbiAgICAgICAgICBAY3R4LmZpbGxTdHlsZSA9IGNvbG9yXG4gICAgICAgICAgQGN0eC5maWxsUmVjdCB4LHkscmVjdFdpZHRoLHJlY3RIZWlnaHRcblxuICAgICAgICB4ID0geCArIHJlY3RXaWR0aFxuXG4gICAgQF9kcmF3U2VsZWN0aW9uKClcblxuICBfZHJhd1NlbGVjdGlvbjogLT5cbiAgICAjIGhpZGUgZHVyaW5nIHNlbGVjdGlvblxuICAgIHJldHVybiBpZiBAZHJhZ1N0YXJ0Lmxlbmd0aCA+IDAgYW5kIG5vdCBAcHJvbG9uZ1NlbGVjdGlvblxuXG4gICAgcmVjdFdpZHRoID0gQGcuem9vbWVyLmdldCBcImJveFJlY3RXaWR0aFwiXG4gICAgcmVjdEhlaWdodCA9IEBnLnpvb21lci5nZXQgXCJib3hSZWN0SGVpZ2h0XCJcbiAgICBtYXhIZWlnaHQgPSByZWN0SGVpZ2h0ICogQG1vZGVsLmxlbmd0aFxuICAgIEBjdHguZmlsbFN0eWxlID0gXCIjZmZmZjAwXCJcbiAgICBAY3R4Lmdsb2JhbEFscGhhID0gMC45XG4gICAgZm9yIGkgaW4gWzAuLiBAZy5zZWxjb2wubGVuZ3RoIC0gMV0gYnkgMVxuICAgICAgc2VsID0gQGcuc2VsY29sLmF0KGkpXG4gICAgICBpZiBzZWwuZ2V0KCd0eXBlJykgaXMgJ2NvbHVtbidcbiAgICAgICAgQGN0eC5maWxsUmVjdCByZWN0V2lkdGggKiBzZWwuZ2V0KCd4U3RhcnQnKSwwLHJlY3RXaWR0aCAqXG4gICAgICAgIChzZWwuZ2V0KCd4RW5kJykgLSBzZWwuZ2V0KCd4U3RhcnQnKSArIDEpLG1heEhlaWdodFxuICAgICAgZWxzZSBpZiBzZWwuZ2V0KCd0eXBlJykgaXMgJ3JvdydcbiAgICAgICAgc2VxID0gKEBtb2RlbC5maWx0ZXIgKGVsKSAtPiBlbC5nZXQoJ2lkJykgaXMgc2VsLmdldCgnc2VxSWQnKSlbMF1cbiAgICAgICAgcG9zID0gQG1vZGVsLmluZGV4T2Yoc2VxKVxuICAgICAgICBAY3R4LmZpbGxSZWN0IDAscmVjdEhlaWdodCAqIHBvcywgcmVjdFdpZHRoICogc2VxLmdldCgnc2VxJykubGVuZ3RoLCByZWN0SGVpZ2h0XG4gICAgICBlbHNlIGlmIHNlbC5nZXQoJ3R5cGUnKSBpcyAncG9zJ1xuICAgICAgICBzZXEgPSAoQG1vZGVsLmZpbHRlciAoZWwpIC0+IGVsLmdldCgnaWQnKSBpcyBzZWwuZ2V0KCdzZXFJZCcpKVswXVxuICAgICAgICBwb3MgPSBAbW9kZWwuaW5kZXhPZihzZXEpXG4gICAgICAgIEBjdHguZmlsbFJlY3QgcmVjdFdpZHRoICogc2VsLmdldCgneFN0YXJ0JykscmVjdEhlaWdodCAqIHBvcywgcmVjdFdpZHRoICogKHNlbC5nZXQoJ3hFbmQnKSAtIHNlbC5nZXQoJ3hTdGFydCcpICsgMSksIHJlY3RIZWlnaHRcblxuICAgIEBjdHguZ2xvYmFsQWxwaGEgPSAxXG5cbiAgX29uY2xpY2s6IChldnQpIC0+XG4gICAgQGcudHJpZ2dlciBcIm1ldGE6Y2xpY2tcIiwge3NlcUlkOiBAbW9kZWwuZ2V0IFwiaWRcIiwgZXZ0OmV2dH1cblxuICBfb25tb3VzZW1vdmU6IChlKSAtPlxuICAgICMgZHVwbGljYXRlIGV2ZW50c1xuICAgIHJldHVybiBpZiBAZHJhZ1N0YXJ0Lmxlbmd0aCBpcyAwXG5cbiAgICBAcmVuZGVyKClcbiAgICBAY3R4LmZpbGxTdHlsZSA9IFwiI2ZmZmYwMFwiXG4gICAgQGN0eC5nbG9iYWxBbHBoYSA9IDAuOVxuXG4gICAgcmVjdCA9IEBfY2FsY1NlbGVjdGlvbiggbW91c2UuYWJzIGUgKVxuICAgIEBjdHguZmlsbFJlY3QgcmVjdFswXVswXSxyZWN0WzFdWzBdLHJlY3RbMF1bMV0gLSByZWN0WzBdWzBdLCByZWN0WzFdWzFdIC0gcmVjdFsxXVswXVxuXG4gICAgIyBhYm9ydCBzZWxlY3Rpb24gZXZlbnRzIG9mIHRoZSBicm93c2VyXG4gICAgZS5wcmV2ZW50RGVmYXVsdCgpXG4gICAgZS5zdG9wUHJvcGFnYXRpb24oKVxuXG4gICMgc3RhcnQgdGhlIHNlbGVjdGlvbiBtb2RlXG4gIF9vbm1vdXNlZG93bjogKGUpIC0+XG4gICAgQGRyYWdTdGFydCA9IG1vdXNlLmFicyBlXG4gICAgQGRyYWdTdGFydFJlbCA9IG1vdXNlLnJlbCBlXG5cbiAgICBpZiBlLmN0cmxLZXkgb3IgZS5tZXRhS2V5XG4gICAgICBAcHJvbG9uZ1NlbGVjdGlvbiA9IHRydWVcbiAgICBlbHNlXG4gICAgICBAcHJvbG9uZ1NlbGVjdGlvbiA9IGZhbHNlXG4gICAgIyBlbmFibGUgZ2xvYmFsIGxpc3RlbmVyc1xuICAgIGpib25lKGRvY3VtZW50LmJvZHkpLm9uICdtb3VzZW1vdmUub3Zlcm1vdmUnLCAoZSkgPT4gQF9vbm1vdXNlbW92ZShlKVxuICAgIGpib25lKGRvY3VtZW50LmJvZHkpLm9uICdtb3VzZXVwLm92ZXJ1cCcsIChlKSA9PiBAX29ubW91c2V1cChlKVxuICAgIHJldHVybiBAZHJhZ1N0YXJ0XG5cbiAgIyBjYWxjdWxhdGVzIHRoZSBjdXJyZW50IHNlbGVjdGlvblxuICBfY2FsY1NlbGVjdGlvbjogKGRyYWdNb3ZlKSAtPlxuICAgICMgcmVsYXRpdmUgdG8gZmlyc3QgY2xpY2tcbiAgICBkcmFnUmVsID0gW2RyYWdNb3ZlWzBdIC0gQGRyYWdTdGFydFswXSwgZHJhZ01vdmVbMV0gLSBAZHJhZ1N0YXJ0WzFdXVxuXG4gICAgIyByZWxhdGl2ZSB0byB0YXJnZXRcbiAgICBmb3IgaSBpbiBbMC4uMV0gYnkgMVxuICAgICAgZHJhZ1JlbFtpXSA9IEBkcmFnU3RhcnRSZWxbaV0gKyBkcmFnUmVsW2ldXG5cbiAgICAjIDA6eCwgMTogeVxuICAgIHJlY3QgPSBbW0BkcmFnU3RhcnRSZWxbMF0sIGRyYWdSZWxbMF1dLCBbQGRyYWdTdGFydFJlbFsxXSwgZHJhZ1JlbFsxXV1dXG5cbiAgICAjIHN3YXAgdGhlIGNvb3JkaW5hdGVzIGlmIG5lZWRlZFxuICAgIGZvciBpIGluIFswLi4xXSBieSAxXG4gICAgICBpZiByZWN0W2ldWzFdIDwgcmVjdFtpXVswXVxuICAgICAgICByZWN0W2ldID0gW3JlY3RbaV1bMV0sIHJlY3RbaV1bMF1dXG5cbiAgICAgICMgbG93ZXIgbGltaXRcbiAgICAgIHJlY3RbaV1bMF0gPSBNYXRoLm1heCByZWN0W2ldWzBdLCAwXG5cbiAgICByZXR1cm4gcmVjdFxuXG4gIF9lbmRTZWxlY3Rpb246IChkcmFnRW5kKSAtPlxuICAgICMgcmVtb3ZlIGxpc3RlbmVyc1xuICAgIGpib25lKGRvY3VtZW50LmJvZHkpLm9mZignLm92ZXJtb3ZlJylcbiAgICBqYm9uZShkb2N1bWVudC5ib2R5KS5vZmYoJy5vdmVydXAnKVxuXG4gICAgIyBkdXBsaWNhdGUgZXZlbnRzXG4gICAgcmV0dXJuIGlmIEBkcmFnU3RhcnQubGVuZ3RoIGlzIDBcblxuICAgIHJlY3QgPSBAX2NhbGNTZWxlY3Rpb24gZHJhZ0VuZFxuXG4gICAgIyB4XG4gICAgZm9yIGkgaW4gWzAuLjFdXG4gICAgICByZWN0WzBdW2ldID0gTWF0aC5mbG9vciggcmVjdFswXVtpXSAvIEBnLnpvb21lci5nZXQoXCJib3hSZWN0V2lkdGhcIikpXG5cbiAgICAjIHlcbiAgICBmb3IgaSBpbiBbMC4uMV1cbiAgICAgIHJlY3RbMV1baV0gPSBNYXRoLmZsb29yKCByZWN0WzFdW2ldIC8gQGcuem9vbWVyLmdldChcImJveFJlY3RIZWlnaHRcIikgKVxuXG4gICAgIyB1cHBlciBsaW1pdFxuICAgIHJlY3RbMF1bMV0gPSBNYXRoLm1pbihAbW9kZWwuZ2V0TWF4TGVuZ3RoKCkgLSAxLCByZWN0WzBdWzFdKVxuICAgIHJlY3RbMV1bMV0gPSBNYXRoLm1pbihAbW9kZWwubGVuZ3RoIC0gMSwgcmVjdFsxXVsxXSlcblxuICAgICMgc2VsZWN0XG4gICAgc2VsaXMgPSBbXVxuICAgIGZvciBqIGluIFtyZWN0WzFdWzBdLi5yZWN0WzFdWzFdXSBieSAxXG4gICAgICBhcmdzID0gc2VxSWQ6IEBtb2RlbC5hdChqKS5nZXQoJ2lkJyksIHhTdGFydDogcmVjdFswXVswXSwgeEVuZDogcmVjdFswXVsxXVxuICAgICAgc2VsaXMucHVzaCBuZXcgc2VsZWN0aW9uLnBvc3NlbCBhcmdzXG5cbiAgICAjIHJlc2V0XG4gICAgQGRyYWdTdGFydCA9IFtdXG4gICAgIyBsb29rIGZvciBjdHJsIGtleVxuICAgIGlmIEBwcm9sb25nU2VsZWN0aW9uXG4gICAgICBAZy5zZWxjb2wuYWRkIHNlbGlzXG4gICAgZWxzZVxuICAgICAgQGcuc2VsY29sLnJlc2V0IHNlbGlzXG5cbiAgICAjIHNhZmV0eSBjaGVjayArIHVwZGF0ZSBvZmZzZXRcbiAgICBAZy56b29tZXIuc2V0TGVmdE9mZnNldCByZWN0WzBdWzBdXG4gICAgQGcuem9vbWVyLnNldFRvcE9mZnNldCByZWN0WzFdWzBdXG5cbiAgIyBlbmRzIHRoZSBzZWxlY3Rpb24gbW9kZVxuICBfb25tb3VzZXVwOiAoZSkgLT5cbiAgICBAX2VuZFNlbGVjdGlvbiBtb3VzZS5hYnMgZVxuXG4gIF9vbm1vdXNlb3V0OiAoZSkgLT5cbiAgICBAX2VuZFNlbGVjdGlvbiBtb3VzZS5hYnMgZVxuXG4gIyBpbml0IHRoZSBjYW52YXNcbiAgX2NyZWF0ZUNhbnZhczogLT5cbiAgICByZWN0V2lkdGggPSBAZy56b29tZXIuZ2V0IFwiYm94UmVjdFdpZHRoXCJcbiAgICByZWN0SGVpZ2h0ID0gQGcuem9vbWVyLmdldCBcImJveFJlY3RIZWlnaHRcIlxuXG4gICAgQGVsLmhlaWdodCA9IEBtb2RlbC5sZW5ndGggKiByZWN0SGVpZ2h0XG4gICAgQGVsLndpZHRoID0gQG1vZGVsLmdldE1heExlbmd0aCgpICogcmVjdFdpZHRoXG4gICAgQGN0eCA9IEBlbC5nZXRDb250ZXh0IFwiMmRcIlxuICAgIEBlbC5zdHlsZS5vdmVyZmxvdyA9IFwic2Nyb2xsXCJcbiAgICBAZWwuc3R5bGUuY3Vyc29yID0gXCJjcm9zc2hhaXJcIlxuIiwiYm9uZVZpZXcgPSByZXF1aXJlKFwiYmFja2JvbmUtY2hpbGRzXCIpXG5BbGlnbm1lbnRCb2R5ID0gcmVxdWlyZSBcIi4vQWxpZ25tZW50Qm9keVwiXG5IZWFkZXJCbG9jayA9IHJlcXVpcmUgXCIuL2hlYWRlci9IZWFkZXJCbG9ja1wiXG5PdmVydmlld0JveCA9IHJlcXVpcmUgXCIuL092ZXJ2aWV3Qm94XCJcbmlkZW50aXR5Q2FsYyA9IHJlcXVpcmUgXCIuLi9hbGdvL2lkZW50aXR5Q2FsY1wiXG5fID0gcmVxdWlyZSAndW5kZXJzY29yZSdcblxuIyBhIG5lYXQgY29sbGVjdGlvbiB2aWV3XG5tb2R1bGUuZXhwb3J0cyA9IGJvbmVWaWV3LmV4dGVuZFxuXG4gIGluaXRpYWxpemU6IChkYXRhKSAtPlxuICAgIEBnID0gZGF0YS5nXG5cbiAgICBAZHJhdygpXG4gICAgQGxpc3RlblRvIEBtb2RlbCxcInJlc2V0XCIsIC0+XG4gICAgICBAaXNOb3REaXJ0eSA9IGZhbHNlXG4gICAgICBAcmVyZW5kZXIoKVxuXG4gICAgIyBkZWJvdW5jZSBhIGJ1bGsgb3BlcmF0aW9uXG4gICAgQGxpc3RlblRvIEBtb2RlbCxcImNoYW5nZTpoaWRkZW5cIiwgXy5kZWJvdW5jZSBAcmVyZW5kZXIsIDEwXG5cbiAgICBAbGlzdGVuVG8gQG1vZGVsLFwic29ydFwiLCBAcmVyZW5kZXJcbiAgICBAbGlzdGVuVG8gQG1vZGVsLFwiYWRkXCIsIC0+XG4gICAgICBjb25zb2xlLmxvZyBcInNlcSBhZGRcIlxuXG4gICAgQGxpc3RlblRvIEBnLnZpcyxcImNoYW5nZTpzZXF1ZW5jZXNcIiwgQHJlcmVuZGVyXG4gICAgQGxpc3RlblRvIEBnLnZpcyxcImNoYW5nZTpvdmVydmlld2JveFwiLCBAcmVyZW5kZXJcbiAgICBAbGlzdGVuVG8gQGcudmlzb3JkZXIsXCJjaGFuZ2VcIiwgQHJlcmVuZGVyXG5cbiAgZHJhdzogLT5cbiAgICBAcmVtb3ZlVmlld3MoKVxuXG4gICAgdW5sZXNzIEBpc05vdERpcnR5XG4gICAgICAjIG9ubHkgZXhlY3V0ZWQgd2hlbiBuZXcgc2VxdWVuY2VzIGFyZSBhZGRlZCBvciBvbiBzdGFydFxuICAgICAgY29uc2Vuc3VzID0gQGcuY29uc2Vuc3VzLmdldENvbnNlbnN1cyBAbW9kZWxcbiAgICAgIGlkZW50aXR5Q2FsYyBAbW9kZWwsIGNvbnNlbnN1c1xuICAgICAgQGlzTm90RGlydHkgPSB0cnVlXG5cbiAgICBpZiBAZy52aXMuZ2V0IFwib3ZlcnZpZXdib3hcIlxuICAgICAgb3ZlcnZpZXdib3ggPSBuZXcgT3ZlcnZpZXdCb3gge21vZGVsOiBAbW9kZWwsIGc6IEBnfVxuICAgICAgb3ZlcnZpZXdib3gub3JkZXJpbmcgPSBAZy52aXNvcmRlci5nZXQgJ292ZXJ2aWV3Qm94J1xuICAgICAgQGFkZFZpZXcgXCJvdmVydmlld2JveFwiLG92ZXJ2aWV3Ym94XG5cbiAgICBpZiB0cnVlXG4gICAgICBoZWFkZXJibG9jayA9IG5ldyBIZWFkZXJCbG9jayB7bW9kZWw6IEBtb2RlbCwgZzogQGd9XG4gICAgICBoZWFkZXJibG9jay5vcmRlcmluZyA9IEBnLnZpc29yZGVyLmdldCAnaGVhZGVyQm94J1xuICAgICAgQGFkZFZpZXcgXCJoZWFkZXJibG9ja1wiLGhlYWRlcmJsb2NrXG5cbiAgICBib2R5ID0gbmV3IEFsaWdubWVudEJvZHkge21vZGVsOiBAbW9kZWwsIGc6IEBnfVxuICAgIGJvZHkub3JkZXJpbmcgPSBAZy52aXNvcmRlci5nZXQgJ2FsaWdubWVudEJvZHknXG4gICAgQGFkZFZpZXcgXCJib2R5XCIsYm9keVxuXG4gIHJlbmRlcjogLT5cbiAgICBAcmVuZGVyU3Vidmlld3MoKVxuICAgIEBlbC5jbGFzc05hbWUgPSBcImJpb2pzX21zYV9zdGFnZVwiXG4gICAgQFxuXG4gIHJlcmVuZGVyOiAtPlxuICAgIEBkcmF3KClcbiAgICBAcmVuZGVyKClcbiIsInZpZXcgPSByZXF1aXJlKFwiYmFja2JvbmUtdmlld2pcIilcbmRvbSA9IHJlcXVpcmUoXCJkb20taGVscGVyXCIpXG5zdmcgPSByZXF1aXJlKFwiLi4vLi4vdXRpbHMvc3ZnXCIpXG5cbkNvbnNlcnZhdGlvblZpZXcgPSB2aWV3LmV4dGVuZFxuXG4gIGNsYXNzTmFtZTogXCJiaW9qc19tc2FfY29uc2VydlwiXG5cbiAgaW5pdGlhbGl6ZTogKGRhdGEpIC0+XG4gICAgQGcgPSBkYXRhLmdcbiAgICBAbGlzdGVuVG8gQGcuem9vbWVyLFwiY2hhbmdlOnN0ZXBTaXplIGNoYW5nZTpsYWJlbFdpZHRoIGNoYW5nZTpjb2x1bW5XaWR0aFwiLCBAcmVuZGVyXG4gICAgQGxpc3RlblRvIEBnLnZpcyxcImNoYW5nZTpsYWJlbHMgY2hhbmdlOm1ldGFjZWxsXCIsIEByZW5kZXJcbiAgICBAbGlzdGVuVG8gQGcuY29sdW1ucywgXCJjaGFuZ2U6c2NhbGluZ1wiLCBAcmVuZGVyXG4gICAgQGxpc3RlblRvIEBtb2RlbCwgXCJyZXNldFwiLEByZW5kZXJcbiAgICBAbWFuYWdlRXZlbnRzKClcblxuICByZW5kZXI6IC0+XG4gICAgQGcuY29sdW1ucy5jYWxjQ29uc2VydmF0aW9uIEBtb2RlbFxuXG4gICAgZG9tLnJlbW92ZUFsbENoaWxkcyBAZWxcblxuICAgIG5NYXggPSBAbW9kZWwuZ2V0TWF4TGVuZ3RoKClcbiAgICBjZWxsV2lkdGggPSBAZy56b29tZXIuZ2V0IFwiY29sdW1uV2lkdGhcIlxuICAgIG1heEhlaWdodCA9IDIwXG4gICAgd2lkdGggPSBjZWxsV2lkdGggKiAobk1heCAtIEBnLmNvbHVtbnMuZ2V0KCdoaWRkZW4nKS5sZW5ndGgpXG4gICAgY29uc29sZS5sb2cgQGcuY29sdW1ucy5nZXQoJ2hpZGRlbicpXG5cbiAgICBzID0gc3ZnLmJhc2UgaGVpZ2h0OiBtYXhIZWlnaHQsIHdpZHRoOiB3aWR0aFxuICAgIHMuc3R5bGUuZGlzcGxheSA9IFwiaW5saW5lLWJsb2NrXCJcbiAgICBzLnN0eWxlLmN1cnNvciA9IFwicG9pbnRlclwiXG5cbiAgICBzdGVwU2l6ZSA9IEBnLnpvb21lci5nZXQgXCJzdGVwU2l6ZVwiXG4gICAgaGlkZGVuID0gQGcuY29sdW1ucy5nZXQgXCJoaWRkZW5cIlxuICAgIHggPSAwXG4gICAgbiA9IDBcbiAgICB3aGlsZSBuIDwgbk1heFxuICAgICAgaWYgaGlkZGVuLmluZGV4T2YobikgPj0gMFxuICAgICAgICBuICs9IHN0ZXBTaXplXG4gICAgICAgIGNvbnRpbnVlXG4gICAgICB3aWR0aCA9IGNlbGxXaWR0aCAqIHN0ZXBTaXplXG4gICAgICBhdmdIZWlnaHQgPSAwXG4gICAgICBmb3IgaSBpbiBbMCAuLiBzdGVwU2l6ZSAtIDFdXG4gICAgICAgIGF2Z0hlaWdodCArPSBAZy5jb2x1bW5zLmdldChcImNvbnNlcnZcIilbbl1cbiAgICAgIGhlaWdodCA9IG1heEhlaWdodCAqICAoYXZnSGVpZ2h0IC8gc3RlcFNpemUpXG5cbiAgICAgIHJlY3QgPSAgc3ZnLnJlY3QgeDp4LHk6IG1heEhlaWdodCAtIGhlaWdodCx3aWR0aDp3aWR0aCAtIGNlbGxXaWR0aCAvIDQsaGVpZ2h0OmhlaWdodCxzdHlsZTpcbiAgICAgICAgXCJzdHJva2U6cmVkO3N0cm9rZS13aWR0aDoxO1wiXG4gICAgICByZWN0LnJvd1BvcyA9IG5cbiAgICAgIHMuYXBwZW5kQ2hpbGQgcmVjdFxuICAgICAgeCArPSB3aWR0aFxuICAgICAgbiArPSBzdGVwU2l6ZVxuXG4gICAgQGVsLmFwcGVuZENoaWxkIHNcbiAgICBAXG5cbiAgI1RPRE86IG1ha2UgbW9yZSBnZW5lcmFsIHdpdGggSGVhZGVyVmlld1xuICBfb25jbGljazogKGV2dCkgLT5cbiAgICByb3dQb3MgPSBldnQudGFyZ2V0LnJvd1Bvc1xuICAgIHN0ZXBTaXplID0gQGcuem9vbWVyLmdldChcInN0ZXBTaXplXCIpXG4gICAgIyBzaW11bGF0ZSBoaWRkZW4gY29sdW1uc1xuICAgIGZvciBpIGluIFswLi5zdGVwU2l6ZSAtIDFdIGJ5IDFcbiAgICAgIEBnLnRyaWdnZXIgXCJiYXI6Y2xpY2tcIiwge3Jvd1Bvczogcm93UG9zICsgaSwgZXZ0OmV2dH1cblxuICBtYW5hZ2VFdmVudHM6IC0+XG4gICAgZXZlbnRzID0ge31cbiAgICBpZiBAZy5jb25maWcuZ2V0IFwicmVnaXN0ZXJNb3VzZUNsaWNrc1wiXG4gICAgICBldmVudHMuY2xpY2sgPSBcIl9vbmNsaWNrXCJcbiAgICBpZiBAZy5jb25maWcuZ2V0IFwicmVnaXN0ZXJNb3VzZUhvdmVyXCJcbiAgICAgIGV2ZW50cy5tb3VzZWluID0gXCJfb25tb3VzZWluXCJcbiAgICAgIGV2ZW50cy5tb3VzZW91dCA9IFwiX29ubW91c2VvdXRcIlxuICAgIEBkZWxlZ2F0ZUV2ZW50cyBldmVudHNcbiAgICBAbGlzdGVuVG8gQGcuY29uZmlnLCBcImNoYW5nZTpyZWdpc3Rlck1vdXNlSG92ZXJcIiwgQG1hbmFnZUV2ZW50c1xuICAgIEBsaXN0ZW5UbyBAZy5jb25maWcsIFwiY2hhbmdlOnJlZ2lzdGVyTW91c2VDbGlja1wiLCBAbWFuYWdlRXZlbnRzXG5cbiAgX29ubW91c2VpbjogKGV2dCkgLT5cbiAgICByb3dQb3MgPSBAZy56b29tZXIuZ2V0IFwic3RlcFNpemVcIiAqIGV2dC5yb3dQb3NcbiAgICBAZy50cmlnZ2VyIFwiYmFyOm1vdXNlaW5cIiwge3Jvd1Bvczogcm93UG9zLCBldnQ6ZXZ0fVxuXG4gIF9vbm1vdXNlb3V0OiAoZXZ0KSAtPlxuICAgIHJvd1BvcyA9IEBnLnpvb21lci5nZXQgXCJzdGVwU2l6ZVwiICogZXZ0LnJvd1Bvc1xuICAgIEBnLnRyaWdnZXIgXCJiYXI6bW91c2VvdXRcIiwge3Jvd1Bvczogcm93UG9zLCBldnQ6ZXZ0fVxuXG5tb2R1bGUuZXhwb3J0cyA9IENvbnNlcnZhdGlvblZpZXdcbiIsIk1hcmtlclZpZXcgPSByZXF1aXJlIFwiLi9NYXJrZXJWaWV3XCJcbkNvbnNlcnZhdGlvblZpZXcgPSByZXF1aXJlIFwiLi9Db25zZXJ2YXRpb25WaWV3XCJcbmlkZW50aXR5Q2FsYyA9IHJlcXVpcmUgXCIuLi8uLi9hbGdvL2lkZW50aXR5Q2FsY1wiXG5ib25lVmlldyA9IHJlcXVpcmUoXCJiYWNrYm9uZS1jaGlsZHNcIilcbl8gPSByZXF1aXJlICd1bmRlcnNjb3JlJ1xuXG5tb2R1bGUuZXhwb3J0cyA9IGJvbmVWaWV3LmV4dGVuZFxuXG4gIGluaXRpYWxpemU6IChkYXRhKSAtPlxuICAgIEBnID0gZGF0YS5nXG4gICAgQGJsb2NrRXZlbnRzID0gZmFsc2VcblxuICAgIEBsaXN0ZW5UbyBAZy52aXMsXCJjaGFuZ2U6bWFya2VycyBjaGFuZ2U6Y29uc2VydlwiLCAtPlxuICAgICAgQGRyYXcoKVxuICAgICAgQHJlbmRlcigpXG4gICAgQGxpc3RlblRvIEBnLnZpcyxcImNoYW5nZVwiLCBAX3NldFNwYWNlclxuICAgIEBsaXN0ZW5UbyBAZy56b29tZXIsXCJjaGFuZ2U6YWxpZ25tZW50V2lkdGhcIiwgLT5cbiAgICAgIEBfYWRqdXN0V2lkdGgoKVxuICAgIEBsaXN0ZW5UbyBAZy56b29tZXIsIFwiY2hhbmdlOl9hbGlnbm1lbnRTY3JvbGxMZWZ0XCIsIEBfYWRqdXN0U2Nyb2xsaW5nTGVmdFxuXG4gICAgIyBUT0RPOiBkdXBsaWNhdGUgcmVuZGVyaW5nXG4gICAgQGxpc3RlblRvIEBnLmNvbHVtbnMsIFwiY2hhbmdlOmhpZGRlblwiLCAtPlxuICAgICAgQGRyYXcoKVxuICAgICAgQHJlbmRlcigpXG5cbiAgICBAZHJhdygpXG4gICAgQF9vbnNjcm9sbCA9IEBfc2VuZFNjcm9sbEV2ZW50XG5cbiAgICBAZy52aXMub25jZSAnY2hhbmdlOmxvYWRlZCcsIEBfYWRqdXN0U2Nyb2xsaW5nTGVmdCwgQFxuXG4gIGV2ZW50czpcbiAgICBcInNjcm9sbFwiOiBcIl9vbnNjcm9sbFwiXG5cbiAgZHJhdzogLT5cbiAgICBAcmVtb3ZlVmlld3MoKVxuXG4gICAgdW5sZXNzIEBpc05vdERpcnR5XG4gICAgICAjIG9ubHkgZXhlY3V0ZWQgd2hlbiBuZXcgc2VxdWVuY2VzIGFyZSBhZGRlZCBvciBvbiBzdGFydFxuICAgICAgY29uc2Vuc3VzID0gQGcuY29uc2Vuc3VzLmdldENvbnNlbnN1cyBAbW9kZWxcbiAgICAgIGlkZW50aXR5Q2FsYyBAbW9kZWwsIGNvbnNlbnN1c1xuICAgICAgQGlzTm90RGlydHkgPSB0cnVlXG5cbiAgICBpZiBAZy52aXMuZ2V0IFwiY29uc2VydlwiXG4gICAgICBjb25zZXJ2ID0gbmV3IENvbnNlcnZhdGlvblZpZXcge21vZGVsOiBAbW9kZWwsIGc6IEBnfVxuICAgICAgY29uc2Vydi5vcmRlcmluZyA9IC0yMFxuICAgICAgQGFkZFZpZXcgXCJjb25zZXJ2XCIsY29uc2VydlxuXG4gICAgaWYgQGcudmlzLmdldCBcIm1hcmtlcnNcIlxuICAgICAgbWFya2VyID0gbmV3IE1hcmtlclZpZXcge21vZGVsOiBAbW9kZWwsIGc6IEBnfVxuICAgICAgbWFya2VyLm9yZGVyaW5nID0gLTEwXG4gICAgICBAYWRkVmlldyBcIm1hcmtlclwiLG1hcmtlclxuXG4gIHJlbmRlcjogLT5cbiAgICBAcmVuZGVyU3Vidmlld3MoKVxuXG4gICAgQF9zZXRTcGFjZXIoKVxuXG4gICAgQGVsLmNsYXNzTmFtZSA9IFwiYmlvanNfbXNhX2hlYWRlclwiXG4gICAgQGVsLnN0eWxlLm92ZXJmbG93WCA9IFwiYXV0b1wiXG4gICAgQF9hZGp1c3RXaWR0aCgpXG4gICAgQF9hZGp1c3RTY3JvbGxpbmdMZWZ0KClcbiAgICBAXG5cbiAgIyBzY3JvbGxMZWZ0IHRyaWdnZXJzIGEgcmVmbG93IG9mIHRoZSB3aG9sZSBhcmVhIChldmVuIG9ubHkgZ2V0KVxuICBfc2VuZFNjcm9sbEV2ZW50OiAtPlxuICAgIHVubGVzcyBAYmxvY2tFdmVudHNcbiAgICAgIEBnLnpvb21lci5zZXQgXCJfYWxpZ25tZW50U2Nyb2xsTGVmdFwiLCBAZWwuc2Nyb2xsTGVmdCwge29yaWdpbjogXCJoZWFkZXJcIn1cbiAgICBAYmxvY2tFdmVudHMgPSBmYWxzZVxuXG4gIF9hZGp1c3RTY3JvbGxpbmdMZWZ0OiAobW9kZWwsdmFsdWUsb3B0aW9ucykgLT5cbiAgICBpZiAobm90IG9wdGlvbnM/Lm9yaWdpbj8pIG9yIG9wdGlvbnMub3JpZ2luIGlzbnQgXCJoZWFkZXJcIlxuICAgICAgc2Nyb2xsTGVmdCA9IEBnLnpvb21lci5nZXQgXCJfYWxpZ25tZW50U2Nyb2xsTGVmdFwiXG4gICAgICBAYmxvY2tFdmVudHMgPSB0cnVlXG4gICAgICBAZWwuc2Nyb2xsTGVmdCA9IHNjcm9sbExlZnRcblxuICBfc2V0U3BhY2VyOiAtPlxuICAgICMgc3BhY2VyIC8gcGFkZGluZyBlbGVtZW50XG4gICAgQGVsLnN0eWxlLm1hcmdpbkxlZnQgPSBAX2dldExhYmVsV2lkdGgoKSArIFwicHhcIlxuXG4gIF9nZXRMYWJlbFdpZHRoOiAtPlxuICAgIHBhZGRpbmdMZWZ0ID0gMFxuICAgIHBhZGRpbmdMZWZ0ICs9IEBnLnpvb21lci5nZXQgXCJsYWJlbFdpZHRoXCIgaWYgQGcudmlzLmdldCBcImxhYmVsc1wiXG4gICAgcGFkZGluZ0xlZnQgKz0gQGcuem9vbWVyLmdldCBcIm1ldGFXaWR0aFwiIGlmIEBnLnZpcy5nZXQgXCJtZXRhY2VsbFwiXG4gICAgcmV0dXJuIHBhZGRpbmdMZWZ0XG5cbiAgX2FkanVzdFdpZHRoOiAtPlxuICAgIEBlbC5zdHlsZS53aWR0aCA9IEBnLnpvb21lci5nZXQoXCJhbGlnbm1lbnRXaWR0aFwiKSArIFwicHhcIlxuIiwidmlldyA9IHJlcXVpcmUoXCJiYWNrYm9uZS12aWV3alwiKVxuZG9tID0gcmVxdWlyZShcImRvbS1oZWxwZXJcIilcbnN2ZyA9IHJlcXVpcmUoXCIuLi8uLi91dGlscy9zdmdcIilcbmpib25lID0gcmVxdWlyZSBcImpib25lXCJcblxuSGVhZGVyVmlldyA9IHZpZXcuZXh0ZW5kXG5cbiAgY2xhc3NOYW1lOiBcImJpb2pzX21zYV9tYXJrZXJcIlxuXG4gIGluaXRpYWxpemU6IChkYXRhKSAtPlxuICAgIEBnID0gZGF0YS5nXG4gICAgQGxpc3RlblRvIEBnLnpvb21lcixcImNoYW5nZTpzdGVwU2l6ZSBjaGFuZ2U6bGFiZWxXaWR0aCBjaGFuZ2U6Y29sdW1uV2lkdGggY2hhbmdlOm1hcmtlclN0ZXBTaXplIGNoYW5nZTptYXJrZXJGb250c2l6ZVwiLCBAcmVuZGVyXG4gICAgQGxpc3RlblRvIEBnLnZpcyxcImNoYW5nZTpsYWJlbHMgY2hhbmdlOm1ldGFjZWxsXCIsIEByZW5kZXJcbiAgICBAbWFuYWdlRXZlbnRzKClcblxuICByZW5kZXI6IC0+XG4gICAgZG9tLnJlbW92ZUFsbENoaWxkcyBAZWxcblxuICAgIEBlbC5zdHlsZS5mb250U2l6ZSA9IEBnLnpvb21lci5nZXQgXCJtYXJrZXJGb250c2l6ZVwiXG5cbiAgICBjb250YWluZXIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50IFwic3BhblwiXG4gICAgbiA9IDBcbiAgICBjZWxsV2lkdGggPSBAZy56b29tZXIuZ2V0IFwiY29sdW1uV2lkdGhcIlxuXG4gICAgbk1heCA9IEBtb2RlbC5nZXRNYXhMZW5ndGgoKVxuICAgIHN0ZXBTaXplID0gQGcuem9vbWVyLmdldChcInN0ZXBTaXplXCIpXG4gICAgaGlkZGVuID0gQGcuY29sdW1ucy5nZXQgXCJoaWRkZW5cIlxuXG4gICAgd2hpbGUgbiA8IG5NYXhcbiAgICAgIGlmIGhpZGRlbi5pbmRleE9mKG4pID49IDBcbiAgICAgICAgQG1hcmtlckhpZGRlbihzcGFuLG4sIHN0ZXBTaXplKVxuICAgICAgICBuICs9IHN0ZXBTaXplXG4gICAgICAgIGNvbnRpbnVlXG4gICAgICBzcGFuID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCBcInNwYW5cIlxuICAgICAgc3Bhbi5zdHlsZS53aWR0aCA9IChjZWxsV2lkdGggKiBzdGVwU2l6ZSkgKyBcInB4XCJcbiAgICAgIHNwYW4uc3R5bGUuZGlzcGxheSA9IFwiaW5saW5lLWJsb2NrXCJcbiAgICAgICMgVE9ETzogdGhpcyBkb2Vzbid0IHdvcmsgZm9yIGEgbGFyZ2VyIHN0ZXBTaXplXG4gICAgICBpZiAobiArIDEpICUgQGcuem9vbWVyLmdldCgnbWFya2VyU3RlcFNpemUnKSBpcyAwXG4gICAgICAgIHNwYW4udGV4dENvbnRlbnQgPSAobiArIDEpXG4gICAgICBlbHNlXG4gICAgICAgIHNwYW4udGV4dENvbnRlbnQgPSBcIi5cIlxuICAgICAgc3Bhbi5yb3dQb3MgPSBuXG5cbiAgICAgIG4gKz0gc3RlcFNpemVcbiAgICAgIGNvbnRhaW5lci5hcHBlbmRDaGlsZCBzcGFuXG5cbiAgICBAZWwuYXBwZW5kQ2hpbGQgY29udGFpbmVyXG4gICAgQFxuXG4gIG1hcmtlckhpZGRlbjogKHNwYW4sbixzdGVwU2l6ZSkgLT5cbiAgICBoaWRkZW4gPSBAZy5jb2x1bW5zLmdldChcImhpZGRlblwiKS5zbGljZSAwXG5cbiAgICBtaW4gPSBNYXRoLm1heCAwLCBuIC0gc3RlcFNpemVcbiAgICBwcmV2SGlkZGVuID0gdHJ1ZVxuICAgIGZvciBqIGluICBbbWluIC4uIG5dIGJ5IDFcbiAgICAgIHByZXZIaWRkZW4gJj0gaGlkZGVuLmluZGV4T2YoaikgPj0gMFxuXG4gICAgIyBmaWx0ZXIgZHVwbGljYXRlc1xuICAgIHJldHVybiBpZiBwcmV2SGlkZGVuXG5cbiAgICBuTWF4ID0gQG1vZGVsLmdldE1heExlbmd0aCgpXG5cbiAgICBsZW5ndGggPSAwXG4gICAgaW5kZXggPSAtMVxuICAgICMgYWNjdW1sYXRlIG11bHRpcGxlIHJvd3NcbiAgICBmb3IgbiBpbiBbbi4ubk1heF0gYnkgMVxuICAgICAgaW5kZXggPSBoaWRkZW4uaW5kZXhPZihuKSB1bmxlc3MgaW5kZXggPj0gMCMgc2V0cyB0aGUgZmlyc3QgaW5kZXhcbiAgICAgIGlmIGhpZGRlbi5pbmRleE9mKG4pID49IDBcbiAgICAgICAgbGVuZ3RoKytcbiAgICAgIGVsc2VcbiAgICAgICAgYnJlYWtcblxuICAgIHMgPSBzdmcuYmFzZSBoZWlnaHQ6IDEwLCB3aWR0aDogMTBcbiAgICBzLnN0eWxlLnBvc2l0aW9uID0gXCJyZWxhdGl2ZVwiXG4gICAgdHJpYW5nbGUgPSBzdmcucG9seWdvbiBwb2ludHM6IFwiMCwwIDUsNSAxMCwwXCIsIHN0eWxlOlxuICAgICAgXCJmaWxsOmxpbWU7c3Ryb2tlOnB1cnBsZTtzdHJva2Utd2lkdGg6MVwiXG4gICAgamJvbmUodHJpYW5nbGUpLm9uIFwiY2xpY2tcIiwgKGV2dCkgPT5cbiAgICAgIGhpZGRlbi5zcGxpY2UgaW5kZXgsIGxlbmd0aFxuICAgICAgQGcuY29sdW1ucy5zZXQgXCJoaWRkZW5cIiwgaGlkZGVuXG5cbiAgICBzLmFwcGVuZENoaWxkIHRyaWFuZ2xlXG4gICAgc3Bhbi5hcHBlbmRDaGlsZCBzXG4gICAgcmV0dXJuIHNcblxuICBtYW5hZ2VFdmVudHM6IC0+XG4gICAgZXZlbnRzID0ge31cbiAgICBpZiBAZy5jb25maWcuZ2V0IFwicmVnaXN0ZXJNb3VzZUNsaWNrc1wiXG4gICAgICBldmVudHMuY2xpY2sgPSBcIl9vbmNsaWNrXCJcbiAgICBpZiBAZy5jb25maWcuZ2V0IFwicmVnaXN0ZXJNb3VzZUhvdmVyXCJcbiAgICAgIGV2ZW50cy5tb3VzZWluID0gXCJfb25tb3VzZWluXCJcbiAgICAgIGV2ZW50cy5tb3VzZW91dCA9IFwiX29ubW91c2VvdXRcIlxuICAgIEBkZWxlZ2F0ZUV2ZW50cyBldmVudHNcbiAgICBAbGlzdGVuVG8gQGcuY29uZmlnLCBcImNoYW5nZTpyZWdpc3Rlck1vdXNlSG92ZXJcIiwgQG1hbmFnZUV2ZW50c1xuICAgIEBsaXN0ZW5UbyBAZy5jb25maWcsIFwiY2hhbmdlOnJlZ2lzdGVyTW91c2VDbGlja1wiLCBAbWFuYWdlRXZlbnRzXG5cbiAgX29uY2xpY2s6IChldnQpIC0+XG4gICAgcm93UG9zID0gZXZ0LnRhcmdldC5yb3dQb3NcbiAgICBzdGVwU2l6ZSA9IEBnLnpvb21lci5nZXQoXCJzdGVwU2l6ZVwiKVxuICAgIEBnLnRyaWdnZXIgXCJjb2x1bW46Y2xpY2tcIiwge3Jvd1Bvczogcm93UG9zLHN0ZXBTaXplOiBzdGVwU2l6ZSwgZXZ0OmV2dH1cblxuICBfb25tb3VzZWluOiAoZXZ0KSAtPlxuICAgIHJvd1BvcyA9IEBnLnpvb21lci5nZXQgXCJzdGVwU2l6ZVwiICogZXZ0LnJvd1Bvc1xuICAgIHN0ZXBTaXplID0gQGcuem9vbWVyLmdldChcInN0ZXBTaXplXCIpXG4gICAgQGcudHJpZ2dlciBcImNvbHVtbjptb3VzZWluXCIsIHtyb3dQb3M6IHJvd1BvcyxzdGVwU2l6ZTogc3RlcFNpemUsIGV2dDpldnR9XG5cbiAgX29ubW91c2VvdXQ6IChldnQpIC0+XG4gICAgcm93UG9zID0gQGcuem9vbWVyLmdldCBcInN0ZXBTaXplXCIgKiBldnQucm93UG9zXG4gICAgc3RlcFNpemUgPSBAZy56b29tZXIuZ2V0KFwic3RlcFNpemVcIilcbiAgICBAZy50cmlnZ2VyIFwiY29sdW1uOm1vdXNlb3V0XCIsIHtyb3dQb3M6IHJvd1BvcyxzdGVwU2l6ZTogc3RlcFNpemUsIGV2dDpldnR9XG5cbm1vZHVsZS5leHBvcnRzID0gSGVhZGVyVmlld1xuIiwiTGFiZWxSb3dWaWV3ID0gcmVxdWlyZSBcIi4vTGFiZWxSb3dWaWV3XCJcbmJvbmVWaWV3ID0gcmVxdWlyZShcImJhY2tib25lLWNoaWxkc1wiKVxuXG5tb2R1bGUuZXhwb3J0cyA9IGJvbmVWaWV3LmV4dGVuZFxuXG4gIGluaXRpYWxpemU6IChkYXRhKSAtPlxuICAgIEBnID0gZGF0YS5nXG4gICAgQGRyYXcoKVxuICAgIEBsaXN0ZW5UbyBAZy56b29tZXIsIFwiY2hhbmdlOl9hbGlnbm1lbnRTY3JvbGxUb3BcIiwgQF9hZGp1c3RTY3JvbGxpbmdUb3BcbiAgICBAZy52aXMub25jZSAnY2hhbmdlOmxvYWRlZCcsIEBfYWRqdXN0U2Nyb2xsaW5nVG9wICwgQFxuXG4gIGRyYXc6IC0+XG4gICAgQHJlbW92ZVZpZXdzKClcbiAgICBmb3IgaSBpbiBbMC4uIEBtb2RlbC5sZW5ndGggLSAxXSBieSAxXG4gICAgICBjb250aW51ZSBpZiBAbW9kZWwuYXQoaSkuZ2V0KCdoaWRkZW4nKVxuICAgICAgdmlldyA9IG5ldyBMYWJlbFJvd1ZpZXcge21vZGVsOiBAbW9kZWwuYXQoaSksIGc6IEBnfVxuICAgICAgdmlldy5vcmRlcmluZyA9IGlcbiAgICAgIEBhZGRWaWV3IFwicm93XyN7aX1cIiwgdmlld1xuXG4gIGV2ZW50czpcbiAgICBcInNjcm9sbFwiOiBcIl9zZW5kU2Nyb2xsRXZlbnRcIlxuXG4gICMgYnJvYWRjYXN0IHRoZSBzY3JvbGxpbmcgZXZlbnQgKGJ5IHRoZSBzY3JvbGxiYXIpXG4gIF9zZW5kU2Nyb2xsRXZlbnQ6IC0+XG4gICAgQGcuem9vbWVyLnNldCBcIl9hbGlnbm1lbnRTY3JvbGxUb3BcIiwgQGVsLnNjcm9sbFRvcCwge29yaWdpbjogXCJsYWJlbFwifVxuXG4gICMgc2V0cyB0aGUgc2Nyb2xsaW5nIHByb3BlcnR5IChmcm9tIGFub3RoZXIgZXZlbnQgZS5nLiBkcmFnZ2luZylcbiAgX2FkanVzdFNjcm9sbGluZ1RvcDogLT5cbiAgICBAZWwuc2Nyb2xsVG9wID0gIEBnLnpvb21lci5nZXQgXCJfYWxpZ25tZW50U2Nyb2xsVG9wXCJcblxuICByZW5kZXI6IC0+XG4gICAgQHJlbmRlclN1YnZpZXdzKClcbiAgICBAZWwuY2xhc3NOYW1lID0gXCJiaW9qc19tc2FfbGFiZWxibG9ja1wiXG4gICAgQGVsLnN0eWxlLmRpc3BsYXkgPSBcImlubGluZS1ibG9ja1wiXG4gICAgQGVsLnN0eWxlLnZlcnRpY2FsQWxpZ24gPSBcInRvcFwiXG4gICAgQGVsLnN0eWxlLmhlaWdodCA9ICBAZy56b29tZXIuZ2V0KFwiYWxpZ25tZW50SGVpZ2h0XCIpICsgXCJweFwiXG4gICAgQGVsLnN0eWxlLm92ZXJmbG93WSA9IFwiYXV0b1wiXG4gICAgQGVsLnN0eWxlLm92ZXJmbG93WCA9IFwiaGlkZGVuXCJcbiAgICBAZWwuc3R5bGUuZm9udFNpemUgPSBcIiN7QGcuem9vbWVyLmdldCBcImxhYmVsRm9udHNpemVcIn1cIlxuICAgIEBlbC5zdHlsZS5saW5lSGVpZ2h0ID0gXCIje0BnLnpvb21lci5nZXQgXCJsYWJlbExpbmVIZWlnaHRcIn1cIlxuICAgIEBcbiIsImJvbmVWaWV3ID0gcmVxdWlyZShcImJhY2tib25lLWNoaWxkc1wiKVxuTGFiZWxWaWV3ID0gcmVxdWlyZShcIi4vTGFiZWxWaWV3XCIpXG5NZXRhVmlldyA9IHJlcXVpcmUoXCIuL01ldGFWaWV3XCIpXG5cbm1vZHVsZS5leHBvcnRzID0gYm9uZVZpZXcuZXh0ZW5kXG5cbiAgaW5pdGlhbGl6ZTogKGRhdGEpIC0+XG4gICAgQGcgPSBkYXRhLmdcbiAgICBAZHJhdygpXG5cbiAgICBAbGlzdGVuVG8gQGcudmlzLFwiY2hhbmdlOmxhYmVsc1wiLCBAZHJhd1JcbiAgICBAbGlzdGVuVG8gQGcudmlzLFwiY2hhbmdlOm1ldGFjZWxsXCIsIEBkcmF3UlxuXG4gIGRyYXc6IC0+XG4gICAgQHJlbW92ZVZpZXdzKClcbiAgICBpZiBAZy52aXMuZ2V0IFwibGFiZWxzXCJcbiAgICAgIEBhZGRWaWV3IFwibGFiZWxzXCIsIG5ldyBMYWJlbFZpZXcge21vZGVsOiBAbW9kZWwsIGc6QGd9XG4gICAgaWYgQGcudmlzLmdldCBcIm1ldGFjZWxsXCJcbiAgICAgIEBhZGRWaWV3IFwibWV0YWNlbGxcIiwgbmV3IE1ldGFWaWV3IHttb2RlbDogQG1vZGVsLCBnOkBnfVxuXG4gIGRyYXdSOiAtPlxuICAgIEBkcmF3KClcbiAgICBAcmVuZGVyKClcblxuICByZW5kZXI6IC0+XG4gICAgQHJlbmRlclN1YnZpZXdzKClcbiAgICBAZWwuc2V0QXR0cmlidXRlIFwiY2xhc3NcIiwgXCJiaW9qc19tc2FfbGFiZWxyb3dcIlxuICAgIEBlbC5zdHlsZS5oZWlnaHQgPSBAZy56b29tZXIuZ2V0IFwicm93SGVpZ2h0XCJcbiAgICBAXG4iLCJ2aWV3ID0gcmVxdWlyZShcImJhY2tib25lLXZpZXdqXCIpXG5kb20gPSByZXF1aXJlIFwiZG9tLWhlbHBlclwiXG5cbkxhYmVsVmlldyA9IHZpZXcuZXh0ZW5kXG5cbiAgaW5pdGlhbGl6ZTogKGRhdGEpIC0+XG4gICAgQHNlcSA9IGRhdGEuc2VxXG4gICAgQGcgPSBkYXRhLmdcblxuICAgIEBtYW5hZ2VFdmVudHMoKVxuXG4gIG1hbmFnZUV2ZW50czogLT5cbiAgICBldmVudHMgPSB7fVxuICAgIGlmIEBnLmNvbmZpZy5nZXQgXCJyZWdpc3Rlck1vdXNlQ2xpY2tzXCJcbiAgICAgIGV2ZW50cy5jbGljayA9IFwiX29uY2xpY2tcIlxuICAgIGlmIEBnLmNvbmZpZy5nZXQgXCJyZWdpc3Rlck1vdXNlSG92ZXJcIlxuICAgICAgZXZlbnRzLm1vdXNlaW4gPSBcIl9vbm1vdXNlaW5cIlxuICAgICAgZXZlbnRzLm1vdXNlb3V0ID0gXCJfb25tb3VzZW91dFwiXG4gICAgQGRlbGVnYXRlRXZlbnRzIGV2ZW50c1xuICAgIEBsaXN0ZW5UbyBAZy5jb25maWcsIFwiY2hhbmdlOnJlZ2lzdGVyTW91c2VIb3ZlclwiLCBAbWFuYWdlRXZlbnRzXG4gICAgQGxpc3RlblRvIEBnLmNvbmZpZywgXCJjaGFuZ2U6cmVnaXN0ZXJNb3VzZUNsaWNrXCIsIEBtYW5hZ2VFdmVudHNcbiAgICBAbGlzdGVuVG8gQGcudmlzLCBcImNoYW5nZTpsYWJlbE5hbWVcIiwgQHJlbmRlclxuICAgIEBsaXN0ZW5UbyBAZy52aXMsIFwiY2hhbmdlOmxhYmVsSWRcIiwgQHJlbmRlclxuICAgIEBsaXN0ZW5UbyBAZy52aXMsIFwiY2hhbmdlOmxhYmVsUGFydGl0aW9uXCIsIEByZW5kZXJcbiAgICBAbGlzdGVuVG8gQGcudmlzLCBcImNoYW5nZTpsYWJlbENoZWNrYm94XCIsIEByZW5kZXJcblxuICByZW5kZXI6IC0+XG4gICAgZG9tLnJlbW92ZUFsbENoaWxkcyBAZWxcblxuICAgIEBlbC5zdHlsZS53aWR0aCA9IFwiI3tAZy56b29tZXIuZ2V0IFwibGFiZWxXaWR0aFwifXB4XCJcbiAgICBAZWwuc3R5bGUuaGVpZ2h0ID0gXCIje0BnLnpvb21lci5nZXQgXCJyb3dIZWlnaHRcIn1weFwiXG4gICAgQGVsLnNldEF0dHJpYnV0ZSBcImNsYXNzXCIsIFwiYmlvanNfbXNhX2xhYmVsc1wiXG5cbiAgICBpZiBALmcudmlzLmdldCBcImxhYmVsQ2hlY2tib3hcIlxuICAgICAgY2hlY2tCb3ggPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50IFwiaW5wdXRcIlxuICAgICAgY2hlY2tCb3guc2V0QXR0cmlidXRlIFwidHlwZVwiLCBcImNoZWNrYm94XCJcbiAgICAgIGNoZWNrQm94LnZhbHVlID0gQG1vZGVsLmdldCgnaWQnKVxuICAgICAgY2hlY2tCb3gubmFtZSA9IFwic2VxXCJcbiAgICAgIEBlbC5hcHBlbmRDaGlsZCBjaGVja0JveFxuXG4gICAgaWYgQC5nLnZpcy5nZXQgXCJsYWJlbElkXCJcbiAgICAgIGlkID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCBcInNwYW5cIlxuICAgICAgaWQudGV4dENvbnRlbnQgPSBAbW9kZWwuZ2V0IFwiaWRcIlxuICAgICAgaWQuc3R5bGUud2lkdGggPSBAZy56b29tZXIuZ2V0IFwibGFiZWxJZExlbmd0aFwiXG4gICAgICBpZC5zdHlsZS5kaXNwbGF5ID0gXCJpbmxpbmUtYmxvY2tcIlxuICAgICAgQGVsLmFwcGVuZENoaWxkIGlkXG5cbiAgICBpZiBALmcudmlzLmdldCBcImxhYmVsUGFydGl0aW9uXCJcbiAgICAgIHBhcnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50IFwic3BhblwiXG4gICAgICBwYXJ0LnN0eWxlLndpZHRoID0gMTVcbiAgICAgIHBhcnQudGV4dENvbnRlbnQgPSBAbW9kZWwuZ2V0KFwicGFydGl0aW9uXCIpXG4gICAgICBwYXJ0LnN0eWxlLmRpc3BsYXkgPSBcImlubGluZS1ibG9ja1wiXG4gICAgICBAZWwuYXBwZW5kQ2hpbGQgaWRcbiAgICAgIEBlbC5hcHBlbmRDaGlsZCBwYXJ0XG5cbiAgICBpZiBALmcudmlzLmdldCBcImxhYmVsTmFtZVwiXG4gICAgICBuYW1lID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCBcInNwYW5cIlxuICAgICAgbmFtZS50ZXh0Q29udGVudCA9IEBtb2RlbC5nZXQoXCJuYW1lXCIpXG4gICAgICBAZWwuYXBwZW5kQ2hpbGQgbmFtZVxuXG5cbiAgICBAZWwuc3R5bGUub3ZlcmZsb3cgPSBzY3JvbGxcbiAgICBAXG5cbiAgX29uY2xpY2s6IChldnQpIC0+XG4gICAgc2VxSWQgPSBAbW9kZWwuZ2V0IFwiaWRcIlxuICAgIEBnLnRyaWdnZXIgXCJyb3c6Y2xpY2tcIiwge3NlcUlkOnNlcUlkLCBldnQ6ZXZ0fVxuXG4gIF9vbm1vdXNlaW46IChldnQpIC0+XG4gICAgc2VxSWQgPSBAbW9kZWwuZ2V0IFwiaWRcIlxuICAgIEBnLnRyaWdnZXIgXCJyb3c6bW91c2VvdXRcIiwge3NlcUlkOnNlcUlkLCBldnQ6ZXZ0fVxuXG4gIF9vbm1vdXNlb3V0OiAoZXZ0KSAtPlxuICAgIHNlcUlkID0gQG1vZGVsLmdldCBcImlkXCJcbiAgICBAZy50cmlnZ2VyIFwicm93Om1vdXNlb3V0XCIsIHtzZXFJZDpzZXFJZCwgZXZ0OmV2dH1cblxubW9kdWxlLmV4cG9ydHMgPSBMYWJlbFZpZXdcbiIsInZpZXcgPSByZXF1aXJlKFwiYmFja2JvbmUtdmlld2pcIilcbk1lbnVCdWlsZGVyID0gcmVxdWlyZSBcIi4uLy4uL21lbnUvbWVudWJ1aWxkZXJcIlxuXyA9IHJlcXVpcmUgJ3VuZGVyc2NvcmUnXG5kb20gPSByZXF1aXJlIFwiZG9tLWhlbHBlclwiXG5cbm1vZHVsZS5leHBvcnRzID0gTWV0YVZpZXcgPSB2aWV3LmV4dGVuZFxuXG4gIGNsYXNzTmFtZTogXCJiaW9qc19tc2FfbWV0YXZpZXdcIlxuXG4gIGluaXRpYWxpemU6IChkYXRhKSAtPlxuICAgIEBnID0gZGF0YS5nXG5cbiAgZXZlbnRzOlxuICAgIGNsaWNrOiBcIl9vbmNsaWNrXCJcbiAgICBtb3VzZWluOiBcIl9vbm1vdXNlaW5cIlxuICAgIG1vdXNlb3V0OiBcIl9vbm1vdXNlb3V0XCJcblxuICByZW5kZXI6IC0+XG4gICAgZG9tLnJlbW92ZUFsbENoaWxkcyBAZWxcblxuICAgIEBlbC5zdHlsZS5kaXNwbGF5ID0gXCJpbmxpbmUtYmxvY2tcIlxuXG4gICAgd2lkdGggPSBAZy56b29tZXIuZ2V0IFwibWV0YVdpZHRoXCJcbiAgICBAZWwuc3R5bGUud2lkdGggPSB3aWR0aCAtIDVcbiAgICBAZWwuc3R5bGUucGFkZGluZ1JpZ2h0ID0gNVxuXG4gICAgIyBhZGRzIGdhcHNcbiAgICBzZXEgPSBAbW9kZWwuZ2V0KCdzZXEnKVxuICAgIGdhcHMgPSBfLnJlZHVjZSBzZXEsICgobWVtbywgYykgLT4gbWVtbysrIGlmIGMgaXMgJy0nO21lbW8pLDBcbiAgICBnYXBzID0gKGdhcHMgLyBzZXEubGVuZ3RoKS50b0ZpeGVkKDEpXG5cbiAgICAjIGFwcGVuZCBnYXAgY291bnRcbiAgICBnYXBTcGFuID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCAnc3BhbidcbiAgICBnYXBTcGFuLnRleHRDb250ZW50ID0gZ2Fwc1xuICAgIGdhcFNwYW4uc3R5bGUuZGlzcGxheSA9IFwiaW5saW5lLWJsb2NrXCJcbiAgICBnYXBTcGFuLnN0eWxlLndpZHRoID0gMzVcbiAgICBAZWwuYXBwZW5kQ2hpbGQgZ2FwU3BhblxuXG4gICAgIyBpZGVudGl0eVxuICAgIGlkZW50ID0gQG1vZGVsLmdldCgnaWRlbnRpdHknKVxuICAgIGlkZW50U3BhbiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQgJ3NwYW4nXG4gICAgaWRlbnRTcGFuLnRleHRDb250ZW50ID0gaWRlbnQudG9GaXhlZCgyKVxuICAgIGlkZW50U3Bhbi5zdHlsZS5kaXNwbGF5ID0gXCJpbmxpbmUtYmxvY2tcIlxuICAgIGlkZW50U3Bhbi5zdHlsZS53aWR0aCA9IDQwXG4gICAgQGVsLmFwcGVuZENoaWxkIGlkZW50U3BhblxuXG4gICAgIyBUT0RPOiB0aGlzIG1lbnUgYnVpbGRlciBpcyBqdXN0IGFuIGV4YW1wbGUgaG93IG9uZSBjb3VsZCBjdXN0b21pemUgdGhpc1xuICAgICMgdmlld1xuICAgIG1lbnUgPSBuZXcgTWVudUJ1aWxkZXIoXCLihpdcIilcbiAgICBtZW51LmFkZE5vZGUgXCJVbmlwcm90XCIsKGUpID0+XG4gICAgICB3aW5kb3cub3BlbiBcImh0dHA6Ly9iZXRhLnVuaXByb3Qub3JnL3VuaXByb3QvUTdUMk44XCJcbiAgICBAZWwuYXBwZW5kQ2hpbGQgbWVudS5idWlsZERPTSgpXG4gICAgQGVsLndpZHRoID0gMTBcblxuICAgIEBlbC5zdHlsZS5oZWlnaHQgPSBcIiN7QGcuem9vbWVyLmdldCBcInJvd0hlaWdodFwifXB4XCJcbiAgICBAZWwuc3R5bGUuY3Vyc29yID0gXCJwb2ludGVyXCJcblxuICBfb25jbGljazogKGV2dCkgLT5cbiAgICBAZy50cmlnZ2VyIFwibWV0YTpjbGlja1wiLCB7c2VxSWQ6IEBtb2RlbC5nZXQgXCJpZFwiLCBldnQ6ZXZ0fVxuXG4gIF9vbm1vdXNlaW46IChldnQpIC0+XG4gICAgQGcudHJpZ2dlciBcIm1ldGE6bW91c2VpblwiLCB7c2VxSWQ6IEBtb2RlbC5nZXQgXCJpZFwiLCBldnQ6ZXZ0fVxuXG4gIF9vbm1vdXNlb3V0OiAoZXZ0KSAtPlxuICAgIEBnLnRyaWdnZXIgXCJtZXRhOm1vdXNlb3V0XCIsIHtzZXFJZDogQG1vZGVsLmdldCBcImlkXCIsIGV2dDpldnR9XG4iLCIvLyBHZW5lcmF0ZWQgYnkgQ29mZmVlU2NyaXB0IDEuOC4wXG52YXIgQ2x1c3RhbCwgR2VuZXJpY1JlYWRlciwgU2VxLCBTdHIsXG4gIF9faGFzUHJvcCA9IHt9Lmhhc093blByb3BlcnR5LFxuICBfX2V4dGVuZHMgPSBmdW5jdGlvbihjaGlsZCwgcGFyZW50KSB7IGZvciAodmFyIGtleSBpbiBwYXJlbnQpIHsgaWYgKF9faGFzUHJvcC5jYWxsKHBhcmVudCwga2V5KSkgY2hpbGRba2V5XSA9IHBhcmVudFtrZXldOyB9IGZ1bmN0aW9uIGN0b3IoKSB7IHRoaXMuY29uc3RydWN0b3IgPSBjaGlsZDsgfSBjdG9yLnByb3RvdHlwZSA9IHBhcmVudC5wcm90b3R5cGU7IGNoaWxkLnByb3RvdHlwZSA9IG5ldyBjdG9yKCk7IGNoaWxkLl9fc3VwZXJfXyA9IHBhcmVudC5wcm90b3R5cGU7IHJldHVybiBjaGlsZDsgfTtcblxuU3RyID0gcmVxdWlyZShcIi4vc3RyaW5nc1wiKTtcblxuR2VuZXJpY1JlYWRlciA9IHJlcXVpcmUoXCIuL2dlbmVyaWNfcmVhZGVyXCIpO1xuXG5TZXEgPSByZXF1aXJlKFwiLi9zZXFcIik7XG5cbm1vZHVsZS5leHBvcnRzID0gQ2x1c3RhbCA9IChmdW5jdGlvbihfc3VwZXIpIHtcbiAgX19leHRlbmRzKENsdXN0YWwsIF9zdXBlcik7XG5cbiAgZnVuY3Rpb24gQ2x1c3RhbCgpIHtcbiAgICByZXR1cm4gQ2x1c3RhbC5fX3N1cGVyX18uY29uc3RydWN0b3IuYXBwbHkodGhpcywgYXJndW1lbnRzKTtcbiAgfVxuXG4gIENsdXN0YWwucGFyc2UgPSBmdW5jdGlvbih0ZXh0KSB7XG4gICAgdmFyIGJsb2Nrc3RhdGUsIGssIGxhYmVsLCBsaW5lLCBsaW5lcywgbWF0Y2gsIHJlZ2V4LCBzZXFDb3VudGVyLCBzZXFzLCBzZXF1ZW5jZTtcbiAgICBzZXFzID0gW107XG4gICAgaWYgKE9iamVjdC5wcm90b3R5cGUudG9TdHJpbmcuY2FsbCh0ZXh0KSA9PT0gJ1tvYmplY3QgQXJyYXldJykge1xuICAgICAgbGluZXMgPSB0ZXh0O1xuICAgIH0gZWxzZSB7XG4gICAgICBsaW5lcyA9IHRleHQuc3BsaXQoXCJcXG5cIik7XG4gICAgfVxuICAgIGlmIChsaW5lc1swXS5zbGljZSgwLCA2KSA9PT0gIVwiQ0xVU1RBTFwiKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJJbnZhbGlkIENMVVNUQUwgSGVhZGVyXCIpO1xuICAgIH1cbiAgICBrID0gMDtcbiAgICBibG9ja3N0YXRlID0gMTtcbiAgICBzZXFDb3VudGVyID0gMDtcbiAgICB3aGlsZSAoayA8IGxpbmVzLmxlbmd0aCkge1xuICAgICAgaysrO1xuICAgICAgbGluZSA9IGxpbmVzW2tdO1xuICAgICAgaWYgKChsaW5lID09IG51bGwpIHx8IGxpbmUubGVuZ3RoID09PSAwKSB7XG4gICAgICAgIGJsb2Nrc3RhdGUgPSAxO1xuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cbiAgICAgIGlmIChsaW5lLnRyaW0oKS5sZW5ndGggPT09IDApIHtcbiAgICAgICAgYmxvY2tzdGF0ZSA9IDE7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgaWYgKFN0ci5jb250YWlucyhsaW5lLCBcIipcIikpIHtcbiAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoYmxvY2tzdGF0ZSA9PT0gMSkge1xuICAgICAgICAgIHNlcUNvdW50ZXIgPSAwO1xuICAgICAgICAgIGJsb2Nrc3RhdGUgPSAwO1xuICAgICAgICB9XG4gICAgICAgIHJlZ2V4ID0gL14oPzpcXHMqKShcXFMrKSg/OlxccyspKFxcUyspKD86XFxzKikoXFxkKikoPzpcXHMqfCQpL2c7XG4gICAgICAgIG1hdGNoID0gcmVnZXguZXhlYyhsaW5lKTtcbiAgICAgICAgaWYgKG1hdGNoICE9IG51bGwpIHtcbiAgICAgICAgICBsYWJlbCA9IG1hdGNoWzFdO1xuICAgICAgICAgIHNlcXVlbmNlID0gbWF0Y2hbMl07XG4gICAgICAgICAgaWYgKHNlcUNvdW50ZXIgPj0gc2Vxcy5sZW5ndGgpIHtcbiAgICAgICAgICAgIHNlcXMucHVzaChuZXcgU2VxKHNlcXVlbmNlLCBsYWJlbCwgc2VxQ291bnRlcikpO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBzZXFzW3NlcUNvdW50ZXJdLnNlcSArPSBzZXF1ZW5jZTtcbiAgICAgICAgICB9XG4gICAgICAgICAgc2VxQ291bnRlcisrO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGNvbnNvbGUubG9nKGxpbmUpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBzZXFzO1xuICB9O1xuXG4gIHJldHVybiBDbHVzdGFsO1xuXG59KShHZW5lcmljUmVhZGVyKTtcbiIsIi8vIEdlbmVyYXRlZCBieSBDb2ZmZWVTY3JpcHQgMS44LjBcbm1vZHVsZS5leHBvcnRzLnBhcnNlID0gcmVxdWlyZShcIi4vcGFyc2VyXCIpO1xuXG5tb2R1bGUuZXhwb3J0cy53cml0ZXIgPSByZXF1aXJlKFwiLi93cml0ZXJcIik7XG4iLCJpZiAodHlwZW9mIGJpb2pzID09PSAndW5kZWZpbmVkJykge1xuICBiaW9qcyA9IHt9O1xufVxuaWYgKHR5cGVvZiBiaW9qcy52aXMgPT09ICd1bmRlZmluZWQnKSB7XG4gIGJpb2pzLnZpcyA9IHt9O1xufVxuLy8gdXNlIHR3byBuYW1lc3BhY2VzXG53aW5kb3cubXNhID0gYmlvanMudmlzLm1zYSA9IG1vZHVsZS5leHBvcnRzID0gcmVxdWlyZSgnLi9pbmRleCcpO1xuXG4vLyBUT0RPOiBob3cgc2hvdWxkIHRoaXMgYmUgYnVuZGxlZFxuXG5pZiAodHlwZW9mIGJpb2pzLmlvID09PSAndW5kZWZpbmVkJykge1xuICBiaW9qcy5pbyA9IHt9O1xufVxuLy8ganVzdCBidW5kbGUgdGhlIHR3byBwYXJzZXJzXG53aW5kb3cuYmlvanMuaW8uZmFzdGEgPSByZXF1aXJlKFwiYmlvanMtaW8tZmFzdGFcIik7XG53aW5kb3cuYmlvanMuaW8uY2x1c3RhbCA9IHJlcXVpcmUoXCJiaW9qcy1pby1jbHVzdGFsXCIpO1xud2luZG93LmJpb2pzLnhociA9IHJlcXVpcmUoXCJuZXRzXCIpO1xuXG4vLyBzaW11bGF0ZSBzdGFuZGFsb25lIGZsYWdcbndpbmRvdy5iaW9qc1Zpc01zYSA9IHdpbmRvdy5tc2E7XG5cbnJlcXVpcmUoJy4vYnVpbGQvbXNhLmNzcycpO1xuIiwidmFyIHJlcSA9IHJlcXVpcmUoJ3JlcXVlc3QnKVxuXG5tb2R1bGUuZXhwb3J0cyA9IE5ldHNcblxuZnVuY3Rpb24gTmV0cyh1cmksIG9wdHMsIGNiKSB7XG4gIHJlcSh1cmksIG9wdHMsIGNiKVxufSJdfQ==
+
+
+
+// this is a way how you use a bundled file parser
+biojs.io.clustal.read("#", function(seqs){
+var opts = {};
+
+// set your custom properties
+// @see: https://github.com/greenify/biojs-vis-msa/tree/master/src/g 
+
+var jalviewData = JSON.parse(document.getElementById("seqData").value); 
+opts.seqs = jalviewData['seqs'];
+
+opts.el = document.getElementById("yourDiv");
+opts.vis = {conserv: false, overviewbox: false, labelId: false};
+opts.zoomer = {alignmentHeight: 225, labelWidth: 130,labelFontsize: "13px",labelIdLength: 20,   menuFontsize: "12px",menuMarginLeft: "3px", menuPadding: "3px 4px 3px 4px", menuItemFontsize: "14px", menuItemLineHeight: "14px"};
+
+
+
+// init msa
+var m = new msa.msa(opts);
+
+m.g.colorscheme.set("scheme", jalviewData['globalColorScheme']);
+
+var x = 0;
+jalviewData.seqs.forEach( function (seq)
+{
+m.seqs.at(x++).set("features", new msa.model.featurecol(seq.features));
+});
+
+// the menu is independent to the MSA container
+var menuOpts = {};
+menuOpts.el = document.getElementById('div');
+menuOpts.msa = m;
+var defMenu = new msa.menu.defaultmenu(menuOpts);
+m.addView("menu", defMenu);
+
+// call render at the end to display the whole MSA
+m.render();
+toggleMenuVisibility(); 
+toggleMenuVisibility(); 
+});
+</script>
\ No newline at end of file
index a1bb272..9b0412a 100644 (file)
@@ -550,7 +550,7 @@ public class AppletPDBCanvas extends Panel implements MouseListener,
     StructureMapping[] mapping = ssm.getMapping(pdbentry.getFile());
 
     boolean showFeatures = false;
-    if (ap.av.getShowSequenceFeatures())
+    if (ap.av.isShowSequenceFeatures())
     {
       if (fr == null)
       {
index dc0f718..4fd7a35 100644 (file)
@@ -110,7 +110,7 @@ public class PDBCanvas extends JPanel implements MouseListener,
 
   boolean seqColoursReady = false;
 
-  jalview.gui.FeatureRenderer fr;
+  jalview.renderer.seqfeatures.FeatureRenderer fr;
 
   Color backgroundColour = Color.black;
 
@@ -520,7 +520,7 @@ public class PDBCanvas extends JPanel implements MouseListener,
     StructureMapping[] mapping = ssm.getMapping(pdbentry.getFile());
 
     boolean showFeatures = false;
-    if (ap.av.getShowSequenceFeatures())
+    if (ap.av.isShowSequenceFeatures())
     {
       if (fr == null)
       {
index a7440e7..2c40e1c 100644 (file)
@@ -1,14 +1,21 @@
 package ext.edu.ucsf.rbvi.strucviz2;
 
+import jalview.ws.HttpClientUtils;
+
 import java.awt.Color;
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.http.NameValuePair;
+import org.apache.http.message.BasicNameValuePair;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -20,10 +27,13 @@ import ext.edu.ucsf.rbvi.strucviz2.port.ListenerThreads;
  */
 public class ChimeraManager
 {
+  private static final boolean debug = false;
+
+  private int chimeraRestPort;
 
   private Process chimera;
 
-  private ListenerThreads chimeraListenerThreads;
+  private ListenerThreads chimeraListenerThread;
 
   private Map<Integer, ChimeraModel> currentModelsMap;
 
@@ -36,7 +46,7 @@ public class ChimeraManager
   {
     this.structureManager = structureManager;
     chimera = null;
-    chimeraListenerThreads = null;
+    chimeraListenerThread = null;
     currentModelsMap = new HashMap<Integer, ChimeraModel>();
 
   }
@@ -208,10 +218,14 @@ public class ChimeraManager
                     modelNumbers[0], modelNumbers[1]);
             currentModelsMap.put(modelNumber, newModel);
             models.add(newModel);
+
+            //
             // patch for Jalview - set model name in Chimera
+            //
             sendChimeraCommand("setattr M name " + modelName + " #"
                     + modelNumbers[0], false);
             // end patch for Jalview
+
             modelNumbers = null;
           }
         }
@@ -354,7 +368,7 @@ public class ChimeraManager
   {
     chimera = null;
     currentModelsMap.clear();
-    chimeraListenerThreads = null;
+      this.chimeraRestPort = 0;
     structureManager.clearOnChimeraExit();
   }
 
@@ -525,12 +539,11 @@ public class ChimeraManager
         List<String> args = new ArrayList<String>();
         args.add(chimeraPath);
         args.add("--start");
-        args.add("ReadStdin");
+        args.add("RESTServer");
         ProcessBuilder pb = new ProcessBuilder(args);
         chimera = pb.start();
         error = "";
         workingPath = chimeraPath;
-        logger.info("Strarting " + chimeraPath);
         break;
       } catch (Exception e)
       {
@@ -541,10 +554,9 @@ public class ChimeraManager
     // If no error, then Chimera was launched successfully
     if (error.length() == 0)
     {
-      // Initialize the listener threads
-      chimeraListenerThreads = new ListenerThreads(chimera,
-              structureManager);
-      chimeraListenerThreads.start();
+      this.chimeraRestPort = getPortNumber();
+      System.out.println("Chimera REST API started on port "
+              + chimeraRestPort);
       // structureManager.initChimTable();
       structureManager.setChimeraPathProperty(workingPath);
       // TODO: [Optional] Check Chimera version and show a warning if below 1.8
@@ -559,6 +571,42 @@ public class ChimeraManager
   }
 
   /**
+   * Read and return the port number returned in the reply to --start RESTServer
+   */
+  private int getPortNumber()
+  {
+    int port = 0;
+    InputStream readChan = chimera.getInputStream();
+    BufferedReader lineReader = new BufferedReader(new InputStreamReader(
+            readChan));
+    String response = null;
+    try
+    {
+      // expect: REST server on host 127.0.0.1 port port_number
+      response = lineReader.readLine();
+      String [] tokens = response.split(" ");
+      if (tokens.length == 7 && "port".equals(tokens[5])) {
+        port = Integer.parseInt(tokens[6]);
+        logger.info("Chimera REST service listening on port "
+                + chimeraRestPort);
+      }
+    } catch (Exception e)
+    {
+      logger.error("Failed to get REST port number from " + response + ": "
+              + e.getMessage());
+    } finally
+    {
+      try
+      {
+        lineReader.close();
+      } catch (IOException e2)
+      {
+      }
+    }
+    return port;
+  }
+
+  /**
    * Determine the color that Chimera is using for this model.
    * 
    * @param model
@@ -683,7 +731,8 @@ public class ChimeraManager
    */
   public List<String> sendChimeraCommand(String command, boolean reply)
   {
-    if (!isChimeraLaunched())
+    if (!isChimeraLaunched() || command == null
+            || "".equals(command.trim()))
     {
       return null;
     }
@@ -696,41 +745,100 @@ public class ChimeraManager
       } catch (InterruptedException q)
       {
       }
-      ;
     }
     busy = true;
+    long startTime = System.currentTimeMillis();
     try
     {
-      chimeraListenerThreads.clearResponse(command);
-      String text = command.concat("\n");
-      // System.out.println("send command to chimera: " + text);
-      try
-      {
-        // send the command
-        chimera.getOutputStream().write(text.getBytes());
-        chimera.getOutputStream().flush();
-      } catch (IOException e)
+      return sendRestCommand(command);
+    } finally
+    {
+      /*
+       * Make sure busy flag is reset come what may!
+       */
+      busy = false;
+      if (debug)
       {
-        // logger.info("Unable to execute command: " + text);
-        // logger.info("Exiting...");
-        logger.warn("Unable to execute command: " + text);
-        logger.warn("Exiting...");
-        clearOnChimeraExit();
-        // busy = false;
-        return null;
+        System.out.println("Chimera command took "
+                + (System.currentTimeMillis() - startTime) + "ms: "
+                + command);
       }
-      if (!reply)
-      {
-        // busy = false;
-        return null;
+
+    }
+  }
+
+  /**
+   * Sends the command to Chimera's REST API, and returns any response lines.
+   * 
+   * @param command
+   * @return
+   */
+  protected List<String> sendRestCommand(String command)
+  {
+    // TODO start a separate thread to do this so we don't block?
+    String restUrl = "http://127.0.0.1:" + this.chimeraRestPort + "/run";
+    List<NameValuePair> commands = new ArrayList<NameValuePair>(1);
+    commands.add(new BasicNameValuePair("command", command));
+
+    List<String> reply = new ArrayList<String>();
+    BufferedReader response = null;
+    try {
+      response = HttpClientUtils.doHttpUrlPost(restUrl,
+              commands);
+      String line = "";
+      while ((line = response.readLine()) != null) {
+        reply.add(line);
       }
-      List<String> rsp = chimeraListenerThreads.getResponse(command);
-      // busy = false;
-      return rsp;
+    } catch (Exception e)
+    {
+      logger.error("REST call " + command + " failed: " + e.getMessage());
     } finally
     {
-      busy = false;
+      if (response != null)
+      {
+        try
+        {
+          response.close();
+        } catch (IOException e)
+        {
+        }
+      }
+    }
+    return reply;
+  }
+
+  /**
+   * Send a command to stdin of Chimera process, and optionally read any
+   * responses.
+   * 
+   * @param command
+   * @param readReply
+   * @return
+   */
+  protected List<String> sendStdinCommand(String command, boolean readReply)
+  {
+    chimeraListenerThread.clearResponse(command);
+    String text = command.concat("\n");
+    try
+    {
+      // send the command
+      chimera.getOutputStream().write(text.getBytes());
+      chimera.getOutputStream().flush();
+    } catch (IOException e)
+    {
+      // logger.info("Unable to execute command: " + text);
+      // logger.info("Exiting...");
+      logger.warn("Unable to execute command: " + text);
+      logger.warn("Exiting...");
+      clearOnChimeraExit();
+      return null;
+    }
+    if (!readReply)
+    {
+      return null;
     }
+    List<String> rsp = chimeraListenerThread.getResponse(command);
+    return rsp;
   }
 
   public StructureManager getStructureManager()
index 883d536..2b2ce48 100644 (file)
@@ -21,207 +21,282 @@ import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
 /**
  * Reply listener thread
  */
-public class ListenerThreads extends Thread {
-       private InputStream readChan = null;
-       private BufferedReader lineReader = null;
-       private Process chimera = null;
-       private Map<String, List<String>> replyLog = null;
-       private Logger logger;
-       private StructureManager structureManager = null;
-
-       /**
-        * Create a new listener thread to read the responses from Chimera
-        * 
-        * @param chimera
-        *            a handle to the Chimera Process
-        * @param log
-        *            a handle to a List to post the responses to
-        * @param chimeraObject
-        *            a handle to the Chimera Object
-        */
-       public ListenerThreads(Process chimera, StructureManager structureManager) {
-               this.chimera = chimera;
-               this.structureManager = structureManager;
-               replyLog = new HashMap<String, List<String>>();
-               // Get a line-oriented reader
-               readChan = chimera.getInputStream();
-               lineReader = new BufferedReader(new InputStreamReader(readChan));
-               logger = LoggerFactory.getLogger(ext.edu.ucsf.rbvi.strucviz2.port.ListenerThreads.class);
-       }
-
-       /**
-        * Start the thread running
-        */
-       public void run() {
-               // System.out.println("ReplyLogListener running");
-               while (true) {
-                       try {
-                               chimeraRead();
-                       } catch (IOException e) {
-                               logger.warn("UCSF Chimera has exited: " + e.getMessage());
-                               return;
-                       }
-               }
-       }
-
-       public List<String> getResponse(String command) {
-               List<String> reply;
-               // System.out.println("getResponse: "+command);
+public class ListenerThreads extends Thread
+{
+  private BufferedReader lineReader = null;
+
+  private Process chimera = null;
+
+  private Map<String, List<String>> replyLog = null;
+
+  private Logger logger;
+
+  private StructureManager structureManager = null;
+
+  private boolean stopMe = false;
+
+  /**
+   * Create a new listener thread to read the responses from Chimera
+   * 
+   * @param chimera
+   *          a handle to the Chimera Process
+   * @param structureManager
+   *          a handle to the Chimera structure manager
+   */
+  public ListenerThreads(Process chimera, StructureManager structureManager)
+  {
+    this.chimera = chimera;
+    this.structureManager = structureManager;
+    replyLog = new HashMap<String, List<String>>();
+    // Get a line-oriented reader
+    InputStream readChan = chimera.getInputStream();
+    lineReader = new BufferedReader(new InputStreamReader(readChan));
+    logger = LoggerFactory
+            .getLogger(ext.edu.ucsf.rbvi.strucviz2.port.ListenerThreads.class);
+  }
+
+  /**
+   * Start the thread running
+   */
+  public void run()
+  {
+    // System.out.println("ReplyLogListener running");
+    while (!stopMe)
+    {
+      try
+      {
+        chimeraRead();
+      } catch (IOException e)
+      {
+        logger.warn("UCSF Chimera has exited: " + e.getMessage());
+        return;
+      } finally
+      {
+        if (lineReader != null)
+        {
+          try
+          {
+            lineReader.close();
+          } catch (IOException e)
+          {
+          }
+        }
+      }
+    }
+  }
+
+  public List<String> getResponse(String command)
+  {
+    List<String> reply;
+    // System.out.println("getResponse: "+command);
     // TODO do we need a maximum wait time before aborting?
-               while (!replyLog.containsKey(command)) {
-                       try {
-                               Thread.currentThread().sleep(100);
-                       } catch (InterruptedException e) {
-                       }
-               }
-
-               synchronized (replyLog) {
-                       reply = replyLog.get(command);
-                       // System.out.println("getResponse ("+command+") = "+reply);
-                       replyLog.remove(command);
-               }
-               return reply;
-       }
-
-       public void clearResponse(String command) {
-               try {
-                       Thread.currentThread().sleep(100);
-               } catch (InterruptedException e) {
-               }
-               if (replyLog.containsKey(command))
+    while (!replyLog.containsKey(command))
+    {
+      try
+      {
+        Thread.currentThread().sleep(100);
+      } catch (InterruptedException e)
+      {
+      }
+    }
+
+    synchronized (replyLog)
     {
+      reply = replyLog.get(command);
+      // System.out.println("getResponse ("+command+") = "+reply);
       replyLog.remove(command);
     }
-               return;
-       }
+    return reply;
+  }
 
-       /**
-        * Read input from Chimera
-        * 
-        * @return a List containing the replies from Chimera
-        */
-       private void chimeraRead() throws IOException {
-               if (chimera == null)
+  public void clearResponse(String command)
+  {
+    try
+    {
+      Thread.currentThread().sleep(100);
+    } catch (InterruptedException e)
+    {
+    }
+    if (replyLog.containsKey(command))
+    {
+      replyLog.remove(command);
+    }
+    return;
+  }
+
+  /**
+   * Read input from Chimera
+   * 
+   * @return a List containing the replies from Chimera
+   */
+  private void chimeraRead() throws IOException
+  {
+    if (chimera == null)
     {
       return;
     }
 
-               String line = null;
-               while ((line = lineReader.readLine()) != null) {
-                       // System.out.println("From Chimera-->" + line);
-                       if (line.startsWith("CMD")) {
-                               chimeraCommandRead(line.substring(4));
-                       } else if (line.startsWith("ModelChanged: ")) {
-                               (new ModelUpdater()).start();
-                       } else if (line.startsWith("SelectionChanged: ")) {
-                               (new SelectionUpdater()).start();
-                       } else if (line.startsWith("Trajectory residue network info:")) {
-                               (new NetworkUpdater(line)).start();
-                       }
-               }
-               return;
-       }
-
-       private void chimeraCommandRead(String command) throws IOException {
-               // Generally -- looking for:
-               // CMD command
-               // ........
-               // END
-               // We return the text in between
-               List<String> reply = new ArrayList<String>();
-               boolean updateModels = false;
-               boolean updateSelection = false;
-               boolean importNetwork = false;
-               String line = null;
-
-               synchronized (replyLog) {
-                       while ((line = lineReader.readLine()) != null) {
-                               // System.out.println("From Chimera (" + command + ") -->" + line);
-                               if (line.startsWith("CMD")) {
-                                       logger.warn("Got unexpected command from Chimera: " + line);
-
-                               } else if (line.startsWith("END")) {
-                                       break;
-                               }
-                               if (line.startsWith("ModelChanged: ")) {
-                                       updateModels = true;
-                               } else if (line.startsWith("SelectionChanged: ")) {
-                                       updateSelection = true;
-                               } else if (line.length() == 0) {
-                                       continue;
-                               } else if (!line.startsWith("CMD")) {
-                                       reply.add(line);
-                               } else if (line.startsWith("Trajectory residue network info:")) {
-                                       importNetwork = true;
-                               }
-                       }
-                       replyLog.put(command, reply);
-               }
-               if (updateModels)
+    String line = null;
+    while ((line = lineReader.readLine()) != null)
+    {
+      // System.out.println("From Chimera-->" + line);
+      if (line.startsWith("CMD"))
+      {
+        chimeraCommandRead(line.substring(4));
+      }
+      else if (line.startsWith("ModelChanged: "))
+      {
+        (new ModelUpdater()).start();
+      }
+      else if (line.startsWith("SelectionChanged: "))
+      {
+        (new SelectionUpdater()).start();
+      }
+      else if (line.startsWith("Trajectory residue network info:"))
+      {
+        (new NetworkUpdater(line)).start();
+      }
+    }
+    return;
+  }
+
+  private void chimeraCommandRead(String command) throws IOException
+  {
+    // Generally -- looking for:
+    // CMD command
+    // ........
+    // END
+    // We return the text in between
+    List<String> reply = new ArrayList<String>();
+    boolean updateModels = false;
+    boolean updateSelection = false;
+    boolean importNetwork = false;
+    String line = null;
+
+    synchronized (replyLog)
+    {
+      while ((line = lineReader.readLine()) != null)
+      {
+        // System.out.println("From Chimera (" + command + ") -->" + line);
+        if (line.startsWith("CMD"))
+        {
+          logger.warn("Got unexpected command from Chimera: " + line);
+
+        }
+        else if (line.startsWith("END"))
+        {
+          break;
+        }
+        if (line.startsWith("ModelChanged: "))
+        {
+          updateModels = true;
+        }
+        else if (line.startsWith("SelectionChanged: "))
+        {
+          updateSelection = true;
+        }
+        else if (line.length() == 0)
+        {
+          continue;
+        }
+        else if (!line.startsWith("CMD"))
+        {
+          reply.add(line);
+        }
+        else if (line.startsWith("Trajectory residue network info:"))
+        {
+          importNetwork = true;
+        }
+      }
+      replyLog.put(command, reply);
+    }
+    if (updateModels)
     {
       (new ModelUpdater()).start();
     }
-               if (updateSelection)
+    if (updateSelection)
     {
       (new SelectionUpdater()).start();
     }
-               if (importNetwork) {
-                       (new NetworkUpdater(line)).start();
-               }
-               return;
-       }
-
-       /**
-        * Model updater thread
-        */
-       class ModelUpdater extends Thread {
-
-               public ModelUpdater() {
-               }
-
-               public void run() {
-                       structureManager.updateModels();
-                       structureManager.modelChanged();
-               }
-       }
-
-       /**
-        * Selection updater thread
-        */
-       class SelectionUpdater extends Thread {
-
-               public SelectionUpdater() {
-               }
-
-               public void run() {
-                       try {
-                         logger.info("Responding to chimera selection");
-                         structureManager.chimeraSelectionChanged();
-                       } catch (Exception e) {
-                               logger.warn("Could not update selection", e);
-                       }
-               }
-       }
-
-       /**
-        * Selection updater thread
-        */
-       class NetworkUpdater extends Thread {
-
-               private String line;
-
-               public NetworkUpdater(String line) {
-                       this.line = line;
-               }
-
-               public void run() {
-                       try {
-//                             ((TaskManager<?, ?>) structureManager.getService(TaskManager.class))
-//                                             .execute(new ImportTrajectoryRINTaskFactory(structureManager, line)
-//                                                             .createTaskIterator());
-                       } catch (Exception e) {
-                               logger.warn("Could not import trajectory network", e);
-                       }
-               }
-       }
+    if (importNetwork)
+    {
+      (new NetworkUpdater(line)).start();
+    }
+    return;
+  }
+
+  /**
+   * Model updater thread
+   */
+  class ModelUpdater extends Thread
+  {
+
+    public ModelUpdater()
+    {
+    }
+
+    public void run()
+    {
+      structureManager.updateModels();
+      structureManager.modelChanged();
+    }
+  }
+
+  /**
+   * Selection updater thread
+   */
+  class SelectionUpdater extends Thread
+  {
+
+    public SelectionUpdater()
+    {
+    }
+
+    public void run()
+    {
+      try
+      {
+        logger.info("Responding to chimera selection");
+        structureManager.chimeraSelectionChanged();
+      } catch (Exception e)
+      {
+        logger.warn("Could not update selection", e);
+      }
+    }
+  }
+
+  /**
+   * Selection updater thread
+   */
+  class NetworkUpdater extends Thread
+  {
+
+    private String line;
+
+    public NetworkUpdater(String line)
+    {
+      this.line = line;
+    }
+
+    public void run()
+    {
+      try
+      {
+        // ((TaskManager<?, ?>) structureManager.getService(TaskManager.class))
+        // .execute(new ImportTrajectoryRINTaskFactory(structureManager, line)
+        // .createTaskIterator());
+      } catch (Exception e)
+      {
+        logger.warn("Could not import trajectory network", e);
+      }
+    }
+  }
+
+  /**
+   * Set a flag that this thread should clean up and exit.
+   */
+  public void requestStop()
+  {
+    this.stopMe = true;
+  }
 }
index ba7e520..2ef1c76 100755 (executable)
@@ -1155,7 +1155,8 @@ public class AlignSeq
           }
           if (sq.getAnnotation() != null && sq.getAnnotation().length > 0)
           {
-            annotations.addAll(inspos, Arrays.asList(sq.getAnnotation()));
+            annotations.addAll(inspos == -1 ? annotations.size() : inspos,
+                    Arrays.asList(sq.getAnnotation()));
           }
         }
       }
index 4d64685..66a6d78 100755 (executable)
@@ -71,6 +71,8 @@ public class Conservation
 
   int[][] cons2;
 
+  private String[] consSymbs;
+
   /**
    * Creates a new Conservation object.
    * 
@@ -366,17 +368,17 @@ public class Conservation
     {
       consString.append('-');
     }
-
+    consSymbs = new String[end-start+1];
     for (int i = start; i <= end; i++)
     {
       gapcons = countConsNGaps(i);
       totGaps = gapcons[1];
       pgaps = ((float) totGaps * 100) / (float) sequences.length;
-
+      consSymbs[i-start]=new String();
+      
       if (percentageGaps > pgaps)
       {
         resultHash = total[i - start];
-
         // Now find the verdict
         count = 0;
         enumeration = resultHash.keys();
@@ -385,12 +387,12 @@ public class Conservation
         {
           type = (String) enumeration.nextElement();
           result = (Integer) resultHash.get(type);
-
           // Do we want to count +ve conservation or +ve and -ve cons.?
           if (consflag)
           {
             if (result.intValue() == 1)
             {
+              consSymbs[i-start] = type+" "+consSymbs[i-start];
               count++;
             }
           }
@@ -398,6 +400,14 @@ public class Conservation
           {
             if (result.intValue() != -1)
             {
+              { 
+                 if (result.intValue()==0) {
+                   consSymbs[i-start] = consSymbs[i-start]+ " !"+type;
+                 } else {
+                   consSymbs[i-start] = type+" "+consSymbs[i-start];
+                 }
+              }
+              
               count++;
             }
           }
@@ -683,7 +693,7 @@ public class Conservation
       float vprop = value - min;
       vprop /= max;
       conservation.annotations[i] = new Annotation(String.valueOf(c),
-              String.valueOf(value), ' ', value, new Color(minR
+              consSymbs[i-start], ' ', value, new Color(minR
                       + (maxR * vprop), minG + (maxG * vprop), minB
                       + (maxB * vprop)));
 
index b253642..61e5709 100644 (file)
@@ -203,7 +203,7 @@ public class Finder
           {
             continue;
           }
-
+// if invalid string used, then regex has no matched to/from
           int sres = seq
                   .findPosition(resIndex
                           + Integer.parseInt(spaces.elementAt(resIndex)
index a8ca3f7..099400e 100644 (file)
  */
 package jalview.analysis;
 
-import java.util.*;
-
 import jalview.api.analysis.ScoreModelI;
-import jalview.datamodel.*;
-import jalview.io.*;
-import jalview.schemes.*;
-import jalview.util.*;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.BinaryNode;
+import jalview.datamodel.CigarArray;
+import jalview.datamodel.NodeTransformI;
+import jalview.datamodel.SeqCigar;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+import jalview.datamodel.SequenceNode;
+import jalview.io.NewickFile;
+import jalview.schemes.ResidueProperties;
+
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Vector;
 
 /**
  * DOCUMENT ME!
@@ -210,7 +218,7 @@ public class NJTree
    *          DOCUMENT ME!
    */
   public NJTree(SequenceI[] sequence, AlignmentView seqData, String type,
-          String pwtype, int start, int end)
+          String pwtype, ScoreModelI sm, int start, int end)
   {
     this.sequence = sequence;
     this.node = new Vector();
@@ -237,7 +245,7 @@ public class NJTree
       type = "AV";
     }
 
-    if (!(pwtype.equals("PID")))
+    if (sm == null && !(pwtype.equals("PID")))
     {
       if (ResidueProperties.getScoreMatrix(pwtype) == null)
       {
@@ -257,7 +265,7 @@ public class NJTree
 
     noseqs = i++;
 
-    distance = findDistances();
+    distance = findDistances(sm);
     // System.err.println("Made distances");// dbg
     makeLeaves();
     // System.err.println("Made leaves");// dbg
@@ -315,7 +323,7 @@ public class NJTree
 
           for (int j = 0; j < seqs.length; j++)
           {
-            seqs[j] = (SequenceI) list.get(j);
+            seqs[j] = list.get(j);
           }
 
           seqmatcher = new SequenceIdMatcher(seqs);
@@ -722,16 +730,18 @@ public class NJTree
    * 
    * @return similarity matrix used to compute tree
    */
-  public float[][] findDistances()
+  public float[][] findDistances(ScoreModelI _pwmatrix)
   {
 
     float[][] distance = new float[noseqs][noseqs];
-
-    // Pairwise substitution score (with no gap penalties)
-    ScoreModelI _pwmatrix = ResidueProperties.getScoreModel(pwtype);
     if (_pwmatrix == null)
     {
-      _pwmatrix = ResidueProperties.getScoreMatrix("BLOSUM62");
+      // Resolve substitution model
+      _pwmatrix = ResidueProperties.getScoreModel(pwtype);
+      if (_pwmatrix == null)
+      {
+        _pwmatrix = ResidueProperties.getScoreMatrix("BLOSUM62");
+      }
     }
     distance = _pwmatrix.findDistances(seqData);
     return distance;
@@ -857,12 +867,12 @@ public class NJTree
     {
       System.out
               .println("Leaf = " + ((SequenceI) node.element()).getName());
-      System.out.println("Dist " + ((SequenceNode) node).dist);
+      System.out.println("Dist " + node.dist);
       System.out.println("Boot " + node.getBootstrap());
     }
     else
     {
-      System.out.println("Dist " + ((SequenceNode) node).dist);
+      System.out.println("Dist " + node.dist);
       printNode((SequenceNode) node.left());
       printNode((SequenceNode) node.right());
     }
@@ -883,11 +893,11 @@ public class NJTree
 
     if ((node.left() == null) && (node.right() == null))
     {
-      float dist = ((SequenceNode) node).dist;
+      float dist = node.dist;
 
       if (dist > maxDistValue)
       {
-        maxdist = (SequenceNode) node;
+        maxdist = node;
         maxDistValue = dist;
       }
     }
@@ -1089,9 +1099,9 @@ public class NJTree
               + ((SequenceI) node.element()).getName());
     }
 
-    System.out.println(" dist = " + ((SequenceNode) node).dist + " "
-            + ((SequenceNode) node).count + " "
-            + ((SequenceNode) node).height);
+    System.out.println(" dist = " + node.dist + " "
+            + node.count + " "
+            + node.height);
   }
 
   /**
@@ -1137,13 +1147,13 @@ public class NJTree
       SequenceNode l = (SequenceNode) node.left();
       SequenceNode r = (SequenceNode) node.right();
 
-      ((SequenceNode) node).count = l.count + r.count;
-      ((SequenceNode) node).ycount = (l.ycount + r.ycount) / 2;
+      node.count = l.count + r.count;
+      node.ycount = (l.ycount + r.ycount) / 2;
     }
     else
     {
-      ((SequenceNode) node).count = 1;
-      ((SequenceNode) node).ycount = ycount++;
+      node.count = 1;
+      node.ycount = ycount++;
     }
     _lycount--;
   }
@@ -1282,7 +1292,9 @@ public class NJTree
   {
     for (Enumeration nodes = node.elements(); nodes.hasMoreElements(); nodeTransformI
             .transform((BinaryNode) nodes.nextElement()))
+    {
       ;
+    }
   }
 }
 
index aec7faf..bedce3f 100755 (executable)
@@ -42,10 +42,15 @@ public class SequenceIdMatcher
       // TODO: deal with ID collisions - SequenceI should be appended to list
       // associated with this key.
       names.put(new SeqIdName(seqs[i].getDisplayId(true)), seqs[i]);
+      SequenceI dbseq = seqs[i];
+      while (dbseq.getDatasetSequence()!=null)
+      {
+        dbseq = dbseq.getDatasetSequence();
+      }
       // add in any interesting identifiers
-      if (seqs[i].getDBRef() != null)
+      if (dbseq.getDBRef() != null)
       {
-        DBRefEntry dbr[] = seqs[i].getDBRef();
+        DBRefEntry dbr[] = dbseq.getDBRef();
         SeqIdName sid = null;
         for (int r = 0; r < dbr.length; r++)
         {
diff --git a/src/jalview/analysis/scoremodels/FeatureScoreModel.java b/src/jalview/analysis/scoremodels/FeatureScoreModel.java
new file mode 100644 (file)
index 0000000..e2a8b9a
--- /dev/null
@@ -0,0 +1,138 @@
+package jalview.analysis.scoremodels;
+
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.ViewBasedAnalysisI;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+import jalview.util.Comparison;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Hashtable;
+import java.util.List;
+
+public class FeatureScoreModel implements ScoreModelI, ViewBasedAnalysisI
+{
+  jalview.api.FeatureRenderer fr;
+
+  @Override
+  public boolean configureFromAlignmentView(
+          jalview.api.AlignmentViewPanel view)
+  {
+    fr = view.cloneFeatureRenderer();
+    return true;
+  }
+
+  @Override
+  public float[][] findDistances(AlignmentView seqData)
+  {
+    int nofeats = 0;
+    List<String> dft = Arrays.asList(fr.getDisplayedFeatureTypes());
+
+    if (dft != null)
+    {
+      nofeats = dft.size();
+    }
+
+    SequenceI[] sequenceString = seqData.getVisibleAlignment(
+            Comparison.GapChars.charAt(0)).getSequencesArray();
+    int noseqs = sequenceString.length;
+    int cpwidth = seqData.getWidth();
+    float[][] distance = new float[noseqs][noseqs];
+    if (nofeats == 0)
+    {
+      for (float[] d : distance)
+      {
+        for (int i = 0; i < d.length; d[i++] = 0f)
+        {
+          ;
+        }
+      }
+      return distance;
+    }
+    float max = 0;
+    for (int cpos = 0; cpos < cpwidth; cpos++)
+    {
+      // get visible features at cpos under view's display settings and compare
+      // them
+      List<Hashtable<String, SequenceFeature>> sfap = new ArrayList<Hashtable<String, SequenceFeature>>();
+      for (int i = 0; i < noseqs; i++)
+      {
+        Hashtable<String, SequenceFeature> types = new Hashtable<String, SequenceFeature>();
+        List<SequenceFeature> sfs = fr.findFeaturesAtRes(sequenceString[i],
+                sequenceString[i].findPosition(cpos));
+        for (SequenceFeature sf : sfs)
+        {
+          types.put(sf.getType(), sf);
+        }
+        sfap.add(types);
+      }
+      for (int i = 0; i < (noseqs - 1); i++)
+      {
+        if (cpos == 0)
+        {
+          distance[i][i] = 0f;
+        }
+        for (int j = i + 1; j < noseqs; j++)
+        {
+          int sfcommon = 0;
+          // compare the two lists of features...
+          Hashtable<String, SequenceFeature> fi = sfap.get(i), fk, fj = sfap
+                  .get(j);
+          if (fi.size() > fj.size())
+          {
+            fk = fj;
+          }
+          else
+          {
+            fk = fi;
+            fi = fj;
+          }
+          for (String k : fi.keySet())
+          {
+            SequenceFeature sfj = fk.get(k);
+            if (sfj != null)
+            {
+              sfcommon++;
+            }
+          }
+          distance[i][j] += (fi.size() + fk.size() - 2f * sfcommon);
+          distance[j][i] += distance[i][j];
+        }
+      }
+    }
+    for (int i = 0; i < noseqs; i++)
+    {
+      for (int j = i + 1; j < noseqs; j++)
+      {
+        distance[i][j] /= cpwidth;
+        distance[j][i] = distance[i][j];
+      }
+    }
+    return distance;
+  }
+
+  @Override
+  public String getName()
+  {
+    return "Sequence Feature Similarity";
+  }
+
+  @Override
+  public boolean isDNA()
+  {
+    return true;
+  }
+
+  @Override
+  public boolean isProtein()
+  {
+    return true;
+  }
+
+  public String toString()
+  {
+    return "Score between sequences based on hamming distance between binary vectors marking features displayed at each column";
+  }
+}
index b9638e5..4010e8b 100644 (file)
@@ -20,6 +20,8 @@
  */
 package jalview.api;
 
+import jalview.commands.CommandI;
+
 /**
  * Interface implemented by gui implementations managing a Jalview Alignment
  * View
@@ -37,4 +39,6 @@ public interface AlignViewControllerGuiI
    */
   void setStatus(String string);
 
+  void addHistoryItem(CommandI command);
+
 }
index 35f084f..9bd3f45 100644 (file)
@@ -64,4 +64,16 @@ public interface AlignViewControllerI
   boolean markColumnsContainingFeatures(boolean invert,
           boolean extendCurrent, boolean clearColumns, String featureType);
 
+  /**
+   * sort the alignment or current selection by average score over the given set of features
+   * @param typ list of feature names or null to use currently displayed features
+   */
+  void sortAlignmentByFeatureScore(String[] typ);
+
+  /**
+   * sort the alignment or current selection by distribution of the given set of features
+   * @param typ list of feature names or null to use currently displayed features
+   */
+  void sortAlignmentByFeatureDensity(String[] typ);
+
 }
index d8ba30d..2dea15e 100644 (file)
  */
 package jalview.api;
 
-import java.awt.Color;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-
 import jalview.analysis.Conservation;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
@@ -36,6 +31,11 @@ import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.schemes.ColourSchemeI;
 
+import java.awt.Color;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
 /**
  * @author jimp
  * 
@@ -163,6 +163,29 @@ public interface AlignViewportI
    */
   void updateGroupAnnotationSettings(boolean applyGlobalSettings,
           boolean preserveNewGroupSettings);
+  
+  /**
+   * @return true if a reference sequence is set and should be displayed
+   */
+  public boolean isDisplayReferenceSeq();
+
+  /**
+   * @return set the flag for displaying reference sequences when they are
+   *         available
+   */
+  public void setDisplayReferenceSeq(boolean displayReferenceSeq);
+
+  /**
+   * @return true if colourschemes should render according to reference sequence
+   *         rather than consensus if available
+   */
+  public boolean isColourByReferenceSeq();
+
+  /**
+   * @return true set flag for deciding if colourschemes should render according
+   *         to reference sequence rather than consensus if available
+   */
+  public void setColourByReferenceSeq(boolean colourByReferenceSeq);
 
   void setSequenceColour(SequenceI seq, Color col);
 
@@ -172,16 +195,58 @@ public interface AlignViewportI
 
   SequenceGroup getSelectionGroup();
 
+  /**
+   * get the currently selected sequence objects or all the sequences in the
+   * alignment. TODO: change to List<>
+   * 
+   * @return array of references to sequence objects
+   */
   SequenceI[] getSequenceSelection();
 
   void clearSequenceColours();
 
+  /**
+   * This method returns the visible alignment as text, as seen on the GUI, ie
+   * if columns are hidden they will not be returned in the result. Use this for
+   * calculating trees, PCA, redundancy etc on views which contain hidden
+   * columns.
+   * 
+   * @return String[]
+   */
   CigarArray getViewAsCigars(boolean selectedRegionOnly);
 
+  /**
+   * return a compact representation of the current alignment selection to pass
+   * to an analysis function
+   * 
+   * @param selectedOnly
+   *          boolean true to just return the selected view
+   * @return AlignmentView
+   */
   AlignmentView getAlignmentView(boolean selectedOnly);
 
+  /**
+   * return a compact representation of the current alignment selection to pass
+   * to an analysis function
+   * 
+   * @param selectedOnly
+   *          boolean true to just return the selected view
+   * @param markGroups
+   *          boolean true to annotate the alignment view with groups on the
+   *          alignment (and intersecting with selected region if selectedOnly
+   *          is true)
+   * @return AlignmentView
+   */
   AlignmentView getAlignmentView(boolean selectedOnly, boolean markGroups);
 
+  /**
+   * This method returns the visible alignment as text, as seen on the GUI, ie
+   * if columns are hidden they will not be returned in the result. Use this for
+   * calculating trees, PCA, redundancy etc on views which contain hidden
+   * columns.
+   * 
+   * @return String[]
+   */
   String[] getViewAsString(boolean selectedRegionOnly);
 
   void setSelectionGroup(SequenceGroup sg);
@@ -200,4 +265,93 @@ public interface AlignViewportI
   List<AlignmentAnnotation> getVisibleAlignmentAnnotation(
           boolean selectedOnly);
 
+  FeaturesDisplayedI getFeaturesDisplayed();
+
+  String getSequenceSetId();
+
+  boolean isShowSequenceFeatures();
+
+  void setShowSequenceFeatures(boolean b);
+
+  /**
+   * 
+   * @param flag
+   *          indicating if annotation panel shown below alignment
+   * 
+   */
+  void setShowAnnotation(boolean b);
+
+  /**
+   * flag indicating if annotation panel shown below alignment
+   * 
+   * @return
+   */
+  boolean isShowAnnotation();
+
+  boolean isRightAlignIds();
+
+  void setRightAlignIds(boolean rightAlignIds);
+
+  boolean areFeaturesDisplayed();
+
+  void setShowSequenceFeaturesHeight(boolean selected);
+
+  boolean isShowSequenceFeaturesHeight();
+
+  void setFeaturesDisplayed(FeaturesDisplayedI featuresDisplayedI);
+
+  void alignmentChanged(AlignmentViewPanel ap);
+
+  /**
+   * @return the padGaps
+   */
+  boolean isPadGaps();
+
+  /**
+   * @param padGaps
+   *          the padGaps to set
+   */
+  void setPadGaps(boolean padGaps);
+
+  /**
+   * return visible region boundaries within given column range
+   * 
+   * @param min
+   *          first column (inclusive, from 0)
+   * @param max
+   *          last column (exclusive)
+   * @return int[][] range of {start,end} visible positions TODO: change to list
+   *         of int ranges
+   */
+  int[][] getVisibleRegionBoundaries(int min, int max);
+
+  /**
+   * This method returns an array of new SequenceI objects derived from the
+   * whole alignment or just the current selection with start and end points
+   * adjusted
+   * 
+   * @note if you need references to the actual SequenceI objects in the
+   *       alignment or currently selected then use getSequenceSelection()
+   * @return selection as new sequenceI objects
+   */
+  SequenceI[] getSelectionAsNewSequence();
+
+  void invertColumnSelection();
+
+  /**
+   * broadcast selection to any interested parties
+   */
+  void sendSelection();
+
+  /**
+   * calculate the row position for alignmentIndex if all hidden sequences were
+   * shown
+   * 
+   * @param alignmentIndex
+   * @return adjusted row position
+   */
+  int adjustForHiddenSeqs(int alignmentIndex);
+
+  boolean hasHiddenRows();
+
 }
index 0955cb4..a68c1f6 100644 (file)
@@ -51,6 +51,10 @@ public interface AlignmentViewPanel extends OOMHandlerI
    */
   void adjustAnnotationHeight();
 
+  FeatureRenderer getFeatureRenderer();
+
+  FeatureRenderer cloneFeatureRenderer();
+  
   /**
    * 
    * @return displayed name for the view
index 679cded..4fd89c1 100644 (file)
  */
 package jalview.api;
 
+import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 
 import java.awt.Color;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
 
 /**
  * Abstract feature renderer interface
@@ -37,4 +41,38 @@ public interface FeatureRenderer
 
   void featuresAdded();
 
+  Object getFeatureStyle(String ft);
+
+  void setColour(String ft, Object ggc);
+
+  AlignViewportI getViewport();
+
+  FeaturesDisplayedI getFeaturesDisplayed();
+
+  Map<String,Object> getFeatureColours();
+
+  void findAllFeatures(boolean newMadeVisible);
+
+  Map<String,Object> getDisplayedFeatureCols();
+
+  List<String> getFeatureGroups();
+
+  List<String> getGroups(boolean visible);
+
+  void setGroupVisibility(List<String> toset, boolean visible);
+
+  void setGroupVisibility(String group, boolean visible);
+
+  List<SequenceFeature> findFeaturesAtRes(SequenceI sequence, int res);
+
+  boolean isTransparencyAvailable();
+
+  String[] getDisplayedFeatureTypes();
+
+  String[] getDisplayedFeatureGroups();
+
+  void setAllVisible(List<String> featureTypes);
+
+  void setVisible(String featureType);
+
 }
diff --git a/src/jalview/api/FeatureSettingsControllerI.java b/src/jalview/api/FeatureSettingsControllerI.java
new file mode 100644 (file)
index 0000000..c718e36
--- /dev/null
@@ -0,0 +1,6 @@
+package jalview.api;
+
+public interface FeatureSettingsControllerI
+{
+  
+}
diff --git a/src/jalview/api/FeatureSettingsModelI.java b/src/jalview/api/FeatureSettingsModelI.java
new file mode 100644 (file)
index 0000000..c148f3f
--- /dev/null
@@ -0,0 +1,6 @@
+package jalview.api;
+
+public interface FeatureSettingsModelI
+{
+
+}
diff --git a/src/jalview/api/FeaturesDisplayedI.java b/src/jalview/api/FeaturesDisplayedI.java
new file mode 100644 (file)
index 0000000..9ed5ae5
--- /dev/null
@@ -0,0 +1,29 @@
+package jalview.api;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+public interface FeaturesDisplayedI
+{
+
+  Iterator<String> getVisibleFeatures();
+
+  boolean isVisible(String featureType);
+
+  boolean areVisible(Collection<String> featureTypes);
+
+  void clear();
+
+  void setVisible(String featureType);
+
+  void setAllVisible(Collection<String> featureTypes);
+
+  boolean isRegistered(String type);
+
+  void setAllRegisteredVisible();
+
+  int getVisibleFeatureCount();
+
+  int getRegisterdFeaturesCount();
+
+}
index fbd5e4a..0ca1758 100644 (file)
@@ -29,4 +29,6 @@ public interface SequenceRenderer
 
   Color getResidueBoxColour(SequenceI sequenceI, int r);
 
+  Color getResidueColour(SequenceI seq, int position, FeatureRenderer fr);
+
 }
index 32c5bca..d620401 100644 (file)
@@ -32,7 +32,7 @@ public interface SequenceStructureBinding
   /**
    * 
    * @return true if Jalview or the Viewer is still restoring state or loading
-   *         is still going on (see setFinsihedLoadingFromArchive)
+   *         is still going on (see setFinishedLoadingFromArchive)
    */
   void setLoadingFromArchive(boolean loadingFromArchive);
 
diff --git a/src/jalview/api/analysis/ViewBasedAnalysisI.java b/src/jalview/api/analysis/ViewBasedAnalysisI.java
new file mode 100644 (file)
index 0000000..3404afc
--- /dev/null
@@ -0,0 +1,17 @@
+package jalview.api.analysis;
+
+import jalview.api.AlignViewportI;
+import jalview.api.AlignmentViewPanel;
+
+public interface ViewBasedAnalysisI
+{
+
+  /**
+   * Parameterise the analysis model using the current view
+   * @param view
+   * @return true if model is applicable and calculation should proceed
+   */
+
+  boolean configureFromAlignmentView(AlignmentViewPanel view);
+
+}
index efb60dd..45b074b 100644 (file)
  */
 package jalview.api.structures;
 
-import jalview.api.FeatureRenderer;
-import jalview.api.SequenceRenderer;
-import jalview.api.SequenceStructureBinding;
-import jalview.datamodel.AlignmentI;
-import jalview.datamodel.SequenceI;
-import jalview.ext.jmol.JalviewJmolBinding;
 import jalview.schemes.ColourSchemeI;
-import jalview.schemes.UserColourScheme;
-import jalview.structure.StructureMappingcommandSet;
-import jalview.structure.StructureSelectionManager;
+import jalview.structures.models.AAStructureBindingModel;
 
 public interface JalviewStructureDisplayI
 {
 
-  SequenceStructureBinding getBinding();
+  AAStructureBindingModel getBinding();
 
   /**
    * @return true if there is an active GUI handling a structure display
index 98a4a1c..c58fc5b 100644 (file)
@@ -152,6 +152,8 @@ public class APopupMenu extends java.awt.PopupMenu implements
   MenuItem selSeqDetails = new MenuItem(
           MessageManager.getString("label.sequence_details") + "...");
 
+  MenuItem makeReferenceSeq = new MenuItem();
+  
   Sequence seq;
 
   MenuItem revealAll = new MenuItem();
@@ -347,6 +349,17 @@ public class APopupMenu extends java.awt.PopupMenu implements
     if (seq != null)
     {
       seqMenu.setLabel(seq.getName());
+      if (seq == ap.av.getAlignment().getSeqrep())
+      {
+        makeReferenceSeq.setLabel(MessageManager
+                .getString("action.unmark_as_reference"));// Unmark
+                                                          // representative");
+      }
+      else
+      {
+        makeReferenceSeq.setLabel(MessageManager
+                .getString("action.set_as_reference")); // );
+      }
       repGroup.setLabel(MessageManager.formatMessage(
               "label.represent_group_with", new String[]
               { seq.getName() }));
@@ -515,6 +528,10 @@ public class APopupMenu extends java.awt.PopupMenu implements
     {
       editName();
     }
+    else if (source == makeReferenceSeq)
+    {
+      makeReferenceSeq_actionPerformed();
+    }
     else if (source == sequenceDetails)
     {
       showSequenceDetails();
@@ -673,7 +690,7 @@ public class APopupMenu extends java.awt.PopupMenu implements
               features, true, ap))
       {
         ap.alignFrame.sequenceFeatures.setState(true);
-        ap.av.showSequenceFeatures(true);
+        ap.av.setShowSequenceFeatures(true);;
         ap.highlightSearchResults(null);
       }
     }
@@ -732,7 +749,7 @@ public class APopupMenu extends java.awt.PopupMenu implements
                       true,
                       true,
                       false,
-                      (ap.seqPanel.seqCanvas.fr != null) ? ap.seqPanel.seqCanvas.fr.minmax
+                      (ap.seqPanel.seqCanvas.fr != null) ? ap.seqPanel.seqCanvas.fr.getMinMax()
                               : null);
       contents.append("</p>");
     }
@@ -909,6 +926,7 @@ public class APopupMenu extends java.awt.PopupMenu implements
     toLower.addActionListener(this);
     editMenu.add(toggleCase);
     seqMenu.add(sequenceName);
+    seqMenu.add(makeReferenceSeq);
     // seqMenu.add(sequenceDetails);
 
     if (!ap.av.applet.useXtrnalSviewer)
@@ -928,6 +946,7 @@ public class APopupMenu extends java.awt.PopupMenu implements
     repGroup.addActionListener(this);
     revealAll.addActionListener(this);
     revealSeq.addActionListener(this);
+    makeReferenceSeq.addActionListener(this);
   }
 
   void refresh()
@@ -1117,6 +1136,28 @@ public class APopupMenu extends java.awt.PopupMenu implements
     getGroup().setDisplayText(showText.getState());
     refresh();
   }
+  public void makeReferenceSeq_actionPerformed()
+  {
+    if (!ap.av.getAlignment().hasSeqrep())
+    {
+      // initialise the display flags so the user sees something happen
+      ap.av.setDisplayReferenceSeq(true);
+      ap.av.setColourByReferenceSeq(true);
+      ap.av.getAlignment().setSeqrep(seq);
+    }
+    else
+    {
+      if (ap.av.getAlignment().getSeqrep() == seq)
+      {
+        ap.av.getAlignment().setSeqrep(null);
+      }
+      else
+      {
+        ap.av.getAlignment().setSeqrep(seq);
+      }
+    }
+    refresh();
+  }
 
   public void showNonconserved_itemStateChanged()
   {
index ed64215..1f32da0 100644 (file)
@@ -23,6 +23,7 @@ package jalview.appletgui;
 import jalview.analysis.AlignmentSorter;
 import jalview.api.AlignViewControllerGuiI;
 import jalview.api.AlignViewControllerI;
+import jalview.api.FeatureRenderer;
 import jalview.api.SequenceStructureBinding;
 import jalview.bin.JalviewLite;
 import jalview.commands.CommandI;
@@ -62,6 +63,7 @@ import jalview.schemes.TaylorColourScheme;
 import jalview.schemes.TurnColourScheme;
 import jalview.schemes.ZappoColourScheme;
 import jalview.structure.StructureSelectionManager;
+import jalview.structures.models.AAStructureBindingModel;
 import jalview.util.MessageManager;
 
 import java.awt.BorderLayout;
@@ -89,9 +91,10 @@ import java.awt.event.WindowEvent;
 import java.io.IOException;
 import java.net.URL;
 import java.net.URLEncoder;
-import java.util.Enumeration;
+import java.util.Arrays;
 import java.util.Hashtable;
 import java.util.List;
+import java.util.Map;
 import java.util.StringTokenizer;
 import java.util.Vector;
 
@@ -285,7 +288,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     {
       featuresFile = new jalview.io.FeaturesFile(file, type)
               .parse(viewport.getAlignment(), alignPanel.seqPanel.seqCanvas
-                      .getFeatureRenderer().featureColours, featureLinks,
+                      .getFeatureRenderer().getFeatureColours(), featureLinks,
                       true, viewport.applet.getDefaultParameter(
                               "relaxedidmatch", false));
     } catch (Exception ex)
@@ -301,9 +304,14 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
       }
       if (autoenabledisplay)
       {
-        viewport.showSequenceFeatures = true;
+        viewport.setShowSequenceFeatures(true);
         sequenceFeatures.setState(true);
       }
+      if (alignPanel.seqPanel.seqCanvas.fr != null)
+      {
+        // update the min/max ranges where necessary
+        alignPanel.seqPanel.seqCanvas.fr.findAllFeatures(true);
+      }
       if (viewport.featureSettings != null)
       {
         viewport.featureSettings.refreshTable();
@@ -729,7 +737,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     }
     else if (evt.getSource() == sequenceFeatures)
     {
-      viewport.showSequenceFeatures(sequenceFeatures.getState());
+      viewport.setShowSequenceFeatures(sequenceFeatures.getState());
       alignPanel.seqPanel.seqCanvas.repaint();
     }
     else if (evt.getSource() == conservationMenuItem)
@@ -1099,6 +1107,10 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     {
       new AnnotationColourChooser(viewport, alignPanel);
     }
+    else if (source == annotationColumnSelection)
+    {
+      new AnnotationColumnChooser(viewport, alignPanel);
+    }
     else if (source == sortPairwiseMenuItem)
     {
       sortPairwiseMenuItem_actionPerformed();
@@ -1191,11 +1203,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
 
   public String outputAnnotations(boolean displayTextbox)
   {
-    String annotation = new AnnotationFile().printAnnotations(
-            viewport.showAnnotation ? viewport.getAlignment()
-                    .getAlignmentAnnotation() : null, viewport
-                    .getAlignment().getGroups(), ((Alignment) viewport
-                    .getAlignment()).alignmentProperties);
+    String annotation = new AnnotationFile()
+            .printAnnotationsForView(viewport);
 
     if (displayTextbox)
     {
@@ -1210,20 +1219,13 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     return annotation;
   }
 
-  private Hashtable getDisplayedFeatureCols()
+  private Map<String,Object> getDisplayedFeatureCols()
   {
     if (alignPanel.getFeatureRenderer() != null
-            && viewport.featuresDisplayed != null)
+            && viewport.getFeaturesDisplayed()!= null)
     {
-      FeatureRenderer fr = alignPanel.getFeatureRenderer();
-      Hashtable fcols = new Hashtable();
-      Enumeration en = viewport.featuresDisplayed.keys();
-      while (en.hasMoreElements())
-      {
-        Object col = en.nextElement();
-        fcols.put(col, fr.featureColours.get(col));
-      }
-      return fcols;
+      return alignPanel.getFeatureRenderer().getDisplayedFeatureCols();
+      
     }
     return null;
   }
@@ -1429,6 +1431,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
   /**
    * TODO: JAL-1104
    */
+  @Override
   public void addHistoryItem(CommandI command)
   {
     if (command.getSize() > 0)
@@ -1687,11 +1690,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     {
       copiedHiddenColumns = new Vector();
       int hiddenOffset = viewport.getSelectionGroup().getStartRes();
-      for (int i = 0; i < viewport.getColumnSelection().getHiddenColumns()
-              .size(); i++)
+      for (int[] region : viewport.getColumnSelection().getHiddenColumns())
       {
-        int[] region = (int[]) viewport.getColumnSelection()
-                .getHiddenColumns().elementAt(i);
 
         copiedHiddenColumns.addElement(new int[]
         { region[0] - hiddenOffset, region[1] - hiddenOffset });
@@ -2271,7 +2271,14 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     if (alignPanel != null
             && (fr = alignPanel.getFeatureRenderer()) != null)
     {
-      return fr.getGroups();
+      List gps = fr.getFeatureGroups();
+      int p=0;
+      String[] _gps = new String[gps.size()];
+      for (Object gp:gps)
+      {
+        _gps[p++] = gp.toString();
+      }
+      return _gps;
     }
     return null;
   }
@@ -2289,7 +2296,14 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     if (alignPanel != null
             && (fr = alignPanel.getFeatureRenderer()) != null)
     {
-      return fr.getGroups(visible);
+      List gps = fr.getGroups(visible);
+      int p=0;
+      String[] _gps = new String[gps.size()];
+      for (Object gp:gps)
+      {
+        _gps[p++] = gp.toString();
+      }
+      return _gps;
     }
     return null;
   }
@@ -2306,11 +2320,12 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
   {
     FeatureRenderer fr = null;
     this.sequenceFeatures.setState(true);
-    viewport.showSequenceFeatures(true);
+    viewport.setShowSequenceFeatures(true);
     if (alignPanel != null
             && (fr = alignPanel.getFeatureRenderer()) != null)
     {
-      fr.setGroupState(groups, state);
+      
+      fr.setGroupVisibility(Arrays.asList(groups), state);
       alignPanel.seqPanel.seqCanvas.repaint();
       if (alignPanel.overviewPanel != null)
       {
@@ -3257,6 +3272,10 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     annotationColour.setLabel(MessageManager
             .getString("action.by_annotation"));
     annotationColour.addActionListener(this);
+
+    annotationColumnSelection.setLabel("Select by Annotation");
+    annotationColumnSelection.addActionListener(this);
+
     invertSequenceMenuItem.setLabel(MessageManager
             .getString("action.invert_sequence_selection"));
     invertColSel.setLabel(MessageManager
@@ -3445,6 +3464,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
     selectMenu.add(unGroup);
     selectMenu.add(grpsFromSelection);
     selectMenu.add(deleteGroups);
+    selectMenu.add(annotationColumnSelection);
 
   }
 
@@ -3459,6 +3479,8 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
 
   MenuItem annotationColour = new MenuItem();
 
+  MenuItem annotationColumnSelection = new MenuItem();
+
   MenuItem invertColSel = new MenuItem();
 
   Menu menu1 = new Menu();
@@ -3615,7 +3637,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
         return null;
       }
     }
-    ExtJmol jmv = null;
+    AAStructureBindingModel jmv = null;
     // TODO: search for a jmv that involves viewer
     if (jmv == null)
     { // create a new viewer/jalview binding.
index 7400122..84a62ae 100644 (file)
  */
 package jalview.appletgui;
 
-import java.util.*;
-
-import java.awt.*;
-
-import jalview.analysis.*;
+import jalview.analysis.NJTree;
 import jalview.api.AlignViewportI;
-import jalview.bin.*;
-import jalview.datamodel.*;
-import jalview.schemes.*;
+import jalview.bin.JalviewLite;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.schemes.ColourSchemeProperty;
+import jalview.schemes.UserColourScheme;
 import jalview.structure.SelectionSource;
 import jalview.structure.VamsasSource;
 import jalview.viewmodel.AlignmentViewport;
 
+import java.awt.Font;
+import java.util.Stack;
+
 public class AlignViewport extends AlignmentViewport implements
         AlignViewportI, SelectionSource, VamsasSource
 {
@@ -58,8 +62,6 @@ public class AlignViewport extends AlignmentViewport implements
 
   boolean renderGaps = true;
 
-  boolean showSequenceFeatures = false;
-
   boolean showAnnotation = true;
 
   boolean upperCasebold = false;
@@ -86,10 +88,6 @@ public class AlignViewport extends AlignmentViewport implements
 
   boolean scaleRightWrapped = true;
 
-  // The following vector holds the features which are
-  // currently visible, in the correct order or rendering
-  public Hashtable featuresDisplayed;
-
   boolean showHiddenMarkers = true;
 
   public jalview.bin.JalviewLite applet;
@@ -100,6 +98,8 @@ public class AlignViewport extends AlignmentViewport implements
 
   Stack redoList = new Stack();
 
+  private AnnotationColumnChooser annotationColumnSelectionState;
+
   public void finalize()
   {
     applet = null;
@@ -256,16 +256,6 @@ public class AlignViewport extends AlignmentViewport implements
 
   }
 
-  public void showSequenceFeatures(boolean b)
-  {
-    showSequenceFeatures = b;
-  }
-
-  public boolean getShowSequenceFeatures()
-  {
-    return showSequenceFeatures;
-  }
-
   /**
    * get the consensus sequence as displayed under the PID consensus annotation
    * row.
@@ -639,12 +629,6 @@ public class AlignViewport extends AlignmentViewport implements
     }
   }
 
-  @Override
-  public boolean hasHiddenColumns()
-  {
-    return hasHiddenColumns;
-  }
-
   public boolean isNormaliseSequenceLogo()
   {
     return normaliseSequenceLogo;
@@ -664,4 +648,15 @@ public class AlignViewport extends AlignmentViewport implements
     return validCharWidth;
   }
 
+  public AnnotationColumnChooser getAnnotationColumnSelectionState()
+  {
+    return annotationColumnSelectionState;
+  }
+
+  public void setAnnotationColumnSelectionState(
+          AnnotationColumnChooser annotationColumnSelectionState)
+  {
+    this.annotationColumnSelectionState = annotationColumnSelectionState;
+  }
+
 }
index a90425f..5308a42 100644 (file)
@@ -157,12 +157,18 @@ public class AlignmentPanel extends Panel implements AdjustmentListener,
   {
     return seqPanel.seqCanvas.sr;
   }
-
-  public FeatureRenderer getFeatureRenderer()
+  @Override
+  public jalview.api.FeatureRenderer getFeatureRenderer()
   {
     return seqPanel.seqCanvas.fr;
   }
-
+  @Override
+  public jalview.api.FeatureRenderer cloneFeatureRenderer()
+  {
+    FeatureRenderer nfr = new FeatureRenderer(av);
+    nfr.transferSettings(seqPanel.seqCanvas.fr);
+    return nfr;
+  }
   public void alignmentChanged()
   {
     av.alignmentChanged(this);
index c7b7c6c..cc2e530 100644 (file)
  */
 package jalview.appletgui;
 
-import java.util.*;
-
-import java.awt.*;
-import java.awt.event.*;
-
-import jalview.datamodel.*;
-import jalview.schemes.*;
+import jalview.datamodel.SequenceGroup;
+import jalview.schemes.AnnotationColourGradient;
+import jalview.schemes.ColourSchemeI;
 import jalview.util.MessageManager;
 
+import java.awt.BorderLayout;
+import java.awt.Button;
+import java.awt.Checkbox;
+import java.awt.Choice;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.Panel;
+import java.awt.Scrollbar;
+import java.awt.TextField;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.AdjustmentListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.util.Hashtable;
+import java.util.Vector;
+
 public class AnnotationColourChooser extends Panel implements
         ActionListener, AdjustmentListener, ItemListener, MouseListener
 {
@@ -104,9 +123,13 @@ public class AnnotationColourChooser extends Panel implements
     {
       String label = av.getAlignment().getAlignmentAnnotation()[i].label;
       if (!list.contains(label))
+      {
         list.addElement(label);
+      }
       else
+      {
         list.addElement(label + "_" + (index++));
+      }
     }
 
     for (int i = 0; i < list.size(); i++)
@@ -137,7 +160,9 @@ public class AnnotationColourChooser extends Panel implements
         threshold.select(1);
         break;
       default:
-        throw new Error(MessageManager.getString("error.implementation_error_dont_know_thereshold_annotationcolourgradient"));
+        throw new Error(
+                MessageManager
+                        .getString("error.implementation_error_dont_know_thereshold_annotationcolourgradient"));
       }
       thresholdIsMin.setState(acg.thresholdIsMinMax);
       thresholdValue.setText("" + acg.getAnnotationThreshold());
@@ -348,14 +373,14 @@ public class AnnotationColourChooser extends Panel implements
   {
     if (!adjusting)
     {
-      thresholdValue.setText(((float) slider.getValue() / 1000f) + "");
+      thresholdValue.setText((slider.getValue() / 1000f) + "");
       if (currentColours.getState()
               && !(av.getGlobalColourScheme() instanceof AnnotationColourGradient))
       {
         changeColour();
       }
 
-      currentAnnotation.threshold.value = (float) slider.getValue() / 1000f;
+      currentAnnotation.threshold.value = slider.getValue() / 1000f;
       ap.paintAlignment(false);
     }
   }
diff --git a/src/jalview/appletgui/AnnotationColumnChooser.java b/src/jalview/appletgui/AnnotationColumnChooser.java
new file mode 100644 (file)
index 0000000..e25ff8f
--- /dev/null
@@ -0,0 +1,927 @@
+package jalview.appletgui;
+
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.ColumnSelection;
+import jalview.gui.JvSwingUtils;
+import jalview.schemes.AnnotationColourGradient;
+import jalview.util.MessageManager;
+import jalview.viewmodel.annotationfilter.AnnotationFilterParameter;
+
+import java.awt.BorderLayout;
+import java.awt.CardLayout;
+import java.awt.Checkbox;
+import java.awt.Choice;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.Panel;
+import java.awt.TextField;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.AdjustmentListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.TextEvent;
+import java.awt.event.TextListener;
+import java.util.Iterator;
+import java.util.Vector;
+
+import javax.swing.JPanel;
+
+//import net.miginfocom.swing.MigLayout;
+
+public class AnnotationColumnChooser extends AnnotationRowFilter implements
+        ActionListener, AdjustmentListener, ItemListener, MouseListener
+{
+
+  private Choice annotations = new Choice();
+
+  private Panel actionPanel = new Panel();
+
+  private TitledPanel thresholdPanel = new TitledPanel();
+
+  private Panel switchableViewsPanel = new Panel(new CardLayout());
+
+  private CardLayout switchableViewsLayout = (CardLayout) (switchableViewsPanel
+          .getLayout());
+
+  private Panel noGraphFilterView = new Panel();
+
+  private Panel graphFilterView = new Panel();
+
+  private Panel annotationComboBoxPanel = new Panel();
+
+  private BorderLayout borderLayout1 = new BorderLayout();
+
+  private BorderLayout gBorderLayout = new BorderLayout();
+
+  private BorderLayout ngBorderLayout = new BorderLayout();
+
+  private Choice threshold = new Choice();
+
+  private StructureFilterPanel gStructureFilterPanel;
+
+  private StructureFilterPanel ngStructureFilterPanel;
+
+  private StructureFilterPanel currentStructureFilterPanel;
+
+  private SearchPanel currentSearchPanel;
+
+  private SearchPanel gSearchPanel;
+
+  private SearchPanel ngSearchPanel;
+
+  private FurtherActionPanel currentFurtherActionPanel;
+
+  private FurtherActionPanel gFurtherActionPanel;
+
+  private FurtherActionPanel ngFurtherActionPanel;
+
+  public static final int ACTION_OPTION_SELECT = 1;
+
+  public static int ACTION_OPTION_HIDE = 2;
+
+  public static String NO_GRAPH_VIEW = "0";
+
+  public static String GRAPH_VIEW = "1";
+
+  private int actionOption = ACTION_OPTION_SELECT;
+
+  private ColumnSelection oldColumnSelection;
+
+  public AnnotationColumnChooser()
+  {
+    try
+    {
+      jbInit();
+    } catch (Exception ex)
+    {
+      ex.printStackTrace();
+    }
+  }
+
+  public AnnotationColumnChooser(AlignViewport av, final AlignmentPanel ap)
+  {
+    super(av, ap);
+    frame = new Frame();
+    frame.add(this);
+    jalview.bin.JalviewLite.addFrame(frame,
+            MessageManager.getString("label.select_by_annotation"), 520,
+            215);
+
+    slider.addAdjustmentListener(this);
+    slider.addMouseListener(this);
+
+    if (av.getAlignment().getAlignmentAnnotation() == null)
+    {
+      return;
+    }
+    setOldColumnSelection(av.getColumnSelection());
+    adjusting = true;
+    Vector list = new Vector();
+    int index = 1;
+    for (int i = 0; i < av.getAlignment().getAlignmentAnnotation().length; i++)
+    {
+      String label = av.getAlignment().getAlignmentAnnotation()[i].label;
+      if (!list.contains(label))
+      {
+        list.addElement(label);
+      }
+      else
+      {
+        list.addElement(label + "_" + (index++));
+      }
+    }
+
+    for (int i = 0; i < list.size(); i++)
+    {
+      annotations.addItem(list.elementAt(i).toString());
+    }
+
+    populateThresholdComboBox(threshold);
+
+    // restore Object state from the previous session if one exists
+    if (av.getAnnotationColumnSelectionState() != null)
+    {
+      currentSearchPanel = av.getAnnotationColumnSelectionState()
+              .getCurrentSearchPanel();
+      currentStructureFilterPanel = av.getAnnotationColumnSelectionState()
+              .getCurrentStructureFilterPanel();
+      annotations.select(av.getAnnotationColumnSelectionState()
+              .getAnnotations().getSelectedIndex());
+      threshold.select(av.getAnnotationColumnSelectionState()
+              .getThreshold().getSelectedIndex());
+      actionOption = av.getAnnotationColumnSelectionState()
+              .getActionOption();
+    }
+
+    try
+    {
+      jbInit();
+    } catch (Exception ex)
+    {
+    }
+    adjusting = false;
+
+    updateView();
+    frame.invalidate();
+    frame.pack();
+  }
+
+  private void jbInit() throws Exception
+  {
+    ok.setLabel(MessageManager.getString("action.ok"));
+
+    cancel.setLabel(MessageManager.getString("action.cancel"));
+
+    thresholdValue.setEnabled(false);
+    thresholdValue.setColumns(7);
+
+    ok.addActionListener(this);
+    cancel.addActionListener(this);
+    annotations.addItemListener(this);
+    thresholdValue.addActionListener(this);
+    threshold.addItemListener(this);
+
+    slider.setBackground(Color.white);
+    slider.setEnabled(false);
+    slider.setPreferredSize(new Dimension(100, 32));
+
+    thresholdPanel.setBackground(Color.white);
+    thresholdPanel.setFont(JvSwingUtils.getLabelFont());
+    // thresholdPanel.setLayout(new MigLayout("", "[left][right]", "[][]"));
+
+    actionPanel.setBackground(Color.white);
+    actionPanel.setFont(JvSwingUtils.getLabelFont());
+
+    graphFilterView.setLayout(gBorderLayout);
+    graphFilterView.setBackground(Color.white);
+
+    noGraphFilterView.setLayout(ngBorderLayout);
+    noGraphFilterView.setBackground(Color.white);
+
+    annotationComboBoxPanel.setBackground(Color.white);
+    annotationComboBoxPanel.setFont(JvSwingUtils.getLabelFont());
+
+    gSearchPanel = new SearchPanel(this);
+    ngSearchPanel = new SearchPanel(this);
+    gFurtherActionPanel = new FurtherActionPanel(this);
+    ngFurtherActionPanel = new FurtherActionPanel(this);
+    gStructureFilterPanel = new StructureFilterPanel(this);
+    ngStructureFilterPanel = new StructureFilterPanel(this);
+
+    thresholdPanel.setTitle("Threshold Filter");
+    thresholdPanel.add(getThreshold());
+    thresholdPanel.add(slider);
+    thresholdPanel.add(thresholdValue);
+
+    actionPanel.add(ok);
+    actionPanel.add(cancel);
+
+    JPanel staticPanel = new JPanel();
+    staticPanel.setLayout(new BorderLayout());
+    staticPanel.setBackground(Color.white);
+
+    staticPanel.add(gSearchPanel, java.awt.BorderLayout.NORTH);
+    staticPanel.add(gStructureFilterPanel, java.awt.BorderLayout.SOUTH);
+
+    graphFilterView.add(staticPanel, java.awt.BorderLayout.NORTH);
+    graphFilterView.add(thresholdPanel, java.awt.BorderLayout.CENTER);
+    graphFilterView.add(gFurtherActionPanel, java.awt.BorderLayout.SOUTH);
+
+    noGraphFilterView.add(ngSearchPanel, java.awt.BorderLayout.PAGE_START);
+    noGraphFilterView.add(ngStructureFilterPanel,
+            java.awt.BorderLayout.CENTER);
+    noGraphFilterView.add(ngFurtherActionPanel,
+            java.awt.BorderLayout.CENTER);
+
+    annotationComboBoxPanel.add(getAnnotations());
+    switchableViewsPanel.add(noGraphFilterView,
+            AnnotationColumnChooser.NO_GRAPH_VIEW);
+    switchableViewsPanel.add(graphFilterView,
+            AnnotationColumnChooser.GRAPH_VIEW);
+
+    this.setLayout(borderLayout1);
+    this.add(annotationComboBoxPanel, java.awt.BorderLayout.PAGE_START);
+    this.add(switchableViewsPanel, java.awt.BorderLayout.CENTER);
+    this.add(actionPanel, java.awt.BorderLayout.SOUTH);
+
+    selectedAnnotationChanged();
+    this.validate();
+  }
+
+  @SuppressWarnings("unchecked")
+  public void reset()
+  {
+    if (this.getOldColumnSelection() != null)
+    {
+      av.getColumnSelection().clear();
+
+      if (av.getAnnotationColumnSelectionState() != null)
+      {
+        ColumnSelection oldSelection = av
+                .getAnnotationColumnSelectionState()
+                .getOldColumnSelection();
+        if (oldSelection != null && oldSelection.getHiddenColumns() != null
+                && !oldSelection.getHiddenColumns().isEmpty())
+        {
+          for (Iterator<int[]> itr = oldSelection.getHiddenColumns()
+                  .iterator(); itr.hasNext();)
+          {
+            int positions[] = itr.next();
+            av.hideColumns(positions[0], positions[1]);
+          }
+        }
+        av.setColumnSelection(oldSelection);
+      }
+      ap.paintAlignment(true);
+    }
+
+  }
+
+  public void adjustmentValueChanged(AdjustmentEvent evt)
+  {
+    if (!adjusting)
+    {
+      thresholdValue.setText((slider.getValue() / 1000f) + "");
+      valueChanged(!sliderDragging);
+    }
+  }
+
+  protected void addSliderMouseListeners()
+  {
+
+    slider.addMouseListener(new MouseAdapter()
+    {
+      @Override
+      public void mousePressed(MouseEvent e)
+      {
+        sliderDragging = true;
+        super.mousePressed(e);
+      }
+
+      @Override
+      public void mouseDragged(MouseEvent e)
+      {
+        sliderDragging = true;
+        super.mouseDragged(e);
+      }
+
+      @Override
+      public void mouseReleased(MouseEvent evt)
+      {
+        if (sliderDragging)
+        {
+          sliderDragging = false;
+          valueChanged(true);
+        }
+        ap.paintAlignment(true);
+      }
+    });
+  }
+
+  public void valueChanged(boolean updateAllAnnotation)
+  {
+    if (slider.isEnabled())
+    {
+      getCurrentAnnotation().threshold.value = slider.getValue() / 1000f;
+      updateView();
+      ap.paintAlignment(false);
+    }
+  }
+
+  public Choice getThreshold()
+  {
+    return threshold;
+  }
+
+  public void setThreshold(Choice threshold)
+  {
+    this.threshold = threshold;
+  }
+
+  public Choice getAnnotations()
+  {
+    return annotations;
+  }
+
+  public void setAnnotations(Choice annotations)
+  {
+    this.annotations = annotations;
+  }
+
+  @Override
+  public void updateView()
+  {
+    // Check if combobox is still adjusting
+    if (adjusting)
+    {
+      return;
+    }
+
+    AnnotationFilterParameter filterParams = new AnnotationFilterParameter();
+    setCurrentAnnotation(av.getAlignment().getAlignmentAnnotation()[getAnnotations()
+            .getSelectedIndex()]);
+
+    int selectedThresholdItem = getSelectedThresholdItem(getThreshold()
+            .getSelectedIndex());
+
+    slider.setEnabled(true);
+    thresholdValue.setEnabled(true);
+
+    if (selectedThresholdItem == AnnotationColourGradient.NO_THRESHOLD)
+    {
+      slider.setEnabled(false);
+      thresholdValue.setEnabled(false);
+      thresholdValue.setText("");
+      // build filter params
+    }
+    else if (selectedThresholdItem != AnnotationColourGradient.NO_THRESHOLD)
+    {
+      if (getCurrentAnnotation().threshold == null)
+      {
+        getCurrentAnnotation()
+                .setThreshold(
+                        new jalview.datamodel.GraphLine(
+                                (getCurrentAnnotation().graphMax - getCurrentAnnotation().graphMin) / 2f,
+                                "Threshold", Color.black));
+      }
+
+      adjusting = true;
+      float range = getCurrentAnnotation().graphMax * 1000
+              - getCurrentAnnotation().graphMin * 1000;
+
+      slider.setMinimum((int) (getCurrentAnnotation().graphMin * 1000));
+      slider.setMaximum((int) (getCurrentAnnotation().graphMax * 1000));
+      slider.setValue((int) (getCurrentAnnotation().threshold.value * 1000));
+      thresholdValue.setText(getCurrentAnnotation().threshold.value + "");
+      // slider.setMajorTickSpacing((int) (range / 10f));
+      slider.setEnabled(true);
+      thresholdValue.setEnabled(true);
+      adjusting = false;
+
+      // build filter params
+      filterParams
+              .setThresholdType(AnnotationFilterParameter.ThresholdType.NO_THRESHOLD);
+      if (getCurrentAnnotation().graph != AlignmentAnnotation.NO_GRAPH)
+      {
+        filterParams
+                .setThresholdValue(getCurrentAnnotation().threshold.value);
+
+        if (selectedThresholdItem == AnnotationColourGradient.ABOVE_THRESHOLD)
+        {
+          filterParams
+                  .setThresholdType(AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD);
+        }
+        else if (selectedThresholdItem == AnnotationColourGradient.BELOW_THRESHOLD)
+        {
+          filterParams
+                  .setThresholdType(AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD);
+        }
+      }
+    }
+
+    if (currentStructureFilterPanel != null)
+    {
+      if (currentStructureFilterPanel.alphaHelix.getState())
+      {
+        filterParams.setFilterAlphaHelix(true);
+      }
+      if (currentStructureFilterPanel.betaStrand.getState())
+      {
+        filterParams.setFilterBetaSheet(true);
+      }
+      if (currentStructureFilterPanel.turn.getState())
+      {
+        filterParams.setFilterTurn(true);
+      }
+    }
+
+    if (currentSearchPanel != null)
+    {
+
+      if (!currentSearchPanel.searchBox.getText().isEmpty())
+      {
+        currentSearchPanel.description.setEnabled(true);
+        currentSearchPanel.displayName.setEnabled(true);
+        filterParams.setRegexString(currentSearchPanel.searchBox.getText());
+        if (currentSearchPanel.displayName.getState())
+        {
+          filterParams
+                  .addRegexSearchField(AnnotationFilterParameter.SearchableAnnotationField.DISPLAY_STRING);
+        }
+        if (currentSearchPanel.description.getState())
+        {
+          filterParams
+                  .addRegexSearchField(AnnotationFilterParameter.SearchableAnnotationField.DESCRIPTION);
+        }
+      }
+      else
+      {
+        currentSearchPanel.description.setEnabled(false);
+        currentSearchPanel.displayName.setEnabled(false);
+      }
+    }
+
+    av.getColumnSelection().filterAnnotations(
+            getCurrentAnnotation().annotations, filterParams);
+
+    av.showAllHiddenColumns();
+    if (getActionOption() == ACTION_OPTION_HIDE)
+    {
+      av.hideSelectedColumns();
+    }
+
+    filterParams = null;
+    av.setAnnotationColumnSelectionState(this);
+    ap.paintAlignment(true);
+  }
+
+  public ColumnSelection getOldColumnSelection()
+  {
+    return oldColumnSelection;
+  }
+
+  public void setOldColumnSelection(ColumnSelection currentColumnSelection)
+  {
+    if (currentColumnSelection != null)
+    {
+      this.oldColumnSelection = new ColumnSelection(currentColumnSelection);
+    }
+  }
+
+  public FurtherActionPanel getCurrentFutherActionPanel()
+  {
+    return currentFurtherActionPanel;
+  }
+
+  public void setCurrentFutherActionPanel(
+          FurtherActionPanel currentFutherActionPanel)
+  {
+    this.currentFurtherActionPanel = currentFutherActionPanel;
+  }
+
+  public SearchPanel getCurrentSearchPanel()
+  {
+    return currentSearchPanel;
+  }
+
+  public void setCurrentSearchPanel(SearchPanel currentSearchPanel)
+  {
+    this.currentSearchPanel = currentSearchPanel;
+  }
+
+  public int getActionOption()
+  {
+    return actionOption;
+  }
+
+  public void setActionOption(int actionOption)
+  {
+    this.actionOption = actionOption;
+  }
+
+  public StructureFilterPanel getCurrentStructureFilterPanel()
+  {
+    return currentStructureFilterPanel;
+  }
+
+  public void setCurrentStructureFilterPanel(
+          StructureFilterPanel currentStructureFilterPanel)
+  {
+    this.currentStructureFilterPanel = currentStructureFilterPanel;
+  }
+
+  @Override
+  public void itemStateChanged(ItemEvent e)
+  {
+    if (e.getSource() == annotations)
+    {
+      selectedAnnotationChanged();
+    }
+    else if (e.getSource() == threshold)
+    {
+      threshold_actionPerformed(null);
+    }
+  }
+
+  public void selectedAnnotationChanged()
+  {
+    String currentView = AnnotationColumnChooser.NO_GRAPH_VIEW;
+    if (av.getAlignment().getAlignmentAnnotation()[getAnnotations()
+            .getSelectedIndex()].graph != AlignmentAnnotation.NO_GRAPH)
+    {
+      currentView = AnnotationColumnChooser.GRAPH_VIEW;
+    }
+
+    gSearchPanel.syncState();
+    gFurtherActionPanel.syncState();
+    gStructureFilterPanel.syncState();
+
+    ngSearchPanel.syncState();
+    ngFurtherActionPanel.syncState();
+    ngStructureFilterPanel.syncState();
+
+    switchableViewsLayout.show(switchableViewsPanel, currentView);
+    updateView();
+  }
+
+  public class FurtherActionPanel extends Panel implements
+          ItemListener
+  {
+    private AnnotationColumnChooser aColChooser;
+
+    private Choice furtherAction = new Choice();
+
+    public FurtherActionPanel(AnnotationColumnChooser aColChooser)
+    {
+      this.aColChooser = aColChooser;
+      furtherAction.addItem("Select");
+      furtherAction.addItem("Hide");
+      furtherAction.addItemListener(this);
+      syncState();
+
+      // this.setTitle("Filter Actions");
+      // this.setFont(JvSwingUtils.getLabelFont());
+
+      this.add(furtherAction);
+    }
+
+    public void syncState()
+    {
+      if (aColChooser.getActionOption() == AnnotationColumnChooser.ACTION_OPTION_HIDE)
+      {
+        furtherAction.select("Hide");
+      }
+      else
+      {
+        furtherAction.select("Select");
+      }
+    }
+
+    @Override
+    public void itemStateChanged(ItemEvent e)
+    {
+      aColChooser.setCurrentFutherActionPanel(this);
+      if (furtherAction.getSelectedItem().equalsIgnoreCase("Select"))
+      {
+        setActionOption(ACTION_OPTION_SELECT);
+        updateView();
+      }
+      else
+      {
+        setActionOption(ACTION_OPTION_HIDE);
+        updateView();
+      }
+
+    }
+  }
+
+  public class StructureFilterPanel extends TitledPanel implements
+          ItemListener
+  {
+    private AnnotationColumnChooser aColChooser;
+
+    private Checkbox alphaHelix = new Checkbox();
+
+    private Checkbox betaStrand = new Checkbox();
+
+    private Checkbox turn = new Checkbox();
+
+    private Checkbox all = new Checkbox();
+
+    public StructureFilterPanel(AnnotationColumnChooser aColChooser)
+    {
+      this.aColChooser = aColChooser;
+
+      alphaHelix.setLabel(MessageManager.getString("label.alpha_helix"));
+      alphaHelix.setBackground(Color.white);
+
+      alphaHelix.addItemListener(this);
+
+      betaStrand.setLabel(MessageManager.getString("label.beta_strand"));
+      betaStrand.setBackground(Color.white);
+      betaStrand.addItemListener(this);
+
+      turn.setLabel(MessageManager.getString("label.turn"));
+      turn.setBackground(Color.white);
+      turn.addItemListener(this);
+
+      all.setLabel(MessageManager.getString("label.select_all"));
+      all.setBackground(Color.white);
+      all.addItemListener(this);
+
+      this.setBackground(Color.white);
+      this.setTitle("Structure Filter");
+      this.setFont(JvSwingUtils.getLabelFont());
+
+      this.add(all);
+      this.add(alphaHelix);
+      this.add(betaStrand);
+      this.add(turn);
+    }
+
+    public void alphaHelix_actionPerformed()
+    {
+      updateSelectAllState();
+      aColChooser.setCurrentStructureFilterPanel(this);
+      aColChooser.updateView();
+    }
+
+    public void betaStrand_actionPerformed()
+    {
+      updateSelectAllState();
+      aColChooser.setCurrentStructureFilterPanel(this);
+      aColChooser.updateView();
+    }
+
+    public void turn_actionPerformed()
+    {
+      updateSelectAllState();
+      aColChooser.setCurrentStructureFilterPanel(this);
+      aColChooser.updateView();
+    }
+
+    public void all_actionPerformed()
+    {
+      if (all.getState())
+      {
+        alphaHelix.setState(true);
+        betaStrand.setState(true);
+        turn.setState(true);
+      }
+      else
+      {
+        alphaHelix.setState(false);
+        betaStrand.setState(false);
+        turn.setState(false);
+      }
+      aColChooser.setCurrentStructureFilterPanel(this);
+      aColChooser.updateView();
+    }
+
+    public void updateSelectAllState()
+    {
+      if (alphaHelix.getState() && betaStrand.getState() && turn.getState())
+      {
+        all.setState(true);
+      }
+      else
+      {
+        all.setState(false);
+      }
+    }
+
+    public void syncState()
+    {
+      StructureFilterPanel sfp = aColChooser
+              .getCurrentStructureFilterPanel();
+      if (sfp != null)
+      {
+        alphaHelix.setState(sfp.alphaHelix.getState());
+        betaStrand.setState(sfp.betaStrand.getState());
+        turn.setState(sfp.turn.getState());
+        if (sfp.all.getState())
+        {
+          all.setState(true);
+          alphaHelix.setState(true);
+          betaStrand.setState(true);
+          turn.setState(true);
+        }
+      }
+
+    }
+
+    @Override
+    public void itemStateChanged(ItemEvent e)
+    {
+      if (e.getSource() == alphaHelix)
+      {
+        alphaHelix_actionPerformed();
+      }
+      else if (e.getSource() == betaStrand)
+      {
+        betaStrand_actionPerformed();
+      }
+      else if (e.getSource() == turn)
+      {
+        turn_actionPerformed();
+      }
+      else if (e.getSource() == all)
+      {
+        all_actionPerformed();
+      }
+    }
+  }
+
+  public class SearchPanel extends TitledPanel implements ItemListener
+  {
+    private AnnotationColumnChooser aColChooser;
+
+    private Checkbox displayName = new Checkbox();
+
+    private Checkbox description = new Checkbox();
+
+    private TextField searchBox = new TextField(10);
+
+    public SearchPanel(AnnotationColumnChooser aColChooser)
+    {
+
+      this.aColChooser = aColChooser;
+      searchBox.addTextListener(new TextListener()
+      {
+
+        @Override
+        public void textValueChanged(TextEvent e)
+        {
+          searchStringAction();
+
+        }
+
+      });
+
+      displayName.setLabel(MessageManager.getString("label.display_name"));
+      displayName.setEnabled(false);
+      displayName.addItemListener(this);
+
+      description.setLabel(MessageManager.getString("label.description"));
+      description.setEnabled(false);
+      description.addItemListener(this);
+      this.setTitle("Search Filter");
+      this.setFont(JvSwingUtils.getLabelFont());
+
+      syncState();
+      this.add(searchBox);
+      this.add(displayName);
+      this.add(description);
+    }
+
+    public void displayNameCheckboxAction()
+    {
+      aColChooser.setCurrentSearchPanel(this);
+      aColChooser.updateView();
+    }
+
+    public void discriptionCheckboxAction()
+    {
+      aColChooser.setCurrentSearchPanel(this);
+      aColChooser.updateView();
+    }
+
+    public void searchStringAction()
+    {
+      aColChooser.setCurrentSearchPanel(this);
+      aColChooser.updateView();
+    }
+
+    public void syncState()
+    {
+      SearchPanel sp = aColChooser.getCurrentSearchPanel();
+      if (sp != null)
+      {
+        description.setEnabled(sp.description.isEnabled());
+        description.setState(sp.description.getState());
+
+        displayName.setEnabled(sp.displayName.isEnabled());
+        displayName.setState(sp.displayName.getState());
+
+        searchBox.setText(sp.searchBox.getText());
+      }
+    }
+
+    @Override
+    public void itemStateChanged(ItemEvent e)
+    {
+      if (e.getSource() == displayName)
+      {
+        displayNameCheckboxAction();
+      }
+      else if (e.getSource() == description)
+      {
+        discriptionCheckboxAction();
+      }
+
+    }
+  }
+
+  public void actionPerformed(ActionEvent evt)
+  {
+    if (evt.getSource() == thresholdValue)
+    {
+      try
+      {
+        float f = new Float(thresholdValue.getText()).floatValue();
+        slider.setValue((int) (f * 1000));
+        adjustmentValueChanged(null);
+      } catch (NumberFormatException ex)
+      {
+      }
+    }
+
+    else if (evt.getSource() == ok)
+    {
+      ok_actionPerformed(null);
+    }
+    else if (evt.getSource() == cancel)
+    {
+      cancel_actionPerformed(null);
+    }
+    else if (evt.getSource() == thresholdValue)
+    {
+      thresholdValue_actionPerformed(null);
+    }
+    else
+    {
+      updateView();
+    }
+  }
+
+  @Override
+  public void mouseClicked(MouseEvent e)
+  {
+    // TODO Auto-generated method stub
+
+  }
+
+  @Override
+  public void mousePressed(MouseEvent e)
+  {
+    if (e.getSource() == slider)
+    {
+      updateView();
+    }
+
+  }
+
+  @Override
+  public void mouseReleased(MouseEvent e)
+  {
+    if (e.getSource() == slider)
+    {
+      updateView();
+    }
+  }
+
+  @Override
+  public void mouseEntered(MouseEvent e)
+  {
+    if (e.getSource() == slider)
+    {
+      updateView();
+    }
+  }
+
+  @Override
+  public void mouseExited(MouseEvent e)
+  {
+    if (e.getSource() == slider)
+    {
+      updateView();
+    }
+  }
+
+}
index 3e81664..6aaa033 100755 (executable)
  */
 package jalview.appletgui;
 
-import java.util.*;
-import java.awt.*;
-import java.awt.event.*;
-
-import jalview.datamodel.*;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
 import jalview.util.MessageManager;
 import jalview.util.ParseHtmlBodyAndLinks;
 
+import java.awt.Checkbox;
+import java.awt.CheckboxMenuItem;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.FontMetrics;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.MenuItem;
+import java.awt.Panel;
+import java.awt.PopupMenu;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.InputEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.util.Arrays;
+import java.util.Vector;
+
 public class AnnotationLabels extends Panel implements ActionListener,
         MouseListener, MouseMotionListener
 {
@@ -221,7 +243,9 @@ public class AnnotationLabels extends Panel implements ActionListener,
       return true;
     }
     else
+    {
       return false;
+    }
 
   }
 
@@ -713,11 +737,8 @@ public class AnnotationLabels extends Panel implements ActionListener,
     if (av.hasHiddenColumns())
     {
       jalview.appletgui.AlignFrame.copiedHiddenColumns = new Vector();
-      for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
+      for (int[] region : av.getColumnSelection().getHiddenColumns())
       {
-        int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
-                .elementAt(i);
-
         jalview.appletgui.AlignFrame.copiedHiddenColumns
                 .addElement(new int[]
                 { region[0], region[1] });
diff --git a/src/jalview/appletgui/AnnotationRowFilter.java b/src/jalview/appletgui/AnnotationRowFilter.java
new file mode 100644 (file)
index 0000000..8a0a44a
--- /dev/null
@@ -0,0 +1,198 @@
+package jalview.appletgui;
+
+import jalview.schemes.AnnotationColourGradient;
+import jalview.util.MessageManager;
+
+import java.awt.Button;
+import java.awt.Checkbox;
+import java.awt.Choice;
+import java.awt.Frame;
+import java.awt.Panel;
+import java.awt.Scrollbar;
+import java.awt.TextField;
+import java.awt.event.ActionEvent;
+import java.util.Vector;
+
+
+
+@SuppressWarnings("serial")
+public abstract class AnnotationRowFilter extends Panel
+{
+  protected AlignViewport av;
+
+  protected AlignmentPanel ap;
+
+  protected int[] annmap;
+
+  protected boolean enableSeqAss = false;
+
+  private jalview.datamodel.AlignmentAnnotation currentAnnotation;
+
+  protected boolean adjusting = false;
+
+  protected Checkbox currentColours = new Checkbox();
+
+  protected Panel minColour = new Panel();
+
+  protected Panel maxColour = new Panel();
+
+  protected Checkbox seqAssociated = new Checkbox();
+
+  protected Checkbox thresholdIsMin = new Checkbox();
+
+  protected Scrollbar slider = new Scrollbar(Scrollbar.HORIZONTAL);
+
+  protected TextField thresholdValue = new TextField(20);
+
+  protected Frame frame;
+
+  protected Button ok = new Button();
+
+  protected Button cancel = new Button();
+
+  /**
+   * enabled if the user is dragging the slider - try to keep updates to a
+   * minimun
+   */
+  protected boolean sliderDragging = false;
+
+
+  public AnnotationRowFilter(AlignViewport av, final AlignmentPanel ap)
+  {
+    this.av = av;
+    this.ap = ap;
+  }
+
+  public AnnotationRowFilter()
+  {
+
+  }
+
+
+  public Vector getAnnotationItems(boolean isSeqAssociated)
+  {
+    Vector list = new Vector();
+    int index = 1;
+    int[] anmap = new int[av.getAlignment().getAlignmentAnnotation().length];
+    for (int i = 0; i < av.getAlignment().getAlignmentAnnotation().length; i++)
+    {
+      if (av.getAlignment().getAlignmentAnnotation()[i].sequenceRef == null)
+      {
+        if (isSeqAssociated)
+        {
+          continue;
+        }
+      }
+      else
+      {
+        enableSeqAss = true;
+      }
+      String label = av.getAlignment().getAlignmentAnnotation()[i].label;
+      if (!list.contains(label))
+      {
+        anmap[list.size()] = i;
+        list.add(label);
+
+      }
+      else
+      {
+        if (!isSeqAssociated)
+        {
+          anmap[list.size()] = i;
+          list.add(label + "_" + (index++));
+        }
+      }
+    }
+    this.annmap = new int[list.size()];
+    System.arraycopy(anmap, 0, this.annmap, 0, this.annmap.length);
+    return list;
+  }
+
+  protected int getSelectedThresholdItem(int indexValue)
+  {
+    int selectedThresholdItem = -1;
+    if (indexValue == 1)
+    {
+      selectedThresholdItem = AnnotationColourGradient.ABOVE_THRESHOLD;
+    }
+    else if (indexValue == 2)
+    {
+      selectedThresholdItem = AnnotationColourGradient.BELOW_THRESHOLD;
+    }
+    return selectedThresholdItem;
+  }
+
+  public void modelChanged()
+  {
+    seqAssociated.setEnabled(enableSeqAss);
+  }
+
+  public void ok_actionPerformed(ActionEvent e)
+  {
+    updateView();
+    frame.setVisible(false);
+  }
+
+  public void cancel_actionPerformed(ActionEvent e)
+  {
+    reset();
+    ap.paintAlignment(true);
+    frame.setVisible(false);
+  }
+
+  public void thresholdCheck_actionPerformed(ActionEvent e)
+  {
+    updateView();
+  }
+
+  public void annotations_actionPerformed(ActionEvent e)
+  {
+    updateView();
+  }
+
+  public void threshold_actionPerformed(ActionEvent e)
+  {
+    updateView();
+  }
+
+  public void thresholdValue_actionPerformed(ActionEvent e)
+  {
+    try
+    {
+      float f = Float.parseFloat(thresholdValue.getText());
+      slider.setValue((int) (f * 1000));
+      updateView();
+    } catch (NumberFormatException ex)
+    {
+    }
+  }
+
+
+  protected void populateThresholdComboBox(Choice threshold)
+  {
+    threshold.addItem(MessageManager
+            .getString("label.threshold_feature_no_thereshold"));
+    threshold.addItem(MessageManager
+            .getString("label.threshold_feature_above_thereshold"));
+    threshold.addItem(MessageManager
+            .getString("label.threshold_feature_below_thereshold"));
+  }
+
+
+  public jalview.datamodel.AlignmentAnnotation getCurrentAnnotation()
+  {
+    return currentAnnotation;
+  }
+
+  public void setCurrentAnnotation(
+          jalview.datamodel.AlignmentAnnotation currentAnnotation)
+  {
+    this.currentAnnotation = currentAnnotation;
+  }
+
+  public abstract void valueChanged(boolean updateAllAnnotation);
+
+  public abstract void updateView();
+
+  public abstract void reset();
+}
\ No newline at end of file
index 42fbd70..4dcb5d7 100644 (file)
  */
 package jalview.appletgui;
 
-import java.util.*;
-import java.awt.*;
-import java.awt.event.*;
-
-import jalview.api.SequenceStructureBinding;
-import jalview.datamodel.*;
-import jalview.structure.*;
-import jalview.io.*;
-
-import jalview.schemes.*;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.io.AppletFormatAdapter;
+import jalview.io.FileParse;
+import jalview.schemes.BuriedColourScheme;
+import jalview.schemes.HelixColourScheme;
+import jalview.schemes.HydrophobicColourScheme;
+import jalview.schemes.PurinePyrimidineColourScheme;
+import jalview.schemes.StrandColourScheme;
+import jalview.schemes.TaylorColourScheme;
+import jalview.schemes.TurnColourScheme;
+import jalview.schemes.UserColourScheme;
+import jalview.schemes.ZappoColourScheme;
+import jalview.structure.StructureSelectionManager;
 import jalview.util.MessageManager;
 
+import java.awt.BorderLayout;
+import java.awt.CheckboxMenuItem;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Menu;
+import java.awt.MenuBar;
+import java.awt.MenuItem;
+import java.awt.Panel;
+import java.awt.Rectangle;
+import java.awt.TextArea;
+import java.awt.TextField;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.Vector;
+
 public class AppletJmol extends EmbmenuFrame implements
 // StructureListener,
         KeyListener, ActionListener, ItemListener
@@ -398,9 +429,9 @@ public class AppletJmol extends EmbmenuFrame implements
       StringBuffer sb = new StringBuffer();
       try
       {
-        for (int s = 0; s < jmb.pdbentry.length; s++)
+        for (int s = 0; s < jmb.getPdbCount(); s++)
         {
-          sb.append(jmb.printMapping(jmb.pdbentry[s].getFile()));
+          sb.append(jmb.printMapping(jmb.getPdbEntry(s).getFile()));
           sb.append("\n");
         }
         cap.setText(sb.toString());
@@ -488,7 +519,9 @@ public class AppletJmol extends EmbmenuFrame implements
       for (int i = 0; i < chainMenu.getItemCount(); i++)
       {
         if (chainMenu.getItem(i) instanceof CheckboxMenuItem)
+        {
           ((CheckboxMenuItem) chainMenu.getItem(i)).setState(true);
+        }
       }
 
       centerViewer();
@@ -519,10 +552,12 @@ public class AppletJmol extends EmbmenuFrame implements
     else if (evt.getSource() == seqColour)
     {
       setEnabled(seqColour);
-      jmb.colourBySequence(ap.av.getShowSequenceFeatures(), ap);
+      jmb.colourBySequence(ap.av.isShowSequenceFeatures(), ap);
     }
     else if (!allChainsSelected)
+    {
       centerViewer();
+    }
   }
 
   public void keyPressed(KeyEvent evt)
@@ -547,7 +582,7 @@ public class AppletJmol extends EmbmenuFrame implements
   public void updateColours(Object source)
   {
     AlignmentPanel ap = (AlignmentPanel) source;
-    jmb.colourBySequence(ap.av.getShowSequenceFeatures(), ap);
+    jmb.colourBySequence(ap.av.isShowSequenceFeatures(), ap);
   }
 
   public void updateTitleAndMenus()
@@ -558,7 +593,7 @@ public class AppletJmol extends EmbmenuFrame implements
       return;
     }
     setChainMenuItems(jmb.chainNames);
-    jmb.colourBySequence(ap.av.getShowSequenceFeatures(), ap);
+    jmb.colourBySequence(ap.av.isShowSequenceFeatures(), ap);
 
     setTitle(jmb.getViewerTitle());
   }
index 650693d..ff1f5b2 100644 (file)
@@ -52,7 +52,7 @@ class AppletJmolBinding extends jalview.ext.jmol.JalviewJmolBinding
           AlignmentViewPanel alignment)
   {
     AlignmentPanel ap = (AlignmentPanel) alignment;
-    if (appletJmolBinding.ap.av.showSequenceFeatures)
+    if (appletJmolBinding.ap.av.isShowSequenceFeatures())
     {
       if (appletJmolBinding.fr == null)
       {
@@ -77,7 +77,9 @@ class AppletJmolBinding extends jalview.ext.jmol.JalviewJmolBinding
   public void sendConsoleEcho(String strEcho)
   {
     if (appletJmolBinding.scriptWindow == null)
+    {
       appletJmolBinding.showConsole(true);
+    }
 
     appletJmolBinding.history.append("\n" + strEcho);
   }
@@ -105,7 +107,7 @@ class AppletJmolBinding extends jalview.ext.jmol.JalviewJmolBinding
   public void updateColours(Object source)
   {
     AlignmentPanel ap = (AlignmentPanel) source;
-    colourBySequence(ap.av.getShowSequenceFeatures(), ap);
+    colourBySequence(ap.av.isShowSequenceFeatures(), ap);
   }
 
   public void showUrl(String url)
@@ -138,10 +140,10 @@ class AppletJmolBinding extends jalview.ext.jmol.JalviewJmolBinding
 
   }
 
+  @Override
   public void refreshPdbEntries()
   {
-    // TODO Auto-generated method stub
-
+    // noop
   }
 
   @Override
@@ -164,20 +166,7 @@ class AppletJmolBinding extends jalview.ext.jmol.JalviewJmolBinding
   protected void releaseUIResources()
   {
     appletJmolBinding = null;
-    if (console != null)
-    {
-      try
-      {
-        console.setVisible(false);
-      } catch (Error e)
-      {
-      } catch (Exception x)
-      {
-      }
-      ;
-      console = null;
-    }
-
+    closeConsole();
   }
 
   @Override
index 636dd7f..0a9ca56 100644 (file)
  */
 package jalview.appletgui;
 
-import java.awt.*;
-import java.awt.event.*;
-
-import jalview.datamodel.*;
-import jalview.io.*;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.Sequence;
+import jalview.io.AnnotationFile;
+import jalview.io.AppletFormatAdapter;
+import jalview.io.IdentifyFile;
+import jalview.io.TCoffeeScoreFile;
 import jalview.schemes.TCoffeeColourScheme;
 import jalview.util.MessageManager;
 
+import java.awt.BorderLayout;
+import java.awt.Button;
+import java.awt.Dialog;
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.Panel;
+import java.awt.TextArea;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+
 public class CutAndPasteTransfer extends Panel implements ActionListener,
         MouseListener
 {
@@ -132,12 +146,15 @@ public class CutAndPasteTransfer extends Panel implements ActionListener,
       pdb.setFile(text);
 
       if (alignFrame.alignPanel.av.applet.jmolAvailable)
+      {
         new jalview.appletgui.AppletJmol(pdb, new Sequence[]
         { seq }, null, alignFrame.alignPanel, AppletFormatAdapter.PASTE);
+      }
       else
-
+      {
         new MCview.AppletPDBViewer(pdb, new Sequence[]
         { seq }, null, alignFrame.alignPanel, AppletFormatAdapter.PASTE);
+      }
 
     }
     else if (treeImport)
@@ -203,8 +220,8 @@ public class CutAndPasteTransfer extends Panel implements ActionListener,
       }
       if (tcf == null)
       {
-        if (new AnnotationFile().readAnnotationFile(
-                alignFrame.viewport.getAlignment(), textarea.getText(),
+        if (new AnnotationFile().annotateAlignmentView(alignFrame.viewport,
+                textarea.getText(),
                 jalview.io.AppletFormatAdapter.PASTE))
         {
           alignFrame.alignPanel.fontChanged();
index 6389250..cb99f87 100644 (file)
  */
 package jalview.appletgui;
 
-import java.awt.Container;
-import java.util.BitSet;
-import java.util.Hashtable;
-import java.util.Vector;
-
-import org.jmol.api.JmolAppConsoleInterface;
-import org.jmol.api.JmolViewer;
-
 import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
@@ -35,6 +27,15 @@ import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.ext.jmol.JalviewJmolBinding;
 
+import java.awt.Container;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+import java.util.Vector;
+
+import org.jmol.api.JmolAppConsoleInterface;
+import org.jmol.api.JmolViewer;
+
 /**
  * bind an alignment view to an external Jmol instance.
  * 
@@ -57,9 +58,8 @@ public class ExtJmol extends JalviewJmolBinding
   public ExtJmol(JmolViewer viewer, AlignmentPanel alignPanel,
           SequenceI[][] seqs)
   {
-    super(alignPanel.getStructureSelectionManager(), viewer);
+    super(alignPanel.getStructureSelectionManager(), seqs, viewer);
     ap = alignPanel;
-    this.sequence = seqs;
     notifyFileLoaded(null, null, null, null, 0);
   }
 
@@ -78,7 +78,7 @@ public class ExtJmol extends JalviewJmolBinding
   public FeatureRenderer getFeatureRenderer(AlignmentViewPanel alignment)
   {
     AlignmentPanel ap = (AlignmentPanel) alignment;
-    if (ap.av.showSequenceFeatures)
+    if (ap.av.isShowSequenceFeatures())
     {
       return ap.getFeatureRenderer();
     }
@@ -125,8 +125,8 @@ public class ExtJmol extends JalviewJmolBinding
 
   public void refreshPdbEntries()
   {
-    Vector pdbe = new Vector();
-    Hashtable fileids = new Hashtable();
+    List<PDBEntry> pdbe = new ArrayList<PDBEntry>();
+    List<String> fileids = new ArrayList<String>();
     SequenceI[] sq = ap.av.getAlignment().getSequencesArray();
     for (int s = 0; s < sq.length; s++)
     {
@@ -136,18 +136,23 @@ public class ExtJmol extends JalviewJmolBinding
         for (int pe = 0, peSize = pdbids.size(); pe < peSize; pe++)
         {
           PDBEntry pentry = (PDBEntry) pdbids.elementAt(pe);
-          if (!fileids.containsKey(pentry.getId()))
+          if (!fileids.contains(pentry.getId()))
           {
-            pdbe.addElement(pentry);
+            pdbe.add(pentry);
+          }
+          else
+          {
+            fileids.add(pentry.getId());
           }
         }
       }
     }
-    pdbentry = new PDBEntry[pdbe.size()];
+    PDBEntry[] newEntries = new PDBEntry[pdbe.size()];
     for (int pe = 0; pe < pdbe.size(); pe++)
     {
-      pdbentry[pe] = (PDBEntry) pdbe.elementAt(pe);
+      newEntries[pe] = pdbe.get(pe);
     }
+    setPdbentry(newEntries);
   }
 
   @Override
@@ -172,19 +177,7 @@ public class ExtJmol extends JalviewJmolBinding
   protected void releaseUIResources()
   {
     ap = null;
-    if (console != null)
-    {
-      try
-      {
-        console.setVisible(false);
-      } catch (Error e)
-      {
-      } catch (Exception x)
-      {
-      }
-      ;
-      console = null;
-    }
+    closeConsole();
 
   }
 
index 32ffdbf..c87803c 100644 (file)
@@ -73,10 +73,10 @@ public class FeatureColourChooser extends Panel implements ActionListener,
   {
     this.type = type;
     fr = frenderer;
-    float mm[] = ((float[][]) fr.minmax.get(type))[0];
+    float mm[] = ((float[][]) fr.getMinMax().get(type))[0];
     min = mm[0];
     max = mm[1];
-    oldcs = fr.featureColours.get(type);
+    oldcs = fr.getFeatureColours().get(type);
     if (oldcs instanceof GraduatedColor)
     {
       cs = new GraduatedColor((GraduatedColor) oldcs, min, max);
@@ -130,7 +130,7 @@ public class FeatureColourChooser extends Panel implements ActionListener,
     {
       // cancel
       reset();
-      PaintRefresher.Refresh(this, fr.av.getSequenceSetId());
+      PaintRefresher.Refresh(this, fr.getViewport().getSequenceSetId());
       frame.setVisible(false);
     }
   }
@@ -289,7 +289,7 @@ public class FeatureColourChooser extends Panel implements ActionListener,
     threshline.value = (float) slider.getValue() / 1000f;
     cs.setThresh(threshline.value);
     changeColour();
-    PaintRefresher.Refresh(this, fr.av.getSequenceSetId());
+    PaintRefresher.Refresh(this, fr.getViewport().getSequenceSetId());
     // ap.paintAlignment(false);
   }
 
@@ -402,16 +402,16 @@ public class FeatureColourChooser extends Panel implements ActionListener,
       }
     }
 
-    fr.featureColours.put(type, acg);
+    fr.setColour(type, acg);
     cs = acg;
-    PaintRefresher.Refresh(this, fr.av.getSequenceSetId());
+    PaintRefresher.Refresh(this, fr.getViewport().getSequenceSetId());
     // ap.paintAlignment(false);
   }
 
   void reset()
   {
-    fr.featureColours.put(type, oldcs);
-    PaintRefresher.Refresh(this, fr.av.getSequenceSetId());
+    fr.setColour(type, oldcs);
+    PaintRefresher.Refresh(this, fr.getViewport().getSequenceSetId());
     // ap.paintAlignment(true);
 
   }
@@ -433,7 +433,7 @@ public class FeatureColourChooser extends Panel implements ActionListener,
     }
     else
     {
-      PaintRefresher.Refresh(this, fr.av.getSequenceSetId());
+      PaintRefresher.Refresh(this, fr.getViewport().getSequenceSetId());
     }
     // ap.paintAlignment(true);
   }
index 29c3ac5..dd2b873 100644 (file)
 package jalview.appletgui;
 
 import java.util.*;
-
 import java.awt.*;
-
 import java.awt.event.*;
 
 import jalview.datamodel.*;
 import jalview.schemes.AnnotationColourGradient;
 import jalview.schemes.GraduatedColor;
 import jalview.util.MessageManager;
+import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
 
 /**
  * DOCUMENT ME!
@@ -37,34 +36,13 @@ import jalview.util.MessageManager;
  * @author $author$
  * @version $Revision$
  */
-public class FeatureRenderer implements jalview.api.FeatureRenderer
+public class FeatureRenderer extends jalview.renderer.seqfeatures.FeatureRenderer
 {
-  AlignViewport av;
-
-  Hashtable featureColours = new Hashtable();
-
-  // A higher level for grouping features of a
-  // particular type
-  Hashtable featureGroups = null;
 
   // Holds web links for feature groups and feature types
   // in the form label|link
   Hashtable featureLinks = null;
 
-  // This is actually an Integer held in the hashtable,
-  // Retrieved using the key feature type
-  Object currentColour;
-
-  String[] renderOrder;
-
-  FontMetrics fm;
-
-  int charOffset;
-
-  float transparency = 1f;
-
-  TransparencySetter transparencySetter = null;
-
   /**
    * Creates a new FeatureRenderer object.
    * 
@@ -73,39 +51,10 @@ public class FeatureRenderer implements jalview.api.FeatureRenderer
    */
   public FeatureRenderer(AlignViewport av)
   {
+    super();
     this.av = av;
 
-    if (!System.getProperty("java.version").startsWith("1.1"))
-    {
-      transparencySetter = new TransparencySetter();
-    }
-  }
-
-  public void transferSettings(FeatureRenderer fr)
-  {
-    renderOrder = fr.renderOrder;
-    featureGroups = fr.featureGroups;
-    featureColours = fr.featureColours;
-    transparency = fr.transparency;
-    if (av != null && fr.av != null && fr.av != av)
-    {
-      if (fr.av.featuresDisplayed != null)
-      {
-        if (av.featuresDisplayed == null)
-        {
-          av.featuresDisplayed = new Hashtable();
-        }
-        else
-        {
-          av.featuresDisplayed.clear();
-        }
-        Enumeration en = fr.av.featuresDisplayed.keys();
-        while (en.hasMoreElements())
-        {
-          av.featuresDisplayed.put(en.nextElement(), Boolean.TRUE);
-        }
-      }
-    }
+    setTransparencyAvailable(!System.getProperty("java.version").startsWith("1.1"));
   }
 
   static String lastFeatureAdded;
@@ -452,6 +401,7 @@ public class FeatureRenderer implements jalview.api.FeatureRenderer
         }
 
         ffile.parseDescriptionHTML(sf, false);
+        setVisible(lastFeatureAdded); // if user edited name then make sure new type is visible
       }
       if (deleteFeature)
       {
@@ -472,36 +422,17 @@ public class FeatureRenderer implements jalview.api.FeatureRenderer
           ffile.parseDescriptionHTML(features[i], false);
         }
 
-        if (av.featuresDisplayed == null)
-        {
-          av.featuresDisplayed = new Hashtable();
-        }
-
-        if (featureGroups == null)
-        {
-          featureGroups = new Hashtable();
-        }
-
         col = colourPanel.getBackground();
         // setColour(lastFeatureAdded, fcol);
 
         if (lastFeatureGroupAdded != null)
         {
-          featureGroups.put(lastFeatureGroupAdded, new Boolean(true));
-        }
-        if (fcol instanceof Color)
-        {
-          setColour(lastFeatureAdded, fcol);
+          setGroupVisibility(lastFeatureGroupAdded, true);
         }
-        av.featuresDisplayed.put(lastFeatureAdded,
-                getFeatureStyle(lastFeatureAdded));
-
-        findAllFeatures();
-
-        String[] tro = new String[renderOrder.length];
-        tro[0] = renderOrder[renderOrder.length - 1];
-        System.arraycopy(renderOrder, 0, tro, 1, renderOrder.length - 1);
-        renderOrder = tro;
+        setColour(lastFeatureAdded, fcol);
+        setVisible(lastFeatureAdded);
+        findAllFeatures(false); // different to original applet behaviour ? 
+        // findAllFeatures();
       }
       else
       {
@@ -510,9 +441,9 @@ public class FeatureRenderer implements jalview.api.FeatureRenderer
       }
     }
     // refresh the alignment and the feature settings dialog
-    if (av.featureSettings != null)
+    if (((jalview.appletgui.AlignViewport) av).featureSettings != null)
     {
-      av.featureSettings.refreshTable();
+      ((jalview.appletgui.AlignViewport) av).featureSettings.refreshTable();
     }
     // findAllFeatures();
 
@@ -520,759 +451,4 @@ public class FeatureRenderer implements jalview.api.FeatureRenderer
 
     return true;
   }
-
-  public Color findFeatureColour(Color initialCol, SequenceI seq, int i)
-  {
-    overview = true;
-    if (!av.showSequenceFeatures)
-    {
-      return initialCol;
-    }
-
-    lastSeq = seq;
-    sequenceFeatures = lastSeq.getSequenceFeatures();
-    if (sequenceFeatures == null)
-    {
-      return initialCol;
-    }
-
-    sfSize = sequenceFeatures.length;
-
-    if (jalview.util.Comparison.isGap(lastSeq.getCharAt(i)))
-    {
-      return Color.white;
-    }
-
-    currentColour = null;
-
-    drawSequence(null, lastSeq, lastSeq.findPosition(i), -1, -1);
-
-    if (currentColour == null)
-    {
-      return initialCol;
-    }
-
-    return new Color(((Integer) currentColour).intValue());
-  }
-
-  /**
-   * This is used by the Molecule Viewer to get the accurate colour of the
-   * rendered sequence
-   */
-  boolean overview = false;
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param g
-   *          DOCUMENT ME!
-   * @param seq
-   *          DOCUMENT ME!
-   * @param sg
-   *          DOCUMENT ME!
-   * @param start
-   *          DOCUMENT ME!
-   * @param end
-   *          DOCUMENT ME!
-   * @param x1
-   *          DOCUMENT ME!
-   * @param y1
-   *          DOCUMENT ME!
-   * @param width
-   *          DOCUMENT ME!
-   * @param height
-   *          DOCUMENT ME!
-   */
-  // String type;
-  // SequenceFeature sf;
-  SequenceI lastSeq;
-
-  SequenceFeature[] sequenceFeatures;
-
-  int sfSize, sfindex, spos, epos;
-
-  synchronized public void drawSequence(Graphics g, SequenceI seq,
-          int start, int end, int y1)
-  {
-    if (seq.getSequenceFeatures() == null
-            || seq.getSequenceFeatures().length == 0)
-    {
-      return;
-    }
-
-    if (transparencySetter != null && g != null)
-    {
-      transparencySetter.setTransparency(g, transparency);
-    }
-
-    if (lastSeq == null || seq != lastSeq
-            || sequenceFeatures != seq.getSequenceFeatures())
-    {
-      lastSeq = seq;
-      sequenceFeatures = seq.getSequenceFeatures();
-      sfSize = sequenceFeatures.length;
-    }
-
-    if (av.featuresDisplayed == null || renderOrder == null)
-    {
-      findAllFeatures();
-      if (av.featuresDisplayed.size() < 1)
-      {
-        return;
-      }
-
-      sequenceFeatures = seq.getSequenceFeatures();
-      sfSize = sequenceFeatures.length;
-    }
-    if (!overview)
-    {
-      spos = lastSeq.findPosition(start);
-      epos = lastSeq.findPosition(end);
-      if (g != null)
-      {
-        fm = g.getFontMetrics();
-      }
-    }
-    String type;
-    for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
-    {
-      type = renderOrder[renderIndex];
-      if (!av.featuresDisplayed.containsKey(type))
-      {
-        continue;
-      }
-
-      // loop through all features in sequence to find
-      // current feature to render
-      for (sfindex = 0; sfindex < sfSize; sfindex++)
-      {
-        if (!sequenceFeatures[sfindex].type.equals(type))
-        {
-          continue;
-        }
-
-        if (featureGroups != null
-                && sequenceFeatures[sfindex].featureGroup != null
-                && featureGroups
-                        .containsKey(sequenceFeatures[sfindex].featureGroup)
-                && !((Boolean) featureGroups
-                        .get(sequenceFeatures[sfindex].featureGroup))
-                        .booleanValue())
-        {
-          continue;
-        }
-
-        if (!overview
-                && (sequenceFeatures[sfindex].getBegin() > epos || sequenceFeatures[sfindex]
-                        .getEnd() < spos))
-        {
-          continue;
-        }
-
-        if (overview)
-        {
-          if (sequenceFeatures[sfindex].begin <= start
-                  && sequenceFeatures[sfindex].end >= start)
-          {
-            currentColour = new Integer(
-                    getColour(sequenceFeatures[sfindex]).getRGB());// av.featuresDisplayed
-            // .get(sequenceFeatures[sfindex].type);
-          }
-
-        }
-        else if (sequenceFeatures[sfindex].type.equals("disulfide bond"))
-        {
-
-          renderFeature(g, seq,
-                  seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
-                  seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
-                  getColour(sequenceFeatures[sfindex])
-                  // new Color(((Integer) av.featuresDisplayed
-                  // .get(sequenceFeatures[sfindex].type)).intValue())
-                  , start, end, y1);
-          renderFeature(g, seq,
-                  seq.findIndex(sequenceFeatures[sfindex].end) - 1,
-                  seq.findIndex(sequenceFeatures[sfindex].end) - 1,
-                  getColour(sequenceFeatures[sfindex])
-                  // new Color(((Integer) av.featuresDisplayed
-                  // .get(sequenceFeatures[sfindex].type)).intValue())
-                  , start, end, y1);
-
-        }
-        else
-        {
-          if (showFeature(sequenceFeatures[sfindex]))
-          {
-            renderFeature(g, seq,
-                    seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
-                    seq.findIndex(sequenceFeatures[sfindex].end) - 1,
-                    getColour(sequenceFeatures[sfindex]), start, end, y1);
-          }
-        }
-
-      }
-    }
-
-    if (transparencySetter != null && g != null)
-    {
-      transparencySetter.setTransparency(g, 1.0f);
-    }
-  }
-
-  char s;
-
-  int i;
-
-  void renderFeature(Graphics g, SequenceI seq, int fstart, int fend,
-          Color featureColour, int start, int end, int y1)
-  {
-
-    if (((fstart <= end) && (fend >= start)))
-    {
-      if (fstart < start)
-      { // fix for if the feature we have starts before the sequence start,
-        fstart = start; // but the feature end is still valid!!
-      }
-
-      if (fend >= end)
-      {
-        fend = end;
-      }
-
-      for (i = fstart; i <= fend; i++)
-      {
-        s = seq.getCharAt(i);
-
-        if (jalview.util.Comparison.isGap(s))
-        {
-          continue;
-        }
-
-        g.setColor(featureColour);
-
-        g.fillRect((i - start) * av.charWidth, y1, av.charWidth,
-                av.charHeight);
-
-        if (!av.validCharWidth)
-        {
-          continue;
-        }
-
-        g.setColor(Color.white);
-        charOffset = (av.charWidth - fm.charWidth(s)) / 2;
-        g.drawString(String.valueOf(s), charOffset
-                + (av.charWidth * (i - start)), (y1 + av.charHeight)
-                - av.charHeight / 5); // pady = height / 5;
-
-      }
-    }
-  }
-
-  Hashtable minmax = null;
-
-  /**
-   * Called when alignment in associated view has new/modified features to
-   * discover and display.
-   * 
-   */
-  public void featuresAdded()
-  {
-    lastSeq = null;
-    findAllFeatures();
-  }
-
-  /**
-   * find all features on the alignment
-   */
-  void findAllFeatures()
-  {
-    jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme();
-
-    av.featuresDisplayed = new Hashtable();
-    Vector allfeatures = new Vector();
-    minmax = new Hashtable();
-    AlignmentI alignment = av.getAlignment();
-    for (int i = 0; i < alignment.getHeight(); i++)
-    {
-      SequenceFeature[] features = alignment.getSequenceAt(i)
-              .getSequenceFeatures();
-
-      if (features == null)
-      {
-        continue;
-      }
-
-      int index = 0;
-      while (index < features.length)
-      {
-        if (features[index].begin == 0 && features[index].end == 0)
-        {
-          index++;
-          continue;
-        }
-        if (!av.featuresDisplayed.containsKey(features[index].getType()))
-        {
-          if (getColour(features[index].getType()) == null)
-          {
-            featureColours.put(features[index].getType(),
-                    ucs.createColourFromName(features[index].getType()));
-          }
-
-          av.featuresDisplayed.put(features[index].getType(), new Integer(
-                  getColour(features[index].getType()).getRGB()));
-          allfeatures.addElement(features[index].getType());
-        }
-        if (features[index].score != Float.NaN)
-        {
-          int nonpos = features[index].getBegin() >= 1 ? 0 : 1;
-          float[][] mm = (float[][]) minmax.get(features[index].getType());
-          if (mm == null)
-          {
-            mm = new float[][]
-            { null, null };
-            minmax.put(features[index].getType(), mm);
-          }
-          if (mm[nonpos] == null)
-          {
-            mm[nonpos] = new float[]
-            { features[index].score, features[index].score };
-
-          }
-          else
-          {
-            if (mm[nonpos][0] > features[index].score)
-            {
-              mm[nonpos][0] = features[index].score;
-            }
-            if (mm[nonpos][1] < features[index].score)
-            {
-              mm[nonpos][1] = features[index].score;
-            }
-          }
-        }
-
-        index++;
-      }
-    }
-
-    renderOrder = new String[allfeatures.size()];
-    Enumeration en = allfeatures.elements();
-    int i = allfeatures.size() - 1;
-    while (en.hasMoreElements())
-    {
-      renderOrder[i] = en.nextElement().toString();
-      i--;
-    }
-  }
-
-  /**
-   * get a feature style object for the given type string. Creates a
-   * java.awt.Color for a featureType with no existing colourscheme. TODO:
-   * replace return type with object implementing standard abstract colour/style
-   * interface
-   * 
-   * @param featureType
-   * @return java.awt.Color or GraduatedColor
-   */
-  public Object getFeatureStyle(String featureType)
-  {
-    Object fc = featureColours.get(featureType);
-    if (fc == null)
-    {
-      jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme();
-      Color col = ucs.createColourFromName(featureType);
-      featureColours.put(featureType, fc = col);
-    }
-    return fc;
-  }
-
-  public Color getColour(String featureType)
-  {
-    Object fc = getFeatureStyle(featureType);
-
-    if (fc instanceof Color)
-    {
-      return (Color) fc;
-    }
-    else
-    {
-      if (fc instanceof GraduatedColor)
-      {
-        return ((GraduatedColor) fc).getMaxColor();
-      }
-    }
-    throw new Error(MessageManager.formatMessage("error.implementation_error_unrecognised_render_object_for_features_type", new String[]{fc.getClass().getCanonicalName(),featureType}));
-  }
-
-  /**
-   * 
-   * @param sequenceFeature
-   * @return true if feature is visible.
-   */
-  private boolean showFeature(SequenceFeature sequenceFeature)
-  {
-    Object fc = getFeatureStyle(sequenceFeature.type);
-    if (fc instanceof GraduatedColor)
-    {
-      return ((GraduatedColor) fc).isColored(sequenceFeature);
-    }
-    else
-    {
-      return true;
-    }
-  }
-
-  /**
-   * implement graduated colouring for features with scores
-   * 
-   * @param feature
-   * @return render colour for the given feature
-   */
-  public Color getColour(SequenceFeature feature)
-  {
-    Object fc = getFeatureStyle(feature.getType());
-    if (fc instanceof Color)
-    {
-      return (Color) fc;
-    }
-    else
-    {
-      if (fc instanceof GraduatedColor)
-      {
-        return ((GraduatedColor) fc).findColor(feature);
-      }
-    }
-    throw new Error("Implementation Error: Unrecognised render object "
-            + fc.getClass() + " for features of type " + feature.getType());
-  }
-
-  public void setColour(String featureType, Object col)
-  {
-    // overwrite
-    // Color _col = (col instanceof Color) ? ((Color) col) : (col instanceof
-    // GraduatedColor) ? ((GraduatedColor) col).getMaxColor() : null;
-    // Object c = featureColours.get(featureType);
-    // if (c == null || c instanceof Color || (c instanceof GraduatedColor &&
-    // !((GraduatedColor)c).getMaxColor().equals(_col)))
-    {
-      featureColours.put(featureType, col);
-    }
-  }
-
-  public void setFeaturePriority(Object[][] data)
-  {
-    // The feature table will display high priority
-    // features at the top, but theses are the ones
-    // we need to render last, so invert the data
-    if (av.featuresDisplayed != null)
-    {
-      av.featuresDisplayed.clear();
-    }
-
-    /*
-     * if (visibleNew) { if (av.featuresDisplayed != null) {
-     * av.featuresDisplayed.clear(); } else { av.featuresDisplayed = new
-     * Hashtable(); } } if (data == null) { return; }
-     */
-
-    renderOrder = new String[data.length];
-
-    if (data.length > 0)
-    {
-      for (int i = 0; i < data.length; i++)
-      {
-        String type = data[i][0].toString();
-        setColour(type, data[i][1]);
-        if (((Boolean) data[i][2]).booleanValue())
-        {
-          av.featuresDisplayed.put(type, new Integer(getColour(type)
-                  .getRGB()));
-        }
-
-        renderOrder[data.length - i - 1] = type;
-      }
-    }
-  }
-
-  /**
-   * @return a simple list of feature group names or null
-   */
-  public String[] getGroups()
-  {
-    buildGroupHash();
-    if (featureGroups != null)
-    {
-      String[] gps = new String[featureGroups.size()];
-      Enumeration gn = featureGroups.keys();
-      int i = 0;
-      while (gn.hasMoreElements())
-      {
-        gps[i++] = (String) gn.nextElement();
-      }
-      return gps;
-    }
-    return null;
-  }
-
-  /**
-   * get visible or invisible groups
-   * 
-   * @param visible
-   *          true to return visible groups, false to return hidden ones.
-   * @return list of groups
-   */
-  public String[] getGroups(boolean visible)
-  {
-    buildGroupHash();
-    if (featureGroups != null)
-    {
-      Vector gp = new Vector();
-
-      Enumeration gn = featureGroups.keys();
-      while (gn.hasMoreElements())
-      {
-        String nm = (String) gn.nextElement();
-        Boolean state = (Boolean) featureGroups.get(nm);
-        if (state.booleanValue() == visible)
-        {
-          gp.addElement(nm);
-        }
-      }
-      String[] gps = new String[gp.size()];
-      gp.copyInto(gps);
-
-      int i = 0;
-      while (gn.hasMoreElements())
-      {
-        gps[i++] = (String) gn.nextElement();
-      }
-      return gps;
-    }
-    return null;
-  }
-
-  /**
-   * set all feature groups in toset to be visible or invisible
-   * 
-   * @param toset
-   *          group names
-   * @param visible
-   *          the state of the named groups to set
-   */
-  public void setGroupState(String[] toset, boolean visible)
-  {
-    buildGroupHash();
-    if (toset != null && toset.length > 0 && featureGroups != null)
-    {
-      boolean rdrw = false;
-      for (int i = 0; i < toset.length; i++)
-      {
-        Object st = featureGroups.get(toset[i]);
-        featureGroups.put(toset[i], new Boolean(visible));
-        if (st != null)
-        {
-          rdrw = rdrw || (visible != ((Boolean) st).booleanValue());
-        }
-      }
-      if (rdrw)
-      {
-        if (this.av != null)
-          if (this.av.featureSettings != null)
-          {
-            av.featureSettings.rebuildGroups();
-            this.av.featureSettings.resetTable(true);
-          }
-          else
-          {
-            buildFeatureHash();
-          }
-        if (av != null)
-        {
-          av.alignmentChanged(null);
-        }
-      }
-    }
-  }
-
-  ArrayList<String> hiddenGroups = new ArrayList<String>();
-
-  /**
-   * analyse alignment for groups and hash tables (used to be embedded in
-   * FeatureSettings.setTableData)
-   * 
-   * @return true if features are on the alignment
-   */
-  public boolean buildGroupHash()
-  {
-    boolean alignmentHasFeatures = false;
-    if (featureGroups == null)
-    {
-      featureGroups = new Hashtable();
-    }
-    hiddenGroups = new ArrayList<String>();
-    hiddenGroups.addAll(featureGroups.keySet());
-    ArrayList allFeatures = new ArrayList();
-    ArrayList allGroups = new ArrayList();
-    SequenceFeature[] tmpfeatures;
-    String group;
-    AlignmentI alignment = av.getAlignment();
-    for (int i = 0; i < alignment.getHeight(); i++)
-    {
-      if (alignment.getSequenceAt(i).getSequenceFeatures() == null)
-      {
-        continue;
-      }
-
-      alignmentHasFeatures = true;
-
-      tmpfeatures = alignment.getSequenceAt(i).getSequenceFeatures();
-      int index = 0;
-      while (index < tmpfeatures.length)
-      {
-        if (tmpfeatures[index].getFeatureGroup() != null)
-        {
-          group = tmpfeatures[index].featureGroup;
-          // Remove group from the hiddenGroup list
-          hiddenGroups.remove(group);
-          if (!allGroups.contains(group))
-          {
-            allGroups.add(group);
-
-            boolean visible = true;
-            if (featureGroups.containsKey(group))
-            {
-              visible = ((Boolean) featureGroups.get(group)).booleanValue();
-            }
-            else
-            {
-              featureGroups.put(group, new Boolean(visible));
-            }
-          }
-        }
-
-        if (!allFeatures.contains(tmpfeatures[index].getType()))
-        {
-          allFeatures.add(tmpfeatures[index].getType());
-        }
-        index++;
-      }
-    }
-
-    return alignmentHasFeatures;
-  }
-
-  /**
-   * rebuild the featuresDisplayed and renderorder list based on the
-   * featureGroups hash and any existing display state and force a repaint if
-   * necessary
-   * 
-   * @return true if alignment has visible features
-   */
-  public boolean buildFeatureHash()
-  {
-    boolean alignmentHasFeatures = false;
-    if (featureGroups == null)
-    {
-      alignmentHasFeatures = buildGroupHash();
-    }
-    if (!alignmentHasFeatures)
-      return false;
-    Hashtable fdisp = av.featuresDisplayed;
-    Vector allFeatures = new Vector();
-    SequenceFeature[] tmpfeatures;
-    String group;
-    AlignmentI alignment = av.getAlignment();
-    for (int i = 0; i < alignment.getHeight(); i++)
-    {
-      if (alignment.getSequenceAt(i).getSequenceFeatures() == null)
-      {
-        continue;
-      }
-
-      alignmentHasFeatures = true;
-
-      tmpfeatures = alignment.getSequenceAt(i).getSequenceFeatures();
-      int index = 0;
-      while (index < tmpfeatures.length)
-      {
-        boolean visible = true;
-        if (tmpfeatures[index].getFeatureGroup() != null)
-        {
-          group = tmpfeatures[index].featureGroup;
-          if (featureGroups.containsKey(group))
-          {
-            visible = ((Boolean) featureGroups.get(group)).booleanValue();
-          }
-        }
-
-        if (visible && !allFeatures.contains(tmpfeatures[index].getType()))
-        {
-          allFeatures.addElement(tmpfeatures[index].getType());
-        }
-        index++;
-      }
-    }
-    if (allFeatures.size() > 0)
-    {
-      String[] neworder = new String[allFeatures.size()];
-      int p = neworder.length - 1;
-      for (int i = renderOrder.length - 1; i >= 0; i--)
-      {
-        if (allFeatures.contains(renderOrder[i]))
-        {
-          neworder[p--] = renderOrder[i];
-          allFeatures.removeElement(renderOrder[i]);
-        }
-        else
-        {
-          av.featuresDisplayed.remove(renderOrder[i]);
-        }
-      }
-      for (int i = allFeatures.size() - 1; i > 0; i++)
-      {
-        Object e = allFeatures.elementAt(i);
-        if (e != null)
-        {
-          neworder[p--] = (String) e;
-          av.featuresDisplayed.put(e, getColour((String) e));
-        }
-      }
-      renderOrder = neworder;
-      return true;
-    }
-
-    return alignmentHasFeatures;
-  }
-
-  /**
-   * 
-   * @return the displayed feature type as an array of strings
-   */
-  protected String[] getDisplayedFeatureTypes()
-  {
-    String[] typ = null;
-    synchronized (renderOrder)
-    {
-      typ = new String[renderOrder.length];
-      System.arraycopy(renderOrder, 0, typ, 0, typ.length);
-      for (int i = 0; i < typ.length; i++)
-      {
-        if (av.featuresDisplayed.get(typ[i]) == null)
-        {
-          typ[i] = null;
-        }
-      }
-    }
-    return typ;
-  }
-}
-
-class TransparencySetter
-{
-  void setTransparency(Graphics g, float value)
-  {
-    Graphics2D g2 = (Graphics2D) g;
-    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
-            value));
-  }
 }
index cd11a35..4b29f53 100755 (executable)
@@ -21,7 +21,7 @@
 package jalview.appletgui;
 
 import java.util.*;
-
+import java.util.List;
 import java.awt.*;
 import java.awt.event.*;
 
@@ -50,8 +50,6 @@ public class FeatureSettings extends Panel implements ItemListener,
 
   ScrollPane scrollPane;
 
-  boolean alignmentHasFeatures = false;
-
   Image linkImage;
 
   Scrollbar transparency;
@@ -64,9 +62,9 @@ public class FeatureSettings extends Panel implements ItemListener,
     fr = ap.seqPanel.seqCanvas.getFeatureRenderer();
 
     transparency = new Scrollbar(Scrollbar.HORIZONTAL,
-            100 - (int) (fr.transparency * 100), 1, 1, 100);
+            100 - (int) (fr.getTransparency() * 100), 1, 1, 100);
 
-    if (fr.transparencySetter != null)
+    if (fr.isTransparencyAvailable())
     {
       transparency.addAdjustmentListener(this);
     }
@@ -81,9 +79,9 @@ public class FeatureSettings extends Panel implements ItemListener,
       linkImage = java.awt.Toolkit.getDefaultToolkit().getImage(url);
     }
 
-    if (av.featuresDisplayed == null)
+    if (av.isShowSequenceFeatures() || !fr.hasRenderOrder())
     {
-      fr.findAllFeatures();
+      fr.findAllFeatures(true); // was default - now true to make all visible
     }
 
     setTableData();
@@ -91,7 +89,7 @@ public class FeatureSettings extends Panel implements ItemListener,
     this.setLayout(new BorderLayout());
     scrollPane = new ScrollPane();
     scrollPane.add(featurePanel);
-    if (alignmentHasFeatures)
+    if (fr.getAllFeatureColours()!=null && fr.getAllFeatureColours().size()>0)
     {
       add(scrollPane, BorderLayout.CENTER);
     }
@@ -104,7 +102,7 @@ public class FeatureSettings extends Panel implements ItemListener,
 
     Panel tPanel = new Panel(new BorderLayout());
 
-    if (fr.transparencySetter != null)
+    if (fr.isTransparencyAvailable())
     {
       tPanel.add(transparency, BorderLayout.CENTER);
       tPanel.add(new Label("Transparency"), BorderLayout.EAST);
@@ -124,8 +122,8 @@ public class FeatureSettings extends Panel implements ItemListener,
     {
       groupPanel
               .setLayout(new GridLayout(
-                      (fr.featureGroups.size() - fr.hiddenGroups.size()) / 4 + 1,
-                      4));
+                      (fr.getFeatureGroupsSize()) / 4 + 1,
+                      4)); // JBPNote - this was scaled on number of visible groups. seems broken
       groupPanel.validate();
 
       add(groupPanel, BorderLayout.NORTH);
@@ -185,7 +183,7 @@ public class FeatureSettings extends Panel implements ItemListener,
 
       public void actionPerformed(ActionEvent e)
       {
-        me.sortByScore(new String[]
+        me.ap.alignFrame.avc.sortAlignmentByFeatureScore(new String[]
         { type });
       }
 
@@ -197,7 +195,7 @@ public class FeatureSettings extends Panel implements ItemListener,
 
       public void actionPerformed(ActionEvent e)
       {
-        me.sortByDens(new String[]
+        me.ap.alignFrame.avc.sortAlignmentByFeatureDensity(new String[]
         { type });
       }
 
@@ -253,8 +251,7 @@ public class FeatureSettings extends Panel implements ItemListener,
 
   public void setTableData()
   {
-    alignmentHasFeatures = fr.buildGroupHash();
-    if (alignmentHasFeatures)
+    if (fr.getAllFeatureColours()!=null && fr.getAllFeatureColours().size()>0)
     {
       rebuildGroups();
 
@@ -279,18 +276,17 @@ public class FeatureSettings extends Panel implements ItemListener,
     }
     // TODO: JAL-964 - smoothly incorporate new group entries if panel already
     // displayed and new groups present
-    Enumeration gps = fr.featureGroups.keys();
-    while (gps.hasMoreElements())
+    for (String group:(List<String>)fr.getFeatureGroups())
     {
-      String group = (String) gps.nextElement();
-      Boolean vis = (Boolean) fr.featureGroups.get(group);
-      Checkbox check = new MyCheckbox(group, vis.booleanValue(),
+      boolean vis = fr.checkGroupVisibility(group, false);
+      Checkbox check = new MyCheckbox(group, vis,
               (fr.featureLinks != null && fr.featureLinks
                       .containsKey(group)));
       check.addMouseListener(this);
       check.setFont(new Font("Serif", Font.BOLD, 12));
-      check.addItemListener(this);
-      check.setVisible(fr.hiddenGroups.contains(group));
+      check.addItemListener(groupItemListener);
+      // note - visibility seems to correlate with displayed. ???wtf ??
+      check.setVisible(vis);
       groupPanel.add(check);
     }
     if (rdrw)
@@ -298,7 +294,6 @@ public class FeatureSettings extends Panel implements ItemListener,
       groupPanel.validate();
     }
   }
-
   // This routine adds and removes checkboxes depending on
   // Group selection states
   void resetTable(boolean groupsChanged)
@@ -320,8 +315,7 @@ public class FeatureSettings extends Panel implements ItemListener,
       {
         group = tmpfeatures[index].featureGroup;
 
-        if (group == null || fr.featureGroups.get(group) == null
-                || ((Boolean) fr.featureGroups.get(group)).booleanValue())
+        if (group == null || fr.checkGroupVisibility(group, true))
         {
           type = tmpfeatures[index].getType();
           if (!visibleChecks.contains(type))
@@ -350,13 +344,14 @@ public class FeatureSettings extends Panel implements ItemListener,
       }
     }
 
-    if (fr.renderOrder != null)
+    if (fr.getRenderOrder() != null)
     {
       // First add the checks in the previous render order,
       // in case the window has been closed and reopened
-      for (int ro = fr.renderOrder.length - 1; ro > -1; ro--)
+      List<String> rol = fr.getRenderOrder();
+      for (int ro = rol.size() - 1; ro > -1; ro--)
       {
-        String item = fr.renderOrder[ro];
+        String item = rol.get(ro);
 
         if (!visibleChecks.contains(item))
         {
@@ -418,7 +413,7 @@ public class FeatureSettings extends Panel implements ItemListener,
     if (addCheck)
     {
       boolean selected = false;
-      if (groupsChanged || av.featuresDisplayed.containsKey(type))
+      if (groupsChanged || av.getFeaturesDisplayed().isVisible(type))
       {
         selected = true;
       }
@@ -455,26 +450,22 @@ public class FeatureSettings extends Panel implements ItemListener,
     selectionChanged();
   }
 
-  public void itemStateChanged(ItemEvent evt)
-  {
-    if (evt != null)
-    {
-      // Is the source a top level featureGroup?
+  private ItemListener groupItemListener = new ItemListener() {
+    public void itemStateChanged(ItemEvent evt) {
       Checkbox source = (Checkbox) evt.getSource();
-      if (fr.featureGroups.containsKey(source.getLabel()))
+      fr.setGroupVisibility(source.getLabel(),
+              source.getState());
+      ap.seqPanel.seqCanvas.repaint();
+      if (ap.overviewPanel != null)
       {
-        fr.featureGroups.put(source.getLabel(),
-                new Boolean(source.getState()));
-        ap.seqPanel.seqCanvas.repaint();
-        if (ap.overviewPanel != null)
-        {
-          ap.overviewPanel.updateOverviewImage();
-        }
-
-        resetTable(true);
-        return;
+        ap.overviewPanel.updateOverviewImage();
       }
-    }
+      resetTable(true);
+      return;
+    };
+  };
+  public void itemStateChanged(ItemEvent evt)
+  {
     selectionChanged();
   }
 
@@ -617,7 +608,7 @@ public class FeatureSettings extends Panel implements ItemListener,
     MyCheckbox check = (MyCheckbox) evt.getSource();
     if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) != 0)
     {
-      this.popupSort(check, fr.minmax, evt.getX(), evt.getY());
+      this.popupSort(check, fr.getMinMax(), evt.getX(), evt.getY());
     }
     if (fr.featureLinks != null && fr.featureLinks.containsKey(check.type))
     {
@@ -657,7 +648,7 @@ public class FeatureSettings extends Panel implements ItemListener,
 
   public void adjustmentValueChanged(AdjustmentEvent evt)
   {
-    fr.transparency = ((float) (100 - transparency.getValue()) / 100f);
+    fr.setTransparency((float) (100 - transparency.getValue()) / 100f);
     ap.seqPanel.seqCanvas.repaint();
 
   }
@@ -771,81 +762,4 @@ public class FeatureSettings extends Panel implements ItemListener,
     }
   }
 
-  protected void sortByDens(String[] typ)
-  {
-    sortBy(typ, "Sort by Density", AlignmentSorter.FEATURE_DENSITY);
-  }
-
-  private String[] getDisplayedFeatureTypes()
-  {
-    String[] typ = null;
-    if (fr != null)
-    {
-      synchronized (fr.renderOrder)
-      {
-        typ = new String[fr.renderOrder.length];
-        System.arraycopy(fr.renderOrder, 0, typ, 0, typ.length);
-        for (int i = 0; i < typ.length; i++)
-        {
-          if (av.featuresDisplayed.get(typ[i]) == null)
-          {
-            typ[i] = null;
-          }
-        }
-      }
-    }
-    return typ;
-  }
-
-  protected void sortBy(String[] typ, String methodText, final String method)
-  {
-    if (typ == null)
-    {
-      typ = getDisplayedFeatureTypes();
-    }
-    String gps[] = null;
-    gps = fr.getGroups(true);
-    if (typ != null)
-    {
-      for (int i = 0; i < typ.length; i++)
-      {
-        System.err.println("Sorting on Types:" + typ[i]);
-      }
-    }
-    if (gps != null)
-    {
-
-      for (int i = 0; i < gps.length; i++)
-      {
-        System.err.println("Sorting on groups:" + gps[i]);
-      }
-    }
-    AlignmentPanel alignPanel = ap;
-    AlignmentI al = alignPanel.av.getAlignment();
-
-    int start, stop;
-    SequenceGroup sg = alignPanel.av.getSelectionGroup();
-    if (sg != null)
-    {
-      start = sg.getStartRes();
-      stop = sg.getEndRes();
-    }
-    else
-    {
-      start = 0;
-      stop = al.getWidth();
-    }
-    SequenceI[] oldOrder = al.getSequencesArray();
-    AlignmentSorter.sortByFeature(typ, gps, start, stop, al, method);
-    this.ap.alignFrame.addHistoryItem(new OrderCommand(methodText,
-            oldOrder, alignPanel.av.getAlignment()));
-    alignPanel.paintAlignment(true);
-
-  }
-
-  protected void sortByScore(String[] typ)
-  {
-    sortBy(typ, "Sort by Feature Score", AlignmentSorter.FEATURE_SCORE);
-  }
-
 }
index 6ca6ddf..4796bca 100644 (file)
@@ -113,7 +113,7 @@ public class Finder extends Panel implements ActionListener
             features, true, ap))
     {
       ap.alignFrame.sequenceFeatures.setState(true);
-      av.showSequenceFeatures(true);
+      av.setShowSequenceFeatures(true);
       ap.highlightSearchResults(null);
     }
   }
index 25aa315..0ac4e55 100755 (executable)
@@ -227,7 +227,7 @@ public class IdCanvas extends Panel
 
           SequenceI s = av.getAlignment().getSequenceAt(i);
           gg.setFont(italic);
-          if (av.hasHiddenRows())
+          if (av.isDisplayReferenceSeq() || av.hasHiddenRows())
           {
             setHiddenFont(s);
           }
@@ -257,7 +257,7 @@ public class IdCanvas extends Panel
         }
         gg.setFont(italic);
         // boolean isrep=false;
-        if (av.hasHiddenRows())
+        if (av.isDisplayReferenceSeq() || av.hasHiddenRows())
         {
           // isrep =
           setHiddenFont(seq);
@@ -366,8 +366,7 @@ public class IdCanvas extends Panel
     Font bold = new Font(av.getFont().getName(), Font.BOLD, av.getFont()
             .getSize());
 
-    if (av.getHiddenRepSequences() != null
-            && av.getHiddenRepSequences().containsKey(seq))
+    if (av.isHiddenRepSequence(seq))
     {
       gg.setFont(bold);
       return true;
index 2c2c41a..8486fe0 100755 (executable)
@@ -69,7 +69,6 @@ public class OverviewPanel extends Panel implements Runnable,
     sr.renderGaps = false;
     sr.forOverview = true;
     fr = new FeatureRenderer(av);
-    fr.overview = true;
 
     // scale the initial size of overviewpanel to shape of alignment
     float initialScale = (float) av.getAlignment().getWidth()
@@ -229,10 +228,9 @@ public class OverviewPanel extends Panel implements Runnable,
       return;
     }
 
-    if (av.showSequenceFeatures)
+    if (av.isShowSequenceFeatures())
     {
-      fr.featureGroups = ap.seqPanel.seqCanvas.getFeatureRenderer().featureGroups;
-      fr.featureColours = ap.seqPanel.seqCanvas.getFeatureRenderer().featureColours;
+      fr.transferSettings(ap.seqPanel.seqCanvas.fr);
     }
 
     resizing = true;
@@ -260,7 +258,7 @@ public class OverviewPanel extends Panel implements Runnable,
     int alwidth = av.getAlignment().getWidth();
     int alheight = av.getAlignment().getHeight();
 
-    if (av.showSequenceFeatures)
+    if (av.isShowSequenceFeatures())
     {
       fr.transferSettings(ap.seqPanel.seqCanvas.getFeatureRenderer());
     }
@@ -337,7 +335,7 @@ public class OverviewPanel extends Panel implements Runnable,
         {
           color = sr.getResidueBoxColour(seq, lastcol);
 
-          if (av.showSequenceFeatures)
+          if (av.isShowSequenceFeatures())
           {
             color = fr.findFeatureColour(color, seq, lastcol);
           }
index 60d89aa..2b6f37a 100755 (executable)
  */
 package jalview.appletgui;
 
-import java.awt.*;
-import java.awt.event.*;
-
-import jalview.datamodel.*;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.SequenceGroup;
 import jalview.util.MessageManager;
 
+import java.awt.Color;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.MenuItem;
+import java.awt.Panel;
+import java.awt.PopupMenu;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+
 public class ScalePanel extends Panel implements MouseMotionListener,
         MouseListener
 {
@@ -97,7 +108,7 @@ public class ScalePanel extends Panel implements MouseMotionListener,
         });
         pop.add(item);
 
-        if (av.getColumnSelection().getHiddenColumns().size() > 1)
+        if (av.getColumnSelection().hasManyHiddenColumns())
         {
           item = new MenuItem(MessageManager.getString("action.reveal_all"));
           item.addActionListener(new ActionListener()
@@ -323,10 +334,8 @@ public class ScalePanel extends Panel implements MouseMotionListener,
     res = av.getColumnSelection().adjustForHiddenColumns(res);
 
     reveal = null;
-    for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
+    for (int[] region : av.getColumnSelection().getHiddenColumns())
     {
-      int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
-              .elementAt(i);
       if (res + 1 == region[0] || res - 1 == region[1])
       {
         reveal = region;
@@ -407,18 +416,18 @@ public class ScalePanel extends Panel implements MouseMotionListener,
         }
 
         gg.drawLine(
-                (int) (((i - startx - 1) * av.charWidth) + (av.charWidth / 2)),
+                ((i - startx - 1) * av.charWidth) + (av.charWidth / 2),
                 y + 2,
-                (int) (((i - startx - 1) * av.charWidth) + (av.charWidth / 2)),
+                ((i - startx - 1) * av.charWidth) + (av.charWidth / 2),
                 y + (fm.getDescent() * 2));
 
       }
       else
       {
         gg.drawLine(
-                (int) (((i - startx - 1) * av.charWidth) + (av.charWidth / 2)),
+                ((i - startx - 1) * av.charWidth) + (av.charWidth / 2),
                 y + fm.getDescent(),
-                (int) (((i - startx - 1) * av.charWidth) + (av.charWidth / 2)),
+                ((i - startx - 1) * av.charWidth) + (av.charWidth / 2),
                 y + (fm.getDescent() * 2));
       }
     }
index 33caf53..3d1f7e2 100755 (executable)
  */
 package jalview.appletgui;
 
-import java.awt.*;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SearchResults;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
 
-import jalview.datamodel.*;
+import java.awt.Color;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.Panel;
 
 public class SeqCanvas extends Panel
 {
@@ -510,49 +517,52 @@ public class SeqCanvas extends Panel
   void drawPanel(Graphics g1, int startRes, int endRes, int startSeq,
           int endSeq, int offset)
   {
+
+
     if (!av.hasHiddenColumns())
     {
       draw(g1, startRes, endRes, startSeq, endSeq, offset);
     }
     else
     {
-      java.util.Vector regions = av.getColumnSelection().getHiddenColumns();
 
       int screenY = 0;
       int blockStart = startRes;
       int blockEnd = endRes;
 
-      for (int i = 0; i < regions.size(); i++)
+      if (av.hasHiddenColumns())
       {
-        int[] region = (int[]) regions.elementAt(i);
-        int hideStart = region[0];
-        int hideEnd = region[1];
-
-        if (hideStart <= blockStart)
+        for (int[] region : av.getColumnSelection().getHiddenColumns())
         {
-          blockStart += (hideEnd - hideStart) + 1;
-          continue;
-        }
+          int hideStart = region[0];
+          int hideEnd = region[1];
 
-        blockEnd = hideStart - 1;
+          if (hideStart <= blockStart)
+          {
+            blockStart += (hideEnd - hideStart) + 1;
+            continue;
+          }
 
-        g1.translate(screenY * av.charWidth, 0);
+          blockEnd = hideStart - 1;
 
-        draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
+          g1.translate(screenY * av.charWidth, 0);
 
-        if (av.getShowHiddenMarkers())
-        {
-          g1.setColor(Color.blue);
-          g1.drawLine((blockEnd - blockStart + 1) * av.charWidth - 1,
-                  0 + offset, (blockEnd - blockStart + 1) * av.charWidth
-                          - 1, (endSeq - startSeq) * av.charHeight + offset);
-        }
+          draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
 
-        g1.translate(-screenY * av.charWidth, 0);
-        screenY += blockEnd - blockStart + 1;
-        blockStart = hideEnd + 1;
-      }
+          if (av.getShowHiddenMarkers())
+          {
+            g1.setColor(Color.blue);
+            g1.drawLine((blockEnd - blockStart + 1) * av.charWidth - 1,
+                    0 + offset, (blockEnd - blockStart + 1) * av.charWidth
+                            - 1, (endSeq - startSeq) * av.charHeight
+                            + offset);
+          }
 
+          g1.translate(-screenY * av.charWidth, 0);
+          screenY += blockEnd - blockStart + 1;
+          blockStart = hideEnd + 1;
+        }
+      }
       if (screenY <= (endRes - startRes))
       {
         blockEnd = blockStart + (endRes - startRes) - screenY;
@@ -589,7 +599,7 @@ public class SeqCanvas extends Panel
       sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
               startRes, endRes, offset + ((i - startSeq) * av.charHeight));
 
-      if (av.showSequenceFeatures)
+      if (av.isShowSequenceFeatures())
       {
         fr.drawSequence(g, nextSeq, startRes, endRes, offset
                 + ((i - startSeq) * av.charHeight));
@@ -646,7 +656,7 @@ public class SeqCanvas extends Panel
 
     if ((group == null) && (av.getAlignment().getGroups().size() > 0))
     {
-      group = (SequenceGroup) av.getAlignment().getGroups().get(0);
+      group = av.getAlignment().getGroups().get(0);
       groupIndex = 0;
     }
 
@@ -803,7 +813,7 @@ public class SeqCanvas extends Panel
           break;
         }
 
-        group = (SequenceGroup) av.getAlignment().getGroups()
+        group = av.getAlignment().getGroups()
                 .get(groupIndex);
       } while (groupIndex < av.getAlignment().getGroups().size());
 
index 592fd4f..a95dd27 100644 (file)
@@ -817,18 +817,14 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     {
       for (int i = 0; i < features.length; i++)
       {
-        if (av.featuresDisplayed == null
-                || !av.featuresDisplayed.containsKey(features[i].getType()))
+        if (av.getFeaturesDisplayed() == null
+                || !av.getFeaturesDisplayed().isVisible(features[i].getType()))
         {
           continue;
         }
 
         if (features[i].featureGroup != null
-                && seqCanvas.fr.featureGroups != null
-                && seqCanvas.fr.featureGroups
-                        .containsKey(features[i].featureGroup)
-                && !((Boolean) seqCanvas.fr.featureGroups
-                        .get(features[i].featureGroup)).booleanValue())
+                && !seqCanvas.fr.checkGroupVisibility(features[i].featureGroup,false))
         {
           continue;
         }
index 697c0d1..9badf43 100755 (executable)
@@ -20,6 +20,7 @@
  */
 package jalview.appletgui;
 
+import jalview.api.FeatureRenderer;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
@@ -86,6 +87,31 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
     return resBoxColour;
   }
 
+  /**
+   * Get the residue colour at the given sequence position - as determined by
+   * the sequence group colour (if any), else the colour scheme, possibly
+   * overridden by a feature colour.
+   * 
+   * @param seq
+   * @param position
+   * @param fr
+   * @return
+   */
+  @Override
+  public Color getResidueColour(final SequenceI seq, int position,
+          FeatureRenderer fr)
+  {
+    // TODO replace 8 or so code duplications with calls to this method
+    // (refactored as needed)
+    Color col = getResidueBoxColour(seq, position);
+
+    if (fr != null)
+    {
+      col = fr.findFeatureColour(col, seq, position);
+    }
+    return col;
+  }
+
   void getBoxColour(ColourSchemeI cs, SequenceI seq, int i)
   {
     if (cs != null)
@@ -204,7 +230,7 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
     }
 
     char s = ' ';
-
+    boolean srep = av.isDisplayReferenceSeq();
     for (int i = start; i <= end; i++)
     {
       graphics.setColor(Color.black);
@@ -229,9 +255,7 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
         }
         if (currentSequenceGroup.getShowNonconserved())
         {
-          // cheat - use this if we have a consensus for each group: s =
-          // getDisplayChar(currentSequenceGroup.getConsensus(), i, s, '.');
-          s = getDisplayChar(av.getAlignmentConsensusAnnotation(), i, s,
+          s = getDisplayChar(srep, i, s,
                   '.');
         }
       }
@@ -256,7 +280,7 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
         }
         if (av.getShowUnconserved())
         {
-          s = getDisplayChar(av.getAlignmentConsensusAnnotation(), i, s,
+          s = getDisplayChar(srep, i, s,
                   '.');
 
         }
@@ -289,10 +313,12 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
 
   }
 
-  private char getDisplayChar(AlignmentAnnotation consensus, int position,
+  private char getDisplayChar(final boolean usesrep, int position,
           char s, char c)
   {
-    char conschar = consensus.annotations[position].displayCharacter
+    // TODO - use currentSequenceGroup rather than alignemnt 
+    // currentSequenceGroup.getConsensus()
+    char conschar = (usesrep) ? av.getAlignment().getSeqrep().getCharAt(position) : av.getAlignmentConsensusAnnotation().annotations[position].displayCharacter
             .charAt(0);
     if (conschar != '-' && s == conschar)
     {
diff --git a/src/jalview/appletgui/TitledPanel.java b/src/jalview/appletgui/TitledPanel.java
new file mode 100644 (file)
index 0000000..1ae36f4
--- /dev/null
@@ -0,0 +1,75 @@
+package jalview.appletgui;
+
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Insets;
+import java.awt.Label;
+import java.awt.Panel;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+public class TitledPanel extends Panel
+{
+
+  private String title;
+
+  private Insets insets = new Insets(10, 10, 10, 10);
+
+  public TitledPanel()
+  {
+    this("");
+  }
+
+  public TitledPanel(String title)
+  {
+    this.setTitle(title);
+  }
+
+  public Insets getInsets()
+  {
+    return insets;
+  }
+
+  public void paint(Graphics g)
+  {
+    super.paint(g);
+    g.setColor(getForeground());
+    g.drawRect(5, 5, getWidth() - 10, getHeight() - 10);
+    int width = g.getFontMetrics().stringWidth(getTitle());
+    g.setColor(getBackground());
+    g.fillRect(10, 0, width, 10);
+    g.setColor(getForeground());
+    g.drawString(getTitle(), 10, 10);
+  }
+
+  public static void main(String[] args)
+  {
+    Frame f = new Frame("TitledPanel Tester");
+
+    TitledPanel p = new TitledPanel("Title of Panel");
+    p.add(new Label("Label 1"));
+    p.add(new Label("Label 2"));
+    p.add(new Label("Label 3"));
+    f.add(p);
+
+    f.addWindowListener(new WindowAdapter()
+    {
+      public void windowClosing(WindowEvent e)
+      {
+        System.exit(0);
+      }
+    });
+    f.setBounds(300, 300, 300, 300);
+    f.setVisible(true);
+  }
+
+  public String getTitle()
+  {
+    return title;
+  }
+
+  public void setTitle(String title)
+  {
+    this.title = title;
+  }
+}
\ No newline at end of file
index b7c766a..655e182 100644 (file)
  */
 package jalview.appletgui;
 
-import java.awt.*;
-import java.awt.event.*;
-
-import jalview.analysis.*;
-import jalview.datamodel.*;
-import jalview.io.*;
+import jalview.analysis.NJTree;
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.ViewBasedAnalysisI;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.SequenceI;
+import jalview.io.NewickFile;
+import jalview.schemes.ResidueProperties;
 import jalview.util.MessageManager;
 
+import java.awt.BorderLayout;
+import java.awt.CheckboxMenuItem;
+import java.awt.Color;
+import java.awt.Menu;
+import java.awt.MenuBar;
+import java.awt.MenuItem;
+import java.awt.ScrollPane;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
 public class TreePanel extends EmbmenuFrame implements ActionListener,
         ItemListener
 {
@@ -204,8 +219,8 @@ public class TreePanel extends EmbmenuFrame implements ActionListener,
       {
         int start, end;
         SequenceI[] seqs;
-        boolean selview = (av.getSelectionGroup() != null)
-                && (av.getSelectionGroup().getSize() > 1);
+        boolean selview = av.getSelectionGroup() != null
+                && av.getSelectionGroup().getSize() > 1;
         AlignmentView seqStrings = av.getAlignmentView(selview);
         if (!selview)
         {
@@ -220,8 +235,27 @@ public class TreePanel extends EmbmenuFrame implements ActionListener,
           seqs = av.getSelectionGroup().getSequencesInOrder(
                   av.getAlignment());
         }
-
-        tree = new NJTree(seqs, seqStrings, type, pwtype, start, end);
+        ScoreModelI sm = ResidueProperties.getScoreModel(pwtype);
+        if (sm instanceof ViewBasedAnalysisI)
+        {
+          try
+          {
+            sm = sm.getClass().newInstance();
+            ((ViewBasedAnalysisI) sm)
+                    .configureFromAlignmentView(treeCanvas.ap);
+          } catch (Exception q)
+          {
+            System.err.println("Couldn't create a scoremodel instance for "
+                    + sm.getName());
+            q.printStackTrace();
+          }
+          tree = new NJTree(seqs, seqStrings, type, pwtype, sm, start, end);
+        }
+        else
+        {
+          tree = new NJTree(seqs, seqStrings, type, pwtype, null, start,
+                  end);
+        }
       }
 
       tree.reCount(tree.getTopNode());
index ff1b180..50e3559 100755 (executable)
@@ -22,6 +22,7 @@ package jalview.bin;
 
 import jalview.gui.AlignFrame;
 import jalview.gui.Desktop;
+import jalview.io.HtmlSvgOutput;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
 import jalview.ws.jws2.Jws2Discoverer;
@@ -397,6 +398,10 @@ public class Jalview
         {
           af.getViewport().setSortByTree(true);
         }
+        if (aparser.contains("no-annotation"))
+        {
+          af.getViewport().setShowAnnotation(false);
+        }
         if (aparser.contains("nosortbytree"))
         {
           af.getViewport().setSortByTree(false);
@@ -484,6 +489,14 @@ public class Jalview
             System.out.println("Creating SVG image: " + file);
             continue;
           }
+          else if (format.equalsIgnoreCase("html"))
+          {
+            File imageFile = new java.io.File(file);
+            imageName = imageFile.getName();
+            new HtmlSvgOutput(new java.io.File(file), af.alignPanel);
+            System.out.println("Creating HTML image: " + file);
+            continue;
+          }
           else if (format.equalsIgnoreCase("imgMap"))
           {
             af.createImageMap(new java.io.File(file), imageName);
@@ -615,6 +628,8 @@ public class Jalview
                     + "-blc FILE\tCreate alignment file FILE in BLC format.\n"
                     + "-jalview FILE\tCreate alignment file FILE in Jalview format.\n"
                     + "-png FILE\tCreate PNG image FILE from alignment.\n"
+                    + "-svg FILE\tCreate SVG image FILE from alignment.\n"
+                    + "-html FILE\tCreate HTML file from alignment.\n"
                     + "-imgMap FILE\tCreate HTML file FILE with image map of PNG image.\n"
                     + "-eps FILE\tCreate EPS file FILE from alignment.\n"
                     + "-questionnaire URL\tQueries the given URL for information about any Jalview user questionnaires.\n"
index ae8bc98..e022c73 100644 (file)
@@ -709,8 +709,8 @@ public class JalviewLite extends Applet implements
    */
   public void loadAnnotationFrom(AlignFrame alf, String annotation)
   {
-    if (new AnnotationFile().readAnnotationFile(alf.getAlignViewport()
-            .getAlignment(), annotation, AppletFormatAdapter.PASTE))
+    if (new AnnotationFile().annotateAlignmentView(alf.getAlignViewport(),
+            annotation, AppletFormatAdapter.PASTE))
     {
       alf.alignPanel.fontChanged();
       alf.alignPanel.setScrollValues(0, 0);
@@ -1990,7 +1990,7 @@ public class JalviewLite extends Applet implements
         param = applet.getParameter("showFeatureSettings");
         if (param != null && param.equalsIgnoreCase("true"))
         {
-          newAlignFrame.viewport.showSequenceFeatures(true);
+          newAlignFrame.viewport.setShowSequenceFeatures(true);
           new FeatureSettings(newAlignFrame.alignPanel);
         }
 
@@ -1999,8 +1999,8 @@ public class JalviewLite extends Applet implements
         {
           param = setProtocolState(param);
 
-          if (new AnnotationFile().readAnnotationFile(
-                  newAlignFrame.viewport.getAlignment(), param, protocol))
+          if (new AnnotationFile().annotateAlignmentView(
+                  newAlignFrame.viewport, param, protocol))
           {
             newAlignFrame.alignPanel.fontChanged();
             newAlignFrame.alignPanel.setScrollValues(0, 0);
@@ -2024,6 +2024,12 @@ public class JalviewLite extends Applet implements
                     param, protocol);
             JnetAnnotationMaker.add_annotation(predictions,
                     newAlignFrame.viewport.getAlignment(), 0, false); // false==do
+            SequenceI repseq = newAlignFrame.viewport.getAlignment()
+                    .getSequenceAt(0);
+            newAlignFrame.viewport.getAlignment().setSeqrep(repseq);
+            ColumnSelection cs = new ColumnSelection();
+            cs.hideInsertionsFor(repseq);
+            newAlignFrame.viewport.setColumnSelection(cs);
             // not
             // add
             // sequence
index dbc3524..4d734c7 100644 (file)
  */
 package jalview.controller;
 
-import java.awt.Color;
-import java.util.BitSet;
-import java.util.List;
-
+import jalview.analysis.AlignmentSorter;
 import jalview.api.AlignViewControllerGuiI;
 import jalview.api.AlignViewControllerI;
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
+import jalview.api.FeatureRenderer;
+import jalview.commands.OrderCommand;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.AnnotatedCollectionI;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.SequenceCollectionI;
 import jalview.datamodel.SequenceFeature;
@@ -37,6 +35,11 @@ import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.util.MessageManager;
 
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+
 public class AlignViewController implements AlignViewControllerI
 {
   AlignViewportI viewport = null;
@@ -97,7 +100,9 @@ public class AlignViewController implements AlignViewControllerI
                 (int) (Math.random() * 255), (int) (Math.random() * 255));
         col = col.brighter();
         for (SequenceI sq : gps[g].getSequences(null))
+        {
           viewport.setSequenceColour(sq, col);
+        }
       }
       return true;
     }
@@ -296,4 +301,76 @@ public class AlignViewController implements AlignViewControllerI
       return false;
     }
   }
+
+
+
+  @Override
+  public void sortAlignmentByFeatureDensity(String[] typ)
+  {
+    sortBy(typ, "Sort by Density", AlignmentSorter.FEATURE_DENSITY);
+  }
+
+  protected void sortBy(String[] typ, String methodText, final String method)
+  {
+    FeatureRenderer fr = alignPanel.getFeatureRenderer();
+    if (typ == null)
+    {
+      typ = fr==null ? null : fr.getDisplayedFeatureTypes();
+    }
+    String gps[] = null;
+    gps = fr==null ? null : fr.getDisplayedFeatureGroups();
+    if (typ != null)
+    {
+      ArrayList types = new ArrayList();
+      for (int i = 0; i < typ.length; i++)
+      {
+        if (typ[i] != null)
+        {
+          types.add(typ[i]);
+        }
+        typ = new String[types.size()];
+        types.toArray(typ);
+      }
+    }
+    if (gps != null)
+    {
+      ArrayList grps = new ArrayList();
+
+      for (int i = 0; i < gps.length; i++)
+      {
+        if (gps[i] != null)
+        {
+          grps.add(gps[i]);
+        }
+      }
+      gps = new String[grps.size()];
+      grps.toArray(gps);
+    }
+    AlignmentI al = viewport.getAlignment();
+
+    int start, stop;
+    SequenceGroup sg = viewport.getSelectionGroup();
+    if (sg != null)
+    {
+      start = sg.getStartRes();
+      stop = sg.getEndRes();
+    }
+    else
+    {
+      start = 0;
+      stop = al.getWidth();
+    }
+    SequenceI[] oldOrder = al.getSequencesArray();
+    AlignmentSorter.sortByFeature(typ, gps, start, stop, al, method);
+    avcg.addHistoryItem(new OrderCommand(methodText, oldOrder, viewport
+            .getAlignment()));
+    alignPanel.paintAlignment(true);
+
+  }
+
+  @Override
+  public void sortAlignmentByFeatureScore(String[] typ)
+  {
+    sortBy(typ, "Sort by Feature Score", AlignmentSorter.FEATURE_SCORE);
+  }
 }
diff --git a/src/jalview/controller/FeatureSettingsController.java b/src/jalview/controller/FeatureSettingsController.java
new file mode 100644 (file)
index 0000000..ebf4958
--- /dev/null
@@ -0,0 +1,12 @@
+package jalview.controller;
+
+import jalview.api.FeatureRenderer;
+import jalview.api.FeatureSettingsModelI;
+
+public class FeatureSettingsController implements jalview.api.FeatureSettingsControllerI
+{
+  FeatureSettingsControllerGuiI settingUI;
+  FeatureRenderer fr;
+  FeatureSettingsModelI fsettings;
+  
+}
diff --git a/src/jalview/controller/FeatureSettingsControllerGuiI.java b/src/jalview/controller/FeatureSettingsControllerGuiI.java
new file mode 100644 (file)
index 0000000..781759e
--- /dev/null
@@ -0,0 +1,6 @@
+package jalview.controller;
+
+public interface FeatureSettingsControllerGuiI
+{
+
+}
index 9c5914f..01d3d8d 100755 (executable)
@@ -1571,6 +1571,39 @@ public class Alignment implements AlignmentI
     }
   }
 
+
+ private SequenceI seqrep=null;
+
+ /**
+  * 
+  * @return the representative sequence for this group
+  */
+ public SequenceI getSeqrep()
+ {
+   return seqrep;
+ }
+
+ /**
+  * set the representative sequence for this group. Note - this affects the
+  * interpretation of the Hidereps attribute.
+  * 
+  * @param seqrep
+  *          the seqrep to set (null means no sequence representative)
+  */
+ public void setSeqrep(SequenceI seqrep)
+ {
+   this.seqrep = seqrep;
+ }
+
+ /**
+  * 
+  * @return true if group has a sequence representative
+  */
+ public boolean hasSeqrep()
+ {
+   return seqrep != null;
+ }
+
   @Override
   public int getEndRes()
   {
index 529891f..a55f676 100644 (file)
@@ -20,7 +20,7 @@
  */
 package jalview.datamodel;
 
-import java.util.Vector;
+import java.util.List;
 
 public class CigarArray extends CigarBase
 {
@@ -148,25 +148,25 @@ public class CigarArray extends CigarBase
    * internal constructor function - called by CigarArray(AlignmentI, ...);
    * 
    * @param alignment
-   * @param columnSelection
+   * @param list
    *          - vector of visible regions as returned from
    *          columnSelection.getHiddenColumns()
    * @param selectionGroup
    */
   private void constructFromAlignment(AlignmentI alignment,
-          Vector columnSelection, SequenceGroup selectionGroup)
+          List<int[]> list, SequenceGroup selectionGroup)
   {
     int[] _startend = _calcStartEndBounds(alignment, selectionGroup);
     int start = _startend[1], end = _startend[2];
     // now construct the CigarArray operations
-    if (columnSelection != null)
+    if (list != null)
     {
       int[] region;
       int hideStart, hideEnd;
       int last = start;
-      for (int j = 0; last < end & j < columnSelection.size(); j++)
+      for (int j = 0; last < end & j < list.size(); j++)
       {
-        region = (int[]) columnSelection.elementAt(j);
+        region = list.get(j);
         hideStart = region[0];
         hideEnd = region[1];
         // edit hidden regions to selection range
index f414d13..6acca32 100644 (file)
 package jalview.datamodel;
 
 import jalview.util.ShiftList;
+import jalview.viewmodel.annotationfilter.AnnotationFilterParameter;
+import jalview.viewmodel.annotationfilter.AnnotationFilterParameter.SearchableAnnotationField;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Enumeration;
 import java.util.List;
 import java.util.Vector;
@@ -35,7 +38,7 @@ public class ColumnSelection
   Vector selected = new Vector();
 
   // Vector of int [] {startCol, endCol}
-  Vector hiddenColumns;
+  Vector<int[]> hiddenColumns;
 
   /**
    * Add a column to the selection
@@ -208,7 +211,7 @@ public class ColumnSelection
       int hSize = hiddenColumns.size();
       for (int i = 0; i < hSize; i++)
       {
-        int[] region = (int[]) hiddenColumns.elementAt(i);
+        int[] region = hiddenColumns.elementAt(i);
         if (region[0] > start && start + change > region[1])
         {
           deletedHiddenColumns.add(region);
@@ -263,7 +266,7 @@ public class ColumnSelection
     {
       for (int i = 0; i < hiddenColumns.size(); i++)
       {
-        int[] region = (int[]) hiddenColumns.elementAt(i);
+        int[] region = hiddenColumns.elementAt(i);
         if (region[0] >= start)
         {
           region[0] -= change;
@@ -477,16 +480,13 @@ public class ColumnSelection
   }
 
   /**
-   * This Method is used to return all the HiddenColumn regions less than the
-   * given index.
-   * 
-   * @param end
-   *          int
-   * @return Vector
+   * This Method is used to return all the HiddenColumn regions
+   * @return empty list or List of hidden column intervals
    */
-  public Vector getHiddenColumns()
+  public List<int[]> getHiddenColumns()
   {
-    return hiddenColumns;
+    return hiddenColumns == null ? Arrays.asList(new int[]
+    {}) : hiddenColumns;
   }
 
   /**
@@ -503,7 +503,7 @@ public class ColumnSelection
     {
       for (int i = 0; i < hiddenColumns.size(); i++)
       {
-        int[] region = (int[]) hiddenColumns.elementAt(i);
+        int[] region = hiddenColumns.elementAt(i);
         if (result >= region[0])
         {
           result += region[1] - region[0] + 1;
@@ -531,7 +531,7 @@ public class ColumnSelection
       int[] region;
       do
       {
-        region = (int[]) hiddenColumns.elementAt(index++);
+        region = hiddenColumns.elementAt(index++);
         if (hiddenColumn > region[1])
         {
           result -= region[1] + 1 - region[0];
@@ -557,7 +557,7 @@ public class ColumnSelection
       int gaps = 0;
       do
       {
-        int[] region = (int[]) hiddenColumns.elementAt(index);
+        int[] region = hiddenColumns.elementAt(index);
         if (hiddenRegion == 0)
         {
           return region[0];
@@ -588,7 +588,7 @@ public class ColumnSelection
       int index = 0;
       do
       {
-        int[] region = (int[]) hiddenColumns.elementAt(index);
+        int[] region = hiddenColumns.elementAt(index);
         if (alPos < region[0])
         {
           return region[0];
@@ -616,7 +616,7 @@ public class ColumnSelection
       int index = hiddenColumns.size() - 1;
       do
       {
-        int[] region = (int[]) hiddenColumns.elementAt(index);
+        int[] region = hiddenColumns.elementAt(index);
         if (alPos > region[1])
         {
           return region[1];
@@ -652,7 +652,7 @@ public class ColumnSelection
 
     for (int i = 0; i < hiddenColumns.size(); i++)
     {
-      int[] region = (int[]) hiddenColumns.elementAt(i);
+      int[] region = hiddenColumns.elementAt(i);
       if (start <= region[1] && end >= region[0])
       {
         hiddenColumns.removeElementAt(i);
@@ -719,7 +719,7 @@ public class ColumnSelection
     {
       for (int i = 0; i < hiddenColumns.size(); i++)
       {
-        int[] region = (int[]) hiddenColumns.elementAt(i);
+        int[] region = hiddenColumns.elementAt(i);
         for (int j = region[0]; j < region[1] + 1; j++)
         {
           addElement(j);
@@ -734,7 +734,7 @@ public class ColumnSelection
   {
     for (int i = 0; i < hiddenColumns.size(); i++)
     {
-      int[] region = (int[]) hiddenColumns.elementAt(i);
+      int[] region = hiddenColumns.elementAt(i);
       if (res == region[0])
       {
         for (int j = region[0]; j < region[1] + 1; j++)
@@ -758,7 +758,7 @@ public class ColumnSelection
     {
       for (int i = 0; i < hiddenColumns.size(); i++)
       {
-        int[] region = (int[]) hiddenColumns.elementAt(i);
+        int[] region = hiddenColumns.elementAt(i);
         if (column >= region[0] && column <= region[1])
         {
           return false;
@@ -792,7 +792,7 @@ public class ColumnSelection
         for (int i = 0, j = copy.hiddenColumns.size(); i < j; i++)
         {
           int[] rh, cp;
-          rh = (int[]) copy.hiddenColumns.elementAt(i);
+          rh = copy.hiddenColumns.elementAt(i);
           if (rh != null)
           {
             cp = new int[rh.length];
@@ -821,7 +821,7 @@ public class ColumnSelection
       for (i = 0; i < iSize; i++)
       {
         StringBuffer visibleSeq = new StringBuffer();
-        Vector regions = getHiddenColumns();
+        List<int[]> regions = getHiddenColumns();
 
         int blockStart = start, blockEnd = end;
         int[] region;
@@ -829,7 +829,7 @@ public class ColumnSelection
 
         for (int j = 0; j < regions.size(); j++)
         {
-          region = (int[]) regions.elementAt(j);
+          region = regions.get(j);
           hideStart = region[0];
           hideEnd = region[1];
 
@@ -886,7 +886,7 @@ public class ColumnSelection
     if (hiddenColumns != null && hiddenColumns.size() > 0)
     {
       Vector visiblecontigs = new Vector();
-      Vector regions = getHiddenColumns();
+      List<int[]> regions = getHiddenColumns();
 
       int vstart = start;
       int[] region;
@@ -894,7 +894,7 @@ public class ColumnSelection
 
       for (int j = 0; vstart < end && j < regions.size(); j++)
       {
-        region = (int[]) regions.elementAt(j);
+        region = regions.get(j);
         hideStart = region[0];
         hideEnd = region[1];
 
@@ -972,14 +972,14 @@ public class ColumnSelection
       // then mangle the alignmentAnnotation annotation array
       Vector annels = new Vector();
       Annotation[] els = null;
-      Vector regions = getHiddenColumns();
+      List<int[]> regions = getHiddenColumns();
       int blockStart = start, blockEnd = end;
       int[] region;
       int hideStart, hideEnd, w = 0;
 
       for (int j = 0; j < regions.size(); j++)
       {
-        region = (int[]) regions.elementAt(j);
+        region = regions.get(j);
         hideStart = region[0];
         hideEnd = region[1];
 
@@ -1265,4 +1265,131 @@ public class ColumnSelection
       }
     }
   }
+
+  /**
+   * 
+   * @return true if there are columns marked
+   */
+  public boolean hasSelectedColumns()
+  {
+    return (selected != null && selected.size() > 0);
+  }
+
+  /**
+   * 
+   * @return true if there are columns hidden
+   */
+  public boolean hasHiddenColumns()
+  {
+    return hiddenColumns != null && hiddenColumns.size() > 0;
+  }
+  
+  /**
+   * 
+   * @return true if there are more than one set of columns hidden
+   */
+  public boolean hasManyHiddenColumns()
+  {
+    return hiddenColumns != null && hiddenColumns.size() > 1;
+  }
+
+  /**
+   * mark the columns corresponding to gap characters as hidden in the column
+   * selection
+   * 
+   * @param sr
+   */
+  public void hideInsertionsFor(SequenceI sr)
+  {
+    List<int[]> inserts = sr.getInsertions();
+    for (int[] r : inserts)
+    {
+      hideColumns(r[0], r[1]);
+    }
+  }
+  
+  public boolean filterAnnotations(Annotation[] annotations,
+          AnnotationFilterParameter filterParams)
+  {
+    this.revealAllHiddenColumns();
+    this.clear();
+    int count = 0;
+    do
+    {
+      if (annotations[count] != null)
+      {
+
+        boolean itemMatched = false;
+
+        if (filterParams.getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
+                && annotations[count].value >= filterParams
+                        .getThresholdValue())
+        {
+          itemMatched = true;
+        }
+        if (filterParams.getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD
+                && annotations[count].value <= filterParams
+                        .getThresholdValue())
+        {
+          itemMatched = true;
+        }
+
+        if (filterParams.isFilterAlphaHelix()
+                && annotations[count].secondaryStructure == 'H')
+        {
+          itemMatched = true;
+        }
+
+        if (filterParams.isFilterBetaSheet()
+                && annotations[count].secondaryStructure == 'E')
+        {
+          itemMatched = true;
+        }
+
+        if (filterParams.isFilterTurn()
+                && annotations[count].secondaryStructure == 'S')
+        {
+          itemMatched = true;
+        }
+
+        String regexSearchString = filterParams.getRegexString();
+        if (regexSearchString != null
+                && !filterParams.getRegexSearchFields().isEmpty())
+        {
+          List<SearchableAnnotationField> fields = filterParams
+                  .getRegexSearchFields();
+          try
+          {
+            if (fields.contains(SearchableAnnotationField.DISPLAY_STRING)
+                    && annotations[count].displayCharacter
+                            .matches(regexSearchString))
+            {
+              itemMatched = true;
+            }
+          } catch (java.util.regex.PatternSyntaxException pse)
+          {
+            if (annotations[count].displayCharacter
+                    .equals(regexSearchString))
+            {
+              itemMatched = true;
+            }
+          }
+          if (fields.contains(SearchableAnnotationField.DESCRIPTION)
+                  && annotations[count].description != null
+                  && annotations[count].description
+                          .matches(regexSearchString))
+          {
+            itemMatched = true;
+          }
+        }
+
+        if (itemMatched)
+        {
+          this.addElement(count);
+        }
+      }
+      count++;
+    } while (count < annotations.length);
+    return false;
+  }
 }
index ee2b549..1cf9cd7 100755 (executable)
@@ -20,7 +20,7 @@
  */
 package jalview.datamodel;
 
-import java.util.*;
+import java.util.Hashtable;
 
 public class PDBEntry
 {
@@ -37,6 +37,7 @@ public class PDBEntry
    * 
    * @see java.lang.Object#equals(java.lang.Object)
    */
+  @Override
   public boolean equals(Object obj)
   {
     if (obj == null || !(obj instanceof PDBEntry))
@@ -44,7 +45,9 @@ public class PDBEntry
       return false;
     }
     if (obj == this)
+    {
       return true;
+    }
     PDBEntry o = (PDBEntry) obj;
     return (file == o.file || (file != null && o.file != null && o.file
             .equals(file)))
@@ -57,10 +60,29 @@ public class PDBEntry
                       .equals(o.properties)));
   }
 
+  /**
+   * Default constructor
+   */
   public PDBEntry()
   {
   }
 
+  /**
+   * Constructor given file path and PDB id.
+   * 
+   * @param filePath
+   */
+  public PDBEntry(String filePath, String pdbId)
+  {
+    this.file = filePath;
+    this.id = pdbId;
+  }
+  
+  /**
+   * Copy constructor.
+   * 
+   * @param entry
+   */
   public PDBEntry(PDBEntry entry)
   {
     file = entry.file;
index 0652fb5..96e469a 100755 (executable)
@@ -661,11 +661,7 @@ public class Sequence implements SequenceI
     return map;
   }
 
-  /*
-   * (non-Javadoc)
-   * 
-   * @see jalview.datamodel.SequenceI#findPositionMap()
-   */
+  @Override
   public int[] findPositionMap()
   {
     int map[] = new int[sequence.length];
@@ -685,11 +681,43 @@ public class Sequence implements SequenceI
     return map;
   }
 
-  /*
-   * (non-Javadoc)
-   * 
-   * @see jalview.datamodel.SequenceI#deleteChars(int, int)
-   */
+  @Override
+  public List<int[]> getInsertions()
+  {
+    ArrayList<int[]> map = new ArrayList<int[]>();
+    int lastj = -1, j = 0;
+    int pos = start;
+    int seqlen = sequence.length;
+    while ((j < seqlen))
+    {
+      if (jalview.util.Comparison.isGap(sequence[j]))
+      {
+        if (lastj == -1)
+        {
+          lastj = j;
+        }
+      }
+      else
+      {
+        if (lastj != -1)
+        {
+          map.add(new int[]
+          { lastj, j - 1 });
+          lastj = -1;
+        }
+      }
+      j++;
+    }
+    if (lastj != -1)
+    {
+      map.add(new int[]
+      { lastj, j - 1 });
+      lastj = -1;
+    }
+    return map;
+  }
+
+  @Override
   public void deleteChars(int i, int j)
   {
     int newstart = start, newend = end;
@@ -768,16 +796,7 @@ public class Sequence implements SequenceI
     sequence = tmp;
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param i
-   *          DOCUMENT ME!
-   * @param c
-   *          DOCUMENT ME!
-   * @param chop
-   *          DOCUMENT ME!
-   */
+  @Override
   public void insertCharAt(int i, int length, char c)
   {
     char[] tmp = new char[sequence.length + length];
@@ -807,26 +826,31 @@ public class Sequence implements SequenceI
     sequence = tmp;
   }
 
+  @Override
   public void insertCharAt(int i, char c)
   {
     insertCharAt(i, 1, c);
   }
 
+  @Override
   public String getVamsasId()
   {
     return vamsasId;
   }
 
+  @Override
   public void setVamsasId(String id)
   {
     vamsasId = id;
   }
 
+  @Override
   public void setDBRef(DBRefEntry[] dbref)
   {
     dbrefs = dbref;
   }
 
+  @Override
   public DBRefEntry[] getDBRef()
   {
     if (dbrefs == null && datasetSequence != null
@@ -837,6 +861,7 @@ public class Sequence implements SequenceI
     return dbrefs;
   }
 
+  @Override
   public void addDBRef(DBRefEntry entry)
   {
     if (dbrefs == null)
@@ -869,40 +894,33 @@ public class Sequence implements SequenceI
     dbrefs = temp;
   }
 
+  @Override
   public void setDatasetSequence(SequenceI seq)
   {
     datasetSequence = seq;
   }
 
+  @Override
   public SequenceI getDatasetSequence()
   {
     return datasetSequence;
   }
 
-  /**
-   * Returns a new array containing this sequence's annotations, or null.
-   */
+  @Override
   public AlignmentAnnotation[] getAnnotation()
   {
     return annotation == null ? null : annotation
             .toArray(new AlignmentAnnotation[annotation.size()]);
   }
 
-  /**
-   * Returns true if this sequence has the given annotation (by object
-   * identity).
-   */
+
   @Override
   public boolean hasAnnotation(AlignmentAnnotation ann)
   {
     return annotation == null ? false : annotation.contains(ann);
   }
 
-  /**
-   * Add the given annotation, if not already added, and set its sequence ref to
-   * be this sequence. Does nothing if this sequence's annotations already
-   * include this annotation (by identical object reference).
-   */
+  @Override
   public void addAlignmentAnnotation(AlignmentAnnotation annotation)
   {
     if (this.annotation == null)
@@ -948,11 +966,7 @@ public class Sequence implements SequenceI
     return true;
   }
 
-  /*
-   * (non-Javadoc)
-   * 
-   * @see jalview.datamodel.SequenceI#deriveSequence()
-   */
+  @Override
   public SequenceI deriveSequence()
   {
     SequenceI seq = new Sequence(this);
@@ -1043,11 +1057,7 @@ public class Sequence implements SequenceI
     }
   }
 
-  /*
-   * (non-Javadoc)
-   * 
-   * @see jalview.datamodel.SequenceI#getAnnotation(java.lang.String)
-   */
+  @Override
   public AlignmentAnnotation[] getAnnotation(String label)
   {
     if (annotation == null || annotation.size() == 0)
@@ -1080,6 +1090,7 @@ public class Sequence implements SequenceI
     return anns;
   }
 
+  @Override
   public boolean updatePDBIds()
   {
     if (datasetSequence != null)
@@ -1133,13 +1144,7 @@ public class Sequence implements SequenceI
     return false;
   }
 
-  /*
-   * (non-Javadoc)
-   * 
-   * @see
-   * jalview.datamodel.SequenceI#transferAnnotation(jalview.datamodel.SequenceI,
-   * jalview.datamodel.Mapping)
-   */
+  @Override
   public void transferAnnotation(SequenceI entry, Mapping mp)
   {
     if (datasetSequence != null)
@@ -1235,13 +1240,6 @@ public class Sequence implements SequenceI
     return rna;
   }
 
-  /**
-   * Returns a (possibly empty) list of any annotations that match on given
-   * calcId (source) and label (type). Null values do not match.
-   * 
-   * @param calcId
-   * @param label
-   */
   @Override
   public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
           String label)
index 3af441b..ee216a4 100644 (file)
@@ -29,8 +29,23 @@ public interface SequenceCollectionI
 
   List<SequenceI> getSequences(
           Map<SequenceI, SequenceCollectionI> hiddenReps);
-
   int getWidth();
+  /**
+   * 
+   * @return true if getSeqrep doesn't return null
+   */
+  boolean hasSeqrep();
+  /**
+   * get the reference or representative sequence within this collection
+   * @return null or the current reference sequence
+   */
+  SequenceI getSeqrep();
+  /**
+   * set the reference or representative sequence for this collection. 
+   * Reference is assumed to be present within the collection.
+   * @return
+   */
+  void setSeqrep(SequenceI refseq);
 
   /**
    * @return the first column included in this collection. Runs from 0<=i<N_cols
index fc67efd..e8c1d71 100755 (executable)
@@ -315,10 +315,22 @@ public interface SequenceI
 
   public SequenceI getDatasetSequence();
 
+  /**
+   * Returns a new array containing this sequence's annotations, or null.
+   */
   public AlignmentAnnotation[] getAnnotation();
 
+  /**
+   * Returns true if this sequence has the given annotation (by object
+   * identity).
+   */
   public boolean hasAnnotation(AlignmentAnnotation ann);
 
+  /**
+   * Add the given annotation, if not already added, and set its sequence ref to
+   * be this sequence. Does nothing if this sequence's annotations already
+   * include this annotation (by identical object reference).
+   */
   public void addAlignmentAnnotation(AlignmentAnnotation annotation);
 
   public void removeAlignmentAnnotation(AlignmentAnnotation annotation);
@@ -347,12 +359,11 @@ public interface SequenceI
   public AlignmentAnnotation[] getAnnotation(String label);
 
   /**
-   * Return a list of any annotations which match the given calcId (source) and
-   * label (type). Null values do not match.
+   * Returns a (possibly empty) list of any annotations that match on given
+   * calcId (source) and label (type). Null values do not match.
    * 
    * @param calcId
    * @param label
-   * @return
    */
   public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
           String label);
@@ -403,4 +414,10 @@ public interface SequenceI
    */
   public void setRNA(RNA rna);
 
+  /**
+   * 
+   * @return list of insertions (gap characters) in sequence
+   */
+  public List<int[]> getInsertions();
+
 }
diff --git a/src/jalview/datamodel/StructureViewerModel.java b/src/jalview/datamodel/StructureViewerModel.java
new file mode 100644 (file)
index 0000000..098372b
--- /dev/null
@@ -0,0 +1,189 @@
+package jalview.datamodel;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A data bean to hold stored data about a structure viewer.
+ */
+public class StructureViewerModel
+{
+  private int x;
+
+  private int y;
+
+  private int width;
+
+  private int height;
+
+  private boolean alignWithPanel;
+
+  private boolean colourWithAlignPanel;
+
+  private boolean colourByViewer;
+
+  private String stateData = "";
+
+  private Map<File, StructureData> fileData = new HashMap<File, StructureData>();
+
+  public class StructureData
+  {
+    private String filePath;
+
+    private String pdbId;
+
+    private List<SequenceI> seqList;
+
+    // TODO and possibly a list of chains?
+
+    /**
+     * Constructor given structure file path and id.
+     * 
+     * @param pdbFile
+     * @param id
+     */
+    public StructureData(String pdbFile, String id)
+    {
+      this.filePath = pdbFile;
+      this.pdbId = id;
+      this.seqList = new ArrayList<SequenceI>();
+    }
+
+    public String getFilePath()
+    {
+      return filePath;
+    }
+
+    protected void setFilePath(String filePath)
+    {
+      this.filePath = filePath;
+    }
+
+    public String getPdbId()
+    {
+      return pdbId;
+    }
+
+    protected void setPdbId(String pdbId)
+    {
+      this.pdbId = pdbId;
+    }
+
+    public List<SequenceI> getSeqList()
+    {
+      return seqList;
+    }
+
+    protected void setSeqList(List<SequenceI> seqList)
+    {
+      this.seqList = seqList;
+    }
+  }
+
+  public StructureViewerModel(int x, int y, int width, int height,
+          boolean alignWithPanel, boolean colourWithAlignPanel,
+          boolean colourByViewer)
+  {
+    this.x = x;
+    this.y = y;
+    this.width = width;
+    this.height = height;
+    this.alignWithPanel = alignWithPanel;
+    this.colourWithAlignPanel = colourWithAlignPanel;
+    this.colourByViewer = colourByViewer;
+  }
+
+  public int getX()
+  {
+    return x;
+  }
+
+  protected void setX(int x)
+  {
+    this.x = x;
+  }
+
+  public int getY()
+  {
+    return y;
+  }
+
+  protected void setY(int y)
+  {
+    this.y = y;
+  }
+
+  public int getWidth()
+  {
+    return width;
+  }
+
+  protected void setWidth(int width)
+  {
+    this.width = width;
+  }
+
+  public int getHeight()
+  {
+    return height;
+  }
+
+  public void setHeight(int height)
+  {
+    this.height = height;
+  }
+
+  public boolean isAlignWithPanel()
+  {
+    return alignWithPanel;
+  }
+
+  public void setAlignWithPanel(boolean alignWithPanel)
+  {
+    this.alignWithPanel = alignWithPanel;
+  }
+
+  public boolean isColourWithAlignPanel()
+  {
+    return colourWithAlignPanel;
+  }
+
+  public void setColourWithAlignPanel(boolean colourWithAlignPanel)
+  {
+    this.colourWithAlignPanel = colourWithAlignPanel;
+  }
+
+  public boolean isColourByViewer()
+  {
+    return colourByViewer;
+  }
+
+  public void setColourByViewer(boolean colourByViewer)
+  {
+    this.colourByViewer = colourByViewer;
+  }
+
+  public String getStateData()
+  {
+    return stateData;
+  }
+
+  public void setStateData(String stateData)
+  {
+    this.stateData = stateData;
+  }
+
+  public Map<File, StructureData> getFileData()
+  {
+    return fileData;
+  }
+
+  protected void setFileData(Map<File, StructureData> fileData)
+  {
+    this.fileData = fileData;
+  }
+
+}
diff --git a/src/jalview/exceptions/JalviewException.java b/src/jalview/exceptions/JalviewException.java
new file mode 100644 (file)
index 0000000..80e0b08
--- /dev/null
@@ -0,0 +1,25 @@
+package jalview.exceptions;
+
+@SuppressWarnings("serial")
+public class JalviewException extends Exception
+{
+  public JalviewException(String exceptionMessage)
+  {
+    super(exceptionMessage);
+  }
+
+  public JalviewException()
+  {
+    super();
+  }
+
+  public JalviewException(String exceptionMessage, Throwable cause)
+  {
+    super(exceptionMessage, cause);
+  }
+
+  public JalviewException(Throwable cause)
+  {
+    super(cause);
+  }
+}
diff --git a/src/jalview/exceptions/NoFileSelectedException.java b/src/jalview/exceptions/NoFileSelectedException.java
new file mode 100644 (file)
index 0000000..5c56f47
--- /dev/null
@@ -0,0 +1,10 @@
+package jalview.exceptions;
+
+@SuppressWarnings("serial")
+public class NoFileSelectedException extends JalviewException
+{
+  public NoFileSelectedException(String msg)
+  {
+    super(msg);
+  }
+}
index 9187912..619144e 100644 (file)
@@ -23,8 +23,6 @@ package jalview.ext.jmol;
 import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
-import jalview.api.SequenceStructureBinding;
-import jalview.api.StructureSelectionManagerProvider;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.PDBEntry;
@@ -32,10 +30,10 @@ import jalview.datamodel.SequenceI;
 import jalview.io.AppletFormatAdapter;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ResidueProperties;
-import jalview.structure.StructureListener;
 import jalview.structure.StructureMapping;
+import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
-import jalview.structures.models.SequenceStructureBindingModel;
+import jalview.structures.models.AAStructureBindingModel;
 import jalview.util.MessageManager;
 
 import java.awt.Color;
@@ -58,30 +56,18 @@ import org.jmol.api.JmolViewer;
 import org.jmol.constant.EnumCallback;
 import org.jmol.popup.JmolPopup;
 
-public abstract class JalviewJmolBinding extends SequenceStructureBindingModel implements StructureListener,
-        JmolStatusListener, SequenceStructureBinding,
-        JmolSelectionListener, ComponentListener,
-        StructureSelectionManagerProvider
-
+public abstract class JalviewJmolBinding extends AAStructureBindingModel
+        implements JmolStatusListener, JmolSelectionListener,
+        ComponentListener
 {
-  /**
+  /*
    * state flag used to check if the Jmol viewer's paint method can be called
    */
   private boolean finishedInit = false;
 
-  public boolean isFinishedInit()
-  {
-    return finishedInit;
-  }
-
-  public void setFinishedInit(boolean finishedInit)
-  {
-    this.finishedInit = finishedInit;
-  }
-
   boolean allChainsSelected = false;
 
-  /**
+  /*
    * when true, try to search the associated datamodel for sequences that are
    * associated with any unknown structures in the Jmol view.
    */
@@ -93,18 +79,11 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
 
   Hashtable chainFile;
 
-  /**
-   * array of target chains for seuqences - tied to pdbentry and sequence[]
-   */
-  protected String[][] chains;
-
-  boolean colourBySequence = true;
-
   StringBuffer eval = new StringBuffer();
 
   public String fileLoadingError;
 
-  /**
+  /*
    * the default or current model displayed if the model cannot be identified
    * from the selection message
    */
@@ -123,37 +102,15 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
    */
   String[] modelFileNames = null;
 
-  public PDBEntry[] pdbentry;
-
-  /**
-   * datasource protocol for access to PDBEntrylatest
-   */
-  String protocol = null;
-
   StringBuffer resetLastRes = new StringBuffer();
 
-  /**
-   * sequences mapped to each pdbentry
-   */
-  public SequenceI[][] sequence;
-
-  public StructureSelectionManager ssm;
-
   public JmolViewer viewer;
 
   public JalviewJmolBinding(StructureSelectionManager ssm,
           PDBEntry[] pdbentry, SequenceI[][] sequenceIs, String[][] chains,
           String protocol)
   {
-    this.ssm = ssm;
-    this.sequence = sequenceIs;
-    this.chains = chains;
-    this.pdbentry = pdbentry;
-    this.protocol = protocol;
-    if (chains == null)
-    {
-      this.chains = new String[pdbentry.length][];
-    }
+    super(ssm, pdbentry, sequenceIs, chains, protocol);
     /*
      * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
      * "jalviewJmol", ap.av.applet .getDocumentBase(),
@@ -164,10 +121,11 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
   }
 
   public JalviewJmolBinding(StructureSelectionManager ssm,
-          JmolViewer viewer2)
+          SequenceI[][] seqs, JmolViewer theViewer)
   {
-    this.ssm = ssm;
-    viewer = viewer2;
+    super(ssm, seqs);
+
+    viewer = theViewer;
     viewer.setJmolStatusListener(this);
     viewer.addSelectionListener(this);
   }
@@ -180,30 +138,7 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
    */
   public String getViewerTitle()
   {
-    if (sequence == null || pdbentry == null || sequence.length < 1
-            || pdbentry.length < 1 || sequence[0].length < 1)
-    {
-      return ("Jalview Jmol Window");
-    }
-    // TODO: give a more informative title when multiple structures are
-    // displayed.
-    StringBuffer title = new StringBuffer(sequence[0][0].getName() + ":"
-            + pdbentry[0].getId());
-
-    if (pdbentry[0].getProperty() != null)
-    {
-      if (pdbentry[0].getProperty().get("method") != null)
-      {
-        title.append(" Method: ");
-        title.append(pdbentry[0].getProperty().get("method"));
-      }
-      if (pdbentry[0].getProperty().get("chains") != null)
-      {
-        title.append(" Chain:");
-        title.append(pdbentry[0].getProperty().get("chains"));
-      }
-    }
-    return title.toString();
+    return getViewerTitle("JMol", true);
   }
 
   /**
@@ -232,7 +167,9 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
               + (1 + getModelNum((String) chainFile.get(lbl))) + " or ");
     }
     if (cmd.length() > 0)
+    {
       cmd.setLength(cmd.length() - 4);
+    }
     evalStateCommand("select *;restrict " + cmd + ";cartoon;center " + cmd);
   }
 
@@ -240,7 +177,7 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
   {
     viewer.setModeMouse(org.jmol.viewer.JmolConstants.MOUSE_NONE);
     // remove listeners for all structures in viewer
-    ssm.removeStructureViewerListener(this, this.getPdbFile());
+    getSsm().removeStructureViewerListener(this, this.getPdbFile());
     // and shut down jmol
     viewer.evalStringQuiet("zap");
     viewer.setJmolStatusListener(null);
@@ -249,12 +186,6 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
     releaseUIResources();
   }
 
-  /**
-   * called by JalviewJmolbinding after closeViewer is called - release any
-   * resources and references so they can be garbage collected.
-   */
-  protected abstract void releaseUIResources();
-
   public void colourByChain()
   {
     colourBySequence = false;
@@ -330,7 +261,7 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
         {
           // HACK - in Jalview 2.8 this call may not be threadsafe so we catch
           // every possible exception
-          StructureMapping[] sm = ssm.getMapping(file);
+          StructureMapping[] sm = getSsm().getMapping(file);
           if (sm == null || sm.length == 0)
           {
             waiting = true;
@@ -408,7 +339,7 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
       String[] chainNames = new String[files.length];
       for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
       {
-        StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
+        StructureMapping[] mapping = getSsm().getMapping(files[pdbfnum]);
         // RACE CONDITION - getMapping only returns Jmol loaded filenames once
         // Jmol callback has completed.
         if (mapping == null || mapping.length < 1)
@@ -416,12 +347,13 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
           throw new Error(MessageManager.getString("error.implementation_error_jmol_getting_data"));
         }
         int lastPos = -1;
-        for (int s = 0; s < sequence[pdbfnum].length; s++)
+        final int sequenceCountForPdbFile = getSequence()[pdbfnum].length;
+        for (int s = 0; s < sequenceCountForPdbFile; s++)
         {
           for (int sp, m = 0; m < mapping.length; m++)
           {
-            if (mapping[m].getSequence() == sequence[pdbfnum][s]
-                    && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1)
+            if (mapping[m].getSequence() == getSequence()[pdbfnum][s]
+                    && (sp = alignment.findIndex(getSequence()[pdbfnum][s])) > -1)
             {
               if (refStructure == -1)
               {
@@ -473,7 +405,7 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
               chainNames[pdbfnum] = mapping[m].getPdbId()
                       + targetC[pdbfnum];
               // move on to next pdb file
-              s = sequence[pdbfnum].length;
+              s = getSequence()[pdbfnum].length;
               break;
             }
           }
@@ -630,8 +562,10 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
           jalview.api.AlignmentViewPanel alignmentv)
   {
     if (!colourBySequence || !isLoadingFinished())
+    {
       return;
-    if (ssm == null)
+    }
+    if (getSsm() == null)
     {
       return;
     }
@@ -646,23 +580,38 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
     }
     AlignmentI alignment = alignmentv.getAlignment();
 
-    for (jalview.structure.StructureMappingcommandSet cpdbbyseq : JmolCommands
-            .getColourBySequenceCommand(ssm, files, sequence, sr, fr,
-                    alignment))
+    for (jalview.structure.StructureMappingcommandSet cpdbbyseq : getColourBySequenceCommands(files, sr, fr, alignment))
+    {
       for (String cbyseq : cpdbbyseq.commands)
       {
-        evalStateCommand(cbyseq);
+        executeWhenReady(cbyseq);
       }
+    }
   }
 
-  public boolean isColourBySequence()
+  /**
+   * @param files
+   * @param sr
+   * @param fr
+   * @param alignment
+   * @return
+   */
+  protected StructureMappingcommandSet[] getColourBySequenceCommands(
+          String[] files, SequenceRenderer sr, FeatureRenderer fr,
+          AlignmentI alignment)
   {
-    return colourBySequence;
+    return JmolCommands
+            .getColourBySequenceCommand(getSsm(), files, getSequence(), sr,
+                    fr,
+                    alignment);
   }
 
-  public void setColourBySequence(boolean colourBySequence)
+  /**
+   * @param command
+   */
+  protected void executeWhenReady(String command)
   {
-    this.colourBySequence = colourBySequence;
+    evalStateCommand(command);
   }
 
   public void createImage(String file, String type, int quality)
@@ -702,7 +651,9 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
           String pdbfile)
   {
     if (getModelNum(pdbfile) < 0)
+    {
       return null;
+    }
     // TODO: verify atomIndex is selecting correct model.
     return new Color(viewer.getAtomArgb(atomIndex));
   }
@@ -735,7 +686,9 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
     for (int i = 0; i < mfn.length; i++)
     {
       if (mfn[i].equalsIgnoreCase(modelFileName))
+      {
         return i;
+      }
     }
     return -1;
   }
@@ -806,7 +759,8 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
   /**
    * map from string to applet
    */
-  public Map getRegistryInfo()
+  @Override
+  public Map<String, Object> getRegistryInfo()
   {
     // TODO Auto-generated method stub
     return null;
@@ -944,8 +898,10 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
     String chainId;
 
     if (strInfo.indexOf(":") > -1)
+    {
       chainId = strInfo.substring(strInfo.indexOf(":") + 1,
               strInfo.indexOf("."));
+    }
     else
     {
       chainId = " ";
@@ -983,7 +939,9 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
       ;
     }
     if (lastMessage == null || !lastMessage.equals(strInfo))
-      ssm.mouseOverStructure(pdbResNum, chainId, pdbfilename);
+    {
+      getSsm().mouseOverStructure(pdbResNum, chainId, pdbfilename);
+    }
 
     lastMessage = strInfo;
   }
@@ -1017,13 +975,17 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
     int chainSeparator = strInfo.indexOf(":");
     int p = 0;
     if (chainSeparator == -1)
+    {
       chainSeparator = strInfo.indexOf(".");
+    }
 
     String picked = strInfo.substring(strInfo.indexOf("]") + 1,
             chainSeparator);
     String mdlString = "";
     if ((p = strInfo.indexOf(":")) > -1)
+    {
       picked += strInfo.substring(p + 1, strInfo.indexOf("."));
+    }
 
     if ((p = strInfo.indexOf("/")) > -1)
     {
@@ -1195,7 +1157,7 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
         }
         // deregister the Jmol instance for these structures - we'll add
         // ourselves again at the end for the current structure set.
-        ssm.removeStructureViewerListener(this, oldmfn);
+        getSsm().removeStructureViewerListener(this, oldmfn);
       }
     }
     refreshPdbEntries();
@@ -1215,70 +1177,68 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
                 + ".0", "PDB");
         pdbfhash = "" + pdbfile.hashCode();
       }
-      if (pdbentry != null)
-      {
         // search pdbentries and sequences to find correct pdbentry for this
         // model
-        for (int pe = 0; pe < pdbentry.length; pe++)
+      for (int pe = 0; pe < getPdbCount(); pe++)
+      {
+        boolean matches = false;
+        if (fileName == null)
         {
-          boolean matches = false;
-          if (fileName == null)
+          if (false)
+          // see JAL-623 - need method of matching pasted data up
           {
-            if (false)
-            // see JAL-623 - need method of matching pasted data up
-            {
-              pdb = ssm.setMapping(sequence[pe], chains[pe], pdbfile,
-                      AppletFormatAdapter.PASTE);
-              pdbentry[modelnum].setFile("INLINE" + pdb.id);
-              matches = true;
-              foundEntry = true;
-            }
+            pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
+                    pdbfile, AppletFormatAdapter.PASTE);
+            getPdbEntry(modelnum).setFile("INLINE" + pdb.id);
+            matches = true;
+            foundEntry = true;
           }
-          else
+        }
+        else
+        {
+          File fl;
+          if (matches = (fl = new File(getPdbEntry(pe).getFile()))
+                  .equals(new File(fileName)))
           {
-            File fl;
-            if (matches = (fl = new File(pdbentry[pe].getFile()))
-                    .equals(new File(fileName)))
+            foundEntry = true;
+            // TODO: Jmol can in principle retrieve from CLASSLOADER but
+            // this
+            // needs
+            // to be tested. See mantis bug
+            // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
+            String protocol = AppletFormatAdapter.URL;
+            try
             {
-              foundEntry = true;
-              // TODO: Jmol can in principle retrieve from CLASSLOADER but
-              // this
-              // needs
-              // to be tested. See mantis bug
-              // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
-              String protocol = AppletFormatAdapter.URL;
-              try
-              {
-                if (fl.exists())
-                {
-                  protocol = AppletFormatAdapter.FILE;
-                }
-              } catch (Exception e)
-              {
-              } catch (Error e)
+              if (fl.exists())
               {
+                protocol = AppletFormatAdapter.FILE;
               }
-              // Explicitly map to the filename used by Jmol ;
-              pdb = ssm.setMapping(sequence[pe], chains[pe], fileName,
-                      protocol);
-              // pdbentry[pe].getFile(), protocol);
-
+            } catch (Exception e)
+            {
+            } catch (Error e)
+            {
             }
+            // Explicitly map to the filename used by Jmol ;
+            pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
+                    fileName, protocol);
+            // pdbentry[pe].getFile(), protocol);
+
           }
-          if (matches)
+        }
+        if (matches)
+        {
+          // add an entry for every chain in the model
+          for (int i = 0; i < pdb.chains.size(); i++)
           {
-            // add an entry for every chain in the model
-            for (int i = 0; i < pdb.chains.size(); i++)
-            {
-              String chid = new String(pdb.id + ":"
-                      + ((MCview.PDBChain) pdb.chains.elementAt(i)).id);
-              chainFile.put(chid, fileName);
-              chainNames.addElement(chid);
-            }
-            notifyLoaded = true;
+            String chid = new String(pdb.id + ":"
+                    + pdb.chains.elementAt(i).id);
+            chainFile.put(chid, fileName);
+            chainNames.addElement(chid);
           }
+          notifyLoaded = true;
         }
       }
+
       if (!foundEntry && associateNewStructs)
       {
         // this is a foreign pdb file that jalview doesn't know about - add
@@ -1307,7 +1267,7 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
     }
     // register ourselves as a listener and notify the gui that it needs to
     // update itself.
-    ssm.addStructureViewerListener(this);
+    getSsm().addStructureViewerListener(this);
     if (notifyLoaded)
     {
       FeatureRenderer fr = getFeatureRenderer(null);
@@ -1364,7 +1324,9 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
     colourBySequence = false;
 
     if (cs == null)
+    {
       return;
+    }
 
     String res;
     int index;
@@ -1378,7 +1340,9 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
       res = en.nextElement().toString();
       index = ((Integer) ResidueProperties.aa3Hash.get(res)).intValue();
       if (index > 20)
+      {
         continue;
+      }
 
       col = cs.findColour(ResidueProperties.aa[index].charAt(0));
 
@@ -1477,185 +1441,93 @@ public abstract class JalviewJmolBinding extends SequenceStructureBindingModel i
 
   protected org.jmol.api.JmolAppConsoleInterface console = null;
 
-  public void componentResized(ComponentEvent e)
+  public void setBackgroundColour(java.awt.Color col)
   {
-
+    jmolHistory(false);
+    viewer.evalStringQuiet("background [" + col.getRed() + ","
+            + col.getGreen() + "," + col.getBlue() + "];");
+    jmolHistory(true);
   }
 
-  public void componentMoved(ComponentEvent e)
+  /**
+   * 
+   * @param pdbfile
+   * @return text report of alignment between pdbfile and any associated
+   *         alignment sequences
+   */
+  public String printMapping(String pdbfile)
   {
-
+    return getSsm().printMapping(pdbfile);
   }
 
-  public void componentShown(ComponentEvent e)
+  @Override
+  public void resizeInnerPanel(String data)
   {
-    showConsole(true);
+    // Jalview doesn't honour resize panel requests
+
   }
 
-  public void componentHidden(ComponentEvent e)
+  public boolean isFinishedInit()
   {
-    showConsole(false);
+    return finishedInit;
   }
 
-  public void setBackgroundColour(java.awt.Color col)
+  public void setFinishedInit(boolean finishedInit)
   {
-    jmolHistory(false);
-    viewer.evalStringQuiet("background [" + col.getRed() + ","
-            + col.getGreen() + "," + col.getBlue() + "];");
-    jmolHistory(true);
+    this.finishedInit = finishedInit;
   }
 
   /**
-   * add structures and any known sequence associations
    * 
-   * @returns the pdb entries added to the current set.
    */
-  public synchronized PDBEntry[] addSequenceAndChain(PDBEntry[] pdbe,
-          SequenceI[][] seq, String[][] chns)
+  protected void closeConsole()
   {
-    int pe = -1;
-    Vector v = new Vector();
-    Vector rtn = new Vector();
-    for (int i = 0; i < pdbentry.length; i++)
-    {
-      v.addElement(pdbentry[i]);
-    }
-    for (int i = 0; i < pdbe.length; i++)
+    if (console != null)
     {
-      int r = v.indexOf(pdbe[i]);
-      if (r == -1 || r >= pdbentry.length)
+      try
       {
-        rtn.addElement(new int[]
-        { v.size(), i });
-        v.addElement(pdbe[i]);
-      }
-      else
+        console.setVisible(false);
+      } catch (Error e)
       {
-        // just make sure the sequence/chain entries are all up to date
-        addSequenceAndChain(r, seq[i], chns[i]);
-      }
-    }
-    pdbe = new PDBEntry[v.size()];
-    v.copyInto(pdbe);
-    pdbentry = pdbe;
-    if (rtn.size() > 0)
-    {
-      // expand the tied seuqence[] and string[] arrays
-      SequenceI[][] sqs = new SequenceI[pdbentry.length][];
-      String[][] sch = new String[pdbentry.length][];
-      System.arraycopy(sequence, 0, sqs, 0, sequence.length);
-      System.arraycopy(chains, 0, sch, 0, this.chains.length);
-      sequence = sqs;
-      chains = sch;
-      pdbe = new PDBEntry[rtn.size()];
-      for (int r = 0; r < pdbe.length; r++)
+      } catch (Exception x)
       {
-        int[] stri = ((int[]) rtn.elementAt(r));
-        // record the pdb file as a new addition
-        pdbe[r] = pdbentry[stri[0]];
-        // and add the new sequence/chain entries
-        addSequenceAndChain(stri[0], seq[stri[1]], chns[stri[1]]);
       }
+      ;
+      console = null;
     }
-    else
-    {
-      pdbe = null;
-    }
-    return pdbe;
   }
 
-  public void addSequence(int pe, SequenceI[] seq)
+  /**
+   * ComponentListener method
+   */
+  @Override
+  public void componentMoved(ComponentEvent e)
   {
-    // add sequences to the pe'th pdbentry's seuqence set.
-    addSequenceAndChain(pe, seq, null);
   }
 
-  private void addSequenceAndChain(int pe, SequenceI[] seq, String[] tchain)
+  /**
+   * ComponentListener method
+   */
+  @Override
+  public void componentResized(ComponentEvent e)
   {
-    if (pe < 0 || pe >= pdbentry.length)
-    {
-      throw new Error(MessageManager.formatMessage("error.implementation_error_no_pdbentry_from_index", new String[]{Integer.valueOf(pe).toString()}));
-    }
-    final String nullChain = "TheNullChain";
-    Vector s = new Vector();
-    Vector c = new Vector();
-    if (chains == null)
-    {
-      chains = new String[pdbentry.length][];
-    }
-    if (sequence[pe] != null)
-    {
-      for (int i = 0; i < sequence[pe].length; i++)
-      {
-        s.addElement(sequence[pe][i]);
-        if (chains[pe] != null)
-        {
-          if (i < chains[pe].length)
-          {
-            c.addElement(chains[pe][i]);
-          }
-          else
-          {
-            c.addElement(nullChain);
-          }
-        }
-        else
-        {
-          if (tchain != null && tchain.length > 0)
-          {
-            c.addElement(nullChain);
-          }
-        }
-      }
-    }
-    for (int i = 0; i < seq.length; i++)
-    {
-      if (!s.contains(seq[i]))
-      {
-        s.addElement(seq[i]);
-        if (tchain != null && i < tchain.length)
-        {
-          c.addElement(tchain[i] == null ? nullChain : tchain[i]);
-        }
-      }
-    }
-    SequenceI[] tmp = new SequenceI[s.size()];
-    s.copyInto(tmp);
-    sequence[pe] = tmp;
-    if (c.size() > 0)
-    {
-      String[] tch = new String[c.size()];
-      c.copyInto(tch);
-      for (int i = 0; i < tch.length; i++)
-      {
-        if (tch[i] == nullChain)
-        {
-          tch[i] = null;
-        }
-      }
-      chains[pe] = tch;
-    }
-    else
-    {
-      chains[pe] = null;
-    }
   }
 
   /**
-   * 
-   * @param pdbfile
-   * @return text report of alignment between pdbfile and any associated
-   *         alignment sequences
+   * ComponentListener method
    */
-  public String printMapping(String pdbfile)
+  @Override
+  public void componentShown(ComponentEvent e)
   {
-    return ssm.printMapping(pdbfile);
+    showConsole(true);
   }
 
+  /**
+   * ComponentListener method
+   */
   @Override
-  public void resizeInnerPanel(String data)
+  public void componentHidden(ComponentEvent e)
   {
-    // Jalview doesn't honour resize panel requests
-
+    showConsole(false);
   }
 }
index d3c8c09..4afc526 100644 (file)
@@ -22,17 +22,20 @@ package jalview.ext.rbvi.chimera;
 
 import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
-import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceI;
 import jalview.structure.StructureMapping;
 import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
-import jalview.util.Format;
+import jalview.util.ColorUtils;
+import jalview.util.Comparison;
 
 import java.awt.Color;
 import java.util.ArrayList;
-import java.util.Hashtable;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
 
 /**
  * Routines for generating Chimera commands for Jalview/Chimera binding
@@ -55,104 +58,233 @@ public class ChimeraCommands
           SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
           AlignmentI alignment)
   {
+    Map<Color, Map<Integer, Map<String, List<int[]>>>> colourMap = buildColoursMap(
+            ssm, files, sequence, sr, fr, alignment);
 
-    ArrayList<StructureMappingcommandSet> cset = new ArrayList<StructureMappingcommandSet>();
-    Hashtable<String,StringBuffer> colranges=new Hashtable<String,StringBuffer>();
+    List<String> colourCommands = buildColourCommands(colourMap);
+
+    StructureMappingcommandSet cs = new StructureMappingcommandSet(
+            ChimeraCommands.class, null,
+            colourCommands.toArray(new String[0]));
+
+    return new StructureMappingcommandSet[]
+    { cs };
+  }
+
+  /**
+   * Traverse the map of colours/models/chains/positions to construct a list of
+   * 'color' commands (one per distinct colour used). The format of each command
+   * is
+   * 
+   * <blockquote> color colorname #modelnumber:range.chain e.g. color #00ff00
+   * #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,...
+   * 
+   * @see http
+   *      ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec
+   *      .html </pre>
+   * 
+   * @param colourMap
+   * @return
+   */
+  protected static List<String> buildColourCommands(
+          Map<Color, Map<Integer, Map<String, List<int[]>>>> colourMap)
+  {
+    /*
+     * This version concatenates all commands into a single String (semi-colon
+     * delimited). If length limit issues arise, refactor to return one color
+     * command per colour.
+     */
+    List<String> commands = new ArrayList<String>();
+    StringBuilder sb = new StringBuilder(256);
+    boolean firstColour = true;
+    for (Color colour : colourMap.keySet())
+    {
+      String colourCode = ColorUtils.toTkCode(colour);
+      if (!firstColour)
+      {
+        sb.append("; ");
+      }
+      sb.append("color ").append(colourCode).append(" ");
+      firstColour = false;
+      boolean firstModelForColour = true;
+      final Map<Integer, Map<String, List<int[]>>> colourData = colourMap.get(colour);
+      for (Integer model : colourData.keySet())
+      {
+        boolean firstPositionForModel = true;
+        if (!firstModelForColour)
+        {
+          sb.append("|");
+        }
+        firstModelForColour = false;
+        sb.append("#").append(model).append(":");
+
+        final Map<String, List<int[]>> modelData = colourData.get(model);
+        for (String chain : modelData.keySet())
+        {
+          for (int[] range : modelData.get(chain))
+          {
+            if (!firstPositionForModel)
+            {
+              sb.append(",");
+            }
+            if (range[0] == range[1])
+            {
+              sb.append(range[0]);
+            }
+            else
+            {
+              sb.append(range[0]).append("-").append(range[1]);
+            }
+            sb.append(".").append(chain);
+            firstPositionForModel = false;
+          }
+        }
+      }
+    }
+    commands.add(sb.toString());
+    return commands;
+  }
+
+  /**
+   * <pre>
+   * Build a data structure which maps contiguous subsequences for each colour. 
+   * This generates a data structure from which we can easily generate the 
+   * Chimera command for colour by sequence.
+   * Color
+   *     Model number
+   *         Chain
+   *             list of start/end ranges
+   * Ordering is by order of addition (for colours and positions), natural ordering (for models and chains)
+   * </pre>
+   */
+  protected static Map<Color, Map<Integer, Map<String, List<int[]>>>> buildColoursMap(
+          StructureSelectionManager ssm, String[] files,
+          SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
+          AlignmentI alignment)
+  {
+    Map<Color, Map<Integer, Map<String, List<int[]>>>> colourMap = new LinkedHashMap<Color, Map<Integer, Map<String, List<int[]>>>>();
+    Color lastColour = null;
     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
     {
-      float cols[] = new float[4];
       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
-      StringBuffer command = new StringBuffer();
-      StructureMappingcommandSet smc;
-      ArrayList<String> str = new ArrayList<String>();
 
       if (mapping == null || mapping.length < 1)
+      {
         continue;
+      }
 
-      int startPos = -1, lastPos = -1, startModel = -1, lastModel = -1;
-      String startChain = "", lastChain = "";
-      Color lastCol = null;
+      int startPos = -1, lastPos = -1;
+      String lastChain = "";
       for (int s = 0; s < sequence[pdbfnum].length; s++)
       {
         for (int sp, m = 0; m < mapping.length; m++)
         {
-          if (mapping[m].getSequence() == sequence[pdbfnum][s]
-                  && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1)
+          final SequenceI seq = sequence[pdbfnum][s];
+          if (mapping[m].getSequence() == seq
+                  && (sp = alignment.findIndex(seq)) > -1)
           {
             SequenceI asp = alignment.getSequenceAt(sp);
             for (int r = 0; r < asp.getLength(); r++)
             {
               // no mapping to gaps in sequence
-              if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
+              if (Comparison.isGap(asp.getCharAt(r)))
               {
                 continue;
               }
               int pos = mapping[m].getPDBResNum(asp.findPosition(r));
 
               if (pos < 1 || pos == lastPos)
+              {
                 continue;
+              }
 
-              Color col = sr.getResidueBoxColour(sequence[pdbfnum][s], r);
+              Color colour = sr.getResidueColour(seq, r, fr);
+              final String chain = mapping[m].getChain();
 
-              if (fr != null)
-                col = fr.findFeatureColour(col, sequence[pdbfnum][s], r);
-              if (lastCol != col || lastPos + 1 != pos
-                      || pdbfnum != lastModel
-                      || !mapping[m].getChain().equals(lastChain))
+              /*
+               * Just keep incrementing the end position for this colour range
+               * _unless_ colour, PDB model or chain has changed, or there is a
+               * gap in the mapped residue sequence
+               */
+              final boolean newColour = !colour.equals(lastColour);
+              final boolean nonContig = lastPos + 1 != pos;
+              final boolean newChain = !chain.equals(lastChain);
+              if (newColour || nonContig || newChain)
               {
-                if (lastCol != null)
+                if (startPos != -1)
                 {
-                  addColourRange(colranges, lastCol,startModel,startPos,lastPos,lastChain); 
+                  addColourRange(colourMap, lastColour, pdbfnum, startPos,
+                          lastPos, lastChain);
                 }
-                lastCol = null;
                 startPos = pos;
-                startModel = pdbfnum;
-                startChain = mapping[m].getChain();
               }
-              lastCol = col;
+              lastColour = colour;
               lastPos = pos;
-              lastModel = pdbfnum;
-              lastChain = mapping[m].getChain();
+              lastChain = chain;
             }
             // final colour range
-            if (lastCol != null)
+            if (lastColour != null)
             {
-              addColourRange(colranges, lastCol,startModel,startPos,lastPos,lastChain); 
+              addColourRange(colourMap, lastColour, pdbfnum, startPos,
+                      lastPos, lastChain);
             }
             break;
           }
         }
       }
-      // Finally, add the command set ready to be returned.
-      StringBuffer coms=new StringBuffer();
-      for (String cr:colranges.keySet())
-      {
-        coms.append("color #"+cr+" "+colranges.get(cr)+";");
-      }
-      cset.add(new StructureMappingcommandSet(ChimeraCommands.class,
-              files[pdbfnum], new String[] { coms.toString() }));
     }
-    return cset.toArray(new StructureMappingcommandSet[cset.size()]);
+    return colourMap;
   }
 
-  private static void addColourRange(Hashtable<String, StringBuffer> colranges, Color lastCol, int startModel,
-          int startPos, int lastPos, String lastChain)
+  /**
+   * Helper method to add one contiguous colour range to the colour map.
+   * 
+   * @param colourMap
+   * @param colour
+   * @param model
+   * @param startPos
+   * @param endPos
+   * @param chain
+   */
+  protected static void addColourRange(
+          Map<Color, Map<Integer, Map<String, List<int[]>>>> colourMap,
+          Color colour, int model, int startPos, int endPos, String chain)
   {
-    
-    String colstring = ((lastCol.getRed()< 16) ? "0":"")+Integer.toHexString(lastCol.getRed())
-            + ((lastCol.getGreen()< 16) ? "0":"")+Integer.toHexString(lastCol.getGreen())
-            + ((lastCol.getBlue()< 16) ? "0":"")+Integer.toHexString(lastCol.getBlue());
-    StringBuffer currange = colranges.get(colstring);
-    if (currange==null)
+    /*
+     * Get/initialize map of data for the colour
+     */
+    Map<Integer, Map<String, List<int[]>>> colourData = colourMap
+            .get(colour);
+    if (colourData == null)
+    {
+      colourMap
+              .put(colour,
+                      colourData = new TreeMap<Integer, Map<String, List<int[]>>>());
+    }
+
+    /*
+     * Get/initialize map of data for the colour and model
+     */
+    Map<String, List<int[]>> modelData = colourData.get(model);
+    if (modelData == null)
     {
-      colranges.put(colstring,currange = new StringBuffer());
+      colourData.put(model, modelData = new TreeMap<String, List<int[]>>());
     }
-    if (currange.length()>0)
+
+    /*
+     * Get/initialize map of data for colour, model and chain
+     */
+    List<int[]> chainData = modelData.get(chain);
+    if (chainData == null)
     {
-      currange.append("|");
+      modelData.put(chain, chainData = new ArrayList<int[]>());
     }
-    currange.append("#" + startModel + ":" + ((startPos==lastPos) ? startPos : startPos + "-"
-            + lastPos) + "." + lastChain);
+
+    /*
+     * Add the start/end positions
+     */
+    chainData.add(new int[]
+    { startPos, endPos });
   }
 
 }
index 82f5e5c..b345c5e 100644 (file)
@@ -23,24 +23,21 @@ package jalview.ext.rbvi.chimera;
 import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
-import jalview.api.SequenceStructureBinding;
-import jalview.api.StructureSelectionManagerProvider;
+import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
-import jalview.io.AppletFormatAdapter;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ResidueProperties;
-import jalview.structure.StructureListener;
 import jalview.structure.StructureMapping;
+import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
-import jalview.structures.models.SequenceStructureBindingModel;
+import jalview.structures.models.AAStructureBindingModel;
+import jalview.util.Comparison;
 import jalview.util.MessageManager;
 
 import java.awt.Color;
-import java.awt.event.ComponentEvent;
-import java.io.File;
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.HashMap;
@@ -53,11 +50,11 @@ import ext.edu.ucsf.rbvi.strucviz2.ChimeraModel;
 import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
 import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
 
-public abstract class JalviewChimeraBinding extends
-        SequenceStructureBindingModel implements StructureListener,
-        SequenceStructureBinding, StructureSelectionManagerProvider
-
+public abstract class JalviewChimeraBinding extends AAStructureBindingModel
 {
+
+  private static final boolean debug = false;
+
   private static final String PHOSPHORUS = "P";
 
   private static final String ALPHACARBON = "CA";
@@ -66,130 +63,106 @@ public abstract class JalviewChimeraBinding extends
 
   private ChimeraManager viewer;
 
-  /**
+  /*
    * set if chimera state is being restored from some source - instructs binding
    * not to apply default display style when structure set is updated for first
    * time.
    */
   private boolean loadingFromArchive = false;
 
-  /**
-   * second flag to indicate if the Chimera viewer should ignore sequence
-   * colouring events from the structure manager because the GUI is still
-   * setting up
+  /*
+   * flag to indicate if the Chimera viewer should ignore sequence colouring
+   * events from the structure manager because the GUI is still setting up
    */
   private boolean loadingFinished = true;
 
-  /**
+  /*
    * state flag used to check if the Chimera viewer's paint method can be called
    */
   private boolean finishedInit = false;
 
-  public boolean isFinishedInit()
-  {
-    return finishedInit;
-  }
-
-  public void setFinishedInit(boolean finishedInit)
-  {
-    this.finishedInit = finishedInit;
-  }
-
-  boolean allChainsSelected = false;
-
-  /**
-   * when true, try to search the associated datamodel for sequences that are
-   * associated with any unknown structures in the Chimera view.
-   */
-  private boolean associateNewStructs = false;
-
-  List<String> atomsPicked = new ArrayList<String>();
+  private List<String> atomsPicked = new ArrayList<String>();
 
-  public List<String> chainNames;
+  private List<String> chainNames;
 
   private Map<String, String> chainFile;
 
-  /**
-   * array of target chains for sequences - tied to pdbentry and sequence[]
-   */
-  protected String[][] chains;
-
-  boolean colourBySequence = true;
-
-  StringBuffer eval = new StringBuffer();
+  private StringBuffer eval = new StringBuffer();
 
   public String fileLoadingError;
 
-  private Map<String, List<ChimeraModel>> chimmaps = new LinkedHashMap<String, List<ChimeraModel>>();
-
-  private List<String> mdlToFile = new ArrayList<String>();
+  /*
+   * Map of ChimeraModel objects keyed by PDB full local file name
+   */
+  private Map<String, List<ChimeraModel>> chimeraMaps = new LinkedHashMap<String, List<ChimeraModel>>();
 
-  /**
+  /*
    * the default or current model displayed if the model cannot be identified
    * from the selection message
    */
-  int frameNo = 0;
+  private int frameNo = 0;
 
-  String lastCommand;
+  private String lastCommand;
 
-  String lastMessage;
+  private String lastMessage;
 
-  boolean loadedInline;
+  private boolean loadedInline;
 
+  /**
+   * Open a PDB structure file in Chimera and set up mappings from Jalview.
+   * 
+   * We check if the PDB model id is already loaded in Chimera, if so don't
+   * reopen it. This is the case if Chimera has opened a saved session file.
+   * 
+   * @param pe
+   * @return
+   */
   public boolean openFile(PDBEntry pe)
   {
     String file = pe.getFile();
     try
     {
+      List<ChimeraModel> modelsToMap = new ArrayList<ChimeraModel>();
       List<ChimeraModel> oldList = viewer.getModelList();
-      viewer.openModel(file, pe.getId(), ModelType.PDB_MODEL);
-      List<ChimeraModel> newList = viewer.getModelList();
-      if (oldList.size() < newList.size())
+      boolean alreadyOpen = false;
+
+      /*
+       * If Chimera already has this model, don't reopen it, but do remap it.
+       */
+      for (ChimeraModel open : oldList)
       {
-        while (oldList.size() > 0)
-        {
-          oldList.remove(0);
-          newList.remove(0);
-        }
-        chimmaps.put(file, newList);
-        for (ChimeraModel cm : newList)
+        if (open.getModelName().equals(pe.getId()))
         {
-          while (mdlToFile.size() < 1 + cm.getModelNumber())
-          {
-            mdlToFile.add(new String(""));
-          }
-          mdlToFile.set(cm.getModelNumber(), file);
+          alreadyOpen = true;
+          modelsToMap.add(open);
         }
+      }
 
-        File fl = new File(file);
-        String protocol = AppletFormatAdapter.URL;
-        try
-        {
-          if (fl.exists())
-          {
-            protocol = AppletFormatAdapter.FILE;
-          }
-        } catch (Exception e)
-        {
-        } catch (Error e)
-        {
-        }
-        // Explicitly map to the filename used by Chimera ;
-        // pdbentry[pe].getFile(), protocol);
+      /*
+       * If Chimera doesn't yet have this model, ask it to open it, and retrieve
+       * the model names added by Chimera.
+       */
+      if (!alreadyOpen)
+      {
+        viewer.openModel(file, pe.getId(), ModelType.PDB_MODEL);
+        modelsToMap = viewer.getModelList();
+        modelsToMap.removeAll(oldList);
+      }
+
+      chimeraMaps.put(file, modelsToMap);
 
-        if (ssm != null)
+      if (getSsm() != null)
+      {
+        getSsm().addStructureViewerListener(this);
+        // ssm.addSelectionListener(this);
+        FeatureRenderer fr = getFeatureRenderer(null);
+        if (fr != null)
         {
-          ssm.addStructureViewerListener(this);
-          // ssm.addSelectionListener(this);
-          FeatureRenderer fr = getFeatureRenderer(null);
-          if (fr != null)
-          {
-            fr.featuresAdded();
-          }
-          refreshGUI();
+          fr.featuresAdded();
         }
-        return true;
+        refreshGUI();
       }
+      return true;
     } catch (Exception q)
     {
       log("Exception when trying to open model " + file + "\n"
@@ -204,46 +177,40 @@ public abstract class JalviewChimeraBinding extends
    */
   String[] modelFileNames = null;
 
-  public PDBEntry[] pdbentry;
-
-  /**
-   * datasource protocol for access to PDBEntrylatest
-   */
-  String protocol = null;
 
   StringBuffer resetLastRes = new StringBuffer();
 
-  /**
-   * sequences mapped to each pdbentry
-   */
-  public SequenceI[][] sequence;
-
-  public StructureSelectionManager ssm;
-
   private List<String> lastReply;
 
+  /**
+   * Constructor
+   * 
+   * @param ssm
+   * @param pdbentry
+   * @param sequenceIs
+   * @param chains
+   * @param protocol
+   */
   public JalviewChimeraBinding(StructureSelectionManager ssm,
           PDBEntry[] pdbentry, SequenceI[][] sequenceIs, String[][] chains,
           String protocol)
   {
-    this.ssm = ssm;
-    this.sequence = sequenceIs;
-    this.chains = chains;
-    this.pdbentry = pdbentry;
-    this.protocol = protocol;
-    if (chains == null)
-    {
-      this.chains = new String[pdbentry.length][];
-    }
+    super(ssm, pdbentry, sequenceIs, chains, protocol);
     viewer = new ChimeraManager(
             csm = new ext.edu.ucsf.rbvi.strucviz2.StructureManager(true));
   }
 
+  /**
+   * Constructor
+   * 
+   * @param ssm
+   * @param theViewer
+   */
   public JalviewChimeraBinding(StructureSelectionManager ssm,
-          ChimeraManager viewer2)
+          ChimeraManager theViewer)
   {
-    this.ssm = ssm;
-    viewer = viewer2;
+    super(ssm, null);
+    viewer = theViewer;
     csm = viewer.getStructureManager();
   }
 
@@ -256,34 +223,7 @@ public abstract class JalviewChimeraBinding extends
    */
   public String getViewerTitle(boolean verbose)
   {
-    if (sequence == null || pdbentry == null || sequence.length < 1
-            || pdbentry.length < 1 || sequence[0].length < 1)
-    {
-      return ("Jalview Chimera Window");
-    }
-    // TODO: give a more informative title when multiple structures are
-    // displayed.
-    StringBuilder title = new StringBuilder(64);
-    title.append("Chimera view for " + sequence[0][0].getName() + ":"
-            + pdbentry[0].getId());
-
-    if (verbose)
-    {
-      if (pdbentry[0].getProperty() != null)
-      {
-        if (pdbentry[0].getProperty().get("method") != null)
-        {
-          title.append(" Method: ");
-          title.append(pdbentry[0].getProperty().get("method"));
-        }
-        if (pdbentry[0].getProperty().get("chains") != null)
-        {
-          title.append(" Chain:");
-          title.append(pdbentry[0].getProperty().get("chains"));
-        }
-      }
-    }
-    return title.toString();
+    return getViewerTitle("Chimera", verbose);
   }
 
   /**
@@ -319,12 +259,12 @@ public abstract class JalviewChimeraBinding extends
   }
 
   /**
-   * Close down the Jalview viewer, and (optionally) the associate Chimera
+   * Close down the Jalview viewer, and (optionally) the associated Chimera
    * window.
    */
   public void closeViewer(boolean closeChimera)
   {
-    ssm.removeStructureViewerListener(this, this.getPdbFile());
+    getSsm().removeStructureViewerListener(this, this.getPdbFile());
     if (closeChimera)
     {
       viewer.exitChimera();
@@ -334,12 +274,6 @@ public abstract class JalviewChimeraBinding extends
     releaseUIResources();
   }
 
-  /**
-   * called by JalviewChimerabinding after closeViewer is called - release any
-   * resources and references so they can be garbage collected.
-   */
-  protected abstract void releaseUIResources();
-
   public void colourByChain()
   {
     colourBySequence = false;
@@ -413,7 +347,7 @@ public abstract class JalviewChimeraBinding extends
         {
           // HACK - in Jalview 2.8 this call may not be threadsafe so we catch
           // every possible exception
-          StructureMapping[] sm = ssm.getMapping(file);
+          StructureMapping[] sm = getSsm().getMapping(file);
           if (sm == null || sm.length == 0)
           {
             waiting = true;
@@ -475,7 +409,7 @@ public abstract class JalviewChimeraBinding extends
       String[] atomSpec = new String[files.length];
       for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
       {
-        StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
+        StructureMapping[] mapping = getSsm().getMapping(files[pdbfnum]);
         // RACE CONDITION - getMapping only returns Jmol loaded filenames once
         // Jmol callback has completed.
         if (mapping == null || mapping.length < 1)
@@ -483,12 +417,14 @@ public abstract class JalviewChimeraBinding extends
           throw new Error(MessageManager.getString("error.implementation_error_chimera_getting_data"));
         }
         int lastPos = -1;
-        for (int s = 0; s < sequence[pdbfnum].length; s++)
+        final int seqCountForPdbFile = getSequence()[pdbfnum].length;
+        for (int s = 0; s < seqCountForPdbFile; s++)
         {
           for (int sp, m = 0; m < mapping.length; m++)
           {
-            if (mapping[m].getSequence() == sequence[pdbfnum][s]
-                    && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1)
+            final SequenceI theSequence = getSequence()[pdbfnum][s];
+            if (mapping[m].getSequence() == theSequence
+                    && (sp = alignment.findIndex(theSequence)) > -1)
             {
               if (refStructure == -1)
               {
@@ -507,7 +443,7 @@ public abstract class JalviewChimeraBinding extends
                   continue;
                 }
 
-                if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
+                if (Comparison.isGap(asp.getCharAt(r)))
                 {
                   // no mapping to gaps in sequence
                   continue;
@@ -541,7 +477,7 @@ public abstract class JalviewChimeraBinding extends
                       + targetC[pdbfnum];
               atomSpec[pdbfnum] = asp.getRNA() != null ? PHOSPHORUS : ALPHACARBON;
               // move on to next pdb file
-              s = sequence[pdbfnum].length;
+              s = seqCountForPdbFile;
               break;
             }
           }
@@ -654,10 +590,12 @@ public abstract class JalviewChimeraBinding extends
       }
       if (selectioncom.length() > 0)
       {
-        // TODO remove debug output
-        System.out.println("Select regions:\n" + selectioncom.toString());
-        System.out
-                .println("Superimpose command(s):\n" + command.toString());
+        if (debug)
+        {
+          System.out.println("Select regions:\n" + selectioncom.toString());
+          System.out.println("Superimpose command(s):\n"
+                  + command.toString());
+        }
         allComs.append("~display all; chain @CA|P; ribbon "
                 + selectioncom.toString() + ";"+command.toString());
         // selcom.append("; ribbons; ");
@@ -669,7 +607,10 @@ public abstract class JalviewChimeraBinding extends
       {
         selectioncom.setLength(selectioncom.length() - 1);
       }
-      System.out.println("Select regions:\n" + selectioncom.toString());
+      if (debug)
+      {
+        System.out.println("Select regions:\n" + selectioncom.toString());
+      }
       allComs.append("; ~display all; chain @CA|P; ribbon "
               + selectioncom.toString() + "; focus");
       // evalStateCommand("select *; backbone; select "+selcom.toString()+"; cartoons; center "+selcom.toString());
@@ -682,7 +623,7 @@ public abstract class JalviewChimeraBinding extends
   {
     if (!viewer.isChimeraLaunched())
     {
-      viewer.launchChimera(csm.getChimeraPaths());
+      viewer.launchChimera(StructureManager.getChimeraPaths());
     }
     if (!viewer.isChimeraLaunched())
     {
@@ -702,7 +643,8 @@ public abstract class JalviewChimeraBinding extends
   }
 
   /**
-   * Send a command to Chimera, and optionally log any responses.
+   * Send a command to Chimera, launching it first if necessary, and optionally
+   * log any responses.
    * 
    * @param command
    * @param logResponse
@@ -713,28 +655,12 @@ public abstract class JalviewChimeraBinding extends
     checkLaunched();
     if (lastCommand == null || !lastCommand.equals(command))
     {
-//      Thread t = new Thread(new Runnable()
-//      {
-//        @Override
-//        public void run()
-//        {
       // trim command or it may never find a match in the replyLog!!
       lastReply = viewer.sendChimeraCommand(command.trim(), logResponse);
       if (debug && logResponse)
-          {
-            log("Response from command ('" + command + "') was:\n"
-                    + lastReply);
-          }
-//        }
-//      });
-      // TODO - use j7/8 thread management
-//      try
-//      {
-//        t.join();
-//      } catch (InterruptedException foo)
-//      {
-//      }
-//      ;
+      {
+        log("Response from command ('" + command + "') was:\n" + lastReply);
+      }
     }
     viewerCommandHistory(true);
     lastCommand = command;
@@ -752,7 +678,7 @@ public abstract class JalviewChimeraBinding extends
     {
       return;
     }
-    if (ssm == null)
+    if (getSsm() == null)
     {
       return;
     }
@@ -767,19 +693,42 @@ public abstract class JalviewChimeraBinding extends
     }
     AlignmentI alignment = alignmentv.getAlignment();
 
-    for (jalview.structure.StructureMappingcommandSet cpdbbyseq : ChimeraCommands
-            .getColourBySequenceCommand(ssm, files, sequence, sr, fr,
-                    alignment))
+    for (jalview.structure.StructureMappingcommandSet cpdbbyseq : getColourBySequenceCommands(files, sr, fr, alignment))
     {
-      for (String cbyseq : cpdbbyseq.commands)
+      for (String command : cpdbbyseq.commands)
       {
-        waitForChimera();
-        evalStateCommand(cbyseq, false);
-        waitForChimera();
+        executeWhenReady(command);
       }
     }
   }
 
+  /**
+   * @param files
+   * @param sr
+   * @param fr
+   * @param alignment
+   * @return
+   */
+  protected StructureMappingcommandSet[] getColourBySequenceCommands(
+          String[] files, SequenceRenderer sr, FeatureRenderer fr,
+          AlignmentI alignment)
+  {
+    return ChimeraCommands
+            .getColourBySequenceCommand(getSsm(), files, getSequence(), sr,
+                    fr,
+                    alignment);
+  }
+
+  /**
+   * @param command
+   */
+  protected void executeWhenReady(String command)
+  {
+    waitForChimera();
+    evalStateCommand(command, false);
+    waitForChimera();
+  }
+
   private void waitForChimera()
   {
     while (viewer != null && viewer.isBusy())
@@ -791,30 +740,11 @@ public abstract class JalviewChimeraBinding extends
     }
   }
 
-  public boolean isColourBySequence()
-  {
-    return colourBySequence;
-  }
-
-  public void setColourBySequence(boolean colourBySequence)
-  {
-    this.colourBySequence = colourBySequence;
-  }
+  
 
   // End StructureListener
   // //////////////////////////
 
-  public float[][] functionXY(String functionName, int x, int y)
-  {
-    return null;
-  }
-
-  public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
-  {
-    // TODO Auto-generated method stub
-    return null;
-  }
-
   public Color getColour(int atomIndex, int pdbResNum, String chain,
           String pdbfile)
   {
@@ -889,8 +819,8 @@ public abstract class JalviewChimeraBinding extends
     // // System.arraycopy(mset, 0, modelFileNames, 0, j);
     // }
 
-    return chimmaps.keySet().toArray(
-            modelFileNames = new String[chimmaps.size()]);
+    return chimeraMaps.keySet().toArray(
+            modelFileNames = new String[chimeraMaps.size()]);
   }
 
   /**
@@ -917,7 +847,7 @@ public abstract class JalviewChimeraBinding extends
   public void highlightAtom(int atomIndex, int pdbResNum, String chain,
           String pdbfile)
   {
-    List<ChimeraModel> cms = chimmaps.get(pdbfile);
+    List<ChimeraModel> cms = chimeraMaps.get(pdbfile);
     if (cms != null)
     {
       int mdlNum = cms.get(0).getModelNumber();
@@ -954,8 +884,6 @@ public abstract class JalviewChimeraBinding extends
     }
   }
 
-  boolean debug = false;
-
   private void log(String message)
   {
     System.err.println("## Chimera log: " + message);
@@ -1057,7 +985,7 @@ public abstract class JalviewChimeraBinding extends
     }
     if (lastMessage == null || !lastMessage.equals(strInfo))
     {
-      ssm.mouseOverStructure(pdbResNum, chainId, pdbfilename);
+      getSsm().mouseOverStructure(pdbResNum, chainId, pdbfilename);
     }
 
     lastMessage = strInfo;
@@ -1182,13 +1110,13 @@ public abstract class JalviewChimeraBinding extends
         }
         // deregister the Jmol instance for these structures - we'll add
         // ourselves again at the end for the current structure set.
-        ssm.removeStructureViewerListener(this, oldmfn);
+        getSsm().removeStructureViewerListener(this, oldmfn);
       }
     }
 
     // register ourselves as a listener and notify the gui that it needs to
     // update itself.
-    ssm.addStructureViewerListener(this);
+    getSsm().addStructureViewerListener(this);
 
     if (notifyLoaded)
     {
@@ -1248,24 +1176,6 @@ public abstract class JalviewChimeraBinding extends
    */
   public abstract void refreshGUI();
 
-  public void componentResized(ComponentEvent e)
-  {
-
-  }
-
-  public void componentMoved(ComponentEvent e)
-  {
-
-  }
-
-  public void componentShown(ComponentEvent e)
-  {
-  }
-
-  public void componentHidden(ComponentEvent e)
-  {
-  }
-
   public void setLoadingFromArchive(boolean loadingFromArchive)
   {
     this.loadingFromArchive = loadingFromArchive;
@@ -1312,154 +1222,69 @@ public abstract class JalviewChimeraBinding extends
   }
 
   /**
-   * add structures and any known sequence associations
    * 
-   * @returns the pdb entries added to the current set.
+   * @param pdbfile
+   * @return text report of alignment between pdbfile and any associated
+   *         alignment sequences
    */
-  public synchronized PDBEntry[] addSequenceAndChain(PDBEntry[] pdbe,
-          SequenceI[][] seq, String[][] chns)
+  public String printMapping(String pdbfile)
   {
-    List<PDBEntry> v = new ArrayList<PDBEntry>();
-    List<int[]> rtn = new ArrayList<int[]>();
-    for (int i = 0; i < pdbentry.length; i++)
-    {
-      v.add(pdbentry[i]);
-    }
-    for (int i = 0; i < pdbe.length; i++)
+    return getSsm().printMapping(pdbfile);
+  }
+
+  /**
+   * Ask Chimera to save its session to the given file. Returns true if
+   * successful, else false.
+   * 
+   * @param filepath
+   * @return
+   */
+  public boolean saveSession(String filepath)
+  {
+    if (isChimeraRunning())
     {
-      int r = v.indexOf(pdbe[i]);
-      if (r == -1 || r >= pdbentry.length)
+      List<String> reply = viewer.sendChimeraCommand("save " + filepath,
+              true);
+      if (reply.contains("Session written"))
       {
-        rtn.add(new int[]
-        { v.size(), i });
-        v.add(pdbe[i]);
+        return true;
       }
       else
       {
-        // just make sure the sequence/chain entries are all up to date
-        addSequenceAndChain(r, seq[i], chns[i]);
+        Cache.log
+                .error("Error saving Chimera session: " + reply.toString());
       }
     }
-    pdbe = v.toArray(new PDBEntry[v.size()]);
-    pdbentry = pdbe;
-    if (rtn.size() > 0)
-    {
-      // expand the tied sequence[] and string[] arrays
-      SequenceI[][] sqs = new SequenceI[pdbentry.length][];
-      String[][] sch = new String[pdbentry.length][];
-      System.arraycopy(sequence, 0, sqs, 0, sequence.length);
-      System.arraycopy(chains, 0, sch, 0, this.chains.length);
-      sequence = sqs;
-      chains = sch;
-      pdbe = new PDBEntry[rtn.size()];
-      for (int r = 0; r < pdbe.length; r++)
-      {
-        int[] stri = (rtn.get(r));
-        // record the pdb file as a new addition
-        pdbe[r] = pdbentry[stri[0]];
-        // and add the new sequence/chain entries
-        addSequenceAndChain(stri[0], seq[stri[1]], chns[stri[1]]);
-      }
-    }
-    else
-    {
-      pdbe = null;
-    }
-    return pdbe;
+    return false;
   }
 
   /**
-   * Adds sequences to the pe'th pdbentry's sequence set.
+   * Ask Chimera to open a session file. Returns true if successful, else false.
+   * The filename must have a .py extension for this command to work.
    * 
-   * @param pe
-   * @param seq
+   * @param filepath
+   * @return
    */
-  public void addSequence(int pe, SequenceI[] seq)
+  public boolean openSession(String filepath)
   {
-    addSequenceAndChain(pe, seq, null);
+    evalStateCommand("open " + filepath, true);
+    // todo: test for failure - how?
+    return true;
   }
 
-  private void addSequenceAndChain(int pe, SequenceI[] seq, String[] tchain)
+  public boolean isFinishedInit()
   {
-    if (pe < 0 || pe >= pdbentry.length)
-    {
-      throw new Error(MessageManager.formatMessage(
-              "error.implementation_error_no_pdbentry_from_index",
-              new Object[]
-              { Integer.valueOf(pe).toString() }));
-    }
-    final String nullChain = "TheNullChain";
-    List<SequenceI> s = new ArrayList<SequenceI>();
-    List<String> c = new ArrayList<String>();
-    if (chains == null)
-    {
-      chains = new String[pdbentry.length][];
-    }
-    if (sequence[pe] != null)
-    {
-      for (int i = 0; i < sequence[pe].length; i++)
-      {
-        s.add(sequence[pe][i]);
-        if (chains[pe] != null)
-        {
-          if (i < chains[pe].length)
-          {
-            c.add(chains[pe][i]);
-          }
-          else
-          {
-            c.add(nullChain);
-          }
-        }
-        else
-        {
-          if (tchain != null && tchain.length > 0)
-          {
-            c.add(nullChain);
-          }
-        }
-      }
-    }
-    for (int i = 0; i < seq.length; i++)
-    {
-      if (!s.contains(seq[i]))
-      {
-        s.add(seq[i]);
-        if (tchain != null && i < tchain.length)
-        {
-          c.add(tchain[i] == null ? nullChain : tchain[i]);
-        }
-      }
-    }
-    SequenceI[] tmp = s.toArray(new SequenceI[s.size()]);
-    sequence[pe] = tmp;
-    if (c.size() > 0)
-    {
-      String[] tch = c.toArray(new String[c.size()]);
-      for (int i = 0; i < tch.length; i++)
-      {
-        if (tch[i] == nullChain)
-        {
-          tch[i] = null;
-        }
-      }
-      chains[pe] = tch;
-    }
-    else
-    {
-      chains[pe] = null;
-    }
+    return finishedInit;
   }
 
-  /**
-   * 
-   * @param pdbfile
-   * @return text report of alignment between pdbfile and any associated
-   *         alignment sequences
-   */
-  public String printMapping(String pdbfile)
+  public void setFinishedInit(boolean finishedInit)
+  {
+    this.finishedInit = finishedInit;
+  }
+
+  public List<String> getChainNames()
   {
-    return ssm.printMapping(pdbfile);
+    return chainNames;
   }
 
 }
index 6a32f30..0df55b1 100644 (file)
  */
 package jalview.ext.varna;
 
-import java.awt.event.*;
-
-import jalview.api.SequenceStructureBinding;
 import jalview.api.StructureSelectionManagerProvider;
-import jalview.structure.*;
+import jalview.structure.StructureListener;
 import jalview.structures.models.SequenceStructureBindingModel;
 
-public abstract class JalviewVarnaBinding extends SequenceStructureBindingModel implements StructureListener,
-        SequenceStructureBinding, ComponentListener,
-        StructureSelectionManagerProvider
+import java.awt.event.ComponentListener;
+
+public abstract class JalviewVarnaBinding extends
+        SequenceStructureBindingModel implements StructureListener,
+        ComponentListener, StructureSelectionManagerProvider
 
 {
 
index 50043e6..6e3024d 100644 (file)
@@ -55,10 +55,11 @@ import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.io.AlignmentProperties;
 import jalview.io.AnnotationFile;
+import jalview.io.BioJsHTMLOutput;
 import jalview.io.FeaturesFile;
 import jalview.io.FileLoader;
 import jalview.io.FormatAdapter;
-import jalview.io.HTMLOutput;
+import jalview.io.HtmlSvgOutput;
 import jalview.io.IdentifyFile;
 import jalview.io.JalviewFileChooser;
 import jalview.io.JalviewFileView;
@@ -381,7 +382,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                         .getKeyCode() <= KeyEvent.VK_NUMPAD9))
                 && Character.isDigit(evt.getKeyChar()))
         {
-          alignPanel.seqPanel.numberPressed(evt.getKeyChar());
+          alignPanel.getSeqPanel().numberPressed(evt.getKeyChar());
         }
 
         switch (evt.getKeyCode())
@@ -399,7 +400,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           }
           if (viewport.cursorMode)
           {
-            alignPanel.seqPanel.moveCursor(0, 1);
+            alignPanel.getSeqPanel().moveCursor(0, 1);
           }
           break;
 
@@ -410,7 +411,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           }
           if (viewport.cursorMode)
           {
-            alignPanel.seqPanel.moveCursor(0, -1);
+            alignPanel.getSeqPanel().moveCursor(0, -1);
           }
 
           break;
@@ -418,11 +419,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         case KeyEvent.VK_LEFT:
           if (evt.isAltDown() || !viewport.cursorMode)
           {
-            slideSequences(false, alignPanel.seqPanel.getKeyboardNo1());
+            slideSequences(false, alignPanel.getSeqPanel().getKeyboardNo1());
           }
           else
           {
-            alignPanel.seqPanel.moveCursor(-1, 0);
+            alignPanel.getSeqPanel().moveCursor(-1, 0);
           }
 
           break;
@@ -430,18 +431,18 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         case KeyEvent.VK_RIGHT:
           if (evt.isAltDown() || !viewport.cursorMode)
           {
-            slideSequences(true, alignPanel.seqPanel.getKeyboardNo1());
+            slideSequences(true, alignPanel.getSeqPanel().getKeyboardNo1());
           }
           else
           {
-            alignPanel.seqPanel.moveCursor(1, 0);
+            alignPanel.getSeqPanel().moveCursor(1, 0);
           }
           break;
 
         case KeyEvent.VK_SPACE:
           if (viewport.cursorMode)
           {
-            alignPanel.seqPanel.insertGapAtCursor(evt.isControlDown()
+            alignPanel.getSeqPanel().insertGapAtCursor(evt.isControlDown()
                     || evt.isShiftDown() || evt.isAltDown());
           }
           break;
@@ -465,7 +466,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
           }
           else
           {
-            alignPanel.seqPanel.deleteGapAtCursor(evt.isControlDown()
+            alignPanel.getSeqPanel().deleteGapAtCursor(evt.isControlDown()
                     || evt.isShiftDown() || evt.isAltDown());
           }
 
@@ -474,19 +475,19 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         case KeyEvent.VK_S:
           if (viewport.cursorMode)
           {
-            alignPanel.seqPanel.setCursorRow();
+            alignPanel.getSeqPanel().setCursorRow();
           }
           break;
         case KeyEvent.VK_C:
           if (viewport.cursorMode && !evt.isControlDown())
           {
-            alignPanel.seqPanel.setCursorColumn();
+            alignPanel.getSeqPanel().setCursorColumn();
           }
           break;
         case KeyEvent.VK_P:
           if (viewport.cursorMode)
           {
-            alignPanel.seqPanel.setCursorPosition();
+            alignPanel.getSeqPanel().setCursorPosition();
           }
           break;
 
@@ -494,20 +495,20 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         case KeyEvent.VK_COMMA:
           if (viewport.cursorMode)
           {
-            alignPanel.seqPanel.setCursorRowAndColumn();
+            alignPanel.getSeqPanel().setCursorRowAndColumn();
           }
           break;
 
         case KeyEvent.VK_Q:
           if (viewport.cursorMode)
           {
-            alignPanel.seqPanel.setSelectionAreaAtCursor(true);
+            alignPanel.getSeqPanel().setSelectionAreaAtCursor(true);
           }
           break;
         case KeyEvent.VK_M:
           if (viewport.cursorMode)
           {
-            alignPanel.seqPanel.setSelectionAreaAtCursor(false);
+            alignPanel.getSeqPanel().setSelectionAreaAtCursor(false);
           }
           break;
 
@@ -518,10 +519,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                   { (viewport.cursorMode ? "on" : "off") }));
           if (viewport.cursorMode)
           {
-            alignPanel.seqPanel.seqCanvas.cursorX = viewport.startRes;
-            alignPanel.seqPanel.seqCanvas.cursorY = viewport.startSeq;
+            alignPanel.getSeqPanel().seqCanvas.cursorX = viewport.startRes;
+            alignPanel.getSeqPanel().seqCanvas.cursorY = viewport.startSeq;
           }
-          alignPanel.seqPanel.seqCanvas.repaint();
+          alignPanel.getSeqPanel().seqCanvas.repaint();
           break;
 
         case KeyEvent.VK_F1:
@@ -740,14 +741,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     abovePIDThreshold.setSelected(av.getAbovePIDThreshold());
     conservationMenuItem.setSelected(av.getConservationSelected());
     seqLimits.setSelected(av.getShowJVSuffix());
-    idRightAlign.setSelected(av.rightAlignIds);
+    idRightAlign.setSelected(av.isRightAlignIds());
     centreColumnLabelsMenuItem.setState(av.centreColumnLabels);
     renderGapsMenuItem.setSelected(av.renderGaps);
     wrapMenuItem.setSelected(av.wrapAlignment);
     scaleAbove.setVisible(av.wrapAlignment);
     scaleLeft.setVisible(av.wrapAlignment);
     scaleRight.setVisible(av.wrapAlignment);
-    annotationPanelMenuItem.setState(av.showAnnotation);
+    annotationPanelMenuItem.setState(av.isShowAnnotation());
     /*
      * Show/hide annotations only enabled if annotation panel is shown
      */
@@ -767,7 +768,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     setColourSelected(ColourSchemeProperty.getColourName(av
             .getGlobalColourScheme()));
 
-    showSeqFeatures.setSelected(av.showSequenceFeatures);
+    showSeqFeatures.setSelected(av.isShowSequenceFeatures());
     hiddenMarkers.setState(av.showHiddenMarkers);
     applyToAllGroups.setState(av.getColourAppliesToAllGroups());
     showNpFeatsMenuitem.setSelected(av.isShowNpFeats());
@@ -898,7 +899,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
   public FeatureRenderer getFeatureRenderer()
   {
-    return alignPanel.seqPanel.seqCanvas.getFeatureRenderer();
+    return alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer();
   }
 
   @Override
@@ -1075,10 +1076,15 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
                 .lastIndexOf(java.io.File.separatorChar) + 1);
       }
 
-      success = new Jalview2XML().SaveAlignment(this, file, shortName);
+      /*
+       * First save any linked Chimera session.
+       */
+      Desktop.instance.saveChimeraSessions(file);
+
+      success = new Jalview2XML().saveAlignment(this, file, shortName);
 
       statusBar.setText(MessageManager.formatMessage(
-              "label.successfully_saved_to_file_in_format", new String[]
+              "label.successfully_saved_to_file_in_format", new Object[]
               { fileName, format }));
 
     }
@@ -1229,11 +1235,18 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   protected void htmlMenuItem_actionPerformed(ActionEvent e)
   {
-    new HTMLOutput(alignPanel,
-            alignPanel.seqPanel.seqCanvas.getSequenceRenderer(),
-            alignPanel.seqPanel.seqCanvas.getFeatureRenderer());
+    // new HTMLOutput(alignPanel,
+    // alignPanel.getSeqPanel().seqCanvas.getSequenceRenderer(),
+    // alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer());
+    new HtmlSvgOutput(null, alignPanel);
   }
 
+  @Override
+  public void bioJSMenuItem_actionPerformed(ActionEvent e)
+  {
+    new BioJsHTMLOutput(alignPanel,
+            alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer());
+  }
   public void createImageMap(File file, String image)
   {
     alignPanel.makePNGImageMap(file, image);
@@ -1297,11 +1310,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   public void exportAnnotations_actionPerformed(ActionEvent e)
   {
-    new AnnotationExporter().exportAnnotations(alignPanel,
-            viewport.showAnnotation ? viewport.getAlignment()
-                    .getAlignmentAnnotation() : null, viewport
-                    .getAlignment().getGroups(), ((Alignment) viewport
-                    .getAlignment()).alignmentProperties);
+    new AnnotationExporter().exportAnnotations(alignPanel);
   }
 
   @Override
@@ -1624,7 +1633,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     if (viewport.cursorMode)
     {
       sg.add(viewport.getAlignment().getSequenceAt(
-              alignPanel.seqPanel.seqCanvas.cursorY));
+              alignPanel.getSeqPanel().seqCanvas.cursorY));
     }
     else if (viewport.getSelectionGroup() != null
             && viewport.getSelectionGroup().getSize() != viewport
@@ -1674,7 +1683,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     {
       if (viewport.cursorMode)
       {
-        alignPanel.seqPanel.moveCursor(size, 0);
+        alignPanel.getSeqPanel().moveCursor(size, 0);
       }
       else
       {
@@ -1685,7 +1694,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     {
       if (viewport.cursorMode)
       {
-        alignPanel.seqPanel.moveCursor(-size, 0);
+        alignPanel.getSeqPanel().moveCursor(-size, 0);
       }
       else
       {
@@ -1763,20 +1772,17 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       return;
     }
 
-    Vector hiddenColumns = null;
+    ArrayList<int[]> hiddenColumns = null;
     if (viewport.hasHiddenColumns())
     {
-      hiddenColumns = new Vector();
+      hiddenColumns = new ArrayList<int[]>();
       int hiddenOffset = viewport.getSelectionGroup().getStartRes(), hiddenCutoff = viewport
               .getSelectionGroup().getEndRes();
-      for (int i = 0; i < viewport.getColumnSelection().getHiddenColumns()
-              .size(); i++)
+      for (int[] region : viewport.getColumnSelection().getHiddenColumns())
       {
-        int[] region = (int[]) viewport.getColumnSelection()
-                .getHiddenColumns().elementAt(i);
         if (region[0] >= hiddenOffset && region[1] <= hiddenCutoff)
         {
-          hiddenColumns.addElement(new int[]
+          hiddenColumns.add(new int[]
           { region[0] - hiddenOffset, region[1] - hiddenOffset });
         }
       }
@@ -2103,19 +2109,18 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
         if (Desktop.jalviewClipboard != null
                 && Desktop.jalviewClipboard[2] != null)
         {
-          Vector hc = (Vector) Desktop.jalviewClipboard[2];
-          for (int i = 0; i < hc.size(); i++)
+          List<int[]> hc = (List<int[]>) Desktop.jalviewClipboard[2];
+          for (int[] region : hc)
           {
-            int[] region = (int[]) hc.elementAt(i);
             af.viewport.hideColumns(region[0], region[1]);
           }
         }
 
         // >>>This is a fix for the moment, until a better solution is
         // found!!<<<
-        af.alignPanel.seqPanel.seqCanvas.getFeatureRenderer()
+        af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
                 .transferSettings(
-                        alignPanel.seqPanel.seqCanvas.getFeatureRenderer());
+                        alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer());
 
         // TODO: maintain provenance of an alignment, rather than just make the
         // title a concatenation of operations.
@@ -2163,19 +2168,18 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       if (Desktop.jalviewClipboard != null
               && Desktop.jalviewClipboard[2] != null)
       {
-        Vector hc = (Vector) Desktop.jalviewClipboard[2];
-        for (int i = 0; i < hc.size(); i++)
+        List<int[]> hc = (List<int[]>) Desktop.jalviewClipboard[2];
+        for (int region[] : hc)
         {
-          int[] region = (int[]) hc.elementAt(i);
           af.viewport.hideColumns(region[0], region[1]);
         }
       }
 
       // >>>This is a fix for the moment, until a better solution is
       // found!!<<<
-      af.alignPanel.seqPanel.seqCanvas.getFeatureRenderer()
+      af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
               .transferSettings(
-                      alignPanel.seqPanel.seqCanvas.getFeatureRenderer());
+                      alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer());
 
       // TODO: maintain provenance of an alignment, rather than just make the
       // title a concatenation of operations.
@@ -2339,14 +2343,14 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   {
     if (viewport.cursorMode)
     {
-      alignPanel.seqPanel.keyboardNo1 = null;
-      alignPanel.seqPanel.keyboardNo2 = null;
+      alignPanel.getSeqPanel().keyboardNo1 = null;
+      alignPanel.getSeqPanel().keyboardNo2 = null;
     }
     viewport.setSelectionGroup(null);
     viewport.getColumnSelection().clear();
     viewport.setSelectionGroup(null);
-    alignPanel.seqPanel.seqCanvas.highlightSearchResults(null);
-    alignPanel.idPanel.idCanvas.searchResults = null;
+    alignPanel.getSeqPanel().seqCanvas.highlightSearchResults(null);
+    alignPanel.getIdPanel().getIdCanvas().searchResults = null;
     alignPanel.paintAlignment(true);
     PaintRefresher.Refresh(alignPanel, viewport.getSequenceSetId());
     viewport.sendSelection();
@@ -2747,7 +2751,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   {
     viewport.setShowJVSuffix(seqLimits.isSelected());
 
-    alignPanel.idPanel.idCanvas.setPreferredSize(alignPanel
+    alignPanel.getIdPanel().getIdCanvas().setPreferredSize(alignPanel
             .calculateIdWidth());
     alignPanel.paintAlignment(true);
   }
@@ -2755,7 +2759,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   @Override
   public void idRightAlign_actionPerformed(ActionEvent e)
   {
-    viewport.rightAlignIds = idRightAlign.isSelected();
+    viewport.setRightAlignIds(idRightAlign.isSelected());
     alignPanel.paintAlignment(true);
   }
 
@@ -2777,7 +2781,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     if (viewport.followHighlight = this.followHighlightMenuItem.getState())
     {
       alignPanel.scrollToPosition(
-              alignPanel.seqPanel.seqCanvas.searchResults, false);
+              alignPanel.getSeqPanel().seqCanvas.searchResults, false);
     }
   }
 
@@ -3084,7 +3088,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   {
     viewport.setShowSequenceFeaturesHeight(showSeqFeaturesHeight
             .isSelected());
-    if (viewport.getShowSequenceFeaturesHeight())
+    if (viewport.isShowSequenceFeaturesHeight())
     {
       // ensure we're actually displaying features
       viewport.setShowSequenceFeatures(true);
@@ -3317,6 +3321,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   }
 
   @Override
+  public void annotationColumn_actionPerformed(ActionEvent e)
+  {
+    new AnnotationColumnChooser(viewport, alignPanel);
+  }
+
+  @Override
   public void rnahelicesColour_actionPerformed(ActionEvent e)
   {
     new RNAHelicesColourChooser(viewport, alignPanel);
@@ -4873,8 +4883,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     try
     {
       featuresFile = new FeaturesFile(file, type).parse(viewport
-              .getAlignment().getDataset(), alignPanel.seqPanel.seqCanvas
-              .getFeatureRenderer().featureColours, false,
+              .getAlignment().getDataset(), alignPanel.getSeqPanel().seqCanvas
+              .getFeatureRenderer().getFeatureColours(), false,
               jalview.bin.Cache.getDefault("RELAXEDSEQIDMATCHING", false));
     } catch (Exception ex)
     {
@@ -4883,12 +4893,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
     if (featuresFile)
     {
-      viewport.showSequenceFeatures = true;
+      viewport.setShowSequenceFeatures(true);
       showSeqFeatures.setSelected(true);
-      if (alignPanel.seqPanel.seqCanvas.fr != null)
+      if (alignPanel.getSeqPanel().seqCanvas.fr != null)
       {
         // update the min/max ranges where necessary
-        alignPanel.seqPanel.seqCanvas.fr.findAllFeatures(true);
+        alignPanel.getSeqPanel().seqCanvas.fr.findAllFeatures(true);
       }
       if (featureSettings != null)
       {
@@ -5140,7 +5150,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       // try to parse as annotation.
       boolean isAnnotation = (format == null || format
               .equalsIgnoreCase("PFAM")) ? new AnnotationFile()
-              .readAnnotationFile(viewport.getAlignment(), file, protocol)
+              .annotateAlignmentView(viewport, file, protocol)
               : false;
 
       if (!isAnnotation)
@@ -5745,6 +5755,16 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
       alignPanel.paintAlignment(true);
     }
   }
+  public void clearAlignmentSeqRep()
+  {
+    // TODO refactor alignmentseqrep to controller
+    if (viewport.getAlignment().hasSeqrep()) {
+      viewport.getAlignment().setSeqrep(null);
+      PaintRefresher.Refresh(this, viewport.getSequenceSetId());
+      alignPanel.updateAnnotation();
+      alignPanel.paintAlignment(true);
+    }
+  }
 
   @Override
   protected void createGroup_actionPerformed(ActionEvent e)
index d24f6c4..1c105d0 100644 (file)
@@ -95,10 +95,6 @@ public class AlignViewport extends AlignmentViewport implements
 
   boolean renderGaps = true;
 
-  boolean showSequenceFeatures = false;
-
-  boolean showAnnotation = true;
-
   SequenceAnnotationOrder sortAnnotationsBy = null;
 
   int charHeight;
@@ -125,12 +121,6 @@ public class AlignViewport extends AlignmentViewport implements
 
   boolean cursorMode = false;
 
-  /**
-   * Keys are the feature types which are currently visible. Note: Values are
-   * not used!
-   */
-  Hashtable featuresDisplayed = null;
-
   boolean antiAlias = false;
 
   Rectangle explodedPosition;
@@ -149,8 +139,7 @@ public class AlignViewport extends AlignmentViewport implements
 
   Color textColour2 = Color.white;
 
-  boolean rightAlignIds = false;
-
+  private AnnotationColumnChooser annotationColumnSelectionState;
   /**
    * Creates a new AlignViewport object.
    * 
@@ -206,16 +195,7 @@ public class AlignViewport extends AlignmentViewport implements
     setAlignment(al);
     if (hiddenColumns != null)
     {
-      this.colSel = hiddenColumns;
-      if (hiddenColumns.getHiddenColumns() != null
-              && hiddenColumns.getHiddenColumns().size() > 0)
-      {
-        hasHiddenColumns = true;
-      }
-      else
-      {
-        hasHiddenColumns = false;
-      }
+      colSel = hiddenColumns;
     }
     init();
   }
@@ -262,16 +242,7 @@ public class AlignViewport extends AlignmentViewport implements
     setAlignment(al);
     if (hiddenColumns != null)
     {
-      this.colSel = hiddenColumns;
-      if (hiddenColumns.getHiddenColumns() != null
-              && hiddenColumns.getHiddenColumns().size() > 0)
-      {
-        hasHiddenColumns = true;
-      }
-      else
-      {
-        hasHiddenColumns = false;
-      }
+      colSel = hiddenColumns;
     }
     init();
   }
@@ -286,9 +257,9 @@ public class AlignViewport extends AlignmentViewport implements
     antiAlias = Cache.getDefault("ANTI_ALIAS", false);
 
     showJVSuffix = Cache.getDefault("SHOW_JVSUFFIX", true);
-    showAnnotation = Cache.getDefault("SHOW_ANNOTATIONS", true);
+    setShowAnnotation(Cache.getDefault("SHOW_ANNOTATIONS", true));
 
-    rightAlignIds = Cache.getDefault("RIGHT_ALIGN_IDS", false);
+    setRightAlignIds(Cache.getDefault("RIGHT_ALIGN_IDS", false));
     centreColumnLabels = Cache.getDefault("CENTRE_COLUMN_LABELS", false);
     autoCalculateConsensus = Cache.getDefault("AUTO_CALC_CONSENSUS", true);
 
@@ -368,22 +339,6 @@ public class AlignViewport extends AlignmentViewport implements
   }
 
   /**
-   * set the flag
-   * 
-   * @param b
-   *          features are displayed if true
-   */
-  public void setShowSequenceFeatures(boolean b)
-  {
-    showSequenceFeatures = b;
-  }
-
-  public boolean getShowSequenceFeatures()
-  {
-    return showSequenceFeatures;
-  }
-
-  /**
    * centre columnar annotation labels in displayed alignment annotation TODO:
    * add to jalviewXML and annotation display settings
    */
@@ -837,27 +792,6 @@ public class AlignViewport extends AlignmentViewport implements
    * 
    * @return DOCUMENT ME!
    */
-  public boolean getShowAnnotation()
-  {
-    return showAnnotation;
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param b
-   *          DOCUMENT ME!
-   */
-  public void setShowAnnotation(boolean b)
-  {
-    showAnnotation = b;
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
   public boolean getScaleAboveWrapped()
   {
     return scaleAboveWrapped;
@@ -1057,23 +991,6 @@ public class AlignViewport extends AlignmentViewport implements
     shownpfeats = show;
   }
 
-  /**
-   * 
-   * @return true if view has hidden rows
-   */
-  public boolean hasHiddenRows()
-  {
-    return hasHiddenRows;
-  }
-
-  /**
-   * 
-   * @return true if view has hidden columns
-   */
-  public boolean hasHiddenColumns()
-  {
-    return hasHiddenColumns;
-  }
 
   /**
    * when set, view will scroll to show the highlighted position
@@ -1101,8 +1018,6 @@ public class AlignViewport extends AlignmentViewport implements
     return followSelection;
   }
 
-  boolean showSeqFeaturesHeight;
-
   public void sendSelection()
   {
     jalview.structure.StructureSelectionManager
@@ -1111,16 +1026,6 @@ public class AlignViewport extends AlignmentViewport implements
                     new ColumnSelection(getColumnSelection()), this);
   }
 
-  public void setShowSequenceFeaturesHeight(boolean selected)
-  {
-    showSeqFeaturesHeight = selected;
-  }
-
-  public boolean getShowSequenceFeaturesHeight()
-  {
-    return showSeqFeaturesHeight;
-  }
-
   /**
    * return the alignPanel containing the given viewport. Use this to get the
    * components currently handling the given viewport.
@@ -1293,4 +1198,15 @@ public class AlignViewport extends AlignmentViewport implements
   {
     this.showAutocalculatedAbove = showAutocalculatedAbove;
   }
+
+  public AnnotationColumnChooser getAnnotationColumnSelectionState()
+  {
+    return annotationColumnSelectionState;
+  }
+
+  public void setAnnotationColumnSelectionState(
+          AnnotationColumnChooser currentAnnotationColumnSelectionState)
+  {
+    this.annotationColumnSelectionState = currentAnnotationColumnSelectionState;
+  }
 }
index 2398bda..6517e70 100644 (file)
@@ -29,6 +29,7 @@ import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.jbgui.GAlignmentPanel;
+import jalview.math.AlignmentDimension;
 import jalview.schemes.ResidueProperties;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.MessageManager;
@@ -66,20 +67,21 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
   OverviewPanel overviewPanel;
 
-  SeqPanel seqPanel;
+  private SeqPanel seqPanel;
 
-  IdPanel idPanel;
+  private IdPanel idPanel;
 
+  private boolean headless;
   IdwidthAdjuster idwidthAdjuster;
 
   /** DOCUMENT ME!! */
   public AlignFrame alignFrame;
 
-  ScalePanel scalePanel;
+  private ScalePanel scalePanel;
 
-  AnnotationPanel annotationPanel;
+  private AnnotationPanel annotationPanel;
 
-  AnnotationLabels alabels;
+  private AnnotationLabels alabels;
 
   // this value is set false when selection area being dragged
   boolean fastPaint = true;
@@ -100,27 +102,27 @@ public class AlignmentPanel extends GAlignmentPanel implements
   {
     alignFrame = af;
     this.av = av;
-    seqPanel = new SeqPanel(av, this);
-    idPanel = new IdPanel(av, this);
+    setSeqPanel(new SeqPanel(av, this));
+    setIdPanel(new IdPanel(av, this));
 
-    scalePanel = new ScalePanel(av, this);
+    setScalePanel(new ScalePanel(av, this));
 
-    idPanelHolder.add(idPanel, BorderLayout.CENTER);
+    idPanelHolder.add(getIdPanel(), BorderLayout.CENTER);
     idwidthAdjuster = new IdwidthAdjuster(this);
     idSpaceFillerPanel1.add(idwidthAdjuster, BorderLayout.CENTER);
 
-    annotationPanel = new AnnotationPanel(this);
-    alabels = new AnnotationLabels(this);
+    setAnnotationPanel(new AnnotationPanel(this));
+    setAlabels(new AnnotationLabels(this));
 
-    annotationScroller.setViewportView(annotationPanel);
-    annotationSpaceFillerHolder.add(alabels, BorderLayout.CENTER);
+    annotationScroller.setViewportView(getAnnotationPanel());
+    annotationSpaceFillerHolder.add(getAlabels(), BorderLayout.CENTER);
 
-    scalePanelHolder.add(scalePanel, BorderLayout.CENTER);
-    seqPanelHolder.add(seqPanel, BorderLayout.CENTER);
+    scalePanelHolder.add(getScalePanel(), BorderLayout.CENTER);
+    seqPanelHolder.add(getSeqPanel(), BorderLayout.CENTER);
 
     setScrollValues(0, 0);
 
-    setAnnotationVisible(av.getShowAnnotation());
+    setAnnotationVisible(av.isShowAnnotation());
 
     hscroll.addAdjustmentListener(this);
     vscroll.addAdjustmentListener(this);
@@ -166,13 +168,13 @@ public class AlignmentPanel extends GAlignmentPanel implements
     idSpaceFillerPanel1.setPreferredSize(new Dimension(10, av.charHeight
             + fm.getDescent()));
 
-    idPanel.idCanvas.gg = null;
-    seqPanel.seqCanvas.img = null;
-    annotationPanel.adjustPanelHeight();
+    getIdPanel().getIdCanvas().gg = null;
+    getSeqPanel().seqCanvas.img = null;
+    getAnnotationPanel().adjustPanelHeight();
 
     Dimension d = calculateIdWidth();
     d.setSize(d.width + 4, d.height);
-    idPanel.idCanvas.setPreferredSize(d);
+    getIdPanel().getIdCanvas().setPreferredSize(d);
     hscrollFillerPanel.setPreferredSize(d);
 
     if (overviewPanel != null)
@@ -240,7 +242,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
     if (al.getAlignmentAnnotation() != null)
     {
-      fm = c.getFontMetrics(alabels.getFont());
+      fm = c.getFontMetrics(getAlabels().getFont());
 
       while (i < al.getAlignmentAnnotation().length)
       {
@@ -266,7 +268,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
   public void highlightSearchResults(SearchResults results)
   {
     scrollToPosition(results);
-    seqPanel.seqCanvas.highlightSearchResults(results);
+    getSeqPanel().seqCanvas.highlightSearchResults(results);
   }
 
   /**
@@ -371,8 +373,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
   void scrollToWrappedVisible(int res)
   {
-    int cwidth = seqPanel.seqCanvas
-            .getWrappedCanvasWidth(seqPanel.seqCanvas.getWidth());
+    int cwidth = getSeqPanel().seqCanvas
+            .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
     if (res < av.getStartRes() || res >= (av.getStartRes() + cwidth))
     {
       vscroll.setValue((res / cwidth));
@@ -441,7 +443,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
    */
   protected void validateAnnotationDimensions(boolean adjustPanelHeight)
   {
-    int height = annotationPanel.adjustPanelHeight();
+    int height = getAnnotationPanel().adjustPanelHeight();
 
     int theight = av.getCharHeight()
             * (av.getAlignment().getHeight() + (!av.hasHiddenRows() ? 0
@@ -503,7 +505,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
       annotationScroller.setVisible(false);
       annotationSpaceFillerHolder.setVisible(false);
     }
-    else if (av.showAnnotation)
+    else if (av.isShowAnnotation())
     {
       annotationScroller.setVisible(true);
       annotationSpaceFillerHolder.setVisible(true);
@@ -603,10 +605,10 @@ public class AlignmentPanel extends GAlignmentPanel implements
       width = av.getColumnSelection().findColumnPosition(width);
     }
 
-    av.setEndRes((x + (seqPanel.seqCanvas.getWidth() / av.charWidth)) - 1);
+    av.setEndRes((x + (getSeqPanel().seqCanvas.getWidth() / av.charWidth)) - 1);
 
-    hextent = seqPanel.seqCanvas.getWidth() / av.charWidth;
-    vextent = seqPanel.seqCanvas.getHeight() / av.charHeight;
+    hextent = getSeqPanel().seqCanvas.getWidth() / av.charWidth;
+    vextent = getSeqPanel().seqCanvas.getHeight() / av.charHeight;
 
     if (hextent > width)
     {
@@ -658,7 +660,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     {
       int x = hscroll.getValue();
       av.setStartRes(x);
-      av.setEndRes((x + (seqPanel.seqCanvas.getWidth() / av.getCharWidth())) - 1);
+      av.setEndRes((x + (getSeqPanel().seqCanvas.getWidth() / av.getCharWidth())) - 1);
     }
 
     if (evt.getSource() == vscroll)
@@ -669,8 +671,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
       {
         if (offy > -1)
         {
-          int rowSize = seqPanel.seqCanvas
-                  .getWrappedCanvasWidth(seqPanel.seqCanvas.getWidth());
+          int rowSize = getSeqPanel().seqCanvas
+                  .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
           av.setStartRes(offy * rowSize);
           av.setEndRes((offy + 1) * rowSize);
         }
@@ -692,7 +694,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
       {
         av.setStartSeq(offy);
         av.setEndSeq(offy
-                + (seqPanel.seqCanvas.getHeight() / av.getCharHeight()));
+                + (getSeqPanel().seqCanvas.getHeight() / av.getCharHeight()));
       }
     }
 
@@ -723,13 +725,13 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
       if (scrollX != 0 || scrollY != 0)
       {
-        idPanel.idCanvas.fastPaint(scrollY);
-        seqPanel.seqCanvas.fastPaint(scrollX, scrollY);
-        scalePanel.repaint();
+        getIdPanel().getIdCanvas().fastPaint(scrollY);
+        getSeqPanel().seqCanvas.fastPaint(scrollX, scrollY);
+        getScalePanel().repaint();
 
-        if (av.getShowAnnotation() && scrollX != 0)
+        if (av.isShowAnnotation() && scrollX != 0)
         {
-          annotationPanel.fastPaint(scrollX);
+          getAnnotationPanel().fastPaint(scrollX);
         }
       }
     }
@@ -769,7 +771,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
   {
     invalidate();
 
-    Dimension d = idPanel.idCanvas.getPreferredSize();
+    Dimension d = getIdPanel().getIdCanvas().getPreferredSize();
     idPanelHolder.setPreferredSize(d);
     hscrollFillerPanel.setPreferredSize(new Dimension(d.width, 12));
     validate();
@@ -783,13 +785,13 @@ public class AlignmentPanel extends GAlignmentPanel implements
         maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
       }
 
-      int canvasWidth = seqPanel.seqCanvas
-              .getWrappedCanvasWidth(seqPanel.seqCanvas.getWidth());
+      int canvasWidth = getSeqPanel().seqCanvas
+              .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
       if (canvasWidth > 0)
       {
         int max = maxwidth
-                / seqPanel.seqCanvas
-                        .getWrappedCanvasWidth(seqPanel.seqCanvas
+                / getSeqPanel().seqCanvas
+                        .getWrappedCanvasWidth(getSeqPanel().seqCanvas
                                 .getWidth()) + 1;
         vscroll.setMaximum(max);
         vscroll.setUnitIncrement(1);
@@ -905,9 +907,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
     int pagesHigh = ((av.getAlignment().getHeight() / totalSeq) + 1)
             * pheight;
 
-    if (av.showAnnotation)
+    if (av.isShowAnnotation())
     {
-      pagesHigh += annotationPanel.adjustPanelHeight() + 3;
+      pagesHigh += getAnnotationPanel().adjustPanelHeight() + 3;
     }
 
     pagesHigh /= pheight;
@@ -919,7 +921,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
     // draw Scale
     pg.translate(idWidth, 0);
-    scalePanel.drawScale(pg, startRes, endRes, pwidth - idWidth,
+    getScalePanel().drawScale(pg, startRes, endRes, pwidth - idWidth,
             scaleHeight);
     pg.translate(-idWidth, scaleHeight);
 
@@ -928,7 +930,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     Color currentColor = null;
     Color currentTextColor = null;
 
-    pg.setFont(idPanel.idCanvas.idfont);
+    pg.setFont(getIdPanel().getIdCanvas().getIdfont());
 
     SequenceI seq;
     for (int i = startSeq; i < endSeq; i++)
@@ -953,7 +955,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
       pg.setColor(currentTextColor);
 
       int xPos = 0;
-      if (av.rightAlignIds)
+      if (av.isRightAlignIds())
       {
         fm = pg.getFontMetrics();
         xPos = idWidth
@@ -972,17 +974,17 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
     // draw main sequence panel
     pg.translate(idWidth, 0);
-    seqPanel.seqCanvas.drawPanel(pg, startRes, endRes, startSeq, endSeq, 0);
+    getSeqPanel().seqCanvas.drawPanel(pg, startRes, endRes, startSeq, endSeq, 0);
 
-    if (av.showAnnotation && (endSeq == av.getAlignment().getHeight()))
+    if (av.isShowAnnotation() && (endSeq == av.getAlignment().getHeight()))
     {
       // draw annotation - need to offset for current scroll position
-      int offset = -alabels.scrollOffset;
+      int offset = -getAlabels().getScrollOffset();
       pg.translate(0, offset);
       pg.translate(-idWidth - 3, (endSeq - startSeq) * av.charHeight + 3);
-      alabels.drawComponent(pg, idWidth);
+      getAlabels().drawComponent(pg, idWidth);
       pg.translate(idWidth + 3, 0);
-      annotationPanel.renderer.drawComponent(annotationPanel, av,
+      getAnnotationPanel().renderer.drawComponent(getAnnotationPanel(), av,
               pg, -1, startRes, endRes + 1);
       pg.translate(0, -offset);
     }
@@ -1013,9 +1015,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
     int annotationHeight = 0;
     AnnotationLabels labels = null;
-    if (av.showAnnotation)
+    if (av.isShowAnnotation())
     {
-      annotationHeight = annotationPanel.adjustPanelHeight();
+      annotationHeight = getAnnotationPanel().adjustPanelHeight();
       labels = new AnnotationLabels(av);
     }
 
@@ -1036,7 +1038,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
     }
 
-    int resWidth = seqPanel.seqCanvas.getWrappedCanvasWidth(pwidth
+    int resWidth = getSeqPanel().seqCanvas.getWrappedCanvasWidth(pwidth
             - idWidth);
 
     int totalHeight = cHeight * (maxwidth / resWidth + 1);
@@ -1059,11 +1061,11 @@ public class AlignmentPanel extends GAlignmentPanel implements
     {
       for (int i = 0; i < av.getAlignment().getHeight(); i++)
       {
-        pg.setFont(idPanel.idCanvas.idfont);
+        pg.setFont(getIdPanel().getIdCanvas().getIdfont());
         SequenceI s = av.getAlignment().getSequenceAt(i);
         String string = s.getDisplayId(av.getShowJVSuffix());
         int xPos = 0;
-        if (av.rightAlignIds)
+        if (av.isRightAlignIds())
         {
           FontMetrics fm = pg.getFontMetrics();
           xPos = idWidth - fm.stringWidth(string) - 4;
@@ -1088,7 +1090,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
     pg.translate(idWidth, 0);
 
-    seqPanel.seqCanvas.drawWrappedPanel(pg, pwidth - idWidth, totalHeight,
+    getSeqPanel().seqCanvas.drawWrappedPanel(pg, pwidth - idWidth, totalHeight,
             0);
 
     if ((pi * pheight) < totalHeight)
@@ -1108,7 +1110,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
    * 
    * @return
    */
-  int getVisibleIdWidth()
+  public int getVisibleIdWidth()
   {
     return getVisibleIdWidth(true);
   }
@@ -1122,7 +1124,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
    *          be returned
    * @return
    */
-  int getVisibleIdWidth(boolean onscreen)
+  public int getVisibleIdWidth(boolean onscreen)
   {
     // see if rendering offscreen - check preferences and calc width accordingly
     if (!onscreen && Cache.getDefault("FIGURE_AUTOIDWIDTH", false))
@@ -1133,7 +1135,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     if (onscreen
             || (idwidth = Cache.getIntegerProperty("FIGURE_FIXEDIDWIDTH")) == null)
     {
-      return (idPanel.getWidth() > 0 ? idPanel.getWidth()
+      return (getIdPanel().getWidth() > 0 ? getIdPanel().getWidth()
               : calculateIdWidth().width + 4);
     }
     return idwidth.intValue() + 4;
@@ -1142,7 +1144,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
   void makeAlignmentImage(jalview.util.ImageMaker.TYPE type, File file)
   {
     long progress = System.currentTimeMillis();
-    boolean headless = (System.getProperty("java.awt.headless") != null && System
+    headless = (System.getProperty("java.awt.headless") != null && System
             .getProperty("java.awt.headless").equals("true"));
     if (alignFrame != null && !headless)
     {
@@ -1153,44 +1155,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
     }
     try
     {
-      int maxwidth = av.getAlignment().getWidth();
-      if (av.hasHiddenColumns())
-      {
-        maxwidth = av.getColumnSelection().findColumnPosition(maxwidth);
-      }
-
-      int height = ((av.getAlignment().getHeight() + 1) * av.charHeight)
-              + scalePanel.getHeight();
-      int width = getVisibleIdWidth(false) + (maxwidth * av.charWidth);
-
-      if (av.getWrapAlignment())
-      {
-        height = getWrappedHeight();
-        if (headless)
-        {
-          // need to obtain default alignment width and then add in any
-          // additional allowance for id margin
-          // this duplicates the calculation in getWrappedHeight but adjusts for
-          // offscreen idWith
-          width = alignFrame.getWidth() - vscroll.getPreferredSize().width
-                  - alignFrame.getInsets().left
-                  - alignFrame.getInsets().right - getVisibleIdWidth()
-                  + getVisibleIdWidth(false);
-        }
-        else
-        {
-          width = seqPanel.getWidth() + getVisibleIdWidth(false);
-        }
-
-      }
-      else if (av.getShowAnnotation())
-      {
-        height += annotationPanel.adjustPanelHeight() + 3;
-      }
-
+      AlignmentDimension aDimension = getAlignmentDimension();
       try
       {
-
         jalview.util.ImageMaker im;
         final String imageAction, imageTitle;
         if (type == jalview.util.ImageMaker.TYPE.PNG)
@@ -1209,13 +1176,15 @@ public class AlignmentPanel extends GAlignmentPanel implements
           imageTitle = alignFrame.getTitle();
         }
 
-        im = new jalview.util.ImageMaker(this, type, imageAction, width,
-                height, file, imageTitle);
+        im = new jalview.util.ImageMaker(this, type, imageAction,
+                aDimension.getWidth(), aDimension.getHeight(), file,
+                imageTitle);
         if (av.getWrapAlignment())
         {
           if (im.getGraphics() != null)
           {
-            printWrappedAlignment(im.getGraphics(), width, height, 0);
+            printWrappedAlignment(im.getGraphics(), aDimension.getWidth(),
+                    aDimension.getHeight(), 0);
             im.writeImage();
           }
         }
@@ -1223,7 +1192,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
         {
           if (im.getGraphics() != null)
           {
-            printUnwrapped(im.getGraphics(), width, height, 0);
+            printUnwrapped(im.getGraphics(), aDimension.getWidth(),
+                    aDimension.getHeight(), 0);
             im.writeImage();
           }
         }
@@ -1247,6 +1217,46 @@ public class AlignmentPanel extends GAlignmentPanel implements
     }
   }
 
+  public AlignmentDimension getAlignmentDimension()
+  {
+    int maxwidth = av.getAlignment().getWidth();
+    if (av.hasHiddenColumns())
+    {
+      maxwidth = av.getColumnSelection().findColumnPosition(maxwidth);
+    }
+
+    int height = ((av.getAlignment().getHeight() + 1) * av.charHeight)
+            + getScalePanel().getHeight();
+    int width = getVisibleIdWidth(false) + (maxwidth * av.charWidth);
+
+    if (av.getWrapAlignment())
+    {
+      height = getWrappedHeight();
+      if (headless)
+      {
+        // need to obtain default alignment width and then add in any
+        // additional allowance for id margin
+        // this duplicates the calculation in getWrappedHeight but adjusts for
+        // offscreen idWith
+        width = alignFrame.getWidth() - vscroll.getPreferredSize().width
+                - alignFrame.getInsets().left
+                - alignFrame.getInsets().right - getVisibleIdWidth()
+                + getVisibleIdWidth(false);
+      }
+      else
+      {
+        width = getSeqPanel().getWidth() + getVisibleIdWidth(false);
+      }
+
+    }
+    else if (av.isShowAnnotation())
+    {
+      height += getAnnotationPanel().adjustPanelHeight() + 3;
+    }
+    return new AlignmentDimension(width, height);
+
+  }
+
   /**
    * DOCUMENT ME!
    */
@@ -1410,7 +1420,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
   int getWrappedHeight()
   {
-    int seqPanelWidth = seqPanel.seqCanvas.getWidth();
+    int seqPanelWidth = getSeqPanel().seqCanvas.getWidth();
 
     if (System.getProperty("java.awt.headless") != null
             && System.getProperty("java.awt.headless").equals("true"))
@@ -1420,7 +1430,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
               - alignFrame.getInsets().left - alignFrame.getInsets().right;
     }
 
-    int chunkWidth = seqPanel.seqCanvas
+    int chunkWidth = getSeqPanel().seqCanvas
             .getWrappedCanvasWidth(seqPanelWidth);
 
     int hgap = av.charHeight;
@@ -1430,9 +1440,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
     }
 
     int annotationHeight = 0;
-    if (av.showAnnotation)
+    if (av.isShowAnnotation())
     {
-      annotationHeight = annotationPanel.adjustPanelHeight();
+      annotationHeight = getAnnotationPanel().adjustPanelHeight();
     }
 
     int cHeight = av.getAlignment().getHeight() * av.charHeight + hgap
@@ -1455,15 +1465,15 @@ public class AlignmentPanel extends GAlignmentPanel implements
    */
   public void closePanel()
   {
-    PaintRefresher.RemoveComponent(seqPanel.seqCanvas);
-    PaintRefresher.RemoveComponent(idPanel.idCanvas);
+    PaintRefresher.RemoveComponent(getSeqPanel().seqCanvas);
+    PaintRefresher.RemoveComponent(getIdPanel().getIdCanvas());
     PaintRefresher.RemoveComponent(this);
     if (av != null)
     {
       jalview.structure.StructureSelectionManager ssm = av
               .getStructureSelectionManager();
-      ssm.removeStructureViewerListener(seqPanel, null);
-      ssm.removeSelectionListener(seqPanel);
+      ssm.removeStructureViewerListener(getSeqPanel(), null);
+      ssm.removeSelectionListener(getSeqPanel());
       av.setAlignment(null);
       av = null;
     }
@@ -1543,22 +1553,77 @@ public class AlignmentPanel extends GAlignmentPanel implements
     new OOMWarning(string, error, this);
   }
 
-  public FeatureRenderer cloneFeatureRenderer()
+  @Override
+  public jalview.api.FeatureRenderer cloneFeatureRenderer()
   {
 
     return new FeatureRenderer(this);
   }
-
-  public void updateFeatureRenderer(FeatureRenderer fr)
+  @Override 
+  public jalview.api.FeatureRenderer getFeatureRenderer()
   {
-    fr.transferSettings(seqPanel.seqCanvas.getFeatureRenderer());
+    return seqPanel.seqCanvas.getFeatureRenderer();
+  }
+  public void updateFeatureRenderer(jalview.renderer.seqfeatures.FeatureRenderer fr)
+  {
+    fr.transferSettings(getSeqPanel().seqCanvas.getFeatureRenderer());
   }
 
-  public void updateFeatureRendererFrom(FeatureRenderer fr)
+  public void updateFeatureRendererFrom(jalview.api.FeatureRenderer fr)
   {
-    if (seqPanel.seqCanvas.getFeatureRenderer() != null)
+    if (getSeqPanel().seqCanvas.getFeatureRenderer() != null)
     {
-      seqPanel.seqCanvas.getFeatureRenderer().transferSettings(fr);
+      getSeqPanel().seqCanvas.getFeatureRenderer().transferSettings(fr);
     }
   }
+
+  public ScalePanel getScalePanel()
+  {
+    return scalePanel;
+  }
+
+  public void setScalePanel(ScalePanel scalePanel)
+  {
+    this.scalePanel = scalePanel;
+  }
+
+  public SeqPanel getSeqPanel()
+  {
+    return seqPanel;
+  }
+
+  public void setSeqPanel(SeqPanel seqPanel)
+  {
+    this.seqPanel = seqPanel;
+  }
+
+  public AnnotationPanel getAnnotationPanel()
+  {
+    return annotationPanel;
+  }
+
+  public void setAnnotationPanel(AnnotationPanel annotationPanel)
+  {
+    this.annotationPanel = annotationPanel;
+  }
+
+  public AnnotationLabels getAlabels()
+  {
+    return alabels;
+  }
+
+  public void setAlabels(AnnotationLabels alabels)
+  {
+    this.alabels = alabels;
+  }
+
+  public IdPanel getIdPanel()
+  {
+    return idPanel;
+  }
+
+  public void setIdPanel(IdPanel idPanel)
+  {
+    this.idPanel = idPanel;
+  }
 }
index b8996b6..2ad0bd2 100644 (file)
@@ -21,8 +21,6 @@
 package jalview.gui;
 
 import jalview.bin.Cache;
-import jalview.datamodel.AlignmentAnnotation;
-import jalview.datamodel.GraphLine;
 import jalview.datamodel.SequenceGroup;
 import jalview.schemes.AnnotationColourGradient;
 import jalview.schemes.ColourSchemeI;
@@ -37,51 +35,50 @@ import java.awt.event.ActionListener;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 import java.util.Hashtable;
-import java.util.Vector;
 
 import javax.swing.BorderFactory;
 import javax.swing.JButton;
-import javax.swing.JCheckBox;
 import javax.swing.JColorChooser;
 import javax.swing.JComboBox;
 import javax.swing.JInternalFrame;
 import javax.swing.JLayeredPane;
 import javax.swing.JPanel;
-import javax.swing.JSlider;
-import javax.swing.JTextField;
-import javax.swing.event.ChangeEvent;
-import javax.swing.event.ChangeListener;
 
 import net.miginfocom.swing.MigLayout;
 
-public class AnnotationColourChooser extends JPanel
+@SuppressWarnings("serial")
+public class AnnotationColourChooser extends AnnotationRowFilter
 {
-  JInternalFrame frame;
-
-  AlignViewport av;
-
-  AlignmentPanel ap;
 
   ColourSchemeI oldcs;
 
-  Hashtable oldgroupColours;
-
-  jalview.datamodel.AlignmentAnnotation currentAnnotation;
-
-  boolean adjusting = false;
+  Hashtable<SequenceGroup, ColourSchemeI> oldgroupColours;
 
   /**
    * enabled if the user is dragging the slider - try to keep updates to a
    * minimun
    */
-  boolean sliderDragging = false;
+
+  JComboBox<String> annotations;
+
+  JButton defColours = new JButton();
+
+
+  JPanel jPanel1 = new JPanel();
+
+  JPanel jPanel2 = new JPanel();
+
+  BorderLayout borderLayout1 = new BorderLayout();
+
+  private JComboBox<String> threshold = new JComboBox<String>();
 
   public AnnotationColourChooser(AlignViewport av, final AlignmentPanel ap)
   {
+    super(av, ap);
     oldcs = av.getGlobalColourScheme();
     if (av.getAlignment().getGroups() != null)
     {
-      oldgroupColours = new Hashtable();
+      oldgroupColours = new Hashtable<SequenceGroup, ColourSchemeI>();
       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
       {
         if (sg.cs != null)
@@ -90,8 +87,6 @@ public class AnnotationColourChooser extends JPanel
         }
       }
     }
-    this.av = av;
-    this.ap = ap;
     frame = new JInternalFrame();
     frame.setContentPane(this);
     frame.setLayer(JLayeredPane.PALETTE_LAYER);
@@ -99,45 +94,8 @@ public class AnnotationColourChooser extends JPanel
             MessageManager.getString("label.colour_by_annotation"), 520,
             215);
 
-    slider.addChangeListener(new ChangeListener()
-    {
-      @Override
-      public void stateChanged(ChangeEvent evt)
-      {
-        if (!adjusting)
-        {
-          thresholdValue.setText((slider.getValue() / 1000f) + "");
-          valueChanged(!sliderDragging);
-        }
-      }
-    });
-    slider.addMouseListener(new MouseAdapter()
-    {
-      @Override
-      public void mousePressed(MouseEvent e)
-      {
-        sliderDragging = true;
-        super.mousePressed(e);
-      }
-
-      @Override
-      public void mouseDragged(MouseEvent e)
-      {
-        sliderDragging = true;
-        super.mouseDragged(e);
-      }
-
-      @Override
-      public void mouseReleased(MouseEvent evt)
-      {
-        if (sliderDragging)
-        {
-          sliderDragging = false;
-          valueChanged(true);
-        }
-        ap.paintAlignment(true);
-      }
-    });
+    addSliderChangeListener();
+    addSliderMouseListeners();
 
     if (av.getAlignment().getAlignmentAnnotation() == null)
     {
@@ -161,15 +119,10 @@ public class AnnotationColourChooser extends JPanel
       seqAssociated.setSelected(acg.isSeqAssociated());
 
     }
-    annotations = new JComboBox(
+    annotations = new JComboBox<String>(
             getAnnotationItems(seqAssociated.isSelected()));
 
-    threshold.addItem(MessageManager
-            .getString("label.threshold_feature_no_thereshold"));
-    threshold.addItem(MessageManager
-            .getString("label.threshold_feature_above_thereshold"));
-    threshold.addItem(MessageManager
-            .getString("label.threshold_feature_below_thereshold"));
+    populateThresholdComboBox(threshold);
 
     if (oldcs instanceof AnnotationColourGradient)
     {
@@ -178,13 +131,13 @@ public class AnnotationColourChooser extends JPanel
       switch (acg.getAboveThreshold())
       {
       case AnnotationColourGradient.NO_THRESHOLD:
-        threshold.setSelectedIndex(0);
+        getThreshold().setSelectedIndex(0);
         break;
       case AnnotationColourGradient.ABOVE_THRESHOLD:
-        threshold.setSelectedIndex(1);
+        getThreshold().setSelectedIndex(1);
         break;
       case AnnotationColourGradient.BELOW_THRESHOLD:
-        threshold.setSelectedIndex(2);
+        getThreshold().setSelectedIndex(2);
         break;
       default:
         throw new Error(MessageManager.getString("error.implementation_error_dont_know_about_thereshold_setting"));
@@ -199,62 +152,11 @@ public class AnnotationColourChooser extends JPanel
     } catch (Exception ex)
     {
     }
-
     adjusting = false;
 
-    changeColour();
+    updateView();
     frame.invalidate();
     frame.pack();
-
-  }
-
-  private Vector<String> getAnnotationItems(boolean isSeqAssociated)
-  {
-    Vector<String> list = new Vector<String>();
-    int index = 1;
-    int[] anmap = new int[av.getAlignment().getAlignmentAnnotation().length];
-    boolean enableSeqAss = false;
-    for (int i = 0; i < av.getAlignment().getAlignmentAnnotation().length; i++)
-    {
-      if (av.getAlignment().getAlignmentAnnotation()[i].sequenceRef == null)
-      {
-        if (isSeqAssociated)
-        {
-          continue;
-        }
-      }
-      else
-      {
-        enableSeqAss = true;
-      }
-      String label = av.getAlignment().getAlignmentAnnotation()[i].label;
-      if (!list.contains(label))
-      {
-        anmap[list.size()] = i;
-        list.add(label);
-
-      }
-      else
-      {
-        if (!isSeqAssociated)
-        {
-          anmap[list.size()] = i;
-          list.add(label + "_" + (index++));
-        }
-      }
-    }
-    seqAssociated.setEnabled(enableSeqAss);
-    this.annmap = new int[list.size()];
-    System.arraycopy(anmap, 0, this.annmap, 0, this.annmap.length);
-    return list;
-  }
-
-  private void setDefaultMinMax()
-  {
-    minColour.setBackground(Cache.getDefaultColour("ANNOTATIONCOLOUR_MIN",
-            Color.orange));
-    maxColour.setBackground(Cache.getDefaultColour("ANNOTATIONCOLOUR_MAX",
-            Color.red));
   }
 
   public AnnotationColourChooser()
@@ -342,7 +244,7 @@ public class AnnotationColourChooser extends JPanel
         annotations_actionPerformed(e);
       }
     });
-    threshold.addActionListener(new ActionListener()
+    getThreshold().addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
@@ -400,7 +302,7 @@ public class AnnotationColourChooser extends JPanel
       @Override
       public void actionPerformed(ActionEvent arg0)
       {
-        seqAssociated_actionPerformed(arg0);
+        seqAssociated_actionPerformed(arg0, annotations, seqAssociated);
       }
     });
 
@@ -419,7 +321,7 @@ public class AnnotationColourChooser extends JPanel
     colpanel.add(minColour);
     colpanel.add(maxColour);
     jPanel2.add(colpanel, "wrap");
-    jPanel2.add(threshold);
+    jPanel2.add(getThreshold());
     jPanel2.add(defColours, "skip 1, wrap");
     jPanel2.add(thresholdIsMin);
     jPanel2.add(slider, "grow");
@@ -429,72 +331,19 @@ public class AnnotationColourChooser extends JPanel
     this.validate();
   }
 
-  protected void seqAssociated_actionPerformed(ActionEvent arg0)
-  {
-    adjusting = true;
-    String cursel = (String) annotations.getSelectedItem();
-    boolean isvalid = false, isseqs = seqAssociated.isSelected();
-    this.annotations.removeAllItems();
-    for (String anitem : getAnnotationItems(seqAssociated.isSelected()))
-    {
-      if (anitem.equals(cursel) || (isseqs && cursel.startsWith(anitem)))
-      {
-        isvalid = true;
-        cursel = anitem;
-      }
-      this.annotations.addItem(anitem);
-    }
-    adjusting = false;
-    if (isvalid)
-    {
-      this.annotations.setSelectedItem(cursel);
-    }
-    else
-    {
-      if (annotations.getItemCount() > 0)
-      {
-        annotations.setSelectedIndex(0);
-      }
-    }
-  }
-
   protected void resetColours_actionPerformed(ActionEvent arg0)
   {
     setDefaultMinMax();
-    changeColour();
+    updateView();
   }
 
-  JComboBox annotations;
-
-  int[] annmap;
-
-  JPanel minColour = new JPanel();
-
-  JPanel maxColour = new JPanel();
-
-  JButton defColours = new JButton();
-
-  JButton ok = new JButton();
-
-  JButton cancel = new JButton();
-
-  JPanel jPanel1 = new JPanel();
-
-  JPanel jPanel2 = new JPanel();
-
-  BorderLayout borderLayout1 = new BorderLayout();
-
-  JComboBox threshold = new JComboBox();
-
-  JSlider slider = new JSlider();
-
-  JTextField thresholdValue = new JTextField(20);
-
-  JCheckBox currentColours = new JCheckBox();
-
-  JCheckBox thresholdIsMin = new JCheckBox();
-
-  JCheckBox seqAssociated = new JCheckBox();
+  private void setDefaultMinMax()
+  {
+    minColour.setBackground(Cache.getDefaultColour("ANNOTATIONCOLOUR_MIN",
+            Color.orange));
+    maxColour.setBackground(Cache.getDefaultColour("ANNOTATIONCOLOUR_MAX",
+            Color.red));
+  }
 
   public void minColour_actionPerformed()
   {
@@ -505,7 +354,7 @@ public class AnnotationColourChooser extends JPanel
       minColour.setBackground(col);
     }
     minColour.repaint();
-    changeColour();
+    updateView();
   }
 
   public void maxColour_actionPerformed()
@@ -517,247 +366,115 @@ public class AnnotationColourChooser extends JPanel
       maxColour.setBackground(col);
     }
     maxColour.repaint();
-    changeColour();
+    updateView();
   }
 
-  void changeColour()
+  public void reset()
   {
-    // Check if combobox is still adjusting
-    if (adjusting)
-    {
-      return;
-    }
-
-    currentAnnotation = av.getAlignment().getAlignmentAnnotation()[annmap[annotations
-            .getSelectedIndex()]];
-
-    int aboveThreshold = -1;
-    if (threshold.getSelectedIndex() == 1)
-    {
-      aboveThreshold = AnnotationColourGradient.ABOVE_THRESHOLD;
-    }
-    else if (threshold.getSelectedIndex() == 2)
-    {
-      aboveThreshold = AnnotationColourGradient.BELOW_THRESHOLD;
-    }
-
-    slider.setEnabled(true);
-    thresholdValue.setEnabled(true);
-    thresholdIsMin.setEnabled(true);
-
-    if (aboveThreshold == AnnotationColourGradient.NO_THRESHOLD)
-    {
-      slider.setEnabled(false);
-      thresholdValue.setEnabled(false);
-      thresholdValue.setText("");
-      thresholdIsMin.setEnabled(false);
-    }
-    else if (aboveThreshold != AnnotationColourGradient.NO_THRESHOLD
-            && currentAnnotation.threshold == null)
-    {
-      currentAnnotation
-              .setThreshold(new jalview.datamodel.GraphLine(
-                      (currentAnnotation.graphMax - currentAnnotation.graphMin) / 2f,
-                      "Threshold", Color.black));
-    }
-
-    if (aboveThreshold != AnnotationColourGradient.NO_THRESHOLD)
-    {
-      adjusting = true;
-      float range = currentAnnotation.graphMax * 1000
-              - currentAnnotation.graphMin * 1000;
-
-      slider.setMinimum((int) (currentAnnotation.graphMin * 1000));
-      slider.setMaximum((int) (currentAnnotation.graphMax * 1000));
-      slider.setValue((int) (currentAnnotation.threshold.value * 1000));
-      thresholdValue.setText(currentAnnotation.threshold.value + "");
-      slider.setMajorTickSpacing((int) (range / 10f));
-      slider.setEnabled(true);
-      thresholdValue.setEnabled(true);
-      adjusting = false;
-    }
-
-    AnnotationColourGradient acg = null;
-    if (currentColours.isSelected())
-    {
-      acg = new AnnotationColourGradient(currentAnnotation,
-              av.getGlobalColourScheme(), aboveThreshold);
-    }
-    else
-    {
-      acg = new AnnotationColourGradient(currentAnnotation,
-              minColour.getBackground(), maxColour.getBackground(),
-              aboveThreshold);
-    }
-    acg.setSeqAssociated(seqAssociated.isSelected());
-
-    if (currentAnnotation.graphMin == 0f
-            && currentAnnotation.graphMax == 0f)
-    {
-      acg.setPredefinedColours(true);
-    }
-
-    acg.thresholdIsMinMax = thresholdIsMin.isSelected();
-
-    av.setGlobalColourScheme(acg);
-
+    av.setGlobalColourScheme(oldcs);
     if (av.getAlignment().getGroups() != null)
     {
 
       for (SequenceGroup sg : ap.av.getAlignment().getGroups())
       {
-        if (sg.cs == null)
-        {
-          continue;
-        }
-
-        if (currentColours.isSelected())
-        {
-          sg.cs = new AnnotationColourGradient(currentAnnotation, sg.cs,
-                  aboveThreshold);
-          ((AnnotationColourGradient) sg.cs).setSeqAssociated(seqAssociated
-                  .isSelected());
-
-        }
-        else
-        {
-          sg.cs = new AnnotationColourGradient(currentAnnotation,
-                  minColour.getBackground(), maxColour.getBackground(),
-                  aboveThreshold);
-          ((AnnotationColourGradient) sg.cs).setSeqAssociated(seqAssociated
-                  .isSelected());
-        }
-
+        sg.cs = oldgroupColours.get(sg);
       }
     }
-    ap.alignmentChanged();
-    // ensure all associated views (overviews, structures, etc) are notified of
-    // updated colours.
-    ap.paintAlignment(true);
   }
 
-  public void ok_actionPerformed(ActionEvent e)
-  {
-    changeColour();
-    try
-    {
-      frame.setClosed(true);
-    } catch (Exception ex)
-    {
-    }
-  }
-
-  public void cancel_actionPerformed(ActionEvent e)
+  public void valueChanged(boolean updateAllAnnotation)
   {
-    reset();
-    // ensure all original colouring is propagated to listeners.
-    ap.paintAlignment(true);
-    try
+    if (slider.isEnabled())
     {
-      frame.setClosed(true);
-    } catch (Exception ex)
-    {
-    }
-  }
-
-  void reset()
-  {
-    av.setGlobalColourScheme(oldcs);
-    if (av.getAlignment().getGroups() != null)
-    {
-
-      for (SequenceGroup sg : ap.av.getAlignment().getGroups())
+      if (currentColours.isSelected()
+              && !(av.getGlobalColourScheme() instanceof AnnotationColourGradient))
       {
-        sg.cs = (ColourSchemeI) oldgroupColours.get(sg);
+        updateView();
       }
+      getCurrentAnnotation().threshold.value = slider.getValue() / 1000f;
+      propagateSeqAssociatedThreshold(updateAllAnnotation,
+              getCurrentAnnotation());
+      ap.paintAlignment(false);
     }
   }
 
-  public void thresholdCheck_actionPerformed(ActionEvent e)
-  {
-    changeColour();
-  }
-
-  public void annotations_actionPerformed(ActionEvent e)
-  {
-    changeColour();
-  }
-
-  public void threshold_actionPerformed(ActionEvent e)
+  public JComboBox<String> getThreshold()
   {
-    changeColour();
+    return threshold;
   }
 
-  public void thresholdValue_actionPerformed(ActionEvent e)
+  public void setThreshold(JComboBox<String> threshold)
   {
-    try
-    {
-      float f = Float.parseFloat(thresholdValue.getText());
-      slider.setValue((int) (f * 1000));
-    } catch (NumberFormatException ex)
-    {
-    }
+    this.threshold = threshold;
   }
 
-  public void valueChanged(boolean updateAllAnnotation)
+  public void currentColours_actionPerformed(ActionEvent e)
   {
-    if (currentColours.isSelected()
-            && !(av.getGlobalColourScheme() instanceof AnnotationColourGradient))
+    if (currentColours.isSelected())
     {
-      changeColour();
+      reset();
     }
-    currentAnnotation.threshold.value = slider.getValue() / 1000f;
-    propagateSeqAssociatedThreshold(updateAllAnnotation);
-    ap.paintAlignment(false);
+    maxColour.setEnabled(!currentColours.isSelected());
+    minColour.setEnabled(!currentColours.isSelected());
+    updateView();
   }
 
-  private void propagateSeqAssociatedThreshold(boolean allAnnotation)
+  @Override
+  public void updateView()
   {
-    if (currentAnnotation.sequenceRef == null
-            || currentAnnotation.threshold == null)
+    // Check if combobox is still adjusting
+    if (adjusting)
     {
       return;
     }
-    // TODO: JAL-1327 only update visible annotation thresholds if allAnnotation
-    // is false, since we only need to provide a quick visual indicator
 
-    float thr = currentAnnotation.threshold.value;
-    for (int i = 0; i < av.getAlignment().getAlignmentAnnotation().length; i++)
+    setCurrentAnnotation(av.getAlignment().getAlignmentAnnotation()[annmap[annotations
+            .getSelectedIndex()]]);
+
+    int selectedThresholdItem = getSelectedThresholdItem(getThreshold()
+            .getSelectedIndex());
+
+    slider.setEnabled(true);
+    thresholdValue.setEnabled(true);
+    thresholdIsMin.setEnabled(true);
+
+    if (selectedThresholdItem == AnnotationColourGradient.NO_THRESHOLD)
     {
-      AlignmentAnnotation aa = av.getAlignment().getAlignmentAnnotation()[i];
-      if (aa.label.equals(currentAnnotation.label)
-              && (currentAnnotation.getCalcId() == null ? aa.getCalcId() == null
-                      : currentAnnotation.getCalcId()
-                              .equals(aa.getCalcId())))
-      {
-        if (aa.threshold == null)
-        {
-          aa.threshold = new GraphLine(currentAnnotation.threshold);
-        }
-        else
-        {
-          aa.threshold.value = thr;
-        }
-      }
+      slider.setEnabled(false);
+      thresholdValue.setEnabled(false);
+      thresholdValue.setText("");
+      thresholdIsMin.setEnabled(false);
     }
-  }
-
-  public void currentColours_actionPerformed(ActionEvent e)
-  {
-    if (currentColours.isSelected())
+    else if (selectedThresholdItem != AnnotationColourGradient.NO_THRESHOLD
+            && getCurrentAnnotation().threshold == null)
     {
-      reset();
+      getCurrentAnnotation()
+              .setThreshold(new jalview.datamodel.GraphLine(
+                      (getCurrentAnnotation().graphMax - getCurrentAnnotation().graphMin) / 2f,
+                      "Threshold", Color.black));
     }
 
-    maxColour.setEnabled(!currentColours.isSelected());
-    minColour.setEnabled(!currentColours.isSelected());
+    if (selectedThresholdItem != AnnotationColourGradient.NO_THRESHOLD)
+    {
+      adjusting = true;
+      float range = getCurrentAnnotation().graphMax * 1000
+              - getCurrentAnnotation().graphMin * 1000;
 
-    changeColour();
-  }
+      slider.setMinimum((int) (getCurrentAnnotation().graphMin * 1000));
+      slider.setMaximum((int) (getCurrentAnnotation().graphMax * 1000));
+      slider.setValue((int) (getCurrentAnnotation().threshold.value * 1000));
+      thresholdValue.setText(getCurrentAnnotation().threshold.value + "");
+      slider.setMajorTickSpacing((int) (range / 10f));
+      slider.setEnabled(true);
+      thresholdValue.setEnabled(true);
+      adjusting = false;
+    }
+    colorAlignmContaining(getCurrentAnnotation(), selectedThresholdItem);
 
-  public void thresholdIsMin_actionPerformed(ActionEvent actionEvent)
-  {
-    changeColour();
+    ap.alignmentChanged();
+    // ensure all associated views (overviews, structures, etc) are notified of
+    // updated colours.
+    ap.paintAlignment(true);
   }
 
 }
diff --git a/src/jalview/gui/AnnotationColumnChooser.java b/src/jalview/gui/AnnotationColumnChooser.java
new file mode 100644 (file)
index 0000000..00c4217
--- /dev/null
@@ -0,0 +1,866 @@
+package jalview.gui;
+
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.ColumnSelection;
+import jalview.schemes.AnnotationColourGradient;
+import jalview.util.MessageManager;
+import jalview.viewmodel.annotationfilter.AnnotationFilterParameter;
+
+import java.awt.BorderLayout;
+import java.awt.CardLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.Iterator;
+
+import javax.swing.ButtonGroup;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JInternalFrame;
+import javax.swing.JLayeredPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JTextField;
+import javax.swing.border.TitledBorder;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+
+import net.miginfocom.swing.MigLayout;
+
+@SuppressWarnings("serial")
+public class AnnotationColumnChooser extends AnnotationRowFilter implements
+        ItemListener
+{
+
+  private JComboBox<String> annotations;
+
+  // private JButton ok = new JButton();
+  //
+  // private JButton cancel = new JButton();
+
+  private JPanel actionPanel = new JPanel();
+
+  private JPanel thresholdPanel = new JPanel();
+
+  private JPanel switchableViewsPanel = new JPanel(new CardLayout());
+
+  private CardLayout switchableViewsLayout = (CardLayout) (switchableViewsPanel
+          .getLayout());
+  private JPanel noGraphFilterView = new JPanel();
+
+  private JPanel graphFilterView = new JPanel();
+
+  private JPanel annotationComboBoxPanel = new JPanel();
+
+  private BorderLayout borderLayout1 = new BorderLayout();
+
+  private JComboBox<String> threshold = new JComboBox<String>();
+
+  private StructureFilterPanel gStructureFilterPanel;
+
+  private StructureFilterPanel ngStructureFilterPanel;
+
+  private StructureFilterPanel currentStructureFilterPanel;
+
+  private SearchPanel currentSearchPanel;
+
+  private SearchPanel gSearchPanel;
+
+  private SearchPanel ngSearchPanel;
+
+  private FurtherActionPanel currentFurtherActionPanel;
+
+  private FurtherActionPanel gFurtherActionPanel;
+
+  private FurtherActionPanel ngFurtherActionPanel;
+
+  public static final int ACTION_OPTION_SELECT = 1;
+
+  public static int ACTION_OPTION_HIDE = 2;
+
+  public static String NO_GRAPH_VIEW = "0";
+
+  public static String GRAPH_VIEW = "1";
+
+  private int actionOption = ACTION_OPTION_SELECT;
+
+  private ColumnSelection oldColumnSelection;
+
+  public AnnotationColumnChooser()
+  {
+    try
+    {
+      jbInit();
+    } catch (Exception ex)
+    {
+      ex.printStackTrace();
+    }
+  }
+
+  public AnnotationColumnChooser(AlignViewport av, final AlignmentPanel ap)
+  {
+    super(av, ap);
+    frame = new JInternalFrame();
+    frame.setContentPane(this);
+    frame.setLayer(JLayeredPane.PALETTE_LAYER);
+    Desktop.addInternalFrame(frame,
+            MessageManager.getString("label.select_by_annotation"), 520,
+            215);
+
+    addSliderChangeListener();
+    addSliderMouseListeners();
+
+    if (av.getAlignment().getAlignmentAnnotation() == null)
+    {
+      return;
+    }
+    setOldColumnSelection(av.getColumnSelection());
+    adjusting = true;
+
+    setAnnotations(new JComboBox<String>(getAnnotationItems(false)));
+    populateThresholdComboBox(threshold);
+
+    // restore Object state from the previous session if one exists
+    if (av.getAnnotationColumnSelectionState() != null)
+    {
+      currentSearchPanel = av.getAnnotationColumnSelectionState()
+              .getCurrentSearchPanel();
+      currentStructureFilterPanel = av.getAnnotationColumnSelectionState()
+              .getCurrentStructureFilterPanel();
+      annotations.setSelectedIndex(av.getAnnotationColumnSelectionState()
+              .getAnnotations().getSelectedIndex());
+      threshold.setSelectedIndex(av.getAnnotationColumnSelectionState()
+              .getThreshold().getSelectedIndex());
+      actionOption = av.getAnnotationColumnSelectionState()
+              .getActionOption();
+    }
+
+    try
+    {
+      jbInit();
+    } catch (Exception ex)
+    {
+    }
+    adjusting = false;
+
+    updateView();
+    frame.invalidate();
+    frame.pack();
+  }
+
+  private void jbInit() throws Exception
+  {
+    ok.setOpaque(false);
+    ok.setText(MessageManager.getString("action.ok"));
+    ok.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        ok_actionPerformed(e);
+      }
+    });
+
+    cancel.setOpaque(false);
+    cancel.setText(MessageManager.getString("action.cancel"));
+    cancel.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        cancel_actionPerformed(e);
+      }
+    });
+
+    annotations.addItemListener(this);
+    threshold.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        threshold_actionPerformed(e);
+      }
+    });
+
+    thresholdValue.setEnabled(false);
+    thresholdValue.setColumns(7);
+    thresholdValue.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        thresholdValue_actionPerformed(e);
+      }
+    });
+
+    slider.setPaintLabels(false);
+    slider.setPaintTicks(true);
+    slider.setBackground(Color.white);
+    slider.setEnabled(false);
+    slider.setOpaque(false);
+    slider.setPreferredSize(new Dimension(100, 32));
+
+    thresholdPanel.setBorder(new TitledBorder(MessageManager
+            .getString("label.threshold_filter")));
+    thresholdPanel.setBackground(Color.white);
+    thresholdPanel.setFont(JvSwingUtils.getLabelFont());
+    thresholdPanel.setLayout(new MigLayout("", "[left][right]", "[][]"));
+
+    actionPanel.setBackground(Color.white);
+    actionPanel.setFont(JvSwingUtils.getLabelFont());
+
+    graphFilterView.setLayout(new MigLayout("", "[left][right]", "[][]"));
+    graphFilterView.setBackground(Color.white);
+
+    noGraphFilterView.setLayout(new MigLayout("", "[left][right]", "[][]"));
+    noGraphFilterView.setBackground(Color.white);
+
+    annotationComboBoxPanel.setBackground(Color.white);
+    annotationComboBoxPanel.setFont(JvSwingUtils.getLabelFont());
+
+    gSearchPanel = new SearchPanel(this);
+    ngSearchPanel = new SearchPanel(this);
+    gFurtherActionPanel = new FurtherActionPanel(this);
+    ngFurtherActionPanel = new FurtherActionPanel(this);
+    gStructureFilterPanel = new StructureFilterPanel(this);
+    ngStructureFilterPanel = new StructureFilterPanel(this);
+
+    thresholdPanel.add(getThreshold());
+    thresholdPanel.add(thresholdValue, "wrap");
+    thresholdPanel.add(slider, "grow, span, wrap");
+
+    actionPanel.add(ok);
+    actionPanel.add(cancel);
+
+    graphFilterView.add(gSearchPanel, "grow, span, wrap");
+    graphFilterView.add(gStructureFilterPanel, "grow, span, wrap");
+    graphFilterView.add(thresholdPanel, "grow, span, wrap");
+    graphFilterView.add(gFurtherActionPanel);
+
+    noGraphFilterView.add(ngSearchPanel, "grow, span, wrap");
+    noGraphFilterView.add(ngStructureFilterPanel, "grow, span, wrap");
+    noGraphFilterView.add(ngFurtherActionPanel);
+
+    annotationComboBoxPanel.add(getAnnotations());
+    switchableViewsPanel.add(noGraphFilterView,
+            AnnotationColumnChooser.NO_GRAPH_VIEW);
+    switchableViewsPanel.add(graphFilterView,
+            AnnotationColumnChooser.GRAPH_VIEW);
+
+    this.setLayout(borderLayout1);
+    this.add(annotationComboBoxPanel, java.awt.BorderLayout.PAGE_START);
+    this.add(switchableViewsPanel, java.awt.BorderLayout.CENTER);
+    this.add(actionPanel, java.awt.BorderLayout.SOUTH);
+
+    selectedAnnotationChanged();
+    this.validate();
+  }
+
+  @SuppressWarnings("unchecked")
+  public void reset()
+  {
+    if (this.getOldColumnSelection() != null)
+    {
+      av.getColumnSelection().clear();
+
+      if (av.getAnnotationColumnSelectionState() != null)
+      {
+        ColumnSelection oldSelection = av
+                .getAnnotationColumnSelectionState()
+                .getOldColumnSelection();
+        if (oldSelection != null && oldSelection.getHiddenColumns() != null
+                && !oldSelection.getHiddenColumns().isEmpty())
+        {
+          for (Iterator<int[]> itr = oldSelection.getHiddenColumns()
+                  .iterator(); itr.hasNext();)
+          {
+            int positions[] = itr.next();
+            av.hideColumns(positions[0], positions[1]);
+          }
+        }
+        av.setColumnSelection(oldSelection);
+      }
+      ap.paintAlignment(true);
+    }
+
+  }
+
+  public void valueChanged(boolean updateAllAnnotation)
+  {
+    if (slider.isEnabled())
+    {
+      getCurrentAnnotation().threshold.value = slider.getValue() / 1000f;
+      updateView();
+      propagateSeqAssociatedThreshold(updateAllAnnotation,
+              getCurrentAnnotation());
+      ap.paintAlignment(false);
+    }
+  }
+
+  public JComboBox<String> getThreshold()
+  {
+    return threshold;
+  }
+
+  public void setThreshold(JComboBox<String> threshold)
+  {
+    this.threshold = threshold;
+  }
+
+  public JComboBox<String> getAnnotations()
+  {
+    return annotations;
+  }
+
+  public void setAnnotations(JComboBox<String> annotations)
+  {
+    this.annotations = annotations;
+  }
+
+  @Override
+  public void updateView()
+  {
+    // Check if combobox is still adjusting
+    if (adjusting)
+    {
+      return;
+    }
+
+    AnnotationFilterParameter filterParams = new AnnotationFilterParameter();
+
+    setCurrentAnnotation(av.getAlignment().getAlignmentAnnotation()[annmap[getAnnotations()
+            .getSelectedIndex()]]);
+
+    int selectedThresholdItem = getSelectedThresholdItem(getThreshold()
+            .getSelectedIndex());
+
+    slider.setEnabled(true);
+    thresholdValue.setEnabled(true);
+
+    if (selectedThresholdItem == AnnotationColourGradient.NO_THRESHOLD)
+    {
+      slider.setEnabled(false);
+      thresholdValue.setEnabled(false);
+      thresholdValue.setText("");
+      // build filter params
+    }
+    else if (selectedThresholdItem != AnnotationColourGradient.NO_THRESHOLD)
+    {
+      if (getCurrentAnnotation().threshold == null)
+      {
+        getCurrentAnnotation()
+                .setThreshold(
+                        new jalview.datamodel.GraphLine(
+                                (getCurrentAnnotation().graphMax - getCurrentAnnotation().graphMin) / 2f,
+                                "Threshold", Color.black));
+      }
+
+      adjusting = true;
+      float range = getCurrentAnnotation().graphMax * 1000
+              - getCurrentAnnotation().graphMin * 1000;
+
+      slider.setMinimum((int) (getCurrentAnnotation().graphMin * 1000));
+      slider.setMaximum((int) (getCurrentAnnotation().graphMax * 1000));
+      slider.setValue((int) (getCurrentAnnotation().threshold.value * 1000));
+      thresholdValue.setText(getCurrentAnnotation().threshold.value + "");
+      slider.setMajorTickSpacing((int) (range / 10f));
+      slider.setEnabled(true);
+      thresholdValue.setEnabled(true);
+      adjusting = false;
+
+      // build filter params
+      filterParams
+              .setThresholdType(AnnotationFilterParameter.ThresholdType.NO_THRESHOLD);
+      if (getCurrentAnnotation().graph != AlignmentAnnotation.NO_GRAPH)
+      {
+        filterParams
+                .setThresholdValue(getCurrentAnnotation().threshold.value);
+
+        if (selectedThresholdItem == AnnotationColourGradient.ABOVE_THRESHOLD)
+        {
+          filterParams
+                  .setThresholdType(AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD);
+        }
+        else if (selectedThresholdItem == AnnotationColourGradient.BELOW_THRESHOLD)
+        {
+          filterParams
+                  .setThresholdType(AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD);
+        }
+      }
+    }
+
+    if (currentStructureFilterPanel != null)
+    {
+      if (currentStructureFilterPanel.alphaHelix.isSelected())
+      {
+        filterParams.setFilterAlphaHelix(true);
+      }
+      if (currentStructureFilterPanel.betaStrand.isSelected())
+      {
+        filterParams.setFilterBetaSheet(true);
+      }
+      if (currentStructureFilterPanel.turn.isSelected())
+      {
+        filterParams.setFilterTurn(true);
+      }
+    }
+
+    if (currentSearchPanel != null)
+    {
+
+      if (!currentSearchPanel.searchBox.getText().isEmpty())
+      {
+        currentSearchPanel.description.setEnabled(true);
+        currentSearchPanel.displayName.setEnabled(true);
+        filterParams.setRegexString(currentSearchPanel.searchBox.getText());
+        if (currentSearchPanel.displayName.isSelected())
+        {
+          filterParams
+                  .addRegexSearchField(AnnotationFilterParameter.SearchableAnnotationField.DISPLAY_STRING);
+        }
+        if (currentSearchPanel.description.isSelected())
+        {
+          filterParams
+                  .addRegexSearchField(AnnotationFilterParameter.SearchableAnnotationField.DESCRIPTION);
+        }
+      }
+      else
+      {
+        currentSearchPanel.description.setEnabled(false);
+        currentSearchPanel.displayName.setEnabled(false);
+      }
+    }
+
+    av.getColumnSelection().filterAnnotations(
+            getCurrentAnnotation().annotations, filterParams);
+
+    av.showAllHiddenColumns();
+    if (getActionOption() == ACTION_OPTION_HIDE)
+    {
+      av.hideSelectedColumns();
+    }
+
+    filterParams = null;
+    av.setAnnotationColumnSelectionState(this);
+    ap.paintAlignment(true);
+  }
+
+  public ColumnSelection getOldColumnSelection()
+  {
+    return oldColumnSelection;
+  }
+
+  public void setOldColumnSelection(ColumnSelection currentColumnSelection)
+  {
+    if (currentColumnSelection != null)
+    {
+      this.oldColumnSelection = new ColumnSelection(currentColumnSelection);
+    }
+  }
+
+  public FurtherActionPanel getCurrentFutherActionPanel()
+  {
+    return currentFurtherActionPanel;
+  }
+
+  public void setCurrentFutherActionPanel(
+          FurtherActionPanel currentFutherActionPanel)
+  {
+    this.currentFurtherActionPanel = currentFutherActionPanel;
+  }
+
+  public SearchPanel getCurrentSearchPanel()
+  {
+    return currentSearchPanel;
+  }
+
+  public void setCurrentSearchPanel(SearchPanel currentSearchPanel)
+  {
+    this.currentSearchPanel = currentSearchPanel;
+  }
+
+  public int getActionOption()
+  {
+    return actionOption;
+  }
+
+  public void setActionOption(int actionOption)
+  {
+    this.actionOption = actionOption;
+  }
+
+  public StructureFilterPanel getCurrentStructureFilterPanel()
+  {
+    return currentStructureFilterPanel;
+  }
+
+  public void setCurrentStructureFilterPanel(
+          StructureFilterPanel currentStructureFilterPanel)
+  {
+    this.currentStructureFilterPanel = currentStructureFilterPanel;
+  }
+
+  public void select_action(ActionEvent actionEvent)
+  {
+    JRadioButton radioButton = (JRadioButton) actionEvent.getSource();
+    if (radioButton.isSelected())
+    {
+      setActionOption(ACTION_OPTION_SELECT);
+      updateView();
+    }
+  }
+
+  public void hide_action(ActionEvent actionEvent)
+  {
+    JRadioButton radioButton = (JRadioButton) actionEvent.getSource();
+    if (radioButton.isSelected())
+    {
+      setActionOption(ACTION_OPTION_HIDE);
+      updateView();
+    }
+  }
+
+  @Override
+  public void itemStateChanged(ItemEvent e)
+  {
+    selectedAnnotationChanged();
+  }
+
+  public void selectedAnnotationChanged()
+  {
+    String currentView = AnnotationColumnChooser.NO_GRAPH_VIEW;
+    if (av.getAlignment().getAlignmentAnnotation()[annmap[getAnnotations()
+            .getSelectedIndex()]].graph != AlignmentAnnotation.NO_GRAPH)
+    {
+      currentView = AnnotationColumnChooser.GRAPH_VIEW;
+    }
+
+    gSearchPanel.syncState();
+    gFurtherActionPanel.syncState();
+    gStructureFilterPanel.syncState();
+
+    ngSearchPanel.syncState();
+    ngFurtherActionPanel.syncState();
+    ngStructureFilterPanel.syncState();
+
+    switchableViewsLayout.show(switchableViewsPanel, currentView);
+    updateView();
+  }
+
+
+  public class FurtherActionPanel extends JPanel
+  {
+    private AnnotationColumnChooser aColChooser;
+
+    private JRadioButton hideOption = new JRadioButton();
+
+    private JRadioButton selectOption = new JRadioButton();
+
+    private ButtonGroup optionsGroup = new ButtonGroup();
+
+    public FurtherActionPanel(AnnotationColumnChooser aColChooser)
+    {
+      this.aColChooser = aColChooser;
+      JvSwingUtils.jvInitComponent(selectOption, "action.select");
+      selectOption.addActionListener(new ActionListener()
+      {
+        @Override
+        public void actionPerformed(ActionEvent actionEvent)
+        {
+          selectRadioAction(actionEvent);
+        }
+      });
+
+      JvSwingUtils.jvInitComponent(hideOption, "action.hide");
+      hideOption.addActionListener(new ActionListener()
+      {
+        @Override
+        public void actionPerformed(ActionEvent actionEvent)
+        {
+          hideRadioAction(actionEvent);
+        }
+      });
+
+      optionsGroup.add(selectOption);
+      optionsGroup.add(hideOption);
+      optionsGroup.setSelected(selectOption.getModel(), true);
+
+      JvSwingUtils.jvInitComponent(this);
+      syncState();
+
+      this.add(selectOption);
+      this.add(hideOption);
+    }
+
+    public void selectRadioAction(ActionEvent actionEvent)
+    {
+      aColChooser.setCurrentFutherActionPanel(this);
+      aColChooser.select_action(actionEvent);
+    }
+
+    public void hideRadioAction(ActionEvent actionEvent)
+    {
+      aColChooser.setCurrentFutherActionPanel(this);
+      aColChooser.hide_action(actionEvent);
+    }
+
+    public void syncState()
+    {
+      if (aColChooser.getActionOption() == AnnotationColumnChooser.ACTION_OPTION_HIDE)
+      {
+        this.optionsGroup.setSelected(this.hideOption.getModel(),
+                true);
+      }
+      else
+      {
+        this.optionsGroup.setSelected(this.selectOption.getModel(), true);
+      }
+    }
+  }
+
+  public class StructureFilterPanel extends JPanel
+  {
+    private AnnotationColumnChooser aColChooser;
+
+    private JCheckBox alphaHelix = new JCheckBox();
+
+    private JCheckBox betaStrand = new JCheckBox();
+
+    private JCheckBox turn = new JCheckBox();
+
+    private JCheckBox all = new JCheckBox();
+
+    public StructureFilterPanel(AnnotationColumnChooser aColChooser)
+    {
+      this.aColChooser = aColChooser;
+
+      JvSwingUtils.jvInitComponent(alphaHelix, "label.alpha_helix");
+      alphaHelix.addActionListener(new ActionListener()
+      {
+        @Override
+        public void actionPerformed(ActionEvent actionEvent)
+        {
+          alphaHelix_actionPerformed();
+        }
+      });
+
+      JvSwingUtils.jvInitComponent(betaStrand, "label.beta_strand");
+      betaStrand.addActionListener(new ActionListener()
+      {
+        @Override
+        public void actionPerformed(ActionEvent actionEvent)
+        {
+          betaStrand_actionPerformed();
+        }
+      });
+
+      JvSwingUtils.jvInitComponent(turn, "label.turn");
+      turn.addActionListener(new ActionListener()
+      {
+        @Override
+        public void actionPerformed(ActionEvent actionEvent)
+        {
+          turn_actionPerformed();
+        }
+      });
+
+      JvSwingUtils.jvInitComponent(all, "label.select_all");
+      all.addActionListener(new ActionListener()
+      {
+        @Override
+        public void actionPerformed(ActionEvent actionEvent)
+        {
+          all_actionPerformed();
+        }
+      });
+
+      this.setBorder(new TitledBorder(MessageManager
+              .getString("label.structures_filter")));
+      JvSwingUtils.jvInitComponent(this);
+
+      this.add(all);
+      this.add(alphaHelix);
+      this.add(betaStrand);
+      this.add(turn);
+    }
+
+    public void alphaHelix_actionPerformed()
+    {
+      updateSelectAllState();
+      aColChooser.setCurrentStructureFilterPanel(this);
+      aColChooser.updateView();
+    }
+
+    public void betaStrand_actionPerformed()
+    {
+      updateSelectAllState();
+      aColChooser.setCurrentStructureFilterPanel(this);
+      aColChooser.updateView();
+    }
+
+    public void turn_actionPerformed()
+    {
+      updateSelectAllState();
+      aColChooser.setCurrentStructureFilterPanel(this);
+      aColChooser.updateView();
+    }
+
+    public void all_actionPerformed()
+    {
+      if (all.isSelected())
+      {
+        alphaHelix.setSelected(true);
+        betaStrand.setSelected(true);
+        turn.setSelected(true);
+      }
+      else
+      {
+        alphaHelix.setSelected(false);
+        betaStrand.setSelected(false);
+        turn.setSelected(false);
+      }
+      aColChooser.setCurrentStructureFilterPanel(this);
+      aColChooser.updateView();
+    }
+
+    public void updateSelectAllState()
+    {
+      if (alphaHelix.isSelected() && betaStrand.isSelected()
+              && turn.isSelected())
+      {
+        all.setSelected(true);
+      }
+      else
+      {
+        all.setSelected(false);
+      }
+    }
+
+    public void syncState()
+    {
+      StructureFilterPanel sfp = aColChooser
+              .getCurrentStructureFilterPanel();
+      if (sfp != null)
+      {
+        alphaHelix.setSelected(sfp.alphaHelix.isSelected());
+        betaStrand.setSelected(sfp.betaStrand.isSelected());
+        turn.setSelected(sfp.turn.isSelected());
+        if (sfp.all.isSelected())
+        {
+          all.setSelected(true);
+          alphaHelix.setSelected(true);
+          betaStrand.setSelected(true);
+          turn.setSelected(true);
+        }
+      }
+
+    }
+  }
+
+  public class SearchPanel extends JPanel
+  {
+    private AnnotationColumnChooser aColChooser;
+
+    private JCheckBox displayName = new JCheckBox();
+
+    private JCheckBox description = new JCheckBox();
+
+    private JTextField searchBox = new JTextField(10);
+
+    public SearchPanel(AnnotationColumnChooser aColChooser)
+    {
+
+      this.aColChooser = aColChooser;
+      JvSwingUtils.jvInitComponent(this);
+      this.setBorder(new TitledBorder(MessageManager
+              .getString("label.search_filter")));
+
+      JvSwingUtils.jvInitComponent(searchBox);
+      searchBox.getDocument().addDocumentListener(
+              new DocumentListener()
+              {
+                @Override
+                public void insertUpdate(DocumentEvent e)
+                {
+                  searchStringAction();
+                }
+
+                @Override
+                public void removeUpdate(DocumentEvent e)
+                {
+                  searchStringAction();
+                }
+
+                @Override
+                public void changedUpdate(DocumentEvent e)
+                {
+                  searchStringAction();
+                }
+              });
+
+      JvSwingUtils.jvInitComponent(displayName, "label.display_name");
+      displayName.setEnabled(false);
+      displayName.addActionListener(new ActionListener()
+      {
+        @Override
+        public void actionPerformed(ActionEvent actionEvent)
+        {
+          displayNameCheckboxAction();
+        }
+      });
+
+      JvSwingUtils.jvInitComponent(description, "label.description");
+      description.setEnabled(false);
+      description.addActionListener(new ActionListener()
+      {
+        @Override
+        public void actionPerformed(ActionEvent actionEvent)
+        {
+          discriptionCheckboxAction();
+        }
+      });
+
+      syncState();
+      this.add(searchBox);
+      this.add(displayName);
+      this.add(description);
+    }
+
+    public void displayNameCheckboxAction()
+    {
+      aColChooser.setCurrentSearchPanel(this);
+      aColChooser.updateView();
+    }
+
+    public void discriptionCheckboxAction()
+    {
+      aColChooser.setCurrentSearchPanel(this);
+      aColChooser.updateView();
+    }
+
+    public void searchStringAction()
+    {
+      aColChooser.setCurrentSearchPanel(this);
+      aColChooser.updateView();
+    }
+
+    public void syncState()
+    {
+      SearchPanel sp = aColChooser.getCurrentSearchPanel();
+      if (sp != null)
+      {
+        description.setEnabled(sp.description.isEnabled());
+        description.setSelected(sp.description.isSelected());
+
+        displayName.setEnabled(sp.displayName.isEnabled());
+        displayName.setSelected(sp.displayName.isSelected());
+
+        searchBox.setText(sp.searchBox.getText());
+      }
+    }
+  }
+
+}
index 315c3e2..df6f9eb 100644 (file)
  */
 package jalview.gui;
 
-import java.util.*;
-import java.util.List;
-
-import java.awt.*;
-import java.awt.event.*;
-import javax.swing.*;
-
-import jalview.datamodel.*;
-import jalview.io.*;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.io.AnnotationFile;
+import jalview.io.FeaturesFile;
+import jalview.io.JalviewFileChooser;
+import jalview.io.JalviewFileView;
 import jalview.util.MessageManager;
 
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JInternalFrame;
+import javax.swing.JLabel;
+import javax.swing.JLayeredPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.SwingConstants;
+
 /**
  * 
  * GUI dialog for exporting features or alignment annotations depending upon
@@ -47,11 +59,9 @@ public class AnnotationExporter extends JPanel
 
   boolean features = true;
 
-  AlignmentAnnotation[] annotations;
+  private AlignmentAnnotation[] annotations;
 
-  List<SequenceGroup> sequenceGroups;
-
-  Hashtable alignmentProperties;
+  private boolean wholeView;
 
   public AnnotationExporter()
   {
@@ -78,17 +88,29 @@ public class AnnotationExporter extends JPanel
     frame.setTitle(MessageManager.getString("label.export_features"));
   }
 
-  public void exportAnnotations(AlignmentPanel ap,
-          AlignmentAnnotation[] annotations, List<SequenceGroup> list,
-          Hashtable alProperties)
+  public void exportAnnotations(AlignmentPanel ap)
   {
     this.ap = ap;
+    annotations = ap.av.isShowAnnotation() ? null : ap.av.getAlignment()
+            .getAlignmentAnnotation();
+    wholeView = true;
+    startExportAnnotation();
+  }
+
+  public void exportAnnotations(AlignmentPanel alp,
+          AlignmentAnnotation[] toExport)
+  {
+    ap = alp;
+    annotations = toExport;
+    wholeView = false;
+    startExportAnnotation();
+  }
+
+  private void startExportAnnotation()
+  {
     features = false;
     GFFFormat.setVisible(false);
     CSVFormat.setVisible(true);
-    this.annotations = annotations;
-    this.sequenceGroups = list;
-    this.alignmentProperties = alProperties;
     frame.setTitle(MessageManager.getString("label.export_annotations"));
   }
 
@@ -106,34 +128,7 @@ public class AnnotationExporter extends JPanel
 
     if (value == JalviewFileChooser.APPROVE_OPTION)
     {
-      String text = MessageManager.getString("label.no_features_on_alignment");
-      if (features)
-      {
-        if (GFFFormat.isSelected())
-        {
-          text = new FeaturesFile().printGFFFormat(ap.av.getAlignment()
-                  .getDataset().getSequencesArray(),
-                  getDisplayedFeatureCols(), true, ap.av.isShowNpFeats());// ap.av.featuresDisplayed//);
-        }
-        else
-        {
-          text = new FeaturesFile().printJalviewFormat(ap.av.getAlignment()
-                  .getDataset().getSequencesArray(),
-                  getDisplayedFeatureCols(), true, ap.av.isShowNpFeats()); // ap.av.featuresDisplayed);
-        }
-      }
-      else
-      {
-        if (CSVFormat.isSelected())
-        {
-          text = new AnnotationFile().printCSVAnnotations(annotations);
-        }
-        else
-        {
-          text = new AnnotationFile().printAnnotations(annotations,
-                  sequenceGroups, alignmentProperties);
-        }
-      }
+      String text = getFileContents();
 
       try
       {
@@ -151,25 +146,26 @@ public class AnnotationExporter extends JPanel
     close_actionPerformed(null);
   }
 
-  public void toTextbox_actionPerformed(ActionEvent e)
+  private String getFileContents()
   {
-    String text = MessageManager.getString("label.no_features_on_alignment");
+    String text = MessageManager
+            .getString("label.no_features_on_alignment");
     if (features)
     {
       if (GFFFormat.isSelected())
       {
         text = new FeaturesFile().printGFFFormat(ap.av.getAlignment()
-                .getDataset().getSequencesArray(),
-                getDisplayedFeatureCols(), true, ap.av.isShowNpFeats());
+                .getDataset().getSequencesArray(), ap.getFeatureRenderer()
+                .getDisplayedFeatureCols(), true, ap.av.isShowNpFeats());// ap.av.featuresDisplayed//);
       }
       else
       {
         text = new FeaturesFile().printJalviewFormat(ap.av.getAlignment()
-                .getDataset().getSequencesArray(),
-                getDisplayedFeatureCols(), true, ap.av.isShowNpFeats());
+                .getDataset().getSequencesArray(), ap.getFeatureRenderer()
+                .getDisplayedFeatureCols(), true, ap.av.isShowNpFeats()); // ap.av.featuresDisplayed);
       }
     }
-    else if (!features)
+    else
     {
       if (CSVFormat.isSelected())
       {
@@ -177,14 +173,26 @@ public class AnnotationExporter extends JPanel
       }
       else
       {
-        text = new AnnotationFile().printAnnotations(annotations,
-                sequenceGroups, alignmentProperties);
+        if (wholeView)
+        {
+          text = new AnnotationFile().printAnnotationsForView(ap.av);
+        }
+        else
+        {
+          text = new AnnotationFile().printAnnotations(annotations, null,
+                  null);
+        }
       }
     }
-
+    return text;
+  }
+  public void toTextbox_actionPerformed(ActionEvent e)
+  {
     CutAndPasteTransfer cap = new CutAndPasteTransfer();
+
     try
     {
+      String text = getFileContents();
       cap.setText(text);
       Desktop.addInternalFrame(
               cap,
@@ -206,27 +214,6 @@ public class AnnotationExporter extends JPanel
 
     close_actionPerformed(null);
   }
-
-  private Hashtable getDisplayedFeatureCols()
-  {
-    Hashtable fcols = new Hashtable();
-    if (ap.av.featuresDisplayed == null)
-    {
-      return fcols;
-    }
-    Enumeration en = ap.av.featuresDisplayed.keys();
-    FeatureRenderer fr = ap.seqPanel.seqCanvas.getFeatureRenderer(); // consider
-                                                                     // higher
-                                                                     // level
-                                                                     // method ?
-    while (en.hasMoreElements())
-    {
-      Object col = en.nextElement();
-      fcols.put(col, fr.featureColours.get(col));
-    }
-    return fcols;
-  }
-
   public void close_actionPerformed(ActionEvent e)
   {
     try
@@ -313,5 +300,4 @@ public class AnnotationExporter extends JPanel
   JPanel jPanel3 = new JPanel();
 
   FlowLayout flowLayout1 = new FlowLayout();
-
 }
index 6e8417f..f732a36 100755 (executable)
@@ -47,8 +47,8 @@ import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
 import java.awt.geom.AffineTransform;
 import java.awt.image.BufferedImage;
+import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Vector;
 import java.util.regex.Pattern;
 
 import javax.swing.JCheckBoxMenuItem;
@@ -99,7 +99,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
 
   int selectedRow;
 
-  int scrollOffset = 0;
+  private int scrollOffset = 0;
 
   Font font = new Font("Arial", Font.PLAIN, 11);
 
@@ -143,7 +143,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
 
     addMouseListener(this);
     addMouseMotionListener(this);
-    addMouseWheelListener(ap.annotationPanel);
+    addMouseWheelListener(ap.getAnnotationPanel());
   }
 
   public AnnotationLabels(AlignViewport av)
@@ -249,7 +249,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
     {
       new AnnotationExporter().exportAnnotations(ap,
               new AlignmentAnnotation[]
-              { aa[selectedRow] }, null, null);
+      { aa[selectedRow] });
     }
     else if (evt.getActionCommand().equals(COPYCONS_SEQ))
     {
@@ -325,7 +325,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
    */
   public void mousePressed(MouseEvent evt)
   {
-    getSelectedRow(evt.getY() - scrollOffset);
+    getSelectedRow(evt.getY() - getScrollOffset());
     oldY = evt.getY();
   }
 
@@ -338,7 +338,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
   public void mouseReleased(MouseEvent evt)
   {
     int start = selectedRow;
-    getSelectedRow(evt.getY() - scrollOffset);
+    getSelectedRow(evt.getY() - getScrollOffset());
     int end = selectedRow;
 
     if (start != end)
@@ -360,7 +360,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
     resizePanel = false;
     dragEvent = null;
     repaint();
-    ap.annotationPanel.repaint();
+    ap.getAnnotationPanel().repaint();
   }
 
   /**
@@ -439,7 +439,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
   {
     resizePanel = evt.getY() < 10;
 
-    getSelectedRow(evt.getY() - scrollOffset);
+    getSelectedRow(evt.getY() - getScrollOffset());
 
     if (selectedRow > -1
             && ap.av.getAlignment().getAlignmentAnnotation().length > selectedRow)
@@ -533,7 +533,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
           {
             // todo: make the ap scroll to the selection - not necessary, first
             // click highlights/scrolls, second selects
-            ap.seqPanel.ap.idPanel.highlightSearchResults(null);
+            ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(null);
             ap.av.setSelectionGroup(// new SequenceGroup(
             aa[selectedRow].groupRef); // );
             ap.paintAlignment(false);
@@ -542,7 +542,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
           }
           else
           {
-            ap.seqPanel.ap.idPanel
+            ap.getSeqPanel().ap.getIdPanel()
                     .highlightSearchResults(aa[selectedRow].groupRef
                             .getSequences(null));
           }
@@ -552,13 +552,13 @@ public class AnnotationLabels extends JPanel implements MouseListener,
         {
           if (evt.getClickCount() == 1)
           {
-            ap.seqPanel.ap.idPanel.highlightSearchResults(Arrays
+            ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(Arrays
                     .asList(new SequenceI[]
                     { aa[selectedRow].sequenceRef }));
           }
           else if (evt.getClickCount() >= 2)
           {
-            ap.seqPanel.ap.idPanel.highlightSearchResults(null);
+            ap.getSeqPanel().ap.getIdPanel().highlightSearchResults(null);
             SequenceGroup sg = ap.av.getSelectionGroup();
             if (sg!=null)
             {
@@ -696,7 +696,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
             {
               // TODO: pass on reference to ap so the view can be updated.
               aaa.groupRef.setIgnoreGapsConsensus(cbmi.getState());
-              ap.annotationPanel.paint(ap.annotationPanel.getGraphics());
+              ap.getAnnotationPanel().paint(ap.getAnnotationPanel().getGraphics());
             }
             else
             {
@@ -872,16 +872,13 @@ public class AnnotationLabels extends JPanel implements MouseListener,
     Toolkit.getDefaultToolkit().getSystemClipboard()
             .setContents(new StringSelection(output), Desktop.instance);
 
-    Vector hiddenColumns = null;
+    ArrayList<int[]> hiddenColumns = null;
     if (av.hasHiddenColumns())
     {
-      hiddenColumns = new Vector();
-      for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
+      hiddenColumns = new ArrayList<int[]>();
+      for (int[] region : av.getColumnSelection().getHiddenColumns())
       {
-        int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
-                .elementAt(i);
-
-        hiddenColumns.addElement(new int[]
+        hiddenColumns.add(new int[]
         { region[0], region[1] });
       }
     }
@@ -962,7 +959,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
     g.setColor(Color.white);
     g.fillRect(0, 0, getWidth(), getHeight());
 
-    g.translate(0, scrollOffset);
+    g.translate(0, getScrollOffset());
     g.setColor(Color.black);
 
     AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
@@ -976,7 +973,7 @@ public class AnnotationLabels extends JPanel implements MouseListener,
     int ofontH = fontHeight;
     int sOffset = 0;
     int visHeight = 0;
-    int[] visr = (ap != null && ap.annotationPanel != null) ? ap.annotationPanel
+    int[] visr = (ap != null && ap.getAnnotationPanel() != null) ? ap.getAnnotationPanel()
             .getVisibleVRange() : null;
     if (clip && visr != null)
     {
@@ -1116,13 +1113,13 @@ public class AnnotationLabels extends JPanel implements MouseListener,
 
     if (resizePanel)
     {
-      g.drawImage(image, 2, 0 - scrollOffset, this);
+      g.drawImage(image, 2, 0 - getScrollOffset(), this);
     }
     else if (dragEvent != null && aa != null)
     {
       g.setColor(Color.lightGray);
       g.drawString(aa[selectedRow].label, dragEvent.getX(),
-              dragEvent.getY() - scrollOffset);
+              dragEvent.getY() - getScrollOffset());
     }
 
     if (!av.wrapAlignment && ((aa == null) || (aa.length < 1)))
@@ -1132,4 +1129,9 @@ public class AnnotationLabels extends JPanel implements MouseListener,
               18);
     }
   }
+
+  public int getScrollOffset()
+  {
+    return scrollOffset;
+  }
 }
index 9f56206..3a97f96 100755 (executable)
@@ -241,7 +241,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
   public void adjustmentValueChanged(AdjustmentEvent evt)
   {
     // update annotation label display
-    ap.alabels.setScrollOffset(-evt.getValue());
+    ap.getAlabels().setScrollOffset(-evt.getValue());
   }
 
   /**
@@ -566,7 +566,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
       return;
     }
 
-    ap.scalePanel.mousePressed(evt);
+    ap.getScalePanel().mousePressed(evt);
 
   }
 
@@ -582,7 +582,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     graphStretch = -1;
     graphStretchY = -1;
     mouseDragging = false;
-    ap.scalePanel.mouseReleased(evt);
+    ap.getScalePanel().mouseReleased(evt);
   }
 
   /**
@@ -594,7 +594,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
   @Override
   public void mouseEntered(MouseEvent evt)
   {
-    ap.scalePanel.mouseEntered(evt);
+    ap.getScalePanel().mouseEntered(evt);
   }
 
   /**
@@ -606,7 +606,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
   @Override
   public void mouseExited(MouseEvent evt)
   {
-    ap.scalePanel.mouseExited(evt);
+    ap.getScalePanel().mouseExited(evt);
   }
 
   /**
@@ -632,7 +632,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     }
     else
     {
-      ap.scalePanel.mouseDragged(evt);
+      ap.getScalePanel().mouseDragged(evt);
     }
   }
 
@@ -809,7 +809,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
     {
       try
       {
-        image = new BufferedImage(imgWidth, ap.annotationPanel.getHeight(),
+        image = new BufferedImage(imgWidth, ap.getAnnotationPanel().getHeight(),
                 BufferedImage.TYPE_INT_RGB);
       } catch (OutOfMemoryError oom)
       {
@@ -1013,9 +1013,9 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
   @Override
   public int[] getVisibleVRange()
   {
-    if (ap != null && ap.alabels != null)
+    if (ap != null && ap.getAlabels() != null)
     {
-      int sOffset = -ap.alabels.scrollOffset;
+      int sOffset = -ap.getAlabels().getScrollOffset();
       int visHeight = sOffset + ap.annotationSpaceFillerHolder.getHeight();
       bounds[0] = sOffset;
       bounds[1] = visHeight;
diff --git a/src/jalview/gui/AnnotationRowFilter.java b/src/jalview/gui/AnnotationRowFilter.java
new file mode 100644 (file)
index 0000000..21c91d8
--- /dev/null
@@ -0,0 +1,384 @@
+package jalview.gui;
+
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.GraphLine;
+import jalview.datamodel.SequenceGroup;
+import jalview.schemes.AnnotationColourGradient;
+import jalview.util.MessageManager;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.Vector;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JInternalFrame;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+import javax.swing.JTextField;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+@SuppressWarnings("serial")
+public abstract class AnnotationRowFilter extends JPanel
+{
+  protected AlignViewport av;
+
+  protected AlignmentPanel ap;
+
+  protected int[] annmap;
+
+  protected boolean enableSeqAss = false;
+
+  private jalview.datamodel.AlignmentAnnotation currentAnnotation;
+
+  protected boolean adjusting = false;
+
+  protected JCheckBox currentColours = new JCheckBox();
+
+  protected JPanel minColour = new JPanel();
+
+  protected JPanel maxColour = new JPanel();
+
+  protected JCheckBox seqAssociated = new JCheckBox();
+
+  protected JCheckBox thresholdIsMin = new JCheckBox();
+
+  protected JSlider slider = new JSlider();
+
+  protected JTextField thresholdValue = new JTextField(20);
+
+  protected JInternalFrame frame;
+
+  protected JButton ok = new JButton();
+
+  protected JButton cancel = new JButton();
+
+  /**
+   * enabled if the user is dragging the slider - try to keep updates to a
+   * minimun
+   */
+  protected boolean sliderDragging = false;
+
+  protected void addSliderChangeListener()
+  {
+
+    slider.addChangeListener(new ChangeListener()
+    {
+      @Override
+      public void stateChanged(ChangeEvent evt)
+      {
+        if (!adjusting)
+        {
+          thresholdValue.setText((slider.getValue() / 1000f) + "");
+          valueChanged(!sliderDragging);
+        }
+      }
+    });
+  }
+
+  protected void addSliderMouseListeners()
+  {
+
+    slider.addMouseListener(new MouseAdapter()
+    {
+      @Override
+      public void mousePressed(MouseEvent e)
+      {
+        sliderDragging = true;
+        super.mousePressed(e);
+      }
+
+      @Override
+      public void mouseDragged(MouseEvent e)
+      {
+        sliderDragging = true;
+        super.mouseDragged(e);
+      }
+
+      @Override
+      public void mouseReleased(MouseEvent evt)
+      {
+        if (sliderDragging)
+        {
+          sliderDragging = false;
+          valueChanged(true);
+        }
+        ap.paintAlignment(true);
+      }
+    });
+  }
+
+
+  public AnnotationRowFilter(AlignViewport av, final AlignmentPanel ap)
+  {
+    this.av = av;
+    this.ap = ap;
+  }
+
+  public AnnotationRowFilter()
+  {
+
+  }
+
+  public Vector<String> getAnnotationItems(boolean isSeqAssociated)
+  {
+    Vector<String> list = new Vector<String>();
+    int index = 1;
+    int[] anmap = new int[av.getAlignment().getAlignmentAnnotation().length];
+    for (int i = 0; i < av.getAlignment().getAlignmentAnnotation().length; i++)
+    {
+      if (av.getAlignment().getAlignmentAnnotation()[i].sequenceRef == null)
+      {
+        if (isSeqAssociated)
+        {
+          continue;
+        }
+      }
+      else
+      {
+        enableSeqAss = true;
+      }
+      String label = av.getAlignment().getAlignmentAnnotation()[i].label;
+      if (!list.contains(label))
+      {
+        anmap[list.size()] = i;
+        list.add(label);
+
+      }
+      else
+      {
+        if (!isSeqAssociated)
+        {
+          anmap[list.size()] = i;
+          list.add(label + "_" + (index++));
+        }
+      }
+    }
+    this.annmap = new int[list.size()];
+    System.arraycopy(anmap, 0, this.annmap, 0, this.annmap.length);
+    return list;
+  }
+
+  protected int getSelectedThresholdItem(int indexValue)
+  {
+    int selectedThresholdItem = -1;
+    if (indexValue == 1)
+    {
+      selectedThresholdItem = AnnotationColourGradient.ABOVE_THRESHOLD;
+    }
+    else if (indexValue == 2)
+    {
+      selectedThresholdItem = AnnotationColourGradient.BELOW_THRESHOLD;
+    }
+    return selectedThresholdItem;
+  }
+
+  public void modelChanged()
+  {
+    seqAssociated.setEnabled(enableSeqAss);
+  }
+
+  public void ok_actionPerformed(ActionEvent e)
+  {
+    updateView();
+    try
+    {
+      frame.setClosed(true);
+    } catch (Exception ex)
+    {
+    }
+  }
+
+  public void cancel_actionPerformed(ActionEvent e)
+  {
+    reset();
+    ap.paintAlignment(true);
+    try
+    {
+      frame.setClosed(true);
+    } catch (Exception ex)
+    {
+    }
+  }
+
+  public void thresholdCheck_actionPerformed(ActionEvent e)
+  {
+    updateView();
+  }
+
+  public void annotations_actionPerformed(ActionEvent e)
+  {
+    updateView();
+  }
+
+  public void threshold_actionPerformed(ActionEvent e)
+  {
+    updateView();
+  }
+
+  public void thresholdValue_actionPerformed(ActionEvent e)
+  {
+    try
+    {
+      float f = Float.parseFloat(thresholdValue.getText());
+      slider.setValue((int) (f * 1000));
+      updateView();
+    } catch (NumberFormatException ex)
+    {
+    }
+  }
+
+  public void thresholdIsMin_actionPerformed(ActionEvent actionEvent)
+  {
+    updateView();
+  }
+
+  protected void populateThresholdComboBox(JComboBox<String> threshold)
+  {
+    threshold.addItem(MessageManager
+            .getString("label.threshold_feature_no_thereshold"));
+    threshold.addItem(MessageManager
+            .getString("label.threshold_feature_above_thereshold"));
+    threshold.addItem(MessageManager
+            .getString("label.threshold_feature_below_thereshold"));
+  }
+
+  protected void seqAssociated_actionPerformed(ActionEvent arg0,
+          JComboBox<String> annotations, JCheckBox seqAssociated)
+  {
+    adjusting = true;
+    String cursel = (String) annotations.getSelectedItem();
+    boolean isvalid = false, isseqs = seqAssociated.isSelected();
+    annotations.removeAllItems();
+    for (String anitem : getAnnotationItems(seqAssociated.isSelected()))
+    {
+      if (anitem.equals(cursel) || (isseqs && cursel.startsWith(anitem)))
+      {
+        isvalid = true;
+        cursel = anitem;
+      }
+      annotations.addItem(anitem);
+    }
+    adjusting = false;
+    if (isvalid)
+    {
+      annotations.setSelectedItem(cursel);
+    }
+    else
+    {
+      if (annotations.getItemCount() > 0)
+      {
+        annotations.setSelectedIndex(0);
+      }
+    }
+  }
+
+  protected void propagateSeqAssociatedThreshold(boolean allAnnotation,
+          AlignmentAnnotation annotation)
+  {
+    if (annotation.sequenceRef == null || annotation.threshold == null)
+    {
+      return;
+    }
+
+    float thr = annotation.threshold.value;
+    for (int i = 0; i < av.getAlignment().getAlignmentAnnotation().length; i++)
+    {
+      AlignmentAnnotation aa = av.getAlignment().getAlignmentAnnotation()[i];
+      if (aa.label.equals(annotation.label)
+              && (annotation.getCalcId() == null ? aa.getCalcId() == null
+                      : annotation.getCalcId().equals(aa.getCalcId())))
+      {
+        if (aa.threshold == null)
+        {
+          aa.threshold = new GraphLine(annotation.threshold);
+        }
+        else
+        {
+          aa.threshold.value = thr;
+        }
+      }
+    }
+  }
+
+  protected boolean colorAlignmContaining(
+          AlignmentAnnotation currentAnnotation, int selectedThresholdItem)
+  {
+
+    AnnotationColourGradient acg = null;
+    if (currentColours.isSelected())
+    {
+      acg = new AnnotationColourGradient(currentAnnotation,
+              av.getGlobalColourScheme(), selectedThresholdItem);
+    }
+    else
+    {
+      acg = new AnnotationColourGradient(currentAnnotation,
+              minColour.getBackground(), maxColour.getBackground(),
+              selectedThresholdItem);
+    }
+    acg.setSeqAssociated(seqAssociated.isSelected());
+
+    if (currentAnnotation.graphMin == 0f
+            && currentAnnotation.graphMax == 0f)
+    {
+      acg.setPredefinedColours(true);
+    }
+
+    acg.thresholdIsMinMax = thresholdIsMin.isSelected();
+
+    av.setGlobalColourScheme(acg);
+
+    if (av.getAlignment().getGroups() != null)
+    {
+
+      for (SequenceGroup sg : ap.av.getAlignment().getGroups())
+      {
+        if (sg.cs == null)
+        {
+          continue;
+        }
+
+        if (currentColours.isSelected())
+        {
+          sg.cs = new AnnotationColourGradient(currentAnnotation, sg.cs,
+                  selectedThresholdItem);
+          ((AnnotationColourGradient) sg.cs).setSeqAssociated(seqAssociated
+                  .isSelected());
+
+        }
+        else
+        {
+          sg.cs = new AnnotationColourGradient(currentAnnotation,
+                  minColour.getBackground(), maxColour.getBackground(),
+                  selectedThresholdItem);
+          ((AnnotationColourGradient) sg.cs).setSeqAssociated(seqAssociated
+                  .isSelected());
+        }
+
+      }
+    }
+    return false;
+  }
+
+
+  public jalview.datamodel.AlignmentAnnotation getCurrentAnnotation()
+  {
+    return currentAnnotation;
+  }
+
+  public void setCurrentAnnotation(
+          jalview.datamodel.AlignmentAnnotation currentAnnotation)
+  {
+    this.currentAnnotation = currentAnnotation;
+  }
+
+  public abstract void valueChanged(boolean updateAllAnnotation);
+
+  public abstract void updateView();
+
+  public abstract void reset();
+}
index fcb31eb..df7f43c 100644 (file)
  */
 package jalview.gui;
 
-import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
-import jalview.ext.jmol.JalviewJmolBinding;
-import jalview.gui.ViewSelectionMenu.ViewSetProvider;
 import jalview.io.AppletFormatAdapter;
 import jalview.io.JalviewFileChooser;
 import jalview.io.JalviewFileView;
-import jalview.jbgui.GStructureViewer;
 import jalview.schemes.BuriedColourScheme;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.HelixColourScheme;
@@ -42,12 +38,12 @@ import jalview.schemes.StrandColourScheme;
 import jalview.schemes.TaylorColourScheme;
 import jalview.schemes.TurnColourScheme;
 import jalview.schemes.ZappoColourScheme;
+import jalview.structures.models.AAStructureBindingModel;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
-import java.awt.Component;
 import java.awt.Dimension;
 import java.awt.Font;
 import java.awt.Graphics;
@@ -61,7 +57,6 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.Vector;
 
@@ -78,9 +73,7 @@ import javax.swing.event.InternalFrameEvent;
 import javax.swing.event.MenuEvent;
 import javax.swing.event.MenuListener;
 
-public class AppJmol extends GStructureViewer implements Runnable,
-        ViewSetProvider, JalviewStructureDisplayI
-
+public class AppJmol extends StructureViewerBase
 {
   AppJmolBinding jmb;
 
@@ -90,8 +83,6 @@ public class AppJmol extends GStructureViewer implements Runnable,
 
   RenderPanel renderPanel;
 
-  AlignmentPanel ap;
-
   Vector atomsPicked = new Vector();
 
   private boolean addingStructures = false;
@@ -156,9 +147,7 @@ public class AppJmol extends GStructureViewer implements Runnable,
     PDBEntry[] pdbentrys = new PDBEntry[files.length];
     for (int i = 0; i < pdbentrys.length; i++)
     {
-      PDBEntry pdbentry = new PDBEntry();
-      pdbentry.setFile(files[i]);
-      pdbentry.setId(ids[i]);
+      PDBEntry pdbentry = new PDBEntry(files[i], ids[i]);
       pdbentrys[i] = pdbentry;
     }
     // / TODO: check if protocol is needed to be set, and if chains are
@@ -178,7 +167,7 @@ public class AppJmol extends GStructureViewer implements Runnable,
       seqColour.setSelected(false);
       viewerColour.setSelected(true);
     }
-    if (usetoColour)
+    else if (usetoColour)
     {
       useAlignmentPanelForColourbyseq(ap);
       jmb.setColourBySequence(true);
@@ -187,7 +176,7 @@ public class AppJmol extends GStructureViewer implements Runnable,
     }
     this.setBounds(bounds);
     initMenus();
-    viewId = viewid;
+    setViewId(viewid);
     // jalview.gui.Desktop.addInternalFrame(this, "Loading File",
     // bounds.width,bounds.height);
 
@@ -315,9 +304,9 @@ public class AppJmol extends GStructureViewer implements Runnable,
         // TODO : Fix multiple seq to one chain issue here.
         ap.getStructureSelectionManager().setMapping(seq, chains,
                 alreadyMapped, AppletFormatAdapter.FILE);
-        if (ap.seqPanel.seqCanvas.fr != null)
+        if (ap.getSeqPanel().seqCanvas.fr != null)
         {
-          ap.seqPanel.seqCanvas.fr.featuresAdded();
+          ap.getSeqPanel().seqCanvas.fr.featuresAdded();
           ap.paintAlignment(true);
         }
 
@@ -332,15 +321,16 @@ public class AppJmol extends GStructureViewer implements Runnable,
             final AppJmol topJmol = ((AppJmol) frames[i]);
             // JBPNOTE: this looks like a binding routine, rather than a gui
             // routine
-            for (int pe = 0; pe < topJmol.jmb.pdbentry.length; pe++)
+            for (int pe = 0; pe < topJmol.jmb.getPdbCount(); pe++)
             {
-              if (topJmol.jmb.pdbentry[pe].getFile().equals(alreadyMapped))
+              if (topJmol.jmb.getPdbEntry(pe).getFile()
+                      .equals(alreadyMapped))
               {
                 topJmol.jmb.addSequence(pe, seq);
                 topJmol.addAlignmentPanel(ap);
                 // add it to the set used for colouring
                 topJmol.useAlignmentPanelForColourbyseq(ap);
-                topJmol.buildJmolActionMenu();
+                topJmol.buildActionMenu();
                 ap.getStructureSelectionManager()
                         .sequenceColoursChanged(ap);
                 break;
@@ -436,133 +426,6 @@ public class AppJmol extends GStructureViewer implements Runnable,
   }
 
   /**
-   * list of sequenceSet ids associated with the view
-   */
-  ArrayList<String> _aps = new ArrayList();
-
-  public AlignmentPanel[] getAllAlignmentPanels()
-  {
-    AlignmentPanel[] t, list = new AlignmentPanel[0];
-    for (String setid : _aps)
-    {
-      AlignmentPanel[] panels = PaintRefresher.getAssociatedPanels(setid);
-      if (panels != null)
-      {
-        t = new AlignmentPanel[list.length + panels.length];
-        System.arraycopy(list, 0, t, 0, list.length);
-        System.arraycopy(panels, 0, t, list.length, panels.length);
-        list = t;
-      }
-    }
-
-    return list;
-  }
-
-  /**
-   * list of alignment panels to use for superposition
-   */
-  Vector<AlignmentPanel> _alignwith = new Vector<AlignmentPanel>();
-
-  /**
-   * list of alignment panels that are used for colouring structures by aligned
-   * sequences
-   */
-  Vector<AlignmentPanel> _colourwith = new Vector<AlignmentPanel>();
-
-  /**
-   * set the primary alignmentPanel reference and add another alignPanel to the
-   * list of ones to use for colouring and aligning
-   * 
-   * @param nap
-   */
-  public void addAlignmentPanel(AlignmentPanel nap)
-  {
-    if (ap == null)
-    {
-      ap = nap;
-    }
-    if (!_aps.contains(nap.av.getSequenceSetId()))
-    {
-      _aps.add(nap.av.getSequenceSetId());
-    }
-  }
-
-  /**
-   * remove any references held to the given alignment panel
-   * 
-   * @param nap
-   */
-  public void removeAlignmentPanel(AlignmentPanel nap)
-  {
-    try
-    {
-      _alignwith.remove(nap);
-      _colourwith.remove(nap);
-      if (ap == nap)
-      {
-        ap = null;
-        for (AlignmentPanel aps : getAllAlignmentPanels())
-        {
-          if (aps != nap)
-          {
-            ap = aps;
-            break;
-          }
-        }
-      }
-    } catch (Exception ex)
-    {
-    }
-    if (ap != null)
-    {
-      buildJmolActionMenu();
-    }
-  }
-
-  public void useAlignmentPanelForSuperposition(AlignmentPanel nap)
-  {
-    addAlignmentPanel(nap);
-    if (!_alignwith.contains(nap))
-    {
-      _alignwith.add(nap);
-    }
-  }
-
-  public void excludeAlignmentPanelForSuperposition(AlignmentPanel nap)
-  {
-    if (_alignwith.contains(nap))
-    {
-      _alignwith.remove(nap);
-    }
-  }
-
-  public void useAlignmentPanelForColourbyseq(AlignmentPanel nap,
-          boolean enableColourBySeq)
-  {
-    useAlignmentPanelForColourbyseq(nap);
-    jmb.setColourBySequence(enableColourBySeq);
-    seqColour.setSelected(enableColourBySeq);
-    viewerColour.setSelected(!enableColourBySeq);
-  }
-
-  public void useAlignmentPanelForColourbyseq(AlignmentPanel nap)
-  {
-    addAlignmentPanel(nap);
-    if (!_colourwith.contains(nap))
-    {
-      _colourwith.add(nap);
-    }
-  }
-
-  public void excludeAlignmentPanelForColourbyseq(AlignmentPanel nap)
-  {
-    if (_colourwith.contains(nap))
-    {
-      _colourwith.remove(nap);
-    }
-  }
-
-  /**
    * pdb retrieval thread.
    */
   private Thread worker = null;
@@ -632,7 +495,7 @@ public class AppJmol extends GStructureViewer implements Runnable,
     {
       if (frame instanceof AppJmol)
       {
-        if (((AppJmol) frame).isLinkedWith(apanel))
+        if (((StructureViewerBase) frame).isLinkedWith(apanel))
         {
           result.addElement(frame);
         }
@@ -742,7 +605,7 @@ public class AppJmol extends GStructureViewer implements Runnable,
   public void closeViewer()
   {
     jmb.closeViewer();
-    ap = null;
+    setAlignmentPanel(null);
     _aps.clear();
     _alignwith.clear();
     _colourwith.clear();
@@ -768,14 +631,14 @@ public class AppJmol extends GStructureViewer implements Runnable,
       // TODO: replace with reference fetching/transfer code (validate PDBentry
       // as a DBRef?)
       jalview.ws.dbsources.Pdb pdbclient = new jalview.ws.dbsources.Pdb();
-      for (int pi = 0; pi < jmb.pdbentry.length; pi++)
+      for (int pi = 0; pi < jmb.getPdbCount(); pi++)
       {
-        String file = jmb.pdbentry[pi].getFile();
+        String file = jmb.getPdbEntry(pi).getFile();
         if (file == null)
         {
           // retrieve the pdb and store it locally
           AlignmentI pdbseq = null;
-          pdbid = jmb.pdbentry[pi].getId();
+          pdbid = jmb.getPdbEntry(pi).getId();
           long hdl = pdbid.hashCode() - System.currentTimeMillis();
           if (progressBar != null)
           {
@@ -783,7 +646,8 @@ public class AppJmol extends GStructureViewer implements Runnable,
           }
           try
           {
-            pdbseq = pdbclient.getSequenceRecords(pdbid = jmb.pdbentry[pi]
+            pdbseq = pdbclient.getSequenceRecords(pdbid = jmb.getPdbEntry(
+                    pi)
                     .getId());
           } catch (OutOfMemoryError oomerror)
           {
@@ -803,7 +667,7 @@ public class AppJmol extends GStructureViewer implements Runnable,
             // PDBEntry
             file = new File(((PDBEntry) pdbseq.getSequenceAt(0).getPDBId()
                     .elementAt(0)).getFile()).getAbsolutePath();
-            jmb.pdbentry[pi].setFile(file);
+            jmb.getPdbEntry(pi).setFile(file);
 
             files.append(" \"" + Platform.escapeString(file) + "\"");
           }
@@ -898,7 +762,7 @@ public class AppJmol extends GStructureViewer implements Runnable,
       // need to wait around until script has finished
       while (addingStructures ? lastnotify >= jmb.getLoadNotifiesHandled()
               : (jmb.isFinishedInit() && jmb.getPdbFile() != null && jmb
-                      .getPdbFile().length != jmb.pdbentry.length))
+                      .getPdbFile().length != jmb.getPdbCount()))
       {
         try
         {
@@ -977,9 +841,9 @@ public class AppJmol extends GStructureViewer implements Runnable,
     jalview.gui.CutAndPasteTransfer cap = new jalview.gui.CutAndPasteTransfer();
     try
     {
-      for (int pdbe = 0; pdbe < jmb.pdbentry.length; pdbe++)
+      for (int pdbe = 0; pdbe < jmb.getPdbCount(); pdbe++)
       {
-        cap.appendText(jmb.printMapping(jmb.pdbentry[pdbe].getFile()));
+        cap.appendText(jmb.printMapping(jmb.getPdbEntry(pdbe).getFile()));
         cap.appendText("\n");
       }
     } catch (OutOfMemoryError e)
@@ -1065,16 +929,16 @@ public class AppJmol extends GStructureViewer implements Runnable,
     {
       if (!jmb.isLoadingFromArchive())
       {
-        if (_colourwith.size() == 0 && ap != null)
+        if (_colourwith.size() == 0 && getAlignmentPanel() != null)
         {
           // Make the currently displayed alignment panel the associated view
-          _colourwith.add(ap.alignFrame.alignPanel);
+          _colourwith.add(getAlignmentPanel().alignFrame.alignPanel);
         }
       }
       // Set the colour using the current view for the associated alignframe
       for (AlignmentPanel ap : _colourwith)
       {
-        jmb.colourBySequence(ap.av.showSequenceFeatures, ap);
+        jmb.colourBySequence(ap.av.isShowSequenceFeatures(), ap);
       }
     }
   }
@@ -1232,15 +1096,15 @@ public class AppJmol extends GStructureViewer implements Runnable,
                 + "...", 20, currentSize.height / 2);
         StringBuffer sb = new StringBuffer();
         int lines = 0;
-        for (int e = 0; e < jmb.pdbentry.length; e++)
+        for (int e = 0; e < jmb.getPdbCount(); e++)
         {
-          sb.append(jmb.pdbentry[e].getId());
-          if (e < jmb.pdbentry.length - 1)
+          sb.append(jmb.getPdbEntry(e).getId());
+          if (e < jmb.getPdbCount() - 1)
           {
             sb.append(",");
           }
 
-          if (e == jmb.pdbentry.length - 1 || sb.length() > 20)
+          if (e == jmb.getPdbCount() - 1 || sb.length() > 20)
           {
             lines++;
             g.drawString(sb.toString(), 20, currentSize.height / 2 - lines
@@ -1264,17 +1128,6 @@ public class AppJmol extends GStructureViewer implements Runnable,
     }
   }
 
-  String viewId = null;
-
-  public String getViewId()
-  {
-    if (viewId == null)
-    {
-      viewId = System.currentTimeMillis() + "." + this.hashCode();
-    }
-    return viewId;
-  }
-
   public void updateTitleAndMenus()
   {
     if (jmb.fileLoadingError != null && jmb.fileLoadingError.length() > 0)
@@ -1285,7 +1138,7 @@ public class AppJmol extends GStructureViewer implements Runnable,
     setChainMenuItems(jmb.chainNames);
 
     this.setTitle(jmb.getViewerTitle());
-    if (jmb.getPdbFile().length > 1 && jmb.sequence.length > 1)
+    if (jmb.getPdbFile().length > 1 && jmb.getSequence().length > 1)
     {
       viewerActionMenu.setVisible(true);
     }
@@ -1295,27 +1148,6 @@ public class AppJmol extends GStructureViewer implements Runnable,
     }
   }
 
-  protected void buildJmolActionMenu()
-  {
-    if (_alignwith == null)
-    {
-      _alignwith = new Vector<AlignmentPanel>();
-    }
-    if (_alignwith.size() == 0 && ap != null)
-    {
-      _alignwith.add(ap);
-    }
-    ;
-    for (Component c : viewerActionMenu.getMenuComponents())
-    {
-      if (c != alignStructs)
-      {
-        viewerActionMenu.remove((JMenuItem) c);
-      }
-    }
-    final ItemListener handler;
-  }
-
   /*
    * (non-Javadoc)
    * 
@@ -1331,14 +1163,14 @@ public class AppJmol extends GStructureViewer implements Runnable,
 
   private void alignStructs_withAllAlignPanels()
   {
-    if (ap == null)
+    if (getAlignmentPanel() == null)
     {
       return;
     }
     ;
     if (_alignwith.size() == 0)
     {
-      _alignwith.add(ap);
+      _alignwith.add(getAlignmentPanel());
     }
     ;
     try
@@ -1390,42 +1222,19 @@ public class AppJmol extends GStructureViewer implements Runnable,
         return ap;
       }
     }
-    return ap;
+    return getAlignmentPanel();
   }
 
-  /**
-   * 
-   * @param ap2
-   * @return true if this Jmol instance is linked with the given alignPanel
-   */
-  public boolean isLinkedWith(AlignmentPanel ap2)
-  {
-    return _aps.contains(ap2.av.getSequenceSetId());
-  }
-
-  public boolean isUsedforaligment(AlignmentPanel ap2)
-  {
-
-    return (_alignwith != null) && _alignwith.contains(ap2);
-  }
-
-  public boolean isUsedforcolourby(AlignmentPanel ap2)
-  {
-    return (_colourwith != null) && _colourwith.contains(ap2);
-  }
-
-  /**
-   * 
-   * @return TRUE if the view is NOT being coloured by sequence associations.
-   */
-  public boolean isColouredByJmol()
+  @Override
+  public AAStructureBindingModel getBinding()
   {
-    return !jmb.isColourBySequence();
+    return this.jmb;
   }
 
-  public JalviewJmolBinding getBinding()
+  @Override
+  public String getStateInfo()
   {
-    return jmb;
+    return jmb == null ? null : jmb.viewer.getStateInfo();
   }
 
 }
index 7fa300e..b7617ce 100644 (file)
  */
 package jalview.gui;
 
-import java.awt.Container;
-import java.util.BitSet;
-
 import jalview.api.AlignmentViewPanel;
 import jalview.bin.Cache;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
+import jalview.ext.jmol.JalviewJmolBinding;
 import jalview.structure.StructureSelectionManager;
 
+import java.awt.Container;
+import java.util.BitSet;
+
 import org.jmol.api.JmolAppConsoleInterface;
 import org.jmol.api.JmolViewer;
 import org.jmol.popup.JmolPopup;
 import org.openscience.jmol.app.jmolpanel.AppConsole;
 
-public class AppJmolBinding extends jalview.ext.jmol.JalviewJmolBinding
+public class AppJmolBinding extends JalviewJmolBinding
 {
-
-  /**
-   * 
-   */
   private AppJmol appJmolWindow;
 
+  private FeatureRenderer fr = null;
+
   public AppJmolBinding(AppJmol appJmol, StructureSelectionManager sSm,
           PDBEntry[] pdbentry, SequenceI[][] sequenceIs, String[][] chains,
           String protocol)
@@ -50,19 +49,17 @@ public class AppJmolBinding extends jalview.ext.jmol.JalviewJmolBinding
     appJmolWindow = appJmol;
   }
 
-  FeatureRenderer fr = null;
-
   @Override
-  public jalview.api.FeatureRenderer getFeatureRenderer(
+  public FeatureRenderer getFeatureRenderer(
           AlignmentViewPanel alignment)
   {
-    AlignmentPanel ap = (alignment == null) ? appJmolWindow.ap
+    AlignmentPanel ap = (alignment == null) ? appJmolWindow.getAlignmentPanel()
             : (AlignmentPanel) alignment;
-    if (ap.av.showSequenceFeatures)
+    if (ap.av.isShowSequenceFeatures())
     {
       if (fr == null)
       {
-        fr = ap.cloneFeatureRenderer();
+        fr = (jalview.gui.FeatureRenderer) ap.cloneFeatureRenderer();
       }
       else
       {
@@ -74,7 +71,7 @@ public class AppJmolBinding extends jalview.ext.jmol.JalviewJmolBinding
   }
 
   @Override
-  public jalview.api.SequenceRenderer getSequenceRenderer(
+  public SequenceRenderer getSequenceRenderer(
           AlignmentViewPanel alignment)
   {
     return new SequenceRenderer(((AlignmentPanel) alignment).av);
@@ -130,10 +127,12 @@ public class AppJmolBinding extends jalview.ext.jmol.JalviewJmolBinding
     AlignmentPanel ap = (AlignmentPanel) source, topap;
     // ignore events from panels not used to colour this view
     if (!appJmolWindow.isUsedforcolourby(ap))
+    {
       return;
+    }
     if (!isLoadingFromArchive())
     {
-      colourBySequence(ap.av.getShowSequenceFeatures(), ap);
+      colourBySequence(ap.av.isShowSequenceFeatures(), ap);
     }
   }
 
@@ -153,7 +152,6 @@ public class AppJmolBinding extends jalview.ext.jmol.JalviewJmolBinding
   public void newJmolPopup(boolean translateLocale, String menuName,
           boolean asPopup)
   {
-
     jmolpopup = new JmolPopup();
     jmolpopup.initialize(viewer, translateLocale, menuName, asPopup);
   }
@@ -175,25 +173,6 @@ public class AppJmolBinding extends jalview.ext.jmol.JalviewJmolBinding
     appJmolWindow.showConsole(b);
   }
 
-  /**
-   * add the given sequences to the mapping scope for the given pdb file handle
-   * 
-   * @param pdbFile
-   *          - pdbFile identifier
-   * @param seq
-   *          - set of sequences it can be mapped to
-   */
-  public void addSequenceForStructFile(String pdbFile, SequenceI[] seq)
-  {
-    for (int pe = 0; pe < pdbentry.length; pe++)
-    {
-      if (pdbentry[pe].getFile().equals(pdbFile))
-      {
-        addSequence(pe, seq);
-      }
-    }
-  }
-
   @Override
   protected JmolAppConsoleInterface createJmolConsole(JmolViewer viewer2,
           Container consolePanel, String buttonsToShow)
@@ -205,20 +184,7 @@ public class AppJmolBinding extends jalview.ext.jmol.JalviewJmolBinding
   protected void releaseUIResources()
   {
     appJmolWindow = null;
-    if (console != null)
-    {
-      try
-      {
-        console.setVisible(false);
-      } catch (Error e)
-      {
-      } catch (Exception x)
-      {
-      }
-      ;
-      console = null;
-    }
-
+    closeConsole();
   }
 
   @Override
@@ -227,8 +193,6 @@ public class AppJmolBinding extends jalview.ext.jmol.JalviewJmolBinding
     if (svl instanceof SeqPanel)
     {
       appJmolWindow.removeAlignmentPanel(((SeqPanel) svl).ap);
-
     }
-    ;
   }
 }
index 513659f..384e1cc 100644 (file)
  */
 package jalview.gui;
 
-import jalview.api.SequenceStructureBinding;
-import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
-import jalview.gui.ViewSelectionMenu.ViewSetProvider;
+import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
 import jalview.io.AppletFormatAdapter;
 import jalview.io.JalviewFileChooser;
 import jalview.io.JalviewFileView;
-import jalview.jbgui.GStructureViewer;
 import jalview.schemes.BuriedColourScheme;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.HelixColourScheme;
@@ -42,11 +39,11 @@ import jalview.schemes.StrandColourScheme;
 import jalview.schemes.TaylorColourScheme;
 import jalview.schemes.TurnColourScheme;
 import jalview.schemes.ZappoColourScheme;
+import jalview.structures.models.AAStructureBindingModel;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
 import jalview.ws.dbsources.Pdb;
 
-import java.awt.Component;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.ItemEvent;
@@ -78,34 +75,14 @@ import javax.swing.event.MenuListener;
  * @author jprocter
  *
  */
-public class ChimeraViewFrame extends GStructureViewer implements Runnable,
-        ViewSetProvider, JalviewStructureDisplayI
-
+public class ChimeraViewFrame extends StructureViewerBase
 {
-  private JalviewChimeraBindingModel jmb;
-
-  /*
-   * list of sequenceSet ids associated with the view
-   */
-  private ArrayList<String> _aps = new ArrayList<String>();
-
-  /*
-   * list of alignment panels to use for superposition
-   */
-  private Vector<AlignmentPanel> _alignwith = new Vector<AlignmentPanel>();
-
-  /*
-   * list of alignment panels that are used for colouring structures by aligned
-   * sequences
-   */
-  private Vector<AlignmentPanel> _colourwith = new Vector<AlignmentPanel>();
+  private JalviewChimeraBinding jmb;
 
   private boolean allChainsSelected = false;
 
   private boolean alignAddedStructures = false;
 
-  AlignmentPanel ap;
-
   /*
    * state flag for PDB retrieval thread
    */
@@ -115,13 +92,18 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable,
 
   private IProgressIndicator progressBar = null;
 
-  private String viewId = null;
-
   /*
    * pdb retrieval thread.
    */
   private Thread worker = null;
 
+  /*
+   * Path to Chimera session file. This is set when an open Jalview/Chimera
+   * session is saved, or on restore from a Jalview project (if it holds the
+   * filename of any saved Chimera sessions).
+   */
+  private String chimeraSessionFile = null;
+
   /**
    * Initialise menu options.
    */
@@ -244,9 +226,9 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable,
         // TODO : Fix multiple seq to one chain issue here.
         ap.getStructureSelectionManager().setMapping(seq, chains,
                 alreadyMapped, AppletFormatAdapter.FILE);
-        if (ap.seqPanel.seqCanvas.fr != null)
+        if (ap.getSeqPanel().seqCanvas.fr != null)
         {
-          ap.seqPanel.seqCanvas.fr.featuresAdded();
+          ap.getSeqPanel().seqCanvas.fr.featuresAdded();
           ap.paintAlignment(true);
         }
 
@@ -261,15 +243,17 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable,
             final ChimeraViewFrame topView = ((ChimeraViewFrame) frame);
             // JBPNOTE: this looks like a binding routine, rather than a gui
             // routine
-            for (int pe = 0; pe < topView.jmb.pdbentry.length; pe++)
+            for (int pe = 0; pe < topView.jmb.getPdbCount(); pe++)
             {
-              if (topView.jmb.pdbentry[pe].getFile().equals(alreadyMapped))
+              if (topView.jmb.getPdbEntry(pe).getFile()
+                      .equals(
+                      alreadyMapped))
               {
                 topView.jmb.addSequence(pe, seq);
                 topView.addAlignmentPanel(ap);
                 // add it to the set used for colouring
                 topView.useAlignmentPanelForColourbyseq(ap);
-                topView.buildChimeraActionMenu();
+                topView.buildActionMenu();
                 ap.getStructureSelectionManager()
                         .sequenceColoursChanged(ap);
                 break;
@@ -328,12 +312,11 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable,
     jmb.setColourBySequence(true);
     setSize(400, 400); // probably should be a configurable/dynamic default here
     initMenus();
-    worker = null;
-    {
-      addingStructures = false;
-      worker = new Thread(this);
-      worker.start();
-    }
+
+    addingStructures = false;
+    worker = new Thread(this);
+    worker.start();
+
     this.addInternalFrameListener(new InternalFrameAdapter()
     {
       public void internalFrameClosing(InternalFrameEvent internalFrameEvent)
@@ -359,114 +342,37 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable,
     openNewChimera(ap, pe, seqs);
   }
 
-  public AlignmentPanel[] getAllAlignmentPanels()
-  {
-    AlignmentPanel[] t, list = new AlignmentPanel[0];
-    for (String setid : _aps)
-    {
-      AlignmentPanel[] panels = PaintRefresher.getAssociatedPanels(setid);
-      if (panels != null)
-      {
-        t = new AlignmentPanel[list.length + panels.length];
-        System.arraycopy(list, 0, t, 0, list.length);
-        System.arraycopy(panels, 0, t, list.length, panels.length);
-        list = t;
-      }
-    }
-
-    return list;
-  }
-
   /**
-   * set the primary alignmentPanel reference and add another alignPanel to the
-   * list of ones to use for colouring and aligning
+   * Create a new viewer from saved session state data including Chimera session
+   * file.
    * 
-   * @param nap
-   */
-  public void addAlignmentPanel(AlignmentPanel nap)
-  {
-    if (ap == null)
-    {
-      ap = nap;
-    }
-    if (!_aps.contains(nap.av.getSequenceSetId()))
-    {
-      _aps.add(nap.av.getSequenceSetId());
-    }
-  }
-
-  /**
-   * remove any references held to the given alignment panel
+   * @param chimeraSession
    * 
-   * @param nap
+   * @param alignPanel
+   * @param pdbArray
+   * @param seqsArray
+   * @param colourByChimera
+   * @param colourBySequence
    */
-  public void removeAlignmentPanel(AlignmentPanel nap)
-  {
-    try
-    {
-      _alignwith.remove(nap);
-      _colourwith.remove(nap);
-      if (ap == nap)
-      {
-        ap = null;
-        for (AlignmentPanel aps : getAllAlignmentPanels())
-        {
-          if (aps != nap)
-          {
-            ap = aps;
-            break;
-          }
-        }
-      }
-    } catch (Exception ex)
-    {
-    }
-    if (ap != null)
-    {
-      buildChimeraActionMenu();
-    }
-  }
-
-  public void useAlignmentPanelForSuperposition(AlignmentPanel nap)
+  public ChimeraViewFrame(String chimeraSession, AlignmentPanel alignPanel,
+          PDBEntry[] pdbArray,
+          SequenceI[][] seqsArray, boolean colourByChimera,
+          boolean colourBySequence)
   {
-    addAlignmentPanel(nap);
-    if (!_alignwith.contains(nap))
-    {
-      _alignwith.add(nap);
-    }
-  }
-
-  public void excludeAlignmentPanelForSuperposition(AlignmentPanel nap)
-  {
-    if (_alignwith.contains(nap))
-    {
-      _alignwith.remove(nap);
-    }
-  }
-
-  public void useAlignmentPanelForColourbyseq(AlignmentPanel nap,
-          boolean enableColourBySeq)
-  {
-    useAlignmentPanelForColourbyseq(nap);
-    jmb.setColourBySequence(enableColourBySeq);
-    seqColour.setSelected(enableColourBySeq);
-    viewerColour.setSelected(!enableColourBySeq);
-  }
-
-  public void useAlignmentPanelForColourbyseq(AlignmentPanel nap)
-  {
-    addAlignmentPanel(nap);
-    if (!_colourwith.contains(nap))
+    super();
+    this.chimeraSessionFile = chimeraSession;
+    openNewChimera(alignPanel, pdbArray, seqsArray);
+    if (colourByChimera)
     {
-      _colourwith.add(nap);
+      jmb.setColourBySequence(false);
+      seqColour.setSelected(false);
+      viewerColour.setSelected(true);
     }
-  }
-
-  public void excludeAlignmentPanelForColourbyseq(AlignmentPanel nap)
-  {
-    if (_colourwith.contains(nap))
+    else if (colourBySequence)
     {
-      _colourwith.remove(nap);
+      jmb.setColourBySequence(true);
+      seqColour.setSelected(true);
+      viewerColour.setSelected(false);
     }
   }
 
@@ -535,7 +441,7 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable,
     {
       if (frame instanceof ChimeraViewFrame)
       {
-        if (((ChimeraViewFrame) frame).isLinkedWith(apanel))
+        if (((StructureViewerBase) frame).isLinkedWith(apanel))
         {
           result.add((ChimeraViewFrame) frame);
         }
@@ -544,18 +450,31 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable,
     return result;
   }
 
-  void initChimera(String command)
+  /**
+   * Launch Chimera. If we have a chimera session file name, send Chimera the
+   * command to open its saved session file.
+   */
+  void initChimera()
   {
     jmb.setFinishedInit(false);
-    // TODO: consider waiting until the structure/view is fully loaded before
-    // displaying
-    jalview.gui.Desktop.addInternalFrame(this, jmb.getViewerTitle(true),
+    jalview.gui.Desktop.addInternalFrame(this, jmb.getViewerTitle("Chimera", true),
             getBounds().width, getBounds().height);
-    if (command == null)
+
+    /*
+     * Pass an empty 'command' to launch Chimera
+     */
+    jmb.evalStateCommand("", false);
+
+    if (this.chimeraSessionFile != null)
     {
-      command = "";
+      boolean opened = jmb.openSession(chimeraSessionFile);
+      if (!opened)
+      {
+        System.err
+                .println("An error occurred opening Chimera session file "
+                        + chimeraSessionFile);
+      }
     }
-    jmb.evalStateCommand(command, false);
     jmb.setFinishedInit(true);
   }
 
@@ -633,14 +552,14 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable,
     {
       String prompt = MessageManager
               .formatMessage("label.confirm_close_chimera", new Object[]
-              { jmb.getViewerTitle(false) });
+              { jmb.getViewerTitle("Chimera", false) });
       prompt = JvSwingUtils.wrapTooltip(true, prompt);
       int confirm = JOptionPane.showConfirmDialog(this, prompt,
               MessageManager.getString("label.close_viewer"),
               JOptionPane.YES_NO_OPTION);
       jmb.closeViewer(confirm == JOptionPane.YES_OPTION);
     }
-    ap = null;
+    setAlignmentPanel(null);
     _aps.clear();
     _alignwith.clear();
     _colourwith.clear();
@@ -667,10 +586,10 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable,
       String[] curfiles = jmb.getPdbFile(); // files currently in viewer
       // TODO: replace with reference fetching/transfer code (validate PDBentry
       // as a DBRef?)
-      for (int pi = 0; pi < jmb.pdbentry.length; pi++)
+      for (int pi = 0; pi < jmb.getPdbCount(); pi++)
       {
         String file = null;
-        thePdbEntry = jmb.pdbentry[pi];
+        thePdbEntry = jmb.getPdbEntry(pi);
         if (thePdbEntry.getFile() == null)
         {
           /*
@@ -736,7 +655,7 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable,
       {
         try
         {
-          initChimera("");
+          initChimera();
         } catch (Exception ex)
         {
           Cache.log.error("Couldn't open Chimera viewer!", ex);
@@ -752,7 +671,7 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable,
           {
             int pos = filePDBpos.get(num).intValue();
             jmb.openFile(pe);
-            jmb.addSequence(pos, jmb.sequence[pos]);
+            jmb.addSequence(pos, jmb.getSequence()[pos]);
             File fl = new File(pe.getFile());
             String protocol = AppletFormatAdapter.URL;
             try
@@ -766,7 +685,8 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable,
             }
             // Explicitly map to the filename used by Chimera ;
             // TODO: use pe.getId() instead of pe.getFile() ?
-            jmb.ssm.setMapping(jmb.sequence[pos], null, pe.getFile(),
+            jmb.getSsm().setMapping(jmb.getSequence()[pos], null,
+                    pe.getFile(),
                     protocol);
           } catch (OutOfMemoryError oomerror)
           {
@@ -919,9 +839,9 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable,
     jalview.gui.CutAndPasteTransfer cap = new jalview.gui.CutAndPasteTransfer();
     try
     {
-      for (int pdbe = 0; pdbe < jmb.pdbentry.length; pdbe++)
+      for (int pdbe = 0; pdbe < jmb.getPdbCount(); pdbe++)
       {
-        cap.appendText(jmb.printMapping(jmb.pdbentry[pdbe].getFile()));
+        cap.appendText(jmb.printMapping(jmb.getPdbEntry(pdbe).getFile()));
         cap.appendText("\n");
       }
     } catch (OutOfMemoryError e)
@@ -975,16 +895,16 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable,
     {
       if (!jmb.isLoadingFromArchive())
       {
-        if (_colourwith.size() == 0 && ap != null)
+        if (_colourwith.size() == 0 && getAlignmentPanel() != null)
         {
           // Make the currently displayed alignment panel the associated view
-          _colourwith.add(ap.alignFrame.alignPanel);
+          _colourwith.add(getAlignmentPanel().alignFrame.alignPanel);
         }
       }
       // Set the colour using the current view for the associated alignframe
       for (AlignmentPanel ap : _colourwith)
       {
-        jmb.colourBySequence(ap.av.showSequenceFeatures, ap);
+        jmb.colourBySequence(ap.av.isShowSequenceFeatures(), ap);
       }
     }
   }
@@ -1089,15 +1009,6 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable,
     }
   }
 
-  public String getViewId()
-  {
-    if (viewId == null)
-    {
-      viewId = System.currentTimeMillis() + "." + this.hashCode();
-    }
-    return viewId;
-  }
-
   public void updateTitleAndMenus()
   {
     if (jmb.fileLoadingError != null && jmb.fileLoadingError.length() > 0)
@@ -1105,10 +1016,10 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable,
       repaint();
       return;
     }
-    setChainMenuItems(jmb.chainNames);
+    setChainMenuItems(jmb.getChainNames());
 
-    this.setTitle(jmb.getViewerTitle(true));
-    if (jmb.getPdbFile().length > 1 && jmb.sequence.length > 1)
+    this.setTitle(jmb.getViewerTitle("Chimera", true));
+    if (jmb.getPdbFile().length > 1 && jmb.getSequence().length > 1)
     {
       viewerActionMenu.setVisible(true);
     }
@@ -1118,26 +1029,6 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable,
     }
   }
 
-  protected void buildChimeraActionMenu()
-  {
-    if (_alignwith == null)
-    {
-      _alignwith = new Vector<AlignmentPanel>();
-    }
-    if (_alignwith.size() == 0 && ap != null)
-    {
-      _alignwith.add(ap);
-    }
-    ;
-    for (Component c : viewerActionMenu.getMenuComponents())
-    {
-      if (c != alignStructs)
-      {
-        viewerActionMenu.remove((JMenuItem) c);
-      }
-    }
-  }
-
   /*
    * (non-Javadoc)
    * 
@@ -1153,14 +1044,14 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable,
 
   private void alignStructs_withAllAlignPanels()
   {
-    if (ap == null)
+    if (getAlignmentPanel() == null)
     {
       return;
     }
     ;
     if (_alignwith.size() == 0)
     {
-      _alignwith.add(ap);
+      _alignwith.add(getAlignmentPanel());
     }
     ;
     try
@@ -1212,42 +1103,40 @@ public class ChimeraViewFrame extends GStructureViewer implements Runnable,
         return ap;
       }
     }
-    return ap;
-  }
-
-  /**
-   * 
-   * @param ap2
-   * @return true if this Chimera instance is linked with the given alignPanel
-   */
-  public boolean isLinkedWith(AlignmentPanel ap2)
-  {
-    return _aps.contains(ap2.av.getSequenceSetId());
+    return getAlignmentPanel();
   }
 
-  public boolean isUsedforaligment(AlignmentPanel ap2)
-  {
-
-    return (_alignwith != null) && _alignwith.contains(ap2);
-  }
-
-  public boolean isUsedforcolourby(AlignmentPanel ap2)
+  @Override
+  public AAStructureBindingModel getBinding()
   {
-    return (_colourwith != null) && _colourwith.contains(ap2);
+    return jmb;
   }
 
   /**
+   * Ask Chimera to save its session to the designated file path. Returns true
+   * if successful, else false.
    * 
-   * @return TRUE if the view is NOT being coloured by sequence associations.
+   * @param filepath
+   * @see getStateInfo
    */
-  public boolean isColouredByChimera()
+  public boolean saveSession(String filepath)
   {
-    return !jmb.isColourBySequence();
+    boolean result = jmb.saveSession(filepath);
+    if (result)
+    {
+      this.chimeraSessionFile = filepath;
+    }
+    return result;
   }
 
-  public SequenceStructureBinding getBinding()
+  /**
+   * Returns the file path of the Chimera session file the last time it was
+   * saved. If it was never saved, returns an empty string. There is no
+   * guarantee that the Chimera session has not changed since it was saved.
+   */
+  @Override
+  public String getStateInfo()
   {
-    return jmb;
+    return this.chimeraSessionFile;
   }
-
 }
index b8f629a..70c8355 100644 (file)
@@ -26,6 +26,7 @@ import jalview.io.FormatAdapter;
 import jalview.io.IdentifyFile;
 import jalview.io.JalviewFileChooser;
 import jalview.io.JalviewFileView;
+import jalview.jbgui.GStructureViewer;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.ImageMaker;
 import jalview.util.MessageManager;
@@ -113,6 +114,8 @@ public class Desktop extends jalview.jbgui.GDesktop implements
    */
   private BlogReader jvnews = null;
 
+  private File projectFile;
+
   /**
    * @param listener
    * @see jalview.gui.JalviewChangeSupport#addJalviewPropertyChangeListener(java.beans.PropertyChangeListener)
@@ -1462,6 +1465,11 @@ public class Desktop extends jalview.jbgui.GDesktop implements
     {
       final Desktop me = this;
       final java.io.File choice = chooser.getSelectedFile();
+      setProjectFile(choice);
+
+      // TODO or move inside the new Thread?
+      saveChimeraSessions(choice.getAbsolutePath());
+
       new Thread(new Runnable()
       {
         public void run()
@@ -1475,7 +1483,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
           // TODO prevent user from messing with the Desktop whilst we're saving
           try
           {
-            new Jalview2XML().SaveState(choice);
+            new Jalview2XML().saveState(choice);
           } catch (OutOfMemoryError oom)
           {
             new OOMWarning("Whilst saving current state to "
@@ -1498,6 +1506,42 @@ public class Desktop extends jalview.jbgui.GDesktop implements
   }
 
   /**
+   * Request any open, linked Chimera sessions to save their state.
+   * 
+   * @param jalviewProjectFilename
+   *          the filename of the Jalview project; Chimera session files should
+   *          be given distinct, but obviously related, names.
+   */
+  public void saveChimeraSessions(String jalviewProjectFilename)
+  {
+    int i = 0;
+    for (JInternalFrame frame : getAllFrames())
+    {
+      if (frame instanceof ChimeraViewFrame)
+      {
+        /*
+         * Construct a filename for the Chimera session by append _chimera<n>.py
+         * to the Jalview project file name.
+         */
+        String chimeraPath = jalviewProjectFilename + "_chimera_" + i
+                + ".py";
+        ((ChimeraViewFrame) frame).saveSession(chimeraPath);
+        i++;
+      }
+    }
+  }
+
+  private void setProjectFile(File choice)
+  {
+    this.projectFile = choice;
+  }
+
+  public File getProjectFile()
+  {
+    return this.projectFile;
+  }
+
+  /**
    * DOCUMENT ME!
    * 
    * @param e
@@ -1517,9 +1561,10 @@ public class Desktop extends jalview.jbgui.GDesktop implements
 
     if (value == JalviewFileChooser.APPROVE_OPTION)
     {
-      final String choice = chooser.getSelectedFile().getAbsolutePath();
-      jalview.bin.Cache.setProperty("LAST_DIRECTORY", chooser
-              .getSelectedFile().getParent());
+      final File selectedFile = chooser.getSelectedFile();
+      setProjectFile(selectedFile);
+      final String choice = selectedFile.getAbsolutePath();
+      jalview.bin.Cache.setProperty("LAST_DIRECTORY", selectedFile.getParent());
       new Thread(new Runnable()
       {
         public void run()
@@ -1528,7 +1573,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
                   choice.hashCode());
           try
           {
-            new Jalview2XML().LoadJalviewAlign(choice);
+            new Jalview2XML().loadJalviewAlign(choice);
           } catch (OutOfMemoryError oom)
           {
             new OOMWarning("Whilst loading project from " + choice, oom);
@@ -2312,7 +2357,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
     return afs;
   }
 
-  public AppJmol[] getJmols()
+  public GStructureViewer[] getJmols()
   {
     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
 
@@ -2328,7 +2373,7 @@ public class Desktop extends jalview.jbgui.GDesktop implements
       {
         if (frames[i] instanceof AppJmol)
         {
-          AppJmol af = (AppJmol) frames[i];
+          GStructureViewer af = (GStructureViewer) frames[i];
           avp.addElement(af);
         }
       }
@@ -2340,10 +2385,10 @@ public class Desktop extends jalview.jbgui.GDesktop implements
     {
       return null;
     }
-    AppJmol afs[] = new AppJmol[avp.size()];
+    GStructureViewer afs[] = new GStructureViewer[avp.size()];
     for (int i = 0, j = avp.size(); i < j; i++)
     {
-      afs[i] = (AppJmol) avp.elementAt(i);
+      afs[i] = (GStructureViewer) avp.elementAt(i);
     }
     avp.clear();
     return afs;
index 8ab7c85..d1d1b6d 100644 (file)
@@ -109,10 +109,10 @@ public class FeatureColourChooser extends JalviewDialog
       }
     });
 
-    float mm[] = ((float[][]) fr.minmax.get(type))[0];
+    float mm[] = ((float[][]) fr.getMinMax().get(type))[0];
     min = mm[0];
     max = mm[1];
-    oldcs = fr.featureColours.get(type);
+    oldcs = fr.getFeatureColours().get(type);
     if (oldcs instanceof GraduatedColor)
     {
       if (((GraduatedColor) oldcs).isAutoScale())
@@ -470,7 +470,7 @@ public class FeatureColourChooser extends JalviewDialog
       maxColour.setForeground(oldmaxColour);
       minColour.setForeground(oldminColour);
     }
-    fr.featureColours.put(type, acg);
+    fr.setColour(type, acg);
     cs = acg;
     ap.paintAlignment(false);
   }
@@ -495,7 +495,7 @@ public class FeatureColourChooser extends JalviewDialog
 
   void reset()
   {
-    fr.featureColours.put(type, oldcs);
+    fr.setColour(type, oldcs);
     ap.paintAlignment(false);
     cs = null;
   }
index d2d9c9c..3ce831e 100644 (file)
  */
 package jalview.gui;
 
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-
-import java.awt.*;
-import java.awt.event.*;
-import java.awt.image.*;
-import java.beans.PropertyChangeListener;
-import java.beans.PropertyChangeSupport;
-
-import javax.swing.*;
-
-import jalview.datamodel.*;
+import jalview.datamodel.SearchResults;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
 import jalview.schemes.GraduatedColor;
 import jalview.util.MessageManager;
 
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import javax.swing.JColorChooser;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSpinner;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+
 /**
  * DOCUMENT ME!
  * 
  * @author $author$
  * @version $Revision$
  */
-public class FeatureRenderer implements jalview.api.FeatureRenderer
+public class FeatureRenderer extends jalview.renderer.seqfeatures.FeatureRenderer implements jalview.api.FeatureRenderer
 {
-  AlignmentPanel ap;
-
-  AlignViewport av;
-
   Color resBoxColour;
 
-  /**
-   * global transparency for feature
-   */
-  float transparency = 1.0f;
-
-  FontMetrics fm;
-
-  int charOffset;
-
-  Map featureColours = new ConcurrentHashMap();
-
-  // A higher level for grouping features of a
-  // particular type
-  Map featureGroups = new ConcurrentHashMap();
-
-  // This is actually an Integer held in the hashtable,
-  // Retrieved using the key feature type
-  Object currentColour;
-
-  String[] renderOrder;
-
-  PropertyChangeSupport changeSupport = new PropertyChangeSupport(this);
-
-  Vector allfeatures;
+  AlignmentPanel ap;
 
   /**
    * Creates a new FeatureRenderer object.
@@ -82,901 +69,13 @@ public class FeatureRenderer implements jalview.api.FeatureRenderer
    */
   public FeatureRenderer(AlignmentPanel ap)
   {
+    super();
     this.ap = ap;
     this.av = ap.av;
-    if (ap != null && ap.seqPanel != null && ap.seqPanel.seqCanvas != null
-            && ap.seqPanel.seqCanvas.fr != null)
-    {
-      transferSettings(ap.seqPanel.seqCanvas.fr);
-    }
-  }
-
-  public class FeatureRendererSettings implements Cloneable
-  {
-    String[] renderOrder;
-
-    Map featureGroups;
-
-    Map featureColours;
-
-    float transparency;
-
-    Map featureOrder;
-
-    public FeatureRendererSettings(String[] renderOrder,
-            Hashtable featureGroups, Hashtable featureColours,
-            float transparency, Hashtable featureOrder)
-    {
-      super();
-      this.renderOrder = renderOrder;
-      this.featureGroups = featureGroups;
-      this.featureColours = featureColours;
-      this.transparency = transparency;
-      this.featureOrder = featureOrder;
-    }
-
-    /**
-     * create an independent instance of the feature renderer settings
-     * 
-     * @param fr
-     */
-    public FeatureRendererSettings(FeatureRenderer fr)
-    {
-      renderOrder = null;
-      featureGroups = new ConcurrentHashMap();
-      featureColours = new ConcurrentHashMap();
-      featureOrder = new ConcurrentHashMap();
-      if (fr.renderOrder != null)
-      {
-        this.renderOrder = new String[fr.renderOrder.length];
-        System.arraycopy(fr.renderOrder, 0, renderOrder, 0,
-                fr.renderOrder.length);
-      }
-      if (fr.featureGroups != null)
-      {
-        this.featureGroups = new ConcurrentHashMap(fr.featureGroups);
-      }
-      if (fr.featureColours != null)
-      {
-        this.featureColours = new ConcurrentHashMap(fr.featureColours);
-      }
-      Iterator en = fr.featureColours.keySet().iterator();
-      while (en.hasNext())
-      {
-        Object next = en.next();
-        Object val = featureColours.get(next);
-        if (val instanceof GraduatedColor)
-        {
-          featureColours
-                  .put(next, new GraduatedColor((GraduatedColor) val));
-        }
-      }
-      this.transparency = fr.transparency;
-      if (fr.featureOrder != null)
-      {
-        this.featureOrder = new ConcurrentHashMap(fr.featureOrder);
-      }
-    }
-  }
-
-  public FeatureRendererSettings getSettings()
-  {
-    return new FeatureRendererSettings(this);
-  }
-
-  public void transferSettings(FeatureRendererSettings fr)
-  {
-    this.renderOrder = fr.renderOrder;
-    this.featureGroups = fr.featureGroups;
-    this.featureColours = fr.featureColours;
-    this.transparency = fr.transparency;
-    this.featureOrder = fr.featureOrder;
-  }
-
-  /**
-   * update from another feature renderer
-   * 
-   * @param fr
-   *          settings to copy
-   */
-  public void transferSettings(FeatureRenderer fr)
-  {
-    FeatureRendererSettings frs = new FeatureRendererSettings(fr);
-    this.renderOrder = frs.renderOrder;
-    this.featureGroups = frs.featureGroups;
-    this.featureColours = frs.featureColours;
-    this.transparency = frs.transparency;
-    this.featureOrder = frs.featureOrder;
-    if (av != null && av != fr.av)
-    {
-      // copy over the displayed feature settings
-      if (fr.av != null)
-      {
-        if (fr.av.featuresDisplayed != null)
-        {
-          // update display settings
-          if (av.featuresDisplayed == null)
-          {
-            av.featuresDisplayed = new Hashtable(fr.av.featuresDisplayed);
-          }
-          else
-          {
-            av.featuresDisplayed.clear();
-            Enumeration en = fr.av.featuresDisplayed.keys();
-            while (en.hasMoreElements())
-            {
-              av.featuresDisplayed.put(en.nextElement(), Boolean.TRUE);
-            }
-
-          }
-        }
-      }
-    }
-  }
-
-  BufferedImage offscreenImage;
-
-  boolean offscreenRender = false;
-
-  public Color findFeatureColour(Color initialCol, SequenceI seq, int res)
-  {
-    return new Color(findFeatureColour(initialCol.getRGB(), seq, res));
-  }
-
-  /**
-   * This is used by the Molecule Viewer and Overview to get the accurate
-   * colourof the rendered sequence
-   */
-  public synchronized int findFeatureColour(int initialCol, SequenceI seq,
-          int column)
-  {
-    if (!av.showSequenceFeatures)
-    {
-      return initialCol;
-    }
-
-    if (seq != lastSeq)
-    {
-      lastSeq = seq;
-      sequenceFeatures = lastSeq.getDatasetSequence().getSequenceFeatures();
-      if (sequenceFeatures != null)
-      {
-        sfSize = sequenceFeatures.length;
-      }
-    }
-
-    if (sequenceFeatures != lastSeq.getDatasetSequence()
-            .getSequenceFeatures())
-    {
-      sequenceFeatures = lastSeq.getDatasetSequence().getSequenceFeatures();
-      if (sequenceFeatures != null)
-      {
-        sfSize = sequenceFeatures.length;
-      }
-    }
-
-    if (sequenceFeatures == null || sfSize == 0)
-    {
-      return initialCol;
-    }
-
-    if (jalview.util.Comparison.isGap(lastSeq.getCharAt(column)))
-    {
-      return Color.white.getRGB();
-    }
-
-    // Only bother making an offscreen image if transparency is applied
-    if (transparency != 1.0f && offscreenImage == null)
-    {
-      offscreenImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
-    }
-
-    currentColour = null;
-    // TODO: non-threadsafe - each rendering thread needs its own instance of
-    // the feature renderer - or this should be synchronized.
-    offscreenRender = true;
-
-    if (offscreenImage != null)
-    {
-      offscreenImage.setRGB(0, 0, initialCol);
-      drawSequence(offscreenImage.getGraphics(), lastSeq, column, column, 0);
-
-      return offscreenImage.getRGB(0, 0);
-    }
-    else
-    {
-      drawSequence(null, lastSeq, lastSeq.findPosition(column), -1, -1);
-
-      if (currentColour == null)
-      {
-        return initialCol;
-      }
-      else
-      {
-        return ((Integer) currentColour).intValue();
-      }
-    }
-
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param g
-   *          DOCUMENT ME!
-   * @param seq
-   *          DOCUMENT ME!
-   * @param sg
-   *          DOCUMENT ME!
-   * @param start
-   *          DOCUMENT ME!
-   * @param end
-   *          DOCUMENT ME!
-   * @param x1
-   *          DOCUMENT ME!
-   * @param y1
-   *          DOCUMENT ME!
-   * @param width
-   *          DOCUMENT ME!
-   * @param height
-   *          DOCUMENT ME!
-   */
-  // String type;
-  // SequenceFeature sf;
-  SequenceI lastSeq;
-
-  SequenceFeature[] sequenceFeatures;
-
-  int sfSize, sfindex, spos, epos;
-
-  /**
-   * show scores as heights
-   */
-  protected boolean varyHeight = false;
-
-  synchronized public void drawSequence(Graphics g, SequenceI seq,
-          int start, int end, int y1)
-  {
-
-    if (seq.getDatasetSequence().getSequenceFeatures() == null
-            || seq.getDatasetSequence().getSequenceFeatures().length == 0)
-    {
-      return;
-    }
-
-    if (g != null)
-    {
-      fm = g.getFontMetrics();
-    }
-
-    if (av.featuresDisplayed == null || renderOrder == null
-            || newFeatureAdded)
-    {
-      findAllFeatures();
-      if (av.featuresDisplayed.size() < 1)
-      {
-        return;
-      }
-
-      sequenceFeatures = seq.getDatasetSequence().getSequenceFeatures();
-    }
-
-    if (lastSeq == null
-            || seq != lastSeq
-            || seq.getDatasetSequence().getSequenceFeatures() != sequenceFeatures)
-    {
-      lastSeq = seq;
-      sequenceFeatures = seq.getDatasetSequence().getSequenceFeatures();
-    }
-
-    if (transparency != 1 && g != null)
-    {
-      Graphics2D g2 = (Graphics2D) g;
-      g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
-              transparency));
-    }
-
-    if (!offscreenRender)
-    {
-      spos = lastSeq.findPosition(start);
-      epos = lastSeq.findPosition(end);
-    }
-
-    sfSize = sequenceFeatures.length;
-    String type;
-    for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
-    {
-      type = renderOrder[renderIndex];
-
-      if (type == null || !av.featuresDisplayed.containsKey(type))
-      {
-        continue;
-      }
-
-      // loop through all features in sequence to find
-      // current feature to render
-      for (sfindex = 0; sfindex < sfSize; sfindex++)
-      {
-        if (!sequenceFeatures[sfindex].type.equals(type))
-        {
-          continue;
-        }
-
-        if (featureGroups != null
-                && sequenceFeatures[sfindex].featureGroup != null
-                && sequenceFeatures[sfindex].featureGroup.length() != 0
-                && featureGroups
-                        .containsKey(sequenceFeatures[sfindex].featureGroup)
-                && !((Boolean) featureGroups
-                        .get(sequenceFeatures[sfindex].featureGroup))
-                        .booleanValue())
-        {
-          continue;
-        }
-
-        if (!offscreenRender
-                && (sequenceFeatures[sfindex].getBegin() > epos || sequenceFeatures[sfindex]
-                        .getEnd() < spos))
-        {
-          continue;
-        }
-
-        if (offscreenRender && offscreenImage == null)
-        {
-          if (sequenceFeatures[sfindex].begin <= start
-                  && sequenceFeatures[sfindex].end >= start)
-          {
-            // this is passed out to the overview and other sequence renderers
-            // (e.g. molecule viewer) to get displayed colour for rendered
-            // sequence
-            currentColour = new Integer(
-                    getColour(sequenceFeatures[sfindex]).getRGB());
-            // used to be retreived from av.featuresDisplayed
-            // currentColour = av.featuresDisplayed
-            // .get(sequenceFeatures[sfindex].type);
-
-          }
-        }
-        else if (sequenceFeatures[sfindex].type.equals("disulfide bond"))
-        {
-
-          renderFeature(g, seq,
-                  seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
-                  seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
-                  getColour(sequenceFeatures[sfindex])
-                  // new Color(((Integer) av.featuresDisplayed
-                  // .get(sequenceFeatures[sfindex].type)).intValue())
-                  , start, end, y1);
-          renderFeature(g, seq,
-                  seq.findIndex(sequenceFeatures[sfindex].end) - 1,
-                  seq.findIndex(sequenceFeatures[sfindex].end) - 1,
-                  getColour(sequenceFeatures[sfindex])
-                  // new Color(((Integer) av.featuresDisplayed
-                  // .get(sequenceFeatures[sfindex].type)).intValue())
-                  , start, end, y1);
-
-        }
-        else if (showFeature(sequenceFeatures[sfindex]))
-        {
-          if (av.showSeqFeaturesHeight
-                  && sequenceFeatures[sfindex].score != Float.NaN)
-          {
-            renderScoreFeature(g, seq,
-                    seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
-                    seq.findIndex(sequenceFeatures[sfindex].end) - 1,
-                    getColour(sequenceFeatures[sfindex]), start, end, y1,
-                    normaliseScore(sequenceFeatures[sfindex]));
-          }
-          else
-          {
-            renderFeature(g, seq,
-                    seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
-                    seq.findIndex(sequenceFeatures[sfindex].end) - 1,
-                    getColour(sequenceFeatures[sfindex]), start, end, y1);
-          }
-        }
-
-      }
-
-    }
-
-    if (transparency != 1.0f && g != null)
-    {
-      Graphics2D g2 = (Graphics2D) g;
-      g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
-              1.0f));
-    }
-  }
-
-  Hashtable minmax = new Hashtable();
-
-  /**
-   * normalise a score against the max/min bounds for the feature type.
-   * 
-   * @param sequenceFeature
-   * @return byte[] { signed, normalised signed (-127 to 127) or unsigned
-   *         (0-255) value.
-   */
-  private final byte[] normaliseScore(SequenceFeature sequenceFeature)
-  {
-    float[] mm = ((float[][]) minmax.get(sequenceFeature.type))[0];
-    final byte[] r = new byte[]
-    { 0, (byte) 255 };
-    if (mm != null)
-    {
-      if (r[0] != 0 || mm[0] < 0.0)
-      {
-        r[0] = 1;
-        r[1] = (byte) ((int) 128.0 + 127.0 * (sequenceFeature.score / mm[1]));
-      }
-      else
-      {
-        r[1] = (byte) ((int) 255.0 * (sequenceFeature.score / mm[1]));
-      }
-    }
-    return r;
-  }
-
-  char s;
-
-  int i;
-
-  void renderFeature(Graphics g, SequenceI seq, int fstart, int fend,
-          Color featureColour, int start, int end, int y1)
-  {
-
-    if (((fstart <= end) && (fend >= start)))
-    {
-      if (fstart < start)
-      { // fix for if the feature we have starts before the sequence start,
-        fstart = start; // but the feature end is still valid!!
-      }
-
-      if (fend >= end)
-      {
-        fend = end;
-      }
-      int pady = (y1 + av.charHeight) - av.charHeight / 5;
-      for (i = fstart; i <= fend; i++)
-      {
-        s = seq.getCharAt(i);
-
-        if (jalview.util.Comparison.isGap(s))
-        {
-          continue;
-        }
-
-        g.setColor(featureColour);
-
-        g.fillRect((i - start) * av.charWidth, y1, av.charWidth,
-                av.charHeight);
-
-        if (offscreenRender || !av.validCharWidth)
-        {
-          continue;
-        }
-
-        g.setColor(Color.white);
-        charOffset = (av.charWidth - fm.charWidth(s)) / 2;
-        g.drawString(String.valueOf(s), charOffset
-                + (av.charWidth * (i - start)), pady);
-
-      }
-    }
-  }
-
-  void renderScoreFeature(Graphics g, SequenceI seq, int fstart, int fend,
-          Color featureColour, int start, int end, int y1, byte[] bs)
-  {
-
-    if (((fstart <= end) && (fend >= start)))
-    {
-      if (fstart < start)
-      { // fix for if the feature we have starts before the sequence start,
-        fstart = start; // but the feature end is still valid!!
-      }
-
-      if (fend >= end)
-      {
-        fend = end;
-      }
-      int pady = (y1 + av.charHeight) - av.charHeight / 5;
-      int ystrt = 0, yend = av.charHeight;
-      if (bs[0] != 0)
-      {
-        // signed - zero is always middle of residue line.
-        if (bs[1] < 128)
-        {
-          yend = av.charHeight * (128 - bs[1]) / 512;
-          ystrt = av.charHeight - yend / 2;
-        }
-        else
-        {
-          ystrt = av.charHeight / 2;
-          yend = av.charHeight * (bs[1] - 128) / 512;
-        }
-      }
-      else
-      {
-        yend = av.charHeight * bs[1] / 255;
-        ystrt = av.charHeight - yend;
-
-      }
-      for (i = fstart; i <= fend; i++)
-      {
-        s = seq.getCharAt(i);
-
-        if (jalview.util.Comparison.isGap(s))
-        {
-          continue;
-        }
-
-        g.setColor(featureColour);
-        int x = (i - start) * av.charWidth;
-        g.drawRect(x, y1, av.charWidth, av.charHeight);
-        g.fillRect(x, y1 + ystrt, av.charWidth, yend);
-
-        if (offscreenRender || !av.validCharWidth)
-        {
-          continue;
-        }
-
-        g.setColor(Color.black);
-        charOffset = (av.charWidth - fm.charWidth(s)) / 2;
-        g.drawString(String.valueOf(s), charOffset
-                + (av.charWidth * (i - start)), pady);
-
-      }
-    }
-  }
-
-  boolean newFeatureAdded = false;
-
-  /**
-   * Called when alignment in associated view has new/modified features to
-   * discover and display.
-   * 
-   */
-  public void featuresAdded()
-  {
-    lastSeq = null;
-    findAllFeatures();
-  }
-
-  boolean findingFeatures = false;
-
-  /**
-   * search the alignment for all new features, give them a colour and display
-   * them. Then fires a PropertyChangeEvent on the changeSupport object.
-   * 
-   */
-  void findAllFeatures()
-  {
-    synchronized (firing)
-    {
-      if (firing.equals(Boolean.FALSE))
-      {
-        firing = Boolean.TRUE;
-        findAllFeatures(true); // add all new features as visible
-        changeSupport.firePropertyChange("changeSupport", null, null);
-        firing = Boolean.FALSE;
-      }
-    }
-  }
-
-  /**
-   * Searches alignment for all features and updates colours
-   * 
-   * @param newMadeVisible
-   *          if true newly added feature types will be rendered immediatly
-   */
-  synchronized void findAllFeatures(boolean newMadeVisible)
-  {
-    newFeatureAdded = false;
-
-    if (findingFeatures)
-    {
-      newFeatureAdded = true;
-      return;
-    }
-
-    findingFeatures = true;
-
-    if (av.featuresDisplayed == null)
-    {
-      av.featuresDisplayed = new Hashtable();
-    }
-
-    allfeatures = new Vector();
-    Vector oldfeatures = new Vector();
-    if (renderOrder != null)
-    {
-      for (int i = 0; i < renderOrder.length; i++)
-      {
-        if (renderOrder[i] != null)
-        {
-          oldfeatures.addElement(renderOrder[i]);
-        }
-      }
-    }
-    if (minmax == null)
-    {
-      minmax = new Hashtable();
-    }
-    AlignmentI alignment = av.getAlignment();
-    for (int i = 0; i < alignment.getHeight(); i++)
-    {
-      SequenceFeature[] features = alignment.getSequenceAt(i)
-              .getDatasetSequence().getSequenceFeatures();
-
-      if (features == null)
-      {
-        continue;
-      }
-
-      int index = 0;
-      while (index < features.length)
-      {
-        if (!av.featuresDisplayed.containsKey(features[index].getType()))
-        {
-
-          if (featureGroups.containsKey(features[index].getType()))
-          {
-            boolean visible = ((Boolean) featureGroups
-                    .get(features[index].featureGroup)).booleanValue();
-
-            if (!visible)
-            {
-              index++;
-              continue;
-            }
-          }
-
-          if (!(features[index].begin == 0 && features[index].end == 0))
-          {
-            // If beginning and end are 0, the feature is for the whole sequence
-            // and we don't want to render the feature in the normal way
-
-            if (newMadeVisible
-                    && !oldfeatures.contains(features[index].getType()))
-            {
-              // this is a new feature type on the alignment. Mark it for
-              // display.
-              av.featuresDisplayed.put(features[index].getType(),
-                      new Integer(getColour(features[index].getType())
-                              .getRGB()));
-              setOrder(features[index].getType(), 0);
-            }
-          }
-        }
-        if (!allfeatures.contains(features[index].getType()))
-        {
-          allfeatures.addElement(features[index].getType());
-        }
-        if (features[index].score != Float.NaN)
-        {
-          int nonpos = features[index].getBegin() >= 1 ? 0 : 1;
-          float[][] mm = (float[][]) minmax.get(features[index].getType());
-          if (mm == null)
-          {
-            mm = new float[][]
-            { null, null };
-            minmax.put(features[index].getType(), mm);
-          }
-          if (mm[nonpos] == null)
-          {
-            mm[nonpos] = new float[]
-            { features[index].score, features[index].score };
-
-          }
-          else
-          {
-            if (mm[nonpos][0] > features[index].score)
-            {
-              mm[nonpos][0] = features[index].score;
-            }
-            if (mm[nonpos][1] < features[index].score)
-            {
-              mm[nonpos][1] = features[index].score;
-            }
-          }
-        }
-        index++;
-      }
-    }
-    updateRenderOrder(allfeatures);
-    findingFeatures = false;
-  }
-
-  protected Boolean firing = Boolean.FALSE;
-
-  /**
-   * replaces the current renderOrder with the unordered features in
-   * allfeatures. The ordering of any types in both renderOrder and allfeatures
-   * is preserved, and all new feature types are rendered on top of the existing
-   * types, in the order given by getOrder or the order given in allFeatures.
-   * Note. this operates directly on the featureOrder hash for efficiency. TODO:
-   * eliminate the float storage for computing/recalling the persistent ordering
-   * New Cability: updates min/max for colourscheme range if its dynamic
-   * 
-   * @param allFeatures
-   */
-  private void updateRenderOrder(Vector allFeatures)
-  {
-    Vector allfeatures = new Vector(allFeatures);
-    String[] oldRender = renderOrder;
-    renderOrder = new String[allfeatures.size()];
-    Object mmrange, fc = null;
-    boolean initOrders = (featureOrder == null);
-    int opos = 0;
-    if (oldRender != null && oldRender.length > 0)
-    {
-      for (int j = 0; j < oldRender.length; j++)
-      {
-        if (oldRender[j] != null)
-        {
-          if (initOrders)
-          {
-            setOrder(oldRender[j], (1 - (1 + (float) j)
-                    / (float) oldRender.length));
-          }
-          if (allfeatures.contains(oldRender[j]))
-          {
-            renderOrder[opos++] = oldRender[j]; // existing features always
-            // appear below new features
-            allfeatures.removeElement(oldRender[j]);
-            if (minmax != null)
-            {
-              mmrange = minmax.get(oldRender[j]);
-              if (mmrange != null)
-              {
-                fc = featureColours.get(oldRender[j]);
-                if (fc != null && fc instanceof GraduatedColor
-                        && ((GraduatedColor) fc).isAutoScale())
-                {
-                  ((GraduatedColor) fc).updateBounds(
-                          ((float[][]) mmrange)[0][0],
-                          ((float[][]) mmrange)[0][1]);
-                }
-              }
-            }
-          }
-        }
-      }
-    }
-    if (allfeatures.size() == 0)
-    {
-      // no new features - leave order unchanged.
-      return;
-    }
-    int i = allfeatures.size() - 1;
-    int iSize = i;
-    boolean sort = false;
-    String[] newf = new String[allfeatures.size()];
-    float[] sortOrder = new float[allfeatures.size()];
-    Enumeration en = allfeatures.elements();
-    // sort remaining elements
-    while (en.hasMoreElements())
-    {
-      newf[i] = en.nextElement().toString();
-      if (minmax != null)
-      {
-        // update from new features minmax if necessary
-        mmrange = minmax.get(newf[i]);
-        if (mmrange != null)
-        {
-          fc = featureColours.get(newf[i]);
-          if (fc != null && fc instanceof GraduatedColor
-                  && ((GraduatedColor) fc).isAutoScale())
-          {
-            ((GraduatedColor) fc).updateBounds(((float[][]) mmrange)[0][0],
-                    ((float[][]) mmrange)[0][1]);
-          }
-        }
-      }
-      if (initOrders || !featureOrder.containsKey(newf[i]))
-      {
-        int denom = initOrders ? allfeatures.size() : featureOrder.size();
-        // new unordered feature - compute persistent ordering at head of
-        // existing features.
-        setOrder(newf[i], i / (float) denom);
-      }
-      // set order from newly found feature from persisted ordering.
-      sortOrder[i] = 2 - ((Float) featureOrder.get(newf[i])).floatValue();
-      if (i < iSize)
-      {
-        // only sort if we need to
-        sort = sort || sortOrder[i] > sortOrder[i + 1];
-      }
-      i--;
-    }
-    if (iSize > 1 && sort)
-    {
-      jalview.util.QuickSort.sort(sortOrder, newf);
-    }
-    sortOrder = null;
-    System.arraycopy(newf, 0, renderOrder, opos, newf.length);
-  }
-
-  /**
-   * get a feature style object for the given type string. Creates a
-   * java.awt.Color for a featureType with no existing colourscheme. TODO:
-   * replace return type with object implementing standard abstract colour/style
-   * interface
-   * 
-   * @param featureType
-   * @return java.awt.Color or GraduatedColor
-   */
-  public Object getFeatureStyle(String featureType)
-  {
-    Object fc = featureColours.get(featureType);
-    if (fc == null)
-    {
-      jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme();
-      Color col = ucs.createColourFromName(featureType);
-      featureColours.put(featureType, fc = col);
-    }
-    return fc;
-  }
-
-  /**
-   * return a nominal colour for this feature
-   * 
-   * @param featureType
-   * @return standard color, or maximum colour for graduated colourscheme
-   */
-  public Color getColour(String featureType)
-  {
-    Object fc = getFeatureStyle(featureType);
-
-    if (fc instanceof Color)
-    {
-      return (Color) fc;
-    }
-    else
-    {
-      if (fc instanceof GraduatedColor)
-      {
-        return ((GraduatedColor) fc).getMaxColor();
-      }
-    }
-    throw new Error(MessageManager.formatMessage("error.implementation_error_unrecognised_render_object_for_features_type", new String[]{fc.getClass().toString(),featureType}));
-  }
-
-  /**
-   * calculate the render colour for a specific feature using current feature
-   * settings.
-   * 
-   * @param feature
-   * @return render colour for the given feature
-   */
-  public Color getColour(SequenceFeature feature)
-  {
-    Object fc = getFeatureStyle(feature.getType());
-    if (fc instanceof Color)
-    {
-      return (Color) fc;
-    }
-    else
-    {
-      if (fc instanceof GraduatedColor)
-      {
-        return ((GraduatedColor) fc).findColor(feature);
-      }
-    }
-    throw new Error(MessageManager.formatMessage("error.implementation_error_unrecognised_render_object_for_features_type", new String[]{fc.getClass().toString(),feature.getType()}));
-  }
-
-  private boolean showFeature(SequenceFeature sequenceFeature)
-  {
-    Object fc = getFeatureStyle(sequenceFeature.type);
-    if (fc instanceof GraduatedColor)
+    if (ap != null && ap.getSeqPanel() != null && ap.getSeqPanel().seqCanvas != null
+            && ap.getSeqPanel().seqCanvas.fr != null)
     {
-      return ((GraduatedColor) fc).isColored(sequenceFeature);
-    }
-    else
-    {
-      return true;
+      transferSettings(ap.getSeqPanel().seqCanvas.fr);
     }
   }
 
@@ -1095,7 +194,7 @@ public class FeatureRenderer implements jalview.api.FeatureRenderer
             highlight.addResult(sequences[0], features[index].getBegin(),
                     features[index].getEnd());
 
-            ap.seqPanel.seqCanvas.highlightSearchResults(highlight);
+            ap.getSeqPanel().seqCanvas.highlightSearchResults(highlight);
 
           }
           Object col = getFeatureStyle(name.getText());
@@ -1235,7 +334,9 @@ public class FeatureRenderer implements jalview.api.FeatureRenderer
       lastDescriptionAdded = description.getText().replaceAll("\n", " ");
       // TODO: determine if the null feature group is valid
       if (lastFeatureGroupAdded.length() < 1)
+      {
         lastFeatureGroupAdded = null;
+      }
     }
 
     if (!newFeatures)
@@ -1253,7 +354,7 @@ public class FeatureRenderer implements jalview.api.FeatureRenderer
         sf.description = lastDescriptionAdded;
 
         setColour(sf.type, fcol);
-        av.featuresDisplayed.put(sf.type, getColour(sf.type));
+        getFeaturesDisplayed().setVisible(sf.type);
 
         try
         {
@@ -1274,27 +375,19 @@ public class FeatureRenderer implements jalview.api.FeatureRenderer
         for (int i = 0; i < sequences.length; i++)
         {
           features[i].type = lastFeatureAdded;
-          if (lastFeatureGroupAdded != null)
-            features[i].featureGroup = lastFeatureGroupAdded;
+          // fix for JAL-1538 - always set feature group here
+          features[i].featureGroup = lastFeatureGroupAdded;
           features[i].description = lastDescriptionAdded;
           sequences[i].addSequenceFeature(features[i]);
           ffile.parseDescriptionHTML(features[i], false);
         }
 
-        if (av.featuresDisplayed == null)
-        {
-          av.featuresDisplayed = new Hashtable();
-        }
-
         if (lastFeatureGroupAdded != null)
         {
-          if (featureGroups == null)
-            featureGroups = new Hashtable();
-          featureGroups.put(lastFeatureGroupAdded, new Boolean(true));
+          setGroupVisibility(lastFeatureGroupAdded, true);
         }
         setColour(lastFeatureAdded, fcol);
-        av.featuresDisplayed.put(lastFeatureAdded,
-                getColour(lastFeatureAdded));
+        setVisible(lastFeatureAdded);
 
         findAllFeatures(false);
 
@@ -1313,6 +406,7 @@ public class FeatureRenderer implements jalview.api.FeatureRenderer
     return true;
   }
 
+
   /**
    * update the amend feature button dependent on the given style
    * 
@@ -1340,145 +434,4 @@ public class FeatureRenderer implements jalview.api.FeatureRenderer
       // colour.setForeground(colour.getBackground());
     }
   }
-
-  public void setColour(String featureType, Object col)
-  {
-    // overwrite
-    // Color _col = (col instanceof Color) ? ((Color) col) : (col instanceof
-    // GraduatedColor) ? ((GraduatedColor) col).getMaxColor() : null;
-    // Object c = featureColours.get(featureType);
-    // if (c == null || c instanceof Color || (c instanceof GraduatedColor &&
-    // !((GraduatedColor)c).getMaxColor().equals(_col)))
-    {
-      featureColours.put(featureType, col);
-    }
-  }
-
-  public void setTransparency(float value)
-  {
-    transparency = value;
-  }
-
-  public float getTransparency()
-  {
-    return transparency;
-  }
-
-  /**
-   * Replace current ordering with new ordering
-   * 
-   * @param data
-   *          { String(Type), Colour(Type), Boolean(Displayed) }
-   */
-  public void setFeaturePriority(Object[][] data)
-  {
-    setFeaturePriority(data, true);
-  }
-
-  /**
-   * 
-   * @param data
-   *          { String(Type), Colour(Type), Boolean(Displayed) }
-   * @param visibleNew
-   *          when true current featureDisplay list will be cleared
-   */
-  public void setFeaturePriority(Object[][] data, boolean visibleNew)
-  {
-    if (visibleNew)
-    {
-      if (av.featuresDisplayed != null)
-      {
-        av.featuresDisplayed.clear();
-      }
-      else
-      {
-        av.featuresDisplayed = new Hashtable();
-      }
-    }
-    if (data == null)
-    {
-      return;
-    }
-
-    // The feature table will display high priority
-    // features at the top, but theses are the ones
-    // we need to render last, so invert the data
-    renderOrder = new String[data.length];
-
-    if (data.length > 0)
-    {
-      for (int i = 0; i < data.length; i++)
-      {
-        String type = data[i][0].toString();
-        setColour(type, data[i][1]); // todo : typesafety - feature color
-        // interface object
-        if (((Boolean) data[i][2]).booleanValue())
-        {
-          av.featuresDisplayed.put(type, new Integer(getColour(type)
-                  .getRGB()));
-        }
-
-        renderOrder[data.length - i - 1] = type;
-      }
-    }
-
-  }
-
-  Map featureOrder = null;
-
-  /**
-   * analogous to colour - store a normalized ordering for all feature types in
-   * this rendering context.
-   * 
-   * @param type
-   *          Feature type string
-   * @param position
-   *          normalized priority - 0 means always appears on top, 1 means
-   *          always last.
-   */
-  public float setOrder(String type, float position)
-  {
-    if (featureOrder == null)
-    {
-      featureOrder = new Hashtable();
-    }
-    featureOrder.put(type, new Float(position));
-    return position;
-  }
-
-  /**
-   * get the global priority (0 (top) to 1 (bottom))
-   * 
-   * @param type
-   * @return [0,1] or -1 for a type without a priority
-   */
-  public float getOrder(String type)
-  {
-    if (featureOrder != null)
-    {
-      if (featureOrder.containsKey(type))
-      {
-        return ((Float) featureOrder.get(type)).floatValue();
-      }
-    }
-    return -1;
-  }
-
-  /**
-   * @param listener
-   * @see java.beans.PropertyChangeSupport#addPropertyChangeListener(java.beans.PropertyChangeListener)
-   */
-  public void addPropertyChangeListener(PropertyChangeListener listener)
-  {
-    changeSupport.addPropertyChangeListener(listener);
-  }
-
-  /**
-   * @param listener
-   * @see java.beans.PropertyChangeSupport#removePropertyChangeListener(java.beans.PropertyChangeListener)
-   */
-  public void removePropertyChangeListener(PropertyChangeListener listener)
-  {
-    changeSupport.removePropertyChangeListener(listener);
-  }
 }
index 2210148..3475fe3 100644 (file)
  */
 package jalview.gui;
 
-import jalview.analysis.AlignmentSorter;
 import jalview.bin.Cache;
-import jalview.commands.OrderCommand;
-import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceFeature;
-import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.gui.Help.HelpId;
 import jalview.io.JalviewFileChooser;
@@ -56,10 +52,10 @@ import java.io.FileOutputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 import java.util.Vector;
 
 import javax.help.HelpSetException;
@@ -107,6 +103,8 @@ public class FeatureSettings extends JPanel
 
   Object[][] originalData;
 
+  private float originalTransparency;
+
   final JInternalFrame frame;
 
   JScrollPane scrollPane = new JScrollPane();
@@ -123,8 +121,8 @@ public class FeatureSettings extends JPanel
   {
     this.af = af;
     fr = af.getFeatureRenderer();
-
-    transparency.setMaximum(100 - (int) (fr.transparency * 100));
+    // allow transparency to be recovered
+    transparency.setMaximum(100 - (int) ((originalTransparency=fr.getTransparency()) * 100));
 
     try
     {
@@ -165,8 +163,8 @@ public class FeatureSettings extends JPanel
         if (SwingUtilities.isRightMouseButton(evt))
         {
           popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0),
-                  table.getValueAt(selectedRow, 1), fr.minmax, evt.getX(),
-                  evt.getY());
+                  table.getValueAt(selectedRow, 1), fr.getMinMax(),
+                  evt.getX(), evt.getY());
         }
         else if (evt.getClickCount() == 2)
         {
@@ -185,7 +183,8 @@ public class FeatureSettings extends JPanel
         if (evt.isPopupTrigger())
         {
           popupSort(selectedRow, (String) table.getValueAt(selectedRow, 0),
-                  table.getValueAt(selectedRow, 1), fr.minmax, evt.getX(),
+                  table.getValueAt(selectedRow, 1), fr.getMinMax(),
+                  evt.getX(),
                   evt.getY());
         }
       }
@@ -222,8 +221,7 @@ public class FeatureSettings extends JPanel
     dassourceBrowser = new DasSourceBrowser(this);
     dasSettingsPane.add(dassourceBrowser, BorderLayout.CENTER);
 
-    if (af.getViewport().featuresDisplayed == null
-            || fr.renderOrder == null)
+    if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
     {
       fr.findAllFeatures(true); // display everything!
     }
@@ -288,7 +286,7 @@ public class FeatureSettings extends JPanel
 
       public void actionPerformed(ActionEvent e)
       {
-        me.sortByScore(new String[]
+        me.af.avc.sortAlignmentByFeatureScore(new String[]
         { type });
       }
 
@@ -300,7 +298,7 @@ public class FeatureSettings extends JPanel
 
       public void actionPerformed(ActionEvent e)
       {
-        me.sortByDens(new String[]
+        me.af.avc.sortAlignmentByFeatureDensity(new String[]
         { type });
       }
 
@@ -424,10 +422,6 @@ public class FeatureSettings extends JPanel
 
   synchronized public void setTableData()
   {
-    if (fr.featureGroups == null)
-    {
-      fr.featureGroups = new Hashtable();
-    }
     Vector allFeatures = new Vector();
     Vector allGroups = new Vector();
     SequenceFeature[] tmpfeatures;
@@ -458,10 +452,7 @@ public class FeatureSettings extends JPanel
           if (!allGroups.contains(group))
           {
             allGroups.addElement(group);
-            if (group != null)
-            {
-              checkGroupState(group);
-            }
+            checkGroupState(group);
           }
         }
 
@@ -479,21 +470,14 @@ public class FeatureSettings extends JPanel
   }
 
   /**
+   * Synchronise gui group list and check visibility of group
    * 
    * @param group
-   * @return true if group has been seen before and is already added to set.
+   * @return true if group is visible
    */
   private boolean checkGroupState(String group)
   {
-    boolean visible;
-    if (fr.featureGroups.containsKey(group))
-    {
-      visible = ((Boolean) fr.featureGroups.get(group)).booleanValue();
-    }
-    else
-    {
-      visible = true; // new group is always made visible
-    }
+    boolean visible = fr.checkGroupVisibility(group, true);
 
     if (groupPanel == null)
     {
@@ -514,10 +498,8 @@ public class FeatureSettings extends JPanel
     if (alreadyAdded)
     {
 
-      return true;
+      return visible;
     }
-
-    fr.featureGroups.put(group, new Boolean(visible));
     final String grp = group;
     final JCheckBox check = new JCheckBox(group, visible);
     check.setFont(new Font("Serif", Font.BOLD, 12));
@@ -525,9 +507,8 @@ public class FeatureSettings extends JPanel
     {
       public void itemStateChanged(ItemEvent evt)
       {
-        fr.featureGroups.put(check.getText(),
-                new Boolean(check.isSelected()));
-        af.alignPanel.seqPanel.seqCanvas.repaint();
+        fr.setGroupVisibility(check.getText(), check.isSelected());
+        af.alignPanel.getSeqPanel().seqCanvas.repaint();
         if (af.alignPanel.overviewPanel != null)
         {
           af.alignPanel.overviewPanel.updateOverviewImage();
@@ -538,7 +519,7 @@ public class FeatureSettings extends JPanel
       }
     });
     groupPanel.add(check);
-    return false;
+    return visible;
   }
 
   boolean resettingTable = false;
@@ -582,13 +563,8 @@ public class FeatureSettings extends JPanel
           continue;
         }
 
-        if (group == null || fr.featureGroups.get(group) == null
-                || ((Boolean) fr.featureGroups.get(group)).booleanValue())
+        if (group == null || checkGroupState(group))
         {
-          if (group != null)
-          {
-            checkGroupState(group);
-          }
           type = tmpfeatures[index].getType();
           if (!visibleChecks.contains(type))
           {
@@ -623,19 +599,20 @@ public class FeatureSettings extends JPanel
     Object[][] data = new Object[fSize][3];
     int dataIndex = 0;
 
-    if (fr.renderOrder != null)
+    if (fr.hasRenderOrder())
     {
       if (!handlingUpdate)
-       {
+      {
         fr.findAllFeatures(groupChanged != null); // prod to update
+        // colourschemes. but don't
+        // affect display
+        // First add the checks in the previous render order,
+        // in case the window has been closed and reopened
       }
-      // colourschemes. but don't
-      // affect display
-      // First add the checks in the previous render order,
-      // in case the window has been closed and reopened
-      for (int ro = fr.renderOrder.length - 1; ro > -1; ro--)
+      List<String> frl = fr.getRenderOrder();
+      for (int ro = frl.size() - 1; ro > -1; ro--)
       {
-        type = fr.renderOrder[ro];
+        type = frl.get(ro);
 
         if (!visibleChecks.contains(type))
         {
@@ -644,8 +621,8 @@ public class FeatureSettings extends JPanel
 
         data[dataIndex][0] = type;
         data[dataIndex][1] = fr.getFeatureStyle(type);
-        data[dataIndex][2] = new Boolean(
-                af.getViewport().featuresDisplayed.containsKey(type));
+        data[dataIndex][2] = new Boolean(af.getViewport()
+                .getFeaturesDisplayed().isVisible(type));
         dataIndex++;
         visibleChecks.removeElement(type);
       }
@@ -663,7 +640,7 @@ public class FeatureSettings extends JPanel
       if (data[dataIndex][1] == null)
       {
         // "Colour has been updated in another view!!"
-        fr.renderOrder = null;
+        fr.clearRenderOrder();
         return;
       }
 
@@ -685,8 +662,8 @@ public class FeatureSettings extends JPanel
 
     if (groupPanel != null)
     {
-      groupPanel.setLayout(new GridLayout(fr.featureGroups.size() / 4 + 1,
-              4));
+      groupPanel.setLayout(new GridLayout(
+              fr.getFeatureGroupsSize() / 4 + 1, 4));
 
       groupPanel.validate();
       bigPanel.add(groupPanel, BorderLayout.NORTH);
@@ -843,9 +820,10 @@ public class FeatureSettings extends JPanel
         PrintWriter out = new PrintWriter(new OutputStreamWriter(
                 new FileOutputStream(choice), "UTF-8"));
 
-        Iterator e = fr.featureColours.keySet().iterator();
-        float[] sortOrder = new float[fr.featureColours.size()];
-        String[] sortTypes = new String[fr.featureColours.size()];
+        Set fr_colours = fr.getAllFeatureColours();
+        Iterator e = fr_colours.iterator();
+        float[] sortOrder = new float[fr_colours.size()];
+        String[] sortTypes = new String[fr_colours.size()];
         int i = 0;
         while (e.hasNext())
         {
@@ -1068,7 +1046,7 @@ public class FeatureSettings extends JPanel
     {
       public void actionPerformed(ActionEvent e)
       {
-        sortByScore(null);
+        af.avc.sortAlignmentByFeatureScore(null);
       }
     });
     sortByDens.setFont(JvSwingUtils.getLabelFont());
@@ -1078,7 +1056,22 @@ public class FeatureSettings extends JPanel
     {
       public void actionPerformed(ActionEvent e)
       {
-        sortByDens(null);
+        af.avc.sortAlignmentByFeatureDensity(null);
+      }
+    });
+    help.setFont(JvSwingUtils.getLabelFont());
+    help.setText(MessageManager.getString("action.help"));
+    help.addActionListener(new ActionListener()
+    {
+      public void actionPerformed(ActionEvent e)
+      {
+        try
+        {
+          Help.showHelpWindow(HelpId.SequenceFeatureSettings);
+        } catch (HelpSetException e1)
+        {
+          e1.printStackTrace();
+        }
       }
     });
     help.setFont(JvSwingUtils.getLabelFont());
@@ -1102,6 +1095,7 @@ public class FeatureSettings extends JPanel
     {
       public void actionPerformed(ActionEvent e)
       {
+        fr.setTransparency(originalTransparency);
         updateFeatureRenderer(originalData);
         close();
       }
@@ -1198,131 +1192,6 @@ public class FeatureSettings extends JPanel
     settingsPane.add(buttonPanel, java.awt.BorderLayout.SOUTH);
   }
 
-  protected void sortByDens(String[] typ)
-  {
-    sortBy(typ, "Sort by Density", AlignmentSorter.FEATURE_DENSITY);
-  }
-
-  protected void sortBy(String[] typ, String methodText, final String method)
-  {
-    if (typ == null)
-    {
-      typ = getDisplayedFeatureTypes();
-    }
-    String gps[] = null;
-    gps = getDisplayedFeatureGroups();
-    if (typ != null)
-    {
-      ArrayList types = new ArrayList();
-      for (int i = 0; i < typ.length; i++)
-      {
-        if (typ[i] != null)
-        {
-          types.add(typ[i]);
-        }
-        typ = new String[types.size()];
-        types.toArray(typ);
-      }
-    }
-    if (gps != null)
-    {
-      ArrayList grps = new ArrayList();
-
-      for (int i = 0; i < gps.length; i++)
-      {
-        if (gps[i] != null)
-        {
-          grps.add(gps[i]);
-        }
-      }
-      gps = new String[grps.size()];
-      grps.toArray(gps);
-    }
-    AlignmentPanel alignPanel = af.alignPanel;
-    AlignmentI al = alignPanel.av.getAlignment();
-
-    int start, stop;
-    SequenceGroup sg = alignPanel.av.getSelectionGroup();
-    if (sg != null)
-    {
-      start = sg.getStartRes();
-      stop = sg.getEndRes();
-    }
-    else
-    {
-      start = 0;
-      stop = al.getWidth();
-    }
-    SequenceI[] oldOrder = al.getSequencesArray();
-    AlignmentSorter.sortByFeature(typ, gps, start, stop, al, method);
-    af.addHistoryItem(new OrderCommand(methodText, oldOrder, alignPanel.av
-            .getAlignment()));
-    alignPanel.paintAlignment(true);
-
-  }
-
-  protected void sortByScore(String[] typ)
-  {
-    sortBy(typ, "Sort by Feature Score", AlignmentSorter.FEATURE_SCORE);
-  }
-
-  private String[] getDisplayedFeatureTypes()
-  {
-    String[] typ = null;
-    if (fr != null)
-    {
-      synchronized (fr.renderOrder)
-      {
-        typ = new String[fr.renderOrder.length];
-        System.arraycopy(fr.renderOrder, 0, typ, 0, typ.length);
-        for (int i = 0; i < typ.length; i++)
-        {
-          if (af.viewport.featuresDisplayed.get(typ[i]) == null)
-          {
-            typ[i] = null;
-          }
-        }
-      }
-    }
-    return typ;
-  }
-
-  private String[] getDisplayedFeatureGroups()
-  {
-    String[] gps = null;
-    ArrayList<String> _gps = new ArrayList<String>();
-    if (fr != null)
-    {
-
-      if (fr.featureGroups != null)
-      {
-        Iterator en = fr.featureGroups.keySet().iterator();
-        int g = 0;
-        boolean valid = false;
-        while (en.hasNext())
-        {
-          String gp = (String) en.next();
-          Boolean on = (Boolean) fr.featureGroups.get(gp);
-          if (on != null && on.booleanValue())
-          {
-            valid = true;
-            _gps.add(gp);
-          }
-        }
-        if (!valid)
-        {
-          return null;
-        }
-        else
-        {
-          gps = new String[_gps.size()];
-          _gps.toArray(gps);
-        }
-      }
-    }
-    return gps;
-  }
-
   public void fetchDAS_actionPerformed(ActionEvent e)
   {
     fetchDAS.setEnabled(false);
index 8eddc06..acdaf92 100755 (executable)
@@ -182,7 +182,7 @@ public class Finder extends GFinder
               searchResults.getResultEnd(i), "Search Results");
     }
 
-    if (ap.seqPanel.seqCanvas.getFeatureRenderer().amendFeatures(seqs,
+    if (ap.getSeqPanel().seqCanvas.getFeatureRenderer().amendFeatures(seqs,
             features, true, ap))
     {
       ap.alignFrame.showSeqFeatures.setSelected(true);
@@ -229,11 +229,11 @@ public class Finder extends GFinder
     if ((idMatch.size() > 0))
     {
       haveResults = true;
-      ap.idPanel.highlightSearchResults(idMatch);
+      ap.getIdPanel().highlightSearchResults(idMatch);
     }
     else
     {
-      ap.idPanel.highlightSearchResults(null);
+      ap.getIdPanel().highlightSearchResults(null);
     }
 
     if (searchResults.getSize() > 0)
index b6116d9..36e4c52 100755 (executable)
@@ -129,7 +129,7 @@ public class FontChooser extends GFontChooser
   public void smoothFont_actionPerformed(ActionEvent e)
   {
     ap.av.antiAlias = smoothFont.isSelected();
-    ap.annotationPanel.image = null;
+    ap.getAnnotationPanel().image = null;
     ap.paintAlignment(true);
   }
 
diff --git a/src/jalview/gui/HTMLOptions.java b/src/jalview/gui/HTMLOptions.java
new file mode 100644 (file)
index 0000000..0ef4710
--- /dev/null
@@ -0,0 +1,143 @@
+package jalview.gui;
+
+import jalview.util.MessageManager;
+
+import java.awt.BorderLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+
+public class HTMLOptions extends JPanel
+{
+  JDialog dialog;
+
+  public boolean cancelled = false;
+
+  String value;
+
+  public HTMLOptions()
+  {
+    try
+    {
+      jbInit();
+    } catch (Exception ex)
+    {
+      ex.printStackTrace();
+    }
+
+    ButtonGroup bg = new ButtonGroup();
+    bg.add(lineart);
+    bg.add(text);
+
+    JOptionPane pane = new JOptionPane(null, JOptionPane.DEFAULT_OPTION,
+            JOptionPane.DEFAULT_OPTION, null, new Object[]
+            { this });
+
+    dialog = pane.createDialog(Desktop.desktop, "HTML Rendering options");
+    dialog.setVisible(true);
+
+  }
+
+  private void jbInit() throws Exception
+  {
+    lineart.setFont(JvSwingUtils.getLabelFont());
+    lineart.setText(MessageManager.getString("label.lineart"));
+    text.setFont(JvSwingUtils.getLabelFont());
+    text.setText(MessageManager.getString("action.text"));
+    text.setSelected(true);
+    askAgain.setFont(JvSwingUtils.getLabelFont());
+    askAgain.setText(MessageManager.getString("label.dont_ask_me_again"));
+    ok.setText(MessageManager.getString("action.ok"));
+    ok.addActionListener(new ActionListener()
+    {
+      public void actionPerformed(ActionEvent e)
+      {
+        ok_actionPerformed(e);
+      }
+    });
+    cancel.setText(MessageManager.getString("action.cancel"));
+    cancel.addActionListener(new ActionListener()
+    {
+      public void actionPerformed(ActionEvent e)
+      {
+        cancel_actionPerformed(e);
+      }
+    });
+    jLabel1.setFont(JvSwingUtils.getLabelFont());
+    jLabel1.setText("Select HTML character rendering style");
+    this.setLayout(borderLayout1);
+    jPanel3.setBorder(BorderFactory.createEtchedBorder());
+    jPanel2.add(text);
+    jPanel2.add(lineart);
+    jPanel2.add(askAgain);
+    jPanel1.add(ok);
+    jPanel1.add(cancel);
+    jPanel3.add(jLabel1);
+    jPanel3.add(jPanel2);
+    this.add(jPanel3, java.awt.BorderLayout.CENTER);
+    this.add(jPanel1, java.awt.BorderLayout.SOUTH);
+  }
+
+  JRadioButton lineart = new JRadioButton();
+
+  JRadioButton text = new JRadioButton();
+
+  JCheckBox askAgain = new JCheckBox();
+
+  JButton ok = new JButton();
+
+  JButton cancel = new JButton();
+
+  JPanel jPanel1 = new JPanel();
+
+  JLabel jLabel1 = new JLabel();
+
+  JPanel jPanel2 = new JPanel();
+
+  JPanel jPanel3 = new JPanel();
+
+  BorderLayout borderLayout1 = new BorderLayout();
+
+  public void ok_actionPerformed(ActionEvent e)
+  {
+    if (lineart.isSelected())
+    {
+      value = "Lineart";
+    }
+    else
+    {
+      value = "Text";
+    }
+
+    if (!askAgain.isSelected())
+    {
+      jalview.bin.Cache.applicationProperties.remove("HTML_RENDERING");
+    }
+    else
+    {
+      jalview.bin.Cache.setProperty("HTML_RENDERING", value);
+    }
+
+    dialog.setVisible(false);
+  }
+
+  public void cancel_actionPerformed(ActionEvent e)
+  {
+    cancelled = true;
+    dialog.setVisible(false);
+  }
+
+  public String getValue()
+  {
+    return value;
+  }
+}
index 91b23e4..3bc3168 100755 (executable)
@@ -60,7 +60,7 @@ public class IdCanvas extends JPanel
 
   AnnotationPanel ap;
 
-  Font idfont;
+  private Font idfont;
 
   /**
    * Creates a new IdCanvas object.
@@ -119,7 +119,7 @@ public class IdCanvas extends JPanel
       gg.setColor(Color.black);
     }
 
-    if (av.rightAlignIds)
+    if (av.isRightAlignIds())
     {
       xPos = panelWidth
               - fm.stringWidth(s.getDisplayId(av.getShowJVSuffix())) - 4;
@@ -247,15 +247,15 @@ public class IdCanvas extends JPanel
   {
     if (av.seqNameItalics)
     {
-      idfont = new Font(av.getFont().getName(), Font.ITALIC, av.getFont()
-              .getSize());
+      setIdfont(new Font(av.getFont().getName(), Font.ITALIC, av.getFont()
+              .getSize()));
     }
     else
     {
-      idfont = av.getFont();
+      setIdfont(av.getFont());
     }
 
-    gg.setFont(idfont);
+    gg.setFont(getIdfont());
     fm = gg.getFontMetrics();
 
     if (av.antiAlias)
@@ -279,7 +279,7 @@ public class IdCanvas extends JPanel
 
       int annotationHeight = 0;
 
-      if (av.showAnnotation)
+      if (av.isShowAnnotation())
       {
         if (ap == null)
         {
@@ -310,19 +310,19 @@ public class IdCanvas extends JPanel
         for (int i = starty; i < alheight; i++)
         {
           SequenceI s = av.getAlignment().getSequenceAt(i);
-          if (av.hasHiddenRows())
+          if (av.isDisplayReferenceSeq() || av.hasHiddenRows())
           {
             setHiddenFont(s);
           }
           else
           {
-            gg.setFont(idfont);
+            gg.setFont(getIdfont());
           }
 
           drawIdString(gg, s, i, 0, ypos);
         }
 
-        if (labels != null && av.showAnnotation)
+        if (labels != null && av.isShowAnnotation())
         {
           gg.translate(0, ypos + (alheight * av.charHeight));
           labels.drawComponent(gg, getWidth());
@@ -350,7 +350,7 @@ public class IdCanvas extends JPanel
           continue;
         }
 
-        if (av.hasHiddenRows())
+        if (av.isDisplayReferenceSeq() || av.hasHiddenRows())
         {
           setHiddenFont(sequence);
         }
@@ -383,7 +383,7 @@ public class IdCanvas extends JPanel
 
         String string = sequence.getDisplayId(av.getShowJVSuffix());
 
-        if (av.rightAlignIds)
+        if (av.isRightAlignIds())
         {
           xPos = panelWidth - fm.stringWidth(string) - 4;
         }
@@ -474,7 +474,7 @@ public class IdCanvas extends JPanel
     }
     else
     {
-      gg.setFont(idfont);
+      gg.setFont(getIdfont());
     }
   }
 
@@ -489,4 +489,14 @@ public class IdCanvas extends JPanel
     searchResults = list;
     repaint();
   }
+
+  public Font getIdfont()
+  {
+    return idfont;
+  }
+
+  public void setIdfont(Font idfont)
+  {
+    this.idfont = idfont;
+  }
 }
index 6b1109a..a22e918 100755 (executable)
@@ -52,7 +52,7 @@ import javax.swing.ToolTipManager;
 public class IdPanel extends JPanel implements MouseListener,
         MouseMotionListener, MouseWheelListener
 {
-  protected IdCanvas idCanvas;
+  private IdCanvas idCanvas;
 
   protected AlignViewport av;
 
@@ -81,11 +81,11 @@ public class IdPanel extends JPanel implements MouseListener,
   {
     this.av = av;
     alignPanel = parent;
-    idCanvas = new IdCanvas(av);
+    setIdCanvas(new IdCanvas(av));
     linkImageURL = getClass().getResource("/images/link.gif").toString();
     seqAnnotReport = new SequenceAnnotationReport(linkImageURL);
     setLayout(new BorderLayout());
-    add(idCanvas, BorderLayout.CENTER);
+    add(getIdCanvas(), BorderLayout.CENTER);
     addMouseListener(this);
     addMouseMotionListener(this);
     addMouseWheelListener(this);
@@ -102,7 +102,7 @@ public class IdPanel extends JPanel implements MouseListener,
   @Override
   public void mouseMoved(MouseEvent e)
   {
-    SeqPanel sp = alignPanel.seqPanel;
+    SeqPanel sp = alignPanel.getSeqPanel();
     int seq = Math.max(0, sp.findSeq(e));
     if (seq > -1 && seq < av.getAlignment().getHeight())
     {
@@ -111,7 +111,7 @@ public class IdPanel extends JPanel implements MouseListener,
       seqAnnotReport
               .createSequenceAnnotationReport(tip, sequence,
                       av.isShowDbRefs(), av.isShowNpFeats(),
-                      sp.seqCanvas.fr.minmax);
+                      sp.seqCanvas.fr.getMinMax());
       setToolTipText("<html>" + sequence.getDisplayId(true) + " "
               + tip.toString() + "</html>");
     }
@@ -128,7 +128,7 @@ public class IdPanel extends JPanel implements MouseListener,
   {
     mouseDragging = true;
 
-    int seq = Math.max(0, alignPanel.seqPanel.findSeq(e));
+    int seq = Math.max(0, alignPanel.getSeqPanel().findSeq(e));
 
     if (seq < lastid)
     {
@@ -202,7 +202,7 @@ public class IdPanel extends JPanel implements MouseListener,
       return;
     }
 
-    int seq = alignPanel.seqPanel.findSeq(e);
+    int seq = alignPanel.getSeqPanel().findSeq(e);
     String url = null;
     int i = 0;
     String id = av.getAlignment().getSequenceAt(seq).getName();
@@ -315,7 +315,7 @@ public class IdPanel extends JPanel implements MouseListener,
       return;
     }
 
-    int seq = alignPanel.seqPanel.findSeq(e);
+    int seq = alignPanel.getSeqPanel().findSeq(e);
 
     if (SwingUtilities.isRightMouseButton(e))
     {
@@ -443,7 +443,7 @@ public class IdPanel extends JPanel implements MouseListener,
    */
   public void highlightSearchResults(List<SequenceI> list)
   {
-    idCanvas.setHighlighted(list);
+    getIdCanvas().setHighlighted(list);
 
     if (list == null)
     {
@@ -459,6 +459,16 @@ public class IdPanel extends JPanel implements MouseListener,
     }
   }
 
+  public IdCanvas getIdCanvas()
+  {
+    return idCanvas;
+  }
+
+  public void setIdCanvas(IdCanvas idCanvas)
+  {
+    this.idCanvas = idCanvas;
+  }
+
   // this class allows scrolling off the bottom of the visible alignment
   class ScrollThread extends Thread
   {
index 0c53c58..6c9c400 100755 (executable)
@@ -119,12 +119,12 @@ public class IdwidthAdjuster extends JPanel implements MouseListener,
   {
     active = true;
 
-    Dimension d = ap.idPanel.idCanvas.getPreferredSize();
+    Dimension d = ap.getIdPanel().getIdCanvas().getPreferredSize();
     int dif = evt.getX() - oldX;
 
     if (((d.width + dif) > 20) || (dif > 0))
     {
-      ap.idPanel.idCanvas.setPreferredSize(new Dimension(d.width + dif,
+      ap.getIdPanel().getIdCanvas().setPreferredSize(new Dimension(d.width + dif,
               d.height));
       ap.paintAlignment(true);
     }
index cccf34d..a2cd147 100644 (file)
@@ -25,7 +25,10 @@ import jalview.bin.Cache;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
+import jalview.datamodel.StructureViewerModel;
+import jalview.datamodel.StructureViewerModel.StructureData;
 import jalview.schemabinding.version2.AlcodMap;
 import jalview.schemabinding.version2.Alcodon;
 import jalview.schemabinding.version2.AlcodonFrame;
@@ -64,11 +67,15 @@ import jalview.schemes.ColourSchemeProperty;
 import jalview.schemes.GraduatedColor;
 import jalview.schemes.ResidueColourScheme;
 import jalview.schemes.ResidueProperties;
+import jalview.schemes.UserColourScheme;
 import jalview.structure.StructureSelectionManager;
+import jalview.structures.models.AAStructureBindingModel;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
 import jalview.util.jarInputStreamProvider;
 import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
+import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
 import jalview.ws.jws2.Jws2Discoverer;
 import jalview.ws.jws2.dm.AAConSettings;
 import jalview.ws.jws2.jabaws2.Jws2Instance;
@@ -92,11 +99,14 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Enumeration;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.StringTokenizer;
@@ -123,6 +133,23 @@ import org.exolab.castor.xml.Unmarshaller;
  */
 public class Jalview2XML
 {
+  /*
+   * SequenceI reference -> XML ID string in jalview XML. Populated as XML reps
+   * of sequence objects are created.
+   */
+  IdentityHashMap<SequenceI, String> seqsToIds = null;
+
+  /**
+   * jalview XML Sequence ID to jalview sequence object reference (both dataset
+   * and alignment sequences. Populated as XML reps of sequence objects are
+   * created.)
+   */
+  Map<String, SequenceI> seqRefIds = null;
+
+  Vector frefedSequence = null;
+
+  boolean raiseGUI = true; // whether errors are raised in dialog boxes or not
+
   /**
    * create/return unique hash string for sq
    * 
@@ -137,7 +164,7 @@ public class Jalview2XML
     }
     if (seqsToIds.containsKey(sq))
     {
-      return (String) seqsToIds.get(sq);
+      return seqsToIds.get(sq);
     }
     else
     {
@@ -178,31 +205,14 @@ public class Jalview2XML
   {
     if (seqsToIds == null)
     {
-      seqsToIds = new IdentityHashMap();
+      seqsToIds = new IdentityHashMap<SequenceI, String>();
     }
     if (seqRefIds == null)
     {
-      seqRefIds = new Hashtable();
+      seqRefIds = new HashMap<String, SequenceI>();
     }
   }
 
-  /**
-   * SequenceI reference -> XML ID string in jalview XML. Populated as XML reps
-   * of sequence objects are created.
-   */
-  java.util.IdentityHashMap seqsToIds = null;
-
-  /**
-   * jalview XML Sequence ID to jalview sequence object reference (both dataset
-   * and alignment sequences. Populated as XML reps of sequence objects are
-   * created.)
-   */
-  java.util.Hashtable seqRefIds = null; // key->SequenceI resolution
-
-  Vector frefedSequence = null;
-
-  boolean raiseGUI = true; // whether errors are raised in dialog boxes or not
-
   public Jalview2XML()
   {
   }
@@ -227,7 +237,7 @@ public class Jalview2XML
           {
             if (ref[1] instanceof jalview.datamodel.Mapping)
             {
-              SequenceI seq = (SequenceI) seqRefIds.get(sref);
+              SequenceI seq = seqRefIds.get(sref);
               while (seq.getDatasetSequence() != null)
               {
                 seq = seq.getDatasetSequence();
@@ -238,7 +248,7 @@ public class Jalview2XML
             {
               if (ref[1] instanceof jalview.datamodel.AlignedCodonFrame)
               {
-                SequenceI seq = (SequenceI) seqRefIds.get(sref);
+                SequenceI seq = seqRefIds.get(sref);
                 while (seq.getDatasetSequence() != null)
                 {
                   seq = seq.getDatasetSequence();
@@ -300,16 +310,17 @@ public class Jalview2XML
   /**
    * List of pdbfiles added to Jar
    */
-  Vector pdbfiles = null;
+  List<String> pdbfiles = null;
 
   // SAVES SEVERAL ALIGNMENT WINDOWS TO SAME JARFILE
-  public void SaveState(File statefile)
+  public void saveState(File statefile)
   {
+    FileOutputStream fos = null;
     try
     {
-      FileOutputStream fos = new FileOutputStream(statefile);
+      fos = new FileOutputStream(statefile);
       JarOutputStream jout = new JarOutputStream(fos);
-      SaveState(jout);
+      saveState(jout);
 
     } catch (Exception e)
     {
@@ -325,6 +336,18 @@ public class Jalview2XML
         errorMessage += "(output file was '" + statefile + "')";
       }
       e.printStackTrace();
+    } finally
+    {
+      if (fos != null)
+      {
+        try
+        {
+          fos.close();
+        } catch (IOException e)
+        {
+          // ignore
+        }
+      }
     }
     reportErrors();
   }
@@ -334,7 +357,7 @@ public class Jalview2XML
    * 
    * @param jout
    */
-  public void SaveState(JarOutputStream jout)
+  public void saveState(JarOutputStream jout)
   {
     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
 
@@ -350,8 +373,6 @@ public class Jalview2XML
 
       // NOTE UTF-8 MUST BE USED FOR WRITING UNICODE CHARS
       // //////////////////////////////////////////////////
-      // NOTE ALSO new PrintWriter must be used for each new JarEntry
-      PrintWriter out = null;
 
       Vector shortNames = new Vector();
 
@@ -410,7 +431,7 @@ public class Jalview2XML
               fileName = fileName + ".xml";
             }
 
-            SaveState(apanel, fileName, jout);
+            saveState(apanel, fileName, jout);
 
             String dssid = getDatasetIdRef(af.getViewport().getAlignment()
                     .getDataset());
@@ -447,7 +468,7 @@ public class Jalview2XML
   }
 
   // USE THIS METHOD TO SAVE A SINGLE ALIGNMENT WINDOW
-  public boolean SaveAlignment(AlignFrame af, String jarFile,
+  public boolean saveAlignment(AlignFrame af, String jarFile,
           String fileName)
   {
     try
@@ -465,7 +486,7 @@ public class Jalview2XML
         {
           jfileName = jfileName + ".xml";
         }
-        SaveState(apanel, jfileName, jout);
+        saveState(apanel, jfileName, jout);
         String dssid = getDatasetIdRef(af.getViewport().getAlignment()
                 .getDataset());
         if (!dsses.containsKey(dssid))
@@ -503,12 +524,12 @@ public class Jalview2XML
       {
         jfileName = jfileName + ".xml";
       }
-      SaveState(_af.alignPanel, jfileName, true, jout);
+      saveState(_af.alignPanel, jfileName, true, jout);
     }
   }
 
   /**
-   * create a JalviewModel from an algnment view and marshall it to a
+   * create a JalviewModel from an alignment view and marshall it to a
    * JarOutputStream
    * 
    * @param ap
@@ -520,14 +541,14 @@ public class Jalview2XML
    * @param out
    *          jar entry name
    */
-  public JalviewModel SaveState(AlignmentPanel ap, String fileName,
+  public JalviewModel saveState(AlignmentPanel ap, String fileName,
           JarOutputStream jout)
   {
-    return SaveState(ap, fileName, false, jout);
+    return saveState(ap, fileName, false, jout);
   }
 
   /**
-   * create a JalviewModel from an algnment view and marshall it to a
+   * create a JalviewModel from an alignment view and marshall it to a
    * JarOutputStream
    * 
    * @param ap
@@ -542,12 +563,12 @@ public class Jalview2XML
    * @param out
    *          jar entry name
    */
-  public JalviewModel SaveState(AlignmentPanel ap, String fileName,
+  public JalviewModel saveState(AlignmentPanel ap, String fileName,
           boolean storeDS, JarOutputStream jout)
   {
     initSeqRefs();
-    Vector jmolViewIds = new Vector(); //
-    Vector userColours = new Vector();
+    List<String> viewIds = new ArrayList<String>();
+    List<UserColourScheme> userColours = new ArrayList<UserColourScheme>();
 
     AlignViewport av = ap.av;
 
@@ -716,81 +737,22 @@ public class Jalview2XML
 
           pdb.setId(entry.getId());
           pdb.setType(entry.getType());
-          //
-          // store any JMol views associated with this seqeunce
-          // this section copes with duplicate entries in the project, so a
-          // dataset only view *should* be coped with sensibly
-          AppJmol jmol;
+
+          /*
+           * Store any structure views associated with this sequence. This
+           * section copes with duplicate entries in the project, so a dataset
+           * only view *should* be coped with sensibly.
+           */
           // This must have been loaded, is it still visible?
           JInternalFrame[] frames = Desktop.desktop.getAllFrames();
           String matchedFile = null;
           for (int f = frames.length - 1; f > -1; f--)
           {
-            if (frames[f] instanceof AppJmol)
+            if (frames[f] instanceof StructureViewerBase)
             {
-              jmol = (AppJmol) frames[f];
-              for (int peid = 0; peid < jmol.jmb.pdbentry.length; peid++)
-              {
-                if (!jmol.jmb.pdbentry[peid].getId().equals(entry.getId())
-                        && !(entry.getId().length() > 4 && entry
-                                .getId()
-                                .toLowerCase()
-                                .startsWith(
-                                        jmol.jmb.pdbentry[peid].getId()
-                                                .toLowerCase())))
-                {
-                  continue;
-                }
-                if (matchedFile == null)
-                {
-                  matchedFile = jmol.jmb.pdbentry[peid].getFile();
-                }
-                else if (!matchedFile.equals(jmol.jmb.pdbentry[peid]
-                        .getFile()))
-                {
-                  Cache.log
-                          .warn("Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
-                                  + jmol.jmb.pdbentry[peid].getFile());
-                  ; // record the
-                }
-                // file so we
-                // can get at it if the ID
-                // match is ambiguous (e.g.
-                // 1QIP==1qipA)
-                String statestring = jmol.jmb.viewer.getStateInfo();
-
-                for (int smap = 0; smap < jmol.jmb.sequence[peid].length; smap++)
-                {
-                  // if (jal.findIndex(jmol.jmb.sequence[peid][smap]) > -1)
-                  if (jds == jmol.jmb.sequence[peid][smap])
-                  {
-                    StructureState state = new StructureState();
-                    state.setVisible(true);
-                    state.setXpos(jmol.getX());
-                    state.setYpos(jmol.getY());
-                    state.setWidth(jmol.getWidth());
-                    state.setHeight(jmol.getHeight());
-                    state.setViewId(jmol.getViewId());
-                    state.setAlignwithAlignPanel(jmol.isUsedforaligment(ap));
-                    state.setColourwithAlignPanel(jmol
-                            .isUsedforcolourby(ap));
-                    state.setColourByJmol(jmol.isColouredByJmol());
-                    if (!jmolViewIds.contains(state.getViewId()))
-                    {
-                      // Make sure we only store a Jmol state once in each XML
-                      // document.
-                      jmolViewIds.addElement(state.getViewId());
-                      state.setContent(statestring.replaceAll("\n", ""));
-                    }
-                    else
-                    {
-                      state.setContent("# duplicate state");
-                    }
-                    pdb.addStructureState(state);
-                  }
-
-                }
-              }
+              StructureViewerBase viewFrame = (StructureViewerBase) frames[f];
+              matchedFile = saveStructureState(ap, jds, pdb, entry,
+                      viewIds, matchedFile, viewFrame);
             }
           }
 
@@ -804,12 +766,13 @@ public class Jalview2XML
             pdb.setFile(matchedFile); // entry.getFile());
             if (pdbfiles == null)
             {
-              pdbfiles = new Vector();
+              pdbfiles = new ArrayList<String>();
             }
 
             if (!pdbfiles.contains(entry.getId()))
             {
-              pdbfiles.addElement(entry.getId());
+              pdbfiles.add(entry.getId());
+              DataInputStream dis = null;
               try
               {
                 File file = new File(matchedFile);
@@ -817,7 +780,7 @@ public class Jalview2XML
                 {
                   byte[] data = new byte[(int) file.length()];
                   jout.putNextEntry(new JarEntry(entry.getId()));
-                  DataInputStream dis = new DataInputStream(
+                  dis = new DataInputStream(
                           new FileInputStream(file));
                   dis.readFully(data);
 
@@ -829,6 +792,18 @@ public class Jalview2XML
               } catch (Exception ex)
               {
                 ex.printStackTrace();
+              } finally
+              {
+                if (dis != null)
+                {
+                  try
+                  {
+                    dis.close();
+                  } catch (IOException e)
+                  {
+                    // ignore
+                  }
+                }
               }
 
             }
@@ -997,7 +972,7 @@ public class Jalview2XML
 
             if (sg.cs instanceof jalview.schemes.UserColourScheme)
             {
-              groups[i].setColour(SetUserColourScheme(sg.cs, userColours,
+              groups[i].setColour(setUserColourScheme(sg.cs, userColours,
                       jms));
             }
             else
@@ -1016,7 +991,7 @@ public class Jalview2XML
           else if (sg.cs instanceof jalview.schemes.UserColourScheme)
           {
             groups[i]
-                    .setColour(SetUserColourScheme(sg.cs, userColours, jms));
+                    .setColour(setUserColourScheme(sg.cs, userColours, jms));
           }
           else
           {
@@ -1079,7 +1054,7 @@ public class Jalview2XML
 
       if (av.getGlobalColourScheme() instanceof jalview.schemes.UserColourScheme)
       {
-        view.setBgColour(SetUserColourScheme(av.getGlobalColourScheme(),
+        view.setBgColour(setUserColourScheme(av.getGlobalColourScheme(),
                 userColours, jms));
       }
       else if (av.getGlobalColourScheme() instanceof jalview.schemes.AnnotationColourGradient)
@@ -1107,7 +1082,7 @@ public class Jalview2XML
           view.setConsThreshold(cs.getConservationInc());
           if (cs instanceof jalview.schemes.UserColourScheme)
           {
-            view.setBgColour(SetUserColourScheme(cs, userColours, jms));
+            view.setBgColour(setUserColourScheme(cs, userColours, jms));
           }
         }
 
@@ -1123,12 +1098,12 @@ public class Jalview2XML
       view.setFontSize(av.font.getSize());
       view.setFontStyle(av.font.getStyle());
       view.setRenderGaps(av.renderGaps);
-      view.setShowAnnotation(av.getShowAnnotation());
+      view.setShowAnnotation(av.isShowAnnotation());
       view.setShowBoxes(av.getShowBoxes());
       view.setShowColourText(av.getColourText());
       view.setShowFullId(av.getShowJVSuffix());
-      view.setRightAlignIds(av.rightAlignIds);
-      view.setShowSequenceFeatures(av.showSequenceFeatures);
+      view.setRightAlignIds(av.isRightAlignIds());
+      view.setShowSequenceFeatures(av.isShowSequenceFeatures());
       view.setShowText(av.getShowText());
       view.setShowUnconserved(av.getShowUnconserved());
       view.setWrapAlignment(av.getWrapAlignment());
@@ -1145,11 +1120,12 @@ public class Jalview2XML
       view.setFollowHighlight(av.followHighlight);
       view.setFollowSelection(av.followSelection);
       view.setIgnoreGapsinConsensus(av.getIgnoreGapsConsensus());
-      if (av.featuresDisplayed != null)
+      if (av.getFeaturesDisplayed() != null)
       {
         jalview.schemabinding.version2.FeatureSettings fs = new jalview.schemabinding.version2.FeatureSettings();
 
-        String[] renderOrder = ap.seqPanel.seqCanvas.getFeatureRenderer().renderOrder;
+        String[] renderOrder = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
+                .getRenderOrder().toArray(new String[0]);
 
         Vector settingsAdded = new Vector();
         Object gstyle = null;
@@ -1158,7 +1134,7 @@ public class Jalview2XML
         {
           for (int ro = 0; ro < renderOrder.length; ro++)
           {
-            gstyle = ap.seqPanel.seqCanvas.getFeatureRenderer()
+            gstyle = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
                     .getFeatureStyle(renderOrder[ro]);
             Setting setting = new Setting();
             setting.setType(renderOrder[ro]);
@@ -1176,13 +1152,13 @@ public class Jalview2XML
             }
             else
             {
-              setting.setColour(ap.seqPanel.seqCanvas.getFeatureRenderer()
+              setting.setColour(ap.getSeqPanel().seqCanvas.getFeatureRenderer()
                       .getColour(renderOrder[ro]).getRGB());
             }
 
-            setting.setDisplay(av.featuresDisplayed
-                    .containsKey(renderOrder[ro]));
-            float rorder = ap.seqPanel.seqCanvas.getFeatureRenderer()
+            setting.setDisplay(av.getFeaturesDisplayed().isVisible(
+                    renderOrder[ro]));
+            float rorder = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
                     .getOrder(renderOrder[ro]);
             if (rorder > -1)
             {
@@ -1194,8 +1170,8 @@ public class Jalview2XML
         }
 
         // Make sure we save none displayed feature settings
-        Iterator en = ap.seqPanel.seqCanvas.getFeatureRenderer().featureColours
-                .keySet().iterator();
+        Iterator en = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
+                .getFeatureColours().keySet().iterator();
         while (en.hasNext())
         {
           String key = en.next().toString();
@@ -1206,11 +1182,11 @@ public class Jalview2XML
 
           Setting setting = new Setting();
           setting.setType(key);
-          setting.setColour(ap.seqPanel.seqCanvas.getFeatureRenderer()
+          setting.setColour(ap.getSeqPanel().seqCanvas.getFeatureRenderer()
                   .getColour(key).getRGB());
 
           setting.setDisplay(false);
-          float rorder = ap.seqPanel.seqCanvas.getFeatureRenderer()
+          float rorder = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
                   .getOrder(key);
           if (rorder > -1)
           {
@@ -1219,8 +1195,9 @@ public class Jalview2XML
           fs.addSetting(setting);
           settingsAdded.addElement(key);
         }
-        en = ap.seqPanel.seqCanvas.getFeatureRenderer().featureGroups
-                .keySet().iterator();
+        // is groups actually supposed to be a map here ?
+        en = ap.getSeqPanel().seqCanvas.getFeatureRenderer().getFeatureGroups()
+                .iterator();
         Vector groupsAdded = new Vector();
         while (en.hasNext())
         {
@@ -1231,8 +1208,8 @@ public class Jalview2XML
           }
           Group g = new Group();
           g.setName(grp);
-          g.setDisplay(((Boolean) ap.seqPanel.seqCanvas
-                  .getFeatureRenderer().featureGroups.get(grp))
+          g.setDisplay(((Boolean) ap.getSeqPanel().seqCanvas
+                  .getFeatureRenderer().checkGroupVisibility(grp, false))
                   .booleanValue());
           fs.addGroup(g);
           groupsAdded.addElement(grp);
@@ -1253,8 +1230,8 @@ public class Jalview2XML
           for (int c = 0; c < av.getColumnSelection().getHiddenColumns()
                   .size(); c++)
           {
-            int[] region = (int[]) av.getColumnSelection()
-                    .getHiddenColumns().elementAt(c);
+            int[] region = av.getColumnSelection()
+                    .getHiddenColumns().get(c);
             HiddenColumns hc = new HiddenColumns();
             hc.setStart(region[0]);
             hc.setEnd(region[1]);
@@ -1308,8 +1285,94 @@ public class Jalview2XML
     return object;
   }
 
+  /**
+   * Save the state of a structure viewer
+   * 
+   * @param ap
+   * @param jds
+   * @param pdb
+   *          the archive XML element under which to save the state
+   * @param entry
+   * @param viewIds
+   * @param matchedFile
+   * @param viewFrame
+   * @return
+   */
+  protected String saveStructureState(AlignmentPanel ap, SequenceI jds,
+          Pdbids pdb, PDBEntry entry, List<String> viewIds,
+          String matchedFile, StructureViewerBase viewFrame)
+  {
+    final AAStructureBindingModel bindingModel = viewFrame
+            .getBinding();
+    for (int peid = 0; peid < bindingModel
+            .getPdbCount(); peid++)
+    {
+      final PDBEntry pdbentry = bindingModel.getPdbEntry(peid);
+      final String pdbId = pdbentry.getId();
+      if (!pdbId.equals(entry.getId())
+              && !(entry.getId().length() > 4 && entry.getId()
+                      .toLowerCase()
+                      .startsWith(pdbId.toLowerCase())))
+      {
+        continue;
+      }
+      if (matchedFile == null)
+      {
+        matchedFile = pdbentry.getFile();
+      }
+      else if (!matchedFile.equals(pdbentry
+              .getFile()))
+      {
+        Cache.log
+                .warn("Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
+                        + pdbentry.getFile());
+      }
+      // record the
+      // file so we
+      // can get at it if the ID
+      // match is ambiguous (e.g.
+      // 1QIP==1qipA)
+      String statestring = viewFrame.getStateInfo();
+
+      for (int smap = 0; smap < viewFrame.getBinding()
+              .getSequence()[peid].length; smap++)
+      {
+        // if (jal.findIndex(jmol.jmb.sequence[peid][smap]) > -1)
+        if (jds == viewFrame.getBinding().getSequence()[peid][smap])
+        {
+          StructureState state = new StructureState();
+          state.setVisible(true);
+          state.setXpos(viewFrame.getX());
+          state.setYpos(viewFrame.getY());
+          state.setWidth(viewFrame.getWidth());
+          state.setHeight(viewFrame.getHeight());
+          final String viewId = viewFrame.getViewId();
+          state.setViewId(viewId);
+          state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap));
+          state.setColourwithAlignPanel(viewFrame
+                  .isUsedforcolourby(ap));
+          state.setColourByJmol(viewFrame.isColouredByViewer());
+          /*
+           * Only store each structure viewer's state once in each XML document.
+           */
+          if (!viewIds.contains(viewId))
+          {
+            viewIds.add(viewId);
+            state.setContent(statestring.replaceAll("\n", ""));
+          }
+          else
+          {
+            state.setContent("# duplicate state");
+          }
+          pdb.addStructureState(state);
+        }
+      }
+    }
+    return matchedFile;
+  }
+
   private AnnotationColours constructAnnotationColours(
-          AnnotationColourGradient acg, Vector userColours,
+          AnnotationColourGradient acg, List<UserColourScheme> userColours,
           JalviewModelSequence jms)
   {
     AnnotationColours ac = new AnnotationColours();
@@ -1318,7 +1381,7 @@ public class Jalview2XML
     ac.setAnnotation(acg.getAnnotation());
     if (acg.getBaseColour() instanceof jalview.schemes.UserColourScheme)
     {
-      ac.setColourScheme(SetUserColourScheme(acg.getBaseColour(),
+      ac.setColourScheme(setUserColourScheme(acg.getBaseColour(),
               userColours, jms));
     }
     else
@@ -1429,6 +1492,7 @@ public class Jalview2XML
           an.addProperty(prop);
         }
       }
+
       AnnotationElement ae;
       if (aa[i].annotations != null)
       {
@@ -1762,8 +1826,8 @@ public class Jalview2XML
     return mp;
   }
 
-  String SetUserColourScheme(jalview.schemes.ColourSchemeI cs,
-          Vector userColours, JalviewModelSequence jms)
+  String setUserColourScheme(jalview.schemes.ColourSchemeI cs,
+          List<UserColourScheme> userColours, JalviewModelSequence jms)
   {
     String id = null;
     jalview.schemes.UserColourScheme ucs = (jalview.schemes.UserColourScheme) cs;
@@ -1808,7 +1872,7 @@ public class Jalview2XML
     return id;
   }
 
-  jalview.schemes.UserColourScheme GetUserColourScheme(
+  jalview.schemes.UserColourScheme getUserColourScheme(
           JalviewModelSequence jms, String id)
   {
     UserColours[] uc = jms.getUserColours();
@@ -1866,7 +1930,7 @@ public class Jalview2XML
    * @param file
    *          - HTTP URL or filename
    */
-  public AlignFrame LoadJalviewAlign(final String file)
+  public AlignFrame loadJalviewAlign(final String file)
   {
 
     jalview.gui.AlignFrame af = null;
@@ -1881,7 +1945,7 @@ public class Jalview2XML
       // so we can re-open the jar input stream for each entry.
 
       jarInputStreamProvider jprovider = createjarInputStreamProvider(file);
-      af = LoadJalviewAlign(jprovider);
+      af = loadJalviewAlign(jprovider);
 
     } catch (MalformedURLException e)
     {
@@ -1954,7 +2018,7 @@ public class Jalview2XML
    * @param jprovider
    * @return
    */
-  public AlignFrame LoadJalviewAlign(final jarInputStreamProvider jprovider)
+  public AlignFrame loadJalviewAlign(final jarInputStreamProvider jprovider)
   {
     errorMessage = null;
     if (uniqueSetSuffix == null)
@@ -1963,7 +2027,7 @@ public class Jalview2XML
     }
     if (seqRefIds == null)
     {
-      seqRefIds = new Hashtable();
+      seqRefIds = new HashMap<String, SequenceI>();
     }
     if (viewportsAdded == null)
     {
@@ -2001,7 +2065,7 @@ public class Jalview2XML
           object = (JalviewModel) unmar.unmarshal(in);
           if (true) // !skipViewport(object))
           {
-            _af = LoadFromObject(object, file, true, jprovider);
+            _af = loadFromObject(object, file, true, jprovider);
             if (object.getJalviewModelSequence().getViewportCount() > 0)
             {
               af = _af;
@@ -2227,7 +2291,7 @@ public class Jalview2XML
    *          data source provider
    * @return alignment frame created from view stored in DOM
    */
-  AlignFrame LoadFromObject(JalviewModel object, String file,
+  AlignFrame loadFromObject(JalviewModel object, String file,
           boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
   {
     SequenceSet vamsasSet = object.getVamsasModel().getSequenceSet(0);
@@ -2248,11 +2312,11 @@ public class Jalview2XML
 
     boolean multipleView = false;
 
-    JSeq[] JSEQ = object.getJalviewModelSequence().getJSeq();
+    JSeq[] jseqs = object.getJalviewModelSequence().getJSeq();
     int vi = 0; // counter in vamsasSeq array
-    for (int i = 0; i < JSEQ.length; i++)
+    for (int i = 0; i < jseqs.length; i++)
     {
-      String seqId = JSEQ[i].getId();
+      String seqId = jseqs[i].getId();
 
       if (seqRefIds.get(seqId) != null)
       {
@@ -2264,15 +2328,15 @@ public class Jalview2XML
         jseq = new jalview.datamodel.Sequence(vamsasSeq[vi].getName(),
                 vamsasSeq[vi].getSequence());
         jseq.setDescription(vamsasSeq[vi].getDescription());
-        jseq.setStart(JSEQ[i].getStart());
-        jseq.setEnd(JSEQ[i].getEnd());
+        jseq.setStart(jseqs[i].getStart());
+        jseq.setEnd(jseqs[i].getEnd());
         jseq.setVamsasId(uniqueSetSuffix + seqId);
         seqRefIds.put(vamsasSeq[vi].getId(), jseq);
         tmpseqs.add(jseq);
         vi++;
       }
 
-      if (JSEQ[i].getHidden())
+      if (jseqs[i].getHidden())
       {
         if (hiddenSeqs == null)
         {
@@ -2327,9 +2391,9 @@ public class Jalview2XML
       // structures for the alignment
       for (int i = 0; i < vamsasSeq.length; i++)
       {
-        if (JSEQ[i].getFeaturesCount() > 0)
+        if (jseqs[i].getFeaturesCount() > 0)
         {
-          Features[] features = JSEQ[i].getFeatures();
+          Features[] features = jseqs[i].getFeatures();
           for (int f = 0; f < features.length; f++)
           {
             jalview.datamodel.SequenceFeature sf = new jalview.datamodel.SequenceFeature(
@@ -2359,9 +2423,9 @@ public class Jalview2XML
         {
           addDBRefs(al.getSequenceAt(i).getDatasetSequence(), vamsasSeq[i]);
         }
-        if (JSEQ[i].getPdbidsCount() > 0)
+        if (jseqs[i].getPdbidsCount() > 0)
         {
-          Pdbids[] ids = JSEQ[i].getPdbids();
+          Pdbids[] ids = jseqs[i].getPdbids();
           for (int p = 0; p < ids.length; p++)
           {
             jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry();
@@ -2424,7 +2488,7 @@ public class Jalview2XML
           AlcodMap[] maps = alc[i].getAlcodMap();
           for (int m = 0; m < maps.length; m++)
           {
-            SequenceI dnaseq = (SequenceI) seqRefIds
+            SequenceI dnaseq = seqRefIds
                     .get(maps[m].getDnasq());
             // Load Mapping
             jalview.datamodel.Mapping mapping = null;
@@ -2678,7 +2742,7 @@ public class Jalview2XML
         {
           if (groups[i].getColour().startsWith("ucs"))
           {
-            cs = GetUserColourScheme(jms, groups[i].getColour());
+            cs = getUserColourScheme(jms, groups[i].getColour());
           }
           else if (groups[i].getColour().equals("AnnotationColourGradient")
                   && groups[i].getAnnotationColours() != null)
@@ -2702,7 +2766,7 @@ public class Jalview2XML
         for (int s = 0; s < groups[i].getSeqCount(); s++)
         {
           String seqId = groups[i].getSeq(s) + "";
-          jalview.datamodel.SequenceI ts = (jalview.datamodel.SequenceI) seqRefIds
+          jalview.datamodel.SequenceI ts = seqRefIds
                   .get(seqId);
 
           if (ts != null)
@@ -2874,7 +2938,7 @@ public class Jalview2XML
 
     if (isnewview)
     {
-      af = loadViewport(file, JSEQ, hiddenSeqs, al, jms, view,
+      af = loadViewport(file, jseqs, hiddenSeqs, al, jms, view,
               uniqueSeqSetId, viewId, autoAlan);
       av = af.viewport;
       ap = af.alignPanel;
@@ -2959,358 +3023,501 @@ public class Jalview2XML
     // //LOAD STRUCTURES
     if (loadTreesAndStructures)
     {
-      // run through all PDB ids on the alignment, and collect mappings between
-      // jmol view ids and all sequences referring to it
-      Hashtable<String, Object[]> jmolViewIds = new Hashtable();
+      loadStructures(jprovider, jseqs, af, ap);
+    }
+    // and finally return.
+    return af;
+  }
+
+  /**
+   * Load and link any saved structure viewers.
+   * 
+   * @param jprovider
+   * @param jseqs
+   * @param af
+   * @param ap
+   */
+  protected void loadStructures(jarInputStreamProvider jprovider,
+          JSeq[] jseqs, AlignFrame af, AlignmentPanel ap)
+  {
+    /*
+     * Run through all PDB ids on the alignment, and collect mappings between
+     * distinct view ids and all sequences referring to that view.
+     */
+    Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<String, StructureViewerModel>();
 
-      for (int i = 0; i < JSEQ.length; i++)
+    for (int i = 0; i < jseqs.length; i++)
+    {
+      if (jseqs[i].getPdbidsCount() > 0)
       {
-        if (JSEQ[i].getPdbidsCount() > 0)
+        Pdbids[] ids = jseqs[i].getPdbids();
+        for (int p = 0; p < ids.length; p++)
         {
-          Pdbids[] ids = JSEQ[i].getPdbids();
-          for (int p = 0; p < ids.length; p++)
+          final int structureStateCount = ids[p].getStructureStateCount();
+          for (int s = 0; s < structureStateCount; s++)
           {
-            for (int s = 0; s < ids[p].getStructureStateCount(); s++)
+            // check to see if we haven't already created this structure view
+            final StructureState structureState = ids[p].getStructureState(s);
+            String sviewid = (structureState.getViewId() == null) ? null
+                    : structureState.getViewId()
+                            + uniqueSetSuffix;
+            jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
+            // Originally : ids[p].getFile()
+            // : TODO: verify external PDB file recovery still works in normal
+            // jalview project load
+            jpdb.setFile(loadPDBFile(jprovider, ids[p].getId()));
+            jpdb.setId(ids[p].getId());
+
+            int x = structureState.getXpos();
+            int y = structureState.getYpos();
+            int width = structureState.getWidth();
+            int height = structureState.getHeight();
+
+            // Probably don't need to do this anymore...
+            // Desktop.desktop.getComponentAt(x, y);
+            // TODO: NOW: check that this recovers the PDB file correctly.
+            String pdbFile = loadPDBFile(jprovider, ids[p].getId());
+            jalview.datamodel.SequenceI seq = seqRefIds
+                    .get(jseqs[i].getId() + "");
+            if (sviewid == null)
             {
-              // check to see if we haven't already created this structure view
-              String sviewid = (ids[p].getStructureState(s).getViewId() == null) ? null
-                      : ids[p].getStructureState(s).getViewId()
-                              + uniqueSetSuffix;
-              jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
-              // Originally : ids[p].getFile()
-              // : TODO: verify external PDB file recovery still works in normal
-              // jalview project load
-              jpdb.setFile(loadPDBFile(jprovider, ids[p].getId()));
-              jpdb.setId(ids[p].getId());
-
-              int x = ids[p].getStructureState(s).getXpos();
-              int y = ids[p].getStructureState(s).getYpos();
-              int width = ids[p].getStructureState(s).getWidth();
-              int height = ids[p].getStructureState(s).getHeight();
-
-              // Probably don't need to do this anymore...
-              // Desktop.desktop.getComponentAt(x, y);
-              // TODO: NOW: check that this recovers the PDB file correctly.
-              String pdbFile = loadPDBFile(jprovider, ids[p].getId());
-              jalview.datamodel.SequenceI seq = (jalview.datamodel.SequenceI) seqRefIds
-                      .get(JSEQ[i].getId() + "");
-              if (sviewid == null)
-              {
-                sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width
-                        + "," + height;
-              }
-              if (!jmolViewIds.containsKey(sviewid))
-              {
-                jmolViewIds.put(sviewid, new Object[]
-                { new int[]
-                { x, y, width, height }, "",
-                    new Hashtable<String, Object[]>(), new boolean[]
-                    { false, false, true } });
-                // Legacy pre-2.7 conversion JAL-823 :
-                // do not assume any view has to be linked for colour by
-                // sequence
-              }
+              sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width
+                      + "," + height;
+            }
+            if (!structureViewers.containsKey(sviewid))
+            {
+              structureViewers.put(sviewid, new StructureViewerModel(x, y, width, height,
+                      false, false, true));
+              // Legacy pre-2.7 conversion JAL-823 :
+              // do not assume any view has to be linked for colour by
+              // sequence
+            }
 
-              // assemble String[] { pdb files }, String[] { id for each
-              // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
-              // seqs_file 2}, boolean[] {
-              // linkAlignPanel,superposeWithAlignpanel}} from hash
-              Object[] jmoldat = jmolViewIds.get(sviewid);
-              ((boolean[]) jmoldat[3])[0] |= ids[p].getStructureState(s)
-                      .hasAlignwithAlignPanel() ? ids[p].getStructureState(
-                      s).getAlignwithAlignPanel() : false;
-              // never colour by linked panel if not specified
-              ((boolean[]) jmoldat[3])[1] |= ids[p].getStructureState(s)
-                      .hasColourwithAlignPanel() ? ids[p]
-                      .getStructureState(s).getColourwithAlignPanel()
-                      : false;
-              // default for pre-2.7 projects is that Jmol colouring is enabled
-              ((boolean[]) jmoldat[3])[2] &= ids[p].getStructureState(s)
-                      .hasColourByJmol() ? ids[p].getStructureState(s)
-                      .getColourByJmol() : true;
-
-              if (((String) jmoldat[1]).length() < ids[p]
-                      .getStructureState(s).getContent().length())
+            // assemble String[] { pdb files }, String[] { id for each
+            // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
+            // seqs_file 2}, boolean[] {
+            // linkAlignPanel,superposeWithAlignpanel}} from hash
+            StructureViewerModel jmoldat = structureViewers.get(sviewid);
+            jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
+                    | (structureState.hasAlignwithAlignPanel() ? structureState
+                            .getAlignwithAlignPanel() : false));
+
+            /*
+             * Default colour by linked panel to false if not specified (e.g.
+             * for pre-2.7 projects)
+             */
+            boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
+            colourWithAlignPanel |= (structureState
+                    .hasColourwithAlignPanel() ? structureState
+                    .getColourwithAlignPanel() : false);
+            jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
+
+            /*
+             * Default colour by viewer to true if not specified (e.g. for
+             * pre-2.7 projects)
+             */
+            boolean colourByViewer = jmoldat.isColourByViewer();
+            colourByViewer &= structureState
+                    .hasColourByJmol() ? structureState
+                    .getColourByJmol() : true;
+            jmoldat.setColourByViewer(colourByViewer);
+
+            if (jmoldat.getStateData().length() < structureState
+                    .getContent().length())
+            {
               {
-                {
-                  jmoldat[1] = ids[p].getStructureState(s).getContent();
-                }
+                jmoldat.setStateData(structureState.getContent());
               }
-              if (ids[p].getFile() != null)
+            }
+            if (ids[p].getFile() != null)
+            {
+              File mapkey = new File(ids[p].getFile());
+              StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
+              if (seqstrmaps == null)
               {
-                File mapkey = new File(ids[p].getFile());
-                Object[] seqstrmaps = (Object[]) ((Hashtable) jmoldat[2])
-                        .get(mapkey);
-                if (seqstrmaps == null)
-                {
-                  ((Hashtable) jmoldat[2]).put(mapkey,
-                          seqstrmaps = new Object[]
-                          { pdbFile, ids[p].getId(), new Vector(),
-                              new Vector() });
-                }
-                if (!((Vector) seqstrmaps[2]).contains(seq))
-                {
-                  ((Vector) seqstrmaps[2]).addElement(seq);
-                  // ((Vector)seqstrmaps[3]).addElement(n) :
-                  // in principle, chains
-                  // should be stored here : do we need to
-                  // TODO: store and recover seq/pdb_id :
-                  // chain mappings
-                }
+                jmoldat.getFileData().put(
+                        mapkey,
+                        seqstrmaps = jmoldat.new StructureData(pdbFile,
+                                ids[p].getId()));
               }
-              else
+              if (!seqstrmaps.getSeqList().contains(seq))
               {
-                errorMessage = ("The Jmol views in this project were imported\nfrom an older version of Jalview.\nPlease review the sequence colour associations\nin the Colour by section of the Jmol View menu.\n\nIn the case of problems, see note at\nhttp://issues.jalview.org/browse/JAL-747");
-                warn(errorMessage);
+                seqstrmaps.getSeqList().add(seq);
+                // TODO and chains?
               }
             }
+            else
+            {
+              errorMessage = ("The Jmol views in this project were imported\nfrom an older version of Jalview.\nPlease review the sequence colour associations\nin the Colour by section of the Jmol View menu.\n\nIn the case of problems, see note at\nhttp://issues.jalview.org/browse/JAL-747");
+              warn(errorMessage);
+            }
           }
         }
       }
+    }
+      // Instantiate the associated structure views
+      for (Entry<String, StructureViewerModel> entry : structureViewers.entrySet())
       {
+        createOrLinkStructureViewer(entry, af, ap);
+      }
+  }
 
-        // Instantiate the associated Jmol views
-        for (Entry<String, Object[]> entry : jmolViewIds.entrySet())
+  /**
+   * 
+   * @param viewerData
+   * @param af
+   * @param ap
+   */
+  protected void createOrLinkStructureViewer(
+          Entry<String, StructureViewerModel> viewerData, AlignFrame af,
+          AlignmentPanel ap)
+  {
+    final StructureViewerModel svattrib = viewerData.getValue();
+
+    /*
+     * Search for any viewer windows already open from other alignment views
+     * that exactly match the stored structure state
+     */
+    StructureViewerBase comp = findMatchingViewer(viewerData);
+
+    if (comp != null)
+    {
+      linkStructureViewer(ap, comp, svattrib);
+      return;
+    }
+
+    /*
+     * Pending an XML element for ViewerType, just check if stateData contains
+     * "chimera" (part of the chimera session filename).
+     */
+    if (svattrib.getStateData().indexOf("chimera") > -1)
+    {
+      createChimeraViewer(viewerData, af);
+    }
+    else
+    {
+      createJmolViewer(viewerData, af);
+    }
+  }
+
+  /**
+   * Create a new Chimera viewer.
+   * 
+   * @param viewerData
+   * @param af
+   */
+  protected void createChimeraViewer(Entry<String, StructureViewerModel> viewerData,
+          AlignFrame af)
+  {
+    final StructureViewerModel data = viewerData.getValue();
+    String chimeraSession = data.getStateData();
+
+    if (new File(chimeraSession).exists())
+    {
+      Set<Entry<File, StructureData>> fileData = data.getFileData()
+              .entrySet();
+      List<PDBEntry> pdbs = new ArrayList<PDBEntry>();
+      List<SequenceI[]> allseqs = new ArrayList<SequenceI[]>();
+      for (Entry<File, StructureData> pdb : fileData)
+      {
+        String filePath = pdb.getValue().getFilePath();
+        String pdbId = pdb.getValue().getPdbId();
+        pdbs.add(new PDBEntry(filePath, pdbId));
+        final List<SequenceI> seqList = pdb.getValue().getSeqList();
+        SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
+        allseqs.add(seqs);
+      }
+
+      boolean colourByChimera = data.isColourByViewer();
+      boolean colourBySequence = data.isColourWithAlignPanel();
+
+      // TODO can/should this be done via StructureViewer (like Jmol)?
+      final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs
+              .size()]);
+      final SequenceI[][] seqsArray = allseqs.toArray(new SequenceI[allseqs.size()][]);
+      new ChimeraViewFrame(chimeraSession, af.alignPanel, pdbArray,
+              seqsArray,
+              colourByChimera, colourBySequence);
+    }
+    else
+    {
+      Cache.log.error("Chimera session file " + chimeraSession
+              + " not found");
+    }
+  }
+
+  /**
+   * Create a new Jmol window. First parse the Jmol state to translate filenames
+   * loaded into the view, and record the order in which files are shown in the
+   * Jmol view, so we can add the sequence mappings in same order.
+   * 
+   * @param viewerData
+   * @param af
+   */
+  protected void createJmolViewer(
+          final Entry<String, StructureViewerModel> viewerData, AlignFrame af)
+  {
+    final StructureViewerModel svattrib = viewerData.getValue();
+    String state = svattrib.getStateData();
+    List<String> pdbfilenames = new ArrayList<String>();
+    List<SequenceI[]> seqmaps = new ArrayList<SequenceI[]>();
+    List<String> pdbids = new ArrayList<String>();
+    StringBuilder newFileLoc = new StringBuilder(64);
+    int cp = 0, ncp, ecp;
+    Map<File, StructureData> oldFiles = svattrib.getFileData();
+    while ((ncp = state.indexOf("load ", cp)) > -1)
+    {
+      do
+      {
+        // look for next filename in load statement
+        newFileLoc.append(state.substring(cp,
+                ncp = (state.indexOf("\"", ncp + 1) + 1)));
+        String oldfilenam = state.substring(ncp,
+                ecp = state.indexOf("\"", ncp));
+        // recover the new mapping data for this old filename
+        // have to normalize filename - since Jmol and jalview do
+        // filename
+        // translation differently.
+        StructureData filedat = oldFiles.get(new File(oldfilenam));
+        newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
+        pdbfilenames.add(filedat.getFilePath());
+        pdbids.add(filedat.getPdbId());
+        seqmaps.add(filedat.getSeqList()
+                .toArray(new SequenceI[0]));
+        newFileLoc.append("\"");
+        cp = ecp + 1; // advance beyond last \" and set cursor so we can
+                      // look for next file statement.
+      } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
+    }
+    if (cp > 0)
+    {
+      // just append rest of state
+      newFileLoc.append(state.substring(cp));
+    }
+    else
+    {
+      System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
+      newFileLoc = new StringBuilder(state);
+      newFileLoc.append("; load append ");
+      for (File id : oldFiles.keySet())
+      {
+        // add this and any other pdb files that should be present in
+        // the viewer
+        StructureData filedat = oldFiles.get(id);
+        newFileLoc.append(filedat.getFilePath());
+        pdbfilenames.add(filedat.getFilePath());
+        pdbids.add(filedat.getPdbId());
+        seqmaps.add(filedat.getSeqList()
+                .toArray(new SequenceI[0]));
+        newFileLoc.append(" \"");
+        newFileLoc.append(filedat.getFilePath());
+        newFileLoc.append("\"");
+
+      }
+      newFileLoc.append(";");
+    }
+
+    if (newFileLoc.length() > 0)
+    {
+      int histbug = newFileLoc.indexOf("history = ");
+      histbug += 10;
+      int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
+      String val = (diff == -1) ? null : newFileLoc
+              .substring(histbug, diff);
+      if (val != null && val.length() >= 4)
+      {
+        if (val.contains("e"))
         {
-          String sviewid = entry.getKey();
-          Object[] svattrib = entry.getValue();
-          int[] geom = (int[]) svattrib[0];
-          String state = (String) svattrib[1];
-          Hashtable<File, Object[]> oldFiles = (Hashtable<File, Object[]>) svattrib[2];
-          final boolean useinJmolsuperpos = ((boolean[]) svattrib[3])[0], usetoColourbyseq = ((boolean[]) svattrib[3])[1], jmolColouring = ((boolean[]) svattrib[3])[2];
-          int x = geom[0], y = geom[1], width = geom[2], height = geom[3];
-          // collate the pdbfile -> sequence mappings from this view
-          Vector<String> pdbfilenames = new Vector<String>();
-          Vector<SequenceI[]> seqmaps = new Vector<SequenceI[]>();
-          Vector<String> pdbids = new Vector<String>();
-
-          // Search to see if we've already created this Jmol view
-          AppJmol comp = null;
-          JInternalFrame[] frames = null;
-          do
+          if (val.trim().equals("true"))
           {
-            try
-            {
-              frames = Desktop.desktop.getAllFrames();
-            } catch (ArrayIndexOutOfBoundsException e)
-            {
-              // occasional No such child exceptions are thrown here...
-              frames = null;
-              try
-              {
-                Thread.sleep(10);
-              } catch (Exception f)
-              {
-              }
-              ;
-            }
-          } while (frames == null);
-          // search for any Jmol windows already open from other
-          // alignment views that exactly match the stored structure state
-          for (int f = 0; comp == null && f < frames.length; f++)
+            val = "1";
+          }
+          else
           {
-            if (frames[f] instanceof AppJmol)
-            {
-              if (sviewid != null
-                      && ((AppJmol) frames[f]).getViewId().equals(sviewid))
-              {
-                // post jalview 2.4 schema includes structure view id
-                comp = (AppJmol) frames[f];
-              }
-              else if (frames[f].getX() == x && frames[f].getY() == y
-                      && frames[f].getHeight() == height
-                      && frames[f].getWidth() == width)
-              {
-                comp = (AppJmol) frames[f];
-              }
-            }
+            val = "0";
           }
+          newFileLoc.replace(histbug, diff, val);
+        }
+      }
 
-          if (comp == null)
+      final String[] pdbf = pdbfilenames.toArray(new String[pdbfilenames
+              .size()]);
+      final String[] id = pdbids.toArray(new String[pdbids.size()]);
+      final SequenceI[][] sq = seqmaps
+              .toArray(new SequenceI[seqmaps.size()][]);
+      final String fileloc = newFileLoc.toString();
+      final String sviewid = viewerData.getKey();
+      final AlignFrame alf = af;
+      final Rectangle rect = new Rectangle(svattrib.getX(),
+              svattrib.getY(), svattrib.getWidth(), svattrib.getHeight());
+      try
+      {
+        javax.swing.SwingUtilities.invokeAndWait(new Runnable()
+        {
+          @Override
+          public void run()
           {
-            // create a new Jmol window.
-            // First parse the Jmol state to translate filenames loaded into the
-            // view, and record the order in which files are shown in the Jmol
-            // view, so we can add the sequence mappings in same order.
-            StringBuffer newFileLoc = null;
-            int cp = 0, ncp, ecp;
-            while ((ncp = state.indexOf("load ", cp)) > -1)
-            {
-              if (newFileLoc == null)
-              {
-                newFileLoc = new StringBuffer();
-              }
-              do
-              {
-                // look for next filename in load statement
-                newFileLoc.append(state.substring(cp,
-                        ncp = (state.indexOf("\"", ncp + 1) + 1)));
-                String oldfilenam = state.substring(ncp,
-                        ecp = state.indexOf("\"", ncp));
-                // recover the new mapping data for this old filename
-                // have to normalize filename - since Jmol and jalview do
-                // filename
-                // translation differently.
-                Object[] filedat = oldFiles.get(new File(oldfilenam));
-                newFileLoc.append(Platform
-                        .escapeString((String) filedat[0]));
-                pdbfilenames.addElement((String) filedat[0]);
-                pdbids.addElement((String) filedat[1]);
-                seqmaps.addElement(((Vector<SequenceI>) filedat[2])
-                        .toArray(new SequenceI[0]));
-                newFileLoc.append("\"");
-                cp = ecp + 1; // advance beyond last \" and set cursor so we can
-                              // look for next file statement.
-              } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
-            }
-            if (cp > 0)
+            JalviewStructureDisplayI sview = null;
+            try
             {
-              // just append rest of state
-              newFileLoc.append(state.substring(cp));
-            }
-            else
+              // JAL-1333 note - we probably can't migrate Jmol views to UCSF
+              // Chimera!
+              sview = new StructureViewer(alf.alignPanel
+                      .getStructureSelectionManager()).createView(
+                      StructureViewer.ViewerType.JMOL, pdbf, id, sq,
+                      alf.alignPanel, svattrib, fileloc, rect, sviewid);
+              addNewStructureViewer(sview);
+            } catch (OutOfMemoryError ex)
             {
-              System.err
-                      .print("Ignoring incomplete Jmol state for PDB ids: ");
-              newFileLoc = new StringBuffer(state);
-              newFileLoc.append("; load append ");
-              for (File id : oldFiles.keySet())
+              new OOMWarning("restoring structure view for PDB id " + id,
+                      (OutOfMemoryError) ex.getCause());
+              if (sview != null && sview.isVisible())
               {
-                // add this and any other pdb files that should be present in
-                // the viewer
-                Object[] filedat = oldFiles.get(id);
-                String nfilename;
-                newFileLoc.append(((String) filedat[0]));
-                pdbfilenames.addElement((String) filedat[0]);
-                pdbids.addElement((String) filedat[1]);
-                seqmaps.addElement(((Vector<SequenceI>) filedat[2])
-                        .toArray(new SequenceI[0]));
-                newFileLoc.append(" \"");
-                newFileLoc.append((String) filedat[0]);
-                newFileLoc.append("\"");
-
+                sview.closeViewer();
+                sview.setVisible(false);
+                sview.dispose();
               }
-              newFileLoc.append(";");
             }
+          }
+        });
+      } catch (InvocationTargetException ex)
+      {
+        warn("Unexpected error when opening Jmol view.", ex);
 
-            if (newFileLoc != null)
-            {
-              int histbug = newFileLoc.indexOf("history = ");
-              histbug += 10;
-              int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";",
-                      histbug);
-              String val = (diff == -1) ? null : newFileLoc.substring(
-                      histbug, diff);
-              if (val != null && val.length() >= 4)
-              {
-                if (val.contains("e"))
-                {
-                  if (val.trim().equals("true"))
-                  {
-                    val = "1";
-                  }
-                  else
-                  {
-                    val = "0";
-                  }
-                  newFileLoc.replace(histbug, diff, val);
-                }
-              }
-              // TODO: assemble String[] { pdb files }, String[] { id for each
-              // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
-              // seqs_file 2}} from hash
-              final String[] pdbf = pdbfilenames
-                      .toArray(new String[pdbfilenames.size()]), id = pdbids
-                      .toArray(new String[pdbids.size()]);
-              final SequenceI[][] sq = seqmaps
-                      .toArray(new SequenceI[seqmaps.size()][]);
-              final String fileloc = newFileLoc.toString(), vid = sviewid;
-              final AlignFrame alf = af;
-              final java.awt.Rectangle rect = new java.awt.Rectangle(x, y,
-                      width, height);
-              try
-              {
-                javax.swing.SwingUtilities.invokeAndWait(new Runnable()
-                {
-                  @Override
-                  public void run()
-                  {
-                    JalviewStructureDisplayI sview = null;
-                    try
-                    {
-                      // JAL-1333 note - we probably can't migrate Jmol views to UCSF Chimera!
-                      sview = new StructureViewer(alf.alignPanel.getStructureSelectionManager()).createView(StructureViewer.Viewer.JMOL, pdbf, id, sq, alf.alignPanel,
-                              useinJmolsuperpos, usetoColourbyseq,
-                              jmolColouring, fileloc, rect, vid);
-                      addNewStructureViewer(sview);
-                    } catch (OutOfMemoryError ex)
-                    {
-                      new OOMWarning("restoring structure view for PDB id "
-                              + id, (OutOfMemoryError) ex.getCause());
-                      if (sview != null && sview.isVisible())
-                      {
-                        sview.closeViewer();
-                        sview.setVisible(false);
-                        sview.dispose();
-                      }
-                    }
-                  }
-                });
-              } catch (InvocationTargetException ex)
-              {
-                warn("Unexpected error when opening Jmol view.", ex);
+      } catch (InterruptedException e)
+      {
+        // e.printStackTrace();
+      }
+    }
+  }
 
-              } catch (InterruptedException e)
-              {
-                // e.printStackTrace();
-              }
-            }
+  /**
+   * Returns any open frame that matches given structure viewer data. The match
+   * is based on the unique viewId, or (for older project versions) the frame's
+   * geometry.
+   * 
+   * @param viewerData
+   * @return
+   */
+  protected StructureViewerBase findMatchingViewer(
+          Entry<String, StructureViewerModel> viewerData)
+  {
+    final String sviewid = viewerData.getKey();
+    final StructureViewerModel svattrib = viewerData.getValue();
+    StructureViewerBase comp = null;
+    JInternalFrame[] frames = getAllFrames();
+    for (JInternalFrame frame : frames)
+    {
+      if (frame instanceof StructureViewerBase)
+      {
+        /*
+         * Post jalview 2.4 schema includes structure view id
+         */
+        if (sviewid != null
+                && ((StructureViewerBase) frame).getViewId().equals(
+                        sviewid))
+        {
+          comp = (AppJmol) frame;
+          // todo: break?
+        }
+        /*
+         * Otherwise test for matching position and size of viewer frame
+         */
+        else if (frame.getX() == svattrib.getX()
+                && frame.getY() == svattrib.getY()
+                && frame.getHeight() == svattrib.getHeight()
+                && frame.getWidth() == svattrib.getWidth())
+        {
+          comp = (AppJmol) frame;
+          // todo: break?
+        }
+      }
+    }
+    return comp;
+  }
 
-          }
-          else
-          // if (comp != null)
-          {
-            // NOTE: if the jalview project is part of a shared session then
-            // view synchronization should/could be done here.
+  /**
+   * Link an AlignmentPanel to an existing structure viewer.
+   * 
+   * @param ap
+   * @param viewer
+   * @param oldFiles
+   * @param useinViewerSuperpos
+   * @param usetoColourbyseq
+   * @param viewerColouring
+   */
+  protected void linkStructureViewer(AlignmentPanel ap,
+          StructureViewerBase viewer, StructureViewerModel svattrib)
+  {
+    // NOTE: if the jalview project is part of a shared session then
+    // view synchronization should/could be done here.
 
-            // add mapping for sequences in this view to an already open Jmol
-            // instance
-            for (File id : oldFiles.keySet())
-            {
-              // add this and any other pdb files that should be present in the
-              // viewer
-              Object[] filedat = oldFiles.get(id);
-              String pdbFile = (String) filedat[0];
-              SequenceI[] seq = ((Vector<SequenceI>) filedat[2])
-                      .toArray(new SequenceI[0]);
-              comp.jmb.ssm.setMapping(seq, null, pdbFile,
-                      jalview.io.AppletFormatAdapter.FILE);
-              comp.jmb.addSequenceForStructFile(pdbFile, seq);
-            }
-            // and add the AlignmentPanel's reference to the Jmol view
-            comp.addAlignmentPanel(ap);
-            if (useinJmolsuperpos)
-            {
-              comp.useAlignmentPanelForSuperposition(ap);
-            }
-            else
-            {
-              comp.excludeAlignmentPanelForSuperposition(ap);
-            }
-            if (usetoColourbyseq)
-            {
-              comp.useAlignmentPanelForColourbyseq(ap, !jmolColouring);
-            }
-            else
-            {
-              comp.excludeAlignmentPanelForColourbyseq(ap);
-            }
-          }
+    final boolean useinViewerSuperpos = svattrib.isAlignWithPanel();
+    final boolean usetoColourbyseq = svattrib.isColourWithAlignPanel();
+    final boolean viewerColouring = svattrib.isColourByViewer();
+    Map<File, StructureData> oldFiles = svattrib.getFileData();
+
+    /*
+     * Add mapping for sequences in this view to an already open viewer
+     */
+    final AAStructureBindingModel binding = viewer.getBinding();
+    for (File id : oldFiles.keySet())
+    {
+      // add this and any other pdb files that should be present in the
+      // viewer
+      StructureData filedat = oldFiles.get(id);
+      String pdbFile = filedat.getFilePath();
+      SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
+      binding.getSsm().setMapping(seq, null, pdbFile,
+              jalview.io.AppletFormatAdapter.FILE);
+      binding.addSequenceForStructFile(pdbFile, seq);
+    }
+    // and add the AlignmentPanel's reference to the view panel
+    viewer.addAlignmentPanel(ap);
+    if (useinViewerSuperpos)
+    {
+      viewer.useAlignmentPanelForSuperposition(ap);
+    }
+    else
+    {
+      viewer.excludeAlignmentPanelForSuperposition(ap);
+    }
+    if (usetoColourbyseq)
+    {
+      viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
+    }
+    else
+    {
+      viewer.excludeAlignmentPanelForColourbyseq(ap);
+    }
+  }
+
+  /**
+   * Get all frames within the Desktop.
+   * 
+   * @return
+   */
+  protected JInternalFrame[] getAllFrames()
+  {
+    JInternalFrame[] frames = null;
+    // TODO is this necessary - is it safe - risk of hanging?
+    do
+    {
+      try
+      {
+        frames = Desktop.desktop.getAllFrames();
+      } catch (ArrayIndexOutOfBoundsException e)
+      {
+        // occasional No such child exceptions are thrown here...
+        try
+        {
+          Thread.sleep(10);
+        } catch (InterruptedException f)
+        {
         }
       }
-    }
-    // and finally return.
-    return af;
+    } while (frames == null);
+    return frames;
   }
 
   /**
@@ -3474,7 +3681,7 @@ public class Jalview2XML
 
     af.viewport.setConservationSelected(view.getConservationSelected());
     af.viewport.setShowJVSuffix(view.getShowFullId());
-    af.viewport.rightAlignIds = view.getRightAlignIds();
+    af.viewport.setRightAlignIds(view.getRightAlignIds());
     af.viewport.setFont(new java.awt.Font(view.getFontName(), view
             .getFontStyle(), view.getFontSize()));
     af.alignPanel.fontChanged();
@@ -3502,7 +3709,7 @@ public class Jalview2XML
     {
       if (view.getBgColour().startsWith("ucs"))
       {
-        cs = GetUserColourScheme(jms, view.getBgColour());
+        cs = getUserColourScheme(jms, view.getBgColour());
       }
       else if (view.getBgColour().startsWith("Annotation"))
       {
@@ -3536,10 +3743,8 @@ public class Jalview2XML
 
     af.viewport.setColourAppliesToAllGroups(true);
 
-    if (view.getShowSequenceFeatures())
-    {
-      af.viewport.showSequenceFeatures = true;
-    }
+    af.viewport.setShowSequenceFeatures(view.getShowSequenceFeatures());
+
     if (view.hasCentreColumnLabels())
     {
       af.viewport.setCentreColumnLabels(view.getCentreColumnLabels());
@@ -3606,9 +3811,14 @@ public class Jalview2XML
     // recover featre settings
     if (jms.getFeatureSettings() != null)
     {
-      af.viewport.featuresDisplayed = new Hashtable();
+      FeaturesDisplayed fdi;
+      af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
       String[] renderOrder = new String[jms.getFeatureSettings()
               .getSettingCount()];
+      Hashtable featureGroups = new Hashtable();
+      Hashtable featureColours = new Hashtable();
+      Hashtable featureOrder = new Hashtable();
+
       for (int fs = 0; fs < jms.getFeatureSettings().getSettingCount(); fs++)
       {
         Setting setting = jms.getFeatureSettings().getSetting(fs);
@@ -3635,41 +3845,42 @@ public class Jalview2XML
             gc.setColourByLabel(setting.getColourByLabel());
           }
           // and put in the feature colour table.
-          af.alignPanel.seqPanel.seqCanvas.getFeatureRenderer().setColour(
-                  setting.getType(), gc);
+          featureColours.put(setting.getType(), gc);
         }
         else
         {
-          af.alignPanel.seqPanel.seqCanvas.getFeatureRenderer().setColour(
-                  setting.getType(),
+          featureColours.put(setting.getType(),
                   new java.awt.Color(setting.getColour()));
         }
         renderOrder[fs] = setting.getType();
         if (setting.hasOrder())
         {
-          af.alignPanel.seqPanel.seqCanvas.getFeatureRenderer().setOrder(
-                  setting.getType(), setting.getOrder());
+          featureOrder.put(setting.getType(), setting.getOrder());
         }
         else
         {
-          af.alignPanel.seqPanel.seqCanvas.getFeatureRenderer().setOrder(
-                  setting.getType(),
-                  fs / jms.getFeatureSettings().getSettingCount());
+          featureOrder.put(setting.getType(), new Float(fs
+                  / jms.getFeatureSettings().getSettingCount()));
         }
         if (setting.getDisplay())
         {
-          af.viewport.featuresDisplayed.put(setting.getType(), new Integer(
-                  setting.getColour()));
+          fdi.setVisible(setting.getType());
         }
       }
-      af.alignPanel.seqPanel.seqCanvas.getFeatureRenderer().renderOrder = renderOrder;
-      Hashtable fgtable;
-      af.alignPanel.seqPanel.seqCanvas.getFeatureRenderer().featureGroups = fgtable = new Hashtable();
+      Hashtable fgtable = new Hashtable();
       for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++)
       {
         Group grp = jms.getFeatureSettings().getGroup(gs);
         fgtable.put(grp.getName(), new Boolean(grp.getDisplay()));
       }
+      // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
+      // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
+      // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
+      FeatureRendererSettings frs = new FeatureRendererSettings(
+              renderOrder, fgtable, featureColours, 1.0f, featureOrder);
+      af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
+              .transferSettings(frs);
+
     }
 
     if (view.getHiddenColumnsCount() > 0)
@@ -3759,7 +3970,7 @@ public class Jalview2XML
           {
             cs = new AnnotationColourGradient(
                     annAlignment.getAlignmentAnnotation()[i],
-                    GetUserColourScheme(jms,
+                    getUserColourScheme(jms,
                             viewAnnColour.getColourScheme()),
                     viewAnnColour.getAboveThreshold());
           }
@@ -3970,7 +4181,7 @@ public class Jalview2XML
     return false;
   }
 
-  public void AddToSkipList(AlignFrame af)
+  public void addToSkipList(AlignFrame af)
   {
     if (skipList == null)
     {
@@ -4051,7 +4262,7 @@ public class Jalview2XML
       // need to create or add a new dataset sequence reference to this sequence
       if (sqid != null)
       {
-        dsq = (jalview.datamodel.SequenceI) seqRefIds.get(sqid);
+        dsq = seqRefIds.get(sqid);
       }
       // check again
       if (dsq == null)
@@ -4247,7 +4458,7 @@ public class Jalview2XML
           /**
            * recover from hash
            */
-          jmap.setTo((SequenceI) seqRefIds.get(dsfor));
+          jmap.setTo(seqRefIds.get(dsfor));
         }
         else
         {
@@ -4306,7 +4517,7 @@ public class Jalview2XML
           boolean keepSeqRefs)
   {
     initSeqRefs();
-    jalview.schemabinding.version2.JalviewModel jm = SaveState(ap, null,
+    jalview.schemabinding.version2.JalviewModel jm = saveState(ap, null,
             null);
 
     if (!keepSeqRefs)
@@ -4329,7 +4540,7 @@ public class Jalview2XML
 
     viewportsAdded = new Hashtable();
 
-    AlignFrame af = LoadFromObject(jm, null, false, null);
+    AlignFrame af = loadFromObject(jm, null, false, null);
     af.alignPanels.clear();
     af.closeMenuItem_actionPerformed(true);
 
@@ -4463,14 +4674,14 @@ public class Jalview2XML
         // register sequence object so the XML parser can recover it.
         if (seqRefIds == null)
         {
-          seqRefIds = new Hashtable();
+          seqRefIds = new HashMap<String, SequenceI>();
         }
         if (seqsToIds == null)
         {
-          seqsToIds = new IdentityHashMap();
+          seqsToIds = new IdentityHashMap<SequenceI, String>();
         }
-        seqRefIds.put(jv2vobj.get(jvobj).toString(), jvobj);
-        seqsToIds.put(jvobj, id);
+        seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
+        seqsToIds.put((SequenceI) jvobj, id);
       }
       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
       {
@@ -4531,5 +4742,4 @@ public class Jalview2XML
   {
     skipList = skipList2;
   }
-
 }
index 9263cd9..586e2fa 100755 (executable)
@@ -40,6 +40,7 @@ import jalview.schemes.ResidueProperties;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.MessageManager;
 import jalview.util.jarInputStreamProvider;
+import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
 
 import java.io.InputStreamReader;
 import java.util.Hashtable;
@@ -411,29 +412,31 @@ public class Jalview2XML_V1
     }
 
     af.viewport.setColourAppliesToAllGroups(true);
-    af.viewport.showSequenceFeatures = view.getShowSequenceFeatures();
+    af.viewport.setShowSequenceFeatures(view.getShowSequenceFeatures());
 
     if (jms.getFeatureSettings() != null)
     {
-      af.viewport.featuresDisplayed = new Hashtable();
+      Hashtable featuresDisplayed = new Hashtable();
+      Hashtable featureColours = new Hashtable();
       String[] renderOrder = new String[jms.getFeatureSettings()
               .getSettingCount()];
       for (int fs = 0; fs < jms.getFeatureSettings().getSettingCount(); fs++)
       {
         Setting setting = jms.getFeatureSettings().getSetting(fs);
 
-        af.alignPanel.seqPanel.seqCanvas.getFeatureRenderer().setColour(
+        featureColours.put(
                 setting.getType(), new java.awt.Color(setting.getColour()));
 
         renderOrder[fs] = setting.getType();
 
         if (setting.getDisplay())
         {
-          af.viewport.featuresDisplayed.put(setting.getType(), new Integer(
+          featuresDisplayed.put(setting.getType(), new Integer(
                   setting.getColour()));
         }
       }
-      af.alignPanel.seqPanel.seqCanvas.getFeatureRenderer().renderOrder = renderOrder;
+      FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder, new Hashtable(), featureColours, 1.0f, null);
+      af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer().transferSettings(frs);
     }
 
     af.setMenusFromViewport(af.viewport);
index f68b585..0dcde11 100644 (file)
@@ -10,6 +10,8 @@ public class JalviewChimeraBindingModel extends JalviewChimeraBinding
 {
   private ChimeraViewFrame cvf;
 
+  private FeatureRenderer fr = null;
+
   public JalviewChimeraBindingModel(ChimeraViewFrame chimeraViewFrame,
           StructureSelectionManager ssm, PDBEntry[] pdbentry,
           SequenceI[][] sequenceIs, String[][] chains, String protocol)
@@ -18,19 +20,17 @@ public class JalviewChimeraBindingModel extends JalviewChimeraBinding
     cvf = chimeraViewFrame;
   }
 
-  FeatureRenderer fr = null;
-
   @Override
-  public jalview.api.FeatureRenderer getFeatureRenderer(
+  public FeatureRenderer getFeatureRenderer(
           AlignmentViewPanel alignment)
   {
-    AlignmentPanel ap = (alignment == null) ? cvf.ap
+    AlignmentPanel ap = (alignment == null) ? cvf.getAlignmentPanel()
             : (AlignmentPanel) alignment;
-    if (ap.av.showSequenceFeatures)
+    if (ap.av.isShowSequenceFeatures())
     {
       if (fr == null)
       {
-        fr = ap.cloneFeatureRenderer();
+        fr = (jalview.gui.FeatureRenderer) ap.cloneFeatureRenderer();
       }
       else
       {
@@ -47,10 +47,10 @@ public class JalviewChimeraBindingModel extends JalviewChimeraBinding
   {
     return new SequenceRenderer(((AlignmentPanel) alignment).av);
   }
+
   @Override
   public void refreshGUI()
   {
-    // appJmolWindow.repaint();
     javax.swing.SwingUtilities.invokeLater(new Runnable()
     {
       public void run()
@@ -63,7 +63,7 @@ public class JalviewChimeraBindingModel extends JalviewChimeraBinding
 
   public void updateColours(Object source)
   {
-    AlignmentPanel ap = (AlignmentPanel) source, topap;
+    AlignmentPanel ap = (AlignmentPanel) source;
     // ignore events from panels not used to colour this view
     if (!cvf.isUsedforcolourby(ap))
     {
@@ -71,27 +71,22 @@ public class JalviewChimeraBindingModel extends JalviewChimeraBinding
     }
     if (!isLoadingFromArchive())
     {
-      colourBySequence(ap.av.getShowSequenceFeatures(), ap);
+      colourBySequence(ap.av.isShowSequenceFeatures(), ap);
     }
   }
+
   @Override
   public void releaseReferences(Object svl)
   {
-    // TODO Auto-generated method stub
-
   }
 
   @Override
   protected void releaseUIResources()
   {
-    // TODO Auto-generated method stub
-
   }
 
   @Override
   public void refreshPdbEntries()
   {
-    // TODO Auto-generated method stub
-
   }
 }
index e433243..b4e0e00 100644 (file)
  */
 package jalview.gui;
 
+import jalview.util.MessageManager;
+
 import java.awt.Color;
 import java.awt.Font;
 import java.awt.Rectangle;
 import java.awt.event.ActionListener;
 
+import javax.swing.AbstractButton;
 import javax.swing.JButton;
 import javax.swing.JComponent;
 import javax.swing.JLabel;
@@ -209,4 +212,24 @@ public final class JvSwingUtils
     }
   }
 
+  public static void jvInitComponent(AbstractButton comp, String i18nString)
+  {
+    setColorAndFont(comp);
+    if (i18nString != null && !i18nString.isEmpty())
+    {
+      comp.setText(MessageManager.getString(i18nString));
+    }
+  }
+
+  public static void jvInitComponent(JComponent comp)
+  {
+    setColorAndFont(comp);
+  }
+
+  private static void setColorAndFont(JComponent comp)
+  {
+    comp.setBackground(Color.white);
+    comp.setFont(JvSwingUtils.getLabelFont());
+  }
+
 }
index e8555f7..5df60d2 100755 (executable)
@@ -67,7 +67,7 @@ public class OverviewPanel extends JPanel implements Runnable
   // main visible SeqCanvas
   SequenceRenderer sr;
 
-  FeatureRenderer fr;
+  jalview.renderer.seqfeatures.FeatureRenderer fr;
 
   /**
    * Creates a new OverviewPanel object.
@@ -85,7 +85,7 @@ public class OverviewPanel extends JPanel implements Runnable
     sr.renderGaps = false;
     sr.forOverview = true;
     fr = new FeatureRenderer(ap);
-
+    
     // scale the initial size of overviewpanel to shape of alignment
     float initialScale = (float) av.getAlignment().getWidth()
             / (float) av.getAlignment().getHeight();
@@ -254,9 +254,9 @@ public class OverviewPanel extends JPanel implements Runnable
   {
     miniMe = null;
 
-    if (av.showSequenceFeatures)
+    if (av.isShowSequenceFeatures())
     {
-      fr.transferSettings(ap.seqPanel.seqCanvas.getFeatureRenderer());
+      fr.transferSettings(ap.getSeqPanel().seqCanvas.getFeatureRenderer());
     }
 
     int alwidth = av.getAlignment().getWidth();
@@ -344,7 +344,7 @@ public class OverviewPanel extends JPanel implements Runnable
         {
           color = sr.getResidueBoxColour(seq, lastcol).getRGB();
 
-          if (av.showSequenceFeatures)
+          if (av.isShowSequenceFeatures())
           {
             color = fr.findFeatureColour(color, seq, lastcol);
           }
index c2bee83..8565f9f 100644 (file)
@@ -29,6 +29,7 @@ import jalview.commands.EditCommand.Action;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Annotation;
+import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.DBRefEntry;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.Sequence;
@@ -140,6 +141,8 @@ public class PopupMenu extends JPopupMenu
   JMenuItem sequenceDetails = new JMenuItem();
 
   JMenuItem sequenceSelDetails = new JMenuItem();
+  
+  JMenuItem makeReferenceSeq = new JMenuItem();
 
   JMenuItem chooseAnnotations = new JMenuItem();
 
@@ -220,6 +223,8 @@ public class PopupMenu extends JPopupMenu
 
   JMenu groupLinksMenu;
 
+  JMenuItem hideInsertions = new JMenuItem();
+
   /**
    * Creates a new PopupMenu object.
    * 
@@ -322,6 +327,12 @@ public class PopupMenu extends JPopupMenu
     if (seq != null)
     {
       sequenceMenu.setText(sequence.getName());
+      if (seq == ap.av.getAlignment().getSeqrep())
+      {
+        makeReferenceSeq.setText("Unmark representative");
+      } else {
+        makeReferenceSeq.setText("Mark as representative");
+      }
 
       if (seq.getDatasetSequence().getPDBId() != null
               && seq.getDatasetSequence().getPDBId().size() > 0)
@@ -369,7 +380,6 @@ public class PopupMenu extends JPopupMenu
         }
         // structureMenu.remove(colStructureMenu);
       }
-
       if (ap.av.getAlignment().isNucleotide() == true)
       {
         AlignmentAnnotation[] aa = ap.av.getAlignment()
@@ -441,7 +451,6 @@ public class PopupMenu extends JPopupMenu
             }
           }
         }
-
       }
 
       menuItem = new JMenuItem(
@@ -1507,7 +1516,28 @@ public class PopupMenu extends JPopupMenu
         editSequence_actionPerformed(actionEvent);
       }
     });
+    makeReferenceSeq.setText(MessageManager
+            .getString("label.mark_as_representative"));
+    makeReferenceSeq.addActionListener(new ActionListener()
+    {
+      
+      @Override
+      public void actionPerformed(ActionEvent actionEvent)
+      {
+        makeReferenceSeq_actionPerformed(actionEvent);
+        
+      }
+    });
+    hideInsertions.setText(MessageManager.getString("label.hide_insertions"));
+    hideInsertions.addActionListener(new ActionListener()
+    {
 
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        hideInsertions_actionPerformed(e);
+      }
+    });
     /*
      * annotationMenuItem.setText("By Annotation");
      * annotationMenuItem.addActionListener(new ActionListener() { public void
@@ -1517,7 +1547,11 @@ public class PopupMenu extends JPopupMenu
     groupMenu.add(sequenceSelDetails);
     add(groupMenu);
     add(sequenceMenu);
-    this.add(structureMenu);
+    add(structureMenu);
+    if (sequence!=null)
+    {
+      add(hideInsertions);
+    }
     // annotations configuration panel suppressed for now
     // groupMenu.add(chooseAnnotations);
 
@@ -1539,6 +1573,7 @@ public class PopupMenu extends JPopupMenu
     groupMenu.add(jMenu1);
     sequenceMenu.add(sequenceName);
     sequenceMenu.add(sequenceDetails);
+    sequenceMenu.add(makeReferenceSeq);
     colourMenu.add(textColour);
     colourMenu.add(noColourmenuItem);
     colourMenu.add(clustalColour);
@@ -1913,6 +1948,43 @@ public class PopupMenu extends JPopupMenu
     refresh();
   }
 
+  protected void makeReferenceSeq_actionPerformed(ActionEvent actionEvent)
+  {
+    if (!ap.av.getAlignment().hasSeqrep())
+    {
+      // initialise the display flags so the user sees something happen
+      ap.av.setDisplayReferenceSeq(true);
+      ap.av.setColourByReferenceSeq(true);
+      ap.av.getAlignment().setSeqrep(sequence);
+    }
+    else
+    {
+      if (ap.av.getAlignment().getSeqrep() == sequence)
+      {
+        ap.av.getAlignment().setSeqrep(null);
+      }
+      else
+      {
+        ap.av.getAlignment().setSeqrep(sequence);
+      }
+    }
+    refresh();
+  }
+
+  protected void hideInsertions_actionPerformed(ActionEvent actionEvent)
+  {
+    if (sequence != null)
+    {
+      ColumnSelection cs = ap.av.getColumnSelection();
+      if (cs == null)
+      {
+        cs = new ColumnSelection();
+      }
+      cs.hideInsertionsFor(sequence);
+      ap.av.setColumnSelection(cs);
+    }
+    refresh();
+  }
   protected void sequenceSelectionDetails_actionPerformed()
   {
     createSequenceDetailsReport(ap.av.getSequenceSelection());
@@ -1943,7 +2015,9 @@ public class PopupMenu extends JPopupMenu
                       true,
                       true,
                       false,
-                      (ap.seqPanel.seqCanvas.fr != null) ? ap.seqPanel.seqCanvas.fr.minmax
+                      (ap.getSeqPanel().seqCanvas.fr != null) ? ap
+                              .getSeqPanel().seqCanvas.fr
+                              .getMinMax()
                               : null);
       contents.append("</p>");
     }
@@ -2660,7 +2734,7 @@ public class PopupMenu extends JPopupMenu
     System.arraycopy(features, 0, tfeatures, 0, rsize);
     features = tfeatures;
     seqs = rseqs;
-    if (ap.seqPanel.seqCanvas.getFeatureRenderer().amendFeatures(seqs,
+    if (ap.getSeqPanel().seqCanvas.getFeatureRenderer().amendFeatures(seqs,
             features, true, ap))
     {
       ap.alignFrame.setShowSeqFeatures(true);
index bdc83e5..c024b1c 100755 (executable)
@@ -23,7 +23,7 @@ package jalview.gui;
 import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder;
 import jalview.bin.Cache;
 import jalview.gui.Help.HelpId;
-import jalview.gui.StructureViewer.Viewer;
+import jalview.gui.StructureViewer.ViewerType;
 import jalview.io.JalviewFileChooser;
 import jalview.io.JalviewFileView;
 import jalview.jbgui.GPreferences;
@@ -289,7 +289,7 @@ public class Preferences extends GPreferences
     addTempFactor.setSelected(Cache.getDefault(ADD_TEMPFACT_ANN, false));
     addTempFactor.setEnabled(structSelected);
     structViewer.setSelectedItem(Cache.getDefault(STRUCTURE_DISPLAY,
-            Viewer.JMOL.name()));
+            ViewerType.JMOL.name()));
     chimeraPath.setText(Cache.getDefault(CHIMERA_PATH, ""));
     chimeraPath.addActionListener(new ActionListener()
     {
@@ -892,7 +892,7 @@ public class Preferences extends GPreferences
   @Override
   protected void structureViewer_actionPerformed(String selectedItem)
   {
-    if (!selectedItem.equals(Viewer.CHIMERA.name()))
+    if (!selectedItem.equals(ViewerType.CHIMERA.name()))
     {
       return;
     }
index ab0a0b8..c8a0ec7 100755 (executable)
@@ -107,7 +107,7 @@ public class RedundancyPanel extends GSliderPanel implements Runnable
     {
       public void internalFrameClosing(InternalFrameEvent evt)
       {
-        ap.idPanel.idCanvas.setHighlighted(null);
+        ap.getIdPanel().getIdCanvas().setHighlighted(null);
       }
     });
 
@@ -197,7 +197,7 @@ public class RedundancyPanel extends GSliderPanel implements Runnable
         redundantSequences.add(originalSequences[i]);
       }
     }
-    ap.idPanel.idCanvas.setHighlighted(redundantSequences);
+    ap.getIdPanel().getIdCanvas().setHighlighted(redundantSequences);
   }
 
   /**
index f2c5154..32773b9 100755 (executable)
  */
 package jalview.gui;
 
-import java.awt.*;
-import java.awt.event.*;
-
-import javax.swing.*;
-
-import jalview.datamodel.*;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
 import jalview.util.MessageManager;
 
+import java.awt.Color;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.SwingUtilities;
+import javax.swing.ToolTipManager;
+
 /**
  * DOCUMENT ME!
  * 
@@ -114,7 +128,7 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
         });
         pop.add(item);
 
-        if (av.getColumnSelection().getHiddenColumns().size() > 1)
+        if (av.getColumnSelection().hasHiddenColumns())
         {
           item = new JMenuItem(
                   MessageManager.getString("action.reveal_all"));
@@ -327,7 +341,7 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
   {
     if (mouseDragging)
     {
-      ap.seqPanel.scrollCanvas(null);
+      ap.getSeqPanel().scrollCanvas(null);
     }
   }
 
@@ -335,7 +349,7 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
   {
     if (mouseDragging)
     {
-      ap.seqPanel.scrollCanvas(evt);
+      ap.getSeqPanel().scrollCanvas(evt);
     }
   }
 
@@ -355,25 +369,24 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
     res = av.getColumnSelection().adjustForHiddenColumns(res);
 
     reveal = null;
-    for (int i = 0; i < av.getColumnSelection().getHiddenColumns().size(); i++)
+    if (av.getColumnSelection().getHiddenColumns() != null)
     {
-      int[] region = (int[]) av.getColumnSelection().getHiddenColumns()
-              .elementAt(i);
-      if (res + 1 == region[0] || res - 1 == region[1])
+      for (int[] region : av.getColumnSelection().getHiddenColumns())
       {
-        reveal = region;
-        ToolTipManager.sharedInstance().registerComponent(this);
-        this.setToolTipText(MessageManager
-                .getString("label.reveal_hidden_columns"));
-        break;
-      }
-      else
-      {
-        this.setToolTipText(null);
+        if (res + 1 == region[0] || res - 1 == region[1])
+        {
+          reveal = region;
+          ToolTipManager.sharedInstance().registerComponent(this);
+          this.setToolTipText(MessageManager
+                  .getString("label.reveal_hidden_columns"));
+          break;
+        }
+        else
+        {
+          this.setToolTipText(null);
+        }
       }
-
     }
-
     repaint();
   }
 
@@ -466,18 +479,18 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
         }
 
         gg.drawLine(
-                (int) (((i - startx - 1) * av.charWidth) + (av.charWidth / 2)),
+                ((i - startx - 1) * av.charWidth) + (av.charWidth / 2),
                 y + 2,
-                (int) (((i - startx - 1) * av.charWidth) + (av.charWidth / 2)),
+                ((i - startx - 1) * av.charWidth) + (av.charWidth / 2),
                 y + (fm.getDescent() * 2));
 
       }
       else
       {
         gg.drawLine(
-                (int) (((i - startx - 1) * av.charWidth) + (av.charWidth / 2)),
+                ((i - startx - 1) * av.charWidth) + (av.charWidth / 2),
                 y + fm.getDescent(),
-                (int) (((i - startx - 1) * av.charWidth) + (av.charWidth / 2)),
+                ((i - startx - 1) * av.charWidth) + (av.charWidth / 2),
                 y + (fm.getDescent() * 2));
       }
     }
@@ -486,7 +499,8 @@ public class ScalePanel extends JPanel implements MouseMotionListener,
     {
       gg.setColor(Color.blue);
       int res;
-      if (av.getShowHiddenMarkers())
+      if (av.getShowHiddenMarkers()
+              && av.getColumnSelection().getHiddenColumns() != null)
       {
         for (int i = 0; i < av.getColumnSelection().getHiddenColumns()
                 .size(); i++)
index d935eb7..641efcb 100644 (file)
@@ -22,10 +22,14 @@ package jalview.gui;
 
 import org.jmol.api.*;
 
+import jalview.jbgui.GStructureViewer;
+
 import java.awt.*;
 import java.awt.event.*;
+
 import javax.swing.*;
 import javax.swing.text.*;
+
 import java.util.Vector;
 
 import org.jmol.i18n.GT;
@@ -53,7 +57,7 @@ public final class ScriptWindow extends JPanel implements ActionListener,
 
   JmolViewer viewer;
 
-  AppJmol appJmol;
+  GStructureViewer appJmol;
 
   public ScriptWindow(AppJmol appJmol)
   {
index d73ee70..5032f69 100755 (executable)
@@ -34,6 +34,7 @@ import java.awt.Graphics2D;
 import java.awt.RenderingHints;
 import java.awt.Shape;
 import java.awt.image.BufferedImage;
+import java.util.List;
 
 import javax.swing.JComponent;
 
@@ -573,7 +574,7 @@ public class SeqCanvas extends JComponent
 
       drawPanel(g, startRes, endx, 0, al.getHeight(), ypos);
 
-      if (av.showAnnotation)
+      if (av.isShowAnnotation())
       {
         g.translate(0, cHeight + ypos + 3);
         if (annotations == null)
@@ -598,7 +599,7 @@ public class SeqCanvas extends JComponent
 
   int getAnnotationHeight()
   {
-    if (!av.showAnnotation)
+    if (!av.isShowAnnotation())
     {
       return 0;
     }
@@ -627,7 +628,8 @@ public class SeqCanvas extends JComponent
    * @param offset
    *          DOCUMENT ME!
    */
-  void drawPanel(Graphics g1, int startRes, int endRes, int startSeq,
+  public void drawPanel(Graphics g1, int startRes, int endRes,
+          int startSeq,
           int endSeq, int offset)
   {
     if (!av.hasHiddenColumns())
@@ -636,7 +638,7 @@ public class SeqCanvas extends JComponent
     }
     else
     {
-      java.util.Vector regions = av.getColumnSelection().getHiddenColumns();
+      List<int[]> regions = av.getColumnSelection().getHiddenColumns();
 
       int screenY = 0;
       int blockStart = startRes;
@@ -644,7 +646,7 @@ public class SeqCanvas extends JComponent
 
       for (int i = 0; regions != null && i < regions.size(); i++)
       {
-        int[] region = (int[]) regions.elementAt(i);
+        int[] region = regions.get(i);
         int hideStart = region[0];
         int hideEnd = region[1];
 
@@ -710,7 +712,7 @@ public class SeqCanvas extends JComponent
       sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
               startRes, endRes, offset + ((i - startSeq) * av.charHeight));
 
-      if (av.showSequenceFeatures)
+      if (av.isShowSequenceFeatures())
       {
         fr.drawSequence(g, nextSeq, startRes, endRes, offset
                 + ((i - startSeq) * av.charHeight));
index c10a4a9..7c6a202 100644 (file)
@@ -243,44 +243,6 @@ public class SeqPanel extends JPanel implements MouseListener,
     return seq;
   }
 
-  SequenceFeature[] findFeaturesAtRes(SequenceI sequence, int res)
-  {
-    Vector tmp = new Vector();
-    SequenceFeature[] features = sequence.getSequenceFeatures();
-    if (features != null)
-    {
-      for (int i = 0; i < features.length; i++)
-      {
-        if (av.featuresDisplayed == null
-                || !av.featuresDisplayed.containsKey(features[i].getType()))
-        {
-          continue;
-        }
-
-        if (features[i].featureGroup != null
-                && seqCanvas.fr.featureGroups != null
-                && seqCanvas.fr.featureGroups
-                        .containsKey(features[i].featureGroup)
-                && !((Boolean) seqCanvas.fr.featureGroups
-                        .get(features[i].featureGroup)).booleanValue())
-        {
-          continue;
-        }
-
-        if ((features[i].getBegin() <= res)
-                && (features[i].getEnd() >= res))
-        {
-          tmp.addElement(features[i]);
-        }
-      }
-    }
-
-    features = new SequenceFeature[tmp.size()];
-    tmp.copyInto(features);
-
-    return features;
-  }
-
   void endEditing()
   {
     if (editCommand != null && editCommand.getSize() > 0)
@@ -743,14 +705,14 @@ public class SeqPanel extends JPanel implements MouseListener,
     }
 
     // use aa to see if the mouse pointer is on a
-    if (av.showSequenceFeatures)
+    if (av.isShowSequenceFeatures())
     {
       int rpos;
-      SequenceFeature[] features = findFeaturesAtRes(
+      List<SequenceFeature> features = ap.getFeatureRenderer().findFeaturesAtRes(
               sequence.getDatasetSequence(),
               rpos = sequence.findPosition(res));
       seqARep.appendFeatures(tooltipText, rpos, features,
-              this.ap.seqPanel.seqCanvas.fr.minmax);
+              this.ap.getSeqPanel().seqCanvas.fr.getMinMax());
     }
     if (tooltipText.length() == 6) // <html></html>
     {
@@ -1389,21 +1351,21 @@ public class SeqPanel extends JPanel implements MouseListener,
         av.setSelectionGroup(null);
       }
 
-      SequenceFeature[] features = findFeaturesAtRes(
+      List<SequenceFeature> features = seqCanvas.getFeatureRenderer().findFeaturesAtRes(
               sequence.getDatasetSequence(),
               sequence.findPosition(findRes(evt)));
 
-      if (features != null && features.length > 0)
+      if (features != null && features.size()> 0)
       {
         SearchResults highlight = new SearchResults();
-        highlight.addResult(sequence, features[0].getBegin(),
-                features[0].getEnd());
+        highlight.addResult(sequence, features.get(0).getBegin(),
+                features.get(0).getEnd());
         seqCanvas.highlightSearchResults(highlight);
       }
-      if (features != null && features.length > 0)
+      if (features != null && features.size()> 0)
       {
         seqCanvas.getFeatureRenderer().amendFeatures(new SequenceI[]
-        { sequence }, features, false, ap);
+        { sequence }, features.toArray(new SequenceFeature[features.size()]), false, ap);
 
         seqCanvas.highlightSearchResults(null);
       }
@@ -1518,16 +1480,16 @@ public class SeqPanel extends JPanel implements MouseListener,
 
     if (javax.swing.SwingUtilities.isRightMouseButton(evt))
     {
-      SequenceFeature[] allFeatures = findFeaturesAtRes(
+      List<SequenceFeature> allFeatures = ap.getFeatureRenderer().findFeaturesAtRes(
               sequence.getDatasetSequence(), sequence.findPosition(res));
       Vector links = new Vector();
-      for (int i = 0; i < allFeatures.length; i++)
+      for (SequenceFeature sf:allFeatures)
       {
-        if (allFeatures[i].links != null)
+        if (sf.links != null)
         {
-          for (int j = 0; j < allFeatures[i].links.size(); j++)
+          for (int j = 0; j < sf.links.size(); j++)
           {
-            links.addElement(allFeatures[i].links.elementAt(j));
+            links.addElement(sf.links.elementAt(j));
           }
         }
       }
index bcbebbd..438ef00 100755 (executable)
@@ -20,6 +20,7 @@
  */
 package jalview.gui;
 
+import jalview.api.FeatureRenderer;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
@@ -80,11 +81,12 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
     // If EPS graphics, stringWidth will be a double, not an int
     double dwidth = fm.getStringBounds("M", g).getWidth();
 
-    monospacedFont = (dwidth == fm.getStringBounds("|", g).getWidth() && (float) av.charWidth == dwidth);
+    monospacedFont = (dwidth == fm.getStringBounds("|", g).getWidth() && av.charWidth == dwidth);
 
     this.renderGaps = renderGaps;
   }
 
+  @Override
   public Color getResidueBoxColour(SequenceI seq, int i)
   {
     allGroups = av.getAlignment().findAllGroups(seq);
@@ -105,6 +107,31 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
   }
 
   /**
+   * Get the residue colour at the given sequence position - as determined by
+   * the sequence group colour (if any), else the colour scheme, possibly
+   * overridden by a feature colour.
+   * 
+   * @param seq
+   * @param position
+   * @param fr
+   * @return
+   */
+  @Override
+  public Color getResidueColour(final SequenceI seq, int position,
+          FeatureRenderer fr)
+  {
+    // TODO replace 8 or so code duplications with calls to this method
+    // (refactored as needed)
+    Color col = getResidueBoxColour(seq, position);
+
+    if (fr != null)
+    {
+      col = fr.findFeatureColour(col, seq, position);
+    }
+    return col;
+  }
+
+  /**
    * DOCUMENT ME!
    * 
    * @param cs
@@ -188,7 +215,9 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
           int y1)
   {
     if (seq == null)
+     {
       return; // fix for racecondition
+    }
     int i = start;
     int length = seq.getLength();
 
@@ -290,6 +319,7 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
     }
     else
     {
+      boolean srep = av.isDisplayReferenceSeq();
       boolean getboxColour = false;
       for (int i = start; i <= end; i++)
       {
@@ -335,7 +365,7 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
           if (currentSequenceGroup.getShowNonconserved()) // todo optimize
           {
             // todo - use sequence group consensus
-            s = getDisplayChar(av.getAlignmentConsensusAnnotation(), i, s,
+            s = getDisplayChar(srep, i, s,
                     '.');
 
           }
@@ -378,7 +408,7 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
           }
           if (av.getShowUnconserved())
           {
-            s = getDisplayChar(av.getAlignmentConsensusAnnotation(), i, s,
+            s = getDisplayChar(srep, i, s,
                     '.');
 
           }
@@ -393,10 +423,12 @@ public class SequenceRenderer implements jalview.api.SequenceRenderer
     }
   }
 
-  private char getDisplayChar(AlignmentAnnotation consensus, int position,
+  private char getDisplayChar(final boolean usesrep, int position,
           char s, char c)
   {
-    char conschar = consensus.annotations[position].displayCharacter
+    // TODO - use currentSequenceGroup rather than alignemnt 
+    // currentSequenceGroup.getConsensus()
+    char conschar = (usesrep) ? av.getAlignment().getSeqrep().getCharAt(position) : av.getAlignmentConsensusAnnotation().annotations[position].displayCharacter
             .charAt(0);
     if (conschar != '-' && s == conschar)
     {
index 81b3bd3..cbd0d58 100755 (executable)
@@ -300,7 +300,7 @@ public class SliderPanel extends GSliderPanel
       }
     }
 
-    ap.seqPanel.seqCanvas.repaint();
+    ap.getSeqPanel().seqCanvas.repaint();
   }
 
   /**
index 14e1a0e..76565ce 100755 (executable)
  */
 package jalview.gui;
 
-import java.awt.*;
-import java.awt.event.*;
-
-import javax.swing.*;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Image;
+import java.awt.MediaTracker;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import javax.swing.JInternalFrame;
+import javax.swing.JLayeredPane;
+import javax.swing.JPanel;
+import javax.swing.JTextPane;
 import javax.swing.event.HyperlinkEvent;
 import javax.swing.event.HyperlinkListener;
 
@@ -172,8 +182,8 @@ public class SplashScreen extends JPanel implements Runnable,
       authlist.setSize(new Dimension(750, 375));
       add(authlist, BorderLayout.CENTER);
       revalidate();
-      iframe.setBounds((int) ((Desktop.instance.getWidth() - 750) / 2),
-              (int) ((Desktop.instance.getHeight() - 375) / 2), 750,
+      iframe.setBounds((Desktop.instance.getWidth() - 750) / 2,
+              (Desktop.instance.getHeight() - 375) / 2, 750,
               authlist.getHeight() + iconimg.getHeight());
       iframe.validate();
       iframe.setVisible(true);
index 22f12ea..8ff4c70 100644 (file)
@@ -24,6 +24,7 @@ import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
+import jalview.datamodel.StructureViewerModel;
 import jalview.structure.StructureSelectionManager;
 
 import java.awt.Rectangle;
@@ -41,19 +42,19 @@ public class StructureViewer
 {
   StructureSelectionManager ssm;
 
-  public enum Viewer
+  public enum ViewerType
   {
     JMOL, CHIMERA
   };
 
-  public Viewer getViewerType()
+  public ViewerType getViewerType()
   {
     String viewType = Cache.getDefault(Preferences.STRUCTURE_DISPLAY,
-            Viewer.JMOL.name());
-    return Viewer.valueOf(viewType);
+            ViewerType.JMOL.name());
+    return ViewerType.valueOf(viewType);
   }
 
-  public void setViewerType(Viewer type)
+  public void setViewerType(ViewerType type)
   {
     Cache.setProperty(Preferences.STRUCTURE_DISPLAY, type.name());
   }
@@ -69,15 +70,15 @@ public class StructureViewer
     return viewStructures(getViewerType(), ap, pr, collateForPDB);
   }
 
-  public JalviewStructureDisplayI viewStructures(Viewer viewerType,
+  public JalviewStructureDisplayI viewStructures(ViewerType viewerType,
           AlignmentPanel ap, PDBEntry[] pr, SequenceI[][] collateForPDB)
   {
     JalviewStructureDisplayI sview = null;
-    if (viewerType.equals(Viewer.JMOL))
+    if (viewerType.equals(ViewerType.JMOL))
     {
       sview = new AppJmol(ap, pr, ap.av.collateForPDB(pr));
     }
-    else if (viewerType.equals(Viewer.CHIMERA))
+    else if (viewerType.equals(ViewerType.CHIMERA))
     {
       sview = new ChimeraViewFrame(ap, pr, ap.av.collateForPDB(pr));
     }
@@ -89,15 +90,15 @@ public class StructureViewer
     return sview;
   }
 
-  public JalviewStructureDisplayI viewStructures(Viewer viewerType,
+  public JalviewStructureDisplayI viewStructures(ViewerType viewerType,
           AlignmentPanel ap, PDBEntry pr, SequenceI[] collateForPDB)
   {
     JalviewStructureDisplayI sview = null;
-    if (viewerType.equals(Viewer.JMOL))
+    if (viewerType.equals(ViewerType.JMOL))
     {
       sview = new AppJmol(pr, collateForPDB, null, ap);
     }
-    else if (viewerType.equals(Viewer.CHIMERA))
+    else if (viewerType.equals(ViewerType.CHIMERA))
     {
       sview = new ChimeraViewFrame(pr, collateForPDB, null, ap);
     }
@@ -115,24 +116,42 @@ public class StructureViewer
     return viewStructures(getViewerType(), ap, pdb, sequenceIs);
   }
 
-  public JalviewStructureDisplayI createView(Viewer viewer, String[] pdbf,
-          String[] id, SequenceI[][] sq, AlignmentPanel alignPanel,
-          boolean useinJmolsuperpos, boolean usetoColourbyseq,
-          boolean jmolColouring, String fileloc, Rectangle rect, String vid)
+  /**
+   * Create a new panel controlling a structure viewer.
+   * 
+   * @param type
+   * @param pdbf
+   * @param id
+   * @param sq
+   * @param alignPanel
+   * @param viewerData
+   * @param fileloc
+   * @param rect
+   * @param vid
+   * @return
+   */
+  public JalviewStructureDisplayI createView(ViewerType type,
+          String[] pdbf, String[] id, SequenceI[][] sq,
+          AlignmentPanel alignPanel, StructureViewerModel viewerData, String fileloc,
+          Rectangle rect, String vid)
   {
+    final boolean useinViewerSuperpos = viewerData.isAlignWithPanel();
+    final boolean usetoColourbyseq = viewerData.isColourWithAlignPanel();
+    final boolean viewerColouring = viewerData.isColourByViewer();
+
     JalviewStructureDisplayI sview = null;
-    switch (viewer)
+    switch (type)
     {
     case JMOL:
-      sview = new AppJmol(pdbf, id, sq, alignPanel, useinJmolsuperpos,
-              usetoColourbyseq, jmolColouring, fileloc, rect, vid);
+      sview = new AppJmol(pdbf, id, sq, alignPanel, usetoColourbyseq,
+              useinViewerSuperpos, viewerColouring, fileloc, rect, vid);
       break;
     case CHIMERA:
       Cache.log.error("Unsupported structure viewer type "
-              + viewer.toString());
+              + type.toString());
       break;
     default:
-      Cache.log.error("Unknown structure viewer type " + viewer.toString());
+      Cache.log.error("Unknown structure viewer type " + type.toString());
     }
     return sview;
   }
diff --git a/src/jalview/gui/StructureViewerBase.java b/src/jalview/gui/StructureViewerBase.java
new file mode 100644 (file)
index 0000000..13af0e8
--- /dev/null
@@ -0,0 +1,225 @@
+package jalview.gui;
+
+import jalview.gui.ViewSelectionMenu.ViewSetProvider;
+import jalview.jbgui.GStructureViewer;
+
+import java.awt.Component;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.JMenuItem;
+
+/**
+ * Base class with common functionality for JMol, Chimera or other structure
+ * viewers.
+ * 
+ * @author gmcarstairs
+ *
+ */
+public abstract class StructureViewerBase extends GStructureViewer
+        implements Runnable, ViewSetProvider
+{
+
+  /**
+   * list of sequenceSet ids associated with the view
+   */
+  protected List<String> _aps = new ArrayList<String>();
+  /**
+   * list of alignment panels to use for superposition
+   */
+  protected Vector<AlignmentPanel> _alignwith = new Vector<AlignmentPanel>();
+  /**
+   * list of alignment panels that are used for colouring structures by aligned
+   * sequences
+   */
+  protected Vector<AlignmentPanel> _colourwith = new Vector<AlignmentPanel>();
+  private String viewId = null;
+  private AlignmentPanel ap;
+
+  /**
+   * 
+   * @param ap2
+   * @return true if this Jmol instance is linked with the given alignPanel
+   */
+  public boolean isLinkedWith(AlignmentPanel ap2)
+  {
+    return _aps.contains(ap2.av.getSequenceSetId());
+  }
+
+  public boolean isUsedforaligment(AlignmentPanel ap2)
+  {
+  
+    return (_alignwith != null) && _alignwith.contains(ap2);
+  }
+
+  public boolean isUsedforcolourby(AlignmentPanel ap2)
+  {
+    return (_colourwith != null) && _colourwith.contains(ap2);
+  }
+
+  /**
+   * 
+   * @return TRUE if the view is NOT being coloured by the alignment colours.
+   */
+  public boolean isColouredByViewer()
+  {
+    return !getBinding().isColourBySequence();
+  }
+
+  public String getViewId()
+  {
+    if (viewId == null)
+    {
+      viewId = System.currentTimeMillis() + "." + this.hashCode();
+    }
+    return viewId;
+  }
+
+  protected void setViewId(String viewId)
+  {
+    this.viewId = viewId;
+  }
+
+  public abstract String getStateInfo();
+
+  protected void buildActionMenu()
+  {
+    if (_alignwith == null)
+    {
+      _alignwith = new Vector<AlignmentPanel>();
+    }
+    if (_alignwith.size() == 0 && ap != null)
+    {
+      _alignwith.add(ap);
+    }
+    ;
+    for (Component c : viewerActionMenu.getMenuComponents())
+    {
+      if (c != alignStructs)
+      {
+        viewerActionMenu.remove((JMenuItem) c);
+      }
+    }
+  }
+
+  public AlignmentPanel getAlignmentPanel()
+  {
+    return ap;
+  }
+
+  protected void setAlignmentPanel(AlignmentPanel alp)
+  {
+    this.ap = alp;
+  }
+
+  public AlignmentPanel[] getAllAlignmentPanels()
+  {
+    AlignmentPanel[] t, list = new AlignmentPanel[0];
+    for (String setid : _aps)
+    {
+      AlignmentPanel[] panels = PaintRefresher.getAssociatedPanels(setid);
+      if (panels != null)
+      {
+        t = new AlignmentPanel[list.length + panels.length];
+        System.arraycopy(list, 0, t, 0, list.length);
+        System.arraycopy(panels, 0, t, list.length, panels.length);
+        list = t;
+      }
+    }
+  
+    return list;
+  }
+
+  /**
+   * set the primary alignmentPanel reference and add another alignPanel to the
+   * list of ones to use for colouring and aligning
+   * 
+   * @param nap
+   */
+  public void addAlignmentPanel(AlignmentPanel nap)
+  {
+    if (getAlignmentPanel() == null)
+    {
+      setAlignmentPanel(nap);
+    }
+    if (!_aps.contains(nap.av.getSequenceSetId()))
+    {
+      _aps.add(nap.av.getSequenceSetId());
+    }
+  }
+
+  /**
+   * remove any references held to the given alignment panel
+   * 
+   * @param nap
+   */
+  public void removeAlignmentPanel(AlignmentPanel nap)
+  {
+    try
+    {
+      _alignwith.remove(nap);
+      _colourwith.remove(nap);
+      if (getAlignmentPanel() == nap)
+      {
+        setAlignmentPanel(null);
+        for (AlignmentPanel aps : getAllAlignmentPanels())
+        {
+          if (aps != nap)
+          {
+            setAlignmentPanel(aps);
+            break;
+          }
+        }
+      }
+    } catch (Exception ex)
+    {
+    }
+    if (getAlignmentPanel() != null)
+    {
+      buildActionMenu();
+    }
+  }
+
+  public void useAlignmentPanelForSuperposition(AlignmentPanel nap)
+  {
+    addAlignmentPanel(nap);
+    if (!_alignwith.contains(nap))
+    {
+      _alignwith.add(nap);
+    }
+  }
+
+  public void excludeAlignmentPanelForSuperposition(AlignmentPanel nap)
+  {
+    if (_alignwith.contains(nap))
+    {
+      _alignwith.remove(nap);
+    }
+  }
+
+  public void useAlignmentPanelForColourbyseq(AlignmentPanel nap, boolean enableColourBySeq)
+  {
+    useAlignmentPanelForColourbyseq(nap);
+    getBinding().setColourBySequence(enableColourBySeq);
+    seqColour.setSelected(enableColourBySeq);
+    viewerColour.setSelected(!enableColourBySeq);
+  }
+
+  public void useAlignmentPanelForColourbyseq(AlignmentPanel nap)
+  {
+    addAlignmentPanel(nap);
+    if (!_colourwith.contains(nap))
+    {
+      _colourwith.add(nap);
+    }
+  }
+
+  public void excludeAlignmentPanelForColourbyseq(AlignmentPanel nap)
+  {
+    if (_colourwith.contains(nap))
+    {
+      _colourwith.remove(nap);
+    }
+  }
+}
index ee0bfaf..6ce68b3 100755 (executable)
  */
 package jalview.gui;
 
-import java.beans.*;
-import java.io.*;
-import java.util.*;
-import java.util.List;
-
-import javax.imageio.*;
-
-import java.awt.*;
-import java.awt.event.*;
-import java.awt.image.*;
-import javax.swing.*;
-
-import org.jibble.epsgraphics.*;
-import jalview.analysis.*;
+import jalview.analysis.AlignmentSorter;
+import jalview.analysis.NJTree;
+import jalview.api.analysis.ScoreModelI;
+import jalview.api.analysis.ViewBasedAnalysisI;
+import jalview.bin.Cache;
 import jalview.commands.CommandI;
 import jalview.commands.OrderCommand;
-import jalview.datamodel.*;
-import jalview.io.*;
-import jalview.jbgui.*;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.AlignmentView;
+import jalview.datamodel.BinaryNode;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.DBRefEntry;
+import jalview.datamodel.NodeTransformI;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+import jalview.datamodel.SequenceNode;
+import jalview.io.JalviewFileChooser;
+import jalview.io.JalviewFileView;
+import jalview.io.NewickFile;
+import jalview.jbgui.GTreePanel;
+import jalview.schemes.ResidueProperties;
 import jalview.util.MessageManager;
 
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.image.BufferedImage;
+import java.beans.PropertyChangeEvent;
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+import javax.swing.ButtonGroup;
+import javax.swing.JMenuItem;
+import javax.swing.JRadioButtonMenuItem;
+
+import org.jibble.epsgraphics.EpsGraphics2D;
+
 /**
  * DOCUMENT ME!
  * 
@@ -293,8 +313,26 @@ public class TreePanel extends GTreePanel
           seqs = av.getSelectionGroup().getSequencesInOrder(
                   av.getAlignment());
         }
-
-        tree = new NJTree(seqs, seqStrings, type, pwtype, start, end);
+        ScoreModelI sm = ResidueProperties.getScoreModel(pwtype);
+        if (sm instanceof ViewBasedAnalysisI)
+        {
+          try
+          {
+            sm = sm.getClass().newInstance();
+            ((ViewBasedAnalysisI) sm)
+                    .configureFromAlignmentView(treeCanvas.ap);
+          } catch (Exception q)
+          {
+            Cache.log.error("Couldn't create a scoremodel instance for "
+                    + sm.getName());
+          }
+          tree = new NJTree(seqs, seqStrings, type, pwtype, sm, start, end);
+        }
+        else
+        {
+          tree = new NJTree(seqs, seqStrings, type, pwtype, null, start,
+                  end);
+        }
         showDistances(true);
       }
 
index 7736332..8dc84b8 100755 (executable)
  */
 package jalview.io;
 
-import java.io.*;
-import java.net.*;
-import java.util.*;
-
-import jalview.analysis.*;
-import jalview.datamodel.*;
-import jalview.schemes.*;
+import jalview.analysis.Conservation;
+import jalview.api.AlignViewportI;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.GraphLine;
+import jalview.datamodel.HiddenSequences;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.schemes.ColourSchemeI;
+import jalview.schemes.ColourSchemeProperty;
+import jalview.schemes.ResidueProperties;
+import jalview.schemes.UserColourScheme;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.Vector;
 
 public class AnnotationFile
 {
@@ -67,18 +88,18 @@ public class AnnotationFile
   }
 
   /**
-   * convenience method for pre-2.4 feature files which have no view, hidden
+   * convenience method for pre-2.9 annotation files which have no view, hidden
    * columns or hidden row keywords.
    * 
    * @param annotations
    * @param list
    * @param properties
-   * @return feature file as a string.
+   * @return annotation file as a string.
    */
   public String printAnnotations(AlignmentAnnotation[] annotations,
           List<SequenceGroup> list, Hashtable properties)
   {
-    return printAnnotations(annotations, list, properties, null);
+    return printAnnotations(annotations, list, properties, null, null, null);
 
   }
 
@@ -119,10 +140,56 @@ public class AnnotationFile
    * @return annotation file
    */
   public String printAnnotations(AlignmentAnnotation[] annotations,
-          List<SequenceGroup> list, Hashtable properties, ViewDef[] views)
+          List<SequenceGroup> list, Hashtable properties,
+          ColumnSelection cs, AlignmentI al, ViewDef view)
   {
-    // TODO: resolve views issue : annotationFile could contain visible region,
-    // or full data + hidden region specifications for a view.
+    if (view != null)
+    {
+      if (view.viewname != null)
+      {
+        text.append("VIEW_DEF\t" + view.viewname + "\n");
+      }
+      if (list == null)
+      {
+        list = view.visibleGroups;
+      }
+      if (cs == null)
+      {
+        cs = view.hiddencols;
+      }
+      if (al == null)
+      {
+        // add hidden rep sequences.
+      }
+    }
+    // first target - store and restore all settings for a view.
+    if (al != null && al.hasSeqrep())
+    {
+      text.append("VIEW_SETREF\t" + al.getSeqrep().getName() + "\n");
+    }
+    if (cs != null && cs.hasHiddenColumns())
+    {
+      text.append("VIEW_HIDECOLS\t");
+      List<int[]> hc = cs.getHiddenColumns();
+      boolean comma = false;
+      for (int[] r : hc)
+      {
+        if (!comma)
+        {
+          comma = true;
+        }
+        else
+        {
+          text.append(",");
+        }
+        text.append(r[0]);
+        text.append("-");
+        text.append(r[1]);
+      }
+      text.append("\n");
+    }
+    // TODO: allow efficient recovery of annotation data shown in several
+    // different views
     if (annotations != null)
     {
       boolean oneColour = true;
@@ -320,7 +387,9 @@ public class AnnotationFile
         }
 
         if (row.hasScore())
+        {
           text.append("\t" + row.score);
+        }
 
         text.append(newline);
 
@@ -403,7 +472,8 @@ public class AnnotationFile
         text.append(properties.get(key));
       }
       // TODO: output alignment visualization settings here if required
-
+      // iterate through one or more views, defining, marking columns and rows as visible/hidden, and emmitting view properties.
+      // View specific annotation is 
     }
 
     return text.toString();
@@ -590,9 +660,33 @@ public class AnnotationFile
 
   String refSeqId = null;
 
+  public boolean annotateAlignmentView(AlignViewportI viewport,
+          String file, String protocol)
+  {
+    ColumnSelection colSel = viewport.getColumnSelection();
+    if (colSel == null)
+    {
+      colSel = new ColumnSelection();
+    }
+    boolean rslt = readAnnotationFile(viewport.getAlignment(), colSel,
+            file, protocol);
+    if (rslt
+            && (colSel.hasSelectedColumns() || colSel.hasHiddenColumns()))
+    {
+      viewport.setColumnSelection(colSel);
+    }
+
+    return rslt;
+  }
   public boolean readAnnotationFile(AlignmentI al, String file,
           String protocol)
   {
+    return readAnnotationFile(al, null, file, protocol);
+  }
+
+  public boolean readAnnotationFile(AlignmentI al, ColumnSelection colSel,
+          String file, String protocol)
+  {
     BufferedReader in = null;
     try
     {
@@ -619,7 +713,7 @@ public class AnnotationFile
       }
       if (in != null)
       {
-        return parseAnnotationFrom(al, in);
+        return parseAnnotationFrom(al, colSel, in);
       }
 
     } catch (Exception ex)
@@ -642,7 +736,8 @@ public class AnnotationFile
 
   private static String GRAPHLINE = "GRAPHLINE", COMBINE = "COMBINE";
 
-  public boolean parseAnnotationFrom(AlignmentI al, BufferedReader in)
+  public boolean parseAnnotationFrom(AlignmentI al, ColumnSelection colSel,
+          BufferedReader in)
           throws Exception
   {
     nlinesread = 0;
@@ -834,6 +929,58 @@ public class AnnotationFile
           modified = true;
           continue;
         }
+        // else if (token.equalsIgnoreCase("VIEW_DEF"))
+        // {
+        // addOrSetView(al,st);
+        // modified = true;
+        // continue;
+        // }
+        else if (token.equalsIgnoreCase("VIEW_SETREF"))
+        {
+          if (refSeq != null)
+          {
+            al.setSeqrep(refSeq);
+          }
+          modified = true;
+          continue;
+        }
+        else if (token.equalsIgnoreCase("VIEW_HIDECOLS"))
+        {
+          if (st.hasMoreTokens())
+          {
+            if (colSel == null)
+            {
+              colSel = new ColumnSelection();
+            }
+            parseHideCols(colSel, st.nextToken());
+          }
+          modified = true;
+          continue;
+        }
+        else if (token.equalsIgnoreCase("HIDE_INSERTIONS"))
+        {
+          SequenceI sr = refSeq == null ? al.getSeqrep() : refSeq;
+          if (sr == null)
+          {
+            sr = al.getSequenceAt(0);
+          }
+          if (sr != null)
+          {
+            if (colSel == null)
+            {
+              System.err
+                      .println("Cannot process HIDE_INSERTIONS without an alignment view: Ignoring line: "
+                              + line);
+            }
+            else
+            {
+              // consider deferring this till after the file has been parsed ?
+              colSel.hideInsertionsFor(sr);
+            }
+          }
+          modified = true;
+          continue;
+        }
 
         // Parse out the annotation row
         graphStyle = AlignmentAnnotation.getGraphValueFromString(token);
@@ -852,7 +999,9 @@ public class AnnotationFile
           {
             description = line;
             if (st.hasMoreTokens())
+            {
               line = st.nextToken();
+            }
           }
 
           if (st.hasMoreTokens())
@@ -1004,7 +1153,7 @@ public class AnnotationFile
                   (StringTokenizer) _deferred_args[1], // st
                   (SequenceI) _deferred_args[2], // refSeq
                   (_deferred_args[3] == null) ? null : groupRefLookup
-                          .get((String) _deferred_args[3]) // the reference
+                          .get(_deferred_args[3]) // the reference
                                                            // group, or null
           );
         }
@@ -1024,7 +1173,7 @@ public class AnnotationFile
                 (StringTokenizer) _combine_args[0], // st
                 (SequenceI) _combine_args[1], // refSeq
                 (_combine_args[2] == null) ? null : groupRefLookup
-                        .get((String) _combine_args[2]) // the reference group,
+                        .get(_combine_args[2]) // the reference group,
                                                         // or null
         );
       }
@@ -1032,6 +1181,40 @@ public class AnnotationFile
     return modified;
   }
 
+  private void parseHideCols(ColumnSelection colSel, String nextToken)
+  {
+    StringTokenizer inval = new StringTokenizer(nextToken,",");
+    while (inval.hasMoreTokens())
+    {
+      String range = inval.nextToken().trim();
+      int from, to = range.indexOf("-");
+      if (to == -1)
+      {
+        from = to = Integer.parseInt(range);
+        if (from >= 0)
+        {
+          colSel.hideColumns(from, to);
+        }
+      }
+      else
+      {
+        from = Integer.parseInt(range.substring(0, to));
+        if (to < range.length() - 1)
+        {
+          to = Integer.parseInt(range.substring(to + 1));
+        }
+        else
+        {
+          to = from;
+        }
+        if (from > 0 && to >= from)
+        {
+          colSel.hideColumns(from, to);
+        }
+      }
+    }
+  }
+
   private Object autoAnnotsKey(AlignmentAnnotation annotation,
           SequenceI refSeq, String groupRef)
   {
@@ -1602,4 +1785,19 @@ public class AnnotationFile
     }
     return sp.toString();
   }
+
+  public String printAnnotationsForView(AlignViewportI viewport)
+  {
+    return printAnnotations(viewport.isShowAnnotation() ? viewport
+            .getAlignment().getAlignmentAnnotation() : null, viewport
+            .getAlignment().getGroups(), viewport.getAlignment()
+            .getProperties(), viewport.getColumnSelection(),
+            viewport.getAlignment(), null);
+  }
+
+  public String printAnnotationsForAlignment(AlignmentI al)
+  {
+    return printAnnotations(al.getAlignmentAnnotation(), al.getGroups(),
+            al.getProperties(), null, al, null);
+  }
 }
index d3b19fa..89adead 100755 (executable)
@@ -47,7 +47,9 @@ public class AppletFormatAdapter
    */
   public static final String[] READABLE_FORMATS = new String[]
           { "BLC", "CLUSTAL", "FASTA", "MSF", "PileUp", "PIR", "PFAM", "STH",
-    "PDB", "JnetFile", "RNAML", PhylipFile.FILE_DESC }; // , "SimpleBLAST" };
+      "PDB", "JnetFile", "RNAML", PhylipFile.FILE_DESC, "HTML" }; // ,
+                                                                              // "SimpleBLAST"
+                                                                              // };
 
   /**
    * List of valid format strings for use by callers of the formatSequences
@@ -62,16 +64,16 @@ public class AppletFormatAdapter
    * that are writable by the application.
    */
   public static final String[] WRITABLE_EXTENSIONS = new String[]
-          { "fa, fasta, mfa, fastq", "aln", "pfam", "msf", "pir", "blc", "amsa",
-    "jvp", "sto,stk", "jar", PhylipFile.FILE_EXT };
+  { "fa, fasta, mfa, fastq", "aln", "pfam", "msf", "pir", "blc", "amsa",
+      "sto,stk", PhylipFile.FILE_EXT, "jvp" };
 
   /**
    * List of writable formats by the application. Order must correspond with the
    * WRITABLE_EXTENSIONS list of formats.
    */
   public static final String[] WRITABLE_FNAMES = new String[]
-          { "Fasta", "Clustal", "PFAM", "MSF", "PIR", "BLC", "AMSA", "Jalview",
-    "STH", "Jalview", PhylipFile.FILE_DESC };
+  { "Fasta", "Clustal", "PFAM", "MSF", "PIR", "BLC", "AMSA", "STH",
+      PhylipFile.FILE_DESC, "Jalview" };
 
   /**
    * List of readable format file extensions by application in order
@@ -79,7 +81,8 @@ public class AppletFormatAdapter
    */
   public static final String[] READABLE_EXTENSIONS = new String[]
           { "fa, fasta, mfa, fastq", "aln", "pfam", "msf", "pir", "blc", "amsa",
-    "jar,jvp", "sto,stk", "xml,rnaml", PhylipFile.FILE_EXT }; // ".blast"
+      "jar,jvp", "sto,stk", "xml,rnaml", PhylipFile.FILE_EXT,
+ "html" }; // ".blast"
 
   /**
    * List of readable formats by application in order corresponding to
@@ -87,7 +90,7 @@ public class AppletFormatAdapter
    */
   public static final String[] READABLE_FNAMES = new String[]
           { "Fasta", "Clustal", "PFAM", "MSF", "PIR", "BLC", "AMSA", "Jalview",
-    "Stockholm", "RNAML", PhylipFile.FILE_DESC };// ,
+      "Stockholm", "RNAML", PhylipFile.FILE_DESC, "HTML" };// ,
 
   // "SimpleBLAST"
   // };
@@ -109,7 +112,7 @@ public class AppletFormatAdapter
     for (int i = 0, iSize = els.length - 1; i < iSize; i++)
     {
       list.append(els[i]);
-      list.append(",");
+      list.append(", ");
     }
     list.append(" and " + els[els.length - 1] + ".");
     return list.toString();
@@ -268,6 +271,10 @@ public class AppletFormatAdapter
       {
         afile = new PhylipFile(inFile, type);
       }
+      // else if (format.equals(HtmlFile.FILE_DESC))
+      // {
+      // afile = new HtmlFile(inFile, type);
+      // }
       else if (format.equals("RNAML"))
       {
         afile = new RnamlFile(inFile, type);
@@ -392,6 +399,10 @@ public class AppletFormatAdapter
       {
         afile = new PhylipFile(source);
       }
+      // else if (format.equals(HtmlFile.FILE_DESC))
+      // {
+      // afile = new HtmlFile(source);
+      // }
       Alignment al = new Alignment(afile.getSeqsAsArray());
 
       afile.addAnnotations(al);
@@ -527,6 +538,10 @@ public class AppletFormatAdapter
       {
         afile = new PhylipFile();
       }
+      // else if (format.equalsIgnoreCase(HtmlFile.FILE_DESC))
+      // {
+      // afile = new HtmlFile();
+      // }
       else if (format.equalsIgnoreCase("RNAML"))
       {
         afile = new RnamlFile();
diff --git a/src/jalview/io/BioJsHTMLOutput.java b/src/jalview/io/BioJsHTMLOutput.java
new file mode 100644 (file)
index 0000000..db43a3f
--- /dev/null
@@ -0,0 +1,222 @@
+package jalview.io;
+
+import jalview.api.FeaturesDisplayedI;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+import jalview.exceptions.NoFileSelectedException;
+import jalview.gui.AlignViewport;
+import jalview.gui.AlignmentPanel;
+import jalview.gui.FeatureRenderer;
+import jalview.json.binding.v1.BioJsAlignmentPojo;
+import jalview.json.binding.v1.BioJsFeaturePojo;
+import jalview.json.binding.v1.BioJsSeqPojo;
+import jalview.schemes.ColourSchemeProperty;
+import jalview.util.MessageManager;
+
+import java.awt.Color;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.URL;
+import java.util.ArrayList;
+
+import com.json.JSONException;
+
+public class BioJsHTMLOutput
+{
+  private AlignViewport av;
+
+  private jalview.api.FeatureRenderer fr;
+
+  private String globalColorScheme;
+
+  private FeaturesDisplayedI displayedFeatures;
+
+  private String jalviewVersion;
+
+  private String webStartLaunchServletUrl = "http://www.jalview.org/services/launchApp";
+
+  public BioJsHTMLOutput(AlignmentPanel ap,
+          FeatureRenderer fr1)
+  {
+
+    jalviewVersion = jalview.bin.Cache.getProperty("VERSION");
+    webStartLaunchServletUrl = jalview.bin.Cache.getDefault(
+            "www.jalview.org", "http://www.jalview.org")
+            + "/services/launchApp";
+    if (ap != null)
+    {
+      this.av = ap.av;
+      this.globalColorScheme = ColourSchemeProperty.getColourName(av
+              .getGlobalColourScheme());
+      this.fr = ap.cloneFeatureRenderer();
+      displayedFeatures = av.getFeaturesDisplayed();
+    }
+  }
+
+  private void exportJalviewAlignmentAsBioJsHtmlFile()
+  {
+    try
+    {
+      String outputFile = getOutputFile();
+      String jalviewAlignmentJson = getJalviewAlignmentAsJsonString(av
+              .getAlignment());
+      String bioJSTemplateString = getBioJsTemplateAsString(this);
+      String generatedBioJsWithJalviewAlignmentAsJson = bioJSTemplateString
+              .replaceAll(
+"#sequenceData#", jalviewAlignmentJson)
+              .toString();
+
+      PrintWriter out = new java.io.PrintWriter(new java.io.FileWriter(
+              outputFile));
+      out.print(generatedBioJsWithJalviewAlignmentAsJson);
+      out.flush();
+      out.close();
+      jalview.util.BrowserLauncher.openURL("file:///" + outputFile);
+    } catch (NoFileSelectedException ex)
+    {
+      // do noting if no file was selected
+    } catch (Exception e)
+    {
+      e.printStackTrace();
+    }
+  }
+
+  public String getOutputFile() throws NoFileSelectedException
+  {
+    String selectedFile = null;
+    JalviewFileChooser jvFileChooser = new JalviewFileChooser(
+            jalview.bin.Cache.getProperty("LAST_DIRECTORY"), new String[]
+            { "html" }, new String[]
+            { "HTML files" }, "HTML files");
+    jvFileChooser.setFileView(new JalviewFileView());
+
+    // TODO uncomment when supported by MassageManager
+    jvFileChooser.setDialogTitle(MessageManager
+            .getString("label.save_as_biojs_html"));
+    jvFileChooser.setDialogTitle("save as BioJs HTML");
+    jvFileChooser.setToolTipText(MessageManager.getString("action.save"));
+
+    int fileChooserOpt = jvFileChooser.showSaveDialog(null);
+    if (fileChooserOpt == JalviewFileChooser.APPROVE_OPTION)
+    {
+      jalview.bin.Cache.setProperty("LAST_DIRECTORY", jvFileChooser
+              .getSelectedFile().getParent());
+      selectedFile = jvFileChooser.getSelectedFile().getPath();
+    }
+    else
+    {
+      throw new NoFileSelectedException("No file was selected.");
+    }
+
+    return selectedFile;
+  }
+
+  public String getJalviewAlignmentAsJsonString(AlignmentI alignment)
+          throws IOException, JSONException
+  {
+    BioJsAlignmentPojo bjsAlignment = new BioJsAlignmentPojo();
+
+    bjsAlignment.setGlobalColorScheme(getGlobalColorScheme());
+    bjsAlignment.setJalviewVersion(jalviewVersion);
+    bjsAlignment.setWebStartUrl(webStartLaunchServletUrl);
+
+    int count = 0;
+    for (SequenceI seq : alignment.getSequences())
+    {
+      StringBuilder name = new StringBuilder();
+      name.append(seq.getName()).append("/").append(seq.getStart())
+              .append("-").append(seq.getEnd());
+
+      BioJsSeqPojo seqPojo = new BioJsSeqPojo();
+      seqPojo.setId(String.valueOf(++count));
+      seqPojo.setEnd(seq.getEnd());
+      seqPojo.setStart(seq.getStart());
+      seqPojo.setName(name.toString());
+      seqPojo.setSeq(seq.getSequenceAsString());
+
+      SequenceFeature[] seqFeatures = seq.getDatasetSequence()
+              .getSequenceFeatures();
+      if (seqFeatures != null)
+      {
+        ArrayList<BioJsFeaturePojo> bjsSeqFeatures = new ArrayList<BioJsFeaturePojo>();
+        for (SequenceFeature sf : seqFeatures)
+        {
+          if (displayedFeatures != null
+                  && displayedFeatures.isVisible(sf.getType()))
+          {
+
+            // TODO: translate graduated/complex colourschemes to biojs model
+            String featureColour = jalview.util.Format.getHexString(fr
+                    .findFeatureColour(Color.white, seq,
+                            seq.findIndex(sf.getBegin())));
+            BioJsFeaturePojo bjsFeature = new BioJsFeaturePojo();
+            bjsFeature.setFillColor(featureColour);
+            bjsFeature.setXstart(seq.findIndex(sf.getBegin()) - 1);
+            bjsFeature.setXend(seq.findIndex(sf.getEnd()));
+            bjsFeature.setText(sf.getType());
+            bjsSeqFeatures.add(bjsFeature);
+          }
+        }
+        seqPojo.setFeatures(bjsSeqFeatures);
+      }
+      bjsAlignment.getSeqs().add(seqPojo);
+    }
+
+    return new com.json.JSONObject(bjsAlignment).toString()
+            .replaceAll("xstart", "xStart").replaceAll("xend", "xEnd");
+  }
+
+  public static String getBioJsTemplateAsString(Object currentObj)
+          throws IOException
+  {
+    InputStreamReader isReader = null;
+    BufferedReader buffReader = null;
+    StringBuilder sb = new StringBuilder();
+    URL url = currentObj.getClass().getResource(
+            "/templates/BioJSTemplate.txt");
+    if (url != null)
+    {
+      try
+      {
+        isReader = new InputStreamReader(url.openStream());
+        buffReader = new BufferedReader(isReader);
+        String line;
+        String lineSeparator = System.getProperty("line.separator");
+        while ((line = buffReader.readLine()) != null)
+        {
+          sb.append(line).append(lineSeparator);
+        }
+
+      } catch (Exception ex)
+      {
+        ex.printStackTrace();
+      } finally
+      {
+        if (isReader != null)
+        {
+          isReader.close();
+        }
+
+        if (buffReader != null)
+        {
+          buffReader.close();
+        }
+      }
+    }
+    return sb.toString();
+  }
+
+  public String getGlobalColorScheme()
+  {
+    return globalColorScheme;
+  }
+
+  public void setGlobalColorScheme(String globalColorScheme)
+  {
+    this.globalColorScheme = globalColorScheme;
+  }
+
+}
index 87b829f..5b621e7 100755 (executable)
@@ -678,7 +678,7 @@ public class FeaturesFile extends AlignFile
    *          hash of feature types and colours
    * @return features file contents
    */
-  public String printJalviewFormat(SequenceI[] seqs, Hashtable visible)
+  public String printJalviewFormat(SequenceI[] seqs, Map<String,Object> visible)
   {
     return printJalviewFormat(seqs, visible, true, true);
   }
@@ -697,7 +697,7 @@ public class FeaturesFile extends AlignFile
    *          of group or type)
    * @return features file contents
    */
-  public String printJalviewFormat(SequenceI[] seqs, Hashtable visible,
+  public String printJalviewFormat(SequenceI[] seqs, Map visible,
           boolean visOnly, boolean nonpos)
   {
     StringBuffer out = new StringBuffer();
@@ -714,11 +714,11 @@ public class FeaturesFile extends AlignFile
       // write feature colours only if we're given them and we are generating
       // viewed features
       // TODO: decide if feature links should also be written here ?
-      Enumeration en = visible.keys();
+      Iterator en = visible.keySet().iterator();
       String type, color;
-      while (en.hasMoreElements())
+      while (en.hasNext())
       {
-        type = en.nextElement().toString();
+        type = en.next().toString();
 
         if (visible.get(type) instanceof GraduatedColor)
         {
@@ -926,12 +926,12 @@ public class FeaturesFile extends AlignFile
    * @param visible
    * @return
    */
-  public String printGFFFormat(SequenceI[] seqs, Hashtable visible)
+  public String printGFFFormat(SequenceI[] seqs, Map<String,Object> visible)
   {
     return printGFFFormat(seqs, visible, true, true);
   }
 
-  public String printGFFFormat(SequenceI[] seqs, Hashtable visible,
+  public String printGFFFormat(SequenceI[] seqs, Map<String,Object> visible,
           boolean visOnly, boolean nonpos)
   {
     StringBuffer out = new StringBuffer();
index 82b94c3..0285bf0 100755 (executable)
@@ -152,6 +152,7 @@ public class FileLoader implements Runnable
   public AlignFrame LoadFileWaitTillLoaded(FileParse source, String format)
   {
     this.source = source;
+
     file = source.getInFile();
     protocol = source.type;
     this.format = format;
@@ -276,7 +277,7 @@ public class FileLoader implements Runnable
                   .println("IMPLEMENTATION ERROR: Cannot read consecutive Jalview XML projects from a stream.");
           // We read the data anyway - it might make sense.
         }
-        alignFrame = new Jalview2XML(raiseGUI).LoadJalviewAlign(file);
+        alignFrame = new Jalview2XML(raiseGUI).loadJalviewAlign(file);
       }
       else
       {
@@ -352,6 +353,11 @@ public class FileLoader implements Runnable
             {
               alignFrame.setFileName(file, format);
             }
+            if (source instanceof HtmlFile)
+            {
+              ((HtmlFile) source).LoadAlignmentFeatures(alignFrame);
+
+            }
             if (raiseGUI)
             {
               // add the window to the GUI
index 8e9a49c..764715f 100755 (executable)
@@ -22,8 +22,17 @@ package jalview.io;
 
 import jalview.util.MessageManager;
 
-import java.io.*;
-import java.net.*;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.net.MalformedURLException;
+import java.net.URL;
 import java.util.zip.GZIPInputStream;
 
 /**
@@ -99,7 +108,9 @@ public class FileParse
       throw new Error(MessageManager.getString("error.implementation_error_null_fileparse"));
     }
     if (from == this)
+    {
       return;
+    }
     index = ++from.index;
     inFile = from.inFile;
     suffixSeparator = from.suffixSeparator;
@@ -157,7 +168,7 @@ public class FileParse
         {
           warningMessage = "Failed  to resolve as a GZ stream ("
                   + x.getMessage() + ")";
-          x.printStackTrace();
+          // x.printStackTrace();
         }
         ;
       }
@@ -281,7 +292,9 @@ public class FileParse
         {
           checkURLSource(fileStr);
           if (suffixSeparator == '#')
+           {
             extractSuffix(fileStr); // URL lref is stored for later reference.
+          }
         } catch (IOException e)
         {
           String suffixLess = extractSuffix(fileStr);
@@ -323,7 +336,9 @@ public class FileParse
       {
         String suffixLess = extractSuffix(fileStr);
         if (suffixLess != null)
+        {
           is = getClass().getResourceAsStream("/" + suffixLess);
+        }
       }
       if (is != null)
       {
@@ -371,7 +386,9 @@ public class FileParse
   public String nextLine() throws IOException
   {
     if (!error)
+    {
       return dataIn.readLine();
+    }
     throw new IOException(MessageManager.formatMessage("exception.invalid_source_stream", new String[]{errormessage}));
   }
 
index 8ca0c35..df5353c 100755 (executable)
@@ -255,6 +255,41 @@ public class FormatAdapter extends AppletFormatAdapter
     return this.formatSequences(format, alignment, suffix);
   }
 
+  public Alignment readFile(String inFile, String type, String format)
+          throws java.io.IOException
+  {
+    Alignment al;
+    if (format.equals("HTML"))
+    {
+      afile = new HtmlFile(inFile, type);
+      al = new Alignment(afile.getSeqsAsArray());
+      afile.addAnnotations(al);
+    }
+    else
+    {
+      al = super.readFile(inFile, type, format);
+    }
+
+    return al;
+  }
+
+  public AlignmentI readFromFile(FileParse source, String format)
+          throws java.io.IOException
+  {
+    Alignment al;
+    if (format.equals("HTML"))
+    {
+      afile = new HtmlFile(source);
+      al = new Alignment(afile.getSeqsAsArray());
+      afile.addAnnotations(al);
+    }
+    else
+    {
+      al = (Alignment) super.readFromFile(source, format);
+    }
+    return al;
+  }
+
   /**
    * validate format is valid for IO in Application. This is basically the
    * AppletFormatAdapter.isValidFormat call with additional checks for
index 50b07e4..2d2626f 100755 (executable)
@@ -34,7 +34,7 @@ public class HTMLOutput
 
   SequenceRenderer sr;
 
-  FeatureRenderer fr;
+  jalview.renderer.seqfeatures.FeatureRenderer fr;
 
   Color color;
 
diff --git a/src/jalview/io/HtmlFile.java b/src/jalview/io/HtmlFile.java
new file mode 100644 (file)
index 0000000..3cb7c3f
--- /dev/null
@@ -0,0 +1,152 @@
+package jalview.io;
+
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceFeature;
+import jalview.gui.AlignFrame;
+import jalview.json.binding.v1.BioJsAlignmentPojo.JalviewBioJsColorSchemeMapper;
+import jalview.schemes.ColourSchemeI;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+
+public class HtmlFile extends AlignFile
+{
+  // public static final String FILE_EXT = "html";
+  //
+  // public static final String FILE_DESC = "HTML";
+
+  private ColourSchemeI cs;
+
+  public HtmlFile()
+  {
+    super();
+  }
+
+  public HtmlFile(FileParse source) throws IOException
+  {
+    super(source);
+  }
+
+  public HtmlFile(String inFile, String type) throws IOException
+  {
+    super(inFile, type);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public void parse() throws IOException
+  {
+    try
+    {
+      StringBuilder htmlData = new StringBuilder();
+      String currentLine;
+      while ((currentLine = nextLine()) != null)
+      {
+        htmlData.append(currentLine);
+      }
+
+      Document doc = Jsoup.parse(htmlData.toString());
+      Element content = doc.getElementById("seqData");
+
+      String alignmentJsonString = content.val();
+      JSONParser jsonParser = new JSONParser();
+      JSONObject alignmentJsonObj = (JSONObject) jsonParser
+              .parse(alignmentJsonString);
+      JSONArray seqJsonArray = (JSONArray) alignmentJsonObj.get("seqs");
+      String bioJsColourScheme = (String) alignmentJsonObj
+              .get("globalColorScheme");
+      cs = getJalviewColorScheme(bioJsColourScheme);
+
+      for (Iterator<JSONObject> sequenceIter = seqJsonArray.iterator(); sequenceIter
+              .hasNext();)
+      {
+        JSONObject sequence = sequenceIter.next();
+        String sequcenceString = sequence.get("seq").toString();
+        Sequence seq = new Sequence(sequence.get("name").toString(),
+                sequcenceString, Integer.valueOf(sequence.get("start")
+                        .toString()), Integer.valueOf(sequence.get("end")
+                        .toString()));
+
+        JSONArray jsonSeqArray = (JSONArray) sequence.get("features");
+        SequenceFeature[] retrievedSeqFeatures = getJalviewSequenceFeatures(
+                jsonSeqArray, seq);
+        if (retrievedSeqFeatures != null)
+        {
+          seq.setSequenceFeatures(retrievedSeqFeatures);
+        }
+        seqs.add(seq);
+
+      }
+    } catch (Exception e)
+    {
+      e.printStackTrace();
+    }
+  }
+
+  public SequenceFeature[] getJalviewSequenceFeatures(
+          JSONArray jsonSeqFeatures, Sequence seq)
+  {
+    SequenceFeature[] seqFeatures = null;
+    int count = 0;
+    if (jsonSeqFeatures != null)
+    {
+      seqFeatures = new SequenceFeature[jsonSeqFeatures.size()];
+      for (@SuppressWarnings("unchecked")
+      Iterator<JSONObject> seqFeatureItr = jsonSeqFeatures.iterator(); seqFeatureItr
+              .hasNext();)
+      {
+
+        SequenceFeature sequenceFeature = new SequenceFeature();
+        JSONObject jsonFeature = seqFeatureItr.next();
+        Long begin = (Long) jsonFeature.get("xStart");
+        Long end = (Long) jsonFeature.get("xEnd");
+        String type = (String) jsonFeature.get("text");
+        // String color = (String) jsonFeature.get("fillColor");
+
+        sequenceFeature.setBegin(seq.findPosition(begin.intValue()));
+        sequenceFeature.setEnd(seq.findPosition(end.intValue()) - 1);
+        sequenceFeature.setType(type);
+        seqFeatures[count++] = sequenceFeature;
+      }
+    }
+    return seqFeatures;
+  }
+
+  public void LoadAlignmentFeatures(AlignFrame af)
+  {
+
+    af.setShowSeqFeatures(true);
+    af.changeColour(cs);
+    af.setMenusForViewport();
+  }
+
+  private ColourSchemeI getJalviewColorScheme(String bioJsColourSchemeName)
+  {
+    ColourSchemeI jalviewColor = null;
+    for (JalviewBioJsColorSchemeMapper cs : JalviewBioJsColorSchemeMapper
+            .values())
+    {
+      if (cs.getBioJsName().equals(bioJsColourSchemeName))
+      {
+        jalviewColor = cs.getJvColourScheme();
+        break;
+      }
+    }
+    return jalviewColor;
+  }
+
+  @Override
+  public String print()
+  {
+    throw new UnsupportedOperationException(
+            "Print method of HtmlFile not yet supported!");
+  }
+
+}
diff --git a/src/jalview/io/HtmlSvgOutput.java b/src/jalview/io/HtmlSvgOutput.java
new file mode 100644 (file)
index 0000000..3c9c608
--- /dev/null
@@ -0,0 +1,299 @@
+package jalview.io;
+
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignViewport;
+import jalview.gui.AlignmentPanel;
+import jalview.gui.AnnotationPanel;
+import jalview.gui.FeatureRenderer;
+import jalview.gui.HTMLOptions;
+import jalview.math.AlignmentDimension;
+import jalview.util.MessageManager;
+
+import java.awt.Color;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.io.File;
+import java.io.FileOutputStream;
+
+import org.jfree.graphics2d.svg.SVGGraphics2D;
+import org.jfree.graphics2d.svg.SVGHints;
+
+public class HtmlSvgOutput
+{
+  AlignViewport av;
+
+  FeatureRenderer fr;
+  AlignmentPanel ap;
+
+  AnnotationPanel annotationPanel;
+
+  public HtmlSvgOutput(File file, AlignmentPanel ap)
+  {
+
+      this.av = ap.av;
+      this.ap = ap;
+      this.annotationPanel = ap.getAnnotationPanel();
+    generateHtmlSvgOutput(file);
+  }
+
+  public void generateHtmlSvgOutput(File file)
+  {
+    try
+    {
+      if (file == null /*
+                        * && !(System.getProperty("java.awt.headless") != null
+                        * && System
+                        * .getProperty("java.awt.headless").equals("true"))
+                        */)
+      {
+
+      JalviewFileChooser chooser = getHTMLChooser();
+      chooser.setFileView(new jalview.io.JalviewFileView());
+      chooser.setDialogTitle(ap.alignFrame.getTitle());
+      chooser.setToolTipText(MessageManager.getString("action.save"));
+      int value = chooser.showSaveDialog(ap.alignFrame);
+
+      if (value == jalview.io.JalviewFileChooser.APPROVE_OPTION)
+      {
+        jalview.bin.Cache.setProperty("LAST_DIRECTORY", chooser
+                .getSelectedFile().getParent());
+        file = chooser.getSelectedFile();
+      }
+      }
+
+      AlignmentDimension aDimension = ap.getAlignmentDimension();
+      SVGGraphics2D g1 = new SVGGraphics2D(aDimension.getWidth(),
+              aDimension.getHeight());
+      SVGGraphics2D g2 = new SVGGraphics2D(aDimension.getWidth(),
+              aDimension.getHeight());
+
+      String renderStyle = jalview.bin.Cache.getDefault("HTML_RENDERING",
+              "Prompt each time");
+
+      // If we need to prompt, and if the GUI is visible then
+      // Prompt for rendering style
+      if (renderStyle.equalsIgnoreCase("Prompt each time")
+              && !(System.getProperty("java.awt.headless") != null && System
+                      .getProperty("java.awt.headless").equals("true")))
+      {
+        HTMLOptions svgOption = new HTMLOptions();
+        renderStyle = svgOption.getValue();
+
+        if (renderStyle == null || svgOption.cancelled)
+        {
+          return;
+        }
+      }
+
+      if (renderStyle.equalsIgnoreCase("lineart"))
+      {
+        g1.setRenderingHint(SVGHints.KEY_DRAW_STRING_TYPE,
+                SVGHints.VALUE_DRAW_STRING_TYPE_VECTOR);
+        g2.setRenderingHint(SVGHints.KEY_DRAW_STRING_TYPE,
+                SVGHints.VALUE_DRAW_STRING_TYPE_VECTOR);
+      }
+      printUnwrapped(aDimension.getWidth(), aDimension.getHeight(), 0, g1,
+              g2);
+      FileOutputStream out = new FileOutputStream(file);
+
+      String titleSvgData = g1.getSVGDocument();
+      String alignSvgData = g2.getSVGDocument();
+      String htmlData = getHtml(titleSvgData, alignSvgData);
+
+      out.write(htmlData.getBytes());
+      out.flush();
+      out.close();
+      if (!(System.getProperty("java.awt.headless") != null && System
+              .getProperty("java.awt.headless").equals("true")))
+      {
+      jalview.util.BrowserLauncher.openURL("file:///" + file);
+      }
+    } catch (Exception e)
+    {
+      e.printStackTrace();
+    }
+  }
+  
+  static JalviewFileChooser getHTMLChooser()
+  {
+    return new jalview.io.JalviewFileChooser(
+            jalview.bin.Cache.getProperty("LAST_DIRECTORY"), new String[]
+            { "html" }, new String[]
+            { "Hypertext Markup Language" }, "Hypertext Markup Language");
+  }
+
+  public int printUnwrapped(int pwidth, int pheight, int pi, Graphics... pg)
+          throws PrinterException
+  {
+    int idWidth = ap.getVisibleIdWidth(false);
+    FontMetrics fm = ap.getFontMetrics(av.getFont());
+    int scaleHeight = av.getCharHeight() + fm.getDescent();
+
+    pg[0].setColor(Color.white);
+    pg[0].fillRect(0, 0, pwidth, pheight);
+    pg[0].setFont(av.getFont());
+
+    // //////////////////////////////////
+    // / How many sequences and residues can we fit on a printable page?
+    int totalRes = (pwidth - idWidth) / av.getCharWidth();
+    int totalSeq = (pheight - scaleHeight) / av.getCharHeight() - 1;
+    int pagesWide = (av.getAlignment().getWidth() / totalRes) + 1;
+
+    // ///////////////////////////
+    // / Only print these sequences and residues on this page
+    int startRes;
+
+    // ///////////////////////////
+    // / Only print these sequences and residues on this page
+    int endRes;
+
+    // ///////////////////////////
+    // / Only print these sequences and residues on this page
+    int startSeq;
+
+    // ///////////////////////////
+    // / Only print these sequences and residues on this page
+    int endSeq;
+    startRes = (pi % pagesWide) * totalRes;
+    endRes = (startRes + totalRes) - 1;
+
+    if (endRes > (av.getAlignment().getWidth() - 1))
+    {
+      endRes = av.getAlignment().getWidth() - 1;
+    }
+    startSeq = (pi / pagesWide) * totalSeq;
+    endSeq = startSeq + totalSeq;
+    if (endSeq > av.getAlignment().getHeight())
+    {
+      endSeq = av.getAlignment().getHeight();
+    }
+    int pagesHigh = ((av.getAlignment().getHeight() / totalSeq) + 1)
+            * pheight;
+    if (av.isShowAnnotation())
+    {
+      pagesHigh += ap.getAnnotationPanel().adjustPanelHeight() + 3;
+    }
+    pagesHigh /= pheight;
+    if (pi >= (pagesWide * pagesHigh))
+    {
+      return Printable.NO_SUCH_PAGE;
+    }
+
+    // draw Scale
+    pg[1].translate(0, 0);
+    ap.getScalePanel().drawScale(pg[1], startRes, endRes, pwidth - idWidth,
+            scaleHeight);
+    pg[1].translate(-idWidth, scaleHeight);
+
+    // //////////////
+    // Draw the ids
+    Color currentColor = null;
+    Color currentTextColor = null;
+    pg[0].translate(0, scaleHeight);
+    pg[0].setFont(ap.getIdPanel().getIdCanvas().getIdfont());
+    SequenceI seq;
+    for (int i = startSeq; i < endSeq; i++)
+    {
+      seq = av.getAlignment().getSequenceAt(i);
+      if ((av.getSelectionGroup() != null)
+              && av.getSelectionGroup().getSequences(null).contains(seq))
+      {
+        currentColor = Color.gray;
+        currentTextColor = Color.black;
+      }
+      else
+      {
+        currentColor = av.getSequenceColour(seq);
+        currentTextColor = Color.black;
+      }
+      pg[0].setColor(currentColor);
+      pg[0].fillRect(0, (i - startSeq) * av.getCharHeight(), idWidth,
+              av.getCharHeight());
+      pg[0].setColor(currentTextColor);
+      int xPos = 0;
+      if (av.isRightAlignIds())
+      {
+        fm = pg[0].getFontMetrics();
+        xPos = idWidth
+                - fm.stringWidth(seq.getDisplayId(av.getShowJVSuffix()))
+                - 4;
+      }
+      pg[0].drawString(
+              seq.getDisplayId(av.getShowJVSuffix()),
+              xPos,
+              (((i - startSeq) * av.getCharHeight()) + av.getCharHeight())
+                      - (av.getCharHeight() / 5));
+    }
+    pg[0].setFont(av.getFont());
+    pg[0].translate(idWidth, 0);
+
+    // draw main sequence panel
+    pg[1].translate(idWidth, 0);
+    ap.getSeqPanel().seqCanvas.drawPanel(pg[1], startRes, endRes, startSeq,
+            endSeq, 0);
+    if (av.isShowAnnotation() && (endSeq == av.getAlignment().getHeight()))
+    {
+      // draw annotation label - need to offset for current scroll position
+      int offset = -ap.getAlabels().getScrollOffset();
+      pg[0].translate(0, offset);
+      pg[0].translate(-idWidth - 3,
+              (endSeq - startSeq) * av.getCharHeight() + 3);
+      ap.getAlabels().drawComponent(pg[0], idWidth);
+      pg[0].translate(idWidth + 3, 0);
+      pg[0].translate(0, -offset);
+
+      // draw annotation - need to offset for current scroll position
+      pg[1].translate(0, offset);
+      pg[1].translate(-idWidth - 3,
+              (endSeq - startSeq) * av.getCharHeight() + 3);
+      pg[1].translate(idWidth + 3, 0);
+      ap.getAnnotationPanel().renderer.drawComponent(
+              ap.getAnnotationPanel(), av, pg[1], -1, startRes, endRes + 1);
+      pg[1].translate(0, -offset);
+    }
+
+    return Printable.PAGE_EXISTS;
+  }
+  
+  private String getHtml(String titleSvg, String alignmentSvg)
+  {
+    StringBuilder htmlSvg = new StringBuilder();
+    htmlSvg.append("<html>"
+            + "<style type=\"text/css\"> "
+            + "div.parent{ width:100%;<!-- overflow: auto; -->}\n"
+            + "div.titlex{ width:11%; float: left; }\n"
+            + "div.align{ width:89%; float: right; }\n"
+            + ".sub-category-container {overflow-y: scroll; overflow-x: hidden; width: 100%; height: 100%;}\n"
+            + "object {pointer-events: none;}"
+            + "</style>");
+    htmlSvg.append("<div>");
+    htmlSvg.append(
+"<div class=\"titlex\">");
+    htmlSvg.append(
+"<div class=\"sub-category-container\"> ")
+            .append(titleSvg)
+            .append("</div>")
+            .append("</div>\n\n<!-- ========================================================================================== -->\n\n");
+    htmlSvg.append(
+"<div class=\"align\" >");
+    htmlSvg.append(
+            "<div class=\"sub-category-container\"> <div style=\"overflow-x: scroll;\">")
+            .append(alignmentSvg)
+.append("</div></div>")
+            .append("</div>");
+    htmlSvg.append("</div>");
+
+    htmlSvg.append("<script language=\"JavaScript\" type=\"text/javascript\" src=\"http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js\"></script>\n"
+            + "<script language=\"JavaScript\" type=\"text/javascript\"  src=\"//ajax.googleapis.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js\"></script>\n"
+            + "<script>\n"
+            + "var subCatContainer = $(\".sub-category-container\");\n"
+            + "subCatContainer.scroll(\nfunction() {\n"
+            + "subCatContainer.scrollTop($(this).scrollTop());\n});\n");
+
+    htmlSvg.append("</script></hmtl>");
+
+    return htmlSvg.toString();
+  }
+}
index 9c7478b..4fb2516 100755 (executable)
@@ -136,8 +136,14 @@ public class IdentifyFile
 
           break;
         }
+        // if (data.matches("<(\"[^\"]*\"|'[^']*'|[^'\">])*>"))
+        if (data.matches("<(?i)html(\"[^\"]*\"|'[^']*'|[^'\">])*>"))
+        {
+          reply = "HTML";
+          break;
+        }
 
-        if ((data.indexOf("<") > -1))
+        if (data.matches("<(?i)rnaml (\"[^\"]*\"|'[^']*'|[^'\">])*>"))
         {
           reply = "RNAML";
 
@@ -275,6 +281,7 @@ public class IdentifyFile
           break;
         }
 
+
         /*
          * // TODO comment out SimpleBLAST identification for Jalview 2.4.1 else
          * if (!lineswereskipped && data.indexOf("BLAST")<4) { reply =
@@ -320,6 +327,7 @@ public class IdentifyFile
 
   public static void main(String[] args)
   {
+
     for (int i = 0; args != null && i < args.length; i++)
     {
       IdentifyFile ider = new IdentifyFile();
index 6fba9c6..1757239 100644 (file)
@@ -22,6 +22,7 @@ package jalview.io;
 
 import java.util.ArrayList;
 import java.util.Hashtable;
+import java.util.List;
 import java.util.Vector;
 
 import jalview.datamodel.DBRefEntry;
@@ -54,30 +55,30 @@ public class SequenceAnnotationReport
    *          TODO refactor to Jalview 'utilities' somehow.
    */
   public void appendFeatures(final StringBuffer tooltipText2, int rpos,
-          SequenceFeature[] features)
+          List<SequenceFeature> features)
   {
     appendFeatures(tooltipText2, rpos, features, null);
   }
 
   public void appendFeatures(final StringBuffer tooltipText2, int rpos,
-          SequenceFeature[] features, Hashtable minmax)
+          List<SequenceFeature> features, Hashtable minmax)
   {
     String tmpString;
     if (features != null)
     {
-      for (int i = 0; i < features.length; i++)
+      for (SequenceFeature feature:features)
       {
-        if (features[i].getType().equals("disulfide bond"))
+        if (feature.getType().equals("disulfide bond"))
         {
-          if (features[i].getBegin() == rpos
-                  || features[i].getEnd() == rpos)
+          if (feature.getBegin() == rpos
+                  || feature.getEnd() == rpos)
           {
             if (tooltipText2.length() > 6)
             {
               tooltipText2.append("<br>");
             }
-            tooltipText2.append("disulfide bond " + features[i].getBegin()
-                    + ":" + features[i].getEnd());
+            tooltipText2.append("disulfide bond " + feature.getBegin()
+                    + ":" + feature.getEnd());
           }
         }
         else
@@ -87,25 +88,25 @@ public class SequenceAnnotationReport
             tooltipText2.append("<br>");
           }
           // TODO: remove this hack to display link only features
-          boolean linkOnly = features[i].getValue("linkonly") != null;
+          boolean linkOnly = feature.getValue("linkonly") != null;
           if (!linkOnly)
           {
-            tooltipText2.append(features[i].getType() + " ");
+            tooltipText2.append(feature.getType() + " ");
             if (rpos != 0)
             {
               // we are marking a positional feature
-              tooltipText2.append(features[i].begin);
+              tooltipText2.append(feature.begin);
             }
-            if (features[i].begin != features[i].end)
+            if (feature.begin != feature.end)
             {
-              tooltipText2.append(" " + features[i].end);
+              tooltipText2.append(" " + feature.end);
             }
 
-            if (features[i].getDescription() != null
-                    && !features[i].description.equals(features[i]
+            if (feature.getDescription() != null
+                    && !feature.description.equals(feature
                             .getType()))
             {
-              tmpString = features[i].getDescription();
+              tmpString = feature.getDescription();
               String tmp2up = tmpString.toUpperCase();
               int startTag = tmp2up.indexOf("<HTML>");
               if (startTag > -1)
@@ -150,27 +151,27 @@ public class SequenceAnnotationReport
               }
             }
             // check score should be shown
-            if (features[i].getScore() != Float.NaN)
+            if (feature.getScore() != Float.NaN)
             {
               float[][] rng = (minmax == null) ? null : ((float[][]) minmax
-                      .get(features[i].getType()));
+                      .get(feature.getType()));
               if (rng != null && rng[0] != null && rng[0][0] != rng[0][1])
               {
-                tooltipText2.append(" Score=" + features[i].getScore());
+                tooltipText2.append(" Score=" + feature.getScore());
               }
             }
-            if (features[i].getValue("status") != null)
+            if (feature.getValue("status") != null)
             {
-              String status = features[i].getValue("status").toString();
+              String status = feature.getValue("status").toString();
               if (status.length() > 0)
               {
-                tooltipText2.append("; (" + features[i].getValue("status")
+                tooltipText2.append("; (" + feature.getValue("status")
                         + ")");
               }
             }
           }
         }
-        if (features[i].links != null)
+        if (feature.links != null)
         {
           if (linkImageURL != null)
           {
@@ -178,7 +179,7 @@ public class SequenceAnnotationReport
           }
           else
           {
-            for (String urlstring : (Vector<String>) features[i].links)
+            for (String urlstring : (Vector<String>) feature.links)
             {
               try
               {
@@ -364,7 +365,6 @@ public class SequenceAnnotationReport
 
     // ADD NON POSITIONAL SEQUENCE INFO
     SequenceFeature[] features = ds.getSequenceFeatures();
-    SequenceFeature[] tfeat = new SequenceFeature[1];
     if (showNpFeats && features != null)
     {
       for (int i = 0; i < features.length; i++)
@@ -372,7 +372,8 @@ public class SequenceAnnotationReport
         if (features[i].begin == 0 && features[i].end == 0)
         {
           int sz = -tip.length();
-          tfeat[0] = features[i];
+          List<SequenceFeature> tfeat = new ArrayList<SequenceFeature>();
+          tfeat.add(features[i]);
           appendFeatures(tip, 0, tfeat, minmax);
           sz += tip.length();
           maxWidth = Math.max(maxWidth, sz);
index 6293a1b..90447db 100644 (file)
@@ -1304,7 +1304,7 @@ public class VamsasAppDatastore
             };
             if (dojvsync)
             {
-              fromxml.LoadJalviewAlign(jprovider);
+              fromxml.loadJalviewAlign(jprovider);
             }
           } catch (Exception e)
           {
@@ -1352,7 +1352,7 @@ public class VamsasAppDatastore
           };
           if (dojvsync)
           {
-            fromxml.LoadJalviewAlign(jarstream);
+            fromxml.loadJalviewAlign(jarstream);
           }
         } catch (Exception e)
         {
@@ -1498,7 +1498,7 @@ public class VamsasAppDatastore
         jxml.setSkipList(skipList);
         if (dojvsync)
         {
-          jxml.SaveState(new JarOutputStream(cappdata
+          jxml.saveState(new JarOutputStream(cappdata
                   .getClientOutputStream()));
         }
 
index df1acf1..8980e2c 100644 (file)
@@ -123,8 +123,9 @@ public class ParsePackedSet
           {
             br = new BufferedReader(src.getReader());
           }
+          // TODO: add columnSelection to context
           if (new jalview.io.AnnotationFile().parseAnnotationFrom(
-                  context.getLastAlignment(), br))
+                  context.getLastAlignment(), null, br))
           {
             context.updateSetModified(true);
           }
index 465a672..2ecaf6c 100644 (file)
@@ -202,7 +202,7 @@ public class MouseOverStructureListener extends JSFunctionExec implements
       SequenceRenderer sr = ((jalview.appletgui.AlignmentPanel) source)
               .getSequenceRenderer();
       FeatureRenderer fr = ((jalview.appletgui.AlignmentPanel) source).av
-              .getShowSequenceFeatures() ? new jalview.appletgui.FeatureRenderer(
+              .isShowSequenceFeatures() ? new jalview.appletgui.FeatureRenderer(
               ((jalview.appletgui.AlignmentPanel) source).av) : null;
       if (fr != null)
       {
index 387bb7f..4ecedaf 100755 (executable)
@@ -214,6 +214,8 @@ public class GAlignFrame extends JInternalFrame
 
   JMenuItem createPNG = new JMenuItem();
 
+  JMenuItem createBioJS = new JMenuItem();
+
   JMenuItem createSVG = new JMenuItem();
 
   protected JMenuItem font = new JMenuItem();
@@ -266,6 +268,8 @@ public class GAlignFrame extends JInternalFrame
 
   JMenuItem annotationColour = new JMenuItem();
 
+  JMenuItem annotationColumn = new JMenuItem();
+
   protected JMenuItem rnahelicesColour = new JMenuItem();
 
   JMenuItem associatedData = new JMenuItem();
@@ -1197,6 +1201,19 @@ public class GAlignFrame extends JInternalFrame
         htmlMenuItem_actionPerformed(e);
       }
     });
+
+    // TODO uncomment when supported by MassageManager
+    // createBioJS.setText(MessageManager.getString("label.biojs_html_export"));
+    createBioJS.setText("BioJS");
+    createBioJS.addActionListener(new java.awt.event.ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        bioJSMenuItem_actionPerformed(e);
+      }
+    });
+
     overviewMenuItem.setText(MessageManager
             .getString("label.overview_window"));
     overviewMenuItem.addActionListener(new java.awt.event.ActionListener()
@@ -1603,7 +1620,6 @@ public class GAlignFrame extends JInternalFrame
         font_actionPerformed(e);
       }
     });
-
     seqLimits.setText(MessageManager
             .getString("label.show_sequence_limits"));
     seqLimits.setState(jalview.bin.Cache.getDefault("SHOW_JVSUFFIX", true));
@@ -1860,6 +1876,17 @@ public class GAlignFrame extends JInternalFrame
       }
     });
 
+    annotationColumn.setText(MessageManager
+            .getString("action.select_by_annotation"));
+    annotationColumn.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        annotationColumn_actionPerformed(e);
+      }
+    });
+
     rnahelicesColour.setText(MessageManager
             .getString("action.by_rna_helixes"));
     rnahelicesColour.addActionListener(new ActionListener()
@@ -2353,6 +2380,7 @@ public class GAlignFrame extends JInternalFrame
     jMenu2.add(htmlMenuItem);
     jMenu2.add(epsFile);
     jMenu2.add(createPNG);
+    jMenu2.add(createBioJS);
     jMenu2.add(createSVG);
     addSequenceMenu.add(addFromFile);
     addSequenceMenu.add(addFromText);
@@ -2393,6 +2421,7 @@ public class GAlignFrame extends JInternalFrame
     selectMenu.add(unGroup);
     selectMenu.add(grpsFromSelection);
     selectMenu.add(deleteGroups);
+    selectMenu.add(annotationColumn);
     calculateMenu.add(expandAlignment);
     // TODO - determine if the listenToViewSelections button is needed : see bug
     // JAL-574
@@ -2611,6 +2640,11 @@ public class GAlignFrame extends JInternalFrame
   {
   }
 
+  protected void bioJSMenuItem_actionPerformed(ActionEvent e)
+  {
+
+  }
+
   protected void closeMenuItem_actionPerformed(boolean b)
   {
   }
@@ -2966,6 +3000,11 @@ public class GAlignFrame extends JInternalFrame
 
   }
 
+  public void annotationColumn_actionPerformed(ActionEvent e)
+  {
+
+  }
+
   public void rnahelicesColour_actionPerformed(ActionEvent e)
   {
 
index 610f32e..3596619 100755 (executable)
@@ -21,7 +21,7 @@
 package jalview.jbgui;
 
 import jalview.gui.JvSwingUtils;
-import jalview.gui.StructureViewer.Viewer;
+import jalview.gui.StructureViewer.ViewerType;
 import jalview.util.MessageManager;
 
 import java.awt.BorderLayout;
@@ -780,8 +780,8 @@ public class GPreferences extends JPanel
 
     structViewer.setFont(verdana11);
     structViewer.setBounds(new Rectangle(160, ypos, 120, height));
-    structViewer.addItem(Viewer.JMOL.name());
-    structViewer.addItem(Viewer.CHIMERA.name());
+    structViewer.addItem(ViewerType.JMOL.name());
+    structViewer.addItem(ViewerType.CHIMERA.name());
     structViewer.addActionListener(new ActionListener()
     {
       @Override
index 58b2eb1..24e7ee3 100644 (file)
@@ -20,6 +20,7 @@
  */
 package jalview.jbgui;
 
+import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.util.MessageManager;
 
 import java.awt.event.ActionEvent;
@@ -32,8 +33,52 @@ import javax.swing.JMenuBar;
 import javax.swing.JMenuItem;
 import javax.swing.JRadioButtonMenuItem;
 
-public class GStructureViewer extends JInternalFrame
+public abstract class GStructureViewer extends JInternalFrame implements
+        JalviewStructureDisplayI
 {
+  // private AAStructureBindingModel bindingModel;
+
+  protected JMenu savemenu = new JMenu();
+
+  protected JMenu viewMenu = new JMenu();
+
+  protected JMenu chainMenu = new JMenu();
+
+  protected JMenu viewerActionMenu = new JMenu();
+
+  protected JMenuItem alignStructs = new JMenuItem();
+
+  protected JRadioButtonMenuItem seqColour = new JRadioButtonMenuItem();
+
+  protected JRadioButtonMenuItem chainColour = new JRadioButtonMenuItem();
+
+  protected JRadioButtonMenuItem chargeColour = new JRadioButtonMenuItem();
+
+  protected JRadioButtonMenuItem zappoColour = new JRadioButtonMenuItem();
+
+  protected JRadioButtonMenuItem taylorColour = new JRadioButtonMenuItem();
+
+  protected JRadioButtonMenuItem hydroColour = new JRadioButtonMenuItem();
+
+  protected JRadioButtonMenuItem strandColour = new JRadioButtonMenuItem();
+
+  protected JRadioButtonMenuItem helixColour = new JRadioButtonMenuItem();
+
+  protected JRadioButtonMenuItem turnColour = new JRadioButtonMenuItem();
+
+  protected JRadioButtonMenuItem buriedColour = new JRadioButtonMenuItem();
+
+  protected JRadioButtonMenuItem purinePyrimidineColour = new JRadioButtonMenuItem();
+
+  protected JRadioButtonMenuItem userColour = new JRadioButtonMenuItem();
+
+  protected JRadioButtonMenuItem viewerColour = new JRadioButtonMenuItem();
+
+  protected JMenuItem helpItem = new JMenuItem();
+
+  /**
+   * Constructor
+   */
   public GStructureViewer()
   {
     try
@@ -47,10 +92,16 @@ public class GStructureViewer extends JInternalFrame
 
   private void jbInit() throws Exception
   {
+    JMenuBar menuBar = new JMenuBar();
     this.setJMenuBar(menuBar);
+
+    JMenu fileMenu = new JMenu();
     fileMenu.setText(MessageManager.getString("action.file"));
+
     savemenu.setActionCommand(MessageManager.getString("action.save_image"));
     savemenu.setText(MessageManager.getString("action.save_as"));
+
+    JMenuItem pdbFile = new JMenuItem();
     pdbFile.setText(MessageManager.getString("label.pdb_file"));
     pdbFile.addActionListener(new ActionListener()
     {
@@ -59,6 +110,8 @@ public class GStructureViewer extends JInternalFrame
         pdbFile_actionPerformed(actionEvent);
       }
     });
+
+    JMenuItem png = new JMenuItem();
     png.setText("PNG");
     png.addActionListener(new ActionListener()
     {
@@ -67,6 +120,8 @@ public class GStructureViewer extends JInternalFrame
         png_actionPerformed(actionEvent);
       }
     });
+
+    JMenuItem eps = new JMenuItem();
     eps.setText("EPS");
     eps.addActionListener(new ActionListener()
     {
@@ -75,6 +130,8 @@ public class GStructureViewer extends JInternalFrame
         eps_actionPerformed(actionEvent);
       }
     });
+
+    JMenuItem viewMapping = new JMenuItem();
     viewMapping.setText(MessageManager.getString("label.view_mapping"));
     viewMapping.addActionListener(new ActionListener()
     {
@@ -85,7 +142,11 @@ public class GStructureViewer extends JInternalFrame
     });
     viewMenu.setText(MessageManager.getString("action.view"));
     chainMenu.setText(MessageManager.getString("action.show_chain"));
+
+    JMenu colourMenu = new JMenu();
     colourMenu.setText(MessageManager.getString("label.colours"));
+
+    JMenuItem backGround = new JMenuItem();
     backGround.setText(MessageManager.getString("label.background_colour")
             + "...");
     backGround.addActionListener(new ActionListener()
@@ -207,6 +268,8 @@ public class GStructureViewer extends JInternalFrame
         viewerColour_actionPerformed(actionEvent);
       }
     });
+
+    JMenu helpMenu = new JMenu();
     helpMenu.setText(MessageManager.getString("action.help"));
     helpItem.setText(MessageManager.getString("label.jmol_help"));
     helpItem.addActionListener(new ActionListener()
@@ -254,6 +317,8 @@ public class GStructureViewer extends JInternalFrame
     colourMenu.add(viewerColour);
     colourMenu.add(backGround);
 
+    ButtonGroup colourButtons = new ButtonGroup();
+
     colourButtons.add(seqColour);
     colourButtons.add(chainColour);
     colourButtons.add(chargeColour);
@@ -279,66 +344,6 @@ public class GStructureViewer extends JInternalFrame
   {
   }
 
-  JMenuBar menuBar = new JMenuBar();
-
-  JMenu fileMenu = new JMenu();
-
-  protected JMenu savemenu = new JMenu();
-
-  JMenuItem pdbFile = new JMenuItem();
-
-  JMenuItem png = new JMenuItem();
-
-  JMenuItem eps = new JMenuItem();
-
-  JMenuItem viewMapping = new JMenuItem();
-
-  protected JMenu viewMenu = new JMenu();
-
-  protected JMenu chainMenu = new JMenu();
-
-  JMenu jMenu1 = new JMenu();
-
-  protected JMenu colourMenu = new JMenu();
-
-  protected JMenu viewerActionMenu = new JMenu();
-
-  protected JMenuItem alignStructs = new JMenuItem();
-
-  JMenuItem backGround = new JMenuItem();
-
-  protected JRadioButtonMenuItem seqColour = new JRadioButtonMenuItem();
-
-  protected JRadioButtonMenuItem chainColour = new JRadioButtonMenuItem();
-
-  protected JRadioButtonMenuItem chargeColour = new JRadioButtonMenuItem();
-
-  protected JRadioButtonMenuItem zappoColour = new JRadioButtonMenuItem();
-
-  protected JRadioButtonMenuItem taylorColour = new JRadioButtonMenuItem();
-
-  protected JRadioButtonMenuItem hydroColour = new JRadioButtonMenuItem();
-
-  protected JRadioButtonMenuItem strandColour = new JRadioButtonMenuItem();
-
-  protected JRadioButtonMenuItem helixColour = new JRadioButtonMenuItem();
-
-  protected JRadioButtonMenuItem turnColour = new JRadioButtonMenuItem();
-
-  protected JRadioButtonMenuItem buriedColour = new JRadioButtonMenuItem();
-
-  protected JRadioButtonMenuItem purinePyrimidineColour = new JRadioButtonMenuItem();
-
-  protected JRadioButtonMenuItem userColour = new JRadioButtonMenuItem();
-
-  protected JRadioButtonMenuItem viewerColour = new JRadioButtonMenuItem();
-
-  protected ButtonGroup colourButtons = new ButtonGroup();
-
-  JMenu helpMenu = new JMenu();
-
-  protected JMenuItem helpItem = new JMenuItem();
-
   public void pdbFile_actionPerformed(ActionEvent actionEvent)
   {
 
@@ -428,4 +433,14 @@ public class GStructureViewer extends JInternalFrame
   {
 
   }
+
+  // {
+  // return bindingModel;
+  // }
+
+  // public void setBindingModel(AAStructureBindingModel bindingModel)
+  // {
+  // this.bindingModel = bindingModel;
+  // }
+
 }
diff --git a/src/jalview/json/binding/v1/BioJsAlignmentPojo.java b/src/jalview/json/binding/v1/BioJsAlignmentPojo.java
new file mode 100644 (file)
index 0000000..8e8747f
--- /dev/null
@@ -0,0 +1,151 @@
+package jalview.json.binding.v1;
+
+import jalview.schemes.Blosum62ColourScheme;
+import jalview.schemes.BuriedColourScheme;
+import jalview.schemes.ColourSchemeI;
+import jalview.schemes.HelixColourScheme;
+import jalview.schemes.HydrophobicColourScheme;
+import jalview.schemes.NucleotideColourScheme;
+import jalview.schemes.PIDColourScheme;
+import jalview.schemes.PurinePyrimidineColourScheme;
+import jalview.schemes.RNAInteractionColourScheme;
+import jalview.schemes.StrandColourScheme;
+import jalview.schemes.TaylorColourScheme;
+import jalview.schemes.TurnColourScheme;
+import jalview.schemes.ZappoColourScheme;
+
+import java.util.ArrayList;
+
+public class BioJsAlignmentPojo
+{
+  private String globalColorScheme = "none";
+
+  private String jalviewVersion;
+
+  private String webStartUrl;
+  private ArrayList<BioJsSeqPojo> seqs = new ArrayList<BioJsSeqPojo>();
+
+  public BioJsAlignmentPojo()
+  {
+
+  }
+  public ArrayList<BioJsSeqPojo> getSeqs()
+  {
+    return seqs;
+  }
+
+  public void setSeqs(ArrayList<BioJsSeqPojo> seqs)
+  {
+    this.seqs = seqs;
+  }
+  public String getGlobalColorScheme()
+  {
+    return globalColorScheme;
+  }
+  public void setGlobalColorScheme(String globalColorScheme)
+  {
+    for (JalviewBioJsColorSchemeMapper cs : JalviewBioJsColorSchemeMapper
+            .values())
+    {
+      if (cs.getJalviewName().equals(globalColorScheme))
+      {
+        this.globalColorScheme = cs.getBioJsName();
+        break;
+      }
+    }
+
+    // JALVIEW colors not in biojs
+    // Blosum62
+    // T-Coffee Scores (almost same with Blosom62
+    // RNA Interaction type - no color applied
+    // RNA Helices - missing
+
+    // BIOJS Colour not in jalview
+    // schemes.push name: "Lesk", id: "lesk"
+    // schemes.push name: "Cinema", id: "cinema"
+    // schemes.push name: "MAE", id: "mae"
+    // schemes.push name: "Clustal2", id: "clustal2"
+
+  }
+
+
+  public String getJalviewVersion()
+  {
+    return jalviewVersion;
+  }
+
+  public void setJalviewVersion(String jalviewVersion)
+  {
+    this.jalviewVersion = jalviewVersion;
+  }
+
+  public String getWebStartUrl()
+  {
+    return webStartUrl;
+  }
+
+  public void setWebStartUrl(String webStartUrl)
+  {
+    this.webStartUrl = webStartUrl;
+  }
+
+  public enum JalviewBioJsColorSchemeMapper
+  {
+    USER_DEFINED("User Defined", "user defined", null), NONE("None", "foo",
+            null), CLUSTAL("Clustal", "clustal", null), ZAPPO("Zappo",
+            "zappo", new ZappoColourScheme()), TAYLOR(
+            "Taylor", "taylor", new TaylorColourScheme()), NUCLEOTIDE(
+            "Nucleotide", "nucleotide", new NucleotideColourScheme()), PURINE_PYRIMIDINE(
+            "Purine/Pyrimidine", "purine",
+            new PurinePyrimidineColourScheme()), HELIX_PROPENCITY(
+            "Helix Propensity", "helix", new HelixColourScheme()), TURN_PROPENSITY(
+            "Turn Propensity", "turn", new TurnColourScheme()), STRAND_PROPENSITY(
+            "Strand Propensity", "strand", new StrandColourScheme()), BURIED_INDEX(
+            "Buried Index", "buried", new BuriedColourScheme()), HYDROPHOBIC(
+            "Hydrophobic", "hydro", new HydrophobicColourScheme()),
+
+    // The color types below are not yet supported by BioJs MSA viewer
+    T_COFFE_SCORES("T-Coffee Scores", "T-Coffee Scores",
+ null), RNA_INT_TYPE(
+            "RNA Interaction type", "RNA Interaction type",
+            new RNAInteractionColourScheme()), BLOSUM62("Blosum62",
+            "Blosum62", new Blosum62ColourScheme()), RNA_HELICES(
+            "RNA Helices", "RNA Helices", null), PERCENTAGE_IDENTITY(
+            "% Identity", "pid",
+            new PIDColourScheme());
+
+    private String jalviewName;
+    private String bioJsName;
+
+    private ColourSchemeI jvColourScheme;
+
+    private JalviewBioJsColorSchemeMapper(String jalviewName,
+            String bioJsName, ColourSchemeI jvColourScheme)
+    {
+      this.jalviewName = jalviewName;
+      this.bioJsName = bioJsName;
+      this.setJvColourScheme(jvColourScheme);
+    }
+
+    public String getJalviewName()
+    {
+      return jalviewName;
+    }
+
+    public String getBioJsName()
+    {
+      return bioJsName;
+    }
+
+    public ColourSchemeI getJvColourScheme()
+    {
+      return jvColourScheme;
+    }
+
+    public void setJvColourScheme(ColourSchemeI jvColourScheme)
+    {
+      this.jvColourScheme = jvColourScheme;
+    }
+
+  }
+}
diff --git a/src/jalview/json/binding/v1/BioJsFeaturePojo.java b/src/jalview/json/binding/v1/BioJsFeaturePojo.java
new file mode 100644 (file)
index 0000000..3c2fdda
--- /dev/null
@@ -0,0 +1,60 @@
+package jalview.json.binding.v1;
+
+public class BioJsFeaturePojo
+{
+
+  private int xstart;
+
+  private int xend;
+
+  private String text;
+
+  private String fillColor;
+
+  public BioJsFeaturePojo()
+  {
+  }
+
+
+  public String getText()
+  {
+    return text;
+  }
+
+  public void setText(String text)
+  {
+    this.text = text;
+  }
+
+  public String getFillColor()
+  {
+    return "#" + fillColor;
+  }
+
+  public void setFillColor(String fillColor)
+  {
+    this.fillColor = fillColor;
+  }
+
+  public int getXstart()
+  {
+    return xstart;
+  }
+
+  public void setXstart(int xstart)
+  {
+    this.xstart = xstart;
+  }
+
+  public int getXend()
+  {
+    return xend;
+  }
+
+  public void setXend(int xend)
+  {
+    this.xend = xend;
+  }
+
+
+}
diff --git a/src/jalview/json/binding/v1/BioJsSeqPojo.java b/src/jalview/json/binding/v1/BioJsSeqPojo.java
new file mode 100644 (file)
index 0000000..bac8601
--- /dev/null
@@ -0,0 +1,90 @@
+package jalview.json.binding.v1;
+
+import java.util.ArrayList;
+
+
+public class BioJsSeqPojo
+{
+  private String seq;
+
+  private String name;
+
+  private String id;
+
+  private int start;
+
+  private int end;
+
+  private ArrayList<BioJsFeaturePojo> features = new ArrayList<BioJsFeaturePojo>();
+
+  public BioJsSeqPojo()
+  {
+  }
+
+  public BioJsSeqPojo(int start, int end, String id, String name, String seq)
+  {
+    this.id = id;
+    this.name = name;
+    this.seq = seq;
+  }
+  public String getSeq()
+  {
+    return seq;
+  }
+
+  public void setSeq(String seq)
+  {
+    this.seq = seq;
+  }
+
+  public String getName()
+  {
+
+    return name;
+  }
+
+  public void setName(String name)
+  {
+    this.name = name;
+  }
+
+  public String getId()
+  {
+    return id;
+  }
+
+  public void setId(String id)
+  {
+    this.id = id;
+  }
+
+  public int getStart()
+  {
+    return start;
+  }
+
+  public void setStart(int start)
+  {
+    this.start = start;
+  }
+
+  public int getEnd()
+  {
+    return end;
+  }
+
+  public void setEnd(int end)
+  {
+    this.end = end;
+  }
+
+  public ArrayList<BioJsFeaturePojo> getFeatures()
+  {
+    return features;
+  }
+
+  public void setFeatures(ArrayList<BioJsFeaturePojo> features)
+  {
+    this.features = features;
+  }
+}
diff --git a/src/jalview/math/AlignmentDimension.java b/src/jalview/math/AlignmentDimension.java
new file mode 100644 (file)
index 0000000..ea3abf4
--- /dev/null
@@ -0,0 +1,35 @@
+package jalview.math;
+
+public class AlignmentDimension
+{
+  private int width;
+
+  private int height;
+
+  public AlignmentDimension(int width, int height)
+  {
+    this.width = width;
+    this.height = height;
+  }
+
+  public int getWidth()
+  {
+    return width;
+  }
+
+  public void setWidth(int width)
+  {
+    this.width = width;
+  }
+
+  public int getHeight()
+  {
+    return height;
+  }
+
+  public void setHeight(int height)
+  {
+    this.height = height;
+  }
+
+}
diff --git a/src/jalview/renderer/seqfeatures/FeatureRenderer.java b/src/jalview/renderer/seqfeatures/FeatureRenderer.java
new file mode 100644 (file)
index 0000000..5e6ac29
--- /dev/null
@@ -0,0 +1,435 @@
+package jalview.renderer.seqfeatures;
+
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+
+public class FeatureRenderer extends
+        jalview.viewmodel.seqfeatures.FeatureRendererModel
+{
+
+  FontMetrics fm;
+
+  int charOffset;
+
+  boolean offscreenRender = false;
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param g
+   *          DOCUMENT ME!
+   * @param seq
+   *          DOCUMENT ME!
+   * @param sg
+   *          DOCUMENT ME!
+   * @param start
+   *          DOCUMENT ME!
+   * @param end
+   *          DOCUMENT ME!
+   * @param x1
+   *          DOCUMENT ME!
+   * @param y1
+   *          DOCUMENT ME!
+   * @param width
+   *          DOCUMENT ME!
+   * @param height
+   *          DOCUMENT ME!
+   */
+  protected SequenceI lastSeq;
+
+  char s;
+
+  int i;
+
+  int av_charHeight, av_charWidth;
+
+  boolean av_validCharWidth, av_isShowSeqFeatureHeight;
+
+  protected void updateAvConfig()
+  {
+    av_charHeight = av.getCharHeight();
+    av_charWidth = av.getCharWidth();
+    av_validCharWidth = av.isValidCharWidth();
+    av_isShowSeqFeatureHeight = av.isShowSequenceFeaturesHeight();
+  }
+
+  void renderFeature(Graphics g, SequenceI seq, int fstart, int fend,
+          Color featureColour, int start, int end, int y1)
+  {
+    updateAvConfig();
+    if (((fstart <= end) && (fend >= start)))
+    {
+      if (fstart < start)
+      { // fix for if the feature we have starts before the sequence start,
+        fstart = start; // but the feature end is still valid!!
+      }
+
+      if (fend >= end)
+      {
+        fend = end;
+      }
+      int pady = (y1 + av_charHeight) - av_charHeight / 5;
+      for (i = fstart; i <= fend; i++)
+      {
+        s = seq.getCharAt(i);
+
+        if (jalview.util.Comparison.isGap(s))
+        {
+          continue;
+        }
+
+        g.setColor(featureColour);
+
+        g.fillRect((i - start) * av_charWidth, y1, av_charWidth,
+                av_charHeight);
+
+        if (offscreenRender || !av_validCharWidth)
+        {
+          continue;
+        }
+
+        g.setColor(Color.white);
+        charOffset = (av_charWidth - fm.charWidth(s)) / 2;
+        g.drawString(String.valueOf(s), charOffset
+                + (av_charWidth * (i - start)), pady);
+
+      }
+    }
+  }
+
+  void renderScoreFeature(Graphics g, SequenceI seq, int fstart, int fend,
+          Color featureColour, int start, int end, int y1, byte[] bs)
+  {
+    updateAvConfig();
+    if (((fstart <= end) && (fend >= start)))
+    {
+      if (fstart < start)
+      { // fix for if the feature we have starts before the sequence start,
+        fstart = start; // but the feature end is still valid!!
+      }
+
+      if (fend >= end)
+      {
+        fend = end;
+      }
+      int pady = (y1 + av_charHeight) - av_charHeight / 5;
+      int ystrt = 0, yend = av_charHeight;
+      if (bs[0] != 0)
+      {
+        // signed - zero is always middle of residue line.
+        if (bs[1] < 128)
+        {
+          yend = av_charHeight * (128 - bs[1]) / 512;
+          ystrt = av_charHeight - yend / 2;
+        }
+        else
+        {
+          ystrt = av_charHeight / 2;
+          yend = av_charHeight * (bs[1] - 128) / 512;
+        }
+      }
+      else
+      {
+        yend = av_charHeight * bs[1] / 255;
+        ystrt = av_charHeight - yend;
+
+      }
+      for (i = fstart; i <= fend; i++)
+      {
+        s = seq.getCharAt(i);
+
+        if (jalview.util.Comparison.isGap(s))
+        {
+          continue;
+        }
+
+        g.setColor(featureColour);
+        int x = (i - start) * av_charWidth;
+        g.drawRect(x, y1, av_charWidth, av_charHeight);
+        g.fillRect(x, y1 + ystrt, av_charWidth, yend);
+
+        if (offscreenRender || !av_validCharWidth)
+        {
+          continue;
+        }
+
+        g.setColor(Color.black);
+        charOffset = (av_charWidth - fm.charWidth(s)) / 2;
+        g.drawString(String.valueOf(s), charOffset
+                + (av_charWidth * (i - start)), pady);
+
+      }
+    }
+  }
+
+  BufferedImage offscreenImage;
+
+  public Color findFeatureColour(Color initialCol, SequenceI seq, int res)
+  {
+    return new Color(findFeatureColour(initialCol.getRGB(), seq, res));
+  }
+
+  /**
+   * This is used by the Molecule Viewer and Overview to get the accurate
+   * colourof the rendered sequence
+   */
+  public synchronized int findFeatureColour(int initialCol, final SequenceI seq,
+          int column)
+  {
+    if (!av.isShowSequenceFeatures())
+    {
+      return initialCol;
+    }
+
+    final SequenceI aseq = (seq.getDatasetSequence() != null) ? seq
+            .getDatasetSequence() : seq;
+    if (seq != lastSeq)
+    {
+      lastSeq = seq;
+      sequenceFeatures = aseq.getSequenceFeatures();
+      if (sequenceFeatures != null)
+      {
+        sfSize = sequenceFeatures.length;
+      }
+    }
+    else
+    {
+      if (sequenceFeatures != aseq.getSequenceFeatures())
+      {
+        sequenceFeatures = aseq.getSequenceFeatures();
+        if (sequenceFeatures != null)
+        {
+          sfSize = sequenceFeatures.length;
+        }
+      }
+    }
+
+    if (sequenceFeatures == null || sfSize == 0)
+    {
+      return initialCol;
+    }
+
+    if (jalview.util.Comparison.isGap(lastSeq.getCharAt(column)))
+    {
+      return Color.white.getRGB();
+    }
+
+    // Only bother making an offscreen image if transparency is applied
+    if (transparency != 1.0f && offscreenImage == null)
+    {
+      offscreenImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
+    }
+
+    currentColour = null;
+    // TODO: non-threadsafe - each rendering thread needs its own instance of
+    // the feature renderer - or this should be synchronized.
+    offscreenRender = true;
+
+    if (offscreenImage != null)
+    {
+      offscreenImage.setRGB(0, 0, initialCol);
+      drawSequence(offscreenImage.getGraphics(), lastSeq, column, column, 0);
+
+      return offscreenImage.getRGB(0, 0);
+    }
+    else
+    {
+      drawSequence(null, lastSeq, lastSeq.findPosition(column), -1, -1);
+
+      if (currentColour == null)
+      {
+        return initialCol;
+      }
+      else
+      {
+        return ((Integer) currentColour).intValue();
+      }
+    }
+
+  }
+
+  private volatile SequenceFeature[] sequenceFeatures;
+
+  int sfSize;
+
+  int sfindex;
+
+  int spos;
+
+  int epos;
+
+  public synchronized void drawSequence(Graphics g, final SequenceI seq,
+          int start, int end, int y1)
+  {
+    final SequenceI aseq = (seq.getDatasetSequence() != null) ? seq
+            .getDatasetSequence() : seq;
+    if (aseq.getSequenceFeatures() == null
+            || aseq.getSequenceFeatures().length == 0)
+    {
+      return;
+    }
+
+    if (g != null)
+    {
+      fm = g.getFontMetrics();
+    }
+
+    updateFeatures();
+
+    if (lastSeq == null || seq != lastSeq
+            || aseq.getSequenceFeatures() != sequenceFeatures)
+    {
+      lastSeq = seq;
+      sequenceFeatures = aseq.getSequenceFeatures();
+    }
+
+    if (transparency != 1 && g != null)
+    {
+      Graphics2D g2 = (Graphics2D) g;
+      g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
+              transparency));
+    }
+
+    if (!offscreenRender)
+    {
+      spos = lastSeq.findPosition(start);
+      epos = lastSeq.findPosition(end);
+    }
+
+    sfSize = sequenceFeatures.length;
+    String type;
+    for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
+    {
+      type = renderOrder[renderIndex];
+
+      if (type == null || !showFeatureOfType(type))
+      {
+        continue;
+      }
+
+      // loop through all features in sequence to find
+      // current feature to render
+      for (sfindex = 0; sfindex < sfSize; sfindex++)
+      {
+        if (!sequenceFeatures[sfindex].type.equals(type))
+        {
+          continue;
+        }
+
+        if (featureGroups != null
+                && sequenceFeatures[sfindex].featureGroup != null
+                && sequenceFeatures[sfindex].featureGroup.length() != 0
+                && featureGroups
+                        .containsKey(sequenceFeatures[sfindex].featureGroup)
+                && !featureGroups
+                        .get(sequenceFeatures[sfindex].featureGroup)
+                        .booleanValue())
+        {
+          continue;
+        }
+
+        if (!offscreenRender
+                && (sequenceFeatures[sfindex].getBegin() > epos || sequenceFeatures[sfindex]
+                        .getEnd() < spos))
+        {
+          continue;
+        }
+
+        if (offscreenRender && offscreenImage == null)
+        {
+          if (sequenceFeatures[sfindex].begin <= start
+                  && sequenceFeatures[sfindex].end >= start)
+          {
+            // this is passed out to the overview and other sequence renderers
+            // (e.g. molecule viewer) to get displayed colour for rendered
+            // sequence
+            currentColour = new Integer(
+                    getColour(sequenceFeatures[sfindex]).getRGB());
+            // used to be retreived from av.featuresDisplayed
+            // currentColour = av.featuresDisplayed
+            // .get(sequenceFeatures[sfindex].type);
+
+          }
+        }
+        else if (sequenceFeatures[sfindex].type.equals("disulfide bond"))
+        {
+
+          renderFeature(g, seq,
+                  seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
+                  seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
+                  getColour(sequenceFeatures[sfindex])
+                  // new Color(((Integer) av.featuresDisplayed
+                  // .get(sequenceFeatures[sfindex].type)).intValue())
+                  , start, end, y1);
+          renderFeature(g, seq,
+                  seq.findIndex(sequenceFeatures[sfindex].end) - 1,
+                  seq.findIndex(sequenceFeatures[sfindex].end) - 1,
+                  getColour(sequenceFeatures[sfindex])
+                  // new Color(((Integer) av.featuresDisplayed
+                  // .get(sequenceFeatures[sfindex].type)).intValue())
+                  , start, end, y1);
+
+        }
+        else if (showFeature(sequenceFeatures[sfindex]))
+        {
+          if (av_isShowSeqFeatureHeight
+                  && sequenceFeatures[sfindex].score != Float.NaN)
+          {
+            renderScoreFeature(g, seq,
+                    seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
+                    seq.findIndex(sequenceFeatures[sfindex].end) - 1,
+                    getColour(sequenceFeatures[sfindex]), start, end, y1,
+                    normaliseScore(sequenceFeatures[sfindex]));
+          }
+          else
+          {
+            renderFeature(g, seq,
+                    seq.findIndex(sequenceFeatures[sfindex].begin) - 1,
+                    seq.findIndex(sequenceFeatures[sfindex].end) - 1,
+                    getColour(sequenceFeatures[sfindex]), start, end, y1);
+          }
+        }
+
+      }
+
+    }
+
+    if (transparency != 1.0f && g != null && transparencyAvailable)
+    {
+      Graphics2D g2 = (Graphics2D) g;
+      g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
+              1.0f));
+    }
+  }
+
+  boolean transparencyAvailable = true;
+
+  protected void setTransparencyAvailable(boolean isTransparencyAvailable)
+  {
+    transparencyAvailable = isTransparencyAvailable;
+  }
+
+  @Override
+  public boolean isTransparencyAvailable()
+  {
+    return transparencyAvailable;
+  }
+
+  /**
+   * Called when alignment in associated view has new/modified features to
+   * discover and display.
+   * 
+   */
+  public void featuresAdded()
+  {
+    lastSeq = null;
+    findAllFeatures();
+  }
+}
index efad430..85ec3ae 100755 (executable)
@@ -258,7 +258,7 @@ public class AnnotationColourGradient extends FollowerColourScheme
   public Color findColour(char c, int j, SequenceI seq)
   {
     Color currentColour = Color.white;
-    AlignmentAnnotation annotation = (seqAssociated ? seqannot.get(seq)
+    AlignmentAnnotation annotation = (seqAssociated && seqannot!=null ? seqannot.get(seq)
             : this.annotation);
     if (annotation == null)
     {
index 41f7781..70c7685 100755 (executable)
 package jalview.schemes;
 
 import jalview.analysis.AAFrequency;
-
-import java.awt.Color;
-import java.util.Map;
-
 import jalview.datamodel.AnnotatedCollectionI;
 import jalview.datamodel.SequenceCollectionI;
 import jalview.datamodel.SequenceI;
 
+import java.awt.Color;
+import java.util.Map;
+
 public class Blosum62ColourScheme extends ResidueColourScheme
 {
   public Blosum62ColourScheme()
@@ -59,6 +58,7 @@ public class Blosum62ColourScheme extends ResidueColourScheme
 
       if (max.indexOf(res) > -1)
       {
+        // TODO use a constant here?
         currentColour = new Color(154, 154, 255);
       }
       else
@@ -74,6 +74,7 @@ public class Blosum62ColourScheme extends ResidueColourScheme
 
         if (c > 0)
         {
+          // TODO use a constant here?
           currentColour = new Color(204, 204, 255);
         }
         else
index 8acf1f2..d13f0a9 100755 (executable)
@@ -20,6 +20,7 @@
  */
 package jalview.schemes;
 
+import jalview.analysis.scoremodels.FeatureScoreModel;
 import jalview.analysis.scoremodels.PIDScoreModel;
 import jalview.api.analysis.ScoreModelI;
 
@@ -1498,6 +1499,7 @@ public class ResidueProperties
     // scoreMatrices.put("Conservation EnhPos", new
     // ScoreMatrix("Conservation EnhPos",propMatrixEpos,0));
     scoreMatrices.put("PID", new PIDScoreModel());
+    scoreMatrices.put("Displayed Features", new FeatureScoreModel());
   }
 
   private ResidueProperties()
diff --git a/src/jalview/structures/models/AAStructureBindingModel.java b/src/jalview/structures/models/AAStructureBindingModel.java
new file mode 100644 (file)
index 0000000..664c903
--- /dev/null
@@ -0,0 +1,364 @@
+package jalview.structures.models;
+
+import jalview.api.StructureSelectionManagerProvider;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.structure.StructureListener;
+import jalview.structure.StructureSelectionManager;
+import jalview.util.MessageManager;
+
+import java.awt.event.ComponentEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A base class to hold common function for protein structure model binding.
+ * Initial version created by refactoring JMol and Chimera binding models, but
+ * other structure viewers could in principle be accommodated in future.
+ * 
+ * @author gmcarstairs
+ *
+ */
+public abstract class AAStructureBindingModel extends
+        SequenceStructureBindingModel implements StructureListener,
+        StructureSelectionManagerProvider
+{
+
+  private StructureSelectionManager ssm;
+
+  private PDBEntry[] pdbEntry;
+
+  /*
+   * sequences mapped to each pdbentry
+   */
+  private SequenceI[][] sequence;
+
+  /*
+   * array of target chains for sequences - tied to pdbentry and sequence[]
+   */
+  private String[][] chains;
+
+  /*
+   * datasource protocol for access to PDBEntrylatest
+   */
+  String protocol = null;
+
+  protected boolean colourBySequence = true;
+
+  /**
+   * Constructor
+   * 
+   * @param ssm
+   * @param seqs
+   */
+  public AAStructureBindingModel(StructureSelectionManager ssm,
+          SequenceI[][] seqs)
+  {
+    this.ssm = ssm;
+    this.sequence = seqs;
+  }
+
+  /**
+   * Constructor
+   * 
+   * @param ssm
+   * @param pdbentry
+   * @param sequenceIs
+   * @param chains
+   * @param protocol
+   */
+  public AAStructureBindingModel(StructureSelectionManager ssm,
+          PDBEntry[] pdbentry, SequenceI[][] sequenceIs, String[][] chains,
+          String protocol)
+  {
+    this.ssm = ssm;
+    this.sequence = sequenceIs;
+    this.chains = chains;
+    this.pdbEntry = pdbentry;
+    this.protocol = protocol;
+    if (chains == null)
+    {
+      this.chains = new String[pdbentry.length][];
+    }
+  }
+
+  public StructureSelectionManager getSsm()
+  {
+    return ssm;
+  }
+
+  /**
+   * Returns the i'th PDBEntry (or null)
+   * 
+   * @param i
+   * @return
+   */
+  public PDBEntry getPdbEntry(int i)
+  {
+    return (pdbEntry != null && pdbEntry.length > i) ? pdbEntry[i] : null;
+  }
+
+  /**
+   * Returns the number of modelled PDB file entries.
+   * 
+   * @return
+   */
+  public int getPdbCount()
+  {
+    return pdbEntry == null ? 0 : pdbEntry.length;
+  }
+
+  public SequenceI[][] getSequence()
+  {
+    return sequence;
+  }
+
+  public String[][] getChains()
+  {
+    return chains;
+  }
+
+  public String getProtocol()
+  {
+    return protocol;
+  }
+
+  // TODO may remove this if calling methods can be pulled up here
+  protected void setPdbentry(PDBEntry[] pdbentry)
+  {
+    this.pdbEntry = pdbentry;
+  }
+
+  protected void setSequence(SequenceI[][] sequence)
+  {
+    this.sequence = sequence;
+  }
+
+  protected void setChains(String[][] chains)
+  {
+    this.chains = chains;
+  }
+
+  /**
+   * Construct a title string for the viewer window based on the data Jalview
+   * knows about
+   * @param viewerName TODO
+   * @param verbose
+   * 
+   * @return
+   */
+  public String getViewerTitle(String viewerName, boolean verbose)
+  {
+    if (getSequence() == null || getSequence().length < 1
+            || getPdbCount() < 1
+            || getSequence()[0].length < 1)
+    {
+      return ("Jalview " + viewerName + " Window");
+    }
+    // TODO: give a more informative title when multiple structures are
+    // displayed.
+    StringBuilder title = new StringBuilder(64);
+    final PDBEntry pdbEntry = getPdbEntry(0);
+    title.append(viewerName + " view for " + getSequence()[0][0].getName()
+            + ":"
+            + pdbEntry.getId());
+  
+    if (verbose)
+    {
+      if (pdbEntry.getProperty() != null)
+      {
+        if (pdbEntry.getProperty().get("method") != null)
+        {
+          title.append(" Method: ");
+          title.append(pdbEntry.getProperty().get("method"));
+        }
+        if (pdbEntry.getProperty().get("chains") != null)
+        {
+          title.append(" Chain:");
+          title.append(pdbEntry.getProperty().get("chains"));
+        }
+      }
+    }
+    return title.toString();
+  }
+
+  /**
+   * Called by after closeViewer is called, to release any resources and
+   * references so they can be garbage collected. Override if needed.
+   */
+  protected void releaseUIResources()
+  {
+
+  }
+
+  public boolean isColourBySequence()
+  {
+    return colourBySequence;
+  }
+
+  public void setColourBySequence(boolean colourBySequence)
+  {
+    this.colourBySequence = colourBySequence;
+  }
+
+  protected void addSequenceAndChain(int pe, SequenceI[] seq,
+          String[] tchain)
+  {
+    if (pe < 0 || pe >= getPdbCount())
+    {
+      throw new Error(MessageManager.formatMessage(
+              "error.implementation_error_no_pdbentry_from_index",
+              new Object[]
+              { Integer.valueOf(pe).toString() }));
+    }
+    final String nullChain = "TheNullChain";
+    List<SequenceI> s = new ArrayList<SequenceI>();
+    List<String> c = new ArrayList<String>();
+    if (getChains() == null)
+    {
+      setChains(new String[getPdbCount()][]);
+    }
+    if (getSequence()[pe] != null)
+    {
+      for (int i = 0; i < getSequence()[pe].length; i++)
+      {
+        s.add(getSequence()[pe][i]);
+        if (getChains()[pe] != null)
+        {
+          if (i < getChains()[pe].length)
+          {
+            c.add(getChains()[pe][i]);
+          }
+          else
+          {
+            c.add(nullChain);
+          }
+        }
+        else
+        {
+          if (tchain != null && tchain.length > 0)
+          {
+            c.add(nullChain);
+          }
+        }
+      }
+    }
+    for (int i = 0; i < seq.length; i++)
+    {
+      if (!s.contains(seq[i]))
+      {
+        s.add(seq[i]);
+        if (tchain != null && i < tchain.length)
+        {
+          c.add(tchain[i] == null ? nullChain : tchain[i]);
+        }
+      }
+    }
+    SequenceI[] tmp = s.toArray(new SequenceI[s.size()]);
+    getSequence()[pe] = tmp;
+    if (c.size() > 0)
+    {
+      String[] tch = c.toArray(new String[c.size()]);
+      for (int i = 0; i < tch.length; i++)
+      {
+        if (tch[i] == nullChain)
+        {
+          tch[i] = null;
+        }
+      }
+      getChains()[pe] = tch;
+    }
+    else
+    {
+      getChains()[pe] = null;
+    }
+  }
+
+  /**
+   * add structures and any known sequence associations
+   * 
+   * @returns the pdb entries added to the current set.
+   */
+  public synchronized PDBEntry[] addSequenceAndChain(PDBEntry[] pdbe, SequenceI[][] seq,
+          String[][] chns)
+  {
+    List<PDBEntry> v = new ArrayList<PDBEntry>();
+    List<int[]> rtn = new ArrayList<int[]>();
+    for (int i = 0; i < getPdbCount(); i++)
+    {
+      v.add(getPdbEntry(i));
+    }
+    for (int i = 0; i < pdbe.length; i++)
+    {
+      int r = v.indexOf(pdbe[i]);
+      if (r == -1 || r >= getPdbCount())
+      {
+        rtn.add(new int[]
+        { v.size(), i });
+        v.add(pdbe[i]);
+      }
+      else
+      {
+        // just make sure the sequence/chain entries are all up to date
+        addSequenceAndChain(r, seq[i], chns[i]);
+      }
+    }
+    pdbe = v.toArray(new PDBEntry[v.size()]);
+    setPdbentry(pdbe);
+    if (rtn.size() > 0)
+    {
+      // expand the tied sequence[] and string[] arrays
+      SequenceI[][] sqs = new SequenceI[getPdbCount()][];
+      String[][] sch = new String[getPdbCount()][];
+      System.arraycopy(getSequence(), 0, sqs, 0, getSequence().length);
+      System.arraycopy(getChains(), 0, sch, 0, this.getChains().length);
+      setSequence(sqs);
+      setChains(sch);
+      pdbe = new PDBEntry[rtn.size()];
+      for (int r = 0; r < pdbe.length; r++)
+      {
+        int[] stri = (rtn.get(r));
+        // record the pdb file as a new addition
+        pdbe[r] = getPdbEntry(stri[0]);
+        // and add the new sequence/chain entries
+        addSequenceAndChain(stri[0], seq[stri[1]], chns[stri[1]]);
+      }
+    }
+    else
+    {
+      pdbe = null;
+    }
+    return pdbe;
+  }
+
+  /**
+   * Add sequences to the pe'th pdbentry's sequence set.
+   * 
+   * @param pe
+   * @param seq
+   */
+  public void addSequence(int pe, SequenceI[] seq)
+  {
+    addSequenceAndChain(pe, seq, null);
+  }
+
+  /**
+   * add the given sequences to the mapping scope for the given pdb file handle
+   * 
+   * @param pdbFile
+   *          - pdbFile identifier
+   * @param seq
+   *          - set of sequences it can be mapped to
+   */
+  public void addSequenceForStructFile(String pdbFile, SequenceI[] seq)
+  {
+    for (int pe = 0; pe < getPdbCount(); pe++)
+    {
+      if (getPdbEntry(pe).getFile().equals(pdbFile))
+      {
+        addSequence(pe, seq);
+      }
+    }
+  }
+
+}
\ No newline at end of file
index 7d023a8..d758726 100644 (file)
@@ -48,8 +48,8 @@ public class SequenceStructureBindingModel implements
 
   /**
    * 
-   * @return true if Jmol is still restoring state or loading is still going on
-   *         (see setFinsihedLoadingFromArchive)
+   * @return true if viewer is still restoring state or loading is still going
+   *         on (see setFinishedLoadingFromArchive)
    */
   @Override
   public boolean isLoadingFromArchive()
index fd76086..44ce010 100644 (file)
@@ -60,6 +60,26 @@ public class ColorUtils
   }
 
   /**
+   * Convert to Tk colour code format
+   * 
+   * @param colour
+   * @return
+   * @see http
+   *      ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/colortool.html#
+   *      tkcode
+   */
+  public static final String toTkCode(Color colour)
+  {
+    String colstring = "#" + ((colour.getRed() < 16) ? "0" : "")
+            + Integer.toHexString(colour.getRed())
+            + ((colour.getGreen() < 16) ? "0" : "")
+            + Integer.toHexString(colour.getGreen())
+            + ((colour.getBlue() < 16) ? "0" : "")
+            + Integer.toHexString(colour.getBlue());
+    return colstring;
+  }
+
+  /**
    * Returns a colour three shades darker. Note you can't guarantee that
    * brighterThan reverses this, as darkerThan may result in black.
    * 
index 1b42faf..08b31fa 100644 (file)
@@ -24,6 +24,7 @@ import jalview.analysis.Conservation;
 import jalview.api.AlignCalcManagerI;
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
+import jalview.api.FeaturesDisplayedI;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentView;
@@ -636,10 +637,6 @@ public abstract class AlignmentViewport implements AlignViewportI
   public void setHiddenColumns(ColumnSelection colsel)
   {
     this.colSel = colsel;
-    if (colSel.getHiddenColumns() != null)
-    {
-      hasHiddenColumns = true;
-    }
   }
 
   @Override
@@ -648,9 +645,14 @@ public abstract class AlignmentViewport implements AlignViewportI
     return colSel;
   }
 
+  @Override
   public void setColumnSelection(ColumnSelection colSel)
   {
     this.colSel = colSel;
+    if (colSel != null)
+    {
+      updateHiddenColumns();
+    }
   }
 
   /**
@@ -670,15 +672,22 @@ public abstract class AlignmentViewport implements AlignViewportI
     this.hiddenRepSequences = hiddenRepSequences;
   }
 
-  protected boolean hasHiddenColumns = false;
+  @Override
+  public boolean hasHiddenColumns()
+  {
+    return colSel != null && colSel.hasHiddenColumns();
+  }
 
   public void updateHiddenColumns()
   {
-    hasHiddenColumns = colSel.getHiddenColumns() != null;
+    // this method doesn't really do anything now. But - it could, since a
+    // column Selection could be in the process of modification
+    // hasHiddenColumns = colSel.hasHiddenColumns();
   }
 
   protected boolean hasHiddenRows = false;
 
+  @Override
   public boolean hasHiddenRows()
   {
     return hasHiddenRows;
@@ -696,6 +705,7 @@ public abstract class AlignmentViewport implements AlignViewportI
     sequenceSetID = new String(newid);
   }
 
+  @Override
   public String getSequenceSetId()
   {
     if (sequenceSetID == null)
@@ -857,7 +867,6 @@ public abstract class AlignmentViewport implements AlignViewportI
     colSel.hideSelectedColumns();
     setSelectionGroup(null);
 
-    hasHiddenColumns = true;
   }
 
   public void hideColumns(int start, int end)
@@ -870,23 +879,17 @@ public abstract class AlignmentViewport implements AlignViewportI
     {
       colSel.hideColumns(start, end);
     }
-
-    hasHiddenColumns = true;
   }
 
   public void showColumn(int col)
   {
     colSel.revealHiddenColumns(col);
-    if (colSel.getHiddenColumns() == null)
-    {
-      hasHiddenColumns = false;
-    }
+
   }
 
   public void showAllHiddenColumns()
   {
     colSel.revealAllHiddenColumns();
-    hasHiddenColumns = false;
   }
 
   // common hide/show seq stuff
@@ -1029,8 +1032,8 @@ public abstract class AlignmentViewport implements AlignViewportI
 
   public boolean isHiddenRepSequence(SequenceI seq)
   {
-    return hiddenRepSequences != null
-            && hiddenRepSequences.containsKey(seq);
+    return alignment.getSeqrep()==seq || (hiddenRepSequences != null
+            && hiddenRepSequences.containsKey(seq));
   }
 
   public SequenceGroup getRepresentedSequences(SequenceI seq)
@@ -1039,32 +1042,24 @@ public abstract class AlignmentViewport implements AlignViewportI
             : hiddenRepSequences.get(seq));
   }
 
+  @Override
   public int adjustForHiddenSeqs(int alignmentIndex)
   {
     return alignment.getHiddenSequences().adjustForHiddenSeqs(
             alignmentIndex);
   }
 
-  // Selection manipulation
-  /**
-   * broadcast selection to any interested parties
-   */
+  @Override
   public abstract void sendSelection();
 
+  @Override
   public void invertColumnSelection()
   {
     colSel.invertColumnSelection(0, alignment.getWidth());
   }
 
-  /**
-   * This method returns an array of new SequenceI objects derived from the
-   * whole alignment or just the current selection with start and end points
-   * adjusted
-   * 
-   * @note if you need references to the actual SequenceI objects in the
-   *       alignment or currently selected then use getSequenceSelection()
-   * @return selection as new sequenceI objects
-   */
+
+  @Override
   public SequenceI[] getSelectionAsNewSequence()
   {
     SequenceI[] sequences;
@@ -1093,12 +1088,7 @@ public abstract class AlignmentViewport implements AlignViewportI
     return sequences;
   }
 
-  /**
-   * get the currently selected sequence objects or all the sequences in the
-   * alignment.
-   * 
-   * @return array of references to sequence objects
-   */
+
   @Override
   public SequenceI[] getSequenceSelection()
   {
@@ -1114,31 +1104,16 @@ public abstract class AlignmentViewport implements AlignViewportI
     return sequences;
   }
 
-  /**
-   * This method returns the visible alignment as text, as seen on the GUI, ie
-   * if columns are hidden they will not be returned in the result. Use this for
-   * calculating trees, PCA, redundancy etc on views which contain hidden
-   * columns.
-   * 
-   * @return String[]
-   */
+
   @Override
   public jalview.datamodel.CigarArray getViewAsCigars(
           boolean selectedRegionOnly)
   {
-    return new jalview.datamodel.CigarArray(alignment,
-            (hasHiddenColumns ? colSel : null),
+    return new jalview.datamodel.CigarArray(alignment, colSel,
             (selectedRegionOnly ? selectionGroup : null));
   }
 
-  /**
-   * return a compact representation of the current alignment selection to pass
-   * to an analysis function
-   * 
-   * @param selectedOnly
-   *          boolean true to just return the selected view
-   * @return AlignmentView
-   */
+
   @Override
   public jalview.datamodel.AlignmentView getAlignmentView(
           boolean selectedOnly)
@@ -1146,34 +1121,17 @@ public abstract class AlignmentViewport implements AlignViewportI
     return getAlignmentView(selectedOnly, false);
   }
 
-  /**
-   * return a compact representation of the current alignment selection to pass
-   * to an analysis function
-   * 
-   * @param selectedOnly
-   *          boolean true to just return the selected view
-   * @param markGroups
-   *          boolean true to annotate the alignment view with groups on the
-   *          alignment (and intersecting with selected region if selectedOnly
-   *          is true)
-   * @return AlignmentView
-   */
+
   @Override
   public jalview.datamodel.AlignmentView getAlignmentView(
           boolean selectedOnly, boolean markGroups)
   {
     return new AlignmentView(alignment, colSel, selectionGroup,
-            hasHiddenColumns, selectedOnly, markGroups);
+            colSel != null && colSel.hasHiddenColumns(), selectedOnly,
+            markGroups);
   }
 
-  /**
-   * This method returns the visible alignment as text, as seen on the GUI, ie
-   * if columns are hidden they will not be returned in the result. Use this for
-   * calculating trees, PCA, redundancy etc on views which contain hidden
-   * columns.
-   * 
-   * @return String[]
-   */
+
   @Override
   public String[] getViewAsString(boolean selectedRegionOnly)
   {
@@ -1196,7 +1154,7 @@ public abstract class AlignmentViewport implements AlignViewportI
     }
 
     selection = new String[iSize];
-    if (hasHiddenColumns)
+    if (colSel != null && colSel.hasHiddenColumns())
     {
       selection = colSel.getVisibleSequenceStrings(start, end, seqs);
     }
@@ -1211,15 +1169,8 @@ public abstract class AlignmentViewport implements AlignViewportI
     return selection;
   }
 
-  /**
-   * return visible region boundaries within given column range
-   * 
-   * @param min
-   *          first column (inclusive, from 0)
-   * @param max
-   *          last column (exclusive)
-   * @return int[][] range of {start,end} visible positions
-   */
+
+  @Override
   public int[][] getVisibleRegionBoundaries(int min, int max)
   {
     Vector regions = new Vector();
@@ -1228,7 +1179,7 @@ public abstract class AlignmentViewport implements AlignViewportI
 
     do
     {
-      if (hasHiddenColumns)
+      if (colSel != null && colSel.hasHiddenColumns())
       {
         if (start == 0)
         {
@@ -1249,7 +1200,7 @@ public abstract class AlignmentViewport implements AlignViewportI
       regions.addElement(new int[]
       { start, end });
 
-      if (hasHiddenColumns)
+      if (colSel != null && colSel.hasHiddenColumns())
       {
         start = colSel.adjustForHiddenColumns(end);
         start = colSel.getHiddenBoundaryLeft(start) + 1;
@@ -1286,18 +1237,15 @@ public abstract class AlignmentViewport implements AlignViewportI
     return ala;
   }
 
-  /**
-   * @return the padGaps
-   */
+
+  @Override
   public boolean isPadGaps()
   {
     return padGaps;
   }
 
-  /**
-   * @param padGaps
-   *          the padGaps to set
-   */
+
+  @Override
   public void setPadGaps(boolean padGaps)
   {
     this.padGaps = padGaps;
@@ -1309,6 +1257,7 @@ public abstract class AlignmentViewport implements AlignViewportI
    * 
    * @param ap
    */
+  @Override
   public void alignmentChanged(AlignmentViewPanel ap)
   {
     if (isPadGaps())
@@ -1470,6 +1419,7 @@ public abstract class AlignmentViewport implements AlignViewportI
    * 
    * @see jalview.api.AlignViewportI#calcPanelHeight()
    */
+  @Override
   public int calcPanelHeight()
   {
     // setHeight of panels
@@ -1601,6 +1551,36 @@ public abstract class AlignmentViewport implements AlignViewportI
     }
     oldrfs.clear();
   }
+  /**
+   * show the reference sequence in the alignment view
+   */
+  private boolean displayReferenceSeq=false;
+  /**
+   * colour according to the reference sequence defined on the alignment
+   */
+  private boolean colourByReferenceSeq=false;
+
+  @Override
+  public boolean isDisplayReferenceSeq()
+  {
+    return alignment.hasSeqrep() && displayReferenceSeq;
+  }
+
+  @Override
+  public void setDisplayReferenceSeq(boolean displayReferenceSeq)
+  {
+    this.displayReferenceSeq = displayReferenceSeq;
+  }
+
+  public boolean isColourByReferenceSeq()
+  {
+    return alignment.hasSeqrep() && colourByReferenceSeq;
+  }
+
+  public void setColourByReferenceSeq(boolean colourByReferenceSeq)
+  {
+    this.colourByReferenceSeq = colourByReferenceSeq;
+  }
 
   @Override
   public Color getSequenceColour(SequenceI seq)
@@ -1659,4 +1639,90 @@ public abstract class AlignmentViewport implements AlignViewportI
   {
     sequenceColours = null;
   };
+
+  FeaturesDisplayedI featuresDisplayed = null;
+
+  @Override
+  public FeaturesDisplayedI getFeaturesDisplayed()
+  {
+    return featuresDisplayed;
+  }
+
+  @Override
+  public void setFeaturesDisplayed(FeaturesDisplayedI featuresDisplayedI)
+  {
+    featuresDisplayed = featuresDisplayedI;
+  }
+
+  @Override
+  public boolean areFeaturesDisplayed()
+  {
+    return featuresDisplayed != null && featuresDisplayed.getRegisterdFeaturesCount()>0;
+  }
+
+  /**
+   * display setting for showing/hiding sequence features on alignment view
+   */
+  boolean showSequenceFeatures = false;
+
+  /**
+   * set the flag
+   * 
+   * @param b
+   *          features are displayed if true
+   */
+  @Override
+  public void setShowSequenceFeatures(boolean b)
+  {
+    showSequenceFeatures = b;
+  }
+  @Override
+  public boolean isShowSequenceFeatures()
+  {
+    return showSequenceFeatures;
+  }
+
+  boolean showSeqFeaturesHeight;
+
+  @Override
+  public void setShowSequenceFeaturesHeight(boolean selected)
+  {
+    showSeqFeaturesHeight = selected;
+  }
+
+  @Override
+  public boolean isShowSequenceFeaturesHeight()
+  {
+    return showSeqFeaturesHeight;
+  }
+
+  private boolean showAnnotation = true;
+
+  private boolean rightAlignIds = false;
+
+
+  @Override
+  public void setShowAnnotation(boolean b)
+  {
+    showAnnotation = b;
+  }
+
+  @Override
+  public boolean isShowAnnotation()
+  {
+    return showAnnotation;
+  }
+
+  @Override
+  public boolean isRightAlignIds()
+  {
+    return rightAlignIds;
+  }
+
+  @Override
+  public void setRightAlignIds(boolean rightAlignIds)
+  {
+    this.rightAlignIds = rightAlignIds;
+  }
+
 }
diff --git a/src/jalview/viewmodel/annotationfilter/AnnotationFilterParameter.java b/src/jalview/viewmodel/annotationfilter/AnnotationFilterParameter.java
new file mode 100644 (file)
index 0000000..3cd4ef7
--- /dev/null
@@ -0,0 +1,101 @@
+package jalview.viewmodel.annotationfilter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AnnotationFilterParameter
+{
+  public enum ThresholdType
+  {
+    NO_THRESHOLD, BELOW_THRESHOLD, ABOVE_THRESHOLD;
+  }
+
+  public enum SearchableAnnotationField
+  {
+    DISPLAY_STRING, DESCRIPTION;
+  }
+  private ThresholdType thresholdType;
+
+  private float thresholdValue;
+
+  private boolean filterAlphaHelix = false;
+
+  private boolean filterBetaSheet = false;
+
+  private boolean filterTurn = false;
+
+  private String regexString;
+
+  private List<SearchableAnnotationField> regexSearchFields = new ArrayList<SearchableAnnotationField>();
+
+  public ThresholdType getThresholdType()
+  {
+    return thresholdType;
+  }
+
+  public void setThresholdType(ThresholdType thresholdType)
+  {
+    this.thresholdType = thresholdType;
+  }
+
+  public float getThresholdValue()
+  {
+    return thresholdValue;
+  }
+
+  public void setThresholdValue(float thresholdValue)
+  {
+    this.thresholdValue = thresholdValue;
+  }
+
+  public String getRegexString()
+  {
+    return regexString;
+  }
+
+  public void setRegexString(String regexString)
+  {
+    this.regexString = regexString;
+  }
+
+  public List<SearchableAnnotationField> getRegexSearchFields()
+  {
+    return regexSearchFields;
+  }
+
+  public void addRegexSearchField(SearchableAnnotationField regexSearchField)
+  {
+    this.regexSearchFields.add(regexSearchField);
+  }
+
+  public boolean isFilterAlphaHelix()
+  {
+    return filterAlphaHelix;
+  }
+
+  public void setFilterAlphaHelix(boolean alphaHelix)
+  {
+    this.filterAlphaHelix = alphaHelix;
+  }
+
+  public boolean isFilterBetaSheet()
+  {
+    return filterBetaSheet;
+  }
+
+  public void setFilterBetaSheet(boolean betaSheet)
+  {
+    this.filterBetaSheet = betaSheet;
+  }
+
+  public boolean isFilterTurn()
+  {
+    return filterTurn;
+  }
+
+  public void setFilterTurn(boolean turn)
+  {
+    this.filterTurn = turn;
+  }
+
+}
diff --git a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
new file mode 100644 (file)
index 0000000..c7cee04
--- /dev/null
@@ -0,0 +1,949 @@
+package jalview.viewmodel.seqfeatures;
+
+import jalview.api.AlignViewportI;
+import jalview.api.FeaturesDisplayedI;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+import jalview.renderer.seqfeatures.FeatureRenderer;
+import jalview.schemes.GraduatedColor;
+import jalview.viewmodel.AlignmentViewport;
+
+import java.awt.Color;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public abstract class FeatureRendererModel implements
+        jalview.api.FeatureRenderer
+{
+
+  /**
+   * global transparency for feature
+   */
+  protected float transparency = 1.0f;
+
+  protected Map<String, Object> featureColours = new ConcurrentHashMap<String, Object>();
+
+  protected Map<String, Boolean> featureGroups = new ConcurrentHashMap<String, Boolean>();
+
+  protected Object currentColour;
+
+  protected String[] renderOrder;
+
+  protected PropertyChangeSupport changeSupport = new PropertyChangeSupport(
+          this);
+
+  protected AlignmentViewport av;
+
+  public AlignViewportI getViewport()
+  {
+    return av;
+  }
+
+  public FeatureRendererSettings getSettings()
+  {
+    return new FeatureRendererSettings(this);
+  }
+
+  public void transferSettings(FeatureRendererSettings fr)
+  {
+    this.renderOrder = fr.renderOrder;
+    this.featureGroups = fr.featureGroups;
+    this.featureColours = fr.featureColours;
+    this.transparency = fr.transparency;
+    this.featureOrder = fr.featureOrder;
+  }
+
+  /**
+   * update from another feature renderer
+   * 
+   * @param fr
+   *          settings to copy
+   */
+  public void transferSettings(jalview.api.FeatureRenderer _fr)
+  {
+    FeatureRenderer fr = (FeatureRenderer) _fr;
+    FeatureRendererSettings frs = new FeatureRendererSettings(fr);
+    this.renderOrder = frs.renderOrder;
+    this.featureGroups = frs.featureGroups;
+    this.featureColours = frs.featureColours;
+    this.transparency = frs.transparency;
+    this.featureOrder = frs.featureOrder;
+    if (av != null && av != fr.getViewport())
+    {
+      // copy over the displayed feature settings
+      if (_fr.getFeaturesDisplayed() != null)
+      {
+        FeaturesDisplayedI fd = getFeaturesDisplayed();
+        if (fd == null)
+        {
+          setFeaturesDisplayedFrom(_fr.getFeaturesDisplayed());
+        }
+        else
+        {
+          synchronized (fd)
+          {
+            fd.clear();
+            java.util.Iterator<String> fdisp = _fr.getFeaturesDisplayed()
+                    .getVisibleFeatures();
+            while (fdisp.hasNext())
+            {
+              fd.setVisible(fdisp.next());
+            }
+          }
+        }
+      }
+    }
+  }
+
+  public void setFeaturesDisplayedFrom(FeaturesDisplayedI featuresDisplayed)
+  {
+    av.setFeaturesDisplayed(new FeaturesDisplayed(featuresDisplayed));
+  }
+
+  @Override
+  public void setVisible(String featureType)
+  {
+    FeaturesDisplayedI fdi = av.getFeaturesDisplayed();
+    if (fdi == null)
+    {
+      av.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
+    }
+    if (!fdi.isRegistered(featureType))
+    {
+      pushFeatureType(Arrays.asList(new String[]
+      { featureType }));
+    }
+    fdi.setVisible(featureType);
+  }
+
+  @Override
+  public void setAllVisible(List<String> featureTypes)
+  {
+    FeaturesDisplayedI fdi = av.getFeaturesDisplayed();
+    if (fdi == null)
+    {
+      av.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
+    }
+    List<String> nft = new ArrayList<String>();
+    for (String featureType : featureTypes)
+    {
+      if (!fdi.isRegistered(featureType))
+      {
+        nft.add(featureType);
+      }
+    }
+    if (nft.size() > 0)
+    {
+      pushFeatureType(nft);
+    }
+    fdi.setAllVisible(featureTypes);
+  }
+
+  /**
+   * push a set of new types onto the render order stack. Note - this is a
+   * direct mechanism rather than the one employed in updateRenderOrder
+   * 
+   * @param types
+   */
+  private void pushFeatureType(List<String> types)
+  {
+
+    int ts = types.size();
+    String neworder[] = new String[(renderOrder == null ? 0
+            : renderOrder.length) + ts];
+    types.toArray(neworder);
+    if (renderOrder != null)
+    {
+      System.arraycopy(neworder,0,neworder,renderOrder.length,ts);
+      System.arraycopy(renderOrder, 0, neworder, 0, renderOrder.length);
+    }
+    renderOrder = neworder;
+  }
+
+  protected Hashtable minmax = new Hashtable();
+
+  public Hashtable getMinMax()
+  {
+    return minmax;
+  }
+
+  /**
+   * normalise a score against the max/min bounds for the feature type.
+   * 
+   * @param sequenceFeature
+   * @return byte[] { signed, normalised signed (-127 to 127) or unsigned
+   *         (0-255) value.
+   */
+  protected final byte[] normaliseScore(SequenceFeature sequenceFeature)
+  {
+    float[] mm = ((float[][]) minmax.get(sequenceFeature.type))[0];
+    final byte[] r = new byte[]
+    { 0, (byte) 255 };
+    if (mm != null)
+    {
+      if (r[0] != 0 || mm[0] < 0.0)
+      {
+        r[0] = 1;
+        r[1] = (byte) ((int) 128.0 + 127.0 * (sequenceFeature.score / mm[1]));
+      }
+      else
+      {
+        r[1] = (byte) ((int) 255.0 * (sequenceFeature.score / mm[1]));
+      }
+    }
+    return r;
+  }
+
+  boolean newFeatureAdded = false;
+
+  boolean findingFeatures = false;
+
+  protected boolean updateFeatures()
+  {
+    if (av.getFeaturesDisplayed() == null || renderOrder == null
+            || newFeatureAdded)
+    {
+      findAllFeatures();
+      if (av.getFeaturesDisplayed().getVisibleFeatureCount() < 1)
+      {
+        return false;
+      }
+    }
+    // TODO: decide if we should check for the visible feature count first
+    return true;
+  }
+
+  /**
+   * search the alignment for all new features, give them a colour and display
+   * them. Then fires a PropertyChangeEvent on the changeSupport object.
+   * 
+   */
+  protected void findAllFeatures()
+  {
+    synchronized (firing)
+    {
+      if (firing.equals(Boolean.FALSE))
+      {
+        firing = Boolean.TRUE;
+        findAllFeatures(true); // add all new features as visible
+        changeSupport.firePropertyChange("changeSupport", null, null);
+        firing = Boolean.FALSE;
+      }
+    }
+  }
+
+  @Override
+  public List<SequenceFeature> findFeaturesAtRes(SequenceI sequence, int res)
+  {
+    ArrayList<SequenceFeature> tmp = new ArrayList<SequenceFeature>();
+    SequenceFeature[] features = sequence.getSequenceFeatures();
+
+    while (features == null && sequence.getDatasetSequence() != null)
+    {
+      sequence = sequence.getDatasetSequence();
+      features = sequence.getSequenceFeatures();
+    }
+
+    if (features != null)
+    {
+      for (int i = 0; i < features.length; i++)
+      {
+        if (!av.areFeaturesDisplayed()
+                || !av.getFeaturesDisplayed().isVisible(
+                        features[i].getType()))
+        {
+          continue;
+        }
+
+        if (features[i].featureGroup != null
+                && featureGroups != null
+                && featureGroups.containsKey(features[i].featureGroup)
+                && !featureGroups.get(features[i].featureGroup)
+                        .booleanValue())
+        {
+          continue;
+        }
+
+        if ((features[i].getBegin() <= res)
+                && (features[i].getEnd() >= res))
+        {
+          tmp.add(features[i]);
+        }
+      }
+    }
+    return tmp;
+  }
+
+  /**
+   * Searches alignment for all features and updates colours
+   * 
+   * @param newMadeVisible
+   *          if true newly added feature types will be rendered immediatly
+   *          TODO: check to see if this method should actually be proxied so
+   *          repaint events can be propagated by the renderer code
+   */
+  @Override
+  public synchronized void findAllFeatures(boolean newMadeVisible)
+  {
+    newFeatureAdded = false;
+
+    if (findingFeatures)
+    {
+      newFeatureAdded = true;
+      return;
+    }
+
+    findingFeatures = true;
+    if (av.getFeaturesDisplayed() == null)
+    {
+      av.setFeaturesDisplayed(new FeaturesDisplayed());
+    }
+    FeaturesDisplayedI featuresDisplayed = av.getFeaturesDisplayed();
+
+    ArrayList<String> allfeatures = new ArrayList<String>();
+    ArrayList<String> oldfeatures = new ArrayList<String>();
+    if (renderOrder != null)
+    {
+      for (int i = 0; i < renderOrder.length; i++)
+      {
+        if (renderOrder[i] != null)
+        {
+          oldfeatures.add(renderOrder[i]);
+        }
+      }
+    }
+    if (minmax == null)
+    {
+      minmax = new Hashtable();
+    }
+    AlignmentI alignment = av.getAlignment();
+    for (int i = 0; i < alignment.getHeight(); i++)
+    {
+      SequenceI asq = alignment.getSequenceAt(i);
+      SequenceI dasq = asq.getDatasetSequence();
+      SequenceFeature[] features = dasq != null ? dasq
+              .getSequenceFeatures() : asq.getSequenceFeatures();
+
+      if (features == null)
+      {
+        continue;
+      }
+
+      int index = 0;
+      while (index < features.length)
+      {
+        if (!featuresDisplayed.isRegistered(features[index].getType()))
+        {
+          String fgrp = features[index].getFeatureGroup();
+          if (fgrp != null)
+          {
+            Boolean groupDisplayed = featureGroups.get(fgrp);
+            if (groupDisplayed == null)
+            {
+              groupDisplayed = Boolean.valueOf(newMadeVisible);
+              featureGroups.put(fgrp, groupDisplayed);
+            }
+            if (!groupDisplayed.booleanValue())
+            {
+              index++;
+              continue;
+            }
+          }
+          if (!(features[index].begin == 0 && features[index].end == 0))
+          {
+            // If beginning and end are 0, the feature is for the whole sequence
+            // and we don't want to render the feature in the normal way
+
+            if (newMadeVisible
+                    && !oldfeatures.contains(features[index].getType()))
+            {
+              // this is a new feature type on the alignment. Mark it for
+              // display.
+              featuresDisplayed.setVisible(features[index].getType());
+              setOrder(features[index].getType(), 0);
+            }
+          }
+        }
+        if (!allfeatures.contains(features[index].getType()))
+        {
+          allfeatures.add(features[index].getType());
+        }
+        if (features[index].score != Float.NaN)
+        {
+          int nonpos = features[index].getBegin() >= 1 ? 0 : 1;
+          float[][] mm = (float[][]) minmax.get(features[index].getType());
+          if (mm == null)
+          {
+            mm = new float[][]
+            { null, null };
+            minmax.put(features[index].getType(), mm);
+          }
+          if (mm[nonpos] == null)
+          {
+            mm[nonpos] = new float[]
+            { features[index].score, features[index].score };
+
+          }
+          else
+          {
+            if (mm[nonpos][0] > features[index].score)
+            {
+              mm[nonpos][0] = features[index].score;
+            }
+            if (mm[nonpos][1] < features[index].score)
+            {
+              mm[nonpos][1] = features[index].score;
+            }
+          }
+        }
+        index++;
+      }
+    }
+    updateRenderOrder(allfeatures);
+    findingFeatures = false;
+  }
+
+  protected Boolean firing = Boolean.FALSE;
+
+  /**
+   * replaces the current renderOrder with the unordered features in
+   * allfeatures. The ordering of any types in both renderOrder and allfeatures
+   * is preserved, and all new feature types are rendered on top of the existing
+   * types, in the order given by getOrder or the order given in allFeatures.
+   * Note. this operates directly on the featureOrder hash for efficiency. TODO:
+   * eliminate the float storage for computing/recalling the persistent ordering
+   * New Cability: updates min/max for colourscheme range if its dynamic
+   * 
+   * @param allFeatures
+   */
+  private void updateRenderOrder(List<String> allFeatures)
+  {
+    List<String> allfeatures = new ArrayList<String>(allFeatures);
+    String[] oldRender = renderOrder;
+    renderOrder = new String[allfeatures.size()];
+    Object mmrange, fc = null;
+    boolean initOrders = (featureOrder == null);
+    int opos = 0;
+    if (oldRender != null && oldRender.length > 0)
+    {
+      for (int j = 0; j < oldRender.length; j++)
+      {
+        if (oldRender[j] != null)
+        {
+          if (initOrders)
+          {
+            setOrder(oldRender[j], (1 - (1 + (float) j)
+                    / oldRender.length));
+          }
+          if (allfeatures.contains(oldRender[j]))
+          {
+            renderOrder[opos++] = oldRender[j]; // existing features always
+            // appear below new features
+            allfeatures.remove(oldRender[j]);
+            if (minmax != null)
+            {
+              mmrange = minmax.get(oldRender[j]);
+              if (mmrange != null)
+              {
+                fc = featureColours.get(oldRender[j]);
+                if (fc != null && fc instanceof GraduatedColor
+                        && ((GraduatedColor) fc).isAutoScale())
+                {
+                  ((GraduatedColor) fc).updateBounds(
+                          ((float[][]) mmrange)[0][0],
+                          ((float[][]) mmrange)[0][1]);
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+    if (allfeatures.size() == 0)
+    {
+      // no new features - leave order unchanged.
+      return;
+    }
+    int i = allfeatures.size() - 1;
+    int iSize = i;
+    boolean sort = false;
+    String[] newf = new String[allfeatures.size()];
+    float[] sortOrder = new float[allfeatures.size()];
+    for (String newfeat : allfeatures)
+    {
+      newf[i] = newfeat;
+      if (minmax != null)
+      {
+        // update from new features minmax if necessary
+        mmrange = minmax.get(newf[i]);
+        if (mmrange != null)
+        {
+          fc = featureColours.get(newf[i]);
+          if (fc != null && fc instanceof GraduatedColor
+                  && ((GraduatedColor) fc).isAutoScale())
+          {
+            ((GraduatedColor) fc).updateBounds(((float[][]) mmrange)[0][0],
+                    ((float[][]) mmrange)[0][1]);
+          }
+        }
+      }
+      if (initOrders || !featureOrder.containsKey(newf[i]))
+      {
+        int denom = initOrders ? allfeatures.size() : featureOrder.size();
+        // new unordered feature - compute persistent ordering at head of
+        // existing features.
+        setOrder(newf[i], i / (float) denom);
+      }
+      // set order from newly found feature from persisted ordering.
+      sortOrder[i] = 2 - ((Float) featureOrder.get(newf[i])).floatValue();
+      if (i < iSize)
+      {
+        // only sort if we need to
+        sort = sort || sortOrder[i] > sortOrder[i + 1];
+      }
+      i--;
+    }
+    if (iSize > 1 && sort)
+    {
+      jalview.util.QuickSort.sort(sortOrder, newf);
+    }
+    sortOrder = null;
+    System.arraycopy(newf, 0, renderOrder, opos, newf.length);
+  }
+
+  /**
+   * get a feature style object for the given type string. Creates a
+   * java.awt.Color for a featureType with no existing colourscheme. TODO:
+   * replace return type with object implementing standard abstract colour/style
+   * interface
+   * 
+   * @param featureType
+   * @return java.awt.Color or GraduatedColor
+   */
+  public Object getFeatureStyle(String featureType)
+  {
+    Object fc = featureColours.get(featureType);
+    if (fc == null)
+    {
+      jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme();
+      Color col = ucs.createColourFromName(featureType);
+      featureColours.put(featureType, fc = col);
+    }
+    return fc;
+  }
+
+  /**
+   * return a nominal colour for this feature
+   * 
+   * @param featureType
+   * @return standard color, or maximum colour for graduated colourscheme
+   */
+  public Color getColour(String featureType)
+  {
+    Object fc = getFeatureStyle(featureType);
+
+    if (fc instanceof Color)
+    {
+      return (Color) fc;
+    }
+    else
+    {
+      if (fc instanceof GraduatedColor)
+      {
+        return ((GraduatedColor) fc).getMaxColor();
+      }
+    }
+    throw new Error("Implementation Error: Unrecognised render object "
+            + fc.getClass() + " for features of type " + featureType);
+  }
+
+  /**
+   * calculate the render colour for a specific feature using current feature
+   * settings.
+   * 
+   * @param feature
+   * @return render colour for the given feature
+   */
+  public Color getColour(SequenceFeature feature)
+  {
+    Object fc = getFeatureStyle(feature.getType());
+    if (fc instanceof Color)
+    {
+      return (Color) fc;
+    }
+    else
+    {
+      if (fc instanceof GraduatedColor)
+      {
+        return ((GraduatedColor) fc).findColor(feature);
+      }
+    }
+    throw new Error("Implementation Error: Unrecognised render object "
+            + fc.getClass() + " for features of type " + feature.getType());
+  }
+
+  protected boolean showFeature(SequenceFeature sequenceFeature)
+  {
+    Object fc = getFeatureStyle(sequenceFeature.type);
+    if (fc instanceof GraduatedColor)
+    {
+      return ((GraduatedColor) fc).isColored(sequenceFeature);
+    }
+    else
+    {
+      return true;
+    }
+  }
+
+  protected boolean showFeatureOfType(String type)
+  {
+    return av.getFeaturesDisplayed().isVisible(type);
+  }
+
+  public void setColour(String featureType, Object col)
+  {
+    // overwrite
+    // Color _col = (col instanceof Color) ? ((Color) col) : (col instanceof
+    // GraduatedColor) ? ((GraduatedColor) col).getMaxColor() : null;
+    // Object c = featureColours.get(featureType);
+    // if (c == null || c instanceof Color || (c instanceof GraduatedColor &&
+    // !((GraduatedColor)c).getMaxColor().equals(_col)))
+    {
+      featureColours.put(featureType, col);
+    }
+  }
+
+  public void setTransparency(float value)
+  {
+    transparency = value;
+  }
+
+  public float getTransparency()
+  {
+    return transparency;
+  }
+
+  Map featureOrder = null;
+
+  /**
+   * analogous to colour - store a normalized ordering for all feature types in
+   * this rendering context.
+   * 
+   * @param type
+   *          Feature type string
+   * @param position
+   *          normalized priority - 0 means always appears on top, 1 means
+   *          always last.
+   */
+  public float setOrder(String type, float position)
+  {
+    if (featureOrder == null)
+    {
+      featureOrder = new Hashtable();
+    }
+    featureOrder.put(type, new Float(position));
+    return position;
+  }
+
+  /**
+   * get the global priority (0 (top) to 1 (bottom))
+   * 
+   * @param type
+   * @return [0,1] or -1 for a type without a priority
+   */
+  public float getOrder(String type)
+  {
+    if (featureOrder != null)
+    {
+      if (featureOrder.containsKey(type))
+      {
+        return ((Float) featureOrder.get(type)).floatValue();
+      }
+    }
+    return -1;
+  }
+
+  @Override
+  public Map<String, Object> getFeatureColours()
+  {
+    return new ConcurrentHashMap<String, Object>(featureColours);
+  }
+
+  /**
+   * Replace current ordering with new ordering
+   * 
+   * @param data
+   *          { String(Type), Colour(Type), Boolean(Displayed) }
+   */
+  public void setFeaturePriority(Object[][] data)
+  {
+    setFeaturePriority(data, true);
+  }
+
+  /**
+   * 
+   * @param data
+   *          { String(Type), Colour(Type), Boolean(Displayed) }
+   * @param visibleNew
+   *          when true current featureDisplay list will be cleared
+   */
+  public void setFeaturePriority(Object[][] data, boolean visibleNew)
+  {
+    FeaturesDisplayedI av_featuresdisplayed = null;
+    if (visibleNew)
+    {
+      if ((av_featuresdisplayed = av.getFeaturesDisplayed()) != null)
+      {
+        av.getFeaturesDisplayed().clear();
+      }
+      else
+      {
+        av.setFeaturesDisplayed(av_featuresdisplayed = new FeaturesDisplayed());
+      }
+    }
+    else
+    {
+      av_featuresdisplayed = av.getFeaturesDisplayed();
+    }
+    if (data == null)
+    {
+      return;
+    }
+    // The feature table will display high priority
+    // features at the top, but theses are the ones
+    // we need to render last, so invert the data
+    renderOrder = new String[data.length];
+
+    if (data.length > 0)
+    {
+      for (int i = 0; i < data.length; i++)
+      {
+        String type = data[i][0].toString();
+        setColour(type, data[i][1]); // todo : typesafety - feature color
+        // interface object
+        if (((Boolean) data[i][2]).booleanValue())
+        {
+          av_featuresdisplayed.setVisible(type);
+        }
+
+        renderOrder[data.length - i - 1] = type;
+      }
+    }
+
+  }
+
+  /**
+   * @param listener
+   * @see java.beans.PropertyChangeSupport#addPropertyChangeListener(java.beans.PropertyChangeListener)
+   */
+  public void addPropertyChangeListener(PropertyChangeListener listener)
+  {
+    changeSupport.addPropertyChangeListener(listener);
+  }
+
+  /**
+   * @param listener
+   * @see java.beans.PropertyChangeSupport#removePropertyChangeListener(java.beans.PropertyChangeListener)
+   */
+  public void removePropertyChangeListener(PropertyChangeListener listener)
+  {
+    changeSupport.removePropertyChangeListener(listener);
+  }
+
+  public Set getAllFeatureColours()
+  {
+    return featureColours.keySet();
+  }
+
+  public void clearRenderOrder()
+  {
+    renderOrder = null;
+  }
+
+  public boolean hasRenderOrder()
+  {
+    return renderOrder != null;
+  }
+
+  public List<String> getRenderOrder()
+  {
+    if (renderOrder == null)
+    {
+      return Arrays.asList(new String[]
+      {});
+    }
+    return Arrays.asList(renderOrder);
+  }
+
+  public int getFeatureGroupsSize()
+  {
+    return featureGroups != null ? 0 : featureGroups.size();
+  }
+
+  @Override
+  public List<String> getFeatureGroups()
+  {
+    // conflict between applet and desktop - featureGroups returns the map in
+    // the desktop featureRenderer
+    return (featureGroups == null) ? Arrays.asList(new String[0]) : Arrays
+            .asList(featureGroups.keySet().toArray(new String[0]));
+  }
+
+  public boolean checkGroupVisibility(String group, boolean newGroupsVisible)
+  {
+    if (featureGroups == null)
+    {
+      // then an exception happens next..
+    }
+    if (featureGroups.containsKey(group))
+    {
+      return featureGroups.get(group).booleanValue();
+    }
+    if (newGroupsVisible)
+    {
+      featureGroups.put(group, new Boolean(true));
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * get visible or invisible groups
+   * 
+   * @param visible
+   *          true to return visible groups, false to return hidden ones.
+   * @return list of groups
+   */
+  @Override
+  public List getGroups(boolean visible)
+  {
+    if (featureGroups != null)
+    {
+      ArrayList gp = new ArrayList();
+
+      for (Object grp : featureGroups.keySet())
+      {
+        Boolean state = featureGroups.get(grp);
+        if (state.booleanValue() == visible)
+        {
+          gp.add(grp);
+        }
+      }
+      return gp;
+    }
+    return null;
+  }
+
+  @Override
+  public void setGroupVisibility(String group, boolean visible)
+  {
+    featureGroups.put(group, new Boolean(visible));
+  }
+
+  @Override
+  public void setGroupVisibility(List<String> toset, boolean visible)
+  {
+    if (toset != null && toset.size() > 0 && featureGroups != null)
+    {
+      boolean rdrw = false;
+      for (String gst : toset)
+      {
+        Boolean st = featureGroups.get(gst);
+        featureGroups.put(gst, new Boolean(visible));
+        if (st != null)
+        {
+          rdrw = rdrw || (visible != st.booleanValue());
+        }
+      }
+      if (rdrw)
+      {
+        // set local flag indicating redraw needed ?
+      }
+    }
+  }
+
+  @Override
+  public Hashtable getDisplayedFeatureCols()
+  {
+    Hashtable fcols = new Hashtable();
+    if (getViewport().getFeaturesDisplayed() == null)
+    {
+      return fcols;
+    }
+    Iterator<String> en = getViewport().getFeaturesDisplayed()
+            .getVisibleFeatures();
+    while (en.hasNext())
+    {
+      String col = en.next();
+      fcols.put(col, getColour(col));
+    }
+    return fcols;
+  }
+
+  @Override
+  public FeaturesDisplayedI getFeaturesDisplayed()
+  {
+    return av.getFeaturesDisplayed();
+  }
+
+  @Override
+  public String[] getDisplayedFeatureTypes()
+  {
+    String[] typ = null;
+    typ = getRenderOrder().toArray(new String[0]);
+    FeaturesDisplayedI feature_disp = av.getFeaturesDisplayed();
+    if (feature_disp != null)
+    {
+      synchronized (feature_disp)
+      {
+        for (int i = 0; i < typ.length; i++)
+        {
+          if (!feature_disp.isVisible(typ[i]))
+          {
+            typ[i] = null;
+          }
+        }
+      }
+    }
+    return typ;
+  }
+
+  @Override
+  public String[] getDisplayedFeatureGroups()
+  {
+    String[] gps = null;
+    ArrayList<String> _gps = new ArrayList<String>();
+    Iterator en = getFeatureGroups().iterator();
+    int g = 0;
+    boolean valid = false;
+    while (en.hasNext())
+    {
+      String gp = (String) en.next();
+      if (checkGroupVisibility(gp, false))
+      {
+        valid = true;
+        _gps.add(gp);
+      }
+      if (!valid)
+      {
+        return null;
+      }
+      else
+      {
+        gps = new String[_gps.size()];
+        _gps.toArray(gps);
+      }
+    }
+    return gps;
+  }
+
+}
diff --git a/src/jalview/viewmodel/seqfeatures/FeatureRendererSettings.java b/src/jalview/viewmodel/seqfeatures/FeatureRendererSettings.java
new file mode 100644 (file)
index 0000000..6e85b83
--- /dev/null
@@ -0,0 +1,77 @@
+package jalview.viewmodel.seqfeatures;
+
+import jalview.schemes.GraduatedColor;
+
+import java.util.Arrays;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class FeatureRendererSettings implements Cloneable
+{
+  String[] renderOrder;
+
+  Map featureGroups;
+
+  Map featureColours;
+
+  float transparency;
+
+  Map featureOrder;
+
+  public FeatureRendererSettings(String[] renderOrder,
+          Hashtable featureGroups, Hashtable featureColours,
+          float transparency, Hashtable featureOrder)
+  {
+    super();
+    this.renderOrder = Arrays.copyOf(renderOrder,renderOrder.length);
+    this.featureGroups = new ConcurrentHashMap(featureGroups);
+    this.featureColours = new ConcurrentHashMap(featureColours);
+    this.transparency = transparency;
+    this.featureOrder = new ConcurrentHashMap(featureOrder);
+  }
+
+  /**
+   * create an independent instance of the feature renderer settings
+   * 
+   * @param fr
+   */
+  public FeatureRendererSettings(
+          jalview.viewmodel.seqfeatures.FeatureRendererModel fr)
+  {
+    renderOrder = null;
+    featureGroups = new ConcurrentHashMap();
+    featureColours = new ConcurrentHashMap();
+    featureOrder = new ConcurrentHashMap();
+    if (fr.renderOrder != null)
+    {
+      this.renderOrder = new String[fr.renderOrder.length];
+      System.arraycopy(fr.renderOrder, 0, renderOrder, 0,
+              fr.renderOrder.length);
+    }
+    if (fr.featureGroups != null)
+    {
+      this.featureGroups = new ConcurrentHashMap(fr.featureGroups);
+    }
+    if (fr.featureColours != null)
+    {
+      this.featureColours = new ConcurrentHashMap(fr.featureColours);
+    }
+    Iterator en = fr.featureColours.keySet().iterator();
+    while (en.hasNext())
+    {
+      Object next = en.next();
+      Object val = featureColours.get(next);
+      if (val instanceof GraduatedColor)
+      {
+        featureColours.put(next, new GraduatedColor((GraduatedColor) val));
+      }
+    }
+    this.transparency = fr.transparency;
+    if (fr.featureOrder != null)
+    {
+      this.featureOrder = new ConcurrentHashMap(fr.featureOrder);
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/jalview/viewmodel/seqfeatures/FeatureSettingsModel.java b/src/jalview/viewmodel/seqfeatures/FeatureSettingsModel.java
new file mode 100644 (file)
index 0000000..57d57da
--- /dev/null
@@ -0,0 +1,8 @@
+package jalview.viewmodel.seqfeatures;
+
+import jalview.api.FeatureSettingsModelI;
+
+public class FeatureSettingsModel implements FeatureSettingsModelI
+{
+
+}
diff --git a/src/jalview/viewmodel/seqfeatures/FeaturesDisplayed.java b/src/jalview/viewmodel/seqfeatures/FeaturesDisplayed.java
new file mode 100644 (file)
index 0000000..b04764c
--- /dev/null
@@ -0,0 +1,94 @@
+package jalview.viewmodel.seqfeatures;
+
+import jalview.api.FeaturesDisplayedI;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+
+public class FeaturesDisplayed implements FeaturesDisplayedI
+{
+  private HashSet<String> featuresDisplayed = new HashSet<String>();
+
+  private HashSet<String> featuresRegistered = new HashSet<String>();
+
+  public FeaturesDisplayed(FeaturesDisplayedI featuresDisplayed2)
+  {
+    Iterator<String> fdisp = featuresDisplayed2.getVisibleFeatures();
+    String ftype;
+    while (fdisp.hasNext())
+    {
+      ftype = fdisp.next();
+      featuresDisplayed.add(ftype);
+      featuresRegistered.add(ftype);
+    }
+  }
+
+  public FeaturesDisplayed()
+  {
+    // TODO Auto-generated constructor stub
+  }
+
+  @Override
+  public Iterator<String> getVisibleFeatures()
+  {
+    return featuresDisplayed.iterator();
+  }
+
+  @Override
+  public boolean isVisible(String featureType)
+  {
+    return featuresDisplayed.contains(featureType);
+  }
+
+  @Override
+  public boolean areVisible(Collection featureTypes)
+  {
+    return featuresDisplayed.containsAll(featureTypes);
+  }
+
+  @Override
+  public void clear()
+  {
+    featuresDisplayed.clear();
+    featuresRegistered.clear();
+  }
+
+  @Override
+  public void setAllVisible(Collection makeVisible)
+  {
+    featuresDisplayed.addAll(makeVisible);
+    featuresRegistered.addAll(makeVisible);
+  }
+
+  @Override
+  public void setAllRegisteredVisible()
+  {
+    featuresDisplayed.addAll(featuresRegistered);
+  }
+
+  @Override
+  public void setVisible(String featureType)
+  {
+    featuresDisplayed.add(featureType);
+    featuresRegistered.add(featureType);
+  }
+
+  @Override
+  public boolean isRegistered(String type)
+  {
+    return featuresRegistered.contains(type);
+  }
+
+  @Override
+  public int getVisibleFeatureCount()
+  {
+    return featuresDisplayed.size();
+  }
+
+  @Override
+  public int getRegisterdFeaturesCount()
+  {
+    return featuresRegistered.size();
+  }
+}
index edb56a9..d3682d4 100644 (file)
@@ -28,7 +28,7 @@ import jalview.datamodel.AlignmentView;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
 import jalview.gui.WebserviceInfo;
-import jalview.gui.FeatureRenderer.FeatureRendererSettings;
+import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
 import jalview.util.MessageManager;
 
 public abstract class AWSThread extends Thread
index 229fa4e..1bdf64f 100644 (file)
@@ -29,6 +29,7 @@ import java.util.List;
 
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
+import org.apache.http.HttpVersion;
 import org.apache.http.NameValuePair;
 import org.apache.http.client.ClientProtocolException;
 import org.apache.http.client.HttpClient;
@@ -40,6 +41,9 @@ import org.apache.http.entity.mime.content.FileBody;
 import org.apache.http.entity.mime.content.InputStreamBody;
 import org.apache.http.entity.mime.content.StringBody;
 import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.CoreProtocolPNames;
+import org.apache.http.params.HttpParams;
 
 /**
  * Helpful procedures for working with services via HTTPClient
@@ -64,7 +68,10 @@ public class HttpClientUtils
           List<NameValuePair> vals) throws ClientProtocolException,
           IOException
   {
-    HttpClient httpclient = new DefaultHttpClient();
+    HttpParams params = new BasicHttpParams();
+    params.setParameter(CoreProtocolPNames.PROTOCOL_VERSION,
+            HttpVersion.HTTP_1_1);
+    HttpClient httpclient = new DefaultHttpClient(params);
     HttpPost httppost = new HttpPost(postUrl);
     UrlEncodedFormEntity ue = new UrlEncodedFormEntity(vals, "UTF-8");
     httppost.setEntity(ue);
index 6c438be..929581d 100644 (file)
@@ -351,7 +351,7 @@ public class AADisorderClient extends JabawsCalcWorker implements
       {
         if (dispFeatures)
         {
-          jalview.gui.FeatureRenderer fr = ((jalview.gui.AlignmentPanel) ap)
+          jalview.api.FeatureRenderer fr = ((jalview.gui.AlignmentPanel) ap)
                   .cloneFeatureRenderer();
           for (String ft : fc.keySet())
           {
index 0268c35..1820182 100644 (file)
@@ -73,9 +73,8 @@ public class AnnotationFile extends InputType
     if (format.equals(JVANNOT))
     {
       return new StringBody(
-              new jalview.io.AnnotationFile().printAnnotations(
-                      al.getAlignmentAnnotation(), al.getGroups(),
-                      al.getProperties()));
+              new jalview.io.AnnotationFile()
+                      .printAnnotationsForAlignment(al));
     }
     else
     {
diff --git a/test/jalview/analysis/scoremodels/FeatureScoreModelTest.java b/test/jalview/analysis/scoremodels/FeatureScoreModelTest.java
new file mode 100644 (file)
index 0000000..1dbaa4a
--- /dev/null
@@ -0,0 +1,74 @@
+package jalview.analysis.scoremodels;
+
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.io.FileLoader;
+import jalview.io.FormatAdapter;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class FeatureScoreModelTest
+{
+  public static String alntestFile = "FER1_MESCR/72-76 DVYIL\nFER1_SPIOL/71-75 DVYIL\nFER3_RAPSA/21-25 DVYVL\nFER1_MAIZE/73-77 DVYIL\n";
+
+  int[] sf1 = new int[]
+  { 74, 74, 73, 73, 23, 23, -1, -1 };
+
+  int[] sf2 = new int[]
+  { -1, -1, 74, 75, -1, -1, 76, 77 };
+
+  int[] sf3 = new int[]
+  { -1, -1, -1, -1, -1, -1, 76, 77 };
+
+  @Test
+  public void testFeatureScoreModel() throws Exception
+  {
+    AlignFrame alf = new FileLoader(false).LoadFileWaitTillLoaded(alntestFile,
+            FormatAdapter.PASTE);
+    AlignmentI al = alf.getViewport().getAlignment();
+    Assert.assertEquals(4, al.getHeight());
+    Assert.assertEquals(5, al.getWidth());
+    for (int i = 0; i < 4; i++)
+    {
+      SequenceI ds = al.getSequenceAt(i).getDatasetSequence();
+      if (sf1[i * 2] > 0)
+      {
+        ds.addSequenceFeature(new SequenceFeature("sf1", "sf1", "sf1",
+                sf1[i * 2], sf1[i * 2 + 1], "sf1"));
+      }
+      if (sf2[i * 2] > 0)
+      {
+        ds.addSequenceFeature(new SequenceFeature("sf2", "sf2", "sf2",
+                sf2[i * 2], sf2[i * 2 + 1], "sf2"));
+      }
+      if (sf3[i * 2] > 0)
+      {
+        ds.addSequenceFeature(new SequenceFeature("sf3", "sf3", "sf3",
+                sf3[i * 2], sf3[i * 2 + 1], "sf3"));
+      }
+    }
+    alf.setShowSeqFeatures(true);
+    alf.getFeatureRenderer().setVisible("sf1");
+    alf.getFeatureRenderer().setVisible("sf2");
+    alf.getFeatureRenderer().setVisible("sf3");
+    alf.getFeatureRenderer().findAllFeatures(true);
+    Assert.assertEquals("Number of feature types", 3, alf
+            .getFeatureRenderer().getDisplayedFeatureTypes().length);
+    Assert.assertTrue(alf.getCurrentView().areFeaturesDisplayed());
+    FeatureScoreModel fsm = new FeatureScoreModel();
+    Assert.assertTrue(fsm.configureFromAlignmentView(alf.getCurrentView()
+            .getAlignPanel()));
+    alf.selectAllSequenceMenuItem_actionPerformed(null);
+    float[][] dm = fsm.findDistances(alf.getViewport().getAlignmentView(
+            true));
+    Assert.assertTrue("FER1_MESCR should be identical with RAPSA (2)",
+            dm[0][2] == 0f);
+    Assert.assertTrue(
+            "FER1_MESCR should be further from SPIOL (1) than it is from RAPSA (2)",
+            dm[0][1] > dm[0][2]);
+
+  }
+}
index 3f91710..40476a0 100644 (file)
@@ -12,7 +12,7 @@ import org.junit.Test;
 
 public class SequenceTest
 {
-  Sequence seq;
+  SequenceI seq;
 
   @Before
   public void setUp()
@@ -20,6 +20,21 @@ public class SequenceTest
     seq = new Sequence("FER1", "AKPNGVL");
   }
   @Test
+  public void testInsertGapsAndGapmaps()
+  {
+    SequenceI aseq = seq.deriveSequence();
+    aseq.insertCharAt(2, 3, '-');
+    aseq.insertCharAt(6, 3, '-');
+    assertEquals("Gap insertions not correct", "AK---P---NGVL",
+            aseq.getSequenceAsString());
+    List<int[]> gapInt = aseq.getInsertions();
+    assertEquals("Gap interval 1 start wrong", 2, gapInt.get(0)[0]);
+    assertEquals("Gap interval 1 end wrong", 4, gapInt.get(0)[1]);
+    assertEquals("Gap interval 2 start wrong", 6, gapInt.get(1)[0]);
+    assertEquals("Gap interval 2 end wrong", 8, gapInt.get(1)[1]);
+  }
+
+  @Test
   public void testGetAnnotation()
   {
     // initial state returns null not an empty array
@@ -94,7 +109,7 @@ public class SequenceTest
   @Test
   public void testAddAlignmentAnnotation()
   {
-    assertNull(seq.annotation);
+    assertNull(seq.getAnnotation());
     final AlignmentAnnotation annotation = new AlignmentAnnotation("a",
             "b", 2d);
     assertNull(annotation.sequenceRef);
diff --git a/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java b/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java
new file mode 100644 (file)
index 0000000..7dfbba1
--- /dev/null
@@ -0,0 +1,92 @@
+package jalview.ext.rbvi.chimera;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.awt.Color;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+
+public class ChimeraCommandsTest
+{
+  @Test
+  public void testAddColourRange()
+  {
+    Map<Color, Map<Integer, Map<String, List<int[]>>>> map = new LinkedHashMap<Color, Map<Integer, Map<String, List<int[]>>>>();
+    ChimeraCommands.addColourRange(map, Color.pink, 1, 2, 4, "A");
+    ChimeraCommands.addColourRange(map, Color.pink, 1, 8, 8, "A");
+    ChimeraCommands.addColourRange(map, Color.pink, 1, 5, 7, "B");
+    ChimeraCommands.addColourRange(map, Color.red, 1, 3, 5, "A");
+    ChimeraCommands.addColourRange(map, Color.red, 0, 1, 4, "B");
+    ChimeraCommands.addColourRange(map, Color.orange, 0, 5, 9, "C");
+
+    // three colours mapped
+    assertEquals(3, map.keySet().size());
+
+    // Red has two models, Pink and Orange one each
+    assertEquals(2, map.get(Color.red).keySet().size());
+    assertEquals(1, map.get(Color.orange).keySet().size());
+    assertEquals(1, map.get(Color.pink).keySet().size());
+
+    // pink model 1 has two chains, red.0 / red.1 / orange.0 one each
+    assertEquals(2, map.get(Color.pink).get(1).keySet().size());
+    assertEquals(1, map.get(Color.red).get(0).keySet().size());
+    assertEquals(1, map.get(Color.red).get(1).keySet().size());
+    assertEquals(1, map.get(Color.orange).get(0).keySet().size());
+
+    // inspect positions
+    List<int[]> posList = map.get(Color.pink).get(1).get("A");
+    assertEquals(2, posList.size());
+    assertTrue(Arrays.equals(new int[]
+      { 2, 4 }, posList.get(0)));
+    assertTrue(Arrays.equals(new int[]
+      { 8, 8 }, posList.get(1)));
+
+    posList = map.get(Color.pink).get(1).get("B");
+    assertEquals(1, posList.size());
+    assertTrue(Arrays.equals(new int[]
+      { 5, 7 }, posList.get(0)));
+
+    posList = map.get(Color.red).get(0).get("B");
+    assertEquals(1, posList.size());
+    assertTrue(Arrays.equals(new int[]
+      { 1, 4 }, posList.get(0)));
+
+    posList = map.get(Color.red).get(1).get("A");
+    assertEquals(1, posList.size());
+    assertTrue(Arrays.equals(new int[]
+      { 3, 5 }, posList.get(0)));
+
+    posList = map.get(Color.orange).get(0).get("C");
+    assertEquals(1, posList.size());
+    assertTrue(Arrays.equals(new int[]
+      { 5, 9 }, posList.get(0)));
+  }
+
+  @Test
+  public void testBuildColourCommands()
+  {
+
+    Map<Color, Map<Integer, Map<String, List<int[]>>>> map = new LinkedHashMap<Color, Map<Integer, Map<String, List<int[]>>>>();
+    ChimeraCommands.addColourRange(map, Color.blue, 0, 2, 5, "A");
+    ChimeraCommands.addColourRange(map, Color.blue, 0, 7, 7, "B");
+    ChimeraCommands.addColourRange(map, Color.blue, 0, 9, 23, "A");
+    ChimeraCommands.addColourRange(map, Color.blue, 1, 1, 1, "A");
+    ChimeraCommands.addColourRange(map, Color.blue, 1, 4, 7, "B");
+    ChimeraCommands.addColourRange(map, Color.yellow, 1, 8, 8, "A");
+    ChimeraCommands.addColourRange(map, Color.yellow, 1, 3, 5, "A");
+    ChimeraCommands.addColourRange(map, Color.red, 0, 3, 5, "A");
+
+    // Colours should appear in the Chimera command in the order in which
+    // they were added; within colour, by model, by chain, and positions as
+    // added
+    String command = ChimeraCommands.buildColourCommands(map).get(0);
+    assertEquals(
+            "color #0000ff #0:2-5.A,9-23.A,7.B|#1:1.A,4-7.B; color #ffff00 #1:8.A,3-5.A; color #ff0000 #0:3-5.A",
+            command);
+  }
+}
index 1736af5..858806b 100644 (file)
@@ -1,19 +1,14 @@
 package jalview.ext.rbvi.chimera;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertTrue;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.gui.AlignFrame;
 import jalview.gui.StructureViewer;
-import jalview.gui.StructureViewer.Viewer;
+import jalview.gui.StructureViewer.ViewerType;
 import jalview.io.FormatAdapter;
 
-import java.awt.Desktop;
-import java.io.File;
-
-import org.junit.After;
 import org.junit.AfterClass;
-import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -58,25 +53,30 @@ public class JalviewChimeraView
       if (dsq.getPDBId()!=null && dsq.getPDBId().size()>0) {
         for (int q=0;q<dsq.getPDBId().size();q++) 
         {
-          new StructureViewer(af.getViewport().getStructureSelectionManager()).viewStructures(Viewer.JMOL,
+          new StructureViewer(af.getViewport()
+                  .getStructureSelectionManager()).viewStructures(
+                  ViewerType.JMOL,
                   af.getCurrentView().getAlignPanel(),
                   new PDBEntry[] { (PDBEntry)dsq.getPDBId().elementAt(q) },
                   new SequenceI[][] { new SequenceI[] { sq } });
 
-          new StructureViewer(af.getViewport().getStructureSelectionManager()).viewStructures(Viewer.CHIMERA,
+          new StructureViewer(af.getViewport()
+                  .getStructureSelectionManager()).viewStructures(
+                  ViewerType.CHIMERA,
                   af.getCurrentView().getAlignPanel(),
                   new PDBEntry[] { (PDBEntry)dsq.getPDBId().elementAt(q) },
                   new SequenceI[][] { new SequenceI[] { sq } });
+          // todo: break here means only once through this loop?
           break;
         }
         break;
       }
    }
-    try {
-      Thread.sleep(200000);
-    } catch (InterruptedException q)
-    {
-      
-    }
+    // try {
+      // why?
+//      Thread.sleep(200000);
+//    } catch (InterruptedException q)
+//    {
+    // }
   }
 }
diff --git a/test/jalview/gui/SequenceRendererTest.java b/test/jalview/gui/SequenceRendererTest.java
new file mode 100644 (file)
index 0000000..3f8b96a
--- /dev/null
@@ -0,0 +1,36 @@
+package jalview.gui;
+
+import static org.junit.Assert.assertEquals;
+import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+import jalview.schemes.ZappoColourScheme;
+
+import java.awt.Color;
+
+import org.junit.Test;
+
+public class SequenceRendererTest
+{
+
+  @Test
+  public void testGetResidueBoxColour_zappo()
+  {
+    SequenceI seq = new Sequence("name", "MATVLGSPRAPAFF"); // FER1_MAIZE...
+    AlignmentI al = new Alignment(new SequenceI[]
+      { seq });
+    final AlignViewport av = new AlignViewport(al);
+    SequenceRenderer sr = new SequenceRenderer(av);
+    av.setGlobalColourScheme(new ZappoColourScheme());
+
+    // @see ResidueProperties.zappo
+    assertEquals(Color.pink, sr.getResidueColour(seq, 0, null)); // M
+    assertEquals(Color.green, sr.getResidueColour(seq, 2, null)); // T
+    assertEquals(Color.magenta, sr.getResidueColour(seq, 5, null)); // G
+    assertEquals(Color.orange, sr.getResidueColour(seq, 12, null)); // F
+  }
+  // TODO more tests for getResidueBoxColour covering groups, feature rendering,
+  // gaps, overview...
+
+}
index 18e008e..56e6cda 100644 (file)
@@ -24,8 +24,11 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import jalview.datamodel.AlignmentI;
+import jalview.datamodel.ColumnSelection;
+import jalview.io.AnnotationFile.ViewDef;
 
 import java.io.File;
+import java.util.Hashtable;
 
 import org.junit.Test;
 
@@ -44,7 +47,9 @@ public class AnnotationFileIOTest
           "examples/uniref50.fa", "examples/testdata/uniref50_iupred.jva" },
       {
           "Test group only annotation file parsing results in parser indicating annotation was parsed",
-          "examples/uniref50.fa", "examples/testdata/test_grpannot.jva" } };
+          "examples/uniref50.fa", "examples/testdata/test_grpannot.jva" },
+      { "Test hiding/showing of insertions on sequence_ref",
+          "examples/uniref50.fa", "examples/testdata/uniref50_seqref.jva" } };
 
   @Test
   public void exampleAnnotationFileIO() throws Exception
@@ -102,17 +107,20 @@ public class AnnotationFileIOTest
     try
     {
       AlignmentI al = readAlignmentFile(f);
-
+      ColumnSelection cs = new ColumnSelection();
       assertTrue(
               "Test "
                       + testname
                       + "\nAlignment was not annotated - annotation file not imported.",
-              new AnnotationFile().readAnnotationFile(al, af,
+              new AnnotationFile().readAnnotationFile(al, cs, af,
                       FormatAdapter.FILE));
 
+      AnnotationFile aff = new AnnotationFile();
+      ViewDef v = aff.new ViewDef(null, al.getHiddenSequences(), cs,
+              new Hashtable());
       String anfileout = new AnnotationFile().printAnnotations(
               al.getAlignmentAnnotation(), al.getGroups(),
-              al.getProperties());
+              al.getProperties(), null, al, v);
       assertTrue(
               "Test "
                       + testname
diff --git a/test/jalview/io/BioJsHTMLOutputTest.java b/test/jalview/io/BioJsHTMLOutputTest.java
new file mode 100644 (file)
index 0000000..4b817ab
--- /dev/null
@@ -0,0 +1,50 @@
+package jalview.io;
+
+import jalview.datamodel.Alignment;
+import jalview.datamodel.Sequence;
+
+import java.io.IOException;
+
+import org.junit.Test;
+
+import com.json.JSONException;
+
+public class BioJsHTMLOutputTest
+{
+
+
+  @Test
+  public void getJalviewAlignmentAsJsonString()
+  {
+    BioJsHTMLOutput bioJsHtmlOuput = new BioJsHTMLOutput(null, null);
+    bioJsHtmlOuput.setGlobalColorScheme("Zappo");
+
+    Sequence[] seqs = new Sequence[1];
+    Sequence seq = new Sequence("name", "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1, 26);
+    // SequenceFeature seqFeature = new SequenceFeature("type", "desc",
+    // "status", 1, 5, "jalview");
+    // seq.addSequenceFeature(seqFeature);
+    seq.setDatasetSequence(seq);
+    seqs[0] = seq;
+
+    Alignment al = new Alignment(seqs);
+    try
+    {
+      String generatedJson = bioJsHtmlOuput
+              .getJalviewAlignmentAsJsonString(al);
+      org.junit.Assert
+              .assertEquals(
+                      generatedJson.toLowerCase(),
+                      "{\"globalColorScheme\":\"zappo\",\"seqs\":[{\"id\":\"1\",\"start\":1,\"name\":\"name/1-26\",\"features\":[],\"seq\":\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\",\"end\":26}]}"
+                              .toLowerCase());
+      System.out.println("Output : " + generatedJson);
+    } catch (IOException e)
+    {
+      e.printStackTrace();
+    } catch (JSONException e)
+    {
+      e.printStackTrace();
+    }
+  }
+
+}
diff --git a/test/jalview/io/HtmlFileTest.java b/test/jalview/io/HtmlFileTest.java
new file mode 100644 (file)
index 0000000..be228b8
--- /dev/null
@@ -0,0 +1,16 @@
+package jalview.io;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class HtmlFileTest
+{
+
+  @Test
+  public void test()
+  {
+    fail("Not yet implemented");
+  }
+
+}
index b0144fc..716755b 100644 (file)
@@ -245,6 +245,8 @@ public class Mapping
     newseq.setStart(refseq.getStart() + 25);
     newseq.setEnd(refseq.getLength() + 25 + refseq.getStart());
     StructureSelectionManager ssm = new jalview.structure.StructureSelectionManager();
+    ssm.setProcessSecondaryStructure(true);
+    ssm.setAddTempFacAnnot(true);
     PDBfile pmap = ssm.setMapping(true, new SequenceI[]
     { newseq }, new String[]
     { null }, "test/jalview/ext/jmol/1QCF.pdb",
@@ -252,7 +254,9 @@ public class Mapping
     assertTrue(pmap != null);
     assertEquals("Original and copied sequence of different lengths.",
             refseq.getLength(), newseq.getLength());
-    assertTrue(refseq.getAnnotation().length > 0
+    assertTrue(refseq.getAnnotation() != null
+            && refseq.getAnnotation().length > 0);
+    assertTrue(newseq.getAnnotation() != null
             && newseq.getAnnotation().length > 0);
     for (AlignmentAnnotation oannot : refseq.getAnnotation())
     {
index da2e6ca..3bbcf27 100644 (file)
@@ -39,4 +39,17 @@ public class ColorUtilsTest
             ColorUtils.brighterThan(darkColour));
     assertNull(ColorUtils.brighterThan(null));
   }
+
+  /**
+   * @see http://www.rtapo.com/notes/named_colors.html
+   */
+  @Test
+  public void testToTkCode()
+  {
+    assertEquals("#fffafa", ColorUtils.toTkCode(new Color(255, 250, 250))); // snow
+    assertEquals("#e6e6fa", ColorUtils.toTkCode(new Color(230, 230, 250))); // lavender
+    assertEquals("#dda0dd", ColorUtils.toTkCode(new Color(221, 160, 221))); // plum
+    assertEquals("#800080", ColorUtils.toTkCode(new Color(128, 0, 128))); // purple
+    assertEquals("#00ff00", ColorUtils.toTkCode(new Color(0, 255, 0))); // lime
+  }
 }
index db7d505..1e47800 100644 (file)
  */
 package jalview.ws.jabaws;
 
-import static org.junit.Assert.*;
-
-import java.util.ArrayList;
-import java.util.List;
-
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.io.AnnotationFile;
@@ -34,6 +32,9 @@ import jalview.ws.jws2.AADisorderClient;
 import jalview.ws.jws2.Jws2Discoverer;
 import jalview.ws.jws2.jabaws2.Jws2Instance;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -124,9 +125,8 @@ public class DisorderAnnotExportImport
     {
       String aligfileout = new FormatAdapter().formatSequences("PFAM",
               al.getSequencesArray());
-      String anfileout = new AnnotationFile().printAnnotations(
-              al.getAlignmentAnnotation(), al.getGroups(),
-              al.getProperties());
+      String anfileout = new AnnotationFile()
+              .printAnnotationsForAlignment(al);
       assertTrue(
               "Test "
                       + testname
index f0b8f99..0743581 100644 (file)
@@ -33,7 +33,6 @@ import jalview.ws.jws2.JabaParamStore;
 import jalview.ws.jws2.Jws2Discoverer;
 import jalview.ws.jws2.SequenceAnnotationWSClient;
 import jalview.ws.jws2.jabaws2.Jws2Instance;
-import jalview.ws.params.ArgumentI;
 import jalview.ws.params.AutoCalcSetting;
 
 import java.awt.Component;
@@ -81,7 +80,9 @@ public class JpredJabaStructExportImport
     System.out.println("State of jpredws: " + jpredws);
 
     if (jpredws == null)
+    {
       System.exit(0);
+    }
 
     jalview.io.FileLoader fl = new jalview.io.FileLoader(false);
 
@@ -186,9 +187,8 @@ public class JpredJabaStructExportImport
       String aligfileout = new FormatAdapter().formatSequences("PFAM",
               al.getSequencesArray());
 
-      String anfileout = new AnnotationFile().printAnnotations(
-              al.getAlignmentAnnotation(), al.getGroups(),
-              al.getProperties());
+      String anfileout = new AnnotationFile()
+              .printAnnotationsForAlignment(al);
       assertTrue(
               "Test "
                       + testname
@@ -270,10 +270,10 @@ public class JpredJabaStructExportImport
     // write out parameters
     jalview.gui.AlignFrame nalf = null;
     assertTrue("Couldn't write out the Jar file",
-            new Jalview2XML(false).SaveAlignment(af,
+            new Jalview2XML(false).saveAlignment(af,
                     "testJPredWS_param.jar", "trial parameter writeout"));
     assertTrue("Couldn't read back the Jar file", (nalf = new Jalview2XML(
-            false).LoadJalviewAlign("testJpredWS_param.jar")) != null);
+            false).loadJalviewAlign("testJpredWS_param.jar")) != null);
     if (nalf != null)
     {
       AutoCalcSetting acs = af.getViewport().getCalcIdSettingsFor(
index 9a723ca..5eeff50 100644 (file)
  */
 package jalview.ws.jabaws;
 
-import static org.junit.Assert.*;
-
-import java.awt.Component;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Vector;
-
-import javax.swing.JMenu;
-import javax.swing.JMenuItem;
-
-import jalview.api.AlignCalcManagerI;
-import jalview.datamodel.AlignmentAnnotation;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.Annotation;
 import jalview.gui.Jalview2XML;
 import jalview.io.AnnotationFile;
 import jalview.io.FormatAdapter;
 import jalview.io.StockholmFileTest;
-import jalview.ws.jws2.AADisorderClient;
 import jalview.ws.jws2.Jws2Discoverer;
 import jalview.ws.jws2.RNAalifoldClient;
 import jalview.ws.jws2.SequenceAnnotationWSClient;
-import jalview.ws.jws2.dm.JabaOption;
 import jalview.ws.jws2.jabaws2.Jws2Instance;
 import jalview.ws.params.AutoCalcSetting;
 
+import java.awt.Component;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -84,7 +78,9 @@ public class RNAStructExportImport
     System.out.println("State of rnaalifoldws: " + rnaalifoldws);
 
     if (rnaalifoldws == null)
+    {
       System.exit(0);
+    }
 
     jalview.io.FileLoader fl = new jalview.io.FileLoader(false);
 
@@ -137,9 +133,8 @@ public class RNAStructExportImport
       String aligfileout = new FormatAdapter().formatSequences("PFAM",
               al.getSequencesArray());
 
-      String anfileout = new AnnotationFile().printAnnotations(
-              al.getAlignmentAnnotation(), al.getGroups(),
-              al.getProperties());
+      String anfileout = new AnnotationFile()
+              .printAnnotationsForAlignment(al);
       assertTrue(
               "Test "
                       + testname
@@ -220,10 +215,10 @@ public class RNAStructExportImport
     // write out parameters
     jalview.gui.AlignFrame nalf = null;
     assertTrue("Couldn't write out the Jar file",
-            new Jalview2XML(false).SaveAlignment(af,
+            new Jalview2XML(false).saveAlignment(af,
                     "testRnalifold_param.jar", "trial parameter writeout"));
     assertTrue("Couldn't read back the Jar file", (nalf = new Jalview2XML(
-            false).LoadJalviewAlign("testRnalifold_param.jar")) != null);
+            false).loadJalviewAlign("testRnalifold_param.jar")) != null);
     if (nalf != null)
     {
       AutoCalcSetting acs = af.getViewport().getCalcIdSettingsFor(
index ecd0306..d77a93b 100755 (executable)
@@ -1040,6 +1040,58 @@ and any path to a file to save to the file]]></string>
                                                        <property name="ruleExpression">
                                                                <string><![CDATA[]]></string>
                                                        </property>
+                                               </object>                               
+                                       </method>               
+                                       <method name="addElement">
+                                               <object class="com.zerog.ia.installer.actions.InstallZipfile" objectID="244fff00ffff">
+                                                       <property name="belongsToUninstallPhase">
+                                                               <boolean>false</boolean>
+                                                       </property>
+                                                       <property name="rollbackEnabledCancel">
+                                                               <boolean>true</boolean>
+                                                       </property>
+                                                       <property name="rollbackEnabledError">
+                                                               <boolean>true</boolean>
+                                                       </property>
+                                                       <property name="ruleExpression">
+                                                               <string><![CDATA[]]></string>
+                                                       </property>
+                                                       <property name="unixPermissions">
+                                                               <string><![CDATA[664]]></string>
+                                                       </property>
+                                                       <property name="sourceName">
+                                                               <string><![CDATA[jsoup-1.8.1.jar]]></string>
+                                                       </property>
+                                                       <property name="overrideUnixPermissions">
+                                                               <boolean>false</boolean>
+                                                       </property>
+                                                       <property name="sourcePath">
+                                                               <string><![CDATA[/home/cruisecontrol/jalview/lib/]]></string>
+                                                       </property>
+                                                       <property name="shouldUninstall">
+                                                               <boolean>true</boolean>
+                                                       </property>
+                                                       <property name="rollbackEnabledCancel">
+                                                               <boolean>true</boolean>
+                                                       </property>
+                                                       <property name="rollbackEnabledError">
+                                                               <boolean>true</boolean>
+                                                       </property>
+                                                       <property name="destinationName">
+                                                               <string><![CDATA[jsoup-1.8.1.jar]]></string>
+                                                       </property>
+                                                       <property name="fileSize">
+                                                               <long>348699</long>
+                                                       </property>
+                                                       <property name="macBinary">
+                                                               <boolean>false</boolean>
+                                                       </property>
+                                                       <property name="targetCheckKind">
+                                                               <int>0</int>
+                                                       </property>
+                                                       <property name="ruleExpression">
+                                                               <string><![CDATA[]]></string>
+                                                       </property>
                                                </object>
                                        </method>
                                        <method name="addElement">
@@ -6324,8 +6376,11 @@ and any path to a file to read from that file]]></string>
                                                                                <object refID="24485f8aa671"/>
                                                                                <object refID="24485f89a672"/>
                                                                                <object refID="24485f8aa672"/>
-                                                                               <object refID="244ffffaa672"/>
-                                                                               <object refID="244f00faa672"/>
+
+                                                                               <object refID="244fff00ffff"/>
+                                                           <object refID="244ffffaa672"/>
+                                                           <object refID="244f00faa672"/>
+
                                                                                <object refID="24485f8ba672"/>
                                                                                <object refID="24485f8aa673"/>
                                                                                <object refID="24485f8ba673"/>
@@ -7062,8 +7117,9 @@ and any path to a file to read from that file]]></string>
                                                                                                <object refID="24485f8aa671"/>
                                                                                                <object refID="24485f89a672"/>
                                                                                                <object refID="24485f8aa672"/>
-                                                                                               <object refID="244ffffaa672"/>
-                                                                                               <object refID="244f00faa672"/>
+                                                                                               <object refID="244fff00ffff"/>
+                                                                       <object refID="244ffffaa672"/>
+                                                                       <object refID="244f00faa672"/>
                                                                                                <object refID="24485f8ba672"/>
                                                                                                <object refID="24485f8aa673"/>
                                                                                                <object refID="24485f8ba673"/>